@adhdev/daemon-standalone 0.9.82-rc.2 → 0.9.82-rc.200

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
  };
@@ -161,16 +248,19 @@ function isCoordinatorVisibleMessage(message) {
161
248
  if (meta?.internal === true || meta?.debug === true || meta?.control === true || meta?.userVisible === false || meta?.user_visible === false) return false;
162
249
  return role === "user" || role === "assistant" || role === "agent";
163
250
  }
251
+ function buildCompactMessageTail(visibleMessages, opts) {
252
+ return visibleMessages.slice(-opts.limit);
253
+ }
164
254
  function compactChatPayload(payload, opts = {}) {
165
255
  const rawMessages = Array.isArray(payload?.messages) ? payload.messages : [];
166
256
  const visible = rawMessages.filter(isCoordinatorVisibleMessage);
167
257
  const limit = Math.max(1, Math.min(opts.limit ?? 10, 10));
168
- const messages = visible.slice(-limit);
169
258
  const finalAssistant = [...visible].reverse().find((message) => {
170
259
  const role = String(message?.role ?? "").toLowerCase();
171
260
  return (role === "assistant" || role === "agent") && messageContent(message).trim();
172
261
  });
173
262
  const summary = typeof payload?.summary === "string" && payload.summary.trim() ? payload.summary.trim() : messageContent(finalAssistant).trim();
263
+ const messages = buildCompactMessageTail(visible, { summary, finalAssistant, limit });
174
264
  return {
175
265
  success: payload?.success !== false,
176
266
  compact: true,
@@ -231,30 +321,86 @@ function annotateRapidReadChatAdvisory(payload, options) {
231
321
 
232
322
  // src/tools/mesh-tools.ts
233
323
  var import_daemon_core = require("@adhdev/daemon-core");
324
+ var SESSION_PROVIDER_METADATA_TTL_MS = 30 * 6e4;
234
325
  var meshSessionProviderMetadata = /* @__PURE__ */ new Map();
326
+ function getSessionMetadata(key) {
327
+ const entry = meshSessionProviderMetadata.get(key);
328
+ if (!entry) return void 0;
329
+ if (entry.expiresAt <= Date.now()) {
330
+ meshSessionProviderMetadata.delete(key);
331
+ return void 0;
332
+ }
333
+ return entry;
334
+ }
335
+ var ACTIVE_WORK_POLLING_BACKOFF_MS = 6e4;
336
+ function buildActiveWorkPollingGuidance(summary, now = Date.now()) {
337
+ if (!summary || summary.generatingCount <= 0) return void 0;
338
+ return {
339
+ activeGeneratingWork: true,
340
+ generatingCount: summary.generatingCount,
341
+ doNotPollBefore: new Date(now + ACTIVE_WORK_POLLING_BACKOFF_MS).toISOString(),
342
+ eventSurface: "pendingCoordinatorEvents",
343
+ 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.",
344
+ 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."
345
+ };
346
+ }
235
347
  function readString(value) {
236
348
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
237
349
  }
350
+ function summarizeTaskMessage(message) {
351
+ const taskSummary = message.replace(/\s+/g, " ").trim();
352
+ const taskTitle = taskSummary.length > 96 ? `${taskSummary.slice(0, 93)}...` : taskSummary;
353
+ return { taskTitle: taskTitle || "(untitled task)", taskSummary };
354
+ }
355
+ function buildDirectTaskPayload(message, via, opts) {
356
+ const descriptor = summarizeTaskMessage(message);
357
+ return {
358
+ source: "direct",
359
+ via,
360
+ taskId: opts.taskId,
361
+ message,
362
+ taskTitle: descriptor.taskTitle,
363
+ taskSummary: descriptor.taskSummary,
364
+ ...opts.taskMode ? { taskMode: opts.taskMode } : {},
365
+ ...opts.providerType ? { providerType: opts.providerType } : {},
366
+ ...opts.targetSessionId ? { targetSessionId: opts.targetSessionId } : {},
367
+ ...opts.dispatchedToIdleSession !== void 0 ? { dispatchedToIdleSession: opts.dispatchedToIdleSession } : {}
368
+ };
369
+ }
370
+ function findNode(mesh, nodeId) {
371
+ const node = mesh.nodes.find((n) => n.id === nodeId);
372
+ if (!node) throw new Error(`Node '${nodeId}' is not a member of mesh '${mesh.name}'`);
373
+ return node;
374
+ }
238
375
  var DUPLICATE_DISPATCH_WINDOW_MS = 6e4;
239
376
  var STALE_ASSIGNED_QUEUE_MS = 30 * 6e4;
240
377
  var OLD_HISTORICAL_QUEUE_RECORD_MS = 7 * 24 * 60 * 6e4;
241
378
  var ACTIVE_QUEUE_STATUSES = /* @__PURE__ */ new Set(["pending", "assigned"]);
242
379
  var HISTORICAL_QUEUE_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
243
380
  async function refreshMeshFromDaemon(ctx) {
244
- if (!(ctx.transport instanceof IpcTransport)) return;
381
+ if (!isLocalTransport(ctx.transport)) return;
245
382
  try {
246
383
  const result = await ctx.transport.command("get_mesh", { meshId: ctx.mesh.id });
247
384
  if (!result?.success || !Array.isArray(result.mesh?.nodes)) return;
248
385
  const refreshedNodes = result.mesh.nodes.filter((n) => n?.id).map((n) => n);
249
- if (!refreshedNodes.length) return;
250
386
  ctx.mesh.nodes.splice(0, ctx.mesh.nodes.length, ...refreshedNodes);
251
387
  ctx.mesh.updatedAt = result.mesh.updatedAt ?? ctx.mesh.updatedAt;
252
388
  } catch {
253
389
  }
254
390
  }
391
+ async function syncCoordinatorDaemonMeshCache(ctx) {
392
+ if (!(ctx.transport instanceof IpcTransport)) return;
393
+ try {
394
+ await ctx.transport.command("get_mesh", {
395
+ meshId: ctx.mesh.id,
396
+ inlineMesh: ctx.mesh
397
+ });
398
+ } catch {
399
+ }
400
+ }
255
401
  async function findNodeWithRefresh(ctx, nodeId) {
256
402
  const hit = ctx.mesh.nodes.find((n) => n.id === nodeId);
257
- if (hit) return hit;
403
+ if (hit && !hit.isLocalWorktree) return hit;
258
404
  await refreshMeshFromDaemon(ctx);
259
405
  const refreshed = ctx.mesh.nodes.find((n) => n.id === nodeId);
260
406
  if (!refreshed) throw new Error(`Node '${nodeId}' is not a member of mesh '${ctx.mesh.name}'`);
@@ -262,7 +408,7 @@ async function findNodeWithRefresh(ctx, nodeId) {
262
408
  }
263
409
  async function findOptionalNodeWithRefresh(ctx, nodeId) {
264
410
  const hit = ctx.mesh.nodes.find((n) => n.id === nodeId);
265
- if (hit) return hit;
411
+ if (hit && !hit.isLocalWorktree) return hit;
266
412
  await refreshMeshFromDaemon(ctx);
267
413
  return ctx.mesh.nodes.find((n) => n.id === nodeId) ?? null;
268
414
  }
@@ -314,9 +460,26 @@ function buildMissingNodeReadChatRecovery(ctx, args) {
314
460
  readDebugLocator: readString(lastTerminal?.payload?.readDebugLocator) || readString(lastTerminal?.payload?.debugBundlePath)
315
461
  };
316
462
  if (finalSummary) {
463
+ if (args.compact === true) {
464
+ return {
465
+ ...compactChatPayload({
466
+ success: true,
467
+ status: "idle",
468
+ providerSessionId,
469
+ summary: finalSummary,
470
+ messages: [{ role: "assistant", content: finalSummary, isHistorical: true }]
471
+ }, {
472
+ nodeId: args.node_id,
473
+ sessionId: args.session_id,
474
+ limit: args.tail ?? 10
475
+ }),
476
+ recoveredFromLedger: true,
477
+ ledger
478
+ };
479
+ }
317
480
  return {
318
481
  success: true,
319
- compact: args.compact === true,
482
+ compact: false,
320
483
  recoveredFromLedger: true,
321
484
  nodeId: args.node_id,
322
485
  sessionId: args.session_id,
@@ -368,6 +531,33 @@ function buildMissingNodeReadChatRecovery(ctx, args) {
368
531
  function readSessionRecordId(session) {
369
532
  return readString(session?.id) || readString(session?.sessionId) || readString(session?.session_id) || readString(session?.runtimeSessionId) || readString(session?.runtime_session_id) || readString(session?.instanceId) || readString(session?.instance_id);
370
533
  }
534
+ function extractStatusMetadataSessions(value) {
535
+ const payload = unwrapCommandPayload(value);
536
+ const status = payload?.status && typeof payload.status === "object" ? payload.status : payload;
537
+ return Array.isArray(status?.sessions) ? status.sessions : [];
538
+ }
539
+ function resolveSessionProviderType(session) {
540
+ return readString(session?.providerType) || readString(session?.cliType) || readString(session?.agentType) || "";
541
+ }
542
+ function isMeshCoordinatorSessionRecord(session) {
543
+ return Boolean(
544
+ readString(session?.settings?.meshCoordinatorFor) || readString(session?.meta?.meshCoordinatorFor) || readString(session?.metadata?.meshCoordinatorFor) || readString(session?.meshCoordinatorFor)
545
+ );
546
+ }
547
+ function isUnmanagedSessionRecord(session) {
548
+ const hasMeshNodeFor = Boolean(
549
+ readString(session?.settings?.meshNodeFor) || readString(session?.meta?.meshNodeFor) || readString(session?.metadata?.meshNodeFor) || readString(session?.meshNodeFor)
550
+ );
551
+ if (hasMeshNodeFor) return false;
552
+ if (isMeshCoordinatorSessionRecord(session)) return false;
553
+ const launchedByCoordinator = Boolean(
554
+ session?.settings?.launchedByCoordinator === true || session?.meta?.launchedByCoordinator === true || session?.launchedByCoordinator === true
555
+ );
556
+ return !launchedByCoordinator;
557
+ }
558
+ function isWorkerTaskMode(taskMode) {
559
+ return taskMode !== "live_debug_readonly";
560
+ }
371
561
  function addSessionRecord(target, session) {
372
562
  if (!session || typeof session !== "object" || isTerminalSessionRecord(session)) return;
373
563
  const sessionId = readSessionRecordId(session);
@@ -436,18 +626,26 @@ function queueAssignmentStaleReason(task, liveness) {
436
626
  }
437
627
  function buildQueueStatusSummary(queue) {
438
628
  const counts = { pending: 0, assigned: 0, completed: 0, failed: 0, cancelled: 0 };
629
+ let staleAssigned = 0;
439
630
  for (const task of queue) {
440
631
  const status = typeof task?.status === "string" ? task.status : void 0;
441
632
  if (status && Object.prototype.hasOwnProperty.call(counts, status)) {
442
633
  counts[status] += 1;
443
634
  }
635
+ if (status === "assigned" && task?.staleAssigned === true) staleAssigned += 1;
444
636
  }
637
+ const liveAssigned = Math.max(0, counts.assigned - staleAssigned);
445
638
  return {
446
639
  totalCount: queue.length,
447
- activeCount: counts.pending + counts.assigned,
640
+ activeCount: counts.pending + liveAssigned,
448
641
  historicalCount: counts.completed + counts.failed + counts.cancelled,
449
642
  counts,
450
643
  activeCounts: {
644
+ pending: counts.pending,
645
+ assigned: liveAssigned
646
+ },
647
+ staleAssignedCount: staleAssigned,
648
+ rawActiveCounts: {
451
649
  pending: counts.pending,
452
650
  assigned: counts.assigned
453
651
  },
@@ -475,6 +673,18 @@ function filterQueueForView(queue, view, statuses) {
475
673
  if (view === "historical") return queue.filter((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
476
674
  return queue;
477
675
  }
676
+ function prioritizeActiveQueueRows(queue) {
677
+ const active = [];
678
+ const historical = [];
679
+ const other = [];
680
+ for (const task of queue) {
681
+ const status = String(task?.status || "");
682
+ if (ACTIVE_QUEUE_STATUSES.has(status)) active.push(task);
683
+ else if (HISTORICAL_QUEUE_STATUSES.has(status)) historical.push(task);
684
+ else other.push(task);
685
+ }
686
+ return [...active, ...other, ...historical];
687
+ }
478
688
  function slimQueueTask(task) {
479
689
  return {
480
690
  id: task?.id,
@@ -568,6 +778,172 @@ function unwrapCommandPayload(value) {
568
778
  }
569
779
  return current;
570
780
  }
781
+ function isDirectDispatchLedgerEntry(entry) {
782
+ if (entry?.kind !== "task_dispatched") return false;
783
+ const payload = entry.payload || {};
784
+ const via = readString(payload.via);
785
+ return payload.source === "direct" || via === "p2p_direct" || via === "local_direct" || via === "mesh_send_task";
786
+ }
787
+ function readMessageTimestampIso(message) {
788
+ for (const value of [message?.timestamp, message?.createdAt, message?.created_at, message?.updatedAt, message?.time]) {
789
+ if (typeof value === "number" && Number.isFinite(value)) {
790
+ const ms = value > 1e10 ? value : value * 1e3;
791
+ return new Date(ms).toISOString();
792
+ }
793
+ if (typeof value === "string" && value.trim()) {
794
+ const ms = new Date(value.trim()).getTime();
795
+ if (Number.isFinite(ms)) return new Date(ms).toISOString();
796
+ }
797
+ }
798
+ return void 0;
799
+ }
800
+ function readFinalAssistantTranscriptEvidence(payload) {
801
+ const rawMessages = Array.isArray(payload?.messages) ? payload.messages : [];
802
+ const finalAssistant = [...rawMessages].reverse().filter(isCoordinatorVisibleMessage).find((message) => {
803
+ const role = String(message?.role ?? "").toLowerCase();
804
+ return (role === "assistant" || role === "agent") && messageContent(message).trim();
805
+ });
806
+ const finalSummary = messageContent(finalAssistant).trim() || (typeof payload?.summary === "string" && payload.summary.trim() ? payload.summary.trim() : void 0);
807
+ return {
808
+ finalSummary,
809
+ transcriptMessageAt: finalAssistant ? readMessageTimestampIso(finalAssistant) : void 0
810
+ };
811
+ }
812
+ function findNodeSession(nodes, nodeId, sessionId) {
813
+ if (!nodeId || !sessionId) return {};
814
+ const node = nodes.find((candidate) => readString(candidate?.id) === nodeId || readString(candidate?.nodeId) === nodeId);
815
+ if (!node) return {};
816
+ const sessions = Array.isArray(node.sessions) ? node.sessions : [];
817
+ const session = sessions.find((candidate) => readSessionRecordId(candidate) === sessionId);
818
+ return { node, session };
819
+ }
820
+ function buildDirectDispatchReconciliationCandidates(directDispatches, ledgerEntries) {
821
+ const candidates = [];
822
+ const seenTaskIds = /* @__PURE__ */ new Set();
823
+ for (const dispatch of directDispatches || []) {
824
+ const taskId = readString(dispatch?.taskId);
825
+ if (!taskId || seenTaskIds.has(taskId)) continue;
826
+ seenTaskIds.add(taskId);
827
+ candidates.push(dispatch);
828
+ }
829
+ for (const entry of ledgerEntries || []) {
830
+ if (!isDirectDispatchLedgerEntry(entry)) continue;
831
+ const taskId = readString(entry.payload?.taskId);
832
+ if (!taskId || seenTaskIds.has(taskId)) continue;
833
+ seenTaskIds.add(taskId);
834
+ candidates.push({
835
+ taskId,
836
+ nodeId: entry.nodeId,
837
+ sessionId: entry.sessionId,
838
+ providerType: entry.providerType || readString(entry.payload?.providerType),
839
+ message: readString(entry.payload?.message),
840
+ dispatchedAt: entry.timestamp,
841
+ via: readString(entry.payload?.via)
842
+ });
843
+ }
844
+ return candidates;
845
+ }
846
+ async function reconcileDirectDispatchesFromTranscriptEvidence(ctx, liveNodes, directDispatches, ledgerEntries) {
847
+ let attempted = 0;
848
+ let reconciled = 0;
849
+ let skipped = 0;
850
+ const candidates = buildDirectDispatchReconciliationCandidates(directDispatches, ledgerEntries);
851
+ for (const dispatch of candidates) {
852
+ const taskId = readString(dispatch?.taskId);
853
+ const nodeId = readString(dispatch?.nodeId);
854
+ const sessionId = readString(dispatch?.sessionId);
855
+ if (!taskId || !nodeId || !sessionId) {
856
+ skipped += 1;
857
+ continue;
858
+ }
859
+ const { session } = findNodeSession(liveNodes, nodeId, sessionId);
860
+ if (!session || !isIdleSessionRecord(session)) {
861
+ skipped += 1;
862
+ continue;
863
+ }
864
+ const node = await findOptionalNodeWithRefresh(ctx, nodeId).catch(() => null);
865
+ if (!node) {
866
+ skipped += 1;
867
+ continue;
868
+ }
869
+ const providerType = readString(dispatch?.providerType) || resolveSessionProviderType(session);
870
+ const providerSessionId = readString(session?.providerSessionId) || readString(session?.activeChat?.providerSessionId) || readString(session?.settings?.providerSessionId) || resolveMeshSessionProviderMetadata(ctx, nodeId, sessionId)?.providerSessionId;
871
+ attempted += 1;
872
+ try {
873
+ const readResult = await commandForNode(ctx, node, "read_chat", {
874
+ sessionId,
875
+ targetSessionId: sessionId,
876
+ workspace: node.workspace,
877
+ ...providerType ? { agentType: providerType, providerType } : {},
878
+ ...providerSessionId ? { providerSessionId } : {},
879
+ tailLimit: 10
880
+ });
881
+ const payload = unwrapCommandPayload(readResult);
882
+ if (payload?.success === false) continue;
883
+ const evidence = readFinalAssistantTranscriptEvidence(payload);
884
+ if (!evidence.finalSummary) continue;
885
+ const result = (0, import_daemon_core.reconcileDirectDispatchCompletionFromTranscript)({
886
+ meshId: ctx.mesh.id,
887
+ nodeId,
888
+ sessionId,
889
+ providerType,
890
+ providerSessionId: readString(payload?.providerSessionId) || providerSessionId,
891
+ taskId,
892
+ finalSummary: evidence.finalSummary,
893
+ transcriptMessageAt: evidence.transcriptMessageAt,
894
+ targetCoordinatorDaemonId: ctx.localDaemonId,
895
+ source: "mcp_mesh_status_transcript_reconciliation"
896
+ });
897
+ if (result.reconciled) reconciled += 1;
898
+ } catch {
899
+ skipped += 1;
900
+ }
901
+ }
902
+ return { attempted, reconciled, skipped };
903
+ }
904
+ async function triggerMeshQueueAndReport(ctx, node, opts) {
905
+ if (!(isLocalTransport(ctx.transport) || ctx.transport instanceof IpcTransport)) return void 0;
906
+ try {
907
+ let raw;
908
+ if (ctx.transport instanceof IpcTransport && node?.daemonId && opts?.localNode === false) {
909
+ raw = await ctx.transport.meshCommand(node.daemonId, "trigger_mesh_queue", { meshId: ctx.mesh.id });
910
+ } else if (isLocalTransport(ctx.transport)) {
911
+ raw = await ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id });
912
+ } else {
913
+ return void 0;
914
+ }
915
+ const payload = unwrapCommandPayload(raw);
916
+ const trigger = payload?.trigger && typeof payload.trigger === "object" ? payload.trigger : payload;
917
+ return trigger && typeof trigger === "object" ? trigger : { success: true };
918
+ } catch (e) {
919
+ return {
920
+ success: false,
921
+ error: e?.message || String(e)
922
+ };
923
+ }
924
+ }
925
+ function buildQueueTriggerGuidance(queueTrigger) {
926
+ if (!queueTrigger || queueTrigger.claimed === true) return void 0;
927
+ if (queueTrigger.success === false) {
928
+ return {
929
+ queueClaimed: false,
930
+ queueDispatchState: "trigger_failed",
931
+ nextAction: "Do not assume the queued task is running. Check mesh_view_queue and daemon connectivity before redispatching."
932
+ };
933
+ }
934
+ if (queueTrigger.noIdleMeshSessionAvailable === true) {
935
+ return {
936
+ queueClaimed: false,
937
+ queueDispatchState: "pending_no_idle_mesh_session",
938
+ 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."
939
+ };
940
+ }
941
+ return {
942
+ queueClaimed: false,
943
+ queueDispatchState: "pending_or_waiting_for_ready",
944
+ 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."
945
+ };
946
+ }
571
947
  function isTerminalSessionRecord(session) {
572
948
  const status = typeof session?.status === "string" ? session.status.toLowerCase() : "";
573
949
  const lifecycle = typeof session?.lifecycle === "string" ? session.lifecycle.toLowerCase() : "";
@@ -580,13 +956,66 @@ function isIdleSessionRecord(session) {
580
956
  const chatStatus = typeof session?.activeChat?.status === "string" ? session.activeChat.status.toLowerCase() : "";
581
957
  return status === "idle" || chatStatus === "waiting_input";
582
958
  }
959
+ function isMeshOwnedDelegateSession(session, meshId, nodeId) {
960
+ const settings = session?.settings;
961
+ const sessionMeshId = typeof settings?.meshNodeFor === "string" ? settings.meshNodeFor.trim() : "";
962
+ const sessionNodeId = typeof settings?.meshNodeId === "string" ? settings.meshNodeId.trim() : "";
963
+ if (sessionMeshId !== meshId) return false;
964
+ return !sessionNodeId || sessionNodeId === nodeId;
965
+ }
966
+ function hasRemoteRelayMetadata(session) {
967
+ return Boolean(
968
+ readString(session?.settings?.meshCoordinatorDaemonId) || readString(session?.meta?.meshCoordinatorDaemonId) || readString(session?.metadata?.meshCoordinatorDaemonId) || readString(session?.meshCoordinatorDaemonId)
969
+ );
970
+ }
971
+ function isRelaySafeRemoteDelegateSession(session, meshId, nodeId) {
972
+ return isMeshOwnedDelegateSession(session, meshId, nodeId) && hasRemoteRelayMetadata(session);
973
+ }
583
974
  function chooseDispatchableSession(sessions, providerType, meshId, nodeId) {
584
975
  const live = sessions.filter((session) => !isTerminalSessionRecord(session));
585
976
  const matchingProvider = (session) => !providerType || session?.providerType === providerType || session?.cliType === providerType;
586
977
  const meshSessions = live.filter(
587
- (session) => session?.settings?.meshNodeFor === meshId || session?.settings?.meshNodeId === nodeId
978
+ (session) => isRelaySafeRemoteDelegateSession(session, meshId, nodeId)
588
979
  );
589
- return meshSessions.find((session) => isIdleSessionRecord(session) && matchingProvider(session)) || meshSessions.find(matchingProvider) || live.find((session) => isIdleSessionRecord(session) && matchingProvider(session)) || live.find(matchingProvider) || live.find(isIdleSessionRecord) || live[0];
980
+ return meshSessions.find((session) => isIdleSessionRecord(session) && matchingProvider(session)) || meshSessions.find(matchingProvider) || void 0;
981
+ }
982
+ function buildRelayUnsafeRemoteSessionFailure(ctx, node, sessionId, providerType) {
983
+ return {
984
+ success: false,
985
+ recoverable: true,
986
+ code: "mesh_delegate_session_missing_relay_metadata",
987
+ reason: "mesh_delegate_session_missing_relay_metadata",
988
+ transport: "mesh_transport",
989
+ retryRecommended: true,
990
+ meshId: ctx.mesh.id,
991
+ nodeId: node.id,
992
+ daemonId: node.daemonId,
993
+ workspace: node.workspace,
994
+ sessionId,
995
+ unsafeTranscriptAlias: true,
996
+ ...providerType ? { resolvedProviderType: providerType } : {},
997
+ 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).`,
998
+ 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.`,
999
+ noFallbackReason: "Blindly reusing a remote session without mesh relay metadata would silently drop task_completed / generating_completed events."
1000
+ };
1001
+ }
1002
+ function buildMissingCoordinatorDaemonIdFailure(ctx, node, providerType) {
1003
+ return {
1004
+ success: false,
1005
+ recoverable: true,
1006
+ code: "mesh_coordinator_daemon_unknown",
1007
+ reason: "mesh_coordinator_daemon_unknown",
1008
+ transport: "mesh_transport",
1009
+ retryRecommended: true,
1010
+ meshId: ctx.mesh.id,
1011
+ nodeId: node.id,
1012
+ daemonId: node.daemonId,
1013
+ workspace: node.workspace,
1014
+ ...providerType ? { resolvedProviderType: providerType } : {},
1015
+ error: `Cannot launch a remote mesh delegate for node '${node.id}': coordinator daemon identity is unavailable, so the worker would be unable to relay completion events back to the coordinator.`,
1016
+ nextAction: "Retry after the coordinator daemon identity is available (for example from an attached daemon-backed MCP session) so meshCoordinatorDaemonId can be stamped on the worker session.",
1017
+ noFallbackReason: "Launching without meshCoordinatorDaemonId would create a worker session that can finish work but cannot emit task_completed / generating_completed back to the coordinator."
1018
+ };
590
1019
  }
591
1020
  function findNestedPayload(value, predicate) {
592
1021
  const seen = /* @__PURE__ */ new Set();
@@ -615,12 +1044,16 @@ function extractGitDiff(value) {
615
1044
  }
616
1045
  function extractSubmodules(value, ignorePaths) {
617
1046
  const payload = unwrapCommandPayload(value);
618
- const subs = payload?.submodules ?? value?.submodules;
1047
+ const subs = payload?.status?.submodules ?? payload?.submodules ?? value?.status?.submodules ?? value?.submodules;
619
1048
  if (!Array.isArray(subs)) return void 0;
620
1049
  if (ignorePaths.length === 0) return subs;
621
1050
  const ignoreSet = new Set(ignorePaths);
622
1051
  return subs.filter((s) => s?.path && !ignoreSet.has(s.path));
623
1052
  }
1053
+ function assignFullGitSnapshot(entry, status) {
1054
+ if (!status || typeof status !== "object" || Array.isArray(status)) return;
1055
+ entry.git = status;
1056
+ }
624
1057
  function extractLaunchPayload(value) {
625
1058
  return findNestedPayload(value, (payload) => Boolean(payload?.sessionId || payload?.id || payload?.runtimeSessionId));
626
1059
  }
@@ -745,20 +1178,76 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
745
1178
  let sessionId = args.session_id?.trim() || "";
746
1179
  const providerPriorityList = Array.isArray(node.policy?.providerPriority) ? node.policy.providerPriority : [];
747
1180
  let resolvedProviderType = args.providerType?.trim() || providerPriorityList[0] || "";
748
- if (!sessionId) {
1181
+ if (sessionId && args.verifiedSession) {
1182
+ const explicitSession = args.verifiedSession;
1183
+ if (!isRelaySafeRemoteDelegateSession(explicitSession, ctx.mesh.id, node.id)) {
1184
+ return buildRelayUnsafeRemoteSessionFailure(
1185
+ ctx,
1186
+ node,
1187
+ sessionId,
1188
+ resolvedProviderType || resolveSessionProviderType(explicitSession) || void 0
1189
+ );
1190
+ }
1191
+ if (!resolvedProviderType) {
1192
+ resolvedProviderType = resolveSessionProviderType(explicitSession);
1193
+ }
1194
+ } else if (!sessionId || args.session_id) {
749
1195
  try {
750
1196
  const relayResult = await transport.meshCommand(daemonId, "get_status_metadata", {});
751
- const innerResult = relayResult?.result ?? relayResult;
752
- const statusObj = innerResult?.status ?? innerResult;
753
- const sessions = Array.isArray(statusObj?.sessions) ? statusObj.sessions : [];
754
- const targetSession = chooseDispatchableSession(sessions, resolvedProviderType, ctx.mesh.id, node.id);
755
- if (targetSession?.id || targetSession?.sessionId) {
756
- sessionId = targetSession.id || targetSession.sessionId;
1197
+ const sessions = extractStatusMetadataSessions(relayResult);
1198
+ if (sessionId) {
1199
+ const explicitSession = sessions.find((session) => readSessionRecordId(session) === sessionId);
1200
+ if (!explicitSession) {
1201
+ return {
1202
+ success: false,
1203
+ recoverable: true,
1204
+ code: "mesh_target_session_not_found",
1205
+ reason: "mesh_target_session_not_found",
1206
+ transport: "mesh_transport",
1207
+ retryRecommended: true,
1208
+ meshId: ctx.mesh.id,
1209
+ nodeId: node.id,
1210
+ daemonId,
1211
+ workspace: node.workspace,
1212
+ sessionId,
1213
+ ...resolvedProviderType ? { resolvedProviderType } : {},
1214
+ error: `Remote session '${sessionId}' is not present in the live status for node '${node.id}'.`,
1215
+ 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.`
1216
+ };
1217
+ }
1218
+ if (!isRelaySafeRemoteDelegateSession(explicitSession, ctx.mesh.id, node.id)) {
1219
+ return buildRelayUnsafeRemoteSessionFailure(
1220
+ ctx,
1221
+ node,
1222
+ sessionId,
1223
+ resolvedProviderType || resolveSessionProviderType(explicitSession) || void 0
1224
+ );
1225
+ }
757
1226
  if (!resolvedProviderType) {
758
- resolvedProviderType = targetSession.providerType || targetSession.cliType || "";
1227
+ resolvedProviderType = resolveSessionProviderType(explicitSession);
1228
+ }
1229
+ } else {
1230
+ const targetSession = chooseDispatchableSession(sessions, resolvedProviderType, ctx.mesh.id, node.id);
1231
+ if (targetSession?.id || targetSession?.sessionId) {
1232
+ sessionId = targetSession.id || targetSession.sessionId;
1233
+ if (!resolvedProviderType) {
1234
+ resolvedProviderType = resolveSessionProviderType(targetSession);
1235
+ }
759
1236
  }
760
1237
  }
761
1238
  } catch (e) {
1239
+ if (sessionId) {
1240
+ return {
1241
+ ...buildCoordinatorP2pRelayFailure(e, {
1242
+ command: "get_status_metadata",
1243
+ targetDaemonId: daemonId,
1244
+ nodeId: node.id,
1245
+ sessionId
1246
+ }),
1247
+ success: false,
1248
+ error: `Cannot verify remote session '${sessionId}' before dispatch: ${e?.message || String(e)}`
1249
+ };
1250
+ }
762
1251
  }
763
1252
  }
764
1253
  if (!resolvedProviderType) {
@@ -770,7 +1259,8 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
770
1259
  agentType: resolvedProviderType,
771
1260
  cliType: resolvedProviderType,
772
1261
  action: "send_chat",
773
- message: args.message
1262
+ message: args.message,
1263
+ ...args.meshContext ? { meshContext: args.meshContext } : {}
774
1264
  });
775
1265
  const dispatchPayload = unwrapCommandPayload(dispatchResult);
776
1266
  if (dispatchPayload?.success === false || dispatchResult?.success === false) {
@@ -788,7 +1278,7 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
788
1278
  error: `P2P dispatch failed: ${errorMessage}`
789
1279
  };
790
1280
  }
791
- return { success: true, dispatched: true, sessionId: sessionId || resolvedProviderType };
1281
+ return { success: true, dispatched: true, sessionId: sessionId || resolvedProviderType, providerType: resolvedProviderType };
792
1282
  } catch (e) {
793
1283
  const errorMessage = e?.message || String(e);
794
1284
  return {
@@ -818,34 +1308,197 @@ function resolveCoordinatorNode(ctx) {
818
1308
  return void 0;
819
1309
  }
820
1310
  function readNodeMachineId(node) {
821
- return readString(node.machineId) || readString(node.machine_id);
1311
+ 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);
822
1312
  }
823
1313
  function readNodeDaemonId(node) {
824
- return readString(node.daemonId) || readString(node.daemon_id);
1314
+ 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);
1315
+ }
1316
+ function normalizeHostname(value) {
1317
+ const hostname = readString(value);
1318
+ if (!hostname) return void 0;
1319
+ return hostname.toLowerCase().replace(/\.$/, "");
1320
+ }
1321
+ function readNodeHostname(node) {
1322
+ 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);
1323
+ }
1324
+ function readNodeDisplayMachineName(node) {
1325
+ 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);
1326
+ }
1327
+ function compactIdentityEvidence(value) {
1328
+ if (!value) return void 0;
1329
+ return value.length > 24 ? `${value.slice(0, 12)}\u2026${value.slice(-8)}` : value;
1330
+ }
1331
+ function pushIdentityEvidence(evidence, label, value) {
1332
+ const compact = compactIdentityEvidence(value);
1333
+ if (compact) evidence.push(`${label}:${compact}`);
1334
+ }
1335
+ function buildNodeMachineIdentity(ctx, node) {
1336
+ const machineId = readNodeMachineId(node);
1337
+ const daemonId = readNodeDaemonId(node);
1338
+ const hostname = readNodeHostname(node);
1339
+ const machineName = readNodeDisplayMachineName(node);
1340
+ const coordinatorHostname = readString(ctx.coordinatorHostname);
1341
+ const localControlPlaneReason = getLocalControlPlaneMatchReason(ctx, node);
1342
+ const directLocal = !!localControlPlaneReason;
1343
+ const hostnameMatches = Boolean(
1344
+ normalizeHostname(hostname) && normalizeHostname(coordinatorHostname) && normalizeHostname(hostname) === normalizeHostname(coordinatorHostname)
1345
+ );
1346
+ const sameMachine = directLocal || hostnameMatches;
1347
+ const evidence = [];
1348
+ pushIdentityEvidence(evidence, "machineName", machineName);
1349
+ pushIdentityEvidence(evidence, "hostname", hostname);
1350
+ pushIdentityEvidence(evidence, "machineId", machineId);
1351
+ pushIdentityEvidence(evidence, "daemonId", daemonId);
1352
+ if (localControlPlaneReason) {
1353
+ pushIdentityEvidence(evidence, "localMatch", localControlPlaneReason);
1354
+ pushIdentityEvidence(evidence, "localMachineId", ctx.localMachineId);
1355
+ pushIdentityEvidence(evidence, "localDaemonId", ctx.localDaemonId);
1356
+ }
1357
+ const locality = sameMachine ? "same_machine" : evidence.length > 0 ? "remote_known" : "remote_or_unknown";
1358
+ 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";
1359
+ return {
1360
+ daemonId,
1361
+ machineId,
1362
+ hostname,
1363
+ machineName,
1364
+ displayName: machineName || hostname || daemonId || machineId,
1365
+ coordinatorHostname,
1366
+ sameMachine,
1367
+ locality,
1368
+ localityReason,
1369
+ identityEvidence: evidence
1370
+ };
1371
+ }
1372
+ function nodeHasLocalDaemonEvidence(ctx, node) {
1373
+ const isLocal = (session) => {
1374
+ if (!session || typeof session !== "object") return false;
1375
+ if (ctx.localDaemonId && session.runtime?.owner === ctx.localDaemonId) return true;
1376
+ if (ctx.localDaemonId && session.daemonClient?.daemonId === ctx.localDaemonId) return true;
1377
+ return false;
1378
+ };
1379
+ const sessionArrays = [
1380
+ node?.sessions,
1381
+ node?.activeSessions,
1382
+ node?.active_sessions,
1383
+ node?.lastProbe?.sessions,
1384
+ node?.last_probe?.sessions,
1385
+ node?.lastProbe?.status?.sessions,
1386
+ node?.last_probe?.status?.sessions
1387
+ ];
1388
+ for (const arr of sessionArrays) {
1389
+ if (Array.isArray(arr) && arr.some(isLocal)) return true;
1390
+ }
1391
+ const sessionRecords = [
1392
+ node?.activeSession,
1393
+ node?.active_session,
1394
+ node?.currentSession,
1395
+ node?.current_session,
1396
+ node?.runtimeSession,
1397
+ node?.runtime_session,
1398
+ node?.session,
1399
+ node?.lastProbe?.activeSession,
1400
+ node?.last_probe?.active_session,
1401
+ node?.lastProbe?.currentSession,
1402
+ node?.last_probe?.current_session,
1403
+ node?.lastProbe?.session,
1404
+ node?.last_probe?.session
1405
+ ];
1406
+ for (const session of sessionRecords) {
1407
+ if (isLocal(session)) return true;
1408
+ }
1409
+ return false;
825
1410
  }
826
1411
  function isDirectLocalNode(ctx, node) {
827
1412
  const machineId = readNodeMachineId(node);
828
1413
  const daemonId = readNodeDaemonId(node);
829
1414
  return Boolean(
830
- ctx.localMachineId && machineId === ctx.localMachineId || ctx.localDaemonId && daemonId === ctx.localDaemonId
1415
+ ctx.localMachineId && machineId === ctx.localMachineId || ctx.localDaemonId && daemonId === ctx.localDaemonId || nodeHasLocalDaemonEvidence(ctx, node)
831
1416
  );
832
1417
  }
1418
+ function isConfiguredCoordinatorNode(ctx, node) {
1419
+ if (!ctx.localMachineId && !ctx.localDaemonId) return false;
1420
+ const nodeId = readString(node.id) || readString(node.nodeId) || readString(node.node_id);
1421
+ if (!nodeId) return false;
1422
+ const nodeDaemonId = readNodeDaemonId(node);
1423
+ const nodeMachineId = readNodeMachineId(node);
1424
+ if (nodeDaemonId && ctx.localDaemonId && nodeDaemonId !== ctx.localDaemonId) return false;
1425
+ if (nodeMachineId && ctx.localMachineId && nodeMachineId !== ctx.localMachineId) return false;
1426
+ const preferredNodeId = readString(ctx.mesh.coordinator?.preferredNodeId) || readString(ctx.mesh.coordinator?.preferred_node_id);
1427
+ if (preferredNodeId) return nodeId === preferredNodeId;
1428
+ const first = ctx.mesh.nodes?.[0];
1429
+ const firstNodeId = readString(first?.id) || readString(first?.nodeId) || readString(first?.node_id);
1430
+ return !!firstNodeId && nodeId === firstNodeId;
1431
+ }
1432
+ function getLocalControlPlaneMatchReason(ctx, node) {
1433
+ if (isDirectLocalNode(ctx, node)) return "matched coordinator daemon or machine id";
1434
+ if (isConfiguredCoordinatorNode(ctx, node)) return "matched configured coordinator node";
1435
+ if (node.isLocalWorktree === true) {
1436
+ const sourceNode = findClonedFromNode(ctx, node);
1437
+ if (sourceNode && isDirectLocalNode(ctx, sourceNode)) return "matched local cloned-from node";
1438
+ if (sourceNode && isConfiguredCoordinatorNode(ctx, sourceNode)) return "matched configured coordinator source node";
1439
+ }
1440
+ return void 0;
1441
+ }
833
1442
  function findClonedFromNode(ctx, node) {
834
1443
  const clonedFromNodeId = readString(node.clonedFromNodeId) || readString(node.cloned_from_node_id);
835
1444
  if (!clonedFromNodeId) return void 0;
836
1445
  return ctx.mesh.nodes.find((n) => n.id === clonedFromNodeId || n.nodeId === clonedFromNodeId || n.node_id === clonedFromNodeId);
837
1446
  }
838
1447
  function isLocalControlPlaneNode(ctx, node) {
839
- if (isDirectLocalNode(ctx, node)) return true;
840
- if (node.isLocalWorktree === true) {
841
- const sourceNode = findClonedFromNode(ctx, node);
842
- if (sourceNode && isDirectLocalNode(ctx, sourceNode)) return true;
843
- }
844
- return false;
1448
+ return !!getLocalControlPlaneMatchReason(ctx, node);
845
1449
  }
846
1450
  function meshSessionCacheKey(nodeId, runtimeSessionId) {
847
1451
  return `${nodeId}:${runtimeSessionId}`;
848
1452
  }
1453
+ function rememberMeshSessionProviderMetadata(nodeId, runtimeSessionId, metadata) {
1454
+ const keyNodeId = readString(nodeId);
1455
+ const keySessionId = readString(runtimeSessionId);
1456
+ if (!keyNodeId || !keySessionId) return;
1457
+ const providerType = readString(metadata.providerType);
1458
+ const providerSessionId = readString(metadata.providerSessionId);
1459
+ if (!providerType && !providerSessionId) return;
1460
+ const existing = getSessionMetadata(meshSessionCacheKey(keyNodeId, keySessionId)) || { providerType: "" };
1461
+ meshSessionProviderMetadata.set(meshSessionCacheKey(keyNodeId, keySessionId), {
1462
+ providerType: providerType || existing.providerType,
1463
+ providerSessionId: providerSessionId || existing.providerSessionId,
1464
+ expiresAt: Date.now() + SESSION_PROVIDER_METADATA_TTL_MS
1465
+ });
1466
+ }
1467
+ function rememberMeshSessionProviderMetadataFromEvent(event) {
1468
+ const metadataEvent = event?.metadataEvent && typeof event.metadataEvent === "object" ? event.metadataEvent : event && typeof event === "object" ? event : {};
1469
+ const nodeId = readString(event?.nodeId) || readString(metadataEvent.nodeId) || readString(metadataEvent.meshNodeId);
1470
+ const sessionId = readString(metadataEvent.targetSessionId) || readString(metadataEvent.sessionId) || readString(metadataEvent.instanceId) || readString(event?.sessionId);
1471
+ rememberMeshSessionProviderMetadata(nodeId, sessionId, {
1472
+ providerType: readString(metadataEvent.providerType) || readString(event?.providerType) || "",
1473
+ providerSessionId: readString(metadataEvent.providerSessionId) || readString(event?.providerSessionId)
1474
+ });
1475
+ }
1476
+ function resolveMeshSessionProviderMetadataFromLedger(ctx, nodeId, runtimeSessionId) {
1477
+ const entries = (0, import_daemon_core.readLedgerEntries)(ctx.mesh.id, { tail: 50 });
1478
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
1479
+ const entry = entries[i];
1480
+ const payload = entry.payload && typeof entry.payload === "object" && !Array.isArray(entry.payload) ? entry.payload : {};
1481
+ const entryNodeId = readString(entry.nodeId) || readString(payload.nodeId) || readString(payload.meshNodeId);
1482
+ if (entryNodeId && entryNodeId !== nodeId) continue;
1483
+ const entrySessionId = readString(entry.sessionId) || readString(payload.targetSessionId) || readString(payload.sessionId) || readString(payload.instanceId);
1484
+ if (entrySessionId !== runtimeSessionId) continue;
1485
+ const providerType = readString(entry.providerType) || readString(payload.providerType);
1486
+ const completionDiagnostic = payload.completionDiagnostic && typeof payload.completionDiagnostic === "object" && !Array.isArray(payload.completionDiagnostic) ? payload.completionDiagnostic : {};
1487
+ const metadataEvent = payload.metadataEvent && typeof payload.metadataEvent === "object" && !Array.isArray(payload.metadataEvent) ? payload.metadataEvent : {};
1488
+ const providerSessionId = readString(payload.providerSessionId) || readString(completionDiagnostic.providerSessionId) || readString(metadataEvent.providerSessionId);
1489
+ if (providerType || providerSessionId) {
1490
+ return { providerType: providerType || "", providerSessionId };
1491
+ }
1492
+ }
1493
+ return void 0;
1494
+ }
1495
+ function resolveMeshSessionProviderMetadata(ctx, nodeId, runtimeSessionId) {
1496
+ const cached = getSessionMetadata(meshSessionCacheKey(nodeId, runtimeSessionId));
1497
+ if (cached?.providerType || cached?.providerSessionId) return cached;
1498
+ const fromLedger = resolveMeshSessionProviderMetadataFromLedger(ctx, nodeId, runtimeSessionId);
1499
+ if (fromLedger) rememberMeshSessionProviderMetadata(nodeId, runtimeSessionId, fromLedger);
1500
+ return fromLedger;
1501
+ }
849
1502
  function countUncommittedChanges(status) {
850
1503
  if (typeof status?.uncommittedChanges === "number") return status.uncommittedChanges;
851
1504
  const keys = ["staged", "modified", "untracked", "deleted", "renamed"];
@@ -856,8 +1509,23 @@ function countUncommittedChanges(status) {
856
1509
  function isGitStatusDirty(status) {
857
1510
  if (typeof status?.isDirty === "boolean") return status.isDirty;
858
1511
  if (typeof status?.dirty === "boolean") return status.dirty;
1512
+ if (Array.isArray(status?.submodules) && status.submodules.some((submodule) => submodule?.dirty || submodule?.outOfSync || submodule?.error)) return true;
859
1513
  return countUncommittedChanges(status) > 0;
860
1514
  }
1515
+ function slimLedgerPayload(payload) {
1516
+ const slim = {};
1517
+ for (const [k, v] of Object.entries(payload)) {
1518
+ if (k === "message" || k === "taskSummary") {
1519
+ slim[k] = typeof v === "string" && v.length > 200 ? v.slice(0, 200) + "\u2026" : v;
1520
+ } else if (k === "evidence" || k === "workerResult" || k === "gitStatus" || k === "validationResults") {
1521
+ } else if (k === "finalSummary") {
1522
+ slim[k] = typeof v === "string" && v.length > 300 ? v.slice(0, 300) + "\u2026" : v;
1523
+ } else {
1524
+ slim[k] = v;
1525
+ }
1526
+ }
1527
+ return slim;
1528
+ }
861
1529
  function readRelatedRepos(node) {
862
1530
  const raw = Array.isArray(node.relatedRepos) ? node.relatedRepos : Array.isArray(node.policy?.relatedRepos) ? node.policy.relatedRepos : [];
863
1531
  return raw.map((entry) => ({
@@ -872,6 +1540,10 @@ function summarizeRelatedRepoStatus(repo, status) {
872
1540
  workspace: repo.workspace,
873
1541
  isGitRepo: status?.isGitRepo === true,
874
1542
  branch: status?.branch ?? null,
1543
+ upstream: status?.upstream ?? null,
1544
+ upstreamStatus: typeof status?.upstreamStatus === "string" ? status.upstreamStatus : status?.upstream ? "unchecked" : "no_upstream",
1545
+ upstreamFetchedAt: Number.isFinite(Number(status?.upstreamFetchedAt)) ? Number(status.upstreamFetchedAt) : null,
1546
+ upstreamFetchError: typeof status?.upstreamFetchError === "string" ? status.upstreamFetchError : null,
875
1547
  ahead: Number.isFinite(Number(status?.ahead)) ? Number(status.ahead) : 0,
876
1548
  behind: Number.isFinite(Number(status?.behind)) ? Number(status.behind) : 0,
877
1549
  dirty,
@@ -888,7 +1560,7 @@ async function collectRelatedRepoStatuses(ctx, node) {
888
1560
  const results = [];
889
1561
  for (const repo of relatedRepos) {
890
1562
  try {
891
- const statusResult = !isLocalTransport(ctx.transport) && node.daemonId ? await ctx.transport.gitStatus(node.daemonId, repo.workspace, false) : await commandForNode(ctx, node, "git_status", { workspace: repo.workspace });
1563
+ const statusResult = !isLocalTransport(ctx.transport) && node.daemonId ? await ctx.transport.gitStatus(node.daemonId, repo.workspace, false, true) : await commandForNode(ctx, node, "git_status", { workspace: repo.workspace, refreshUpstream: true });
892
1564
  const status = extractGitStatus(statusResult);
893
1565
  results.push(summarizeRelatedRepoStatus(repo, status));
894
1566
  } catch (e) {
@@ -912,6 +1584,16 @@ function missingProviderPriorityMessage(nodeId) {
912
1584
  return `Node '${nodeId}' has no providerPriority policy; pass type explicitly or configure node.policy.providerPriority`;
913
1585
  }
914
1586
  function getNodeLaunchReadiness(node) {
1587
+ const bootstrap = node.worktreeBootstrap;
1588
+ if (node.isLocalWorktree && bootstrap?.status === "failed" && bootstrap?.required !== false) {
1589
+ return {
1590
+ providerPriority: readProviderPriority(node.policy),
1591
+ launchReady: false,
1592
+ launchBlockedReason: "worktree_bootstrap_failed",
1593
+ launchBlockedMessage: typeof bootstrap.error === "string" && bootstrap.error.trim() ? bootstrap.error.trim() : "Required worktree bootstrap failed; resolve it before launching an agent into this node.",
1594
+ worktreeBootstrap: bootstrap
1595
+ };
1596
+ }
915
1597
  const providerPriority = readProviderPriority(node.policy);
916
1598
  if (providerPriority.length) {
917
1599
  return {
@@ -926,6 +1608,33 @@ function getNodeLaunchReadiness(node) {
926
1608
  launchBlockedMessage: missingProviderPriorityMessage(node.id)
927
1609
  };
928
1610
  }
1611
+ function getWorktreeBootstrapLaunchBlock(node) {
1612
+ const bootstrap = node.worktreeBootstrap;
1613
+ if (!node.isLocalWorktree || bootstrap?.status !== "failed" || bootstrap?.required === false) return void 0;
1614
+ return {
1615
+ success: false,
1616
+ code: "worktree_bootstrap_failed",
1617
+ error: typeof bootstrap.error === "string" && bootstrap.error.trim() ? bootstrap.error.trim() : `Node '${node.id}' has a failed required worktree bootstrap.`,
1618
+ nodeId: node.id,
1619
+ worktreeBootstrap: bootstrap,
1620
+ recoveryHint: "Fix the configured worktree bootstrap command or remove/recreate the worktree node before launching an agent."
1621
+ };
1622
+ }
1623
+ async function collectLiveStatusSessions(ctx, node) {
1624
+ try {
1625
+ const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
1626
+ return extractStatusMetadataSessions(statusResult);
1627
+ } catch {
1628
+ return [];
1629
+ }
1630
+ }
1631
+ async function collectMeshViewQueueNodesWithLiveSessions(ctx) {
1632
+ const nodes = await Promise.all(ctx.mesh.nodes.map(async (node) => {
1633
+ const liveSessions = await collectLiveStatusSessions(ctx, node);
1634
+ return liveSessions.length > 0 ? { ...node, sessions: liveSessions } : node;
1635
+ }));
1636
+ return nodes;
1637
+ }
929
1638
  function readNumeric(value, fallback = 0) {
930
1639
  const parsed = Number(value);
931
1640
  return Number.isFinite(parsed) ? parsed : fallback;
@@ -936,11 +1645,13 @@ function buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges) {
936
1645
  const ahead = readNumeric(status?.ahead);
937
1646
  const behind = readNumeric(status?.behind);
938
1647
  const upstream = readString(status?.upstream) ?? null;
1648
+ const upstreamStatus = readString(status?.upstreamStatus) ?? (upstream ? "unchecked" : "no_upstream");
939
1649
  const hasConflicts = status?.hasConflicts === true || Array.isArray(status?.conflictFiles) && status.conflictFiles.length > 0;
940
1650
  const base = {
941
1651
  defaultBranch,
942
1652
  branch,
943
1653
  upstream,
1654
+ upstreamStatus,
944
1655
  ahead,
945
1656
  behind,
946
1657
  isWorktree: node.isLocalWorktree === true,
@@ -974,6 +1685,15 @@ function buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges) {
974
1685
  };
975
1686
  }
976
1687
  if (branch === defaultBranch) {
1688
+ if (upstream && upstreamStatus !== "fresh") {
1689
+ return {
1690
+ ...base,
1691
+ status: "blocked_review",
1692
+ needsConvergence: true,
1693
+ reason: "default_branch_upstream_unverified",
1694
+ nextStep: `Refresh ${defaultBranch}'s upstream refs or resolve the fetch failure before declaring convergence complete for node '${node.id}'.`
1695
+ };
1696
+ }
977
1697
  if (ahead > 0 || behind > 0) {
978
1698
  return {
979
1699
  ...base,
@@ -1000,6 +1720,15 @@ function buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges) {
1000
1720
  nextStep: `Run mesh_refine_node(node_id: "${node.id}") or explicitly classify this worktree as blocked_review/not_mergeable before ending the task.`
1001
1721
  };
1002
1722
  }
1723
+ if (upstream && upstreamStatus !== "fresh") {
1724
+ return {
1725
+ ...base,
1726
+ status: "blocked_review",
1727
+ needsConvergence: true,
1728
+ reason: "feature_branch_upstream_unverified",
1729
+ nextStep: `Refresh branch '${branch}' upstream refs or resolve the fetch failure before deciding whether it is ready to merge into ${defaultBranch}.`
1730
+ };
1731
+ }
1003
1732
  if (!upstream || ahead > 0 || behind > 0) {
1004
1733
  return {
1005
1734
  ...base,
@@ -1041,7 +1770,91 @@ async function commandForNode(ctx, node, command, args = {}) {
1041
1770
  if (isLocalTransport(ctx.transport)) {
1042
1771
  return ctx.transport.command(command, args);
1043
1772
  }
1044
- throw new Error(`Command '${command}' requires daemon IPC/local transport for node '${node.id}'`);
1773
+ const identity = buildNodeMachineIdentity(ctx, node);
1774
+ 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})`);
1775
+ }
1776
+ function normalizePendingMeshCoordinatorEvents(value) {
1777
+ const payload = unwrapCommandPayload(value);
1778
+ const events = Array.isArray(payload?.events) ? payload.events : Array.isArray(value?.events) ? value.events : [];
1779
+ return events.filter((event) => event && typeof event === "object");
1780
+ }
1781
+ function buildMeshForwardPayloadFromPendingEvent(event) {
1782
+ const metadataEvent = event?.metadataEvent && typeof event.metadataEvent === "object" ? event.metadataEvent : {};
1783
+ return {
1784
+ event: readString(event?.event),
1785
+ meshId: readString(event?.meshId),
1786
+ nodeId: readString(event?.nodeId) || readString(metadataEvent.meshNodeId),
1787
+ workspace: readString(event?.workspace) || readString(metadataEvent.workspace),
1788
+ targetSessionId: readString(metadataEvent.targetSessionId) || readString(metadataEvent.sessionId) || readString(metadataEvent.instanceId),
1789
+ providerType: readString(metadataEvent.providerType),
1790
+ providerSessionId: readString(metadataEvent.providerSessionId),
1791
+ finalSummary: readString(metadataEvent.finalSummary) || readString(metadataEvent.summary),
1792
+ jobId: readString(metadataEvent.jobId),
1793
+ interactionId: readString(metadataEvent.interactionId),
1794
+ status: readString(metadataEvent.status),
1795
+ targetDaemonId: readString(metadataEvent.targetDaemonId),
1796
+ startedAt: readString(metadataEvent.startedAt),
1797
+ completedAt: readString(metadataEvent.completedAt),
1798
+ retryOfJobId: readString(metadataEvent.retryOfJobId),
1799
+ ...metadataEvent.result && typeof metadataEvent.result === "object" && !Array.isArray(metadataEvent.result) ? { result: metadataEvent.result } : {},
1800
+ ...metadataEvent.intentional === true ? { intentional: true } : {},
1801
+ ...metadataEvent.intentionalStop === true ? { intentionalStop: true } : {},
1802
+ ...metadataEvent.operatorCleanup === true ? { operatorCleanup: true } : {},
1803
+ ...readString(metadataEvent.reason) ? { reason: readString(metadataEvent.reason) } : {},
1804
+ ...readString(metadataEvent.stopReason) ? { stopReason: readString(metadataEvent.stopReason) } : {},
1805
+ ...readString(metadataEvent.cleanupReason) ? { cleanupReason: readString(metadataEvent.cleanupReason) } : {},
1806
+ ...readString(metadataEvent.source) ? { source: readString(metadataEvent.source) } : {}
1807
+ };
1808
+ }
1809
+ async function drainCoordinatorPendingEvents(ctx, opts) {
1810
+ const requestedNodeIds = opts?.nodeIds?.length ? new Set(opts.nodeIds) : null;
1811
+ const matchesCurrentMesh = (event) => readString(event?.meshId) === ctx.mesh.id;
1812
+ if (ctx.transport instanceof IpcTransport) {
1813
+ const surfacedEvents = [];
1814
+ const coordinatorDaemonId = readString(ctx.localDaemonId);
1815
+ const pendingEventArgs = {
1816
+ meshId: ctx.mesh.id,
1817
+ ...coordinatorDaemonId ? { coordinatorDaemonId } : {}
1818
+ };
1819
+ try {
1820
+ surfacedEvents.push(
1821
+ ...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", pendingEventArgs)).filter(matchesCurrentMesh)
1822
+ );
1823
+ surfacedEvents.forEach(rememberMeshSessionProviderMetadataFromEvent);
1824
+ } catch {
1825
+ }
1826
+ for (const node of ctx.mesh.nodes) {
1827
+ if (!node.daemonId || isLocalControlPlaneNode(ctx, node)) continue;
1828
+ if (requestedNodeIds && !requestedNodeIds.has(node.id)) continue;
1829
+ try {
1830
+ const remoteEvents = normalizePendingMeshCoordinatorEvents(
1831
+ await ctx.transport.meshCommand(node.daemonId, "get_pending_mesh_events", pendingEventArgs)
1832
+ ).filter(matchesCurrentMesh);
1833
+ if (remoteEvents.length === 0) continue;
1834
+ for (const event of remoteEvents) {
1835
+ const payload = buildMeshForwardPayloadFromPendingEvent(event);
1836
+ if (!payload.event || !payload.meshId) continue;
1837
+ await ctx.transport.command("mesh_forward_event", payload);
1838
+ rememberMeshSessionProviderMetadataFromEvent({ ...event, metadataEvent: payload });
1839
+ }
1840
+ } catch {
1841
+ }
1842
+ }
1843
+ try {
1844
+ surfacedEvents.push(
1845
+ ...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", pendingEventArgs)).filter(matchesCurrentMesh)
1846
+ );
1847
+ surfacedEvents.forEach(rememberMeshSessionProviderMetadataFromEvent);
1848
+ } catch {
1849
+ }
1850
+ return surfacedEvents;
1851
+ }
1852
+ if (isLocalTransport(ctx.transport)) {
1853
+ const events = (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id, ctx.localDaemonId).filter(matchesCurrentMesh);
1854
+ events.forEach(rememberMeshSessionProviderMetadataFromEvent);
1855
+ return events;
1856
+ }
1857
+ return [];
1045
1858
  }
1046
1859
  function isP2pTransportUnavailableError(error) {
1047
1860
  return (0, import_daemon_core.isP2pRelayTransportFailure)(error);
@@ -1056,11 +1869,12 @@ function buildRemoveNodeArgs(ctx, nodeId, sessionCleanupMode) {
1056
1869
  }
1057
1870
  var MESH_STATUS_TOOL = {
1058
1871
  name: "mesh_status",
1059
- 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.",
1872
+ 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.",
1060
1873
  inputSchema: {
1061
1874
  type: "object",
1062
1875
  properties: {
1063
- _gemini_compat: { type: "string", description: "Dummy property for Gemini compatibility. Ignore this." }
1876
+ _gemini_compat: { type: "string", description: "Dummy property for Gemini compatibility. Ignore this." },
1877
+ includeStaleDirectWorkDetails: { type: "boolean", description: "Opt in to the full staleDirectWork array. Defaults false; normal status returns compact staleDirectWorkSummary only." }
1064
1878
  }
1065
1879
  }
1066
1880
  };
@@ -1080,14 +1894,18 @@ var MESH_ENQUEUE_TASK_TOOL = {
1080
1894
  inputSchema: {
1081
1895
  type: "object",
1082
1896
  properties: {
1083
- message: { type: "string", description: "The task instruction for the agent." }
1897
+ message: { type: "string", description: "The task instruction for the agent." },
1898
+ 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." },
1899
+ taskMode: { type: "string", enum: ["code_change", "validation", "live_debug_readonly", "launch_app", "convergence"], description: "CamelCase alias for task_mode." },
1900
+ requiredTags: { type: "array", items: { type: "string" }, description: "Optional capability tags that every eligible node must have, e.g. os=darwin, provider=codex-cli, gpu." },
1901
+ required_tags: { type: "array", items: { type: "string" }, description: "Snake_case alias for requiredTags." }
1084
1902
  },
1085
1903
  required: ["message"]
1086
1904
  }
1087
1905
  };
1088
1906
  var MESH_VIEW_QUEUE_TOOL = {
1089
1907
  name: "mesh_view_queue",
1090
- description: "View the mesh work queue with source-of-truth active counts separated from historical completed/failed/cancelled records.",
1908
+ 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.",
1091
1909
  inputSchema: {
1092
1910
  type: "object",
1093
1911
  properties: {
@@ -1140,7 +1958,9 @@ var MESH_SEND_TASK_TOOL = {
1140
1958
  properties: {
1141
1959
  node_id: { type: "string", description: "Target node ID (from mesh_list_nodes)." },
1142
1960
  session_id: { type: "string", description: "Agent session ID on the target node." },
1143
- message: { type: "string", description: "Natural-language task to send to the agent." }
1961
+ message: { type: "string", description: "Natural-language task to send to the agent." },
1962
+ 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." },
1963
+ taskMode: { type: "string", enum: ["code_change", "validation", "live_debug_readonly", "launch_app", "convergence"], description: "CamelCase alias for task_mode." }
1144
1964
  },
1145
1965
  required: ["node_id", "session_id", "message"]
1146
1966
  }
@@ -1198,6 +2018,21 @@ var MESH_GIT_STATUS_TOOL = {
1198
2018
  required: ["node_id"]
1199
2019
  }
1200
2020
  };
2021
+ var MESH_FAST_FORWARD_NODE_TOOL = {
2022
+ name: "mesh_fast_forward_node",
2023
+ 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.",
2024
+ inputSchema: {
2025
+ type: "object",
2026
+ properties: {
2027
+ node_id: { type: "string", description: "Target node ID." },
2028
+ branch: { type: "string", description: "Optional guard: require the node's current branch to match this branch before planning/executing." },
2029
+ execute: { type: "boolean", description: "When true, apply the fast-forward if all safety gates pass. Defaults false/dry-run." },
2030
+ dry_run: { type: "boolean", description: "Preview only. Defaults true unless execute=true; dry_run=true overrides execute." },
2031
+ 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." }
2032
+ },
2033
+ required: ["node_id"]
2034
+ }
2035
+ };
1201
2036
  var MESH_CHECKPOINT_TOOL = {
1202
2037
  name: "mesh_checkpoint",
1203
2038
  description: "Create a git checkpoint (commit) on a mesh node workspace.",
@@ -1274,38 +2109,75 @@ var MESH_CLEANUP_SESSIONS_TOOL = {
1274
2109
  required: ["node_id", "mode"]
1275
2110
  }
1276
2111
  };
1277
- var MESH_TASK_HISTORY_TOOL = {
1278
- name: "mesh_task_history",
1279
- description: "Read the task ledger for this mesh \u2014 dispatched tasks, completions, failures, checkpoints, and node lifecycle events. Use to understand what has been done before deciding next steps, to detect repeated failures, and to inform recovery decisions.",
2112
+ var MESH_TASK_HISTORY_TOOL = {
2113
+ name: "mesh_task_history",
2114
+ description: "Read the task ledger for this mesh \u2014 dispatched tasks, completions, failures, checkpoints, and node lifecycle events. Use to understand what has been done before deciding next steps, to detect repeated failures, and to inform recovery decisions.",
2115
+ inputSchema: {
2116
+ type: "object",
2117
+ properties: {
2118
+ tail: { type: "number", description: "Number of recent entries to return (default: 20)." },
2119
+ 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." }
2120
+ }
2121
+ }
2122
+ };
2123
+ var MESH_RECONCILE_LEDGER_TOOL = {
2124
+ name: "mesh_reconcile_ledger",
2125
+ description: "Reconcile daemon-local mesh ledgers by querying bounded ledger slices over P2P/DataChannel and importing missing entries into the coordinator local JSONL ledger. Cloud/D1 is not used as a ledger source of truth.",
2126
+ inputSchema: {
2127
+ type: "object",
2128
+ properties: {
2129
+ node_ids: { type: "array", items: { type: "string" }, description: "Optional node IDs to query. Defaults to all mesh nodes." },
2130
+ limit: { type: "number", description: "Bounded slice size per node. Defaults to 100 and is clamped by daemon-core." },
2131
+ after_id: { type: "string", description: "Optional cursor entry ID; remote slices return entries strictly after this ID when present." },
2132
+ since: { type: "string", description: "Optional ISO timestamp lower bound for queried entries." },
2133
+ import_entries: { type: "boolean", description: "When false, query and report evidence without importing remote entries. Defaults true." }
2134
+ }
2135
+ }
2136
+ };
2137
+ var MESH_REFINE_NODE_TOOL = {
2138
+ name: "mesh_refine_node",
2139
+ 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.",
2140
+ inputSchema: {
2141
+ type: "object",
2142
+ properties: {
2143
+ node_id: { type: "string", description: "Node ID of the completed worktree node to refine and merge." }
2144
+ },
2145
+ required: ["node_id"]
2146
+ }
2147
+ };
2148
+ var MESH_REFINE_CONFIG_SCHEMA_TOOL = {
2149
+ name: "mesh_refine_config_schema",
2150
+ 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.",
2151
+ inputSchema: { type: "object", properties: {} }
2152
+ };
2153
+ var MESH_VALIDATE_REFINE_CONFIG_TOOL = {
2154
+ name: "mesh_validate_refine_config",
2155
+ description: "Validate the repo mesh/refine config for a node/workspace without running validation commands or merging.",
1280
2156
  inputSchema: {
1281
2157
  type: "object",
1282
2158
  properties: {
1283
- tail: { type: "number", description: "Number of recent entries to return (default: 20)." },
1284
- kind: { type: "string", description: "Filter by entry kind: task_dispatched, task_completed, task_failed, task_stalled, session_launched, checkpoint_created, node_cloned, node_removed." }
2159
+ node_id: { type: "string", description: "Optional node/workspace whose refine config should be loaded. Defaults to the first mesh node." },
2160
+ config: { type: "object", description: "Optional inline config object to validate instead of loading from the repo." }
1285
2161
  }
1286
2162
  }
1287
2163
  };
1288
- var MESH_RECONCILE_LEDGER_TOOL = {
1289
- name: "mesh_reconcile_ledger",
1290
- description: "Reconcile daemon-local mesh ledgers by querying bounded ledger slices over P2P/DataChannel and importing missing entries into the coordinator local JSONL ledger. Cloud/D1 is not used as a ledger source of truth.",
2164
+ var MESH_SUGGEST_REFINE_CONFIG_TOOL = {
2165
+ name: "mesh_suggest_refine_config",
2166
+ description: "Suggest a repo mesh/refine config scaffold from project context/package scripts. Suggestions are never executed until saved as explicit refine config.",
1291
2167
  inputSchema: {
1292
2168
  type: "object",
1293
2169
  properties: {
1294
- node_ids: { type: "array", items: { type: "string" }, description: "Optional node IDs to query. Defaults to all mesh nodes." },
1295
- limit: { type: "number", description: "Bounded slice size per node. Defaults to 100 and is clamped by daemon-core." },
1296
- after_id: { type: "string", description: "Optional cursor entry ID; remote slices return entries strictly after this ID when present." },
1297
- since: { type: "string", description: "Optional ISO timestamp lower bound for queried entries." },
1298
- import_entries: { type: "boolean", description: "When false, query and report evidence without importing remote entries. Defaults true." }
2170
+ node_id: { type: "string", description: "Optional node/workspace used for suggestions. Defaults to the first mesh node." }
1299
2171
  }
1300
2172
  }
1301
2173
  };
1302
- var MESH_REFINE_NODE_TOOL = {
1303
- name: "mesh_refine_node",
1304
- 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.",
2174
+ var MESH_REFINE_PLAN_TOOL = {
2175
+ name: "mesh_refine_plan",
2176
+ 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.",
1305
2177
  inputSchema: {
1306
2178
  type: "object",
1307
2179
  properties: {
1308
- node_id: { type: "string", description: "Node ID of the completed worktree node to refine and merge." }
2180
+ node_id: { type: "string", description: "Node ID of the worktree node to plan." }
1309
2181
  },
1310
2182
  required: ["node_id"]
1311
2183
  }
@@ -1322,33 +2194,41 @@ var ALL_MESH_TOOLS = [
1322
2194
  MESH_READ_DEBUG_TOOL,
1323
2195
  MESH_LAUNCH_SESSION_TOOL,
1324
2196
  MESH_GIT_STATUS_TOOL,
2197
+ MESH_FAST_FORWARD_NODE_TOOL,
1325
2198
  MESH_CHECKPOINT_TOOL,
1326
2199
  MESH_APPROVE_TOOL,
1327
2200
  MESH_CLONE_NODE_TOOL,
1328
2201
  MESH_REMOVE_NODE_TOOL,
1329
2202
  MESH_REFINE_NODE_TOOL,
2203
+ MESH_REFINE_CONFIG_SCHEMA_TOOL,
2204
+ MESH_VALIDATE_REFINE_CONFIG_TOOL,
2205
+ MESH_SUGGEST_REFINE_CONFIG_TOOL,
2206
+ MESH_REFINE_PLAN_TOOL,
1330
2207
  MESH_CLEANUP_SESSIONS_TOOL,
1331
2208
  MESH_TASK_HISTORY_TOOL,
1332
2209
  MESH_RECONCILE_LEDGER_TOOL
1333
2210
  ];
1334
- async function meshStatus(ctx) {
2211
+ async function meshStatus(ctx, args = {}) {
1335
2212
  await refreshMeshFromDaemon(ctx);
1336
2213
  const { mesh, transport } = ctx;
1337
- const results = [];
1338
- const ledgerSummary = (0, import_daemon_core.getLedgerSummary)(mesh.id);
1339
- for (const node of mesh.nodes) {
2214
+ let ledgerSummary = (0, import_daemon_core.getLedgerSummary)(mesh.id);
2215
+ const results = await Promise.all(mesh.nodes.map(async (node) => {
1340
2216
  const entry = {
1341
2217
  nodeId: node.id,
1342
2218
  workspace: node.workspace,
2219
+ machine: buildNodeMachineIdentity(ctx, node),
2220
+ daemonId: readNodeDaemonId(node),
2221
+ machineId: readNodeMachineId(node),
1343
2222
  ...getNodeLaunchReadiness(node)
1344
2223
  };
1345
2224
  try {
1346
2225
  if (!isLocalTransport(transport) && node.daemonId) {
1347
- const result = await transport.gitStatus(node.daemonId, node.workspace, false);
2226
+ const result = await transport.gitStatus(node.daemonId, node.workspace, false, true);
1348
2227
  const status = extractGitStatus(result);
1349
2228
  const uncommittedChanges = countUncommittedChanges(status);
1350
2229
  const dirty = isGitStatusDirty(status);
1351
2230
  entry.health = status?.isGitRepo ? dirty ? "dirty" : "online" : "degraded";
2231
+ assignFullGitSnapshot(entry, status);
1352
2232
  entry.branch = status?.branch;
1353
2233
  entry.isDirty = dirty;
1354
2234
  entry.uncommittedChanges = uncommittedChanges;
@@ -1362,6 +2242,7 @@ async function meshStatus(ctx) {
1362
2242
  const autoDiscover = node.policy?.autoDiscoverSubmodules !== false;
1363
2243
  const statusResult = await commandForNode(ctx, node, "git_status", {
1364
2244
  workspace: node.workspace,
2245
+ refreshUpstream: true,
1365
2246
  includeSubmodules: autoDiscover,
1366
2247
  submoduleIgnorePaths: node.policy?.submoduleIgnorePaths || void 0
1367
2248
  });
@@ -1369,6 +2250,7 @@ async function meshStatus(ctx) {
1369
2250
  const uncommittedChanges = countUncommittedChanges(status);
1370
2251
  const dirty = isGitStatusDirty(status);
1371
2252
  entry.health = status?.isGitRepo ? dirty ? "dirty" : "online" : "degraded";
2253
+ assignFullGitSnapshot(entry, status);
1372
2254
  entry.branch = status?.branch;
1373
2255
  entry.isDirty = dirty;
1374
2256
  entry.uncommittedChanges = uncommittedChanges;
@@ -1404,7 +2286,7 @@ async function meshStatus(ctx) {
1404
2286
  if (recoveryContext.consecutiveNodeFailures > 0) {
1405
2287
  entry.recoveryHints = {
1406
2288
  consecutiveFailures: recoveryContext.consecutiveNodeFailures,
1407
- lastTaskMessage: recoveryContext.lastTaskMessage,
2289
+ lastTaskMessage: typeof recoveryContext.lastTaskMessage === "string" ? recoveryContext.lastTaskMessage.slice(0, 100) + (recoveryContext.lastTaskMessage.length > 100 ? "\u2026" : "") : recoveryContext.lastTaskMessage,
1408
2290
  advice: recoveryContext.advice,
1409
2291
  retryRecommended: recoveryContext.retryRecommended
1410
2292
  };
@@ -1444,7 +2326,55 @@ async function meshStatus(ctx) {
1444
2326
  }
1445
2327
  const relatedRepos = await collectRelatedRepoStatuses(ctx, node);
1446
2328
  if (relatedRepos.length) entry.relatedRepos = relatedRepos;
1447
- results.push(entry);
2329
+ const liveSessions = await collectLiveStatusSessions(ctx, node);
2330
+ if (liveSessions.length > 0) {
2331
+ entry.sessions = liveSessions.map((s) => {
2332
+ const coordinatorMeshId = typeof s.coordinator?.meshId === "string" ? s.coordinator.meshId : void 0;
2333
+ const isSelfCoordinator = coordinatorMeshId === mesh.id;
2334
+ return {
2335
+ id: s.instanceId ?? s.id ?? s.sessionId,
2336
+ status: s.status ?? s.lifecycle ?? s.state,
2337
+ providerType: s.providerType ?? s.cliType ?? s.type,
2338
+ ...s.activeChat?.status ? { chatStatus: s.activeChat.status } : {},
2339
+ ...isSelfCoordinator ? { isSelfCoordinator: true, role: "coordinator" } : {}
2340
+ };
2341
+ }).filter((s) => s.id);
2342
+ }
2343
+ return entry;
2344
+ }));
2345
+ let ledgerEntries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail: 200 });
2346
+ let directDispatches = (0, import_daemon_core.getActiveDirectDispatches)(mesh.id);
2347
+ const directReconciliation = await reconcileDirectDispatchesFromTranscriptEvidence(ctx, results, directDispatches, ledgerEntries);
2348
+ if (directReconciliation.reconciled > 0) {
2349
+ ledgerEntries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail: 200 });
2350
+ directDispatches = (0, import_daemon_core.getActiveDirectDispatches)(mesh.id);
2351
+ ledgerSummary = (0, import_daemon_core.getLedgerSummary)(mesh.id);
2352
+ }
2353
+ const activeWorkEvidence = (0, import_daemon_core.buildMeshActiveWork)({
2354
+ meshId: mesh.id,
2355
+ queue: (0, import_daemon_core.getQueue)(mesh.id),
2356
+ ledgerEntries,
2357
+ directDispatches,
2358
+ nodes: results
2359
+ });
2360
+ const pollingGuidance = buildActiveWorkPollingGuidance(activeWorkEvidence.summary);
2361
+ const staleDirectWorkSummary = (0, import_daemon_core.buildCompactStaleDirectWorkSummary)(activeWorkEvidence.staleDirectWork, {
2362
+ note: activeWorkEvidence.staleDirectWorkNote,
2363
+ 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."
2364
+ });
2365
+ const coordinatorSessions = [];
2366
+ for (const nodeEntry of results) {
2367
+ const sessions = Array.isArray(nodeEntry.sessions) ? nodeEntry.sessions : [];
2368
+ for (const s of sessions) {
2369
+ if (s?.isSelfCoordinator === true && s.id) {
2370
+ coordinatorSessions.push({
2371
+ nodeId: nodeEntry.nodeId,
2372
+ sessionId: s.id,
2373
+ providerType: s.providerType,
2374
+ status: s.status
2375
+ });
2376
+ }
2377
+ }
1448
2378
  }
1449
2379
  const response = {
1450
2380
  meshId: mesh.id,
@@ -1452,20 +2382,43 @@ async function meshStatus(ctx) {
1452
2382
  repoIdentity: mesh.repoIdentity,
1453
2383
  policy: mesh.policy,
1454
2384
  refreshedAt: (/* @__PURE__ */ new Date()).toISOString(),
2385
+ sourceOfTruth: {
2386
+ membership: "coordinator_daemon_live_mesh",
2387
+ currentStatus: "live_git_and_session_probes",
2388
+ activeWork: "mesh_queue_file_and_local_ledger",
2389
+ historicalEvidenceOnly: ["recoveryHints", "ledgerSummary"]
2390
+ },
1455
2391
  nodes: results,
1456
- branchConvergenceSummary: summarizeBranchConvergence(results)
2392
+ activeWork: activeWorkEvidence.activeWork,
2393
+ staleDirectWorkSummary,
2394
+ ...args.includeStaleDirectWorkDetails === true ? { staleDirectWork: activeWorkEvidence.staleDirectWork } : {},
2395
+ // terminalDirectWork is historical (completed/failed direct dispatches) — opt-in only.
2396
+ ...args.includeTerminalDirectWork === true ? { terminalDirectWork: activeWorkEvidence.terminalDirectWork } : {},
2397
+ activeWorkSummary: activeWorkEvidence.summary,
2398
+ ...pollingGuidance ? { pollingGuidance } : {},
2399
+ branchConvergenceSummary: summarizeBranchConvergence(results),
2400
+ ...coordinatorSessions.length > 0 ? {
2401
+ coordinatorSessions,
2402
+ selfIdentification: {
2403
+ meshId: mesh.id,
2404
+ coordinatorSessions,
2405
+ 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."
2406
+ }
2407
+ } : {}
1457
2408
  };
1458
2409
  try {
1459
2410
  response.ledgerSummary = ledgerSummary;
1460
2411
  } catch {
1461
2412
  }
1462
2413
  try {
1463
- let pendingEvents = [];
1464
- if (ctx.transport instanceof IpcTransport) {
1465
- const eventsResult = await ctx.transport.command("get_pending_mesh_events", {});
1466
- pendingEvents = Array.isArray(eventsResult?.events) ? eventsResult.events : [];
1467
- } else if (isLocalTransport(ctx.transport)) {
1468
- pendingEvents = (0, import_daemon_core.drainPendingMeshCoordinatorEvents)();
2414
+ const pendingEvents = await drainCoordinatorPendingEvents(ctx);
2415
+ const asyncRefineJobs = (0, import_daemon_core.buildMeshAsyncRefineJobs)({
2416
+ meshId: mesh.id,
2417
+ ledgerEntries,
2418
+ pendingEvents
2419
+ });
2420
+ if (asyncRefineJobs.length > 0) {
2421
+ response.asyncRefineJobs = asyncRefineJobs;
1469
2422
  }
1470
2423
  if (pendingEvents.length > 0) {
1471
2424
  response.pendingCoordinatorEvents = pendingEvents;
@@ -1476,11 +2429,21 @@ async function meshStatus(ctx) {
1476
2429
  }
1477
2430
  async function meshTaskHistory(ctx, args) {
1478
2431
  const { mesh } = ctx;
2432
+ const pendingEvents = await drainCoordinatorPendingEvents(ctx);
1479
2433
  const tail = typeof args.tail === "number" && args.tail > 0 ? args.tail : 20;
1480
2434
  const kind = typeof args.kind === "string" && args.kind.trim() ? [args.kind.trim()] : void 0;
1481
- const entries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail, kind });
2435
+ const rawEntries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail, kind });
2436
+ const entries = rawEntries.map((e) => ({
2437
+ ...e,
2438
+ payload: e.payload ? slimLedgerPayload(e.payload) : e.payload
2439
+ }));
1482
2440
  const summary = (0, import_daemon_core.getLedgerSummary)(mesh.id);
1483
- return JSON.stringify({ meshId: mesh.id, entries, summary }, null, 2);
2441
+ return JSON.stringify({
2442
+ meshId: mesh.id,
2443
+ entries,
2444
+ summary,
2445
+ ...pendingEvents.length > 0 ? { pendingCoordinatorEvents: pendingEvents } : {}
2446
+ }, null, 2);
1484
2447
  }
1485
2448
  async function meshReconcileLedger(ctx, args) {
1486
2449
  await refreshMeshFromDaemon(ctx);
@@ -1570,6 +2533,9 @@ async function meshListNodes(ctx) {
1570
2533
  nodeId: n.id,
1571
2534
  workspace: n.workspace,
1572
2535
  repoRoot: n.repoRoot,
2536
+ daemonId: readNodeDaemonId(n),
2537
+ machineId: readNodeMachineId(n),
2538
+ machine: buildNodeMachineIdentity(ctx, n),
1573
2539
  isLocalWorktree: n.isLocalWorktree,
1574
2540
  policy: n.policy,
1575
2541
  relatedRepos: readRelatedRepos(n),
@@ -1579,57 +2545,135 @@ async function meshListNodes(ctx) {
1579
2545
  }, null, 2);
1580
2546
  }
1581
2547
  async function meshEnqueueTask(ctx, args) {
2548
+ const taskMode = readString(args.task_mode) || readString(args.taskMode);
2549
+ const requiredTags = (0, import_daemon_core.normalizeMeshCapabilityTags)(Array.isArray(args.requiredTags) ? args.requiredTags : args.required_tags);
1582
2550
  try {
1583
- const task = (0, import_daemon_core.enqueueTask)(ctx.mesh.id, args.message);
2551
+ const task = (0, import_daemon_core.enqueueTask)(ctx.mesh.id, args.message, { taskMode, requiredTags });
1584
2552
  if (isLocalTransport(ctx.transport) && !(ctx.transport instanceof IpcTransport)) {
1585
- ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
2553
+ const queueTrigger = await triggerMeshQueueAndReport(ctx);
2554
+ return JSON.stringify({
2555
+ success: true,
2556
+ source: "queue",
2557
+ taskId: task.id,
2558
+ status: task.status,
2559
+ taskMode: task.taskMode,
2560
+ requiredTags: task.requiredTags,
2561
+ queueTrigger,
2562
+ ...buildQueueTriggerGuidance(queueTrigger)
1586
2563
  });
1587
- return JSON.stringify({ success: true, taskId: task.id, status: task.status });
1588
2564
  }
1589
2565
  if (ctx.transport instanceof IpcTransport) {
1590
- ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
1591
- });
2566
+ const queueTrigger = await triggerMeshQueueAndReport(ctx);
1592
2567
  const dispatchPromises = [];
1593
2568
  for (const node of ctx.mesh.nodes) {
1594
2569
  const isLocalNode = isLocalControlPlaneNode(ctx, node);
1595
2570
  if (isLocalNode || !node.daemonId) continue;
2571
+ if (!(0, import_daemon_core.nodeSatisfiesRequiredTags)(requiredTags, (0, import_daemon_core.buildMeshNodeCapabilityTags)(node))) continue;
1596
2572
  dispatchPromises.push(
1597
2573
  ipcDispatchToRemoteAgent(ctx, node, { message: args.message }).then((result) => {
1598
2574
  if (result.success) {
1599
2575
  try {
2576
+ const providerType = result.providerType;
2577
+ const descriptor = summarizeTaskMessage(args.message);
1600
2578
  (0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
1601
2579
  kind: "task_dispatched",
1602
2580
  nodeId: node.id,
1603
2581
  sessionId: result.sessionId,
1604
- payload: { message: args.message, via: "p2p_direct", taskId: task.id }
2582
+ providerType,
2583
+ payload: {
2584
+ source: "queue",
2585
+ via: "p2p_direct",
2586
+ taskId: task.id,
2587
+ message: args.message,
2588
+ taskTitle: descriptor.taskTitle,
2589
+ taskSummary: descriptor.taskSummary,
2590
+ ...task.taskMode ? { taskMode: task.taskMode } : {},
2591
+ ...providerType ? { providerType } : {},
2592
+ targetSessionId: result.sessionId
2593
+ }
1605
2594
  });
1606
2595
  } catch {
1607
2596
  }
1608
2597
  }
1609
- }).catch(() => {
2598
+ }).catch((err) => {
2599
+ try {
2600
+ (0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
2601
+ kind: "p2p_dispatch_failed",
2602
+ nodeId: node.id,
2603
+ payload: {
2604
+ source: "queue",
2605
+ via: "p2p_direct",
2606
+ taskId: task.id,
2607
+ error: err?.message || String(err),
2608
+ dispatchFailedAt: (/* @__PURE__ */ new Date()).toISOString()
2609
+ }
2610
+ });
2611
+ } catch {
2612
+ }
1610
2613
  })
1611
2614
  );
1612
2615
  }
1613
2616
  Promise.all(dispatchPromises).catch(() => {
1614
2617
  });
1615
- return JSON.stringify({ success: true, taskId: task.id, status: task.status });
2618
+ return JSON.stringify({
2619
+ success: true,
2620
+ source: "queue",
2621
+ taskId: task.id,
2622
+ status: task.status,
2623
+ taskMode: task.taskMode,
2624
+ requiredTags: task.requiredTags,
2625
+ queueTrigger,
2626
+ ...buildQueueTriggerGuidance(queueTrigger)
2627
+ });
1616
2628
  }
1617
- return JSON.stringify({ success: true, taskId: task.id, status: task.status });
2629
+ return JSON.stringify({ success: true, source: "queue", taskId: task.id, status: task.status, taskMode: task.taskMode, requiredTags: task.requiredTags });
1618
2630
  } catch (e) {
1619
- return JSON.stringify({ success: false, error: e.message });
2631
+ const message = e?.message || String(e);
2632
+ if (message.includes("live_debug_readonly_guardrail_violation")) {
2633
+ return JSON.stringify({ success: false, code: "live_debug_readonly_guardrail_violation", taskMode, error: message });
2634
+ }
2635
+ return JSON.stringify({ success: false, error: message });
1620
2636
  }
1621
2637
  }
1622
2638
  async function meshViewQueue(ctx, args) {
1623
2639
  try {
2640
+ await refreshMeshFromDaemon(ctx);
1624
2641
  const statusFilter = sanitizeQueueStatusFilter(args.status);
1625
2642
  const view = normalizeQueueViewMode(args.view);
1626
- const fullQueue = annotateQueueStaleness((0, import_daemon_core.getQueue)(ctx.mesh.id), ctx.mesh);
2643
+ const fullQueue = prioritizeActiveQueueRows(annotateQueueStaleness((0, import_daemon_core.getQueue)(ctx.mesh.id), ctx.mesh));
1627
2644
  const queue = filterQueueForView(fullQueue, view, statusFilter);
1628
2645
  const summary = buildQueueStatusSummary(fullQueue);
1629
2646
  const visibleSummary = buildQueueStatusSummary(queue);
1630
2647
  const maintenance = buildQueueMaintenanceReport(fullQueue);
2648
+ const liveNodes = await collectMeshViewQueueNodesWithLiveSessions(ctx);
2649
+ let ledgerEntries = (0, import_daemon_core.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
2650
+ let directDispatches = (0, import_daemon_core.getActiveDirectDispatches)(ctx.mesh.id);
2651
+ const directReconciliation = await reconcileDirectDispatchesFromTranscriptEvidence(ctx, liveNodes, directDispatches, ledgerEntries);
2652
+ if (directReconciliation.reconciled > 0) {
2653
+ ledgerEntries = (0, import_daemon_core.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
2654
+ directDispatches = (0, import_daemon_core.getActiveDirectDispatches)(ctx.mesh.id);
2655
+ }
2656
+ (0, import_daemon_core.markStaleDirectDispatches)(ctx.mesh.id);
2657
+ directDispatches = (0, import_daemon_core.getActiveDirectDispatches)(ctx.mesh.id);
2658
+ const activeWorkEvidence = (0, import_daemon_core.buildMeshActiveWork)({
2659
+ meshId: ctx.mesh.id,
2660
+ queue: fullQueue,
2661
+ ledgerEntries,
2662
+ // Always pass MeshRuntimeStore records (may be empty). buildMeshActiveWork uses them for local
2663
+ // dispatches and falls through to ledger scan for remote P2P dispatches not in MeshRuntimeStore.
2664
+ directDispatches,
2665
+ nodes: liveNodes
2666
+ });
2667
+ const recentDispatchFailures = ledgerEntries.filter((e) => e.kind === "p2p_dispatch_failed").slice(-20).map((e) => ({
2668
+ nodeId: e.nodeId,
2669
+ taskId: e.payload?.taskId,
2670
+ error: e.payload?.error,
2671
+ via: e.payload?.via,
2672
+ failedAt: e.payload?.dispatchFailedAt || e.timestamp
2673
+ }));
1631
2674
  const staleAssignedTasks = maintenance.staleAssignedTasks || [];
1632
2675
  const requestedHistoricalRows = queue.some((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
2676
+ const pollingGuidance = buildActiveWorkPollingGuidance(activeWorkEvidence.summary);
1633
2677
  return JSON.stringify({
1634
2678
  success: true,
1635
2679
  sourceOfTruth: {
@@ -1644,21 +2688,29 @@ async function meshViewQueue(ctx, args) {
1644
2688
  filtered: Boolean(statusFilter?.length) || view !== "all"
1645
2689
  },
1646
2690
  queue,
1647
- visibleQueue: queue,
1648
- visibleSummary,
2691
+ activeWork: activeWorkEvidence.activeWork,
2692
+ staleDirectWork: activeWorkEvidence.staleDirectWork,
2693
+ activeWorkSummary: activeWorkEvidence.summary,
2694
+ ...pollingGuidance ? { pollingGuidance } : {},
1649
2695
  summary,
2696
+ visibleSummary,
1650
2697
  activeCounts: summary.activeCounts,
1651
2698
  historicalCounts: summary.historicalCounts,
1652
- activeCount: summary.activeCount,
1653
- historicalCount: summary.historicalCount,
1654
2699
  visibleActiveCounts: visibleSummary.activeCounts,
1655
2700
  visibleHistoricalCounts: visibleSummary.historicalCounts,
2701
+ activeCount: summary.activeCount,
2702
+ historicalCount: summary.historicalCount,
1656
2703
  visibleActiveCount: visibleSummary.activeCount,
1657
2704
  visibleHistoricalCount: visibleSummary.historicalCount,
1658
2705
  staleAssignedTasks,
1659
2706
  staleAssignedCount: maintenance.staleAssignedCount,
1660
2707
  queueMaintenance: maintenance,
1661
2708
  cleanupDryRun: maintenance,
2709
+ ...recentDispatchFailures.length > 0 ? {
2710
+ recentDispatchFailures,
2711
+ dispatchFailureCount: recentDispatchFailures.length,
2712
+ dispatchFailureNote: "Remote P2P dispatch attempts that failed. Affected tasks remain pending and may require mesh_queue_requeue if no idle session picks them up."
2713
+ } : {},
1662
2714
  ...view === "active" || statusFilter?.some((status) => ACTIVE_QUEUE_STATUSES.has(status)) ? {
1663
2715
  activeQueue: queue.filter((task) => ACTIVE_QUEUE_STATUSES.has(String(task?.status || "")))
1664
2716
  } : {},
@@ -1678,6 +2730,10 @@ async function meshQueueCancel(ctx, args) {
1678
2730
  if (!taskId) return JSON.stringify({ success: false, error: "task_id required" });
1679
2731
  const task = (0, import_daemon_core.cancelTask)(ctx.mesh.id, taskId, { reason: args.reason });
1680
2732
  if (!task) return JSON.stringify({ success: false, error: `Queue task '${taskId}' not found` });
2733
+ if (isLocalTransport(ctx.transport)) {
2734
+ ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
2735
+ });
2736
+ }
1681
2737
  return JSON.stringify({ success: true, task }, null, 2);
1682
2738
  } catch (e) {
1683
2739
  return JSON.stringify({ success: false, error: e.message });
@@ -1708,10 +2764,60 @@ async function meshQueueRequeue(ctx, args) {
1708
2764
  }
1709
2765
  }
1710
2766
  async function meshSendTask(ctx, args) {
2767
+ const requestedTaskMode = readString(args.task_mode) || readString(args.taskMode);
2768
+ const modeValidation = (0, import_daemon_core.validateMeshTaskModeRequest)(requestedTaskMode, args.message);
2769
+ if (!modeValidation.valid) {
2770
+ return JSON.stringify({
2771
+ success: false,
2772
+ code: "live_debug_readonly_guardrail_violation",
2773
+ taskMode: modeValidation.taskMode || requestedTaskMode,
2774
+ violations: modeValidation.violations,
2775
+ allowedOperations: modeValidation.allowedOperations,
2776
+ error: `live_debug_readonly_guardrail_violation: forbidden operations (${modeValidation.violations.join(", ")})`
2777
+ });
2778
+ }
2779
+ const taskMode = modeValidation.taskMode;
1711
2780
  const node = await findNodeWithRefresh(ctx, args.node_id);
1712
2781
  if (node.policy?.readOnly) {
1713
2782
  return JSON.stringify({ error: `Node '${args.node_id}' is read-only` });
1714
2783
  }
2784
+ let explicitTargetSession;
2785
+ if (args.session_id && isWorkerTaskMode(taskMode) && (ctx.transport instanceof IpcTransport || isLocalTransport(ctx.transport))) {
2786
+ try {
2787
+ const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
2788
+ const sessions = extractStatusMetadataSessions(statusResult);
2789
+ explicitTargetSession = sessions.find((session) => readSessionRecordId(session) === args.session_id);
2790
+ if (explicitTargetSession && isMeshCoordinatorSessionRecord(explicitTargetSession)) {
2791
+ return JSON.stringify({
2792
+ success: false,
2793
+ recoverable: true,
2794
+ code: "mesh_target_session_is_coordinator",
2795
+ reason: "mesh_target_session_is_coordinator",
2796
+ nodeId: args.node_id,
2797
+ sessionId: args.session_id,
2798
+ taskMode: taskMode || "unspecified",
2799
+ 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.`,
2800
+ 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.`
2801
+ });
2802
+ }
2803
+ if (explicitTargetSession && isUnmanagedSessionRecord(explicitTargetSession)) {
2804
+ return JSON.stringify({
2805
+ success: false,
2806
+ recoverable: true,
2807
+ code: "mesh_target_session_unmanaged",
2808
+ reason: "mesh_target_session_unmanaged",
2809
+ nodeId: args.node_id,
2810
+ sessionId: args.session_id,
2811
+ taskMode: taskMode || "unspecified",
2812
+ unsafeTranscriptAlias: true,
2813
+ 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.`,
2814
+ 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.`
2815
+ });
2816
+ }
2817
+ } catch {
2818
+ explicitTargetSession = void 0;
2819
+ }
2820
+ }
1715
2821
  const duplicate = hasRecentDuplicateDispatch(ctx, args);
1716
2822
  if (duplicate.duplicate) {
1717
2823
  return JSON.stringify({
@@ -1735,47 +2841,191 @@ async function meshSendTask(ctx, args) {
1735
2841
  const res = await ctx.transport.meshEnqueueTask(node.daemonId, {
1736
2842
  meshId: ctx.mesh.id,
1737
2843
  message: args.message,
1738
- targetNodeId: args.node_id
2844
+ targetNodeId: args.node_id,
2845
+ ...taskMode ? { taskMode } : {}
1739
2846
  });
1740
2847
  return JSON.stringify(res);
1741
2848
  }
1742
2849
  const isLocalNode = isLocalControlPlaneNode(ctx, node);
1743
2850
  if (ctx.transport instanceof IpcTransport && node.daemonId && !isLocalNode) {
1744
- const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id || ""));
2851
+ const cached = getSessionMetadata(meshSessionCacheKey(args.node_id, args.session_id || ""));
2852
+ const taskId = (0, import_node_crypto.randomUUID)();
1745
2853
  const result2 = await ipcDispatchToRemoteAgent(ctx, node, {
1746
2854
  session_id: args.session_id,
1747
2855
  message: args.message,
1748
- providerType: cached?.providerType
2856
+ providerType: cached?.providerType,
2857
+ verifiedSession: explicitTargetSession,
2858
+ meshContext: {
2859
+ meshId: ctx.mesh.id,
2860
+ nodeId: args.node_id,
2861
+ taskId
2862
+ }
1749
2863
  });
1750
2864
  if (result2.success) {
1751
2865
  const dispatchedSessionId = args.session_id || result2.sessionId;
2866
+ const dispatchedAt = (/* @__PURE__ */ new Date()).toISOString();
1752
2867
  try {
2868
+ const providerType = result2.providerType || cached?.providerType;
1753
2869
  (0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
1754
2870
  kind: "task_dispatched",
1755
2871
  nodeId: args.node_id,
1756
2872
  sessionId: dispatchedSessionId,
1757
- payload: {
1758
- message: args.message,
1759
- via: "p2p_direct",
1760
- ...dispatchedSessionId ? { targetSessionId: dispatchedSessionId } : {}
1761
- }
2873
+ providerType,
2874
+ payload: buildDirectTaskPayload(args.message, "p2p_direct", {
2875
+ taskId,
2876
+ taskMode,
2877
+ providerType,
2878
+ targetSessionId: dispatchedSessionId
2879
+ })
2880
+ });
2881
+ (0, import_daemon_core.insertDirectDispatch)(ctx.mesh.id, {
2882
+ taskId,
2883
+ nodeId: args.node_id,
2884
+ sessionId: dispatchedSessionId,
2885
+ providerType: providerType || void 0,
2886
+ message: args.message,
2887
+ taskMode: taskMode || void 0,
2888
+ via: "p2p_direct",
2889
+ dispatchedAt
1762
2890
  });
1763
2891
  } catch {
1764
2892
  }
1765
2893
  }
1766
- return JSON.stringify({ ...result2, nodeId: args.node_id, dispatched: result2.success === true });
2894
+ return JSON.stringify({
2895
+ ...result2,
2896
+ nodeId: args.node_id,
2897
+ sessionId: result2.success ? args.session_id || result2.sessionId : args.session_id,
2898
+ ...result2.success ? { source: "direct", taskId } : {},
2899
+ taskMode,
2900
+ ...result2.success && result2.providerType ? { providerType: result2.providerType } : {},
2901
+ dispatched: result2.success === true
2902
+ });
1767
2903
  }
1768
2904
  if (args.session_id && isLocalTransport(ctx.transport)) {
1769
- const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id));
2905
+ const cached = getSessionMetadata(meshSessionCacheKey(args.node_id, args.session_id));
2906
+ let resolvedProviderType = cached?.providerType || "";
2907
+ if (!resolvedProviderType) {
2908
+ let explicitSession = explicitTargetSession;
2909
+ if (!explicitSession) {
2910
+ const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
2911
+ const sessions = extractStatusMetadataSessions(statusResult);
2912
+ explicitSession = sessions.find((session) => readSessionRecordId(session) === args.session_id);
2913
+ }
2914
+ if (!explicitSession) {
2915
+ return JSON.stringify({
2916
+ success: false,
2917
+ recoverable: true,
2918
+ code: "mesh_target_session_not_found",
2919
+ reason: "mesh_target_session_not_found",
2920
+ transport: "local_ipc",
2921
+ retryRecommended: true,
2922
+ nodeId: args.node_id,
2923
+ sessionId: args.session_id,
2924
+ error: `Local session '${args.session_id}' is not present in live status for node '${args.node_id}'.`,
2925
+ 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.`
2926
+ });
2927
+ }
2928
+ if (isMeshCoordinatorSessionRecord(explicitSession)) {
2929
+ return JSON.stringify({
2930
+ success: false,
2931
+ recoverable: true,
2932
+ code: "mesh_target_session_is_coordinator",
2933
+ reason: "mesh_target_session_is_coordinator",
2934
+ nodeId: args.node_id,
2935
+ sessionId: args.session_id,
2936
+ taskMode: taskMode || "unspecified",
2937
+ 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.`,
2938
+ 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.`
2939
+ });
2940
+ }
2941
+ if (isUnmanagedSessionRecord(explicitSession)) {
2942
+ return JSON.stringify({
2943
+ success: false,
2944
+ recoverable: true,
2945
+ code: "mesh_target_session_unmanaged",
2946
+ reason: "mesh_target_session_unmanaged",
2947
+ nodeId: args.node_id,
2948
+ sessionId: args.session_id,
2949
+ taskMode: taskMode || "unspecified",
2950
+ unsafeTranscriptAlias: true,
2951
+ unsafeDelegateTarget: true,
2952
+ 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.`,
2953
+ 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.`
2954
+ });
2955
+ }
2956
+ resolvedProviderType = resolveSessionProviderType(explicitSession);
2957
+ if (resolvedProviderType) {
2958
+ meshSessionProviderMetadata.set(meshSessionCacheKey(args.node_id, args.session_id), {
2959
+ providerType: resolvedProviderType,
2960
+ providerSessionId: readString(explicitSession?.providerSessionId) || void 0,
2961
+ expiresAt: Date.now() + SESSION_PROVIDER_METADATA_TTL_MS
2962
+ });
2963
+ }
2964
+ }
2965
+ if (!resolvedProviderType) {
2966
+ return JSON.stringify({
2967
+ success: false,
2968
+ recoverable: true,
2969
+ code: "mesh_target_session_provider_unknown",
2970
+ reason: "mesh_target_session_provider_unknown",
2971
+ transport: "local_ipc",
2972
+ retryRecommended: false,
2973
+ nodeId: args.node_id,
2974
+ sessionId: args.session_id,
2975
+ error: `Local session '${args.session_id}' is live but does not expose providerType/cliType, so agent_command cannot be routed safely.`,
2976
+ 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.`
2977
+ });
2978
+ }
2979
+ if (explicitTargetSession && !isIdleSessionRecord(explicitTargetSession) && !isTerminalSessionRecord(explicitTargetSession)) {
2980
+ const sessionStatus = typeof explicitTargetSession?.status === "string" ? explicitTargetSession.status : "unknown";
2981
+ const { createSessionDelivery: createDelivery, resolveDeliveryDecision } = await import("@adhdev/daemon-core");
2982
+ const policyResult = resolveDeliveryDecision(sessionStatus, { kind: "task" });
2983
+ if (policyResult.decision === "queued") {
2984
+ const delivery = createDelivery({
2985
+ meshId: ctx.mesh.id,
2986
+ nodeId: args.node_id,
2987
+ sessionId: args.session_id,
2988
+ providerType: resolvedProviderType,
2989
+ kind: "task",
2990
+ message: args.message,
2991
+ status: "queued"
2992
+ });
2993
+ return JSON.stringify({
2994
+ success: true,
2995
+ dispatched: false,
2996
+ decision: "queued_delivery",
2997
+ deliveryId: delivery.id,
2998
+ reason: policyResult.reason,
2999
+ nodeId: args.node_id,
3000
+ sessionId: args.session_id,
3001
+ sessionStatus,
3002
+ taskMode: taskMode || void 0,
3003
+ message: policyResult.message,
3004
+ 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.`
3005
+ });
3006
+ }
3007
+ }
3008
+ const sessionWasIdle = explicitTargetSession ? isIdleSessionRecord(explicitTargetSession) : false;
3009
+ const taskId = (0, import_node_crypto.randomUUID)();
3010
+ const dispatchedAt = (/* @__PURE__ */ new Date()).toISOString();
1770
3011
  const dispatchResult = await commandForNode(ctx, node, "agent_command", {
1771
3012
  targetSessionId: args.session_id,
1772
- ...cached?.providerType ? { agentType: cached.providerType, cliType: cached.providerType, providerType: cached.providerType } : {},
3013
+ agentType: resolvedProviderType,
3014
+ cliType: resolvedProviderType,
3015
+ providerType: resolvedProviderType,
1773
3016
  action: "send_chat",
1774
- message: args.message
3017
+ message: args.message,
3018
+ meshContext: {
3019
+ meshId: ctx.mesh.id,
3020
+ nodeId: args.node_id,
3021
+ taskId
3022
+ }
1775
3023
  });
1776
3024
  const dispatchPayload = unwrapCommandPayload(dispatchResult);
1777
3025
  if (dispatchPayload?.success === false || dispatchResult?.success === false) {
3026
+ const source = dispatchPayload?.success === false ? dispatchPayload : dispatchResult;
1778
3027
  return JSON.stringify({
3028
+ ...source && typeof source === "object" ? source : {},
1779
3029
  success: false,
1780
3030
  nodeId: args.node_id,
1781
3031
  sessionId: args.session_id,
@@ -1787,23 +3037,79 @@ async function meshSendTask(ctx, args) {
1787
3037
  kind: "task_dispatched",
1788
3038
  nodeId: args.node_id,
1789
3039
  sessionId: args.session_id,
1790
- providerType: cached?.providerType,
1791
- payload: { message: args.message, via: "local_direct" }
3040
+ providerType: resolvedProviderType,
3041
+ payload: buildDirectTaskPayload(args.message, "local_direct", {
3042
+ taskId,
3043
+ taskMode,
3044
+ providerType: resolvedProviderType,
3045
+ targetSessionId: args.session_id,
3046
+ dispatchedToIdleSession: sessionWasIdle
3047
+ })
3048
+ });
3049
+ } catch {
3050
+ }
3051
+ (0, import_daemon_core.insertDirectDispatch)(ctx.mesh.id, {
3052
+ taskId,
3053
+ nodeId: args.node_id,
3054
+ sessionId: args.session_id,
3055
+ providerType: resolvedProviderType || void 0,
3056
+ message: args.message,
3057
+ taskMode: taskMode || void 0,
3058
+ via: "local_direct",
3059
+ dispatchedToIdleSession: sessionWasIdle,
3060
+ dispatchedAt
3061
+ });
3062
+ let deliveryId;
3063
+ try {
3064
+ const { createSessionDelivery: createDelivery } = await import("@adhdev/daemon-core");
3065
+ const delivery = createDelivery({
3066
+ meshId: ctx.mesh.id,
3067
+ nodeId: args.node_id,
3068
+ sessionId: args.session_id,
3069
+ providerType: resolvedProviderType || void 0,
3070
+ taskId,
3071
+ kind: "task",
3072
+ message: args.message,
3073
+ status: sessionWasIdle ? "delivered" : "delivering"
1792
3074
  });
3075
+ deliveryId = delivery.id;
1793
3076
  } catch {
1794
3077
  }
1795
- return JSON.stringify({ success: true, dispatched: true, nodeId: args.node_id, sessionId: args.session_id });
3078
+ return JSON.stringify({
3079
+ success: true,
3080
+ dispatched: true,
3081
+ decision: "immediate",
3082
+ source: "direct",
3083
+ taskId,
3084
+ deliveryId,
3085
+ taskMode,
3086
+ providerType: resolvedProviderType,
3087
+ nodeId: args.node_id,
3088
+ sessionId: args.session_id,
3089
+ ...sessionWasIdle ? {
3090
+ dispatchAcknowledgementRisk: true,
3091
+ dispatchAcknowledgementRiskReason: "session_was_idle_at_dispatch",
3092
+ 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.`
3093
+ } : {}
3094
+ });
1796
3095
  }
1797
3096
  const task = (0, import_daemon_core.enqueueTask)(ctx.mesh.id, args.message, {
1798
3097
  targetNodeId: args.node_id,
1799
- targetSessionId: args.session_id
3098
+ targetSessionId: args.session_id,
3099
+ taskMode
1800
3100
  });
1801
- if (isLocalTransport(ctx.transport) || ctx.transport instanceof IpcTransport) {
1802
- ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
1803
- });
1804
- }
1805
- const pendingEvents = isLocalTransport(ctx.transport) ? (0, import_daemon_core.drainPendingMeshCoordinatorEvents)() : [];
1806
- const result = { success: true, nodeId: args.node_id, taskId: task.id, status: task.status };
3101
+ const queueTrigger = isLocalTransport(ctx.transport) || ctx.transport instanceof IpcTransport ? await triggerMeshQueueAndReport(ctx) : void 0;
3102
+ const pendingEvents = isLocalTransport(ctx.transport) ? (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id, ctx.localDaemonId) : [];
3103
+ const result = {
3104
+ success: true,
3105
+ source: "queue",
3106
+ nodeId: args.node_id,
3107
+ taskId: task.id,
3108
+ status: task.status,
3109
+ taskMode: task.taskMode,
3110
+ queueTrigger,
3111
+ ...buildQueueTriggerGuidance(queueTrigger)
3112
+ };
1807
3113
  if (pendingEvents.length > 0) {
1808
3114
  result.pendingCoordinatorEvents = pendingEvents;
1809
3115
  }
@@ -1823,8 +3129,11 @@ async function meshReadChat(ctx, args) {
1823
3129
  if (!node) {
1824
3130
  return JSON.stringify(buildMissingNodeReadChatRecovery(ctx, args), null, 2);
1825
3131
  }
3132
+ if (ctx.transport instanceof IpcTransport || isLocalTransport(ctx.transport)) {
3133
+ await drainCoordinatorPendingEvents(ctx, { nodeIds: [args.node_id] });
3134
+ }
1826
3135
  if (isLocalTransport(ctx.transport)) {
1827
- const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id));
3136
+ const cached = resolveMeshSessionProviderMetadata(ctx, args.node_id, args.session_id);
1828
3137
  const providerSessionId = typeof args.provider_session_id === "string" && args.provider_session_id.trim() ? args.provider_session_id.trim() : cached?.providerSessionId;
1829
3138
  const result = await commandForNode(ctx, node, "read_chat", {
1830
3139
  sessionId: args.session_id,
@@ -1832,18 +3141,19 @@ async function meshReadChat(ctx, args) {
1832
3141
  workspace: node.workspace,
1833
3142
  ...cached?.providerType ? { agentType: cached.providerType, providerType: cached.providerType } : {},
1834
3143
  ...providerSessionId ? { providerSessionId } : {},
1835
- tailLimit: args.tail ?? 10
3144
+ tailLimit: args.tail ?? 3
1836
3145
  });
1837
3146
  const payload = annotateRapidReadChatAdvisory(unwrapCommandPayload(result), {
1838
3147
  key: `mesh:${args.node_id}:${args.session_id}`,
1839
3148
  toolName: "mesh_read_chat",
1840
3149
  completionCallbackExpected: true
1841
3150
  });
1842
- if (args.compact) {
3151
+ const useCompact = args.compact !== false;
3152
+ if (useCompact) {
1843
3153
  const compactPayload = compactChatPayload(payload, {
1844
3154
  nodeId: args.node_id,
1845
3155
  sessionId: args.session_id,
1846
- limit: args.tail ?? 10
3156
+ limit: args.tail ?? 3
1847
3157
  });
1848
3158
  return JSON.stringify(
1849
3159
  payload.pollingAdvisory ? { ...compactPayload, pollingAdvisory: payload.pollingAdvisory } : compactPayload,
@@ -1856,7 +3166,7 @@ async function meshReadChat(ctx, args) {
1856
3166
  try {
1857
3167
  const targetId = `${node.daemonId}:session:${args.session_id}`;
1858
3168
  const res = await ctx.transport.readChat(targetId, {
1859
- limit: args.tail ?? 10,
3169
+ limit: args.tail ?? 3,
1860
3170
  sessionId: args.session_id
1861
3171
  });
1862
3172
  return JSON.stringify(res, null, 2);
@@ -1870,7 +3180,7 @@ async function meshReadChat(ctx, args) {
1870
3180
  async function meshReadDebug(ctx, args) {
1871
3181
  const node = await findNodeWithRefresh(ctx, args.node_id);
1872
3182
  if (isLocalTransport(ctx.transport)) {
1873
- const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id));
3183
+ const cached = resolveMeshSessionProviderMetadata(ctx, args.node_id, args.session_id);
1874
3184
  const providerSessionId = typeof args.provider_session_id === "string" && args.provider_session_id.trim() ? args.provider_session_id.trim() : cached?.providerSessionId;
1875
3185
  const delivery = args.delivery === "inline" ? void 0 : "daemon_file";
1876
3186
  const result = await commandForNode(ctx, node, "get_chat_debug_bundle", {
@@ -1901,6 +3211,8 @@ async function meshReadDebug(ctx, args) {
1901
3211
  }
1902
3212
  async function meshLaunchSession(ctx, args) {
1903
3213
  const node = await findNodeWithRefresh(ctx, args.node_id);
3214
+ const bootstrapBlock = getWorktreeBootstrapLaunchBlock(node);
3215
+ if (bootstrapBlock) return JSON.stringify(bootstrapBlock, null, 2);
1904
3216
  if (isLocalTransport(ctx.transport)) {
1905
3217
  let resolvedProviderType = typeof args.type === "string" && args.type.trim() ? args.type : "";
1906
3218
  if (!resolvedProviderType) {
@@ -1925,6 +3237,10 @@ async function meshLaunchSession(ctx, args) {
1925
3237
  const coordinatorNode = resolveCoordinatorNode(ctx);
1926
3238
  const coordinatorDaemonId = coordinatorNode?.daemonId || ctx.localDaemonId;
1927
3239
  const spawnedSessionVisibility = readSpawnedSessionVisibility(ctx.mesh.policy);
3240
+ const isLocalNode = isLocalControlPlaneNode(ctx, node);
3241
+ if (node.daemonId && !isLocalNode && !coordinatorDaemonId) {
3242
+ return JSON.stringify(buildMissingCoordinatorDaemonIdFailure(ctx, node, resolvedProviderType), null, 2);
3243
+ }
1928
3244
  let result;
1929
3245
  try {
1930
3246
  result = await commandForNode(ctx, node, "launch_cli", {
@@ -1952,7 +3268,8 @@ async function meshLaunchSession(ctx, args) {
1952
3268
  if (runtimeSessionId) {
1953
3269
  meshSessionProviderMetadata.set(meshSessionCacheKey(args.node_id, runtimeSessionId), {
1954
3270
  providerType: resolvedProviderType,
1955
- ...providerSessionId ? { providerSessionId } : {}
3271
+ ...providerSessionId ? { providerSessionId } : {},
3272
+ expiresAt: Date.now() + SESSION_PROVIDER_METADATA_TTL_MS
1956
3273
  });
1957
3274
  }
1958
3275
  try {
@@ -1965,18 +3282,13 @@ async function meshLaunchSession(ctx, args) {
1965
3282
  });
1966
3283
  } catch {
1967
3284
  }
1968
- const isLocalNode = isLocalControlPlaneNode(ctx, node);
1969
- if (ctx.transport instanceof IpcTransport && node.daemonId && !isLocalNode) {
1970
- ctx.transport.meshCommand(node.daemonId, "trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
1971
- });
1972
- } else if (isLocalTransport(ctx.transport)) {
1973
- ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
1974
- });
1975
- }
3285
+ const queueTrigger = await triggerMeshQueueAndReport(ctx, node, { localNode: isLocalNode });
1976
3286
  return JSON.stringify({
1977
3287
  ...launchPayload,
1978
3288
  resolvedProviderType,
1979
- ...providerSessionId ? { providerSessionId } : {}
3289
+ ...providerSessionId ? { providerSessionId } : {},
3290
+ queueTrigger,
3291
+ ...buildQueueTriggerGuidance(queueTrigger)
1980
3292
  }, null, 2);
1981
3293
  } else if (!isLocalTransport(ctx.transport) && node.daemonId) {
1982
3294
  let resolvedProviderType = typeof args.type === "string" && args.type.trim() ? args.type : "";
@@ -1990,6 +3302,9 @@ async function meshLaunchSession(ctx, args) {
1990
3302
  const coordinatorNode = resolveCoordinatorNode(ctx);
1991
3303
  const coordinatorDaemonId = coordinatorNode?.daemonId || ctx.localDaemonId;
1992
3304
  const spawnedSessionVisibility = readSpawnedSessionVisibility(ctx.mesh.policy);
3305
+ if (!coordinatorDaemonId) {
3306
+ return JSON.stringify(buildMissingCoordinatorDaemonIdFailure(ctx, node, resolvedProviderType), null, 2);
3307
+ }
1993
3308
  try {
1994
3309
  const res = await ctx.transport.launch(node.daemonId, {
1995
3310
  type: resolvedProviderType,
@@ -2028,7 +3343,7 @@ async function meshGitStatus(ctx, args) {
2028
3343
  const submoduleIgnorePaths = node.policy?.submoduleIgnorePaths || [];
2029
3344
  try {
2030
3345
  if (!isLocalTransport(ctx.transport) && node.daemonId) {
2031
- const result = await ctx.transport.gitStatus(node.daemonId, node.workspace, true);
3346
+ const result = await ctx.transport.gitStatus(node.daemonId, node.workspace, true, true);
2032
3347
  return JSON.stringify({
2033
3348
  nodeId: args.node_id,
2034
3349
  workspace: node.workspace,
@@ -2040,6 +3355,7 @@ async function meshGitStatus(ctx, args) {
2040
3355
  } else if (isLocalTransport(ctx.transport)) {
2041
3356
  const statusResult = await commandForNode(ctx, node, "git_status", {
2042
3357
  workspace: node.workspace,
3358
+ refreshUpstream: true,
2043
3359
  includeSubmodules: autoDiscoverSubmodules,
2044
3360
  submoduleIgnorePaths: submoduleIgnorePaths.length > 0 ? submoduleIgnorePaths : void 0
2045
3361
  });
@@ -2069,6 +3385,51 @@ async function meshGitStatus(ctx, args) {
2069
3385
  }, null, 2);
2070
3386
  }
2071
3387
  }
3388
+ async function meshFastForwardNode(ctx, args) {
3389
+ await refreshMeshFromDaemon(ctx);
3390
+ const node = await findNodeWithRefresh(ctx, args.node_id);
3391
+ const submoduleIgnorePaths = node.policy?.submoduleIgnorePaths || [];
3392
+ if (node.policy?.readOnly) {
3393
+ return JSON.stringify({
3394
+ success: false,
3395
+ code: "node_read_only",
3396
+ nodeId: args.node_id,
3397
+ workspace: node.workspace,
3398
+ allowed: false,
3399
+ willRun: false,
3400
+ executed: false,
3401
+ blockingReasons: ["node_read_only"]
3402
+ }, null, 2);
3403
+ }
3404
+ try {
3405
+ const dryRun = args.dry_run === true || args.execute !== true;
3406
+ const result = await commandForNode(ctx, node, "fast_forward_mesh_node", {
3407
+ meshId: ctx.mesh.id,
3408
+ nodeId: node.id,
3409
+ workspace: node.workspace,
3410
+ branch: typeof args.branch === "string" ? args.branch : void 0,
3411
+ execute: args.execute === true && args.dry_run !== true,
3412
+ dryRun,
3413
+ updateSubmodules: args.update_submodules === true,
3414
+ submoduleIgnorePaths: submoduleIgnorePaths.length > 0 ? submoduleIgnorePaths : void 0
3415
+ });
3416
+ return JSON.stringify(unwrapCommandPayload(result), null, 2);
3417
+ } catch (e) {
3418
+ const failure = buildCoordinatorP2pRelayFailure(e, {
3419
+ command: "fast_forward_mesh_node",
3420
+ targetDaemonId: node.daemonId,
3421
+ nodeId: args.node_id
3422
+ });
3423
+ return JSON.stringify({
3424
+ ...failure,
3425
+ workspace: node.workspace,
3426
+ allowed: false,
3427
+ willRun: false,
3428
+ executed: false,
3429
+ blockingReasons: [failure.code || "mesh_fast_forward_unavailable"]
3430
+ }, null, 2);
3431
+ }
3432
+ }
2072
3433
  async function meshCheckpoint(ctx, args) {
2073
3434
  const node = await findNodeWithRefresh(ctx, args.node_id);
2074
3435
  if (node.policy?.readOnly) {
@@ -2084,7 +3445,13 @@ async function meshCheckpoint(ctx, args) {
2084
3445
  (0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
2085
3446
  kind: "checkpoint_created",
2086
3447
  nodeId: args.node_id,
2087
- payload: { message: args.message, commit: result?.checkpoint?.commit }
3448
+ payload: {
3449
+ message: args.message,
3450
+ commit: result?.checkpoint?.commit,
3451
+ outcome: result?.checkpoint?.status || (result?.checkpoint?.noop ? "skipped" : void 0),
3452
+ noop: result?.checkpoint?.noop === true,
3453
+ reason: result?.checkpoint?.reason
3454
+ }
2088
3455
  });
2089
3456
  } catch {
2090
3457
  }
@@ -2100,7 +3467,13 @@ async function meshCheckpoint(ctx, args) {
2100
3467
  (0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
2101
3468
  kind: "checkpoint_created",
2102
3469
  nodeId: args.node_id,
2103
- payload: { message: args.message, commit: res?.checkpoint?.commit }
3470
+ payload: {
3471
+ message: args.message,
3472
+ commit: res?.checkpoint?.commit,
3473
+ outcome: res?.checkpoint?.status || (res?.checkpoint?.noop ? "skipped" : void 0),
3474
+ noop: res?.checkpoint?.noop === true,
3475
+ reason: res?.checkpoint?.reason
3476
+ }
2104
3477
  });
2105
3478
  } catch {
2106
3479
  }
@@ -2115,7 +3488,7 @@ async function meshCheckpoint(ctx, args) {
2115
3488
  async function meshApprove(ctx, args) {
2116
3489
  const node = await findNodeWithRefresh(ctx, args.node_id);
2117
3490
  if (isLocalTransport(ctx.transport)) {
2118
- const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id));
3491
+ const cached = getSessionMetadata(meshSessionCacheKey(args.node_id, args.session_id));
2119
3492
  const providerSessionId = cached?.providerSessionId;
2120
3493
  const result = await commandForNode(ctx, node, "resolve_action", {
2121
3494
  sessionId: args.session_id,
@@ -2154,6 +3527,7 @@ async function meshCloneNode(ctx, args) {
2154
3527
  if (existingIndex >= 0) ctx.mesh.nodes[existingIndex] = clonePayload.node;
2155
3528
  else ctx.mesh.nodes.push(clonePayload.node);
2156
3529
  ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
3530
+ await syncCoordinatorDaemonMeshCache(ctx);
2157
3531
  }
2158
3532
  return JSON.stringify(result, null, 2);
2159
3533
  } else if (!isLocalTransport(ctx.transport) && sourceNode.daemonId) {
@@ -2171,6 +3545,7 @@ async function meshCloneNode(ctx, args) {
2171
3545
  if (existingIndex >= 0) ctx.mesh.nodes[existingIndex] = clonePayload.node;
2172
3546
  else ctx.mesh.nodes.push(clonePayload.node);
2173
3547
  ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
3548
+ await syncCoordinatorDaemonMeshCache(ctx);
2174
3549
  }
2175
3550
  return JSON.stringify(res, null, 2);
2176
3551
  } catch (e) {
@@ -2266,6 +3641,43 @@ async function meshRemoveNode(ctx, args) {
2266
3641
  return JSON.stringify({ error: "Cloud mesh remove_node requires node daemonId" });
2267
3642
  }
2268
3643
  }
3644
+ function resolveRefineConfigNode(ctx, nodeId) {
3645
+ if (nodeId) return findNode(ctx.mesh, nodeId);
3646
+ const node = ctx.mesh.nodes.find((entry) => !!entry.workspace);
3647
+ if (!node) throw new Error("No mesh node with a workspace is available");
3648
+ return node;
3649
+ }
3650
+ async function meshRefineConfigSchema(ctx) {
3651
+ const node = resolveRefineConfigNode(ctx);
3652
+ const result = await commandForNode(ctx, node, "get_mesh_refine_config_schema", {});
3653
+ return JSON.stringify(result, null, 2);
3654
+ }
3655
+ async function meshValidateRefineConfig(ctx, args) {
3656
+ const node = resolveRefineConfigNode(ctx, args.node_id);
3657
+ const result = await commandForNode(ctx, node, "validate_mesh_refine_config", {
3658
+ workspace: node.workspace,
3659
+ inlineMesh: ctx.mesh,
3660
+ ...args.config ? { config: args.config } : {}
3661
+ });
3662
+ return JSON.stringify(result, null, 2);
3663
+ }
3664
+ async function meshSuggestRefineConfig(ctx, args) {
3665
+ const node = resolveRefineConfigNode(ctx, args.node_id);
3666
+ const result = await commandForNode(ctx, node, "suggest_mesh_refine_config", {
3667
+ workspace: node.workspace,
3668
+ inlineMesh: ctx.mesh
3669
+ });
3670
+ return JSON.stringify(result, null, 2);
3671
+ }
3672
+ async function meshRefinePlan(ctx, args) {
3673
+ const node = await findNodeWithRefresh(ctx, args.node_id);
3674
+ const result = await commandForNode(ctx, node, "plan_mesh_refine_node", {
3675
+ meshId: ctx.mesh.id,
3676
+ nodeId: args.node_id,
3677
+ inlineMesh: ctx.mesh
3678
+ });
3679
+ return JSON.stringify(result, null, 2);
3680
+ }
2269
3681
  async function meshRefineNode(ctx, args) {
2270
3682
  const node = await findNodeWithRefresh(ctx, args.node_id);
2271
3683
  if (isLocalTransport(ctx.transport)) {
@@ -2274,7 +3686,7 @@ async function meshRefineNode(ctx, args) {
2274
3686
  nodeId: args.node_id,
2275
3687
  inlineMesh: ctx.mesh
2276
3688
  });
2277
- if (result?.success && result.removeResult?.removed !== false) {
3689
+ if (result?.success && result.async !== true && result.removeResult?.removed !== false) {
2278
3690
  const idx = ctx.mesh.nodes.findIndex((n) => n.id === args.node_id);
2279
3691
  if (idx >= 0) {
2280
3692
  ctx.mesh.nodes.splice(idx, 1);
@@ -2289,7 +3701,7 @@ async function meshRefineNode(ctx, args) {
2289
3701
  nodeId: args.node_id,
2290
3702
  inlineMesh: ctx.mesh
2291
3703
  });
2292
- if (res?.success && res.removeResult?.removed !== false) {
3704
+ if (res?.success && res.async !== true && res.removeResult?.removed !== false) {
2293
3705
  const idx = ctx.mesh.nodes.findIndex((n) => n.id === args.node_id);
2294
3706
  if (idx >= 0) {
2295
3707
  ctx.mesh.nodes.splice(idx, 1);
@@ -2326,13 +3738,13 @@ var STANDARD_TOOLS = [
2326
3738
  function buildMcpHelpText() {
2327
3739
  const meshTools = ALL_MESH_TOOLS.map((tool) => tool.name);
2328
3740
  return `
2329
- adhdev-mcp \u2014 ADHDev MCP Server
3741
+ ADHDev MCP Server
2330
3742
 
2331
3743
  Usage:
2332
- adhdev-mcp Local mode (requires standalone daemon)
2333
- adhdev-mcp --api-key <key> Cloud mode (ADHDev cloud API)
2334
- adhdev-mcp --mode ipc --repo-mesh <mesh_id> Cloud daemon IPC mesh mode
2335
- adhdev-mcp --repo-mesh <mesh_id> Mesh mode (coordinator-scoped tools)
3744
+ adhdev mcp Local mode (requires standalone daemon)
3745
+ adhdev mcp --api-key <key> Cloud mode (ADHDev cloud API)
3746
+ adhdev mcp --mode ipc --repo-mesh <mesh_id> Cloud daemon IPC mesh mode
3747
+ adhdev-mcp --help Compatibility bin (same server, legacy package entrypoint)
2336
3748
 
2337
3749
  Options:
2338
3750
  --mode <mode> Transport: local, cloud, or ipc
@@ -2357,6 +3769,7 @@ Mesh tools: ${meshTools.join(", ")}
2357
3769
  // src/server.ts
2358
3770
  var import_server = require("@modelcontextprotocol/sdk/server/index.js");
2359
3771
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
3772
+ var import_node_os = __toESM(require("os"));
2360
3773
  var import_types = require("@modelcontextprotocol/sdk/types.js");
2361
3774
 
2362
3775
  // src/transports/local.ts
@@ -2511,8 +3924,8 @@ var CloudTransport = class {
2511
3924
  if (!res.ok) throw new Error(`Approve failed: ${res.status}`);
2512
3925
  return res.json();
2513
3926
  }
2514
- async gitStatus(daemonId, workspace, includeDiff = true) {
2515
- const params = new URLSearchParams({ workspace, includeDiff: String(includeDiff) });
3927
+ async gitStatus(daemonId, workspace, includeDiff = true, refreshUpstream = false) {
3928
+ const params = new URLSearchParams({ workspace, includeDiff: String(includeDiff), refreshUpstream: String(refreshUpstream) });
2516
3929
  const res = await fetch(
2517
3930
  `${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/git-status?${params}`,
2518
3931
  { headers: this.headers() }
@@ -2907,6 +4320,28 @@ function formatChatResult(result, sessionId, format, limit = 50, compact = false
2907
4320
  }))
2908
4321
  }, null, 2);
2909
4322
  }
4323
+ if ((format === "text" || format === void 0) && compact && compactPayload) {
4324
+ const summaryText = typeof compactPayload.summary === "string" ? compactPayload.summary.trim() : "";
4325
+ const tail = outputMessages.slice(-limit);
4326
+ const lastIndex = tail.length - 1;
4327
+ const lines2 = tail.flatMap((m, idx) => {
4328
+ const role = m.role === "user" ? "User" : m.role === "assistant" ? "Agent" : m.role;
4329
+ const content = messageContent(m);
4330
+ if (idx === lastIndex && (role === "Agent" || m.role === "agent") && summaryText && content.trim() === summaryText) {
4331
+ return [];
4332
+ }
4333
+ const truncated = content.length > 500 ? `${content.slice(0, 500)}\u2026` : content;
4334
+ return [`[${role}] ${truncated}`];
4335
+ });
4336
+ if (compactPayload.summary) {
4337
+ const truncatedSummary = compactPayload.summary.length > 500 ? `${compactPayload.summary.slice(0, 500)}\u2026` : compactPayload.summary;
4338
+ lines2.push(`[Summary] ${truncatedSummary}`);
4339
+ }
4340
+ if (result?.pollingAdvisory) {
4341
+ lines2.push(`Advisory: ${result.pollingAdvisory.message}`);
4342
+ }
4343
+ return lines2.length > 0 ? lines2.join("\n\n") : "No messages in chat.";
4344
+ }
2910
4345
  if (outputMessages.length === 0) {
2911
4346
  return result?.pollingAdvisory ? `No messages in chat.
2912
4347
 
@@ -3011,6 +4446,90 @@ function formatChatDebugResult(result, options) {
3011
4446
  return JSON.stringify(result, null, 2);
3012
4447
  }
3013
4448
 
4449
+ // src/tools/spec-debug.ts
4450
+ var SPEC_DEBUG_TOOL = {
4451
+ name: "spec_debug",
4452
+ 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.",
4453
+ inputSchema: {
4454
+ type: "object",
4455
+ properties: {
4456
+ session_id: {
4457
+ type: "string",
4458
+ description: "Target session ID (from list_sessions)."
4459
+ },
4460
+ daemon_id: {
4461
+ type: "string",
4462
+ description: "Daemon ID (cloud mode only). Omit for local mode."
4463
+ },
4464
+ ...FORMAT_PROP
4465
+ },
4466
+ required: ["session_id"]
4467
+ }
4468
+ };
4469
+ async function specDebug(transport, args) {
4470
+ const sessionId = typeof args.session_id === "string" ? args.session_id.trim() : "";
4471
+ if (!sessionId) throw new Error("session_id is required");
4472
+ let result;
4473
+ if (isLocalTransport(transport)) {
4474
+ result = await transport.command("get_spec_debug", { targetSessionId: sessionId });
4475
+ } else {
4476
+ if (!args.daemon_id) throw new Error("daemon_id is required in cloud mode");
4477
+ const targetId = `${args.daemon_id}:session:${sessionId}`;
4478
+ result = await transport.sendCommand(targetId, "get_spec_debug", { targetSessionId: sessionId });
4479
+ }
4480
+ return formatSpecDebugResult(result, { sessionId, format: args.format });
4481
+ }
4482
+ function formatSpecDebugResult(result, options) {
4483
+ if (!result?.success) {
4484
+ const err = result?.error || "Unknown error";
4485
+ if (options.format === "json") return JSON.stringify({ success: false, error: err }, null, 2);
4486
+ return `Error: ${err}`;
4487
+ }
4488
+ if (options.format === "json") return JSON.stringify(result, null, 2);
4489
+ const snap = result.snapshot;
4490
+ if (!snap) {
4491
+ return [
4492
+ `session_id: ${options.sessionId}`,
4493
+ `provider_type: ${String(result.providerType || "")}`,
4494
+ "is_spec_provider: false",
4495
+ "No spec debug data available (not a spec-driven provider)."
4496
+ ].join("\n");
4497
+ }
4498
+ const lines = [];
4499
+ lines.push(`session_id: ${options.sessionId}`);
4500
+ lines.push(`provider_type: ${String(result.providerType || snap.cliType || "")}`);
4501
+ lines.push(`spec_id: ${String(snap.spec_id || "")}`);
4502
+ lines.push(`spec_path: ${String(snap.specPath || "")}`);
4503
+ lines.push(`current_state: ${snap.current_state ? `${snap.current_state.id} (${snap.current_state.label})` : "none"}`);
4504
+ lines.push(`idle_hold_pending: ${String(snap.idleHoldPending ?? false)}`);
4505
+ lines.push(`last_busy_at: ${snap.lastBusyAt ? new Date(snap.lastBusyAt).toISOString() : "never"}`);
4506
+ lines.push(`exited: ${String(snap.exited ?? false)}`);
4507
+ if (snap.current_modal) {
4508
+ lines.push(`current_modal: ${JSON.stringify(snap.current_modal)}`);
4509
+ }
4510
+ if (snap.sections && typeof snap.sections === "object") {
4511
+ lines.push("");
4512
+ lines.push("\u2500\u2500 sections \u2500\u2500");
4513
+ for (const [id, text] of Object.entries(snap.sections)) {
4514
+ const preview = String(text || "").replace(/\n/g, "\u21B5").slice(0, 120);
4515
+ lines.push(` ${id}: ${preview}`);
4516
+ }
4517
+ }
4518
+ const history = Array.isArray(snap.stateHistory) ? snap.stateHistory : [];
4519
+ if (history.length > 0) {
4520
+ lines.push("");
4521
+ lines.push("\u2500\u2500 state history (newest first) \u2500\u2500");
4522
+ const now = Date.now();
4523
+ for (const entry of [...history].reverse().slice(0, 20)) {
4524
+ const agoMs = now - entry.at;
4525
+ const ago = agoMs < 2e3 ? `${agoMs}ms ago` : `${(agoMs / 1e3).toFixed(1)}s ago`;
4526
+ const dur = entry.durationMs > 0 ? ` held ${entry.durationMs}ms` : "";
4527
+ lines.push(` ${String(entry.stateId).padEnd(18)} ${ago}${dur}`);
4528
+ }
4529
+ }
4530
+ return lines.join("\n");
4531
+ }
4532
+
3014
4533
  // src/tools/send-chat.ts
3015
4534
  var SEND_CHAT_TOOL = {
3016
4535
  name: "send_chat",
@@ -3874,6 +5393,7 @@ async function startMcpServer(opts) {
3874
5393
  requirePreTaskCheckpoint: false,
3875
5394
  requirePostTaskCheckpoint: true,
3876
5395
  requireApprovalForPush: true,
5396
+ allowAutoPublishSubmoduleMainCommits: false,
3877
5397
  requireApprovalForDestructiveGit: true,
3878
5398
  dirtyWorkspaceBehavior: "warn",
3879
5399
  maxParallelTasks: 2,
@@ -3930,11 +5450,13 @@ async function startMcpServer(opts) {
3930
5450
  }
3931
5451
  let localDaemonId;
3932
5452
  let localMachineId;
5453
+ let coordinatorHostname = import_node_os.default.hostname();
3933
5454
  if (transport instanceof LocalTransport || transport instanceof IpcTransport) {
3934
5455
  try {
3935
5456
  const { loadConfig } = await import("@adhdev/daemon-core");
3936
5457
  const cfg = loadConfig();
3937
- if (cfg.registeredMachineId) localMachineId = cfg.registeredMachineId;
5458
+ if (cfg.machineId) localMachineId = cfg.machineId;
5459
+ else if (cfg.registeredMachineId) localMachineId = cfg.registeredMachineId;
3938
5460
  } catch {
3939
5461
  }
3940
5462
  }
@@ -3942,14 +5464,16 @@ async function startMcpServer(opts) {
3942
5464
  try {
3943
5465
  const statusResult = await transport.getStatus();
3944
5466
  const instanceId = typeof statusResult?.status?.instanceId === "string" ? statusResult.status.instanceId.trim() : "";
5467
+ const hostname = typeof statusResult?.status?.hostname === "string" ? statusResult.status.hostname.trim() : typeof statusResult?.status?.machine?.hostname === "string" ? statusResult.status.machine.hostname.trim() : "";
3945
5468
  if (instanceId) localDaemonId = instanceId;
5469
+ if (hostname) coordinatorHostname = hostname;
3946
5470
  } catch {
3947
5471
  }
3948
5472
  }
3949
- const meshCtx = { mesh, transport, ...localDaemonId ? { localDaemonId } : {}, ...localMachineId ? { localMachineId } : {} };
5473
+ const meshCtx = { mesh, transport, ...localDaemonId ? { localDaemonId } : {}, ...localMachineId ? { localMachineId } : {}, ...coordinatorHostname ? { coordinatorHostname } : {} };
3950
5474
  const coordinatorPrompt = await buildMeshModeCoordinatorPrompt(mesh);
3951
5475
  const server2 = new import_server.Server(
3952
- { name: "adhdev-mcp-server", version: "0.9.81" },
5476
+ { name: "adhdev-mcp-server", version: "0.9.82" },
3953
5477
  { capabilities: { tools: {}, resources: {} } }
3954
5478
  );
3955
5479
  const { ListResourcesRequestSchema, ReadResourceRequestSchema } = await import("@modelcontextprotocol/sdk/types.js");
@@ -3975,7 +5499,7 @@ async function startMcpServer(opts) {
3975
5499
  let text;
3976
5500
  switch (name) {
3977
5501
  case "mesh_status":
3978
- text = await meshStatus(meshCtx);
5502
+ text = await meshStatus(meshCtx, a);
3979
5503
  break;
3980
5504
  case "mesh_list_nodes":
3981
5505
  text = await meshListNodes(meshCtx);
@@ -4007,6 +5531,9 @@ async function startMcpServer(opts) {
4007
5531
  case "mesh_git_status":
4008
5532
  text = await meshGitStatus(meshCtx, a);
4009
5533
  break;
5534
+ case "mesh_fast_forward_node":
5535
+ text = await meshFastForwardNode(meshCtx, a);
5536
+ break;
4010
5537
  case "mesh_checkpoint":
4011
5538
  text = await meshCheckpoint(meshCtx, a);
4012
5539
  break;
@@ -4022,6 +5549,18 @@ async function startMcpServer(opts) {
4022
5549
  case "mesh_refine_node":
4023
5550
  text = await meshRefineNode(meshCtx, a);
4024
5551
  break;
5552
+ case "mesh_refine_config_schema":
5553
+ text = await meshRefineConfigSchema(meshCtx);
5554
+ break;
5555
+ case "mesh_validate_refine_config":
5556
+ text = await meshValidateRefineConfig(meshCtx, a);
5557
+ break;
5558
+ case "mesh_suggest_refine_config":
5559
+ text = await meshSuggestRefineConfig(meshCtx, a);
5560
+ break;
5561
+ case "mesh_refine_plan":
5562
+ text = await meshRefinePlan(meshCtx, a);
5563
+ break;
4025
5564
  case "mesh_cleanup_sessions":
4026
5565
  text = await meshCleanupSessions(meshCtx, a);
4027
5566
  break;
@@ -4053,6 +5592,7 @@ async function startMcpServer(opts) {
4053
5592
  CHECK_PENDING_TOOL,
4054
5593
  READ_CHAT_TOOL,
4055
5594
  READ_CHAT_DEBUG_TOOL,
5595
+ SPEC_DEBUG_TOOL,
4056
5596
  SEND_CHAT_TOOL,
4057
5597
  APPROVE_TOOL,
4058
5598
  GIT_STATUS_TOOL,
@@ -4088,6 +5628,10 @@ async function startMcpServer(opts) {
4088
5628
  const text = await readChatDebug(transport, a);
4089
5629
  return { content: [{ type: "text", text }] };
4090
5630
  }
5631
+ case "spec_debug": {
5632
+ const text = await specDebug(transport, a);
5633
+ return { content: [{ type: "text", text }] };
5634
+ }
4091
5635
  case "send_chat": {
4092
5636
  const text = await sendChat(transport, { message: a.message, session_id: a.session_id, daemon_id: a.daemon_id });
4093
5637
  return { content: [{ type: "text", text }] };