@adhdev/daemon-standalone 0.9.82-rc.259 → 0.9.82-rc.260

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
@@ -32396,6 +32396,7 @@ Valid status values: \`completed\` | \`failed\` | \`blocked\` | \`partial\`.`;
32396
32396
  nodeSatisfiesRequiredTags: () => nodeSatisfiesRequiredTags,
32397
32397
  normalizeMeshCapabilityTags: () => normalizeMeshCapabilityTags,
32398
32398
  normalizeMeshTaskMode: () => normalizeMeshTaskMode,
32399
+ recordDirectDispatchTask: () => recordDirectDispatchTask,
32399
32400
  recordMeshToolCall: () => recordMeshToolCall,
32400
32401
  recordTaskAutoLaunch: () => recordTaskAutoLaunch,
32401
32402
  requeueTask: () => requeueTask,
@@ -32560,6 +32561,37 @@ Valid status values: \`completed\` | \`failed\` | \`blocked\` | \`partial\`.`;
32560
32561
  return entry;
32561
32562
  });
32562
32563
  }
32564
+ function recordDirectDispatchTask(meshId, message, opts) {
32565
+ const missionId = typeof opts.missionId === "string" ? opts.missionId.trim() : "";
32566
+ if (!missionId) return null;
32567
+ const taskId = typeof opts.id === "string" ? opts.id.trim() : "";
32568
+ if (!taskId) return null;
32569
+ const modeValidation = validateMeshTaskModeRequest(opts.taskMode, message);
32570
+ if (!modeValidation.valid) {
32571
+ throw new Error(`live_debug_readonly_guardrail_violation: forbidden operations (${modeValidation.violations.join(", ")})`);
32572
+ }
32573
+ const now = opts.dispatchedAt && opts.dispatchedAt.trim() ? opts.dispatchedAt : (/* @__PURE__ */ new Date()).toISOString();
32574
+ return withQueueLock(meshId, () => {
32575
+ if (MeshRuntimeStore.getInstance().findQueueEntryById(meshId, taskId)) {
32576
+ return null;
32577
+ }
32578
+ const entry = {
32579
+ id: taskId,
32580
+ meshId,
32581
+ message,
32582
+ status: "assigned",
32583
+ ...modeValidation.taskMode ? { taskMode: modeValidation.taskMode } : {},
32584
+ missionId,
32585
+ ...opts.assignedNodeId ? { targetNodeId: opts.assignedNodeId, assignedNodeId: opts.assignedNodeId } : {},
32586
+ ...opts.assignedSessionId ? { targetSessionId: opts.assignedSessionId, assignedSessionId: opts.assignedSessionId } : {},
32587
+ dispatchTimestamp: now,
32588
+ createdAt: now,
32589
+ updatedAt: now
32590
+ };
32591
+ MeshRuntimeStore.getInstance().insertQueueEntry(entry);
32592
+ return entry;
32593
+ });
32594
+ }
32563
32595
  function getQueue(meshId, opts) {
32564
32596
  return MeshRuntimeStore.getInstance().getQueueEntries(meshId, opts?.status?.length ? opts.status : void 0);
32565
32597
  }
@@ -34148,9 +34180,50 @@ Valid status values: \`completed\` | \`failed\` | \`blocked\` | \`partial\`.`;
34148
34180
  return (Number.isFinite(bTime) ? bTime : 0) - (Number.isFinite(aTime) ? aTime : 0);
34149
34181
  });
34150
34182
  }
34183
+ function jobActivityTime(job) {
34184
+ const raw = job.lastUpdatedAt || job.completedAt || job.startedAt || "";
34185
+ const t = new Date(raw).getTime();
34186
+ return Number.isFinite(t) ? t : 0;
34187
+ }
34188
+ function summarizeMeshAsyncRefineJobs(jobs) {
34189
+ const activeJobs = [];
34190
+ const terminalJobs = [];
34191
+ for (const job of jobs) {
34192
+ if (TERMINAL_REFINE_STATUSES.has(job.status)) terminalJobs.push(job);
34193
+ else activeJobs.push(job);
34194
+ }
34195
+ let newest = 0;
34196
+ for (const job of jobs) newest = Math.max(newest, jobActivityTime(job));
34197
+ const cutoff = newest - STALE_TERMINAL_REFINE_WINDOW_MS;
34198
+ const terminalByRecency = [...terminalJobs].sort(
34199
+ (a, b) => jobActivityTime(b) - jobActivityTime(a)
34200
+ );
34201
+ const freshTerminal = terminalByRecency.filter((job) => jobActivityTime(job) >= cutoff).slice(0, RECENT_TERMINAL_REFINE_CAP);
34202
+ const freshTerminalIds = new Set(freshTerminal.map((job) => job.jobId));
34203
+ const byStatus = {};
34204
+ for (const job of activeJobs) {
34205
+ byStatus[job.status] = (byStatus[job.status] ?? 0) + 1;
34206
+ }
34207
+ for (const job of freshTerminal) {
34208
+ byStatus[job.status] = (byStatus[job.status] ?? 0) + 1;
34209
+ }
34210
+ const staleTerminal = terminalJobs.length - freshTerminalIds.size;
34211
+ return {
34212
+ total: activeJobs.length + freshTerminal.length,
34213
+ byStatus,
34214
+ staleTerminal,
34215
+ activeJobs
34216
+ };
34217
+ }
34218
+ var TERMINAL_REFINE_STATUSES;
34219
+ var STALE_TERMINAL_REFINE_WINDOW_MS;
34220
+ var RECENT_TERMINAL_REFINE_CAP;
34151
34221
  var init_mesh_refine_status = __esm2({
34152
34222
  "src/mesh/mesh-refine-status.ts"() {
34153
34223
  "use strict";
34224
+ TERMINAL_REFINE_STATUSES = /* @__PURE__ */ new Set(["completed", "failed"]);
34225
+ STALE_TERMINAL_REFINE_WINDOW_MS = 6 * 60 * 60 * 1e3;
34226
+ RECENT_TERMINAL_REFINE_CAP = 8;
34154
34227
  }
34155
34228
  });
34156
34229
  var mesh_review_inbox_exports = {};
