@adhdev/daemon-standalone 0.9.82-rc.24 → 0.9.82-rc.241

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,87 +185,46 @@ 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
  };
139
226
 
140
- // src/transports/mode.ts
141
- function isLocalTransport(transport) {
142
- return typeof transport.command === "function";
143
- }
144
-
145
227
  // src/tools/chat-compact.ts
146
- function isAssistantLike(message) {
147
- const role = String(message?.role ?? "").toLowerCase();
148
- return role === "assistant" || role === "agent";
149
- }
150
228
  function messageContent(message) {
151
229
  const content = message?.content;
152
230
  if (typeof content === "string") return content;
@@ -165,11 +243,36 @@ function isCoordinatorVisibleMessage(message) {
165
243
  if (meta?.internal === true || meta?.debug === true || meta?.control === true || meta?.userVisible === false || meta?.user_visible === false) return false;
166
244
  return role === "user" || role === "assistant" || role === "agent";
167
245
  }
246
+ function summarizeToolMessage(message) {
247
+ if (!message || typeof message !== "object") return null;
248
+ const kind = String(message.kind ?? message.type ?? message.messageKind ?? "").toLowerCase();
249
+ const role = String(message.role ?? "").toLowerCase();
250
+ if (kind === "terminal" || kind === "bash") {
251
+ const cmd = message.command ?? message.cmd ?? message.input ?? messageContent(message);
252
+ const exit = message.exitCode ?? message.exit_code ?? message.code;
253
+ const cmdShort = typeof cmd === "string" ? cmd.split("\n")[0].slice(0, 120) : null;
254
+ if (!cmdShort) return null;
255
+ return exit !== void 0 && exit !== null ? `[Bash] ${cmdShort} \u2192 exit ${exit}` : `[Bash] ${cmdShort}`;
256
+ }
257
+ if (kind === "tool_call" || kind === "tool" || role === "tool") {
258
+ const name = message.name ?? message.toolName ?? message.tool_name ?? message.function?.name;
259
+ if (typeof name === "string" && name.trim()) return `[Tool] ${name.trim()}`;
260
+ return null;
261
+ }
262
+ if (kind === "tool_result") {
263
+ const exit = message.exitCode ?? message.exit_code ?? message.code;
264
+ const name = message.name ?? message.toolName ?? message.tool_name;
265
+ const label = typeof name === "string" && name.trim() ? name.trim() : "tool";
266
+ return exit !== void 0 && exit !== null ? `[Tool result: ${label}] exit ${exit}` : null;
267
+ }
268
+ return null;
269
+ }
168
270
  function buildCompactMessageTail(visibleMessages, opts) {
169
- const summary = typeof opts.summary === "string" ? opts.summary.trim() : "";
170
- const shouldOmitSummaryMessage = !!summary && !!opts.finalAssistant && isAssistantLike(opts.finalAssistant) && messageContent(opts.finalAssistant).trim() === summary;
171
- const sourceMessages = shouldOmitSummaryMessage ? visibleMessages.filter((message) => message !== opts.finalAssistant) : visibleMessages;
172
- return sourceMessages.slice(-opts.limit);
271
+ const tail = visibleMessages.slice(-opts.limit);
272
+ if (opts.finalAssistant && !tail.includes(opts.finalAssistant)) {
273
+ return [opts.finalAssistant, ...tail];
274
+ }
275
+ return tail;
173
276
  }
174
277
  function compactChatPayload(payload, opts = {}) {
175
278
  const rawMessages = Array.isArray(payload?.messages) ? payload.messages : [];
@@ -181,6 +284,9 @@ function compactChatPayload(payload, opts = {}) {
181
284
  });
182
285
  const summary = typeof payload?.summary === "string" && payload.summary.trim() ? payload.summary.trim() : messageContent(finalAssistant).trim();
183
286
  const messages = buildCompactMessageTail(visible, { summary, finalAssistant, limit });
287
+ const toolSummaries = rawMessages.filter((m) => !isCoordinatorVisibleMessage(m)).map(summarizeToolMessage).filter((s) => s !== null);
288
+ const omittedMessages = Math.max(0, rawMessages.length - messages.length);
289
+ const filteredMessages = Math.max(0, rawMessages.length - visible.length);
184
290
  return {
185
291
  success: payload?.success !== false,
186
292
  compact: true,
@@ -190,8 +296,9 @@ function compactChatPayload(payload, opts = {}) {
190
296
  providerSessionId: payload?.providerSessionId ?? null,
191
297
  totalMessages: rawMessages.length,
192
298
  visibleMessages: visible.length,
193
- filteredMessages: visible.length,
194
- omittedMessages: Math.max(0, rawMessages.length - visible.length),
299
+ filteredMessages,
300
+ omittedMessages,
301
+ ...toolSummaries.length > 0 ? { toolSummaries } : {},
195
302
  summary,
196
303
  ...payload?.changedFiles !== void 0 ? { changedFiles: payload.changedFiles } : {},
197
304
  ...payload?.testsRun !== void 0 ? { testsRun: payload.testsRun } : {},
@@ -241,17 +348,63 @@ function annotateRapidReadChatAdvisory(payload, options) {
241
348
 
242
349
  // src/tools/mesh-tools.ts
243
350
  var import_daemon_core = require("@adhdev/daemon-core");
351
+ var SESSION_PROVIDER_METADATA_TTL_MS = 30 * 6e4;
244
352
  var meshSessionProviderMetadata = /* @__PURE__ */ new Map();
353
+ function getSessionMetadata(key) {
354
+ const entry = meshSessionProviderMetadata.get(key);
355
+ if (!entry) return void 0;
356
+ if (entry.expiresAt <= Date.now()) {
357
+ meshSessionProviderMetadata.delete(key);
358
+ return void 0;
359
+ }
360
+ return entry;
361
+ }
362
+ var ACTIVE_WORK_POLLING_BACKOFF_MS = 6e4;
363
+ function buildActiveWorkPollingGuidance(summary, now = Date.now()) {
364
+ if (!summary || summary.generatingCount <= 0) return void 0;
365
+ return {
366
+ activeGeneratingWork: true,
367
+ generatingCount: summary.generatingCount,
368
+ doNotPollBefore: new Date(now + ACTIVE_WORK_POLLING_BACKOFF_MS).toISOString(),
369
+ eventSurface: "pendingCoordinatorEvents",
370
+ 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.",
371
+ 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."
372
+ };
373
+ }
245
374
  function readString(value) {
246
375
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
247
376
  }
377
+ function summarizeTaskMessage(message) {
378
+ const taskSummary = message.replace(/\s+/g, " ").trim();
379
+ const taskTitle = taskSummary.length > 96 ? `${taskSummary.slice(0, 93)}...` : taskSummary;
380
+ return { taskTitle: taskTitle || "(untitled task)", taskSummary };
381
+ }
382
+ function buildDirectTaskPayload(message, via, opts) {
383
+ const descriptor = summarizeTaskMessage(message);
384
+ return {
385
+ source: "direct",
386
+ via,
387
+ taskId: opts.taskId,
388
+ message,
389
+ taskTitle: descriptor.taskTitle,
390
+ taskSummary: descriptor.taskSummary,
391
+ ...opts.taskMode ? { taskMode: opts.taskMode } : {},
392
+ ...opts.providerType ? { providerType: opts.providerType } : {},
393
+ ...opts.targetSessionId ? { targetSessionId: opts.targetSessionId } : {},
394
+ ...opts.dispatchedToIdleSession !== void 0 ? { dispatchedToIdleSession: opts.dispatchedToIdleSession } : {}
395
+ };
396
+ }
397
+ function findNode(mesh, nodeId) {
398
+ const node = mesh.nodes.find((n) => n.id === nodeId);
399
+ if (!node) throw new Error(`Node '${nodeId}' is not a member of mesh '${mesh.name}'`);
400
+ return node;
401
+ }
248
402
  var DUPLICATE_DISPATCH_WINDOW_MS = 6e4;
249
403
  var STALE_ASSIGNED_QUEUE_MS = 30 * 6e4;
250
404
  var OLD_HISTORICAL_QUEUE_RECORD_MS = 7 * 24 * 60 * 6e4;
251
405
  var ACTIVE_QUEUE_STATUSES = /* @__PURE__ */ new Set(["pending", "assigned"]);
252
406
  var HISTORICAL_QUEUE_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
253
407
  async function refreshMeshFromDaemon(ctx) {
254
- if (!(ctx.transport instanceof IpcTransport)) return;
255
408
  try {
256
409
  const result = await ctx.transport.command("get_mesh", { meshId: ctx.mesh.id });
257
410
  if (!result?.success || !Array.isArray(result.mesh?.nodes)) return;
@@ -412,6 +565,25 @@ function extractStatusMetadataSessions(value) {
412
565
  function resolveSessionProviderType(session) {
413
566
  return readString(session?.providerType) || readString(session?.cliType) || readString(session?.agentType) || "";
414
567
  }
568
+ function isMeshCoordinatorSessionRecord(session) {
569
+ return Boolean(
570
+ readString(session?.settings?.meshCoordinatorFor) || readString(session?.meta?.meshCoordinatorFor) || readString(session?.metadata?.meshCoordinatorFor) || readString(session?.meshCoordinatorFor)
571
+ );
572
+ }
573
+ function isUnmanagedSessionRecord(session) {
574
+ const hasMeshNodeFor = Boolean(
575
+ readString(session?.settings?.meshNodeFor) || readString(session?.meta?.meshNodeFor) || readString(session?.metadata?.meshNodeFor) || readString(session?.meshNodeFor)
576
+ );
577
+ if (hasMeshNodeFor) return false;
578
+ if (isMeshCoordinatorSessionRecord(session)) return false;
579
+ const launchedByCoordinator = Boolean(
580
+ session?.settings?.launchedByCoordinator === true || session?.meta?.launchedByCoordinator === true || session?.launchedByCoordinator === true
581
+ );
582
+ return !launchedByCoordinator;
583
+ }
584
+ function isWorkerTaskMode(taskMode) {
585
+ return taskMode !== "live_debug_readonly";
586
+ }
415
587
  function addSessionRecord(target, session) {
416
588
  if (!session || typeof session !== "object" || isTerminalSessionRecord(session)) return;
417
589
  const sessionId = readSessionRecordId(session);
@@ -480,18 +652,26 @@ function queueAssignmentStaleReason(task, liveness) {
480
652
  }
481
653
  function buildQueueStatusSummary(queue) {
482
654
  const counts = { pending: 0, assigned: 0, completed: 0, failed: 0, cancelled: 0 };
655
+ let staleAssigned = 0;
483
656
  for (const task of queue) {
484
657
  const status = typeof task?.status === "string" ? task.status : void 0;
485
658
  if (status && Object.prototype.hasOwnProperty.call(counts, status)) {
486
659
  counts[status] += 1;
487
660
  }
661
+ if (status === "assigned" && task?.staleAssigned === true) staleAssigned += 1;
488
662
  }
663
+ const liveAssigned = Math.max(0, counts.assigned - staleAssigned);
489
664
  return {
490
665
  totalCount: queue.length,
491
- activeCount: counts.pending + counts.assigned,
666
+ activeCount: counts.pending + liveAssigned,
492
667
  historicalCount: counts.completed + counts.failed + counts.cancelled,
493
668
  counts,
494
669
  activeCounts: {
670
+ pending: counts.pending,
671
+ assigned: liveAssigned
672
+ },
673
+ staleAssignedCount: staleAssigned,
674
+ rawActiveCounts: {
495
675
  pending: counts.pending,
496
676
  assigned: counts.assigned
497
677
  },
@@ -519,6 +699,18 @@ function filterQueueForView(queue, view, statuses) {
519
699
  if (view === "historical") return queue.filter((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
520
700
  return queue;
521
701
  }
702
+ function prioritizeActiveQueueRows(queue) {
703
+ const active = [];
704
+ const historical = [];
705
+ const other = [];
706
+ for (const task of queue) {
707
+ const status = String(task?.status || "");
708
+ if (ACTIVE_QUEUE_STATUSES.has(status)) active.push(task);
709
+ else if (HISTORICAL_QUEUE_STATUSES.has(status)) historical.push(task);
710
+ else other.push(task);
711
+ }
712
+ return [...active, ...other, ...historical];
713
+ }
522
714
  function slimQueueTask(task) {
523
715
  return {
524
716
  id: task?.id,
@@ -612,6 +804,169 @@ function unwrapCommandPayload(value) {
612
804
  }
613
805
  return current;
614
806
  }
807
+ function isDirectDispatchLedgerEntry(entry) {
808
+ if (entry?.kind !== "task_dispatched") return false;
809
+ const payload = entry.payload || {};
810
+ const via = readString(payload.via);
811
+ return payload.source === "direct" || via === "p2p_direct" || via === "local_direct" || via === "mesh_send_task";
812
+ }
813
+ function readMessageTimestampIso(message) {
814
+ for (const value of [message?.timestamp, message?.createdAt, message?.created_at, message?.updatedAt, message?.time]) {
815
+ if (typeof value === "number" && Number.isFinite(value)) {
816
+ const ms = value > 1e10 ? value : value * 1e3;
817
+ return new Date(ms).toISOString();
818
+ }
819
+ if (typeof value === "string" && value.trim()) {
820
+ const ms = new Date(value.trim()).getTime();
821
+ if (Number.isFinite(ms)) return new Date(ms).toISOString();
822
+ }
823
+ }
824
+ return void 0;
825
+ }
826
+ function readFinalAssistantTranscriptEvidence(payload) {
827
+ const rawMessages = Array.isArray(payload?.messages) ? payload.messages : [];
828
+ const finalAssistant = [...rawMessages].reverse().filter(isCoordinatorVisibleMessage).find((message) => {
829
+ const role = String(message?.role ?? "").toLowerCase();
830
+ return (role === "assistant" || role === "agent") && messageContent(message).trim();
831
+ });
832
+ const finalSummary = messageContent(finalAssistant).trim() || (typeof payload?.summary === "string" && payload.summary.trim() ? payload.summary.trim() : void 0);
833
+ return {
834
+ finalSummary,
835
+ transcriptMessageAt: finalAssistant ? readMessageTimestampIso(finalAssistant) : void 0
836
+ };
837
+ }
838
+ function findNodeSession(nodes, nodeId, sessionId) {
839
+ if (!nodeId || !sessionId) return {};
840
+ const node = nodes.find((candidate) => readString(candidate?.id) === nodeId || readString(candidate?.nodeId) === nodeId);
841
+ if (!node) return {};
842
+ const sessions = Array.isArray(node.sessions) ? node.sessions : [];
843
+ const session = sessions.find((candidate) => readSessionRecordId(candidate) === sessionId);
844
+ return { node, session };
845
+ }
846
+ function buildDirectDispatchReconciliationCandidates(directDispatches, ledgerEntries) {
847
+ const candidates = [];
848
+ const seenTaskIds = /* @__PURE__ */ new Set();
849
+ for (const dispatch of directDispatches || []) {
850
+ const taskId = readString(dispatch?.taskId);
851
+ if (!taskId || seenTaskIds.has(taskId)) continue;
852
+ seenTaskIds.add(taskId);
853
+ candidates.push(dispatch);
854
+ }
855
+ for (const entry of ledgerEntries || []) {
856
+ if (!isDirectDispatchLedgerEntry(entry)) continue;
857
+ const taskId = readString(entry.payload?.taskId);
858
+ if (!taskId || seenTaskIds.has(taskId)) continue;
859
+ seenTaskIds.add(taskId);
860
+ candidates.push({
861
+ taskId,
862
+ nodeId: entry.nodeId,
863
+ sessionId: entry.sessionId,
864
+ providerType: entry.providerType || readString(entry.payload?.providerType),
865
+ message: readString(entry.payload?.message),
866
+ dispatchedAt: entry.timestamp,
867
+ via: readString(entry.payload?.via)
868
+ });
869
+ }
870
+ return candidates;
871
+ }
872
+ async function reconcileDirectDispatchesFromTranscriptEvidence(ctx, liveNodes, directDispatches, ledgerEntries) {
873
+ let attempted = 0;
874
+ let reconciled = 0;
875
+ let skipped = 0;
876
+ const candidates = buildDirectDispatchReconciliationCandidates(directDispatches, ledgerEntries);
877
+ for (const dispatch of candidates) {
878
+ const taskId = readString(dispatch?.taskId);
879
+ const nodeId = readString(dispatch?.nodeId);
880
+ const sessionId = readString(dispatch?.sessionId);
881
+ if (!taskId || !nodeId || !sessionId) {
882
+ skipped += 1;
883
+ continue;
884
+ }
885
+ const { session } = findNodeSession(liveNodes, nodeId, sessionId);
886
+ if (!session || !isIdleSessionRecord(session)) {
887
+ skipped += 1;
888
+ continue;
889
+ }
890
+ const node = await findOptionalNodeWithRefresh(ctx, nodeId).catch(() => null);
891
+ if (!node) {
892
+ skipped += 1;
893
+ continue;
894
+ }
895
+ const providerType = readString(dispatch?.providerType) || resolveSessionProviderType(session);
896
+ const providerSessionId = readString(session?.providerSessionId) || readString(session?.activeChat?.providerSessionId) || readString(session?.settings?.providerSessionId) || resolveMeshSessionProviderMetadata(ctx, nodeId, sessionId)?.providerSessionId;
897
+ attempted += 1;
898
+ try {
899
+ const readResult = await commandForNode(ctx, node, "read_chat", {
900
+ sessionId,
901
+ targetSessionId: sessionId,
902
+ workspace: node.workspace,
903
+ ...providerType ? { agentType: providerType, providerType } : {},
904
+ ...providerSessionId ? { providerSessionId } : {},
905
+ tailLimit: 10
906
+ });
907
+ const payload = unwrapCommandPayload(readResult);
908
+ if (payload?.success === false) continue;
909
+ const evidence = readFinalAssistantTranscriptEvidence(payload);
910
+ if (!evidence.finalSummary) continue;
911
+ const result = (0, import_daemon_core.reconcileDirectDispatchCompletionFromTranscript)({
912
+ meshId: ctx.mesh.id,
913
+ nodeId,
914
+ sessionId,
915
+ providerType,
916
+ providerSessionId: readString(payload?.providerSessionId) || providerSessionId,
917
+ taskId,
918
+ finalSummary: evidence.finalSummary,
919
+ transcriptMessageAt: evidence.transcriptMessageAt,
920
+ targetCoordinatorDaemonId: ctx.localDaemonId,
921
+ source: "mcp_mesh_status_transcript_reconciliation"
922
+ });
923
+ if (result.reconciled) reconciled += 1;
924
+ } catch {
925
+ skipped += 1;
926
+ }
927
+ }
928
+ return { attempted, reconciled, skipped };
929
+ }
930
+ async function triggerMeshQueueAndReport(ctx, node, opts) {
931
+ try {
932
+ let raw;
933
+ if (ctx.transport instanceof IpcTransport && node?.daemonId && opts?.localNode === false) {
934
+ raw = await ctx.transport.meshCommand(node.daemonId, "trigger_mesh_queue", { meshId: ctx.mesh.id });
935
+ } else {
936
+ raw = await ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id });
937
+ }
938
+ const payload = unwrapCommandPayload(raw);
939
+ const trigger = payload?.trigger && typeof payload.trigger === "object" ? payload.trigger : payload;
940
+ return trigger && typeof trigger === "object" ? trigger : { success: true };
941
+ } catch (e) {
942
+ return {
943
+ success: false,
944
+ error: e?.message || String(e)
945
+ };
946
+ }
947
+ }
948
+ function buildQueueTriggerGuidance(queueTrigger) {
949
+ if (!queueTrigger || queueTrigger.claimed === true) return void 0;
950
+ if (queueTrigger.success === false) {
951
+ return {
952
+ queueClaimed: false,
953
+ queueDispatchState: "trigger_failed",
954
+ nextAction: "Do not assume the queued task is running. Check mesh_view_queue and daemon connectivity before redispatching."
955
+ };
956
+ }
957
+ if (queueTrigger.noIdleMeshSessionAvailable === true) {
958
+ return {
959
+ queueClaimed: false,
960
+ queueDispatchState: "pending_no_idle_mesh_session",
961
+ 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."
962
+ };
963
+ }
964
+ return {
965
+ queueClaimed: false,
966
+ queueDispatchState: "pending_or_waiting_for_ready",
967
+ 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."
968
+ };
969
+ }
615
970
  function isTerminalSessionRecord(session) {
616
971
  const status = typeof session?.status === "string" ? session.status.toLowerCase() : "";
617
972
  const lifecycle = typeof session?.lifecycle === "string" ? session.lifecycle.toLowerCase() : "";
@@ -627,16 +982,23 @@ function isIdleSessionRecord(session) {
627
982
  function isMeshOwnedDelegateSession(session, meshId, nodeId) {
628
983
  const settings = session?.settings;
629
984
  const sessionMeshId = typeof settings?.meshNodeFor === "string" ? settings.meshNodeFor.trim() : "";
630
- const coordinatorDaemonId = typeof settings?.meshCoordinatorDaemonId === "string" ? settings.meshCoordinatorDaemonId.trim() : "";
631
985
  const sessionNodeId = typeof settings?.meshNodeId === "string" ? settings.meshNodeId.trim() : "";
632
- if (sessionMeshId !== meshId || !coordinatorDaemonId) return false;
986
+ if (sessionMeshId !== meshId) return false;
633
987
  return !sessionNodeId || sessionNodeId === nodeId;
634
988
  }
989
+ function hasRemoteRelayMetadata(session) {
990
+ return Boolean(
991
+ readString(session?.settings?.meshCoordinatorDaemonId) || readString(session?.meta?.meshCoordinatorDaemonId) || readString(session?.metadata?.meshCoordinatorDaemonId) || readString(session?.meshCoordinatorDaemonId)
992
+ );
993
+ }
994
+ function isRelaySafeRemoteDelegateSession(session, meshId, nodeId) {
995
+ return isMeshOwnedDelegateSession(session, meshId, nodeId) && hasRemoteRelayMetadata(session);
996
+ }
635
997
  function chooseDispatchableSession(sessions, providerType, meshId, nodeId) {
636
998
  const live = sessions.filter((session) => !isTerminalSessionRecord(session));
637
999
  const matchingProvider = (session) => !providerType || session?.providerType === providerType || session?.cliType === providerType;
638
1000
  const meshSessions = live.filter(
639
- (session) => isMeshOwnedDelegateSession(session, meshId, nodeId)
1001
+ (session) => isRelaySafeRemoteDelegateSession(session, meshId, nodeId)
640
1002
  );
641
1003
  return meshSessions.find((session) => isIdleSessionRecord(session) && matchingProvider(session)) || meshSessions.find(matchingProvider) || void 0;
642
1004
  }
@@ -653,8 +1015,9 @@ function buildRelayUnsafeRemoteSessionFailure(ctx, node, sessionId, providerType
653
1015
  daemonId: node.daemonId,
654
1016
  workspace: node.workspace,
655
1017
  sessionId,
1018
+ unsafeTranscriptAlias: true,
656
1019
  ...providerType ? { resolvedProviderType: providerType } : {},
657
- error: `Remote session '${sessionId}' is not relay-safe for mesh '${ctx.mesh.id}': missing meshNodeFor/meshCoordinatorDaemonId metadata, so completion events would not reach the coordinator ledger.`,
1020
+ error: `Remote session '${sessionId}' is not relay-safe for mesh '${ctx.mesh.id}': missing meshNodeFor/meshCoordinatorDaemonId metadata, so completion events would not reach the coordinator ledger. This session may be the coordinator itself or an unrelated session (unsafe_transcript_alias risk).`,
658
1021
  nextAction: `Launch a fresh relay-safe session with mesh_launch_session(node_id: '${node.id}'${providerType ? `, type: '${providerType}'` : ""}) or dispatch without session_id so Repo Mesh can choose a valid delegate session.`,
659
1022
  noFallbackReason: "Blindly reusing a remote session without mesh relay metadata would silently drop task_completed / generating_completed events."
660
1023
  };
@@ -704,12 +1067,16 @@ function extractGitDiff(value) {
704
1067
  }
705
1068
  function extractSubmodules(value, ignorePaths) {
706
1069
  const payload = unwrapCommandPayload(value);
707
- const subs = payload?.submodules ?? value?.submodules;
1070
+ const subs = payload?.status?.submodules ?? payload?.submodules ?? value?.status?.submodules ?? value?.submodules;
708
1071
  if (!Array.isArray(subs)) return void 0;
709
1072
  if (ignorePaths.length === 0) return subs;
710
1073
  const ignoreSet = new Set(ignorePaths);
711
1074
  return subs.filter((s) => s?.path && !ignoreSet.has(s.path));
712
1075
  }
1076
+ function assignFullGitSnapshot(entry, status) {
1077
+ if (!status || typeof status !== "object" || Array.isArray(status)) return;
1078
+ entry.git = status;
1079
+ }
713
1080
  function extractLaunchPayload(value) {
714
1081
  return findNestedPayload(value, (payload) => Boolean(payload?.sessionId || payload?.id || payload?.runtimeSessionId));
715
1082
  }
@@ -834,7 +1201,20 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
834
1201
  let sessionId = args.session_id?.trim() || "";
835
1202
  const providerPriorityList = Array.isArray(node.policy?.providerPriority) ? node.policy.providerPriority : [];
836
1203
  let resolvedProviderType = args.providerType?.trim() || providerPriorityList[0] || "";
837
- if (!sessionId || args.session_id) {
1204
+ if (sessionId && args.verifiedSession) {
1205
+ const explicitSession = args.verifiedSession;
1206
+ if (!isRelaySafeRemoteDelegateSession(explicitSession, ctx.mesh.id, node.id)) {
1207
+ return buildRelayUnsafeRemoteSessionFailure(
1208
+ ctx,
1209
+ node,
1210
+ sessionId,
1211
+ resolvedProviderType || resolveSessionProviderType(explicitSession) || void 0
1212
+ );
1213
+ }
1214
+ if (!resolvedProviderType) {
1215
+ resolvedProviderType = resolveSessionProviderType(explicitSession);
1216
+ }
1217
+ } else if (!sessionId || args.session_id) {
838
1218
  try {
839
1219
  const relayResult = await transport.meshCommand(daemonId, "get_status_metadata", {});
840
1220
  const sessions = extractStatusMetadataSessions(relayResult);
@@ -858,7 +1238,7 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
858
1238
  nextAction: `Launch a fresh session with mesh_launch_session(node_id: '${node.id}'${resolvedProviderType ? `, type: '${resolvedProviderType}'` : ""}) or retry without session_id so Repo Mesh can target a live delegate session.`
859
1239
  };
860
1240
  }
861
- if (!isMeshOwnedDelegateSession(explicitSession, ctx.mesh.id, node.id)) {
1241
+ if (!isRelaySafeRemoteDelegateSession(explicitSession, ctx.mesh.id, node.id)) {
862
1242
  return buildRelayUnsafeRemoteSessionFailure(
863
1243
  ctx,
864
1244
  node,
@@ -902,7 +1282,8 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
902
1282
  agentType: resolvedProviderType,
903
1283
  cliType: resolvedProviderType,
904
1284
  action: "send_chat",
905
- message: args.message
1285
+ message: args.message,
1286
+ ...args.meshContext ? { meshContext: args.meshContext } : {}
906
1287
  });
907
1288
  const dispatchPayload = unwrapCommandPayload(dispatchResult);
908
1289
  if (dispatchPayload?.success === false || dispatchResult?.success === false) {
@@ -920,7 +1301,7 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
920
1301
  error: `P2P dispatch failed: ${errorMessage}`
921
1302
  };
922
1303
  }
923
- return { success: true, dispatched: true, sessionId: sessionId || resolvedProviderType };
1304
+ return { success: true, dispatched: true, sessionId: sessionId || resolvedProviderType, providerType: resolvedProviderType };
924
1305
  } catch (e) {
925
1306
  const errorMessage = e?.message || String(e);
926
1307
  return {
@@ -950,34 +1331,197 @@ function resolveCoordinatorNode(ctx) {
950
1331
  return void 0;
951
1332
  }
952
1333
  function readNodeMachineId(node) {
953
- return readString(node.machineId) || readString(node.machine_id);
1334
+ return readString(node.machineId) || readString(node.machine_id) || readString(node.machine?.id) || readString(node.machine?.machineId) || readString(node.lastProbe?.machineId) || readString(node.last_probe?.machine_id) || readString(node.lastProbe?.machine?.id) || readString(node.lastProbe?.machine?.machineId) || readString(node.last_probe?.machine?.id) || readString(node.last_probe?.machine?.machine_id);
954
1335
  }
955
1336
  function readNodeDaemonId(node) {
956
- return readString(node.daemonId) || readString(node.daemon_id);
1337
+ 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);
1338
+ }
1339
+ function normalizeHostname(value) {
1340
+ const hostname = readString(value);
1341
+ if (!hostname) return void 0;
1342
+ return hostname.toLowerCase().replace(/\.$/, "");
1343
+ }
1344
+ function readNodeHostname(node) {
1345
+ 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);
1346
+ }
1347
+ function readNodeDisplayMachineName(node) {
1348
+ 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);
1349
+ }
1350
+ function compactIdentityEvidence(value) {
1351
+ if (!value) return void 0;
1352
+ return value.length > 24 ? `${value.slice(0, 12)}\u2026${value.slice(-8)}` : value;
1353
+ }
1354
+ function pushIdentityEvidence(evidence, label, value) {
1355
+ const compact = compactIdentityEvidence(value);
1356
+ if (compact) evidence.push(`${label}:${compact}`);
1357
+ }
1358
+ function buildNodeMachineIdentity(ctx, node) {
1359
+ const machineId = readNodeMachineId(node);
1360
+ const daemonId = readNodeDaemonId(node);
1361
+ const hostname = readNodeHostname(node);
1362
+ const machineName = readNodeDisplayMachineName(node);
1363
+ const coordinatorHostname = readString(ctx.coordinatorHostname);
1364
+ const localControlPlaneReason = getLocalControlPlaneMatchReason(ctx, node);
1365
+ const directLocal = !!localControlPlaneReason;
1366
+ const hostnameMatches = Boolean(
1367
+ normalizeHostname(hostname) && normalizeHostname(coordinatorHostname) && normalizeHostname(hostname) === normalizeHostname(coordinatorHostname)
1368
+ );
1369
+ const sameMachine = directLocal || hostnameMatches;
1370
+ const evidence = [];
1371
+ pushIdentityEvidence(evidence, "machineName", machineName);
1372
+ pushIdentityEvidence(evidence, "hostname", hostname);
1373
+ pushIdentityEvidence(evidence, "machineId", machineId);
1374
+ pushIdentityEvidence(evidence, "daemonId", daemonId);
1375
+ if (localControlPlaneReason) {
1376
+ pushIdentityEvidence(evidence, "localMatch", localControlPlaneReason);
1377
+ pushIdentityEvidence(evidence, "localMachineId", ctx.localMachineId);
1378
+ pushIdentityEvidence(evidence, "localDaemonId", ctx.localDaemonId);
1379
+ }
1380
+ const locality = sameMachine ? "same_machine" : evidence.length > 0 ? "remote_known" : "remote_or_unknown";
1381
+ 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";
1382
+ return {
1383
+ daemonId,
1384
+ machineId,
1385
+ hostname,
1386
+ machineName,
1387
+ displayName: machineName || hostname || daemonId || machineId,
1388
+ coordinatorHostname,
1389
+ sameMachine,
1390
+ locality,
1391
+ localityReason,
1392
+ identityEvidence: evidence
1393
+ };
1394
+ }
1395
+ function nodeHasLocalDaemonEvidence(ctx, node) {
1396
+ const isLocal = (session) => {
1397
+ if (!session || typeof session !== "object") return false;
1398
+ if (ctx.localDaemonId && session.runtime?.owner === ctx.localDaemonId) return true;
1399
+ if (ctx.localDaemonId && session.daemonClient?.daemonId === ctx.localDaemonId) return true;
1400
+ return false;
1401
+ };
1402
+ const sessionArrays = [
1403
+ node?.sessions,
1404
+ node?.activeSessions,
1405
+ node?.active_sessions,
1406
+ node?.lastProbe?.sessions,
1407
+ node?.last_probe?.sessions,
1408
+ node?.lastProbe?.status?.sessions,
1409
+ node?.last_probe?.status?.sessions
1410
+ ];
1411
+ for (const arr of sessionArrays) {
1412
+ if (Array.isArray(arr) && arr.some(isLocal)) return true;
1413
+ }
1414
+ const sessionRecords = [
1415
+ node?.activeSession,
1416
+ node?.active_session,
1417
+ node?.currentSession,
1418
+ node?.current_session,
1419
+ node?.runtimeSession,
1420
+ node?.runtime_session,
1421
+ node?.session,
1422
+ node?.lastProbe?.activeSession,
1423
+ node?.last_probe?.active_session,
1424
+ node?.lastProbe?.currentSession,
1425
+ node?.last_probe?.current_session,
1426
+ node?.lastProbe?.session,
1427
+ node?.last_probe?.session
1428
+ ];
1429
+ for (const session of sessionRecords) {
1430
+ if (isLocal(session)) return true;
1431
+ }
1432
+ return false;
957
1433
  }
958
1434
  function isDirectLocalNode(ctx, node) {
959
1435
  const machineId = readNodeMachineId(node);
960
1436
  const daemonId = readNodeDaemonId(node);
961
1437
  return Boolean(
962
- ctx.localMachineId && machineId === ctx.localMachineId || ctx.localDaemonId && daemonId === ctx.localDaemonId
1438
+ ctx.localMachineId && machineId === ctx.localMachineId || ctx.localDaemonId && daemonId === ctx.localDaemonId || nodeHasLocalDaemonEvidence(ctx, node)
963
1439
  );
964
1440
  }
1441
+ function isConfiguredCoordinatorNode(ctx, node) {
1442
+ if (!ctx.localMachineId && !ctx.localDaemonId) return false;
1443
+ const nodeId = readString(node.id) || readString(node.nodeId) || readString(node.node_id);
1444
+ if (!nodeId) return false;
1445
+ const nodeDaemonId = readNodeDaemonId(node);
1446
+ const nodeMachineId = readNodeMachineId(node);
1447
+ if (nodeDaemonId && ctx.localDaemonId && nodeDaemonId !== ctx.localDaemonId) return false;
1448
+ if (nodeMachineId && ctx.localMachineId && nodeMachineId !== ctx.localMachineId) return false;
1449
+ const preferredNodeId = readString(ctx.mesh.coordinator?.preferredNodeId) || readString(ctx.mesh.coordinator?.preferred_node_id);
1450
+ if (preferredNodeId) return nodeId === preferredNodeId;
1451
+ const first = ctx.mesh.nodes?.[0];
1452
+ const firstNodeId = readString(first?.id) || readString(first?.nodeId) || readString(first?.node_id);
1453
+ return !!firstNodeId && nodeId === firstNodeId;
1454
+ }
1455
+ function getLocalControlPlaneMatchReason(ctx, node) {
1456
+ if (isDirectLocalNode(ctx, node)) return "matched coordinator daemon or machine id";
1457
+ if (isConfiguredCoordinatorNode(ctx, node)) return "matched configured coordinator node";
1458
+ if (node.isLocalWorktree === true) {
1459
+ const sourceNode = findClonedFromNode(ctx, node);
1460
+ if (sourceNode && isDirectLocalNode(ctx, sourceNode)) return "matched local cloned-from node";
1461
+ if (sourceNode && isConfiguredCoordinatorNode(ctx, sourceNode)) return "matched configured coordinator source node";
1462
+ }
1463
+ return void 0;
1464
+ }
965
1465
  function findClonedFromNode(ctx, node) {
966
1466
  const clonedFromNodeId = readString(node.clonedFromNodeId) || readString(node.cloned_from_node_id);
967
1467
  if (!clonedFromNodeId) return void 0;
968
1468
  return ctx.mesh.nodes.find((n) => n.id === clonedFromNodeId || n.nodeId === clonedFromNodeId || n.node_id === clonedFromNodeId);
969
1469
  }
970
1470
  function isLocalControlPlaneNode(ctx, node) {
971
- if (isDirectLocalNode(ctx, node)) return true;
972
- if (node.isLocalWorktree === true) {
973
- const sourceNode = findClonedFromNode(ctx, node);
974
- if (sourceNode && isDirectLocalNode(ctx, sourceNode)) return true;
975
- }
976
- return false;
1471
+ return !!getLocalControlPlaneMatchReason(ctx, node);
977
1472
  }
978
1473
  function meshSessionCacheKey(nodeId, runtimeSessionId) {
979
1474
  return `${nodeId}:${runtimeSessionId}`;
980
1475
  }
1476
+ function rememberMeshSessionProviderMetadata(nodeId, runtimeSessionId, metadata) {
1477
+ const keyNodeId = readString(nodeId);
1478
+ const keySessionId = readString(runtimeSessionId);
1479
+ if (!keyNodeId || !keySessionId) return;
1480
+ const providerType = readString(metadata.providerType);
1481
+ const providerSessionId = readString(metadata.providerSessionId);
1482
+ if (!providerType && !providerSessionId) return;
1483
+ const existing = getSessionMetadata(meshSessionCacheKey(keyNodeId, keySessionId)) || { providerType: "" };
1484
+ meshSessionProviderMetadata.set(meshSessionCacheKey(keyNodeId, keySessionId), {
1485
+ providerType: providerType || existing.providerType,
1486
+ providerSessionId: providerSessionId || existing.providerSessionId,
1487
+ expiresAt: Date.now() + SESSION_PROVIDER_METADATA_TTL_MS
1488
+ });
1489
+ }
1490
+ function rememberMeshSessionProviderMetadataFromEvent(event) {
1491
+ const metadataEvent = event?.metadataEvent && typeof event.metadataEvent === "object" ? event.metadataEvent : event && typeof event === "object" ? event : {};
1492
+ const nodeId = readString(event?.nodeId) || readString(metadataEvent.nodeId) || readString(metadataEvent.meshNodeId);
1493
+ const sessionId = readString(metadataEvent.targetSessionId) || readString(metadataEvent.sessionId) || readString(metadataEvent.instanceId) || readString(event?.sessionId);
1494
+ rememberMeshSessionProviderMetadata(nodeId, sessionId, {
1495
+ providerType: readString(metadataEvent.providerType) || readString(event?.providerType) || "",
1496
+ providerSessionId: readString(metadataEvent.providerSessionId) || readString(event?.providerSessionId)
1497
+ });
1498
+ }
1499
+ function resolveMeshSessionProviderMetadataFromLedger(ctx, nodeId, runtimeSessionId) {
1500
+ const entries = (0, import_daemon_core.readLedgerEntries)(ctx.mesh.id, { tail: 50 });
1501
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
1502
+ const entry = entries[i];
1503
+ const payload = entry.payload && typeof entry.payload === "object" && !Array.isArray(entry.payload) ? entry.payload : {};
1504
+ const entryNodeId = readString(entry.nodeId) || readString(payload.nodeId) || readString(payload.meshNodeId);
1505
+ if (entryNodeId && entryNodeId !== nodeId) continue;
1506
+ const entrySessionId = readString(entry.sessionId) || readString(payload.targetSessionId) || readString(payload.sessionId) || readString(payload.instanceId);
1507
+ if (entrySessionId !== runtimeSessionId) continue;
1508
+ const providerType = readString(entry.providerType) || readString(payload.providerType);
1509
+ const completionDiagnostic = payload.completionDiagnostic && typeof payload.completionDiagnostic === "object" && !Array.isArray(payload.completionDiagnostic) ? payload.completionDiagnostic : {};
1510
+ const metadataEvent = payload.metadataEvent && typeof payload.metadataEvent === "object" && !Array.isArray(payload.metadataEvent) ? payload.metadataEvent : {};
1511
+ const providerSessionId = readString(payload.providerSessionId) || readString(completionDiagnostic.providerSessionId) || readString(metadataEvent.providerSessionId);
1512
+ if (providerType || providerSessionId) {
1513
+ return { providerType: providerType || "", providerSessionId };
1514
+ }
1515
+ }
1516
+ return void 0;
1517
+ }
1518
+ function resolveMeshSessionProviderMetadata(ctx, nodeId, runtimeSessionId) {
1519
+ const cached = getSessionMetadata(meshSessionCacheKey(nodeId, runtimeSessionId));
1520
+ if (cached?.providerType || cached?.providerSessionId) return cached;
1521
+ const fromLedger = resolveMeshSessionProviderMetadataFromLedger(ctx, nodeId, runtimeSessionId);
1522
+ if (fromLedger) rememberMeshSessionProviderMetadata(nodeId, runtimeSessionId, fromLedger);
1523
+ return fromLedger;
1524
+ }
981
1525
  function countUncommittedChanges(status) {
982
1526
  if (typeof status?.uncommittedChanges === "number") return status.uncommittedChanges;
983
1527
  const keys = ["staged", "modified", "untracked", "deleted", "renamed"];
@@ -988,8 +1532,23 @@ function countUncommittedChanges(status) {
988
1532
  function isGitStatusDirty(status) {
989
1533
  if (typeof status?.isDirty === "boolean") return status.isDirty;
990
1534
  if (typeof status?.dirty === "boolean") return status.dirty;
1535
+ if (Array.isArray(status?.submodules) && status.submodules.some((submodule) => submodule?.dirty || submodule?.outOfSync || submodule?.error)) return true;
991
1536
  return countUncommittedChanges(status) > 0;
992
1537
  }
1538
+ function slimLedgerPayload(payload) {
1539
+ const slim = {};
1540
+ for (const [k, v] of Object.entries(payload)) {
1541
+ if (k === "message" || k === "taskSummary") {
1542
+ slim[k] = typeof v === "string" && v.length > 200 ? v.slice(0, 200) + "\u2026" : v;
1543
+ } else if (k === "evidence" || k === "workerResult" || k === "gitStatus" || k === "validationResults") {
1544
+ } else if (k === "finalSummary") {
1545
+ slim[k] = typeof v === "string" && v.length > 300 ? v.slice(0, 300) + "\u2026" : v;
1546
+ } else {
1547
+ slim[k] = v;
1548
+ }
1549
+ }
1550
+ return slim;
1551
+ }
993
1552
  function readRelatedRepos(node) {
994
1553
  const raw = Array.isArray(node.relatedRepos) ? node.relatedRepos : Array.isArray(node.policy?.relatedRepos) ? node.policy.relatedRepos : [];
995
1554
  return raw.map((entry) => ({
@@ -1024,7 +1583,7 @@ async function collectRelatedRepoStatuses(ctx, node) {
1024
1583
  const results = [];
1025
1584
  for (const repo of relatedRepos) {
1026
1585
  try {
1027
- 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 });
1586
+ const statusResult = await commandForNode(ctx, node, "git_status", { workspace: repo.workspace, refreshUpstream: true });
1028
1587
  const status = extractGitStatus(statusResult);
1029
1588
  results.push(summarizeRelatedRepoStatus(repo, status));
1030
1589
  } catch (e) {
@@ -1048,6 +1607,16 @@ function missingProviderPriorityMessage(nodeId) {
1048
1607
  return `Node '${nodeId}' has no providerPriority policy; pass type explicitly or configure node.policy.providerPriority`;
1049
1608
  }
1050
1609
  function getNodeLaunchReadiness(node) {
1610
+ const bootstrap = node.worktreeBootstrap;
1611
+ if (node.isLocalWorktree && bootstrap?.status === "failed" && bootstrap?.required !== false) {
1612
+ return {
1613
+ providerPriority: readProviderPriority(node.policy),
1614
+ launchReady: false,
1615
+ launchBlockedReason: "worktree_bootstrap_failed",
1616
+ launchBlockedMessage: typeof bootstrap.error === "string" && bootstrap.error.trim() ? bootstrap.error.trim() : "Required worktree bootstrap failed; resolve it before launching an agent into this node.",
1617
+ worktreeBootstrap: bootstrap
1618
+ };
1619
+ }
1051
1620
  const providerPriority = readProviderPriority(node.policy);
1052
1621
  if (providerPriority.length) {
1053
1622
  return {
@@ -1062,6 +1631,45 @@ function getNodeLaunchReadiness(node) {
1062
1631
  launchBlockedMessage: missingProviderPriorityMessage(node.id)
1063
1632
  };
1064
1633
  }
1634
+ function getWorktreeBootstrapLaunchBlock(node, meshPolicy) {
1635
+ if (!node.isLocalWorktree) return void 0;
1636
+ const bootstrap = node.worktreeBootstrap;
1637
+ const requireReady = !!(meshPolicy && typeof meshPolicy === "object" && meshPolicy.requireBootstrapBeforeLaunch === true);
1638
+ if (requireReady && bootstrap?.status !== "ready") {
1639
+ return {
1640
+ success: false,
1641
+ code: "bootstrap_not_ready",
1642
+ error: `Node '${node.id}' bootstrap state is '${bootstrap?.status ?? "unknown"}' and mesh policy requireBootstrapBeforeLaunch is enabled.`,
1643
+ nodeId: node.id,
1644
+ worktreeBootstrap: bootstrap ?? null,
1645
+ recoveryHint: "Run the worktree bootstrap (clone runOnClone or a refine with bootstrap inherit) until the node reports ready, or disable requireBootstrapBeforeLaunch."
1646
+ };
1647
+ }
1648
+ if (bootstrap?.status !== "failed" || bootstrap?.required === false) return void 0;
1649
+ return {
1650
+ success: false,
1651
+ code: "worktree_bootstrap_failed",
1652
+ error: typeof bootstrap.error === "string" && bootstrap.error.trim() ? bootstrap.error.trim() : `Node '${node.id}' has a failed required worktree bootstrap.`,
1653
+ nodeId: node.id,
1654
+ worktreeBootstrap: bootstrap,
1655
+ recoveryHint: "Fix the configured worktree bootstrap command or remove/recreate the worktree node before launching an agent."
1656
+ };
1657
+ }
1658
+ async function collectLiveStatusSessions(ctx, node) {
1659
+ try {
1660
+ const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
1661
+ return extractStatusMetadataSessions(statusResult);
1662
+ } catch {
1663
+ return [];
1664
+ }
1665
+ }
1666
+ async function collectMeshViewQueueNodesWithLiveSessions(ctx) {
1667
+ const nodes = await Promise.all(ctx.mesh.nodes.map(async (node) => {
1668
+ const liveSessions = await collectLiveStatusSessions(ctx, node);
1669
+ return liveSessions.length > 0 ? { ...node, sessions: liveSessions } : node;
1670
+ }));
1671
+ return nodes;
1672
+ }
1065
1673
  function readNumeric(value, fallback = 0) {
1066
1674
  const parsed = Number(value);
1067
1675
  return Number.isFinite(parsed) ? parsed : fallback;
@@ -1194,10 +1802,7 @@ async function commandForNode(ctx, node, command, args = {}) {
1194
1802
  if (ctx.transport instanceof IpcTransport && node.daemonId && !isLocalNode) {
1195
1803
  return ctx.transport.meshCommand(node.daemonId, command, args);
1196
1804
  }
1197
- if (isLocalTransport(ctx.transport)) {
1198
- return ctx.transport.command(command, args);
1199
- }
1200
- throw new Error(`Command '${command}' requires daemon IPC/local transport for node '${node.id}'`);
1805
+ return ctx.transport.command(command, args);
1201
1806
  }
1202
1807
  function normalizePendingMeshCoordinatorEvents(value) {
1203
1808
  const payload = unwrapCommandPayload(value);
@@ -1215,6 +1820,14 @@ function buildMeshForwardPayloadFromPendingEvent(event) {
1215
1820
  providerType: readString(metadataEvent.providerType),
1216
1821
  providerSessionId: readString(metadataEvent.providerSessionId),
1217
1822
  finalSummary: readString(metadataEvent.finalSummary) || readString(metadataEvent.summary),
1823
+ jobId: readString(metadataEvent.jobId),
1824
+ interactionId: readString(metadataEvent.interactionId),
1825
+ status: readString(metadataEvent.status),
1826
+ targetDaemonId: readString(metadataEvent.targetDaemonId),
1827
+ startedAt: readString(metadataEvent.startedAt),
1828
+ completedAt: readString(metadataEvent.completedAt),
1829
+ retryOfJobId: readString(metadataEvent.retryOfJobId),
1830
+ ...metadataEvent.result && typeof metadataEvent.result === "object" && !Array.isArray(metadataEvent.result) ? { result: metadataEvent.result } : {},
1218
1831
  ...metadataEvent.intentional === true ? { intentional: true } : {},
1219
1832
  ...metadataEvent.intentionalStop === true ? { intentionalStop: true } : {},
1220
1833
  ...metadataEvent.operatorCleanup === true ? { operatorCleanup: true } : {},
@@ -1229,10 +1842,16 @@ async function drainCoordinatorPendingEvents(ctx, opts) {
1229
1842
  const matchesCurrentMesh = (event) => readString(event?.meshId) === ctx.mesh.id;
1230
1843
  if (ctx.transport instanceof IpcTransport) {
1231
1844
  const surfacedEvents = [];
1845
+ const coordinatorDaemonId = readString(ctx.localDaemonId);
1846
+ const pendingEventArgs = {
1847
+ meshId: ctx.mesh.id,
1848
+ ...coordinatorDaemonId ? { coordinatorDaemonId } : {}
1849
+ };
1232
1850
  try {
1233
1851
  surfacedEvents.push(
1234
- ...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", { meshId: ctx.mesh.id })).filter(matchesCurrentMesh)
1852
+ ...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", pendingEventArgs)).filter(matchesCurrentMesh)
1235
1853
  );
1854
+ surfacedEvents.forEach(rememberMeshSessionProviderMetadataFromEvent);
1236
1855
  } catch {
1237
1856
  }
1238
1857
  for (const node of ctx.mesh.nodes) {
@@ -1240,29 +1859,30 @@ async function drainCoordinatorPendingEvents(ctx, opts) {
1240
1859
  if (requestedNodeIds && !requestedNodeIds.has(node.id)) continue;
1241
1860
  try {
1242
1861
  const remoteEvents = normalizePendingMeshCoordinatorEvents(
1243
- await ctx.transport.meshCommand(node.daemonId, "get_pending_mesh_events", { meshId: ctx.mesh.id })
1862
+ await ctx.transport.meshCommand(node.daemonId, "get_pending_mesh_events", pendingEventArgs)
1244
1863
  ).filter(matchesCurrentMesh);
1245
1864
  if (remoteEvents.length === 0) continue;
1246
1865
  for (const event of remoteEvents) {
1247
1866
  const payload = buildMeshForwardPayloadFromPendingEvent(event);
1248
1867
  if (!payload.event || !payload.meshId) continue;
1249
1868
  await ctx.transport.command("mesh_forward_event", payload);
1869
+ rememberMeshSessionProviderMetadataFromEvent({ ...event, metadataEvent: payload });
1250
1870
  }
1251
1871
  } catch {
1252
1872
  }
1253
1873
  }
1254
1874
  try {
1255
1875
  surfacedEvents.push(
1256
- ...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", { meshId: ctx.mesh.id })).filter(matchesCurrentMesh)
1876
+ ...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", pendingEventArgs)).filter(matchesCurrentMesh)
1257
1877
  );
1878
+ surfacedEvents.forEach(rememberMeshSessionProviderMetadataFromEvent);
1258
1879
  } catch {
1259
1880
  }
1260
1881
  return surfacedEvents;
1261
1882
  }
1262
- if (isLocalTransport(ctx.transport)) {
1263
- return (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id).filter(matchesCurrentMesh);
1264
- }
1265
- return [];
1883
+ const events = (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id, ctx.localDaemonId).filter(matchesCurrentMesh);
1884
+ events.forEach(rememberMeshSessionProviderMetadataFromEvent);
1885
+ return events;
1266
1886
  }
1267
1887
  function isP2pTransportUnavailableError(error) {
1268
1888
  return (0, import_daemon_core.isP2pRelayTransportFailure)(error);
@@ -1277,11 +1897,12 @@ function buildRemoveNodeArgs(ctx, nodeId, sessionCleanupMode) {
1277
1897
  }
1278
1898
  var MESH_STATUS_TOOL = {
1279
1899
  name: "mesh_status",
1280
- description: "Get the current status of all nodes in the repo mesh \u2014 health, git state, active sessions, recovery hints, and recommended next steps. Use this to decide which node to send work to or how to recover from failures.",
1900
+ description: "Get the current status of all nodes in the repo mesh \u2014 health, git state, active sessions, recovery hints, and recommended next steps. Use this to decide which node to send work to or how to recover from failures. Do not repeatedly call this to wait for generating delegated work; wait for pendingCoordinatorEvents/completion events or an explicit user status request.",
1281
1901
  inputSchema: {
1282
1902
  type: "object",
1283
1903
  properties: {
1284
- _gemini_compat: { type: "string", description: "Dummy property for Gemini compatibility. Ignore this." }
1904
+ _gemini_compat: { type: "string", description: "Dummy property for Gemini compatibility. Ignore this." },
1905
+ includeStaleDirectWorkDetails: { type: "boolean", description: "Opt in to the full staleDirectWork array. Defaults false; normal status returns compact staleDirectWorkSummary only." }
1285
1906
  }
1286
1907
  }
1287
1908
  };
@@ -1301,14 +1922,22 @@ var MESH_ENQUEUE_TASK_TOOL = {
1301
1922
  inputSchema: {
1302
1923
  type: "object",
1303
1924
  properties: {
1304
- message: { type: "string", description: "The task instruction for the agent." }
1925
+ message: { type: "string", description: "The task instruction for the agent." },
1926
+ 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." },
1927
+ taskMode: { type: "string", enum: ["code_change", "validation", "live_debug_readonly", "launch_app", "convergence"], description: "CamelCase alias for task_mode." },
1928
+ requiredTags: { type: "array", items: { type: "string" }, description: "Optional capability tags that every eligible node must have, e.g. os=darwin, provider=codex-cli, gpu." },
1929
+ required_tags: { type: "array", items: { type: "string" }, description: "Snake_case alias for requiredTags." },
1930
+ depends_on: { type: "array", items: { type: "string" }, description: "Task ids that must complete before this task becomes claimable. Cycles are rejected at enqueue." },
1931
+ dependsOn: { type: "array", items: { type: "string" }, description: "CamelCase alias for depends_on." },
1932
+ mission_id: { type: "string", description: "Mission this task belongs to (mesh_mission record id)." },
1933
+ missionId: { type: "string", description: "CamelCase alias for mission_id." }
1305
1934
  },
1306
1935
  required: ["message"]
1307
1936
  }
1308
1937
  };
1309
1938
  var MESH_VIEW_QUEUE_TOOL = {
1310
1939
  name: "mesh_view_queue",
1311
- description: "View the mesh work queue with source-of-truth active counts separated from historical completed/failed/cancelled records.",
1940
+ description: "View the mesh work queue with source-of-truth active counts separated from historical completed/failed/cancelled records. Do not repeatedly call this to wait for generating assigned work; wait for pendingCoordinatorEvents/completion events or an explicit user status request.",
1312
1941
  inputSchema: {
1313
1942
  type: "object",
1314
1943
  properties: {
@@ -1339,7 +1968,7 @@ var MESH_QUEUE_CANCEL_TOOL = {
1339
1968
  };
1340
1969
  var MESH_QUEUE_REQUEUE_TOOL = {
1341
1970
  name: "mesh_queue_requeue",
1342
- description: "Return a mesh queue task to pending for retry. By default clears stale assigned owner and target session so another live session can claim it.",
1971
+ description: "Return a mesh queue task to pending for retry. By default clears stale assigned owner and target session so another live session can claim it. When the task has exceeded its retry cap it is auto-failed instead; use force=true to override.",
1343
1972
  inputSchema: {
1344
1973
  type: "object",
1345
1974
  properties: {
@@ -1348,7 +1977,8 @@ var MESH_QUEUE_REQUEUE_TOOL = {
1348
1977
  target_node_id: { type: "string", description: "Optional replacement target node ID." },
1349
1978
  target_session_id: { type: "string", description: "Optional replacement target runtime session ID." },
1350
1979
  clear_target_node: { type: "boolean", description: "When true, remove any existing target node constraint." },
1351
- keep_target_session: { type: "boolean", description: "When true, preserve an existing target session if target_session_id is not provided. Defaults false to avoid stale session targets." }
1980
+ keep_target_session: { type: "boolean", description: "When true, preserve an existing target session if target_session_id is not provided. Defaults false to avoid stale session targets." },
1981
+ force: { type: "boolean", description: "When true, bypass the retry cap and requeue even if maxRetries has been exceeded. Use only for explicit operator recovery." }
1352
1982
  },
1353
1983
  required: ["task_id"]
1354
1984
  }
@@ -1361,7 +1991,9 @@ var MESH_SEND_TASK_TOOL = {
1361
1991
  properties: {
1362
1992
  node_id: { type: "string", description: "Target node ID (from mesh_list_nodes)." },
1363
1993
  session_id: { type: "string", description: "Agent session ID on the target node." },
1364
- message: { type: "string", description: "Natural-language task to send to the agent." }
1994
+ message: { type: "string", description: "Natural-language task to send to the agent." },
1995
+ 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." },
1996
+ taskMode: { type: "string", enum: ["code_change", "validation", "live_debug_readonly", "launch_app", "convergence"], description: "CamelCase alias for task_mode." }
1365
1997
  },
1366
1998
  required: ["node_id", "session_id", "message"]
1367
1999
  }
@@ -1419,6 +2051,21 @@ var MESH_GIT_STATUS_TOOL = {
1419
2051
  required: ["node_id"]
1420
2052
  }
1421
2053
  };
2054
+ var MESH_FAST_FORWARD_NODE_TOOL = {
2055
+ name: "mesh_fast_forward_node",
2056
+ 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.",
2057
+ inputSchema: {
2058
+ type: "object",
2059
+ properties: {
2060
+ node_id: { type: "string", description: "Target node ID." },
2061
+ branch: { type: "string", description: "Optional guard: require the node's current branch to match this branch before planning/executing." },
2062
+ execute: { type: "boolean", description: "When true, apply the fast-forward if all safety gates pass. Defaults false/dry-run." },
2063
+ dry_run: { type: "boolean", description: "Preview only. Defaults true unless execute=true; dry_run=true overrides execute." },
2064
+ 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." }
2065
+ },
2066
+ required: ["node_id"]
2067
+ }
2068
+ };
1422
2069
  var MESH_CHECKPOINT_TOOL = {
1423
2070
  name: "mesh_checkpoint",
1424
2071
  description: "Create a git checkpoint (commit) on a mesh node workspace.",
@@ -1431,6 +2078,20 @@ var MESH_CHECKPOINT_TOOL = {
1431
2078
  required: ["node_id", "message"]
1432
2079
  }
1433
2080
  };
2081
+ var MESH_MISSION_UPSERT_TOOL = {
2082
+ name: "mesh_mission_upsert",
2083
+ description: "Create or update a persistent mission record so the plan survives coordinator restarts. Create a mission before enqueueing a multi-task batch, attach tasks via mesh_enqueue_task mission_id, and update status to completed/abandoned when the outcome is decided. Progress is derived from task statuses \u2014 there is no separate progress field.",
2084
+ inputSchema: {
2085
+ type: "object",
2086
+ properties: {
2087
+ mission_id: { type: "string", description: "Mission id to update. Omit to create a new mission." },
2088
+ title: { type: "string", description: "Short mission title." },
2089
+ goal: { type: "string", description: "Free-text mission goal/definition of done." },
2090
+ status: { type: "string", enum: ["active", "paused", "completed", "abandoned"], description: "Mission lifecycle status. Defaults to active on create." }
2091
+ },
2092
+ required: ["title"]
2093
+ }
2094
+ };
1434
2095
  var MESH_APPROVE_TOOL = {
1435
2096
  name: "mesh_approve",
1436
2097
  description: "Approve or reject a pending action on a delegated agent session.",
@@ -1502,7 +2163,7 @@ var MESH_TASK_HISTORY_TOOL = {
1502
2163
  type: "object",
1503
2164
  properties: {
1504
2165
  tail: { type: "number", description: "Number of recent entries to return (default: 20)." },
1505
- kind: { type: "string", description: "Filter by entry kind: task_dispatched, task_completed, task_failed, task_stalled, session_launched, checkpoint_created, node_cloned, node_removed." }
2166
+ kind: { type: "string", description: "Filter by entry kind: task_dispatched, task_completed, task_failed, task_stalled, session_launched, checkpoint_created, node_cloned, node_removed, direct_fast_forward." }
1506
2167
  }
1507
2168
  }
1508
2169
  };
@@ -1522,7 +2183,7 @@ var MESH_RECONCILE_LEDGER_TOOL = {
1522
2183
  };
1523
2184
  var MESH_REFINE_NODE_TOOL = {
1524
2185
  name: "mesh_refine_node",
1525
- description: "The Refinery: Automatically validate and merge a completed worktree node back into its base branch. This tool automates the validation gate and merge queue step. It will merge the node's branch into its base branch and cleanly remove the worktree node and its sessions.",
2186
+ description: "The Refinery: Accept an async validation/merge/cleanup job for a completed worktree node. The immediate response includes async:true, status:'accepted', jobId, interactionId, target node, and startedAt; completion/failure evidence is delivered through pending mesh events and the mesh task ledger.",
1526
2187
  inputSchema: {
1527
2188
  type: "object",
1528
2189
  properties: {
@@ -1531,78 +2192,117 @@ var MESH_REFINE_NODE_TOOL = {
1531
2192
  required: ["node_id"]
1532
2193
  }
1533
2194
  };
1534
- var ALL_MESH_TOOLS = [
1535
- MESH_STATUS_TOOL,
1536
- MESH_LIST_NODES_TOOL,
1537
- MESH_ENQUEUE_TASK_TOOL,
1538
- MESH_VIEW_QUEUE_TOOL,
1539
- MESH_QUEUE_CANCEL_TOOL,
1540
- MESH_QUEUE_REQUEUE_TOOL,
1541
- MESH_SEND_TASK_TOOL,
1542
- MESH_READ_CHAT_TOOL,
1543
- MESH_READ_DEBUG_TOOL,
2195
+ var MESH_REFINE_CONFIG_SCHEMA_TOOL = {
2196
+ name: "mesh_refine_config_schema",
2197
+ 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.",
2198
+ inputSchema: { type: "object", properties: {} }
2199
+ };
2200
+ var MESH_VALIDATE_REFINE_CONFIG_TOOL = {
2201
+ name: "mesh_validate_refine_config",
2202
+ description: "Validate the repo mesh/refine config for a node/workspace without running validation commands or merging.",
2203
+ inputSchema: {
2204
+ type: "object",
2205
+ properties: {
2206
+ node_id: { type: "string", description: "Optional node/workspace whose refine config should be loaded. Defaults to the first mesh node." },
2207
+ config: { type: "object", description: "Optional inline config object to validate instead of loading from the repo." }
2208
+ }
2209
+ }
2210
+ };
2211
+ var MESH_SUGGEST_REFINE_CONFIG_TOOL = {
2212
+ name: "mesh_suggest_refine_config",
2213
+ description: "Suggest a repo mesh/refine config scaffold from project context/package scripts. Suggestions are never executed until saved as explicit refine config.",
2214
+ inputSchema: {
2215
+ type: "object",
2216
+ properties: {
2217
+ node_id: { type: "string", description: "Optional node/workspace used for suggestions. Defaults to the first mesh node." }
2218
+ }
2219
+ }
2220
+ };
2221
+ var MESH_REFINE_PLAN_TOOL = {
2222
+ name: "mesh_refine_plan",
2223
+ 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.",
2224
+ inputSchema: {
2225
+ type: "object",
2226
+ properties: {
2227
+ node_id: { type: "string", description: "Node ID of the worktree node to plan." }
2228
+ },
2229
+ required: ["node_id"]
2230
+ }
2231
+ };
2232
+ var MESH_REVIEW_INBOX_TOOL = {
2233
+ name: "mesh_review_inbox",
2234
+ description: "List local worktree nodes that need human review: merge candidates (pushed feature branches ready to merge) and Refinery-blocked review results. Returns evidence summaries, diff stats vs. the default branch, and suggested actions (Refine / Requeue / Dismiss). Remote nodes are excluded in M4.0.",
2235
+ inputSchema: {
2236
+ type: "object",
2237
+ properties: {
2238
+ mesh_id: { type: "string", description: "Mesh ID (optional \u2014 inferred from active mesh if omitted)." }
2239
+ },
2240
+ required: []
2241
+ }
2242
+ };
2243
+ var ALL_MESH_TOOLS = [
2244
+ MESH_STATUS_TOOL,
2245
+ MESH_LIST_NODES_TOOL,
2246
+ MESH_ENQUEUE_TASK_TOOL,
2247
+ MESH_VIEW_QUEUE_TOOL,
2248
+ MESH_QUEUE_CANCEL_TOOL,
2249
+ MESH_QUEUE_REQUEUE_TOOL,
2250
+ MESH_SEND_TASK_TOOL,
2251
+ MESH_READ_CHAT_TOOL,
2252
+ MESH_READ_DEBUG_TOOL,
1544
2253
  MESH_LAUNCH_SESSION_TOOL,
1545
2254
  MESH_GIT_STATUS_TOOL,
2255
+ MESH_FAST_FORWARD_NODE_TOOL,
1546
2256
  MESH_CHECKPOINT_TOOL,
1547
2257
  MESH_APPROVE_TOOL,
1548
2258
  MESH_CLONE_NODE_TOOL,
1549
2259
  MESH_REMOVE_NODE_TOOL,
1550
2260
  MESH_REFINE_NODE_TOOL,
2261
+ MESH_REFINE_CONFIG_SCHEMA_TOOL,
2262
+ MESH_VALIDATE_REFINE_CONFIG_TOOL,
2263
+ MESH_SUGGEST_REFINE_CONFIG_TOOL,
2264
+ MESH_REFINE_PLAN_TOOL,
1551
2265
  MESH_CLEANUP_SESSIONS_TOOL,
1552
2266
  MESH_TASK_HISTORY_TOOL,
1553
- MESH_RECONCILE_LEDGER_TOOL
2267
+ MESH_RECONCILE_LEDGER_TOOL,
2268
+ MESH_MISSION_UPSERT_TOOL,
2269
+ MESH_REVIEW_INBOX_TOOL
1554
2270
  ];
1555
- async function meshStatus(ctx) {
2271
+ async function meshStatus(ctx, args = {}) {
2272
+ const rateResult = (0, import_daemon_core.recordMeshToolCall)({ meshId: ctx.mesh.id, tool: "mesh_status" });
1556
2273
  await refreshMeshFromDaemon(ctx);
1557
2274
  const { mesh, transport } = ctx;
1558
- const results = [];
1559
- const ledgerSummary = (0, import_daemon_core.getLedgerSummary)(mesh.id);
1560
- for (const node of mesh.nodes) {
2275
+ let ledgerSummary = (0, import_daemon_core.getLedgerSummary)(mesh.id);
2276
+ const results = await Promise.all(mesh.nodes.map(async (node) => {
1561
2277
  const entry = {
1562
2278
  nodeId: node.id,
1563
2279
  workspace: node.workspace,
2280
+ machine: buildNodeMachineIdentity(ctx, node),
2281
+ daemonId: readNodeDaemonId(node),
2282
+ machineId: readNodeMachineId(node),
1564
2283
  ...getNodeLaunchReadiness(node)
1565
2284
  };
1566
2285
  try {
1567
- if (!isLocalTransport(transport) && node.daemonId) {
1568
- const result = await transport.gitStatus(node.daemonId, node.workspace, false, true);
1569
- const status = extractGitStatus(result);
1570
- const uncommittedChanges = countUncommittedChanges(status);
1571
- const dirty = isGitStatusDirty(status);
1572
- entry.health = status?.isGitRepo ? dirty ? "dirty" : "online" : "degraded";
1573
- entry.branch = status?.branch;
1574
- entry.isDirty = dirty;
1575
- entry.uncommittedChanges = uncommittedChanges;
1576
- entry.branchConvergence = buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges);
1577
- const submodules = extractSubmodules(result, node.policy?.submoduleIgnorePaths || []);
1578
- if (submodules && submodules.some((s) => s?.outOfSync)) {
1579
- entry.submoduleWarning = "One or more submodules are out of sync with the parent repo. Run `git submodule update` or check deployment readiness.";
1580
- entry.outOfSyncSubmodules = submodules.filter((s) => s?.outOfSync).map((s) => s.path);
1581
- }
1582
- } else if (isLocalTransport(transport)) {
1583
- const autoDiscover = node.policy?.autoDiscoverSubmodules !== false;
1584
- const statusResult = await commandForNode(ctx, node, "git_status", {
1585
- workspace: node.workspace,
1586
- refreshUpstream: true,
1587
- includeSubmodules: autoDiscover,
1588
- submoduleIgnorePaths: node.policy?.submoduleIgnorePaths || void 0
1589
- });
1590
- const status = extractGitStatus(statusResult);
1591
- const uncommittedChanges = countUncommittedChanges(status);
1592
- const dirty = isGitStatusDirty(status);
1593
- entry.health = status?.isGitRepo ? dirty ? "dirty" : "online" : "degraded";
1594
- entry.branch = status?.branch;
1595
- entry.isDirty = dirty;
1596
- entry.uncommittedChanges = uncommittedChanges;
1597
- entry.branchConvergence = buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges);
1598
- const submodules = extractSubmodules(statusResult, node.policy?.submoduleIgnorePaths || []);
1599
- if (submodules && submodules.some((s) => s?.outOfSync)) {
1600
- entry.submoduleWarning = "One or more submodules are out of sync with the parent repo. Run `git submodule update` or check deployment readiness.";
1601
- entry.outOfSyncSubmodules = submodules.filter((s) => s?.outOfSync).map((s) => s.path);
1602
- }
1603
- } else {
1604
- entry.health = "unknown";
1605
- entry.note = "No daemonId available for cloud status probe";
2286
+ const autoDiscover = node.policy?.autoDiscoverSubmodules !== false;
2287
+ const statusResult = await commandForNode(ctx, node, "git_status", {
2288
+ workspace: node.workspace,
2289
+ refreshUpstream: true,
2290
+ includeSubmodules: autoDiscover,
2291
+ submoduleIgnorePaths: node.policy?.submoduleIgnorePaths || void 0
2292
+ });
2293
+ const status = extractGitStatus(statusResult);
2294
+ const uncommittedChanges = countUncommittedChanges(status);
2295
+ const dirty = isGitStatusDirty(status);
2296
+ entry.health = status?.isGitRepo ? dirty ? "dirty" : "online" : "degraded";
2297
+ assignFullGitSnapshot(entry, status);
2298
+ entry.branch = status?.branch;
2299
+ entry.isDirty = dirty;
2300
+ entry.uncommittedChanges = uncommittedChanges;
2301
+ entry.branchConvergence = buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges);
2302
+ const submodules = extractSubmodules(statusResult, node.policy?.submoduleIgnorePaths || []);
2303
+ if (submodules && submodules.some((s) => s?.outOfSync)) {
2304
+ entry.submoduleWarning = "One or more submodules are out of sync with the parent repo. Run `git submodule update` or check deployment readiness.";
2305
+ entry.outOfSyncSubmodules = submodules.filter((s) => s?.outOfSync).map((s) => s.path);
1606
2306
  }
1607
2307
  } catch (e) {
1608
2308
  const failure = buildCoordinatorP2pRelayFailure(e, {
@@ -1626,7 +2326,7 @@ async function meshStatus(ctx) {
1626
2326
  if (recoveryContext.consecutiveNodeFailures > 0) {
1627
2327
  entry.recoveryHints = {
1628
2328
  consecutiveFailures: recoveryContext.consecutiveNodeFailures,
1629
- lastTaskMessage: recoveryContext.lastTaskMessage,
2329
+ lastTaskMessage: typeof recoveryContext.lastTaskMessage === "string" ? recoveryContext.lastTaskMessage.slice(0, 100) + (recoveryContext.lastTaskMessage.length > 100 ? "\u2026" : "") : recoveryContext.lastTaskMessage,
1630
2330
  advice: recoveryContext.advice,
1631
2331
  retryRecommended: recoveryContext.retryRecommended
1632
2332
  };
@@ -1666,7 +2366,55 @@ async function meshStatus(ctx) {
1666
2366
  }
1667
2367
  const relatedRepos = await collectRelatedRepoStatuses(ctx, node);
1668
2368
  if (relatedRepos.length) entry.relatedRepos = relatedRepos;
1669
- results.push(entry);
2369
+ const liveSessions = await collectLiveStatusSessions(ctx, node);
2370
+ if (liveSessions.length > 0) {
2371
+ entry.sessions = liveSessions.map((s) => {
2372
+ const coordinatorMeshId = typeof s.coordinator?.meshId === "string" ? s.coordinator.meshId : void 0;
2373
+ const isSelfCoordinator = coordinatorMeshId === mesh.id;
2374
+ return {
2375
+ id: s.instanceId ?? s.id ?? s.sessionId,
2376
+ status: s.status ?? s.lifecycle ?? s.state,
2377
+ providerType: s.providerType ?? s.cliType ?? s.type,
2378
+ ...s.activeChat?.status ? { chatStatus: s.activeChat.status } : {},
2379
+ ...isSelfCoordinator ? { isSelfCoordinator: true, role: "coordinator" } : {}
2380
+ };
2381
+ }).filter((s) => s.id);
2382
+ }
2383
+ return entry;
2384
+ }));
2385
+ let ledgerEntries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail: 200 });
2386
+ let directDispatches = (0, import_daemon_core.getActiveDirectDispatches)(mesh.id);
2387
+ const directReconciliation = await reconcileDirectDispatchesFromTranscriptEvidence(ctx, results, directDispatches, ledgerEntries);
2388
+ if (directReconciliation.reconciled > 0) {
2389
+ ledgerEntries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail: 200 });
2390
+ directDispatches = (0, import_daemon_core.getActiveDirectDispatches)(mesh.id);
2391
+ ledgerSummary = (0, import_daemon_core.getLedgerSummary)(mesh.id);
2392
+ }
2393
+ const activeWorkEvidence = (0, import_daemon_core.buildMeshActiveWork)({
2394
+ meshId: mesh.id,
2395
+ queue: (0, import_daemon_core.getQueue)(mesh.id),
2396
+ ledgerEntries,
2397
+ directDispatches,
2398
+ nodes: results
2399
+ });
2400
+ const pollingGuidance = buildActiveWorkPollingGuidance(activeWorkEvidence.summary);
2401
+ const staleDirectWorkSummary = (0, import_daemon_core.buildCompactStaleDirectWorkSummary)(activeWorkEvidence.staleDirectWork, {
2402
+ note: activeWorkEvidence.staleDirectWorkNote,
2403
+ 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."
2404
+ });
2405
+ const coordinatorSessions = [];
2406
+ for (const nodeEntry of results) {
2407
+ const sessions = Array.isArray(nodeEntry.sessions) ? nodeEntry.sessions : [];
2408
+ for (const s of sessions) {
2409
+ if (s?.isSelfCoordinator === true && s.id) {
2410
+ coordinatorSessions.push({
2411
+ nodeId: nodeEntry.nodeId,
2412
+ sessionId: s.id,
2413
+ providerType: s.providerType,
2414
+ status: s.status
2415
+ });
2416
+ }
2417
+ }
1670
2418
  }
1671
2419
  const response = {
1672
2420
  meshId: mesh.id,
@@ -1677,17 +2425,55 @@ async function meshStatus(ctx) {
1677
2425
  sourceOfTruth: {
1678
2426
  membership: "coordinator_daemon_live_mesh",
1679
2427
  currentStatus: "live_git_and_session_probes",
2428
+ activeWork: "mesh_queue_file_and_local_ledger",
1680
2429
  historicalEvidenceOnly: ["recoveryHints", "ledgerSummary"]
1681
2430
  },
1682
2431
  nodes: results,
1683
- branchConvergenceSummary: summarizeBranchConvergence(results)
2432
+ activeWork: activeWorkEvidence.activeWork,
2433
+ staleDirectWorkSummary,
2434
+ ...args.includeStaleDirectWorkDetails === true ? { staleDirectWork: activeWorkEvidence.staleDirectWork } : {},
2435
+ // terminalDirectWork is historical (completed/failed direct dispatches) — opt-in only.
2436
+ ...args.includeTerminalDirectWork === true ? { terminalDirectWork: activeWorkEvidence.terminalDirectWork } : {},
2437
+ activeWorkSummary: activeWorkEvidence.summary,
2438
+ ...pollingGuidance ? { pollingGuidance } : {},
2439
+ ...rateResult.rateLimitExceeded ? { pollingRateAdvisory: { type: "rate_limit_exceeded", tool: "mesh_status", callsInWindow: rateResult.callsInWindow, message: rateResult.advisory } } : {},
2440
+ branchConvergenceSummary: summarizeBranchConvergence(results),
2441
+ ...coordinatorSessions.length > 0 ? {
2442
+ coordinatorSessions,
2443
+ selfIdentification: {
2444
+ meshId: mesh.id,
2445
+ coordinatorSessions,
2446
+ 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."
2447
+ }
2448
+ } : {}
1684
2449
  };
1685
2450
  try {
1686
2451
  response.ledgerSummary = ledgerSummary;
1687
2452
  } catch {
1688
2453
  }
2454
+ try {
2455
+ const missions = (0, import_daemon_core.getActiveMeshMissionSummaries)(mesh.id);
2456
+ if (missions.length > 0) {
2457
+ response.missions = missions.map((mission) => {
2458
+ try {
2459
+ return { ...mission, stats: (0, import_daemon_core.computeMeshMissionStats)(mesh.id, mission.id) };
2460
+ } catch {
2461
+ return mission;
2462
+ }
2463
+ });
2464
+ }
2465
+ } catch {
2466
+ }
1689
2467
  try {
1690
2468
  const pendingEvents = await drainCoordinatorPendingEvents(ctx);
2469
+ const asyncRefineJobs = (0, import_daemon_core.buildMeshAsyncRefineJobs)({
2470
+ meshId: mesh.id,
2471
+ ledgerEntries,
2472
+ pendingEvents
2473
+ });
2474
+ if (asyncRefineJobs.length > 0) {
2475
+ response.asyncRefineJobs = asyncRefineJobs;
2476
+ }
1691
2477
  if (pendingEvents.length > 0) {
1692
2478
  response.pendingCoordinatorEvents = pendingEvents;
1693
2479
  }
@@ -1697,12 +2483,31 @@ async function meshStatus(ctx) {
1697
2483
  }
1698
2484
  async function meshTaskHistory(ctx, args) {
1699
2485
  const { mesh } = ctx;
1700
- await drainCoordinatorPendingEvents(ctx);
2486
+ const pendingEvents = await drainCoordinatorPendingEvents(ctx);
1701
2487
  const tail = typeof args.tail === "number" && args.tail > 0 ? args.tail : 20;
1702
2488
  const kind = typeof args.kind === "string" && args.kind.trim() ? [args.kind.trim()] : void 0;
1703
- const entries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail, kind });
2489
+ const rawEntries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail, kind });
2490
+ const entries = rawEntries.map((e) => ({
2491
+ ...e,
2492
+ payload: e.payload ? slimLedgerPayload(e.payload) : e.payload
2493
+ }));
1704
2494
  const summary = (0, import_daemon_core.getLedgerSummary)(mesh.id);
1705
- return JSON.stringify({ meshId: mesh.id, entries, summary }, null, 2);
2495
+ let taskStats;
2496
+ try {
2497
+ const taskIds = [...new Set(rawEntries.map((e) => typeof e.payload?.taskId === "string" ? e.payload.taskId : "").filter(Boolean))];
2498
+ if (taskIds.length > 0) {
2499
+ const stats = (0, import_daemon_core.computeMeshTaskStats)(mesh.id, { taskIds });
2500
+ if (stats.length > 0) taskStats = stats;
2501
+ }
2502
+ } catch {
2503
+ }
2504
+ return JSON.stringify({
2505
+ meshId: mesh.id,
2506
+ entries,
2507
+ summary,
2508
+ ...taskStats ? { taskStats } : {},
2509
+ ...pendingEvents.length > 0 ? { pendingCoordinatorEvents: pendingEvents } : {}
2510
+ }, null, 2);
1706
2511
  }
1707
2512
  async function meshReconcileLedger(ctx, args) {
1708
2513
  await refreshMeshFromDaemon(ctx);
@@ -1719,7 +2524,7 @@ async function meshReconcileLedger(ctx, args) {
1719
2524
  for (const node of nodes) {
1720
2525
  try {
1721
2526
  if (isLocalControlPlaneNode(ctx, node) || !node.daemonId) {
1722
- const slice2 = (0, import_daemon_core.readLedgerSlice)(ctx.mesh.id, queryArgs);
2527
+ const slice2 = (0, import_daemon_core.readLedgerSliceFromStore)(ctx.mesh.id, queryArgs);
1723
2528
  replicas.push((0, import_daemon_core.buildMeshLedgerReplicaEvidence)({
1724
2529
  nodeId: node.id,
1725
2530
  daemonId: node.daemonId,
@@ -1792,6 +2597,9 @@ async function meshListNodes(ctx) {
1792
2597
  nodeId: n.id,
1793
2598
  workspace: n.workspace,
1794
2599
  repoRoot: n.repoRoot,
2600
+ daemonId: readNodeDaemonId(n),
2601
+ machineId: readNodeMachineId(n),
2602
+ machine: buildNodeMachineIdentity(ctx, n),
1795
2603
  isLocalWorktree: n.isLocalWorktree,
1796
2604
  policy: n.policy,
1797
2605
  relatedRepos: readRelatedRepos(n),
@@ -1800,58 +2608,167 @@ async function meshListNodes(ctx) {
1800
2608
  }))
1801
2609
  }, null, 2);
1802
2610
  }
2611
+ async function meshMissionUpsert(ctx, args) {
2612
+ try {
2613
+ const mission = (0, import_daemon_core.upsertMeshMission)(ctx.mesh.id, {
2614
+ id: readString(args.mission_id) || readString(args.missionId) || void 0,
2615
+ title: args.title,
2616
+ goal: typeof args.goal === "string" ? args.goal : void 0,
2617
+ status: readString(args.status) || void 0
2618
+ });
2619
+ return JSON.stringify({
2620
+ success: true,
2621
+ mission,
2622
+ nextAction: "Attach tasks with mesh_enqueue_task mission_id and depends_on. mesh_status shows live task aggregates for this mission."
2623
+ });
2624
+ } catch (e) {
2625
+ const message = e?.message || String(e);
2626
+ const code = message.includes("mission_title_required") ? "mission_title_required" : message.includes("invalid_mission_status") ? "invalid_mission_status" : void 0;
2627
+ return JSON.stringify({ success: false, ...code ? { code } : {}, error: message });
2628
+ }
2629
+ }
1803
2630
  async function meshEnqueueTask(ctx, args) {
2631
+ const taskMode = readString(args.task_mode) || readString(args.taskMode);
2632
+ const requiredTags = (0, import_daemon_core.normalizeMeshCapabilityTags)(Array.isArray(args.requiredTags) ? args.requiredTags : args.required_tags);
2633
+ const dependsOn = Array.isArray(args.dependsOn) ? args.dependsOn : Array.isArray(args.depends_on) ? args.depends_on : void 0;
2634
+ const missionId = readString(args.missionId) || readString(args.mission_id) || void 0;
1804
2635
  try {
1805
- const task = (0, import_daemon_core.enqueueTask)(ctx.mesh.id, args.message);
1806
- if (isLocalTransport(ctx.transport) && !(ctx.transport instanceof IpcTransport)) {
1807
- ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
2636
+ const task = (0, import_daemon_core.enqueueTask)(ctx.mesh.id, args.message, { taskMode, requiredTags, dependsOn, missionId });
2637
+ if (!(ctx.transport instanceof IpcTransport)) {
2638
+ const queueTrigger = await triggerMeshQueueAndReport(ctx);
2639
+ return JSON.stringify({
2640
+ success: true,
2641
+ source: "queue",
2642
+ taskId: task.id,
2643
+ status: task.status,
2644
+ taskMode: task.taskMode,
2645
+ requiredTags: task.requiredTags,
2646
+ queueTrigger,
2647
+ ...buildQueueTriggerGuidance(queueTrigger)
1808
2648
  });
1809
- return JSON.stringify({ success: true, taskId: task.id, status: task.status });
1810
2649
  }
1811
- if (ctx.transport instanceof IpcTransport) {
1812
- ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
1813
- });
2650
+ {
2651
+ const queueTrigger = await triggerMeshQueueAndReport(ctx);
1814
2652
  const dispatchPromises = [];
1815
2653
  for (const node of ctx.mesh.nodes) {
1816
2654
  const isLocalNode = isLocalControlPlaneNode(ctx, node);
1817
2655
  if (isLocalNode || !node.daemonId) continue;
2656
+ if (!(0, import_daemon_core.nodeSatisfiesRequiredTags)(requiredTags, (0, import_daemon_core.buildMeshNodeCapabilityTags)(node))) continue;
1818
2657
  dispatchPromises.push(
1819
2658
  ipcDispatchToRemoteAgent(ctx, node, { message: args.message }).then((result) => {
1820
2659
  if (result.success) {
1821
2660
  try {
2661
+ const providerType = result.providerType;
2662
+ const descriptor = summarizeTaskMessage(args.message);
1822
2663
  (0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
1823
2664
  kind: "task_dispatched",
1824
2665
  nodeId: node.id,
1825
2666
  sessionId: result.sessionId,
1826
- payload: { message: args.message, via: "p2p_direct", taskId: task.id }
2667
+ providerType,
2668
+ payload: {
2669
+ source: "queue",
2670
+ via: "p2p_direct",
2671
+ taskId: task.id,
2672
+ message: args.message,
2673
+ taskTitle: descriptor.taskTitle,
2674
+ taskSummary: descriptor.taskSummary,
2675
+ ...task.taskMode ? { taskMode: task.taskMode } : {},
2676
+ ...providerType ? { providerType } : {},
2677
+ targetSessionId: result.sessionId
2678
+ }
1827
2679
  });
1828
2680
  } catch {
1829
2681
  }
1830
2682
  }
1831
- }).catch(() => {
2683
+ }).catch((err) => {
2684
+ try {
2685
+ (0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
2686
+ kind: "p2p_dispatch_failed",
2687
+ nodeId: node.id,
2688
+ payload: {
2689
+ source: "queue",
2690
+ via: "p2p_direct",
2691
+ taskId: task.id,
2692
+ error: err?.message || String(err),
2693
+ dispatchFailedAt: (/* @__PURE__ */ new Date()).toISOString()
2694
+ }
2695
+ });
2696
+ } catch {
2697
+ }
1832
2698
  })
1833
2699
  );
1834
2700
  }
1835
2701
  Promise.all(dispatchPromises).catch(() => {
1836
2702
  });
1837
- return JSON.stringify({ success: true, taskId: task.id, status: task.status });
2703
+ return JSON.stringify({
2704
+ success: true,
2705
+ source: "queue",
2706
+ taskId: task.id,
2707
+ status: task.status,
2708
+ taskMode: task.taskMode,
2709
+ requiredTags: task.requiredTags,
2710
+ queueTrigger,
2711
+ ...buildQueueTriggerGuidance(queueTrigger)
2712
+ });
1838
2713
  }
1839
- return JSON.stringify({ success: true, taskId: task.id, status: task.status });
1840
2714
  } catch (e) {
1841
- return JSON.stringify({ success: false, error: e.message });
2715
+ const message = e?.message || String(e);
2716
+ if (message.includes("live_debug_readonly_guardrail_violation")) {
2717
+ return JSON.stringify({ success: false, code: "live_debug_readonly_guardrail_violation", taskMode, error: message });
2718
+ }
2719
+ if (message.includes("dependency_cycle_detected")) {
2720
+ return JSON.stringify({ success: false, code: "dependency_cycle_detected", dependsOn, error: message });
2721
+ }
2722
+ return JSON.stringify({ success: false, error: message });
1842
2723
  }
1843
2724
  }
1844
2725
  async function meshViewQueue(ctx, args) {
2726
+ const rateResult = (0, import_daemon_core.recordMeshToolCall)({ meshId: ctx.mesh.id, tool: "mesh_view_queue" });
1845
2727
  try {
2728
+ await refreshMeshFromDaemon(ctx);
1846
2729
  const statusFilter = sanitizeQueueStatusFilter(args.status);
1847
2730
  const view = normalizeQueueViewMode(args.view);
1848
- const fullQueue = annotateQueueStaleness((0, import_daemon_core.getQueue)(ctx.mesh.id), ctx.mesh);
2731
+ const rawQueue = (0, import_daemon_core.getQueue)(ctx.mesh.id);
2732
+ const statusById = new Map(rawQueue.map((task) => [task.id, task.status]));
2733
+ const withDependencies = rawQueue.map((task) => {
2734
+ if (!Array.isArray(task.dependsOn) || task.dependsOn.length === 0) return task;
2735
+ const depState = (0, import_daemon_core.describeTaskDependencyState)(task, statusById);
2736
+ return { ...task, ...depState };
2737
+ });
2738
+ const fullQueue = prioritizeActiveQueueRows(annotateQueueStaleness(withDependencies, ctx.mesh));
1849
2739
  const queue = filterQueueForView(fullQueue, view, statusFilter);
1850
2740
  const summary = buildQueueStatusSummary(fullQueue);
1851
2741
  const visibleSummary = buildQueueStatusSummary(queue);
1852
2742
  const maintenance = buildQueueMaintenanceReport(fullQueue);
2743
+ const liveNodes = await collectMeshViewQueueNodesWithLiveSessions(ctx);
2744
+ let ledgerEntries = (0, import_daemon_core.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
2745
+ let directDispatches = (0, import_daemon_core.getActiveDirectDispatches)(ctx.mesh.id);
2746
+ const directReconciliation = await reconcileDirectDispatchesFromTranscriptEvidence(ctx, liveNodes, directDispatches, ledgerEntries);
2747
+ if (directReconciliation.reconciled > 0) {
2748
+ ledgerEntries = (0, import_daemon_core.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
2749
+ directDispatches = (0, import_daemon_core.getActiveDirectDispatches)(ctx.mesh.id);
2750
+ }
2751
+ (0, import_daemon_core.markStaleDirectDispatches)(ctx.mesh.id);
2752
+ directDispatches = (0, import_daemon_core.getActiveDirectDispatches)(ctx.mesh.id);
2753
+ const activeWorkEvidence = (0, import_daemon_core.buildMeshActiveWork)({
2754
+ meshId: ctx.mesh.id,
2755
+ queue: fullQueue,
2756
+ ledgerEntries,
2757
+ // Always pass MeshRuntimeStore records (may be empty). buildMeshActiveWork uses them for local
2758
+ // dispatches and falls through to ledger scan for remote P2P dispatches not in MeshRuntimeStore.
2759
+ directDispatches,
2760
+ nodes: liveNodes
2761
+ });
2762
+ const recentDispatchFailures = ledgerEntries.filter((e) => e.kind === "p2p_dispatch_failed").slice(-20).map((e) => ({
2763
+ nodeId: e.nodeId,
2764
+ taskId: e.payload?.taskId,
2765
+ error: e.payload?.error,
2766
+ via: e.payload?.via,
2767
+ failedAt: e.payload?.dispatchFailedAt || e.timestamp
2768
+ }));
1853
2769
  const staleAssignedTasks = maintenance.staleAssignedTasks || [];
1854
2770
  const requestedHistoricalRows = queue.some((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
2771
+ const pollingGuidance = buildActiveWorkPollingGuidance(activeWorkEvidence.summary);
1855
2772
  return JSON.stringify({
1856
2773
  success: true,
1857
2774
  sourceOfTruth: {
@@ -1866,21 +2783,30 @@ async function meshViewQueue(ctx, args) {
1866
2783
  filtered: Boolean(statusFilter?.length) || view !== "all"
1867
2784
  },
1868
2785
  queue,
1869
- visibleQueue: queue,
1870
- visibleSummary,
2786
+ activeWork: activeWorkEvidence.activeWork,
2787
+ staleDirectWork: activeWorkEvidence.staleDirectWork,
2788
+ activeWorkSummary: activeWorkEvidence.summary,
2789
+ ...pollingGuidance ? { pollingGuidance } : {},
2790
+ ...rateResult.rateLimitExceeded ? { pollingRateAdvisory: { type: "rate_limit_exceeded", tool: "mesh_view_queue", callsInWindow: rateResult.callsInWindow, message: rateResult.advisory } } : {},
1871
2791
  summary,
2792
+ visibleSummary,
1872
2793
  activeCounts: summary.activeCounts,
1873
2794
  historicalCounts: summary.historicalCounts,
1874
- activeCount: summary.activeCount,
1875
- historicalCount: summary.historicalCount,
1876
2795
  visibleActiveCounts: visibleSummary.activeCounts,
1877
2796
  visibleHistoricalCounts: visibleSummary.historicalCounts,
2797
+ activeCount: summary.activeCount,
2798
+ historicalCount: summary.historicalCount,
1878
2799
  visibleActiveCount: visibleSummary.activeCount,
1879
2800
  visibleHistoricalCount: visibleSummary.historicalCount,
1880
2801
  staleAssignedTasks,
1881
2802
  staleAssignedCount: maintenance.staleAssignedCount,
1882
2803
  queueMaintenance: maintenance,
1883
2804
  cleanupDryRun: maintenance,
2805
+ ...recentDispatchFailures.length > 0 ? {
2806
+ recentDispatchFailures,
2807
+ dispatchFailureCount: recentDispatchFailures.length,
2808
+ dispatchFailureNote: "Remote P2P dispatch attempts that failed. Affected tasks remain pending and may require mesh_queue_requeue if no idle session picks them up."
2809
+ } : {},
1884
2810
  ...view === "active" || statusFilter?.some((status) => ACTIVE_QUEUE_STATUSES.has(status)) ? {
1885
2811
  activeQueue: queue.filter((task) => ACTIVE_QUEUE_STATUSES.has(String(task?.status || "")))
1886
2812
  } : {},
@@ -1900,6 +2826,8 @@ async function meshQueueCancel(ctx, args) {
1900
2826
  if (!taskId) return JSON.stringify({ success: false, error: "task_id required" });
1901
2827
  const task = (0, import_daemon_core.cancelTask)(ctx.mesh.id, taskId, { reason: args.reason });
1902
2828
  if (!task) return JSON.stringify({ success: false, error: `Queue task '${taskId}' not found` });
2829
+ ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
2830
+ });
1903
2831
  return JSON.stringify({ success: true, task }, null, 2);
1904
2832
  } catch (e) {
1905
2833
  return JSON.stringify({ success: false, error: e.message });
@@ -1917,23 +2845,81 @@ async function meshQueueRequeue(ctx, args) {
1917
2845
  targetNodeId,
1918
2846
  targetSessionId,
1919
2847
  clearTargetNode: args.clear_target_node === true || args.clearTargetNode === true,
1920
- clearTargetSession: targetSessionId ? false : !keepTargetSession
2848
+ clearTargetSession: targetSessionId ? false : !keepTargetSession,
2849
+ force: args.force === true
1921
2850
  });
1922
2851
  if (!task) return JSON.stringify({ success: false, error: `Queue task '${taskId}' not found` });
1923
- if (isLocalTransport(ctx.transport)) {
1924
- ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
1925
- });
2852
+ if (task.status === "failed" && task.cancelReason?.startsWith("max_retries_exceeded")) {
2853
+ return JSON.stringify({
2854
+ success: false,
2855
+ code: "max_retries_exceeded",
2856
+ error: task.cancelReason,
2857
+ task,
2858
+ hint: "Use force=true to bypass the retry cap for explicit operator recovery."
2859
+ }, null, 2);
1926
2860
  }
2861
+ ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
2862
+ });
1927
2863
  return JSON.stringify({ success: true, task }, null, 2);
1928
2864
  } catch (e) {
1929
2865
  return JSON.stringify({ success: false, error: e.message });
1930
2866
  }
1931
2867
  }
1932
2868
  async function meshSendTask(ctx, args) {
2869
+ const requestedTaskMode = readString(args.task_mode) || readString(args.taskMode);
2870
+ const modeValidation = (0, import_daemon_core.validateMeshTaskModeRequest)(requestedTaskMode, args.message);
2871
+ if (!modeValidation.valid) {
2872
+ return JSON.stringify({
2873
+ success: false,
2874
+ code: "live_debug_readonly_guardrail_violation",
2875
+ taskMode: modeValidation.taskMode || requestedTaskMode,
2876
+ violations: modeValidation.violations,
2877
+ allowedOperations: modeValidation.allowedOperations,
2878
+ error: `live_debug_readonly_guardrail_violation: forbidden operations (${modeValidation.violations.join(", ")})`
2879
+ });
2880
+ }
2881
+ const taskMode = modeValidation.taskMode;
1933
2882
  const node = await findNodeWithRefresh(ctx, args.node_id);
1934
2883
  if (node.policy?.readOnly) {
1935
2884
  return JSON.stringify({ error: `Node '${args.node_id}' is read-only` });
1936
2885
  }
2886
+ let explicitTargetSession;
2887
+ if (args.session_id && isWorkerTaskMode(taskMode)) {
2888
+ try {
2889
+ const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
2890
+ const sessions = extractStatusMetadataSessions(statusResult);
2891
+ explicitTargetSession = sessions.find((session) => readSessionRecordId(session) === args.session_id);
2892
+ if (explicitTargetSession && isMeshCoordinatorSessionRecord(explicitTargetSession)) {
2893
+ return JSON.stringify({
2894
+ success: false,
2895
+ recoverable: true,
2896
+ code: "mesh_target_session_is_coordinator",
2897
+ reason: "mesh_target_session_is_coordinator",
2898
+ nodeId: args.node_id,
2899
+ sessionId: args.session_id,
2900
+ taskMode: taskMode || "unspecified",
2901
+ 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.`,
2902
+ 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.`
2903
+ });
2904
+ }
2905
+ if (explicitTargetSession && isUnmanagedSessionRecord(explicitTargetSession)) {
2906
+ return JSON.stringify({
2907
+ success: false,
2908
+ recoverable: true,
2909
+ code: "mesh_target_session_unmanaged",
2910
+ reason: "mesh_target_session_unmanaged",
2911
+ nodeId: args.node_id,
2912
+ sessionId: args.session_id,
2913
+ taskMode: taskMode || "unspecified",
2914
+ unsafeTranscriptAlias: true,
2915
+ 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.`,
2916
+ 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.`
2917
+ });
2918
+ }
2919
+ } catch {
2920
+ explicitTargetSession = void 0;
2921
+ }
2922
+ }
1937
2923
  const duplicate = hasRecentDuplicateDispatch(ctx, args);
1938
2924
  if (duplicate.duplicate) {
1939
2925
  return JSON.stringify({
@@ -1953,47 +2939,73 @@ async function meshSendTask(ctx, args) {
1953
2939
  });
1954
2940
  }
1955
2941
  try {
1956
- if (!isLocalTransport(ctx.transport) && node.daemonId) {
1957
- const res = await ctx.transport.meshEnqueueTask(node.daemonId, {
1958
- meshId: ctx.mesh.id,
1959
- message: args.message,
1960
- targetNodeId: args.node_id
1961
- });
1962
- return JSON.stringify(res);
1963
- }
1964
2942
  const isLocalNode = isLocalControlPlaneNode(ctx, node);
1965
2943
  if (ctx.transport instanceof IpcTransport && node.daemonId && !isLocalNode) {
1966
- const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id || ""));
2944
+ const cached = getSessionMetadata(meshSessionCacheKey(args.node_id, args.session_id || ""));
2945
+ const taskId = (0, import_node_crypto.randomUUID)();
2946
+ const coordinatorDaemonId = resolveCoordinatorNode(ctx)?.daemonId || ctx.localDaemonId;
1967
2947
  const result2 = await ipcDispatchToRemoteAgent(ctx, node, {
1968
2948
  session_id: args.session_id,
1969
2949
  message: args.message,
1970
- providerType: cached?.providerType
2950
+ providerType: cached?.providerType,
2951
+ verifiedSession: explicitTargetSession,
2952
+ meshContext: {
2953
+ meshId: ctx.mesh.id,
2954
+ nodeId: args.node_id,
2955
+ taskId,
2956
+ ...coordinatorDaemonId ? { coordinatorDaemonId } : {}
2957
+ }
1971
2958
  });
1972
2959
  if (result2.success) {
1973
2960
  const dispatchedSessionId = args.session_id || result2.sessionId;
2961
+ const dispatchedAt = (/* @__PURE__ */ new Date()).toISOString();
1974
2962
  try {
2963
+ const providerType = result2.providerType || cached?.providerType;
1975
2964
  (0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
1976
2965
  kind: "task_dispatched",
1977
2966
  nodeId: args.node_id,
1978
2967
  sessionId: dispatchedSessionId,
1979
- payload: {
1980
- message: args.message,
1981
- via: "p2p_direct",
1982
- ...dispatchedSessionId ? { targetSessionId: dispatchedSessionId } : {}
1983
- }
2968
+ providerType,
2969
+ payload: buildDirectTaskPayload(args.message, "p2p_direct", {
2970
+ taskId,
2971
+ taskMode,
2972
+ providerType,
2973
+ targetSessionId: dispatchedSessionId
2974
+ })
2975
+ });
2976
+ (0, import_daemon_core.insertDirectDispatch)(ctx.mesh.id, {
2977
+ taskId,
2978
+ nodeId: args.node_id,
2979
+ sessionId: dispatchedSessionId,
2980
+ providerType: providerType || void 0,
2981
+ message: args.message,
2982
+ taskMode: taskMode || void 0,
2983
+ via: "p2p_direct",
2984
+ dispatchedAt
1984
2985
  });
1985
2986
  } catch {
1986
2987
  }
1987
2988
  }
1988
- return JSON.stringify({ ...result2, nodeId: args.node_id, dispatched: result2.success === true });
2989
+ return JSON.stringify({
2990
+ ...result2,
2991
+ nodeId: args.node_id,
2992
+ sessionId: result2.success ? args.session_id || result2.sessionId : args.session_id,
2993
+ ...result2.success ? { source: "direct", taskId } : {},
2994
+ taskMode,
2995
+ ...result2.success && result2.providerType ? { providerType: result2.providerType } : {},
2996
+ dispatched: result2.success === true
2997
+ });
1989
2998
  }
1990
- if (args.session_id && isLocalTransport(ctx.transport)) {
1991
- const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id));
2999
+ if (args.session_id) {
3000
+ const cached = getSessionMetadata(meshSessionCacheKey(args.node_id, args.session_id));
1992
3001
  let resolvedProviderType = cached?.providerType || "";
1993
3002
  if (!resolvedProviderType) {
1994
- const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
1995
- const sessions = extractStatusMetadataSessions(statusResult);
1996
- const explicitSession = sessions.find((session) => readSessionRecordId(session) === args.session_id);
3003
+ let explicitSession = explicitTargetSession;
3004
+ if (!explicitSession) {
3005
+ const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
3006
+ const sessions = extractStatusMetadataSessions(statusResult);
3007
+ explicitSession = sessions.find((session) => readSessionRecordId(session) === args.session_id);
3008
+ }
1997
3009
  if (!explicitSession) {
1998
3010
  return JSON.stringify({
1999
3011
  success: false,
@@ -2008,11 +3020,40 @@ async function meshSendTask(ctx, args) {
2008
3020
  nextAction: `Launch a fresh session with mesh_launch_session(node_id: '${args.node_id}') or retry without session_id so Repo Mesh can target a live delegate session.`
2009
3021
  });
2010
3022
  }
3023
+ if (isMeshCoordinatorSessionRecord(explicitSession)) {
3024
+ return JSON.stringify({
3025
+ success: false,
3026
+ recoverable: true,
3027
+ code: "mesh_target_session_is_coordinator",
3028
+ reason: "mesh_target_session_is_coordinator",
3029
+ nodeId: args.node_id,
3030
+ sessionId: args.session_id,
3031
+ taskMode: taskMode || "unspecified",
3032
+ 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.`,
3033
+ 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.`
3034
+ });
3035
+ }
3036
+ if (isUnmanagedSessionRecord(explicitSession)) {
3037
+ return JSON.stringify({
3038
+ success: false,
3039
+ recoverable: true,
3040
+ code: "mesh_target_session_unmanaged",
3041
+ reason: "mesh_target_session_unmanaged",
3042
+ nodeId: args.node_id,
3043
+ sessionId: args.session_id,
3044
+ taskMode: taskMode || "unspecified",
3045
+ unsafeTranscriptAlias: true,
3046
+ unsafeDelegateTarget: true,
3047
+ 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.`,
3048
+ 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.`
3049
+ });
3050
+ }
2011
3051
  resolvedProviderType = resolveSessionProviderType(explicitSession);
2012
3052
  if (resolvedProviderType) {
2013
3053
  meshSessionProviderMetadata.set(meshSessionCacheKey(args.node_id, args.session_id), {
2014
3054
  providerType: resolvedProviderType,
2015
- providerSessionId: readString(explicitSession?.providerSessionId) || void 0
3055
+ providerSessionId: readString(explicitSession?.providerSessionId) || void 0,
3056
+ expiresAt: Date.now() + SESSION_PROVIDER_METADATA_TTL_MS
2016
3057
  });
2017
3058
  }
2018
3059
  }
@@ -2030,17 +3071,58 @@ async function meshSendTask(ctx, args) {
2030
3071
  nextAction: `Relaunch the target session on node '${args.node_id}' or retry without session_id so Repo Mesh can pick a session with provider metadata.`
2031
3072
  });
2032
3073
  }
3074
+ if (explicitTargetSession && !isIdleSessionRecord(explicitTargetSession) && !isTerminalSessionRecord(explicitTargetSession)) {
3075
+ const sessionStatus = typeof explicitTargetSession?.status === "string" ? explicitTargetSession.status : "unknown";
3076
+ const { createSessionDelivery: createDelivery, resolveDeliveryDecision } = await import("@adhdev/daemon-core");
3077
+ const policyResult = resolveDeliveryDecision(sessionStatus, { kind: "task" });
3078
+ if (policyResult.decision === "queued") {
3079
+ const delivery = createDelivery({
3080
+ meshId: ctx.mesh.id,
3081
+ nodeId: args.node_id,
3082
+ sessionId: args.session_id,
3083
+ providerType: resolvedProviderType,
3084
+ kind: "task",
3085
+ message: args.message,
3086
+ status: "queued"
3087
+ });
3088
+ return JSON.stringify({
3089
+ success: true,
3090
+ dispatched: false,
3091
+ decision: "queued_delivery",
3092
+ deliveryId: delivery.id,
3093
+ reason: policyResult.reason,
3094
+ nodeId: args.node_id,
3095
+ sessionId: args.session_id,
3096
+ sessionStatus,
3097
+ taskMode: taskMode || void 0,
3098
+ message: policyResult.message,
3099
+ 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.`
3100
+ });
3101
+ }
3102
+ }
3103
+ const sessionWasIdle = explicitTargetSession ? isIdleSessionRecord(explicitTargetSession) : false;
3104
+ const taskId = (0, import_node_crypto.randomUUID)();
3105
+ const dispatchedAt = (/* @__PURE__ */ new Date()).toISOString();
3106
+ const coordinatorDaemonId = resolveCoordinatorNode(ctx)?.daemonId || ctx.localDaemonId;
2033
3107
  const dispatchResult = await commandForNode(ctx, node, "agent_command", {
2034
3108
  targetSessionId: args.session_id,
2035
3109
  agentType: resolvedProviderType,
2036
3110
  cliType: resolvedProviderType,
2037
3111
  providerType: resolvedProviderType,
2038
3112
  action: "send_chat",
2039
- message: args.message
3113
+ message: args.message,
3114
+ meshContext: {
3115
+ meshId: ctx.mesh.id,
3116
+ nodeId: args.node_id,
3117
+ taskId,
3118
+ ...coordinatorDaemonId ? { coordinatorDaemonId } : {}
3119
+ }
2040
3120
  });
2041
3121
  const dispatchPayload = unwrapCommandPayload(dispatchResult);
2042
3122
  if (dispatchPayload?.success === false || dispatchResult?.success === false) {
3123
+ const source = dispatchPayload?.success === false ? dispatchPayload : dispatchResult;
2043
3124
  return JSON.stringify({
3125
+ ...source && typeof source === "object" ? source : {},
2044
3126
  success: false,
2045
3127
  nodeId: args.node_id,
2046
3128
  sessionId: args.session_id,
@@ -2053,22 +3135,78 @@ async function meshSendTask(ctx, args) {
2053
3135
  nodeId: args.node_id,
2054
3136
  sessionId: args.session_id,
2055
3137
  providerType: resolvedProviderType,
2056
- payload: { message: args.message, via: "local_direct" }
3138
+ payload: buildDirectTaskPayload(args.message, "local_direct", {
3139
+ taskId,
3140
+ taskMode,
3141
+ providerType: resolvedProviderType,
3142
+ targetSessionId: args.session_id,
3143
+ dispatchedToIdleSession: sessionWasIdle
3144
+ })
3145
+ });
3146
+ } catch {
3147
+ }
3148
+ (0, import_daemon_core.insertDirectDispatch)(ctx.mesh.id, {
3149
+ taskId,
3150
+ nodeId: args.node_id,
3151
+ sessionId: args.session_id,
3152
+ providerType: resolvedProviderType || void 0,
3153
+ message: args.message,
3154
+ taskMode: taskMode || void 0,
3155
+ via: "local_direct",
3156
+ dispatchedToIdleSession: sessionWasIdle,
3157
+ dispatchedAt
3158
+ });
3159
+ let deliveryId;
3160
+ try {
3161
+ const { createSessionDelivery: createDelivery } = await import("@adhdev/daemon-core");
3162
+ const delivery = createDelivery({
3163
+ meshId: ctx.mesh.id,
3164
+ nodeId: args.node_id,
3165
+ sessionId: args.session_id,
3166
+ providerType: resolvedProviderType || void 0,
3167
+ taskId,
3168
+ kind: "task",
3169
+ message: args.message,
3170
+ status: sessionWasIdle ? "delivered" : "delivering"
2057
3171
  });
3172
+ deliveryId = delivery.id;
2058
3173
  } catch {
2059
3174
  }
2060
- return JSON.stringify({ success: true, dispatched: true, nodeId: args.node_id, sessionId: args.session_id });
3175
+ return JSON.stringify({
3176
+ success: true,
3177
+ dispatched: true,
3178
+ decision: "immediate",
3179
+ source: "direct",
3180
+ taskId,
3181
+ deliveryId,
3182
+ taskMode,
3183
+ providerType: resolvedProviderType,
3184
+ nodeId: args.node_id,
3185
+ sessionId: args.session_id,
3186
+ ...sessionWasIdle ? {
3187
+ dispatchAcknowledgementRisk: true,
3188
+ dispatchAcknowledgementRiskReason: "session_was_idle_at_dispatch",
3189
+ 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.`
3190
+ } : {}
3191
+ });
2061
3192
  }
2062
3193
  const task = (0, import_daemon_core.enqueueTask)(ctx.mesh.id, args.message, {
2063
3194
  targetNodeId: args.node_id,
2064
- targetSessionId: args.session_id
3195
+ targetSessionId: args.session_id,
3196
+ taskMode
2065
3197
  });
2066
- if (isLocalTransport(ctx.transport) || ctx.transport instanceof IpcTransport) {
2067
- ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
2068
- });
2069
- }
2070
- const pendingEvents = isLocalTransport(ctx.transport) ? (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id) : [];
2071
- const result = { success: true, nodeId: args.node_id, taskId: task.id, status: task.status };
3198
+ const queueTrigger = await triggerMeshQueueAndReport(ctx);
3199
+ const pendingEvents = (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id, ctx.localDaemonId);
3200
+ const result = {
3201
+ success: true,
3202
+ source: "queue",
3203
+ nodeId: args.node_id,
3204
+ taskId: task.id,
3205
+ status: task.status,
3206
+ taskMode: task.taskMode,
3207
+ queueTrigger,
3208
+ ...buildQueueTriggerGuidance(queueTrigger)
3209
+ };
2072
3210
  if (pendingEvents.length > 0) {
2073
3211
  result.pendingCoordinatorEvents = pendingEvents;
2074
3212
  }
@@ -2088,88 +3226,59 @@ async function meshReadChat(ctx, args) {
2088
3226
  if (!node) {
2089
3227
  return JSON.stringify(buildMissingNodeReadChatRecovery(ctx, args), null, 2);
2090
3228
  }
2091
- if (ctx.transport instanceof IpcTransport || isLocalTransport(ctx.transport)) {
2092
- await drainCoordinatorPendingEvents(ctx, { nodeIds: [args.node_id] });
2093
- }
2094
- if (isLocalTransport(ctx.transport)) {
2095
- const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id));
2096
- const providerSessionId = typeof args.provider_session_id === "string" && args.provider_session_id.trim() ? args.provider_session_id.trim() : cached?.providerSessionId;
2097
- const result = await commandForNode(ctx, node, "read_chat", {
3229
+ await drainCoordinatorPendingEvents(ctx, { nodeIds: [args.node_id] });
3230
+ const cached = resolveMeshSessionProviderMetadata(ctx, args.node_id, args.session_id);
3231
+ const providerSessionId = typeof args.provider_session_id === "string" && args.provider_session_id.trim() ? args.provider_session_id.trim() : cached?.providerSessionId;
3232
+ const result = await commandForNode(ctx, node, "read_chat", {
3233
+ sessionId: args.session_id,
3234
+ targetSessionId: args.session_id,
3235
+ workspace: node.workspace,
3236
+ ...cached?.providerType ? { agentType: cached.providerType, providerType: cached.providerType } : {},
3237
+ ...providerSessionId ? { providerSessionId } : {},
3238
+ tailLimit: args.tail ?? 10
3239
+ });
3240
+ const payload = annotateRapidReadChatAdvisory(unwrapCommandPayload(result), {
3241
+ key: `mesh:${args.node_id}:${args.session_id}`,
3242
+ toolName: "mesh_read_chat",
3243
+ completionCallbackExpected: true
3244
+ });
3245
+ const useCompact = args.compact !== false;
3246
+ if (useCompact) {
3247
+ const compactPayload = compactChatPayload(payload, {
3248
+ nodeId: args.node_id,
2098
3249
  sessionId: args.session_id,
2099
- targetSessionId: args.session_id,
2100
- workspace: node.workspace,
2101
- ...cached?.providerType ? { agentType: cached.providerType, providerType: cached.providerType } : {},
2102
- ...providerSessionId ? { providerSessionId } : {},
2103
- tailLimit: args.tail ?? 10
3250
+ limit: args.tail ?? 10
2104
3251
  });
2105
- const payload = annotateRapidReadChatAdvisory(unwrapCommandPayload(result), {
2106
- key: `mesh:${args.node_id}:${args.session_id}`,
2107
- toolName: "mesh_read_chat",
2108
- completionCallbackExpected: true
2109
- });
2110
- if (args.compact) {
2111
- const compactPayload = compactChatPayload(payload, {
2112
- nodeId: args.node_id,
2113
- sessionId: args.session_id,
2114
- limit: args.tail ?? 10
2115
- });
2116
- return JSON.stringify(
2117
- payload.pollingAdvisory ? { ...compactPayload, pollingAdvisory: payload.pollingAdvisory } : compactPayload,
2118
- null,
2119
- 2
2120
- );
2121
- }
2122
- return JSON.stringify(payload, null, 2);
2123
- } else if (!isLocalTransport(ctx.transport) && node.daemonId) {
2124
- try {
2125
- const targetId = `${node.daemonId}:session:${args.session_id}`;
2126
- const res = await ctx.transport.readChat(targetId, {
2127
- limit: args.tail ?? 10,
2128
- sessionId: args.session_id
2129
- });
2130
- return JSON.stringify(res, null, 2);
2131
- } catch (e) {
2132
- return JSON.stringify({ success: false, error: e.message });
2133
- }
2134
- } else {
2135
- return JSON.stringify({ error: "Cloud mesh read_chat requires node daemonId" });
3252
+ return JSON.stringify(
3253
+ payload.pollingAdvisory ? { ...compactPayload, pollingAdvisory: payload.pollingAdvisory } : compactPayload,
3254
+ null,
3255
+ 2
3256
+ );
2136
3257
  }
3258
+ return JSON.stringify(payload, null, 2);
2137
3259
  }
2138
3260
  async function meshReadDebug(ctx, args) {
2139
3261
  const node = await findNodeWithRefresh(ctx, args.node_id);
2140
- if (isLocalTransport(ctx.transport)) {
2141
- const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id));
2142
- const providerSessionId = typeof args.provider_session_id === "string" && args.provider_session_id.trim() ? args.provider_session_id.trim() : cached?.providerSessionId;
2143
- const delivery = args.delivery === "inline" ? void 0 : "daemon_file";
2144
- const result = await commandForNode(ctx, node, "get_chat_debug_bundle", {
2145
- sessionId: args.session_id,
2146
- targetSessionId: args.session_id,
2147
- workspace: node.workspace,
2148
- ...cached?.providerType ? { agentType: cached.providerType, providerType: cached.providerType } : {},
2149
- ...providerSessionId ? { providerSessionId } : {},
2150
- tailLimit: args.tail ?? 40,
2151
- ...delivery ? { delivery } : {}
2152
- });
2153
- const payload = unwrapCommandPayload(result);
2154
- return JSON.stringify(payload, null, 2);
2155
- } else if (!isLocalTransport(ctx.transport) && node.daemonId) {
2156
- try {
2157
- const targetId = `${node.daemonId}:session:${args.session_id}`;
2158
- const res = await ctx.transport.getChatDebugBundle(targetId, {
2159
- sessionId: args.session_id,
2160
- tailLimit: args.tail ?? 40,
2161
- delivery: args.delivery
2162
- });
2163
- return JSON.stringify(res, null, 2);
2164
- } catch (e) {
2165
- return JSON.stringify({ success: false, error: e.message });
2166
- }
2167
- }
2168
- return JSON.stringify({ error: "Cloud mesh read_debug requires node daemonId" });
3262
+ const cached = resolveMeshSessionProviderMetadata(ctx, args.node_id, args.session_id);
3263
+ const providerSessionId = typeof args.provider_session_id === "string" && args.provider_session_id.trim() ? args.provider_session_id.trim() : cached?.providerSessionId;
3264
+ const delivery = args.delivery === "inline" ? void 0 : "daemon_file";
3265
+ const result = await commandForNode(ctx, node, "get_chat_debug_bundle", {
3266
+ sessionId: args.session_id,
3267
+ targetSessionId: args.session_id,
3268
+ workspace: node.workspace,
3269
+ ...cached?.providerType ? { agentType: cached.providerType, providerType: cached.providerType } : {},
3270
+ ...providerSessionId ? { providerSessionId } : {},
3271
+ tailLimit: args.tail ?? 40,
3272
+ ...delivery ? { delivery } : {}
3273
+ });
3274
+ const payload = unwrapCommandPayload(result);
3275
+ return JSON.stringify(payload, null, 2);
2169
3276
  }
2170
3277
  async function meshLaunchSession(ctx, args) {
2171
3278
  const node = await findNodeWithRefresh(ctx, args.node_id);
2172
- if (isLocalTransport(ctx.transport)) {
3279
+ const bootstrapBlock = getWorktreeBootstrapLaunchBlock(node, ctx.mesh.policy);
3280
+ if (bootstrapBlock) return JSON.stringify(bootstrapBlock, null, 2);
3281
+ {
2173
3282
  let resolvedProviderType = typeof args.type === "string" && args.type.trim() ? args.type : "";
2174
3283
  if (!resolvedProviderType) {
2175
3284
  const providerPriority = readProviderPriority(node.policy);
@@ -2203,6 +3312,9 @@ async function meshLaunchSession(ctx, args) {
2203
3312
  cliType: resolvedProviderType,
2204
3313
  dir: node.workspace,
2205
3314
  settings: {
3315
+ // Worker launch envelope (A5): structured metadata so worker sessions
3316
+ // know their role and can route completion events back correctly.
3317
+ role: "worker",
2206
3318
  meshNodeFor: ctx.mesh.id,
2207
3319
  meshNodeId: args.node_id,
2208
3320
  spawnedSessionVisibility,
@@ -2224,7 +3336,8 @@ async function meshLaunchSession(ctx, args) {
2224
3336
  if (runtimeSessionId) {
2225
3337
  meshSessionProviderMetadata.set(meshSessionCacheKey(args.node_id, runtimeSessionId), {
2226
3338
  providerType: resolvedProviderType,
2227
- ...providerSessionId ? { providerSessionId } : {}
3339
+ ...providerSessionId ? { providerSessionId } : {},
3340
+ expiresAt: Date.now() + SESSION_PROVIDER_METADATA_TTL_MS
2228
3341
  });
2229
3342
  }
2230
3343
  try {
@@ -2237,63 +3350,14 @@ async function meshLaunchSession(ctx, args) {
2237
3350
  });
2238
3351
  } catch {
2239
3352
  }
2240
- if (ctx.transport instanceof IpcTransport && node.daemonId && !isLocalNode) {
2241
- ctx.transport.meshCommand(node.daemonId, "trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
2242
- });
2243
- } else if (isLocalTransport(ctx.transport)) {
2244
- ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
2245
- });
2246
- }
3353
+ const queueTrigger = await triggerMeshQueueAndReport(ctx, node, { localNode: isLocalNode });
2247
3354
  return JSON.stringify({
2248
3355
  ...launchPayload,
2249
3356
  resolvedProviderType,
2250
- ...providerSessionId ? { providerSessionId } : {}
3357
+ ...providerSessionId ? { providerSessionId } : {},
3358
+ queueTrigger,
3359
+ ...buildQueueTriggerGuidance(queueTrigger)
2251
3360
  }, null, 2);
2252
- } else if (!isLocalTransport(ctx.transport) && node.daemonId) {
2253
- let resolvedProviderType = typeof args.type === "string" && args.type.trim() ? args.type : "";
2254
- if (!resolvedProviderType) {
2255
- const providerPriority = readProviderPriority(node.policy);
2256
- if (!providerPriority.length) {
2257
- return JSON.stringify({ success: false, error: missingProviderPriorityMessage(args.node_id) });
2258
- }
2259
- resolvedProviderType = providerPriority[0];
2260
- }
2261
- const coordinatorNode = resolveCoordinatorNode(ctx);
2262
- const coordinatorDaemonId = coordinatorNode?.daemonId || ctx.localDaemonId;
2263
- const spawnedSessionVisibility = readSpawnedSessionVisibility(ctx.mesh.policy);
2264
- if (!coordinatorDaemonId) {
2265
- return JSON.stringify(buildMissingCoordinatorDaemonIdFailure(ctx, node, resolvedProviderType), null, 2);
2266
- }
2267
- try {
2268
- const res = await ctx.transport.launch(node.daemonId, {
2269
- type: resolvedProviderType,
2270
- dir: node.workspace,
2271
- settings: {
2272
- meshNodeFor: ctx.mesh.id,
2273
- meshNodeId: args.node_id,
2274
- spawnedSessionVisibility,
2275
- ...coordinatorDaemonId ? { meshCoordinatorDaemonId: coordinatorDaemonId } : {},
2276
- ...coordinatorNode?.id ? { meshCoordinatorNodeId: coordinatorNode.id } : {},
2277
- launchedByCoordinator: true
2278
- }
2279
- });
2280
- const runtimeSessionId = typeof res?.sessionId === "string" ? res.sessionId : typeof res?.id === "string" ? res.id : "";
2281
- try {
2282
- (0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
2283
- kind: "session_launched",
2284
- nodeId: args.node_id,
2285
- sessionId: runtimeSessionId || void 0,
2286
- providerType: resolvedProviderType,
2287
- payload: {}
2288
- });
2289
- } catch {
2290
- }
2291
- return JSON.stringify({ ...res, resolvedProviderType }, null, 2);
2292
- } catch (e) {
2293
- return JSON.stringify(recordRecoverableLaunchFailure(ctx, node, resolvedProviderType, e), null, 2);
2294
- }
2295
- } else {
2296
- return JSON.stringify({ error: "Cloud mesh launch_session requires node daemonId" });
2297
3361
  }
2298
3362
  }
2299
3363
  async function meshGitStatus(ctx, args) {
@@ -2301,37 +3365,23 @@ async function meshGitStatus(ctx, args) {
2301
3365
  const autoDiscoverSubmodules = node.policy?.autoDiscoverSubmodules !== false;
2302
3366
  const submoduleIgnorePaths = node.policy?.submoduleIgnorePaths || [];
2303
3367
  try {
2304
- if (!isLocalTransport(ctx.transport) && node.daemonId) {
2305
- const result = await ctx.transport.gitStatus(node.daemonId, node.workspace, true, true);
2306
- return JSON.stringify({
2307
- nodeId: args.node_id,
2308
- workspace: node.workspace,
2309
- status: extractGitStatus(result),
2310
- diff: extractGitDiff(result),
2311
- submodules: autoDiscoverSubmodules ? extractSubmodules(result, submoduleIgnorePaths) : void 0,
2312
- relatedRepos: await collectRelatedRepoStatuses(ctx, node)
2313
- }, null, 2);
2314
- } else if (isLocalTransport(ctx.transport)) {
2315
- const statusResult = await commandForNode(ctx, node, "git_status", {
2316
- workspace: node.workspace,
2317
- refreshUpstream: true,
2318
- includeSubmodules: autoDiscoverSubmodules,
2319
- submoduleIgnorePaths: submoduleIgnorePaths.length > 0 ? submoduleIgnorePaths : void 0
2320
- });
2321
- const diffResult = await commandForNode(ctx, node, "git_diff_summary", {
2322
- workspace: node.workspace
2323
- });
2324
- return JSON.stringify({
2325
- nodeId: args.node_id,
2326
- workspace: node.workspace,
2327
- status: extractGitStatus(statusResult),
2328
- diff: extractGitDiff(diffResult),
2329
- submodules: autoDiscoverSubmodules ? extractSubmodules(statusResult, submoduleIgnorePaths) : void 0,
2330
- relatedRepos: await collectRelatedRepoStatuses(ctx, node)
2331
- }, null, 2);
2332
- } else {
2333
- return JSON.stringify({ error: "No daemonId available for cloud git_status probe" });
2334
- }
3368
+ const statusResult = await commandForNode(ctx, node, "git_status", {
3369
+ workspace: node.workspace,
3370
+ refreshUpstream: true,
3371
+ includeSubmodules: autoDiscoverSubmodules,
3372
+ submoduleIgnorePaths: submoduleIgnorePaths.length > 0 ? submoduleIgnorePaths : void 0
3373
+ });
3374
+ const diffResult = await commandForNode(ctx, node, "git_diff_summary", {
3375
+ workspace: node.workspace
3376
+ });
3377
+ return JSON.stringify({
3378
+ nodeId: args.node_id,
3379
+ workspace: node.workspace,
3380
+ status: extractGitStatus(statusResult),
3381
+ diff: extractGitDiff(diffResult),
3382
+ submodules: autoDiscoverSubmodules ? extractSubmodules(statusResult, submoduleIgnorePaths) : void 0,
3383
+ relatedRepos: await collectRelatedRepoStatuses(ctx, node)
3384
+ }, null, 2);
2335
3385
  } catch (e) {
2336
3386
  const failure = buildCoordinatorP2pRelayFailure(e, {
2337
3387
  command: "git_status",
@@ -2344,242 +3394,216 @@ async function meshGitStatus(ctx, args) {
2344
3394
  }, null, 2);
2345
3395
  }
2346
3396
  }
2347
- async function meshCheckpoint(ctx, args) {
3397
+ async function meshFastForwardNode(ctx, args) {
3398
+ await refreshMeshFromDaemon(ctx);
2348
3399
  const node = await findNodeWithRefresh(ctx, args.node_id);
3400
+ const submoduleIgnorePaths = node.policy?.submoduleIgnorePaths || [];
2349
3401
  if (node.policy?.readOnly) {
2350
- return JSON.stringify({ error: `Node '${args.node_id}' is read-only \u2014 cannot checkpoint` });
3402
+ return JSON.stringify({
3403
+ success: false,
3404
+ code: "node_read_only",
3405
+ nodeId: args.node_id,
3406
+ workspace: node.workspace,
3407
+ allowed: false,
3408
+ willRun: false,
3409
+ executed: false,
3410
+ blockingReasons: ["node_read_only"]
3411
+ }, null, 2);
2351
3412
  }
2352
- if (isLocalTransport(ctx.transport)) {
2353
- const result = await commandForNode(ctx, node, "git_checkpoint", {
3413
+ try {
3414
+ const dryRun = args.dry_run === true || args.execute !== true;
3415
+ const result = await commandForNode(ctx, node, "fast_forward_mesh_node", {
3416
+ meshId: ctx.mesh.id,
3417
+ nodeId: node.id,
2354
3418
  workspace: node.workspace,
2355
- message: args.message,
2356
- includeUntracked: true
3419
+ branch: typeof args.branch === "string" ? args.branch : void 0,
3420
+ execute: args.execute === true && args.dry_run !== true,
3421
+ dryRun,
3422
+ updateSubmodules: args.update_submodules === true,
3423
+ submoduleIgnorePaths: submoduleIgnorePaths.length > 0 ? submoduleIgnorePaths : void 0
2357
3424
  });
2358
- try {
2359
- (0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
2360
- kind: "checkpoint_created",
2361
- nodeId: args.node_id,
2362
- payload: { message: args.message, commit: result?.checkpoint?.commit }
2363
- });
2364
- } catch {
2365
- }
2366
- return JSON.stringify(result, null, 2);
2367
- } else if (!isLocalTransport(ctx.transport) && node.daemonId) {
2368
- try {
2369
- const res = await ctx.transport.gitCheckpoint(node.daemonId, {
2370
- workspace: node.workspace,
3425
+ return JSON.stringify(unwrapCommandPayload(result), null, 2);
3426
+ } catch (e) {
3427
+ const failure = buildCoordinatorP2pRelayFailure(e, {
3428
+ command: "fast_forward_mesh_node",
3429
+ targetDaemonId: node.daemonId,
3430
+ nodeId: args.node_id
3431
+ });
3432
+ return JSON.stringify({
3433
+ ...failure,
3434
+ workspace: node.workspace,
3435
+ allowed: false,
3436
+ willRun: false,
3437
+ executed: false,
3438
+ blockingReasons: [failure.code || "mesh_fast_forward_unavailable"]
3439
+ }, null, 2);
3440
+ }
3441
+ }
3442
+ async function meshCheckpoint(ctx, args) {
3443
+ const node = await findNodeWithRefresh(ctx, args.node_id);
3444
+ if (node.policy?.readOnly) {
3445
+ return JSON.stringify({ error: `Node '${args.node_id}' is read-only \u2014 cannot checkpoint` });
3446
+ }
3447
+ const result = await commandForNode(ctx, node, "git_checkpoint", {
3448
+ workspace: node.workspace,
3449
+ message: args.message,
3450
+ includeUntracked: true
3451
+ });
3452
+ try {
3453
+ (0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
3454
+ kind: "checkpoint_created",
3455
+ nodeId: args.node_id,
3456
+ payload: {
2371
3457
  message: args.message,
2372
- includeUntracked: true
2373
- });
2374
- try {
2375
- (0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
2376
- kind: "checkpoint_created",
2377
- nodeId: args.node_id,
2378
- payload: { message: args.message, commit: res?.checkpoint?.commit }
2379
- });
2380
- } catch {
3458
+ commit: result?.checkpoint?.commit,
3459
+ outcome: result?.checkpoint?.status || (result?.checkpoint?.noop ? "skipped" : void 0),
3460
+ noop: result?.checkpoint?.noop === true,
3461
+ reason: result?.checkpoint?.reason
2381
3462
  }
2382
- return JSON.stringify(res, null, 2);
2383
- } catch (e) {
2384
- return JSON.stringify({ success: false, error: e.message });
2385
- }
2386
- } else {
2387
- return JSON.stringify({ error: "Cloud mesh checkpoint requires node daemonId" });
3463
+ });
3464
+ } catch {
2388
3465
  }
3466
+ return JSON.stringify(result, null, 2);
2389
3467
  }
2390
3468
  async function meshApprove(ctx, args) {
2391
3469
  const node = await findNodeWithRefresh(ctx, args.node_id);
2392
- if (isLocalTransport(ctx.transport)) {
2393
- const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id));
2394
- const providerSessionId = cached?.providerSessionId;
2395
- const result = await commandForNode(ctx, node, "resolve_action", {
2396
- sessionId: args.session_id,
2397
- targetSessionId: args.session_id,
2398
- workspace: node.workspace,
2399
- ...cached?.providerType ? { agentType: cached.providerType, providerType: cached.providerType } : {},
2400
- ...providerSessionId ? { providerSessionId } : {},
2401
- action: args.action === "reject" ? "reject" : "approve"
2402
- });
2403
- return JSON.stringify(result, null, 2);
2404
- } else if (!isLocalTransport(ctx.transport) && node.daemonId) {
2405
- try {
2406
- const targetId = `${node.daemonId}:session:${args.session_id}`;
2407
- const res = await ctx.transport.approve(targetId, args.action === "reject" ? "reject" : "approve");
2408
- return JSON.stringify(res, null, 2);
2409
- } catch (e) {
2410
- return JSON.stringify({ success: false, error: e.message });
2411
- }
2412
- } else {
2413
- return JSON.stringify({ error: "Cloud mesh approve requires node daemonId" });
2414
- }
3470
+ const cached = getSessionMetadata(meshSessionCacheKey(args.node_id, args.session_id));
3471
+ const providerSessionId = cached?.providerSessionId;
3472
+ const result = await commandForNode(ctx, node, "resolve_action", {
3473
+ sessionId: args.session_id,
3474
+ targetSessionId: args.session_id,
3475
+ workspace: node.workspace,
3476
+ ...cached?.providerType ? { agentType: cached.providerType, providerType: cached.providerType } : {},
3477
+ ...providerSessionId ? { providerSessionId } : {},
3478
+ action: args.action === "reject" ? "reject" : "approve"
3479
+ });
3480
+ return JSON.stringify(result, null, 2);
2415
3481
  }
2416
3482
  async function meshCloneNode(ctx, args) {
2417
3483
  const sourceNode = await findNodeWithRefresh(ctx, args.source_node_id);
2418
- if (isLocalTransport(ctx.transport)) {
2419
- const result = await commandForNode(ctx, sourceNode, "clone_mesh_node", {
2420
- meshId: ctx.mesh.id,
2421
- sourceNodeId: args.source_node_id,
2422
- branch: args.branch,
2423
- baseBranch: args.base_branch,
2424
- inlineMesh: ctx.mesh
2425
- });
2426
- const clonePayload = extractCloneNodePayload(result);
2427
- if (clonePayload?.success && clonePayload.node?.id) {
2428
- const existingIndex = ctx.mesh.nodes.findIndex((n) => n.id === clonePayload.node.id);
2429
- if (existingIndex >= 0) ctx.mesh.nodes[existingIndex] = clonePayload.node;
2430
- else ctx.mesh.nodes.push(clonePayload.node);
2431
- ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2432
- await syncCoordinatorDaemonMeshCache(ctx);
2433
- }
2434
- return JSON.stringify(result, null, 2);
2435
- } else if (!isLocalTransport(ctx.transport) && sourceNode.daemonId) {
2436
- try {
2437
- const res = await ctx.transport.meshCloneNode(sourceNode.daemonId, {
2438
- meshId: ctx.mesh.id,
2439
- sourceNodeId: args.source_node_id,
2440
- branch: args.branch,
2441
- baseBranch: args.base_branch,
2442
- inlineMesh: ctx.mesh
2443
- });
2444
- const clonePayload = extractCloneNodePayload(res);
2445
- if (clonePayload?.success && clonePayload.node?.id) {
2446
- const existingIndex = ctx.mesh.nodes.findIndex((n) => n.id === clonePayload.node.id);
2447
- if (existingIndex >= 0) ctx.mesh.nodes[existingIndex] = clonePayload.node;
2448
- else ctx.mesh.nodes.push(clonePayload.node);
2449
- ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2450
- await syncCoordinatorDaemonMeshCache(ctx);
2451
- }
2452
- return JSON.stringify(res, null, 2);
2453
- } catch (e) {
2454
- return JSON.stringify({ success: false, error: e.message });
2455
- }
2456
- } else {
2457
- return JSON.stringify({ error: "Cloud mesh clone_node requires source node daemonId" });
3484
+ const result = await commandForNode(ctx, sourceNode, "clone_mesh_node", {
3485
+ meshId: ctx.mesh.id,
3486
+ sourceNodeId: args.source_node_id,
3487
+ branch: args.branch,
3488
+ baseBranch: args.base_branch,
3489
+ inlineMesh: ctx.mesh
3490
+ });
3491
+ const clonePayload = extractCloneNodePayload(result);
3492
+ if (clonePayload?.success && clonePayload.node?.id) {
3493
+ const existingIndex = ctx.mesh.nodes.findIndex((n) => n.id === clonePayload.node.id);
3494
+ if (existingIndex >= 0) ctx.mesh.nodes[existingIndex] = clonePayload.node;
3495
+ else ctx.mesh.nodes.push(clonePayload.node);
3496
+ ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
3497
+ await syncCoordinatorDaemonMeshCache(ctx);
2458
3498
  }
3499
+ return JSON.stringify(result, null, 2);
2459
3500
  }
2460
3501
  async function meshCleanupSessions(ctx, args) {
2461
3502
  const node = await findNodeWithRefresh(ctx, args.node_id);
2462
- if (isLocalTransport(ctx.transport)) {
2463
- const result = await commandForNode(ctx, node, "cleanup_mesh_sessions", {
2464
- meshId: ctx.mesh.id,
2465
- nodeId: args.node_id,
2466
- mode: args.mode,
2467
- sessionIds: args.session_ids,
2468
- dryRun: args.dry_run === true,
2469
- inlineMesh: ctx.mesh
2470
- });
2471
- return JSON.stringify(result, null, 2);
2472
- } else if (!isLocalTransport(ctx.transport) && node.daemonId) {
2473
- try {
2474
- const res = await ctx.transport.meshCleanupSessions(node.daemonId, {
2475
- meshId: ctx.mesh.id,
2476
- nodeId: args.node_id,
2477
- mode: args.mode,
2478
- sessionIds: args.session_ids,
2479
- dryRun: args.dry_run === true,
2480
- inlineMesh: ctx.mesh
2481
- });
2482
- return JSON.stringify(res, null, 2);
2483
- } catch (e) {
2484
- return JSON.stringify({ success: false, error: e.message });
2485
- }
2486
- } else {
2487
- return JSON.stringify({ error: "Cloud mesh cleanup_sessions requires node daemonId" });
2488
- }
3503
+ const result = await commandForNode(ctx, node, "cleanup_mesh_sessions", {
3504
+ meshId: ctx.mesh.id,
3505
+ nodeId: args.node_id,
3506
+ mode: args.mode,
3507
+ sessionIds: args.session_ids,
3508
+ dryRun: args.dry_run === true,
3509
+ inlineMesh: ctx.mesh
3510
+ });
3511
+ return JSON.stringify(result, null, 2);
2489
3512
  }
2490
3513
  async function meshRemoveNode(ctx, args) {
2491
3514
  const node = await findNodeWithRefresh(ctx, args.node_id);
2492
- if (isLocalTransport(ctx.transport)) {
2493
- const removeArgs = buildRemoveNodeArgs(ctx, args.node_id, args.session_cleanup_mode);
2494
- let result;
2495
- let transportFallback;
2496
- try {
2497
- result = await commandForNode(ctx, node, "remove_mesh_node", removeArgs);
2498
- } catch (e) {
2499
- if (ctx.transport instanceof IpcTransport && node.isLocalWorktree && isP2pTransportUnavailableError(e)) {
2500
- result = await ctx.transport.command("remove_mesh_node", removeArgs);
2501
- transportFallback = {
2502
- from: "p2p_mesh_relay",
2503
- to: "local_control_plane",
2504
- reason: e?.message || String(e)
2505
- };
2506
- } else {
2507
- return JSON.stringify({
2508
- success: false,
2509
- code: isP2pTransportUnavailableError(e) ? "p2p_unavailable" : "mesh_remove_node_failed",
2510
- error: e?.message || String(e),
2511
- recoveryHint: isP2pTransportUnavailableError(e) ? "If this is an ADHDev-managed local worktree, retry from a coordinator connected to the daemon that owns the worktree; dashboard command/data-plane traffic still requires P2P." : "Inspect mesh_status and retry after resolving the reported failure."
2512
- }, null, 2);
2513
- }
2514
- }
2515
- if (result?.success && result.removed !== false) {
2516
- const idx = ctx.mesh.nodes.findIndex((n) => n.id === args.node_id);
2517
- if (idx >= 0) {
2518
- ctx.mesh.nodes.splice(idx, 1);
2519
- ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2520
- }
3515
+ const removeArgs = buildRemoveNodeArgs(ctx, args.node_id, args.session_cleanup_mode);
3516
+ let result;
3517
+ let transportFallback;
3518
+ try {
3519
+ result = await commandForNode(ctx, node, "remove_mesh_node", removeArgs);
3520
+ } catch (e) {
3521
+ if (ctx.transport instanceof IpcTransport && node.isLocalWorktree && isP2pTransportUnavailableError(e)) {
3522
+ result = await ctx.transport.command("remove_mesh_node", removeArgs);
3523
+ transportFallback = {
3524
+ from: "p2p_mesh_relay",
3525
+ to: "local_control_plane",
3526
+ reason: e?.message || String(e)
3527
+ };
3528
+ } else {
3529
+ return JSON.stringify({
3530
+ success: false,
3531
+ code: isP2pTransportUnavailableError(e) ? "p2p_unavailable" : "mesh_remove_node_failed",
3532
+ error: e?.message || String(e),
3533
+ recoveryHint: isP2pTransportUnavailableError(e) ? "If this is an ADHDev-managed local worktree, retry from a coordinator connected to the daemon that owns the worktree; dashboard command/data-plane traffic still requires P2P." : "Inspect mesh_status and retry after resolving the reported failure."
3534
+ }, null, 2);
2521
3535
  }
2522
- return JSON.stringify({ ...result || {}, ...transportFallback ? { transportFallback } : {} }, null, 2);
2523
- } else if (!isLocalTransport(ctx.transport) && node.daemonId) {
2524
- try {
2525
- const res = await ctx.transport.meshRemoveNode(node.daemonId, {
2526
- meshId: ctx.mesh.id,
2527
- nodeId: args.node_id,
2528
- ...args.session_cleanup_mode ? { sessionCleanupMode: args.session_cleanup_mode } : {},
2529
- inlineMesh: ctx.mesh
2530
- });
2531
- if (res?.success && res.removed !== false) {
2532
- const idx = ctx.mesh.nodes.findIndex((n) => n.id === args.node_id);
2533
- if (idx >= 0) {
2534
- ctx.mesh.nodes.splice(idx, 1);
2535
- ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2536
- }
2537
- }
2538
- return JSON.stringify(res, null, 2);
2539
- } catch (e) {
2540
- return JSON.stringify({ success: false, error: e.message });
3536
+ }
3537
+ if (result?.success && result.removed !== false) {
3538
+ const idx = ctx.mesh.nodes.findIndex((n) => n.id === args.node_id);
3539
+ if (idx >= 0) {
3540
+ ctx.mesh.nodes.splice(idx, 1);
3541
+ ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2541
3542
  }
2542
- } else {
2543
- return JSON.stringify({ error: "Cloud mesh remove_node requires node daemonId" });
2544
3543
  }
3544
+ return JSON.stringify({ ...result || {}, ...transportFallback ? { transportFallback } : {} }, null, 2);
3545
+ }
3546
+ function resolveRefineConfigNode(ctx, nodeId) {
3547
+ if (nodeId) return findNode(ctx.mesh, nodeId);
3548
+ const node = ctx.mesh.nodes.find((entry) => !!entry.workspace);
3549
+ if (!node) throw new Error("No mesh node with a workspace is available");
3550
+ return node;
3551
+ }
3552
+ async function meshRefineConfigSchema(ctx) {
3553
+ const node = resolveRefineConfigNode(ctx);
3554
+ const result = await commandForNode(ctx, node, "get_mesh_refine_config_schema", {});
3555
+ return JSON.stringify(result, null, 2);
3556
+ }
3557
+ async function meshValidateRefineConfig(ctx, args) {
3558
+ const node = resolveRefineConfigNode(ctx, args.node_id);
3559
+ const result = await commandForNode(ctx, node, "validate_mesh_refine_config", {
3560
+ workspace: node.workspace,
3561
+ inlineMesh: ctx.mesh,
3562
+ ...args.config ? { config: args.config } : {}
3563
+ });
3564
+ return JSON.stringify(result, null, 2);
3565
+ }
3566
+ async function meshSuggestRefineConfig(ctx, args) {
3567
+ const node = resolveRefineConfigNode(ctx, args.node_id);
3568
+ const result = await commandForNode(ctx, node, "suggest_mesh_refine_config", {
3569
+ workspace: node.workspace,
3570
+ inlineMesh: ctx.mesh
3571
+ });
3572
+ return JSON.stringify(result, null, 2);
3573
+ }
3574
+ async function meshRefinePlan(ctx, args) {
3575
+ const node = await findNodeWithRefresh(ctx, args.node_id);
3576
+ const result = await commandForNode(ctx, node, "plan_mesh_refine_node", {
3577
+ meshId: ctx.mesh.id,
3578
+ nodeId: args.node_id,
3579
+ inlineMesh: ctx.mesh
3580
+ });
3581
+ return JSON.stringify(result, null, 2);
2545
3582
  }
2546
3583
  async function meshRefineNode(ctx, args) {
2547
3584
  const node = await findNodeWithRefresh(ctx, args.node_id);
2548
- if (isLocalTransport(ctx.transport)) {
2549
- const result = await commandForNode(ctx, node, "refine_mesh_node", {
2550
- meshId: ctx.mesh.id,
2551
- nodeId: args.node_id,
2552
- inlineMesh: ctx.mesh
2553
- });
2554
- if (result?.success && result.removeResult?.removed !== false) {
2555
- const idx = ctx.mesh.nodes.findIndex((n) => n.id === args.node_id);
2556
- if (idx >= 0) {
2557
- ctx.mesh.nodes.splice(idx, 1);
2558
- ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2559
- }
2560
- }
2561
- return JSON.stringify(result, null, 2);
2562
- } else if (!isLocalTransport(ctx.transport) && node.daemonId) {
2563
- try {
2564
- const res = await ctx.transport.meshRefineNode(node.daemonId, {
2565
- meshId: ctx.mesh.id,
2566
- nodeId: args.node_id,
2567
- inlineMesh: ctx.mesh
2568
- });
2569
- if (res?.success && res.removeResult?.removed !== false) {
2570
- const idx = ctx.mesh.nodes.findIndex((n) => n.id === args.node_id);
2571
- if (idx >= 0) {
2572
- ctx.mesh.nodes.splice(idx, 1);
2573
- ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2574
- }
2575
- }
2576
- return JSON.stringify(res, null, 2);
2577
- } catch (e) {
2578
- return JSON.stringify({ success: false, error: e.message });
3585
+ const result = await commandForNode(ctx, node, "refine_mesh_node", {
3586
+ meshId: ctx.mesh.id,
3587
+ nodeId: args.node_id,
3588
+ inlineMesh: ctx.mesh
3589
+ });
3590
+ if (result?.success && result.async !== true && result.removeResult?.removed !== false) {
3591
+ const idx = ctx.mesh.nodes.findIndex((n) => n.id === args.node_id);
3592
+ if (idx >= 0) {
3593
+ ctx.mesh.nodes.splice(idx, 1);
3594
+ ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2579
3595
  }
2580
- } else {
2581
- return JSON.stringify({ error: "Cloud mesh refine_node requires node daemonId" });
2582
3596
  }
3597
+ return JSON.stringify(result, null, 2);
3598
+ }
3599
+ async function meshReviewInbox(ctx, args = {}) {
3600
+ await refreshMeshFromDaemon(ctx);
3601
+ const meshId = (args.mesh_id ?? ctx.mesh.id).trim();
3602
+ const result = await commandForNode(ctx, ctx.mesh.nodes[0], "get_mesh_review_inbox", {
3603
+ meshId,
3604
+ inlineMesh: ctx.mesh
3605
+ });
3606
+ return JSON.stringify(result, null, 2);
2583
3607
  }
2584
3608
 
2585
3609
  // src/help.ts
@@ -2603,28 +3627,24 @@ var STANDARD_TOOLS = [
2603
3627
  function buildMcpHelpText() {
2604
3628
  const meshTools = ALL_MESH_TOOLS.map((tool) => tool.name);
2605
3629
  return `
2606
- adhdev-mcp \u2014 ADHDev MCP Server
3630
+ ADHDev MCP Server
2607
3631
 
2608
3632
  Usage:
2609
- adhdev-mcp Local mode (requires standalone daemon)
2610
- adhdev-mcp --api-key <key> Cloud mode (ADHDev cloud API)
2611
- adhdev-mcp --mode ipc --repo-mesh <mesh_id> Cloud daemon IPC mesh mode
2612
- adhdev-mcp --repo-mesh <mesh_id> Mesh mode (coordinator-scoped tools)
3633
+ adhdev mcp Local mode (requires standalone daemon)
3634
+ adhdev mcp --mode ipc --repo-mesh <mesh_id> Cloud daemon IPC mesh mode
3635
+ adhdev-mcp --help Compatibility bin (same server, legacy package entrypoint)
2613
3636
 
2614
3637
  Options:
2615
- --mode <mode> Transport: local, cloud, or ipc
3638
+ --mode <mode> Transport: local or ipc
2616
3639
  --port <n> Standalone or IPC daemon port (defaults: local 3847, ipc 19222)
2617
3640
  --password <pass> Standalone daemon password (if set)
2618
- --api-key <key> ADHDev cloud API key (switches to cloud mode)
2619
- --base-url <url> Override cloud API base URL
2620
3641
  --repo-mesh <mesh_id> Enable mesh mode \u2014 exposes only mesh-scoped coordinator tools
2621
3642
  --help Show this help
2622
3643
 
2623
3644
  Environment variables:
2624
- ADHDEV_API_KEY API key (cloud mode)
2625
3645
  ADHDEV_PASSWORD Daemon password (local mode)
2626
3646
  ADHDEV_MESH_ID Mesh ID (mesh mode)
2627
- ADHDEV_MCP_TRANSPORT Transport: local, cloud, or ipc
3647
+ ADHDEV_MCP_TRANSPORT Transport: local or ipc
2628
3648
 
2629
3649
  Standard tools: ${STANDARD_TOOLS.join(", ")}
2630
3650
  Mesh tools: ${meshTools.join(", ")}
@@ -2634,6 +3654,7 @@ Mesh tools: ${meshTools.join(", ")}
2634
3654
  // src/server.ts
2635
3655
  var import_server = require("@modelcontextprotocol/sdk/server/index.js");
2636
3656
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
3657
+ var import_node_os = __toESM(require("os"));
2637
3658
  var import_types = require("@modelcontextprotocol/sdk/types.js");
2638
3659
 
2639
3660
  // src/transports/local.ts
@@ -2652,287 +3673,24 @@ var LocalTransport = class {
2652
3673
  }
2653
3674
  async getStatus() {
2654
3675
  const res = await fetch(`${this.baseUrl}/api/v1/status`, { headers: this.headers() });
2655
- if (!res.ok) throw new Error(`Status fetch failed: ${res.status}`);
2656
- return res.json();
2657
- }
2658
- async command(type, args = {}) {
2659
- const res = await fetch(`${this.baseUrl}/api/v1/command`, {
2660
- method: "POST",
2661
- headers: this.headers(),
2662
- body: JSON.stringify({ type, ...args })
2663
- });
2664
- if (!res.ok) {
2665
- const text = await res.text().catch(() => res.statusText);
2666
- throw new Error(`Command ${type} failed: ${res.status} ${text}`);
2667
- }
2668
- return res.json();
2669
- }
2670
- async ping() {
2671
- try {
2672
- await this.getStatus();
2673
- return true;
2674
- } catch {
2675
- return false;
2676
- }
2677
- }
2678
- };
2679
-
2680
- // src/transports/cloud.ts
2681
- var DEFAULT_BASE_URL = "https://api.adhf.dev";
2682
- var CloudTransport = class {
2683
- baseUrl;
2684
- apiKey;
2685
- constructor(opts) {
2686
- this.apiKey = opts.apiKey;
2687
- this.baseUrl = opts.baseUrl ?? DEFAULT_BASE_URL;
2688
- }
2689
- headers() {
2690
- return {
2691
- "Content-Type": "application/json",
2692
- "Authorization": `Bearer ${this.apiKey}`
2693
- };
2694
- }
2695
- async listRemoteMeshes() {
2696
- const res = await fetch(`${this.baseUrl}/api/v1/repo-meshes`, { headers: this.headers() });
2697
- if (!res.ok) throw new Error(`List remote meshes failed: ${res.status}`);
3676
+ if (!res.ok) throw new Error(`Status fetch failed: ${res.status}`);
2698
3677
  return res.json();
2699
3678
  }
2700
- async createRemoteMesh(data) {
2701
- const res = await fetch(`${this.baseUrl}/api/v1/repo-meshes`, {
3679
+ async command(type, args = {}) {
3680
+ const res = await fetch(`${this.baseUrl}/api/v1/command`, {
2702
3681
  method: "POST",
2703
3682
  headers: this.headers(),
2704
- body: JSON.stringify(data)
2705
- });
2706
- if (!res.ok) throw new Error(`Create remote mesh failed: ${res.status}`);
2707
- return res.json();
2708
- }
2709
- async deleteRemoteMesh(meshId) {
2710
- const res = await fetch(`${this.baseUrl}/api/v1/repo-meshes/${encodeURIComponent(meshId)}`, {
2711
- method: "DELETE",
2712
- headers: this.headers()
3683
+ body: JSON.stringify({ type, ...args })
2713
3684
  });
2714
- if (!res.ok) throw new Error(`Delete remote mesh failed: ${res.status}`);
2715
- }
2716
- async listDaemons() {
2717
- const res = await fetch(`${this.baseUrl}/api/v1/daemons`, { headers: this.headers() });
2718
- if (!res.ok) throw new Error(`List daemons failed: ${res.status}`);
2719
- return res.json();
2720
- }
2721
- async getStatus(targetId) {
2722
- const res = await fetch(
2723
- `${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(targetId)}/status`,
2724
- { headers: this.headers() }
2725
- );
2726
- if (!res.ok) throw new Error(`Status failed: ${res.status}`);
2727
- return res.json();
2728
- }
2729
- /** Get all sessions for a daemon (returns CompactSessionEntry[]). */
2730
- async getDaemonStatus(daemonId) {
2731
- const res = await fetch(
2732
- `${this.baseUrl}/api/v1/daemons/${encodeURIComponent(daemonId)}/status`,
2733
- { headers: this.headers() }
2734
- );
2735
- if (!res.ok) throw new Error(`Daemon status failed: ${res.status}`);
2736
- return res.json();
2737
- }
2738
- async readChat(targetId, opts = {}) {
2739
- const params = new URLSearchParams();
2740
- if (opts.limit) params.set("limit", String(opts.limit));
2741
- if (opts.sessionId) params.set("sessionId", opts.sessionId);
2742
- const qs = params.toString() ? `?${params}` : "";
2743
- const res = await fetch(
2744
- `${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(targetId)}/chat${qs}`,
2745
- { headers: this.headers() }
2746
- );
2747
- if (!res.ok) throw new Error(`Read chat failed: ${res.status}`);
2748
- return res.json();
2749
- }
2750
- async getChatDebugBundle(targetId, opts = {}) {
2751
- const res = await fetch(
2752
- `${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(targetId)}/chat/debug`,
2753
- {
2754
- method: "POST",
2755
- headers: this.headers(),
2756
- body: JSON.stringify({
2757
- ...opts.agentType ? { agentType: opts.agentType } : {},
2758
- ...opts.sessionId ? { sessionId: opts.sessionId } : {},
2759
- ...opts.tailLimit ? { tailLimit: opts.tailLimit } : {},
2760
- ...opts.delivery ? { delivery: opts.delivery } : {}
2761
- })
2762
- }
2763
- );
2764
- if (!res.ok) throw new Error(`Chat debug bundle failed: ${res.status}`);
2765
- return res.json();
2766
- }
2767
- async sendChat(targetId, message, opts = {}) {
2768
- const res = await fetch(
2769
- `${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(targetId)}/chat`,
2770
- {
2771
- method: "POST",
2772
- headers: this.headers(),
2773
- body: JSON.stringify({ message, ...opts })
2774
- }
2775
- );
2776
- if (!res.ok) throw new Error(`Send chat failed: ${res.status}`);
2777
- return res.json();
2778
- }
2779
- async approve(targetId, action, agentType) {
2780
- const res = await fetch(
2781
- `${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(targetId)}/approve`,
2782
- {
2783
- method: "POST",
2784
- headers: this.headers(),
2785
- body: JSON.stringify({ action, ...agentType ? { agentType } : {} })
2786
- }
2787
- );
2788
- if (!res.ok) throw new Error(`Approve failed: ${res.status}`);
2789
- return res.json();
2790
- }
2791
- async gitStatus(daemonId, workspace, includeDiff = true, refreshUpstream = false) {
2792
- const params = new URLSearchParams({ workspace, includeDiff: String(includeDiff), refreshUpstream: String(refreshUpstream) });
2793
- const res = await fetch(
2794
- `${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/git-status?${params}`,
2795
- { headers: this.headers() }
2796
- );
2797
- if (!res.ok) throw new Error(`Git status failed: ${res.status}`);
2798
- return res.json();
2799
- }
2800
- async stop(daemonId, opts) {
2801
- const res = await fetch(
2802
- `${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/stop`,
2803
- {
2804
- method: "POST",
2805
- headers: this.headers(),
2806
- body: JSON.stringify(opts)
2807
- }
2808
- );
2809
- if (!res.ok) throw new Error(`Stop failed: ${res.status}`);
2810
- return res.json();
2811
- }
2812
- async launch(daemonId, opts) {
2813
- const res = await fetch(
2814
- `${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/launch`,
2815
- {
2816
- method: "POST",
2817
- headers: this.headers(),
2818
- body: JSON.stringify(opts)
2819
- }
2820
- );
2821
- if (!res.ok) throw new Error(`Launch failed: ${res.status}`);
2822
- return res.json();
2823
- }
2824
- async gitLog(daemonId, workspace, opts = {}) {
2825
- const params = new URLSearchParams({ workspace });
2826
- if (opts.limit) params.set("limit", String(opts.limit));
2827
- if (opts.file) params.set("file", opts.file);
2828
- if (opts.since) params.set("since", opts.since);
2829
- if (opts.until) params.set("until", opts.until);
2830
- const res = await fetch(
2831
- `${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/git-log?${params}`,
2832
- { headers: this.headers() }
2833
- );
2834
- if (!res.ok) throw new Error(`Git log failed: ${res.status}`);
2835
- return res.json();
2836
- }
2837
- async gitDiff(daemonId, workspace, opts = {}) {
2838
- const params = new URLSearchParams({ workspace });
2839
- if (opts.file) params.set("file", opts.file);
2840
- if (opts.maxLines) params.set("maxLines", String(opts.maxLines));
2841
- if (opts.staged) params.set("staged", "true");
2842
- const res = await fetch(
2843
- `${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/git-diff?${params}`,
2844
- { headers: this.headers() }
2845
- );
2846
- if (!res.ok) throw new Error(`Git diff failed: ${res.status}`);
2847
- return res.json();
2848
- }
2849
- async gitPush(daemonId, opts) {
2850
- const res = await fetch(
2851
- `${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/git-push`,
2852
- {
2853
- method: "POST",
2854
- headers: this.headers(),
2855
- body: JSON.stringify(opts)
2856
- }
2857
- );
2858
- if (!res.ok) throw new Error(`Git push failed: ${res.status}`);
2859
- return res.json();
2860
- }
2861
- async gitCheckpoint(daemonId, opts) {
2862
- const res = await fetch(
2863
- `${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/git-checkpoint`,
2864
- {
2865
- method: "POST",
2866
- headers: this.headers(),
2867
- body: JSON.stringify(opts)
2868
- }
2869
- );
2870
- if (!res.ok) throw new Error(`Git checkpoint failed: ${res.status}`);
2871
- return res.json();
2872
- }
2873
- async meshCloneNode(daemonId, payload) {
2874
- const res = await fetch(
2875
- `${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/mesh/clone-node`,
2876
- {
2877
- method: "POST",
2878
- headers: this.headers(),
2879
- body: JSON.stringify(payload)
2880
- }
2881
- );
2882
- if (!res.ok) throw new Error(`Mesh clone node failed: ${res.status}`);
2883
- return res.json();
2884
- }
2885
- async meshRemoveNode(daemonId, payload) {
2886
- const res = await fetch(
2887
- `${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/mesh/remove-node`,
2888
- {
2889
- method: "POST",
2890
- headers: this.headers(),
2891
- body: JSON.stringify(payload)
2892
- }
2893
- );
2894
- if (!res.ok) throw new Error(`Mesh remove node failed: ${res.status}`);
2895
- return res.json();
2896
- }
2897
- async meshCleanupSessions(daemonId, payload) {
2898
- const res = await fetch(
2899
- `${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/mesh/cleanup-sessions`,
2900
- {
2901
- method: "POST",
2902
- headers: this.headers(),
2903
- body: JSON.stringify(payload)
2904
- }
2905
- );
2906
- if (!res.ok) throw new Error(`Mesh cleanup sessions failed: ${res.status}`);
2907
- return res.json();
2908
- }
2909
- async meshEnqueueTask(daemonId, payload) {
2910
- const res = await fetch(
2911
- `${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/mesh/enqueue`,
2912
- {
2913
- method: "POST",
2914
- headers: this.headers(),
2915
- body: JSON.stringify(payload)
2916
- }
2917
- );
2918
- if (!res.ok) throw new Error(`Mesh enqueue task failed: ${res.status}`);
2919
- return res.json();
2920
- }
2921
- async meshRefineNode(daemonId, payload) {
2922
- const res = await fetch(
2923
- `${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/mesh/refine-node`,
2924
- {
2925
- method: "POST",
2926
- headers: this.headers(),
2927
- body: JSON.stringify(payload)
2928
- }
2929
- );
2930
- if (!res.ok) throw new Error(`Mesh refine node failed: ${res.status}`);
3685
+ if (!res.ok) {
3686
+ const text = await res.text().catch(() => res.statusText);
3687
+ throw new Error(`Command ${type} failed: ${res.status} ${text}`);
3688
+ }
2931
3689
  return res.json();
2932
3690
  }
2933
3691
  async ping() {
2934
3692
  try {
2935
- await this.listDaemons();
3693
+ await this.getStatus();
2936
3694
  return true;
2937
3695
  } catch {
2938
3696
  return false;
@@ -2950,14 +3708,10 @@ var FORMAT_PROP = {
2950
3708
  };
2951
3709
  var LIST_SESSIONS_TOOL = {
2952
3710
  name: "list_sessions",
2953
- description: "List all connected agent sessions. In cloud mode, fetches session state from each daemon (data is sourced from daemon WS status reports, up to 30s stale). Pass daemon_id to scope to a single daemon.",
3711
+ description: "List all connected agent sessions.",
2954
3712
  inputSchema: {
2955
3713
  type: "object",
2956
3714
  properties: {
2957
- daemon_id: {
2958
- type: "string",
2959
- description: "Daemon ID (cloud mode only). Omit to list sessions across all daemons."
2960
- },
2961
3715
  ...FORMAT_PROP
2962
3716
  },
2963
3717
  required: []
@@ -2965,87 +3719,35 @@ var LIST_SESSIONS_TOOL = {
2965
3719
  };
2966
3720
  async function listSessions(transport, args = {}) {
2967
3721
  const asJson = args.format === "json";
2968
- if (isLocalTransport(transport)) {
2969
- const status = await transport.getStatus();
2970
- const sessions = status?.sessions ?? [];
2971
- if (asJson) {
2972
- return JSON.stringify({
2973
- sessions: sessions.map((s) => ({
2974
- id: s.id,
2975
- type: s.providerType ?? s.type ?? "unknown",
2976
- label: s.label ?? null,
2977
- status: s.status ?? s.agentStatus ?? null,
2978
- workspace: s.workspace ?? null
2979
- }))
2980
- }, null, 2);
2981
- }
2982
- if (sessions.length === 0) return "No active sessions.";
2983
- const lines = sessions.map((s) => {
2984
- const parts = [`id: ${s.id}`, `type: ${s.providerType ?? s.type ?? "unknown"}`];
2985
- if (s.label) parts.push(`label: ${s.label}`);
2986
- if (s.status ?? s.agentStatus) parts.push(`status: ${s.status ?? s.agentStatus}`);
2987
- if (s.workspace) parts.push(`workspace: ${s.workspace}`);
2988
- return parts.join(", ");
2989
- });
2990
- return `Sessions (${sessions.length}):
2991
- ${lines.join("\n")}`;
2992
- }
2993
- return listSessionsCloud(transport, args.daemon_id, asJson);
2994
- }
2995
- async function listSessionsCloud(transport, daemonId, asJson) {
2996
- const collected = [];
2997
- if (daemonId) {
2998
- const daemonStatus = await transport.getDaemonStatus(daemonId);
2999
- for (const s of daemonStatus?.sessions ?? []) {
3000
- collected.push({ daemonId, session: s });
3001
- }
3002
- } else {
3003
- const data = await transport.listDaemons();
3004
- const daemons = data?.daemons ?? [];
3005
- for (let i = 0; i < daemons.length; i += 5) {
3006
- await Promise.allSettled(
3007
- daemons.slice(i, i + 5).map(async (d) => {
3008
- try {
3009
- const daemonStatus = await transport.getDaemonStatus(d.id);
3010
- for (const s of daemonStatus?.sessions ?? []) {
3011
- collected.push({ daemonId: d.id, session: s });
3012
- }
3013
- } catch {
3014
- }
3015
- })
3016
- );
3017
- }
3018
- }
3722
+ const status = await transport.getStatus();
3723
+ const sessions = status?.sessions ?? [];
3019
3724
  if (asJson) {
3020
3725
  return JSON.stringify({
3021
- sessions: collected.map(({ daemonId: dId, session: s }) => ({
3022
- daemon_id: dId,
3726
+ sessions: sessions.map((s) => ({
3023
3727
  id: s.id,
3024
- type: s.providerType ?? "unknown",
3025
- status: s.status ?? null,
3728
+ type: s.providerType ?? s.type ?? "unknown",
3729
+ label: s.label ?? null,
3730
+ status: s.status ?? s.agentStatus ?? null,
3026
3731
  workspace: s.workspace ?? null
3027
3732
  }))
3028
3733
  }, null, 2);
3029
3734
  }
3030
- if (collected.length === 0) return "No active sessions.";
3031
- const lines = collected.map(({ daemonId: dId, session: s }) => {
3032
- const parts = [
3033
- `daemon: ${dId}`,
3034
- `session: ${s.id}`,
3035
- `type: ${s.providerType ?? "unknown"}`
3036
- ];
3037
- if (s.status) parts.push(`status: ${s.status}`);
3735
+ if (sessions.length === 0) return "No active sessions.";
3736
+ const lines = sessions.map((s) => {
3737
+ const parts = [`id: ${s.id}`, `type: ${s.providerType ?? s.type ?? "unknown"}`];
3738
+ if (s.label) parts.push(`label: ${s.label}`);
3739
+ if (s.status ?? s.agentStatus) parts.push(`status: ${s.status ?? s.agentStatus}`);
3038
3740
  if (s.workspace) parts.push(`workspace: ${s.workspace}`);
3039
3741
  return parts.join(", ");
3040
3742
  });
3041
- return `Sessions (${collected.length}):
3743
+ return `Sessions (${sessions.length}):
3042
3744
  ${lines.join("\n")}`;
3043
3745
  }
3044
3746
 
3045
3747
  // src/tools/list-daemons.ts
3046
3748
  var LIST_DAEMONS_TOOL = {
3047
3749
  name: "list_daemons",
3048
- description: "List all connected daemons (machines running the ADHDev agent). Use this to discover daemon IDs before calling launch_session, git_status, or other tools that require daemon_id. In local mode returns the single standalone daemon info.",
3750
+ description: "List the connected daemon (machine running the ADHDev agent). Returns the daemon identity extracted from its status report.",
3049
3751
  inputSchema: {
3050
3752
  type: "object",
3051
3753
  properties: {
@@ -3056,46 +3758,17 @@ var LIST_DAEMONS_TOOL = {
3056
3758
  };
3057
3759
  async function listDaemons(transport, args = {}) {
3058
3760
  const asJson = args.format === "json";
3059
- if (isLocalTransport(transport)) {
3060
- const status = await transport.getStatus();
3061
- const daemon = {
3062
- id: status?.id ?? status?.instanceId ?? "standalone",
3063
- hostname: status?.hostname ?? status?.machine?.hostname ?? "localhost",
3064
- platform: status?.platform ?? status?.machine?.platform ?? "unknown",
3065
- version: status?.version ?? null,
3066
- sessions: (status?.sessions ?? []).length
3067
- };
3068
- if (asJson) return JSON.stringify({ daemons: [daemon] }, null, 2);
3069
- return `Daemons (1):
3761
+ const status = await transport.getStatus();
3762
+ const daemon = {
3763
+ id: status?.id ?? status?.instanceId ?? "standalone",
3764
+ hostname: status?.hostname ?? status?.machine?.hostname ?? "localhost",
3765
+ platform: status?.platform ?? status?.machine?.platform ?? "unknown",
3766
+ version: status?.version ?? null,
3767
+ sessions: (status?.sessions ?? []).length
3768
+ };
3769
+ if (asJson) return JSON.stringify({ daemons: [daemon] }, null, 2);
3770
+ return `Daemons (1):
3070
3771
  id: ${daemon.id}, hostname: ${daemon.hostname}, platform: ${daemon.platform}${daemon.version ? `, version: ${daemon.version}` : ""}, sessions: ${daemon.sessions}`;
3071
- }
3072
- const data = await transport.listDaemons();
3073
- const daemons = data?.daemons ?? [];
3074
- if (asJson) {
3075
- return JSON.stringify({
3076
- daemons: daemons.map((d) => ({
3077
- id: d.id,
3078
- hostname: d.hostname ?? null,
3079
- platform: d.platform ?? null,
3080
- nickname: d.nickname ?? null,
3081
- version: d.version ?? null,
3082
- p2p_available: d.p2p?.available ?? null,
3083
- cdp_connected: d.cdpConnected ?? null
3084
- }))
3085
- }, null, 2);
3086
- }
3087
- if (daemons.length === 0) return "No connected daemons.";
3088
- const lines = daemons.map((d) => {
3089
- const parts = [`id: ${d.id}`];
3090
- if (d.nickname) parts.push(`nickname: ${d.nickname}`);
3091
- if (d.hostname) parts.push(`hostname: ${d.hostname}`);
3092
- if (d.platform) parts.push(`platform: ${d.platform}`);
3093
- if (d.version) parts.push(`version: ${d.version}`);
3094
- if (d.p2p?.available != null) parts.push(`p2p: ${d.p2p.available ? "yes" : "no"}`);
3095
- return parts.join(", ");
3096
- });
3097
- return `Daemons (${daemons.length}):
3098
- ${lines.join("\n")}`;
3099
3772
  }
3100
3773
 
3101
3774
  // src/tools/read-chat.ts
@@ -3113,10 +3786,6 @@ var READ_CHAT_TOOL = {
3113
3786
  type: "number",
3114
3787
  description: "Max messages to return (default: 50)."
3115
3788
  },
3116
- daemon_id: {
3117
- type: "string",
3118
- description: "Daemon ID (cloud mode only). Omit for local mode."
3119
- },
3120
3789
  compact: {
3121
3790
  type: "boolean",
3122
3791
  description: "Opt-in compact mode: filters tool/terminal/system/internal/control/debug/status chatter and returns user-visible messages plus lightweight summary metadata."
@@ -3128,23 +3797,12 @@ var READ_CHAT_TOOL = {
3128
3797
  };
3129
3798
  async function readChat(transport, args) {
3130
3799
  const limit = args.limit ?? 50;
3131
- if (isLocalTransport(transport)) {
3132
- const result2 = await transport.command("read_chat", {
3133
- ...args.session_id ? { targetSessionId: args.session_id } : {},
3134
- tailLimit: limit
3135
- });
3136
- const annotated2 = annotateRapidReadChatAdvisory(result2, {
3137
- key: `local:${args.session_id ?? "__active__"}`,
3138
- toolName: "read_chat",
3139
- completionCallbackExpected: false
3140
- });
3141
- return formatChatResult(annotated2, args.session_id, args.format, limit, args.compact);
3142
- }
3143
- if (!args.daemon_id) throw new Error("daemon_id is required in cloud mode");
3144
- const targetId = args.session_id ? `${args.daemon_id}:session:${args.session_id}` : args.daemon_id;
3145
- const result = await transport.readChat(targetId, { limit, sessionId: args.session_id });
3800
+ const result = await transport.command("read_chat", {
3801
+ ...args.session_id ? { targetSessionId: args.session_id } : {},
3802
+ tailLimit: limit
3803
+ });
3146
3804
  const annotated = annotateRapidReadChatAdvisory(result, {
3147
- key: `cloud:${args.daemon_id}:${args.session_id ?? "__active__"}`,
3805
+ key: `local:${args.session_id ?? "__active__"}`,
3148
3806
  toolName: "read_chat",
3149
3807
  completionCallbackExpected: false
3150
3808
  });
@@ -3185,11 +3843,17 @@ function formatChatResult(result, sessionId, format, limit = 50, compact = false
3185
3843
  }, null, 2);
3186
3844
  }
3187
3845
  if ((format === "text" || format === void 0) && compact && compactPayload) {
3188
- const lines2 = outputMessages.slice(-limit).map((m) => {
3846
+ const summaryText = typeof compactPayload.summary === "string" ? compactPayload.summary.trim() : "";
3847
+ const tail = outputMessages.slice(-limit);
3848
+ const lastIndex = tail.length - 1;
3849
+ const lines2 = tail.flatMap((m, idx) => {
3189
3850
  const role = m.role === "user" ? "User" : m.role === "assistant" ? "Agent" : m.role;
3190
3851
  const content = messageContent(m);
3852
+ if (idx === lastIndex && (role === "Agent" || m.role === "agent") && summaryText && content.trim() === summaryText) {
3853
+ return [];
3854
+ }
3191
3855
  const truncated = content.length > 500 ? `${content.slice(0, 500)}\u2026` : content;
3192
- return `[${role}] ${truncated}`;
3856
+ return [`[${role}] ${truncated}`];
3193
3857
  });
3194
3858
  if (compactPayload.summary) {
3195
3859
  const truncatedSummary = compactPayload.summary.length > 500 ? `${compactPayload.summary.slice(0, 500)}\u2026` : compactPayload.summary;
@@ -3228,10 +3892,6 @@ var READ_CHAT_DEBUG_TOOL = {
3228
3892
  type: "string",
3229
3893
  description: "Target session ID (from list_sessions). Required for reliable routing."
3230
3894
  },
3231
- daemon_id: {
3232
- type: "string",
3233
- description: "Daemon ID (cloud mode only). Omit for local mode."
3234
- },
3235
3895
  agent_type: {
3236
3896
  type: "string",
3237
3897
  description: "Optional provider/agent type hint, e.g. hermes-cli, claude-cli, codex-cli."
@@ -3261,19 +3921,7 @@ async function readChatDebug(transport, args) {
3261
3921
  ...args.agent_type ? { agentType: args.agent_type, providerType: args.agent_type } : {},
3262
3922
  ...delivery === "daemon_file" ? { delivery: "daemon_file" } : {}
3263
3923
  };
3264
- let result;
3265
- if (isLocalTransport(transport)) {
3266
- result = await transport.command("get_chat_debug_bundle", commandArgs);
3267
- } else {
3268
- if (!args.daemon_id) throw new Error("daemon_id is required in cloud mode");
3269
- const targetId = `${args.daemon_id}:session:${sessionId}`;
3270
- result = await transport.getChatDebugBundle(targetId, {
3271
- sessionId,
3272
- agentType: args.agent_type,
3273
- tailLimit,
3274
- delivery
3275
- });
3276
- }
3924
+ const result = await transport.command("get_chat_debug_bundle", commandArgs);
3277
3925
  return formatChatDebugResult(result, { sessionId, delivery, format: args.format });
3278
3926
  }
3279
3927
  function formatChatDebugResult(result, options) {
@@ -3304,6 +3952,79 @@ function formatChatDebugResult(result, options) {
3304
3952
  return JSON.stringify(result, null, 2);
3305
3953
  }
3306
3954
 
3955
+ // src/tools/spec-debug.ts
3956
+ var SPEC_DEBUG_TOOL = {
3957
+ name: "spec_debug",
3958
+ 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.",
3959
+ inputSchema: {
3960
+ type: "object",
3961
+ properties: {
3962
+ session_id: {
3963
+ type: "string",
3964
+ description: "Target session ID (from list_sessions)."
3965
+ },
3966
+ ...FORMAT_PROP
3967
+ },
3968
+ required: ["session_id"]
3969
+ }
3970
+ };
3971
+ async function specDebug(transport, args) {
3972
+ const sessionId = typeof args.session_id === "string" ? args.session_id.trim() : "";
3973
+ if (!sessionId) throw new Error("session_id is required");
3974
+ const result = await transport.command("get_spec_debug", { targetSessionId: sessionId });
3975
+ return formatSpecDebugResult(result, { sessionId, format: args.format });
3976
+ }
3977
+ function formatSpecDebugResult(result, options) {
3978
+ if (!result?.success) {
3979
+ const err = result?.error || "Unknown error";
3980
+ if (options.format === "json") return JSON.stringify({ success: false, error: err }, null, 2);
3981
+ return `Error: ${err}`;
3982
+ }
3983
+ if (options.format === "json") return JSON.stringify(result, null, 2);
3984
+ const snap = result.snapshot;
3985
+ if (!snap) {
3986
+ return [
3987
+ `session_id: ${options.sessionId}`,
3988
+ `provider_type: ${String(result.providerType || "")}`,
3989
+ "is_spec_provider: false",
3990
+ "No spec debug data available (not a spec-driven provider)."
3991
+ ].join("\n");
3992
+ }
3993
+ const lines = [];
3994
+ lines.push(`session_id: ${options.sessionId}`);
3995
+ lines.push(`provider_type: ${String(result.providerType || snap.cliType || "")}`);
3996
+ lines.push(`spec_id: ${String(snap.spec_id || "")}`);
3997
+ lines.push(`spec_path: ${String(snap.specPath || "")}`);
3998
+ lines.push(`current_state: ${snap.current_state ? `${snap.current_state.id} (${snap.current_state.label})` : "none"}`);
3999
+ lines.push(`idle_hold_pending: ${String(snap.idleHoldPending ?? false)}`);
4000
+ lines.push(`last_busy_at: ${snap.lastBusyAt ? new Date(snap.lastBusyAt).toISOString() : "never"}`);
4001
+ lines.push(`exited: ${String(snap.exited ?? false)}`);
4002
+ if (snap.current_modal) {
4003
+ lines.push(`current_modal: ${JSON.stringify(snap.current_modal)}`);
4004
+ }
4005
+ if (snap.sections && typeof snap.sections === "object") {
4006
+ lines.push("");
4007
+ lines.push("\u2500\u2500 sections \u2500\u2500");
4008
+ for (const [id, text] of Object.entries(snap.sections)) {
4009
+ const preview = String(text || "").replace(/\n/g, "\u21B5").slice(0, 120);
4010
+ lines.push(` ${id}: ${preview}`);
4011
+ }
4012
+ }
4013
+ const history = Array.isArray(snap.stateHistory) ? snap.stateHistory : [];
4014
+ if (history.length > 0) {
4015
+ lines.push("");
4016
+ lines.push("\u2500\u2500 state history (newest first) \u2500\u2500");
4017
+ const now = Date.now();
4018
+ for (const entry of [...history].reverse().slice(0, 20)) {
4019
+ const agoMs = now - entry.at;
4020
+ const ago = agoMs < 2e3 ? `${agoMs}ms ago` : `${(agoMs / 1e3).toFixed(1)}s ago`;
4021
+ const dur = entry.durationMs > 0 ? ` held ${entry.durationMs}ms` : "";
4022
+ lines.push(` ${String(entry.stateId).padEnd(18)} ${ago}${dur}`);
4023
+ }
4024
+ }
4025
+ return lines.join("\n");
4026
+ }
4027
+
3307
4028
  // src/tools/send-chat.ts
3308
4029
  var SEND_CHAT_TOOL = {
3309
4030
  name: "send_chat",
@@ -3318,10 +4039,6 @@ var SEND_CHAT_TOOL = {
3318
4039
  session_id: {
3319
4040
  type: "string",
3320
4041
  description: "Target session ID (from list_sessions). Omit to use the active session."
3321
- },
3322
- daemon_id: {
3323
- type: "string",
3324
- description: "Daemon ID (cloud mode only). Omit for local mode."
3325
4042
  }
3326
4043
  },
3327
4044
  required: ["message"]
@@ -3329,18 +4046,9 @@ var SEND_CHAT_TOOL = {
3329
4046
  };
3330
4047
  async function sendChat(transport, args) {
3331
4048
  if (!args.message?.trim()) throw new Error("message is required");
3332
- if (isLocalTransport(transport)) {
3333
- const result2 = await transport.command("send_chat", {
3334
- message: args.message,
3335
- ...args.session_id ? { targetSessionId: args.session_id } : {}
3336
- });
3337
- if (result2?.success === false) return `Error: ${result2.error ?? "send_chat failed"}`;
3338
- return "Message sent.";
3339
- }
3340
- if (!args.daemon_id) throw new Error("daemon_id is required in cloud mode");
3341
- const targetId = args.session_id ? `${args.daemon_id}:session:${args.session_id}` : args.daemon_id;
3342
- const result = await transport.sendChat(targetId, args.message, {
3343
- ...args.session_id ? { sessionId: args.session_id } : {}
4049
+ const result = await transport.command("send_chat", {
4050
+ message: args.message,
4051
+ ...args.session_id ? { targetSessionId: args.session_id } : {}
3344
4052
  });
3345
4053
  if (result?.success === false) return `Error: ${result.error ?? "send_chat failed"}`;
3346
4054
  return "Message sent.";
@@ -3361,10 +4069,6 @@ var APPROVE_TOOL = {
3361
4069
  session_id: {
3362
4070
  type: "string",
3363
4071
  description: "Target session ID. Omit to use the active session."
3364
- },
3365
- daemon_id: {
3366
- type: "string",
3367
- description: "Daemon ID (cloud mode only)."
3368
4072
  }
3369
4073
  },
3370
4074
  required: ["action"]
@@ -3372,25 +4076,18 @@ var APPROVE_TOOL = {
3372
4076
  };
3373
4077
  async function approve(transport, args) {
3374
4078
  const action = args.action === "reject" ? "reject" : "approve";
3375
- if (isLocalTransport(transport)) {
3376
- const result2 = await transport.command("resolve_action", {
3377
- action,
3378
- ...args.session_id ? { targetSessionId: args.session_id } : {}
3379
- });
3380
- if (result2?.success === false) return `Error: ${result2.error ?? "resolve_action failed"}`;
3381
- return `Action ${action}d.`;
3382
- }
3383
- if (!args.daemon_id) throw new Error("daemon_id is required in cloud mode");
3384
- const targetId = args.session_id ? `${args.daemon_id}:session:${args.session_id}` : args.daemon_id;
3385
- const result = await transport.approve(targetId, action);
3386
- if (result?.success === false) return `Error: ${result.error ?? "approve failed"}`;
4079
+ const result = await transport.command("resolve_action", {
4080
+ action,
4081
+ ...args.session_id ? { targetSessionId: args.session_id } : {}
4082
+ });
4083
+ if (result?.success === false) return `Error: ${result.error ?? "resolve_action failed"}`;
3387
4084
  return `Action ${action}d.`;
3388
4085
  }
3389
4086
 
3390
4087
  // src/tools/screenshot.ts
3391
4088
  var SCREENSHOT_TOOL = {
3392
4089
  name: "screenshot",
3393
- description: "Capture a screenshot of the current IDE window. Returns the image. Local mode only \u2014 screenshots require direct P2P access to the daemon and are not available in cloud mode.",
4090
+ description: "Capture a screenshot of the current IDE window. Returns the image.",
3394
4091
  inputSchema: {
3395
4092
  type: "object",
3396
4093
  properties: {
@@ -3403,14 +4100,9 @@ var SCREENSHOT_TOOL = {
3403
4100
  }
3404
4101
  };
3405
4102
  async function screenshot(transport, args) {
3406
- let result;
3407
- if (isLocalTransport(transport)) {
3408
- result = await transport.command("screenshot", {
3409
- ...args.session_id ? { targetSessionId: args.session_id } : {}
3410
- });
3411
- } else {
3412
- return { type: "text", text: "Screenshots are not available in cloud mode. Run adhdev mcp in local mode (requires standalone daemon)." };
3413
- }
4103
+ const result = await transport.command("screenshot", {
4104
+ ...args.session_id ? { targetSessionId: args.session_id } : {}
4105
+ });
3414
4106
  if (result?.success === false) {
3415
4107
  return { type: "text", text: `Error: ${result.error ?? "screenshot failed"}` };
3416
4108
  }
@@ -3437,42 +4129,22 @@ var GIT_STATUS_TOOL = {
3437
4129
  type: "boolean",
3438
4130
  description: "Include changed file list (default: true)."
3439
4131
  },
3440
- daemon_id: {
3441
- type: "string",
3442
- description: "Daemon ID (cloud mode only)."
3443
- },
3444
4132
  ...FORMAT_PROP
3445
4133
  },
3446
4134
  required: ["workspace"]
3447
4135
  }
3448
4136
  };
3449
4137
  async function gitStatus(transport, args) {
3450
- let status;
3451
4138
  let diffSummary;
3452
- if (isLocalTransport(transport)) {
3453
- const statusResult = await transport.command("git_status", {
4139
+ const statusResult = await transport.command("git_status", {
4140
+ workspace: args.workspace
4141
+ });
4142
+ const status = statusResult?.status ?? statusResult;
4143
+ if (args.include_diff !== false) {
4144
+ const diffResult = await transport.command("git_diff_summary", {
3454
4145
  workspace: args.workspace
3455
4146
  });
3456
- status = statusResult?.status ?? statusResult;
3457
- if (args.include_diff !== false) {
3458
- const diffResult = await transport.command("git_diff_summary", {
3459
- workspace: args.workspace
3460
- });
3461
- diffSummary = diffResult?.diffSummary ?? diffResult;
3462
- }
3463
- } else {
3464
- if (!args.daemon_id) throw new Error("daemon_id is required in cloud mode");
3465
- const result = await transport.gitStatus(
3466
- args.daemon_id,
3467
- args.workspace,
3468
- args.include_diff !== false
3469
- );
3470
- if (result?.error) {
3471
- if (args.format === "json") return JSON.stringify({ error: result.error }, null, 2);
3472
- return `Error: ${result.error}`;
3473
- }
3474
- status = result?.status;
3475
- diffSummary = result?.diff;
4147
+ diffSummary = diffResult?.diffSummary ?? diffResult;
3476
4148
  }
3477
4149
  if (status?.success === false || status?.reason) {
3478
4150
  const msg = status?.error ?? status?.reason ?? "unknown";
@@ -3564,10 +4236,6 @@ var GIT_LOG_TOOL = {
3564
4236
  type: "string",
3565
4237
  description: "Only commits before this date (ISO 8601 or git date string, optional)."
3566
4238
  },
3567
- daemon_id: {
3568
- type: "string",
3569
- description: "Daemon ID (cloud mode only, required)."
3570
- },
3571
4239
  ...FORMAT_PROP
3572
4240
  },
3573
4241
  required: ["workspace"]
@@ -3575,26 +4243,14 @@ var GIT_LOG_TOOL = {
3575
4243
  };
3576
4244
  async function gitLog(transport, args) {
3577
4245
  const limit = Math.max(1, Math.min(100, args.limit ?? 20));
3578
- let raw;
3579
- if (isLocalTransport(transport)) {
3580
- raw = await transport.command("git_log", {
3581
- workspace: args.workspace,
3582
- limit,
3583
- ...args.file ? { path: args.file } : {},
3584
- ...args.since ? { since: args.since } : {},
3585
- ...args.until ? { until: args.until } : {}
3586
- });
3587
- raw = raw?.log ?? raw;
3588
- } else {
3589
- if (!args.daemon_id) throw new Error("daemon_id is required in cloud mode");
3590
- const result = await transport.gitLog(args.daemon_id, args.workspace, {
3591
- limit,
3592
- file: args.file,
3593
- since: args.since,
3594
- until: args.until
3595
- });
3596
- raw = result?.log ?? result;
3597
- }
4246
+ let raw = await transport.command("git_log", {
4247
+ workspace: args.workspace,
4248
+ limit,
4249
+ ...args.file ? { path: args.file } : {},
4250
+ ...args.since ? { since: args.since } : {},
4251
+ ...args.until ? { until: args.until } : {}
4252
+ });
4253
+ raw = raw?.log ?? raw;
3598
4254
  if (raw?.success === false || raw?.reason) {
3599
4255
  const msg = raw?.error ?? raw?.reason ?? "unknown";
3600
4256
  if (args.format === "json") return JSON.stringify({ error: msg }, null, 2);
@@ -3657,10 +4313,6 @@ var GIT_DIFF_TOOL = {
3657
4313
  type: "boolean",
3658
4314
  description: "Show staged changes instead of unstaged (default: false)."
3659
4315
  },
3660
- daemon_id: {
3661
- type: "string",
3662
- description: "Daemon ID (cloud mode only, required)."
3663
- },
3664
4316
  ...FORMAT_PROP
3665
4317
  },
3666
4318
  required: ["workspace"]
@@ -3669,20 +4321,7 @@ var GIT_DIFF_TOOL = {
3669
4321
  async function gitDiff(transport, args) {
3670
4322
  const maxLines = Math.max(10, Math.min(2e3, args.max_lines ?? 300));
3671
4323
  const staged = args.staged ?? false;
3672
- if (isLocalTransport(transport)) {
3673
- return localGitDiff(transport, args.workspace, args.file, maxLines, staged, args.format);
3674
- }
3675
- if (!args.daemon_id) throw new Error("daemon_id is required in cloud mode");
3676
- const result = await transport.gitDiff(args.daemon_id, args.workspace, {
3677
- file: args.file,
3678
- maxLines,
3679
- staged
3680
- });
3681
- if (result?.error) {
3682
- if (args.format === "json") return JSON.stringify({ error: result.error }, null, 2);
3683
- return `Git diff error: ${result.error}`;
3684
- }
3685
- return formatDiffResult(result, args.format);
4324
+ return localGitDiff(transport, args.workspace, args.file, maxLines, staged, args.format);
3686
4325
  }
3687
4326
  async function localGitDiff(transport, workspace, file, maxLines, staged, format) {
3688
4327
  if (file) {
@@ -3804,10 +4443,6 @@ var GIT_CHECKPOINT_TOOL = {
3804
4443
  include_untracked: {
3805
4444
  type: "boolean",
3806
4445
  description: "Also stage and commit untracked files (default: false)."
3807
- },
3808
- daemon_id: {
3809
- type: "string",
3810
- description: "Daemon ID (cloud mode only, required)."
3811
4446
  }
3812
4447
  },
3813
4448
  required: ["workspace", "message"]
@@ -3817,23 +4452,12 @@ async function gitCheckpoint(transport, args) {
3817
4452
  const message = args.message?.trim();
3818
4453
  if (!message) return "Error: message is required";
3819
4454
  if (message.length > 200) return "Error: message must be 200 characters or fewer";
3820
- let raw;
3821
- if (isLocalTransport(transport)) {
3822
- raw = await transport.command("git_checkpoint", {
3823
- workspace: args.workspace,
3824
- message,
3825
- includeUntracked: args.include_untracked ?? false
3826
- });
3827
- raw = raw?.checkpoint ?? raw;
3828
- } else {
3829
- if (!args.daemon_id) throw new Error("daemon_id is required in cloud mode");
3830
- const result = await transport.gitCheckpoint(args.daemon_id, {
3831
- workspace: args.workspace,
3832
- message,
3833
- includeUntracked: args.include_untracked ?? false
3834
- });
3835
- raw = result?.checkpoint ?? result;
3836
- }
4455
+ let raw = await transport.command("git_checkpoint", {
4456
+ workspace: args.workspace,
4457
+ message,
4458
+ includeUntracked: args.include_untracked ?? false
4459
+ });
4460
+ raw = raw?.checkpoint ?? raw;
3837
4461
  if (raw?.success === false || raw?.reason) {
3838
4462
  const msg = raw?.error ?? raw?.reason ?? "unknown";
3839
4463
  if (msg.includes("Nothing to commit") || msg.includes("nothing to commit")) {
@@ -3864,33 +4488,18 @@ var GIT_PUSH_TOOL = {
3864
4488
  branch: {
3865
4489
  type: "string",
3866
4490
  description: "Branch to push (default: current branch)."
3867
- },
3868
- daemon_id: {
3869
- type: "string",
3870
- description: "Daemon ID (cloud mode only, required)."
3871
4491
  }
3872
4492
  },
3873
4493
  required: ["workspace"]
3874
4494
  }
3875
4495
  };
3876
4496
  async function gitPush(transport, args) {
3877
- let raw;
3878
- if (isLocalTransport(transport)) {
3879
- raw = await transport.command("git_push", {
3880
- workspace: args.workspace,
3881
- remote: args.remote ?? "origin",
3882
- ...args.branch ? { branch: args.branch } : {}
3883
- });
3884
- raw = raw?.push ?? raw;
3885
- } else {
3886
- if (!args.daemon_id) throw new Error("daemon_id is required in cloud mode");
3887
- const result = await transport.gitPush(args.daemon_id, {
3888
- workspace: args.workspace,
3889
- remote: args.remote,
3890
- branch: args.branch
3891
- });
3892
- raw = result?.push ?? result;
3893
- }
4497
+ let raw = await transport.command("git_push", {
4498
+ workspace: args.workspace,
4499
+ remote: args.remote ?? "origin",
4500
+ ...args.branch ? { branch: args.branch } : {}
4501
+ });
4502
+ raw = raw?.push ?? raw;
3894
4503
  if (raw?.success === false || raw?.reason) {
3895
4504
  const msg = raw?.error ?? raw?.reason ?? "unknown";
3896
4505
  return `Git push error: ${msg}`;
@@ -3921,32 +4530,17 @@ var LAUNCH_SESSION_TOOL = {
3921
4530
  model: {
3922
4531
  type: "string",
3923
4532
  description: "Model override for ACP agents (e.g. claude-opus-4-7)."
3924
- },
3925
- daemon_id: {
3926
- type: "string",
3927
- description: "Daemon ID (cloud mode only). Required in cloud mode."
3928
4533
  }
3929
4534
  },
3930
4535
  required: ["type"]
3931
4536
  }
3932
4537
  };
3933
4538
  async function launchSession(transport, args) {
3934
- if (isLocalTransport(transport)) {
3935
- const isCliOrAcp = args.type.includes("-cli") || args.type.includes("-acp") || args.type === "codex";
3936
- const commandType = isCliOrAcp ? "launch_cli" : "launch_ide";
3937
- const payload = isCliOrAcp ? { cliType: args.type, dir: args.workspace ?? "~", ...args.model ? { model: args.model } : {} } : { ideType: args.type, enableCdp: true };
3938
- const result2 = await transport.command(commandType, payload);
3939
- if (result2?.success === false) return `Error: ${result2.error ?? "launch failed"}`;
3940
- const id2 = result2?.id ?? result2?.sessionId;
3941
- return id2 ? `Session launched. id: ${id2}, type: ${args.type}` : `Launched: ${JSON.stringify(result2)}`;
3942
- }
3943
- if (!args.daemon_id) throw new Error("daemon_id is required in cloud mode");
3944
- const result = await transport.launch(args.daemon_id, {
3945
- type: args.type,
3946
- dir: args.workspace,
3947
- model: args.model
3948
- });
3949
- if (result?.success === false || result?.error) return `Error: ${result.error ?? "launch failed"}`;
4539
+ const isCliOrAcp = args.type.includes("-cli") || args.type.includes("-acp") || args.type === "codex";
4540
+ const commandType = isCliOrAcp ? "launch_cli" : "launch_ide";
4541
+ const payload = isCliOrAcp ? { cliType: args.type, dir: args.workspace ?? "~", ...args.model ? { model: args.model } : {} } : { ideType: args.type, enableCdp: true };
4542
+ const result = await transport.command(commandType, payload);
4543
+ if (result?.success === false) return `Error: ${result.error ?? "launch failed"}`;
3950
4544
  const id = result?.id ?? result?.sessionId;
3951
4545
  return id ? `Session launched. id: ${id}, type: ${args.type}` : `Launched: ${JSON.stringify(result)}`;
3952
4546
  }
@@ -3962,43 +4556,29 @@ var STOP_SESSION_TOOL = {
3962
4556
  type: "string",
3963
4557
  description: "Session ID to stop (from list_sessions)."
3964
4558
  },
3965
- daemon_id: {
3966
- type: "string",
3967
- description: "Daemon ID (cloud mode only, required)."
3968
- },
3969
4559
  type: {
3970
4560
  type: "string",
3971
- description: "Provider type (e.g. hermes-cli, claude-cli). Local mode auto-resolves from session_id if omitted; cloud mode forwards the session_id and omits type unless explicitly provided."
4561
+ description: "Provider type (e.g. hermes-cli, claude-cli). Auto-resolved from session_id if omitted."
3972
4562
  }
3973
4563
  },
3974
4564
  required: ["session_id"]
3975
4565
  }
3976
4566
  };
3977
4567
  async function stopSession(transport, args) {
3978
- if (isLocalTransport(transport)) {
3979
- const local = transport;
3980
- let resolvedType = args.type;
3981
- if (!resolvedType) {
3982
- const status = await local.getStatus();
3983
- const session = (status?.sessions ?? []).find((s) => s.id === args.session_id);
3984
- resolvedType = session?.providerType ?? session?.type;
3985
- }
3986
- if (!resolvedType) {
3987
- return `Error: could not resolve session type for ${args.session_id}. Pass type= explicitly.`;
3988
- }
3989
- const result2 = await local.command("stop_cli", {
3990
- targetSessionId: args.session_id,
3991
- cliType: resolvedType
3992
- });
3993
- if (result2?.success === false) return `Error: ${result2.error ?? "stop failed"}`;
3994
- return `Session ${args.session_id} stopped.`;
4568
+ let resolvedType = args.type;
4569
+ if (!resolvedType) {
4570
+ const status = await transport.getStatus();
4571
+ const session = (status?.sessions ?? []).find((s) => s.id === args.session_id);
4572
+ resolvedType = session?.providerType ?? session?.type;
4573
+ }
4574
+ if (!resolvedType) {
4575
+ return `Error: could not resolve session type for ${args.session_id}. Pass type= explicitly.`;
3995
4576
  }
3996
- if (!args.daemon_id) throw new Error("daemon_id is required in cloud mode");
3997
- const result = await transport.stop(args.daemon_id, {
3998
- id: args.session_id,
3999
- ...args.type ? { type: args.type } : {}
4577
+ const result = await transport.command("stop_cli", {
4578
+ targetSessionId: args.session_id,
4579
+ cliType: resolvedType
4000
4580
  });
4001
- if (result?.success === false || result?.error) return `Error: ${result.error ?? "stop failed"}`;
4581
+ if (result?.success === false) return `Error: ${result.error ?? "stop failed"}`;
4002
4582
  return `Session ${args.session_id} stopped.`;
4003
4583
  }
4004
4584
 
@@ -4009,28 +4589,18 @@ var CHECK_PENDING_TOOL = {
4009
4589
  inputSchema: {
4010
4590
  type: "object",
4011
4591
  properties: {
4012
- daemon_id: {
4013
- type: "string",
4014
- description: "Daemon ID to check (cloud mode). Omit to check all daemons."
4015
- },
4016
4592
  ...FORMAT_PROP
4017
4593
  },
4018
4594
  required: []
4019
4595
  }
4020
4596
  };
4021
4597
  async function checkPending(transport, args) {
4022
- if (isLocalTransport(transport)) {
4023
- return checkPendingLocal(transport, args.format);
4024
- }
4025
- return checkPendingCloud(transport, args.daemon_id, args.format);
4026
- }
4027
- async function checkPendingLocal(transport, format) {
4028
4598
  const status = await transport.getStatus();
4029
4599
  const sessions = status?.sessions ?? [];
4030
4600
  const pending = sessions.filter(
4031
4601
  (s) => s.status === "waiting_approval" || s.agentStatus === "waiting_approval"
4032
4602
  );
4033
- if (format === "json") {
4603
+ if (args.format === "json") {
4034
4604
  return JSON.stringify({
4035
4605
  pending: pending.map((s) => ({
4036
4606
  session_id: s.id,
@@ -4053,56 +4623,6 @@ async function checkPendingLocal(transport, format) {
4053
4623
  });
4054
4624
  return `Pending approvals (${pending.length}):
4055
4625
 
4056
- ${lines.join("\n\n")}`;
4057
- }
4058
- async function checkPendingCloud(transport, daemonId, format) {
4059
- const pending = [];
4060
- if (daemonId) {
4061
- const daemonStatus = await transport.getDaemonStatus(daemonId);
4062
- const sessions = daemonStatus?.sessions ?? [];
4063
- for (const s of sessions) {
4064
- if (s.status === "waiting_approval") pending.push({ daemonId, session: s });
4065
- }
4066
- } else {
4067
- const data = await transport.listDaemons();
4068
- const daemons = data?.daemons ?? [];
4069
- for (let i = 0; i < daemons.length; i += 5) {
4070
- await Promise.allSettled(
4071
- daemons.slice(i, i + 5).map(async (d) => {
4072
- try {
4073
- const daemonStatus = await transport.getDaemonStatus(d.id);
4074
- const sessions = daemonStatus?.sessions ?? [];
4075
- for (const s of sessions) {
4076
- if (s.status === "waiting_approval") pending.push({ daemonId: d.id, session: s });
4077
- }
4078
- } catch {
4079
- }
4080
- })
4081
- );
4082
- }
4083
- }
4084
- if (format === "json") {
4085
- return JSON.stringify({
4086
- pending: pending.map(({ daemonId: dId, session: s }) => ({
4087
- daemon_id: dId,
4088
- session_id: s.id,
4089
- workspace: s.workspace ?? null,
4090
- type: s.providerType ?? null,
4091
- modal_message: null,
4092
- buttons: []
4093
- }))
4094
- }, null, 2);
4095
- }
4096
- if (pending.length === 0) return "No sessions waiting for approval.";
4097
- const lines = pending.map(({ daemonId: dId, session: s }) => {
4098
- const parts = [`daemon_id: ${dId}`, `session_id: ${s.id}`];
4099
- if (s.workspace) parts.push(`workspace: ${s.workspace}`);
4100
- if (s.providerType) parts.push(`type: ${s.providerType}`);
4101
- parts.push("(use read_chat to see the approval prompt)");
4102
- return parts.join("\n ");
4103
- });
4104
- return `Pending approvals (${pending.length}):
4105
-
4106
4626
  ${lines.join("\n\n")}`;
4107
4627
  }
4108
4628
 
@@ -4116,10 +4636,10 @@ async function buildMeshModeCoordinatorPrompt(mesh) {
4116
4636
  }
4117
4637
  }
4118
4638
  async function startMcpServer(opts) {
4119
- const transport = opts.mode === "cloud" ? new CloudTransport({ apiKey: opts.apiKey, baseUrl: opts.baseUrl }) : opts.mode === "ipc" ? new IpcTransport({ port: opts.port }) : new LocalTransport({ port: opts.port, password: opts.password });
4639
+ const transport = opts.mode === "ipc" ? new IpcTransport({ port: opts.port }) : new LocalTransport({ port: opts.port, password: opts.password });
4120
4640
  const alive = await transport.ping();
4121
4641
  if (!alive) {
4122
- const hint = opts.mode === "local" ? `Make sure the standalone daemon is running (adhdev standalone or npx @adhdev/daemon-standalone).` : opts.mode === "ipc" ? `Make sure the cloud daemon is running with local IPC enabled (adhdev daemon).` : `Check your API key and network connectivity.`;
4642
+ const hint = opts.mode === "local" ? `Make sure the standalone daemon is running (adhdev standalone or npx @adhdev/daemon-standalone).` : `Make sure the cloud daemon is running with local IPC enabled (adhdev daemon).`;
4123
4643
  process.stderr.write(`[adhdev-mcp] Cannot reach ${opts.mode} daemon. ${hint}
4124
4644
  `);
4125
4645
  process.exit(1);
@@ -4134,63 +4654,6 @@ async function startMcpServer(opts) {
4134
4654
  `);
4135
4655
  } catch (e) {
4136
4656
  process.stderr.write(`[adhdev-mcp] Failed to parse ADHDEV_INLINE_MESH: ${e.message}
4137
- `);
4138
- }
4139
- }
4140
- if (!mesh && opts.mode === "cloud" && opts.apiKey) {
4141
- try {
4142
- const base = opts.baseUrl || "https://api.adhf.dev";
4143
- const res = await fetch(`${base}/api/v1/repo-meshes/${opts.meshId}`, {
4144
- headers: { "Authorization": `Bearer ${opts.apiKey}`, "Content-Type": "application/json" }
4145
- });
4146
- if (res.ok) {
4147
- const data = await res.json();
4148
- const rm = data.mesh;
4149
- const nodes = data.nodes || [];
4150
- let policy = {};
4151
- try {
4152
- policy = JSON.parse(rm.policy_json || rm.policy || "{}");
4153
- } catch {
4154
- }
4155
- let coordinator = {};
4156
- try {
4157
- coordinator = JSON.parse(rm.coordinator_json || rm.coordinator_config || "{}");
4158
- } catch {
4159
- }
4160
- mesh = {
4161
- id: rm.id,
4162
- name: rm.name,
4163
- repoIdentity: rm.repo_identity,
4164
- repoRemoteUrl: rm.repo_remote_url,
4165
- defaultBranch: rm.default_branch,
4166
- policy: {
4167
- requirePreTaskCheckpoint: false,
4168
- requirePostTaskCheckpoint: true,
4169
- requireApprovalForPush: true,
4170
- requireApprovalForDestructiveGit: true,
4171
- dirtyWorkspaceBehavior: "warn",
4172
- maxParallelTasks: 2,
4173
- spawnedSessionVisibility: "visible",
4174
- ...policy
4175
- },
4176
- coordinator,
4177
- nodes: nodes.map((n) => ({
4178
- id: n.id,
4179
- workspace: n.workspace,
4180
- repoRoot: n.repo_root,
4181
- daemonId: n.daemon_id,
4182
- userOverrides: {},
4183
- policy: {},
4184
- isLocalWorktree: false
4185
- })),
4186
- createdAt: rm.created_at,
4187
- updatedAt: rm.updated_at
4188
- };
4189
- process.stderr.write(`[adhdev-mcp] Loaded mesh config from cloud API
4190
- `);
4191
- }
4192
- } catch (e) {
4193
- process.stderr.write(`[adhdev-mcp] Cloud mesh fetch failed, falling back to local: ${e.message}
4194
4657
  `);
4195
4658
  }
4196
4659
  }
@@ -4217,17 +4680,19 @@ async function startMcpServer(opts) {
4217
4680
  }
4218
4681
  }
4219
4682
  if (!mesh) {
4220
- process.stderr.write(`[adhdev-mcp] Mesh '${opts.meshId}' not found in ${opts.mode === "cloud" ? "cloud or local" : "local"} config. Use 'adhdev mesh list' to see available meshes.
4683
+ process.stderr.write(`[adhdev-mcp] Mesh '${opts.meshId}' not found in local config. Use 'adhdev mesh list' to see available meshes.
4221
4684
  `);
4222
4685
  process.exit(1);
4223
4686
  }
4224
4687
  let localDaemonId;
4225
4688
  let localMachineId;
4689
+ let coordinatorHostname = import_node_os.default.hostname();
4226
4690
  if (transport instanceof LocalTransport || transport instanceof IpcTransport) {
4227
4691
  try {
4228
4692
  const { loadConfig } = await import("@adhdev/daemon-core");
4229
4693
  const cfg = loadConfig();
4230
- if (cfg.registeredMachineId) localMachineId = cfg.registeredMachineId;
4694
+ if (cfg.machineId) localMachineId = cfg.machineId;
4695
+ else if (cfg.registeredMachineId) localMachineId = cfg.registeredMachineId;
4231
4696
  } catch {
4232
4697
  }
4233
4698
  }
@@ -4235,14 +4700,16 @@ async function startMcpServer(opts) {
4235
4700
  try {
4236
4701
  const statusResult = await transport.getStatus();
4237
4702
  const instanceId = typeof statusResult?.status?.instanceId === "string" ? statusResult.status.instanceId.trim() : "";
4703
+ const hostname = typeof statusResult?.status?.hostname === "string" ? statusResult.status.hostname.trim() : typeof statusResult?.status?.machine?.hostname === "string" ? statusResult.status.machine.hostname.trim() : "";
4238
4704
  if (instanceId) localDaemonId = instanceId;
4705
+ if (hostname) coordinatorHostname = hostname;
4239
4706
  } catch {
4240
4707
  }
4241
4708
  }
4242
- const meshCtx = { mesh, transport, ...localDaemonId ? { localDaemonId } : {}, ...localMachineId ? { localMachineId } : {} };
4709
+ const meshCtx = { mesh, transport, ...localDaemonId ? { localDaemonId } : {}, ...localMachineId ? { localMachineId } : {}, ...coordinatorHostname ? { coordinatorHostname } : {} };
4243
4710
  const coordinatorPrompt = await buildMeshModeCoordinatorPrompt(mesh);
4244
4711
  const server2 = new import_server.Server(
4245
- { name: "adhdev-mcp-server", version: "0.9.81" },
4712
+ { name: "adhdev-mcp-server", version: "0.9.82" },
4246
4713
  { capabilities: { tools: {}, resources: {} } }
4247
4714
  );
4248
4715
  const { ListResourcesRequestSchema, ReadResourceRequestSchema } = await import("@modelcontextprotocol/sdk/types.js");
@@ -4268,7 +4735,7 @@ async function startMcpServer(opts) {
4268
4735
  let text;
4269
4736
  switch (name) {
4270
4737
  case "mesh_status":
4271
- text = await meshStatus(meshCtx);
4738
+ text = await meshStatus(meshCtx, a);
4272
4739
  break;
4273
4740
  case "mesh_list_nodes":
4274
4741
  text = await meshListNodes(meshCtx);
@@ -4300,6 +4767,9 @@ async function startMcpServer(opts) {
4300
4767
  case "mesh_git_status":
4301
4768
  text = await meshGitStatus(meshCtx, a);
4302
4769
  break;
4770
+ case "mesh_fast_forward_node":
4771
+ text = await meshFastForwardNode(meshCtx, a);
4772
+ break;
4303
4773
  case "mesh_checkpoint":
4304
4774
  text = await meshCheckpoint(meshCtx, a);
4305
4775
  break;
@@ -4315,6 +4785,18 @@ async function startMcpServer(opts) {
4315
4785
  case "mesh_refine_node":
4316
4786
  text = await meshRefineNode(meshCtx, a);
4317
4787
  break;
4788
+ case "mesh_refine_config_schema":
4789
+ text = await meshRefineConfigSchema(meshCtx);
4790
+ break;
4791
+ case "mesh_validate_refine_config":
4792
+ text = await meshValidateRefineConfig(meshCtx, a);
4793
+ break;
4794
+ case "mesh_suggest_refine_config":
4795
+ text = await meshSuggestRefineConfig(meshCtx, a);
4796
+ break;
4797
+ case "mesh_refine_plan":
4798
+ text = await meshRefinePlan(meshCtx, a);
4799
+ break;
4318
4800
  case "mesh_cleanup_sessions":
4319
4801
  text = await meshCleanupSessions(meshCtx, a);
4320
4802
  break;
@@ -4324,6 +4806,12 @@ async function startMcpServer(opts) {
4324
4806
  case "mesh_reconcile_ledger":
4325
4807
  text = await meshReconcileLedger(meshCtx, a);
4326
4808
  break;
4809
+ case "mesh_mission_upsert":
4810
+ text = await meshMissionUpsert(meshCtx, a);
4811
+ break;
4812
+ case "mesh_review_inbox":
4813
+ text = await meshReviewInbox(meshCtx, a);
4814
+ break;
4327
4815
  default:
4328
4816
  return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
4329
4817
  }
@@ -4346,6 +4834,7 @@ async function startMcpServer(opts) {
4346
4834
  CHECK_PENDING_TOOL,
4347
4835
  READ_CHAT_TOOL,
4348
4836
  READ_CHAT_DEBUG_TOOL,
4837
+ SPEC_DEBUG_TOOL,
4349
4838
  SEND_CHAT_TOOL,
4350
4839
  APPROVE_TOOL,
4351
4840
  GIT_STATUS_TOOL,
@@ -4370,7 +4859,7 @@ async function startMcpServer(opts) {
4370
4859
  return { content: [{ type: "text", text }] };
4371
4860
  }
4372
4861
  case "list_sessions": {
4373
- const text = await listSessions(transport, { format: a.format, daemon_id: a.daemon_id });
4862
+ const text = await listSessions(transport, { format: a.format });
4374
4863
  return { content: [{ type: "text", text }] };
4375
4864
  }
4376
4865
  case "read_chat": {
@@ -4381,13 +4870,17 @@ async function startMcpServer(opts) {
4381
4870
  const text = await readChatDebug(transport, a);
4382
4871
  return { content: [{ type: "text", text }] };
4383
4872
  }
4873
+ case "spec_debug": {
4874
+ const text = await specDebug(transport, a);
4875
+ return { content: [{ type: "text", text }] };
4876
+ }
4384
4877
  case "send_chat": {
4385
- const text = await sendChat(transport, { message: a.message, session_id: a.session_id, daemon_id: a.daemon_id });
4878
+ const text = await sendChat(transport, { message: a.message, session_id: a.session_id });
4386
4879
  return { content: [{ type: "text", text }] };
4387
4880
  }
4388
4881
  case "approve": {
4389
4882
  const action = a.action === "reject" ? "reject" : "approve";
4390
- const text = await approve(transport, { action, session_id: a.session_id, daemon_id: a.daemon_id });
4883
+ const text = await approve(transport, { action, session_id: a.session_id });
4391
4884
  return { content: [{ type: "text", text }] };
4392
4885
  }
4393
4886
  case "screenshot": {
@@ -4400,44 +4893,42 @@ async function startMcpServer(opts) {
4400
4893
  return { content: [{ type: "text", text: result.text }] };
4401
4894
  }
4402
4895
  case "git_status": {
4403
- const text = await gitStatus(transport, { workspace: a.workspace, include_diff: a.include_diff, daemon_id: a.daemon_id, format: a.format });
4896
+ const text = await gitStatus(transport, { workspace: a.workspace, include_diff: a.include_diff, format: a.format });
4404
4897
  return { content: [{ type: "text", text }] };
4405
4898
  }
4406
4899
  case "git_log": {
4407
- const text = await gitLog(transport, { workspace: a.workspace, limit: a.limit, file: a.file, since: a.since, until: a.until, daemon_id: a.daemon_id, format: a.format });
4900
+ const text = await gitLog(transport, { workspace: a.workspace, limit: a.limit, file: a.file, since: a.since, until: a.until, format: a.format });
4408
4901
  return { content: [{ type: "text", text }] };
4409
4902
  }
4410
4903
  case "git_diff": {
4411
- const text = await gitDiff(transport, { workspace: a.workspace, file: a.file, max_lines: a.max_lines, staged: a.staged, daemon_id: a.daemon_id, format: a.format });
4904
+ const text = await gitDiff(transport, { workspace: a.workspace, file: a.file, max_lines: a.max_lines, staged: a.staged, format: a.format });
4412
4905
  return { content: [{ type: "text", text }] };
4413
4906
  }
4414
4907
  case "git_checkpoint": {
4415
- const text = await gitCheckpoint(transport, { workspace: a.workspace, message: a.message, include_untracked: a.include_untracked, daemon_id: a.daemon_id });
4908
+ const text = await gitCheckpoint(transport, { workspace: a.workspace, message: a.message, include_untracked: a.include_untracked });
4416
4909
  return { content: [{ type: "text", text }] };
4417
4910
  }
4418
4911
  case "git_push": {
4419
- const text = await gitPush(transport, { workspace: a.workspace, remote: a.remote, branch: a.branch, daemon_id: a.daemon_id });
4912
+ const text = await gitPush(transport, { workspace: a.workspace, remote: a.remote, branch: a.branch });
4420
4913
  return { content: [{ type: "text", text }] };
4421
4914
  }
4422
4915
  case "launch_session": {
4423
4916
  const text = await launchSession(transport, {
4424
4917
  type: a.type,
4425
4918
  workspace: a.workspace,
4426
- model: a.model,
4427
- daemon_id: a.daemon_id
4919
+ model: a.model
4428
4920
  });
4429
4921
  return { content: [{ type: "text", text }] };
4430
4922
  }
4431
4923
  case "stop_session": {
4432
4924
  const text = await stopSession(transport, {
4433
4925
  session_id: a.session_id,
4434
- daemon_id: a.daemon_id,
4435
4926
  type: a.type
4436
4927
  });
4437
4928
  return { content: [{ type: "text", text }] };
4438
4929
  }
4439
4930
  case "check_pending": {
4440
- const text = await checkPending(transport, { daemon_id: a.daemon_id, format: a.format });
4931
+ const text = await checkPending(transport, { format: a.format });
4441
4932
  return { content: [{ type: "text", text }] };
4442
4933
  }
4443
4934
  default:
@@ -4459,26 +4950,18 @@ async function startMcpServer(opts) {
4459
4950
  // src/index.ts
4460
4951
  function parseArgs(argv, env = process.env) {
4461
4952
  const args = argv.slice(2);
4462
- let apiKey;
4463
- let baseUrl;
4464
4953
  let port;
4465
4954
  let password;
4466
4955
  let meshId;
4467
4956
  let explicitMode;
4468
4957
  for (let i = 0; i < args.length; i++) {
4469
4958
  const arg = args[i];
4470
- if ((arg === "--api-key" || arg === "-k") && args[i + 1]) {
4471
- apiKey = args[++i];
4472
- } else if (arg?.startsWith("--api-key=")) {
4473
- apiKey = arg.slice("--api-key=".length);
4474
- } else if (arg === "--base-url" && args[i + 1]) {
4475
- baseUrl = args[++i];
4476
- } else if (arg === "--mode" && args[i + 1]) {
4959
+ if (arg === "--mode" && args[i + 1]) {
4477
4960
  const value = String(args[++i]).trim();
4478
- if (value === "local" || value === "cloud" || value === "ipc") explicitMode = value;
4961
+ if (value === "local" || value === "ipc") explicitMode = value;
4479
4962
  } else if (arg?.startsWith("--mode=")) {
4480
4963
  const value = arg.slice("--mode=".length).trim();
4481
- if (value === "local" || value === "cloud" || value === "ipc") explicitMode = value;
4964
+ if (value === "local" || value === "ipc") explicitMode = value;
4482
4965
  } else if (arg === "--port" && args[i + 1]) {
4483
4966
  port = Number(args[++i]);
4484
4967
  } else if (arg?.startsWith("--port=")) {
@@ -4494,15 +4977,14 @@ function parseArgs(argv, env = process.env) {
4494
4977
  process.exit(0);
4495
4978
  }
4496
4979
  }
4497
- if (!apiKey && env.ADHDEV_API_KEY) apiKey = env.ADHDEV_API_KEY;
4498
4980
  if (!password && env.ADHDEV_PASSWORD) password = env.ADHDEV_PASSWORD;
4499
4981
  if (!meshId && env.ADHDEV_MESH_ID) meshId = env.ADHDEV_MESH_ID;
4500
4982
  if (!explicitMode && env.ADHDEV_MCP_TRANSPORT) {
4501
4983
  const value = env.ADHDEV_MCP_TRANSPORT.trim();
4502
- if (value === "local" || value === "cloud" || value === "ipc") explicitMode = value;
4984
+ if (value === "local" || value === "ipc") explicitMode = value;
4503
4985
  }
4504
- const mode = explicitMode || (apiKey ? "cloud" : meshId && env.ADHDEV_INLINE_MESH ? "ipc" : "local");
4505
- return { mode, port, password, apiKey, baseUrl, meshId };
4986
+ const mode = explicitMode || (meshId && env.ADHDEV_INLINE_MESH ? "ipc" : "local");
4987
+ return { mode, port, password, meshId };
4506
4988
  }
4507
4989
  function printHelp() {
4508
4990
  console.error(buildMcpHelpText());