@adhdev/daemon-standalone 0.9.82-rc.261 → 0.9.82-rc.262

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
@@ -70297,8 +70297,37 @@ ${formatManifestValidationIssues2(validation2.issues)}`);
70297
70297
  maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES
70298
70298
  });
70299
70299
  const mergeBase = git(["merge-base", baseHead, branchHead]).trim();
70300
- const mergeTreeStdout = git(["merge-tree", "--write-tree", baseHead, branchHead]);
70301
- const mergedTree = mergeTreeStdout.trim().split(/\s+/)[0] || "";
70300
+ let mergedTree = "";
70301
+ let mergeTreeStdout = "";
70302
+ let gitlinkTrivialFastForward;
70303
+ try {
70304
+ mergeTreeStdout = git(["merge-tree", "--write-tree", baseHead, branchHead]);
70305
+ mergedTree = mergeTreeStdout.trim().split(/\s+/)[0] || "";
70306
+ } catch (mergeTreeErr) {
70307
+ const output = `${mergeTreeErr?.message || ""}
70308
+ ${mergeTreeErr?.stdout || ""}
70309
+ ${mergeTreeErr?.stderr || ""}`;
70310
+ const isSubmoduleConflict = /(submodule|160000)/i.test(output) || /Recursive merging with submodules/i.test(output);
70311
+ if (!isSubmoduleConflict) throw mergeTreeErr;
70312
+ const evaluation = evaluateGitlinkTrivialFastForward(repoRoot, baseHead, branchHead);
70313
+ if (!evaluation.trivial) {
70314
+ return {
70315
+ status: "failed",
70316
+ equivalent: false,
70317
+ baseHead,
70318
+ branchHead,
70319
+ mergeBase: mergeBase || void 0,
70320
+ durationMs: Date.now() - startedAt,
70321
+ error: mergeTreeErr?.message || String(mergeTreeErr),
70322
+ stdout: truncateValidationOutput(mergeTreeErr?.stdout),
70323
+ stderr: truncateValidationOutput(mergeTreeErr?.stderr),
70324
+ gitlinkTrivialFastForward: { resolved: false, gitlinks: evaluation.gitlinks, reason: evaluation.reason },
70325
+ actionableHint: buildPatchEquivalenceSubmoduleConflictHint(repoRoot, baseHead, branchHead, output)
70326
+ };
70327
+ }
70328
+ mergedTree = synthesizeTrivialFastForwardMergeTree(repoRoot, baseHead, branchHead, evaluation.gitlinks) || "";
70329
+ gitlinkTrivialFastForward = { resolved: true, gitlinks: evaluation.gitlinks };
70330
+ }
70302
70331
  if (!mergeBase || !mergedTree) {
70303
70332
  return {
70304
70333
  status: "failed",
@@ -70309,7 +70338,8 @@ ${formatManifestValidationIssues2(validation2.issues)}`);
70309
70338
  mergedTree: mergedTree || void 0,
70310
70339
  durationMs: Date.now() - startedAt,
70311
70340
  error: "patch equivalence preflight could not resolve merge-base or synthetic merge tree",
70312
- stdout: truncateValidationOutput(mergeTreeStdout)
70341
+ stdout: truncateValidationOutput(mergeTreeStdout),
70342
+ gitlinkTrivialFastForward
70313
70343
  };
70314
70344
  }
70315
70345
  const expectedPatchId = await computeGitPatchId(repoRoot, mergeBase, branchHead);
@@ -70324,7 +70354,8 @@ ${formatManifestValidationIssues2(validation2.issues)}`);
70324
70354
  mergedTree,
70325
70355
  expectedPatchId,
70326
70356
  actualPatchId,
70327
- durationMs: Date.now() - startedAt
70357
+ durationMs: Date.now() - startedAt,
70358
+ gitlinkTrivialFastForward
70328
70359
  };
70329
70360
  } catch (e) {
70330
70361
  return {
@@ -70347,6 +70378,65 @@ ${e?.stderr || ""}`
70347
70378
  };