@@ -40176,6 +40249,26 @@ ${cont}` : cont;
40176
40249
  // ── Approval ─────────────────────────────────────
40177
40250
  lastApprovalResolvedAt = 0;
40178
40251
  lastResolvedModalMessage = "";
40252
+ /**
40253
+ * Monotonic counter bumped every time the FSM *enters* waiting_approval
40254
+ * with a freshly captured modal (see `applyWaitingApproval`). It is the
40255
+ * single discriminator between "the same approval re-observed across TUI
40256
+ * paint flaps" and "a genuinely new, distinct approval".
40257
+ *
40258
+ * The message-equality cooldown below (`lastResolvedModalMessage`) cannot
40259
+ * tell these apart on its own: claude-cli routinely presents consecutive
40260
+ * approvals whose modal message text is identical (e.g. two back-to-back
40261
+ * Bash-command prompts). When that second approval arrived inside
40262
+ * `approvalCooldown`, the message-equality guard silently swallowed the
40263
+ * key write and the approval stuck forever — fatal under auto-approval.
40264
+ *
40265
+ * `approvalEntrySeq` increments on every fresh entry; `lastResolvedEntrySeq`
40266
+ * records which entry the cooldown belongs to. We only short-circuit the
40267
+ * write when we are still resolving *that same* entry — a new entry (new
40268
+ * seq) is always a real, distinct approval and must be written.
40269
+ */
40270
+ approvalEntrySeq = 0;
40271
+ lastResolvedEntrySeq = -1;
40179
40272
  /**
40180
40273
  * When the engine previously held a modal but the latest parse failed
40181
40274
  * to extract one, we record the timestamp here and only drop the modal
@@ -40281,6 +40374,7 @@ ${cont}` : cont;
40281
40374
  if (parsed?.status === "waiting_approval" && parsedModal) {
40282
40375
  modal = parsedModal;
40283
40376
  this.activeModal = parsedModal;
40377
+ this.approvalEntrySeq++;
40284
40378
  if (this.currentStatus !== "waiting_approval") {
40285
40379
  this.setStatus("waiting_approval", "resolve_modal_parse");
40286
40380
  this.callbacks.onStatusChange();
@@ -40294,12 +40388,14 @@ ${cont}` : cont;
40294
40388
  if (!modal || !buttonsValid) return;
40295
40389
  const currentModalMessage = typeof modal?.message === "string" ? modal.message.trim() : "";
40296
40390
  const inCooldown = !!this.lastApprovalResolvedAt && Date.now() - this.lastApprovalResolvedAt < this.timeouts.approvalCooldown;
40297
- if (inCooldown && currentModalMessage === this.lastResolvedModalMessage) return;
40391
+ const sameEntryReResolve = this.approvalEntrySeq === this.lastResolvedEntrySeq;
40392
+ if (inCooldown && sameEntryReResolve && currentModalMessage === this.lastResolvedModalMessage) return;
40298
40393
  this.clearIdleFinishCandidate("resolve_modal");
40299
- this.recordTrace("resolve_modal", { buttonIndex, activeModal: modal });
40394
+ this.recordTrace("resolve_modal", { buttonIndex, activeModal: modal, approvalEntrySeq: this.approvalEntrySeq });
40300
40395
  this.activeModal = null;
40301
40396
  this.lastApprovalResolvedAt = Date.now();
40302
40397
  this.lastResolvedModalMessage = currentModalMessage;
40398
+ this.lastResolvedEntrySeq = this.approvalEntrySeq;
40303
40399
  this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
40304
40400
  if (this.approvalExitTimeout) {
40305
40401
  clearTimeout(this.approvalExitTimeout);
@@ -40656,7 +40752,7 @@ ${cont}` : cont;
40656
40752
  this.callbacks.onStatusChange();
40657
40753
  return;
40658
40754
  }
40659
- if (!inCooldown) {
40755
+ if (!inCooldown || modal) {
40660
40756
  if (!modal) {
40661
40757
  LOG2.warn("CLI", `[${this.provider.type}] detectStatus=waiting_approval but parseApproval returned null; ignoring`);
40662
40758
  if (this.currentStatus === "waiting_approval" && this.activeModal) {
@@ -40679,6 +40775,7 @@ ${cont}` : cont;
40679
40775
  const nextBtnCount = Array.isArray(modal.buttons) ? modal.buttons.length : 0;
40680
40776
  if (!prev || prevBtnCount !== nextBtnCount) {
40681
40777
  this.activeModal = modal;
40778
+ this.approvalEntrySeq++;
40682
40779
  this.callbacks.onStatusChange();
40683
40780
  }
40684
40781
  if (this.idleTimeout) clearTimeout(this.idleTimeout);
@@ -41872,6 +41969,7 @@ ${lastSnapshot}`;
41872
41969
  messages: [],
41873
41970
  workingDir: this.workingDir,
41874
41971
  activeModal: effectiveModal,
41972
+ approvalEntrySeq: this.engine.approvalEntrySeq,
41875
41973
  pendingOutboundCount: this.pendingOutboundQueue.length,
41876
41974
  pendingOutboundMessages: this.pendingOutboundQueue.map((message) => ({
41877
41975
  id: message.id,
@@ -43770,7 +43868,9 @@ ${lastSnapshot}`;
43770
43868
  ProviderCliAdapter: () => ProviderCliAdapter,
43771
43869
  ProviderInstanceManager: () => ProviderInstanceManager,
43772
43870
  ProviderLoader: () => ProviderLoader,
43871
+ RECENT_TERMINAL_REFINE_CAP: () => RECENT_TERMINAL_REFINE_CAP,
43773
43872
  RawTerminalAttachment: () => RawTerminalAttachment,
43873
+ STALE_TERMINAL_REFINE_WINDOW_MS: () => STALE_TERMINAL_REFINE_WINDOW_MS,
43774
43874
  STANDALONE_CDP_SCAN_INTERVAL_MS: () => STANDALONE_CDP_SCAN_INTERVAL_MS2,
43775
43875
  SessionHostPtyTransportFactory: () => SessionHostPtyTransportFactory2,
43776
43876
  TerminalAdapter: () => TerminalAdapter,
@@ -43978,6 +44078,7 @@ ${lastSnapshot}`;
43978
44078
  reconcileDirectDispatchCompletionFromTranscript: () => reconcileDirectDispatchCompletionFromTranscript,
43979
44079
  recordCompletionConflict: () => recordCompletionConflict,
43980
44080
  recordDebugTrace: () => recordDebugTrace,
44081
+ recordDirectDispatchTask: () => recordDirectDispatchTask,
43981
44082
  recordMeshToolCall: () => recordMeshToolCall,
43982
44083
  registerExtensionProviders: () => registerExtensionProviders,
43983
44084
  registerMeshCoordinator: () => registerMeshCoordinator,
@@ -44014,6 +44115,7 @@ ${lastSnapshot}`;
44014
44115
  startLocalIpcServer: () => startLocalIpcServer2,
44015
44116
  suggestMeshRefineConfig: () => suggestMeshRefineConfig,
44016
44117
  summarizeGitStatus: () => summarizeGitStatus,
44118
+ summarizeMeshAsyncRefineJobs: () => summarizeMeshAsyncRefineJobs,
44017
44119
  summarizeMeshMission: () => summarizeMeshMission,
44018
44120
  summarizeMissionTasks: () => summarizeMissionTasks,
44019
44121
  triggerMeshQueue: () => triggerMeshQueue,
@@ -46236,7 +46338,7 @@ ${lastSnapshot}`;
46236
46338
  if (!validation.valid) {
46237
46339
  return { status: "failed", required: required2, configSource: loaded.path || loaded.source, configSourceType: "invalid", error: String(validation.rejectedCommands[0]?.reason || validation.errors.join("; ")), commandsRun: [] };
46238
46340
  }
46239
- const execFileAsync3 = (0, import_node_util3.promisify)(import_node_child_process3.execFile);
46341
+ const execFileAsync4 = (0, import_node_util3.promisify)(import_node_child_process3.execFile);
46240
46342
  const state = {
46241
46343
  status: "running",
46242
46344
  required: required2,
@@ -46262,7 +46364,7 @@ ${lastSnapshot}`;
46262
46364
  const startedAt = Date.now();
46263
46365
  state.lastCommand = command.displayCommand;
46264
46366
  try {
46265
- const result = await execFileAsync3(command.command, command.args, {
46367
+ const result = await execFileAsync4(command.command, command.args, {
46266
46368
  cwd,
46267
46369
  encoding: "utf8",
46268
46370
  timeout: command.timeoutMs || DEFAULT_TIMEOUT_MS2,
@@ -61244,7 +61346,9 @@ ${formatManifestValidationIssues2(validation.issues)}`,
61244
61346
  if (buttonIndex < 0) {
61245
61347
  return autoApproveActive;
61246
61348
  }
61349
+ const approvalEntrySeq = typeof adapterStatus?.approvalEntrySeq === "number" ? adapterStatus.approvalEntrySeq : 0;
61247
61350
  const signature = [
61351
+ approvalEntrySeq,
61248
61352
  typeof modal?.message === "string" ? modal.message.trim() : "",
61249
61353
  buttons.join("|"),
61250
61354
  buttonIndex
@@ -66886,8 +66990,8 @@ Run 'adhdev doctor' for detailed diagnostics.`
66886
66990
  }
66887
66991
  const https = require("https");
66888
66992
  const { exec: exec7 } = require("child_process");
66889
- const { promisify: promisify7 } = require("util");
66890
- const execAsync5 = promisify7(exec7);
66993
+ const { promisify: promisify8 } = require("util");
66994
+ const execAsync5 = promisify8(exec7);
66891
66995
  const metaPath = path30.join(this.upstreamDir, _ProviderLoader.META_FILE);
66892
66996
  let prevEtag = "";
66893
66997
  let prevTimestamp = 0;
@@ -68157,12 +68261,97 @@ ${formatManifestValidationIssues2(validation2.issues)}`);
68157
68261
  init_mesh_host_ownership();
68158
68262
  init_mesh_fast_forward();
68159
68263
  var import_node_child_process4 = require("child_process");
68264
+ var import_node_util4 = require("util");
68265
+ var execFileAsync3 = (0, import_node_util4.promisify)(import_node_child_process4.execFile);
68266
+ var MAX_CHANGED_FILES2 = 500;
68267
+ function topLevel(path39) {
68268
+ const slash = path39.indexOf("/");
68269
+ return slash === -1 ? path39 : path39.slice(0, slash);
68270
+ }
68271
+ async function analyzeMeshRefineNodeChangeArea(args) {
68272
+ const { nodeId, workspace, branch, baseRef, branchRef, diffCwd, submodulePaths } = args;
68273
+ const base = {
68274
+ nodeId,
68275
+ workspace,
68276
+ branch,
68277
+ changedTopLevelPaths: [],
68278
+ changedFiles: [],
68279
+ touchedSubmodulePaths: [],
68280
+ touchesSubmodule: false,
68281
+ aheadCount: 0
68282
+ };
68283
+ try {
68284
+ let mergeBase = baseRef;
68285
+ try {
68286
+ const { stdout } = await execFileAsync3("git", ["merge-base", baseRef, branchRef], { cwd: diffCwd, encoding: "utf8" });
68287
+ const resolved = stdout.trim();
68288
+ if (resolved) mergeBase = resolved;
68289
+ } catch {
68290
+ }
68291
+ const { stdout: countStdout } = await execFileAsync3(
68292
+ "git",
68293
+ ["rev-list", "--count", `${mergeBase}..${branchRef}`],
68294
+ { cwd: diffCwd, encoding: "utf8" }
68295
+ );
68296
+ base.aheadCount = Number.parseInt(countStdout.trim(), 10) || 0;
68297
+ const { stdout: nameStdout } = await execFileAsync3(
68298
+ "git",
68299
+ ["diff", "--name-only", `${mergeBase}..${branchRef}`],
68300
+ { cwd: diffCwd, encoding: "utf8" }
68301
+ );
68302
+ const files = nameStdout.split("\n").map((line) => line.trim()).filter(Boolean).slice(0, MAX_CHANGED_FILES2);
68303
+ base.changedFiles = files;
68304
+ const topSet = /* @__PURE__ */ new Set();
68305
+ const submoduleSet = /* @__PURE__ */ new Set();
68306
+ for (const file2 of files) {
68307
+ const top = topLevel(file2);
68308
+ topSet.add(top);
68309
+ if (submodulePaths.has(file2) || submodulePaths.has(top)) {
68310
+ submoduleSet.add(submodulePaths.has(file2) ? file2 : top);
68311
+ }
68312
+ }
68313
+ base.changedTopLevelPaths = [...topSet].sort();
68314
+ base.touchedSubmodulePaths = [...submoduleSet].sort();
68315
+ base.touchesSubmodule = submoduleSet.size > 0;
68316
+ return base;
68317
+ } catch (e) {
68318
+ base.error = e?.message || String(e);
68319
+ return base;
68320
+ }
68321
+ }
68322
+ function orderMeshRefineBatchNodes(changeAreas) {
68323
+ const areaById = {};
68324
+ for (const area of changeAreas) areaById[area.nodeId] = area;
68325
+ const ranked = [...changeAreas].sort((a, b) => {
68326
+ const aSub = a.touchesSubmodule ? 1 : 0;
68327
+ const bSub = b.touchesSubmodule ? 1 : 0;
68328
+ if (aSub !== bSub) return aSub - bSub;
68329
+ const aBreadth = a.changedTopLevelPaths.length;
68330
+ const bBreadth = b.changedTopLevelPaths.length;
68331
+ if (aBreadth !== bBreadth) return aBreadth - bBreadth;
68332
+ return a.nodeId.localeCompare(b.nodeId);
68333
+ });
68334
+ const rationale = [];
68335
+ const nonSub = ranked.filter((a) => !a.touchesSubmodule).map((a) => a.nodeId);
68336
+ const sub = ranked.filter((a) => a.touchesSubmodule).map((a) => a.nodeId);
68337
+ if (nonSub.length) {
68338
+ rationale.push(`Non-submodule nodes first (no submodule-main advance, conflict-free ordering): ${nonSub.join(", ")}`);
68339
+ }
68340
+ if (sub.length) {
68341
+ rationale.push(`Submodule-touching nodes last, serialized (each merge advances submodule main, forcing rebase of the next): ${sub.join(", ")}`);
68342
+ }
68343
+ for (const area of ranked) {
68344
+ if (area.error) rationale.push(`Node ${area.nodeId}: change-area analysis degraded (${area.error}); placed with neutral priority.`);
68345
+ }
68346
+ return { order: ranked.map((a) => a.nodeId), changeAreas: areaById, rationale };
68347
+ }
68348
+ var import_node_child_process5 = require("child_process");
68160
68349
  var import_node_fs4 = require("fs");
68161
68350
  var import_node_path2 = require("path");
68162
68351
  var PREVIEW_DEPLOY_RECORD = ".adhdev/preview-deploy.json";
68163
68352
  function runGit2(repoRoot, args) {
68164
68353
  try {
68165
- return (0, import_node_child_process4.execFileSync)("git", args, {
68354
+ return (0, import_node_child_process5.execFileSync)("git", args, {
68166
68355
  cwd: repoRoot,
68167
68356
  encoding: "utf8",
68168
68357
  stdio: ["ignore", "pipe", "ignore"],
@@ -68922,7 +69111,7 @@ ${formatManifestValidationIssues2(validation2.issues)}`);
68922
69111
  var import_os3 = require("os");
68923
69112
  var import_path10 = require("path");
68924
69113
  var fs23 = __toESM2(require("fs"));
68925
- var import_node_child_process5 = require("child_process");
69114
+ var import_node_child_process6 = require("child_process");
68926
69115
  var CHANNEL_NPM_TAG = { stable: "latest", preview: "next" };
68927
69116
  var CHANNEL_SERVER_URL = {
68928
69117
  stable: "https://api.adhf.dev",
@@ -70080,7 +70269,7 @@ ${e?.stderr || ""}`
70080
70269
  }
70081
70270
  function readChangedGitlinkPaths(repoRoot, fromRef, toRef) {
70082
70271
  try {
70083
- const output = (0, import_node_child_process5.execFileSync)("git", ["diff", "--raw", "--no-abbrev", fromRef, toRef], {
70272
+ const output = (0, import_node_child_process6.execFileSync)("git", ["diff", "--raw", "--no-abbrev", fromRef, toRef], {
70084
70273
  cwd: repoRoot,
70085
70274
  encoding: "utf8",
70086
70275
  maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES
@@ -70104,7 +70293,7 @@ ${e?.stderr || ""}`
70104
70293
  }
70105
70294
  function readTreeObject(repoRoot, ref, path39) {
70106
70295
  try {
70107
- const output = (0, import_node_child_process5.execFileSync)("git", ["ls-tree", ref, "--", path39], {
70296
+ const output = (0, import_node_child_process6.execFileSync)("git", ["ls-tree", ref, "--", path39], {
70108
70297
  cwd: repoRoot,
70109
70298
  encoding: "utf8",
70110
70299
  maxBuffer: 1024 * 1024
@@ -70138,10 +70327,10 @@ ${e?.stderr || ""}`
70138
70327
  }
70139
70328
  const commandArgs = ["submodule", "update", "--init", "--recursive", "--", ...updatePaths];
70140
70329
  try {
70141
- const { execFile: execFile4 } = await import("child_process");
70142
- const { promisify: promisify7 } = await import("util");
70143
- const execFileAsync3 = promisify7(execFile4);
70144
- const result = await execFileAsync3("git", commandArgs, {
70330
+ const { execFile: execFile5 } = await import("child_process");
70331
+ const { promisify: promisify8 } = await import("util");
70332
+ const execFileAsync4 = promisify8(execFile5);
70333
+ const result = await execFileAsync4("git", commandArgs, {
70145
70334
  cwd: repoRoot,
70146
70335
  encoding: "utf8",
70147
70336
  maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES,
@@ -70184,11 +70373,11 @@ ${e?.stderr || ""}`
70184
70373
  const startedAt = Date.now();
70185
70374
  const entries = [];
70186
70375
  try {
70187
- const { execFile: execFile4 } = await import("child_process");
70188
- const { promisify: promisify7 } = await import("util");
70189
- const execFileAsync3 = promisify7(execFile4);
70376
+ const { execFile: execFile5 } = await import("child_process");
70377
+ const { promisify: promisify8 } = await import("util");
70378
+ const execFileAsync4 = promisify8(execFile5);
70190
70379
  const runGit3 = async (cwd, args) => {
70191
- const { stdout } = await execFileAsync3("git", args, {
70380
+ const { stdout } = await execFileAsync4("git", args, {
70192
70381
  cwd,
70193
70382
  encoding: "utf8",
70194
70383
  timeout: 3e4,
@@ -70203,7 +70392,7 @@ ${e?.stderr || ""}`
70203
70392
  };
70204
70393
  const publishCommitToRemoteMain = async (submodulePath, commit, branch = "main") => {
70205
70394
  const refspec = `${commit}:refs/heads/${branch}`;
70206
- const { stdout, stderr } = await execFileAsync3("git", ["push", "origin", refspec], {
70395
+ const { stdout, stderr } = await execFileAsync4("git", ["push", "origin", refspec], {
70207
70396
  cwd: submodulePath,
70208
70397
  encoding: "utf8",
70209
70398
  timeout: 3e4,
@@ -70388,9 +70577,9 @@ ${e?.stderr || ""}`
70388
70577
  };
70389
70578
  }
70390
70579
  async function runMeshRefineValidationGate(mesh, workspace, opts) {
70391
- const { execFile: execFile4 } = await import("child_process");
70392
- const { promisify: promisify7 } = await import("util");
70393
- const execFileAsync3 = promisify7(execFile4);
70580
+ const { execFile: execFile5 } = await import("child_process");
70581
+ const { promisify: promisify8 } = await import("util");
70582
+ const execFileAsync4 = promisify8(execFile5);
70394
70583
  const selection = resolveMeshRefineValidationPlan(mesh, workspace);
70395
70584
  const summary = {
70396
70585
  status: "skipped",
@@ -70484,7 +70673,7 @@ ${e?.stderr || ""}`
70484
70673
  const cwd = candidate.cwd ? (0, import_path10.resolve)(workspace, candidate.cwd) : workspace;
70485
70674
  const timeout = candidate.timeoutMs || REFINE_VALIDATION_TIMEOUT_MS;
70486
70675
  try {
70487
- const result = await execFileAsync3(candidate.command, candidate.args, {
70676
+ const result = await execFileAsync4(candidate.command, candidate.args, {
70488
70677
  cwd,
70489
70678
  encoding: "utf8",
70490
70679
  timeout,
@@ -70526,7 +70715,7 @@ ${e?.stderr || ""}`
70526
70715
  return summary;
70527
70716
  }
70528
70717
  try {
70529
- const result = await execFileAsync3(candidate.command, candidate.args, {
70718
+ const result = await execFileAsync4(candidate.command, candidate.args, {
70530
70719
  cwd,
70531
70720
  encoding: "utf8",
70532
70721
  timeout,
@@ -71094,19 +71283,19 @@ ${e?.stderr || ""}`
71094
71283
  const isSubmoduleGuard = /working trees containing submodules cannot be moved or removed/i.test(message);
71095
71284
  const submoduleForceBlocked = isSubmoduleGuard && !forceFallbackConvergence.allow;
71096
71285
  if (isSubmoduleGuard && forceFallbackConvergence.allow) {
71097
- const { execFile: execFile4 } = await import("child_process");
71098
- const { promisify: promisify7 } = await import("util");
71099
- const execFileAsync3 = promisify7(execFile4);
71286
+ const { execFile: execFile5 } = await import("child_process");
71287
+ const { promisify: promisify8 } = await import("util");
71288
+ const execFileAsync4 = promisify8(execFile5);
71100
71289
  const GIT_TIMEOUT_CLEANUP = 3e4;
71101
71290
  const GIT_MAX_BUFFER_CLEANUP = 4 * 1024 * 1024;
71102
71291
  try {
71103
- await execFileAsync3("git", ["-C", workspace, "submodule", "deinit", "--all", "-f"], {
71292
+ await execFileAsync4("git", ["-C", workspace, "submodule", "deinit", "--all", "-f"], {
71104
71293
  encoding: "utf8",
71105
71294
  timeout: GIT_TIMEOUT_CLEANUP,
71106
71295
  maxBuffer: GIT_MAX_BUFFER_CLEANUP,
71107
71296
  windowsHide: true
71108
71297
  });
71109
- await execFileAsync3("git", ["worktree", "remove", "--force", workspace], {
71298
+ await execFileAsync4("git", ["worktree", "remove", "--force", workspace], {
71110
71299
  cwd: repoRoot,
71111
71300
  encoding: "utf8",
71112
71301
  timeout: GIT_TIMEOUT_CLEANUP,
@@ -71125,7 +71314,7 @@ ${e?.stderr || ""}`
71125
71314
  } catch (deinitError) {
71126
71315
  try {
71127
71316
  fs23.rmSync(workspace, { recursive: true, force: true });
71128
- await execFileAsync3("git", ["worktree", "prune"], {
71317
+ await execFileAsync4("git", ["worktree", "prune"], {
71129
71318
  cwd: repoRoot,
71130
71319
  encoding: "utf8",
71131
71320
  timeout: GIT_TIMEOUT_CLEANUP,
@@ -71169,11 +71358,11 @@ ${e?.stderr || ""}`
71169
71358
  if (refinedConvergence === "merged_pushed" || refinedConvergence === "merged_to_main") {
71170
71359
  return { allow: true, status: refinedConvergence, source: "node_refine_state" };
71171
71360
  }
71172
- const { execFile: execFile4 } = await import("child_process");
71173
- const { promisify: promisify7 } = await import("util");
71174
- const execFileAsync3 = promisify7(execFile4);
71361
+ const { execFile: execFile5 } = await import("child_process");
71362
+ const { promisify: promisify8 } = await import("util");
71363
+ const execFileAsync4 = promisify8(execFile5);
71175
71364
  const runGit3 = async (gitArgs, cwd) => {
71176
- const { stdout } = await execFileAsync3("git", gitArgs, {
71365
+ const { stdout } = await execFileAsync4("git", gitArgs, {
71177
71366
  cwd,
71178
71367
  encoding: "utf8",
71179
71368
  timeout: 3e4,
@@ -71645,30 +71834,30 @@ ${e?.stderr || ""}`
71645
71834
  const sourceNode = node.clonedFromNodeId ? mesh?.nodes.find((n) => n.id === node.clonedFromNodeId || n.nodeId === node.clonedFromNodeId) : mesh?.nodes.find((n) => !n.isLocalWorktree);
71646
71835
  const repoRoot = sourceNode?.repoRoot || sourceNode?.workspace;
71647
71836
  if (!repoRoot) return { success: false, error: "Source node repoRoot not found", refineStages };
71648
- const { execFile: execFile4 } = await import("child_process");
71649
- const { promisify: promisify7 } = await import("util");
71650
- const execFileAsync3 = promisify7(execFile4);
71837
+ const { execFile: execFile5 } = await import("child_process");
71838
+ const { promisify: promisify8 } = await import("util");
71839
+ const execFileAsync4 = promisify8(execFile5);
71651
71840
  const resolveStarted = Date.now();
71652
- const { stdout: branchStdout } = await execFileAsync3("git", ["branch", "--show-current"], { cwd: node.workspace, encoding: "utf8" });
71841
+ const { stdout: branchStdout } = await execFileAsync4("git", ["branch", "--show-current"], { cwd: node.workspace, encoding: "utf8" });
71653
71842
  const branch = branchStdout.trim();
71654
71843
  if (!branch) return { success: false, error: "Could not determine branch of the worktree node", refineStages };
71655
- const { stdout: baseBranchStdout } = await execFileAsync3("git", ["branch", "--show-current"], { cwd: repoRoot, encoding: "utf8" });
71844
+ const { stdout: baseBranchStdout } = await execFileAsync4("git", ["branch", "--show-current"], { cwd: repoRoot, encoding: "utf8" });
71656
71845
  const baseBranch = baseBranchStdout.trim();
71657
71846
  let fetchWarning;
71658
71847
  try {
71659
- await execFileAsync3("git", ["fetch", "origin", baseBranch], { cwd: repoRoot, encoding: "utf8" });
71848
+ await execFileAsync4("git", ["fetch", "origin", baseBranch], { cwd: repoRoot, encoding: "utf8" });
71660
71849
  } catch (e) {
71661
71850
  fetchWarning = `git fetch origin ${baseBranch} failed (proceeding with local HEAD): ${e?.message}`;
71662
71851
  }
71663
71852
  let baseHeadRaw;
71664
71853
  try {
71665
- const { stdout } = await execFileAsync3("git", ["rev-parse", `origin/${baseBranch}`], { cwd: repoRoot, encoding: "utf8" });
71854
+ const { stdout } = await execFileAsync4("git", ["rev-parse", `origin/${baseBranch}`], { cwd: repoRoot, encoding: "utf8" });
71666
71855
  baseHeadRaw = stdout.trim();
71667
71856
  } catch {
71668
- const { stdout: localHead } = await execFileAsync3("git", ["rev-parse", "HEAD"], { cwd: repoRoot, encoding: "utf8" });
71857
+ const { stdout: localHead } = await execFileAsync4("git", ["rev-parse", "HEAD"], { cwd: repoRoot, encoding: "utf8" });
71669
71858
  baseHeadRaw = localHead.trim();
71670
71859
  }
71671
- const { stdout: branchHeadStdout } = await execFileAsync3("git", ["rev-parse", branch], { cwd: node.workspace, encoding: "utf8" });
71860
+ const { stdout: branchHeadStdout } = await execFileAsync4("git", ["rev-parse", branch], { cwd: node.workspace, encoding: "utf8" });
71672
71861
  const baseHead = baseHeadRaw;
71673
71862
  let branchHead = branchHeadStdout.trim();
71674
71863
  recordMeshRefineStage(refineStages, "resolve_refs", "passed", resolveStarted, { branch, baseBranch, baseHead, branchHead, ...fetchWarning ? { fetchWarning } : {} });
@@ -71756,7 +71945,7 @@ ${tail}` : ""
71756
71945
  let didAutoRebase = false;
71757
71946
  let isBehindBase = false;
71758
71947
  try {
71759
- (0, import_node_child_process5.execFileSync)("git", ["merge-base", "--is-ancestor", branchHead, baseHead], {
71948
+ (0, import_node_child_process6.execFileSync)("git", ["merge-base", "--is-ancestor", branchHead, baseHead], {
71760
71949
  cwd: node.workspace,
71761
71950
  stdio: "ignore"
71762
71951
  });
@@ -71766,11 +71955,11 @@ ${tail}` : ""
71766
71955
  if (isBehindBase) {
71767
71956
  const autoRebaseStarted = Date.now();
71768
71957
  try {
71769
- (0, import_node_child_process5.execFileSync)("git", ["rebase", baseHead], {
71958
+ (0, import_node_child_process6.execFileSync)("git", ["rebase", baseHead], {
71770
71959
  cwd: node.workspace,
71771
71960
  stdio: ["ignore", "pipe", "pipe"]
71772
71961
  });
71773
- const { stdout: rebasedHeadStdout } = await execFileAsync3("git", ["rev-parse", "HEAD"], { cwd: node.workspace, encoding: "utf8" });
71962
+ const { stdout: rebasedHeadStdout } = await execFileAsync4("git", ["rev-parse", "HEAD"], { cwd: node.workspace, encoding: "utf8" });
71774
71963
  branchHead = rebasedHeadStdout.trim();
71775
71964
  const rebasedPatchEquivalence = await runMeshRefinePatchEquivalenceGate(repoRoot, baseHead, branchHead);
71776
71965
  recordMeshRefineStage(refineStages, "patch_equivalence_after_auto_rebase", rebasedPatchEquivalence.status, autoRebaseStarted, {
@@ -71807,7 +71996,7 @@ ${tail}` : ""
71807
71996
  }
71808
71997
  } catch (rebaseErr) {
71809
71998
  try {
71810
- (0, import_node_child_process5.execFileSync)("git", ["rebase", "--abort"], { cwd: node.workspace, stdio: "ignore" });
71999
+ (0, import_node_child_process6.execFileSync)("git", ["rebase", "--abort"], { cwd: node.workspace, stdio: "ignore" });
71811
72000
  } catch {
71812
72001
  }
71813
72002
  recordMeshRefineStage(refineStages, "patch_equivalence_after_auto_rebase", "failed", autoRebaseStarted, {
@@ -72014,7 +72203,7 @@ ${tail}` : ""
72014
72203
  let mergeResult;
72015
72204
  const mergeStarted = Date.now();
72016
72205
  try {
72017
- const result = await execFileAsync3("git", ["merge", "--no-ff", branch, "-m", `Auto-merge branch '${branch}' via Refinery`], { cwd: repoRoot, encoding: "utf8" });
72206
+ const result = await execFileAsync4("git", ["merge", "--no-ff", branch, "-m", `Auto-merge branch '${branch}' via Refinery`], { cwd: repoRoot, encoding: "utf8" });
72018
72207
  mergeResult = {
72019
72208
  stdout: truncateValidationOutput(result.stdout),
72020
72209
  stderr: truncateValidationOutput(result.stderr),
@@ -72148,7 +72337,7 @@ ${tail}` : ""
72148
72337
  if (!requireApprovalForPush) {
72149
72338
  const pushStarted = Date.now();
72150
72339
  try {
72151
- await execFileAsync3("git", ["push", "origin", baseBranch], { cwd: repoRoot, encoding: "utf8" });
72340
+ await execFileAsync4("git", ["push", "origin", baseBranch], { cwd: repoRoot, encoding: "utf8" });
72152
72341
  pushResult = { pushed: true, remote: "origin", branch: baseBranch, durationMs: Date.now() - pushStarted };
72153
72342
  recordMeshRefineStage(refineStages, "push", "passed", pushStarted, pushResult);
72154
72343
  finalBranchConvergenceState.status = "merged_pushed";
@@ -72189,6 +72378,223 @@ ${tail}` : ""
72189
72378
  return { success: false, error: e.message, refineStages };
72190
72379
  }
72191
72380
  }
72381
+ /**
72382
+ * Batch refinery: converge multiple sibling worktree nodes onto the base branch
72383
+ * in one sequential pipeline, absorbing the rebase + patch-equivalence churn that
72384
+ * arises when several siblings touch the same submodule.
72385
+ *
72386
+ * Reuses executeMeshRefineNodeSynchronously per node — every node goes through the
72387
+ * exact same validation / patch-equivalence / submodule-reachability / merge / cleanup
72388
+ * gates, including its built-in auto-rebase onto fresh origin/<base>. Because each
72389
+ * node fetches origin/<base> at the start of its own refine, a node merged earlier in
72390
+ * the batch advances the base, and the next node's refine auto-rebases onto it before
72391
+ * re-running patch-equivalence. No force-push, no reset — conflicting nodes are
72392
+ * isolated as blocked_review while the rest of the batch proceeds.
72393
+ */
72394
+ async batchRefineMeshNodes(meshId, requestedNodeIds, args) {
72395
+ const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
72396
+ const mesh = meshRecord?.mesh;
72397
+ if (!mesh) return { success: false, error: `Mesh '${meshId}' not found` };
72398
+ const allNodes = Array.isArray(mesh.nodes) ? mesh.nodes : [];
72399
+ const isConvergeable = (n) => n?.isLocalWorktree && typeof n.workspace === "string" && n.workspace;
72400
+ let targetNodes;
72401
+ if (Array.isArray(requestedNodeIds) && requestedNodeIds.length > 0) {
72402
+ targetNodes = [];
72403
+ const missing = [];
72404
+ const nonWorktree = [];
72405
+ for (const nodeId of requestedNodeIds) {
72406
+ const node = allNodes.find((n) => n.id === nodeId || n.nodeId === nodeId);
72407
+ if (!node) {
72408
+ missing.push(nodeId);
72409
+ continue;
72410
+ }
72411
+ if (!isConvergeable(node)) {
72412
+ nonWorktree.push(nodeId);
72413
+ continue;
72414
+ }
72415
+ targetNodes.push(node);
72416
+ }
72417
+ if (missing.length || nonWorktree.length) {
72418
+ return {
72419
+ success: false,
72420
+ error: "One or more requested nodes are not convergeable local worktree nodes.",
72421
+ ...missing.length ? { missingNodeIds: missing } : {},
72422
+ ...nonWorktree.length ? { nonWorktreeNodeIds: nonWorktree } : {}
72423
+ };
72424
+ }
72425
+ } else {
72426
+ targetNodes = allNodes.filter(isConvergeable);
72427
+ }
72428
+ if (targetNodes.length === 0) {
72429
+ return { success: true, batch: true, dryRun: args?.dryRun !== false, nodeCount: 0, order: [], results: [], note: "No convergeable local worktree nodes found." };
72430
+ }
72431
+ const { execFile: execFile5 } = await import("child_process");
72432
+ const { promisify: promisify8 } = await import("util");
72433
+ const execFileAsync4 = promisify8(execFile5);
72434
+ const resolveRepoRootFor = (node) => {
72435
+ const sourceNode = node.clonedFromNodeId ? allNodes.find((n) => n.id === node.clonedFromNodeId || n.nodeId === node.clonedFromNodeId) : allNodes.find((n) => !n.isLocalWorktree);
72436
+ return sourceNode?.repoRoot || sourceNode?.workspace;
72437
+ };
72438
+ const repoRootBaseRef = /* @__PURE__ */ new Map();
72439
+ const submodulePathsByRepoRoot = /* @__PURE__ */ new Map();
72440
+ const resolveBaseRef = async (repoRoot) => {
72441
+ const cached2 = repoRootBaseRef.get(repoRoot);
72442
+ if (cached2) return cached2;
72443
+ let baseBranch = "main";
72444
+ try {
72445
+ const { stdout } = await execFileAsync4("git", ["branch", "--show-current"], { cwd: repoRoot, encoding: "utf8" });
72446
+ if (stdout.trim()) baseBranch = stdout.trim();
72447
+ } catch {
72448
+ }
72449
+ let baseRef = "HEAD";
72450
+ try {
72451
+ await execFileAsync4("git", ["fetch", "origin", baseBranch], { cwd: repoRoot, encoding: "utf8" });
72452
+ } catch {
72453
+ }
72454
+ try {
72455
+ const { stdout } = await execFileAsync4("git", ["rev-parse", `origin/${baseBranch}`], { cwd: repoRoot, encoding: "utf8" });
72456
+ baseRef = stdout.trim();
72457
+ } catch {
72458
+ try {
72459
+ const { stdout } = await execFileAsync4("git", ["rev-parse", "HEAD"], { cwd: repoRoot, encoding: "utf8" });
72460
+ baseRef = stdout.trim();
72461
+ } catch {
72462
+ }
72463
+ }
72464
+ repoRootBaseRef.set(repoRoot, baseRef);
72465
+ return baseRef;
72466
+ };
72467
+ const changeAreas = [];
72468
+ for (const node of targetNodes) {
72469
+ const repoRoot = resolveRepoRootFor(node);
72470
+ let branch = typeof node.worktreeBranch === "string" ? node.worktreeBranch : "";
72471
+ try {
72472
+ const { stdout } = await execFileAsync4("git", ["branch", "--show-current"], { cwd: node.workspace, encoding: "utf8" });
72473
+ if (stdout.trim()) branch = stdout.trim();
72474
+ } catch {
72475
+ }
72476
+ if (!repoRoot || !branch) {
72477
+ changeAreas.push({
72478
+ nodeId: node.id,
72479
+ workspace: node.workspace,
72480
+ branch: branch || "(unknown)",
72481
+ changedTopLevelPaths: [],
72482
+ changedFiles: [],
72483
+ touchedSubmodulePaths: [],
72484
+ touchesSubmodule: false,
72485
+ aheadCount: 0,
72486
+ error: !repoRoot ? "source repoRoot not found" : "branch not resolved"
72487
+ });
72488
+ continue;
72489
+ }
72490
+ if (!submodulePathsByRepoRoot.has(repoRoot)) {
72491
+ let subPaths = /* @__PURE__ */ new Set();
72492
+ try {
72493
+ const { stdout } = await execFileAsync4("git", ["config", "--file", ".gitmodules", "--get-regexp", "path"], { cwd: repoRoot, encoding: "utf8" });
72494
+ for (const line of stdout.split("\n")) {
72495
+ const trimmed = line.trim();
72496
+ const spaceIdx = trimmed.indexOf(" ");
72497
+ if (spaceIdx === -1) continue;
72498
+ const value = trimmed.slice(spaceIdx + 1).trim();
72499
+ if (value) subPaths.add(value);
72500
+ }
72501
+ } catch {
72502
+ subPaths = /* @__PURE__ */ new Set();
72503
+ }
72504
+ submodulePathsByRepoRoot.set(repoRoot, subPaths);
72505
+ }
72506
+ const baseRef = await resolveBaseRef(repoRoot);
72507
+ let branchRef = branch;
72508
+ try {
72509
+ const { stdout } = await execFileAsync4("git", ["rev-parse", branch], { cwd: node.workspace, encoding: "utf8" });
72510
+ branchRef = stdout.trim() || branch;
72511
+ } catch {
72512
+ }
72513
+ changeAreas.push(await analyzeMeshRefineNodeChangeArea({
72514
+ nodeId: node.id,
72515
+ workspace: node.workspace,
72516
+ branch,
72517
+ baseRef,
72518
+ branchRef,
72519
+ diffCwd: node.workspace,
72520
+ submodulePaths: submodulePathsByRepoRoot.get(repoRoot)
72521
+ }));
72522
+ }
72523
+ const ordering = orderMeshRefineBatchNodes(changeAreas);
72524
+ const orderedNodes = ordering.order.map((nodeId) => targetNodes.find((n) => n.id === nodeId || n.nodeId === nodeId)).filter((n) => !!n);
72525
+ const dryRun = args?.dryRun !== false && args?.execute !== true;
72526
+ if (dryRun) {
72527
+ return {
72528
+ success: true,
72529
+ batch: true,
72530
+ dryRun: true,
72531
+ nodeCount: orderedNodes.length,
72532
+ order: ordering.order,
72533
+ orderingRationale: ordering.rationale,
72534
+ changeAreas: ordering.changeAreas,
72535
+ plan: orderedNodes.map((node) => ({
72536
+ nodeId: node.id,
72537
+ workspace: node.workspace,
72538
+ validationPlan: buildMeshRefineValidationPlan(mesh, node.workspace),
72539
+ mergeWillRun: false
72540
+ })),
72541
+ note: "Dry-run: no validation, rebase, or merge was executed. Re-run with execute=true to converge nodes in this order."
72542
+ };
72543
+ }
72544
+ const results = [];
72545
+ for (const node of orderedNodes) {
72546
+ let result;
72547
+ try {
72548
+ result = await this.executeMeshRefineNodeSynchronously(meshId, node.id, args);
72549
+ } catch (e) {
72550
+ result = { success: false, error: e?.message || String(e) };
72551
+ }
72552
+ const code = typeof result.code === "string" ? result.code : "";
72553
+ let convergence;
72554
+ if (code === "already_merged" && result.alreadyMergedViaOtherPath) {
72555
+ convergence = "skipped_patch_equivalent";
72556
+ } else if (result.success === true) {
72557
+ convergence = "merged_to_main";
72558
+ } else if (code === "merge_failed") {
72559
+ convergence = "not_mergeable";
72560
+ } else {
72561
+ convergence = "blocked_review";
72562
+ }
72563
+ const fbcs = result.finalBranchConvergenceState && typeof result.finalBranchConvergenceState === "object" ? result.finalBranchConvergenceState : void 0;
72564
+ const stage = Array.isArray(result.refineStages) ? result.refineStages.filter((s) => s.status === "failed").map((s) => s.stage).filter(Boolean).pop() : void 0;
72565
+ results.push({
72566
+ nodeId: node.id,
72567
+ workspace: node.workspace,
72568
+ convergence,
72569
+ ...code ? { code } : {},
72570
+ ...typeof result.blockedReason === "string" ? { reason: result.blockedReason } : {},
72571
+ ...stage ? { stage } : {},
72572
+ ...typeof result.error === "string" ? { error: result.error } : {},
72573
+ ...fbcs ? { finalBranchConvergenceState: fbcs } : {}
72574
+ });
72575
+ }
72576
+ const summary = {
72577
+ merged: results.filter((r) => r.convergence === "merged_to_main").length,
72578
+ skipped: results.filter((r) => r.convergence === "skipped_patch_equivalent").length,
72579
+ blocked: results.filter((r) => r.convergence === "blocked_review").length,
72580
+ notMergeable: results.filter((r) => r.convergence === "not_mergeable").length
72581
+ };
72582
+ const allConverged = summary.blocked === 0 && summary.notMergeable === 0;
72583
+ return {
72584
+ success: true,
72585
+ batch: true,
72586
+ dryRun: false,
72587
+ nodeCount: orderedNodes.length,
72588
+ order: ordering.order,
72589
+ orderingRationale: ordering.rationale,
72590
+ summary,
72591
+ allConverged,
72592
+ results,
72593
+ ...allConverged ? {} : {
72594
+ nextStep: "Resolve blocked_review / not_mergeable nodes manually (see per-node code/stage/error), then re-run mesh_refine_batch for the remaining nodes."
72595
+ }
72596
+ };
72597
+ }
72192
72598
  async finishMeshRefineJob(handle, args) {
72193
72599
  const key = this.buildRefineJobKey(handle.meshId, handle.targetNodeId);
72194
72600
  let result;
@@ -73732,6 +74138,12 @@ ${tail}` : ""
73732
74138
  if (!meshId || !nodeId) return { success: false, error: "meshId and nodeId required" };
73733
74139
  return this.startMeshRefineJob(meshId, nodeId, args);
73734
74140
  }
74141
+ case "batch_refine_mesh_nodes": {
74142
+ const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
74143
+ if (!meshId) return { success: false, error: "meshId required" };
74144
+ const requestedNodeIds = Array.isArray(args?.nodeIds) ? args.nodeIds.filter((v) => typeof v === "string" && v.trim().length > 0).map((v) => v.trim()) : void 0;
74145
+ return this.batchRefineMeshNodes(meshId, requestedNodeIds, args);
74146
+ }
73735
74147
  case "remove_mesh_node": {
73736
74148
  const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
73737
74149
  const nodeId = typeof args?.nodeId === "string" ? args.nodeId.trim() : "";