@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.js CHANGED
@@ -256,6 +256,29 @@ var init_git_executor = __esm({
256
256
  }
257
257
  });
258
258
 
259
+ // src/build-info.ts
260
+ function readInjected(value) {
261
+ if (typeof value !== "string") return void 0;
262
+ const trimmed = value.trim();
263
+ if (!trimmed || trimmed === "unknown") return void 0;
264
+ return trimmed;
265
+ }
266
+ function getDaemonBuildInfo() {
267
+ if (cached) return cached;
268
+ const commit = readInjected(true ? "2cf602f99ce68b3d3f1af02b429f6fb7a7c8e686" : void 0) ?? "unknown";
269
+ const commitShort = readInjected(true ? "2cf602f" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
270
+ const version = readInjected(true ? "0.9.82-rc.263" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
271
+ const builtAt = readInjected(true ? "2026-06-14T13:48:10.699Z" : void 0);
272
+ cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
273
+ return cached;
274
+ }
275
+ var cached;
276
+ var init_build_info = __esm({
277
+ "src/build-info.ts"() {
278
+ "use strict";
279
+ }
280
+ });
281
+
259
282
  // src/git/git-status.ts
260
283
  async function getGitRepoStatus(workspace, options = {}) {
261
284
  const lastCheckedAt = Date.now();
@@ -278,6 +301,7 @@ async function getGitRepoStatus(workspace, options = {}) {
278
301
  }
279
302
  const submoduleDirty = (submodules || []).some((submodule) => submodule.dirty || submodule.outOfSync || !!submodule.error);
280
303
  const dirty = parsed.staged + parsed.modified + parsed.untracked + parsed.deleted + parsed.renamed > 0 || parsed.conflictFiles.length > 0 || stashCount > 0 || submoduleDirty;
304
+ const daemonBuildBehind = await detectDaemonBuildBehind(repo, submodules, options);
281
305
  return {
282
306
  workspace: repo.workspace,
283
307
  repoRoot: repo.repoRoot,
@@ -301,7 +325,8 @@ async function getGitRepoStatus(workspace, options = {}) {
301
325
  conflictFiles: parsed.conflictFiles,
302
326
  stashCount,
303
327
  lastCheckedAt,
304
- submodules
328
+ submodules,
329
+ ...daemonBuildBehind ? { daemonBuildBehind } : {}
305
330
  };
306
331
  } catch (error) {
307
332
  if (error instanceof GitCommandError) {
@@ -314,6 +339,35 @@ async function getGitRepoStatus(workspace, options = {}) {
314
339
  );
315
340
  }
316
341
  }
342
+ async function detectDaemonBuildBehind(repo, submodules, options) {
343
+ const build = options.daemonBuildInfo ?? getDaemonBuildInfo();
344
+ if (!build.commit || build.commit === "unknown") return void 0;
345
+ const scopes = [
346
+ { scope: "root", repoPath: repo.repoRoot || repo.workspace }
347
+ ];
348
+ for (const sub of submodules || []) {
349
+ if (sub.repoPath && !sub.error) scopes.push({ scope: sub.path, repoPath: sub.repoPath });
350
+ }
351
+ for (const { scope, repoPath } of scopes) {
352
+ try {
353
+ await runGit(repoPath, ["cat-file", "-e", `${build.commit}^{commit}`], options);
354
+ const headResult = await runGit(repoPath, ["rev-parse", "HEAD"], options);
355
+ const head = headResult.stdout.trim();
356
+ if (!head || head === build.commit) continue;
357
+ await runGit(repoPath, ["merge-base", "--is-ancestor", build.commit, "HEAD"], options);
358
+ return {
359
+ buildCommit: build.commit,
360
+ buildCommitShort: build.commitShort,
361
+ head,
362
+ scope,
363
+ 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.`
364
+ };
365
+ } catch {
366
+ continue;
367
+ }
368
+ }
369
+ return void 0;
370
+ }
317
371
  async function readPorcelainStatus(repo, options) {
318
372
  const statusOutput = await runGit(repo, ["status", "--porcelain=v2", "--branch"], options);
319
373
  return parsePorcelainV2Status(statusOutput.stdout);
@@ -530,6 +584,7 @@ var init_git_status = __esm({
530
584
  "src/git/git-status.ts"() {
531
585
  "use strict";
532
586
  init_git_executor();
587
+ init_build_info();
533
588
  }
534
589
  });
535
590
 
@@ -2386,8 +2441,8 @@ function readLedgerFromStore(meshId) {
2386
2441
  }
2387
2442
  function getCachedRawEntries(meshId) {
2388
2443
  const now = Date.now();
2389
- const cached = ledgerReadCache.get(meshId);
2390
- if (cached && now - cached.cachedAt < LEDGER_CACHE_TTL_MS) return cached.entries;
2444
+ const cached2 = ledgerReadCache.get(meshId);
2445
+ if (cached2 && now - cached2.cachedAt < LEDGER_CACHE_TTL_MS) return cached2.entries;
2391
2446
  let entries;
2392
2447
  try {
2393
2448
  entries = readLedgerFromStore(meshId);
@@ -2650,6 +2705,7 @@ __export(mesh_work_queue_exports, {
2650
2705
  cancelTask: () => cancelTask,
2651
2706
  claimNextTask: () => claimNextTask,
2652
2707
  cleanupTerminalDirectDispatches: () => cleanupTerminalDirectDispatches,
2708
+ deleteDirectDispatchesByTaskId: () => deleteDirectDispatchesByTaskId,
2653
2709
  describeTaskDependencyState: () => describeTaskDependencyState,
2654
2710
  enqueueTask: () => enqueueTask,
2655
2711
  getActiveDirectDispatches: () => getActiveDirectDispatches,
@@ -3058,6 +3114,13 @@ function markStaleDirectDispatches(meshId, olderThanMs = 60 * 6e4) {
3058
3114
  } catch {
3059
3115
  }
3060
3116
  }
3117
+ function deleteDirectDispatchesByTaskId(meshId, taskIds) {
3118
+ try {
3119
+ return MeshRuntimeStore.getInstance().deleteDirectDispatchesByTaskId(meshId, taskIds);
3120
+ } catch {
3121
+ return 0;
3122
+ }
3123
+ }
3061
3124
  function recordMeshToolCall(opts) {
3062
3125
  try {
3063
3126
  return MeshRuntimeStore.getInstance().recordMeshToolCall(opts);
@@ -3691,6 +3754,24 @@ var init_mesh_runtime_store = __esm({
3691
3754
  deleteDirectDispatches(meshId) {
3692
3755
  this.db.prepare(`DELETE FROM mesh_direct_dispatches WHERE mesh_id = ?`).run(meshId);
3693
3756
  }
3757
+ /**
3758
+ * Delete specific direct dispatch rows by taskId for a mesh. Used by the staleDirect prune
3759
+ * path to remove orphaned/terminal dispatch records whose node/session is no longer in the
3760
+ * live mesh. Returns the number of rows actually deleted. No-op for an empty taskId list.
3761
+ */
3762
+ deleteDirectDispatchesByTaskId(meshId, taskIds) {
3763
+ const ids = (taskIds || []).map((id) => typeof id === "string" ? id.trim() : "").filter(Boolean);
3764
+ if (!ids.length) return 0;
3765
+ const stmt = this.db.prepare(`DELETE FROM mesh_direct_dispatches WHERE mesh_id = ? AND task_id = ?`);
3766
+ let deleted = 0;
3767
+ const run = this.db.transaction((rows) => {
3768
+ for (const taskId of rows) {
3769
+ deleted += stmt.run(meshId, taskId).changes;
3770
+ }
3771
+ });
3772
+ run(ids);
3773
+ return deleted;
3774
+ }
3694
3775
  markStaleDirectDispatches(meshId, olderThanMs) {
3695
3776
  const cutoff = new Date(Date.now() - olderThanMs).toISOString();
3696
3777
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -5359,11 +5440,14 @@ async function fastForwardMeshNode(args) {
5359
5440
  const trigger = normalizeOptionalString(args.trigger) || "manual";
5360
5441
  const updateSubmodules = args.updateSubmodules === true;
5361
5442
  const dryRun = args.dryRun === true || args.execute !== true;
5362
- const plannedSteps = buildPlannedSteps(updateSubmodules);
5443
+ const mode = args.mode === "push" ? "push" : "merge";
5444
+ const pushSubmodules = mode === "push" && args.pushSubmodules === true;
5445
+ const plannedSteps = buildPlannedSteps(mode, updateSubmodules, pushSubmodules);
5363
5446
  const base = {
5364
5447
  ...nodeId ? { nodeId } : {},
5365
5448
  ...meshId ? { meshId } : {},
5366
5449
  workspace,
5450
+ mode,
5367
5451
  dryRun,
5368
5452
  updateSubmodules,
5369
5453
  plannedSteps,
@@ -5377,13 +5461,24 @@ async function fastForwardMeshNode(args) {
5377
5461
  submoduleIgnorePaths: args.submoduleIgnorePaths,
5378
5462
  timeoutMs: args.timeoutMs ?? STATUS_OPTIONS.timeoutMs
5379
5463
  });
5464
+ if (mode === "push") {
5465
+ return pushMeshNode(base, args, current, {
5466
+ pushSubmodules,
5467
+ allowAutoPublishSubmoduleMainCommits: args.allowAutoPublishSubmoduleMainCommits === true
5468
+ });
5469
+ }
5380
5470
  const earlyBlockers = collectPreflightBlockers(current, requestedBranch);
5381
5471
  if (earlyBlockers.length > 0) {
5472
+ const blockCode = chooseBlockCode(current, earlyBlockers);
5382
5473
  const result2 = {
5383
- ...block(base, chooseBlockCode(current, earlyBlockers), earlyBlockers),
5474
+ ...block(base, blockCode, earlyBlockers),
5384
5475
  current,
5385
- finalBranchConvergenceState: buildConvergenceState(current, codeToConvergenceStatus(chooseBlockCode(current, earlyBlockers)))
5476
+ finalBranchConvergenceState: buildConvergenceState(current, codeToConvergenceStatus(blockCode))
5386
5477
  };
5478
+ if (blockCode === "branch_ahead" && current.ahead > 0 && current.behind === 0 && otherBlockersAreOnlyAhead(earlyBlockers)) {
5479
+ result2.code = "ahead_needs_push";
5480
+ 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.';
5481
+ }
5387
5482
  await appendFastForwardLedger(result2, "blocked");
5388
5483
  return result2;
5389
5484
  }
@@ -5494,7 +5589,246 @@ async function fastForwardMeshNode(args) {
5494
5589
  await appendFastForwardLedger(result, success ? "executed" : "failed");
5495
5590
  return result;
5496
5591
  }
5497
- function buildPlannedSteps(updateSubmodules) {
5592
+ async function pushMeshNode(base, args, current, options) {
5593
+ const workspace = base.workspace;
5594
+ const requestedBranch = normalizeOptionalString(args.branch);
5595
+ const dryRun = base.dryRun;
5596
+ const blockers = collectPushPreflightBlockers(current, requestedBranch);
5597
+ if (blockers.length > 0) {
5598
+ const code2 = choosePushBlockCode(current, blockers);
5599
+ const result2 = {
5600
+ ...block(base, code2, blockers),
5601
+ current,
5602
+ preStatus: current,
5603
+ finalBranchConvergenceState: buildConvergenceState(current, codeToConvergenceStatus(code2))
5604
+ };
5605
+ await appendFastForwardLedger(result2, "blocked");
5606
+ return result2;
5607
+ }
5608
+ const target = parseUpstreamTarget(current.upstream || "");
5609
+ if (!target) {
5610
+ const result2 = {
5611
+ ...block(base, "upstream_unparseable", ["upstream_unparseable"]),
5612
+ current,
5613
+ preStatus: current,
5614
+ finalBranchConvergenceState: buildConvergenceState(current, "blocked")
5615
+ };
5616
+ await appendFastForwardLedger(result2, "blocked");
5617
+ return result2;
5618
+ }
5619
+ const refspec = `HEAD:refs/heads/${target.remoteBranch}`;
5620
+ const pushTarget = { remote: target.remote, remoteBranch: target.remoteBranch, refspec };
5621
+ if (current.ahead <= 0) {
5622
+ const result2 = {
5623
+ ...base,
5624
+ success: true,
5625
+ code: "nothing_to_push",
5626
+ allowed: true,
5627
+ willRun: false,
5628
+ executed: false,
5629
+ blockingReasons: [],
5630
+ current,
5631
+ preStatus: current,
5632
+ postStatus: current,
5633
+ pushTarget,
5634
+ finalBranchConvergenceState: buildConvergenceState(current, "up_to_date")
5635
+ };
5636
+ await appendFastForwardLedger(result2, "noop");
5637
+ return result2;
5638
+ }
5639
+ const descendant = await verifyUpstreamIsAncestorOfHead(workspace, current.upstream || "", args.timeoutMs);
5640
+ if (!descendant.ok) {
5641
+ const result2 = {
5642
+ ...block(base, "non_fast_forward_push", ["head_is_not_descendant_of_upstream"]),
5643
+ current,
5644
+ preStatus: current,
5645
+ pushTarget,
5646
+ operationError: descendant.error,
5647
+ 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.",
5648
+ finalBranchConvergenceState: buildConvergenceState(current, "not_mergeable")
5649
+ };
5650
+ await appendFastForwardLedger(result2, "blocked");
5651
+ return result2;
5652
+ }
5653
+ if (dryRun) {
5654
+ const result2 = {
5655
+ ...base,
5656
+ success: true,
5657
+ code: "push_available",
5658
+ allowed: true,
5659
+ willRun: false,
5660
+ executed: false,
5661
+ blockingReasons: [],
5662
+ current,
5663
+ preStatus: current,
5664
+ pushTarget,
5665
+ ...options.pushSubmodules ? { submodulePushes: await planSubmodulePushes(current, options, args.timeoutMs) } : {},
5666
+ finalBranchConvergenceState: buildConvergenceState(current, "push_available")
5667
+ };
5668
+ await appendFastForwardLedger(result2, "dry_run");
5669
+ return result2;
5670
+ }
5671
+ try {
5672
+ await runGit(workspace, ["push", target.remote, refspec], { timeoutMs: args.timeoutMs ?? 3e4 });
5673
+ } catch (error) {
5674
+ const result2 = {
5675
+ ...block(base, "push_ff_only_failed", ["push_ff_only_failed"]),
5676
+ current,
5677
+ preStatus: current,
5678
+ pushTarget,
5679
+ operationError: formatGitError2(error),
5680
+ finalBranchConvergenceState: buildConvergenceState(current, "not_mergeable")
5681
+ };
5682
+ await appendFastForwardLedger(result2, "failed");
5683
+ return result2;
5684
+ }
5685
+ let submodulePushes;
5686
+ if (options.pushSubmodules) {
5687
+ submodulePushes = await executeSubmodulePushes(current, options, args.timeoutMs);
5688
+ }
5689
+ const postStatus = await getGitRepoStatus(workspace, {
5690
+ ...STATUS_OPTIONS,
5691
+ submoduleIgnorePaths: args.submoduleIgnorePaths,
5692
+ timeoutMs: args.timeoutMs ?? STATUS_OPTIONS.timeoutMs
5693
+ });
5694
+ const submodulePushFailed = (submodulePushes || []).some((entry) => !entry.pushed && !entry.skipped);
5695
+ const blockingReasons = [];
5696
+ if (postStatus.ahead !== 0) blockingReasons.push("post_branch_ahead");
5697
+ if (submodulePushFailed) blockingReasons.push("submodule_push_failed");
5698
+ const success = blockingReasons.length === 0;
5699
+ const code = success ? "push_applied" : submodulePushFailed && postStatus.ahead === 0 ? "push_applied_submodule_push_failed" : "post_push_verify_failed";
5700
+ const result = {
5701
+ ...base,
5702
+ success,
5703
+ code,
5704
+ allowed: true,
5705
+ willRun: true,
5706
+ executed: true,
5707
+ blockingReasons,
5708
+ current,
5709
+ preStatus: current,
5710
+ postStatus,
5711
+ pushTarget,
5712
+ ...submodulePushes ? { submodulePushes } : {},
5713
+ finalBranchConvergenceState: buildConvergenceState(postStatus, success ? "pushed" : "post_verify_failed")
5714
+ };
5715
+ await appendFastForwardLedger(result, success ? "executed" : "failed");
5716
+ return result;
5717
+ }
5718
+ function collectPushPreflightBlockers(status, requestedBranch) {
5719
+ const blockers = [];
5720
+ if (!status.isGitRepo) blockers.push("not_git_repo");
5721
+ if (!status.branch) blockers.push("detached_head_or_unknown_branch");
5722
+ if (requestedBranch && status.branch !== requestedBranch) blockers.push("branch_mismatch");
5723
+ if (!status.upstream) blockers.push("upstream_missing");
5724
+ if (status.upstreamStatus !== "fresh") blockers.push("upstream_not_fresh");
5725
+ if (status.hasConflicts) blockers.push("conflicts_present");
5726
+ if (status.staged > 0) blockers.push("staged_changes_present");
5727
+ if (status.modified > 0) blockers.push("modified_changes_present");
5728
+ if (status.untracked > 0) blockers.push("untracked_changes_present");
5729
+ if (status.deleted > 0) blockers.push("deleted_changes_present");
5730
+ if (status.renamed > 0) blockers.push("renamed_changes_present");
5731
+ if (status.stashCount > 0) blockers.push("stash_entries_present");
5732
+ if (status.ahead > 0 && status.behind > 0) blockers.push("branch_diverged_from_upstream");
5733
+ else if (status.behind > 0) blockers.push("branch_behind_upstream");
5734
+ return blockers;
5735
+ }
5736
+ function choosePushBlockCode(status, blockers) {
5737
+ if (blockers.includes("not_git_repo")) return "not_git_repo";
5738
+ if (blockers.includes("branch_mismatch")) return "branch_mismatch";
5739
+ if (blockers.includes("upstream_missing")) return "upstream_missing";
5740
+ if (blockers.includes("upstream_not_fresh")) return "upstream_not_fresh";
5741
+ if (blockers.includes("branch_diverged_from_upstream")) return "branch_diverged";
5742
+ if (blockers.includes("branch_behind_upstream")) return "non_fast_forward_push";
5743
+ if (blockers.some((reason) => reason.includes("changes") || reason.includes("conflicts") || reason.includes("stash"))) return "dirty_worktree";
5744
+ return "preflight_blocked";
5745
+ }
5746
+ function parseUpstreamTarget(upstream) {
5747
+ const trimmed = upstream.trim();
5748
+ const slash = trimmed.indexOf("/");
5749
+ if (slash <= 0 || slash >= trimmed.length - 1) return null;
5750
+ return { remote: trimmed.slice(0, slash), remoteBranch: trimmed.slice(slash + 1) };
5751
+ }
5752
+ async function verifyUpstreamIsAncestorOfHead(workspace, upstream, timeoutMs) {
5753
+ if (!upstream) return { ok: false, error: "missing upstream" };
5754
+ try {
5755
+ await runGit(workspace, ["merge-base", "--is-ancestor", upstream, "HEAD"], { timeoutMs: timeoutMs ?? 15e3 });
5756
+ return { ok: true };
5757
+ } catch (error) {
5758
+ return { ok: false, error: formatGitError2(error) };
5759
+ }
5760
+ }
5761
+ async function planSubmodulePushes(status, options, timeoutMs) {
5762
+ return resolveSubmodulePushes(status, options, false, timeoutMs);
5763
+ }
5764
+ async function executeSubmodulePushes(status, options, timeoutMs) {
5765
+ return resolveSubmodulePushes(status, options, true, timeoutMs);
5766
+ }
5767
+ async function resolveSubmodulePushes(status, options, execute, timeoutMs) {
5768
+ const submodules = Array.isArray(status.submodules) ? status.submodules : [];
5769
+ const results = [];
5770
+ for (const submodule of submodules) {
5771
+ const base = {
5772
+ path: submodule.path,
5773
+ commit: submodule.commit,
5774
+ remote: "origin",
5775
+ remoteBranch: "main",
5776
+ pushed: false,
5777
+ skipped: true,
5778
+ code: "submodule_push_skipped"
5779
+ };
5780
+ if (!options.allowAutoPublishSubmoduleMainCommits) {
5781
+ results.push({ ...base, code: "submodule_push_policy_disabled", error: "allowAutoPublishSubmoduleMainCommits is not enabled" });
5782
+ continue;
5783
+ }
5784
+ if (submodule.error || submodule.dirty) {
5785
+ results.push({ ...base, code: "submodule_not_clean", error: submodule.error || "submodule worktree is dirty" });
5786
+ continue;
5787
+ }
5788
+ const repoPath = submodule.repoPath;
5789
+ if (!repoPath || !submodule.commit) {
5790
+ results.push({ ...base, code: "submodule_status_incomplete" });
5791
+ continue;
5792
+ }
5793
+ try {
5794
+ await runGit(repoPath, ["-c", "protocol.file.allow=always", "fetch", "origin", "refs/heads/main:refs/remotes/origin/main"], { timeoutMs: timeoutMs ?? 3e4 });
5795
+ } catch (error) {
5796
+ results.push({ ...base, code: "submodule_fetch_failed", error: formatGitError2(error) });
5797
+ continue;
5798
+ }
5799
+ let alreadyReachable = false;
5800
+ try {
5801
+ await runGit(repoPath, ["merge-base", "--is-ancestor", submodule.commit, "refs/remotes/origin/main"], { timeoutMs: timeoutMs ?? 15e3 });
5802
+ alreadyReachable = true;
5803
+ } catch {
5804
+ }
5805
+ if (alreadyReachable) {
5806
+ results.push({ ...base, pushed: false, skipped: true, code: "submodule_already_reachable" });
5807
+ continue;
5808
+ }
5809
+ try {
5810
+ await runGit(repoPath, ["merge-base", "--is-ancestor", "refs/remotes/origin/main", submodule.commit], { timeoutMs: timeoutMs ?? 15e3 });
5811
+ } catch (error) {
5812
+ results.push({ ...base, pushed: false, skipped: false, code: "submodule_non_fast_forward", error: formatGitError2(error) });
5813
+ continue;
5814
+ }
5815
+ const refspec = `${submodule.commit}:refs/heads/main`;
5816
+ if (!execute) {
5817
+ results.push({ ...base, pushed: false, skipped: false, code: "submodule_push_available", refspec });
5818
+ continue;
5819
+ }
5820
+ try {
5821
+ await runGit(repoPath, ["push", "origin", refspec], { timeoutMs: timeoutMs ?? 3e4 });
5822
+ await runGit(repoPath, ["-c", "protocol.file.allow=always", "fetch", "origin", "refs/heads/main:refs/remotes/origin/main"], { timeoutMs: timeoutMs ?? 3e4 });
5823
+ await runGit(repoPath, ["merge-base", "--is-ancestor", submodule.commit, "refs/remotes/origin/main"], { timeoutMs: timeoutMs ?? 15e3 });
5824
+ results.push({ ...base, pushed: true, skipped: false, code: "submodule_pushed", refspec });
5825
+ } catch (error) {
5826
+ results.push({ ...base, pushed: false, skipped: false, code: "submodule_push_failed", refspec, error: formatGitError2(error) });
5827
+ }
5828
+ }
5829
+ return results;
5830
+ }
5831
+ function buildPlannedSteps(mode, updateSubmodules, pushSubmodules) {
5498
5832
  const steps = [
5499
5833
  {
5500
5834
  operation: "refresh_upstream",
@@ -5507,20 +5841,49 @@ function buildPlannedSteps(updateSubmodules) {
5507
5841
  description: "Require clean staged/modified/untracked/deleted/renamed/conflict/stash/submodule state.",
5508
5842
  safe: true,
5509
5843
  willMutateWorktree: false
5510
- },
5511
- {
5512
- operation: "verify_fast_forward",
5513
- description: "Require ahead=0, behind>0, and HEAD to be an ancestor of the upstream ref.",
5844
+ }
5845
+ ];
5846
+ if (mode === "push") {
5847
+ steps.push({
5848
+ operation: "verify_push_descendant",
5849
+ description: "Require HEAD to be a descendant of origin/<branch> (origin/<branch> is an ancestor of HEAD); refuse any non-fast-forward push.",
5514
5850
  safe: true,
5515
5851
  willMutateWorktree: false
5516
- },
5517
- {
5518
- operation: "merge_ff_only",
5519
- description: "Apply git merge --ff-only against the tracked upstream; no force, reset, rebase, push, or deploy.",
5852
+ });
5853
+ steps.push({
5854
+ operation: "push_ff_only",
5855
+ 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.",
5520
5856
  safe: true,
5521
- willMutateWorktree: true
5857
+ willMutateWorktree: false
5858
+ });
5859
+ if (pushSubmodules) {
5860
+ steps.push({
5861
+ operation: "push_submodules_ff_only",
5862
+ 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.",
5863
+ safe: true,
5864
+ willMutateWorktree: false
5865
+ });
5522
5866
  }
5523
- ];
5867
+ steps.push({
5868
+ operation: "verify_post_status",
5869
+ description: "Re-read daemon-owned git status and report final branch convergence state.",
5870
+ safe: true,
5871
+ willMutateWorktree: false
5872
+ });
5873
+ return steps;
5874
+ }
5875
+ steps.push({
5876
+ operation: "verify_fast_forward",
5877
+ description: "Require ahead=0, behind>0, and HEAD to be an ancestor of the upstream ref.",
5878
+ safe: true,
5879
+ willMutateWorktree: false
5880
+ });
5881
+ steps.push({
5882
+ operation: "merge_ff_only",
5883
+ description: "Apply git merge --ff-only against the tracked upstream; no force, reset, rebase, push, or deploy.",
5884
+ safe: true,
5885
+ willMutateWorktree: true
5886
+ });
5524
5887
  if (updateSubmodules) {
5525
5888
  steps.push({
5526
5889
  operation: "submodule_update",
@@ -5537,6 +5900,10 @@ function buildPlannedSteps(updateSubmodules) {
5537
5900
  });
5538
5901
  return steps;
5539
5902
  }
5903
+ function otherBlockersAreOnlyAhead(blockers) {
5904
+ const aheadOnly = /* @__PURE__ */ new Set(["branch_has_local_commits"]);
5905
+ return blockers.every((reason) => aheadOnly.has(reason));
5906
+ }
5540
5907
  function collectPreflightBlockers(status, requestedBranch) {
5541
5908
  const blockers = [];
5542
5909
  if (!status.isGitRepo) blockers.push("not_git_repo");
@@ -5593,7 +5960,7 @@ function chooseBlockCode(status, blockers) {
5593
5960
  return "preflight_blocked";
5594
5961
  }
5595
5962
  function codeToConvergenceStatus(code) {
5596
- if (code === "branch_diverged" || code === "branch_ahead" || code === "non_fast_forward") return "not_mergeable";
5963
+ if (code === "branch_diverged" || code === "branch_ahead" || code === "non_fast_forward" || code === "non_fast_forward_push" || code === "upstream_unparseable") return "not_mergeable";
5597
5964
  if (code === "dirty_worktree" || code === "submodule_not_clean") return "blocked_review";
5598
5965
  return "blocked";
5599
5966
  }
@@ -5676,6 +6043,7 @@ async function appendFastForwardLedger(result, outcome) {
5676
6043
  ...result.nodeId ? { nodeId: result.nodeId } : {},
5677
6044
  payload: {
5678
6045
  operation: "mesh_fast_forward_node",
6046
+ mode: result.mode,
5679
6047
  trigger: result.trigger || "manual",
5680
6048
  outcome,
5681
6049
  code: result.code,
@@ -5686,6 +6054,7 @@ async function appendFastForwardLedger(result, outcome) {
5686
6054
  executed: result.executed,
5687
6055
  branch: result.postStatus?.branch ?? result.current?.branch,
5688
6056
  upstream: result.postStatus?.upstream ?? result.current?.upstream,
6057
+ ...result.pushTarget ? { pushTarget: result.pushTarget } : {},
5689
6058
  before: result.current ? {
5690
6059
  headCommit: result.current.headCommit,
5691
6060
  ahead: result.current.ahead,
@@ -5696,6 +6065,16 @@ async function appendFastForwardLedger(result, outcome) {
5696
6065
  ahead: result.postStatus.ahead,
5697
6066
  behind: result.postStatus.behind
5698
6067
  } : void 0,
6068
+ ...result.submodulePushes ? {
6069
+ submodulePushes: result.submodulePushes.map((entry) => ({
6070
+ path: entry.path,
6071
+ commit: entry.commit,
6072
+ pushed: entry.pushed,
6073
+ skipped: entry.skipped,
6074
+ code: entry.code,
6075
+ ...entry.refspec ? { refspec: entry.refspec } : {}
6076
+ }))
6077
+ } : {},
5699
6078
  blockingReasons: result.blockingReasons
5700
6079
  }
5701
6080
  });
@@ -6866,8 +7245,8 @@ var init_mesh_routing = __esm({
6866
7245
  // src/mesh/mesh-events-coordinator.ts
6867
7246
  function getCachedMeshByWorkspace(workspace) {
6868
7247
  const now = Date.now();
6869
- const cached = meshByWorkspaceCache.get(workspace);
6870
- if (cached && now - cached.cachedAt < MESH_WORKSPACE_CACHE_TTL_MS) return cached.mesh;
7248
+ const cached2 = meshByWorkspaceCache.get(workspace);
7249
+ if (cached2 && now - cached2.cachedAt < MESH_WORKSPACE_CACHE_TTL_MS) return cached2.mesh;
6871
7250
  const mesh = getMeshByRepo(workspace);
6872
7251
  meshByWorkspaceCache.set(workspace, { mesh, cachedAt: now });
6873
7252
  return mesh;
@@ -11759,10 +12138,10 @@ ${lastSnapshot}`;
11759
12138
  return this.accumulatedRawBuffer.replace(/\x1B\][^\x07]*(?:\x07|\x1B\\)/g, "").replace(/\x1B[P^_X][\s\S]*?(?:\x07|\x1B\\)/g, "").replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "");
11760
12139
  }
11761
12140
  getFreshParsedStatusCache() {
11762
- const cached = this.parsedStatusCache;
12141
+ const cached2 = this.parsedStatusCache;
11763
12142
  const accumulatedRawBufferKey = this.getAccumulatedRawBufferCacheKey();
11764
- 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) {
11765
- return cached.result;
12143
+ 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) {
12144
+ return cached2.result;
11766
12145
  }
11767
12146
  return null;
11768
12147
  }
@@ -12222,10 +12601,10 @@ ${lastSnapshot}`;
12222
12601
  getScriptParsedStatus() {
12223
12602
  const screenText = this.readTerminalScreenText();
12224
12603
  const parseScreenText = this.getParseScreenText(screenText);
12225
- const cached = this.parsedStatusCache;
12604
+ const cached2 = this.parsedStatusCache;
12226
12605
  const accumulatedRawBufferKey = this.getAccumulatedRawBufferCacheKey();
12227
- 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) {
12228
- return cached.result;
12606
+ 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) {
12607
+ return cached2.result;
12229
12608
  }
12230
12609
  const parsed = this.runParseSession();
12231
12610
  if (!parsed || !Array.isArray(parsed.messages)) {
@@ -14091,6 +14470,7 @@ __export(index_exports, {
14091
14470
  MIN_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS: () => MIN_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS,
14092
14471
  NodePtyTransportFactory: () => NodePtyTransportFactory,
14093
14472
  P2pRelayFailureError: () => P2pRelayFailureError,
14473
+ PRUNABLE_ORPHAN_STALE_REASONS: () => PRUNABLE_ORPHAN_STALE_REASONS,
14094
14474
  ProviderCliAdapter: () => ProviderCliAdapter,
14095
14475
  ProviderInstanceManager: () => ProviderInstanceManager,
14096
14476
  ProviderLoader: () => ProviderLoader,
@@ -14144,6 +14524,7 @@ __export(index_exports, {
14144
14524
  classifyChatMessageVisibility: () => classifyChatMessageVisibility,
14145
14525
  classifyHotChatSessionsForSubscriptionFlush: () => classifyHotChatSessionsForSubscriptionFlush,
14146
14526
  classifyP2pRelayFailure: () => classifyP2pRelayFailure,
14527
+ classifyStaleDirectForPrune: () => classifyStaleDirectForPrune,
14147
14528
  cleanupTerminalDirectDispatches: () => cleanupTerminalDirectDispatches,
14148
14529
  clearDebugTrace: () => clearDebugTrace,
14149
14530
  clearPendingMeshCoordinatorEvents: () => clearPendingMeshCoordinatorEvents,
@@ -14163,6 +14544,7 @@ __export(index_exports, {
14163
14544
  createNativeHistoryDispatcher: () => createNativeHistoryDispatcher,
14164
14545
  createSessionDelivery: () => createSessionDelivery,
14165
14546
  createWorktree: () => createWorktree,
14547
+ deleteDirectDispatchesByTaskId: () => deleteDirectDispatchesByTaskId,
14166
14548
  deleteMesh: () => deleteMesh,
14167
14549
  deriveMeshReviewInboxItems: () => deriveMeshReviewInboxItems,
14168
14550
  describeTaskDependencyState: () => describeTaskDependencyState,
@@ -14191,6 +14573,7 @@ __export(index_exports, {
14191
14573
  getAvailableIdeIds: () => getAvailableIdeIds,
14192
14574
  getCoordinatorForSession: () => getCoordinatorForSession,
14193
14575
  getCurrentDaemonLogPath: () => getCurrentDaemonLogPath,
14576
+ getDaemonBuildInfo: () => getDaemonBuildInfo,
14194
14577
  getDaemonDataDir: () => getDaemonDataDir,
14195
14578
  getDaemonLogDir: () => getDaemonLogDir,
14196
14579
  getDebugRuntimeConfig: () => getDebugRuntimeConfig,
@@ -17037,6 +17420,17 @@ function buildMeshActiveWork(opts) {
17037
17420
  }
17038
17421
  return { activeWork: records, staleDirectWork, staleDirectWorkNote, terminalDirectWork, summary };
17039
17422
  }
17423
+ var PRUNABLE_ORPHAN_STALE_REASONS = /* @__PURE__ */ new Set([
17424
+ "direct task node is no longer in the live mesh",
17425
+ "direct task session is not present in live session records",
17426
+ "direct task has no node id"
17427
+ ]);
17428
+ function classifyStaleDirectForPrune(record, opts = {}) {
17429
+ if (record.staleDispatchUnacknowledged === true) return "preserve_unacknowledged";
17430
+ if (record.terminal === true) return opts.includeTerminal ? "prunable_terminal" : "preserve_active";
17431
+ if (record.staleReason && PRUNABLE_ORPHAN_STALE_REASONS.has(record.staleReason)) return "prunable_orphan";
17432
+ return "preserve_active";
17433
+ }
17040
17434
  function buildCompactStaleDirectWorkSummary(staleDirectWork, opts = {}) {
17041
17435
  const sampleLimit = Math.max(0, Math.min(10, Math.floor(opts.sampleLimit ?? 3)));
17042
17436
  const reasonCounts = {};
@@ -20260,9 +20654,9 @@ function computeSavedHistorySessionSummaries(agentType, dir, files, fileSignatur
20260
20654
  for (const file of files.slice().sort()) {
20261
20655
  const filePath = path12.join(dir, file);
20262
20656
  const signature = fileSignatures.get(file) || `${file}:missing`;
20263
- const cached = savedHistoryFileSummaryCache.get(filePath);
20657
+ const cached2 = savedHistoryFileSummaryCache.get(filePath);
20264
20658
  const persisted = persistedEntries.get(file);
20265
- const reusableEntry = cached?.signature === signature ? cached : persisted?.signature === signature ? persisted : null;
20659
+ const reusableEntry = cached2?.signature === signature ? cached2 : persisted?.signature === signature ? persisted : null;
20266
20660
  const fileSummary = reusableEntry?.summary || computeSavedHistoryFileSummary(dir, file);
20267
20661
  const nextEntry = reusableEntry || {
20268
20662
  signature,
@@ -20714,23 +21108,23 @@ function listSavedHistorySessions(agentType, options = {}, historyBehavior) {
20714
21108
  savedHistorySessionCache.delete(sanitized);
20715
21109
  return { sessions: [], hasMore: false };
20716
21110
  }
20717
- const cached = savedHistorySessionCache.get(sanitized);
21111
+ const cached2 = savedHistorySessionCache.get(sanitized);
20718
21112
  const offset = Math.max(0, options.offset || 0);
20719
21113
  const limit = Math.max(1, options.limit || 30);
20720
21114
  const indexSignature = buildSavedHistoryIndexFileSignature(dir);
20721
21115
  let cacheWasInvalidated = false;
20722
- if (cached) {
20723
- const cacheLooksPersisted = cached.signature.startsWith("index:");
20724
- const cacheStillValid = cacheLooksPersisted ? cached.signature === indexSignature : (() => {
21116
+ if (cached2) {
21117
+ const cacheLooksPersisted = cached2.signature.startsWith("index:");
21118
+ const cacheStillValid = cacheLooksPersisted ? cached2.signature === indexSignature : (() => {
20725
21119
  const files2 = listHistoryFiles(dir);
20726
21120
  const fileSignatures2 = buildSavedHistoryFileSignatureMap(dir, files2);
20727
- return cached.signature === buildSavedHistoryCacheSignature(files2, fileSignatures2);
21121
+ return cached2.signature === buildSavedHistoryCacheSignature(files2, fileSignatures2);
20728
21122
  })();
20729
21123
  if (cacheStillValid) {
20730
- const sliced2 = cached.summaries.slice(offset, offset + limit);
21124
+ const sliced2 = cached2.summaries.slice(offset, offset + limit);
20731
21125
  return {
20732
21126
  sessions: sliced2,
20733
- hasMore: cached.summaries.length > offset + limit
21127
+ hasMore: cached2.summaries.length > offset + limit
20734
21128
  };
20735
21129
  }
20736
21130
  cacheWasInvalidated = true;
@@ -37319,8 +37713,8 @@ var ProviderLoader = class _ProviderLoader {
37319
37713
  return null;
37320
37714
  }
37321
37715
  registerProviderScriptRootSafely(path30.dirname(path30.dirname(providerDir)));
37322
- const cached = this.scriptsCache.get(dir);
37323
- if (cached) return cached;
37716
+ const cached2 = this.scriptsCache.get(dir);
37717
+ if (cached2) return cached2;
37324
37718
  const scriptsJs = path30.join(dir, "scripts.js");
37325
37719
  if (fs20.existsSync(scriptsJs)) {
37326
37720
  try {
@@ -39318,6 +39712,9 @@ function buildStatusSnapshot(options) {
39318
39712
  };
39319
39713
  }
39320
39714
 
39715
+ // src/commands/router.ts
39716
+ init_build_info();
39717
+
39321
39718
  // src/commands/upgrade-helper.ts
39322
39719
  var import_child_process7 = require("child_process");
39323
39720
  var import_child_process8 = require("child_process");
@@ -40103,13 +40500,13 @@ function sanitizeInlineMesh(inlineMesh) {
40103
40500
  nodes
40104
40501
  };
40105
40502
  }
40106
- function reconcileInlineMeshCache(cached, incoming) {
40107
- if (!cached || typeof cached !== "object" || Array.isArray(cached)) return incoming;
40108
- if (!incoming || typeof incoming !== "object" || Array.isArray(incoming)) return cached;
40109
- const cachedNodes = Array.isArray(cached.nodes) ? cached.nodes : [];
40503
+ function reconcileInlineMeshCache(cached2, incoming) {
40504
+ if (!cached2 || typeof cached2 !== "object" || Array.isArray(cached2)) return incoming;
40505
+ if (!incoming || typeof incoming !== "object" || Array.isArray(incoming)) return cached2;
40506
+ const cachedNodes = Array.isArray(cached2.nodes) ? cached2.nodes : [];
40110
40507
  const incomingNodes = Array.isArray(incoming.nodes) ? incoming.nodes : [];
40111
- if (!cachedNodes.length || !incomingNodes.length) return { ...cached, ...incoming };
40112
- const cachedUpdatedAt = Date.parse(readStringValue(cached.updatedAt, cached.updated_at) || "");
40508
+ if (!cachedNodes.length || !incomingNodes.length) return { ...cached2, ...incoming };
40509
+ const cachedUpdatedAt = Date.parse(readStringValue(cached2.updatedAt, cached2.updated_at) || "");
40113
40510
  const incomingUpdatedAt = Date.parse(readStringValue(incoming.updatedAt, incoming.updated_at) || "");
40114
40511
  const preserveCachedMembership = Number.isFinite(cachedUpdatedAt) && (!Number.isFinite(incomingUpdatedAt) || cachedUpdatedAt > incomingUpdatedAt);
40115
40512
  const cachedById = /* @__PURE__ */ new Map();
@@ -40138,7 +40535,7 @@ function reconcileInlineMeshCache(cached, incoming) {
40138
40535
  }
40139
40536
  }
40140
40537
  return {
40141
- ...cached,
40538
+ ...cached2,
40142
40539
  ...incoming,
40143
40540
  nodes
40144
40541
  };
@@ -40749,8 +41146,37 @@ async function runMeshRefinePatchEquivalenceGate(repoRoot, baseHead, branchHead)
40749
41146
  maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES
40750
41147
  });
40751
41148
  const mergeBase = git(["merge-base", baseHead, branchHead]).trim();
40752
- const mergeTreeStdout = git(["merge-tree", "--write-tree", baseHead, branchHead]);
40753
- const mergedTree = mergeTreeStdout.trim().split(/\s+/)[0] || "";
41149
+ let mergedTree = "";
41150
+ let mergeTreeStdout = "";
41151
+ let gitlinkTrivialFastForward;
41152
+ try {
41153
+ mergeTreeStdout = git(["merge-tree", "--write-tree", baseHead, branchHead]);
41154
+ mergedTree = mergeTreeStdout.trim().split(/\s+/)[0] || "";
41155
+ } catch (mergeTreeErr) {
41156
+ const output = `${mergeTreeErr?.message || ""}
41157
+ ${mergeTreeErr?.stdout || ""}
41158
+ ${mergeTreeErr?.stderr || ""}`;
41159
+ const isSubmoduleConflict = /(submodule|160000)/i.test(output) || /Recursive merging with submodules/i.test(output);
41160
+ if (!isSubmoduleConflict) throw mergeTreeErr;
41161
+ const evaluation = evaluateGitlinkTrivialFastForward(repoRoot, baseHead, branchHead);
41162
+ if (!evaluation.trivial) {
41163
+ return {
41164
+ status: "failed",
41165
+ equivalent: false,
41166
+ baseHead,
41167
+ branchHead,
41168
+ mergeBase: mergeBase || void 0,
41169
+ durationMs: Date.now() - startedAt,
41170
+ error: mergeTreeErr?.message || String(mergeTreeErr),
41171
+ stdout: truncateValidationOutput(mergeTreeErr?.stdout),
41172
+ stderr: truncateValidationOutput(mergeTreeErr?.stderr),
41173
+ gitlinkTrivialFastForward: { resolved: false, gitlinks: evaluation.gitlinks, reason: evaluation.reason },
41174
+ actionableHint: buildPatchEquivalenceSubmoduleConflictHint(repoRoot, baseHead, branchHead, output)
41175
+ };
41176
+ }
41177
+ mergedTree = synthesizeTrivialFastForwardMergeTree(repoRoot, baseHead, branchHead, evaluation.gitlinks) || "";
41178
+ gitlinkTrivialFastForward = { resolved: true, gitlinks: evaluation.gitlinks };
41179
+ }
40754
41180
  if (!mergeBase || !mergedTree) {
40755
41181
  return {
40756
41182
  status: "failed",
@@ -40761,7 +41187,8 @@ async function runMeshRefinePatchEquivalenceGate(repoRoot, baseHead, branchHead)
40761
41187
  mergedTree: mergedTree || void 0,
40762
41188
  durationMs: Date.now() - startedAt,
40763
41189
  error: "patch equivalence preflight could not resolve merge-base or synthetic merge tree",
40764
- stdout: truncateValidationOutput(mergeTreeStdout)
41190
+ stdout: truncateValidationOutput(mergeTreeStdout),
41191
+ gitlinkTrivialFastForward
40765
41192
  };
40766
41193
  }
40767
41194
  const expectedPatchId = await computeGitPatchId(repoRoot, mergeBase, branchHead);
@@ -40776,7 +41203,8 @@ async function runMeshRefinePatchEquivalenceGate(repoRoot, baseHead, branchHead)
40776
41203
  mergedTree,
40777
41204
  expectedPatchId,
40778
41205
  actualPatchId,
40779
- durationMs: Date.now() - startedAt
41206
+ durationMs: Date.now() - startedAt,
41207
+ gitlinkTrivialFastForward
40780
41208
  };
40781
41209
  } catch (e) {
40782
41210
  return {
@@ -40799,6 +41227,65 @@ ${e?.stderr || ""}`
40799
41227
  };
40800
41228
  }
40801
41229
  }