70348
70379
  }
70349
70380
  }
70381
+ async function runMeshRefineEffectiveDiffGate(repoRoot, baseHead, branchHead) {
70382
+ const startedAt = Date.now();
70383
+ try {
70384
+ const { execFileSync: execFileSync6 } = await import("child_process");
70385
+ const git = (args, opts) => execFileSync6("git", args, {
70386
+ cwd: opts?.cwd || repoRoot,
70387
+ encoding: "utf8",
70388
+ maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES
70389
+ });
70390
+ const rawDiff = git(["diff", "--raw", baseHead, branchHead]).trim();
70391
+ if (rawDiff) {
70392
+ const changedPaths = rawDiff.split("\n").map((line) => line.split(" ").slice(1).join(" ").trim()).filter(Boolean).slice(0, 50);
70393
+ return {
70394
+ status: "passed",
70395
+ hasEffectiveDiff: true,
70396
+ baseHead,
70397
+ branchHead,
70398
+ changedPaths,
70399
+ durationMs: Date.now() - startedAt
70400
+ };
70401
+ }
70402
+ const submoduleHints = [];
70403
+ try {
70404
+ const status = git(["submodule", "status"]);
70405
+ for (const line of status.split("\n")) {
70406
+ const trimmed = line.trimEnd();
70407
+ if (!trimmed) continue;
70408
+ if (trimmed.startsWith("+")) {
70409
+ const parts = trimmed.slice(1).trim().split(/\s+/);
70410
+ const path39 = parts[1] || parts[0] || "(unknown)";
70411
+ submoduleHints.push({
70412
+ path: path39,
70413
+ reason: "submodule checked-out commit differs from the committed gitlink (pointer bump not committed on the root branch)"
70414
+ });
70415
+ }
70416
+ }
70417
+ } catch {
70418
+ }
70419
+ return {
70420
+ status: "failed",
70421
+ hasEffectiveDiff: false,
70422
+ baseHead,
70423
+ branchHead,
70424
+ ...submoduleHints.length ? { submoduleHints } : {},
70425
+ durationMs: Date.now() - startedAt
70426
+ };
70427
+ } catch (e) {
70428
+ return {
70429
+ status: "skipped",
70430
+ hasEffectiveDiff: true,
70431
+ baseHead,
70432
+ branchHead,
70433
+ durationMs: Date.now() - startedAt,
70434
+ error: e?.message || String(e),
70435
+ stdout: truncateValidationOutput(e?.stdout),
70436
+ stderr: truncateValidationOutput(e?.stderr)
70437
+ };
70438
+ }
70439
+ }
70350
70440
  function buildPatchEquivalenceSubmoduleConflictHint(repoRoot, baseHead, branchHead, output) {
70351
70441
  if (!/(submodule|160000)/i.test(output) || !/(conflict|failed to merge)/i.test(output)) return void 0;
70352
70442
  const conflicts = readChangedGitlinkPaths(repoRoot, baseHead, branchHead).map((path39) => ({
@@ -70403,6 +70493,135 @@ ${e?.stderr || ""}`
70403
70493
  return void 0;
70404
70494
  }
70405
70495
  }
70496
+ function resolveGitDir(repoRoot) {
70497
+ const out = (0, import_node_child_process6.execFileSync)("git", ["rev-parse", "--absolute-git-dir"], {
70498
+ cwd: repoRoot,
70499
+ encoding: "utf8",
70500
+ maxBuffer: 1024 * 1024
70501
+ }).trim();
70502
+ return out;
70503
+ }
70504
+ function isSubmoduleFastForward(submoduleRepoPath, baseCommit, branchCommit) {
70505
+ if (!baseCommit || !branchCommit) return false;
70506
+ if (baseCommit === branchCommit) return true;
70507
+ try {
70508
+ if (!fs23.existsSync(submoduleRepoPath)) return false;
70509
+ (0, import_node_child_process6.execFileSync)("git", ["cat-file", "-e", `${baseCommit}^{commit}`], { cwd: submoduleRepoPath, stdio: "ignore" });
70510
+ (0, import_node_child_process6.execFileSync)("git", ["cat-file", "-e", `${branchCommit}^{commit}`], { cwd: submoduleRepoPath, stdio: "ignore" });
70511
+ (0, import_node_child_process6.execFileSync)("git", ["merge-base", "--is-ancestor", baseCommit, branchCommit], { cwd: submoduleRepoPath, stdio: "ignore" });
70512
+ return true;
70513
+ } catch {
70514
+ return false;
70515
+ }
70516
+ }
70517
+ function readChangedPathKinds(repoRoot, fromRef, toRef) {
70518
+ try {
70519
+ const output = (0, import_node_child_process6.execFileSync)("git", ["diff", "--raw", "--no-abbrev", fromRef, toRef], {
70520
+ cwd: repoRoot,
70521
+ encoding: "utf8",
70522
+ maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES
70523
+ });
70524
+ const result = [];
70525
+ const seen = /* @__PURE__ */ new Set();
70526
+ for (const line of output.split("\n")) {
70527
+ if (!line.trim()) continue;
70528
+ const metaAndPath = line.split(" ");
70529
+ const meta3 = metaAndPath[0] || "";
70530
+ const path39 = metaAndPath[metaAndPath.length - 1]?.trim();
70531
+ if (!path39 || seen.has(path39)) continue;
70532
+ seen.add(path39);
70533
+ const parts = meta3.split(/\s+/);
70534
+ const isGitlink = !!(parts[0]?.includes("160000") || parts[1]?.includes("160000"));
70535
+ result.push({ path: path39, isGitlink });
70536
+ }
70537
+ return result;
70538
+ } catch {
70539
+ return [];
70540
+ }
70541
+ }
70542
+ function evaluateGitlinkTrivialFastForward(repoRoot, baseHead, branchHead) {
70543
+ const changedGitlinks = readChangedGitlinkPaths(repoRoot, baseHead, branchHead).map((path39) => {
70544
+ const baseCommit = readTreeObject(repoRoot, baseHead, path39);
70545
+ const branchCommit = readTreeObject(repoRoot, branchHead, path39);
70546
+ const submoduleRepoPath = (0, import_path10.resolve)(repoRoot, path39);
70547
+ const fastForward = !!baseCommit && !!branchCommit && isSubmoduleFastForward(submoduleRepoPath, baseCommit, branchCommit);
70548
+ return { path: path39, baseCommit, branchCommit, fastForward };
70549
+ });
70550
+ if (changedGitlinks.length === 0) {
70551
+ return { trivial: false, reason: "no_changed_gitlinks", gitlinks: changedGitlinks };
70552
+ }
70553
+ const nonFastForward = changedGitlinks.filter((entry) => !entry.fastForward);
70554
+ if (nonFastForward.length > 0) {
70555
+ return {
70556
+ trivial: false,
70557
+ reason: `diverged_gitlinks:${nonFastForward.map((entry) => entry.path).join(",")}`,
70558
+ gitlinks: changedGitlinks
70559
+ };
70560
+ }
70561
+ let mergeBase = "";
70562
+ try {
70563
+ mergeBase = (0, import_node_child_process6.execFileSync)("git", ["merge-base", baseHead, branchHead], {
70564
+ cwd: repoRoot,
70565
+ encoding: "utf8",
70566
+ maxBuffer: 1024 * 1024
70567
+ }).trim();
70568
+ } catch {
70569
+ return { trivial: false, reason: "merge_base_unresolved", gitlinks: changedGitlinks };
70570
+ }
70571
+ if (!mergeBase) {
70572
+ return { trivial: false, reason: "merge_base_unresolved", gitlinks: changedGitlinks };
70573
+ }
70574
+ const baseSideChanges = readChangedPathKinds(repoRoot, mergeBase, baseHead);
70575
+ const branchSideChanges = readChangedPathKinds(repoRoot, mergeBase, branchHead);
70576
+ const baseChangedPaths = new Map(baseSideChanges.map((entry) => [entry.path, entry]));
70577
+ const overlapping = branchSideChanges.filter((entry) => baseChangedPaths.has(entry.path));
70578
+ const nonGitlinkOverlap = overlapping.filter((entry) => {
70579
+ const baseEntry = baseChangedPaths.get(entry.path);
70580
+ return !(entry.isGitlink && baseEntry?.isGitlink);
70581
+ });
70582
+ if (nonGitlinkOverlap.length > 0) {
70583
+ return {
70584
+ trivial: false,
70585
+ reason: `non_gitlink_overlap:${nonGitlinkOverlap.map((entry) => entry.path).join(",")}`,
70586
+ gitlinks: changedGitlinks
70587
+ };
70588
+ }
70589
+ return { trivial: true, gitlinks: changedGitlinks };
70590
+ }
70591
+ function synthesizeTrivialFastForwardMergeTree(repoRoot, baseHead, branchHead, gitlinks) {
70592
+ try {
70593
+ const baseTree = (0, import_node_child_process6.execFileSync)("git", ["rev-parse", `${baseHead}^{tree}`], {
70594
+ cwd: repoRoot,
70595
+ encoding: "utf8",
70596
+ maxBuffer: 1024 * 1024
70597
+ }).trim();
70598
+ if (!baseTree) return void 0;
70599
+ const updates = gitlinks.filter((entry) => entry.branchCommit).map((entry) => `160000 commit ${entry.branchCommit} ${entry.path}`).join("\n");
70600
+ if (!updates) return baseTree;
70601
+ const tmpIndex = (0, import_path10.join)(resolveGitDir(repoRoot), `adhdev-refine-ff-${baseHead.slice(0, 12)}-${branchHead.slice(0, 12)}.index`);
70602
+ const env2 = { ...process.env, GIT_INDEX_FILE: tmpIndex };
70603
+ try {
70604
+ (0, import_node_child_process6.execFileSync)("git", ["read-tree", baseTree], { cwd: repoRoot, env: env2, stdio: "ignore" });
70605
+ (0, import_node_child_process6.execFileSync)("git", ["update-index", "--index-info"], {
70606
+ cwd: repoRoot,
70607
+ env: env2,
70608
+ input: `${updates}
70609
+ `,
70610
+ encoding: "utf8",
70611
+ stdio: ["pipe", "ignore", "ignore"]
70612
+ });
70613
+ const newTree = (0, import_node_child_process6.execFileSync)("git", ["write-tree"], { cwd: repoRoot, env: env2, encoding: "utf8" }).trim();
70614
+ return newTree || void 0;
70615
+ } finally {
70616
+ try {
70617
+ fs23.rmSync(tmpIndex, { force: true });
70618
+ } catch {
70619
+ }
70620
+ }
70621
+ } catch {
70622
+ return void 0;
70623
+ }
70624
+ }
70406
70625
  async function alignRefinerySubmodulesAfterMerge(repoRoot, previousBaseHead, currentHead, options = {}) {
70407
70626
  const startedAt = Date.now();
70408
70627
  const changedGitlinkPaths = readChangedGitlinkPaths(repoRoot, previousBaseHead, currentHead).filter((path39) => !(options.submoduleIgnorePaths || []).includes(path39));
@@ -71070,6 +71289,10 @@ ${e?.stderr || ""}`
71070
71289
  runningRefineJobs = /* @__PURE__ */ new Map();
71071
71290
  /** Terminal async Refinery jobs preserve a clear answer after the worktree node has been removed. */
71072
71291
  terminalRefineJobs = /* @__PURE__ */ new Map();
71292
+ /** In-memory async batch Refinery jobs keyed by meshId (one batch convergence per mesh at a time). */
71293
+ runningRefineBatchJobs = /* @__PURE__ */ new Map();
71294
+ /** Terminal async batch Refinery jobs preserve the last batch outcome for late readers. */
71295
+ terminalRefineBatchJobs = /* @__PURE__ */ new Map();
71073
71296
  constructor(deps) {
71074
71297
  this.deps = deps;
71075
71298
  }
@@ -72299,6 +72522,48 @@ ${tail}` : ""
72299
72522
  }
72300
72523
  };
72301
72524
  }
72525
+ const effectiveDiffStarted = Date.now();
72526
+ const effectiveDiff = await runMeshRefineEffectiveDiffGate(repoRoot, baseHead, branchHead);
72527
+ recordMeshRefineStage(refineStages, "effective_diff", effectiveDiff.status, effectiveDiffStarted, {
72528
+ hasEffectiveDiff: effectiveDiff.hasEffectiveDiff,
72529
+ changedPaths: effectiveDiff.changedPaths,
72530
+ submoduleHints: effectiveDiff.submoduleHints,
72531
+ ...effectiveDiff.error ? { error: effectiveDiff.error } : {}
72532
+ });
72533
+ if (effectiveDiff.status === "failed" && !effectiveDiff.hasEffectiveDiff) {
72534
+ const hintLines = (effectiveDiff.submoduleHints || []).map((h) => ` - ${h.path}: ${h.reason}`);
72535
+ const message = [
72536
+ `Refinery no-op guard: branch '${branch}' has no effective root-tree diff against '${baseBranch}' (${baseHead.slice(0, 12)}); nothing would merge.`,
72537
+ "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.",
72538
+ hintLines.length ? `Submodules with uncommitted pointer bumps:
72539
+ ${hintLines.join("\n")}` : "",
72540
+ `Fix: commit the submodule pointer bump on '${branch}' (git add <submodule-path> && git commit), then re-run refine.`
72541
+ ].filter(Boolean).join("\n");
72542
+ return {
72543
+ success: false,
72544
+ code: "no_effective_diff",
72545
+ convergenceStatus: "blocked_review",
72546
+ error: message,
72547
+ branch,
72548
+ into: baseBranch,
72549
+ validationSummary,
72550
+ patchEquivalence,
72551
+ effectiveDiff,
72552
+ refineStages,
72553
+ finalBranchConvergenceState: {
72554
+ branch,
72555
+ baseBranch,
72556
+ merged: false,
72557
+ removed: false,
72558
+ validation: "passed",
72559
+ patchEquivalence: "passed",
72560
+ effectiveDiff: "no_effective_diff",
72561
+ status: "blocked_review",
72562
+ reason: "no_effective_diff",
72563
+ ...effectiveDiff.submoduleHints?.length ? { submoduleHints: effectiveDiff.submoduleHints } : {}
72564
+ }
72565
+ };
72566
+ }
72302
72567
  let mergeResult;
72303
72568
  const mergeStarted = Date.now();
72304
72569
  try {
@@ -72640,6 +72905,17 @@ ${tail}` : ""
72640
72905
  note: "Dry-run: no validation, rebase, or merge was executed. Re-run with execute=true to converge nodes in this order."
72641
72906
  };
72642
72907
  }
72908
+ return this.runMeshRefineBatchConvergence(meshId, orderedNodes, ordering, args);
72909
+ }
72910
+ /**
72911
+ * Convergence core shared by the synchronous batch entry and the async batch job.
72912
+ * Refines each node in order: the per-node refine pipeline fetches origin/<base>
72913
+ * fresh, so each merged sibling advances the base before the next node's auto-rebase
72914
+ * + patch-equivalence re-check. A blocked/failed node is isolated; the batch
72915
+ * continues with the remaining nodes. Does NOT touch the per-node merge logic — it
72916
+ * only sequences calls to executeMeshRefineNodeSynchronously and aggregates outcomes.
72917
+ */
72918
+ async runMeshRefineBatchConvergence(meshId, orderedNodes, ordering, args) {
72643
72919
  const results = [];
72644
72920
  for (const node of orderedNodes) {
72645
72921
  let result;
@@ -72694,6 +72970,204 @@ ${tail}` : ""
72694
72970
  }
72695
72971
  };
72696
72972
  }
72973
+ buildRefineBatchJobKey(meshId) {
72974
+ return `${meshId}::batch`;
72975
+ }
72976
+ buildRefineBatchJobHandle(args) {
72977
+ return {
72978
+ success: true,
72979
+ async: true,
72980
+ batch: true,
72981
+ status: args.status || "accepted",
72982
+ jobId: args.jobId || `refine_batch_${createInteractionId()}`,
72983
+ interactionId: args.interactionId || createInteractionId(),
72984
+ meshId: args.meshId,
72985
+ batchLabel: `batch:${args.nodeIds.length} node${args.nodeIds.length === 1 ? "" : "s"}`,
72986
+ nodeIds: args.nodeIds,
72987
+ nodeCount: args.nodeIds.length,
72988
+ order: args.order,
72989
+ startedAt: args.startedAt || (/* @__PURE__ */ new Date()).toISOString(),
72990
+ ...args.completedAt ? { completedAt: args.completedAt } : {},
72991
+ ...args.coordinatorDaemonId ? { targetCoordinatorDaemonId: args.coordinatorDaemonId } : {},
72992
+ eventDelivery: { pendingEvents: true, ledger: true },
72993
+ evidence: {
72994
+ pendingEventsCommand: "get_pending_mesh_events",
72995
+ ledgerCommand: "get_mesh_ledger_slice",
72996
+ taskHistoryKind: args.status === "completed" ? "task_completed" : args.status === "failed" ? "task_failed" : "task_dispatched"
72997
+ }
72998
+ };
72999
+ }
73000
+ /**
73001
+ * Emit a batch Refinery terminal/accepted event through the SAME pending-event +
73002
+ * forward mechanism single-node refine uses (queueRefineJobEvent), so the
73003
+ * coordinator's existing refine:accepted/completed/failed handling and message
73004
+ * renderer apply unchanged. The aggregate per-node results ride along in `result`.
73005
+ */
73006
+ queueRefineBatchJobEvent(event, handle, result) {
73007
+ const metadataEvent = {
73008
+ source: "refine_mesh_node_async_job",
73009
+ batch: true,
73010
+ jobId: handle.jobId,
73011
+ interactionId: handle.interactionId,
73012
+ meshId: handle.meshId,
73013
+ nodeId: handle.batchLabel,
73014
+ nodeIds: handle.nodeIds,
73015
+ workspace: void 0,
73016
+ status: handle.status,
73017
+ startedAt: handle.startedAt,
73018
+ completedAt: handle.completedAt,
73019
+ order: handle.order,
73020
+ ...result ? { result } : {}
73021
+ };
73022
+ const eventPayload = {
73023
+ event,
73024
+ meshId: handle.meshId,
73025
+ nodeLabel: handle.batchLabel,
73026
+ nodeId: handle.batchLabel,
73027
+ metadataEvent,
73028
+ queuedAt: Date.now(),
73029
+ ...handle.targetCoordinatorDaemonId ? { targetCoordinatorDaemonId: handle.targetCoordinatorDaemonId } : {}
73030
+ };
73031
+ if (typeof this.deps.instanceManager?.getByCategory === "function") {
73032
+ const forwarded = handleMeshForwardEvent(
73033
+ { instanceManager: this.deps.instanceManager },
73034
+ {
73035
+ event,
73036
+ meshId: handle.meshId,
73037
+ nodeId: handle.batchLabel,
73038
+ jobId: handle.jobId,
73039
+ interactionId: handle.interactionId,
73040
+ status: handle.status,
73041
+ startedAt: handle.startedAt,
73042
+ completedAt: handle.completedAt,
73043
+ ...result ? { result } : {}
73044
+ }
73045
+ );
73046
+ if (forwarded?.success === true) return;
73047
+ LOG2.warn("Mesh", `[Refinery] Failed to forward async refine batch event ${event}: ${forwarded?.error || "unknown error"}`);
73048
+ }
73049
+ queuePendingMeshCoordinatorEvent(eventPayload);
73050
+ }
73051
+ async appendRefineBatchJobLedger(kind, handle, result) {
73052
+ try {
73053
+ const { appendLedgerEntry: appendLedgerEntry2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
73054
+ appendLedgerEntry2(handle.meshId, {
73055
+ kind,
73056
+ nodeId: handle.batchLabel,
73057
+ payload: {
73058
+ source: "refine_mesh_node_async_job",
73059
+ refineJob: {
73060
+ batch: true,
73061
+ jobId: handle.jobId,
73062
+ interactionId: handle.interactionId,
73063
+ status: handle.status,
73064
+ meshId: handle.meshId,
73065
+ nodeIds: handle.nodeIds,
73066
+ order: handle.order,
73067
+ targetCoordinatorDaemonId: handle.targetCoordinatorDaemonId,
73068
+ startedAt: handle.startedAt,
73069
+ completedAt: handle.completedAt
73070
+ },
73071
+ async: true,
73072
+ batch: true,
73073
+ ...result ? {
73074
+ success: result.success === true,
73075
+ result
73076
+ } : {}
73077
+ }
73078
+ });
73079
+ } catch (e) {
73080
+ LOG2.warn("Mesh", `[Refinery] Failed to append async refine batch ledger entry: ${e?.message || e}`);
73081
+ }
73082
+ }
73083
+ async finishMeshRefineBatchJob(handle, orderedNodes, ordering, args) {
73084
+ const key = this.buildRefineBatchJobKey(handle.meshId);
73085
+ let result;
73086
+ try {
73087
+ result = await this.runMeshRefineBatchConvergence(handle.meshId, orderedNodes, ordering, args);
73088
+ } catch (e) {
73089
+ result = { success: false, error: e?.message || String(e), batch: true };
73090
+ }
73091
+ const completedAt = (/* @__PURE__ */ new Date()).toISOString();
73092
+ const summary = result.summary && typeof result.summary === "object" ? result.summary : void 0;
73093
+ const allConverged = result.allConverged === true;
73094
+ const isTerminalSuccess = result.success === true && allConverged;
73095
+ 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.";
73096
+ const normalizedResult = {
73097
+ ...result,
73098
+ batch: true,
73099
+ nextStep,
73100
+ ...summary ? {
73101
+ convergenceStatus: allConverged ? "all_converged" : "partial"
73102
+ } : {}
73103
+ };
73104
+ const terminalHandle = this.buildRefineBatchJobHandle({
73105
+ meshId: handle.meshId,
73106
+ nodeIds: handle.nodeIds,
73107
+ order: handle.order,
73108
+ status: isTerminalSuccess ? "completed" : "failed",
73109
+ startedAt: handle.startedAt,
73110
+ completedAt,
73111
+ jobId: handle.jobId,
73112
+ interactionId: handle.interactionId,
73113
+ coordinatorDaemonId: handle.targetCoordinatorDaemonId
73114
+ });
73115
+ const terminal = { ...terminalHandle, result: normalizedResult };
73116
+ this.terminalRefineBatchJobs.set(key, terminal);
73117
+ this.runningRefineBatchJobs.delete(key);
73118
+ this.invalidateAggregateMeshStatus(handle.meshId);
73119
+ await this.appendRefineBatchJobLedger(isTerminalSuccess ? "task_completed" : "task_failed", terminalHandle, normalizedResult);
73120
+ this.queueRefineBatchJobEvent(isTerminalSuccess ? "refine:completed" : "refine:failed", terminalHandle, normalizedResult);
73121
+ }
73122
+ /**
73123
+ * Async entry for the batch Refinery execute path. Mirrors startMeshRefineJob:
73124
+ * resolves the plan synchronously (so target/ordering errors and the dry-run shape
73125
+ * stay synchronous), then for execute=true registers an in-flight batch job, returns
73126
+ * {async:true, status:'accepted', batch:true, ...plan} immediately, and runs the
73127
+ * convergence loop in the background — emitting the same terminal refine event.
73128
+ * Idempotent: a batch already in flight for this mesh returns the running handle
73129
+ * with duplicate:true rather than spawning a second background job.
73130
+ */
73131
+ async startMeshRefineBatchJob(meshId, requestedNodeIds, args) {
73132
+ const plan = await this.batchRefineMeshNodes(meshId, requestedNodeIds, { ...args, dryRun: true, execute: false });
73133
+ const planRecord = plan;
73134
+ if (planRecord.success !== true) return plan;
73135
+ if (args?.dryRun === true && args?.execute !== true) return plan;
73136
+ const order = Array.isArray(planRecord.order) ? planRecord.order.filter((v) => typeof v === "string") : [];
73137
+ const nodeIds = order.slice();
73138
+ if (nodeIds.length === 0) {
73139
+ return { ...planRecord, success: true, batch: true, dryRun: false, async: false };
73140
+ }
73141
+ const key = this.buildRefineBatchJobKey(meshId);
73142
+ const running = this.runningRefineBatchJobs.get(key);
73143
+ if (running) return { ...running, duplicate: true };
73144
+ const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
73145
+ const mesh = meshRecord?.mesh;
73146
+ const allNodes = Array.isArray(mesh?.nodes) ? mesh.nodes : [];
73147
+ const orderedNodes = nodeIds.map((id) => allNodes.find((n) => n.id === id || n.nodeId === id)).filter((n) => !!n);
73148
+ if (orderedNodes.length === 0) {
73149
+ return { success: false, error: "Batch nodes no longer resolvable in mesh", batch: true };
73150
+ }
73151
+ const ordering = {
73152
+ order,
73153
+ rationale: planRecord.orderingRationale
73154
+ };
73155
+ const coordinatorDaemonId = typeof args?.coordinatorDaemonId === "string" && args.coordinatorDaemonId.trim() ? args.coordinatorDaemonId.trim() : this.deps.statusInstanceId || void 0;
73156
+ const handle = this.buildRefineBatchJobHandle({ meshId, nodeIds, order, coordinatorDaemonId });
73157
+ this.runningRefineBatchJobs.set(key, handle);
73158
+ await this.appendRefineBatchJobLedger("task_dispatched", handle);
73159
+ this.queueRefineBatchJobEvent("refine:accepted", handle);
73160
+ setImmediate(() => {
73161
+ void this.finishMeshRefineBatchJob(handle, orderedNodes, ordering, args);
73162
+ });
73163
+ return {
73164
+ ...handle,
73165
+ order,
73166
+ orderingRationale: planRecord.orderingRationale,
73167
+ plan: planRecord.plan,
73168
+ 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."
73169
+ };
73170
+ }
72697
73171
  async finishMeshRefineJob(handle, args) {
72698
73172
  const key = this.buildRefineJobKey(handle.meshId, handle.targetNodeId);
72699
73173
  let result;
@@ -74241,7 +74715,9 @@ ${tail}` : ""
74241
74715
  const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
74242
74716
  if (!meshId) return { success: false, error: "meshId required" };
74243
74717
  const requestedNodeIds = Array.isArray(args?.nodeIds) ? args.nodeIds.filter((v) => typeof v === "string" && v.trim().length > 0).map((v) => v.trim()) : void 0;
74244
- return this.batchRefineMeshNodes(meshId, requestedNodeIds, args);
74718
+ const isDryRun = args?.dryRun !== false && args?.execute !== true;
74719
+ if (isDryRun) return this.batchRefineMeshNodes(meshId, requestedNodeIds, args);
74720
+ return this.startMeshRefineBatchJob(meshId, requestedNodeIds, args);
74245
74721
  }
74246
74722
  case "remove_mesh_node": {
74247
74723
  const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";