@adhdev/daemon-standalone 0.9.77-rc.5 → 0.9.77-rc.50

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.
@@ -25090,10 +25090,12 @@ __export(dist_exports, {
25090
25090
  IdeProviderInstance: () => IdeProviderInstance,
25091
25091
  InMemoryGitSnapshotStore: () => InMemoryGitSnapshotStore,
25092
25092
  LOG: () => LOG,
25093
+ MAX_LEDGER_SLICE_LIMIT: () => MAX_LEDGER_SLICE_LIMIT,
25093
25094
  MIN_GIT_WORKSPACE_POLL_INTERVAL_MS: () => MIN_GIT_WORKSPACE_POLL_INTERVAL_MS,
25094
25095
  MIN_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS: () => MIN_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS,
25095
25096
  MIN_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS: () => MIN_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS,
25096
25097
  NodePtyTransportFactory: () => NodePtyTransportFactory,
25098
+ P2pRelayFailureError: () => P2pRelayFailureError,
25097
25099
  ProviderCliAdapter: () => ProviderCliAdapter,
25098
25100
  ProviderInstanceManager: () => ProviderInstanceManager,
25099
25101
  ProviderLoader: () => ProviderLoader,
@@ -25104,12 +25106,16 @@ __export(dist_exports, {
25104
25106
  addNode: () => addNode,
25105
25107
  appendLedgerEntry: () => appendLedgerEntry,
25106
25108
  appendRecentActivity: () => appendRecentActivity,
25109
+ appendRemoteLedgerEntries: () => appendRemoteLedgerEntries,
25107
25110
  buildAssistantChatMessage: () => buildAssistantChatMessage,
25108
25111
  buildChatMessage: () => buildChatMessage,
25109
25112
  buildChatMessageSignature: () => buildChatMessageSignature,
25110
25113
  buildChatTailDeliverySignature: () => buildChatTailDeliverySignature,
25111
25114
  buildCoordinatorSystemPrompt: () => buildCoordinatorSystemPrompt,
25112
25115
  buildMachineInfo: () => buildMachineInfo,
25116
+ buildMeshLedgerReconciliationEvidence: () => buildMeshLedgerReconciliationEvidence,
25117
+ buildMeshLedgerReplicaEvidence: () => buildMeshLedgerReplicaEvidence,
25118
+ buildP2pRelayFailurePayload: () => buildP2pRelayFailurePayload,
25113
25119
  buildPinnedGlobalInstallCommand: () => buildPinnedGlobalInstallCommand,
25114
25120
  buildRuntimeSystemChatMessage: () => buildRuntimeSystemChatMessage,
25115
25121
  buildSessionEntries: () => buildSessionEntries,
@@ -25120,9 +25126,11 @@ __export(dist_exports, {
25120
25126
  buildThoughtChatMessage: () => buildThoughtChatMessage,
25121
25127
  buildToolChatMessage: () => buildToolChatMessage,
25122
25128
  buildUserChatMessage: () => buildUserChatMessage,
25129
+ cancelTask: () => cancelTask,
25123
25130
  claimNextTask: () => claimNextTask,
25124
25131
  classifyChatMessageVisibility: () => classifyChatMessageVisibility,
25125
25132
  classifyHotChatSessionsForSubscriptionFlush: () => classifyHotChatSessionsForSubscriptionFlush,
25133
+ classifyP2pRelayFailure: () => classifyP2pRelayFailure,
25126
25134
  clearDebugTrace: () => clearDebugTrace,
25127
25135
  compareGitSnapshots: () => compareGitSnapshots,
25128
25136
  configureDebugTraceStore: () => configureDebugTraceStore,
@@ -25163,6 +25171,7 @@ __export(dist_exports, {
25163
25171
  getLogLevel: () => getLogLevel,
25164
25172
  getMesh: () => getMesh,
25165
25173
  getMeshByRepo: () => getMeshByRepo,
25174
+ getMeshQueueStats: () => getMeshQueueStats,
25166
25175
  getNpmExecOptions: () => getNpmExecOptions,
25167
25176
  getQueue: () => getQueue,
25168
25177
  getRecentActivity: () => getRecentActivity,
@@ -25189,6 +25198,7 @@ __export(dist_exports, {
25189
25198
  isInternalChatMessage: () => isInternalChatMessage,
25190
25199
  isManagedStatusWaiting: () => isManagedStatusWaiting,
25191
25200
  isManagedStatusWorking: () => isManagedStatusWorking,
25201
+ isP2pRelayTransportFailure: () => isP2pRelayTransportFailure,
25192
25202
  isPathInside: () => isPathInside,
25193
25203
  isSessionHostLiveRuntime: () => isSessionHostLiveRuntime,
25194
25204
  isSessionHostRecoverySnapshot: () => isSessionHostRecoverySnapshot,
@@ -25227,10 +25237,12 @@ __export(dist_exports, {
25227
25237
  probeCdpPort: () => probeCdpPort,
25228
25238
  readChatHistory: () => readChatHistory,
25229
25239
  readLedgerEntries: () => readLedgerEntries,
25240
+ readLedgerSlice: () => readLedgerSlice,
25230
25241
  recordDebugTrace: () => recordDebugTrace,
25231
25242
  registerExtensionProviders: () => registerExtensionProviders,
25232
25243
  removeNode: () => removeNode,
25233
25244
  removeWorktree: () => removeWorktree,
25245
+ requeueTask: () => requeueTask,
25234
25246
  resetConfig: () => resetConfig,
25235
25247
  resetDebugRuntimeConfig: () => resetDebugRuntimeConfig,
25236
25248
  resetState: () => resetState,
@@ -25301,13 +25313,25 @@ async function createWorktree(opts) {
25301
25313
  branch
25302
25314
  };
25303
25315
  }
25304
- async function removeWorktree(repoRoot, worktreePath) {
25316
+ async function removeWorktree(repoRoot, worktreePath, opts = {}) {
25305
25317
  if (!(0, import_fs3.existsSync)(worktreePath)) {
25306
25318
  await pruneWorktrees(repoRoot);
25307
25319
  return { success: true, removedPath: worktreePath };
25308
25320
  }
25321
+ if (opts.requireClean) {
25322
+ const { stdout } = await execFileAsync2("git", ["status", "--porcelain"], {
25323
+ cwd: worktreePath,
25324
+ encoding: "utf8",
25325
+ timeout: GIT_TIMEOUT_MS,
25326
+ maxBuffer: GIT_MAX_BUFFER,
25327
+ windowsHide: true
25328
+ });
25329
+ if (stdout.trim()) {
25330
+ throw new Error(`Refusing to remove dirty worktree: ${worktreePath}`);
25331
+ }
25332
+ }
25309
25333
  try {
25310
- await execFileAsync2("git", ["worktree", "remove", worktreePath, "--force"], {
25334
+ await execFileAsync2("git", ["worktree", "remove", worktreePath], {
25311
25335
  cwd: repoRoot,
25312
25336
  encoding: "utf8",
25313
25337
  timeout: GIT_TIMEOUT_MS,
@@ -25316,7 +25340,33 @@ async function removeWorktree(repoRoot, worktreePath) {
25316
25340
  });
25317
25341
  } catch (error48) {
25318
25342
  const stderr = typeof error48.stderr === "string" ? error48.stderr : "";
25319
- throw new Error(`git worktree remove failed: ${stderr.trim() || error48.message}`);
25343
+ const stdout = typeof error48.stdout === "string" ? error48.stdout : "";
25344
+ const detail = `${stderr}
25345
+ ${stdout}
25346
+ ${error48.message || ""}`;
25347
+ if (opts.allowSubmoduleForceFallback && SUBMODULE_WORKTREE_REMOVE_RE.test(detail)) {
25348
+ try {
25349
+ await execFileAsync2("git", ["worktree", "remove", "--force", worktreePath], {
25350
+ cwd: repoRoot,
25351
+ encoding: "utf8",
25352
+ timeout: GIT_TIMEOUT_MS,
25353
+ maxBuffer: GIT_MAX_BUFFER,
25354
+ windowsHide: true
25355
+ });
25356
+ } catch (forceError) {
25357
+ const forceStderr = typeof forceError.stderr === "string" ? forceError.stderr : "";
25358
+ const forceStdout = typeof forceError.stdout === "string" ? forceError.stdout : "";
25359
+ throw new Error(`git worktree remove --force fallback failed: ${forceStderr.trim() || forceStdout.trim() || forceError.message}`);
25360
+ }
25361
+ return {
25362
+ success: true,
25363
+ removedPath: worktreePath,
25364
+ fallback: "git_worktree_remove_force_submodule",
25365
+ forced: true,
25366
+ reason: "working_trees_containing_submodules"
25367
+ };
25368
+ }
25369
+ throw new Error(`git worktree remove failed: ${stderr.trim() || stdout.trim() || error48.message}`);
25320
25370
  }
25321
25371
  return { success: true, removedPath: worktreePath };
25322
25372
  }
@@ -25458,7 +25508,8 @@ function ensureMachineId(config2) {
25458
25508
  };
25459
25509
  }
25460
25510
  function getConfigDir() {
25461
- const dir = (0, import_path.join)((0, import_os2.homedir)(), ".adhdev");
25511
+ const override = process.env.ADHDEV_CONFIG_DIR;
25512
+ const dir = override && override.trim() ? override.trim() : (0, import_path.join)((0, import_os2.homedir)(), ".adhdev");
25462
25513
  if (!(0, import_fs4.existsSync)(dir)) {
25463
25514
  (0, import_fs4.mkdirSync)(dir, { recursive: true });
25464
25515
  }
@@ -25671,6 +25722,7 @@ function addNode(meshId, opts) {
25671
25722
  workspace: opts.workspace.trim(),
25672
25723
  repoRoot: opts.repoRoot,
25673
25724
  daemonId: opts.daemonId,
25725
+ machineId: opts.machineId,
25674
25726
  userOverrides: opts.userOverrides || {},
25675
25727
  policy: opts.policy || {},
25676
25728
  isLocalWorktree: opts.isLocalWorktree,
@@ -25782,7 +25834,8 @@ function buildRulesSection(coordinatorCliType) {
25782
25834
  - **Minimize coordinator context.** The coordinator's job is routing, not implementing. Do not read source files, run commands, or analyze code directly \u2014 delegate all of that to node agents. Your context should stay lean.
25783
25835
  - **Delegate analysis too.** If you need to understand a bug or explore the codebase, send that investigation as a task to the queue or a node. Do not do it yourself.
25784
25836
  - **Respect explicit provider requests.** If the user names an agent/provider, pass the matching provider type to \`mesh_launch_session\`: Hermes \u2192 \`hermes-cli\`, Claude Code/Claude \u2192 \`claude-cli\`, Codex \u2192 \`codex-cli\`, Gemini \u2192 \`gemini-cli\`. Never substitute \`claude-cli\` just because the coordinator itself is Claude Code.
25785
- - **Front-load the task message.** When calling \`mesh_enqueue_task\` or \`mesh_send_task\`, include everything the agent needs: what files to touch, what the problem is, what the fix should look like. The agent won't ask follow-up questions.
25837
+ - **Front-load new task messages.** When calling \`mesh_enqueue_task\` or \`mesh_send_task\` for a new task, include everything the agent needs: what files to touch, what the problem is, what the fix should look like. The agent won't ask follow-up questions.
25838
+ - **Avoid context-wasting restarts.** For follow-up, retry, commit/push, preview, or cleanup work on the same issue, prefer the existing idle session and send only the delta from its last verified state. Start a fresh chat/session only for genuinely independent work, explicit provider/user request, unsafe transcript contamination, or required branch/worktree isolation.
25786
25839
  - **Don't inspect code.** Treat delegated agent summaries as self-reports, not verification. Verify side effects via \`mesh_git_status\` (including related repo freshness when configured), not by reading source files.
25787
25840
  - **Don't over-parallelize.** Start with 1-2 concurrent tasks. Scale up if they succeed. Never launch a duplicate session or second worker solely because \`mesh_read_chat\` has no final assistant message while the delegated session is still showing tool/terminal activity.
25788
25841
  - **Handle failures with context.** If a task fails, check \`mesh_task_history\` first to see if this task was attempted before and how it failed. Read the chat to understand why, then decide: retry on the same node, reassign to a different node, or escalate to the user.
@@ -25791,8 +25844,14 @@ function buildRulesSection(coordinatorCliType) {
25791
25844
  - **Respect node capabilities.** Don't send build tasks to read-only nodes. Don't push from nodes that aren't allowed to.
25792
25845
  - **Never fabricate tool results.** Always call the actual tool; never pretend you did.
25793
25846
  - **Clean up worktree nodes.** After a worktree task completes and its changes are merged or checkpointed, call \`mesh_remove_node\` to free resources.
25847
+ - **Do not strand completed branches.** A checkpointed or clean feature/worktree branch is not done by itself. Merge/refine it to the mesh default branch, or explicitly report one of \`pushed_feature_branch_needs_merge\`, \`blocked_review\`, \`cleanup_candidate\`, or \`not_mergeable\` with the next action.
25794
25848
  - **Name worktree branches meaningfully.** Use descriptive names like \`feat/auth-refactor\` or \`fix/build-123\`.${coordinatorNote}`;
25795
25849
  }
25850
+ function isIntentionalCleanupStopEntry(entry) {
25851
+ if (entry.kind !== "session_stopped" && entry.kind !== "task_failed" && entry.kind !== "task_stalled") return false;
25852
+ const payload = entry.payload && typeof entry.payload === "object" && !Array.isArray(entry.payload) ? entry.payload : {};
25853
+ return payload.intentional === true && (payload.reason === "operator_cleanup" || payload.intentionalStopReason === "operator_cleanup" || payload.source === "mesh_cleanup_sessions" || payload.source === "mesh_remove_node");
25854
+ }
25796
25855
  function getLedgerDir() {
25797
25856
  const dir = (0, import_path3.join)(getConfigDir(), LEDGER_DIR_NAME);
25798
25857
  if (!(0, import_fs6.existsSync)(dir)) {
@@ -25808,6 +25867,37 @@ function getRotatedPath(meshId, index) {
25808
25867
  const safe = meshId.replace(/[^a-zA-Z0-9_-]/g, "_");
25809
25868
  return (0, import_path3.join)(getLedgerDir(), `${safe}.${index}.jsonl`);
25810
25869
  }
25870
+ function buildTaskCompletionEvidence(opts) {
25871
+ const providerSessionId = opts.providerSessionId?.trim() || void 0;
25872
+ const providerType = opts.providerType?.trim() || void 0;
25873
+ return {
25874
+ source: "agent_status_event",
25875
+ event: opts.event,
25876
+ nodeId: opts.nodeId,
25877
+ sessionId: opts.sessionId,
25878
+ providerType,
25879
+ completedAt: opts.completedAt || (/* @__PURE__ */ new Date()).toISOString(),
25880
+ transcriptHandle: {
25881
+ kind: providerSessionId ? "provider_session" : "runtime_session",
25882
+ sessionId: opts.sessionId,
25883
+ providerSessionId,
25884
+ finalSummaryAvailable: typeof opts.finalSummary === "string" && opts.finalSummary.trim().length > 0
25885
+ },
25886
+ git: {
25887
+ status: "deferred",
25888
+ reason: "ordinary_completion_git_status_not_checked"
25889
+ },
25890
+ validation: {
25891
+ status: "deferred",
25892
+ commandsRun: [],
25893
+ reason: "ordinary_completion_validation_not_run"
25894
+ },
25895
+ checkpoint: {
25896
+ attempted: false,
25897
+ reason: "not_attempted_for_ordinary_completion"
25898
+ }
25899
+ };
25900
+ }
25811
25901
  function appendLedgerEntry(meshId, partial2) {
25812
25902
  const entry = {
25813
25903
  id: (0, import_crypto4.randomUUID)(),
@@ -25834,15 +25924,49 @@ function appendLedgerEntry(meshId, partial2) {
25834
25924
  throw new Error(`Failed to append to ledger for mesh ${meshId}: ${e.message}`);
25835
25925
  }
25836
25926
  }
25927
+ function clampLedgerSliceLimit(limit) {
25928
+ if (typeof limit !== "number" || !Number.isFinite(limit)) return DEFAULT_LEDGER_SLICE_LIMIT;
25929
+ return Math.max(1, Math.min(MAX_LEDGER_SLICE_LIMIT, Math.floor(limit)));
25930
+ }
25931
+ function isValidRemoteLedgerEntry(meshId, value) {
25932
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
25933
+ const entry = value;
25934
+ if (typeof entry.id !== "string" || !entry.id.trim()) return false;
25935
+ if (entry.meshId !== meshId) return false;
25936
+ if (typeof entry.timestamp !== "string" || Number.isNaN(new Date(entry.timestamp).getTime())) return false;
25937
+ if (typeof entry.kind !== "string" || !entry.kind.trim()) return false;
25938
+ if (!entry.payload || typeof entry.payload !== "object" || Array.isArray(entry.payload)) return false;
25939
+ return true;
25940
+ }
25837
25941
  function appendRemoteLedgerEntries(meshId, entries) {
25838
- if (entries.length === 0) return;
25942
+ if (entries.length === 0) return { accepted: 0, skippedDuplicate: 0, rejectedInvalid: 0, entries: [] };
25839
25943
  const ledgerPath = getLedgerPath(meshId);
25840
25944
  const existing = new Set(readLedgerEntries(meshId).map((e) => e.id));
25841
- const newEntries = entries.filter((e) => !existing.has(e.id));
25842
- if (newEntries.length === 0) return;
25945
+ const validEntries = [];
25946
+ let rejectedInvalid = 0;
25947
+ let skippedDuplicate = 0;
25948
+ for (const entry of entries) {
25949
+ if (!isValidRemoteLedgerEntry(meshId, entry)) {
25950
+ rejectedInvalid++;
25951
+ continue;
25952
+ }
25953
+ if (existing.has(entry.id)) {
25954
+ skippedDuplicate++;
25955
+ continue;
25956
+ }
25957
+ existing.add(entry.id);
25958
+ validEntries.push(entry);
25959
+ }
25960
+ if (validEntries.length === 0) {
25961
+ return { accepted: 0, skippedDuplicate, rejectedInvalid, entries: [] };
25962
+ }
25843
25963
  try {
25844
- const lines = newEntries.map((e) => JSON.stringify(e)).join("\n") + "\n";
25964
+ const lines = validEntries.map((e) => JSON.stringify(e)).join("\n") + "\n";
25845
25965
  (0, import_fs6.appendFileSync)(ledgerPath, lines, { encoding: "utf-8", mode: 384 });
25966
+ for (const entry of validEntries) {
25967
+ meshLedgerEvents.emit("append", meshId, entry);
25968
+ }
25969
+ return { accepted: validEntries.length, skippedDuplicate, rejectedInvalid, entries: validEntries };
25846
25970
  } catch (e) {
25847
25971
  throw new Error(`Failed to append remote ledger entries for mesh ${meshId}: ${e.message}`);
25848
25972
  }
@@ -25881,6 +26005,34 @@ function readLedgerEntries(meshId, opts) {
25881
26005
  }
25882
26006
  return entries;
25883
26007
  }
26008
+ function readLedgerSlice(meshId, opts) {
26009
+ const limit = clampLedgerSliceLimit(opts?.limit);
26010
+ let entries = readLedgerEntries(meshId, { since: opts?.since, kind: opts?.kind });
26011
+ const afterId = typeof opts?.afterId === "string" && opts.afterId.trim() ? opts.afterId.trim() : null;
26012
+ if (afterId) {
26013
+ const index = entries.findIndex((entry) => entry.id === afterId);
26014
+ entries = index >= 0 ? entries.slice(index + 1) : entries;
26015
+ }
26016
+ const bounded = entries.slice(0, limit);
26017
+ return {
26018
+ protocol: "adhdev.mesh.ledger.slice.v1",
26019
+ meshId,
26020
+ entries: bounded,
26021
+ cursor: {
26022
+ afterId,
26023
+ nextAfterId: bounded.length ? bounded[bounded.length - 1].id : afterId,
26024
+ limit,
26025
+ hasMore: entries.length > bounded.length
26026
+ },
26027
+ summary: getLedgerSummary(meshId),
26028
+ sourceOfTruth: {
26029
+ kind: "local_jsonl",
26030
+ path: getLedgerPath(meshId),
26031
+ bounded: true,
26032
+ maxLimit: MAX_LEDGER_SLICE_LIMIT
26033
+ }
26034
+ };
26035
+ }
25884
26036
  function getLedgerSummary(meshId) {
25885
26037
  const entries = readLedgerEntries(meshId);
25886
26038
  const now = Date.now();
@@ -25906,15 +26058,17 @@ function getLedgerSummary(meshId) {
25906
26058
  summary.taskCompleted++;
25907
26059
  break;
25908
26060
  case "task_failed": {
26061
+ if (isIntentionalCleanupStopEntry(entry)) break;
25909
26062
  summary.taskFailed++;
25910
26063
  if (new Date(entry.timestamp).getTime() >= recentFailureCutoff) {
25911
26064
  summary.recentFailures++;
25912
26065
  }
25913
26066
  break;
25914
26067
  }
25915
- case "task_stalled":
25916
- summary.taskStalled++;
26068
+ case "task_stalled": {
26069
+ if (!isIntentionalCleanupStopEntry(entry)) summary.taskStalled++;
25917
26070
  break;
26071
+ }
25918
26072
  case "session_launched":
25919
26073
  summary.sessionLaunched++;
25920
26074
  break;
@@ -25953,6 +26107,7 @@ function getSessionRecoveryContext(meshId, opts) {
25953
26107
  if (new Date(e.timestamp).getTime() < recentWindow) break;
25954
26108
  if (opts.nodeId && e.nodeId !== opts.nodeId) continue;
25955
26109
  if (e.kind === "task_failed") {
26110
+ if (isIntentionalCleanupStopEntry(e)) continue;
25956
26111
  consecutiveNodeFailures++;
25957
26112
  } else if (e.kind === "task_completed" || e.kind === "task_dispatched") {
25958
26113
  break;
@@ -26029,6 +26184,7 @@ function enqueueTask(meshId, message, opts) {
26029
26184
  message,
26030
26185
  status: "pending",
26031
26186
  targetNodeId: opts?.targetNodeId,
26187
+ targetSessionId: opts?.targetSessionId,
26032
26188
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
26033
26189
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
26034
26190
  };
@@ -26046,9 +26202,14 @@ function getQueue(meshId, opts) {
26046
26202
  }
26047
26203
  function claimNextTask(meshId, nodeId, sessionId) {
26048
26204
  const queue = readQueue(meshId);
26049
- let targetIdx = queue.findIndex((q) => q.status === "pending" && q.targetNodeId === nodeId);
26205
+ const hasActiveAssignment = queue.some((q) => q.status === "assigned" && (q.assignedSessionId === sessionId || q.assignedNodeId === nodeId));
26206
+ if (hasActiveAssignment) return null;
26207
+ let targetIdx = queue.findIndex((q) => q.status === "pending" && q.targetSessionId === sessionId);
26208
+ if (targetIdx === -1) {
26209
+ targetIdx = queue.findIndex((q) => q.status === "pending" && q.targetNodeId === nodeId && !q.targetSessionId);
26210
+ }
26050
26211
  if (targetIdx === -1) {
26051
- targetIdx = queue.findIndex((q) => q.status === "pending" && !q.targetNodeId);
26212
+ targetIdx = queue.findIndex((q) => q.status === "pending" && !q.targetNodeId && !q.targetSessionId);
26052
26213
  }
26053
26214
  if (targetIdx === -1) return null;
26054
26215
  const entry = queue[targetIdx];
@@ -26068,6 +26229,53 @@ function updateTaskStatus(meshId, taskId, status) {
26068
26229
  writeQueue(meshId, queue);
26069
26230
  return queue[idx];
26070
26231
  }
26232
+ function recordTaskAutoLaunch(meshId, taskId, autoLaunch) {
26233
+ const queue = readQueue(meshId);
26234
+ const idx = queue.findIndex((q) => q.id === taskId);
26235
+ if (idx === -1) return null;
26236
+ const now = (/* @__PURE__ */ new Date()).toISOString();
26237
+ queue[idx].autoLaunch = {
26238
+ ...autoLaunch,
26239
+ updatedAt: now
26240
+ };
26241
+ queue[idx].updatedAt = now;
26242
+ writeQueue(meshId, queue);
26243
+ return queue[idx];
26244
+ }
26245
+ function cancelTask(meshId, taskId, opts) {
26246
+ const queue = readQueue(meshId);
26247
+ const idx = queue.findIndex((q) => q.id === taskId);
26248
+ if (idx === -1) return null;
26249
+ const now = (/* @__PURE__ */ new Date()).toISOString();
26250
+ queue[idx].status = "cancelled";
26251
+ queue[idx].updatedAt = now;
26252
+ queue[idx].cancelledAt = now;
26253
+ if (opts?.reason) queue[idx].cancelReason = opts.reason;
26254
+ writeQueue(meshId, queue);
26255
+ return queue[idx];
26256
+ }
26257
+ function requeueTask(meshId, taskId, opts) {
26258
+ const queue = readQueue(meshId);
26259
+ const idx = queue.findIndex((q) => q.id === taskId);
26260
+ if (idx === -1) return null;
26261
+ const entry = queue[idx];
26262
+ const now = (/* @__PURE__ */ new Date()).toISOString();
26263
+ entry.status = "pending";
26264
+ delete entry.assignedNodeId;
26265
+ delete entry.assignedSessionId;
26266
+ delete entry.cancelledAt;
26267
+ delete entry.cancelReason;
26268
+ if (opts?.clearTargetNode) delete entry.targetNodeId;
26269
+ if (typeof opts?.targetNodeId === "string") entry.targetNodeId = opts.targetNodeId;
26270
+ if (opts?.clearTargetSession !== false) delete entry.targetSessionId;
26271
+ if (typeof opts?.targetSessionId === "string") entry.targetSessionId = opts.targetSessionId;
26272
+ entry.updatedAt = now;
26273
+ entry.requeuedAt = now;
26274
+ entry.requeueCount = (entry.requeueCount || 0) + 1;
26275
+ if (opts?.reason) entry.requeueReason = opts.reason;
26276
+ writeQueue(meshId, queue);
26277
+ return entry;
26278
+ }
26071
26279
  function updateSessionTaskStatus(meshId, sessionId, status) {
26072
26280
  const queue = readQueue(meshId);
26073
26281
  for (let i = queue.length - 1; i >= 0; i--) {
@@ -26082,13 +26290,160 @@ function updateSessionTaskStatus(meshId, sessionId, status) {
26082
26290
  }
26083
26291
  function getMeshQueueStats(meshId) {
26084
26292
  const queue = readQueue(meshId);
26293
+ const pending = queue.filter((q) => q.status === "pending").length;
26294
+ const assigned = queue.filter((q) => q.status === "assigned").length;
26295
+ const completed = queue.filter((q) => q.status === "completed").length;
26296
+ const failed = queue.filter((q) => q.status === "failed").length;
26297
+ const cancelled = queue.filter((q) => q.status === "cancelled").length;
26085
26298
  return {
26086
- pending: queue.filter((q) => q.status === "pending").length,
26087
- assigned: queue.filter((q) => q.status === "assigned").length,
26088
- completed: queue.filter((q) => q.status === "completed").length,
26089
- failed: queue.filter((q) => q.status === "failed").length
26299
+ total: queue.length,
26300
+ active: pending + assigned,
26301
+ historical: completed + failed + cancelled,
26302
+ pending,
26303
+ assigned,
26304
+ completed,
26305
+ failed,
26306
+ cancelled,
26307
+ activeCounts: {
26308
+ pending,
26309
+ assigned
26310
+ },
26311
+ historicalCounts: {
26312
+ completed,
26313
+ failed,
26314
+ cancelled
26315
+ },
26316
+ activeAssignments: queue.filter((q) => q.status === "assigned").map((q) => ({
26317
+ id: q.id,
26318
+ nodeId: q.assignedNodeId,
26319
+ sessionId: q.assignedSessionId,
26320
+ message: q.message
26321
+ }))
26090
26322
  };
26091
26323
  }
26324
+ function parseVersion(raw) {
26325
+ const match = raw.match(/v?(\d+\.\d+(?:\.\d+)?(?:-[a-zA-Z0-9.]+)?)/);
26326
+ return match ? match[1] : raw.split("\n")[0].slice(0, 100);
26327
+ }
26328
+ function shellQuote(value) {
26329
+ if (/^[a-zA-Z0-9_./:@%+=,-]+$/.test(value)) return value;
26330
+ return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
26331
+ }
26332
+ function expandHome(value) {
26333
+ const trimmed = value.trim();
26334
+ if (!trimmed.startsWith("~")) return trimmed;
26335
+ return path8.join(os22.homedir(), trimmed.slice(1));
26336
+ }
26337
+ function isExplicitCommandPath(command) {
26338
+ const trimmed = command.trim();
26339
+ return path8.isAbsolute(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~");
26340
+ }
26341
+ function resolveCommandPath(command) {
26342
+ const trimmed = command.trim();
26343
+ if (!trimmed) return null;
26344
+ if (isExplicitCommandPath(trimmed)) {
26345
+ const expanded = expandHome(trimmed);
26346
+ const candidate = path8.isAbsolute(expanded) ? expanded : path8.resolve(expanded);
26347
+ return (0, import_fs8.existsSync)(candidate) ? candidate : null;
26348
+ }
26349
+ return null;
26350
+ }
26351
+ function execAsync(cmd, timeoutMs = 5e3) {
26352
+ return new Promise((resolve162) => {
26353
+ const child = (0, import_child_process2.exec)(cmd, {
26354
+ encoding: "utf-8",
26355
+ timeout: timeoutMs,
26356
+ ...process.platform === "win32" ? { windowsHide: true } : {}
26357
+ }, (err, stdout) => {
26358
+ if (err || !stdout?.trim()) {
26359
+ resolve162(null);
26360
+ } else {
26361
+ resolve162(stdout.trim());
26362
+ }
26363
+ });
26364
+ child.on("error", () => resolve162(null));
26365
+ });
26366
+ }
26367
+ async function detectCLIs(providerLoader, options) {
26368
+ const platform10 = os22.platform();
26369
+ const whichCmd = platform10 === "win32" ? "where" : "which";
26370
+ const includeVersion = options?.includeVersion !== false;
26371
+ const cliList = providerLoader ? providerLoader.getCliDetectionList() : [];
26372
+ const results = await Promise.all(
26373
+ cliList.map(async (cli) => {
26374
+ try {
26375
+ const explicitPath = resolveCommandPath(cli.command);
26376
+ const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(cli.command)}`);
26377
+ if (!pathResult) return { ...cli, installed: false };
26378
+ const firstPath = explicitPath || pathResult.split("\n")[0];
26379
+ let version2;
26380
+ if (includeVersion) {
26381
+ const versionCommands = [
26382
+ `"${firstPath}" --version`,
26383
+ `"${firstPath}" -V`,
26384
+ `"${firstPath}" -v`,
26385
+ cli.versionCommand
26386
+ ].filter((v) => !!v);
26387
+ try {
26388
+ for (const versionCommand of versionCommands) {
26389
+ const versionResult = await execAsync(versionCommand, 3e3);
26390
+ if (versionResult) {
26391
+ version2 = parseVersion(versionResult);
26392
+ break;
26393
+ }
26394
+ }
26395
+ } catch {
26396
+ }
26397
+ }
26398
+ return { ...cli, installed: true, version: version2, path: firstPath };
26399
+ } catch {
26400
+ return { ...cli, installed: false };
26401
+ }
26402
+ })
26403
+ );
26404
+ return results;
26405
+ }
26406
+ async function detectCLI(cliId, providerLoader, options) {
26407
+ const resolvedId = providerLoader ? providerLoader.resolveAlias(cliId) : cliId;
26408
+ if (providerLoader) {
26409
+ const cliList = providerLoader.getCliDetectionList();
26410
+ const target = cliList.find((c) => c.id === resolvedId);
26411
+ if (target) {
26412
+ const platform10 = os22.platform();
26413
+ const whichCmd = platform10 === "win32" ? "where" : "which";
26414
+ try {
26415
+ const explicitPath = resolveCommandPath(target.command);
26416
+ const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(target.command)}`);
26417
+ if (!pathResult) return null;
26418
+ const firstPath = explicitPath || pathResult.split("\n")[0];
26419
+ let version2;
26420
+ if (options?.includeVersion !== false) {
26421
+ const versionCommands = [
26422
+ `"${firstPath}" --version`,
26423
+ `"${firstPath}" -V`,
26424
+ `"${firstPath}" -v`,
26425
+ target.versionCommand
26426
+ ].filter((v) => !!v);
26427
+ try {
26428
+ for (const versionCommand of versionCommands) {
26429
+ const versionResult = await execAsync(versionCommand, 3e3);
26430
+ if (versionResult) {
26431
+ version2 = parseVersion(versionResult);
26432
+ break;
26433
+ }
26434
+ }
26435
+ } catch {
26436
+ }
26437
+ }
26438
+ return { ...target, installed: true, version: version2, path: firstPath };
26439
+ } catch {
26440
+ return null;
26441
+ }
26442
+ }
26443
+ }
26444
+ const all = await detectCLIs(providerLoader, options);
26445
+ return all.find((c) => c.id === resolvedId && c.installed) || null;
26446
+ }
26092
26447
  function setLogLevel(level) {
26093
26448
  currentLevel = level;
26094
26449
  daemonLog("Logger", `Log level set to: ${level}`, "info");
@@ -26103,13 +26458,13 @@ function getDaemonLogDir() {
26103
26458
  return LOG_DIR;
26104
26459
  }
26105
26460
  function getCurrentDaemonLogPath(date5 = /* @__PURE__ */ new Date()) {
26106
- return path8.join(LOG_DIR, `daemon-${date5.toISOString().slice(0, 10)}.log`);
26461
+ return path9.join(LOG_DIR, `daemon-${date5.toISOString().slice(0, 10)}.log`);
26107
26462
  }
26108
26463
  function checkDateRotation() {
26109
26464
  const today = getDateStr();
26110
26465
  if (today !== currentDate) {
26111
26466
  currentDate = today;
26112
- currentLogFile = path8.join(LOG_DIR, `daemon-${currentDate}.log`);
26467
+ currentLogFile = path9.join(LOG_DIR, `daemon-${currentDate}.log`);
26113
26468
  cleanOldLogs();
26114
26469
  }
26115
26470
  }
@@ -26123,7 +26478,7 @@ function cleanOldLogs() {
26123
26478
  const dateMatch = file2.match(/daemon-(\d{4}-\d{2}-\d{2})/);
26124
26479
  if (dateMatch && dateMatch[1] < cutoffStr) {
26125
26480
  try {
26126
- fs2.unlinkSync(path8.join(LOG_DIR, file2));
26481
+ fs2.unlinkSync(path9.join(LOG_DIR, file2));
26127
26482
  } catch {
26128
26483
  }
26129
26484
  }
@@ -26245,6 +26600,9 @@ function drainPendingMeshCoordinatorEvents() {
26245
26600
  function readNonEmptyString(value) {
26246
26601
  return typeof value === "string" && value.trim() ? value.trim() : "";
26247
26602
  }
26603
+ function resolveEventSessionId(event, fallback) {
26604
+ return readNonEmptyString(event.targetSessionId) || readNonEmptyString(event.sessionId) || readNonEmptyString(event.instanceId) || readNonEmptyString(fallback);
26605
+ }
26248
26606
  function isMeshCoordinatorEvent(eventName) {
26249
26607
  return typeof eventName === "string" && MESH_COORDINATOR_EVENTS.has(eventName);
26250
26608
  }
@@ -26256,38 +26614,323 @@ function formatCompletionMetadata(event) {
26256
26614
  ].filter(Boolean);
26257
26615
  return parts.length > 0 ? ` (${parts.join("; ")})` : "";
26258
26616
  }
26617
+ function getMeshWithCache(components, meshId) {
26618
+ const localMesh = getMesh(meshId);
26619
+ if (localMesh) return localMesh;
26620
+ return components.router?.getCachedInlineMesh(meshId);
26621
+ }
26622
+ function isIntentionalCleanupStopMetadata(event) {
26623
+ return event.intentional === true || event.intentionalStop === true || event.operatorCleanup === true || event.reason === "operator_cleanup" || event.stopReason === "operator_cleanup" || event.cleanupReason === "operator_cleanup" || event.source === "mesh_cleanup_sessions" || event.source === "mesh_remove_node";
26624
+ }
26625
+ function hasRecentIntentionalCleanupStop(meshId, sessionId, nodeId) {
26626
+ if (!sessionId && !nodeId) return false;
26627
+ const cutoff = Date.now() - INTENTIONAL_CLEANUP_STOP_SUPPRESSION_MS;
26628
+ const entries = readLedgerEntries(meshId);
26629
+ for (let i = entries.length - 1; i >= 0; i--) {
26630
+ const entry = entries[i];
26631
+ const timestamp2 = new Date(entry.timestamp).getTime();
26632
+ if (!Number.isNaN(timestamp2) && timestamp2 < cutoff) break;
26633
+ if (!isIntentionalCleanupStopEntry(entry)) continue;
26634
+ if (sessionId && entry.sessionId === sessionId) return true;
26635
+ if (!sessionId && nodeId && entry.nodeId === nodeId) return true;
26636
+ }
26637
+ return false;
26638
+ }
26639
+ function shouldSuppressIntentionalCleanupStop(args) {
26640
+ if (args.event !== "agent:stopped" && args.event !== "monitor:long_generating") return false;
26641
+ if (isIntentionalCleanupStopMetadata(args.metadataEvent)) return true;
26642
+ return hasRecentIntentionalCleanupStop(args.meshId, args.sessionId, args.nodeId);
26643
+ }
26259
26644
  function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType) {
26260
26645
  const task = claimNextTask(meshId, nodeId, sessionId);
26261
- if (!task) return false;
26646
+ if (!task) {
26647
+ return false;
26648
+ }
26262
26649
  LOG.info("MeshQueue", `Node ${nodeId} (${sessionId}) pulled task ${task.id}`);
26650
+ const mesh = getMeshWithCache(components, meshId);
26651
+ const node = mesh?.nodes.find((n) => n.id === nodeId);
26652
+ if (node?.daemonId && components.dispatchMeshCommand) {
26653
+ const isLocalNode = components.cliManager.adapters.has(sessionId);
26654
+ if (!isLocalNode) {
26655
+ components.dispatchMeshCommand(node.daemonId, "agent_command", {
26656
+ targetSessionId: sessionId,
26657
+ cliType: providerType,
26658
+ action: "send_chat",
26659
+ message: task.message
26660
+ }).catch((e) => {
26661
+ LOG.error("MeshQueue", `Failed to dispatch task via P2P to remote node ${nodeId}: ${e?.message}`);
26662
+ updateTaskStatus(meshId, task.id, "failed");
26663
+ });
26664
+ return true;
26665
+ }
26666
+ }
26263
26667
  components.cliManager.handleCliCommand("agent_command", {
26264
26668
  targetSessionId: sessionId,
26265
26669
  cliType: providerType,
26266
26670
  action: "send_chat",
26267
- input: task.message
26671
+ message: task.message
26268
26672
  }).catch((e) => {
26269
- LOG.error("MeshQueue", `Failed to dispatch task to node ${nodeId}: ${e?.message}`);
26673
+ LOG.error("MeshQueue", `Failed to dispatch task locally to node ${nodeId}: ${e?.message}`);
26674
+ updateTaskStatus(meshId, task.id, "failed");
26270
26675
  });
26271
26676
  return true;
26272
26677
  }
26273
- function triggerMeshQueue(components, meshId) {
26274
- const mesh = getMesh(meshId);
26678
+ function normalizeProviderPriority(policy) {
26679
+ const raw = policy && typeof policy === "object" && !Array.isArray(policy) ? policy.providerPriority : void 0;
26680
+ if (!Array.isArray(raw)) return [];
26681
+ const seen = /* @__PURE__ */ new Set();
26682
+ return raw.map((type2) => typeof type2 === "string" ? type2.trim() : "").filter(Boolean).filter((type2) => {
26683
+ if (seen.has(type2)) return false;
26684
+ seen.add(type2);
26685
+ return true;
26686
+ });
26687
+ }
26688
+ function isTerminalSessionStatus(status) {
26689
+ return ["stopped", "failed", "terminated", "exited", "closed"].includes(status);
26690
+ }
26691
+ function isIdleSessionState(state) {
26692
+ const status = readNonEmptyString(state?.status).toLowerCase();
26693
+ if (isTerminalSessionStatus(status)) return false;
26694
+ return status === "idle" || state?.activeChat?.status === "waiting_input";
26695
+ }
26696
+ function isDirtyNode(node) {
26697
+ return node?.health === "dirty" || node?.git?.dirty === true;
26698
+ }
26699
+ function isLaunchableNode(node) {
26700
+ if (!node || node.status === "disabled" || node.status === "removed") return false;
26701
+ const health = readNonEmptyString(node.health).toLowerCase();
26702
+ if (!health) return true;
26703
+ return health === "online" || health === "unknown";
26704
+ }
26705
+ function localAutoLaunchSkipReason(node) {
26706
+ const daemonId = readNonEmptyString(node?.daemonId);
26707
+ const machineId = readNonEmptyString(node?.machineId);
26708
+ const appConfig = loadConfig();
26709
+ const localMachineId = readNonEmptyString(appConfig.machineId) || readNonEmptyString(appConfig.registeredMachineId);
26710
+ const cloudDaemonId = localMachineId ? `daemon_${localMachineId}` : "";
26711
+ const standaloneDaemonId = localMachineId ? `standalone_${localMachineId}` : "";
26712
+ const daemonMatchesLocal = !daemonId || daemonId === cloudDaemonId || daemonId === standaloneDaemonId;
26713
+ const machineMatchesLocal = !machineId || localMachineId && machineId === localMachineId;
26714
+ if (node?.isLocalWorktree === true) {
26715
+ return daemonMatchesLocal && machineMatchesLocal ? null : "remote_auto_launch_unsupported";
26716
+ }
26717
+ if (daemonId || machineId) {
26718
+ return daemonMatchesLocal && machineMatchesLocal ? null : "remote_auto_launch_unsupported";
26719
+ }
26720
+ return null;
26721
+ }
26722
+ function activeAssignedCount(meshId) {
26723
+ return getQueue(meshId, { status: ["assigned"] }).length;
26724
+ }
26725
+ function nodeHasActiveAssignment(meshId, nodeId) {
26726
+ return getQueue(meshId, { status: ["assigned"] }).some((task) => task.assignedNodeId === nodeId);
26727
+ }
26728
+ function liveSessionCountForNode(components, meshId, nodeId) {
26729
+ return components.instanceManager.getByCategory("cli").filter((inst) => {
26730
+ const state = inst.getState();
26731
+ const settings = state.settings || {};
26732
+ if (readNonEmptyString(settings.meshNodeFor) !== meshId) return false;
26733
+ const instNodeId = readNonEmptyString(settings.meshNodeId) || readNonEmptyString(settings.nodeId);
26734
+ if (instNodeId !== nodeId) return false;
26735
+ const status = readNonEmptyString(state.status).toLowerCase();
26736
+ return !isTerminalSessionStatus(status);
26737
+ }).length;
26738
+ }
26739
+ function recordAutoLaunchEvent(meshId, args) {
26740
+ try {
26741
+ appendLedgerEntry(meshId, {
26742
+ kind: "session_auto_launch",
26743
+ nodeId: args.nodeId,
26744
+ sessionId: args.sessionId,
26745
+ providerType: args.providerType,
26746
+ payload: {
26747
+ phase: args.phase,
26748
+ taskId: args.taskId,
26749
+ reason: args.reason,
26750
+ error: args.error
26751
+ }
26752
+ });
26753
+ } catch (e) {
26754
+ LOG.warn("MeshQueue", `Failed to record auto-launch ledger event: ${e?.message || e}`);
26755
+ }
26756
+ }
26757
+ function markAutoLaunch(meshId, taskId, args) {
26758
+ recordTaskAutoLaunch(meshId, taskId, {
26759
+ status: args.status,
26760
+ reason: args.reason || args.error,
26761
+ nodeId: args.nodeId,
26762
+ providerType: args.providerType,
26763
+ sessionId: args.sessionId
26764
+ });
26765
+ recordAutoLaunchEvent(meshId, {
26766
+ phase: args.status,
26767
+ taskId,
26768
+ nodeId: args.nodeId,
26769
+ providerType: args.providerType,
26770
+ sessionId: args.sessionId,
26771
+ reason: args.reason,
26772
+ error: args.error
26773
+ });
26774
+ }
26775
+ async function resolveUsableProvider(components, nodeId, node) {
26776
+ const providerPriority = normalizeProviderPriority(node?.policy);
26777
+ if (!providerPriority.length) return { reason: "missing_provider_priority" };
26778
+ const providerLoader = components.providerLoader;
26779
+ if (!providerLoader) return { reason: "provider_loader_unavailable" };
26780
+ const failed = [];
26781
+ for (const requestedType of providerPriority) {
26782
+ const normalizedType = typeof providerLoader.resolveAlias === "function" ? providerLoader.resolveAlias(requestedType) : requestedType;
26783
+ if (typeof providerLoader.isMachineProviderEnabled === "function" && !providerLoader.isMachineProviderEnabled(normalizedType)) {
26784
+ failed.push(`${requestedType}: disabled`);
26785
+ continue;
26786
+ }
26787
+ let detected;
26788
+ try {
26789
+ detected = await detectCLI(normalizedType, providerLoader, { includeVersion: false });
26790
+ } catch (e) {
26791
+ failed.push(`${requestedType}: detect failed: ${e?.message || e}`);
26792
+ continue;
26793
+ }
26794
+ if (typeof providerLoader.setCliDetectionResults === "function") {
26795
+ providerLoader.setCliDetectionResults([{
26796
+ id: normalizedType,
26797
+ installed: !!detected,
26798
+ path: detected?.path
26799
+ }], false);
26800
+ }
26801
+ components.onStatusChange?.();
26802
+ if (detected) return { providerType: normalizedType };
26803
+ failed.push(`${requestedType}: not detected`);
26804
+ }
26805
+ return { reason: `provider_priority_unusable: ${failed.join("; ") || nodeId}` };
26806
+ }
26807
+ async function maybeAutoLaunchOneQueueSession(components, meshId, mesh) {
26808
+ const queue = getQueue(meshId);
26809
+ const pending = queue.filter((task) => task.status === "pending");
26810
+ if (!pending.length) return false;
26811
+ const maxParallelTasks = Math.max(1, Math.floor(Number(mesh?.policy?.maxParallelTasks) || 2));
26812
+ for (const task of pending) {
26813
+ if (activeAssignedCount(meshId) >= maxParallelTasks) {
26814
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "max_parallel_tasks_reached" });
26815
+ return false;
26816
+ }
26817
+ if (task.targetSessionId) {
26818
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "target_session_constraint" });
26819
+ continue;
26820
+ }
26821
+ const candidateNodes = Array.isArray(mesh?.nodes) ? mesh.nodes.filter((node) => task.targetNodeId ? node?.id === task.targetNodeId : true) : [];
26822
+ if (!candidateNodes.length) {
26823
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "no_matching_node", nodeId: task.targetNodeId });
26824
+ continue;
26825
+ }
26826
+ for (const node of candidateNodes) {
26827
+ const nodeId = readNonEmptyString(node?.id);
26828
+ if (!nodeId) continue;
26829
+ const launchKey = `${meshId}:${nodeId}`;
26830
+ const cooldownUntil = autoLaunchCooldownUntil.get(launchKey) || 0;
26831
+ if (autoLaunchInProgress.has(launchKey)) {
26832
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "auto_launch_in_progress", nodeId });
26833
+ continue;
26834
+ }
26835
+ if (Date.now() < cooldownUntil) {
26836
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "auto_launch_cooldown", nodeId });
26837
+ continue;
26838
+ }
26839
+ if (isDirtyNode(node)) {
26840
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "dirty_workspace", nodeId });
26841
+ continue;
26842
+ }
26843
+ if (!isLaunchableNode(node)) {
26844
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "node_not_launch_ready", nodeId });
26845
+ continue;
26846
+ }
26847
+ const localSkipReason = localAutoLaunchSkipReason(node);
26848
+ if (localSkipReason) {
26849
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: localSkipReason, nodeId });
26850
+ continue;
26851
+ }
26852
+ if (nodeHasActiveAssignment(meshId, nodeId)) {
26853
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "node_has_active_assignment", nodeId });
26854
+ continue;
26855
+ }
26856
+ const maxConcurrentSessions = Number(node?.policy?.maxConcurrentSessions);
26857
+ if (Number.isFinite(maxConcurrentSessions) && maxConcurrentSessions >= 0 && liveSessionCountForNode(components, meshId, nodeId) >= maxConcurrentSessions) {
26858
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "max_concurrent_sessions_reached", nodeId });
26859
+ continue;
26860
+ }
26861
+ autoLaunchInProgress.add(launchKey);
26862
+ try {
26863
+ const resolved = await resolveUsableProvider(components, nodeId, node);
26864
+ if (!resolved.providerType) {
26865
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: resolved.reason || "provider_unusable", nodeId });
26866
+ continue;
26867
+ }
26868
+ markAutoLaunch(meshId, task.id, { status: "started", nodeId, providerType: resolved.providerType });
26869
+ const launchResult = await components.cliManager.handleCliCommand("launch_cli", {
26870
+ cliType: resolved.providerType,
26871
+ dir: node.workspace,
26872
+ settings: {
26873
+ meshNodeFor: meshId,
26874
+ meshNodeId: nodeId,
26875
+ spawnedSessionVisibility: mesh?.policy?.spawnedSessionVisibility || "hidden",
26876
+ launchedByCoordinator: true,
26877
+ autoLaunchedForQueueTaskId: task.id
26878
+ }
26879
+ });
26880
+ if (!launchResult?.success) {
26881
+ const reason = launchResult?.error || "launch_cli_failed";
26882
+ markAutoLaunch(meshId, task.id, { status: "failed", reason, nodeId, providerType: resolved.providerType });
26883
+ autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
26884
+ return false;
26885
+ }
26886
+ const sessionId = readNonEmptyString(launchResult.sessionId) || readNonEmptyString(launchResult.id) || readNonEmptyString(launchResult.runtimeSessionId);
26887
+ if (!sessionId) {
26888
+ markAutoLaunch(meshId, task.id, { status: "failed", reason: "launch_missing_session_id", nodeId, providerType: resolved.providerType });
26889
+ autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
26890
+ return false;
26891
+ }
26892
+ markAutoLaunch(meshId, task.id, { status: "completed", nodeId, providerType: resolved.providerType, sessionId });
26893
+ tryAssignQueueTask(components, meshId, nodeId, sessionId, resolved.providerType);
26894
+ return true;
26895
+ } catch (e) {
26896
+ markAutoLaunch(meshId, task.id, { status: "failed", error: e?.message || String(e), nodeId });
26897
+ autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
26898
+ return false;
26899
+ } finally {
26900
+ autoLaunchInProgress.delete(launchKey);
26901
+ }
26902
+ }
26903
+ }
26904
+ return false;
26905
+ }
26906
+ async function triggerMeshQueue(components, meshId) {
26907
+ const mesh = getMeshWithCache(components, meshId);
26275
26908
  if (!mesh) return;
26276
26909
  const cliInstances = components.instanceManager.getByCategory("cli");
26277
26910
  for (const inst of cliInstances) {
26278
26911
  const state = inst.getState();
26279
26912
  const settings = state.settings || {};
26280
26913
  const instMeshId = readNonEmptyString(settings.meshNodeFor);
26281
- if (instMeshId !== meshId && !settings.launchedByCoordinator) continue;
26914
+ if (instMeshId !== meshId) continue;
26282
26915
  const nodeId = readNonEmptyString(settings.meshNodeId) || readNonEmptyString(settings.nodeId);
26283
26916
  if (!nodeId) continue;
26284
- if (state.status !== "idle" && state.status !== "stopped" && state.activeChat?.status !== "waiting_input") continue;
26917
+ if (!isIdleSessionState(state)) continue;
26285
26918
  const sessionId = state.instanceId;
26286
26919
  const providerType = state.type || readNonEmptyString(settings.providerType);
26287
26920
  if (providerType) {
26288
26921
  tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType);
26289
26922
  }
26290
26923
  }
26924
+ for (const [key, idle] of remoteIdleSessions.entries()) {
26925
+ const node = mesh.nodes.find((n) => n.id === idle.nodeId);
26926
+ if (node) {
26927
+ const assigned = tryAssignQueueTask(components, meshId, idle.nodeId, idle.sessionId, idle.providerType);
26928
+ if (assigned) {
26929
+ remoteIdleSessions.delete(key);
26930
+ }
26931
+ }
26932
+ }
26933
+ await maybeAutoLaunchOneQueueSession(components, meshId, mesh);
26291
26934
  }
26292
26935
  function buildMeshSystemMessage(args) {
26293
26936
  const metadata = formatCompletionMetadata(args.metadataEvent);
@@ -26334,20 +26977,91 @@ Do NOT retry on this node. Consider reassigning to a different node or asking th
26334
26977
  return "";
26335
26978
  }
26336
26979
  function injectMeshSystemMessage(components, args) {
26980
+ const eventSessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
26981
+ const eventNodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
26982
+ const intentionalCleanupStop = shouldSuppressIntentionalCleanupStop({
26983
+ event: args.event,
26984
+ meshId: args.meshId,
26985
+ metadataEvent: args.metadataEvent,
26986
+ sessionId: eventSessionId || void 0,
26987
+ nodeId: eventNodeId || void 0
26988
+ });
26989
+ if (intentionalCleanupStop) {
26990
+ if (eventSessionId && eventNodeId) {
26991
+ remoteIdleSessions.delete(`${eventNodeId}:${eventSessionId}`);
26992
+ }
26993
+ LOG.info("MeshEvents", `Suppressed ${args.event} for intentionally cleanup-stopped session ${eventSessionId || "(unknown session)"}`);
26994
+ return { success: true, forwarded: 0, suppressed: true, intentionalCleanupStop: true };
26995
+ }
26996
+ let completedTaskForLedger = null;
26337
26997
  if (args.event === "agent:generating_completed") {
26338
- const sessionId = readNonEmptyString(args.metadataEvent.targetSessionId);
26339
- const nodeId = readNonEmptyString(args.metadataEvent.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
26998
+ const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
26999
+ const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
26340
27000
  const providerType = readNonEmptyString(args.metadataEvent.providerType);
26341
27001
  if (sessionId) {
26342
- updateSessionTaskStatus(args.meshId, sessionId, "completed");
27002
+ const completedTask = updateSessionTaskStatus(args.meshId, sessionId, "completed");
27003
+ completedTaskForLedger = completedTask ? { id: completedTask.id } : null;
26343
27004
  if (nodeId && providerType) {
26344
27005
  setTimeout(() => {
26345
27006
  tryAssignQueueTask(components, args.meshId, nodeId, sessionId, providerType);
26346
27007
  }, 500);
26347
27008
  }
26348
27009
  }
27010
+ } else if (args.event === "agent:ready") {
27011
+ const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
27012
+ const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
27013
+ const providerType = readNonEmptyString(args.metadataEvent.providerType);
27014
+ const completedTask = sessionId ? updateSessionTaskStatus(args.meshId, sessionId, "completed") : null;
27015
+ if (completedTask) {
27016
+ completedTaskForLedger = { id: completedTask.id };
27017
+ try {
27018
+ appendLedgerEntry(args.meshId, {
27019
+ kind: "task_completed",
27020
+ nodeId: nodeId || void 0,
27021
+ sessionId,
27022
+ providerType: providerType || void 0,
27023
+ payload: {
27024
+ event: args.event,
27025
+ nodeLabel: args.nodeLabel,
27026
+ taskId: completedTask.id,
27027
+ completedViaReady: true,
27028
+ providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0,
27029
+ finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0,
27030
+ evidence: buildTaskCompletionEvidence({
27031
+ event: "agent:ready",
27032
+ nodeId,
27033
+ sessionId,
27034
+ providerType: providerType || void 0,
27035
+ providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0,
27036
+ finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0
27037
+ })
27038
+ }
27039
+ });
27040
+ } catch (e) {
27041
+ LOG.warn("MeshLedger", `Failed to record task_completed from ready: ${e?.message || e}`);
27042
+ }
27043
+ }
27044
+ if (sessionId && nodeId && providerType) {
27045
+ remoteIdleSessions.set(`${nodeId}:${sessionId}`, { nodeId, sessionId, providerType });
27046
+ setTimeout(() => {
27047
+ const assigned = tryAssignQueueTask(components, args.meshId, nodeId, sessionId, providerType);
27048
+ if (assigned) {
27049
+ remoteIdleSessions.delete(`${nodeId}:${sessionId}`);
27050
+ }
27051
+ }, 500);
27052
+ }
27053
+ } else if (args.event === "agent:generating_started") {
27054
+ const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
27055
+ const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
27056
+ if (sessionId && nodeId) {
27057
+ remoteIdleSessions.delete(`${nodeId}:${sessionId}`);
27058
+ }
26349
27059
  } else if (args.event === "agent:stopped") {
26350
- const sessionId = readNonEmptyString(args.metadataEvent.targetSessionId);
27060
+ const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
27061
+ const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
27062
+ if (sessionId && nodeId) {
27063
+ remoteIdleSessions.delete(`${nodeId}:${sessionId}`);
27064
+ }
26351
27065
  if (sessionId) {
26352
27066
  updateSessionTaskStatus(args.meshId, sessionId, "failed");
26353
27067
  }
@@ -26355,15 +27069,29 @@ function injectMeshSystemMessage(components, args) {
26355
27069
  const ledgerKind = EVENT_TO_LEDGER_KIND[args.event];
26356
27070
  if (ledgerKind) {
26357
27071
  try {
27072
+ const ledgerNodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId) || void 0;
27073
+ const ledgerSessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId) || void 0;
27074
+ const ledgerProviderType = readNonEmptyString(args.metadataEvent.providerType) || void 0;
27075
+ const completionEvidence = ledgerKind === "task_completed" && ledgerNodeId && ledgerSessionId ? buildTaskCompletionEvidence({
27076
+ event: "agent:generating_completed",
27077
+ nodeId: ledgerNodeId,
27078
+ sessionId: ledgerSessionId,
27079
+ providerType: ledgerProviderType,
27080
+ providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0,
27081
+ finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0
27082
+ }) : void 0;
26358
27083
  appendLedgerEntry(args.meshId, {
26359
27084
  kind: ledgerKind,
26360
- nodeId: readNonEmptyString(args.metadataEvent.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId) || void 0,
26361
- sessionId: readNonEmptyString(args.metadataEvent.targetSessionId) || void 0,
26362
- providerType: readNonEmptyString(args.metadataEvent.providerType) || void 0,
27085
+ nodeId: ledgerNodeId,
27086
+ sessionId: ledgerSessionId,
27087
+ providerType: ledgerProviderType,
26363
27088
  payload: {
26364
27089
  event: args.event,
26365
27090
  nodeLabel: args.nodeLabel,
26366
- providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0
27091
+ taskId: completedTaskForLedger?.id || void 0,
27092
+ providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0,
27093
+ finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0,
27094
+ evidence: completionEvidence
26367
27095
  }
26368
27096
  });
26369
27097
  } catch (e) {
@@ -26376,8 +27104,8 @@ function injectMeshSystemMessage(components, args) {
26376
27104
  const mesh = getMesh(args.meshId);
26377
27105
  const maxRetries = mesh?.policy?.maxTaskRetries ?? 1;
26378
27106
  recoveryContext = getSessionRecoveryContext(args.meshId, {
26379
- sessionId: readNonEmptyString(args.metadataEvent.targetSessionId) || void 0,
26380
- nodeId: readNonEmptyString(args.metadataEvent.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId) || void 0,
27107
+ sessionId: resolveEventSessionId(args.metadataEvent, args.sourceInstanceId) || void 0,
27108
+ nodeId: readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId) || void 0,
26381
27109
  maxRetries
26382
27110
  });
26383
27111
  recoveryContext.failedProviderType = readNonEmptyString(args.metadataEvent.providerType) || null;
@@ -26472,12 +27200,21 @@ function handleMeshForwardEvent(components, payload) {
26472
27200
  const nodeLabel = nodeId ? `Node '${nodeId}'` : workspace ? `Agent at ${workspace}` : "Remote agent";
26473
27201
  return injectMeshSystemMessage(components, {
26474
27202
  meshId,
27203
+ nodeId,
26475
27204
  nodeLabel,
26476
27205
  event: eventName,
26477
27206
  metadataEvent: {
26478
- targetSessionId: readNonEmptyString(payload.targetSessionId) || readNonEmptyString(payload.sessionId),
27207
+ targetSessionId: readNonEmptyString(payload.targetSessionId) || readNonEmptyString(payload.sessionId) || readNonEmptyString(payload.instanceId),
26479
27208
  providerType: readNonEmptyString(payload.providerType),
26480
- providerSessionId: readNonEmptyString(payload.providerSessionId)
27209
+ providerSessionId: readNonEmptyString(payload.providerSessionId),
27210
+ finalSummary: readNonEmptyString(payload.finalSummary) || readNonEmptyString(payload.summary),
27211
+ intentional: payload.intentional === true,
27212
+ intentionalStop: payload.intentionalStop === true,
27213
+ operatorCleanup: payload.operatorCleanup === true,
27214
+ reason: readNonEmptyString(payload.reason),
27215
+ stopReason: readNonEmptyString(payload.stopReason),
27216
+ cleanupReason: readNonEmptyString(payload.cleanupReason),
27217
+ source: readNonEmptyString(payload.source)
26481
27218
  }
26482
27219
  });
26483
27220
  }
@@ -26496,15 +27233,17 @@ function setupMeshEventForwarding(components) {
26496
27233
  const meshIdFromRuntime = readNonEmptyString(settings.meshNodeFor);
26497
27234
  const isMeshDelegate = Boolean(meshIdFromRuntime || settings.launchedByCoordinator);
26498
27235
  if (!isMeshDelegate) return;
26499
- const mesh = meshIdFromRuntime ? getMesh(meshIdFromRuntime) : getMeshByRepo(workspace);
27236
+ const mesh = meshIdFromRuntime ? getMeshWithCache(components, meshIdFromRuntime) : getMeshByRepo(workspace);
26500
27237
  const meshId = meshIdFromRuntime || readNonEmptyString(mesh?.id);
26501
27238
  if (!meshId) return;
26502
27239
  const targetNode = mesh?.nodes?.find((n) => n.workspace === workspace);
26503
27240
  const runtimeNodeId = readNonEmptyString(settings.meshNodeId);
27241
+ const resolvedNodeId = targetNode?.id || runtimeNodeId;
26504
27242
  const nodeLabel = targetNode ? `Node '${targetNode.id}'` : runtimeNodeId ? `Node '${runtimeNodeId}'` : `Agent at ${workspace}`;
26505
27243
  injectMeshSystemMessage(components, {
26506
27244
  meshId,
26507
27245
  sourceInstanceId: instanceId,
27246
+ nodeId: resolvedNodeId,
26508
27247
  nodeLabel,
26509
27248
  event: event.event,
26510
27249
  metadataEvent: event
@@ -26742,7 +27481,7 @@ function findBinary(name) {
26742
27481
  const isWin = os9.platform() === "win32";
26743
27482
  try {
26744
27483
  const cmd = isWin ? `where ${trimmed}` : `which ${trimmed}`;
26745
- return (0, import_child_process2.execSync)(cmd, {
27484
+ return (0, import_child_process3.execSync)(cmd, {
26746
27485
  encoding: "utf-8",
26747
27486
  timeout: 5e3,
26748
27487
  stdio: ["pipe", "pipe", "pipe"],
@@ -27117,7 +27856,7 @@ async function validateWorkspace(workspace) {
27117
27856
  cwd: normalizedWorkspace
27118
27857
  });
27119
27858
  }
27120
- await (0, import_promises5.access)(normalizedWorkspace, import_fs8.constants.R_OK);
27859
+ await (0, import_promises5.access)(normalizedWorkspace, import_fs9.constants.R_OK);
27121
27860
  } catch (error48) {
27122
27861
  if (error48 instanceof GitCommandError) throw error48;
27123
27862
  throw new GitCommandError("invalid_args", "Workspace must be an existing directory", {
@@ -28246,7 +28985,7 @@ function addWorkspaceEntry(config2, rawPath, label, options) {
28246
28985
  }
28247
28986
  }
28248
28987
  const v = validateWorkspacePath(abs);
28249
- if (!v.ok) return { error: v.error };
28988
+ if (v.ok !== true) return { error: v.error };
28250
28989
  const list = [...config2.workspaces || []];
28251
28990
  if (list.some((w) => path5.resolve(w.path) === abs)) {
28252
28991
  return { error: "Workspace already in list" };
@@ -28610,25 +29349,143 @@ async function syncMeshes(transport) {
28610
29349
  }
28611
29350
  }
28612
29351
  }
28613
- if (transport.syncMeshLedger) {
28614
- for (const local of localMeshes) {
28615
- try {
28616
- await syncMeshLedger(local.id, transport);
28617
- } catch (e) {
28618
- result.errors.push(`Ledger sync failed for "${local.name}": ${e.message}`);
28619
- }
28620
- }
28621
- }
28622
29352
  return result;
28623
29353
  }
28624
- async function syncMeshLedger(meshId, transport) {
28625
- if (!transport.syncMeshLedger) return;
28626
- const { readLedgerEntries: readLedgerEntries2, appendRemoteLedgerEntries: appendRemoteLedgerEntries2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
28627
- const localEntries = readLedgerEntries2(meshId);
28628
- const res = await transport.syncMeshLedger(meshId, { newEntries: localEntries });
28629
- if (res.missingEntries && res.missingEntries.length > 0) {
28630
- appendRemoteLedgerEntries2(meshId, res.missingEntries);
29354
+ function lastTimestamp(slice) {
29355
+ const entries = Array.isArray(slice?.entries) ? slice.entries : [];
29356
+ return entries.length ? entries[entries.length - 1].timestamp : null;
29357
+ }
29358
+ function buildMeshLedgerReplicaEvidence(args) {
29359
+ const entriesReceived = Array.isArray(args.slice?.entries) ? args.slice.entries.length : 0;
29360
+ return {
29361
+ nodeId: args.nodeId,
29362
+ ...args.daemonId ? { daemonId: args.daemonId } : {},
29363
+ status: args.status ?? (args.importResult && args.importResult.accepted > 0 ? "imported" : "queried"),
29364
+ transport: args.transport,
29365
+ protocol: "adhdev.mesh.ledger.slice.v1",
29366
+ entriesReceived,
29367
+ entriesImported: args.importResult?.accepted ?? 0,
29368
+ skippedDuplicate: args.importResult?.skippedDuplicate ?? 0,
29369
+ rejectedInvalid: args.importResult?.rejectedInvalid ?? 0,
29370
+ hasMore: args.slice?.cursor?.hasMore === true,
29371
+ nextAfterId: args.slice?.cursor?.nextAfterId ?? null,
29372
+ lastTimestamp: lastTimestamp(args.slice),
29373
+ ...args.slice?.summary ? { summary: args.slice.summary } : {},
29374
+ ...args.error ? {
29375
+ error: args.error,
29376
+ noFallbackReason: "Ledger reconciliation is P2P/local-first only; Cloud/D1 ledger sync is intentionally disabled."
29377
+ } : {}
29378
+ };
29379
+ }
29380
+ function buildMeshLedgerReconciliationEvidence(meshId, replicas) {
29381
+ const failedNodes = replicas.filter((replica) => replica.status === "failed").map((replica) => replica.nodeId);
29382
+ const pendingNodes = replicas.filter((replica) => replica.hasMore && replica.status !== "failed").map((replica) => replica.nodeId);
29383
+ return {
29384
+ protocol: "adhdev.mesh.ledger.reconciliation.v1",
29385
+ meshId,
29386
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
29387
+ sourceOfTruth: {
29388
+ kind: "coordinator_local_jsonl",
29389
+ p2pOnly: true,
29390
+ cloudD1LedgerSync: false,
29391
+ notes: "Coordinator reconciles bounded slices from daemon-local JSONL ledgers over P2P DataChannel; Cloud/D1 is not a ledger source of truth."
29392
+ },
29393
+ replicas,
29394
+ totals: {
29395
+ replicas: replicas.length,
29396
+ queried: replicas.filter((replica) => replica.status !== "failed").length,
29397
+ failed: failedNodes.length,
29398
+ entriesReceived: replicas.reduce((sum, replica) => sum + replica.entriesReceived, 0),
29399
+ entriesImported: replicas.reduce((sum, replica) => sum + replica.entriesImported, 0),
29400
+ skippedDuplicate: replicas.reduce((sum, replica) => sum + replica.skippedDuplicate, 0),
29401
+ rejectedInvalid: replicas.reduce((sum, replica) => sum + replica.rejectedInvalid, 0)
29402
+ },
29403
+ convergence: {
29404
+ complete: failedNodes.length === 0 && pendingNodes.length === 0,
29405
+ pendingNodes,
29406
+ failedNodes
29407
+ }
29408
+ };
29409
+ }
29410
+ function messageFromError(error48) {
29411
+ if (error48 instanceof Error) return error48.message;
29412
+ if (typeof error48 === "string") return error48;
29413
+ if (error48 && typeof error48 === "object") {
29414
+ const candidate = error48.error ?? error48.message ?? error48.reason;
29415
+ if (typeof candidate === "string") return candidate;
29416
+ }
29417
+ return String(error48 || "mesh relay command failed");
29418
+ }
29419
+ function classifyP2pRelayFailure(error48, _context = {}) {
29420
+ const message = messageFromError(error48);
29421
+ const lower = message.toLowerCase();
29422
+ const hasP2pSignal = /p2p|datachannel|node-datachannel|webrtc|ice|mesh_relay_command|daemon_mesh_p2p_transport/i.test(message);
29423
+ const hasFailureSignal = /unavailable|missing|failed|failure|timeout|timed out|not connected|closed|disconnected|offline|no route|route unavailable|cannot send|cannot establish/i.test(message);
29424
+ if (/requires targetdaemonid and command|providerpriority|no inference provider|permission denied|read-only|not a member/i.test(message)) {
29425
+ return {
29426
+ code: "mesh_logic_or_provider_failure",
29427
+ reason: "mesh_logic_or_provider_failure",
29428
+ transport: "unknown",
29429
+ recoverable: false,
29430
+ retryRecommended: false,
29431
+ nextAction: NON_P2P_NEXT_ACTION,
29432
+ noFallbackReason: NO_FALLBACK_REASON
29433
+ };
29434
+ }
29435
+ let code = null;
29436
+ let reason = "";
29437
+ if (/timeout|timed out/i.test(message) && (hasP2pSignal || /mesh transport/i.test(message))) {
29438
+ code = "p2p_timeout";
29439
+ reason = "daemon_mesh_p2p_timeout";
29440
+ } else if (/no route|route unavailable/i.test(message)) {
29441
+ code = "p2p_no_route";
29442
+ reason = "daemon_mesh_p2p_no_route";
29443
+ } else if (/offline|not owned|not found|not connected to server/i.test(message) && /daemon|peer|target/i.test(message)) {
29444
+ code = "p2p_daemon_offline";
29445
+ reason = "daemon_mesh_target_offline";
29446
+ } else if (/closed|disconnected/i.test(message) && (hasP2pSignal || /state changed/i.test(message))) {
29447
+ code = "p2p_datachannel_closed";
29448
+ reason = "daemon_mesh_p2p_datachannel_closed";
29449
+ } else if (/not connected|cannot send|cannot establish/i.test(message) && hasP2pSignal) {
29450
+ code = "p2p_not_connected";
29451
+ reason = "daemon_mesh_p2p_not_connected";
29452
+ } else if (hasP2pSignal && hasFailureSignal) {
29453
+ code = "p2p_unavailable";
29454
+ reason = "daemon_mesh_p2p_transport_unavailable";
29455
+ }
29456
+ if (!code) {
29457
+ return {
29458
+ code: "mesh_logic_or_provider_failure",
29459
+ reason: "mesh_logic_or_provider_failure",
29460
+ transport: "unknown",
29461
+ recoverable: false,
29462
+ retryRecommended: false,
29463
+ nextAction: NON_P2P_NEXT_ACTION,
29464
+ noFallbackReason: NO_FALLBACK_REASON
29465
+ };
28631
29466
  }
29467
+ return {
29468
+ code,
29469
+ reason,
29470
+ transport: "p2p",
29471
+ recoverable: true,
29472
+ retryRecommended: true,
29473
+ nextAction: P2P_NEXT_ACTION,
29474
+ noFallbackReason: NO_FALLBACK_REASON
29475
+ };
29476
+ }
29477
+ function isP2pRelayTransportFailure(error48) {
29478
+ return classifyP2pRelayFailure(error48).recoverable === true;
29479
+ }
29480
+ function buildP2pRelayFailurePayload(error48, context = {}) {
29481
+ const classification = classifyP2pRelayFailure(error48, context);
29482
+ return {
29483
+ success: false,
29484
+ ...classification,
29485
+ error: messageFromError(error48),
29486
+ ...context.command ? { command: context.command } : {},
29487
+ ...context.targetDaemonId ? { targetDaemonId: context.targetDaemonId } : {}
29488
+ };
28632
29489
  }
28633
29490
  function isPlainObject22(value) {
28634
29491
  return !!value && typeof value === "object" && !Array.isArray(value);
@@ -28670,11 +29527,11 @@ function normalizeState(raw) {
28670
29527
  }
28671
29528
  function loadState() {
28672
29529
  const statePath = getStatePath();
28673
- if (!(0, import_fs9.existsSync)(statePath)) {
29530
+ if (!(0, import_fs10.existsSync)(statePath)) {
28674
29531
  return { ...DEFAULT_STATE };
28675
29532
  }
28676
29533
  try {
28677
- const raw = (0, import_fs9.readFileSync)(statePath, "utf-8");
29534
+ const raw = (0, import_fs10.readFileSync)(statePath, "utf-8");
28678
29535
  return normalizeState(JSON.parse(raw));
28679
29536
  } catch {
28680
29537
  return { ...DEFAULT_STATE };
@@ -28683,7 +29540,7 @@ function loadState() {
28683
29540
  function saveState(state) {
28684
29541
  const statePath = getStatePath();
28685
29542
  const normalized = normalizeState(state);
28686
- (0, import_fs9.writeFileSync)(statePath, JSON.stringify(normalized, null, 2), { encoding: "utf-8", mode: 384 });
29543
+ (0, import_fs10.writeFileSync)(statePath, JSON.stringify(normalized, null, 2), { encoding: "utf-8", mode: 384 });
28687
29544
  }
28688
29545
  function resetState() {
28689
29546
  saveState({ ...DEFAULT_STATE });
@@ -28704,13 +29561,13 @@ function getMergedDefinitions() {
28704
29561
  function findCliCommand(command) {
28705
29562
  const trimmed = String(command || "").trim();
28706
29563
  if (!trimmed) return null;
28707
- if (path9.isAbsolute(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~")) {
28708
- const candidate = trimmed.startsWith("~") ? path9.join((0, import_os3.homedir)(), trimmed.slice(1)) : trimmed;
28709
- const resolved = path9.isAbsolute(candidate) ? candidate : path9.resolve(candidate);
28710
- return (0, import_fs10.existsSync)(resolved) ? resolved : null;
29564
+ if (path10.isAbsolute(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~")) {
29565
+ const candidate = trimmed.startsWith("~") ? path10.join((0, import_os3.homedir)(), trimmed.slice(1)) : trimmed;
29566
+ const resolved = path10.isAbsolute(candidate) ? candidate : path10.resolve(candidate);
29567
+ return (0, import_fs11.existsSync)(resolved) ? resolved : null;
28711
29568
  }
28712
29569
  try {
28713
- const result = (0, import_child_process4.execSync)(
29570
+ const result = (0, import_child_process5.execSync)(
28714
29571
  (0, import_os3.platform)() === "win32" ? `where ${trimmed}` : `which ${trimmed}`,
28715
29572
  { encoding: "utf-8", timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }
28716
29573
  ).trim();
@@ -28721,7 +29578,7 @@ function findCliCommand(command) {
28721
29578
  }
28722
29579
  function getIdeVersion(cliCommand) {
28723
29580
  try {
28724
- const result = (0, import_child_process4.execSync)(`"${cliCommand}" --version`, {
29581
+ const result = (0, import_child_process5.execSync)(`"${cliCommand}" --version`, {
28725
29582
  encoding: "utf-8",
28726
29583
  timeout: 1e4,
28727
29584
  stdio: ["pipe", "pipe", "pipe"]
@@ -28734,13 +29591,13 @@ function getIdeVersion(cliCommand) {
28734
29591
  function checkPathExists(paths) {
28735
29592
  const home = (0, import_os3.homedir)();
28736
29593
  for (const p of paths) {
28737
- const normalized = p.startsWith("~") ? path9.join(home, p.slice(1)) : p;
29594
+ const normalized = p.startsWith("~") ? path10.join(home, p.slice(1)) : p;
28738
29595
  if (normalized.includes("*")) {
28739
29596
  const username = home.split(/[\\/]/).pop() || "";
28740
29597
  const resolved = normalized.replace("*", username);
28741
- if ((0, import_fs10.existsSync)(resolved)) return resolved;
29598
+ if ((0, import_fs11.existsSync)(resolved)) return resolved;
28742
29599
  } else {
28743
- if ((0, import_fs10.existsSync)(normalized)) return normalized;
29600
+ if ((0, import_fs11.existsSync)(normalized)) return normalized;
28744
29601
  }
28745
29602
  }
28746
29603
  return null;
@@ -28754,7 +29611,7 @@ async function detectIDEs(providerLoader) {
28754
29611
  let resolvedCli = cliPath;
28755
29612
  if (!resolvedCli && appPath && os222 === "darwin") {
28756
29613
  const bundledCli = `${appPath}/Contents/Resources/app/bin/${def.cli}`;
28757
- if ((0, import_fs10.existsSync)(bundledCli)) resolvedCli = bundledCli;
29614
+ if ((0, import_fs11.existsSync)(bundledCli)) resolvedCli = bundledCli;
28758
29615
  }
28759
29616
  if (!resolvedCli && appPath && os222 === "win32") {
28760
29617
  const { dirname: dirname92 } = await import("path");
@@ -28767,7 +29624,7 @@ async function detectIDEs(providerLoader) {
28767
29624
  `${appDir}\\\\resources\\\\app\\\\bin\\\\${def.cli}.cmd`
28768
29625
  ];
28769
29626
  for (const c of candidates) {
28770
- if ((0, import_fs10.existsSync)(c)) {
29627
+ if ((0, import_fs11.existsSync)(c)) {
28771
29628
  resolvedCli = c;
28772
29629
  break;
28773
29630
  }
@@ -28788,129 +29645,6 @@ async function detectIDEs(providerLoader) {
28788
29645
  }
28789
29646
  return results;
28790
29647
  }
28791
- function parseVersion(raw) {
28792
- const match = raw.match(/v?(\d+\.\d+(?:\.\d+)?(?:-[a-zA-Z0-9.]+)?)/);
28793
- return match ? match[1] : raw.split("\n")[0].slice(0, 100);
28794
- }
28795
- function shellQuote(value) {
28796
- if (/^[a-zA-Z0-9_./:@%+=,-]+$/.test(value)) return value;
28797
- return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
28798
- }
28799
- function expandHome(value) {
28800
- const trimmed = value.trim();
28801
- if (!trimmed.startsWith("~")) return trimmed;
28802
- return path10.join(os32.homedir(), trimmed.slice(1));
28803
- }
28804
- function isExplicitCommandPath(command) {
28805
- const trimmed = command.trim();
28806
- return path10.isAbsolute(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~");
28807
- }
28808
- function resolveCommandPath(command) {
28809
- const trimmed = command.trim();
28810
- if (!trimmed) return null;
28811
- if (isExplicitCommandPath(trimmed)) {
28812
- const expanded = expandHome(trimmed);
28813
- const candidate = path10.isAbsolute(expanded) ? expanded : path10.resolve(expanded);
28814
- return (0, import_fs11.existsSync)(candidate) ? candidate : null;
28815
- }
28816
- return null;
28817
- }
28818
- function execAsync(cmd, timeoutMs = 5e3) {
28819
- return new Promise((resolve162) => {
28820
- const child = (0, import_child_process5.exec)(cmd, {
28821
- encoding: "utf-8",
28822
- timeout: timeoutMs,
28823
- ...process.platform === "win32" ? { windowsHide: true } : {}
28824
- }, (err, stdout) => {
28825
- if (err || !stdout?.trim()) {
28826
- resolve162(null);
28827
- } else {
28828
- resolve162(stdout.trim());
28829
- }
28830
- });
28831
- child.on("error", () => resolve162(null));
28832
- });
28833
- }
28834
- async function detectCLIs(providerLoader, options) {
28835
- const platform10 = os32.platform();
28836
- const whichCmd = platform10 === "win32" ? "where" : "which";
28837
- const includeVersion = options?.includeVersion !== false;
28838
- const cliList = providerLoader ? providerLoader.getCliDetectionList() : [];
28839
- const results = await Promise.all(
28840
- cliList.map(async (cli) => {
28841
- try {
28842
- const explicitPath = resolveCommandPath(cli.command);
28843
- const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(cli.command)}`);
28844
- if (!pathResult) return { ...cli, installed: false };
28845
- const firstPath = explicitPath || pathResult.split("\n")[0];
28846
- let version2;
28847
- if (includeVersion) {
28848
- const versionCommands = [
28849
- `"${firstPath}" --version`,
28850
- `"${firstPath}" -V`,
28851
- `"${firstPath}" -v`,
28852
- cli.versionCommand
28853
- ].filter((v) => !!v);
28854
- try {
28855
- for (const versionCommand of versionCommands) {
28856
- const versionResult = await execAsync(versionCommand, 3e3);
28857
- if (versionResult) {
28858
- version2 = parseVersion(versionResult);
28859
- break;
28860
- }
28861
- }
28862
- } catch {
28863
- }
28864
- }
28865
- return { ...cli, installed: true, version: version2, path: firstPath };
28866
- } catch {
28867
- return { ...cli, installed: false };
28868
- }
28869
- })
28870
- );
28871
- return results;
28872
- }
28873
- async function detectCLI(cliId, providerLoader, options) {
28874
- const resolvedId = providerLoader ? providerLoader.resolveAlias(cliId) : cliId;
28875
- if (providerLoader) {
28876
- const cliList = providerLoader.getCliDetectionList();
28877
- const target = cliList.find((c) => c.id === resolvedId);
28878
- if (target) {
28879
- const platform10 = os32.platform();
28880
- const whichCmd = platform10 === "win32" ? "where" : "which";
28881
- try {
28882
- const explicitPath = resolveCommandPath(target.command);
28883
- const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(target.command)}`);
28884
- if (!pathResult) return null;
28885
- const firstPath = explicitPath || pathResult.split("\n")[0];
28886
- let version2;
28887
- if (options?.includeVersion !== false) {
28888
- const versionCommands = [
28889
- `"${firstPath}" --version`,
28890
- `"${firstPath}" -V`,
28891
- `"${firstPath}" -v`,
28892
- target.versionCommand
28893
- ].filter((v) => !!v);
28894
- try {
28895
- for (const versionCommand of versionCommands) {
28896
- const versionResult = await execAsync(versionCommand, 3e3);
28897
- if (versionResult) {
28898
- version2 = parseVersion(versionResult);
28899
- break;
28900
- }
28901
- }
28902
- } catch {
28903
- }
28904
- }
28905
- return { ...target, installed: true, version: version2, path: firstPath };
28906
- } catch {
28907
- return null;
28908
- }
28909
- }
28910
- }
28911
- const all = await detectCLIs(providerLoader, options);
28912
- return all.find((c) => c.id === resolvedId && c.installed) || null;
28913
- }
28914
29648
  function parseDarwinAvailableBytes(totalMem) {
28915
29649
  if (os42.platform() !== "darwin") return null;
28916
29650
  try {
@@ -33701,11 +34435,13 @@ async function handleOpenPanel(h, args) {
33701
34435
  async function handlePtyInput(h, args) {
33702
34436
  const { cliType, data, targetSessionId } = args || {};
33703
34437
  if (!data) return { success: false, error: "data required" };
34438
+ const cleanData = typeof data === "string" ? data.replace(/\x1b\[[?>][0-9;]*c/g, "") : data;
34439
+ if (!cleanData) return { success: true };
33704
34440
  const adapter = h.getCliAdapter(targetSessionId || cliType);
33705
34441
  if (!adapter || typeof adapter.writeRaw !== "function") {
33706
34442
  return { success: false, error: `CLI adapter not found: ${targetSessionId || cliType || "unknown"}` };
33707
34443
  }
33708
- await adapter.writeRaw(data);
34444
+ await adapter.writeRaw(cleanData);
33709
34445
  return { success: true };
33710
34446
  }
33711
34447
  function handlePtyResize(_h, args) {
@@ -34245,6 +34981,13 @@ function cleanupStaleMaterializedImages(dir) {
34245
34981
  } catch {
34246
34982
  }
34247
34983
  }
34984
+ function hasNonEmptyCliModalButtons(activeModal) {
34985
+ const buttons = activeModal?.buttons;
34986
+ return Array.isArray(buttons) && buttons.some((button) => String(button || "").trim().length > 0);
34987
+ }
34988
+ function isCliGeneratingLikeStatus(status) {
34989
+ return status === "generating" || status === "streaming" || status === "long_generating" || status === "starting";
34990
+ }
34248
34991
  function buildCliStructuredInputPrompt(input, options = {}) {
34249
34992
  const promptParts = [];
34250
34993
  const imageRefs = [];
@@ -34532,9 +35275,6 @@ function buildCoordinatorDelegatedCliLaunchOptions(input) {
34532
35275
  const cliType = String(input.cliType || "").trim();
34533
35276
  const cliArgs = Array.isArray(input.cliArgs) ? [...input.cliArgs] : [];
34534
35277
  const env2 = { ...input.env || {}, ...COORDINATOR_DELEGATED_ENV_UNSETS };
34535
- if (cliType === "hermes-cli" && !hasCliArg(cliArgs, "--ignore-user-config")) {
34536
- cliArgs.unshift("--ignore-user-config");
34537
- }
34538
35278
  if (cliType === "claude-cli" && !hasCliArg(cliArgs, "--mcp-config")) {
34539
35279
  cliArgs.unshift("--mcp-config", ensureEmptyDelegatedMcpConfig(input.workspace));
34540
35280
  }
@@ -35478,7 +36218,9 @@ function resolveHermesMeshCoordinatorSetup(options) {
35478
36218
  const mcpServer = resolveAdhdevMcpServerLaunch({
35479
36219
  meshId: options.meshId,
35480
36220
  nodeExecutable: options.nodeExecutable,
35481
- adhdevMcpEntryPath: options.adhdevMcpEntryPath
36221
+ adhdevMcpEntryPath: options.adhdevMcpEntryPath,
36222
+ adhdevMcpTransport: options.adhdevMcpTransport,
36223
+ adhdevMcpPort: options.adhdevMcpPort
35482
36224
  });
35483
36225
  if (!mcpServer) {
35484
36226
  return {
@@ -35545,7 +36287,9 @@ function resolveMeshCoordinatorSetup(options) {
35545
36287
  const mcpServer = resolveAdhdevMcpServerLaunch({
35546
36288
  meshId,
35547
36289
  nodeExecutable: options.nodeExecutable,
35548
- adhdevMcpEntryPath: options.adhdevMcpEntryPath
36290
+ adhdevMcpEntryPath: options.adhdevMcpEntryPath,
36291
+ adhdevMcpTransport: options.adhdevMcpTransport,
36292
+ adhdevMcpPort: options.adhdevMcpPort
35549
36293
  });
35550
36294
  if (!mcpServer) {
35551
36295
  return {
@@ -35567,6 +36311,22 @@ function resolveMeshCoordinatorSetup(options) {
35567
36311
  if (!instructions || !template?.trim()) {
35568
36312
  return { kind: "unsupported", reason: "Provider manual MCP setup is missing instructions or template" };
35569
36313
  }
36314
+ const renderedTemplate = renderMeshCoordinatorTemplate(template, {
36315
+ meshId,
36316
+ workspace,
36317
+ serverName,
36318
+ adhdevMcpCommand: options.adhdevMcpCommand || DEFAULT_ADHDEV_MCP_COMMAND
36319
+ });
36320
+ const isCliCommand = !renderedTemplate.trim().includes("\n") && !renderedTemplate.trim().startsWith("{");
36321
+ if (isCliCommand) {
36322
+ return {
36323
+ kind: "cli_command",
36324
+ serverName,
36325
+ command: renderedTemplate.trim(),
36326
+ requiresRestart: mcpConfig.requiresRestart === true,
36327
+ instructions
36328
+ };
36329
+ }
35570
36330
  return {
35571
36331
  kind: "manual",
35572
36332
  serverName,
@@ -35574,12 +36334,7 @@ function resolveMeshCoordinatorSetup(options) {
35574
36334
  configPathCommand: mcpConfig.configPathCommand,
35575
36335
  requiresRestart: mcpConfig.requiresRestart === true,
35576
36336
  instructions,
35577
- template: renderMeshCoordinatorTemplate(template, {
35578
- meshId,
35579
- workspace,
35580
- serverName,
35581
- adhdevMcpCommand: options.adhdevMcpCommand || DEFAULT_ADHDEV_MCP_COMMAND
35582
- })
36337
+ template: renderedTemplate
35583
36338
  };
35584
36339
  }
35585
36340
  return {
@@ -35608,11 +36363,27 @@ function resolveAdhdevMcpServerLaunch(options) {
35608
36363
  if (!entryPath) return null;
35609
36364
  const nodeExecutable = resolveMcpNodeExecutable(options.nodeExecutable);
35610
36365
  if (!nodeExecutable) return null;
36366
+ const transport = resolveMcpTransport(options.adhdevMcpTransport);
36367
+ const args = [entryPath, "--mode", transport, "--repo-mesh", options.meshId];
36368
+ const port = resolveMcpPort(options.adhdevMcpPort);
36369
+ if (port !== void 0) args.push("--port", String(port));
35611
36370
  return {
35612
36371
  command: nodeExecutable,
35613
- args: [entryPath, "--mode", "ipc", "--repo-mesh", options.meshId]
36372
+ args
35614
36373
  };
35615
36374
  }
36375
+ function resolveMcpTransport(explicitTransport) {
36376
+ if (explicitTransport === "local" || explicitTransport === "ipc") return explicitTransport;
36377
+ const envTransport = process.env.ADHDEV_COORDINATOR_MCP_TRANSPORT?.trim();
36378
+ return envTransport === "local" ? "local" : "ipc";
36379
+ }
36380
+ function resolveMcpPort(explicitPort) {
36381
+ if (typeof explicitPort === "number" && Number.isInteger(explicitPort) && explicitPort > 0) return explicitPort;
36382
+ const raw = process.env.ADHDEV_COORDINATOR_MCP_PORT?.trim();
36383
+ if (!raw) return void 0;
36384
+ const parsed = Number(raw);
36385
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : void 0;
36386
+ }
35616
36387
  function resolveMcpNodeExecutable(explicitExecutable) {
35617
36388
  const explicit = explicitExecutable?.trim();
35618
36389
  if (explicit) return explicit;
@@ -36395,6 +37166,204 @@ async function resolveProviderTypeFromPriority(args) {
36395
37166
  }
36396
37167
  return { error: `No usable provider detected for node '${args.nodeId}' from providerPriority: ${failed.join("; ")}` };
36397
37168
  }
37169
+ function truncateValidationOutput(value) {
37170
+ const text = typeof value === "string" ? value : value == null ? "" : String(value);
37171
+ if (text.length <= REFINE_VALIDATION_SUMMARY_CHARS) return text;
37172
+ return `${text.slice(0, REFINE_VALIDATION_SUMMARY_CHARS)}
37173
+ [truncated ${text.length - REFINE_VALIDATION_SUMMARY_CHARS} chars]`;
37174
+ }
37175
+ function readPackageScripts(workspace) {
37176
+ try {
37177
+ const packageJsonPath = (0, import_path7.join)(workspace, "package.json");
37178
+ const parsed = JSON.parse(fs10.readFileSync(packageJsonPath, "utf-8"));
37179
+ return parsed?.scripts && typeof parsed.scripts === "object" && !Array.isArray(parsed.scripts) ? parsed.scripts : {};
37180
+ } catch {
37181
+ return {};
37182
+ }
37183
+ }
37184
+ function tokenizeValidationCommand(command) {
37185
+ const trimmed = command.trim();
37186
+ if (!trimmed) return null;
37187
+ if (/[;&|<>`$\\\n\r'\"]/.test(trimmed)) return null;
37188
+ const tokens = trimmed.split(/\s+/).filter(Boolean);
37189
+ if (!tokens.length) return null;
37190
+ if (tokens.some((token) => !/^[A-Za-z0-9_@./:=+-]+$/.test(token))) return null;
37191
+ return tokens;
37192
+ }
37193
+ function scriptMatchesValidationCategory(scriptName, category) {
37194
+ return scriptName === category || scriptName.startsWith(`${category}:`);
37195
+ }
37196
+ function parsePackageManagerValidationCommand(rawCommand, category, scripts, source) {
37197
+ const tokens = tokenizeValidationCommand(rawCommand);
37198
+ if (!tokens) {
37199
+ return { rejected: { command: rawCommand, category, source, reason: "unsafe command string is not allowlisted" } };
37200
+ }
37201
+ const [binary2, second, third, ...rest] = tokens;
37202
+ let scriptName = "";
37203
+ let command = binary2;
37204
+ let args = [];
37205
+ if ((binary2 === "npm" || binary2 === "pnpm" || binary2 === "bun") && second === "run" && third) {
37206
+ scriptName = third;
37207
+ args = ["run", scriptName, ...rest];
37208
+ } else if (binary2 === "npm" && second === "test" && !third) {
37209
+ scriptName = "test";
37210
+ args = ["test"];
37211
+ } else if (binary2 === "yarn" && second === "run" && third) {
37212
+ scriptName = third;
37213
+ args = ["run", scriptName, ...rest];
37214
+ } else if (binary2 === "yarn" && second && !third) {
37215
+ scriptName = second;
37216
+ args = [scriptName];
37217
+ } else {
37218
+ return { rejected: { command: rawCommand, category, source, reason: "command is not a supported package-manager script invocation" } };
37219
+ }
37220
+ if (!scriptName || !Object.prototype.hasOwnProperty.call(scripts, scriptName)) {
37221
+ return { rejected: { command: rawCommand, category, source, script: scriptName, reason: "script is not declared in package.json" } };
37222
+ }
37223
+ if (!scriptMatchesValidationCategory(scriptName, category)) {
37224
+ return { rejected: { command: rawCommand, category, source, script: scriptName, reason: "script name is outside the validation category allowlist" } };
37225
+ }
37226
+ return {
37227
+ command: {
37228
+ command,
37229
+ args,
37230
+ displayCommand: [command, ...args].join(" "),
37231
+ category,
37232
+ source
37233
+ }
37234
+ };
37235
+ }
37236
+ function collectProjectContextValidationCandidates(mesh) {
37237
+ const commands = mesh?.projectContext?.commands;
37238
+ if (!commands || typeof commands !== "object" || Array.isArray(commands)) return [];
37239
+ const candidates = [];
37240
+ for (const category of REFINE_VALIDATION_CATEGORIES) {
37241
+ const entries = Array.isArray(commands[category]) ? commands[category] : [];
37242
+ for (const entry of entries) {
37243
+ if (typeof entry?.command !== "string") continue;
37244
+ candidates.push({
37245
+ command: entry.command,
37246
+ category,
37247
+ source: typeof entry.sourcePath === "string" ? entry.sourcePath : "projectContext.commands",
37248
+ confidence: typeof entry.confidence === "string" ? entry.confidence : void 0
37249
+ });
37250
+ }
37251
+ }
37252
+ return candidates.sort((a, b) => {
37253
+ const rank = (value) => value === "high" ? 0 : value === "medium" ? 1 : 2;
37254
+ return rank(a.confidence) - rank(b.confidence);
37255
+ });
37256
+ }
37257
+ function collectPolicyValidationCandidates(mesh) {
37258
+ const policy = mesh?.policy && typeof mesh.policy === "object" && !Array.isArray(mesh.policy) ? mesh.policy : {};
37259
+ const configured = Array.isArray(policy.validationCommands) ? policy.validationCommands : Array.isArray(policy.validationGate?.commands) ? policy.validationGate.commands : [];
37260
+ return configured.map((entry) => typeof entry === "string" ? { command: entry, category: "", source: "mesh.policy.validationCommands" } : entry).filter((entry) => entry && typeof entry.command === "string").map((entry) => {
37261
+ const commandText = entry.command.trim();
37262
+ const category = REFINE_VALIDATION_CATEGORIES.find((cat) => commandText.includes(` ${cat}`)) ?? "";
37263
+ return { command: commandText, category, source: "mesh.policy.validationCommands" };
37264
+ }).filter((entry) => !!entry.category);
37265
+ }
37266
+ function selectMeshRefineValidationCommands(mesh, workspace) {
37267
+ const scripts = readPackageScripts(workspace);
37268
+ const rejectedCommands = [];
37269
+ const selected = [];
37270
+ const seen = /* @__PURE__ */ new Set();
37271
+ const candidates = [
37272
+ ...collectPolicyValidationCandidates(mesh),
37273
+ ...collectProjectContextValidationCandidates(mesh)
37274
+ ];
37275
+ for (const candidate of candidates) {
37276
+ const parsed = parsePackageManagerValidationCommand(candidate.command, candidate.category, scripts, candidate.source);
37277
+ if (parsed.rejected) {
37278
+ rejectedCommands.push(parsed.rejected);
37279
+ continue;
37280
+ }
37281
+ if (!parsed.command || seen.has(parsed.command.displayCommand)) continue;
37282
+ selected.push(parsed.command);
37283
+ seen.add(parsed.command.displayCommand);
37284
+ if (selected.length >= REFINE_VALIDATION_MAX_COMMANDS) break;
37285
+ }
37286
+ if (!selected.length && candidates.length === 0) {
37287
+ for (const category of REFINE_VALIDATION_CATEGORIES) {
37288
+ if (!Object.prototype.hasOwnProperty.call(scripts, category)) continue;
37289
+ const fallback = parsePackageManagerValidationCommand(`npm run ${category}`, category, scripts, "package.json:scripts");
37290
+ if (fallback.command && !seen.has(fallback.command.displayCommand)) {
37291
+ selected.push(fallback.command);
37292
+ seen.add(fallback.command.displayCommand);
37293
+ } else if (fallback.rejected) {
37294
+ rejectedCommands.push(fallback.rejected);
37295
+ }
37296
+ if (selected.length >= 2) break;
37297
+ }
37298
+ }
37299
+ return {
37300
+ commands: selected,
37301
+ rejectedCommands,
37302
+ source: selected.some((command) => command.source === "mesh.policy.validationCommands") ? "mesh_policy" : selected.some((command) => command.source !== "package.json:scripts") ? "project_context" : selected.length ? "package_json_scripts" : "unavailable"
37303
+ };
37304
+ }
37305
+ async function runMeshRefineValidationGate(mesh, workspace) {
37306
+ const { execFile: execFile3 } = await import("child_process");
37307
+ const { promisify: promisify3 } = await import("util");
37308
+ const execFileAsync3 = promisify3(execFile3);
37309
+ const selection = selectMeshRefineValidationCommands(mesh, workspace);
37310
+ const summary = {
37311
+ status: "skipped",
37312
+ required: true,
37313
+ commandsRun: [],
37314
+ rejectedCommands: selection.rejectedCommands,
37315
+ skippedReason: void 0,
37316
+ timeoutMs: REFINE_VALIDATION_TIMEOUT_MS,
37317
+ outputLimitBytes: REFINE_VALIDATION_OUTPUT_LIMIT_BYTES
37318
+ };
37319
+ if (!selection.commands.length) {
37320
+ summary.skippedReason = "validation_unavailable: no allowlisted projectContext, mesh policy, or package.json build/test/typecheck/lint command was available";
37321
+ return summary;
37322
+ }
37323
+ for (const candidate of selection.commands) {
37324
+ const startedAt = Date.now();
37325
+ try {
37326
+ const result = await execFileAsync3(candidate.command, candidate.args, {
37327
+ cwd: workspace,
37328
+ encoding: "utf8",
37329
+ timeout: REFINE_VALIDATION_TIMEOUT_MS,
37330
+ maxBuffer: REFINE_VALIDATION_OUTPUT_LIMIT_BYTES,
37331
+ env: { ...process.env, CI: process.env.CI || "1" }
37332
+ });
37333
+ summary.commandsRun.push({
37334
+ command: candidate.command,
37335
+ args: candidate.args,
37336
+ displayCommand: candidate.displayCommand,
37337
+ category: candidate.category,
37338
+ source: candidate.source,
37339
+ passed: true,
37340
+ exitCode: 0,
37341
+ durationMs: Date.now() - startedAt,
37342
+ stdout: truncateValidationOutput(result.stdout),
37343
+ stderr: truncateValidationOutput(result.stderr)
37344
+ });
37345
+ } catch (error48) {
37346
+ summary.commandsRun.push({
37347
+ command: candidate.command,
37348
+ args: candidate.args,
37349
+ displayCommand: candidate.displayCommand,
37350
+ category: candidate.category,
37351
+ source: candidate.source,
37352
+ passed: false,
37353
+ exitCode: typeof error48?.code === "number" ? error48.code : null,
37354
+ signal: typeof error48?.signal === "string" ? error48.signal : null,
37355
+ timedOut: error48?.killed === true || /timed out/i.test(String(error48?.message || "")),
37356
+ durationMs: Date.now() - startedAt,
37357
+ stdout: truncateValidationOutput(error48?.stdout),
37358
+ stderr: truncateValidationOutput(error48?.stderr || error48?.message)
37359
+ });
37360
+ summary.status = "failed";
37361
+ return summary;
37362
+ }
37363
+ }
37364
+ summary.status = "passed";
37365
+ return summary;
37366
+ }
36398
37367
  function loadYamlModule() {
36399
37368
  return js_yaml_exports;
36400
37369
  }
@@ -36424,6 +37393,34 @@ function loadHermesCoordinatorBaseConfig(targetConfigPath) {
36424
37393
  const { mcp_servers: _mcpServers, ...baseConfig } = parsed;
36425
37394
  return { config: baseConfig, sourceHome, sourceConfigPath };
36426
37395
  }
37396
+ function stripHermesCoordinatorTempModelProviderOverrides(config2) {
37397
+ const {
37398
+ model: _model,
37399
+ provider: _provider,
37400
+ default_model: _defaultModel,
37401
+ defaultProvider: _defaultProvider,
37402
+ default_provider: _defaultProviderSnake,
37403
+ modelProvider: _modelProvider,
37404
+ model_provider: _modelProviderSnake,
37405
+ ...sanitized
37406
+ } = config2;
37407
+ const delegation = sanitized.delegation;
37408
+ if (delegation && typeof delegation === "object" && !Array.isArray(delegation)) {
37409
+ const {
37410
+ model: _delegationModel,
37411
+ provider: _delegationProvider,
37412
+ modelProvider: _delegationModelProvider,
37413
+ model_provider: _delegationModelProviderSnake,
37414
+ ...delegationRest
37415
+ } = delegation;
37416
+ if (Object.keys(delegationRest).length > 0) {
37417
+ sanitized.delegation = delegationRest;
37418
+ } else {
37419
+ delete sanitized.delegation;
37420
+ }
37421
+ }
37422
+ return sanitized;
37423
+ }
36427
37424
  function copyHermesCoordinatorCredentialFiles(sourceHome, targetHome) {
36428
37425
  if ((0, import_path7.resolve)(sourceHome) === (0, import_path7.resolve)(targetHome)) return;
36429
37426
  for (const fileName of [".env", "auth.json"]) {
@@ -40855,7 +41852,8 @@ async function initDaemonComponents(config2) {
40855
41852
  cdpManagers,
40856
41853
  sessionRegistry,
40857
41854
  detectedIdes: detectedIdesRef,
40858
- refreshProviderAvailability
41855
+ refreshProviderAvailability,
41856
+ dispatchMeshCommand: config2.dispatchMeshCommand
40859
41857
  };
40860
41858
  setupMeshEventForwarding(components);
40861
41859
  return components;
@@ -40913,7 +41911,7 @@ async function shutdownDaemonComponents(components) {
40913
41911
  }
40914
41912
  cdpManagers.clear();
40915
41913
  }
40916
- var path4, import_promises4, import_fs3, import_child_process, import_util3, import_os2, import_path, import_fs4, import_crypto2, import_fs5, import_path2, import_crypto3, import_fs6, import_path3, import_crypto4, import_events2, import_fs7, import_path4, import_crypto5, fs2, path8, os22, os8, os9, path14, import_child_process2, os10, path15, os11, import_child_process3, import_fs8, import_promises5, path, import_util4, import_promises6, path22, path32, fs, os4, path5, import_crypto6, path6, path7, import_fs9, import_path5, import_child_process4, import_fs10, import_os3, path9, import_child_process5, os32, path10, import_fs11, os42, import_child_process6, http, crypto2, fs3, path11, os5, fs4, os6, path12, import_crypto7, fs5, path13, os7, os13, path18, crypto4, import_fs12, import_child_process7, os12, path16, crypto3, fs6, import_module, path17, import_stream2, import_child_process8, import_child_process9, net2, os15, path20, fs7, path19, os14, fs8, path21, os16, import_child_process10, import_crypto8, import_fs13, import_module2, os17, import_path6, os18, import_child_process11, import_child_process12, fs9, os19, path222, import_os4, import_path7, fs10, fs11, path23, os20, import_child_process13, import_os5, http2, fs15, path27, fs12, path24, fs13, path25, fs14, path26, os21, import_child_process14, __defProp2, __getOwnPropDesc2, __getOwnPropNames2, __hasOwnProp2, __require2, __esm2, __export2, __copyProps2, __toCommonJS2, DEFAULT_MESH_POLICY, init_repo_mesh_types, git_worktree_exports, execFileAsync2, WORKTREE_DIR_NAME, GIT_TIMEOUT_MS, GIT_MAX_BUFFER, init_git_worktree, config_exports, DEFAULT_CONFIG, MACHINE_ID_PREFIX, init_config, mesh_config_exports, SESSION_CLEANUP_MODES, SPAWNED_SESSION_VISIBILITY_MODES, init_mesh_config, coordinator_prompt_exports, TOOLS_SECTION, TOOL_EXPOSURE_PREFLIGHT_SECTION, WORKFLOW_SECTION, init_coordinator_prompt, mesh_ledger_exports, LEDGER_DIR_NAME, MAX_FILE_SIZE_BYTES, RECENT_FAILURE_WINDOW_MS, meshLedgerEvents, init_mesh_ledger, init_mesh_work_queue, LEVEL_NUM, LEVEL_LABEL, currentLevel, LOG_DIR, MAX_LOG_SIZE, MAX_LOG_DAYS, currentDate, currentLogFile, writeCount, RING_BUFFER_SIZE, ringBuffer, origConsoleLog, origConsoleError, origConsoleWarn, LOG, interceptorInstalled, LOG_PATH, init_logger, mesh_events_exports, MAX_PENDING_EVENTS, pendingMeshCoordinatorEvents, MESH_COORDINATOR_EVENTS, EVENT_TO_LEDGER_KIND, init_mesh_events, NORMAL_TRACE_BUFFER_SIZE, DEV_TRACE_BUFFER_SIZE, DEFAULT_CONFIG2, currentConfig, init_debug_config, DEFAULT_BINDING_CANDIDATES, cachedBinding, cachedBindingError, GhosttyVtTerminalBackend, init_ghostty_vt_backend, TerminalCtor, XtermTerminalBackend, init_xterm_backend, DEFAULT_SCROLLBACK, loggedTerminalBackends, TerminalScreen, init_terminal_screen, init_spawn_env, cachedPty, NodePtyRuntimeTransport, NodePtyTransportFactory, init_pty_transport, TerminalTranscriptAccumulator, buildCliSpawnEnv, init_provider_cli_shared, init_provider_cli_parse, init_provider_cli_config, init_provider_cli_runtime, provider_cli_adapter_exports, ProviderCliAdapter, init_provider_cli_adapter, execFileAsync, DEFAULT_TIMEOUT_MS, DEFAULT_MAX_BUFFER, GitCommandError, DEFAULT_MAX_FILES, DEFAULT_MAX_BYTES, summarizeGitStatus, InMemoryGitSnapshotStore, DEFAULT_GIT_WORKSPACE_POLL_INTERVAL_MS, MIN_GIT_WORKSPACE_POLL_INTERVAL_MS, GitWorkspaceMonitor, GIT_COMMAND_NAMES, SNAPSHOT_REASONS, FAILURE_REASONS, defaultSnapshotStore, defaultGitCommandServices, BUSY_STATUSES, TERMINAL_STATUSES, TurnSnapshotTracker, MAX_WORKSPACES, MAX_ACTIVITY, MAX_SAVED_SESSIONS, DEFAULT_STATE, BUILTIN_IDE_DEFINITIONS, registeredIDEs, LIVE_LIFECYCLES, DEFAULT_ACTIVE_CHAT_POLL_STATUSES, DEFAULT_CHAT_TAIL_RECENT_MESSAGE_GRACE_MS, LIVE_RUNTIME_LIFECYCLES, DaemonCdpManager, CdpDomHandlers, DEFAULT_MONITOR_CONFIG, StatusMonitor, BUILTIN_CHAT_MESSAGE_KINDS, CHAT_MESSAGE_VISIBILITIES, CHAT_MESSAGE_TRANSCRIPT_VISIBILITIES, CHAT_MESSAGE_AUDIENCES, CHAT_MESSAGE_SOURCES, CHAT_MESSAGE_ACTIVITY_SOURCES, CHAT_MESSAGE_INTERNAL_SOURCES, KNOWN_CHAT_MESSAGE_KINDS, CHAT_MESSAGE_KIND_ALIASES, EXPLICIT_HIDDEN_VISIBILITIES, EXPLICIT_VISIBLE_VISIBILITIES, HIDDEN_AUDIENCES, ACTIVITY_SOURCE_SET, INTERNAL_SOURCE_SET, HISTORY_DIR, RETAIN_DAYS, SAVED_HISTORY_INDEX_VERSION, SAVED_HISTORY_INDEX_FILE, SAVED_HISTORY_INDEX_LOCK_SUFFIX, SAVED_HISTORY_INDEX_LOCK_WAIT_MS, SAVED_HISTORY_INDEX_LOCK_STALE_MS, SAVED_HISTORY_INDEX_LOCK_POLL_MS, SAVED_HISTORY_ROLLUP_THRESHOLD_BYTES, savedHistorySessionCache, savedHistoryFileSummaryCache, savedHistoryBackgroundRefresh, savedHistoryRollupInFlight, ChatHistoryWriter, IDE_PROVIDER_SESSION_CAPABILITIES_BASE, EXTENSION_PROVIDER_SESSION_CAPABILITIES_BASE, ExtensionProviderInstance, VALID_STATUSES, VALID_ROLES, VALID_BUBBLE_STATES, VALID_TURN_STATUSES, DEFAULT_APPROVAL_POSITIVE_HINTS, IdeProviderInstance, DEFAULT_CDP_SCAN_INTERVAL_MS, DEFAULT_CDP_DISCOVERY_INTERVAL_MS, DEFAULT_STATUS_INITIAL_REPORT_DELAY_MS, DEFAULT_STATUS_SERVER_REPORT_INTERVAL_MS, DEFAULT_STATUS_P2P_REPORT_INTERVAL_MS, MIN_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS, DEFAULT_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS, MIN_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS, DEFAULT_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS, DEFAULT_SESSION_HOST_READY_TIMEOUT_MS, STANDALONE_CDP_SCAN_INTERVAL_MS, DaemonCdpScanner, DaemonCdpInitializer, WORKING_STATUSES, FULL_STATUS_ACTIVE_CHAT_OPTIONS, LIVE_STATUS_ACTIVE_CHAT_OPTIONS, STATUS_MODAL_MESSAGE_LIMIT, STATUS_MODAL_BUTTON_LIMIT, VALID_INPUT_MEDIA_TYPES, VALID_INPUT_STRATEGIES, TEXT_ONLY_MESSAGE_INPUT_SUPPORT, IDE_SESSION_CAPABILITIES, EXTENSION_SESSION_CAPABILITIES, PTY_SESSION_CAPABILITIES, CLI_CHAT_SESSION_CAPABILITIES, ACP_SESSION_CAPABILITIES, globalStore, RECENT_SEND_WINDOW_MS, READ_CHAT_PROVIDER_EVAL_TIMEOUT_MS, HERMES_CLI_STARTING_SEND_SETTLE_MS, recentSendByTarget, DEFAULT_DEBUG_SANITIZE_OPTIONS, SECRET_KEY_PATTERN, KEY_TO_VK, COMMAND_DEBUG_LEVELS, DaemonCommandHandler, IMAGE_MIME_EXTENSIONS, MATERIALIZED_IMAGE_MAX_AGE_MS, MATERIALIZED_IMAGE_CLEANUP_INTERVAL_MS, lastMaterializedImageCleanupAt, CachedDatabaseSync, CliProviderInstance, AcpProviderInstance, chalkModule, chalkApi, COORDINATOR_DELEGATED_ENV_UNSETS, DaemonCliManager, VALID_CAPABILITY_MEDIA_TYPES, VALID_INPUT_STRATEGIES2, KNOWN_PROVIDER_FIELDS, VALUE_CONTROL_TYPES, ProviderLoader, _providerLoader, LOG_DIR2, MAX_FILE_SIZE, MAX_DAYS, SENSITIVE_KEYS, currentDate2, currentFile, writeCount2, SKIP_COMMANDS, DEFAULT_SERVER_NAME, DEFAULT_ADHDEV_MCP_COMMAND, HERMES_CLI_TYPE, HERMES_MCP_CONFIG_PATH, READ_DEBUG_ENABLED, recentReadDebugSignatureBySession, UPGRADE_HELPER_ENV, CHANNEL_NPM_TAG, CHANNEL_SERVER_URL, CHAT_COMMANDS, READ_DEBUG_ENABLED2, DaemonCommandRouter, DaemonStatusReporter, DEFAULT_DAEMON_PORT, DAEMON_WS_PATH, ProviderStreamAdapter, DaemonAgentStreamManager, AgentStreamPoller, ProviderInstanceManager, ARCHIVE_PATH, MAX_ENTRIES_PER_PROVIDER, VersionArchive, DEV_SERVER_PORT, DevServer, SessionHostRuntimeTransport, SessionHostPtyTransportFactory, DEFAULT_SESSION_HOST_APP_NAME, DEFAULT_STANDALONE_SESSION_HOST_APP_NAME, STARTUP_TIMEOUT_MS, STARTUP_POLL_MS, SessionHostCompatibilityError, EXTENSION_CATALOG, SessionRegistry;
41914
+ var path4, import_promises4, import_fs3, import_child_process, import_util3, import_os2, import_path, import_fs4, import_crypto2, import_fs5, import_path2, import_crypto3, import_fs6, import_path3, import_crypto4, import_events2, import_fs7, import_path4, import_crypto5, import_child_process2, os22, path8, import_fs8, fs2, path9, os32, os8, os9, path14, import_child_process3, os10, path15, os11, import_child_process4, import_fs9, import_promises5, path, import_util4, import_promises6, path22, path32, fs, os4, path5, import_crypto6, path6, path7, import_fs10, import_path5, import_child_process5, import_fs11, import_os3, path10, os42, import_child_process6, http, crypto2, fs3, path11, os5, fs4, os6, path12, import_crypto7, fs5, path13, os7, os13, path18, crypto4, import_fs12, import_child_process7, os12, path16, crypto3, fs6, import_module, path17, import_stream2, import_child_process8, import_child_process9, net2, os15, path20, fs7, path19, os14, fs8, path21, os16, import_child_process10, import_crypto8, import_fs13, import_module2, os17, import_path6, os18, import_child_process11, import_child_process12, fs9, os19, path222, import_os4, import_path7, fs10, fs11, path23, os20, import_child_process13, import_os5, http2, fs15, path27, fs12, path24, fs13, path25, fs14, path26, os21, import_child_process14, __defProp2, __getOwnPropDesc2, __getOwnPropNames2, __hasOwnProp2, __require2, __esm2, __export2, __copyProps2, __toCommonJS2, DEFAULT_MESH_POLICY, init_repo_mesh_types, git_worktree_exports, execFileAsync2, WORKTREE_DIR_NAME, GIT_TIMEOUT_MS, GIT_MAX_BUFFER, SUBMODULE_WORKTREE_REMOVE_RE, init_git_worktree, config_exports, DEFAULT_CONFIG, MACHINE_ID_PREFIX, init_config, mesh_config_exports, SESSION_CLEANUP_MODES, SPAWNED_SESSION_VISIBILITY_MODES, init_mesh_config, coordinator_prompt_exports, TOOLS_SECTION, TOOL_EXPOSURE_PREFLIGHT_SECTION, WORKFLOW_SECTION, init_coordinator_prompt, mesh_ledger_exports, LEDGER_DIR_NAME, MAX_FILE_SIZE_BYTES, RECENT_FAILURE_WINDOW_MS, DEFAULT_LEDGER_SLICE_LIMIT, MAX_LEDGER_SLICE_LIMIT, meshLedgerEvents, init_mesh_ledger, mesh_work_queue_exports, ACTIVE_MESH_QUEUE_STATUSES, HISTORICAL_MESH_QUEUE_STATUSES, init_mesh_work_queue, init_cli_detector, LEVEL_NUM, LEVEL_LABEL, currentLevel, LOG_DIR, MAX_LOG_SIZE, MAX_LOG_DAYS, currentDate, currentLogFile, writeCount, RING_BUFFER_SIZE, ringBuffer, origConsoleLog, origConsoleError, origConsoleWarn, LOG, interceptorInstalled, LOG_PATH, init_logger, mesh_events_exports, remoteIdleSessions, MAX_PENDING_EVENTS, pendingMeshCoordinatorEvents, MESH_COORDINATOR_EVENTS, EVENT_TO_LEDGER_KIND, INTENTIONAL_CLEANUP_STOP_SUPPRESSION_MS, autoLaunchInProgress, autoLaunchCooldownUntil, AUTO_LAUNCH_COOLDOWN_MS, init_mesh_events, NORMAL_TRACE_BUFFER_SIZE, DEV_TRACE_BUFFER_SIZE, DEFAULT_CONFIG2, currentConfig, init_debug_config, DEFAULT_BINDING_CANDIDATES, cachedBinding, cachedBindingError, GhosttyVtTerminalBackend, init_ghostty_vt_backend, TerminalCtor, XtermTerminalBackend, init_xterm_backend, DEFAULT_SCROLLBACK, loggedTerminalBackends, TerminalScreen, init_terminal_screen, init_spawn_env, cachedPty, NodePtyRuntimeTransport, NodePtyTransportFactory, init_pty_transport, TerminalTranscriptAccumulator, buildCliSpawnEnv, init_provider_cli_shared, init_provider_cli_parse, init_provider_cli_config, init_provider_cli_runtime, provider_cli_adapter_exports, ProviderCliAdapter, init_provider_cli_adapter, execFileAsync, DEFAULT_TIMEOUT_MS, DEFAULT_MAX_BUFFER, GitCommandError, DEFAULT_MAX_FILES, DEFAULT_MAX_BYTES, summarizeGitStatus, InMemoryGitSnapshotStore, DEFAULT_GIT_WORKSPACE_POLL_INTERVAL_MS, MIN_GIT_WORKSPACE_POLL_INTERVAL_MS, GitWorkspaceMonitor, GIT_COMMAND_NAMES, SNAPSHOT_REASONS, FAILURE_REASONS, defaultSnapshotStore, defaultGitCommandServices, BUSY_STATUSES, TERMINAL_STATUSES, TurnSnapshotTracker, MAX_WORKSPACES, MAX_ACTIVITY, MAX_SAVED_SESSIONS, NO_FALLBACK_REASON, P2P_NEXT_ACTION, NON_P2P_NEXT_ACTION, P2pRelayFailureError, DEFAULT_STATE, BUILTIN_IDE_DEFINITIONS, registeredIDEs, LIVE_LIFECYCLES, DEFAULT_ACTIVE_CHAT_POLL_STATUSES, DEFAULT_CHAT_TAIL_RECENT_MESSAGE_GRACE_MS, LIVE_RUNTIME_LIFECYCLES, DaemonCdpManager, CdpDomHandlers, DEFAULT_MONITOR_CONFIG, StatusMonitor, BUILTIN_CHAT_MESSAGE_KINDS, CHAT_MESSAGE_VISIBILITIES, CHAT_MESSAGE_TRANSCRIPT_VISIBILITIES, CHAT_MESSAGE_AUDIENCES, CHAT_MESSAGE_SOURCES, CHAT_MESSAGE_ACTIVITY_SOURCES, CHAT_MESSAGE_INTERNAL_SOURCES, KNOWN_CHAT_MESSAGE_KINDS, CHAT_MESSAGE_KIND_ALIASES, EXPLICIT_HIDDEN_VISIBILITIES, EXPLICIT_VISIBLE_VISIBILITIES, HIDDEN_AUDIENCES, ACTIVITY_SOURCE_SET, INTERNAL_SOURCE_SET, HISTORY_DIR, RETAIN_DAYS, SAVED_HISTORY_INDEX_VERSION, SAVED_HISTORY_INDEX_FILE, SAVED_HISTORY_INDEX_LOCK_SUFFIX, SAVED_HISTORY_INDEX_LOCK_WAIT_MS, SAVED_HISTORY_INDEX_LOCK_STALE_MS, SAVED_HISTORY_INDEX_LOCK_POLL_MS, SAVED_HISTORY_ROLLUP_THRESHOLD_BYTES, savedHistorySessionCache, savedHistoryFileSummaryCache, savedHistoryBackgroundRefresh, savedHistoryRollupInFlight, ChatHistoryWriter, IDE_PROVIDER_SESSION_CAPABILITIES_BASE, EXTENSION_PROVIDER_SESSION_CAPABILITIES_BASE, ExtensionProviderInstance, VALID_STATUSES, VALID_ROLES, VALID_BUBBLE_STATES, VALID_TURN_STATUSES, DEFAULT_APPROVAL_POSITIVE_HINTS, IdeProviderInstance, DEFAULT_CDP_SCAN_INTERVAL_MS, DEFAULT_CDP_DISCOVERY_INTERVAL_MS, DEFAULT_STATUS_INITIAL_REPORT_DELAY_MS, DEFAULT_STATUS_SERVER_REPORT_INTERVAL_MS, DEFAULT_STATUS_P2P_REPORT_INTERVAL_MS, MIN_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS, DEFAULT_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS, MIN_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS, DEFAULT_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS, DEFAULT_SESSION_HOST_READY_TIMEOUT_MS, STANDALONE_CDP_SCAN_INTERVAL_MS, DaemonCdpScanner, DaemonCdpInitializer, WORKING_STATUSES, FULL_STATUS_ACTIVE_CHAT_OPTIONS, LIVE_STATUS_ACTIVE_CHAT_OPTIONS, STATUS_MODAL_MESSAGE_LIMIT, STATUS_MODAL_BUTTON_LIMIT, VALID_INPUT_MEDIA_TYPES, VALID_INPUT_STRATEGIES, TEXT_ONLY_MESSAGE_INPUT_SUPPORT, IDE_SESSION_CAPABILITIES, EXTENSION_SESSION_CAPABILITIES, PTY_SESSION_CAPABILITIES, CLI_CHAT_SESSION_CAPABILITIES, ACP_SESSION_CAPABILITIES, globalStore, RECENT_SEND_WINDOW_MS, READ_CHAT_PROVIDER_EVAL_TIMEOUT_MS, HERMES_CLI_STARTING_SEND_SETTLE_MS, recentSendByTarget, DEFAULT_DEBUG_SANITIZE_OPTIONS, SECRET_KEY_PATTERN, KEY_TO_VK, COMMAND_DEBUG_LEVELS, DaemonCommandHandler, COMPLETED_FINALIZATION_RETRY_MS, COMPLETED_FINALIZATION_MAX_WAIT_MS, IMAGE_MIME_EXTENSIONS, MATERIALIZED_IMAGE_MAX_AGE_MS, MATERIALIZED_IMAGE_CLEANUP_INTERVAL_MS, lastMaterializedImageCleanupAt, CachedDatabaseSync, CliProviderInstance, AcpProviderInstance, chalkModule, chalkApi, COORDINATOR_DELEGATED_ENV_UNSETS, DaemonCliManager, VALID_CAPABILITY_MEDIA_TYPES, VALID_INPUT_STRATEGIES2, KNOWN_PROVIDER_FIELDS, VALUE_CONTROL_TYPES, ProviderLoader, _providerLoader, LOG_DIR2, MAX_FILE_SIZE, MAX_DAYS, SENSITIVE_KEYS, currentDate2, currentFile, writeCount2, SKIP_COMMANDS, DEFAULT_SERVER_NAME, DEFAULT_ADHDEV_MCP_COMMAND, HERMES_CLI_TYPE, HERMES_MCP_CONFIG_PATH, READ_DEBUG_ENABLED, recentReadDebugSignatureBySession, UPGRADE_HELPER_ENV, CHANNEL_NPM_TAG, CHANNEL_SERVER_URL, REFINE_VALIDATION_CATEGORIES, REFINE_VALIDATION_TIMEOUT_MS, REFINE_VALIDATION_OUTPUT_LIMIT_BYTES, REFINE_VALIDATION_SUMMARY_CHARS, REFINE_VALIDATION_MAX_COMMANDS, CHAT_COMMANDS, READ_DEBUG_ENABLED2, DaemonCommandRouter, DaemonStatusReporter, DEFAULT_DAEMON_PORT, DAEMON_WS_PATH, ProviderStreamAdapter, DaemonAgentStreamManager, AgentStreamPoller, ProviderInstanceManager, ARCHIVE_PATH, MAX_ENTRIES_PER_PROVIDER, VersionArchive, DEV_SERVER_PORT, DevServer, SessionHostRuntimeTransport, SessionHostPtyTransportFactory, DEFAULT_SESSION_HOST_APP_NAME, DEFAULT_STANDALONE_SESSION_HOST_APP_NAME, STARTUP_TIMEOUT_MS, STARTUP_POLL_MS, SessionHostCompatibilityError, EXTENSION_CATALOG, SessionRegistry;
40917
41915
  var init_dist2 = __esm({
40918
41916
  "../daemon-core/dist/index.mjs"() {
40919
41917
  "use strict";
@@ -40936,20 +41934,24 @@ var init_dist2 = __esm({
40936
41934
  import_fs7 = require("fs");
40937
41935
  import_path4 = require("path");
40938
41936
  import_crypto5 = require("crypto");
40939
- fs2 = __toESM(require("fs"), 1);
40940
- path8 = __toESM(require("path"), 1);
41937
+ import_child_process2 = require("child_process");
40941
41938
  os22 = __toESM(require("os"), 1);
41939
+ path8 = __toESM(require("path"), 1);
41940
+ import_fs8 = require("fs");
41941
+ fs2 = __toESM(require("fs"), 1);
41942
+ path9 = __toESM(require("path"), 1);
41943
+ os32 = __toESM(require("os"), 1);
40942
41944
  init_dist();
40943
41945
  os8 = __toESM(require("os"), 1);
40944
41946
  os9 = __toESM(require("os"), 1);
40945
41947
  path14 = __toESM(require("path"), 1);
40946
- import_child_process2 = require("child_process");
41948
+ import_child_process3 = require("child_process");
40947
41949
  os10 = __toESM(require("os"), 1);
40948
41950
  path15 = __toESM(require("path"), 1);
40949
41951
  init_dist();
40950
41952
  os11 = __toESM(require("os"), 1);
40951
- import_child_process3 = require("child_process");
40952
- import_fs8 = require("fs");
41953
+ import_child_process4 = require("child_process");
41954
+ import_fs9 = require("fs");
40953
41955
  import_promises5 = require("fs/promises");
40954
41956
  path = __toESM(require("path"), 1);
40955
41957
  import_util4 = require("util");
@@ -40962,16 +41964,12 @@ var init_dist2 = __esm({
40962
41964
  import_crypto6 = require("crypto");
40963
41965
  path6 = __toESM(require("path"), 1);
40964
41966
  path7 = __toESM(require("path"), 1);
40965
- import_fs9 = require("fs");
40966
- import_path5 = require("path");
40967
- import_child_process4 = require("child_process");
40968
41967
  import_fs10 = require("fs");
40969
- import_os3 = require("os");
40970
- path9 = __toESM(require("path"), 1);
41968
+ import_path5 = require("path");
40971
41969
  import_child_process5 = require("child_process");
40972
- os32 = __toESM(require("os"), 1);
40973
- path10 = __toESM(require("path"), 1);
40974
41970
  import_fs11 = require("fs");
41971
+ import_os3 = require("os");
41972
+ path10 = __toESM(require("path"), 1);
40975
41973
  os42 = __toESM(require("os"), 1);
40976
41974
  import_child_process6 = require("child_process");
40977
41975
  init_wrapper();
@@ -41104,6 +42102,7 @@ var init_dist2 = __esm({
41104
42102
  WORKTREE_DIR_NAME = ".adhdev-worktrees";
41105
42103
  GIT_TIMEOUT_MS = 3e4;
41106
42104
  GIT_MAX_BUFFER = 4 * 1024 * 1024;
42105
+ SUBMODULE_WORKTREE_REMOVE_RE = /working trees containing submodules cannot be moved or removed/i;
41107
42106
  }
41108
42107
  });
41109
42108
  config_exports = {};
@@ -41183,17 +42182,24 @@ var init_dist2 = __esm({
41183
42182
 
41184
42183
  | Tool | Purpose |
41185
42184
  |------|---------|
41186
- | \`mesh_status\` | Check all nodes' health, git state, and active sessions |
42185
+ | \`mesh_status\` | Check all nodes' health, git state, active sessions, and branch convergence |
41187
42186
  | \`mesh_list_nodes\` | List nodes with workspace paths |
42187
+ | \`mesh_enqueue_task\` | Add a task to the pull-based work queue; idle nodes auto-claim |
42188
+ | \`mesh_view_queue\` | View queue status \u2014 pending, assigned, completed, failed, cancelled tasks |
42189
+ | \`mesh_queue_cancel\` | Cancel a queue task without deleting audit history |
42190
+ | \`mesh_queue_requeue\` | Return a task to pending for retry; clears stale session targets |
42191
+ | \`mesh_send_task\` | Legacy push: enqueue a task targeted at a specific node |
41188
42192
  | \`mesh_launch_session\` | Start a new agent session on a node |
41189
- | \`mesh_send_task\` | Send a task (natural language) to a running agent |
41190
- | \`mesh_read_chat\` | Read an agent's recent messages to check progress |
42193
+ | \`mesh_read_chat\` | Read recent chat messages from a delegated agent session |
42194
+ | \`mesh_read_debug\` | Collect a daemon-side chat/parser debug bundle for a session |
41191
42195
  | \`mesh_task_history\` | Read the task ledger \u2014 dispatches, completions, failures. Use to understand what has been done before deciding next steps |
41192
42196
  | \`mesh_git_status\` | Check git status on a specific node |
41193
42197
  | \`mesh_checkpoint\` | Create a git checkpoint on a node |
41194
42198
  | \`mesh_approve\` | Approve/reject a pending agent action |
41195
42199
  | \`mesh_clone_node\` | Create a worktree node for isolated parallel branch work |
41196
- | \`mesh_remove_node\` | Remove a node (cleans up worktree if applicable) |`;
42200
+ | \`mesh_refine_node\` | Validate and merge a completed worktree node back into its base branch |
42201
+ | \`mesh_remove_node\` | Remove a node (cleans up worktree if applicable) |
42202
+ | \`mesh_cleanup_sessions\` | Manually clean up delegated session records for a node |`;
41197
42203
  TOOL_EXPOSURE_PREFLIGHT_SECTION = `## Tool Exposure Preflight
41198
42204
 
41199
42205
  Before doing any coordinator work, confirm that the actual callable tool list includes \`mesh_status\` and the other \`mesh_*\` tools from the table above. If this Repo Mesh coordinator prompt is present but the callable \`mesh_*\` tools are missing, the MCP server/tool manifest is stale or not injected yet. Do not substitute terminal/file/git tools, do not inspect or edit the repository directly, and do not continue as a non-mesh local coding agent. Stop immediately and tell the user to run \`/reload-mcp\` or start a fresh coordinator session so ADHDev can reconnect \`adhdev-mesh\`.`;
@@ -41203,14 +42209,16 @@ Before doing any coordinator work, confirm that the actual callable tool list in
41203
42209
  2. **Plan** \u2014 Decompose the user's request into independent tasks for parallel execution, or sequential tasks when dependencies exist. If \`mesh_task_history\` shows a recent failure for a task, decide whether to retry or reassign.
41204
42210
  3. **Queue / Delegate** \u2014 The Mesh uses an autonomous pull-based Work Queue:
41205
42211
  a. **General Tasks**: Enqueue tasks using \`mesh_enqueue_task\`. Idle node agents will automatically pull tasks from the queue and begin working.
41206
- b. **Node Preparation**: Call \`mesh_launch_session\` to ensure enough agent sessions are active to handle the queue. If you need branch isolation for parallel work, call \`mesh_clone_node\` to create a worktree node first.
42212
+ b. **Node Preparation**: Reuse an existing idle session on the correct node/provider before launching a new chat/session. Call \`mesh_launch_session\` only when no suitable session exists, when the user explicitly asks for a fresh provider/session, or when branch/worktree isolation requires it. If you need branch isolation for parallel work, call \`mesh_clone_node\` to create a worktree node first.
41207
42213
  c. **Targeted Tasks**: Use \`mesh_send_task\` only when you need to bypass the queue and force a specific node to execute a task immediately.
41208
- d. Always provide a **complete, self-contained** instruction that includes all context the agent needs (file paths, line numbers, what to change, why). Do not send partial instructions expecting future follow-up.
42214
+ d. For the first dispatch of a new task, provide a **complete, self-contained** instruction that includes all context the agent needs (file paths, line numbers, what to change, why). Do not send partial instructions expecting future follow-up.
42215
+ e. For a continuation of the same issue in an existing session, send a concise **delta instruction**: current verified state, the exact failed/blocked step, the newly approved action, and final reporting requirements. Do not resend the full original task or open a new chat solely to continue the same work; that wastes coordinator and worker context.
41209
42216
  4. **Monitor** \u2014 Prefer event-driven completion/status notifications. Do **not** poll \`mesh_read_chat\` repeatedly. Use \`mesh_view_queue\` to see the status of all pending, assigned, completed, and failed tasks. Do not call \`mesh_read_chat\` again within a few seconds for the same generating session. Use at most one compact \`mesh_read_chat\` check after a completion/approval signal. Handle approvals via \`mesh_approve\`.
41210
42217
  5. **Verify** \u2014 When a task reports completion or git work is visible, call \`mesh_git_status\` to verify changes were made.
41211
42218
  6. **Checkpoint** \u2014 Call \`mesh_checkpoint\` to save the work.
41212
- 7. **Clean up** \u2014 Remove worktree nodes via \`mesh_remove_node\` after their work is merged or no longer needed.
41213
- 8. **Report** \u2014 Summarize what was done, what changed, and any issues.
42219
+ 7. **Converge branches** \u2014 Before marking any task complete, classify every touched node/branch into exactly one final state: \`merged_to_main\`, \`pushed_feature_branch_needs_merge\`, \`blocked_review\`, \`cleanup_candidate\`, or \`not_mergeable\`. Use \`mesh_status\` branchConvergenceSummary and \`mesh_refine_node\` for clean worktree branches when safe. A task that remains on a non-main branch is not fully complete unless the final report names the follow-up state and next step.
42220
+ 8. **Clean up** \u2014 Remove worktree nodes via \`mesh_remove_node\` after their work is merged or no longer needed.
42221
+ 9. **Report** \u2014 Summarize what was done, what changed, any issues, and the branch convergence state.
41214
42222
 
41215
42223
  ## Failure Recovery
41216
42224
 
@@ -41220,7 +42228,7 @@ When a node agent stops unexpectedly, the daemon automatically enriches the syst
41220
42228
  - A recommendation: **retry**, **reassign**, or **escalate**
41221
42229
 
41222
42230
  Follow these recovery rules:
41223
- 1. **If "Retry recommended"**: Re-launch the session on the same node (\`mesh_launch_session\`), then resend the original task (\`mesh_send_task\`). The system message includes the original task text.
42231
+ 1. **If "Retry recommended"**: Check \`mesh_view_queue\` first \u2014 the daemon may have auto-requeued. If not, re-launch the session on the same node (\`mesh_launch_session\`), then resend the original task (\`mesh_send_task\`). The system message includes the original task text.
41224
42232
  2. **If "Max retries exceeded"**: Do NOT retry on the same node. Either reassign the task to a different node, or inform the user that the task requires manual intervention.
41225
42233
  3. **If no recovery context**: The stop may be intentional (normal completion). Use \`mesh_read_chat\` once to verify, then move on.
41226
42234
  4. **Always record what happened**: After handling a failure, briefly note the outcome in your report to the user.`;
@@ -41228,13 +42236,17 @@ Follow these recovery rules:
41228
42236
  });
41229
42237
  mesh_ledger_exports = {};
41230
42238
  __export2(mesh_ledger_exports, {
42239
+ MAX_LEDGER_SLICE_LIMIT: () => MAX_LEDGER_SLICE_LIMIT,
41231
42240
  appendLedgerEntry: () => appendLedgerEntry,
41232
42241
  appendRemoteLedgerEntries: () => appendRemoteLedgerEntries,
42242
+ buildTaskCompletionEvidence: () => buildTaskCompletionEvidence,
41233
42243
  getLedgerDir: () => getLedgerDir,
41234
42244
  getLedgerSummary: () => getLedgerSummary,
41235
42245
  getSessionRecoveryContext: () => getSessionRecoveryContext,
42246
+ isIntentionalCleanupStopEntry: () => isIntentionalCleanupStopEntry,
41236
42247
  meshLedgerEvents: () => meshLedgerEvents,
41237
- readLedgerEntries: () => readLedgerEntries
42248
+ readLedgerEntries: () => readLedgerEntries,
42249
+ readLedgerSlice: () => readLedgerSlice
41238
42250
  });
41239
42251
  init_mesh_ledger = __esm2({
41240
42252
  "src/mesh/mesh-ledger.ts"() {
@@ -41243,13 +42255,36 @@ Follow these recovery rules:
41243
42255
  LEDGER_DIR_NAME = "mesh-ledger";
41244
42256
  MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024;
41245
42257
  RECENT_FAILURE_WINDOW_MS = 30 * 60 * 1e3;
42258
+ DEFAULT_LEDGER_SLICE_LIMIT = 100;
42259
+ MAX_LEDGER_SLICE_LIMIT = 500;
41246
42260
  meshLedgerEvents = new import_events2.EventEmitter();
41247
42261
  }
41248
42262
  });
42263
+ mesh_work_queue_exports = {};
42264
+ __export2(mesh_work_queue_exports, {
42265
+ ACTIVE_MESH_QUEUE_STATUSES: () => ACTIVE_MESH_QUEUE_STATUSES,
42266
+ HISTORICAL_MESH_QUEUE_STATUSES: () => HISTORICAL_MESH_QUEUE_STATUSES,
42267
+ cancelTask: () => cancelTask,
42268
+ claimNextTask: () => claimNextTask,
42269
+ enqueueTask: () => enqueueTask,
42270
+ getMeshQueueStats: () => getMeshQueueStats,
42271
+ getQueue: () => getQueue,
42272
+ recordTaskAutoLaunch: () => recordTaskAutoLaunch,
42273
+ requeueTask: () => requeueTask,
42274
+ updateSessionTaskStatus: () => updateSessionTaskStatus,
42275
+ updateTaskStatus: () => updateTaskStatus
42276
+ });
41249
42277
  init_mesh_work_queue = __esm2({
41250
42278
  "src/mesh/mesh-work-queue.ts"() {
41251
42279
  "use strict";
41252
42280
  init_mesh_ledger();
42281
+ ACTIVE_MESH_QUEUE_STATUSES = ["pending", "assigned"];
42282
+ HISTORICAL_MESH_QUEUE_STATUSES = ["completed", "failed", "cancelled"];
42283
+ }
42284
+ });
42285
+ init_cli_detector = __esm2({
42286
+ "src/detection/cli-detector.ts"() {
42287
+ "use strict";
41253
42288
  }
41254
42289
  });
41255
42290
  init_logger = __esm2({
@@ -41258,7 +42293,7 @@ Follow these recovery rules:
41258
42293
  LEVEL_NUM = { debug: 0, info: 1, warn: 2, error: 3 };
41259
42294
  LEVEL_LABEL = { debug: "DBG", info: "INF", warn: "WRN", error: "ERR" };
41260
42295
  currentLevel = "info";
41261
- LOG_DIR = process.platform === "win32" ? path8.join(process.env.LOCALAPPDATA || process.env.APPDATA || path8.join(os22.homedir(), "AppData", "Local"), "adhdev", "logs") : process.platform === "darwin" ? path8.join(os22.homedir(), "Library", "Logs", "adhdev") : path8.join(os22.homedir(), ".local", "share", "adhdev", "logs");
42296
+ LOG_DIR = process.platform === "win32" ? path9.join(process.env.LOCALAPPDATA || process.env.APPDATA || path9.join(os32.homedir(), "AppData", "Local"), "adhdev", "logs") : process.platform === "darwin" ? path9.join(os32.homedir(), "Library", "Logs", "adhdev") : path9.join(os32.homedir(), ".local", "share", "adhdev", "logs");
41262
42297
  MAX_LOG_SIZE = 5 * 1024 * 1024;
41263
42298
  MAX_LOG_DAYS = 7;
41264
42299
  try {
@@ -41266,16 +42301,16 @@ Follow these recovery rules:
41266
42301
  } catch {
41267
42302
  }
41268
42303
  currentDate = getDateStr();
41269
- currentLogFile = path8.join(LOG_DIR, `daemon-${currentDate}.log`);
42304
+ currentLogFile = path9.join(LOG_DIR, `daemon-${currentDate}.log`);
41270
42305
  cleanOldLogs();
41271
42306
  try {
41272
- const oldLog = path8.join(LOG_DIR, "daemon.log");
42307
+ const oldLog = path9.join(LOG_DIR, "daemon.log");
41273
42308
  if (fs2.existsSync(oldLog)) {
41274
42309
  const stat22 = fs2.statSync(oldLog);
41275
42310
  const oldDate = stat22.mtime.toISOString().slice(0, 10);
41276
- fs2.renameSync(oldLog, path8.join(LOG_DIR, `daemon-${oldDate}.log`));
42311
+ fs2.renameSync(oldLog, path9.join(LOG_DIR, `daemon-${oldDate}.log`));
41277
42312
  }
41278
- const oldLogBackup = path8.join(LOG_DIR, "daemon.log.old");
42313
+ const oldLogBackup = path9.join(LOG_DIR, "daemon.log.old");
41279
42314
  if (fs2.existsSync(oldLogBackup)) {
41280
42315
  fs2.unlinkSync(oldLogBackup);
41281
42316
  }
@@ -41307,7 +42342,7 @@ Follow these recovery rules:
41307
42342
  }
41308
42343
  };
41309
42344
  interceptorInstalled = false;
41310
- LOG_PATH = path8.join(LOG_DIR, `daemon-${getDateStr()}.log`);
42345
+ LOG_PATH = path9.join(LOG_DIR, `daemon-${getDateStr()}.log`);
41311
42346
  }
41312
42347
  });
41313
42348
  mesh_events_exports = {};
@@ -41321,16 +42356,21 @@ Follow these recovery rules:
41321
42356
  init_mesh_events = __esm2({
41322
42357
  "src/mesh/mesh-events.ts"() {
41323
42358
  "use strict";
42359
+ init_config();
41324
42360
  init_mesh_config();
42361
+ init_cli_detector();
41325
42362
  init_logger();
41326
42363
  init_mesh_ledger();
41327
42364
  init_mesh_work_queue();
42365
+ remoteIdleSessions = /* @__PURE__ */ new Map();
41328
42366
  MAX_PENDING_EVENTS = 50;
41329
42367
  pendingMeshCoordinatorEvents = [];
41330
42368
  MESH_COORDINATOR_EVENTS = /* @__PURE__ */ new Set([
42369
+ "agent:generating_started",
41331
42370
  "agent:generating_completed",
41332
42371
  "agent:waiting_approval",
41333
42372
  "agent:stopped",
42373
+ "agent:ready",
41334
42374
  "monitor:long_generating"
41335
42375
  ]);
41336
42376
  EVENT_TO_LEDGER_KIND = {
@@ -41339,6 +42379,10 @@ Follow these recovery rules:
41339
42379
  "agent:stopped": "task_failed",
41340
42380
  "monitor:long_generating": "task_stalled"
41341
42381
  };
42382
+ INTENTIONAL_CLEANUP_STOP_SUPPRESSION_MS = 30 * 60 * 1e3;
42383
+ autoLaunchInProgress = /* @__PURE__ */ new Set();
42384
+ autoLaunchCooldownUntil = /* @__PURE__ */ new Map();
42385
+ AUTO_LAUNCH_COOLDOWN_MS = 5e3;
41342
42386
  }
41343
42387
  });
41344
42388
  init_debug_config = __esm2({
@@ -41783,6 +42827,7 @@ Follow these recovery rules:
41783
42827
  this.requirePromptEchoBeforeSubmit = resolvedConfig.requirePromptEchoBeforeSubmit;
41784
42828
  this.providerResolutionMeta = resolvedConfig.providerResolutionMeta;
41785
42829
  this.cliScripts = provider.scripts || {};
42830
+ this.scriptState = typeof this.cliScripts.createState === "function" ? this.cliScripts.createState() ?? null : null;
41786
42831
  const scriptNames = listCliScriptNames(this.cliScripts);
41787
42832
  if (scriptNames.length > 0) {
41788
42833
  LOG.info("CLI", `[${this.cliType}] CLI scripts: [${scriptNames.join(", ")}]`);
@@ -41865,6 +42910,8 @@ Follow these recovery rules:
41865
42910
  statusHistory = [];
41866
42911
  // ─── CLI Scripts (script-based parsing) ───
41867
42912
  cliScripts;
42913
+ /** Per-session opaque state object created by cliScripts.createState(), reset on stop. */
42914
+ scriptState = null;
41868
42915
  runtimeSettings = {};
41869
42916
  /** Full accumulated rendered PTY transcript for parser/readback use */
41870
42917
  accumulatedBuffer = "";
@@ -41941,9 +42988,13 @@ ${lastSnapshot}`;
41941
42988
  this.lastScreenChangeAt = 0;
41942
42989
  this.lastScreenSnapshotReadAt = Number.NEGATIVE_INFINITY;
41943
42990
  }
42991
+ getAccumulatedRawBufferCacheKey() {
42992
+ return this.accumulatedRawBuffer.replace(/\x1B\][^\x07]*(?:\x07|\x1B\\)/g, "").replace(/\x1B[P^_X][\s\S]*?(?:\x07|\x1B\\)/g, "").replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "");
42993
+ }
41944
42994
  getFreshParsedStatusCache() {
41945
42995
  const cached2 = this.parsedStatusCache;
41946
- if (cached2 && cached2.responseBuffer === this.responseBuffer && cached2.currentTurnScope === this.currentTurnScope && cached2.recentOutputBuffer === this.recentOutputBuffer && cached2.accumulatedBuffer === this.accumulatedBuffer && cached2.screenText === this.lastScreenText && cached2.currentStatus === this.currentStatus && cached2.activeModal === this.activeModal && cached2.cliName === this.cliName) {
42996
+ const accumulatedRawBufferKey = this.getAccumulatedRawBufferCacheKey();
42997
+ if (cached2 && cached2.responseBuffer === this.responseBuffer && cached2.currentTurnScope === this.currentTurnScope && cached2.recentOutputBuffer === this.recentOutputBuffer && cached2.accumulatedBuffer === this.accumulatedBuffer && cached2.accumulatedRawBufferKey === accumulatedRawBufferKey && cached2.screenText === this.lastScreenText && cached2.currentStatus === this.currentStatus && cached2.activeModal === this.activeModal && cached2.cliName === this.cliName) {
41947
42998
  return cached2.result;
41948
42999
  }
41949
43000
  return null;
@@ -42046,6 +43097,7 @@ ${lastSnapshot}`;
42046
43097
  this.cliScripts = scripts;
42047
43098
  this.parsedStatusCache = null;
42048
43099
  this.parseErrorMessage = null;
43100
+ this.scriptState = typeof scripts.createState === "function" ? scripts.createState() ?? null : null;
42049
43101
  const scriptNames = listCliScriptNames(scripts);
42050
43102
  LOG.info("CLI", `[${this.cliType}] CLI scripts injected: [${scriptNames.join(", ")}]`);
42051
43103
  }
@@ -42163,6 +43215,7 @@ ${lastSnapshot}`;
42163
43215
  this.ready = false;
42164
43216
  this.startupParseGate = false;
42165
43217
  this.spawnAt = 0;
43218
+ this.scriptState = null;
42166
43219
  this.onStatusChange?.();
42167
43220
  });
42168
43221
  this.spawnAt = Date.now();
@@ -42916,6 +43969,11 @@ ${lastSnapshot}`;
42916
43969
  };
42917
43970
  }
42918
43971
  // ─── Script Execution ──────────────────────────
43972
+ invokeCliScript(script, input) {
43973
+ const hasStateFactory = typeof this.cliScripts?.createState === "function";
43974
+ const expectsStateArgument = hasStateFactory || this.scriptState !== null || script.length >= 2;
43975
+ return expectsStateArgument ? script(this.scriptState, input) : script(input);
43976
+ }
42919
43977
  runParseSession() {
42920
43978
  if (typeof this.cliScripts?.parseSession !== "function") {
42921
43979
  this.parseErrorMessage = `${this.cliType} parseSession unavailable`;
@@ -42936,7 +43994,10 @@ ${lastSnapshot}`;
42936
43994
  scope: this.currentTurnScope,
42937
43995
  runtimeSettings: this.runtimeSettings
42938
43996
  });
42939
- const session = this.cliScripts.parseSession({ ...input, tail, tailScreen: buildCliScreenSnapshot(tail) });
43997
+ const session = this.invokeCliScript(
43998
+ this.cliScripts.parseSession,
43999
+ { ...input, tail, tailScreen: buildCliScreenSnapshot(tail) }
44000
+ );
42940
44001
  this.parseErrorMessage = null;
42941
44002
  return session && typeof session === "object" ? session : null;
42942
44003
  } catch (e) {
@@ -42950,7 +44011,7 @@ ${lastSnapshot}`;
42950
44011
  if (!this.cliScripts?.detectStatus) return null;
42951
44012
  try {
42952
44013
  const screenText = this.terminalScreen.getText();
42953
- const status = this.cliScripts.detectStatus({
44014
+ const status = this.invokeCliScript(this.cliScripts.detectStatus, {
42954
44015
  tail: text.slice(-500),
42955
44016
  screenText,
42956
44017
  rawBuffer: this.accumulatedRawBuffer,
@@ -42969,7 +44030,7 @@ ${lastSnapshot}`;
42969
44030
  try {
42970
44031
  const screenText = this.terminalScreen.getText();
42971
44032
  const buffer = screenText || this.accumulatedBuffer;
42972
- return this.cliScripts.parseApproval({
44033
+ return this.invokeCliScript(this.cliScripts.parseApproval, {
42973
44034
  buffer,
42974
44035
  screenText,
42975
44036
  rawBuffer: this.accumulatedRawBuffer,
@@ -43025,7 +44086,8 @@ ${lastSnapshot}`;
43025
44086
  const screenText = this.readTerminalScreenText();
43026
44087
  const parseScreenText = this.getParseScreenText(screenText);
43027
44088
  const cached2 = this.parsedStatusCache;
43028
- if (cached2 && cached2.responseBuffer === this.responseBuffer && cached2.currentTurnScope === this.currentTurnScope && cached2.recentOutputBuffer === this.recentOutputBuffer && cached2.accumulatedBuffer === this.accumulatedBuffer && cached2.screenText === parseScreenText && cached2.currentStatus === this.currentStatus && cached2.activeModal === this.activeModal && cached2.cliName === this.cliName) {
44089
+ const accumulatedRawBufferKey = this.getAccumulatedRawBufferCacheKey();
44090
+ if (cached2 && cached2.responseBuffer === this.responseBuffer && cached2.currentTurnScope === this.currentTurnScope && cached2.recentOutputBuffer === this.recentOutputBuffer && cached2.accumulatedBuffer === this.accumulatedBuffer && cached2.accumulatedRawBufferKey === accumulatedRawBufferKey && cached2.screenText === parseScreenText && cached2.currentStatus === this.currentStatus && cached2.activeModal === this.activeModal && cached2.cliName === this.cliName) {
43029
44091
  return cached2.result;
43030
44092
  }
43031
44093
  const parsed = this.runParseSession();
@@ -43053,6 +44115,7 @@ ${lastSnapshot}`;
43053
44115
  currentTurnScope: this.currentTurnScope,
43054
44116
  recentOutputBuffer: this.recentOutputBuffer,
43055
44117
  accumulatedBuffer: this.accumulatedBuffer,
44118
+ accumulatedRawBufferKey,
43056
44119
  screenText: parseScreenText,
43057
44120
  currentStatus: this.currentStatus,
43058
44121
  activeModal: this.activeModal,
@@ -43077,7 +44140,7 @@ ${lastSnapshot}`;
43077
44140
  scope: this.currentTurnScope,
43078
44141
  runtimeSettings: this.runtimeSettings
43079
44142
  });
43080
- return await Promise.resolve(fn({
44143
+ return await Promise.resolve(this.invokeCliScript(fn, {
43081
44144
  ...input,
43082
44145
  args: args && typeof args === "object" ? { ...args } : {}
43083
44146
  }));
@@ -43813,7 +44876,7 @@ ${lastSnapshot}`;
43813
44876
  }
43814
44877
  });
43815
44878
  init_repo_mesh_types();
43816
- execFileAsync = (0, import_util4.promisify)(import_child_process3.execFile);
44879
+ execFileAsync = (0, import_util4.promisify)(import_child_process4.execFile);
43817
44880
  DEFAULT_TIMEOUT_MS = 5e3;
43818
44881
  DEFAULT_MAX_BUFFER = 1024 * 1024;
43819
44882
  GitCommandError = class extends Error {
@@ -44072,6 +45135,34 @@ ${lastSnapshot}`;
44072
45135
  init_mesh_ledger();
44073
45136
  init_mesh_work_queue();
44074
45137
  init_mesh_events();
45138
+ NO_FALLBACK_REASON = "Repo Mesh command/data-plane is P2P-only; WS/REST command fallback is intentionally disabled to preserve the transport boundary.";
45139
+ P2P_NEXT_ACTION = "Check daemon/P2P health, wait briefly for connection establishment, then do one bounded retry or requeue the mesh task after clearing stale target session metadata.";
45140
+ NON_P2P_NEXT_ACTION = "Inspect the provider/command error and fix the underlying logic or configuration before retrying.";
45141
+ P2pRelayFailureError = class extends Error {
45142
+ code;
45143
+ reason;
45144
+ transport;
45145
+ recoverable;
45146
+ retryRecommended;
45147
+ nextAction;
45148
+ noFallbackReason;
45149
+ command;
45150
+ targetDaemonId;
45151
+ constructor(message, context = {}) {
45152
+ super(message);
45153
+ this.name = "P2pRelayFailureError";
45154
+ const payload = buildP2pRelayFailurePayload(message, context);
45155
+ this.code = payload.code;
45156
+ this.reason = payload.reason;
45157
+ this.transport = payload.transport;
45158
+ this.recoverable = payload.recoverable;
45159
+ this.retryRecommended = payload.retryRecommended;
45160
+ this.nextAction = payload.nextAction;
45161
+ this.noFallbackReason = payload.noFallbackReason;
45162
+ this.command = context.command;
45163
+ this.targetDaemonId = context.targetDaemonId;
45164
+ }
45165
+ };
44075
45166
  init_config();
44076
45167
  DEFAULT_STATE = {
44077
45168
  recentActivity: [],
@@ -44083,6 +45174,7 @@ ${lastSnapshot}`;
44083
45174
  };
44084
45175
  BUILTIN_IDE_DEFINITIONS = [];
44085
45176
  registeredIDEs = /* @__PURE__ */ new Map();
45177
+ init_cli_detector();
44086
45178
  LIVE_LIFECYCLES = /* @__PURE__ */ new Set(["starting", "running", "stopping", "interrupted"]);
44087
45179
  DEFAULT_ACTIVE_CHAT_POLL_STATUSES = /* @__PURE__ */ new Set([
44088
45180
  "generating",
@@ -47814,9 +48906,12 @@ ${effect.notification.body || ""}`.trim();
47814
48906
  }
47815
48907
  };
47816
48908
  init_provider_cli_adapter();
48909
+ init_cli_detector();
47817
48910
  init_config();
47818
48911
  init_provider_cli_adapter();
47819
48912
  init_logger();
48913
+ COMPLETED_FINALIZATION_RETRY_MS = 1e3;
48914
+ COMPLETED_FINALIZATION_MAX_WAIT_MS = 3e4;
47820
48915
  IMAGE_MIME_EXTENSIONS = {
47821
48916
  "image/png": ".png",
47822
48917
  "image/jpeg": ".jpg",
@@ -48002,10 +49097,12 @@ ${effect.notification.body || ""}`.trim();
48002
49097
  const mergedMessages = this.mergeConversationMessages(parsedMessages);
48003
49098
  const canonicalBackedHistory = this.syncCanonicalSavedHistoryIfNeeded();
48004
49099
  const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
49100
+ const parsedChatStatus = typeof parsedStatus?.status === "string" && parsedStatus.status.trim() ? parsedStatus.status.trim() : void 0;
49101
+ const suppressStaleParsedBusyStatus = this.shouldSuppressStaleParsedBusyStatus(parsedStatus, adapterStatus);
48005
49102
  if (parsedMessages.length > 0) {
48006
49103
  const shouldSkipReplayPersist = this.suppressIdleHistoryReplay && adapterStatus.status === "idle" && parsedStatus?.status === "idle";
48007
49104
  let messagesToSave = parsedMessages;
48008
- if (parsedStatus?.status === "generating" || parsedStatus?.status === "long_generating") {
49105
+ if (!suppressStaleParsedBusyStatus && (parsedChatStatus === "generating" || parsedChatStatus === "long_generating")) {
48009
49106
  const lastIdx = messagesToSave.length - 1;
48010
49107
  if (lastIdx >= 0 && messagesToSave[lastIdx]?.role === "assistant") {
48011
49108
  messagesToSave = messagesToSave.slice(0, lastIdx);
@@ -48039,6 +49136,7 @@ ${effect.notification.body || ""}`.trim();
48039
49136
  summaryMetadata: this.summaryMetadata,
48040
49137
  controlValues: this.controlValues
48041
49138
  });
49139
+ const activeChatStatus = parseErrorMessage ? "error" : autoApproveActive && parsedStatus?.status === "waiting_approval" ? "generating" : adapterStatus.status !== "idle" ? visibleStatus : suppressStaleParsedBusyStatus ? visibleStatus : parsedChatStatus || visibleStatus;
48042
49140
  return {
48043
49141
  type: this.type,
48044
49142
  name: this.provider.name,
@@ -48048,7 +49146,7 @@ ${effect.notification.body || ""}`.trim();
48048
49146
  activeChat: {
48049
49147
  id: `${this.type}_${this.workingDir}`,
48050
49148
  title: parsedStatus?.title || dirName,
48051
- status: parseErrorMessage ? "error" : autoApproveActive && parsedStatus?.status === "waiting_approval" ? "generating" : adapterStatus.status !== "idle" ? visibleStatus : parsedStatus?.status || visibleStatus,
49149
+ status: activeChatStatus,
48052
49150
  messages: mergedMessages,
48053
49151
  activeModal: autoApproveActive ? null : parsedStatus?.activeModal ?? adapterStatus.activeModal,
48054
49152
  inputContent: ""
@@ -48178,6 +49276,102 @@ ${effect.notification.body || ""}`.trim();
48178
49276
  }
48179
49277
  this.applyProviderResponse(parsed.payload, { phase: "immediate" });
48180
49278
  }
49279
+ completionHasFinalAssistantMessage(messages) {
49280
+ const visibleMessages = (Array.isArray(messages) ? messages : []).filter((message) => isUserFacingChatMessage(message));
49281
+ const lastVisible = visibleMessages[visibleMessages.length - 1];
49282
+ const role = typeof lastVisible?.role === "string" ? lastVisible.role.trim().toLowerCase() : "";
49283
+ const content = lastVisible ? flattenContent(lastVisible.content).trim() : "";
49284
+ return role === "assistant" && !!content;
49285
+ }
49286
+ hasAdapterPendingResponse() {
49287
+ const adapterAny = this.adapter;
49288
+ if (adapterAny?.isWaitingForResponse === true) return true;
49289
+ if (adapterAny?.currentTurnScope) return true;
49290
+ try {
49291
+ if (typeof this.adapter.isProcessing === "function" && this.adapter.isProcessing()) return true;
49292
+ } catch {
49293
+ }
49294
+ try {
49295
+ const partial2 = typeof this.adapter.getPartialResponse === "function" ? this.adapter.getPartialResponse() : "";
49296
+ if (typeof partial2 === "string" && partial2.trim()) return true;
49297
+ } catch {
49298
+ }
49299
+ return false;
49300
+ }
49301
+ shouldSuppressStaleParsedBusyStatus(parsedStatus, adapterStatus) {
49302
+ const parsedRawStatus = typeof parsedStatus?.status === "string" ? parsedStatus.status.trim() : "";
49303
+ const adapterRawStatus = typeof adapterStatus?.status === "string" ? adapterStatus.status.trim() : "";
49304
+ if (!isCliGeneratingLikeStatus(parsedRawStatus)) return false;
49305
+ if (adapterRawStatus !== "idle") return false;
49306
+ if (hasNonEmptyCliModalButtons(parsedStatus?.activeModal ?? parsedStatus?.modal)) return false;
49307
+ return !this.hasAdapterPendingResponse();
49308
+ }
49309
+ getCompletedFinalizationBlockReason(latestVisibleStatus) {
49310
+ if (latestVisibleStatus !== "idle") return `status:${latestVisibleStatus}`;
49311
+ const adapterAny = this.adapter;
49312
+ if (adapterAny?.isWaitingForResponse === true) return "adapter_waiting_for_response";
49313
+ if (adapterAny?.currentTurnScope) return "adapter_turn_scope_active";
49314
+ const partial2 = typeof this.adapter.getPartialResponse === "function" ? this.adapter.getPartialResponse() : "";
49315
+ if (typeof partial2 === "string" && partial2.trim()) return "partial_response_pending";
49316
+ let parsed;
49317
+ try {
49318
+ parsed = this.adapter.getScriptParsedStatus();
49319
+ } catch (error48) {
49320
+ return `parse_error:${error48?.message || String(error48)}`;
49321
+ }
49322
+ const parsedStatus = typeof parsed?.status === "string" ? parsed.status : "unknown";
49323
+ if (parsedStatus !== "idle") return `parsed_status:${parsedStatus}`;
49324
+ if (parsed?.activeModal || parsed?.modal) return "parsed_modal_active";
49325
+ if (!this.completionHasFinalAssistantMessage(parsed?.messages)) return "missing_final_assistant";
49326
+ return null;
49327
+ }
49328
+ scheduleCompletedDebounceFlush(delayMs) {
49329
+ if (this.completedDebounceTimer) clearTimeout(this.completedDebounceTimer);
49330
+ this.completedDebounceTimer = setTimeout(() => this.flushCompletedDebounceIfFinalized(), delayMs);
49331
+ }
49332
+ flushCompletedDebounceIfFinalized() {
49333
+ const pending = this.completedDebouncePending;
49334
+ if (!pending) {
49335
+ this.completedDebounceTimer = null;
49336
+ return;
49337
+ }
49338
+ const latestStatus = this.adapter.getStatus({ allowParse: false });
49339
+ const latestAutoApproveActive = latestStatus.status === "waiting_approval" && this.shouldAutoApprove();
49340
+ const latestVisibleStatus = latestAutoApproveActive ? "generating" : latestStatus.status;
49341
+ if (latestVisibleStatus !== "idle") {
49342
+ LOG.info("CLI", `[${this.type}] cancelled pending completed (resumed ${latestVisibleStatus})`);
49343
+ this.completedDebouncePending = null;
49344
+ this.completedDebounceTimer = null;
49345
+ return;
49346
+ }
49347
+ const blockReason = this.getCompletedFinalizationBlockReason(latestVisibleStatus);
49348
+ if (blockReason) {
49349
+ const waitedMs = Date.now() - pending.firstObservedAt;
49350
+ if (waitedMs < COMPLETED_FINALIZATION_MAX_WAIT_MS) {
49351
+ if (pending.loggedBlockReason !== blockReason) {
49352
+ LOG.info("CLI", `[${this.type}] waiting to emit completed until transcript finalizes (${blockReason})`);
49353
+ pending.loggedBlockReason = blockReason;
49354
+ }
49355
+ this.scheduleCompletedDebounceFlush(COMPLETED_FINALIZATION_RETRY_MS);
49356
+ return;
49357
+ }
49358
+ LOG.warn("CLI", `[${this.type}] suppressed completed event after ${waitedMs}ms without finalized assistant turn (${blockReason})`);
49359
+ this.completedDebouncePending = null;
49360
+ this.completedDebounceTimer = null;
49361
+ this.generatingStartedAt = 0;
49362
+ return;
49363
+ }
49364
+ LOG.info("CLI", `[${this.type}] completed in ${pending.duration}s`);
49365
+ this.pushEvent({
49366
+ event: "agent:generating_completed",
49367
+ chatTitle: pending.chatTitle,
49368
+ duration: pending.duration,
49369
+ timestamp: pending.timestamp
49370
+ });
49371
+ this.completedDebouncePending = null;
49372
+ this.completedDebounceTimer = null;
49373
+ this.generatingStartedAt = 0;
49374
+ }
48181
49375
  maybeAutoApproveStatus(adapterStatus, now = Date.now()) {
48182
49376
  const autoApproveActive = adapterStatus?.status === "waiting_approval" && this.shouldAutoApprove();
48183
49377
  if (autoApproveActive && !this.autoApproveBusy) {
@@ -48275,27 +49469,11 @@ ${effect.notification.body || ""}`.trim();
48275
49469
  this.generatingDebouncePending = null;
48276
49470
  this.generatingStartedAt = 0;
48277
49471
  } else {
48278
- if (this.completedDebounceTimer) clearTimeout(this.completedDebounceTimer);
48279
- this.completedDebouncePending = { chatTitle, duration: duration3, timestamp: now };
48280
- this.completedDebounceTimer = setTimeout(() => {
48281
- if (this.completedDebouncePending) {
48282
- const latestStatus = this.adapter.getStatus({ allowParse: false });
48283
- const latestAutoApproveActive = latestStatus.status === "waiting_approval" && this.shouldAutoApprove();
48284
- const latestVisibleStatus = latestAutoApproveActive ? "generating" : latestStatus.status;
48285
- if (latestVisibleStatus !== "idle") {
48286
- LOG.info("CLI", `[${this.type}] cancelled pending completed (resumed ${latestVisibleStatus})`);
48287
- this.completedDebouncePending = null;
48288
- this.completedDebounceTimer = null;
48289
- return;
48290
- }
48291
- LOG.info("CLI", `[${this.type}] completed in ${this.completedDebouncePending.duration}s`);
48292
- this.pushEvent({ event: "agent:generating_completed", ...this.completedDebouncePending });
48293
- this.completedDebouncePending = null;
48294
- this.generatingStartedAt = 0;
48295
- }
48296
- this.completedDebounceTimer = null;
48297
- }, 3e3);
49472
+ this.completedDebouncePending = { chatTitle, duration: duration3, timestamp: now, firstObservedAt: now };
49473
+ this.scheduleCompletedDebounceFlush(3e3);
48298
49474
  }
49475
+ } else if (newStatus === "idle" && this.lastStatus === "starting") {
49476
+ this.pushEvent({ event: "agent:ready", chatTitle, timestamp: now });
48299
49477
  } else if (newStatus === "stopped") {
48300
49478
  if (this.generatingDebounceTimer) {
48301
49479
  clearTimeout(this.generatingDebounceTimer);
@@ -52080,6 +53258,7 @@ Run 'adhdev doctor' for detailed diagnostics.`
52080
53258
  };
52081
53259
  _providerLoader = null;
52082
53260
  init_config();
53261
+ init_cli_detector();
52083
53262
  init_logger();
52084
53263
  LOG_DIR2 = process.platform === "win32" ? path21.join(process.env.LOCALAPPDATA || process.env.APPDATA || path21.join(os16.homedir(), "AppData", "Local"), "adhdev", "logs") : process.platform === "darwin" ? path21.join(os16.homedir(), "Library", "Logs", "adhdev") : path21.join(os16.homedir(), ".local", "share", "adhdev", "logs");
52085
53264
  MAX_FILE_SIZE = 5 * 1024 * 1024;
@@ -52128,6 +53307,11 @@ Run 'adhdev doctor' for detailed diagnostics.`
52128
53307
  stable: "https://api.adhf.dev",
52129
53308
  preview: "https://api-preview.adhf.dev"
52130
53309
  };
53310
+ REFINE_VALIDATION_CATEGORIES = ["typecheck", "test", "lint", "build"];
53311
+ REFINE_VALIDATION_TIMEOUT_MS = 12e4;
53312
+ REFINE_VALIDATION_OUTPUT_LIMIT_BYTES = 128 * 1024;
53313
+ REFINE_VALIDATION_SUMMARY_CHARS = 2e3;
53314
+ REFINE_VALIDATION_MAX_COMMANDS = 4;
52131
53315
  CHAT_COMMANDS = [
52132
53316
  "send_chat",
52133
53317
  "new_chat",
@@ -52191,9 +53375,191 @@ Run 'adhdev doctor' for detailed diagnostics.`
52191
53375
  if (record2?.meta?.meshNodeId === nodeId) return true;
52192
53376
  return false;
52193
53377
  }
53378
+ async cleanupLocalWorktreeNode(args) {
53379
+ const workspace = typeof args.node?.workspace === "string" ? args.node.workspace.trim() : "";
53380
+ if (!workspace) {
53381
+ return {
53382
+ success: false,
53383
+ code: "mesh_worktree_cleanup_missing_workspace",
53384
+ error: `Worktree node '${args.nodeId}' is missing workspace metadata`,
53385
+ recoveryHint: "Inspect the mesh node record before removing it, or remove stale metadata manually only after confirming no managed worktree remains."
53386
+ };
53387
+ }
53388
+ const worktreeExists = fs10.existsSync(workspace);
53389
+ const sourceNode = args.node?.clonedFromNodeId ? args.mesh?.nodes?.find((n) => n.id === args.node.clonedFromNodeId || n.nodeId === args.node.clonedFromNodeId) : args.mesh?.nodes?.find((n) => !n.isLocalWorktree);
53390
+ const repoRoot = typeof sourceNode?.repoRoot === "string" && sourceNode.repoRoot.trim() ? sourceNode.repoRoot.trim() : typeof sourceNode?.workspace === "string" && sourceNode.workspace.trim() ? sourceNode.workspace.trim() : "";
53391
+ if (!worktreeExists) {
53392
+ return { success: true, skipped: true, removedPath: workspace, repoRoot: repoRoot || void 0, reason: "worktree_path_missing" };
53393
+ }
53394
+ if (!repoRoot || !fs10.existsSync(repoRoot)) {
53395
+ return {
53396
+ success: false,
53397
+ code: "mesh_worktree_cleanup_missing_source_repo",
53398
+ error: `Refusing to remove worktree '${workspace}' because the source repo root is unavailable`,
53399
+ recoveryHint: "Run mesh_remove_node from the machine that owns the source repo, or verify the source node metadata before retrying."
53400
+ };
53401
+ }
53402
+ if (typeof args.node?.worktreeBranch !== "string" || !args.node.worktreeBranch.trim()) {
53403
+ return {
53404
+ success: false,
53405
+ code: "mesh_worktree_cleanup_missing_branch",
53406
+ error: `Refusing to remove worktree '${workspace}' because worktreeBranch metadata is missing`,
53407
+ recoveryHint: "Confirm this is an ADHDev-managed worktree before removing it manually; managed worktree nodes include worktreeBranch metadata."
53408
+ };
53409
+ }
53410
+ const { resolveWorktreePath: resolveWorktreePath2, listWorktrees: listWorktrees2, removeWorktree: removeWorktree2 } = await Promise.resolve().then(() => (init_git_worktree(), git_worktree_exports));
53411
+ const normalizePath2 = (value) => {
53412
+ const resolved = (0, import_path7.resolve)(value);
53413
+ try {
53414
+ return fs10.realpathSync(resolved);
53415
+ } catch {
53416
+ return resolved;
53417
+ }
53418
+ };
53419
+ const expectedPath = normalizePath2(resolveWorktreePath2(repoRoot, String(args.mesh?.name || args.mesh?.id || "mesh"), args.node.worktreeBranch));
53420
+ const actualPath = normalizePath2(workspace);
53421
+ if (actualPath !== expectedPath) {
53422
+ return {
53423
+ success: false,
53424
+ code: "mesh_worktree_cleanup_unexpected_path",
53425
+ error: `Refusing to remove worktree '${workspace}' because it is not at the expected managed path '${expectedPath}'`,
53426
+ recoveryHint: "Use git worktree list/status to inspect the path. Retry only after confirming the mesh node metadata points to an ADHDev-managed worktree."
53427
+ };
53428
+ }
53429
+ const entries = await listWorktrees2(repoRoot);
53430
+ const managedEntry = entries.find((entry) => normalizePath2(entry.path) === actualPath);
53431
+ if (!managedEntry) {
53432
+ return {
53433
+ success: false,
53434
+ code: "mesh_worktree_cleanup_not_registered",
53435
+ error: `Refusing to remove '${workspace}' because it is not registered in git worktree list for '${repoRoot}'`,
53436
+ recoveryHint: "Inspect git worktree list --porcelain from the source repo. If the path was already removed, prune git worktrees before retrying."
53437
+ };
53438
+ }
53439
+ if (managedEntry.branch && managedEntry.branch !== args.node.worktreeBranch) {
53440
+ return {
53441
+ success: false,
53442
+ code: "mesh_worktree_cleanup_branch_mismatch",
53443
+ error: `Refusing to remove '${workspace}' because git reports branch '${managedEntry.branch}', expected '${args.node.worktreeBranch}'`,
53444
+ recoveryHint: "Inspect the worktree branch and mesh metadata before retrying cleanup."
53445
+ };
53446
+ }
53447
+ const forceFallbackConvergence = await this.getWorktreeForceCleanupConvergence({
53448
+ repoRoot,
53449
+ workspace,
53450
+ node: args.node
53451
+ });
53452
+ try {
53453
+ const result = await removeWorktree2(repoRoot, workspace, {
53454
+ requireClean: true,
53455
+ allowSubmoduleForceFallback: forceFallbackConvergence.allow
53456
+ });
53457
+ return {
53458
+ success: true,
53459
+ removedPath: result.removedPath,
53460
+ repoRoot,
53461
+ ...result.fallback ? {
53462
+ fallback: result.fallback,
53463
+ forced: result.forced,
53464
+ reason: result.reason,
53465
+ convergence: forceFallbackConvergence
53466
+ } : {}
53467
+ };
53468
+ } catch (e) {
53469
+ const message = String(e?.message || e || "worktree cleanup failed");
53470
+ const dirty = message.includes("dirty worktree") || message.includes("local changes");
53471
+ const submoduleForceBlocked = /working trees containing submodules cannot be moved or removed/i.test(message) && !forceFallbackConvergence.allow;
53472
+ return {
53473
+ success: false,
53474
+ code: dirty ? "mesh_worktree_cleanup_dirty" : submoduleForceBlocked ? "mesh_worktree_cleanup_force_fallback_blocked" : "mesh_worktree_cleanup_failed",
53475
+ error: submoduleForceBlocked ? `${message}; refusing --force fallback because convergence could not be verified: ${forceFallbackConvergence.error || "unknown convergence state"}` : message,
53476
+ recoveryHint: dirty ? "Commit, stash, or intentionally discard the worktree changes before retrying mesh_remove_node. The mesh registry entry is preserved until cleanup is safe." : submoduleForceBlocked ? "Verify the worktree branch is merged/contained in the source default branch (for example origin/main) or mark the node with a safe branchConvergence final state before retrying. The mesh registry entry is preserved." : "Inspect git worktree status/list from the source repo and retry after resolving the reported cleanup failure.",
53477
+ ...submoduleForceBlocked ? { convergence: forceFallbackConvergence } : {}
53478
+ };
53479
+ }
53480
+ }
53481
+ async getWorktreeForceCleanupConvergence(args) {
53482
+ const metadataStatus = typeof args.node?.branchConvergence?.status === "string" ? args.node.branchConvergence.status : "";
53483
+ if (metadataStatus === "merged_to_main" || metadataStatus === "cleanup_candidate") {
53484
+ return { allow: true, status: metadataStatus, source: "node_branch_convergence" };
53485
+ }
53486
+ const { execFile: execFile3 } = await import("child_process");
53487
+ const { promisify: promisify3 } = await import("util");
53488
+ const execFileAsync3 = promisify3(execFile3);
53489
+ const runGit2 = async (gitArgs, cwd) => {
53490
+ const { stdout } = await execFileAsync3("git", gitArgs, {
53491
+ cwd,
53492
+ encoding: "utf8",
53493
+ timeout: 3e4,
53494
+ maxBuffer: 4 * 1024 * 1024,
53495
+ windowsHide: true
53496
+ });
53497
+ return String(stdout || "").trim();
53498
+ };
53499
+ let head = "";
53500
+ try {
53501
+ head = await runGit2(["rev-parse", "HEAD"], args.workspace);
53502
+ } catch (e) {
53503
+ return { allow: false, error: `could not resolve worktree HEAD: ${e?.message || e}` };
53504
+ }
53505
+ if (!head) return { allow: false, error: "worktree HEAD is empty" };
53506
+ const candidateRefs = [];
53507
+ try {
53508
+ const defaultBranch = await runGit2(["branch", "--show-current"], args.repoRoot);
53509
+ if (defaultBranch) {
53510
+ candidateRefs.push(defaultBranch, `origin/${defaultBranch}`);
53511
+ }
53512
+ } catch {
53513
+ }
53514
+ candidateRefs.push("origin/main", "origin/master", "main", "master");
53515
+ const seen = /* @__PURE__ */ new Set();
53516
+ const checkedRefs = [];
53517
+ for (const ref of candidateRefs) {
53518
+ if (!ref || seen.has(ref)) continue;
53519
+ seen.add(ref);
53520
+ let commit = "";
53521
+ try {
53522
+ commit = await runGit2(["rev-parse", "--verify", `${ref}^{commit}`], args.repoRoot);
53523
+ } catch {
53524
+ continue;
53525
+ }
53526
+ checkedRefs.push(ref);
53527
+ try {
53528
+ await runGit2(["merge-base", "--is-ancestor", head, commit], args.repoRoot);
53529
+ return { allow: true, status: "merged_to_default_ref", source: "git_merge_base", ref };
53530
+ } catch {
53531
+ }
53532
+ }
53533
+ return {
53534
+ allow: false,
53535
+ status: metadataStatus || void 0,
53536
+ error: checkedRefs.length ? `worktree HEAD is not contained in checked refs: ${checkedRefs.join(", ")}` : "no default/main refs were available for convergence verification"
53537
+ };
53538
+ }
52194
53539
  isCompletedHostedSession(record2) {
52195
53540
  return record2?.lifecycle === "stopped" || record2?.lifecycle === "failed" || record2?.lifecycle === "interrupted";
52196
53541
  }
53542
+ async recordIntentionalMeshSessionStop(args) {
53543
+ try {
53544
+ const { appendLedgerEntry: appendLedgerEntry2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
53545
+ appendLedgerEntry2(args.meshId, {
53546
+ kind: "session_stopped",
53547
+ nodeId: args.nodeId,
53548
+ sessionId: args.sessionId,
53549
+ payload: {
53550
+ intentional: true,
53551
+ reason: "operator_cleanup",
53552
+ intentionalStopReason: "operator_cleanup",
53553
+ source: args.source,
53554
+ cleanupMode: args.mode,
53555
+ action: args.action,
53556
+ workspace: typeof args.node?.workspace === "string" ? args.node.workspace : void 0
53557
+ }
53558
+ });
53559
+ } catch (e) {
53560
+ LOG.warn("MeshCleanup", `Failed to record intentional cleanup stop for ${args.sessionId}: ${e?.message || e}`);
53561
+ }
53562
+ }
52197
53563
  async cleanupMeshSessions(args) {
52198
53564
  if (args.mode === "preserve") {
52199
53565
  return { success: true, mode: "preserve", matchedCount: 0, stoppedSessionIds: [], deletedSessionIds: [], skippedSessionIds: [] };
@@ -52210,6 +53576,21 @@ Run 'adhdev doctor' for detailed diagnostics.`
52210
53576
  const deleteUnsupportedSessionIds = [];
52211
53577
  const recordsRemainSessionIds = [];
52212
53578
  const errors = [];
53579
+ const cleanupSource = args.source || "mesh_cleanup_sessions";
53580
+ const markedIntentionalStopSessionIds = /* @__PURE__ */ new Set();
53581
+ const markIntentionalStop = async (sessionId, action) => {
53582
+ if (args.dryRun || markedIntentionalStopSessionIds.has(sessionId)) return;
53583
+ markedIntentionalStopSessionIds.add(sessionId);
53584
+ await this.recordIntentionalMeshSessionStop({
53585
+ meshId: args.meshId,
53586
+ nodeId: args.nodeId,
53587
+ node: args.node,
53588
+ sessionId,
53589
+ mode: args.mode,
53590
+ source: cleanupSource,
53591
+ action
53592
+ });
53593
+ };
52213
53594
  const matchedBySurfaceKind = {
52214
53595
  live_runtime: 0,
52215
53596
  recovery_snapshot: 0,
@@ -52232,7 +53613,10 @@ Run 'adhdev doctor' for detailed diagnostics.`
52232
53613
  try {
52233
53614
  if (args.mode === "stop") {
52234
53615
  if (!completed) {
52235
- if (!args.dryRun) await this.deps.sessionHostControl.stopSession(sessionId);
53616
+ if (!args.dryRun) {
53617
+ await markIntentionalStop(sessionId, "stop_session");
53618
+ await this.deps.sessionHostControl.stopSession(sessionId);
53619
+ }
52236
53620
  stoppedSessionIds.push(sessionId);
52237
53621
  } else {
52238
53622
  skippedSessionIds.push(sessionId);
@@ -52249,6 +53633,7 @@ Run 'adhdev doctor' for detailed diagnostics.`
52249
53633
  continue;
52250
53634
  }
52251
53635
  if (args.mode === "stop_and_delete") {
53636
+ if (!completed) await markIntentionalStop(sessionId, "delete_session_force");
52252
53637
  if (!args.dryRun) await this.deps.sessionHostControl.deleteSession(sessionId, { force: true });
52253
53638
  deletedSessionIds.push(sessionId);
52254
53639
  continue;
@@ -52260,6 +53645,7 @@ Run 'adhdev doctor' for detailed diagnostics.`
52260
53645
  recordsRemainSessionIds.push(sessionId);
52261
53646
  if (args.mode === "stop_and_delete" && !completed) {
52262
53647
  try {
53648
+ await markIntentionalStop(sessionId, "stop_session");
52263
53649
  await this.deps.sessionHostControl.stopSession(sessionId);
52264
53650
  stoppedSessionIds.push(sessionId);
52265
53651
  } catch (stopError) {
@@ -53014,6 +54400,91 @@ Run 'adhdev doctor' for detailed diagnostics.`
53014
54400
  return { success: false, error: e.message };
53015
54401
  }
53016
54402
  }
54403
+ case "get_mesh_ledger_slice": {
54404
+ const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
54405
+ if (!meshId) return { success: false, error: "meshId required" };
54406
+ try {
54407
+ const { readLedgerSlice: readLedgerSlice2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
54408
+ const kind = Array.isArray(args?.kind) ? args.kind.filter((k) => typeof k === "string") : void 0;
54409
+ const slice = readLedgerSlice2(meshId, {
54410
+ afterId: typeof args?.afterId === "string" ? args.afterId : void 0,
54411
+ since: typeof args?.since === "string" ? args.since : void 0,
54412
+ kind,
54413
+ limit: typeof args?.limit === "number" ? args.limit : void 0
54414
+ });
54415
+ return { success: true, slice };
54416
+ } catch (e) {
54417
+ return { success: false, error: e.message };
54418
+ }
54419
+ }
54420
+ case "import_mesh_ledger_slice": {
54421
+ const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
54422
+ if (!meshId) return { success: false, error: "meshId required" };
54423
+ try {
54424
+ const { appendRemoteLedgerEntries: appendRemoteLedgerEntries2, getLedgerSummary: getLedgerSummary2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
54425
+ const entries = Array.isArray(args?.entries) ? args.entries : Array.isArray(args?.slice?.entries) ? args.slice.entries : [];
54426
+ const result = appendRemoteLedgerEntries2(meshId, entries);
54427
+ return { success: true, result, summary: getLedgerSummary2(meshId) };
54428
+ } catch (e) {
54429
+ return { success: false, error: e.message };
54430
+ }
54431
+ }
54432
+ case "get_mesh_queue": {
54433
+ const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
54434
+ if (!meshId) return { success: false, error: "meshId required" };
54435
+ try {
54436
+ const { getMeshQueueStats: getMeshQueueStats2, getQueue: getQueue2 } = await Promise.resolve().then(() => (init_mesh_work_queue(), mesh_work_queue_exports));
54437
+ const status = Array.isArray(args?.status) ? args.status.map((s) => typeof s === "string" ? s.trim() : "").filter(Boolean) : void 0;
54438
+ const queue = getQueue2(meshId, { status });
54439
+ const summary = getMeshQueueStats2(meshId);
54440
+ return {
54441
+ success: true,
54442
+ queue,
54443
+ summary,
54444
+ sourceOfTruth: {
54445
+ kind: "mesh_work_queue_file",
54446
+ activeStatuses: ["pending", "assigned"],
54447
+ historicalStatuses: ["completed", "failed", "cancelled"],
54448
+ notes: "pending/assigned are active work; completed/failed/cancelled are historical records."
54449
+ }
54450
+ };
54451
+ } catch (e) {
54452
+ return { success: false, error: e.message };
54453
+ }
54454
+ }
54455
+ case "cancel_mesh_queue_task": {
54456
+ const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
54457
+ const taskId = typeof args?.taskId === "string" ? args.taskId.trim() : "";
54458
+ if (!meshId || !taskId) return { success: false, error: "meshId and taskId required" };
54459
+ try {
54460
+ const { cancelTask: cancelTask2 } = await Promise.resolve().then(() => (init_mesh_work_queue(), mesh_work_queue_exports));
54461
+ const reason = typeof args?.reason === "string" ? args.reason : void 0;
54462
+ const task = cancelTask2(meshId, taskId, { reason });
54463
+ if (!task) return { success: false, error: `Queue task '${taskId}' not found` };
54464
+ return { success: true, task };
54465
+ } catch (e) {
54466
+ return { success: false, error: e.message };
54467
+ }
54468
+ }
54469
+ case "requeue_mesh_queue_task": {
54470
+ const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
54471
+ const taskId = typeof args?.taskId === "string" ? args.taskId.trim() : "";
54472
+ if (!meshId || !taskId) return { success: false, error: "meshId and taskId required" };
54473
+ try {
54474
+ const { requeueTask: requeueTask2 } = await Promise.resolve().then(() => (init_mesh_work_queue(), mesh_work_queue_exports));
54475
+ const task = requeueTask2(meshId, taskId, {
54476
+ reason: typeof args?.reason === "string" ? args.reason : void 0,
54477
+ targetNodeId: typeof args?.targetNodeId === "string" ? args.targetNodeId.trim() : void 0,
54478
+ targetSessionId: typeof args?.targetSessionId === "string" ? args.targetSessionId.trim() : void 0,
54479
+ clearTargetNode: args?.clearTargetNode === true,
54480
+ clearTargetSession: args?.clearTargetSession !== false
54481
+ });
54482
+ if (!task) return { success: false, error: `Queue task '${taskId}' not found` };
54483
+ return { success: true, task };
54484
+ } catch (e) {
54485
+ return { success: false, error: e.message };
54486
+ }
54487
+ }
53017
54488
  case "add_mesh_node": {
53018
54489
  const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
53019
54490
  const workspace = typeof args?.workspace === "string" ? args.workspace.trim() : "";
@@ -53075,7 +54546,8 @@ Run 'adhdev doctor' for detailed diagnostics.`
53075
54546
  node,
53076
54547
  mode,
53077
54548
  sessionIds,
53078
- dryRun: args?.dryRun === true
54549
+ dryRun: args?.dryRun === true,
54550
+ source: "mesh_cleanup_sessions"
53079
54551
  });
53080
54552
  return result;
53081
54553
  } catch (e) {
@@ -53105,10 +54577,61 @@ Run 'adhdev doctor' for detailed diagnostics.`
53105
54577
  if (!branch) return { success: false, error: "Could not determine branch of the worktree node" };
53106
54578
  const { stdout: baseBranchStdout } = await execFileAsync3("git", ["branch", "--show-current"], { cwd: repoRoot, encoding: "utf8" });
53107
54579
  const baseBranch = baseBranchStdout.trim();
54580
+ const validationSummary = await runMeshRefineValidationGate(mesh, node.workspace);
54581
+ if (validationSummary.status === "failed") {
54582
+ return {
54583
+ success: false,
54584
+ code: "validation_failed",
54585
+ convergenceStatus: "blocked_review",
54586
+ error: "Refinery validation gate failed; merge/refine was not attempted.",
54587
+ branch,
54588
+ into: baseBranch,
54589
+ validationSummary,
54590
+ finalBranchConvergenceState: {
54591
+ branch,
54592
+ baseBranch,
54593
+ merged: false,
54594
+ removed: false,
54595
+ validation: "failed",
54596
+ status: "blocked_review"
54597
+ }
54598
+ };
54599
+ }
54600
+ if (validationSummary.status === "skipped") {
54601
+ return {
54602
+ success: false,
54603
+ code: "validation_unavailable",
54604
+ convergenceStatus: "blocked_review",
54605
+ error: "Refinery validation gate is required but no allowlisted validation command was available; merge/refine was not attempted.",
54606
+ branch,
54607
+ into: baseBranch,
54608
+ validationSummary,
54609
+ finalBranchConvergenceState: {
54610
+ branch,
54611
+ baseBranch,
54612
+ merged: false,
54613
+ removed: false,
54614
+ validation: "unavailable",
54615
+ status: "blocked_review"
54616
+ }
54617
+ };
54618
+ }
53108
54619
  try {
53109
54620
  await execFileAsync3("git", ["merge", "--no-ff", branch, "-m", `Auto-merge branch '${branch}' via Refinery`], { cwd: repoRoot, encoding: "utf8" });
53110
54621
  } catch (e) {
53111
- return { success: false, error: `Merge failed (conflicts?): ${e.message}` };
54622
+ return {
54623
+ success: false,
54624
+ error: `Merge failed (conflicts?): ${e.message}`,
54625
+ validationSummary,
54626
+ finalBranchConvergenceState: {
54627
+ branch,
54628
+ baseBranch,
54629
+ merged: false,
54630
+ removed: false,
54631
+ validation: "passed",
54632
+ status: "not_mergeable"
54633
+ }
54634
+ };
53112
54635
  }
53113
54636
  const removeResult = await this.execute("remove_mesh_node", {
53114
54637
  meshId,
@@ -53121,11 +54644,27 @@ Run 'adhdev doctor' for detailed diagnostics.`
53121
54644
  appendLedgerEntry2(meshId, {
53122
54645
  kind: "node_removed",
53123
54646
  nodeId,
53124
- payload: { refined: true, mergedBranch: branch, into: baseBranch }
54647
+ payload: { refined: true, mergedBranch: branch, into: baseBranch, validationSummary }
53125
54648
  });
53126
54649
  } catch {
53127
54650
  }
53128
- return { success: true, merged: true, branch, into: baseBranch, removeResult };
54651
+ return {
54652
+ success: true,
54653
+ merged: true,
54654
+ branch,
54655
+ into: baseBranch,
54656
+ removeResult,
54657
+ validationSummary,
54658
+ finalBranchConvergenceState: {
54659
+ branch: baseBranch,
54660
+ mergedBranch: branch,
54661
+ baseBranch,
54662
+ merged: true,
54663
+ removed: removeResult?.success !== false,
54664
+ validation: "passed",
54665
+ status: removeResult?.success === false ? "merged_cleanup_failed" : "merged"
54666
+ }
54667
+ };
53129
54668
  } catch (e) {
53130
54669
  return { success: false, error: e.message };
53131
54670
  }
@@ -53143,20 +54682,24 @@ Run 'adhdev doctor' for detailed diagnostics.`
53143
54682
  );
53144
54683
  let sessionCleanup;
53145
54684
  if (node && sessionCleanupMode !== "preserve") {
53146
- sessionCleanup = await this.cleanupMeshSessions({ meshId, nodeId, node, mode: sessionCleanupMode });
54685
+ sessionCleanup = await this.cleanupMeshSessions({ meshId, nodeId, node, mode: sessionCleanupMode, source: "mesh_remove_node" });
53147
54686
  if (sessionCleanup.success === false) return { success: false, removed: false, sessionCleanup };
53148
54687
  }
53149
- if (node?.isLocalWorktree && node.workspace) {
53150
- try {
53151
- const sourceNode = node.clonedFromNodeId ? mesh?.nodes.find((n) => n.id === node.clonedFromNodeId || n.nodeId === node.clonedFromNodeId) : mesh?.nodes.find((n) => !n.isLocalWorktree);
53152
- const repoRoot = sourceNode?.repoRoot || sourceNode?.workspace;
53153
- if (repoRoot) {
53154
- const { removeWorktree: removeWorktree2 } = await Promise.resolve().then(() => (init_git_worktree(), git_worktree_exports));
53155
- await removeWorktree2(repoRoot, node.workspace);
53156
- }
53157
- } catch (e) {
53158
- LOG.warn("MeshNode", `Worktree cleanup failed for ${nodeId}: ${e.message}`);
54688
+ let worktreeCleanup;
54689
+ if (node?.isLocalWorktree) {
54690
+ const cleanupResult = await this.cleanupLocalWorktreeNode({ mesh, node, nodeId });
54691
+ if (cleanupResult.success === false) {
54692
+ return {
54693
+ success: false,
54694
+ removed: false,
54695
+ code: cleanupResult.code,
54696
+ error: cleanupResult.error,
54697
+ recoveryHint: cleanupResult.recoveryHint,
54698
+ ...sessionCleanup ? { sessionCleanup } : {},
54699
+ worktreeCleanup: cleanupResult
54700
+ };
53159
54701
  }
54702
+ worktreeCleanup = cleanupResult;
53160
54703
  }
53161
54704
  let removed = false;
53162
54705
  if (meshRecord?.inline) {
@@ -53171,12 +54714,21 @@ Run 'adhdev doctor' for detailed diagnostics.`
53171
54714
  appendLedgerEntry2(meshId, {
53172
54715
  kind: "node_removed",
53173
54716
  nodeId,
53174
- payload: { worktree: !!node?.isLocalWorktree, sessionCleanupMode }
54717
+ payload: {
54718
+ worktree: !!node?.isLocalWorktree,
54719
+ sessionCleanupMode,
54720
+ workspace: typeof node?.workspace === "string" ? node.workspace : void 0,
54721
+ daemonId: typeof node?.daemonId === "string" ? node.daemonId : void 0,
54722
+ worktreeBranch: typeof node?.worktreeBranch === "string" ? node.worktreeBranch : void 0,
54723
+ worktreeCleanupFallback: typeof worktreeCleanup?.fallback === "string" ? worktreeCleanup.fallback : void 0,
54724
+ forced: worktreeCleanup?.forced === true ? true : void 0,
54725
+ forceFallbackReason: typeof worktreeCleanup?.reason === "string" ? worktreeCleanup.reason : void 0
54726
+ }
53175
54727
  });
53176
54728
  } catch {
53177
54729
  }
53178
54730
  }
53179
- return { success: true, removed, ...sessionCleanup ? { sessionCleanup } : {} };
54731
+ return { success: true, removed, ...sessionCleanup ? { sessionCleanup } : {}, ...worktreeCleanup ? { worktreeCleanup } : {} };
53180
54732
  } catch (e) {
53181
54733
  return { success: false, error: e.message };
53182
54734
  }
@@ -53211,6 +54763,7 @@ Run 'adhdev doctor' for detailed diagnostics.`
53211
54763
  workspace: result.worktreePath,
53212
54764
  repoRoot: result.worktreePath,
53213
54765
  daemonId: sourceNode.daemonId,
54766
+ machineId: sourceNode.machineId ?? sourceNode.machine_id,
53214
54767
  userOverrides: { ...sourceNode.userOverrides || {} },
53215
54768
  policy: { ...sourceNode.policy || {} },
53216
54769
  isLocalWorktree: true,
@@ -53224,6 +54777,7 @@ Run 'adhdev doctor' for detailed diagnostics.`
53224
54777
  workspace: result.worktreePath,
53225
54778
  repoRoot: result.worktreePath,
53226
54779
  daemonId: sourceNode.daemonId,
54780
+ machineId: sourceNode.machineId ?? sourceNode.machine_id,
53227
54781
  userOverrides: { ...sourceNode.userOverrides || {} },
53228
54782
  isLocalWorktree: true,
53229
54783
  worktreeBranch: result.branch,
@@ -53342,6 +54896,93 @@ Run 'adhdev doctor' for detailed diagnostics.`
53342
54896
  meshCoordinatorSetup: coordinatorSetup
53343
54897
  };
53344
54898
  }
54899
+ if (coordinatorSetup.kind === "cli_command") {
54900
+ let cliCmdSystemPrompt = "";
54901
+ try {
54902
+ cliCmdSystemPrompt = buildCoordinatorSystemPrompt2({ mesh, coordinatorCliType: cliType });
54903
+ } catch (error48) {
54904
+ const message = error48?.message || String(error48);
54905
+ LOG.error("MeshCoordinator", `Failed to build coordinator prompt: ${message}`);
54906
+ return {
54907
+ success: false,
54908
+ code: "mesh_coordinator_prompt_failed",
54909
+ error: `Failed to build Repo Mesh coordinator prompt: ${message}`,
54910
+ meshId,
54911
+ cliType,
54912
+ workspace
54913
+ };
54914
+ }
54915
+ try {
54916
+ const { execFileSync: execCmdSync } = await import("child_process");
54917
+ const cmdParts = coordinatorSetup.command.trim().split(/\s+/);
54918
+ const [regCmd, ...regArgs] = cmdParts;
54919
+ LOG.info("MeshCoordinator", `Running MCP registration: ${coordinatorSetup.command}`);
54920
+ execCmdSync(regCmd, regArgs, { stdio: "pipe", timeout: 15e3 });
54921
+ } catch (error48) {
54922
+ LOG.warn("MeshCoordinator", `MCP registration command failed (may be pre-registered): ${error48?.message || error48}`);
54923
+ }
54924
+ const cliCmdArgs = [];
54925
+ const cliCmdEnv = {};
54926
+ if (cliCmdSystemPrompt) {
54927
+ if (cliType === "codex-cli") {
54928
+ cliCmdArgs.push("-c", `developer_instructions=${JSON.stringify(cliCmdSystemPrompt)}`);
54929
+ } else if (cliType === "gemini-cli") {
54930
+ try {
54931
+ const { writeFileSync: wfs, existsSync: efs, readFileSync: rfs } = await import("fs");
54932
+ const geminiMdPath = `${workspace}/GEMINI.md`;
54933
+ const marker = "<!-- adhdev-mesh-coordinator-prompt -->";
54934
+ const markerEnd = "<!-- /adhdev-mesh-coordinator-prompt -->";
54935
+ const block = `${marker}
54936
+ ${cliCmdSystemPrompt}
54937
+ ${markerEnd}`;
54938
+ if (efs(geminiMdPath)) {
54939
+ const existing = rfs(geminiMdPath, "utf-8");
54940
+ const replaced = existing.replace(
54941
+ new RegExp(`${marker}[\\s\\S]*?${markerEnd}`, "g"),
54942
+ block
54943
+ );
54944
+ wfs(geminiMdPath, replaced.includes(marker) ? replaced : `${existing}
54945
+
54946
+ ${block}`);
54947
+ } else {
54948
+ wfs(geminiMdPath, block);
54949
+ }
54950
+ LOG.info("MeshCoordinator", `Wrote coordinator prompt to ${workspace}/GEMINI.md`);
54951
+ } catch (e) {
54952
+ LOG.warn("MeshCoordinator", `Could not write GEMINI.md: ${e?.message || e}`);
54953
+ }
54954
+ }
54955
+ }
54956
+ const cliCmdLaunch = await this.deps.cliManager.handleCliCommand("launch_cli", {
54957
+ cliType,
54958
+ dir: workspace,
54959
+ cliArgs: cliCmdArgs.length > 0 ? cliCmdArgs : void 0,
54960
+ env: Object.keys(cliCmdEnv).length > 0 ? cliCmdEnv : void 0,
54961
+ settings: { meshCoordinatorFor: meshId }
54962
+ });
54963
+ if (!cliCmdLaunch?.success) {
54964
+ return { success: false, error: cliCmdLaunch?.error || "Failed to launch CLI session" };
54965
+ }
54966
+ LOG.info("MeshCoordinator", `Launched ${cliType} coordinator (cli_command) for mesh ${meshId}`);
54967
+ try {
54968
+ const { appendLedgerEntry: appendLedgerEntry2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
54969
+ appendLedgerEntry2(meshId, {
54970
+ kind: "coordinator_started",
54971
+ sessionId: cliCmdLaunch.sessionId || cliCmdLaunch.id,
54972
+ providerType: cliType,
54973
+ payload: { workspace }
54974
+ });
54975
+ } catch {
54976
+ }
54977
+ return {
54978
+ success: true,
54979
+ meshId,
54980
+ cliType,
54981
+ workspace,
54982
+ sessionId: cliCmdLaunch.sessionId || cliCmdLaunch.id,
54983
+ mcpRegistered: true
54984
+ };
54985
+ }
53345
54986
  const configFormat = coordinatorSetup.configFormat;
53346
54987
  if (configFormat !== "claude_mcp_json" && configFormat !== "hermes_config_yaml") {
53347
54988
  return {
@@ -53396,9 +55037,11 @@ Run 'adhdev doctor' for detailed diagnostics.`
53396
55037
  args: coordinatorSetup.mcpServer.args
53397
55038
  };
53398
55039
  if (args?.inlineMesh) {
55040
+ const modeArgIndex = coordinatorSetup.mcpServer.args.findIndex((value) => value === "--mode");
55041
+ const mcpTransport = modeArgIndex >= 0 ? coordinatorSetup.mcpServer.args[modeArgIndex + 1] : "ipc";
53399
55042
  mcpServerEntry.env = {
53400
55043
  ADHDEV_INLINE_MESH: JSON.stringify(mesh),
53401
- ADHDEV_MCP_TRANSPORT: "ipc"
55044
+ ADHDEV_MCP_TRANSPORT: mcpTransport === "local" ? "local" : "ipc"
53402
55045
  };
53403
55046
  }
53404
55047
  try {
@@ -53417,7 +55060,8 @@ Run 'adhdev doctor' for detailed diagnostics.`
53417
55060
  if (hadExistingMcpConfig) {
53418
55061
  try {
53419
55062
  const parsedExistingMcpConfig = parseMeshCoordinatorMcpConfig(readFileSync17(mcpConfigPath, "utf-8"), configFormat);
53420
- existingMcpConfig = { ...existingMcpConfig, ...parsedExistingMcpConfig };
55063
+ const existingCoordinatorConfig = hermesManualFallback ? stripHermesCoordinatorTempModelProviderOverrides(parsedExistingMcpConfig) : parsedExistingMcpConfig;
55064
+ existingMcpConfig = { ...existingMcpConfig, ...existingCoordinatorConfig };
53421
55065
  copyFileSync4(mcpConfigPath, mcpConfigPath + ".backup");
53422
55066
  } catch (error48) {
53423
55067
  LOG.error("MeshCoordinator", `Failed to parse existing MCP config ${mcpConfigPath}: ${error48?.message || error48}`);
@@ -56975,6 +58619,7 @@ data: ${JSON.stringify(msg.data)}
56975
58619
  apiKeyName: "OpenAI/Anthropic API key"
56976
58620
  }
56977
58621
  ];
58622
+ init_cli_detector();
56978
58623
  SessionRegistry = class {
56979
58624
  bySessionId = /* @__PURE__ */ new Map();
56980
58625
  byManagerKey = /* @__PURE__ */ new Map();
@@ -57240,6 +58885,14 @@ function annotateRapidReadChatAdvisory(payload, options) {
57240
58885
  // src/tools/mesh-tools.ts
57241
58886
  init_dist2();
57242
58887
  var meshSessionProviderMetadata = /* @__PURE__ */ new Map();
58888
+ function readString(value) {
58889
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
58890
+ }
58891
+ var DUPLICATE_DISPATCH_WINDOW_MS = 6e4;
58892
+ var STALE_ASSIGNED_QUEUE_MS = 30 * 6e4;
58893
+ var OLD_HISTORICAL_QUEUE_RECORD_MS = 7 * 24 * 60 * 6e4;
58894
+ var ACTIVE_QUEUE_STATUSES = /* @__PURE__ */ new Set(["pending", "assigned"]);
58895
+ var HISTORICAL_QUEUE_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
57243
58896
  async function refreshMeshFromDaemon(ctx) {
57244
58897
  if (!(ctx.transport instanceof IpcTransport)) return;
57245
58898
  try {
@@ -57260,6 +58913,302 @@ async function findNodeWithRefresh(ctx, nodeId) {
57260
58913
  if (!refreshed) throw new Error(`Node '${nodeId}' is not a member of mesh '${ctx.mesh.name}'`);
57261
58914
  return refreshed;
57262
58915
  }
58916
+ async function findOptionalNodeWithRefresh(ctx, nodeId) {
58917
+ const hit = ctx.mesh.nodes.find((n) => n.id === nodeId);
58918
+ if (hit) return hit;
58919
+ await refreshMeshFromDaemon(ctx);
58920
+ return ctx.mesh.nodes.find((n) => n.id === nodeId) ?? null;
58921
+ }
58922
+ function hasRecentDuplicateDispatch(ctx, args) {
58923
+ const now = Date.now();
58924
+ const normalizedMessage = args.message.trim();
58925
+ for (const task of getQueue(ctx.mesh.id)) {
58926
+ const timestamp2 = new Date(task.updatedAt || task.createdAt).getTime();
58927
+ if (!Number.isFinite(timestamp2) || now - timestamp2 > DUPLICATE_DISPATCH_WINDOW_MS) continue;
58928
+ if (task.targetNodeId && task.targetNodeId !== args.node_id) continue;
58929
+ if (task.assignedNodeId && task.assignedNodeId !== args.node_id) continue;
58930
+ if (args.session_id && task.targetSessionId !== args.session_id && task.assignedSessionId !== args.session_id) continue;
58931
+ if (task.message?.trim() === normalizedMessage) {
58932
+ return { duplicate: true, entry: task, source: "queue" };
58933
+ }
58934
+ }
58935
+ const entries = readLedgerEntries(ctx.mesh.id, { tail: 200 });
58936
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
58937
+ const entry = entries[i];
58938
+ const timestamp2 = new Date(entry.timestamp).getTime();
58939
+ if (Number.isFinite(timestamp2) && now - timestamp2 > DUPLICATE_DISPATCH_WINDOW_MS) break;
58940
+ if (entry.kind !== "task_dispatched") continue;
58941
+ if (entry.nodeId !== args.node_id) continue;
58942
+ if (args.session_id && entry.sessionId !== args.session_id) continue;
58943
+ if (typeof entry.payload?.message !== "string") continue;
58944
+ if (entry.payload.message.trim() === normalizedMessage) {
58945
+ return { duplicate: true, entry, source: "ledger" };
58946
+ }
58947
+ }
58948
+ return { duplicate: false };
58949
+ }
58950
+ function buildMissingNodeReadChatRecovery(ctx, args) {
58951
+ const entries = readLedgerEntries(ctx.mesh.id, { tail: 300 });
58952
+ const relatedEntries = entries.filter((entry) => entry.nodeId === args.node_id || entry.sessionId === args.session_id);
58953
+ const completedEntries = relatedEntries.filter((entry) => entry.kind === "task_completed");
58954
+ const lastDispatch = [...relatedEntries].reverse().find((entry) => entry.kind === "task_dispatched");
58955
+ const lastTerminal = [...relatedEntries].reverse().find((entry) => entry.kind === "task_completed" || entry.kind === "task_failed" || entry.kind === "task_stalled");
58956
+ const lastRemoved = [...relatedEntries].reverse().find((entry) => entry.kind === "node_removed");
58957
+ const lastLaunch = [...relatedEntries].reverse().find((entry) => entry.kind === "session_launched");
58958
+ const providerSessionId = args.provider_session_id || readString(lastTerminal?.payload?.providerSessionId) || readString(lastLaunch?.payload?.providerSessionId) || readString(lastDispatch?.payload?.providerSessionId);
58959
+ const finalSummary = readString(lastTerminal?.payload?.finalSummary) || readString(lastTerminal?.payload?.compactSummary) || readString(lastTerminal?.payload?.summary);
58960
+ const ledger = {
58961
+ taskCompletedFound: completedEntries.length > 0,
58962
+ nodeRemovedFound: !!lastRemoved,
58963
+ providerType: lastTerminal?.providerType || lastLaunch?.providerType || lastDispatch?.providerType,
58964
+ providerSessionId,
58965
+ nodeRemovedAt: lastRemoved?.timestamp,
58966
+ sessionCleanupMode: readString(lastRemoved?.payload?.sessionCleanupMode),
58967
+ readDebugLocator: readString(lastTerminal?.payload?.readDebugLocator) || readString(lastTerminal?.payload?.debugBundlePath)
58968
+ };
58969
+ if (finalSummary) {
58970
+ return {
58971
+ success: true,
58972
+ compact: args.compact === true,
58973
+ recoveredFromLedger: true,
58974
+ nodeId: args.node_id,
58975
+ sessionId: args.session_id,
58976
+ summary: finalSummary,
58977
+ ledger,
58978
+ messages: [{ role: "assistant", content: finalSummary, isHistorical: true }]
58979
+ };
58980
+ }
58981
+ return {
58982
+ success: false,
58983
+ recoverable: true,
58984
+ code: "mesh_removed_node_transcript_unavailable",
58985
+ error: `Node '${args.node_id}' is not a current member of mesh '${ctx.mesh.name}'.`,
58986
+ nodeId: args.node_id,
58987
+ sessionId: args.session_id,
58988
+ providerSessionId,
58989
+ reason: "node_not_in_current_mesh_snapshot",
58990
+ ledger,
58991
+ completedSessionSeenInLedger: ledger.taskCompletedFound,
58992
+ lastDispatch: lastDispatch ? {
58993
+ timestamp: lastDispatch.timestamp,
58994
+ sessionId: lastDispatch.sessionId,
58995
+ providerType: lastDispatch.providerType,
58996
+ taskId: typeof lastDispatch.payload?.taskId === "string" ? lastDispatch.payload.taskId : void 0,
58997
+ messagePreview: typeof lastDispatch.payload?.message === "string" ? lastDispatch.payload.message.slice(0, 500) : void 0
58998
+ } : null,
58999
+ lastTerminalEvent: lastTerminal ? {
59000
+ kind: lastTerminal.kind,
59001
+ timestamp: lastTerminal.timestamp,
59002
+ sessionId: lastTerminal.sessionId,
59003
+ providerType: lastTerminal.providerType,
59004
+ taskId: typeof lastTerminal.payload?.taskId === "string" ? lastTerminal.payload.taskId : void 0,
59005
+ payload: lastTerminal.payload
59006
+ } : null,
59007
+ nextSteps: [
59008
+ providerSessionId ? `Retry mesh_read_chat with provider_session_id='${providerSessionId}' on a current live node for the same daemon if one exists.` : "If the node UI shows a provider transcript id, retry mesh_read_chat/mesh_read_debug with provider_session_id.",
59009
+ "Use mesh_read_debug with the provider_session_id or daemon-side debug bundle locator if available.",
59010
+ "Check mesh_task_history for task_completed and node_removed entries before redispatching; do not resend solely because transcript recovery failed.",
59011
+ "If this node was removed with stop_and_delete, the runtime transcript may be gone; rely on the ledger summary/locator or ask the operator for the saved UI output."
59012
+ ],
59013
+ recoveryHints: [
59014
+ "The worktree/node may have been removed or the mesh snapshot may be stale after task completion.",
59015
+ "If you have a provider_session_id, retry mesh_read_chat with that value while targeting a live node for the same daemon if available.",
59016
+ "Use mesh_read_debug with provider_session_id, or inspect the daemon/session-host history locator if the transcript has already been archived.",
59017
+ "Avoid redispatching the same task solely because read_chat could not recover the transcript; check task_history and git status first."
59018
+ ]
59019
+ };
59020
+ }
59021
+ function readSessionRecordId(session) {
59022
+ return readString(session?.id) || readString(session?.sessionId) || readString(session?.session_id) || readString(session?.runtimeSessionId) || readString(session?.runtime_session_id) || readString(session?.instanceId) || readString(session?.instance_id);
59023
+ }
59024
+ function addSessionRecord(target, session) {
59025
+ if (!session || typeof session !== "object" || isTerminalSessionRecord(session)) return;
59026
+ const sessionId = readSessionRecordId(session);
59027
+ if (sessionId) target.add(sessionId);
59028
+ }
59029
+ function collectNodeSessionIds(node) {
59030
+ const sessions = /* @__PURE__ */ new Set();
59031
+ const sessionArrays = [
59032
+ node?.sessions,
59033
+ node?.activeSessions,
59034
+ node?.active_sessions,
59035
+ node?.lastProbe?.sessions,
59036
+ node?.last_probe?.sessions,
59037
+ node?.lastProbe?.status?.sessions,
59038
+ node?.last_probe?.status?.sessions
59039
+ ];
59040
+ for (const value of sessionArrays) {
59041
+ if (Array.isArray(value)) value.forEach((session) => addSessionRecord(sessions, session));
59042
+ }
59043
+ const sessionRecords = [
59044
+ node?.activeSession,
59045
+ node?.active_session,
59046
+ node?.currentSession,
59047
+ node?.current_session,
59048
+ node?.runtimeSession,
59049
+ node?.runtime_session,
59050
+ node?.session,
59051
+ node?.lastProbe?.activeSession,
59052
+ node?.last_probe?.active_session,
59053
+ node?.lastProbe?.currentSession,
59054
+ node?.last_probe?.current_session,
59055
+ node?.lastProbe?.session,
59056
+ node?.last_probe?.session
59057
+ ];
59058
+ sessionRecords.forEach((session) => addSessionRecord(sessions, session));
59059
+ return sessions;
59060
+ }
59061
+ function buildQueueLivenessIndex(mesh) {
59062
+ const nodeIds = /* @__PURE__ */ new Set();
59063
+ const nodeSessionIds = /* @__PURE__ */ new Map();
59064
+ for (const node of Array.isArray(mesh?.nodes) ? mesh.nodes : []) {
59065
+ const nodeId = readString(node.id) || readString(node.nodeId) || readString(node.node_id);
59066
+ if (!nodeId) continue;
59067
+ nodeIds.add(nodeId);
59068
+ const sessions = collectNodeSessionIds(node);
59069
+ if (sessions.size > 0) nodeSessionIds.set(nodeId, sessions);
59070
+ }
59071
+ return { nodeIds, nodeSessionIds };
59072
+ }
59073
+ function queueAssignmentStaleReason(task, liveness) {
59074
+ if (task?.status !== "assigned") return void 0;
59075
+ const nodeId = readString(task.assignedNodeId) || readString(task.nodeId) || readString(task.node_id) || readString(task.targetNodeId);
59076
+ const sessionId = readString(task.assignedSessionId) || readString(task.sessionId) || readString(task.session_id) || readString(task.targetSessionId);
59077
+ if (nodeId && liveness.nodeIds.size > 0 && !liveness.nodeIds.has(nodeId)) {
59078
+ return "assigned node is not present in the current mesh snapshot";
59079
+ }
59080
+ if (nodeId && sessionId && liveness.nodeSessionIds.has(nodeId) && !liveness.nodeSessionIds.get(nodeId).has(sessionId)) {
59081
+ return "assigned session is not live on the assigned node";
59082
+ }
59083
+ const updatedAt = new Date(task.updatedAt).getTime();
59084
+ const ageMs = Number.isFinite(updatedAt) ? Date.now() - updatedAt : null;
59085
+ if (!nodeId && ageMs !== null && ageMs >= STALE_ASSIGNED_QUEUE_MS) {
59086
+ return "assigned task has no assigned node metadata";
59087
+ }
59088
+ return void 0;
59089
+ }
59090
+ function buildQueueStatusSummary(queue) {
59091
+ const counts = { pending: 0, assigned: 0, completed: 0, failed: 0, cancelled: 0 };
59092
+ for (const task of queue) {
59093
+ const status = typeof task?.status === "string" ? task.status : void 0;
59094
+ if (status && Object.prototype.hasOwnProperty.call(counts, status)) {
59095
+ counts[status] += 1;
59096
+ }
59097
+ }
59098
+ return {
59099
+ totalCount: queue.length,
59100
+ activeCount: counts.pending + counts.assigned,
59101
+ historicalCount: counts.completed + counts.failed + counts.cancelled,
59102
+ counts,
59103
+ activeCounts: {
59104
+ pending: counts.pending,
59105
+ assigned: counts.assigned
59106
+ },
59107
+ historicalCounts: {
59108
+ completed: counts.completed,
59109
+ failed: counts.failed,
59110
+ cancelled: counts.cancelled
59111
+ }
59112
+ };
59113
+ }
59114
+ function normalizeQueueViewMode(value) {
59115
+ return value === "active" || value === "historical" || value === "all" ? value : "all";
59116
+ }
59117
+ function sanitizeQueueStatusFilter(value) {
59118
+ if (!Array.isArray(value)) return void 0;
59119
+ const statuses = value.map((item) => typeof item === "string" ? item.trim() : "").filter((status) => ACTIVE_QUEUE_STATUSES.has(status) || HISTORICAL_QUEUE_STATUSES.has(status));
59120
+ return statuses.length ? Array.from(new Set(statuses)) : void 0;
59121
+ }
59122
+ function filterQueueForView(queue, view, statuses) {
59123
+ if (statuses?.length) {
59124
+ const allowed = new Set(statuses);
59125
+ return queue.filter((task) => allowed.has(String(task?.status || "")));
59126
+ }
59127
+ if (view === "active") return queue.filter((task) => ACTIVE_QUEUE_STATUSES.has(String(task?.status || "")));
59128
+ if (view === "historical") return queue.filter((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
59129
+ return queue;
59130
+ }
59131
+ function slimQueueTask(task) {
59132
+ return {
59133
+ id: task?.id,
59134
+ status: task?.status,
59135
+ assignedNodeId: task?.assignedNodeId,
59136
+ assignedSessionId: task?.assignedSessionId,
59137
+ targetNodeId: task?.targetNodeId,
59138
+ targetSessionId: task?.targetSessionId,
59139
+ updatedAt: task?.updatedAt,
59140
+ staleAssigned: task?.staleAssigned === true,
59141
+ staleReason: task?.staleReason
59142
+ };
59143
+ }
59144
+ function buildQueueMaintenanceReport(queue) {
59145
+ const now = Date.now();
59146
+ const staleAssignedTasks = queue.filter((task) => task?.status === "assigned" && task?.staleAssigned === true).map(slimQueueTask);
59147
+ const historicalTasks = queue.filter((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
59148
+ const oldHistoricalTasks = historicalTasks.filter((task) => {
59149
+ const updatedAt = new Date(task?.updatedAt).getTime();
59150
+ return Number.isFinite(updatedAt) && now - updatedAt >= OLD_HISTORICAL_QUEUE_RECORD_MS;
59151
+ }).map((task) => ({
59152
+ ...slimQueueTask(task),
59153
+ cleanupClass: "old_historical_record",
59154
+ reason: "terminal queue record is older than the read-only maintenance threshold"
59155
+ }));
59156
+ const cleanupCandidates = [
59157
+ ...staleAssignedTasks.map((task) => ({
59158
+ ...task,
59159
+ cleanupClass: "stale_assigned",
59160
+ reason: typeof task.staleReason === "string" ? task.staleReason : "active assigned task does not match current live mesh node/session state",
59161
+ suggestedOperation: "operator_review_then_requeue_or_cancel"
59162
+ })),
59163
+ ...oldHistoricalTasks.map((task) => ({
59164
+ ...task,
59165
+ suggestedOperation: "operator_review_then_archive_or_keep"
59166
+ }))
59167
+ ];
59168
+ return {
59169
+ readOnly: true,
59170
+ mutationPerformed: false,
59171
+ sourceOfTruth: "mesh_work_queue_file",
59172
+ staleAssignedDefinition: "Only active assigned queue rows are stale candidates, and only when the assigned node/session is absent from the current live mesh snapshot.",
59173
+ historicalDefinition: "completed/failed/cancelled rows are historical ledger records and never active assignments.",
59174
+ staleAssignedTasks,
59175
+ staleAssignedCount: staleAssignedTasks.length,
59176
+ historicalRecordCount: historicalTasks.length,
59177
+ oldHistoricalRecordCount: oldHistoricalTasks.length,
59178
+ cleanupCandidates,
59179
+ cleanupCandidateCount: cleanupCandidates.length
59180
+ };
59181
+ }
59182
+ function annotateQueueStaleness(queue, mesh) {
59183
+ const liveness = buildQueueLivenessIndex(mesh);
59184
+ const now = Date.now();
59185
+ return queue.map((task) => {
59186
+ const taskStatus = typeof task?.status === "string" ? task.status : void 0;
59187
+ const annotated = {
59188
+ ...task,
59189
+ taskStatus,
59190
+ isActive: taskStatus ? ACTIVE_QUEUE_STATUSES.has(taskStatus) : false,
59191
+ isHistorical: taskStatus ? HISTORICAL_QUEUE_STATUSES.has(taskStatus) : false,
59192
+ dispatchedAt: task?.createdAt,
59193
+ ...taskStatus === "assigned" ? { activeTaskId: task.id } : {},
59194
+ ...taskStatus === "completed" || taskStatus === "failed" ? {
59195
+ completedAt: task.updatedAt
59196
+ } : {}
59197
+ };
59198
+ if (taskStatus !== "assigned") return annotated;
59199
+ const updatedAt = new Date(task.updatedAt).getTime();
59200
+ const ageMs = Number.isFinite(updatedAt) ? now - updatedAt : null;
59201
+ const staleReason = queueAssignmentStaleReason(task, liveness);
59202
+ if (!staleReason) return annotated;
59203
+ return {
59204
+ ...annotated,
59205
+ stale: true,
59206
+ staleAssigned: true,
59207
+ staleReason,
59208
+ ...ageMs !== null ? { assignedAgeMs: ageMs } : {}
59209
+ };
59210
+ });
59211
+ }
57263
59212
  function unwrapCommandPayload(value) {
57264
59213
  let current = value;
57265
59214
  const seen = /* @__PURE__ */ new Set();
@@ -57272,6 +59221,26 @@ function unwrapCommandPayload(value) {
57272
59221
  }
57273
59222
  return current;
57274
59223
  }
59224
+ function isTerminalSessionRecord(session) {
59225
+ const status = typeof session?.status === "string" ? session.status.toLowerCase() : "";
59226
+ const lifecycle = typeof session?.lifecycle === "string" ? session.lifecycle.toLowerCase() : "";
59227
+ const state = typeof session?.state === "string" ? session.state.toLowerCase() : "";
59228
+ return [status, lifecycle, state].some((value) => ["stopped", "failed", "terminated", "exited", "closed"].includes(value));
59229
+ }
59230
+ function isIdleSessionRecord(session) {
59231
+ if (isTerminalSessionRecord(session)) return false;
59232
+ const status = typeof session?.status === "string" ? session.status.toLowerCase() : "";
59233
+ const chatStatus = typeof session?.activeChat?.status === "string" ? session.activeChat.status.toLowerCase() : "";
59234
+ return status === "idle" || chatStatus === "waiting_input";
59235
+ }
59236
+ function chooseDispatchableSession(sessions, providerType, meshId, nodeId) {
59237
+ const live = sessions.filter((session) => !isTerminalSessionRecord(session));
59238
+ const matchingProvider = (session) => !providerType || session?.providerType === providerType || session?.cliType === providerType;
59239
+ const meshSessions = live.filter(
59240
+ (session) => session?.settings?.meshNodeFor === meshId || session?.settings?.meshNodeId === nodeId
59241
+ );
59242
+ return meshSessions.find((session) => isIdleSessionRecord(session) && matchingProvider(session)) || meshSessions.find(matchingProvider) || live.find((session) => isIdleSessionRecord(session) && matchingProvider(session)) || live.find(matchingProvider) || live.find(isIdleSessionRecord) || live[0];
59243
+ }
57275
59244
  function findNestedPayload(value, predicate) {
57276
59245
  const seen = /* @__PURE__ */ new Set();
57277
59246
  const stack = [{ payload: value, depth: 0 }];
@@ -57300,17 +59269,225 @@ function extractGitDiff(value) {
57300
59269
  function extractLaunchPayload(value) {
57301
59270
  return findNestedPayload(value, (payload) => Boolean(payload?.sessionId || payload?.id || payload?.runtimeSessionId));
57302
59271
  }
59272
+ function classifyMeshLaunchFailure(error48) {
59273
+ const message = error48 instanceof Error ? error48.message : String(error48 || "launch failed");
59274
+ const lower = message.toLowerCase();
59275
+ const p2pClassification = classifyP2pRelayFailure(error48, { command: "launch_cli" });
59276
+ if (p2pClassification.recoverable) {
59277
+ return p2pClassification;
59278
+ }
59279
+ if (lower.includes("cannot connect to daemon ipc") || lower.includes("daemon ipc command")) {
59280
+ return {
59281
+ code: "local_ipc_unavailable",
59282
+ reason: "local_daemon_ipc_unavailable",
59283
+ transport: "local_ipc",
59284
+ recoverable: true,
59285
+ retryRecommended: true,
59286
+ nextAction: "Check the local daemon IPC connection, then retry mesh_launch_session once after the daemon is reachable."
59287
+ };
59288
+ }
59289
+ if (lower.includes("timed out") || lower.includes("timeout")) {
59290
+ return {
59291
+ code: "mesh_transport_timeout",
59292
+ reason: "mesh_transport_timeout",
59293
+ transport: "mesh_transport",
59294
+ recoverable: true,
59295
+ retryRecommended: true,
59296
+ nextAction: "Check mesh transport health, then do one bounded retry before requeueing or relaunching the task."
59297
+ };
59298
+ }
59299
+ return {
59300
+ code: "mesh_launch_failed",
59301
+ reason: "provider_launch_failed",
59302
+ transport: "mesh_transport",
59303
+ recoverable: false,
59304
+ retryRecommended: false,
59305
+ nextAction: "Inspect the provider launch error and fix the underlying provider/configuration issue before retrying."
59306
+ };
59307
+ }
59308
+ function buildWorktreeCleanupHint(node) {
59309
+ if (!node.isLocalWorktree) return void 0;
59310
+ return {
59311
+ tool: "mesh_remove_node",
59312
+ args: { node_id: node.id, session_cleanup_mode: "preserve" },
59313
+ hint: `If the worktree is no longer needed, remove the orphan worktree node with mesh_remove_node(node_id: "${node.id}").`
59314
+ };
59315
+ }
59316
+ function buildRecoverableLaunchFailure(ctx, node, providerType, error48) {
59317
+ const message = error48 instanceof Error ? error48.message : String(error48 || "launch failed");
59318
+ const classified = classifyMeshLaunchFailure(error48);
59319
+ const cleanup = buildWorktreeCleanupHint(node);
59320
+ return {
59321
+ success: false,
59322
+ recoverable: classified.recoverable,
59323
+ code: classified.code,
59324
+ reason: classified.reason,
59325
+ transport: classified.transport,
59326
+ retryRecommended: classified.retryRecommended,
59327
+ nextAction: classified.nextAction,
59328
+ ...classified.noFallbackReason ? { noFallbackReason: classified.noFallbackReason } : {},
59329
+ error: message,
59330
+ meshId: ctx.mesh.id,
59331
+ nodeId: node.id,
59332
+ daemonId: node.daemonId,
59333
+ workspace: node.workspace,
59334
+ isLocalWorktree: node.isLocalWorktree === true,
59335
+ worktreeBranch: node.worktreeBranch,
59336
+ clonedFromNodeId: node.clonedFromNodeId,
59337
+ ...providerType ? { resolvedProviderType: providerType } : {},
59338
+ retryHint: `Retry mesh_launch_session(node_id: "${node.id}"${providerType ? `, type: "${providerType}"` : ""}) after daemon mesh transport/P2P is healthy.`,
59339
+ ...cleanup ? { cleanup } : {},
59340
+ nextStepHints: [
59341
+ `Retry mesh_launch_session(node_id: "${node.id}"${providerType ? `, type: "${providerType}"` : ""}) after checking daemon/P2P health.`,
59342
+ ...cleanup ? [`Cleanup orphan worktree node with mesh_remove_node(node_id: "${node.id}") if retry is not desired.`] : [],
59343
+ "Run mesh_status to see the degraded reason and recovery hints before redispatching work."
59344
+ ]
59345
+ };
59346
+ }
59347
+ function recordRecoverableLaunchFailure(ctx, node, providerType, error48) {
59348
+ const failure2 = buildRecoverableLaunchFailure(ctx, node, providerType, error48);
59349
+ try {
59350
+ appendLedgerEntry(ctx.mesh.id, {
59351
+ kind: "recovery_attempted",
59352
+ nodeId: node.id,
59353
+ providerType,
59354
+ payload: {
59355
+ event: "session_launch_failed",
59356
+ ...failure2
59357
+ }
59358
+ });
59359
+ } catch {
59360
+ }
59361
+ return failure2;
59362
+ }
59363
+ function getLatestActiveLaunchFailure(meshId, nodeId) {
59364
+ const entries = readLedgerEntries(meshId, { tail: 200 });
59365
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
59366
+ const entry = entries[i];
59367
+ if (entry.nodeId !== nodeId) continue;
59368
+ if (entry.kind === "session_launched" || entry.kind === "node_removed") return null;
59369
+ if (entry.kind === "recovery_attempted" && entry.payload?.event === "session_launch_failed") {
59370
+ return { timestamp: entry.timestamp, ...entry.payload };
59371
+ }
59372
+ }
59373
+ return null;
59374
+ }
59375
+ function buildCoordinatorP2pRelayFailure(error48, context) {
59376
+ const payload = buildP2pRelayFailurePayload(error48, {
59377
+ command: context.command,
59378
+ targetDaemonId: context.targetDaemonId
59379
+ });
59380
+ return {
59381
+ ...payload,
59382
+ ...context.nodeId ? { nodeId: context.nodeId } : {},
59383
+ ...context.sessionId ? { sessionId: context.sessionId } : {},
59384
+ retryHint: payload.retryRecommended ? payload.nextAction : "Do not retry as a P2P transport recovery; inspect the command/provider error first."
59385
+ };
59386
+ }
59387
+ async function ipcDispatchToRemoteAgent(ctx, node, args) {
59388
+ const transport = ctx.transport;
59389
+ const daemonId = node.daemonId;
59390
+ let sessionId = args.session_id?.trim() || "";
59391
+ const providerPriorityList = Array.isArray(node.policy?.providerPriority) ? node.policy.providerPriority : [];
59392
+ let resolvedProviderType = args.providerType?.trim() || providerPriorityList[0] || "";
59393
+ if (!sessionId) {
59394
+ try {
59395
+ const relayResult = await transport.meshCommand(daemonId, "get_status_metadata", {});
59396
+ const innerResult = relayResult?.result ?? relayResult;
59397
+ const statusObj = innerResult?.status ?? innerResult;
59398
+ const sessions = Array.isArray(statusObj?.sessions) ? statusObj.sessions : [];
59399
+ const targetSession = chooseDispatchableSession(sessions, resolvedProviderType, ctx.mesh.id, node.id);
59400
+ if (targetSession?.id || targetSession?.sessionId) {
59401
+ sessionId = targetSession.id || targetSession.sessionId;
59402
+ if (!resolvedProviderType) {
59403
+ resolvedProviderType = targetSession.providerType || targetSession.cliType || "";
59404
+ }
59405
+ }
59406
+ } catch (e) {
59407
+ }
59408
+ }
59409
+ if (!resolvedProviderType) {
59410
+ return { success: false, error: `Cannot dispatch to remote node '${node.id}': providerType unknown. Set providerPriority on the node policy or call mesh_launch_session first.` };
59411
+ }
59412
+ try {
59413
+ const dispatchResult = await transport.meshCommand(daemonId, "agent_command", {
59414
+ ...sessionId ? { targetSessionId: sessionId } : {},
59415
+ agentType: resolvedProviderType,
59416
+ cliType: resolvedProviderType,
59417
+ action: "send_chat",
59418
+ message: args.message
59419
+ });
59420
+ const dispatchPayload = unwrapCommandPayload(dispatchResult);
59421
+ if (dispatchPayload?.success === false || dispatchResult?.success === false) {
59422
+ const source = dispatchPayload?.success === false ? dispatchPayload : dispatchResult;
59423
+ const errorMessage = dispatchPayload?.error || dispatchResult?.error || "agent_command rejected the task";
59424
+ return {
59425
+ ...buildCoordinatorP2pRelayFailure(source?.error || errorMessage, {
59426
+ command: "agent_command",
59427
+ targetDaemonId: daemonId,
59428
+ nodeId: node.id,
59429
+ sessionId
59430
+ }),
59431
+ ...source && typeof source === "object" ? source : {},
59432
+ success: false,
59433
+ error: `P2P dispatch failed: ${errorMessage}`
59434
+ };
59435
+ }
59436
+ return { success: true, dispatched: true, sessionId: sessionId || resolvedProviderType };
59437
+ } catch (e) {
59438
+ const errorMessage = e?.message || String(e);
59439
+ return {
59440
+ ...buildCoordinatorP2pRelayFailure(e, {
59441
+ command: "agent_command",
59442
+ targetDaemonId: daemonId,
59443
+ nodeId: node.id,
59444
+ sessionId
59445
+ }),
59446
+ error: `P2P dispatch failed: ${errorMessage}`
59447
+ };
59448
+ }
59449
+ }
57303
59450
  function resolveCoordinatorNode(ctx) {
57304
59451
  const preferredNodeId = typeof ctx.mesh.coordinator?.preferredNodeId === "string" ? ctx.mesh.coordinator.preferredNodeId.trim() : "";
57305
59452
  if (preferredNodeId) {
57306
59453
  const preferred = ctx.mesh.nodes.find((n) => n.id === preferredNodeId && typeof n.daemonId === "string" && n.daemonId.trim());
57307
59454
  if (preferred) return preferred;
57308
59455
  }
59456
+ if (ctx.localMachineId) {
59457
+ const byMachine = ctx.mesh.nodes.find((n) => readNodeMachineId(n) === ctx.localMachineId);
59458
+ if (byMachine) return byMachine;
59459
+ }
57309
59460
  if (ctx.localDaemonId) {
57310
- return ctx.mesh.nodes.find((n) => n.daemonId === ctx.localDaemonId);
59461
+ return ctx.mesh.nodes.find((n) => readNodeDaemonId(n) === ctx.localDaemonId);
57311
59462
  }
57312
59463
  return void 0;
57313
59464
  }
59465
+ function readNodeMachineId(node) {
59466
+ return readString(node.machineId) || readString(node.machine_id);
59467
+ }
59468
+ function readNodeDaemonId(node) {
59469
+ return readString(node.daemonId) || readString(node.daemon_id);
59470
+ }
59471
+ function isDirectLocalNode(ctx, node) {
59472
+ const machineId = readNodeMachineId(node);
59473
+ const daemonId = readNodeDaemonId(node);
59474
+ return Boolean(
59475
+ ctx.localMachineId && machineId === ctx.localMachineId || ctx.localDaemonId && daemonId === ctx.localDaemonId
59476
+ );
59477
+ }
59478
+ function findClonedFromNode(ctx, node) {
59479
+ const clonedFromNodeId = readString(node.clonedFromNodeId) || readString(node.cloned_from_node_id);
59480
+ if (!clonedFromNodeId) return void 0;
59481
+ return ctx.mesh.nodes.find((n) => n.id === clonedFromNodeId || n.nodeId === clonedFromNodeId || n.node_id === clonedFromNodeId);
59482
+ }
59483
+ function isLocalControlPlaneNode(ctx, node) {
59484
+ if (isDirectLocalNode(ctx, node)) return true;
59485
+ if (node.isLocalWorktree === true) {
59486
+ const sourceNode = findClonedFromNode(ctx, node);
59487
+ if (sourceNode && isDirectLocalNode(ctx, sourceNode)) return true;
59488
+ }
59489
+ return false;
59490
+ }
57314
59491
  function meshSessionCacheKey(nodeId, runtimeSessionId) {
57315
59492
  return `${nodeId}:${runtimeSessionId}`;
57316
59493
  }
@@ -57394,8 +59571,116 @@ function getNodeLaunchReadiness(node) {
57394
59571
  launchBlockedMessage: missingProviderPriorityMessage(node.id)
57395
59572
  };
57396
59573
  }
59574
+ function readNumeric(value, fallback = 0) {
59575
+ const parsed = Number(value);
59576
+ return Number.isFinite(parsed) ? parsed : fallback;
59577
+ }
59578
+ function buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges) {
59579
+ const defaultBranch = readString(mesh.defaultBranch) ?? "main";
59580
+ const branch = readString(status?.branch) ?? readString(node.worktreeBranch) ?? null;
59581
+ const ahead = readNumeric(status?.ahead);
59582
+ const behind = readNumeric(status?.behind);
59583
+ const upstream = readString(status?.upstream) ?? null;
59584
+ const hasConflicts = status?.hasConflicts === true || Array.isArray(status?.conflictFiles) && status.conflictFiles.length > 0;
59585
+ const base = {
59586
+ defaultBranch,
59587
+ branch,
59588
+ upstream,
59589
+ ahead,
59590
+ behind,
59591
+ isWorktree: node.isLocalWorktree === true,
59592
+ isDefaultBranch: branch === defaultBranch
59593
+ };
59594
+ if (status?.isGitRepo !== true) {
59595
+ return {
59596
+ ...base,
59597
+ status: "blocked_review",
59598
+ needsConvergence: true,
59599
+ reason: "git_status_unavailable",
59600
+ nextStep: `Resolve git status for node '${node.id}' before marking the task complete.`
59601
+ };
59602
+ }
59603
+ if (!branch) {
59604
+ return {
59605
+ ...base,
59606
+ status: "blocked_review",
59607
+ needsConvergence: true,
59608
+ reason: "branch_unknown",
59609
+ nextStep: `Inspect node '${node.id}' git branch before deciding whether it is merged to ${defaultBranch}.`
59610
+ };
59611
+ }
59612
+ if (hasConflicts || dirty || uncommittedChanges > 0) {
59613
+ return {
59614
+ ...base,
59615
+ status: "not_mergeable",
59616
+ needsConvergence: true,
59617
+ reason: hasConflicts ? "conflicts_present" : "dirty_workspace",
59618
+ nextStep: `Commit, checkpoint, or resolve node '${node.id}' before any main convergence step.`
59619
+ };
59620
+ }
59621
+ if (branch === defaultBranch) {
59622
+ if (ahead > 0 || behind > 0) {
59623
+ return {
59624
+ ...base,
59625
+ status: "blocked_review",
59626
+ needsConvergence: true,
59627
+ reason: "default_branch_not_even_with_upstream",
59628
+ nextStep: `Bring ${defaultBranch} even with its upstream before declaring convergence complete.`
59629
+ };
59630
+ }
59631
+ return {
59632
+ ...base,
59633
+ status: "merged_to_main",
59634
+ needsConvergence: false,
59635
+ reason: "clean_default_branch",
59636
+ nextStep: null
59637
+ };
59638
+ }
59639
+ if (node.isLocalWorktree) {
59640
+ return {
59641
+ ...base,
59642
+ status: "cleanup_candidate",
59643
+ needsConvergence: true,
59644
+ reason: "clean_non_default_worktree_branch",
59645
+ nextStep: `Run mesh_refine_node(node_id: "${node.id}") or explicitly classify this worktree as blocked_review/not_mergeable before ending the task.`
59646
+ };
59647
+ }
59648
+ if (!upstream || ahead > 0 || behind > 0) {
59649
+ return {
59650
+ ...base,
59651
+ status: "blocked_review",
59652
+ needsConvergence: true,
59653
+ reason: !upstream ? "feature_branch_missing_upstream" : "feature_branch_not_even_with_upstream",
59654
+ nextStep: `Push or reconcile branch '${branch}', then merge it into ${defaultBranch} or mark it not_mergeable with a reason.`
59655
+ };
59656
+ }
59657
+ return {
59658
+ ...base,
59659
+ status: "pushed_feature_branch_needs_merge",
59660
+ needsConvergence: true,
59661
+ reason: "clean_non_default_branch",
59662
+ nextStep: `Review and merge branch '${branch}' into ${defaultBranch}; do not report the task as fully complete while it remains off main.`
59663
+ };
59664
+ }
59665
+ function summarizeBranchConvergence(nodes) {
59666
+ const followUps = nodes.filter((node) => node?.branchConvergence?.needsConvergence === true).map((node) => ({
59667
+ nodeId: node.nodeId,
59668
+ workspace: node.workspace,
59669
+ branch: node.branchConvergence.branch,
59670
+ status: node.branchConvergence.status,
59671
+ reason: node.branchConvergence.reason,
59672
+ nextStep: node.branchConvergence.nextStep
59673
+ }));
59674
+ return {
59675
+ needsFollowUp: followUps.length > 0,
59676
+ unresolvedCount: followUps.length,
59677
+ requiredFinalStates: ["merged_to_main", "pushed_feature_branch_needs_merge", "blocked_review", "cleanup_candidate", "not_mergeable"],
59678
+ followUps
59679
+ };
59680
+ }
57397
59681
  async function commandForNode(ctx, node, command, args = {}) {
57398
- if (ctx.transport instanceof IpcTransport && node.daemonId && node.daemonId !== ctx.localDaemonId) {
59682
+ const isLocalNode = isLocalControlPlaneNode(ctx, node);
59683
+ if (ctx.transport instanceof IpcTransport && node.daemonId && !isLocalNode) {
57399
59684
  return ctx.transport.meshCommand(node.daemonId, command, args);
57400
59685
  }
57401
59686
  if (isLocalTransport(ctx.transport)) {
@@ -57403,12 +59688,25 @@ async function commandForNode(ctx, node, command, args = {}) {
57403
59688
  }
57404
59689
  throw new Error(`Command '${command}' requires daemon IPC/local transport for node '${node.id}'`);
57405
59690
  }
59691
+ function isP2pTransportUnavailableError(error48) {
59692
+ return isP2pRelayTransportFailure(error48);
59693
+ }
59694
+ function buildRemoveNodeArgs(ctx, nodeId, sessionCleanupMode) {
59695
+ return {
59696
+ meshId: ctx.mesh.id,
59697
+ nodeId,
59698
+ ...sessionCleanupMode ? { sessionCleanupMode } : {},
59699
+ inlineMesh: ctx.mesh
59700
+ };
59701
+ }
57406
59702
  var MESH_STATUS_TOOL = {
57407
59703
  name: "mesh_status",
57408
- description: "Get the current status of all nodes in the repo mesh \u2014 health, git state, active sessions. Use this to decide which node to send work to.",
59704
+ description: "Get the current status of all nodes in the repo mesh \u2014 health, git state, active sessions, recovery hints, and recommended next steps. Use this to decide which node to send work to or how to recover from failures.",
57409
59705
  inputSchema: {
57410
59706
  type: "object",
57411
- properties: {}
59707
+ properties: {
59708
+ _gemini_compat: { type: "string", description: "Dummy property for Gemini compatibility. Ignore this." }
59709
+ }
57412
59710
  }
57413
59711
  };
57414
59712
  var MESH_LIST_NODES_TOOL = {
@@ -57416,7 +59714,9 @@ var MESH_LIST_NODES_TOOL = {
57416
59714
  description: "List all nodes in the mesh with their capabilities, platform, and workspace paths.",
57417
59715
  inputSchema: {
57418
59716
  type: "object",
57419
- properties: {}
59717
+ properties: {
59718
+ _gemini_compat: { type: "string", description: "Dummy property for Gemini compatibility. Ignore this." }
59719
+ }
57420
59720
  }
57421
59721
  };
57422
59722
  var MESH_ENQUEUE_TASK_TOOL = {
@@ -57432,18 +59732,51 @@ var MESH_ENQUEUE_TASK_TOOL = {
57432
59732
  };
57433
59733
  var MESH_VIEW_QUEUE_TOOL = {
57434
59734
  name: "mesh_view_queue",
57435
- description: "View the current status of the mesh work queue (pending, assigned, completed, failed tasks).",
59735
+ description: "View the mesh work queue with source-of-truth active counts separated from historical completed/failed/cancelled records.",
57436
59736
  inputSchema: {
57437
59737
  type: "object",
57438
59738
  properties: {
57439
59739
  status: {
57440
59740
  type: "array",
57441
59741
  items: { type: "string" },
57442
- description: "Filter by task status: pending, assigned, completed, failed. Returns all if omitted."
59742
+ description: "Explicit row filter by task status: pending, assigned, completed, failed, cancelled. Source-of-truth counts remain unfiltered; visible* counts describe returned rows."
59743
+ },
59744
+ view: {
59745
+ type: "string",
59746
+ enum: ["all", "active", "historical"],
59747
+ description: "Optional row view. active returns pending/assigned rows, historical returns completed/failed/cancelled rows, all returns every persisted queue row. Defaults to all for compatibility."
57443
59748
  }
57444
59749
  }
57445
59750
  }
57446
59751
  };
59752
+ var MESH_QUEUE_CANCEL_TOOL = {
59753
+ name: "mesh_queue_cancel",
59754
+ description: "Cancel a pending/assigned/completed/failed mesh queue task without deleting audit history. Use this to retire stale queue items that target dead sessions.",
59755
+ inputSchema: {
59756
+ type: "object",
59757
+ properties: {
59758
+ task_id: { type: "string", description: "Queue task ID to cancel." },
59759
+ reason: { type: "string", description: "Optional operator-visible reason for cancellation." }
59760
+ },
59761
+ required: ["task_id"]
59762
+ }
59763
+ };
59764
+ var MESH_QUEUE_REQUEUE_TOOL = {
59765
+ name: "mesh_queue_requeue",
59766
+ description: "Return a mesh queue task to pending for retry. By default clears stale assigned owner and target session so another live session can claim it.",
59767
+ inputSchema: {
59768
+ type: "object",
59769
+ properties: {
59770
+ task_id: { type: "string", description: "Queue task ID to requeue." },
59771
+ reason: { type: "string", description: "Optional operator-visible reason for requeueing." },
59772
+ target_node_id: { type: "string", description: "Optional replacement target node ID." },
59773
+ target_session_id: { type: "string", description: "Optional replacement target runtime session ID." },
59774
+ clear_target_node: { type: "boolean", description: "When true, remove any existing target node constraint." },
59775
+ keep_target_session: { type: "boolean", description: "When true, preserve an existing target session if target_session_id is not provided. Defaults false to avoid stale session targets." }
59776
+ },
59777
+ required: ["task_id"]
59778
+ }
59779
+ };
57447
59780
  var MESH_SEND_TASK_TOOL = {
57448
59781
  name: "mesh_send_task",
57449
59782
  description: "Legacy push-based task assignment. Enqueues a task specifically targeted at a given node. The node will pull it immediately if idle.",
@@ -57597,6 +59930,20 @@ var MESH_TASK_HISTORY_TOOL = {
57597
59930
  }
57598
59931
  }
57599
59932
  };
59933
+ var MESH_RECONCILE_LEDGER_TOOL = {
59934
+ name: "mesh_reconcile_ledger",
59935
+ description: "Reconcile daemon-local mesh ledgers by querying bounded ledger slices over P2P/DataChannel and importing missing entries into the coordinator local JSONL ledger. Cloud/D1 is not used as a ledger source of truth.",
59936
+ inputSchema: {
59937
+ type: "object",
59938
+ properties: {
59939
+ node_ids: { type: "array", items: { type: "string" }, description: "Optional node IDs to query. Defaults to all mesh nodes." },
59940
+ limit: { type: "number", description: "Bounded slice size per node. Defaults to 100 and is clamped by daemon-core." },
59941
+ after_id: { type: "string", description: "Optional cursor entry ID; remote slices return entries strictly after this ID when present." },
59942
+ since: { type: "string", description: "Optional ISO timestamp lower bound for queried entries." },
59943
+ import_entries: { type: "boolean", description: "When false, query and report evidence without importing remote entries. Defaults true." }
59944
+ }
59945
+ }
59946
+ };
57600
59947
  var MESH_REFINE_NODE_TOOL = {
57601
59948
  name: "mesh_refine_node",
57602
59949
  description: "The Refinery: Automatically validate and merge a completed worktree node back into its base branch. This tool automates the validation gate and merge queue step. It will merge the node's branch into its base branch and cleanly remove the worktree node and its sessions.",
@@ -57613,6 +59960,8 @@ var ALL_MESH_TOOLS = [
57613
59960
  MESH_LIST_NODES_TOOL,
57614
59961
  MESH_ENQUEUE_TASK_TOOL,
57615
59962
  MESH_VIEW_QUEUE_TOOL,
59963
+ MESH_QUEUE_CANCEL_TOOL,
59964
+ MESH_QUEUE_REQUEUE_TOOL,
57616
59965
  MESH_SEND_TASK_TOOL,
57617
59966
  MESH_READ_CHAT_TOOL,
57618
59967
  MESH_READ_DEBUG_TOOL,
@@ -57624,12 +59973,14 @@ var ALL_MESH_TOOLS = [
57624
59973
  MESH_REMOVE_NODE_TOOL,
57625
59974
  MESH_REFINE_NODE_TOOL,
57626
59975
  MESH_CLEANUP_SESSIONS_TOOL,
57627
- MESH_TASK_HISTORY_TOOL
59976
+ MESH_TASK_HISTORY_TOOL,
59977
+ MESH_RECONCILE_LEDGER_TOOL
57628
59978
  ];
57629
59979
  async function meshStatus(ctx) {
57630
59980
  await refreshMeshFromDaemon(ctx);
57631
59981
  const { mesh, transport } = ctx;
57632
59982
  const results = [];
59983
+ const ledgerSummary = getLedgerSummary(mesh.id);
57633
59984
  for (const node of mesh.nodes) {
57634
59985
  const entry = {
57635
59986
  nodeId: node.id,
@@ -57646,6 +59997,7 @@ async function meshStatus(ctx) {
57646
59997
  entry.branch = status?.branch;
57647
59998
  entry.isDirty = dirty;
57648
59999
  entry.uncommittedChanges = uncommittedChanges;
60000
+ entry.branchConvergence = buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges);
57649
60001
  } else if (isLocalTransport(transport)) {
57650
60002
  const statusResult = await commandForNode(ctx, node, "git_status", { workspace: node.workspace });
57651
60003
  const status = extractGitStatus(statusResult);
@@ -57655,13 +60007,70 @@ async function meshStatus(ctx) {
57655
60007
  entry.branch = status?.branch;
57656
60008
  entry.isDirty = dirty;
57657
60009
  entry.uncommittedChanges = uncommittedChanges;
60010
+ entry.branchConvergence = buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges);
57658
60011
  } else {
57659
60012
  entry.health = "unknown";
57660
60013
  entry.note = "No daemonId available for cloud status probe";
57661
60014
  }
57662
60015
  } catch (e) {
60016
+ const failure2 = buildCoordinatorP2pRelayFailure(e, {
60017
+ command: "git_status",
60018
+ targetDaemonId: node.daemonId,
60019
+ nodeId: node.id
60020
+ });
57663
60021
  entry.health = "degraded";
57664
- entry.error = e.message;
60022
+ entry.error = failure2.error;
60023
+ entry.degradedReason = failure2.recoverable ? "p2p_relay_failure" : "git_status_unavailable";
60024
+ Object.assign(entry, {
60025
+ code: failure2.code,
60026
+ transport: failure2.transport,
60027
+ recoverable: failure2.recoverable,
60028
+ retryRecommended: failure2.retryRecommended,
60029
+ nextAction: failure2.nextAction,
60030
+ noFallbackReason: failure2.noFallbackReason
60031
+ });
60032
+ }
60033
+ const recoveryContext = getSessionRecoveryContext(mesh.id, { nodeId: node.id });
60034
+ if (recoveryContext.consecutiveNodeFailures > 0) {
60035
+ entry.recoveryHints = {
60036
+ consecutiveFailures: recoveryContext.consecutiveNodeFailures,
60037
+ lastTaskMessage: recoveryContext.lastTaskMessage,
60038
+ advice: recoveryContext.advice,
60039
+ retryRecommended: recoveryContext.retryRecommended
60040
+ };
60041
+ }
60042
+ const activeLaunchFailure = getLatestActiveLaunchFailure(mesh.id, node.id);
60043
+ if (activeLaunchFailure && node.isLocalWorktree) {
60044
+ entry.health = "degraded";
60045
+ entry.degradedReason = "worktree_launch_failed";
60046
+ entry.launchReady = false;
60047
+ entry.launchBlockedReason = activeLaunchFailure.code || "mesh_launch_failed";
60048
+ entry.launchBlockedMessage = activeLaunchFailure.error || "Previous worktree session launch failed";
60049
+ entry.lastLaunchFailure = activeLaunchFailure;
60050
+ }
60051
+ const nextStepHints = [];
60052
+ if (entry.degradedReason === "worktree_launch_failed") {
60053
+ nextStepHints.push(`Retry mesh_launch_session(node_id: "${node.id}") after daemon mesh transport/P2P is healthy.`);
60054
+ nextStepHints.push(`If retry is not desired, cleanup the orphan worktree node with mesh_remove_node(node_id: "${node.id}").`);
60055
+ } else if (entry.health === "online" && node.isLocalWorktree) {
60056
+ nextStepHints.push(`Merge worktree to base via mesh_refine_node(node_id: "${node.id}")`);
60057
+ } else if (entry.health === "dirty") {
60058
+ nextStepHints.push(`Commit changes via mesh_checkpoint(node_id: "${node.id}", message: "...")`);
60059
+ } else if (entry.health === "degraded" && entry.error?.includes("git")) {
60060
+ nextStepHints.push("Initialize git repository or check workspace path.");
60061
+ }
60062
+ if (entry.branchConvergence?.needsConvergence === true && entry.branchConvergence.nextStep) {
60063
+ nextStepHints.push(String(entry.branchConvergence.nextStep));
60064
+ }
60065
+ if (recoveryContext.consecutiveNodeFailures > 0) {
60066
+ if (recoveryContext.retryRecommended) {
60067
+ nextStepHints.push(`Retry task on this node or launch a fresh session.`);
60068
+ } else {
60069
+ nextStepHints.push(`Consider reassigning work to a different node.`);
60070
+ }
60071
+ }
60072
+ if (nextStepHints.length > 0) {
60073
+ entry.nextStepHints = nextStepHints;
57665
60074
  }
57666
60075
  const relatedRepos = await collectRelatedRepoStatuses(ctx, node);
57667
60076
  if (relatedRepos.length) entry.relatedRepos = relatedRepos;
@@ -57673,10 +60082,11 @@ async function meshStatus(ctx) {
57673
60082
  repoIdentity: mesh.repoIdentity,
57674
60083
  policy: mesh.policy,
57675
60084
  refreshedAt: (/* @__PURE__ */ new Date()).toISOString(),
57676
- nodes: results
60085
+ nodes: results,
60086
+ branchConvergenceSummary: summarizeBranchConvergence(results)
57677
60087
  };
57678
60088
  try {
57679
- response.ledgerSummary = getLedgerSummary(mesh.id);
60089
+ response.ledgerSummary = ledgerSummary;
57680
60090
  } catch {
57681
60091
  }
57682
60092
  if (ctx.transport instanceof IpcTransport) {
@@ -57699,6 +60109,84 @@ async function meshTaskHistory(ctx, args) {
57699
60109
  const summary = getLedgerSummary(mesh.id);
57700
60110
  return JSON.stringify({ meshId: mesh.id, entries, summary }, null, 2);
57701
60111
  }
60112
+ async function meshReconcileLedger(ctx, args) {
60113
+ await refreshMeshFromDaemon(ctx);
60114
+ const requestedNodeIds = Array.isArray(args.node_ids) ? new Set(args.node_ids.map((id) => typeof id === "string" ? id.trim() : "").filter(Boolean)) : null;
60115
+ const nodes = ctx.mesh.nodes.filter((node) => !requestedNodeIds || requestedNodeIds.has(node.id));
60116
+ const replicas = [];
60117
+ const shouldImport = args.import_entries !== false;
60118
+ const queryArgs = {
60119
+ meshId: ctx.mesh.id,
60120
+ ...typeof args.limit === "number" ? { limit: args.limit } : {},
60121
+ ...typeof args.after_id === "string" && args.after_id.trim() ? { afterId: args.after_id.trim() } : {},
60122
+ ...typeof args.since === "string" && args.since.trim() ? { since: args.since.trim() } : {}
60123
+ };
60124
+ for (const node of nodes) {
60125
+ try {
60126
+ if (isLocalControlPlaneNode(ctx, node) || !node.daemonId) {
60127
+ const slice2 = readLedgerSlice(ctx.mesh.id, queryArgs);
60128
+ replicas.push(buildMeshLedgerReplicaEvidence({
60129
+ nodeId: node.id,
60130
+ daemonId: node.daemonId,
60131
+ transport: "local",
60132
+ slice: slice2,
60133
+ status: "local"
60134
+ }));
60135
+ continue;
60136
+ }
60137
+ const result = await commandForNode(ctx, node, "get_mesh_ledger_slice", queryArgs);
60138
+ const payload = unwrapCommandPayload(result);
60139
+ if (payload?.success === false) {
60140
+ throw new Error(payload.error || "remote get_mesh_ledger_slice failed");
60141
+ }
60142
+ const slice = payload?.slice ?? payload;
60143
+ if (slice?.protocol !== "adhdev.mesh.ledger.slice.v1" || !Array.isArray(slice.entries)) {
60144
+ throw new Error("remote daemon returned an invalid ledger slice payload");
60145
+ }
60146
+ const importResult = shouldImport ? appendRemoteLedgerEntries(ctx.mesh.id, slice.entries) : { accepted: 0, skippedDuplicate: 0, rejectedInvalid: 0, entries: [] };
60147
+ replicas.push(buildMeshLedgerReplicaEvidence({
60148
+ nodeId: node.id,
60149
+ daemonId: node.daemonId,
60150
+ transport: "p2p_datachannel",
60151
+ slice,
60152
+ importResult
60153
+ }));
60154
+ if (shouldImport && importResult.accepted > 0) {
60155
+ appendLedgerEntry(ctx.mesh.id, {
60156
+ kind: "ledger_replicated",
60157
+ nodeId: node.id,
60158
+ payload: {
60159
+ protocol: "adhdev.mesh.ledger.slice.v1",
60160
+ imported: importResult.accepted,
60161
+ skippedDuplicate: importResult.skippedDuplicate,
60162
+ rejectedInvalid: importResult.rejectedInvalid,
60163
+ nextAfterId: slice.cursor?.nextAfterId ?? null,
60164
+ via: "p2p_datachannel"
60165
+ }
60166
+ });
60167
+ }
60168
+ } catch (e) {
60169
+ replicas.push(buildMeshLedgerReplicaEvidence({
60170
+ nodeId: node.id,
60171
+ daemonId: node.daemonId,
60172
+ transport: node.daemonId ? "p2p_datachannel" : "local",
60173
+ status: "failed",
60174
+ error: e?.message ?? String(e)
60175
+ }));
60176
+ }
60177
+ }
60178
+ const evidence = buildMeshLedgerReconciliationEvidence(ctx.mesh.id, replicas);
60179
+ appendLedgerEntry(ctx.mesh.id, {
60180
+ kind: "ledger_reconciled",
60181
+ payload: {
60182
+ protocol: evidence.protocol,
60183
+ sourceOfTruth: evidence.sourceOfTruth,
60184
+ totals: evidence.totals,
60185
+ convergence: evidence.convergence
60186
+ }
60187
+ });
60188
+ return JSON.stringify({ success: true, evidence }, null, 2);
60189
+ }
57702
60190
  async function meshListNodes(ctx) {
57703
60191
  await refreshMeshFromDaemon(ctx);
57704
60192
  const { mesh } = ctx;
@@ -57720,12 +60208,38 @@ async function meshListNodes(ctx) {
57720
60208
  async function meshEnqueueTask(ctx, args) {
57721
60209
  try {
57722
60210
  const task = enqueueTask(ctx.mesh.id, args.message);
57723
- if (ctx.transport instanceof IpcTransport && ctx.localDaemonId) {
57724
- ctx.transport.meshCommand(ctx.localDaemonId, "trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
60211
+ if (isLocalTransport(ctx.transport) && !(ctx.transport instanceof IpcTransport)) {
60212
+ ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
57725
60213
  });
57726
- } else if (isLocalTransport(ctx.transport)) {
60214
+ return JSON.stringify({ success: true, taskId: task.id, status: task.status });
60215
+ }
60216
+ if (ctx.transport instanceof IpcTransport) {
57727
60217
  ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
57728
60218
  });
60219
+ const dispatchPromises = [];
60220
+ for (const node of ctx.mesh.nodes) {
60221
+ const isLocalNode = isLocalControlPlaneNode(ctx, node);
60222
+ if (isLocalNode || !node.daemonId) continue;
60223
+ dispatchPromises.push(
60224
+ ipcDispatchToRemoteAgent(ctx, node, { message: args.message }).then((result) => {
60225
+ if (result.success) {
60226
+ try {
60227
+ appendLedgerEntry(ctx.mesh.id, {
60228
+ kind: "task_dispatched",
60229
+ nodeId: node.id,
60230
+ sessionId: result.sessionId,
60231
+ payload: { message: args.message, via: "p2p_direct", taskId: task.id }
60232
+ });
60233
+ } catch {
60234
+ }
60235
+ }
60236
+ }).catch(() => {
60237
+ })
60238
+ );
60239
+ }
60240
+ Promise.all(dispatchPromises).catch(() => {
60241
+ });
60242
+ return JSON.stringify({ success: true, taskId: task.id, status: task.status });
57729
60243
  }
57730
60244
  return JSON.stringify({ success: true, taskId: task.id, status: task.status });
57731
60245
  } catch (e) {
@@ -57734,8 +60248,88 @@ async function meshEnqueueTask(ctx, args) {
57734
60248
  }
57735
60249
  async function meshViewQueue(ctx, args) {
57736
60250
  try {
57737
- const queue = getQueue(ctx.mesh.id, { status: args.status });
57738
- return JSON.stringify({ success: true, queue }, null, 2);
60251
+ const statusFilter = sanitizeQueueStatusFilter(args.status);
60252
+ const view = normalizeQueueViewMode(args.view);
60253
+ const fullQueue = annotateQueueStaleness(getQueue(ctx.mesh.id), ctx.mesh);
60254
+ const queue = filterQueueForView(fullQueue, view, statusFilter);
60255
+ const summary = buildQueueStatusSummary(fullQueue);
60256
+ const visibleSummary = buildQueueStatusSummary(queue);
60257
+ const maintenance = buildQueueMaintenanceReport(fullQueue);
60258
+ const staleAssignedTasks = maintenance.staleAssignedTasks || [];
60259
+ const requestedHistoricalRows = queue.some((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
60260
+ return JSON.stringify({
60261
+ success: true,
60262
+ sourceOfTruth: {
60263
+ kind: "mesh_work_queue_file",
60264
+ activeStatuses: ["pending", "assigned"],
60265
+ historicalStatuses: ["completed", "failed", "cancelled"],
60266
+ notes: "pending/assigned are active work; completed/failed/cancelled are historical ledger records and never stale assignments."
60267
+ },
60268
+ filter: {
60269
+ view,
60270
+ statuses: statusFilter,
60271
+ filtered: Boolean(statusFilter?.length) || view !== "all"
60272
+ },
60273
+ queue,
60274
+ visibleQueue: queue,
60275
+ visibleSummary,
60276
+ summary,
60277
+ activeCounts: summary.activeCounts,
60278
+ historicalCounts: summary.historicalCounts,
60279
+ activeCount: summary.activeCount,
60280
+ historicalCount: summary.historicalCount,
60281
+ visibleActiveCounts: visibleSummary.activeCounts,
60282
+ visibleHistoricalCounts: visibleSummary.historicalCounts,
60283
+ visibleActiveCount: visibleSummary.activeCount,
60284
+ visibleHistoricalCount: visibleSummary.historicalCount,
60285
+ staleAssignedTasks,
60286
+ staleAssignedCount: maintenance.staleAssignedCount,
60287
+ queueMaintenance: maintenance,
60288
+ cleanupDryRun: maintenance,
60289
+ ...view === "active" || statusFilter?.some((status) => ACTIVE_QUEUE_STATUSES.has(status)) ? {
60290
+ activeQueue: queue.filter((task) => ACTIVE_QUEUE_STATUSES.has(String(task?.status || "")))
60291
+ } : {},
60292
+ ...view === "historical" || requestedHistoricalRows ? {
60293
+ historicalQueue: queue.filter((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")))
60294
+ } : {},
60295
+ // Back-compat alias for callers already reading the first hardening payload.
60296
+ staleAssignments: staleAssignedTasks
60297
+ }, null, 2);
60298
+ } catch (e) {
60299
+ return JSON.stringify({ success: false, error: e.message });
60300
+ }
60301
+ }
60302
+ async function meshQueueCancel(ctx, args) {
60303
+ try {
60304
+ const taskId = (args.task_id || args.taskId || "").trim();
60305
+ if (!taskId) return JSON.stringify({ success: false, error: "task_id required" });
60306
+ const task = cancelTask(ctx.mesh.id, taskId, { reason: args.reason });
60307
+ if (!task) return JSON.stringify({ success: false, error: `Queue task '${taskId}' not found` });
60308
+ return JSON.stringify({ success: true, task }, null, 2);
60309
+ } catch (e) {
60310
+ return JSON.stringify({ success: false, error: e.message });
60311
+ }
60312
+ }
60313
+ async function meshQueueRequeue(ctx, args) {
60314
+ try {
60315
+ const taskId = (args.task_id || args.taskId || "").trim();
60316
+ if (!taskId) return JSON.stringify({ success: false, error: "task_id required" });
60317
+ const targetNodeId = (args.target_node_id || args.targetNodeId || "").trim() || void 0;
60318
+ const targetSessionId = (args.target_session_id || args.targetSessionId || "").trim() || void 0;
60319
+ const keepTargetSession = args.keep_target_session === true || args.keepTargetSession === true;
60320
+ const task = requeueTask(ctx.mesh.id, taskId, {
60321
+ reason: args.reason,
60322
+ targetNodeId,
60323
+ targetSessionId,
60324
+ clearTargetNode: args.clear_target_node === true || args.clearTargetNode === true,
60325
+ clearTargetSession: targetSessionId ? false : !keepTargetSession
60326
+ });
60327
+ if (!task) return JSON.stringify({ success: false, error: `Queue task '${taskId}' not found` });
60328
+ if (isLocalTransport(ctx.transport)) {
60329
+ ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
60330
+ });
60331
+ }
60332
+ return JSON.stringify({ success: true, task }, null, 2);
57739
60333
  } catch (e) {
57740
60334
  return JSON.stringify({ success: false, error: e.message });
57741
60335
  }
@@ -57745,6 +60339,24 @@ async function meshSendTask(ctx, args) {
57745
60339
  if (node.policy?.readOnly) {
57746
60340
  return JSON.stringify({ error: `Node '${args.node_id}' is read-only` });
57747
60341
  }
60342
+ const duplicate = hasRecentDuplicateDispatch(ctx, args);
60343
+ if (duplicate.duplicate) {
60344
+ return JSON.stringify({
60345
+ success: true,
60346
+ duplicate: true,
60347
+ dispatched: false,
60348
+ warning: "Duplicate mesh_send_task suppressed: the same node/session/message was dispatched recently.",
60349
+ nodeId: args.node_id,
60350
+ sessionId: args.session_id,
60351
+ source: duplicate.source,
60352
+ previousDispatch: duplicate.entry ? {
60353
+ id: duplicate.entry.id,
60354
+ timestamp: duplicate.entry.timestamp || duplicate.entry.updatedAt || duplicate.entry.createdAt,
60355
+ nodeId: duplicate.entry.nodeId || duplicate.entry.targetNodeId || duplicate.entry.assignedNodeId,
60356
+ sessionId: duplicate.entry.sessionId || duplicate.entry.targetSessionId || duplicate.entry.assignedSessionId
60357
+ } : void 0
60358
+ });
60359
+ }
57748
60360
  try {
57749
60361
  if (!isLocalTransport(ctx.transport) && node.daemonId) {
57750
60362
  const res = await ctx.transport.meshEnqueueTask(node.daemonId, {
@@ -57754,21 +60366,85 @@ async function meshSendTask(ctx, args) {
57754
60366
  });
57755
60367
  return JSON.stringify(res);
57756
60368
  }
57757
- const task = enqueueTask(ctx.mesh.id, args.message, { targetNodeId: args.node_id });
57758
- if (ctx.transport instanceof IpcTransport && node.daemonId && node.daemonId !== ctx.localDaemonId) {
57759
- ctx.transport.meshCommand(node.daemonId, "trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
60369
+ const isLocalNode = isLocalControlPlaneNode(ctx, node);
60370
+ if (ctx.transport instanceof IpcTransport && node.daemonId && !isLocalNode) {
60371
+ const cached2 = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id || ""));
60372
+ const result = await ipcDispatchToRemoteAgent(ctx, node, {
60373
+ session_id: args.session_id,
60374
+ message: args.message,
60375
+ providerType: cached2?.providerType
57760
60376
  });
57761
- } else if (isLocalTransport(ctx.transport)) {
60377
+ if (result.success) {
60378
+ const dispatchedSessionId = args.session_id || result.sessionId;
60379
+ try {
60380
+ appendLedgerEntry(ctx.mesh.id, {
60381
+ kind: "task_dispatched",
60382
+ nodeId: args.node_id,
60383
+ sessionId: dispatchedSessionId,
60384
+ payload: {
60385
+ message: args.message,
60386
+ via: "p2p_direct",
60387
+ ...dispatchedSessionId ? { targetSessionId: dispatchedSessionId } : {}
60388
+ }
60389
+ });
60390
+ } catch {
60391
+ }
60392
+ }
60393
+ return JSON.stringify({ ...result, nodeId: args.node_id, dispatched: result.success === true });
60394
+ }
60395
+ if (args.session_id && isLocalTransport(ctx.transport)) {
60396
+ const cached2 = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id));
60397
+ const dispatchResult = await commandForNode(ctx, node, "agent_command", {
60398
+ targetSessionId: args.session_id,
60399
+ ...cached2?.providerType ? { agentType: cached2.providerType, cliType: cached2.providerType, providerType: cached2.providerType } : {},
60400
+ action: "send_chat",
60401
+ message: args.message
60402
+ });
60403
+ const dispatchPayload = unwrapCommandPayload(dispatchResult);
60404
+ if (dispatchPayload?.success === false || dispatchResult?.success === false) {
60405
+ return JSON.stringify({
60406
+ success: false,
60407
+ nodeId: args.node_id,
60408
+ sessionId: args.session_id,
60409
+ error: dispatchPayload?.error || dispatchResult?.error || "agent_command rejected the task"
60410
+ });
60411
+ }
60412
+ try {
60413
+ appendLedgerEntry(ctx.mesh.id, {
60414
+ kind: "task_dispatched",
60415
+ nodeId: args.node_id,
60416
+ sessionId: args.session_id,
60417
+ providerType: cached2?.providerType,
60418
+ payload: { message: args.message, via: "local_direct" }
60419
+ });
60420
+ } catch {
60421
+ }
60422
+ return JSON.stringify({ success: true, dispatched: true, nodeId: args.node_id, sessionId: args.session_id });
60423
+ }
60424
+ const task = enqueueTask(ctx.mesh.id, args.message, {
60425
+ targetNodeId: args.node_id,
60426
+ targetSessionId: args.session_id
60427
+ });
60428
+ if (isLocalTransport(ctx.transport) || ctx.transport instanceof IpcTransport) {
57762
60429
  ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
57763
60430
  });
57764
60431
  }
57765
60432
  return JSON.stringify({ success: true, nodeId: args.node_id, taskId: task.id, status: task.status });
57766
60433
  } catch (e) {
57767
- return JSON.stringify({ success: false, error: e.message });
60434
+ const failure2 = buildCoordinatorP2pRelayFailure(e, {
60435
+ command: "mesh_send_task",
60436
+ targetDaemonId: node.daemonId,
60437
+ nodeId: args.node_id,
60438
+ sessionId: args.session_id
60439
+ });
60440
+ return JSON.stringify(failure2);
57768
60441
  }
57769
60442
  }
57770
60443
  async function meshReadChat(ctx, args) {
57771
- const node = await findNodeWithRefresh(ctx, args.node_id);
60444
+ const node = await findOptionalNodeWithRefresh(ctx, args.node_id);
60445
+ if (!node) {
60446
+ return JSON.stringify(buildMissingNodeReadChatRecovery(ctx, args), null, 2);
60447
+ }
57772
60448
  if (isLocalTransport(ctx.transport)) {
57773
60449
  const cached2 = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id));
57774
60450
  const providerSessionId = typeof args.provider_session_id === "string" && args.provider_session_id.trim() ? args.provider_session_id.trim() : cached2?.providerSessionId;
@@ -57871,19 +60547,28 @@ async function meshLaunchSession(ctx, args) {
57871
60547
  const coordinatorNode = resolveCoordinatorNode(ctx);
57872
60548
  const coordinatorDaemonId = coordinatorNode?.daemonId || ctx.localDaemonId;
57873
60549
  const spawnedSessionVisibility = readSpawnedSessionVisibility(ctx.mesh.policy);
57874
- const result = await commandForNode(ctx, node, "launch_cli", {
57875
- cliType: resolvedProviderType,
57876
- dir: node.workspace,
57877
- settings: {
57878
- meshNodeFor: ctx.mesh.id,
57879
- meshNodeId: args.node_id,
57880
- spawnedSessionVisibility,
57881
- ...coordinatorDaemonId ? { meshCoordinatorDaemonId: coordinatorDaemonId } : {},
57882
- ...coordinatorNode?.id ? { meshCoordinatorNodeId: coordinatorNode.id } : {},
57883
- launchedByCoordinator: true
57884
- }
57885
- });
60550
+ let result;
60551
+ try {
60552
+ result = await commandForNode(ctx, node, "launch_cli", {
60553
+ cliType: resolvedProviderType,
60554
+ dir: node.workspace,
60555
+ settings: {
60556
+ meshNodeFor: ctx.mesh.id,
60557
+ meshNodeId: args.node_id,
60558
+ spawnedSessionVisibility,
60559
+ ...coordinatorDaemonId ? { meshCoordinatorDaemonId: coordinatorDaemonId } : {},
60560
+ ...coordinatorNode?.id ? { meshCoordinatorNodeId: coordinatorNode.id } : {},
60561
+ launchedByCoordinator: true
60562
+ }
60563
+ });
60564
+ } catch (e) {
60565
+ return JSON.stringify(recordRecoverableLaunchFailure(ctx, node, resolvedProviderType, e), null, 2);
60566
+ }
57886
60567
  const launchPayload = extractLaunchPayload(result);
60568
+ if (launchPayload?.success === false || result?.success === false) {
60569
+ const launchError = new Error(launchPayload?.error || result?.error || "launch_cli rejected the session launch");
60570
+ return JSON.stringify(recordRecoverableLaunchFailure(ctx, node, resolvedProviderType, launchError), null, 2);
60571
+ }
57887
60572
  const runtimeSessionId = typeof launchPayload?.sessionId === "string" ? launchPayload.sessionId : typeof launchPayload?.id === "string" ? launchPayload.id : typeof launchPayload?.runtimeSessionId === "string" ? launchPayload.runtimeSessionId : "";
57888
60573
  const providerSessionId = typeof launchPayload?.providerSessionId === "string" && launchPayload.providerSessionId.trim() ? launchPayload.providerSessionId.trim() : void 0;
57889
60574
  if (runtimeSessionId) {
@@ -57902,7 +60587,8 @@ async function meshLaunchSession(ctx, args) {
57902
60587
  });
57903
60588
  } catch {
57904
60589
  }
57905
- if (ctx.transport instanceof IpcTransport && node.daemonId && node.daemonId !== ctx.localDaemonId) {
60590
+ const isLocalNode = isLocalControlPlaneNode(ctx, node);
60591
+ if (ctx.transport instanceof IpcTransport && node.daemonId && !isLocalNode) {
57906
60592
  ctx.transport.meshCommand(node.daemonId, "trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
57907
60593
  });
57908
60594
  } else if (isLocalTransport(ctx.transport)) {
@@ -57952,7 +60638,7 @@ async function meshLaunchSession(ctx, args) {
57952
60638
  }
57953
60639
  return JSON.stringify({ ...res, resolvedProviderType }, null, 2);
57954
60640
  } catch (e) {
57955
- return JSON.stringify({ success: false, error: e.message });
60641
+ return JSON.stringify(recordRecoverableLaunchFailure(ctx, node, resolvedProviderType, e), null, 2);
57956
60642
  }
57957
60643
  } else {
57958
60644
  return JSON.stringify({ error: "Cloud mesh launch_session requires node daemonId" });
@@ -57960,31 +60646,43 @@ async function meshLaunchSession(ctx, args) {
57960
60646
  }
57961
60647
  async function meshGitStatus(ctx, args) {
57962
60648
  const node = await findNodeWithRefresh(ctx, args.node_id);
57963
- if (!isLocalTransport(ctx.transport) && node.daemonId) {
57964
- const result = await ctx.transport.gitStatus(node.daemonId, node.workspace, true);
57965
- return JSON.stringify({
57966
- nodeId: args.node_id,
57967
- workspace: node.workspace,
57968
- status: extractGitStatus(result),
57969
- diff: extractGitDiff(result),
57970
- relatedRepos: await collectRelatedRepoStatuses(ctx, node)
57971
- }, null, 2);
57972
- } else if (isLocalTransport(ctx.transport)) {
57973
- const statusResult = await commandForNode(ctx, node, "git_status", {
57974
- workspace: node.workspace
57975
- });
57976
- const diffResult = await commandForNode(ctx, node, "git_diff_summary", {
57977
- workspace: node.workspace
60649
+ try {
60650
+ if (!isLocalTransport(ctx.transport) && node.daemonId) {
60651
+ const result = await ctx.transport.gitStatus(node.daemonId, node.workspace, true);
60652
+ return JSON.stringify({
60653
+ nodeId: args.node_id,
60654
+ workspace: node.workspace,
60655
+ status: extractGitStatus(result),
60656
+ diff: extractGitDiff(result),
60657
+ relatedRepos: await collectRelatedRepoStatuses(ctx, node)
60658
+ }, null, 2);
60659
+ } else if (isLocalTransport(ctx.transport)) {
60660
+ const statusResult = await commandForNode(ctx, node, "git_status", {
60661
+ workspace: node.workspace
60662
+ });
60663
+ const diffResult = await commandForNode(ctx, node, "git_diff_summary", {
60664
+ workspace: node.workspace
60665
+ });
60666
+ return JSON.stringify({
60667
+ nodeId: args.node_id,
60668
+ workspace: node.workspace,
60669
+ status: extractGitStatus(statusResult),
60670
+ diff: extractGitDiff(diffResult),
60671
+ relatedRepos: await collectRelatedRepoStatuses(ctx, node)
60672
+ }, null, 2);
60673
+ } else {
60674
+ return JSON.stringify({ error: "No daemonId available for cloud git_status probe" });
60675
+ }
60676
+ } catch (e) {
60677
+ const failure2 = buildCoordinatorP2pRelayFailure(e, {
60678
+ command: "git_status",
60679
+ targetDaemonId: node.daemonId,
60680
+ nodeId: args.node_id
57978
60681
  });
57979
60682
  return JSON.stringify({
57980
- nodeId: args.node_id,
57981
- workspace: node.workspace,
57982
- status: extractGitStatus(statusResult),
57983
- diff: extractGitDiff(diffResult),
57984
- relatedRepos: await collectRelatedRepoStatuses(ctx, node)
60683
+ ...failure2,
60684
+ workspace: node.workspace
57985
60685
  }, null, 2);
57986
- } else {
57987
- return JSON.stringify({ error: "No daemonId available for cloud git_status probe" });
57988
60686
  }
57989
60687
  }
57990
60688
  async function meshCheckpoint(ctx, args) {
@@ -58131,12 +60829,28 @@ async function meshCleanupSessions(ctx, args) {
58131
60829
  async function meshRemoveNode(ctx, args) {
58132
60830
  const node = await findNodeWithRefresh(ctx, args.node_id);
58133
60831
  if (isLocalTransport(ctx.transport)) {
58134
- const result = await commandForNode(ctx, node, "remove_mesh_node", {
58135
- meshId: ctx.mesh.id,
58136
- nodeId: args.node_id,
58137
- ...args.session_cleanup_mode ? { sessionCleanupMode: args.session_cleanup_mode } : {},
58138
- inlineMesh: ctx.mesh
58139
- });
60832
+ const removeArgs = buildRemoveNodeArgs(ctx, args.node_id, args.session_cleanup_mode);
60833
+ let result;
60834
+ let transportFallback;
60835
+ try {
60836
+ result = await commandForNode(ctx, node, "remove_mesh_node", removeArgs);
60837
+ } catch (e) {
60838
+ if (ctx.transport instanceof IpcTransport && node.isLocalWorktree && isP2pTransportUnavailableError(e)) {
60839
+ result = await ctx.transport.command("remove_mesh_node", removeArgs);
60840
+ transportFallback = {
60841
+ from: "p2p_mesh_relay",
60842
+ to: "local_control_plane",
60843
+ reason: e?.message || String(e)
60844
+ };
60845
+ } else {
60846
+ return JSON.stringify({
60847
+ success: false,
60848
+ code: isP2pTransportUnavailableError(e) ? "p2p_unavailable" : "mesh_remove_node_failed",
60849
+ error: e?.message || String(e),
60850
+ recoveryHint: isP2pTransportUnavailableError(e) ? "If this is an ADHDev-managed local worktree, retry from a coordinator connected to the daemon that owns the worktree; dashboard command/data-plane traffic still requires P2P." : "Inspect mesh_status and retry after resolving the reported failure."
60851
+ }, null, 2);
60852
+ }
60853
+ }
58140
60854
  if (result?.success && result.removed !== false) {
58141
60855
  const idx = ctx.mesh.nodes.findIndex((n) => n.id === args.node_id);
58142
60856
  if (idx >= 0) {
@@ -58144,7 +60858,7 @@ async function meshRemoveNode(ctx, args) {
58144
60858
  ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
58145
60859
  }
58146
60860
  }
58147
- return JSON.stringify(result, null, 2);
60861
+ return JSON.stringify({ ...result || {}, ...transportFallback ? { transportFallback } : {} }, null, 2);
58148
60862
  } else if (!isLocalTransport(ctx.transport) && node.daemonId) {
58149
60863
  try {
58150
60864
  const res = await ctx.transport.meshRemoveNode(node.daemonId, {
@@ -58338,15 +61052,6 @@ var CloudTransport = class {
58338
61052
  });
58339
61053
  if (!res.ok) throw new Error(`Delete remote mesh failed: ${res.status}`);
58340
61054
  }
58341
- async syncMeshLedger(meshId, data) {
58342
- const res = await fetch(`${this.baseUrl}/api/v1/repo-meshes/${encodeURIComponent(meshId)}/ledger/sync`, {
58343
- method: "POST",
58344
- headers: this.headers(),
58345
- body: JSON.stringify(data)
58346
- });
58347
- if (!res.ok) throw new Error(`Sync mesh ledger failed: ${res.status}`);
58348
- return res.json();
58349
- }
58350
61055
  async listDaemons() {
58351
61056
  const res = await fetch(`${this.baseUrl}/api/v1/daemons`, { headers: this.headers() });
58352
61057
  if (!res.ok) throw new Error(`List daemons failed: ${res.status}`);
@@ -59840,6 +62545,15 @@ async function startMcpServer(opts) {
59840
62545
  process.exit(1);
59841
62546
  }
59842
62547
  let localDaemonId;
62548
+ let localMachineId;
62549
+ if (transport instanceof LocalTransport || transport instanceof IpcTransport) {
62550
+ try {
62551
+ const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports));
62552
+ const cfg = loadConfig2();
62553
+ if (cfg.registeredMachineId) localMachineId = cfg.registeredMachineId;
62554
+ } catch {
62555
+ }
62556
+ }
59843
62557
  if (transport instanceof IpcTransport) {
59844
62558
  try {
59845
62559
  const statusResult = await transport.getStatus();
@@ -59848,7 +62562,7 @@ async function startMcpServer(opts) {
59848
62562
  } catch {
59849
62563
  }
59850
62564
  }
59851
- const meshCtx = { mesh, transport, ...localDaemonId ? { localDaemonId } : {} };
62565
+ const meshCtx = { mesh, transport, ...localDaemonId ? { localDaemonId } : {}, ...localMachineId ? { localMachineId } : {} };
59852
62566
  const coordinatorPrompt = await buildMeshModeCoordinatorPrompt(mesh);
59853
62567
  const server2 = new import_server.Server(
59854
62568
  { name: "adhdev-mcp-server", version: "0.9.76" },
@@ -59888,6 +62602,12 @@ async function startMcpServer(opts) {
59888
62602
  case "mesh_view_queue":
59889
62603
  text = await meshViewQueue(meshCtx, a);
59890
62604
  break;
62605
+ case "mesh_queue_cancel":
62606
+ text = await meshQueueCancel(meshCtx, a);
62607
+ break;
62608
+ case "mesh_queue_requeue":
62609
+ text = await meshQueueRequeue(meshCtx, a);
62610
+ break;
59891
62611
  case "mesh_send_task":
59892
62612
  text = await meshSendTask(meshCtx, a);
59893
62613
  break;
@@ -59924,6 +62644,9 @@ async function startMcpServer(opts) {
59924
62644
  case "mesh_task_history":
59925
62645
  text = await meshTaskHistory(meshCtx, a);
59926
62646
  break;
62647
+ case "mesh_reconcile_ledger":
62648
+ text = await meshReconcileLedger(meshCtx, a);
62649
+ break;
59927
62650
  default:
59928
62651
  return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
59929
62652
  }