41230
+ async function runMeshRefineEffectiveDiffGate(repoRoot, baseHead, branchHead) {
41231
+ const startedAt = Date.now();
41232
+ try {
41233
+ const { execFileSync: execFileSync6 } = await import("child_process");
41234
+ const git = (args, opts) => execFileSync6("git", args, {
41235
+ cwd: opts?.cwd || repoRoot,
41236
+ encoding: "utf8",
41237
+ maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES
41238
+ });
41239
+ const rawDiff = git(["diff", "--raw", baseHead, branchHead]).trim();
41240
+ if (rawDiff) {
41241
+ const changedPaths = rawDiff.split("\n").map((line) => line.split(" ").slice(1).join(" ").trim()).filter(Boolean).slice(0, 50);
41242
+ return {
41243
+ status: "passed",
41244
+ hasEffectiveDiff: true,
41245
+ baseHead,
41246
+ branchHead,
41247
+ changedPaths,
41248
+ durationMs: Date.now() - startedAt
41249
+ };
41250
+ }
41251
+ const submoduleHints = [];
41252
+ try {
41253
+ const status = git(["submodule", "status"]);
41254
+ for (const line of status.split("\n")) {
41255
+ const trimmed = line.trimEnd();
41256
+ if (!trimmed) continue;
41257
+ if (trimmed.startsWith("+")) {
41258
+ const parts = trimmed.slice(1).trim().split(/\s+/);
41259
+ const path39 = parts[1] || parts[0] || "(unknown)";
41260
+ submoduleHints.push({
41261
+ path: path39,
41262
+ reason: "submodule checked-out commit differs from the committed gitlink (pointer bump not committed on the root branch)"
41263
+ });
41264
+ }
41265
+ }
41266
+ } catch {
41267
+ }
41268
+ return {
41269
+ status: "failed",
41270
+ hasEffectiveDiff: false,
41271
+ baseHead,
41272
+ branchHead,
41273
+ ...submoduleHints.length ? { submoduleHints } : {},
41274
+ durationMs: Date.now() - startedAt
41275
+ };
41276
+ } catch (e) {
41277
+ return {
41278
+ status: "skipped",
41279
+ hasEffectiveDiff: true,
41280
+ baseHead,
41281
+ branchHead,
41282
+ durationMs: Date.now() - startedAt,
41283
+ error: e?.message || String(e),
41284
+ stdout: truncateValidationOutput(e?.stdout),
41285
+ stderr: truncateValidationOutput(e?.stderr)
41286
+ };
41287
+ }
41288
+ }
40802
41289
  function buildPatchEquivalenceSubmoduleConflictHint(repoRoot, baseHead, branchHead, output) {
40803
41290
  if (!/(submodule|160000)/i.test(output) || !/(conflict|failed to merge)/i.test(output)) return void 0;
40804
41291
  const conflicts = readChangedGitlinkPaths(repoRoot, baseHead, branchHead).map((path39) => ({
@@ -40855,6 +41342,135 @@ function readTreeObject(repoRoot, ref, path39) {
40855
41342
  return void 0;
40856
41343
  }
40857
41344
  }
41345
+ function resolveGitDir(repoRoot) {
41346
+ const out = (0, import_node_child_process6.execFileSync)("git", ["rev-parse", "--absolute-git-dir"], {
41347
+ cwd: repoRoot,
41348
+ encoding: "utf8",
41349
+ maxBuffer: 1024 * 1024
41350
+ }).trim();
41351
+ return out;
41352
+ }
41353
+ function isSubmoduleFastForward(submoduleRepoPath, baseCommit, branchCommit) {
41354
+ if (!baseCommit || !branchCommit) return false;
41355
+ if (baseCommit === branchCommit) return true;
41356
+ try {
41357
+ if (!fs23.existsSync(submoduleRepoPath)) return false;
41358
+ (0, import_node_child_process6.execFileSync)("git", ["cat-file", "-e", `${baseCommit}^{commit}`], { cwd: submoduleRepoPath, stdio: "ignore" });
41359
+ (0, import_node_child_process6.execFileSync)("git", ["cat-file", "-e", `${branchCommit}^{commit}`], { cwd: submoduleRepoPath, stdio: "ignore" });
41360
+ (0, import_node_child_process6.execFileSync)("git", ["merge-base", "--is-ancestor", baseCommit, branchCommit], { cwd: submoduleRepoPath, stdio: "ignore" });
41361
+ return true;
41362
+ } catch {
41363
+ return false;
41364
+ }
41365
+ }
41366
+ function readChangedPathKinds(repoRoot, fromRef, toRef) {
41367
+ try {
41368
+ const output = (0, import_node_child_process6.execFileSync)("git", ["diff", "--raw", "--no-abbrev", fromRef, toRef], {
41369
+ cwd: repoRoot,
41370
+ encoding: "utf8",
41371
+ maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES
41372
+ });
41373
+ const result = [];
41374
+ const seen = /* @__PURE__ */ new Set();
41375
+ for (const line of output.split("\n")) {
41376
+ if (!line.trim()) continue;
41377
+ const metaAndPath = line.split(" ");
41378
+ const meta = metaAndPath[0] || "";
41379
+ const path39 = metaAndPath[metaAndPath.length - 1]?.trim();
41380
+ if (!path39 || seen.has(path39)) continue;
41381
+ seen.add(path39);
41382
+ const parts = meta.split(/\s+/);
41383
+ const isGitlink = !!(parts[0]?.includes("160000") || parts[1]?.includes("160000"));
41384
+ result.push({ path: path39, isGitlink });
41385
+ }
41386
+ return result;
41387
+ } catch {
41388
+ return [];
41389
+ }
41390
+ }
41391
+ function evaluateGitlinkTrivialFastForward(repoRoot, baseHead, branchHead) {
41392
+ const changedGitlinks = readChangedGitlinkPaths(repoRoot, baseHead, branchHead).map((path39) => {
41393
+ const baseCommit = readTreeObject(repoRoot, baseHead, path39);
41394
+ const branchCommit = readTreeObject(repoRoot, branchHead, path39);
41395
+ const submoduleRepoPath = (0, import_path10.resolve)(repoRoot, path39);
41396
+ const fastForward = !!baseCommit && !!branchCommit && isSubmoduleFastForward(submoduleRepoPath, baseCommit, branchCommit);
41397
+ return { path: path39, baseCommit, branchCommit, fastForward };
41398
+ });
41399
+ if (changedGitlinks.length === 0) {
41400
+ return { trivial: false, reason: "no_changed_gitlinks", gitlinks: changedGitlinks };
41401
+ }
41402
+ const nonFastForward = changedGitlinks.filter((entry) => !entry.fastForward);
41403
+ if (nonFastForward.length > 0) {
41404
+ return {
41405
+ trivial: false,
41406
+ reason: `diverged_gitlinks:${nonFastForward.map((entry) => entry.path).join(",")}`,
41407
+ gitlinks: changedGitlinks
41408
+ };
41409
+ }
41410
+ let mergeBase = "";
41411
+ try {
41412
+ mergeBase = (0, import_node_child_process6.execFileSync)("git", ["merge-base", baseHead, branchHead], {
41413
+ cwd: repoRoot,
41414
+ encoding: "utf8",
41415
+ maxBuffer: 1024 * 1024
41416
+ }).trim();
41417
+ } catch {
41418
+ return { trivial: false, reason: "merge_base_unresolved", gitlinks: changedGitlinks };
41419
+ }
41420
+ if (!mergeBase) {
41421
+ return { trivial: false, reason: "merge_base_unresolved", gitlinks: changedGitlinks };
41422
+ }
41423
+ const baseSideChanges = readChangedPathKinds(repoRoot, mergeBase, baseHead);
41424
+ const branchSideChanges = readChangedPathKinds(repoRoot, mergeBase, branchHead);
41425
+ const baseChangedPaths = new Map(baseSideChanges.map((entry) => [entry.path, entry]));
41426
+ const overlapping = branchSideChanges.filter((entry) => baseChangedPaths.has(entry.path));
41427
+ const nonGitlinkOverlap = overlapping.filter((entry) => {
41428
+ const baseEntry = baseChangedPaths.get(entry.path);
41429
+ return !(entry.isGitlink && baseEntry?.isGitlink);
41430
+ });
41431
+ if (nonGitlinkOverlap.length > 0) {
41432
+ return {
41433
+ trivial: false,
41434
+ reason: `non_gitlink_overlap:${nonGitlinkOverlap.map((entry) => entry.path).join(",")}`,
41435
+ gitlinks: changedGitlinks
41436
+ };
41437
+ }
41438
+ return { trivial: true, gitlinks: changedGitlinks };
41439
+ }
41440
+ function synthesizeTrivialFastForwardMergeTree(repoRoot, baseHead, branchHead, gitlinks) {
41441
+ try {
41442
+ const baseTree = (0, import_node_child_process6.execFileSync)("git", ["rev-parse", `${baseHead}^{tree}`], {
41443
+ cwd: repoRoot,
41444
+ encoding: "utf8",
41445
+ maxBuffer: 1024 * 1024
41446
+ }).trim();
41447
+ if (!baseTree) return void 0;
41448
+ const updates = gitlinks.filter((entry) => entry.branchCommit).map((entry) => `160000 commit ${entry.branchCommit} ${entry.path}`).join("\n");
41449
+ if (!updates) return baseTree;
41450
+ const tmpIndex = (0, import_path10.join)(resolveGitDir(repoRoot), `adhdev-refine-ff-${baseHead.slice(0, 12)}-${branchHead.slice(0, 12)}.index`);
41451
+ const env = { ...process.env, GIT_INDEX_FILE: tmpIndex };
41452
+ try {
41453
+ (0, import_node_child_process6.execFileSync)("git", ["read-tree", baseTree], { cwd: repoRoot, env, stdio: "ignore" });
41454
+ (0, import_node_child_process6.execFileSync)("git", ["update-index", "--index-info"], {
41455
+ cwd: repoRoot,
41456
+ env,
41457
+ input: `${updates}
41458
+ `,
41459
+ encoding: "utf8",
41460
+ stdio: ["pipe", "ignore", "ignore"]
41461
+ });
41462
+ const newTree = (0, import_node_child_process6.execFileSync)("git", ["write-tree"], { cwd: repoRoot, env, encoding: "utf8" }).trim();
41463
+ return newTree || void 0;
41464
+ } finally {
41465
+ try {
41466
+ fs23.rmSync(tmpIndex, { force: true });
41467
+ } catch {
41468
+ }
41469
+ }
41470
+ } catch {
41471
+ return void 0;
41472
+ }
41473
+ }
40858
41474
  async function alignRefinerySubmodulesAfterMerge(repoRoot, previousBaseHead, currentHead, options = {}) {
40859
41475
  const startedAt = Date.now();
40860
41476
  const changedGitlinkPaths = readChangedGitlinkPaths(repoRoot, previousBaseHead, currentHead).filter((path39) => !(options.submoduleIgnorePaths || []).includes(path39));
@@ -41522,6 +42138,10 @@ var DaemonCommandRouter = class {
41522
42138
  runningRefineJobs = /* @__PURE__ */ new Map();
41523
42139
  /** Terminal async Refinery jobs preserve a clear answer after the worktree node has been removed. */
41524
42140
  terminalRefineJobs = /* @__PURE__ */ new Map();
42141
+ /** In-memory async batch Refinery jobs keyed by meshId (one batch convergence per mesh at a time). */
42142
+ runningRefineBatchJobs = /* @__PURE__ */ new Map();
42143
+ /** Terminal async batch Refinery jobs preserve the last batch outcome for late readers. */
42144
+ terminalRefineBatchJobs = /* @__PURE__ */ new Map();
41525
42145
  constructor(deps) {
41526
42146
  this.deps = deps;
41527
42147
  }
@@ -41599,13 +42219,13 @@ var DaemonCommandRouter = class {
41599
42219
  };
41600
42220
  }
41601
42221
  getCachedAggregateMeshStatus(meshId, mesh, options) {
41602
- const cached = this.aggregateMeshStatusCache.get(meshId);
41603
- if (!cached?.snapshot || cached.snapshot.success !== true || !Array.isArray(cached.snapshot.nodes)) return null;
41604
- if (cached.queueRevision !== getMeshQueueRevision(meshId)) return null;
41605
- let snapshot = this.cloneJsonValue(cached.snapshot);
42222
+ const cached2 = this.aggregateMeshStatusCache.get(meshId);
42223
+ if (!cached2?.snapshot || cached2.snapshot.success !== true || !Array.isArray(cached2.snapshot.nodes)) return null;
42224
+ if (cached2.queueRevision !== getMeshQueueRevision(meshId)) return null;
42225
+ let snapshot = this.cloneJsonValue(cached2.snapshot);
41606
42226
  snapshot = this.hydrateCachedAggregateMeshStatusFromInline(snapshot, mesh, options);
41607
42227
  if (shouldRefreshStalePendingAggregate(snapshot, options)) return null;
41608
- const ageMs = Math.max(0, Date.now() - cached.builtAt);
42228
+ const ageMs = Math.max(0, Date.now() - cached2.builtAt);
41609
42229
  const sourceOfTruth = snapshot.sourceOfTruth && typeof snapshot.sourceOfTruth === "object" ? snapshot.sourceOfTruth : {};
41610
42230
  snapshot.sourceOfTruth = {
41611
42231
  ...sourceOfTruth,
@@ -41616,7 +42236,7 @@ var DaemonCommandRouter = class {
41616
42236
  source: "memory",
41617
42237
  refreshReason: "memory_cache_hit",
41618
42238
  ageMs,
41619
- cachedAt: new Date(cached.builtAt).toISOString(),
42239
+ cachedAt: new Date(cached2.builtAt).toISOString(),
41620
42240
  returnedAt: (/* @__PURE__ */ new Date()).toISOString()
41621
42241
  }
41622
42242
  };
@@ -41660,9 +42280,9 @@ var DaemonCommandRouter = class {
41660
42280
  warmInlineMeshCache(meshId, inlineMesh) {
41661
42281
  if (!inlineMesh || typeof inlineMesh !== "object") return void 0;
41662
42282
  const sanitizedInlineMesh = sanitizeInlineMesh(inlineMesh);
41663
- const cached = this.inlineMeshCache.get(meshId);
41664
- if (cached) {
41665
- const merged = reconcileInlineMeshCache(cached, sanitizedInlineMesh);
42283
+ const cached2 = this.inlineMeshCache.get(meshId);
42284
+ if (cached2) {
42285
+ const merged = reconcileInlineMeshCache(cached2, sanitizedInlineMesh);
41666
42286
  this.inlineMeshCache.set(meshId, merged);
41667
42287
  return merged;
41668
42288
  }
@@ -41672,14 +42292,14 @@ var DaemonCommandRouter = class {
41672
42292
  async getMeshForCommand(meshId, inlineMesh, options) {
41673
42293
  const preferInline = options?.preferInline === true;
41674
42294
  if (preferInline) {
41675
- const cached2 = this.getCachedInlineMesh(meshId);
41676
- if (cached2) {
42295
+ const cached3 = this.getCachedInlineMesh(meshId);
42296
+ if (cached3) {
41677
42297
  if (inlineMeshCarriesTransientNodeTruth(inlineMesh)) {
41678
- const merged = reconcileInlineMeshCache(cached2, inlineMesh);
42298
+ const merged = reconcileInlineMeshCache(cached3, inlineMesh);
41679
42299
  this.inlineMeshCache.set(meshId, sanitizeInlineMesh(merged));
41680
42300
  return { mesh: merged, inline: true, source: "inline_cache" };
41681
42301
  }
41682
- return { mesh: cached2, inline: true, source: "inline_cache" };
42302
+ return { mesh: cached3, inline: true, source: "inline_cache" };
41683
42303
  }
41684
42304
  if (inlineMeshCarriesTransientNodeTruth(inlineMesh)) {
41685
42305
  this.warmInlineMeshCache(meshId, inlineMesh);
@@ -41692,8 +42312,8 @@ var DaemonCommandRouter = class {
41692
42312
  if (mesh) return { mesh, inline: false, source: "local_config" };
41693
42313
  } catch {
41694
42314
  }
41695
- const cached = this.getCachedInlineMesh(meshId);
41696
- if (cached) return { mesh: cached, inline: true, source: "inline_cache" };
42315
+ const cached2 = this.getCachedInlineMesh(meshId);
42316
+ if (cached2) return { mesh: cached2, inline: true, source: "inline_cache" };
41697
42317
  const warmedInline = this.warmInlineMeshCache(meshId, inlineMesh);
41698
42318
  return warmedInline ? { mesh: warmedInline, inline: true, source: "inline_bootstrap" } : null;
41699
42319
  }
@@ -42000,6 +42620,8 @@ var DaemonCommandRouter = class {
42000
42620
  const skippedSessionIds = [];
42001
42621
  const skippedLiveSessionIds = [];
42002
42622
  const skippedCoordinatorSessionIds = [];
42623
+ const skippedLiveSessionReasons = [];
42624
+ const actedLiveDelegateSessionIds = [];
42003
42625
  const deleteUnsupportedSessionIds = [];
42004
42626
  const recordsRemainSessionIds = [];
42005
42627
  const errors = [];
@@ -42033,16 +42655,31 @@ var DaemonCommandRouter = class {
42033
42655
  const surfaceKind = getSessionHostSurfaceKind(record);
42034
42656
  const liveRuntime = surfaceKind === "live_runtime";
42035
42657
  const coordinatorSession = readStringValue(record?.meta?.meshCoordinatorFor) === args.meshId;
42658
+ const recordNodeId = readStringValue(record?.meta?.meshNodeId);
42659
+ const recordMeshNodeFor = readStringValue(record?.meta?.meshNodeFor);
42660
+ const delegateBoundToThisNode = !!recordNodeId && recordNodeId === args.nodeId && (!recordMeshNodeFor || recordMeshNodeFor === args.meshId);
42036
42661
  if (!hasExplicitSessionIds && coordinatorSession) {
42037
42662
  skippedSessionIds.push(sessionId);
42038
42663
  skippedCoordinatorSessionIds.push(sessionId);
42039
42664
  continue;
42040
42665
  }
42041
- if (!hasExplicitSessionIds && liveRuntime) {
42666
+ if (!hasExplicitSessionIds && liveRuntime && !delegateBoundToThisNode) {
42667
+ skippedSessionIds.push(sessionId);
42668
+ skippedLiveSessionIds.push(sessionId);
42669
+ const matchedByWorkspaceOnly = !recordNodeId;
42670
+ 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";
42671
+ skippedLiveSessionReasons.push({ sessionId, reason });
42672
+ continue;
42673
+ }
42674
+ if (!hasExplicitSessionIds && liveRuntime && delegateBoundToThisNode && args.mode === "delete_stopped") {
42042
42675
  skippedSessionIds.push(sessionId);
42043
42676
  skippedLiveSessionIds.push(sessionId);
42677
+ skippedLiveSessionReasons.push({ sessionId, reason: "live_delegate_preserved_by_delete_stopped_mode_use_stop_or_stop_and_delete" });
42044
42678
  continue;
42045
42679
  }
42680
+ if (!hasExplicitSessionIds && liveRuntime && delegateBoundToThisNode) {
42681
+ actedLiveDelegateSessionIds.push(sessionId);
42682
+ }
42046
42683
  try {
42047
42684
  if (args.mode === "stop") {
42048
42685
  if (!completed) {
@@ -42104,6 +42741,8 @@ var DaemonCommandRouter = class {
42104
42741
  skippedSessionIds,
42105
42742
  skippedLiveSessionIds,
42106
42743
  skippedCoordinatorSessionIds,
42744
+ ...actedLiveDelegateSessionIds.length ? { actedLiveDelegateSessionIds } : {},
42745
+ ...skippedLiveSessionReasons.length ? { skippedLiveSessionReasons } : {},
42107
42746
  ...deleteUnsupported ? {
42108
42747
  deleteUnsupported: true,
42109
42748
  effectiveCleanup: args.mode === "stop_and_delete" ? "stopped_only_records_remain" : "delete_unsupported_records_remain",
@@ -42751,6 +43390,48 @@ ${tail}` : ""
42751
43390
  }
42752
43391
  };
42753
43392
  }
43393
+ const effectiveDiffStarted = Date.now();
43394
+ const effectiveDiff = await runMeshRefineEffectiveDiffGate(repoRoot, baseHead, branchHead);
43395
+ recordMeshRefineStage(refineStages, "effective_diff", effectiveDiff.status, effectiveDiffStarted, {
43396
+ hasEffectiveDiff: effectiveDiff.hasEffectiveDiff,
43397
+ changedPaths: effectiveDiff.changedPaths,
43398
+ submoduleHints: effectiveDiff.submoduleHints,
43399
+ ...effectiveDiff.error ? { error: effectiveDiff.error } : {}
43400
+ });
43401
+ if (effectiveDiff.status === "failed" && !effectiveDiff.hasEffectiveDiff) {
43402
+ const hintLines = (effectiveDiff.submoduleHints || []).map((h) => ` - ${h.path}: ${h.reason}`);
43403
+ const message = [
43404
+ `Refinery no-op guard: branch '${branch}' has no effective root-tree diff against '${baseBranch}' (${baseHead.slice(0, 12)}); nothing would merge.`,
43405
+ "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.",
43406
+ hintLines.length ? `Submodules with uncommitted pointer bumps:
43407
+ ${hintLines.join("\n")}` : "",
43408
+ `Fix: commit the submodule pointer bump on '${branch}' (git add <submodule-path> && git commit), then re-run refine.`
43409
+ ].filter(Boolean).join("\n");
43410
+ return {
43411
+ success: false,
43412
+ code: "no_effective_diff",
43413
+ convergenceStatus: "blocked_review",
43414
+ error: message,
43415
+ branch,
43416
+ into: baseBranch,
43417
+ validationSummary,
43418
+ patchEquivalence,
43419
+ effectiveDiff,
43420
+ refineStages,
43421
+ finalBranchConvergenceState: {
43422
+ branch,
43423
+ baseBranch,
43424
+ merged: false,
43425
+ removed: false,
43426
+ validation: "passed",
43427
+ patchEquivalence: "passed",
43428
+ effectiveDiff: "no_effective_diff",
43429
+ status: "blocked_review",
43430
+ reason: "no_effective_diff",
43431
+ ...effectiveDiff.submoduleHints?.length ? { submoduleHints: effectiveDiff.submoduleHints } : {}
43432
+ }
43433
+ };
43434
+ }
42754
43435
  let mergeResult;
42755
43436
  const mergeStarted = Date.now();
42756
43437
  try {
@@ -42989,8 +43670,8 @@ ${tail}` : ""
42989
43670
  const repoRootBaseRef = /* @__PURE__ */ new Map();
42990
43671
  const submodulePathsByRepoRoot = /* @__PURE__ */ new Map();
42991
43672
  const resolveBaseRef = async (repoRoot) => {
42992
- const cached = repoRootBaseRef.get(repoRoot);
42993
- if (cached) return cached;
43673
+ const cached2 = repoRootBaseRef.get(repoRoot);
43674
+ if (cached2) return cached2;
42994
43675
  let baseBranch = "main";
42995
43676
  try {
42996
43677
  const { stdout } = await execFileAsync4("git", ["branch", "--show-current"], { cwd: repoRoot, encoding: "utf8" });
@@ -43092,6 +43773,17 @@ ${tail}` : ""
43092
43773
  note: "Dry-run: no validation, rebase, or merge was executed. Re-run with execute=true to converge nodes in this order."
43093
43774
  };
43094
43775
  }
43776
+ return this.runMeshRefineBatchConvergence(meshId, orderedNodes, ordering, args);
43777
+ }
43778
+ /**
43779
+ * Convergence core shared by the synchronous batch entry and the async batch job.
43780
+ * Refines each node in order: the per-node refine pipeline fetches origin/<base>
43781
+ * fresh, so each merged sibling advances the base before the next node's auto-rebase
43782
+ * + patch-equivalence re-check. A blocked/failed node is isolated; the batch
43783
+ * continues with the remaining nodes. Does NOT touch the per-node merge logic — it
43784
+ * only sequences calls to executeMeshRefineNodeSynchronously and aggregates outcomes.
43785
+ */
43786
+ async runMeshRefineBatchConvergence(meshId, orderedNodes, ordering, args) {
43095
43787
  const results = [];
43096
43788
  for (const node of orderedNodes) {
43097
43789
  let result;
@@ -43146,6 +43838,204 @@ ${tail}` : ""
43146
43838
  }
43147
43839
  };
43148
43840
  }
43841
+ buildRefineBatchJobKey(meshId) {
43842
+ return `${meshId}::batch`;
43843
+ }
43844
+ buildRefineBatchJobHandle(args) {
43845
+ return {
43846
+ success: true,
43847
+ async: true,
43848
+ batch: true,
43849
+ status: args.status || "accepted",
43850
+ jobId: args.jobId || `refine_batch_${createInteractionId()}`,
43851
+ interactionId: args.interactionId || createInteractionId(),
43852
+ meshId: args.meshId,
43853
+ batchLabel: `batch:${args.nodeIds.length} node${args.nodeIds.length === 1 ? "" : "s"}`,
43854
+ nodeIds: args.nodeIds,
43855
+ nodeCount: args.nodeIds.length,
43856
+ order: args.order,
43857
+ startedAt: args.startedAt || (/* @__PURE__ */ new Date()).toISOString(),
43858
+ ...args.completedAt ? { completedAt: args.completedAt } : {},
43859
+ ...args.coordinatorDaemonId ? { targetCoordinatorDaemonId: args.coordinatorDaemonId } : {},
43860
+ eventDelivery: { pendingEvents: true, ledger: true },
43861
+ evidence: {
43862
+ pendingEventsCommand: "get_pending_mesh_events",
43863
+ ledgerCommand: "get_mesh_ledger_slice",
43864
+ taskHistoryKind: args.status === "completed" ? "task_completed" : args.status === "failed" ? "task_failed" : "task_dispatched"
43865
+ }
43866
+ };
43867
+ }
43868
+ /**
43869
+ * Emit a batch Refinery terminal/accepted event through the SAME pending-event +
43870
+ * forward mechanism single-node refine uses (queueRefineJobEvent), so the
43871
+ * coordinator's existing refine:accepted/completed/failed handling and message
43872
+ * renderer apply unchanged. The aggregate per-node results ride along in `result`.
43873
+ */
43874
+ queueRefineBatchJobEvent(event, handle, result) {
43875
+ const metadataEvent = {
43876
+ source: "refine_mesh_node_async_job",
43877
+ batch: true,
43878
+ jobId: handle.jobId,
43879
+ interactionId: handle.interactionId,
43880
+ meshId: handle.meshId,
43881
+ nodeId: handle.batchLabel,
43882
+ nodeIds: handle.nodeIds,
43883
+ workspace: void 0,
43884
+ status: handle.status,
43885
+ startedAt: handle.startedAt,
43886
+ completedAt: handle.completedAt,
43887
+ order: handle.order,
43888
+ ...result ? { result } : {}
43889
+ };
43890
+ const eventPayload = {
43891
+ event,
43892
+ meshId: handle.meshId,
43893
+ nodeLabel: handle.batchLabel,
43894
+ nodeId: handle.batchLabel,
43895
+ metadataEvent,
43896
+ queuedAt: Date.now(),
43897
+ ...handle.targetCoordinatorDaemonId ? { targetCoordinatorDaemonId: handle.targetCoordinatorDaemonId } : {}
43898
+ };
43899
+ if (typeof this.deps.instanceManager?.getByCategory === "function") {
43900
+ const forwarded = handleMeshForwardEvent(
43901
+ { instanceManager: this.deps.instanceManager },
43902
+ {
43903
+ event,
43904
+ meshId: handle.meshId,
43905
+ nodeId: handle.batchLabel,
43906
+ jobId: handle.jobId,
43907
+ interactionId: handle.interactionId,
43908
+ status: handle.status,
43909
+ startedAt: handle.startedAt,
43910
+ completedAt: handle.completedAt,
43911
+ ...result ? { result } : {}
43912
+ }
43913
+ );
43914
+ if (forwarded?.success === true) return;
43915
+ LOG.warn("Mesh", `[Refinery] Failed to forward async refine batch event ${event}: ${forwarded?.error || "unknown error"}`);
43916
+ }
43917
+ queuePendingMeshCoordinatorEvent(eventPayload);
43918
+ }
43919
+ async appendRefineBatchJobLedger(kind, handle, result) {
43920
+ try {
43921
+ const { appendLedgerEntry: appendLedgerEntry2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
43922
+ appendLedgerEntry2(handle.meshId, {
43923
+ kind,
43924
+ nodeId: handle.batchLabel,
43925
+ payload: {
43926
+ source: "refine_mesh_node_async_job",
43927
+ refineJob: {
43928
+ batch: true,
43929
+ jobId: handle.jobId,
43930
+ interactionId: handle.interactionId,
43931
+ status: handle.status,
43932
+ meshId: handle.meshId,
43933
+ nodeIds: handle.nodeIds,
43934
+ order: handle.order,
43935
+ targetCoordinatorDaemonId: handle.targetCoordinatorDaemonId,
43936
+ startedAt: handle.startedAt,
43937
+ completedAt: handle.completedAt
43938
+ },
43939
+ async: true,
43940
+ batch: true,
43941
+ ...result ? {
43942
+ success: result.success === true,
43943
+ result
43944
+ } : {}
43945
+ }
43946
+ });
43947
+ } catch (e) {
43948
+ LOG.warn("Mesh", `[Refinery] Failed to append async refine batch ledger entry: ${e?.message || e}`);
43949
+ }
43950
+ }
43951
+ async finishMeshRefineBatchJob(handle, orderedNodes, ordering, args) {
43952
+ const key = this.buildRefineBatchJobKey(handle.meshId);
43953
+ let result;
43954
+ try {
43955
+ result = await this.runMeshRefineBatchConvergence(handle.meshId, orderedNodes, ordering, args);
43956
+ } catch (e) {
43957
+ result = { success: false, error: e?.message || String(e), batch: true };
43958
+ }
43959
+ const completedAt = (/* @__PURE__ */ new Date()).toISOString();
43960
+ const summary = result.summary && typeof result.summary === "object" ? result.summary : void 0;
43961
+ const allConverged = result.allConverged === true;
43962
+ const isTerminalSuccess = result.success === true && allConverged;
43963
+ 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.";
43964
+ const normalizedResult = {
43965
+ ...result,
43966
+ batch: true,
43967
+ nextStep,
43968
+ ...summary ? {
43969
+ convergenceStatus: allConverged ? "all_converged" : "partial"
43970
+ } : {}
43971
+ };
43972
+ const terminalHandle = this.buildRefineBatchJobHandle({
43973
+ meshId: handle.meshId,
43974
+ nodeIds: handle.nodeIds,
43975
+ order: handle.order,
43976
+ status: isTerminalSuccess ? "completed" : "failed",
43977
+ startedAt: handle.startedAt,
43978
+ completedAt,
43979
+ jobId: handle.jobId,
43980
+ interactionId: handle.interactionId,
43981
+ coordinatorDaemonId: handle.targetCoordinatorDaemonId
43982
+ });
43983
+ const terminal = { ...terminalHandle, result: normalizedResult };
43984
+ this.terminalRefineBatchJobs.set(key, terminal);
43985
+ this.runningRefineBatchJobs.delete(key);
43986
+ this.invalidateAggregateMeshStatus(handle.meshId);
43987
+ await this.appendRefineBatchJobLedger(isTerminalSuccess ? "task_completed" : "task_failed", terminalHandle, normalizedResult);
43988
+ this.queueRefineBatchJobEvent(isTerminalSuccess ? "refine:completed" : "refine:failed", terminalHandle, normalizedResult);
43989
+ }
43990
+ /**
43991
+ * Async entry for the batch Refinery execute path. Mirrors startMeshRefineJob:
43992
+ * resolves the plan synchronously (so target/ordering errors and the dry-run shape
43993
+ * stay synchronous), then for execute=true registers an in-flight batch job, returns
43994
+ * {async:true, status:'accepted', batch:true, ...plan} immediately, and runs the
43995
+ * convergence loop in the background — emitting the same terminal refine event.
43996
+ * Idempotent: a batch already in flight for this mesh returns the running handle
43997
+ * with duplicate:true rather than spawning a second background job.
43998
+ */
43999
+ async startMeshRefineBatchJob(meshId, requestedNodeIds, args) {
44000
+ const plan = await this.batchRefineMeshNodes(meshId, requestedNodeIds, { ...args, dryRun: true, execute: false });
44001
+ const planRecord = plan;
44002
+ if (planRecord.success !== true) return plan;
44003
+ if (args?.dryRun === true && args?.execute !== true) return plan;
44004
+ const order = Array.isArray(planRecord.order) ? planRecord.order.filter((v) => typeof v === "string") : [];
44005
+ const nodeIds = order.slice();
44006
+ if (nodeIds.length === 0) {
44007
+ return { ...planRecord, success: true, batch: true, dryRun: false, async: false };
44008
+ }
44009
+ const key = this.buildRefineBatchJobKey(meshId);
44010
+ const running = this.runningRefineBatchJobs.get(key);
44011
+ if (running) return { ...running, duplicate: true };
44012
+ const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
44013
+ const mesh = meshRecord?.mesh;
44014
+ const allNodes = Array.isArray(mesh?.nodes) ? mesh.nodes : [];
44015
+ const orderedNodes = nodeIds.map((id) => allNodes.find((n) => n.id === id || n.nodeId === id)).filter((n) => !!n);
44016
+ if (orderedNodes.length === 0) {
44017
+ return { success: false, error: "Batch nodes no longer resolvable in mesh", batch: true };
44018
+ }
44019
+ const ordering = {
44020
+ order,
44021
+ rationale: planRecord.orderingRationale
44022
+ };
44023
+ const coordinatorDaemonId = typeof args?.coordinatorDaemonId === "string" && args.coordinatorDaemonId.trim() ? args.coordinatorDaemonId.trim() : this.deps.statusInstanceId || void 0;
44024
+ const handle = this.buildRefineBatchJobHandle({ meshId, nodeIds, order, coordinatorDaemonId });
44025
+ this.runningRefineBatchJobs.set(key, handle);
44026
+ await this.appendRefineBatchJobLedger("task_dispatched", handle);
44027
+ this.queueRefineBatchJobEvent("refine:accepted", handle);
44028
+ setImmediate(() => {
44029
+ void this.finishMeshRefineBatchJob(handle, orderedNodes, ordering, args);
44030
+ });
44031
+ return {
44032
+ ...handle,
44033
+ order,
44034
+ orderingRationale: planRecord.orderingRationale,
44035
+ plan: planRecord.plan,
44036
+ 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."
44037
+ };
44038
+ }
43149
44039
  async finishMeshRefineJob(handle, args) {
43150
44040
  const key = this.buildRefineJobKey(handle.meshId, handle.targetNodeId);
43151
44041
  let result;
@@ -43679,7 +44569,7 @@ ${tail}` : ""
43679
44569
  version: this.deps.statusVersion || "unknown",
43680
44570
  profile: "metadata"
43681
44571
  });
43682
- return { success: true, status: snapshot };
44572
+ return { success: true, status: snapshot, daemonBuild: getDaemonBuildInfo() };
43683
44573
  }
43684
44574
  case "get_machine_runtime_stats": {
43685
44575
  return {
@@ -44649,6 +45539,7 @@ ${tail}` : ""
44649
45539
  let workspace = typeof args?.workspace === "string" ? args.workspace.trim() : "";
44650
45540
  let submoduleIgnorePaths = Array.isArray(args?.submoduleIgnorePaths) ? args.submoduleIgnorePaths.filter((value) => typeof value === "string") : void 0;
44651
45541
  let nodeDaemonId;
45542
+ let allowAutoPublishSubmoduleMainCommits = false;
44652
45543
  if (meshId && nodeId) {
44653
45544
  const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
44654
45545
  const mesh = meshRecord?.mesh;
@@ -44659,6 +45550,7 @@ ${tail}` : ""
44659
45550
  if (!submoduleIgnorePaths && Array.isArray(node?.policy?.submoduleIgnorePaths)) {
44660
45551
  submoduleIgnorePaths = node.policy.submoduleIgnorePaths.filter((value) => typeof value === "string");
44661
45552
  }
45553
+ allowAutoPublishSubmoduleMainCommits = mesh?.policy?.allowAutoPublishSubmoduleMainCommits === true;
44662
45554
  nodeDaemonId = typeof node?.daemonId === "string" ? node.daemonId.trim() : void 0;
44663
45555
  }
44664
45556
  const selfDaemonId = this.deps.statusInstanceId;
@@ -44679,7 +45571,10 @@ ${tail}` : ""
44679
45571
  execute: args?.execute === true,
44680
45572
  dryRun: args?.dryRun === true,
44681
45573
  updateSubmodules: args?.updateSubmodules === true,
44682
- submoduleIgnorePaths
45574
+ submoduleIgnorePaths,
45575
+ mode: args?.mode === "push" ? "push" : "merge",
45576
+ pushSubmodules: args?.pushSubmodules === true,
45577
+ allowAutoPublishSubmoduleMainCommits
44683
45578
  });
44684
45579
  return result;
44685
45580
  }
@@ -44693,7 +45588,9 @@ ${tail}` : ""
44693
45588
  const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
44694
45589
  if (!meshId) return { success: false, error: "meshId required" };
44695
45590
  const requestedNodeIds = Array.isArray(args?.nodeIds) ? args.nodeIds.filter((v) => typeof v === "string" && v.trim().length > 0).map((v) => v.trim()) : void 0;
44696
- return this.batchRefineMeshNodes(meshId, requestedNodeIds, args);
45591
+ const isDryRun = args?.dryRun !== false && args?.execute !== true;
45592
+ if (isDryRun) return this.batchRefineMeshNodes(meshId, requestedNodeIds, args);
45593
+ return this.startMeshRefineBatchJob(meshId, requestedNodeIds, args);
44697
45594
  }
44698
45595
  case "remove_mesh_node": {
44699
45596
  const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
@@ -46377,6 +47274,7 @@ var DaemonStatusReporter = class {
46377
47274
  };
46378
47275
 
46379
47276
  // src/index.ts
47277
+ init_build_info();
46380
47278
  init_logger();
46381
47279
  init_debug_config();
46382
47280
 
@@ -54648,6 +55546,7 @@ var V1_CONTRACT_VERSION = "1.0.0";
54648
55546
  MIN_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS,
54649
55547
  NodePtyTransportFactory,
54650
55548
  P2pRelayFailureError,
55549
+ PRUNABLE_ORPHAN_STALE_REASONS,
54651
55550
  ProviderCliAdapter,
54652
55551
  ProviderInstanceManager,
54653
55552
  ProviderLoader,
@@ -54701,6 +55600,7 @@ var V1_CONTRACT_VERSION = "1.0.0";
54701
55600
  classifyChatMessageVisibility,
54702
55601
  classifyHotChatSessionsForSubscriptionFlush,
54703
55602
  classifyP2pRelayFailure,
55603
+ classifyStaleDirectForPrune,
54704
55604
  cleanupTerminalDirectDispatches,
54705
55605
  clearDebugTrace,
54706
55606
  clearPendingMeshCoordinatorEvents,
@@ -54720,6 +55620,7 @@ var V1_CONTRACT_VERSION = "1.0.0";
54720
55620
  createNativeHistoryDispatcher,
54721
55621
  createSessionDelivery,
54722
55622
  createWorktree,
55623
+ deleteDirectDispatchesByTaskId,
54723
55624
  deleteMesh,
54724
55625
  deriveMeshReviewInboxItems,
54725
55626
  describeTaskDependencyState,
@@ -54748,6 +55649,7 @@ var V1_CONTRACT_VERSION = "1.0.0";
54748
55649
  getAvailableIdeIds,
54749
55650
  getCoordinatorForSession,
54750
55651
  getCurrentDaemonLogPath,
55652
+ getDaemonBuildInfo,
54751
55653
  getDaemonDataDir,
54752
55654
  getDaemonLogDir,
54753
55655
  getDebugRuntimeConfig,