@adhdev/daemon-standalone 0.9.82-rc.22 → 0.9.82-rc.221

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.
@@ -35,9 +35,128 @@ __export(index_exports, {
35
35
  });
36
36
  module.exports = __toCommonJS(index_exports);
37
37
 
38
+ // src/tools/mesh-tools.ts
39
+ var import_node_crypto = require("crypto");
40
+
38
41
  // src/transports/ipc.ts
39
42
  var DEFAULT_IPC_PORT = 19222;
40
43
  var DEFAULT_IPC_PATH = "/ipc";
44
+ var DEFAULT_IPC_COMMAND_TIMEOUT_MS = 15e3;
45
+ var IPC_COMMAND_TIMEOUTS_MS = {
46
+ mesh_relay_command: 12e4,
47
+ agent_command: 3e4,
48
+ git_status: 45e3,
49
+ git_diff_summary: 45e3,
50
+ fast_forward_mesh_node: 12e4,
51
+ mesh_status: 12e4
52
+ };
53
+ var WS_CONNECTING = 0;
54
+ var WS_OPEN = 1;
55
+ var POOL_IDLE_EVICT_MS = 5 * 6e4;
56
+ var POOL_MAX_AGE_MS = 10 * 6e4;
57
+ var connectionPool = /* @__PURE__ */ new Map();
58
+ function buildRequestId() {
59
+ return `mcp_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
60
+ }
61
+ function getTimeoutMs(type, nestedCommand) {
62
+ return Math.max(
63
+ IPC_COMMAND_TIMEOUTS_MS[type] ?? DEFAULT_IPC_COMMAND_TIMEOUT_MS,
64
+ IPC_COMMAND_TIMEOUTS_MS[nestedCommand] ?? DEFAULT_IPC_COMMAND_TIMEOUT_MS
65
+ );
66
+ }
67
+ function getOrCreateConnection(WebSocketCtor, url) {
68
+ const existing = connectionPool.get(url);
69
+ if (existing) {
70
+ const { readyState } = existing.ws;
71
+ const now2 = Date.now();
72
+ const isAlive = readyState === WS_CONNECTING || readyState === WS_OPEN;
73
+ const isIdle = now2 - existing.lastUsedAt > POOL_IDLE_EVICT_MS && existing.pending.size === 0;
74
+ const isTooOld = now2 - existing.createdAt > POOL_MAX_AGE_MS && existing.pending.size === 0;
75
+ if (isAlive && !isIdle && !isTooOld) {
76
+ return existing;
77
+ }
78
+ if (isAlive && (isIdle || isTooOld)) {
79
+ try {
80
+ existing.ws.close();
81
+ } catch {
82
+ }
83
+ connectionPool.delete(url);
84
+ }
85
+ connectionPool.delete(url);
86
+ }
87
+ const now = Date.now();
88
+ const conn = {
89
+ ws: new WebSocketCtor(url),
90
+ ready: false,
91
+ commandQueue: [],
92
+ pending: /* @__PURE__ */ new Map(),
93
+ lastUsedAt: now,
94
+ createdAt: now
95
+ };
96
+ connectionPool.set(url, conn);
97
+ const drainQueue = () => {
98
+ conn.ready = true;
99
+ for (const { type, args, requestId } of conn.commandQueue) {
100
+ conn.ws.send(JSON.stringify({ type: "ext:command", payload: { command: type, args, requestId } }));
101
+ }
102
+ conn.commandQueue = [];
103
+ };
104
+ let tornDown = false;
105
+ const teardown = (error) => {
106
+ if (tornDown) return;
107
+ tornDown = true;
108
+ connectionPool.delete(url);
109
+ conn.ready = false;
110
+ for (const [, req] of conn.pending) {
111
+ clearTimeout(req.timer);
112
+ req.reject(error);
113
+ }
114
+ conn.pending.clear();
115
+ conn.commandQueue = [];
116
+ };
117
+ conn.ws.addEventListener("open", () => {
118
+ conn.ws.send(JSON.stringify({
119
+ type: "ext:register",
120
+ payload: {
121
+ ideType: "mcp-server",
122
+ ideVersion: "1.0.0",
123
+ extensionVersion: "1.0.0",
124
+ instanceId: `mcp-server-${process.pid}`,
125
+ machineId: "mcp-server",
126
+ workspaceFolders: []
127
+ }
128
+ }));
129
+ });
130
+ conn.ws.addEventListener("message", (event) => {
131
+ try {
132
+ const raw = typeof event.data === "string" ? event.data : String(event.data);
133
+ const msg = JSON.parse(raw);
134
+ if (msg?.type === "daemon:welcome") {
135
+ drainQueue();
136
+ return;
137
+ }
138
+ if (msg?.type !== "ext:command_result") return;
139
+ const req = conn.pending.get(msg?.payload?.requestId);
140
+ if (!req) return;
141
+ conn.pending.delete(msg.payload.requestId);
142
+ clearTimeout(req.timer);
143
+ const payload = msg.payload;
144
+ if (payload?.success === false) {
145
+ req.reject(new Error(payload.error || "Daemon IPC command failed"));
146
+ } else {
147
+ req.resolve(payload?.result ?? payload);
148
+ }
149
+ } catch {
150
+ }
151
+ });
152
+ conn.ws.addEventListener("error", () => {
153
+ teardown(new Error(`Cannot connect to daemon IPC at ${url}`));
154
+ });
155
+ conn.ws.addEventListener("close", () => {
156
+ teardown(new Error(`Daemon IPC connection closed: ${url}`));
157
+ });
158
+ return conn;
159
+ }
41
160
  var IpcTransport = class {
42
161
  port;
43
162
  path;
@@ -66,73 +185,41 @@ var IpcTransport = class {
66
185
  args
67
186
  });
68
187
  }
69
- async sendIpcCommand(type, args) {
188
+ sendIpcCommand(type, args) {
70
189
  const WebSocketCtor = globalThis.WebSocket;
71
190
  if (!WebSocketCtor) {
72
- throw new Error("WebSocket is not available in this Node runtime; Node 20+ is required for daemon IPC mode");
191
+ return Promise.reject(new Error("WebSocket is not available in this Node runtime; Node 20+ is required for daemon IPC mode"));
73
192
  }
193
+ const requestId = buildRequestId();
194
+ const nestedCommand = typeof args?.command === "string" ? args.command : "";
195
+ const timeoutMs = getTimeoutMs(type, nestedCommand);
196
+ const targetDaemonId = typeof args?.targetDaemonId === "string" ? args.targetDaemonId : "";
197
+ const diagnosticParts = [
198
+ `command='${type}'`,
199
+ ...nestedCommand ? [`relayedCommand='${nestedCommand}'`] : [],
200
+ ...targetDaemonId ? [`targetDaemonId='${targetDaemonId.slice(0, 12)}'`] : [],
201
+ ...typeof args?.nodeId === "string" ? [`nodeId='${args.nodeId}'`] : [],
202
+ ...typeof args?.workspace === "string" ? [`workspace='${args.workspace}'`] : []
203
+ ];
204
+ const url = `ws://127.0.0.1:${this.port}${this.path}`;
74
205
  return new Promise((resolve, reject) => {
75
- const requestId = `mcp_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
76
- const ws = new WebSocketCtor(`ws://127.0.0.1:${this.port}${this.path}`);
77
- let settled = false;
78
- const finish = (fn) => {
79
- if (settled) return;
80
- settled = true;
81
- clearTimeout(timeout);
82
- try {
83
- ws.close();
84
- } catch {
85
- }
86
- fn();
87
- };
88
- const timeoutMs = type === "mesh_relay_command" ? 6e4 : 15e3;
89
- const timeout = setTimeout(() => {
90
- finish(() => reject(new Error(`Daemon IPC command '${type}' timed out after ${Math.round(timeoutMs / 1e3)}s`)));
206
+ let conn;
207
+ try {
208
+ conn = getOrCreateConnection(WebSocketCtor, url);
209
+ } catch (e) {
210
+ return reject(new Error(`Failed to create IPC connection: ${e?.message || e}`));
211
+ }
212
+ const timer = setTimeout(() => {
213
+ conn.pending.delete(requestId);
214
+ reject(new Error(`Daemon IPC ${diagnosticParts.join(" ")} timed out after ${Math.round(timeoutMs / 1e3)}s (requestId=${requestId})`));
91
215
  }, timeoutMs);
92
- let commandSent = false;
93
- const send = () => {
94
- if (commandSent) return;
95
- commandSent = true;
96
- ws.send(JSON.stringify({
97
- type: "ext:command",
98
- payload: { command: type, args, requestId }
99
- }));
100
- };
101
- ws.addEventListener("open", () => {
102
- ws.send(JSON.stringify({
103
- type: "ext:register",
104
- payload: {
105
- ideType: "mcp-server",
106
- ideVersion: "1.0.0",
107
- extensionVersion: "1.0.0",
108
- instanceId: `mcp-server-${process.pid}`,
109
- machineId: "mcp-server",
110
- workspaceFolders: []
111
- }
112
- }));
113
- });
114
- ws.addEventListener("message", (event) => {
115
- try {
116
- const raw = typeof event.data === "string" ? event.data : String(event.data);
117
- const msg = JSON.parse(raw);
118
- if (msg?.type === "daemon:welcome") {
119
- send();
120
- return;
121
- }
122
- if (msg?.type !== "ext:command_result") return;
123
- if (msg?.payload?.requestId !== requestId) return;
124
- const payload = msg.payload;
125
- if (payload?.success === false) {
126
- finish(() => reject(new Error(payload.error || `Daemon IPC command '${type}' failed`)));
127
- return;
128
- }
129
- finish(() => resolve(payload?.result ?? payload));
130
- } catch {
131
- }
132
- });
133
- ws.addEventListener("error", () => {
134
- finish(() => reject(new Error(`Cannot connect to daemon IPC at ws://127.0.0.1:${this.port}${this.path}`)));
135
- });
216
+ conn.pending.set(requestId, { resolve, reject, timer });
217
+ conn.lastUsedAt = Date.now();
218
+ if (conn.ready) {
219
+ conn.ws.send(JSON.stringify({ type: "ext:command", payload: { command: type, args, requestId } }));
220
+ } else {
221
+ conn.commandQueue.push({ type, args, requestId });
222
+ }
136
223
  });
137
224
  }
138
225
  };
@@ -143,10 +230,6 @@ function isLocalTransport(transport) {
143
230
  }
144
231
 
145
232
  // src/tools/chat-compact.ts
146
- function isAssistantLike(message) {
147
- const role = String(message?.role ?? "").toLowerCase();
148
- return role === "assistant" || role === "agent";
149
- }
150
233
  function messageContent(message) {
151
234
  const content = message?.content;
152
235
  if (typeof content === "string") return content;
@@ -165,11 +248,36 @@ function isCoordinatorVisibleMessage(message) {
165
248
  if (meta?.internal === true || meta?.debug === true || meta?.control === true || meta?.userVisible === false || meta?.user_visible === false) return false;
166
249
  return role === "user" || role === "assistant" || role === "agent";
167
250
  }
251
+ function summarizeToolMessage(message) {
252
+ if (!message || typeof message !== "object") return null;
253
+ const kind = String(message.kind ?? message.type ?? message.messageKind ?? "").toLowerCase();
254
+ const role = String(message.role ?? "").toLowerCase();
255
+ if (kind === "terminal" || kind === "bash") {
256
+ const cmd = message.command ?? message.cmd ?? message.input ?? messageContent(message);
257
+ const exit = message.exitCode ?? message.exit_code ?? message.code;
258
+ const cmdShort = typeof cmd === "string" ? cmd.split("\n")[0].slice(0, 120) : null;
259
+ if (!cmdShort) return null;
260
+ return exit !== void 0 && exit !== null ? `[Bash] ${cmdShort} \u2192 exit ${exit}` : `[Bash] ${cmdShort}`;
261
+ }
262
+ if (kind === "tool_call" || kind === "tool" || role === "tool") {
263
+ const name = message.name ?? message.toolName ?? message.tool_name ?? message.function?.name;
264
+ if (typeof name === "string" && name.trim()) return `[Tool] ${name.trim()}`;
265
+ return null;
266
+ }
267
+ if (kind === "tool_result") {
268
+ const exit = message.exitCode ?? message.exit_code ?? message.code;
269
+ const name = message.name ?? message.toolName ?? message.tool_name;
270
+ const label = typeof name === "string" && name.trim() ? name.trim() : "tool";
271
+ return exit !== void 0 && exit !== null ? `[Tool result: ${label}] exit ${exit}` : null;
272
+ }
273
+ return null;
274
+ }
168
275
  function buildCompactMessageTail(visibleMessages, opts) {
169
- const summary = typeof opts.summary === "string" ? opts.summary.trim() : "";
170
- const shouldOmitSummaryMessage = !!summary && !!opts.finalAssistant && isAssistantLike(opts.finalAssistant) && messageContent(opts.finalAssistant).trim() === summary;
171
- const sourceMessages = shouldOmitSummaryMessage ? visibleMessages.filter((message) => message !== opts.finalAssistant) : visibleMessages;
172
- return sourceMessages.slice(-opts.limit);
276
+ const tail = visibleMessages.slice(-opts.limit);
277
+ if (opts.finalAssistant && !tail.includes(opts.finalAssistant)) {
278
+ return [opts.finalAssistant, ...tail];
279
+ }
280
+ return tail;
173
281
  }
174
282
  function compactChatPayload(payload, opts = {}) {
175
283
  const rawMessages = Array.isArray(payload?.messages) ? payload.messages : [];
@@ -181,6 +289,9 @@ function compactChatPayload(payload, opts = {}) {
181
289
  });
182
290
  const summary = typeof payload?.summary === "string" && payload.summary.trim() ? payload.summary.trim() : messageContent(finalAssistant).trim();
183
291
  const messages = buildCompactMessageTail(visible, { summary, finalAssistant, limit });
292
+ const toolSummaries = rawMessages.filter((m) => !isCoordinatorVisibleMessage(m)).map(summarizeToolMessage).filter((s) => s !== null);
293
+ const omittedMessages = Math.max(0, rawMessages.length - messages.length);
294
+ const filteredMessages = Math.max(0, rawMessages.length - visible.length);
184
295
  return {
185
296
  success: payload?.success !== false,
186
297
  compact: true,
@@ -190,8 +301,9 @@ function compactChatPayload(payload, opts = {}) {
190
301
  providerSessionId: payload?.providerSessionId ?? null,
191
302
  totalMessages: rawMessages.length,
192
303
  visibleMessages: visible.length,
193
- filteredMessages: visible.length,
194
- omittedMessages: Math.max(0, rawMessages.length - visible.length),
304
+ filteredMessages,
305
+ omittedMessages,
306
+ ...toolSummaries.length > 0 ? { toolSummaries } : {},
195
307
  summary,
196
308
  ...payload?.changedFiles !== void 0 ? { changedFiles: payload.changedFiles } : {},
197
309
  ...payload?.testsRun !== void 0 ? { testsRun: payload.testsRun } : {},
@@ -241,17 +353,64 @@ function annotateRapidReadChatAdvisory(payload, options) {
241
353
 
242
354
  // src/tools/mesh-tools.ts
243
355
  var import_daemon_core = require("@adhdev/daemon-core");
356
+ var SESSION_PROVIDER_METADATA_TTL_MS = 30 * 6e4;
244
357
  var meshSessionProviderMetadata = /* @__PURE__ */ new Map();
358
+ function getSessionMetadata(key) {
359
+ const entry = meshSessionProviderMetadata.get(key);
360
+ if (!entry) return void 0;
361
+ if (entry.expiresAt <= Date.now()) {
362
+ meshSessionProviderMetadata.delete(key);
363
+ return void 0;
364
+ }
365
+ return entry;
366
+ }
367
+ var ACTIVE_WORK_POLLING_BACKOFF_MS = 6e4;
368
+ function buildActiveWorkPollingGuidance(summary, now = Date.now()) {
369
+ if (!summary || summary.generatingCount <= 0) return void 0;
370
+ return {
371
+ activeGeneratingWork: true,
372
+ generatingCount: summary.generatingCount,
373
+ doNotPollBefore: new Date(now + ACTIVE_WORK_POLLING_BACKOFF_MS).toISOString(),
374
+ eventSurface: "pendingCoordinatorEvents",
375
+ nextRecommendedAction: "Wait for pendingCoordinatorEvents/completion events or an explicit user status request. If no terminal evidence appears and the user asks for status, make one bounded status check, then wait again.",
376
+ message: "Do not repeatedly poll mesh_status/mesh_view_queue/mesh_read_chat while delegated work is generating; terminal ledger or completion evidence will be surfaced through pendingCoordinatorEvents when available."
377
+ };
378
+ }
245
379
  function readString(value) {
246
380
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
247
381
  }
382
+ function summarizeTaskMessage(message) {
383
+ const taskSummary = message.replace(/\s+/g, " ").trim();
384
+ const taskTitle = taskSummary.length > 96 ? `${taskSummary.slice(0, 93)}...` : taskSummary;
385
+ return { taskTitle: taskTitle || "(untitled task)", taskSummary };
386
+ }
387
+ function buildDirectTaskPayload(message, via, opts) {
388
+ const descriptor = summarizeTaskMessage(message);
389
+ return {
390
+ source: "direct",
391
+ via,
392
+ taskId: opts.taskId,
393
+ message,
394
+ taskTitle: descriptor.taskTitle,
395
+ taskSummary: descriptor.taskSummary,
396
+ ...opts.taskMode ? { taskMode: opts.taskMode } : {},
397
+ ...opts.providerType ? { providerType: opts.providerType } : {},
398
+ ...opts.targetSessionId ? { targetSessionId: opts.targetSessionId } : {},
399
+ ...opts.dispatchedToIdleSession !== void 0 ? { dispatchedToIdleSession: opts.dispatchedToIdleSession } : {}
400
+ };
401
+ }
402
+ function findNode(mesh, nodeId) {
403
+ const node = mesh.nodes.find((n) => n.id === nodeId);
404
+ if (!node) throw new Error(`Node '${nodeId}' is not a member of mesh '${mesh.name}'`);
405
+ return node;
406
+ }
248
407
  var DUPLICATE_DISPATCH_WINDOW_MS = 6e4;
249
408
  var STALE_ASSIGNED_QUEUE_MS = 30 * 6e4;
250
409
  var OLD_HISTORICAL_QUEUE_RECORD_MS = 7 * 24 * 60 * 6e4;
251
410
  var ACTIVE_QUEUE_STATUSES = /* @__PURE__ */ new Set(["pending", "assigned"]);
252
411
  var HISTORICAL_QUEUE_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
253
412
  async function refreshMeshFromDaemon(ctx) {
254
- if (!(ctx.transport instanceof IpcTransport)) return;
413
+ if (!isLocalTransport(ctx.transport)) return;
255
414
  try {
256
415
  const result = await ctx.transport.command("get_mesh", { meshId: ctx.mesh.id });
257
416
  if (!result?.success || !Array.isArray(result.mesh?.nodes)) return;
@@ -412,6 +571,25 @@ function extractStatusMetadataSessions(value) {
412
571
  function resolveSessionProviderType(session) {
413
572
  return readString(session?.providerType) || readString(session?.cliType) || readString(session?.agentType) || "";
414
573
  }
574
+ function isMeshCoordinatorSessionRecord(session) {
575
+ return Boolean(
576
+ readString(session?.settings?.meshCoordinatorFor) || readString(session?.meta?.meshCoordinatorFor) || readString(session?.metadata?.meshCoordinatorFor) || readString(session?.meshCoordinatorFor)
577
+ );
578
+ }
579
+ function isUnmanagedSessionRecord(session) {
580
+ const hasMeshNodeFor = Boolean(
581
+ readString(session?.settings?.meshNodeFor) || readString(session?.meta?.meshNodeFor) || readString(session?.metadata?.meshNodeFor) || readString(session?.meshNodeFor)
582
+ );
583
+ if (hasMeshNodeFor) return false;
584
+ if (isMeshCoordinatorSessionRecord(session)) return false;
585
+ const launchedByCoordinator = Boolean(
586
+ session?.settings?.launchedByCoordinator === true || session?.meta?.launchedByCoordinator === true || session?.launchedByCoordinator === true
587
+ );
588
+ return !launchedByCoordinator;
589
+ }
590
+ function isWorkerTaskMode(taskMode) {
591
+ return taskMode !== "live_debug_readonly";
592
+ }
415
593
  function addSessionRecord(target, session) {
416
594
  if (!session || typeof session !== "object" || isTerminalSessionRecord(session)) return;
417
595
  const sessionId = readSessionRecordId(session);
@@ -480,18 +658,26 @@ function queueAssignmentStaleReason(task, liveness) {
480
658
  }
481
659
  function buildQueueStatusSummary(queue) {
482
660
  const counts = { pending: 0, assigned: 0, completed: 0, failed: 0, cancelled: 0 };
661
+ let staleAssigned = 0;
483
662
  for (const task of queue) {
484
663
  const status = typeof task?.status === "string" ? task.status : void 0;
485
664
  if (status && Object.prototype.hasOwnProperty.call(counts, status)) {
486
665
  counts[status] += 1;
487
666
  }
667
+ if (status === "assigned" && task?.staleAssigned === true) staleAssigned += 1;
488
668
  }
669
+ const liveAssigned = Math.max(0, counts.assigned - staleAssigned);
489
670
  return {
490
671
  totalCount: queue.length,
491
- activeCount: counts.pending + counts.assigned,
672
+ activeCount: counts.pending + liveAssigned,
492
673
  historicalCount: counts.completed + counts.failed + counts.cancelled,
493
674
  counts,
494
675
  activeCounts: {
676
+ pending: counts.pending,
677
+ assigned: liveAssigned
678
+ },
679
+ staleAssignedCount: staleAssigned,
680
+ rawActiveCounts: {
495
681
  pending: counts.pending,
496
682
  assigned: counts.assigned
497
683
  },
@@ -519,6 +705,18 @@ function filterQueueForView(queue, view, statuses) {
519
705
  if (view === "historical") return queue.filter((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
520
706
  return queue;
521
707
  }
708
+ function prioritizeActiveQueueRows(queue) {
709
+ const active = [];
710
+ const historical = [];
711
+ const other = [];
712
+ for (const task of queue) {
713
+ const status = String(task?.status || "");
714
+ if (ACTIVE_QUEUE_STATUSES.has(status)) active.push(task);
715
+ else if (HISTORICAL_QUEUE_STATUSES.has(status)) historical.push(task);
716
+ else other.push(task);
717
+ }
718
+ return [...active, ...other, ...historical];
719
+ }
522
720
  function slimQueueTask(task) {
523
721
  return {
524
722
  id: task?.id,
@@ -612,6 +810,172 @@ function unwrapCommandPayload(value) {
612
810
  }
613
811
  return current;
614
812
  }
813
+ function isDirectDispatchLedgerEntry(entry) {
814
+ if (entry?.kind !== "task_dispatched") return false;
815
+ const payload = entry.payload || {};
816
+ const via = readString(payload.via);
817
+ return payload.source === "direct" || via === "p2p_direct" || via === "local_direct" || via === "mesh_send_task";
818
+ }
819
+ function readMessageTimestampIso(message) {
820
+ for (const value of [message?.timestamp, message?.createdAt, message?.created_at, message?.updatedAt, message?.time]) {
821
+ if (typeof value === "number" && Number.isFinite(value)) {
822
+ const ms = value > 1e10 ? value : value * 1e3;
823
+ return new Date(ms).toISOString();
824
+ }
825
+ if (typeof value === "string" && value.trim()) {
826
+ const ms = new Date(value.trim()).getTime();
827
+ if (Number.isFinite(ms)) return new Date(ms).toISOString();
828
+ }
829
+ }
830
+ return void 0;
831
+ }
832
+ function readFinalAssistantTranscriptEvidence(payload) {
833
+ const rawMessages = Array.isArray(payload?.messages) ? payload.messages : [];
834
+ const finalAssistant = [...rawMessages].reverse().filter(isCoordinatorVisibleMessage).find((message) => {
835
+ const role = String(message?.role ?? "").toLowerCase();
836
+ return (role === "assistant" || role === "agent") && messageContent(message).trim();
837
+ });
838
+ const finalSummary = messageContent(finalAssistant).trim() || (typeof payload?.summary === "string" && payload.summary.trim() ? payload.summary.trim() : void 0);
839
+ return {
840
+ finalSummary,
841
+ transcriptMessageAt: finalAssistant ? readMessageTimestampIso(finalAssistant) : void 0
842
+ };
843
+ }
844
+ function findNodeSession(nodes, nodeId, sessionId) {
845
+ if (!nodeId || !sessionId) return {};
846
+ const node = nodes.find((candidate) => readString(candidate?.id) === nodeId || readString(candidate?.nodeId) === nodeId);
847
+ if (!node) return {};
848
+ const sessions = Array.isArray(node.sessions) ? node.sessions : [];
849
+ const session = sessions.find((candidate) => readSessionRecordId(candidate) === sessionId);
850
+ return { node, session };
851
+ }
852
+ function buildDirectDispatchReconciliationCandidates(directDispatches, ledgerEntries) {
853
+ const candidates = [];
854
+ const seenTaskIds = /* @__PURE__ */ new Set();
855
+ for (const dispatch of directDispatches || []) {
856
+ const taskId = readString(dispatch?.taskId);
857
+ if (!taskId || seenTaskIds.has(taskId)) continue;
858
+ seenTaskIds.add(taskId);
859
+ candidates.push(dispatch);
860
+ }
861
+ for (const entry of ledgerEntries || []) {
862
+ if (!isDirectDispatchLedgerEntry(entry)) continue;
863
+ const taskId = readString(entry.payload?.taskId);
864
+ if (!taskId || seenTaskIds.has(taskId)) continue;
865
+ seenTaskIds.add(taskId);
866
+ candidates.push({
867
+ taskId,
868
+ nodeId: entry.nodeId,
869
+ sessionId: entry.sessionId,
870
+ providerType: entry.providerType || readString(entry.payload?.providerType),
871
+ message: readString(entry.payload?.message),
872
+ dispatchedAt: entry.timestamp,
873
+ via: readString(entry.payload?.via)
874
+ });
875
+ }
876
+ return candidates;
877
+ }
878
+ async function reconcileDirectDispatchesFromTranscriptEvidence(ctx, liveNodes, directDispatches, ledgerEntries) {
879
+ let attempted = 0;
880
+ let reconciled = 0;
881
+ let skipped = 0;
882
+ const candidates = buildDirectDispatchReconciliationCandidates(directDispatches, ledgerEntries);
883
+ for (const dispatch of candidates) {
884
+ const taskId = readString(dispatch?.taskId);
885
+ const nodeId = readString(dispatch?.nodeId);
886
+ const sessionId = readString(dispatch?.sessionId);
887
+ if (!taskId || !nodeId || !sessionId) {
888
+ skipped += 1;
889
+ continue;
890
+ }
891
+ const { session } = findNodeSession(liveNodes, nodeId, sessionId);
892
+ if (!session || !isIdleSessionRecord(session)) {
893
+ skipped += 1;
894
+ continue;
895
+ }
896
+ const node = await findOptionalNodeWithRefresh(ctx, nodeId).catch(() => null);
897
+ if (!node) {
898
+ skipped += 1;
899
+ continue;
900
+ }
901
+ const providerType = readString(dispatch?.providerType) || resolveSessionProviderType(session);
902
+ const providerSessionId = readString(session?.providerSessionId) || readString(session?.activeChat?.providerSessionId) || readString(session?.settings?.providerSessionId) || resolveMeshSessionProviderMetadata(ctx, nodeId, sessionId)?.providerSessionId;
903
+ attempted += 1;
904
+ try {
905
+ const readResult = await commandForNode(ctx, node, "read_chat", {
906
+ sessionId,
907
+ targetSessionId: sessionId,
908
+ workspace: node.workspace,
909
+ ...providerType ? { agentType: providerType, providerType } : {},
910
+ ...providerSessionId ? { providerSessionId } : {},
911
+ tailLimit: 10
912
+ });
913
+ const payload = unwrapCommandPayload(readResult);
914
+ if (payload?.success === false) continue;
915
+ const evidence = readFinalAssistantTranscriptEvidence(payload);
916
+ if (!evidence.finalSummary) continue;
917
+ const result = (0, import_daemon_core.reconcileDirectDispatchCompletionFromTranscript)({
918
+ meshId: ctx.mesh.id,
919
+ nodeId,
920
+ sessionId,
921
+ providerType,
922
+ providerSessionId: readString(payload?.providerSessionId) || providerSessionId,
923
+ taskId,
924
+ finalSummary: evidence.finalSummary,
925
+ transcriptMessageAt: evidence.transcriptMessageAt,
926
+ targetCoordinatorDaemonId: ctx.localDaemonId,
927
+ source: "mcp_mesh_status_transcript_reconciliation"
928
+ });
929
+ if (result.reconciled) reconciled += 1;
930
+ } catch {
931
+ skipped += 1;
932
+ }
933
+ }
934
+ return { attempted, reconciled, skipped };
935
+ }
936
+ async function triggerMeshQueueAndReport(ctx, node, opts) {
937
+ if (!(isLocalTransport(ctx.transport) || ctx.transport instanceof IpcTransport)) return void 0;
938
+ try {
939
+ let raw;
940
+ if (ctx.transport instanceof IpcTransport && node?.daemonId && opts?.localNode === false) {
941
+ raw = await ctx.transport.meshCommand(node.daemonId, "trigger_mesh_queue", { meshId: ctx.mesh.id });
942
+ } else if (isLocalTransport(ctx.transport)) {
943
+ raw = await ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id });
944
+ } else {
945
+ return void 0;
946
+ }
947
+ const payload = unwrapCommandPayload(raw);
948
+ const trigger = payload?.trigger && typeof payload.trigger === "object" ? payload.trigger : payload;
949
+ return trigger && typeof trigger === "object" ? trigger : { success: true };
950
+ } catch (e) {
951
+ return {
952
+ success: false,
953
+ error: e?.message || String(e)
954
+ };
955
+ }
956
+ }
957
+ function buildQueueTriggerGuidance(queueTrigger) {
958
+ if (!queueTrigger || queueTrigger.claimed === true) return void 0;
959
+ if (queueTrigger.success === false) {
960
+ return {
961
+ queueClaimed: false,
962
+ queueDispatchState: "trigger_failed",
963
+ nextAction: "Do not assume the queued task is running. Check mesh_view_queue and daemon connectivity before redispatching."
964
+ };
965
+ }
966
+ if (queueTrigger.noIdleMeshSessionAvailable === true) {
967
+ return {
968
+ queueClaimed: false,
969
+ queueDispatchState: "pending_no_idle_mesh_session",
970
+ nextAction: "The task is queued but not running. Launch a managed worker with mesh_launch_session, or wait for a delegated session to become ready and trigger the queue again."
971
+ };
972
+ }
973
+ return {
974
+ queueClaimed: false,
975
+ queueDispatchState: "pending_or_waiting_for_ready",
976
+ nextAction: "The task is queued but this trigger did not claim it. Use mesh_view_queue for the current active-work source of truth before retrying."
977
+ };
978
+ }
615
979
  function isTerminalSessionRecord(session) {
616
980
  const status = typeof session?.status === "string" ? session.status.toLowerCase() : "";
617
981
  const lifecycle = typeof session?.lifecycle === "string" ? session.lifecycle.toLowerCase() : "";
@@ -627,16 +991,23 @@ function isIdleSessionRecord(session) {
627
991
  function isMeshOwnedDelegateSession(session, meshId, nodeId) {
628
992
  const settings = session?.settings;
629
993
  const sessionMeshId = typeof settings?.meshNodeFor === "string" ? settings.meshNodeFor.trim() : "";
630
- const coordinatorDaemonId = typeof settings?.meshCoordinatorDaemonId === "string" ? settings.meshCoordinatorDaemonId.trim() : "";
631
994
  const sessionNodeId = typeof settings?.meshNodeId === "string" ? settings.meshNodeId.trim() : "";
632
- if (sessionMeshId !== meshId || !coordinatorDaemonId) return false;
995
+ if (sessionMeshId !== meshId) return false;
633
996
  return !sessionNodeId || sessionNodeId === nodeId;
634
997
  }
998
+ function hasRemoteRelayMetadata(session) {
999
+ return Boolean(
1000
+ readString(session?.settings?.meshCoordinatorDaemonId) || readString(session?.meta?.meshCoordinatorDaemonId) || readString(session?.metadata?.meshCoordinatorDaemonId) || readString(session?.meshCoordinatorDaemonId)
1001
+ );
1002
+ }
1003
+ function isRelaySafeRemoteDelegateSession(session, meshId, nodeId) {
1004
+ return isMeshOwnedDelegateSession(session, meshId, nodeId) && hasRemoteRelayMetadata(session);
1005
+ }
635
1006
  function chooseDispatchableSession(sessions, providerType, meshId, nodeId) {
636
1007
  const live = sessions.filter((session) => !isTerminalSessionRecord(session));
637
1008
  const matchingProvider = (session) => !providerType || session?.providerType === providerType || session?.cliType === providerType;
638
1009
  const meshSessions = live.filter(
639
- (session) => isMeshOwnedDelegateSession(session, meshId, nodeId)
1010
+ (session) => isRelaySafeRemoteDelegateSession(session, meshId, nodeId)
640
1011
  );
641
1012
  return meshSessions.find((session) => isIdleSessionRecord(session) && matchingProvider(session)) || meshSessions.find(matchingProvider) || void 0;
642
1013
  }
@@ -653,8 +1024,9 @@ function buildRelayUnsafeRemoteSessionFailure(ctx, node, sessionId, providerType
653
1024
  daemonId: node.daemonId,
654
1025
  workspace: node.workspace,
655
1026
  sessionId,
1027
+ unsafeTranscriptAlias: true,
656
1028
  ...providerType ? { resolvedProviderType: providerType } : {},
657
- error: `Remote session '${sessionId}' is not relay-safe for mesh '${ctx.mesh.id}': missing meshNodeFor/meshCoordinatorDaemonId metadata, so completion events would not reach the coordinator ledger.`,
1029
+ error: `Remote session '${sessionId}' is not relay-safe for mesh '${ctx.mesh.id}': missing meshNodeFor/meshCoordinatorDaemonId metadata, so completion events would not reach the coordinator ledger. This session may be the coordinator itself or an unrelated session (unsafe_transcript_alias risk).`,
658
1030
  nextAction: `Launch a fresh relay-safe session with mesh_launch_session(node_id: '${node.id}'${providerType ? `, type: '${providerType}'` : ""}) or dispatch without session_id so Repo Mesh can choose a valid delegate session.`,
659
1031
  noFallbackReason: "Blindly reusing a remote session without mesh relay metadata would silently drop task_completed / generating_completed events."
660
1032
  };
@@ -704,12 +1076,16 @@ function extractGitDiff(value) {
704
1076
  }
705
1077
  function extractSubmodules(value, ignorePaths) {
706
1078
  const payload = unwrapCommandPayload(value);
707
- const subs = payload?.submodules ?? value?.submodules;
1079
+ const subs = payload?.status?.submodules ?? payload?.submodules ?? value?.status?.submodules ?? value?.submodules;
708
1080
  if (!Array.isArray(subs)) return void 0;
709
1081
  if (ignorePaths.length === 0) return subs;
710
1082
  const ignoreSet = new Set(ignorePaths);
711
1083
  return subs.filter((s) => s?.path && !ignoreSet.has(s.path));
712
1084
  }
1085
+ function assignFullGitSnapshot(entry, status) {
1086
+ if (!status || typeof status !== "object" || Array.isArray(status)) return;
1087
+ entry.git = status;
1088
+ }
713
1089
  function extractLaunchPayload(value) {
714
1090
  return findNestedPayload(value, (payload) => Boolean(payload?.sessionId || payload?.id || payload?.runtimeSessionId));
715
1091
  }
@@ -834,7 +1210,20 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
834
1210
  let sessionId = args.session_id?.trim() || "";
835
1211
  const providerPriorityList = Array.isArray(node.policy?.providerPriority) ? node.policy.providerPriority : [];
836
1212
  let resolvedProviderType = args.providerType?.trim() || providerPriorityList[0] || "";
837
- if (!sessionId || args.session_id) {
1213
+ if (sessionId && args.verifiedSession) {
1214
+ const explicitSession = args.verifiedSession;
1215
+ if (!isRelaySafeRemoteDelegateSession(explicitSession, ctx.mesh.id, node.id)) {
1216
+ return buildRelayUnsafeRemoteSessionFailure(
1217
+ ctx,
1218
+ node,
1219
+ sessionId,
1220
+ resolvedProviderType || resolveSessionProviderType(explicitSession) || void 0
1221
+ );
1222
+ }
1223
+ if (!resolvedProviderType) {
1224
+ resolvedProviderType = resolveSessionProviderType(explicitSession);
1225
+ }
1226
+ } else if (!sessionId || args.session_id) {
838
1227
  try {
839
1228
  const relayResult = await transport.meshCommand(daemonId, "get_status_metadata", {});
840
1229
  const sessions = extractStatusMetadataSessions(relayResult);
@@ -858,7 +1247,7 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
858
1247
  nextAction: `Launch a fresh session with mesh_launch_session(node_id: '${node.id}'${resolvedProviderType ? `, type: '${resolvedProviderType}'` : ""}) or retry without session_id so Repo Mesh can target a live delegate session.`
859
1248
  };
860
1249
  }
861
- if (!isMeshOwnedDelegateSession(explicitSession, ctx.mesh.id, node.id)) {
1250
+ if (!isRelaySafeRemoteDelegateSession(explicitSession, ctx.mesh.id, node.id)) {
862
1251
  return buildRelayUnsafeRemoteSessionFailure(
863
1252
  ctx,
864
1253
  node,
@@ -902,7 +1291,8 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
902
1291
  agentType: resolvedProviderType,
903
1292
  cliType: resolvedProviderType,
904
1293
  action: "send_chat",
905
- message: args.message
1294
+ message: args.message,
1295
+ ...args.meshContext ? { meshContext: args.meshContext } : {}
906
1296
  });
907
1297
  const dispatchPayload = unwrapCommandPayload(dispatchResult);
908
1298
  if (dispatchPayload?.success === false || dispatchResult?.success === false) {
@@ -920,7 +1310,7 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
920
1310
  error: `P2P dispatch failed: ${errorMessage}`
921
1311
  };
922
1312
  }
923
- return { success: true, dispatched: true, sessionId: sessionId || resolvedProviderType };
1313
+ return { success: true, dispatched: true, sessionId: sessionId || resolvedProviderType, providerType: resolvedProviderType };
924
1314
  } catch (e) {
925
1315
  const errorMessage = e?.message || String(e);
926
1316
  return {
@@ -950,34 +1340,197 @@ function resolveCoordinatorNode(ctx) {
950
1340
  return void 0;
951
1341
  }
952
1342
  function readNodeMachineId(node) {
953
- return readString(node.machineId) || readString(node.machine_id);
1343
+ return readString(node.machineId) || readString(node.machine_id) || readString(node.machine?.id) || readString(node.machine?.machineId) || readString(node.lastProbe?.machineId) || readString(node.last_probe?.machine_id) || readString(node.lastProbe?.machine?.id) || readString(node.lastProbe?.machine?.machineId) || readString(node.last_probe?.machine?.id) || readString(node.last_probe?.machine?.machine_id);
954
1344
  }
955
1345
  function readNodeDaemonId(node) {
956
- return readString(node.daemonId) || readString(node.daemon_id);
1346
+ return readString(node.daemonId) || readString(node.daemon_id) || readString(node.machine?.daemonId) || readString(node.machine?.daemon_id) || readString(node.lastProbe?.daemonId) || readString(node.last_probe?.daemon_id) || readString(node.lastProbe?.machine?.daemonId) || readString(node.lastProbe?.machine?.daemon_id) || readString(node.last_probe?.machine?.daemonId) || readString(node.last_probe?.machine?.daemon_id);
1347
+ }
1348
+ function normalizeHostname(value) {
1349
+ const hostname = readString(value);
1350
+ if (!hostname) return void 0;
1351
+ return hostname.toLowerCase().replace(/\.$/, "");
1352
+ }
1353
+ function readNodeHostname(node) {
1354
+ return readString(node.hostname) || readString(node.host) || readString(node.machineHostname) || readString(node.machine_hostname) || readString(node.machine?.hostname) || readString(node.machine?.host) || readString(node.lastProbe?.hostname) || readString(node.last_probe?.hostname) || readString(node.lastProbe?.machine?.hostname) || readString(node.last_probe?.machine?.hostname);
1355
+ }
1356
+ function readNodeDisplayMachineName(node) {
1357
+ return readString(node.machineName) || readString(node.machine_name) || readString(node.machineLabel) || readString(node.machine_label) || readString(node.machineNickname) || readString(node.machine_nickname) || readString(node.alias) || readString(node.machine?.name) || readString(node.machine?.displayName) || readString(node.machine?.display_name) || readString(node.lastProbe?.machineName) || readString(node.last_probe?.machine_name) || readString(node.lastProbe?.machine?.name) || readString(node.last_probe?.machine?.name) || readNodeHostname(node);
1358
+ }
1359
+ function compactIdentityEvidence(value) {
1360
+ if (!value) return void 0;
1361
+ return value.length > 24 ? `${value.slice(0, 12)}\u2026${value.slice(-8)}` : value;
1362
+ }
1363
+ function pushIdentityEvidence(evidence, label, value) {
1364
+ const compact = compactIdentityEvidence(value);
1365
+ if (compact) evidence.push(`${label}:${compact}`);
1366
+ }
1367
+ function buildNodeMachineIdentity(ctx, node) {
1368
+ const machineId = readNodeMachineId(node);
1369
+ const daemonId = readNodeDaemonId(node);
1370
+ const hostname = readNodeHostname(node);
1371
+ const machineName = readNodeDisplayMachineName(node);
1372
+ const coordinatorHostname = readString(ctx.coordinatorHostname);
1373
+ const localControlPlaneReason = getLocalControlPlaneMatchReason(ctx, node);
1374
+ const directLocal = !!localControlPlaneReason;
1375
+ const hostnameMatches = Boolean(
1376
+ normalizeHostname(hostname) && normalizeHostname(coordinatorHostname) && normalizeHostname(hostname) === normalizeHostname(coordinatorHostname)
1377
+ );
1378
+ const sameMachine = directLocal || hostnameMatches;
1379
+ const evidence = [];
1380
+ pushIdentityEvidence(evidence, "machineName", machineName);
1381
+ pushIdentityEvidence(evidence, "hostname", hostname);
1382
+ pushIdentityEvidence(evidence, "machineId", machineId);
1383
+ pushIdentityEvidence(evidence, "daemonId", daemonId);
1384
+ if (localControlPlaneReason) {
1385
+ pushIdentityEvidence(evidence, "localMatch", localControlPlaneReason);
1386
+ pushIdentityEvidence(evidence, "localMachineId", ctx.localMachineId);
1387
+ pushIdentityEvidence(evidence, "localDaemonId", ctx.localDaemonId);
1388
+ }
1389
+ const locality = sameMachine ? "same_machine" : evidence.length > 0 ? "remote_known" : "remote_or_unknown";
1390
+ const localityReason = sameMachine ? localControlPlaneReason || "matched coordinator hostname" : evidence.length > 0 ? `known remote/other machine identity; no local coordinator match (${evidence.join(", ")})` : "no useful machine identity evidence available";
1391
+ return {
1392
+ daemonId,
1393
+ machineId,
1394
+ hostname,
1395
+ machineName,
1396
+ displayName: machineName || hostname || daemonId || machineId,
1397
+ coordinatorHostname,
1398
+ sameMachine,
1399
+ locality,
1400
+ localityReason,
1401
+ identityEvidence: evidence
1402
+ };
1403
+ }
1404
+ function nodeHasLocalDaemonEvidence(ctx, node) {
1405
+ const isLocal = (session) => {
1406
+ if (!session || typeof session !== "object") return false;
1407
+ if (ctx.localDaemonId && session.runtime?.owner === ctx.localDaemonId) return true;
1408
+ if (ctx.localDaemonId && session.daemonClient?.daemonId === ctx.localDaemonId) return true;
1409
+ return false;
1410
+ };
1411
+ const sessionArrays = [
1412
+ node?.sessions,
1413
+ node?.activeSessions,
1414
+ node?.active_sessions,
1415
+ node?.lastProbe?.sessions,
1416
+ node?.last_probe?.sessions,
1417
+ node?.lastProbe?.status?.sessions,
1418
+ node?.last_probe?.status?.sessions
1419
+ ];
1420
+ for (const arr of sessionArrays) {
1421
+ if (Array.isArray(arr) && arr.some(isLocal)) return true;
1422
+ }
1423
+ const sessionRecords = [
1424
+ node?.activeSession,
1425
+ node?.active_session,
1426
+ node?.currentSession,
1427
+ node?.current_session,
1428
+ node?.runtimeSession,
1429
+ node?.runtime_session,
1430
+ node?.session,
1431
+ node?.lastProbe?.activeSession,
1432
+ node?.last_probe?.active_session,
1433
+ node?.lastProbe?.currentSession,
1434
+ node?.last_probe?.current_session,
1435
+ node?.lastProbe?.session,
1436
+ node?.last_probe?.session
1437
+ ];
1438
+ for (const session of sessionRecords) {
1439
+ if (isLocal(session)) return true;
1440
+ }
1441
+ return false;
957
1442
  }
958
1443
  function isDirectLocalNode(ctx, node) {
959
1444
  const machineId = readNodeMachineId(node);
960
1445
  const daemonId = readNodeDaemonId(node);
961
1446
  return Boolean(
962
- ctx.localMachineId && machineId === ctx.localMachineId || ctx.localDaemonId && daemonId === ctx.localDaemonId
1447
+ ctx.localMachineId && machineId === ctx.localMachineId || ctx.localDaemonId && daemonId === ctx.localDaemonId || nodeHasLocalDaemonEvidence(ctx, node)
963
1448
  );
964
1449
  }
1450
+ function isConfiguredCoordinatorNode(ctx, node) {
1451
+ if (!ctx.localMachineId && !ctx.localDaemonId) return false;
1452
+ const nodeId = readString(node.id) || readString(node.nodeId) || readString(node.node_id);
1453
+ if (!nodeId) return false;
1454
+ const nodeDaemonId = readNodeDaemonId(node);
1455
+ const nodeMachineId = readNodeMachineId(node);
1456
+ if (nodeDaemonId && ctx.localDaemonId && nodeDaemonId !== ctx.localDaemonId) return false;
1457
+ if (nodeMachineId && ctx.localMachineId && nodeMachineId !== ctx.localMachineId) return false;
1458
+ const preferredNodeId = readString(ctx.mesh.coordinator?.preferredNodeId) || readString(ctx.mesh.coordinator?.preferred_node_id);
1459
+ if (preferredNodeId) return nodeId === preferredNodeId;
1460
+ const first = ctx.mesh.nodes?.[0];
1461
+ const firstNodeId = readString(first?.id) || readString(first?.nodeId) || readString(first?.node_id);
1462
+ return !!firstNodeId && nodeId === firstNodeId;
1463
+ }
1464
+ function getLocalControlPlaneMatchReason(ctx, node) {
1465
+ if (isDirectLocalNode(ctx, node)) return "matched coordinator daemon or machine id";
1466
+ if (isConfiguredCoordinatorNode(ctx, node)) return "matched configured coordinator node";
1467
+ if (node.isLocalWorktree === true) {
1468
+ const sourceNode = findClonedFromNode(ctx, node);
1469
+ if (sourceNode && isDirectLocalNode(ctx, sourceNode)) return "matched local cloned-from node";
1470
+ if (sourceNode && isConfiguredCoordinatorNode(ctx, sourceNode)) return "matched configured coordinator source node";
1471
+ }
1472
+ return void 0;
1473
+ }
965
1474
  function findClonedFromNode(ctx, node) {
966
1475
  const clonedFromNodeId = readString(node.clonedFromNodeId) || readString(node.cloned_from_node_id);
967
1476
  if (!clonedFromNodeId) return void 0;
968
1477
  return ctx.mesh.nodes.find((n) => n.id === clonedFromNodeId || n.nodeId === clonedFromNodeId || n.node_id === clonedFromNodeId);
969
1478
  }
970
1479
  function isLocalControlPlaneNode(ctx, node) {
971
- if (isDirectLocalNode(ctx, node)) return true;
972
- if (node.isLocalWorktree === true) {
973
- const sourceNode = findClonedFromNode(ctx, node);
974
- if (sourceNode && isDirectLocalNode(ctx, sourceNode)) return true;
975
- }
976
- return false;
1480
+ return !!getLocalControlPlaneMatchReason(ctx, node);
977
1481
  }
978
1482
  function meshSessionCacheKey(nodeId, runtimeSessionId) {
979
1483
  return `${nodeId}:${runtimeSessionId}`;
980
1484
  }
1485
+ function rememberMeshSessionProviderMetadata(nodeId, runtimeSessionId, metadata) {
1486
+ const keyNodeId = readString(nodeId);
1487
+ const keySessionId = readString(runtimeSessionId);
1488
+ if (!keyNodeId || !keySessionId) return;
1489
+ const providerType = readString(metadata.providerType);
1490
+ const providerSessionId = readString(metadata.providerSessionId);
1491
+ if (!providerType && !providerSessionId) return;
1492
+ const existing = getSessionMetadata(meshSessionCacheKey(keyNodeId, keySessionId)) || { providerType: "" };
1493
+ meshSessionProviderMetadata.set(meshSessionCacheKey(keyNodeId, keySessionId), {
1494
+ providerType: providerType || existing.providerType,
1495
+ providerSessionId: providerSessionId || existing.providerSessionId,
1496
+ expiresAt: Date.now() + SESSION_PROVIDER_METADATA_TTL_MS
1497
+ });
1498
+ }
1499
+ function rememberMeshSessionProviderMetadataFromEvent(event) {
1500
+ const metadataEvent = event?.metadataEvent && typeof event.metadataEvent === "object" ? event.metadataEvent : event && typeof event === "object" ? event : {};
1501
+ const nodeId = readString(event?.nodeId) || readString(metadataEvent.nodeId) || readString(metadataEvent.meshNodeId);
1502
+ const sessionId = readString(metadataEvent.targetSessionId) || readString(metadataEvent.sessionId) || readString(metadataEvent.instanceId) || readString(event?.sessionId);
1503
+ rememberMeshSessionProviderMetadata(nodeId, sessionId, {
1504
+ providerType: readString(metadataEvent.providerType) || readString(event?.providerType) || "",
1505
+ providerSessionId: readString(metadataEvent.providerSessionId) || readString(event?.providerSessionId)
1506
+ });
1507
+ }
1508
+ function resolveMeshSessionProviderMetadataFromLedger(ctx, nodeId, runtimeSessionId) {
1509
+ const entries = (0, import_daemon_core.readLedgerEntries)(ctx.mesh.id, { tail: 50 });
1510
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
1511
+ const entry = entries[i];
1512
+ const payload = entry.payload && typeof entry.payload === "object" && !Array.isArray(entry.payload) ? entry.payload : {};
1513
+ const entryNodeId = readString(entry.nodeId) || readString(payload.nodeId) || readString(payload.meshNodeId);
1514
+ if (entryNodeId && entryNodeId !== nodeId) continue;
1515
+ const entrySessionId = readString(entry.sessionId) || readString(payload.targetSessionId) || readString(payload.sessionId) || readString(payload.instanceId);
1516
+ if (entrySessionId !== runtimeSessionId) continue;
1517
+ const providerType = readString(entry.providerType) || readString(payload.providerType);
1518
+ const completionDiagnostic = payload.completionDiagnostic && typeof payload.completionDiagnostic === "object" && !Array.isArray(payload.completionDiagnostic) ? payload.completionDiagnostic : {};
1519
+ const metadataEvent = payload.metadataEvent && typeof payload.metadataEvent === "object" && !Array.isArray(payload.metadataEvent) ? payload.metadataEvent : {};
1520
+ const providerSessionId = readString(payload.providerSessionId) || readString(completionDiagnostic.providerSessionId) || readString(metadataEvent.providerSessionId);
1521
+ if (providerType || providerSessionId) {
1522
+ return { providerType: providerType || "", providerSessionId };
1523
+ }
1524
+ }
1525
+ return void 0;
1526
+ }
1527
+ function resolveMeshSessionProviderMetadata(ctx, nodeId, runtimeSessionId) {
1528
+ const cached = getSessionMetadata(meshSessionCacheKey(nodeId, runtimeSessionId));
1529
+ if (cached?.providerType || cached?.providerSessionId) return cached;
1530
+ const fromLedger = resolveMeshSessionProviderMetadataFromLedger(ctx, nodeId, runtimeSessionId);
1531
+ if (fromLedger) rememberMeshSessionProviderMetadata(nodeId, runtimeSessionId, fromLedger);
1532
+ return fromLedger;
1533
+ }
981
1534
  function countUncommittedChanges(status) {
982
1535
  if (typeof status?.uncommittedChanges === "number") return status.uncommittedChanges;
983
1536
  const keys = ["staged", "modified", "untracked", "deleted", "renamed"];
@@ -988,8 +1541,23 @@ function countUncommittedChanges(status) {
988
1541
  function isGitStatusDirty(status) {
989
1542
  if (typeof status?.isDirty === "boolean") return status.isDirty;
990
1543
  if (typeof status?.dirty === "boolean") return status.dirty;
1544
+ if (Array.isArray(status?.submodules) && status.submodules.some((submodule) => submodule?.dirty || submodule?.outOfSync || submodule?.error)) return true;
991
1545
  return countUncommittedChanges(status) > 0;
992
1546
  }
1547
+ function slimLedgerPayload(payload) {
1548
+ const slim = {};
1549
+ for (const [k, v] of Object.entries(payload)) {
1550
+ if (k === "message" || k === "taskSummary") {
1551
+ slim[k] = typeof v === "string" && v.length > 200 ? v.slice(0, 200) + "\u2026" : v;
1552
+ } else if (k === "evidence" || k === "workerResult" || k === "gitStatus" || k === "validationResults") {
1553
+ } else if (k === "finalSummary") {
1554
+ slim[k] = typeof v === "string" && v.length > 300 ? v.slice(0, 300) + "\u2026" : v;
1555
+ } else {
1556
+ slim[k] = v;
1557
+ }
1558
+ }
1559
+ return slim;
1560
+ }
993
1561
  function readRelatedRepos(node) {
994
1562
  const raw = Array.isArray(node.relatedRepos) ? node.relatedRepos : Array.isArray(node.policy?.relatedRepos) ? node.policy.relatedRepos : [];
995
1563
  return raw.map((entry) => ({
@@ -1048,6 +1616,16 @@ function missingProviderPriorityMessage(nodeId) {
1048
1616
  return `Node '${nodeId}' has no providerPriority policy; pass type explicitly or configure node.policy.providerPriority`;
1049
1617
  }
1050
1618
  function getNodeLaunchReadiness(node) {
1619
+ const bootstrap = node.worktreeBootstrap;
1620
+ if (node.isLocalWorktree && bootstrap?.status === "failed" && bootstrap?.required !== false) {
1621
+ return {
1622
+ providerPriority: readProviderPriority(node.policy),
1623
+ launchReady: false,
1624
+ launchBlockedReason: "worktree_bootstrap_failed",
1625
+ launchBlockedMessage: typeof bootstrap.error === "string" && bootstrap.error.trim() ? bootstrap.error.trim() : "Required worktree bootstrap failed; resolve it before launching an agent into this node.",
1626
+ worktreeBootstrap: bootstrap
1627
+ };
1628
+ }
1051
1629
  const providerPriority = readProviderPriority(node.policy);
1052
1630
  if (providerPriority.length) {
1053
1631
  return {
@@ -1062,6 +1640,45 @@ function getNodeLaunchReadiness(node) {
1062
1640
  launchBlockedMessage: missingProviderPriorityMessage(node.id)
1063
1641
  };
1064
1642
  }
1643
+ function getWorktreeBootstrapLaunchBlock(node, meshPolicy) {
1644
+ if (!node.isLocalWorktree) return void 0;
1645
+ const bootstrap = node.worktreeBootstrap;
1646
+ const requireReady = !!(meshPolicy && typeof meshPolicy === "object" && meshPolicy.requireBootstrapBeforeLaunch === true);
1647
+ if (requireReady && bootstrap?.status !== "ready") {
1648
+ return {
1649
+ success: false,
1650
+ code: "bootstrap_not_ready",
1651
+ error: `Node '${node.id}' bootstrap state is '${bootstrap?.status ?? "unknown"}' and mesh policy requireBootstrapBeforeLaunch is enabled.`,
1652
+ nodeId: node.id,
1653
+ worktreeBootstrap: bootstrap ?? null,
1654
+ recoveryHint: "Run the worktree bootstrap (clone runOnClone or a refine with bootstrap inherit) until the node reports ready, or disable requireBootstrapBeforeLaunch."
1655
+ };
1656
+ }
1657
+ if (bootstrap?.status !== "failed" || bootstrap?.required === false) return void 0;
1658
+ return {
1659
+ success: false,
1660
+ code: "worktree_bootstrap_failed",
1661
+ error: typeof bootstrap.error === "string" && bootstrap.error.trim() ? bootstrap.error.trim() : `Node '${node.id}' has a failed required worktree bootstrap.`,
1662
+ nodeId: node.id,
1663
+ worktreeBootstrap: bootstrap,
1664
+ recoveryHint: "Fix the configured worktree bootstrap command or remove/recreate the worktree node before launching an agent."
1665
+ };
1666
+ }
1667
+ async function collectLiveStatusSessions(ctx, node) {
1668
+ try {
1669
+ const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
1670
+ return extractStatusMetadataSessions(statusResult);
1671
+ } catch {
1672
+ return [];
1673
+ }
1674
+ }
1675
+ async function collectMeshViewQueueNodesWithLiveSessions(ctx) {
1676
+ const nodes = await Promise.all(ctx.mesh.nodes.map(async (node) => {
1677
+ const liveSessions = await collectLiveStatusSessions(ctx, node);
1678
+ return liveSessions.length > 0 ? { ...node, sessions: liveSessions } : node;
1679
+ }));
1680
+ return nodes;
1681
+ }
1065
1682
  function readNumeric(value, fallback = 0) {
1066
1683
  const parsed = Number(value);
1067
1684
  return Number.isFinite(parsed) ? parsed : fallback;
@@ -1197,7 +1814,8 @@ async function commandForNode(ctx, node, command, args = {}) {
1197
1814
  if (isLocalTransport(ctx.transport)) {
1198
1815
  return ctx.transport.command(command, args);
1199
1816
  }
1200
- throw new Error(`Command '${command}' requires daemon IPC/local transport for node '${node.id}'`);
1817
+ const identity = buildNodeMachineIdentity(ctx, node);
1818
+ throw new Error(`Command '${command}' requires daemon IPC/local transport for node '${node.id}' (hostname=${identity.hostname || "unknown"}, coordinatorHostname=${identity.coordinatorHostname || "unknown"}, sameMachine=${identity.sameMachine})`);
1201
1819
  }
1202
1820
  function normalizePendingMeshCoordinatorEvents(value) {
1203
1821
  const payload = unwrapCommandPayload(value);
@@ -1215,6 +1833,14 @@ function buildMeshForwardPayloadFromPendingEvent(event) {
1215
1833
  providerType: readString(metadataEvent.providerType),
1216
1834
  providerSessionId: readString(metadataEvent.providerSessionId),
1217
1835
  finalSummary: readString(metadataEvent.finalSummary) || readString(metadataEvent.summary),
1836
+ jobId: readString(metadataEvent.jobId),
1837
+ interactionId: readString(metadataEvent.interactionId),
1838
+ status: readString(metadataEvent.status),
1839
+ targetDaemonId: readString(metadataEvent.targetDaemonId),
1840
+ startedAt: readString(metadataEvent.startedAt),
1841
+ completedAt: readString(metadataEvent.completedAt),
1842
+ retryOfJobId: readString(metadataEvent.retryOfJobId),
1843
+ ...metadataEvent.result && typeof metadataEvent.result === "object" && !Array.isArray(metadataEvent.result) ? { result: metadataEvent.result } : {},
1218
1844
  ...metadataEvent.intentional === true ? { intentional: true } : {},
1219
1845
  ...metadataEvent.intentionalStop === true ? { intentionalStop: true } : {},
1220
1846
  ...metadataEvent.operatorCleanup === true ? { operatorCleanup: true } : {},
@@ -1229,10 +1855,16 @@ async function drainCoordinatorPendingEvents(ctx, opts) {
1229
1855
  const matchesCurrentMesh = (event) => readString(event?.meshId) === ctx.mesh.id;
1230
1856
  if (ctx.transport instanceof IpcTransport) {
1231
1857
  const surfacedEvents = [];
1858
+ const coordinatorDaemonId = readString(ctx.localDaemonId);
1859
+ const pendingEventArgs = {
1860
+ meshId: ctx.mesh.id,
1861
+ ...coordinatorDaemonId ? { coordinatorDaemonId } : {}
1862
+ };
1232
1863
  try {
1233
1864
  surfacedEvents.push(
1234
- ...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", {})).filter(matchesCurrentMesh)
1865
+ ...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", pendingEventArgs)).filter(matchesCurrentMesh)
1235
1866
  );
1867
+ surfacedEvents.forEach(rememberMeshSessionProviderMetadataFromEvent);
1236
1868
  } catch {
1237
1869
  }
1238
1870
  for (const node of ctx.mesh.nodes) {
@@ -1240,27 +1872,31 @@ async function drainCoordinatorPendingEvents(ctx, opts) {
1240
1872
  if (requestedNodeIds && !requestedNodeIds.has(node.id)) continue;
1241
1873
  try {
1242
1874
  const remoteEvents = normalizePendingMeshCoordinatorEvents(
1243
- await ctx.transport.meshCommand(node.daemonId, "get_pending_mesh_events", {})
1875
+ await ctx.transport.meshCommand(node.daemonId, "get_pending_mesh_events", pendingEventArgs)
1244
1876
  ).filter(matchesCurrentMesh);
1245
1877
  if (remoteEvents.length === 0) continue;
1246
1878
  for (const event of remoteEvents) {
1247
1879
  const payload = buildMeshForwardPayloadFromPendingEvent(event);
1248
1880
  if (!payload.event || !payload.meshId) continue;
1249
1881
  await ctx.transport.command("mesh_forward_event", payload);
1882
+ rememberMeshSessionProviderMetadataFromEvent({ ...event, metadataEvent: payload });
1250
1883
  }
1251
1884
  } catch {
1252
1885
  }
1253
1886
  }
1254
1887
  try {
1255
1888
  surfacedEvents.push(
1256
- ...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", {})).filter(matchesCurrentMesh)
1889
+ ...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", pendingEventArgs)).filter(matchesCurrentMesh)
1257
1890
  );
1891
+ surfacedEvents.forEach(rememberMeshSessionProviderMetadataFromEvent);
1258
1892
  } catch {
1259
1893
  }
1260
1894
  return surfacedEvents;
1261
1895
  }
1262
1896
  if (isLocalTransport(ctx.transport)) {
1263
- return (0, import_daemon_core.drainPendingMeshCoordinatorEvents)().filter(matchesCurrentMesh);
1897
+ const events = (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id, ctx.localDaemonId).filter(matchesCurrentMesh);
1898
+ events.forEach(rememberMeshSessionProviderMetadataFromEvent);
1899
+ return events;
1264
1900
  }
1265
1901
  return [];
1266
1902
  }
@@ -1277,11 +1913,12 @@ function buildRemoveNodeArgs(ctx, nodeId, sessionCleanupMode) {
1277
1913
  }
1278
1914
  var MESH_STATUS_TOOL = {
1279
1915
  name: "mesh_status",
1280
- 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.",
1916
+ 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. Do not repeatedly call this to wait for generating delegated work; wait for pendingCoordinatorEvents/completion events or an explicit user status request.",
1281
1917
  inputSchema: {
1282
1918
  type: "object",
1283
1919
  properties: {
1284
- _gemini_compat: { type: "string", description: "Dummy property for Gemini compatibility. Ignore this." }
1920
+ _gemini_compat: { type: "string", description: "Dummy property for Gemini compatibility. Ignore this." },
1921
+ includeStaleDirectWorkDetails: { type: "boolean", description: "Opt in to the full staleDirectWork array. Defaults false; normal status returns compact staleDirectWorkSummary only." }
1285
1922
  }
1286
1923
  }
1287
1924
  };
@@ -1301,14 +1938,22 @@ var MESH_ENQUEUE_TASK_TOOL = {
1301
1938
  inputSchema: {
1302
1939
  type: "object",
1303
1940
  properties: {
1304
- message: { type: "string", description: "The task instruction for the agent." }
1941
+ message: { type: "string", description: "The task instruction for the agent." },
1942
+ task_mode: { type: "string", enum: ["code_change", "validation", "live_debug_readonly", "launch_app", "convergence"], description: "Optional task-mode contract. live_debug_readonly rejects obvious write/commit/push/deploy/destructive instructions before dispatch." },
1943
+ taskMode: { type: "string", enum: ["code_change", "validation", "live_debug_readonly", "launch_app", "convergence"], description: "CamelCase alias for task_mode." },
1944
+ requiredTags: { type: "array", items: { type: "string" }, description: "Optional capability tags that every eligible node must have, e.g. os=darwin, provider=codex-cli, gpu." },
1945
+ required_tags: { type: "array", items: { type: "string" }, description: "Snake_case alias for requiredTags." },
1946
+ depends_on: { type: "array", items: { type: "string" }, description: "Task ids that must complete before this task becomes claimable. Cycles are rejected at enqueue." },
1947
+ dependsOn: { type: "array", items: { type: "string" }, description: "CamelCase alias for depends_on." },
1948
+ mission_id: { type: "string", description: "Mission this task belongs to (mesh_mission record id)." },
1949
+ missionId: { type: "string", description: "CamelCase alias for mission_id." }
1305
1950
  },
1306
1951
  required: ["message"]
1307
1952
  }
1308
1953
  };
1309
1954
  var MESH_VIEW_QUEUE_TOOL = {
1310
1955
  name: "mesh_view_queue",
1311
- description: "View the mesh work queue with source-of-truth active counts separated from historical completed/failed/cancelled records.",
1956
+ description: "View the mesh work queue with source-of-truth active counts separated from historical completed/failed/cancelled records. Do not repeatedly call this to wait for generating assigned work; wait for pendingCoordinatorEvents/completion events or an explicit user status request.",
1312
1957
  inputSchema: {
1313
1958
  type: "object",
1314
1959
  properties: {
@@ -1339,7 +1984,7 @@ var MESH_QUEUE_CANCEL_TOOL = {
1339
1984
  };
1340
1985
  var MESH_QUEUE_REQUEUE_TOOL = {
1341
1986
  name: "mesh_queue_requeue",
1342
- 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.",
1987
+ 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. When the task has exceeded its retry cap it is auto-failed instead; use force=true to override.",
1343
1988
  inputSchema: {
1344
1989
  type: "object",
1345
1990
  properties: {
@@ -1348,7 +1993,8 @@ var MESH_QUEUE_REQUEUE_TOOL = {
1348
1993
  target_node_id: { type: "string", description: "Optional replacement target node ID." },
1349
1994
  target_session_id: { type: "string", description: "Optional replacement target runtime session ID." },
1350
1995
  clear_target_node: { type: "boolean", description: "When true, remove any existing target node constraint." },
1351
- 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." }
1996
+ 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." },
1997
+ force: { type: "boolean", description: "When true, bypass the retry cap and requeue even if maxRetries has been exceeded. Use only for explicit operator recovery." }
1352
1998
  },
1353
1999
  required: ["task_id"]
1354
2000
  }
@@ -1361,7 +2007,9 @@ var MESH_SEND_TASK_TOOL = {
1361
2007
  properties: {
1362
2008
  node_id: { type: "string", description: "Target node ID (from mesh_list_nodes)." },
1363
2009
  session_id: { type: "string", description: "Agent session ID on the target node." },
1364
- message: { type: "string", description: "Natural-language task to send to the agent." }
2010
+ message: { type: "string", description: "Natural-language task to send to the agent." },
2011
+ task_mode: { type: "string", enum: ["code_change", "validation", "live_debug_readonly", "launch_app", "convergence"], description: "Optional task-mode contract. live_debug_readonly rejects obvious write/commit/push/deploy/destructive instructions before local or remote direct dispatch." },
2012
+ taskMode: { type: "string", enum: ["code_change", "validation", "live_debug_readonly", "launch_app", "convergence"], description: "CamelCase alias for task_mode." }
1365
2013
  },
1366
2014
  required: ["node_id", "session_id", "message"]
1367
2015
  }
@@ -1419,6 +2067,21 @@ var MESH_GIT_STATUS_TOOL = {
1419
2067
  required: ["node_id"]
1420
2068
  }
1421
2069
  };
2070
+ var MESH_FAST_FORWARD_NODE_TOOL = {
2071
+ name: "mesh_fast_forward_node",
2072
+ description: "Safely dry-run or execute an obvious direct fast-forward for a mesh node without launching an agent session. Defaults to dry-run; execution requires execute=true. Never pushes, rebases, resets, cleans, or checks out arbitrary revisions.",
2073
+ inputSchema: {
2074
+ type: "object",
2075
+ properties: {
2076
+ node_id: { type: "string", description: "Target node ID." },
2077
+ branch: { type: "string", description: "Optional guard: require the node's current branch to match this branch before planning/executing." },
2078
+ execute: { type: "boolean", description: "When true, apply the fast-forward if all safety gates pass. Defaults false/dry-run." },
2079
+ dry_run: { type: "boolean", description: "Preview only. Defaults true unless execute=true; dry_run=true overrides execute." },
2080
+ update_submodules: { type: "boolean", description: "When true, if the root fast-forward changes gitlinks, run only git submodule update --init --recursive and verify submodules clean." }
2081
+ },
2082
+ required: ["node_id"]
2083
+ }
2084
+ };
1422
2085
  var MESH_CHECKPOINT_TOOL = {
1423
2086
  name: "mesh_checkpoint",
1424
2087
  description: "Create a git checkpoint (commit) on a mesh node workspace.",
@@ -1431,6 +2094,20 @@ var MESH_CHECKPOINT_TOOL = {
1431
2094
  required: ["node_id", "message"]
1432
2095
  }
1433
2096
  };
2097
+ var MESH_MISSION_UPSERT_TOOL = {
2098
+ name: "mesh_mission_upsert",
2099
+ description: "Create or update a persistent mission record so the plan survives coordinator restarts. Create a mission before enqueueing a multi-task batch, attach tasks via mesh_enqueue_task mission_id, and update status to completed/abandoned when the outcome is decided. Progress is derived from task statuses \u2014 there is no separate progress field.",
2100
+ inputSchema: {
2101
+ type: "object",
2102
+ properties: {
2103
+ mission_id: { type: "string", description: "Mission id to update. Omit to create a new mission." },
2104
+ title: { type: "string", description: "Short mission title." },
2105
+ goal: { type: "string", description: "Free-text mission goal/definition of done." },
2106
+ status: { type: "string", enum: ["active", "paused", "completed", "abandoned"], description: "Mission lifecycle status. Defaults to active on create." }
2107
+ },
2108
+ required: ["title"]
2109
+ }
2110
+ };
1434
2111
  var MESH_APPROVE_TOOL = {
1435
2112
  name: "mesh_approve",
1436
2113
  description: "Approve or reject a pending action on a delegated agent session.",
@@ -1502,7 +2179,7 @@ var MESH_TASK_HISTORY_TOOL = {
1502
2179
  type: "object",
1503
2180
  properties: {
1504
2181
  tail: { type: "number", description: "Number of recent entries to return (default: 20)." },
1505
- kind: { type: "string", description: "Filter by entry kind: task_dispatched, task_completed, task_failed, task_stalled, session_launched, checkpoint_created, node_cloned, node_removed." }
2182
+ kind: { type: "string", description: "Filter by entry kind: task_dispatched, task_completed, task_failed, task_stalled, session_launched, checkpoint_created, node_cloned, node_removed, direct_fast_forward." }
1506
2183
  }
1507
2184
  }
1508
2185
  };
@@ -1522,7 +2199,7 @@ var MESH_RECONCILE_LEDGER_TOOL = {
1522
2199
  };
1523
2200
  var MESH_REFINE_NODE_TOOL = {
1524
2201
  name: "mesh_refine_node",
1525
- 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.",
2202
+ description: "The Refinery: Accept an async validation/merge/cleanup job for a completed worktree node. The immediate response includes async:true, status:'accepted', jobId, interactionId, target node, and startedAt; completion/failure evidence is delivered through pending mesh events and the mesh task ledger.",
1526
2203
  inputSchema: {
1527
2204
  type: "object",
1528
2205
  properties: {
@@ -1531,6 +2208,54 @@ var MESH_REFINE_NODE_TOOL = {
1531
2208
  required: ["node_id"]
1532
2209
  }
1533
2210
  };
2211
+ var MESH_REFINE_CONFIG_SCHEMA_TOOL = {
2212
+ name: "mesh_refine_config_schema",
2213
+ description: "Return the Repo Mesh Refinery config JSON schema and supported repo-local config locations. This is the validation source of truth; heuristic command detection is suggestions-only.",
2214
+ inputSchema: { type: "object", properties: {} }
2215
+ };
2216
+ var MESH_VALIDATE_REFINE_CONFIG_TOOL = {
2217
+ name: "mesh_validate_refine_config",
2218
+ description: "Validate the repo mesh/refine config for a node/workspace without running validation commands or merging.",
2219
+ inputSchema: {
2220
+ type: "object",
2221
+ properties: {
2222
+ node_id: { type: "string", description: "Optional node/workspace whose refine config should be loaded. Defaults to the first mesh node." },
2223
+ config: { type: "object", description: "Optional inline config object to validate instead of loading from the repo." }
2224
+ }
2225
+ }
2226
+ };
2227
+ var MESH_SUGGEST_REFINE_CONFIG_TOOL = {
2228
+ name: "mesh_suggest_refine_config",
2229
+ description: "Suggest a repo mesh/refine config scaffold from project context/package scripts. Suggestions are never executed until saved as explicit refine config.",
2230
+ inputSchema: {
2231
+ type: "object",
2232
+ properties: {
2233
+ node_id: { type: "string", description: "Optional node/workspace used for suggestions. Defaults to the first mesh node." }
2234
+ }
2235
+ }
2236
+ };
2237
+ var MESH_REFINE_PLAN_TOOL = {
2238
+ name: "mesh_refine_plan",
2239
+ description: "Dry-run Refinery plan for a worktree node: reports config source, validation commands, suggestions/unavailable reason, and merge/cleanup intent without executing validation or git merge.",
2240
+ inputSchema: {
2241
+ type: "object",
2242
+ properties: {
2243
+ node_id: { type: "string", description: "Node ID of the worktree node to plan." }
2244
+ },
2245
+ required: ["node_id"]
2246
+ }
2247
+ };
2248
+ var MESH_REVIEW_INBOX_TOOL = {
2249
+ name: "mesh_review_inbox",
2250
+ description: "List local worktree nodes that need human review: merge candidates (pushed feature branches ready to merge) and Refinery-blocked review results. Returns evidence summaries, diff stats vs. the default branch, and suggested actions (Refine / Requeue / Dismiss). Remote nodes are excluded in M4.0.",
2251
+ inputSchema: {
2252
+ type: "object",
2253
+ properties: {
2254
+ mesh_id: { type: "string", description: "Mesh ID (optional \u2014 inferred from active mesh if omitted)." }
2255
+ },
2256
+ required: []
2257
+ }
2258
+ };
1534
2259
  var ALL_MESH_TOOLS = [
1535
2260
  MESH_STATUS_TOOL,
1536
2261
  MESH_LIST_NODES_TOOL,
@@ -1543,24 +2268,34 @@ var ALL_MESH_TOOLS = [
1543
2268
  MESH_READ_DEBUG_TOOL,
1544
2269
  MESH_LAUNCH_SESSION_TOOL,
1545
2270
  MESH_GIT_STATUS_TOOL,
2271
+ MESH_FAST_FORWARD_NODE_TOOL,
1546
2272
  MESH_CHECKPOINT_TOOL,
1547
2273
  MESH_APPROVE_TOOL,
1548
2274
  MESH_CLONE_NODE_TOOL,
1549
2275
  MESH_REMOVE_NODE_TOOL,
1550
2276
  MESH_REFINE_NODE_TOOL,
2277
+ MESH_REFINE_CONFIG_SCHEMA_TOOL,
2278
+ MESH_VALIDATE_REFINE_CONFIG_TOOL,
2279
+ MESH_SUGGEST_REFINE_CONFIG_TOOL,
2280
+ MESH_REFINE_PLAN_TOOL,
1551
2281
  MESH_CLEANUP_SESSIONS_TOOL,
1552
2282
  MESH_TASK_HISTORY_TOOL,
1553
- MESH_RECONCILE_LEDGER_TOOL
2283
+ MESH_RECONCILE_LEDGER_TOOL,
2284
+ MESH_MISSION_UPSERT_TOOL,
2285
+ MESH_REVIEW_INBOX_TOOL
1554
2286
  ];
1555
- async function meshStatus(ctx) {
2287
+ async function meshStatus(ctx, args = {}) {
2288
+ const rateResult = (0, import_daemon_core.recordMeshToolCall)({ meshId: ctx.mesh.id, tool: "mesh_status" });
1556
2289
  await refreshMeshFromDaemon(ctx);
1557
2290
  const { mesh, transport } = ctx;
1558
- const results = [];
1559
- const ledgerSummary = (0, import_daemon_core.getLedgerSummary)(mesh.id);
1560
- for (const node of mesh.nodes) {
2291
+ let ledgerSummary = (0, import_daemon_core.getLedgerSummary)(mesh.id);
2292
+ const results = await Promise.all(mesh.nodes.map(async (node) => {
1561
2293
  const entry = {
1562
2294
  nodeId: node.id,
1563
2295
  workspace: node.workspace,
2296
+ machine: buildNodeMachineIdentity(ctx, node),
2297
+ daemonId: readNodeDaemonId(node),
2298
+ machineId: readNodeMachineId(node),
1564
2299
  ...getNodeLaunchReadiness(node)
1565
2300
  };
1566
2301
  try {
@@ -1570,6 +2305,7 @@ async function meshStatus(ctx) {
1570
2305
  const uncommittedChanges = countUncommittedChanges(status);
1571
2306
  const dirty = isGitStatusDirty(status);
1572
2307
  entry.health = status?.isGitRepo ? dirty ? "dirty" : "online" : "degraded";
2308
+ assignFullGitSnapshot(entry, status);
1573
2309
  entry.branch = status?.branch;
1574
2310
  entry.isDirty = dirty;
1575
2311
  entry.uncommittedChanges = uncommittedChanges;
@@ -1591,6 +2327,7 @@ async function meshStatus(ctx) {
1591
2327
  const uncommittedChanges = countUncommittedChanges(status);
1592
2328
  const dirty = isGitStatusDirty(status);
1593
2329
  entry.health = status?.isGitRepo ? dirty ? "dirty" : "online" : "degraded";
2330
+ assignFullGitSnapshot(entry, status);
1594
2331
  entry.branch = status?.branch;
1595
2332
  entry.isDirty = dirty;
1596
2333
  entry.uncommittedChanges = uncommittedChanges;
@@ -1626,7 +2363,7 @@ async function meshStatus(ctx) {
1626
2363
  if (recoveryContext.consecutiveNodeFailures > 0) {
1627
2364
  entry.recoveryHints = {
1628
2365
  consecutiveFailures: recoveryContext.consecutiveNodeFailures,
1629
- lastTaskMessage: recoveryContext.lastTaskMessage,
2366
+ lastTaskMessage: typeof recoveryContext.lastTaskMessage === "string" ? recoveryContext.lastTaskMessage.slice(0, 100) + (recoveryContext.lastTaskMessage.length > 100 ? "\u2026" : "") : recoveryContext.lastTaskMessage,
1630
2367
  advice: recoveryContext.advice,
1631
2368
  retryRecommended: recoveryContext.retryRecommended
1632
2369
  };
@@ -1661,12 +2398,60 @@ async function meshStatus(ctx) {
1661
2398
  nextStepHints.push(`Consider reassigning work to a different node.`);
1662
2399
  }
1663
2400
  }
1664
- if (nextStepHints.length > 0) {
1665
- entry.nextStepHints = nextStepHints;
1666
- }
1667
- const relatedRepos = await collectRelatedRepoStatuses(ctx, node);
1668
- if (relatedRepos.length) entry.relatedRepos = relatedRepos;
1669
- results.push(entry);
2401
+ if (nextStepHints.length > 0) {
2402
+ entry.nextStepHints = nextStepHints;
2403
+ }
2404
+ const relatedRepos = await collectRelatedRepoStatuses(ctx, node);
2405
+ if (relatedRepos.length) entry.relatedRepos = relatedRepos;
2406
+ const liveSessions = await collectLiveStatusSessions(ctx, node);
2407
+ if (liveSessions.length > 0) {
2408
+ entry.sessions = liveSessions.map((s) => {
2409
+ const coordinatorMeshId = typeof s.coordinator?.meshId === "string" ? s.coordinator.meshId : void 0;
2410
+ const isSelfCoordinator = coordinatorMeshId === mesh.id;
2411
+ return {
2412
+ id: s.instanceId ?? s.id ?? s.sessionId,
2413
+ status: s.status ?? s.lifecycle ?? s.state,
2414
+ providerType: s.providerType ?? s.cliType ?? s.type,
2415
+ ...s.activeChat?.status ? { chatStatus: s.activeChat.status } : {},
2416
+ ...isSelfCoordinator ? { isSelfCoordinator: true, role: "coordinator" } : {}
2417
+ };
2418
+ }).filter((s) => s.id);
2419
+ }
2420
+ return entry;
2421
+ }));
2422
+ let ledgerEntries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail: 200 });
2423
+ let directDispatches = (0, import_daemon_core.getActiveDirectDispatches)(mesh.id);
2424
+ const directReconciliation = await reconcileDirectDispatchesFromTranscriptEvidence(ctx, results, directDispatches, ledgerEntries);
2425
+ if (directReconciliation.reconciled > 0) {
2426
+ ledgerEntries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail: 200 });
2427
+ directDispatches = (0, import_daemon_core.getActiveDirectDispatches)(mesh.id);
2428
+ ledgerSummary = (0, import_daemon_core.getLedgerSummary)(mesh.id);
2429
+ }
2430
+ const activeWorkEvidence = (0, import_daemon_core.buildMeshActiveWork)({
2431
+ meshId: mesh.id,
2432
+ queue: (0, import_daemon_core.getQueue)(mesh.id),
2433
+ ledgerEntries,
2434
+ directDispatches,
2435
+ nodes: results
2436
+ });
2437
+ const pollingGuidance = buildActiveWorkPollingGuidance(activeWorkEvidence.summary);
2438
+ const staleDirectWorkSummary = (0, import_daemon_core.buildCompactStaleDirectWorkSummary)(activeWorkEvidence.staleDirectWork, {
2439
+ note: activeWorkEvidence.staleDirectWorkNote,
2440
+ detailHint: "Full stale direct entries are omitted from mesh_status by default. Call mesh_status with includeStaleDirectWorkDetails=true or inspect mesh_task_history for ledger detail."
2441
+ });
2442
+ const coordinatorSessions = [];
2443
+ for (const nodeEntry of results) {
2444
+ const sessions = Array.isArray(nodeEntry.sessions) ? nodeEntry.sessions : [];
2445
+ for (const s of sessions) {
2446
+ if (s?.isSelfCoordinator === true && s.id) {
2447
+ coordinatorSessions.push({
2448
+ nodeId: nodeEntry.nodeId,
2449
+ sessionId: s.id,
2450
+ providerType: s.providerType,
2451
+ status: s.status
2452
+ });
2453
+ }
2454
+ }
1670
2455
  }
1671
2456
  const response = {
1672
2457
  meshId: mesh.id,
@@ -1677,17 +2462,55 @@ async function meshStatus(ctx) {
1677
2462
  sourceOfTruth: {
1678
2463
  membership: "coordinator_daemon_live_mesh",
1679
2464
  currentStatus: "live_git_and_session_probes",
2465
+ activeWork: "mesh_queue_file_and_local_ledger",
1680
2466
  historicalEvidenceOnly: ["recoveryHints", "ledgerSummary"]
1681
2467
  },
1682
2468
  nodes: results,
1683
- branchConvergenceSummary: summarizeBranchConvergence(results)
2469
+ activeWork: activeWorkEvidence.activeWork,
2470
+ staleDirectWorkSummary,
2471
+ ...args.includeStaleDirectWorkDetails === true ? { staleDirectWork: activeWorkEvidence.staleDirectWork } : {},
2472
+ // terminalDirectWork is historical (completed/failed direct dispatches) — opt-in only.
2473
+ ...args.includeTerminalDirectWork === true ? { terminalDirectWork: activeWorkEvidence.terminalDirectWork } : {},
2474
+ activeWorkSummary: activeWorkEvidence.summary,
2475
+ ...pollingGuidance ? { pollingGuidance } : {},
2476
+ ...rateResult.rateLimitExceeded ? { pollingRateAdvisory: { type: "rate_limit_exceeded", tool: "mesh_status", callsInWindow: rateResult.callsInWindow, message: rateResult.advisory } } : {},
2477
+ branchConvergenceSummary: summarizeBranchConvergence(results),
2478
+ ...coordinatorSessions.length > 0 ? {
2479
+ coordinatorSessions,
2480
+ selfIdentification: {
2481
+ meshId: mesh.id,
2482
+ coordinatorSessions,
2483
+ note: "Sessions listed here are coordinator sessions for this mesh. The calling coordinator IS one of these sessions \u2014 do not treat its own generating CLI session as a foreign delegated task. Per-session marker: sessions[].isSelfCoordinator === true."
2484
+ }
2485
+ } : {}
1684
2486
  };
1685
2487
  try {
1686
2488
  response.ledgerSummary = ledgerSummary;
1687
2489
  } catch {
1688
2490
  }
2491
+ try {
2492
+ const missions = (0, import_daemon_core.getActiveMeshMissionSummaries)(mesh.id);
2493
+ if (missions.length > 0) {
2494
+ response.missions = missions.map((mission) => {
2495
+ try {
2496
+ return { ...mission, stats: (0, import_daemon_core.computeMeshMissionStats)(mesh.id, mission.id) };
2497
+ } catch {
2498
+ return mission;
2499
+ }
2500
+ });
2501
+ }
2502
+ } catch {
2503
+ }
1689
2504
  try {
1690
2505
  const pendingEvents = await drainCoordinatorPendingEvents(ctx);
2506
+ const asyncRefineJobs = (0, import_daemon_core.buildMeshAsyncRefineJobs)({
2507
+ meshId: mesh.id,
2508
+ ledgerEntries,
2509
+ pendingEvents
2510
+ });
2511
+ if (asyncRefineJobs.length > 0) {
2512
+ response.asyncRefineJobs = asyncRefineJobs;
2513
+ }
1691
2514
  if (pendingEvents.length > 0) {
1692
2515
  response.pendingCoordinatorEvents = pendingEvents;
1693
2516
  }
@@ -1697,12 +2520,31 @@ async function meshStatus(ctx) {
1697
2520
  }
1698
2521
  async function meshTaskHistory(ctx, args) {
1699
2522
  const { mesh } = ctx;
1700
- await drainCoordinatorPendingEvents(ctx);
2523
+ const pendingEvents = await drainCoordinatorPendingEvents(ctx);
1701
2524
  const tail = typeof args.tail === "number" && args.tail > 0 ? args.tail : 20;
1702
2525
  const kind = typeof args.kind === "string" && args.kind.trim() ? [args.kind.trim()] : void 0;
1703
- const entries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail, kind });
2526
+ const rawEntries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail, kind });
2527
+ const entries = rawEntries.map((e) => ({
2528
+ ...e,
2529
+ payload: e.payload ? slimLedgerPayload(e.payload) : e.payload
2530
+ }));
1704
2531
  const summary = (0, import_daemon_core.getLedgerSummary)(mesh.id);
1705
- return JSON.stringify({ meshId: mesh.id, entries, summary }, null, 2);
2532
+ let taskStats;
2533
+ try {
2534
+ const taskIds = [...new Set(rawEntries.map((e) => typeof e.payload?.taskId === "string" ? e.payload.taskId : "").filter(Boolean))];
2535
+ if (taskIds.length > 0) {
2536
+ const stats = (0, import_daemon_core.computeMeshTaskStats)(mesh.id, { taskIds });
2537
+ if (stats.length > 0) taskStats = stats;
2538
+ }
2539
+ } catch {
2540
+ }
2541
+ return JSON.stringify({
2542
+ meshId: mesh.id,
2543
+ entries,
2544
+ summary,
2545
+ ...taskStats ? { taskStats } : {},
2546
+ ...pendingEvents.length > 0 ? { pendingCoordinatorEvents: pendingEvents } : {}
2547
+ }, null, 2);
1706
2548
  }
1707
2549
  async function meshReconcileLedger(ctx, args) {
1708
2550
  await refreshMeshFromDaemon(ctx);
@@ -1719,7 +2561,7 @@ async function meshReconcileLedger(ctx, args) {
1719
2561
  for (const node of nodes) {
1720
2562
  try {
1721
2563
  if (isLocalControlPlaneNode(ctx, node) || !node.daemonId) {
1722
- const slice2 = (0, import_daemon_core.readLedgerSlice)(ctx.mesh.id, queryArgs);
2564
+ const slice2 = (0, import_daemon_core.readLedgerSliceFromStore)(ctx.mesh.id, queryArgs);
1723
2565
  replicas.push((0, import_daemon_core.buildMeshLedgerReplicaEvidence)({
1724
2566
  nodeId: node.id,
1725
2567
  daemonId: node.daemonId,
@@ -1792,6 +2634,9 @@ async function meshListNodes(ctx) {
1792
2634
  nodeId: n.id,
1793
2635
  workspace: n.workspace,
1794
2636
  repoRoot: n.repoRoot,
2637
+ daemonId: readNodeDaemonId(n),
2638
+ machineId: readNodeMachineId(n),
2639
+ machine: buildNodeMachineIdentity(ctx, n),
1795
2640
  isLocalWorktree: n.isLocalWorktree,
1796
2641
  policy: n.policy,
1797
2642
  relatedRepos: readRelatedRepos(n),
@@ -1800,58 +2645,168 @@ async function meshListNodes(ctx) {
1800
2645
  }))
1801
2646
  }, null, 2);
1802
2647
  }
2648
+ async function meshMissionUpsert(ctx, args) {
2649
+ try {
2650
+ const mission = (0, import_daemon_core.upsertMeshMission)(ctx.mesh.id, {
2651
+ id: readString(args.mission_id) || readString(args.missionId) || void 0,
2652
+ title: args.title,
2653
+ goal: typeof args.goal === "string" ? args.goal : void 0,
2654
+ status: readString(args.status) || void 0
2655
+ });
2656
+ return JSON.stringify({
2657
+ success: true,
2658
+ mission,
2659
+ nextAction: "Attach tasks with mesh_enqueue_task mission_id and depends_on. mesh_status shows live task aggregates for this mission."
2660
+ });
2661
+ } catch (e) {
2662
+ const message = e?.message || String(e);
2663
+ const code = message.includes("mission_title_required") ? "mission_title_required" : message.includes("invalid_mission_status") ? "invalid_mission_status" : void 0;
2664
+ return JSON.stringify({ success: false, ...code ? { code } : {}, error: message });
2665
+ }
2666
+ }
1803
2667
  async function meshEnqueueTask(ctx, args) {
2668
+ const taskMode = readString(args.task_mode) || readString(args.taskMode);
2669
+ const requiredTags = (0, import_daemon_core.normalizeMeshCapabilityTags)(Array.isArray(args.requiredTags) ? args.requiredTags : args.required_tags);
2670
+ const dependsOn = Array.isArray(args.dependsOn) ? args.dependsOn : Array.isArray(args.depends_on) ? args.depends_on : void 0;
2671
+ const missionId = readString(args.missionId) || readString(args.mission_id) || void 0;
1804
2672
  try {
1805
- const task = (0, import_daemon_core.enqueueTask)(ctx.mesh.id, args.message);
2673
+ const task = (0, import_daemon_core.enqueueTask)(ctx.mesh.id, args.message, { taskMode, requiredTags, dependsOn, missionId });
1806
2674
  if (isLocalTransport(ctx.transport) && !(ctx.transport instanceof IpcTransport)) {
1807
- ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
2675
+ const queueTrigger = await triggerMeshQueueAndReport(ctx);
2676
+ return JSON.stringify({
2677
+ success: true,
2678
+ source: "queue",
2679
+ taskId: task.id,
2680
+ status: task.status,
2681
+ taskMode: task.taskMode,
2682
+ requiredTags: task.requiredTags,
2683
+ queueTrigger,
2684
+ ...buildQueueTriggerGuidance(queueTrigger)
1808
2685
  });
1809
- return JSON.stringify({ success: true, taskId: task.id, status: task.status });
1810
2686
  }
1811
2687
  if (ctx.transport instanceof IpcTransport) {
1812
- ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
1813
- });
2688
+ const queueTrigger = await triggerMeshQueueAndReport(ctx);
1814
2689
  const dispatchPromises = [];
1815
2690
  for (const node of ctx.mesh.nodes) {
1816
2691
  const isLocalNode = isLocalControlPlaneNode(ctx, node);
1817
2692
  if (isLocalNode || !node.daemonId) continue;
2693
+ if (!(0, import_daemon_core.nodeSatisfiesRequiredTags)(requiredTags, (0, import_daemon_core.buildMeshNodeCapabilityTags)(node))) continue;
1818
2694
  dispatchPromises.push(
1819
2695
  ipcDispatchToRemoteAgent(ctx, node, { message: args.message }).then((result) => {
1820
2696
  if (result.success) {
1821
2697
  try {
2698
+ const providerType = result.providerType;
2699
+ const descriptor = summarizeTaskMessage(args.message);
1822
2700
  (0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
1823
2701
  kind: "task_dispatched",
1824
2702
  nodeId: node.id,
1825
2703
  sessionId: result.sessionId,
1826
- payload: { message: args.message, via: "p2p_direct", taskId: task.id }
2704
+ providerType,
2705
+ payload: {
2706
+ source: "queue",
2707
+ via: "p2p_direct",
2708
+ taskId: task.id,
2709
+ message: args.message,
2710
+ taskTitle: descriptor.taskTitle,
2711
+ taskSummary: descriptor.taskSummary,
2712
+ ...task.taskMode ? { taskMode: task.taskMode } : {},
2713
+ ...providerType ? { providerType } : {},
2714
+ targetSessionId: result.sessionId
2715
+ }
1827
2716
  });
1828
2717
  } catch {
1829
2718
  }
1830
2719
  }
1831
- }).catch(() => {
2720
+ }).catch((err) => {
2721
+ try {
2722
+ (0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
2723
+ kind: "p2p_dispatch_failed",
2724
+ nodeId: node.id,
2725
+ payload: {
2726
+ source: "queue",
2727
+ via: "p2p_direct",
2728
+ taskId: task.id,
2729
+ error: err?.message || String(err),
2730
+ dispatchFailedAt: (/* @__PURE__ */ new Date()).toISOString()
2731
+ }
2732
+ });
2733
+ } catch {
2734
+ }
1832
2735
  })
1833
2736
  );
1834
2737
  }
1835
2738
  Promise.all(dispatchPromises).catch(() => {
1836
2739
  });
1837
- return JSON.stringify({ success: true, taskId: task.id, status: task.status });
2740
+ return JSON.stringify({
2741
+ success: true,
2742
+ source: "queue",
2743
+ taskId: task.id,
2744
+ status: task.status,
2745
+ taskMode: task.taskMode,
2746
+ requiredTags: task.requiredTags,
2747
+ queueTrigger,
2748
+ ...buildQueueTriggerGuidance(queueTrigger)
2749
+ });
1838
2750
  }
1839
- return JSON.stringify({ success: true, taskId: task.id, status: task.status });
2751
+ return JSON.stringify({ success: true, source: "queue", taskId: task.id, status: task.status, taskMode: task.taskMode, requiredTags: task.requiredTags });
1840
2752
  } catch (e) {
1841
- return JSON.stringify({ success: false, error: e.message });
2753
+ const message = e?.message || String(e);
2754
+ if (message.includes("live_debug_readonly_guardrail_violation")) {
2755
+ return JSON.stringify({ success: false, code: "live_debug_readonly_guardrail_violation", taskMode, error: message });
2756
+ }
2757
+ if (message.includes("dependency_cycle_detected")) {
2758
+ return JSON.stringify({ success: false, code: "dependency_cycle_detected", dependsOn, error: message });
2759
+ }
2760
+ return JSON.stringify({ success: false, error: message });
1842
2761
  }
1843
2762
  }
1844
2763
  async function meshViewQueue(ctx, args) {
2764
+ const rateResult = (0, import_daemon_core.recordMeshToolCall)({ meshId: ctx.mesh.id, tool: "mesh_view_queue" });
1845
2765
  try {
2766
+ await refreshMeshFromDaemon(ctx);
1846
2767
  const statusFilter = sanitizeQueueStatusFilter(args.status);
1847
2768
  const view = normalizeQueueViewMode(args.view);
1848
- const fullQueue = annotateQueueStaleness((0, import_daemon_core.getQueue)(ctx.mesh.id), ctx.mesh);
2769
+ const rawQueue = (0, import_daemon_core.getQueue)(ctx.mesh.id);
2770
+ const statusById = new Map(rawQueue.map((task) => [task.id, task.status]));
2771
+ const withDependencies = rawQueue.map((task) => {
2772
+ if (!Array.isArray(task.dependsOn) || task.dependsOn.length === 0) return task;
2773
+ const depState = (0, import_daemon_core.describeTaskDependencyState)(task, statusById);
2774
+ return { ...task, ...depState };
2775
+ });
2776
+ const fullQueue = prioritizeActiveQueueRows(annotateQueueStaleness(withDependencies, ctx.mesh));
1849
2777
  const queue = filterQueueForView(fullQueue, view, statusFilter);
1850
2778
  const summary = buildQueueStatusSummary(fullQueue);
1851
2779
  const visibleSummary = buildQueueStatusSummary(queue);
1852
2780
  const maintenance = buildQueueMaintenanceReport(fullQueue);
2781
+ const liveNodes = await collectMeshViewQueueNodesWithLiveSessions(ctx);
2782
+ let ledgerEntries = (0, import_daemon_core.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
2783
+ let directDispatches = (0, import_daemon_core.getActiveDirectDispatches)(ctx.mesh.id);
2784
+ const directReconciliation = await reconcileDirectDispatchesFromTranscriptEvidence(ctx, liveNodes, directDispatches, ledgerEntries);
2785
+ if (directReconciliation.reconciled > 0) {
2786
+ ledgerEntries = (0, import_daemon_core.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
2787
+ directDispatches = (0, import_daemon_core.getActiveDirectDispatches)(ctx.mesh.id);
2788
+ }
2789
+ (0, import_daemon_core.markStaleDirectDispatches)(ctx.mesh.id);
2790
+ directDispatches = (0, import_daemon_core.getActiveDirectDispatches)(ctx.mesh.id);
2791
+ const activeWorkEvidence = (0, import_daemon_core.buildMeshActiveWork)({
2792
+ meshId: ctx.mesh.id,
2793
+ queue: fullQueue,
2794
+ ledgerEntries,
2795
+ // Always pass MeshRuntimeStore records (may be empty). buildMeshActiveWork uses them for local
2796
+ // dispatches and falls through to ledger scan for remote P2P dispatches not in MeshRuntimeStore.
2797
+ directDispatches,
2798
+ nodes: liveNodes
2799
+ });
2800
+ const recentDispatchFailures = ledgerEntries.filter((e) => e.kind === "p2p_dispatch_failed").slice(-20).map((e) => ({
2801
+ nodeId: e.nodeId,
2802
+ taskId: e.payload?.taskId,
2803
+ error: e.payload?.error,
2804
+ via: e.payload?.via,
2805
+ failedAt: e.payload?.dispatchFailedAt || e.timestamp
2806
+ }));
1853
2807
  const staleAssignedTasks = maintenance.staleAssignedTasks || [];
1854
2808
  const requestedHistoricalRows = queue.some((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
2809
+ const pollingGuidance = buildActiveWorkPollingGuidance(activeWorkEvidence.summary);
1855
2810
  return JSON.stringify({
1856
2811
  success: true,
1857
2812
  sourceOfTruth: {
@@ -1866,21 +2821,30 @@ async function meshViewQueue(ctx, args) {
1866
2821
  filtered: Boolean(statusFilter?.length) || view !== "all"
1867
2822
  },
1868
2823
  queue,
1869
- visibleQueue: queue,
1870
- visibleSummary,
2824
+ activeWork: activeWorkEvidence.activeWork,
2825
+ staleDirectWork: activeWorkEvidence.staleDirectWork,
2826
+ activeWorkSummary: activeWorkEvidence.summary,
2827
+ ...pollingGuidance ? { pollingGuidance } : {},
2828
+ ...rateResult.rateLimitExceeded ? { pollingRateAdvisory: { type: "rate_limit_exceeded", tool: "mesh_view_queue", callsInWindow: rateResult.callsInWindow, message: rateResult.advisory } } : {},
1871
2829
  summary,
2830
+ visibleSummary,
1872
2831
  activeCounts: summary.activeCounts,
1873
2832
  historicalCounts: summary.historicalCounts,
1874
- activeCount: summary.activeCount,
1875
- historicalCount: summary.historicalCount,
1876
2833
  visibleActiveCounts: visibleSummary.activeCounts,
1877
2834
  visibleHistoricalCounts: visibleSummary.historicalCounts,
2835
+ activeCount: summary.activeCount,
2836
+ historicalCount: summary.historicalCount,
1878
2837
  visibleActiveCount: visibleSummary.activeCount,
1879
2838
  visibleHistoricalCount: visibleSummary.historicalCount,
1880
2839
  staleAssignedTasks,
1881
2840
  staleAssignedCount: maintenance.staleAssignedCount,
1882
2841
  queueMaintenance: maintenance,
1883
2842
  cleanupDryRun: maintenance,
2843
+ ...recentDispatchFailures.length > 0 ? {
2844
+ recentDispatchFailures,
2845
+ dispatchFailureCount: recentDispatchFailures.length,
2846
+ dispatchFailureNote: "Remote P2P dispatch attempts that failed. Affected tasks remain pending and may require mesh_queue_requeue if no idle session picks them up."
2847
+ } : {},
1884
2848
  ...view === "active" || statusFilter?.some((status) => ACTIVE_QUEUE_STATUSES.has(status)) ? {
1885
2849
  activeQueue: queue.filter((task) => ACTIVE_QUEUE_STATUSES.has(String(task?.status || "")))
1886
2850
  } : {},
@@ -1900,6 +2864,10 @@ async function meshQueueCancel(ctx, args) {
1900
2864
  if (!taskId) return JSON.stringify({ success: false, error: "task_id required" });
1901
2865
  const task = (0, import_daemon_core.cancelTask)(ctx.mesh.id, taskId, { reason: args.reason });
1902
2866
  if (!task) return JSON.stringify({ success: false, error: `Queue task '${taskId}' not found` });
2867
+ if (isLocalTransport(ctx.transport)) {
2868
+ ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
2869
+ });
2870
+ }
1903
2871
  return JSON.stringify({ success: true, task }, null, 2);
1904
2872
  } catch (e) {
1905
2873
  return JSON.stringify({ success: false, error: e.message });
@@ -1917,9 +2885,19 @@ async function meshQueueRequeue(ctx, args) {
1917
2885
  targetNodeId,
1918
2886
  targetSessionId,
1919
2887
  clearTargetNode: args.clear_target_node === true || args.clearTargetNode === true,
1920
- clearTargetSession: targetSessionId ? false : !keepTargetSession
2888
+ clearTargetSession: targetSessionId ? false : !keepTargetSession,
2889
+ force: args.force === true
1921
2890
  });
1922
2891
  if (!task) return JSON.stringify({ success: false, error: `Queue task '${taskId}' not found` });
2892
+ if (task.status === "failed" && task.cancelReason?.startsWith("max_retries_exceeded")) {
2893
+ return JSON.stringify({
2894
+ success: false,
2895
+ code: "max_retries_exceeded",
2896
+ error: task.cancelReason,
2897
+ task,
2898
+ hint: "Use force=true to bypass the retry cap for explicit operator recovery."
2899
+ }, null, 2);
2900
+ }
1923
2901
  if (isLocalTransport(ctx.transport)) {
1924
2902
  ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
1925
2903
  });
@@ -1930,10 +2908,60 @@ async function meshQueueRequeue(ctx, args) {
1930
2908
  }
1931
2909
  }
1932
2910
  async function meshSendTask(ctx, args) {
2911
+ const requestedTaskMode = readString(args.task_mode) || readString(args.taskMode);
2912
+ const modeValidation = (0, import_daemon_core.validateMeshTaskModeRequest)(requestedTaskMode, args.message);
2913
+ if (!modeValidation.valid) {
2914
+ return JSON.stringify({
2915
+ success: false,
2916
+ code: "live_debug_readonly_guardrail_violation",
2917
+ taskMode: modeValidation.taskMode || requestedTaskMode,
2918
+ violations: modeValidation.violations,
2919
+ allowedOperations: modeValidation.allowedOperations,
2920
+ error: `live_debug_readonly_guardrail_violation: forbidden operations (${modeValidation.violations.join(", ")})`
2921
+ });
2922
+ }
2923
+ const taskMode = modeValidation.taskMode;
1933
2924
  const node = await findNodeWithRefresh(ctx, args.node_id);
1934
2925
  if (node.policy?.readOnly) {
1935
2926
  return JSON.stringify({ error: `Node '${args.node_id}' is read-only` });
1936
2927
  }
2928
+ let explicitTargetSession;
2929
+ if (args.session_id && isWorkerTaskMode(taskMode) && (ctx.transport instanceof IpcTransport || isLocalTransport(ctx.transport))) {
2930
+ try {
2931
+ const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
2932
+ const sessions = extractStatusMetadataSessions(statusResult);
2933
+ explicitTargetSession = sessions.find((session) => readSessionRecordId(session) === args.session_id);
2934
+ if (explicitTargetSession && isMeshCoordinatorSessionRecord(explicitTargetSession)) {
2935
+ return JSON.stringify({
2936
+ success: false,
2937
+ recoverable: true,
2938
+ code: "mesh_target_session_is_coordinator",
2939
+ reason: "mesh_target_session_is_coordinator",
2940
+ nodeId: args.node_id,
2941
+ sessionId: args.session_id,
2942
+ taskMode: taskMode || "unspecified",
2943
+ error: `Session '${args.session_id}' is a Repo Mesh coordinator session, not a visible worker session. Launch or use a visible worker session before dispatching this task.`,
2944
+ nextAction: `Call mesh_launch_session for node '${args.node_id}' and then retry mesh_send_task with that worker session_id, or use mesh_enqueue_task for queue-based worker assignment.`
2945
+ });
2946
+ }
2947
+ if (explicitTargetSession && isUnmanagedSessionRecord(explicitTargetSession)) {
2948
+ return JSON.stringify({
2949
+ success: false,
2950
+ recoverable: true,
2951
+ code: "mesh_target_session_unmanaged",
2952
+ reason: "mesh_target_session_unmanaged",
2953
+ nodeId: args.node_id,
2954
+ sessionId: args.session_id,
2955
+ taskMode: taskMode || "unspecified",
2956
+ unsafeTranscriptAlias: true,
2957
+ error: `Session '${args.session_id}' on node '${args.node_id}' has no Repo Mesh delegation metadata (missing meshNodeFor/meshCoordinatorFor/launchedByCoordinator). It may be the coordinator's own session or an unrelated session \u2014 dispatching risks self-send and orphaned completion events that never reach the coordinator ledger.`,
2958
+ nextAction: `Call mesh_launch_session for node '${args.node_id}' to start a fresh managed worker session, then retry mesh_send_task with the returned session_id. Alternatively use mesh_enqueue_task for queue-based assignment without specifying session_id.`
2959
+ });
2960
+ }
2961
+ } catch {
2962
+ explicitTargetSession = void 0;
2963
+ }
2964
+ }
1937
2965
  const duplicate = hasRecentDuplicateDispatch(ctx, args);
1938
2966
  if (duplicate.duplicate) {
1939
2967
  return JSON.stringify({
@@ -1957,43 +2985,76 @@ async function meshSendTask(ctx, args) {
1957
2985
  const res = await ctx.transport.meshEnqueueTask(node.daemonId, {
1958
2986
  meshId: ctx.mesh.id,
1959
2987
  message: args.message,
1960
- targetNodeId: args.node_id
2988
+ targetNodeId: args.node_id,
2989
+ ...taskMode ? { taskMode } : {}
1961
2990
  });
1962
2991
  return JSON.stringify(res);
1963
2992
  }
1964
2993
  const isLocalNode = isLocalControlPlaneNode(ctx, node);
1965
2994
  if (ctx.transport instanceof IpcTransport && node.daemonId && !isLocalNode) {
1966
- const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id || ""));
2995
+ const cached = getSessionMetadata(meshSessionCacheKey(args.node_id, args.session_id || ""));
2996
+ const taskId = (0, import_node_crypto.randomUUID)();
1967
2997
  const result2 = await ipcDispatchToRemoteAgent(ctx, node, {
1968
2998
  session_id: args.session_id,
1969
2999
  message: args.message,
1970
- providerType: cached?.providerType
3000
+ providerType: cached?.providerType,
3001
+ verifiedSession: explicitTargetSession,
3002
+ meshContext: {
3003
+ meshId: ctx.mesh.id,
3004
+ nodeId: args.node_id,
3005
+ taskId
3006
+ }
1971
3007
  });
1972
3008
  if (result2.success) {
1973
3009
  const dispatchedSessionId = args.session_id || result2.sessionId;
3010
+ const dispatchedAt = (/* @__PURE__ */ new Date()).toISOString();
1974
3011
  try {
3012
+ const providerType = result2.providerType || cached?.providerType;
1975
3013
  (0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
1976
3014
  kind: "task_dispatched",
1977
3015
  nodeId: args.node_id,
1978
3016
  sessionId: dispatchedSessionId,
1979
- payload: {
1980
- message: args.message,
1981
- via: "p2p_direct",
1982
- ...dispatchedSessionId ? { targetSessionId: dispatchedSessionId } : {}
1983
- }
3017
+ providerType,
3018
+ payload: buildDirectTaskPayload(args.message, "p2p_direct", {
3019
+ taskId,
3020
+ taskMode,
3021
+ providerType,
3022
+ targetSessionId: dispatchedSessionId
3023
+ })
3024
+ });
3025
+ (0, import_daemon_core.insertDirectDispatch)(ctx.mesh.id, {
3026
+ taskId,
3027
+ nodeId: args.node_id,
3028
+ sessionId: dispatchedSessionId,
3029
+ providerType: providerType || void 0,
3030
+ message: args.message,
3031
+ taskMode: taskMode || void 0,
3032
+ via: "p2p_direct",
3033
+ dispatchedAt
1984
3034
  });
1985
3035
  } catch {
1986
3036
  }
1987
3037
  }
1988
- return JSON.stringify({ ...result2, nodeId: args.node_id, dispatched: result2.success === true });
3038
+ return JSON.stringify({
3039
+ ...result2,
3040
+ nodeId: args.node_id,
3041
+ sessionId: result2.success ? args.session_id || result2.sessionId : args.session_id,
3042
+ ...result2.success ? { source: "direct", taskId } : {},
3043
+ taskMode,
3044
+ ...result2.success && result2.providerType ? { providerType: result2.providerType } : {},
3045
+ dispatched: result2.success === true
3046
+ });
1989
3047
  }
1990
3048
  if (args.session_id && isLocalTransport(ctx.transport)) {
1991
- const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id));
3049
+ const cached = getSessionMetadata(meshSessionCacheKey(args.node_id, args.session_id));
1992
3050
  let resolvedProviderType = cached?.providerType || "";
1993
3051
  if (!resolvedProviderType) {
1994
- const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
1995
- const sessions = extractStatusMetadataSessions(statusResult);
1996
- const explicitSession = sessions.find((session) => readSessionRecordId(session) === args.session_id);
3052
+ let explicitSession = explicitTargetSession;
3053
+ if (!explicitSession) {
3054
+ const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
3055
+ const sessions = extractStatusMetadataSessions(statusResult);
3056
+ explicitSession = sessions.find((session) => readSessionRecordId(session) === args.session_id);
3057
+ }
1997
3058
  if (!explicitSession) {
1998
3059
  return JSON.stringify({
1999
3060
  success: false,
@@ -2008,11 +3069,40 @@ async function meshSendTask(ctx, args) {
2008
3069
  nextAction: `Launch a fresh session with mesh_launch_session(node_id: '${args.node_id}') or retry without session_id so Repo Mesh can target a live delegate session.`
2009
3070
  });
2010
3071
  }
3072
+ if (isMeshCoordinatorSessionRecord(explicitSession)) {
3073
+ return JSON.stringify({
3074
+ success: false,
3075
+ recoverable: true,
3076
+ code: "mesh_target_session_is_coordinator",
3077
+ reason: "mesh_target_session_is_coordinator",
3078
+ nodeId: args.node_id,
3079
+ sessionId: args.session_id,
3080
+ taskMode: taskMode || "unspecified",
3081
+ error: `Session '${args.session_id}' is a Repo Mesh coordinator session, not a visible worker session. Launch or use a visible worker session before dispatching this task.`,
3082
+ nextAction: `Call mesh_launch_session for node '${args.node_id}' and then retry mesh_send_task with that worker session_id, or use mesh_enqueue_task for queue-based worker assignment.`
3083
+ });
3084
+ }
3085
+ if (isUnmanagedSessionRecord(explicitSession)) {
3086
+ return JSON.stringify({
3087
+ success: false,
3088
+ recoverable: true,
3089
+ code: "mesh_target_session_unmanaged",
3090
+ reason: "mesh_target_session_unmanaged",
3091
+ nodeId: args.node_id,
3092
+ sessionId: args.session_id,
3093
+ taskMode: taskMode || "unspecified",
3094
+ unsafeTranscriptAlias: true,
3095
+ unsafeDelegateTarget: true,
3096
+ error: `Session '${args.session_id}' on node '${args.node_id}' has no Repo Mesh delegation metadata (missing meshNodeFor/meshCoordinatorFor/launchedByCoordinator). It may be the coordinator's own session or an unrelated session \u2014 dispatching risks self-send and orphaned completion events that never reach the coordinator ledger.`,
3097
+ nextAction: `Call mesh_launch_session for node '${args.node_id}' to start a fresh managed worker session, then retry mesh_send_task with the returned session_id. Alternatively use mesh_enqueue_task for queue-based assignment without specifying session_id.`
3098
+ });
3099
+ }
2011
3100
  resolvedProviderType = resolveSessionProviderType(explicitSession);
2012
3101
  if (resolvedProviderType) {
2013
3102
  meshSessionProviderMetadata.set(meshSessionCacheKey(args.node_id, args.session_id), {
2014
3103
  providerType: resolvedProviderType,
2015
- providerSessionId: readString(explicitSession?.providerSessionId) || void 0
3104
+ providerSessionId: readString(explicitSession?.providerSessionId) || void 0,
3105
+ expiresAt: Date.now() + SESSION_PROVIDER_METADATA_TTL_MS
2016
3106
  });
2017
3107
  }
2018
3108
  }
@@ -2030,17 +3120,56 @@ async function meshSendTask(ctx, args) {
2030
3120
  nextAction: `Relaunch the target session on node '${args.node_id}' or retry without session_id so Repo Mesh can pick a session with provider metadata.`
2031
3121
  });
2032
3122
  }
3123
+ if (explicitTargetSession && !isIdleSessionRecord(explicitTargetSession) && !isTerminalSessionRecord(explicitTargetSession)) {
3124
+ const sessionStatus = typeof explicitTargetSession?.status === "string" ? explicitTargetSession.status : "unknown";
3125
+ const { createSessionDelivery: createDelivery, resolveDeliveryDecision } = await import("@adhdev/daemon-core");
3126
+ const policyResult = resolveDeliveryDecision(sessionStatus, { kind: "task" });
3127
+ if (policyResult.decision === "queued") {
3128
+ const delivery = createDelivery({
3129
+ meshId: ctx.mesh.id,
3130
+ nodeId: args.node_id,
3131
+ sessionId: args.session_id,
3132
+ providerType: resolvedProviderType,
3133
+ kind: "task",
3134
+ message: args.message,
3135
+ status: "queued"
3136
+ });
3137
+ return JSON.stringify({
3138
+ success: true,
3139
+ dispatched: false,
3140
+ decision: "queued_delivery",
3141
+ deliveryId: delivery.id,
3142
+ reason: policyResult.reason,
3143
+ nodeId: args.node_id,
3144
+ sessionId: args.session_id,
3145
+ sessionStatus,
3146
+ taskMode: taskMode || void 0,
3147
+ message: policyResult.message,
3148
+ nextAction: `Use mesh_status to watch for session idle transition, or use mesh_enqueue_task for queue-based assignment. Check deliveryId '${delivery.id}' to track queued delivery.`
3149
+ });
3150
+ }
3151
+ }
3152
+ const sessionWasIdle = explicitTargetSession ? isIdleSessionRecord(explicitTargetSession) : false;
3153
+ const taskId = (0, import_node_crypto.randomUUID)();
3154
+ const dispatchedAt = (/* @__PURE__ */ new Date()).toISOString();
2033
3155
  const dispatchResult = await commandForNode(ctx, node, "agent_command", {
2034
3156
  targetSessionId: args.session_id,
2035
3157
  agentType: resolvedProviderType,
2036
3158
  cliType: resolvedProviderType,
2037
3159
  providerType: resolvedProviderType,
2038
3160
  action: "send_chat",
2039
- message: args.message
3161
+ message: args.message,
3162
+ meshContext: {
3163
+ meshId: ctx.mesh.id,
3164
+ nodeId: args.node_id,
3165
+ taskId
3166
+ }
2040
3167
  });
2041
3168
  const dispatchPayload = unwrapCommandPayload(dispatchResult);
2042
3169
  if (dispatchPayload?.success === false || dispatchResult?.success === false) {
3170
+ const source = dispatchPayload?.success === false ? dispatchPayload : dispatchResult;
2043
3171
  return JSON.stringify({
3172
+ ...source && typeof source === "object" ? source : {},
2044
3173
  success: false,
2045
3174
  nodeId: args.node_id,
2046
3175
  sessionId: args.session_id,
@@ -2053,22 +3182,78 @@ async function meshSendTask(ctx, args) {
2053
3182
  nodeId: args.node_id,
2054
3183
  sessionId: args.session_id,
2055
3184
  providerType: resolvedProviderType,
2056
- payload: { message: args.message, via: "local_direct" }
3185
+ payload: buildDirectTaskPayload(args.message, "local_direct", {
3186
+ taskId,
3187
+ taskMode,
3188
+ providerType: resolvedProviderType,
3189
+ targetSessionId: args.session_id,
3190
+ dispatchedToIdleSession: sessionWasIdle
3191
+ })
3192
+ });
3193
+ } catch {
3194
+ }
3195
+ (0, import_daemon_core.insertDirectDispatch)(ctx.mesh.id, {
3196
+ taskId,
3197
+ nodeId: args.node_id,
3198
+ sessionId: args.session_id,
3199
+ providerType: resolvedProviderType || void 0,
3200
+ message: args.message,
3201
+ taskMode: taskMode || void 0,
3202
+ via: "local_direct",
3203
+ dispatchedToIdleSession: sessionWasIdle,
3204
+ dispatchedAt
3205
+ });
3206
+ let deliveryId;
3207
+ try {
3208
+ const { createSessionDelivery: createDelivery } = await import("@adhdev/daemon-core");
3209
+ const delivery = createDelivery({
3210
+ meshId: ctx.mesh.id,
3211
+ nodeId: args.node_id,
3212
+ sessionId: args.session_id,
3213
+ providerType: resolvedProviderType || void 0,
3214
+ taskId,
3215
+ kind: "task",
3216
+ message: args.message,
3217
+ status: sessionWasIdle ? "delivered" : "delivering"
2057
3218
  });
3219
+ deliveryId = delivery.id;
2058
3220
  } catch {
2059
3221
  }
2060
- return JSON.stringify({ success: true, dispatched: true, nodeId: args.node_id, sessionId: args.session_id });
3222
+ return JSON.stringify({
3223
+ success: true,
3224
+ dispatched: true,
3225
+ decision: "immediate",
3226
+ source: "direct",
3227
+ taskId,
3228
+ deliveryId,
3229
+ taskMode,
3230
+ providerType: resolvedProviderType,
3231
+ nodeId: args.node_id,
3232
+ sessionId: args.session_id,
3233
+ ...sessionWasIdle ? {
3234
+ dispatchAcknowledgementRisk: true,
3235
+ dispatchAcknowledgementRiskReason: "session_was_idle_at_dispatch",
3236
+ dispatchAcknowledgementNote: `Session '${args.session_id}' was idle at dispatch time. If it does not transition to generating, this direct task was not acknowledged. Use mesh_status to verify; if the session remains idle, it may appear as stale direct work \u2014 launch a fresh session and retry.`
3237
+ } : {}
3238
+ });
2061
3239
  }
2062
3240
  const task = (0, import_daemon_core.enqueueTask)(ctx.mesh.id, args.message, {
2063
3241
  targetNodeId: args.node_id,
2064
- targetSessionId: args.session_id
3242
+ targetSessionId: args.session_id,
3243
+ taskMode
2065
3244
  });
2066
- if (isLocalTransport(ctx.transport) || ctx.transport instanceof IpcTransport) {
2067
- ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
2068
- });
2069
- }
2070
- const pendingEvents = isLocalTransport(ctx.transport) ? (0, import_daemon_core.drainPendingMeshCoordinatorEvents)() : [];
2071
- const result = { success: true, nodeId: args.node_id, taskId: task.id, status: task.status };
3245
+ const queueTrigger = isLocalTransport(ctx.transport) || ctx.transport instanceof IpcTransport ? await triggerMeshQueueAndReport(ctx) : void 0;
3246
+ const pendingEvents = isLocalTransport(ctx.transport) ? (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id, ctx.localDaemonId) : [];
3247
+ const result = {
3248
+ success: true,
3249
+ source: "queue",
3250
+ nodeId: args.node_id,
3251
+ taskId: task.id,
3252
+ status: task.status,
3253
+ taskMode: task.taskMode,
3254
+ queueTrigger,
3255
+ ...buildQueueTriggerGuidance(queueTrigger)
3256
+ };
2072
3257
  if (pendingEvents.length > 0) {
2073
3258
  result.pendingCoordinatorEvents = pendingEvents;
2074
3259
  }
@@ -2092,7 +3277,7 @@ async function meshReadChat(ctx, args) {
2092
3277
  await drainCoordinatorPendingEvents(ctx, { nodeIds: [args.node_id] });
2093
3278
  }
2094
3279
  if (isLocalTransport(ctx.transport)) {
2095
- const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id));
3280
+ const cached = resolveMeshSessionProviderMetadata(ctx, args.node_id, args.session_id);
2096
3281
  const providerSessionId = typeof args.provider_session_id === "string" && args.provider_session_id.trim() ? args.provider_session_id.trim() : cached?.providerSessionId;
2097
3282
  const result = await commandForNode(ctx, node, "read_chat", {
2098
3283
  sessionId: args.session_id,
@@ -2107,7 +3292,8 @@ async function meshReadChat(ctx, args) {
2107
3292
  toolName: "mesh_read_chat",
2108
3293
  completionCallbackExpected: true
2109
3294
  });
2110
- if (args.compact) {
3295
+ const useCompact = args.compact !== false;
3296
+ if (useCompact) {
2111
3297
  const compactPayload = compactChatPayload(payload, {
2112
3298
  nodeId: args.node_id,
2113
3299
  sessionId: args.session_id,
@@ -2138,7 +3324,7 @@ async function meshReadChat(ctx, args) {
2138
3324
  async function meshReadDebug(ctx, args) {
2139
3325
  const node = await findNodeWithRefresh(ctx, args.node_id);
2140
3326
  if (isLocalTransport(ctx.transport)) {
2141
- const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id));
3327
+ const cached = resolveMeshSessionProviderMetadata(ctx, args.node_id, args.session_id);
2142
3328
  const providerSessionId = typeof args.provider_session_id === "string" && args.provider_session_id.trim() ? args.provider_session_id.trim() : cached?.providerSessionId;
2143
3329
  const delivery = args.delivery === "inline" ? void 0 : "daemon_file";
2144
3330
  const result = await commandForNode(ctx, node, "get_chat_debug_bundle", {
@@ -2169,6 +3355,8 @@ async function meshReadDebug(ctx, args) {
2169
3355
  }
2170
3356
  async function meshLaunchSession(ctx, args) {
2171
3357
  const node = await findNodeWithRefresh(ctx, args.node_id);
3358
+ const bootstrapBlock = getWorktreeBootstrapLaunchBlock(node, ctx.mesh.policy);
3359
+ if (bootstrapBlock) return JSON.stringify(bootstrapBlock, null, 2);
2172
3360
  if (isLocalTransport(ctx.transport)) {
2173
3361
  let resolvedProviderType = typeof args.type === "string" && args.type.trim() ? args.type : "";
2174
3362
  if (!resolvedProviderType) {
@@ -2203,6 +3391,9 @@ async function meshLaunchSession(ctx, args) {
2203
3391
  cliType: resolvedProviderType,
2204
3392
  dir: node.workspace,
2205
3393
  settings: {
3394
+ // Worker launch envelope (A5): structured metadata so worker sessions
3395
+ // know their role and can route completion events back correctly.
3396
+ role: "worker",
2206
3397
  meshNodeFor: ctx.mesh.id,
2207
3398
  meshNodeId: args.node_id,
2208
3399
  spawnedSessionVisibility,
@@ -2224,7 +3415,8 @@ async function meshLaunchSession(ctx, args) {
2224
3415
  if (runtimeSessionId) {
2225
3416
  meshSessionProviderMetadata.set(meshSessionCacheKey(args.node_id, runtimeSessionId), {
2226
3417
  providerType: resolvedProviderType,
2227
- ...providerSessionId ? { providerSessionId } : {}
3418
+ ...providerSessionId ? { providerSessionId } : {},
3419
+ expiresAt: Date.now() + SESSION_PROVIDER_METADATA_TTL_MS
2228
3420
  });
2229
3421
  }
2230
3422
  try {
@@ -2237,17 +3429,13 @@ async function meshLaunchSession(ctx, args) {
2237
3429
  });
2238
3430
  } catch {
2239
3431
  }
2240
- if (ctx.transport instanceof IpcTransport && node.daemonId && !isLocalNode) {
2241
- ctx.transport.meshCommand(node.daemonId, "trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
2242
- });
2243
- } else if (isLocalTransport(ctx.transport)) {
2244
- ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
2245
- });
2246
- }
3432
+ const queueTrigger = await triggerMeshQueueAndReport(ctx, node, { localNode: isLocalNode });
2247
3433
  return JSON.stringify({
2248
3434
  ...launchPayload,
2249
3435
  resolvedProviderType,
2250
- ...providerSessionId ? { providerSessionId } : {}
3436
+ ...providerSessionId ? { providerSessionId } : {},
3437
+ queueTrigger,
3438
+ ...buildQueueTriggerGuidance(queueTrigger)
2251
3439
  }, null, 2);
2252
3440
  } else if (!isLocalTransport(ctx.transport) && node.daemonId) {
2253
3441
  let resolvedProviderType = typeof args.type === "string" && args.type.trim() ? args.type : "";
@@ -2344,6 +3532,51 @@ async function meshGitStatus(ctx, args) {
2344
3532
  }, null, 2);
2345
3533
  }
2346
3534
  }
3535
+ async function meshFastForwardNode(ctx, args) {
3536
+ await refreshMeshFromDaemon(ctx);
3537
+ const node = await findNodeWithRefresh(ctx, args.node_id);
3538
+ const submoduleIgnorePaths = node.policy?.submoduleIgnorePaths || [];
3539
+ if (node.policy?.readOnly) {
3540
+ return JSON.stringify({
3541
+ success: false,
3542
+ code: "node_read_only",
3543
+ nodeId: args.node_id,
3544
+ workspace: node.workspace,
3545
+ allowed: false,
3546
+ willRun: false,
3547
+ executed: false,
3548
+ blockingReasons: ["node_read_only"]
3549
+ }, null, 2);
3550
+ }
3551
+ try {
3552
+ const dryRun = args.dry_run === true || args.execute !== true;
3553
+ const result = await commandForNode(ctx, node, "fast_forward_mesh_node", {
3554
+ meshId: ctx.mesh.id,
3555
+ nodeId: node.id,
3556
+ workspace: node.workspace,
3557
+ branch: typeof args.branch === "string" ? args.branch : void 0,
3558
+ execute: args.execute === true && args.dry_run !== true,
3559
+ dryRun,
3560
+ updateSubmodules: args.update_submodules === true,
3561
+ submoduleIgnorePaths: submoduleIgnorePaths.length > 0 ? submoduleIgnorePaths : void 0
3562
+ });
3563
+ return JSON.stringify(unwrapCommandPayload(result), null, 2);
3564
+ } catch (e) {
3565
+ const failure = buildCoordinatorP2pRelayFailure(e, {
3566
+ command: "fast_forward_mesh_node",
3567
+ targetDaemonId: node.daemonId,
3568
+ nodeId: args.node_id
3569
+ });
3570
+ return JSON.stringify({
3571
+ ...failure,
3572
+ workspace: node.workspace,
3573
+ allowed: false,
3574
+ willRun: false,
3575
+ executed: false,
3576
+ blockingReasons: [failure.code || "mesh_fast_forward_unavailable"]
3577
+ }, null, 2);
3578
+ }
3579
+ }
2347
3580
  async function meshCheckpoint(ctx, args) {
2348
3581
  const node = await findNodeWithRefresh(ctx, args.node_id);
2349
3582
  if (node.policy?.readOnly) {
@@ -2359,7 +3592,13 @@ async function meshCheckpoint(ctx, args) {
2359
3592
  (0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
2360
3593
  kind: "checkpoint_created",
2361
3594
  nodeId: args.node_id,
2362
- payload: { message: args.message, commit: result?.checkpoint?.commit }
3595
+ payload: {
3596
+ message: args.message,
3597
+ commit: result?.checkpoint?.commit,
3598
+ outcome: result?.checkpoint?.status || (result?.checkpoint?.noop ? "skipped" : void 0),
3599
+ noop: result?.checkpoint?.noop === true,
3600
+ reason: result?.checkpoint?.reason
3601
+ }
2363
3602
  });
2364
3603
  } catch {
2365
3604
  }
@@ -2375,7 +3614,13 @@ async function meshCheckpoint(ctx, args) {
2375
3614
  (0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
2376
3615
  kind: "checkpoint_created",
2377
3616
  nodeId: args.node_id,
2378
- payload: { message: args.message, commit: res?.checkpoint?.commit }
3617
+ payload: {
3618
+ message: args.message,
3619
+ commit: res?.checkpoint?.commit,
3620
+ outcome: res?.checkpoint?.status || (res?.checkpoint?.noop ? "skipped" : void 0),
3621
+ noop: res?.checkpoint?.noop === true,
3622
+ reason: res?.checkpoint?.reason
3623
+ }
2379
3624
  });
2380
3625
  } catch {
2381
3626
  }
@@ -2390,7 +3635,7 @@ async function meshCheckpoint(ctx, args) {
2390
3635
  async function meshApprove(ctx, args) {
2391
3636
  const node = await findNodeWithRefresh(ctx, args.node_id);
2392
3637
  if (isLocalTransport(ctx.transport)) {
2393
- const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id));
3638
+ const cached = getSessionMetadata(meshSessionCacheKey(args.node_id, args.session_id));
2394
3639
  const providerSessionId = cached?.providerSessionId;
2395
3640
  const result = await commandForNode(ctx, node, "resolve_action", {
2396
3641
  sessionId: args.session_id,
@@ -2543,6 +3788,43 @@ async function meshRemoveNode(ctx, args) {
2543
3788
  return JSON.stringify({ error: "Cloud mesh remove_node requires node daemonId" });
2544
3789
  }
2545
3790
  }
3791
+ function resolveRefineConfigNode(ctx, nodeId) {
3792
+ if (nodeId) return findNode(ctx.mesh, nodeId);
3793
+ const node = ctx.mesh.nodes.find((entry) => !!entry.workspace);
3794
+ if (!node) throw new Error("No mesh node with a workspace is available");
3795
+ return node;
3796
+ }
3797
+ async function meshRefineConfigSchema(ctx) {
3798
+ const node = resolveRefineConfigNode(ctx);
3799
+ const result = await commandForNode(ctx, node, "get_mesh_refine_config_schema", {});
3800
+ return JSON.stringify(result, null, 2);
3801
+ }
3802
+ async function meshValidateRefineConfig(ctx, args) {
3803
+ const node = resolveRefineConfigNode(ctx, args.node_id);
3804
+ const result = await commandForNode(ctx, node, "validate_mesh_refine_config", {
3805
+ workspace: node.workspace,
3806
+ inlineMesh: ctx.mesh,
3807
+ ...args.config ? { config: args.config } : {}
3808
+ });
3809
+ return JSON.stringify(result, null, 2);
3810
+ }
3811
+ async function meshSuggestRefineConfig(ctx, args) {
3812
+ const node = resolveRefineConfigNode(ctx, args.node_id);
3813
+ const result = await commandForNode(ctx, node, "suggest_mesh_refine_config", {
3814
+ workspace: node.workspace,
3815
+ inlineMesh: ctx.mesh
3816
+ });
3817
+ return JSON.stringify(result, null, 2);
3818
+ }
3819
+ async function meshRefinePlan(ctx, args) {
3820
+ const node = await findNodeWithRefresh(ctx, args.node_id);
3821
+ const result = await commandForNode(ctx, node, "plan_mesh_refine_node", {
3822
+ meshId: ctx.mesh.id,
3823
+ nodeId: args.node_id,
3824
+ inlineMesh: ctx.mesh
3825
+ });
3826
+ return JSON.stringify(result, null, 2);
3827
+ }
2546
3828
  async function meshRefineNode(ctx, args) {
2547
3829
  const node = await findNodeWithRefresh(ctx, args.node_id);
2548
3830
  if (isLocalTransport(ctx.transport)) {
@@ -2551,7 +3833,7 @@ async function meshRefineNode(ctx, args) {
2551
3833
  nodeId: args.node_id,
2552
3834
  inlineMesh: ctx.mesh
2553
3835
  });
2554
- if (result?.success && result.removeResult?.removed !== false) {
3836
+ if (result?.success && result.async !== true && result.removeResult?.removed !== false) {
2555
3837
  const idx = ctx.mesh.nodes.findIndex((n) => n.id === args.node_id);
2556
3838
  if (idx >= 0) {
2557
3839
  ctx.mesh.nodes.splice(idx, 1);
@@ -2566,7 +3848,7 @@ async function meshRefineNode(ctx, args) {
2566
3848
  nodeId: args.node_id,
2567
3849
  inlineMesh: ctx.mesh
2568
3850
  });
2569
- if (res?.success && res.removeResult?.removed !== false) {
3851
+ if (res?.success && res.async !== true && res.removeResult?.removed !== false) {
2570
3852
  const idx = ctx.mesh.nodes.findIndex((n) => n.id === args.node_id);
2571
3853
  if (idx >= 0) {
2572
3854
  ctx.mesh.nodes.splice(idx, 1);
@@ -2581,6 +3863,18 @@ async function meshRefineNode(ctx, args) {
2581
3863
  return JSON.stringify({ error: "Cloud mesh refine_node requires node daemonId" });
2582
3864
  }
2583
3865
  }
3866
+ async function meshReviewInbox(ctx, args = {}) {
3867
+ if (!isLocalTransport(ctx.transport)) {
3868
+ return JSON.stringify({ error: "mesh_review_inbox requires a local daemon transport (M4.0 scope: local nodes only)" });
3869
+ }
3870
+ await refreshMeshFromDaemon(ctx);
3871
+ const meshId = (args.mesh_id ?? ctx.mesh.id).trim();
3872
+ const result = await commandForNode(ctx, ctx.mesh.nodes[0], "get_mesh_review_inbox", {
3873
+ meshId,
3874
+ inlineMesh: ctx.mesh
3875
+ });
3876
+ return JSON.stringify(result, null, 2);
3877
+ }
2584
3878
 
2585
3879
  // src/help.ts
2586
3880
  var STANDARD_TOOLS = [
@@ -2603,13 +3897,13 @@ var STANDARD_TOOLS = [
2603
3897
  function buildMcpHelpText() {
2604
3898
  const meshTools = ALL_MESH_TOOLS.map((tool) => tool.name);
2605
3899
  return `
2606
- adhdev-mcp \u2014 ADHDev MCP Server
3900
+ ADHDev MCP Server
2607
3901
 
2608
3902
  Usage:
2609
- adhdev-mcp Local mode (requires standalone daemon)
2610
- adhdev-mcp --api-key <key> Cloud mode (ADHDev cloud API)
2611
- adhdev-mcp --mode ipc --repo-mesh <mesh_id> Cloud daemon IPC mesh mode
2612
- adhdev-mcp --repo-mesh <mesh_id> Mesh mode (coordinator-scoped tools)
3903
+ adhdev mcp Local mode (requires standalone daemon)
3904
+ adhdev mcp --api-key <key> Cloud mode (ADHDev cloud API)
3905
+ adhdev mcp --mode ipc --repo-mesh <mesh_id> Cloud daemon IPC mesh mode
3906
+ adhdev-mcp --help Compatibility bin (same server, legacy package entrypoint)
2613
3907
 
2614
3908
  Options:
2615
3909
  --mode <mode> Transport: local, cloud, or ipc
@@ -2634,6 +3928,7 @@ Mesh tools: ${meshTools.join(", ")}
2634
3928
  // src/server.ts
2635
3929
  var import_server = require("@modelcontextprotocol/sdk/server/index.js");
2636
3930
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
3931
+ var import_node_os = __toESM(require("os"));
2637
3932
  var import_types = require("@modelcontextprotocol/sdk/types.js");
2638
3933
 
2639
3934
  // src/transports/local.ts
@@ -3185,11 +4480,17 @@ function formatChatResult(result, sessionId, format, limit = 50, compact = false
3185
4480
  }, null, 2);
3186
4481
  }
3187
4482
  if ((format === "text" || format === void 0) && compact && compactPayload) {
3188
- const lines2 = outputMessages.slice(-limit).map((m) => {
4483
+ const summaryText = typeof compactPayload.summary === "string" ? compactPayload.summary.trim() : "";
4484
+ const tail = outputMessages.slice(-limit);
4485
+ const lastIndex = tail.length - 1;
4486
+ const lines2 = tail.flatMap((m, idx) => {
3189
4487
  const role = m.role === "user" ? "User" : m.role === "assistant" ? "Agent" : m.role;
3190
4488
  const content = messageContent(m);
4489
+ if (idx === lastIndex && (role === "Agent" || m.role === "agent") && summaryText && content.trim() === summaryText) {
4490
+ return [];
4491
+ }
3191
4492
  const truncated = content.length > 500 ? `${content.slice(0, 500)}\u2026` : content;
3192
- return `[${role}] ${truncated}`;
4493
+ return [`[${role}] ${truncated}`];
3193
4494
  });
3194
4495
  if (compactPayload.summary) {
3195
4496
  const truncatedSummary = compactPayload.summary.length > 500 ? `${compactPayload.summary.slice(0, 500)}\u2026` : compactPayload.summary;
@@ -3304,6 +4605,90 @@ function formatChatDebugResult(result, options) {
3304
4605
  return JSON.stringify(result, null, 2);
3305
4606
  }
3306
4607
 
4608
+ // src/tools/spec-debug.ts
4609
+ var SPEC_DEBUG_TOOL = {
4610
+ name: "spec_debug",
4611
+ description: "Get current spec state, sections, and state transition history for a spec-driven CLI session (claude-cli, antigravity-cli, etc.). Use to diagnose idle/busy detection issues, inspect section parsing, or verify idle_hold and busy_hold behavior.",
4612
+ inputSchema: {
4613
+ type: "object",
4614
+ properties: {
4615
+ session_id: {
4616
+ type: "string",
4617
+ description: "Target session ID (from list_sessions)."
4618
+ },
4619
+ daemon_id: {
4620
+ type: "string",
4621
+ description: "Daemon ID (cloud mode only). Omit for local mode."
4622
+ },
4623
+ ...FORMAT_PROP
4624
+ },
4625
+ required: ["session_id"]
4626
+ }
4627
+ };
4628
+ async function specDebug(transport, args) {
4629
+ const sessionId = typeof args.session_id === "string" ? args.session_id.trim() : "";
4630
+ if (!sessionId) throw new Error("session_id is required");
4631
+ let result;
4632
+ if (isLocalTransport(transport)) {
4633
+ result = await transport.command("get_spec_debug", { targetSessionId: sessionId });
4634
+ } else {
4635
+ if (!args.daemon_id) throw new Error("daemon_id is required in cloud mode");
4636
+ const targetId = `${args.daemon_id}:session:${sessionId}`;
4637
+ result = await transport.sendCommand(targetId, "get_spec_debug", { targetSessionId: sessionId });
4638
+ }
4639
+ return formatSpecDebugResult(result, { sessionId, format: args.format });
4640
+ }
4641
+ function formatSpecDebugResult(result, options) {
4642
+ if (!result?.success) {
4643
+ const err = result?.error || "Unknown error";
4644
+ if (options.format === "json") return JSON.stringify({ success: false, error: err }, null, 2);
4645
+ return `Error: ${err}`;
4646
+ }
4647
+ if (options.format === "json") return JSON.stringify(result, null, 2);
4648
+ const snap = result.snapshot;
4649
+ if (!snap) {
4650
+ return [
4651
+ `session_id: ${options.sessionId}`,
4652
+ `provider_type: ${String(result.providerType || "")}`,
4653
+ "is_spec_provider: false",
4654
+ "No spec debug data available (not a spec-driven provider)."
4655
+ ].join("\n");
4656
+ }
4657
+ const lines = [];
4658
+ lines.push(`session_id: ${options.sessionId}`);
4659
+ lines.push(`provider_type: ${String(result.providerType || snap.cliType || "")}`);
4660
+ lines.push(`spec_id: ${String(snap.spec_id || "")}`);
4661
+ lines.push(`spec_path: ${String(snap.specPath || "")}`);
4662
+ lines.push(`current_state: ${snap.current_state ? `${snap.current_state.id} (${snap.current_state.label})` : "none"}`);
4663
+ lines.push(`idle_hold_pending: ${String(snap.idleHoldPending ?? false)}`);
4664
+ lines.push(`last_busy_at: ${snap.lastBusyAt ? new Date(snap.lastBusyAt).toISOString() : "never"}`);
4665
+ lines.push(`exited: ${String(snap.exited ?? false)}`);
4666
+ if (snap.current_modal) {
4667
+ lines.push(`current_modal: ${JSON.stringify(snap.current_modal)}`);
4668
+ }
4669
+ if (snap.sections && typeof snap.sections === "object") {
4670
+ lines.push("");
4671
+ lines.push("\u2500\u2500 sections \u2500\u2500");
4672
+ for (const [id, text] of Object.entries(snap.sections)) {
4673
+ const preview = String(text || "").replace(/\n/g, "\u21B5").slice(0, 120);
4674
+ lines.push(` ${id}: ${preview}`);
4675
+ }
4676
+ }
4677
+ const history = Array.isArray(snap.stateHistory) ? snap.stateHistory : [];
4678
+ if (history.length > 0) {
4679
+ lines.push("");
4680
+ lines.push("\u2500\u2500 state history (newest first) \u2500\u2500");
4681
+ const now = Date.now();
4682
+ for (const entry of [...history].reverse().slice(0, 20)) {
4683
+ const agoMs = now - entry.at;
4684
+ const ago = agoMs < 2e3 ? `${agoMs}ms ago` : `${(agoMs / 1e3).toFixed(1)}s ago`;
4685
+ const dur = entry.durationMs > 0 ? ` held ${entry.durationMs}ms` : "";
4686
+ lines.push(` ${String(entry.stateId).padEnd(18)} ${ago}${dur}`);
4687
+ }
4688
+ }
4689
+ return lines.join("\n");
4690
+ }
4691
+
3307
4692
  // src/tools/send-chat.ts
3308
4693
  var SEND_CHAT_TOOL = {
3309
4694
  name: "send_chat",
@@ -4167,6 +5552,7 @@ async function startMcpServer(opts) {
4167
5552
  requirePreTaskCheckpoint: false,
4168
5553
  requirePostTaskCheckpoint: true,
4169
5554
  requireApprovalForPush: true,
5555
+ allowAutoPublishSubmoduleMainCommits: false,
4170
5556
  requireApprovalForDestructiveGit: true,
4171
5557
  dirtyWorkspaceBehavior: "warn",
4172
5558
  maxParallelTasks: 2,
@@ -4223,11 +5609,13 @@ async function startMcpServer(opts) {
4223
5609
  }
4224
5610
  let localDaemonId;
4225
5611
  let localMachineId;
5612
+ let coordinatorHostname = import_node_os.default.hostname();
4226
5613
  if (transport instanceof LocalTransport || transport instanceof IpcTransport) {
4227
5614
  try {
4228
5615
  const { loadConfig } = await import("@adhdev/daemon-core");
4229
5616
  const cfg = loadConfig();
4230
- if (cfg.registeredMachineId) localMachineId = cfg.registeredMachineId;
5617
+ if (cfg.machineId) localMachineId = cfg.machineId;
5618
+ else if (cfg.registeredMachineId) localMachineId = cfg.registeredMachineId;
4231
5619
  } catch {
4232
5620
  }
4233
5621
  }
@@ -4235,14 +5623,16 @@ async function startMcpServer(opts) {
4235
5623
  try {
4236
5624
  const statusResult = await transport.getStatus();
4237
5625
  const instanceId = typeof statusResult?.status?.instanceId === "string" ? statusResult.status.instanceId.trim() : "";
5626
+ const hostname = typeof statusResult?.status?.hostname === "string" ? statusResult.status.hostname.trim() : typeof statusResult?.status?.machine?.hostname === "string" ? statusResult.status.machine.hostname.trim() : "";
4238
5627
  if (instanceId) localDaemonId = instanceId;
5628
+ if (hostname) coordinatorHostname = hostname;
4239
5629
  } catch {
4240
5630
  }
4241
5631
  }
4242
- const meshCtx = { mesh, transport, ...localDaemonId ? { localDaemonId } : {}, ...localMachineId ? { localMachineId } : {} };
5632
+ const meshCtx = { mesh, transport, ...localDaemonId ? { localDaemonId } : {}, ...localMachineId ? { localMachineId } : {}, ...coordinatorHostname ? { coordinatorHostname } : {} };
4243
5633
  const coordinatorPrompt = await buildMeshModeCoordinatorPrompt(mesh);
4244
5634
  const server2 = new import_server.Server(
4245
- { name: "adhdev-mcp-server", version: "0.9.81" },
5635
+ { name: "adhdev-mcp-server", version: "0.9.82" },
4246
5636
  { capabilities: { tools: {}, resources: {} } }
4247
5637
  );
4248
5638
  const { ListResourcesRequestSchema, ReadResourceRequestSchema } = await import("@modelcontextprotocol/sdk/types.js");
@@ -4268,7 +5658,7 @@ async function startMcpServer(opts) {
4268
5658
  let text;
4269
5659
  switch (name) {
4270
5660
  case "mesh_status":
4271
- text = await meshStatus(meshCtx);
5661
+ text = await meshStatus(meshCtx, a);
4272
5662
  break;
4273
5663
  case "mesh_list_nodes":
4274
5664
  text = await meshListNodes(meshCtx);
@@ -4300,6 +5690,9 @@ async function startMcpServer(opts) {
4300
5690
  case "mesh_git_status":
4301
5691
  text = await meshGitStatus(meshCtx, a);
4302
5692
  break;
5693
+ case "mesh_fast_forward_node":
5694
+ text = await meshFastForwardNode(meshCtx, a);
5695
+ break;
4303
5696
  case "mesh_checkpoint":
4304
5697
  text = await meshCheckpoint(meshCtx, a);
4305
5698
  break;
@@ -4315,6 +5708,18 @@ async function startMcpServer(opts) {
4315
5708
  case "mesh_refine_node":
4316
5709
  text = await meshRefineNode(meshCtx, a);
4317
5710
  break;
5711
+ case "mesh_refine_config_schema":
5712
+ text = await meshRefineConfigSchema(meshCtx);
5713
+ break;
5714
+ case "mesh_validate_refine_config":
5715
+ text = await meshValidateRefineConfig(meshCtx, a);
5716
+ break;
5717
+ case "mesh_suggest_refine_config":
5718
+ text = await meshSuggestRefineConfig(meshCtx, a);
5719
+ break;
5720
+ case "mesh_refine_plan":
5721
+ text = await meshRefinePlan(meshCtx, a);
5722
+ break;
4318
5723
  case "mesh_cleanup_sessions":
4319
5724
  text = await meshCleanupSessions(meshCtx, a);
4320
5725
  break;
@@ -4324,6 +5729,12 @@ async function startMcpServer(opts) {
4324
5729
  case "mesh_reconcile_ledger":
4325
5730
  text = await meshReconcileLedger(meshCtx, a);
4326
5731
  break;
5732
+ case "mesh_mission_upsert":
5733
+ text = await meshMissionUpsert(meshCtx, a);
5734
+ break;
5735
+ case "mesh_review_inbox":
5736
+ text = await meshReviewInbox(meshCtx, a);
5737
+ break;
4327
5738
  default:
4328
5739
  return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
4329
5740
  }
@@ -4346,6 +5757,7 @@ async function startMcpServer(opts) {
4346
5757
  CHECK_PENDING_TOOL,
4347
5758
  READ_CHAT_TOOL,
4348
5759
  READ_CHAT_DEBUG_TOOL,
5760
+ SPEC_DEBUG_TOOL,
4349
5761
  SEND_CHAT_TOOL,
4350
5762
  APPROVE_TOOL,
4351
5763
  GIT_STATUS_TOOL,
@@ -4381,6 +5793,10 @@ async function startMcpServer(opts) {
4381
5793
  const text = await readChatDebug(transport, a);
4382
5794
  return { content: [{ type: "text", text }] };
4383
5795
  }
5796
+ case "spec_debug": {
5797
+ const text = await specDebug(transport, a);
5798
+ return { content: [{ type: "text", text }] };
5799
+ }
4384
5800
  case "send_chat": {
4385
5801
  const text = await sendChat(transport, { message: a.message, session_id: a.session_id, daemon_id: a.daemon_id });
4386
5802
  return { content: [{ type: "text", text }] };