@adhdev/daemon-standalone 0.9.82-rc.12 → 0.9.82-rc.122
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +27232 -21374
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/public/assets/index--3Wc6sJr.js +100 -0
- package/public/assets/index-BJ05ceSl.js +98 -0
- package/public/assets/index-BKsjvBcH.js +100 -0
- package/public/assets/index-BNKDsz6G.css +1 -0
- package/public/assets/index-BfLqOvWH.js +98 -0
- package/public/assets/index-BggZArlj.js +99 -0
- package/public/assets/index-Bso1b8Lh.css +1 -0
- package/public/assets/index-BzfqwAXl.js +98 -0
- package/public/assets/index-CAip3He8.js +100 -0
- package/public/assets/index-CCQPbtl4.css +1 -0
- package/public/assets/index-CYwXUKol.js +98 -0
- package/public/assets/index-CiB8XPwO.css +1 -0
- package/public/assets/index-CsEmn20l.css +1 -0
- package/public/assets/index-CsR4qRd4.js +98 -0
- package/public/assets/index-DDB8O9lr.js +100 -0
- package/public/assets/index-DJmCzwTk.js +98 -0
- package/public/assets/index-DW3PmABW.js +99 -0
- package/public/assets/index-DaIkPFUd.js +99 -0
- package/public/assets/index-Dc9o_onl.js +98 -0
- package/public/assets/index-DcAeIiVq.css +1 -0
- package/public/assets/index-Deb1Bk0M.js +98 -0
- package/public/assets/index-DhQzhjNN.js +98 -0
- package/public/assets/index-Dt2Xvr87.js +98 -0
- package/public/assets/index-R9GDvGJ8.js +98 -0
- package/public/assets/index-kt4CV7OM.js +100 -0
- package/public/assets/index-phY3RKNC.js +98 -0
- package/public/assets/index-xSlc1ntZ.css +1 -0
- package/public/assets/index-yF4m-swI.js +100 -0
- package/public/assets/terminal-D46M5EWH.js +143 -0
- package/public/assets/terminal-NZFQPQy6.js +143 -0
- package/public/assets/vendor-C7br1-G2.js +2745 -0
- package/public/index.html +2 -2
- package/vendor/mcp-server/index.js +979 -88
- package/vendor/mcp-server/index.js.map +1 -1
|
@@ -35,9 +35,21 @@ __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
|
+
};
|
|
41
53
|
var IpcTransport = class {
|
|
42
54
|
port;
|
|
43
55
|
path;
|
|
@@ -85,9 +97,22 @@ var IpcTransport = class {
|
|
|
85
97
|
}
|
|
86
98
|
fn();
|
|
87
99
|
};
|
|
88
|
-
const
|
|
100
|
+
const nestedCommand = typeof args?.command === "string" ? args.command : "";
|
|
101
|
+
const targetDaemonId = typeof args?.targetDaemonId === "string" ? args.targetDaemonId : "";
|
|
102
|
+
const effectiveType = type === "mesh_relay_command" && nestedCommand ? nestedCommand : type;
|
|
103
|
+
const timeoutMs = Math.max(
|
|
104
|
+
IPC_COMMAND_TIMEOUTS_MS[type] ?? DEFAULT_IPC_COMMAND_TIMEOUT_MS,
|
|
105
|
+
IPC_COMMAND_TIMEOUTS_MS[effectiveType] ?? DEFAULT_IPC_COMMAND_TIMEOUT_MS
|
|
106
|
+
);
|
|
107
|
+
const diagnosticParts = [
|
|
108
|
+
`command='${type}'`,
|
|
109
|
+
...nestedCommand ? [`relayedCommand='${nestedCommand}'`] : [],
|
|
110
|
+
...targetDaemonId ? [`targetDaemonId='${targetDaemonId.slice(0, 12)}'`] : [],
|
|
111
|
+
...typeof args?.nodeId === "string" ? [`nodeId='${args.nodeId}'`] : [],
|
|
112
|
+
...typeof args?.workspace === "string" ? [`workspace='${args.workspace}'`] : []
|
|
113
|
+
];
|
|
89
114
|
const timeout = setTimeout(() => {
|
|
90
|
-
finish(() => reject(new Error(`Daemon IPC
|
|
115
|
+
finish(() => reject(new Error(`Daemon IPC ${diagnosticParts.join(" ")} timed out after ${Math.round(timeoutMs / 1e3)}s (requestId=${requestId})`)));
|
|
91
116
|
}, timeoutMs);
|
|
92
117
|
let commandSent = false;
|
|
93
118
|
const send = () => {
|
|
@@ -143,6 +168,10 @@ function isLocalTransport(transport) {
|
|
|
143
168
|
}
|
|
144
169
|
|
|
145
170
|
// src/tools/chat-compact.ts
|
|
171
|
+
function isAssistantLike(message) {
|
|
172
|
+
const role = String(message?.role ?? "").toLowerCase();
|
|
173
|
+
return role === "assistant" || role === "agent";
|
|
174
|
+
}
|
|
146
175
|
function messageContent(message) {
|
|
147
176
|
const content = message?.content;
|
|
148
177
|
if (typeof content === "string") return content;
|
|
@@ -161,16 +190,22 @@ function isCoordinatorVisibleMessage(message) {
|
|
|
161
190
|
if (meta?.internal === true || meta?.debug === true || meta?.control === true || meta?.userVisible === false || meta?.user_visible === false) return false;
|
|
162
191
|
return role === "user" || role === "assistant" || role === "agent";
|
|
163
192
|
}
|
|
193
|
+
function buildCompactMessageTail(visibleMessages, opts) {
|
|
194
|
+
const summary = typeof opts.summary === "string" ? opts.summary.trim() : "";
|
|
195
|
+
const shouldOmitSummaryMessage = !!summary && !!opts.finalAssistant && isAssistantLike(opts.finalAssistant) && messageContent(opts.finalAssistant).trim() === summary;
|
|
196
|
+
const sourceMessages = shouldOmitSummaryMessage ? visibleMessages.filter((message) => message !== opts.finalAssistant) : visibleMessages;
|
|
197
|
+
return sourceMessages.slice(-opts.limit);
|
|
198
|
+
}
|
|
164
199
|
function compactChatPayload(payload, opts = {}) {
|
|
165
200
|
const rawMessages = Array.isArray(payload?.messages) ? payload.messages : [];
|
|
166
201
|
const visible = rawMessages.filter(isCoordinatorVisibleMessage);
|
|
167
202
|
const limit = Math.max(1, Math.min(opts.limit ?? 10, 10));
|
|
168
|
-
const messages = visible.slice(-limit);
|
|
169
203
|
const finalAssistant = [...visible].reverse().find((message) => {
|
|
170
204
|
const role = String(message?.role ?? "").toLowerCase();
|
|
171
205
|
return (role === "assistant" || role === "agent") && messageContent(message).trim();
|
|
172
206
|
});
|
|
173
207
|
const summary = typeof payload?.summary === "string" && payload.summary.trim() ? payload.summary.trim() : messageContent(finalAssistant).trim();
|
|
208
|
+
const messages = buildCompactMessageTail(visible, { summary, finalAssistant, limit });
|
|
174
209
|
return {
|
|
175
210
|
success: payload?.success !== false,
|
|
176
211
|
compact: true,
|
|
@@ -232,9 +267,45 @@ function annotateRapidReadChatAdvisory(payload, options) {
|
|
|
232
267
|
// src/tools/mesh-tools.ts
|
|
233
268
|
var import_daemon_core = require("@adhdev/daemon-core");
|
|
234
269
|
var meshSessionProviderMetadata = /* @__PURE__ */ new Map();
|
|
270
|
+
var ACTIVE_WORK_POLLING_BACKOFF_MS = 6e4;
|
|
271
|
+
function buildActiveWorkPollingGuidance(summary, now = Date.now()) {
|
|
272
|
+
if (!summary || summary.generatingCount <= 0) return void 0;
|
|
273
|
+
return {
|
|
274
|
+
activeGeneratingWork: true,
|
|
275
|
+
generatingCount: summary.generatingCount,
|
|
276
|
+
doNotPollBefore: new Date(now + ACTIVE_WORK_POLLING_BACKOFF_MS).toISOString(),
|
|
277
|
+
eventSurface: "pendingCoordinatorEvents",
|
|
278
|
+
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.",
|
|
279
|
+
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."
|
|
280
|
+
};
|
|
281
|
+
}
|
|
235
282
|
function readString(value) {
|
|
236
283
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
237
284
|
}
|
|
285
|
+
function summarizeTaskMessage(message) {
|
|
286
|
+
const taskSummary = message.replace(/\s+/g, " ").trim();
|
|
287
|
+
const taskTitle = taskSummary.length > 96 ? `${taskSummary.slice(0, 93)}...` : taskSummary;
|
|
288
|
+
return { taskTitle: taskTitle || "(untitled task)", taskSummary };
|
|
289
|
+
}
|
|
290
|
+
function buildDirectTaskPayload(message, via, opts) {
|
|
291
|
+
const descriptor = summarizeTaskMessage(message);
|
|
292
|
+
return {
|
|
293
|
+
source: "direct",
|
|
294
|
+
via,
|
|
295
|
+
taskId: opts.taskId,
|
|
296
|
+
message,
|
|
297
|
+
taskTitle: descriptor.taskTitle,
|
|
298
|
+
taskSummary: descriptor.taskSummary,
|
|
299
|
+
...opts.taskMode ? { taskMode: opts.taskMode } : {},
|
|
300
|
+
...opts.providerType ? { providerType: opts.providerType } : {},
|
|
301
|
+
...opts.targetSessionId ? { targetSessionId: opts.targetSessionId } : {}
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
function findNode(mesh, nodeId) {
|
|
305
|
+
const node = mesh.nodes.find((n) => n.id === nodeId);
|
|
306
|
+
if (!node) throw new Error(`Node '${nodeId}' is not a member of mesh '${mesh.name}'`);
|
|
307
|
+
return node;
|
|
308
|
+
}
|
|
238
309
|
var DUPLICATE_DISPATCH_WINDOW_MS = 6e4;
|
|
239
310
|
var STALE_ASSIGNED_QUEUE_MS = 30 * 6e4;
|
|
240
311
|
var OLD_HISTORICAL_QUEUE_RECORD_MS = 7 * 24 * 60 * 6e4;
|
|
@@ -246,15 +317,24 @@ async function refreshMeshFromDaemon(ctx) {
|
|
|
246
317
|
const result = await ctx.transport.command("get_mesh", { meshId: ctx.mesh.id });
|
|
247
318
|
if (!result?.success || !Array.isArray(result.mesh?.nodes)) return;
|
|
248
319
|
const refreshedNodes = result.mesh.nodes.filter((n) => n?.id).map((n) => n);
|
|
249
|
-
if (!refreshedNodes.length) return;
|
|
250
320
|
ctx.mesh.nodes.splice(0, ctx.mesh.nodes.length, ...refreshedNodes);
|
|
251
321
|
ctx.mesh.updatedAt = result.mesh.updatedAt ?? ctx.mesh.updatedAt;
|
|
252
322
|
} catch {
|
|
253
323
|
}
|
|
254
324
|
}
|
|
325
|
+
async function syncCoordinatorDaemonMeshCache(ctx) {
|
|
326
|
+
if (!(ctx.transport instanceof IpcTransport)) return;
|
|
327
|
+
try {
|
|
328
|
+
await ctx.transport.command("get_mesh", {
|
|
329
|
+
meshId: ctx.mesh.id,
|
|
330
|
+
inlineMesh: ctx.mesh
|
|
331
|
+
});
|
|
332
|
+
} catch {
|
|
333
|
+
}
|
|
334
|
+
}
|
|
255
335
|
async function findNodeWithRefresh(ctx, nodeId) {
|
|
256
336
|
const hit = ctx.mesh.nodes.find((n) => n.id === nodeId);
|
|
257
|
-
if (hit) return hit;
|
|
337
|
+
if (hit && !hit.isLocalWorktree) return hit;
|
|
258
338
|
await refreshMeshFromDaemon(ctx);
|
|
259
339
|
const refreshed = ctx.mesh.nodes.find((n) => n.id === nodeId);
|
|
260
340
|
if (!refreshed) throw new Error(`Node '${nodeId}' is not a member of mesh '${ctx.mesh.name}'`);
|
|
@@ -262,7 +342,7 @@ async function findNodeWithRefresh(ctx, nodeId) {
|
|
|
262
342
|
}
|
|
263
343
|
async function findOptionalNodeWithRefresh(ctx, nodeId) {
|
|
264
344
|
const hit = ctx.mesh.nodes.find((n) => n.id === nodeId);
|
|
265
|
-
if (hit) return hit;
|
|
345
|
+
if (hit && !hit.isLocalWorktree) return hit;
|
|
266
346
|
await refreshMeshFromDaemon(ctx);
|
|
267
347
|
return ctx.mesh.nodes.find((n) => n.id === nodeId) ?? null;
|
|
268
348
|
}
|
|
@@ -314,9 +394,26 @@ function buildMissingNodeReadChatRecovery(ctx, args) {
|
|
|
314
394
|
readDebugLocator: readString(lastTerminal?.payload?.readDebugLocator) || readString(lastTerminal?.payload?.debugBundlePath)
|
|
315
395
|
};
|
|
316
396
|
if (finalSummary) {
|
|
397
|
+
if (args.compact === true) {
|
|
398
|
+
return {
|
|
399
|
+
...compactChatPayload({
|
|
400
|
+
success: true,
|
|
401
|
+
status: "idle",
|
|
402
|
+
providerSessionId,
|
|
403
|
+
summary: finalSummary,
|
|
404
|
+
messages: [{ role: "assistant", content: finalSummary, isHistorical: true }]
|
|
405
|
+
}, {
|
|
406
|
+
nodeId: args.node_id,
|
|
407
|
+
sessionId: args.session_id,
|
|
408
|
+
limit: args.tail ?? 10
|
|
409
|
+
}),
|
|
410
|
+
recoveredFromLedger: true,
|
|
411
|
+
ledger
|
|
412
|
+
};
|
|
413
|
+
}
|
|
317
414
|
return {
|
|
318
415
|
success: true,
|
|
319
|
-
compact:
|
|
416
|
+
compact: false,
|
|
320
417
|
recoveredFromLedger: true,
|
|
321
418
|
nodeId: args.node_id,
|
|
322
419
|
sessionId: args.session_id,
|
|
@@ -368,6 +465,33 @@ function buildMissingNodeReadChatRecovery(ctx, args) {
|
|
|
368
465
|
function readSessionRecordId(session) {
|
|
369
466
|
return readString(session?.id) || readString(session?.sessionId) || readString(session?.session_id) || readString(session?.runtimeSessionId) || readString(session?.runtime_session_id) || readString(session?.instanceId) || readString(session?.instance_id);
|
|
370
467
|
}
|
|
468
|
+
function extractStatusMetadataSessions(value) {
|
|
469
|
+
const payload = unwrapCommandPayload(value);
|
|
470
|
+
const status = payload?.status && typeof payload.status === "object" ? payload.status : payload;
|
|
471
|
+
return Array.isArray(status?.sessions) ? status.sessions : [];
|
|
472
|
+
}
|
|
473
|
+
function resolveSessionProviderType(session) {
|
|
474
|
+
return readString(session?.providerType) || readString(session?.cliType) || readString(session?.agentType) || "";
|
|
475
|
+
}
|
|
476
|
+
function isMeshCoordinatorSessionRecord(session) {
|
|
477
|
+
return Boolean(
|
|
478
|
+
readString(session?.settings?.meshCoordinatorFor) || readString(session?.meta?.meshCoordinatorFor) || readString(session?.metadata?.meshCoordinatorFor) || readString(session?.meshCoordinatorFor)
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
function isUnmanagedSessionRecord(session) {
|
|
482
|
+
const hasMeshNodeFor = Boolean(
|
|
483
|
+
readString(session?.settings?.meshNodeFor) || readString(session?.meta?.meshNodeFor) || readString(session?.metadata?.meshNodeFor) || readString(session?.meshNodeFor)
|
|
484
|
+
);
|
|
485
|
+
if (hasMeshNodeFor) return false;
|
|
486
|
+
if (isMeshCoordinatorSessionRecord(session)) return false;
|
|
487
|
+
const launchedByCoordinator = Boolean(
|
|
488
|
+
session?.settings?.launchedByCoordinator === true || session?.meta?.launchedByCoordinator === true || session?.launchedByCoordinator === true
|
|
489
|
+
);
|
|
490
|
+
return !launchedByCoordinator;
|
|
491
|
+
}
|
|
492
|
+
function isWorkerTaskMode(taskMode) {
|
|
493
|
+
return taskMode !== "live_debug_readonly";
|
|
494
|
+
}
|
|
371
495
|
function addSessionRecord(target, session) {
|
|
372
496
|
if (!session || typeof session !== "object" || isTerminalSessionRecord(session)) return;
|
|
373
497
|
const sessionId = readSessionRecordId(session);
|
|
@@ -436,18 +560,26 @@ function queueAssignmentStaleReason(task, liveness) {
|
|
|
436
560
|
}
|
|
437
561
|
function buildQueueStatusSummary(queue) {
|
|
438
562
|
const counts = { pending: 0, assigned: 0, completed: 0, failed: 0, cancelled: 0 };
|
|
563
|
+
let staleAssigned = 0;
|
|
439
564
|
for (const task of queue) {
|
|
440
565
|
const status = typeof task?.status === "string" ? task.status : void 0;
|
|
441
566
|
if (status && Object.prototype.hasOwnProperty.call(counts, status)) {
|
|
442
567
|
counts[status] += 1;
|
|
443
568
|
}
|
|
569
|
+
if (status === "assigned" && task?.staleAssigned === true) staleAssigned += 1;
|
|
444
570
|
}
|
|
571
|
+
const liveAssigned = Math.max(0, counts.assigned - staleAssigned);
|
|
445
572
|
return {
|
|
446
573
|
totalCount: queue.length,
|
|
447
|
-
activeCount: counts.pending +
|
|
574
|
+
activeCount: counts.pending + liveAssigned,
|
|
448
575
|
historicalCount: counts.completed + counts.failed + counts.cancelled,
|
|
449
576
|
counts,
|
|
450
577
|
activeCounts: {
|
|
578
|
+
pending: counts.pending,
|
|
579
|
+
assigned: liveAssigned
|
|
580
|
+
},
|
|
581
|
+
staleAssignedCount: staleAssigned,
|
|
582
|
+
rawActiveCounts: {
|
|
451
583
|
pending: counts.pending,
|
|
452
584
|
assigned: counts.assigned
|
|
453
585
|
},
|
|
@@ -475,6 +607,18 @@ function filterQueueForView(queue, view, statuses) {
|
|
|
475
607
|
if (view === "historical") return queue.filter((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
|
|
476
608
|
return queue;
|
|
477
609
|
}
|
|
610
|
+
function prioritizeActiveQueueRows(queue) {
|
|
611
|
+
const active = [];
|
|
612
|
+
const historical = [];
|
|
613
|
+
const other = [];
|
|
614
|
+
for (const task of queue) {
|
|
615
|
+
const status = String(task?.status || "");
|
|
616
|
+
if (ACTIVE_QUEUE_STATUSES.has(status)) active.push(task);
|
|
617
|
+
else if (HISTORICAL_QUEUE_STATUSES.has(status)) historical.push(task);
|
|
618
|
+
else other.push(task);
|
|
619
|
+
}
|
|
620
|
+
return [...active, ...other, ...historical];
|
|
621
|
+
}
|
|
478
622
|
function slimQueueTask(task) {
|
|
479
623
|
return {
|
|
480
624
|
id: task?.id,
|
|
@@ -580,22 +724,60 @@ function isIdleSessionRecord(session) {
|
|
|
580
724
|
const chatStatus = typeof session?.activeChat?.status === "string" ? session.activeChat.status.toLowerCase() : "";
|
|
581
725
|
return status === "idle" || chatStatus === "waiting_input";
|
|
582
726
|
}
|
|
727
|
+
function isMeshOwnedDelegateSession(session, meshId, nodeId) {
|
|
728
|
+
const settings = session?.settings;
|
|
729
|
+
const sessionMeshId = typeof settings?.meshNodeFor === "string" ? settings.meshNodeFor.trim() : "";
|
|
730
|
+
const coordinatorDaemonId = typeof settings?.meshCoordinatorDaemonId === "string" ? settings.meshCoordinatorDaemonId.trim() : "";
|
|
731
|
+
const sessionNodeId = typeof settings?.meshNodeId === "string" ? settings.meshNodeId.trim() : "";
|
|
732
|
+
if (sessionMeshId !== meshId || !coordinatorDaemonId) return false;
|
|
733
|
+
return !sessionNodeId || sessionNodeId === nodeId;
|
|
734
|
+
}
|
|
583
735
|
function chooseDispatchableSession(sessions, providerType, meshId, nodeId) {
|
|
584
736
|
const live = sessions.filter((session) => !isTerminalSessionRecord(session));
|
|
585
737
|
const matchingProvider = (session) => !providerType || session?.providerType === providerType || session?.cliType === providerType;
|
|
586
|
-
const isMeshOwnedDelegateSession = (session) => {
|
|
587
|
-
const settings = session?.settings;
|
|
588
|
-
const sessionMeshId = typeof settings?.meshNodeFor === "string" ? settings.meshNodeFor.trim() : "";
|
|
589
|
-
const coordinatorDaemonId = typeof settings?.meshCoordinatorDaemonId === "string" ? settings.meshCoordinatorDaemonId.trim() : "";
|
|
590
|
-
const sessionNodeId = typeof settings?.meshNodeId === "string" ? settings.meshNodeId.trim() : "";
|
|
591
|
-
if (sessionMeshId !== meshId || !coordinatorDaemonId) return false;
|
|
592
|
-
return !sessionNodeId || sessionNodeId === nodeId;
|
|
593
|
-
};
|
|
594
738
|
const meshSessions = live.filter(
|
|
595
|
-
(session) => isMeshOwnedDelegateSession(session)
|
|
739
|
+
(session) => isMeshOwnedDelegateSession(session, meshId, nodeId)
|
|
596
740
|
);
|
|
597
741
|
return meshSessions.find((session) => isIdleSessionRecord(session) && matchingProvider(session)) || meshSessions.find(matchingProvider) || void 0;
|
|
598
742
|
}
|
|
743
|
+
function buildRelayUnsafeRemoteSessionFailure(ctx, node, sessionId, providerType) {
|
|
744
|
+
return {
|
|
745
|
+
success: false,
|
|
746
|
+
recoverable: true,
|
|
747
|
+
code: "mesh_delegate_session_missing_relay_metadata",
|
|
748
|
+
reason: "mesh_delegate_session_missing_relay_metadata",
|
|
749
|
+
transport: "mesh_transport",
|
|
750
|
+
retryRecommended: true,
|
|
751
|
+
meshId: ctx.mesh.id,
|
|
752
|
+
nodeId: node.id,
|
|
753
|
+
daemonId: node.daemonId,
|
|
754
|
+
workspace: node.workspace,
|
|
755
|
+
sessionId,
|
|
756
|
+
unsafeTranscriptAlias: true,
|
|
757
|
+
...providerType ? { resolvedProviderType: providerType } : {},
|
|
758
|
+
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).`,
|
|
759
|
+
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.`,
|
|
760
|
+
noFallbackReason: "Blindly reusing a remote session without mesh relay metadata would silently drop task_completed / generating_completed events."
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
function buildMissingCoordinatorDaemonIdFailure(ctx, node, providerType) {
|
|
764
|
+
return {
|
|
765
|
+
success: false,
|
|
766
|
+
recoverable: true,
|
|
767
|
+
code: "mesh_coordinator_daemon_unknown",
|
|
768
|
+
reason: "mesh_coordinator_daemon_unknown",
|
|
769
|
+
transport: "mesh_transport",
|
|
770
|
+
retryRecommended: true,
|
|
771
|
+
meshId: ctx.mesh.id,
|
|
772
|
+
nodeId: node.id,
|
|
773
|
+
daemonId: node.daemonId,
|
|
774
|
+
workspace: node.workspace,
|
|
775
|
+
...providerType ? { resolvedProviderType: providerType } : {},
|
|
776
|
+
error: `Cannot launch a remote mesh delegate for node '${node.id}': coordinator daemon identity is unavailable, so the worker would be unable to relay completion events back to the coordinator.`,
|
|
777
|
+
nextAction: "Retry after the coordinator daemon identity is available (for example from an attached daemon-backed MCP session) so meshCoordinatorDaemonId can be stamped on the worker session.",
|
|
778
|
+
noFallbackReason: "Launching without meshCoordinatorDaemonId would create a worker session that can finish work but cannot emit task_completed / generating_completed back to the coordinator."
|
|
779
|
+
};
|
|
780
|
+
}
|
|
599
781
|
function findNestedPayload(value, predicate) {
|
|
600
782
|
const seen = /* @__PURE__ */ new Set();
|
|
601
783
|
const stack = [{ payload: value, depth: 0 }];
|
|
@@ -623,12 +805,16 @@ function extractGitDiff(value) {
|
|
|
623
805
|
}
|
|
624
806
|
function extractSubmodules(value, ignorePaths) {
|
|
625
807
|
const payload = unwrapCommandPayload(value);
|
|
626
|
-
const subs = payload?.submodules ?? value?.submodules;
|
|
808
|
+
const subs = payload?.status?.submodules ?? payload?.submodules ?? value?.status?.submodules ?? value?.submodules;
|
|
627
809
|
if (!Array.isArray(subs)) return void 0;
|
|
628
810
|
if (ignorePaths.length === 0) return subs;
|
|
629
811
|
const ignoreSet = new Set(ignorePaths);
|
|
630
812
|
return subs.filter((s) => s?.path && !ignoreSet.has(s.path));
|
|
631
813
|
}
|
|
814
|
+
function assignFullGitSnapshot(entry, status) {
|
|
815
|
+
if (!status || typeof status !== "object" || Array.isArray(status)) return;
|
|
816
|
+
entry.git = status;
|
|
817
|
+
}
|
|
632
818
|
function extractLaunchPayload(value) {
|
|
633
819
|
return findNestedPayload(value, (payload) => Boolean(payload?.sessionId || payload?.id || payload?.runtimeSessionId));
|
|
634
820
|
}
|
|
@@ -753,20 +939,76 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
|
|
|
753
939
|
let sessionId = args.session_id?.trim() || "";
|
|
754
940
|
const providerPriorityList = Array.isArray(node.policy?.providerPriority) ? node.policy.providerPriority : [];
|
|
755
941
|
let resolvedProviderType = args.providerType?.trim() || providerPriorityList[0] || "";
|
|
756
|
-
if (
|
|
942
|
+
if (sessionId && args.verifiedSession) {
|
|
943
|
+
const explicitSession = args.verifiedSession;
|
|
944
|
+
if (!isMeshOwnedDelegateSession(explicitSession, ctx.mesh.id, node.id)) {
|
|
945
|
+
return buildRelayUnsafeRemoteSessionFailure(
|
|
946
|
+
ctx,
|
|
947
|
+
node,
|
|
948
|
+
sessionId,
|
|
949
|
+
resolvedProviderType || resolveSessionProviderType(explicitSession) || void 0
|
|
950
|
+
);
|
|
951
|
+
}
|
|
952
|
+
if (!resolvedProviderType) {
|
|
953
|
+
resolvedProviderType = resolveSessionProviderType(explicitSession);
|
|
954
|
+
}
|
|
955
|
+
} else if (!sessionId || args.session_id) {
|
|
757
956
|
try {
|
|
758
957
|
const relayResult = await transport.meshCommand(daemonId, "get_status_metadata", {});
|
|
759
|
-
const
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
958
|
+
const sessions = extractStatusMetadataSessions(relayResult);
|
|
959
|
+
if (sessionId) {
|
|
960
|
+
const explicitSession = sessions.find((session) => readSessionRecordId(session) === sessionId);
|
|
961
|
+
if (!explicitSession) {
|
|
962
|
+
return {
|
|
963
|
+
success: false,
|
|
964
|
+
recoverable: true,
|
|
965
|
+
code: "mesh_target_session_not_found",
|
|
966
|
+
reason: "mesh_target_session_not_found",
|
|
967
|
+
transport: "mesh_transport",
|
|
968
|
+
retryRecommended: true,
|
|
969
|
+
meshId: ctx.mesh.id,
|
|
970
|
+
nodeId: node.id,
|
|
971
|
+
daemonId,
|
|
972
|
+
workspace: node.workspace,
|
|
973
|
+
sessionId,
|
|
974
|
+
...resolvedProviderType ? { resolvedProviderType } : {},
|
|
975
|
+
error: `Remote session '${sessionId}' is not present in the live status for node '${node.id}'.`,
|
|
976
|
+
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.`
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
if (!isMeshOwnedDelegateSession(explicitSession, ctx.mesh.id, node.id)) {
|
|
980
|
+
return buildRelayUnsafeRemoteSessionFailure(
|
|
981
|
+
ctx,
|
|
982
|
+
node,
|
|
983
|
+
sessionId,
|
|
984
|
+
resolvedProviderType || resolveSessionProviderType(explicitSession) || void 0
|
|
985
|
+
);
|
|
986
|
+
}
|
|
765
987
|
if (!resolvedProviderType) {
|
|
766
|
-
resolvedProviderType =
|
|
988
|
+
resolvedProviderType = resolveSessionProviderType(explicitSession);
|
|
989
|
+
}
|
|
990
|
+
} else {
|
|
991
|
+
const targetSession = chooseDispatchableSession(sessions, resolvedProviderType, ctx.mesh.id, node.id);
|
|
992
|
+
if (targetSession?.id || targetSession?.sessionId) {
|
|
993
|
+
sessionId = targetSession.id || targetSession.sessionId;
|
|
994
|
+
if (!resolvedProviderType) {
|
|
995
|
+
resolvedProviderType = resolveSessionProviderType(targetSession);
|
|
996
|
+
}
|
|
767
997
|
}
|
|
768
998
|
}
|
|
769
999
|
} catch (e) {
|
|
1000
|
+
if (sessionId) {
|
|
1001
|
+
return {
|
|
1002
|
+
...buildCoordinatorP2pRelayFailure(e, {
|
|
1003
|
+
command: "get_status_metadata",
|
|
1004
|
+
targetDaemonId: daemonId,
|
|
1005
|
+
nodeId: node.id,
|
|
1006
|
+
sessionId
|
|
1007
|
+
}),
|
|
1008
|
+
success: false,
|
|
1009
|
+
error: `Cannot verify remote session '${sessionId}' before dispatch: ${e?.message || String(e)}`
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
770
1012
|
}
|
|
771
1013
|
}
|
|
772
1014
|
if (!resolvedProviderType) {
|
|
@@ -796,7 +1038,7 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
|
|
|
796
1038
|
error: `P2P dispatch failed: ${errorMessage}`
|
|
797
1039
|
};
|
|
798
1040
|
}
|
|
799
|
-
return { success: true, dispatched: true, sessionId: sessionId || resolvedProviderType };
|
|
1041
|
+
return { success: true, dispatched: true, sessionId: sessionId || resolvedProviderType, providerType: resolvedProviderType };
|
|
800
1042
|
} catch (e) {
|
|
801
1043
|
const errorMessage = e?.message || String(e);
|
|
802
1044
|
return {
|
|
@@ -826,34 +1068,198 @@ function resolveCoordinatorNode(ctx) {
|
|
|
826
1068
|
return void 0;
|
|
827
1069
|
}
|
|
828
1070
|
function readNodeMachineId(node) {
|
|
829
|
-
return readString(node.machineId) || readString(node.machine_id);
|
|
1071
|
+
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);
|
|
830
1072
|
}
|
|
831
1073
|
function readNodeDaemonId(node) {
|
|
832
|
-
return readString(node.daemonId) || readString(node.daemon_id);
|
|
1074
|
+
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);
|
|
1075
|
+
}
|
|
1076
|
+
function normalizeHostname(value) {
|
|
1077
|
+
const hostname = readString(value);
|
|
1078
|
+
if (!hostname) return void 0;
|
|
1079
|
+
return hostname.toLowerCase().replace(/\.$/, "");
|
|
1080
|
+
}
|
|
1081
|
+
function readNodeHostname(node) {
|
|
1082
|
+
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);
|
|
1083
|
+
}
|
|
1084
|
+
function readNodeDisplayMachineName(node) {
|
|
1085
|
+
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);
|
|
1086
|
+
}
|
|
1087
|
+
function compactIdentityEvidence(value) {
|
|
1088
|
+
if (!value) return void 0;
|
|
1089
|
+
return value.length > 24 ? `${value.slice(0, 12)}\u2026${value.slice(-8)}` : value;
|
|
1090
|
+
}
|
|
1091
|
+
function pushIdentityEvidence(evidence, label, value) {
|
|
1092
|
+
const compact = compactIdentityEvidence(value);
|
|
1093
|
+
if (compact) evidence.push(`${label}:${compact}`);
|
|
1094
|
+
}
|
|
1095
|
+
function buildNodeMachineIdentity(ctx, node) {
|
|
1096
|
+
const machineId = readNodeMachineId(node);
|
|
1097
|
+
const daemonId = readNodeDaemonId(node);
|
|
1098
|
+
const hostname = readNodeHostname(node);
|
|
1099
|
+
const machineName = readNodeDisplayMachineName(node);
|
|
1100
|
+
const coordinatorHostname = readString(ctx.coordinatorHostname);
|
|
1101
|
+
const localControlPlaneReason = getLocalControlPlaneMatchReason(ctx, node);
|
|
1102
|
+
const directLocal = !!localControlPlaneReason;
|
|
1103
|
+
const hostnameMatches = Boolean(
|
|
1104
|
+
normalizeHostname(hostname) && normalizeHostname(coordinatorHostname) && normalizeHostname(hostname) === normalizeHostname(coordinatorHostname)
|
|
1105
|
+
);
|
|
1106
|
+
const sameMachine = directLocal || hostnameMatches;
|
|
1107
|
+
const evidence = [];
|
|
1108
|
+
pushIdentityEvidence(evidence, "machineName", machineName);
|
|
1109
|
+
pushIdentityEvidence(evidence, "hostname", hostname);
|
|
1110
|
+
pushIdentityEvidence(evidence, "machineId", machineId);
|
|
1111
|
+
pushIdentityEvidence(evidence, "daemonId", daemonId);
|
|
1112
|
+
if (localControlPlaneReason) {
|
|
1113
|
+
pushIdentityEvidence(evidence, "localMatch", localControlPlaneReason);
|
|
1114
|
+
pushIdentityEvidence(evidence, "localMachineId", ctx.localMachineId);
|
|
1115
|
+
pushIdentityEvidence(evidence, "localDaemonId", ctx.localDaemonId);
|
|
1116
|
+
}
|
|
1117
|
+
const locality = sameMachine ? "same_machine" : evidence.length > 0 ? "remote_known" : "remote_or_unknown";
|
|
1118
|
+
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";
|
|
1119
|
+
return {
|
|
1120
|
+
daemonId,
|
|
1121
|
+
machineId,
|
|
1122
|
+
hostname,
|
|
1123
|
+
machineName,
|
|
1124
|
+
displayName: machineName || hostname || daemonId || machineId,
|
|
1125
|
+
coordinatorHostname,
|
|
1126
|
+
sameMachine,
|
|
1127
|
+
locality,
|
|
1128
|
+
localityReason,
|
|
1129
|
+
identityEvidence: evidence
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
function nodeHasLocalDaemonEvidence(ctx, node) {
|
|
1133
|
+
const isLocal = (session) => {
|
|
1134
|
+
if (!session || typeof session !== "object") return false;
|
|
1135
|
+
if (ctx.localDaemonId && session.settings?.meshCoordinatorDaemonId === ctx.localDaemonId) return true;
|
|
1136
|
+
if (session.launchedByCoordinator === true) return true;
|
|
1137
|
+
if (ctx.localDaemonId && session.runtime?.owner === ctx.localDaemonId) return true;
|
|
1138
|
+
if (ctx.localDaemonId && session.daemonClient?.daemonId === ctx.localDaemonId) return true;
|
|
1139
|
+
return false;
|
|
1140
|
+
};
|
|
1141
|
+
const sessionArrays = [
|
|
1142
|
+
node?.sessions,
|
|
1143
|
+
node?.activeSessions,
|
|
1144
|
+
node?.active_sessions,
|
|
1145
|
+
node?.lastProbe?.sessions,
|
|
1146
|
+
node?.last_probe?.sessions,
|
|
1147
|
+
node?.lastProbe?.status?.sessions,
|
|
1148
|
+
node?.last_probe?.status?.sessions
|
|
1149
|
+
];
|
|
1150
|
+
for (const arr of sessionArrays) {
|
|
1151
|
+
if (Array.isArray(arr) && arr.some(isLocal)) return true;
|
|
1152
|
+
}
|
|
1153
|
+
const sessionRecords = [
|
|
1154
|
+
node?.activeSession,
|
|
1155
|
+
node?.active_session,
|
|
1156
|
+
node?.currentSession,
|
|
1157
|
+
node?.current_session,
|
|
1158
|
+
node?.runtimeSession,
|
|
1159
|
+
node?.runtime_session,
|
|
1160
|
+
node?.session,
|
|
1161
|
+
node?.lastProbe?.activeSession,
|
|
1162
|
+
node?.last_probe?.active_session,
|
|
1163
|
+
node?.lastProbe?.currentSession,
|
|
1164
|
+
node?.last_probe?.current_session,
|
|
1165
|
+
node?.lastProbe?.session,
|
|
1166
|
+
node?.last_probe?.session
|
|
1167
|
+
];
|
|
1168
|
+
for (const session of sessionRecords) {
|
|
1169
|
+
if (isLocal(session)) return true;
|
|
1170
|
+
}
|
|
1171
|
+
return false;
|
|
833
1172
|
}
|
|
834
1173
|
function isDirectLocalNode(ctx, node) {
|
|
835
1174
|
const machineId = readNodeMachineId(node);
|
|
836
1175
|
const daemonId = readNodeDaemonId(node);
|
|
837
1176
|
return Boolean(
|
|
838
|
-
ctx.localMachineId && machineId === ctx.localMachineId || ctx.localDaemonId && daemonId === ctx.localDaemonId
|
|
1177
|
+
ctx.localMachineId && machineId === ctx.localMachineId || ctx.localDaemonId && daemonId === ctx.localDaemonId || nodeHasLocalDaemonEvidence(ctx, node)
|
|
839
1178
|
);
|
|
840
1179
|
}
|
|
1180
|
+
function isConfiguredCoordinatorNode(ctx, node) {
|
|
1181
|
+
if (!ctx.localMachineId && !ctx.localDaemonId) return false;
|
|
1182
|
+
const nodeId = readString(node.id) || readString(node.nodeId) || readString(node.node_id);
|
|
1183
|
+
if (!nodeId) return false;
|
|
1184
|
+
const nodeDaemonId = readNodeDaemonId(node);
|
|
1185
|
+
const nodeMachineId = readNodeMachineId(node);
|
|
1186
|
+
if (nodeDaemonId && ctx.localDaemonId && nodeDaemonId !== ctx.localDaemonId) return false;
|
|
1187
|
+
if (nodeMachineId && ctx.localMachineId && nodeMachineId !== ctx.localMachineId) return false;
|
|
1188
|
+
const preferredNodeId = readString(ctx.mesh.coordinator?.preferredNodeId) || readString(ctx.mesh.coordinator?.preferred_node_id);
|
|
1189
|
+
if (preferredNodeId) return nodeId === preferredNodeId;
|
|
1190
|
+
const first = ctx.mesh.nodes?.[0];
|
|
1191
|
+
const firstNodeId = readString(first?.id) || readString(first?.nodeId) || readString(first?.node_id);
|
|
1192
|
+
return !!firstNodeId && nodeId === firstNodeId;
|
|
1193
|
+
}
|
|
1194
|
+
function getLocalControlPlaneMatchReason(ctx, node) {
|
|
1195
|
+
if (isDirectLocalNode(ctx, node)) return "matched coordinator daemon or machine id";
|
|
1196
|
+
if (isConfiguredCoordinatorNode(ctx, node)) return "matched configured coordinator node";
|
|
1197
|
+
if (node.isLocalWorktree === true) {
|
|
1198
|
+
const sourceNode = findClonedFromNode(ctx, node);
|
|
1199
|
+
if (sourceNode && isDirectLocalNode(ctx, sourceNode)) return "matched local cloned-from node";
|
|
1200
|
+
if (sourceNode && isConfiguredCoordinatorNode(ctx, sourceNode)) return "matched configured coordinator source node";
|
|
1201
|
+
}
|
|
1202
|
+
return void 0;
|
|
1203
|
+
}
|
|
841
1204
|
function findClonedFromNode(ctx, node) {
|
|
842
1205
|
const clonedFromNodeId = readString(node.clonedFromNodeId) || readString(node.cloned_from_node_id);
|
|
843
1206
|
if (!clonedFromNodeId) return void 0;
|
|
844
1207
|
return ctx.mesh.nodes.find((n) => n.id === clonedFromNodeId || n.nodeId === clonedFromNodeId || n.node_id === clonedFromNodeId);
|
|
845
1208
|
}
|
|
846
1209
|
function isLocalControlPlaneNode(ctx, node) {
|
|
847
|
-
|
|
848
|
-
if (node.isLocalWorktree === true) {
|
|
849
|
-
const sourceNode = findClonedFromNode(ctx, node);
|
|
850
|
-
if (sourceNode && isDirectLocalNode(ctx, sourceNode)) return true;
|
|
851
|
-
}
|
|
852
|
-
return false;
|
|
1210
|
+
return !!getLocalControlPlaneMatchReason(ctx, node);
|
|
853
1211
|
}
|
|
854
1212
|
function meshSessionCacheKey(nodeId, runtimeSessionId) {
|
|
855
1213
|
return `${nodeId}:${runtimeSessionId}`;
|
|
856
1214
|
}
|
|
1215
|
+
function rememberMeshSessionProviderMetadata(nodeId, runtimeSessionId, metadata) {
|
|
1216
|
+
const keyNodeId = readString(nodeId);
|
|
1217
|
+
const keySessionId = readString(runtimeSessionId);
|
|
1218
|
+
if (!keyNodeId || !keySessionId) return;
|
|
1219
|
+
const providerType = readString(metadata.providerType);
|
|
1220
|
+
const providerSessionId = readString(metadata.providerSessionId);
|
|
1221
|
+
if (!providerType && !providerSessionId) return;
|
|
1222
|
+
const existing = meshSessionProviderMetadata.get(meshSessionCacheKey(keyNodeId, keySessionId)) || { providerType: "" };
|
|
1223
|
+
meshSessionProviderMetadata.set(meshSessionCacheKey(keyNodeId, keySessionId), {
|
|
1224
|
+
providerType: providerType || existing.providerType,
|
|
1225
|
+
providerSessionId: providerSessionId || existing.providerSessionId
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
function rememberMeshSessionProviderMetadataFromEvent(event) {
|
|
1229
|
+
const metadataEvent = event?.metadataEvent && typeof event.metadataEvent === "object" ? event.metadataEvent : event && typeof event === "object" ? event : {};
|
|
1230
|
+
const nodeId = readString(event?.nodeId) || readString(metadataEvent.nodeId) || readString(metadataEvent.meshNodeId);
|
|
1231
|
+
const sessionId = readString(metadataEvent.targetSessionId) || readString(metadataEvent.sessionId) || readString(metadataEvent.instanceId) || readString(event?.sessionId);
|
|
1232
|
+
rememberMeshSessionProviderMetadata(nodeId, sessionId, {
|
|
1233
|
+
providerType: readString(metadataEvent.providerType) || readString(event?.providerType) || "",
|
|
1234
|
+
providerSessionId: readString(metadataEvent.providerSessionId) || readString(event?.providerSessionId)
|
|
1235
|
+
});
|
|
1236
|
+
}
|
|
1237
|
+
function resolveMeshSessionProviderMetadataFromLedger(ctx, nodeId, runtimeSessionId) {
|
|
1238
|
+
const entries = (0, import_daemon_core.readLedgerEntries)(ctx.mesh.id, { tail: 500 });
|
|
1239
|
+
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
1240
|
+
const entry = entries[i];
|
|
1241
|
+
const payload = entry.payload && typeof entry.payload === "object" && !Array.isArray(entry.payload) ? entry.payload : {};
|
|
1242
|
+
const entryNodeId = readString(entry.nodeId) || readString(payload.nodeId) || readString(payload.meshNodeId);
|
|
1243
|
+
if (entryNodeId && entryNodeId !== nodeId) continue;
|
|
1244
|
+
const entrySessionId = readString(entry.sessionId) || readString(payload.targetSessionId) || readString(payload.sessionId) || readString(payload.instanceId);
|
|
1245
|
+
if (entrySessionId !== runtimeSessionId) continue;
|
|
1246
|
+
const providerType = readString(entry.providerType) || readString(payload.providerType);
|
|
1247
|
+
const completionDiagnostic = payload.completionDiagnostic && typeof payload.completionDiagnostic === "object" && !Array.isArray(payload.completionDiagnostic) ? payload.completionDiagnostic : {};
|
|
1248
|
+
const metadataEvent = payload.metadataEvent && typeof payload.metadataEvent === "object" && !Array.isArray(payload.metadataEvent) ? payload.metadataEvent : {};
|
|
1249
|
+
const providerSessionId = readString(payload.providerSessionId) || readString(completionDiagnostic.providerSessionId) || readString(metadataEvent.providerSessionId);
|
|
1250
|
+
if (providerType || providerSessionId) {
|
|
1251
|
+
return { providerType: providerType || "", providerSessionId };
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
return void 0;
|
|
1255
|
+
}
|
|
1256
|
+
function resolveMeshSessionProviderMetadata(ctx, nodeId, runtimeSessionId) {
|
|
1257
|
+
const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(nodeId, runtimeSessionId));
|
|
1258
|
+
if (cached?.providerType || cached?.providerSessionId) return cached;
|
|
1259
|
+
const fromLedger = resolveMeshSessionProviderMetadataFromLedger(ctx, nodeId, runtimeSessionId);
|
|
1260
|
+
if (fromLedger) rememberMeshSessionProviderMetadata(nodeId, runtimeSessionId, fromLedger);
|
|
1261
|
+
return fromLedger;
|
|
1262
|
+
}
|
|
857
1263
|
function countUncommittedChanges(status) {
|
|
858
1264
|
if (typeof status?.uncommittedChanges === "number") return status.uncommittedChanges;
|
|
859
1265
|
const keys = ["staged", "modified", "untracked", "deleted", "renamed"];
|
|
@@ -924,6 +1330,16 @@ function missingProviderPriorityMessage(nodeId) {
|
|
|
924
1330
|
return `Node '${nodeId}' has no providerPriority policy; pass type explicitly or configure node.policy.providerPriority`;
|
|
925
1331
|
}
|
|
926
1332
|
function getNodeLaunchReadiness(node) {
|
|
1333
|
+
const bootstrap = node.worktreeBootstrap;
|
|
1334
|
+
if (node.isLocalWorktree && bootstrap?.status === "failed" && bootstrap?.required !== false) {
|
|
1335
|
+
return {
|
|
1336
|
+
providerPriority: readProviderPriority(node.policy),
|
|
1337
|
+
launchReady: false,
|
|
1338
|
+
launchBlockedReason: "worktree_bootstrap_failed",
|
|
1339
|
+
launchBlockedMessage: typeof bootstrap.error === "string" && bootstrap.error.trim() ? bootstrap.error.trim() : "Required worktree bootstrap failed; resolve it before launching an agent into this node.",
|
|
1340
|
+
worktreeBootstrap: bootstrap
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
927
1343
|
const providerPriority = readProviderPriority(node.policy);
|
|
928
1344
|
if (providerPriority.length) {
|
|
929
1345
|
return {
|
|
@@ -938,6 +1354,33 @@ function getNodeLaunchReadiness(node) {
|
|
|
938
1354
|
launchBlockedMessage: missingProviderPriorityMessage(node.id)
|
|
939
1355
|
};
|
|
940
1356
|
}
|
|
1357
|
+
function getWorktreeBootstrapLaunchBlock(node) {
|
|
1358
|
+
const bootstrap = node.worktreeBootstrap;
|
|
1359
|
+
if (!node.isLocalWorktree || bootstrap?.status !== "failed" || bootstrap?.required === false) return void 0;
|
|
1360
|
+
return {
|
|
1361
|
+
success: false,
|
|
1362
|
+
code: "worktree_bootstrap_failed",
|
|
1363
|
+
error: typeof bootstrap.error === "string" && bootstrap.error.trim() ? bootstrap.error.trim() : `Node '${node.id}' has a failed required worktree bootstrap.`,
|
|
1364
|
+
nodeId: node.id,
|
|
1365
|
+
worktreeBootstrap: bootstrap,
|
|
1366
|
+
recoveryHint: "Fix the configured worktree bootstrap command or remove/recreate the worktree node before launching an agent."
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1369
|
+
async function collectLiveStatusSessions(ctx, node) {
|
|
1370
|
+
try {
|
|
1371
|
+
const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
|
|
1372
|
+
return extractStatusMetadataSessions(statusResult);
|
|
1373
|
+
} catch {
|
|
1374
|
+
return [];
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
async function collectMeshViewQueueNodesWithLiveSessions(ctx) {
|
|
1378
|
+
const nodes = await Promise.all(ctx.mesh.nodes.map(async (node) => {
|
|
1379
|
+
const liveSessions = await collectLiveStatusSessions(ctx, node);
|
|
1380
|
+
return liveSessions.length > 0 ? { ...node, sessions: liveSessions } : node;
|
|
1381
|
+
}));
|
|
1382
|
+
return nodes;
|
|
1383
|
+
}
|
|
941
1384
|
function readNumeric(value, fallback = 0) {
|
|
942
1385
|
const parsed = Number(value);
|
|
943
1386
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
@@ -1073,7 +1516,8 @@ async function commandForNode(ctx, node, command, args = {}) {
|
|
|
1073
1516
|
if (isLocalTransport(ctx.transport)) {
|
|
1074
1517
|
return ctx.transport.command(command, args);
|
|
1075
1518
|
}
|
|
1076
|
-
|
|
1519
|
+
const identity = buildNodeMachineIdentity(ctx, node);
|
|
1520
|
+
throw new Error(`Command '${command}' requires daemon IPC/local transport for node '${node.id}' (hostname=${identity.hostname || "unknown"}, coordinatorHostname=${identity.coordinatorHostname || "unknown"}, sameMachine=${identity.sameMachine})`);
|
|
1077
1521
|
}
|
|
1078
1522
|
function normalizePendingMeshCoordinatorEvents(value) {
|
|
1079
1523
|
const payload = unwrapCommandPayload(value);
|
|
@@ -1091,6 +1535,14 @@ function buildMeshForwardPayloadFromPendingEvent(event) {
|
|
|
1091
1535
|
providerType: readString(metadataEvent.providerType),
|
|
1092
1536
|
providerSessionId: readString(metadataEvent.providerSessionId),
|
|
1093
1537
|
finalSummary: readString(metadataEvent.finalSummary) || readString(metadataEvent.summary),
|
|
1538
|
+
jobId: readString(metadataEvent.jobId),
|
|
1539
|
+
interactionId: readString(metadataEvent.interactionId),
|
|
1540
|
+
status: readString(metadataEvent.status),
|
|
1541
|
+
targetDaemonId: readString(metadataEvent.targetDaemonId),
|
|
1542
|
+
startedAt: readString(metadataEvent.startedAt),
|
|
1543
|
+
completedAt: readString(metadataEvent.completedAt),
|
|
1544
|
+
retryOfJobId: readString(metadataEvent.retryOfJobId),
|
|
1545
|
+
...metadataEvent.result && typeof metadataEvent.result === "object" && !Array.isArray(metadataEvent.result) ? { result: metadataEvent.result } : {},
|
|
1094
1546
|
...metadataEvent.intentional === true ? { intentional: true } : {},
|
|
1095
1547
|
...metadataEvent.intentionalStop === true ? { intentionalStop: true } : {},
|
|
1096
1548
|
...metadataEvent.operatorCleanup === true ? { operatorCleanup: true } : {},
|
|
@@ -1107,8 +1559,9 @@ async function drainCoordinatorPendingEvents(ctx, opts) {
|
|
|
1107
1559
|
const surfacedEvents = [];
|
|
1108
1560
|
try {
|
|
1109
1561
|
surfacedEvents.push(
|
|
1110
|
-
...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", {})).filter(matchesCurrentMesh)
|
|
1562
|
+
...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", { meshId: ctx.mesh.id })).filter(matchesCurrentMesh)
|
|
1111
1563
|
);
|
|
1564
|
+
surfacedEvents.forEach(rememberMeshSessionProviderMetadataFromEvent);
|
|
1112
1565
|
} catch {
|
|
1113
1566
|
}
|
|
1114
1567
|
for (const node of ctx.mesh.nodes) {
|
|
@@ -1116,27 +1569,31 @@ async function drainCoordinatorPendingEvents(ctx, opts) {
|
|
|
1116
1569
|
if (requestedNodeIds && !requestedNodeIds.has(node.id)) continue;
|
|
1117
1570
|
try {
|
|
1118
1571
|
const remoteEvents = normalizePendingMeshCoordinatorEvents(
|
|
1119
|
-
await ctx.transport.meshCommand(node.daemonId, "get_pending_mesh_events", {})
|
|
1572
|
+
await ctx.transport.meshCommand(node.daemonId, "get_pending_mesh_events", { meshId: ctx.mesh.id })
|
|
1120
1573
|
).filter(matchesCurrentMesh);
|
|
1121
1574
|
if (remoteEvents.length === 0) continue;
|
|
1122
1575
|
for (const event of remoteEvents) {
|
|
1123
1576
|
const payload = buildMeshForwardPayloadFromPendingEvent(event);
|
|
1124
1577
|
if (!payload.event || !payload.meshId) continue;
|
|
1125
1578
|
await ctx.transport.command("mesh_forward_event", payload);
|
|
1579
|
+
rememberMeshSessionProviderMetadataFromEvent({ ...event, metadataEvent: payload });
|
|
1126
1580
|
}
|
|
1127
1581
|
} catch {
|
|
1128
1582
|
}
|
|
1129
1583
|
}
|
|
1130
1584
|
try {
|
|
1131
1585
|
surfacedEvents.push(
|
|
1132
|
-
...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", {})).filter(matchesCurrentMesh)
|
|
1586
|
+
...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", { meshId: ctx.mesh.id })).filter(matchesCurrentMesh)
|
|
1133
1587
|
);
|
|
1588
|
+
surfacedEvents.forEach(rememberMeshSessionProviderMetadataFromEvent);
|
|
1134
1589
|
} catch {
|
|
1135
1590
|
}
|
|
1136
1591
|
return surfacedEvents;
|
|
1137
1592
|
}
|
|
1138
1593
|
if (isLocalTransport(ctx.transport)) {
|
|
1139
|
-
|
|
1594
|
+
const events = (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id).filter(matchesCurrentMesh);
|
|
1595
|
+
events.forEach(rememberMeshSessionProviderMetadataFromEvent);
|
|
1596
|
+
return events;
|
|
1140
1597
|
}
|
|
1141
1598
|
return [];
|
|
1142
1599
|
}
|
|
@@ -1153,11 +1610,12 @@ function buildRemoveNodeArgs(ctx, nodeId, sessionCleanupMode) {
|
|
|
1153
1610
|
}
|
|
1154
1611
|
var MESH_STATUS_TOOL = {
|
|
1155
1612
|
name: "mesh_status",
|
|
1156
|
-
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.",
|
|
1613
|
+
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.",
|
|
1157
1614
|
inputSchema: {
|
|
1158
1615
|
type: "object",
|
|
1159
1616
|
properties: {
|
|
1160
|
-
_gemini_compat: { type: "string", description: "Dummy property for Gemini compatibility. Ignore this." }
|
|
1617
|
+
_gemini_compat: { type: "string", description: "Dummy property for Gemini compatibility. Ignore this." },
|
|
1618
|
+
includeStaleDirectWorkDetails: { type: "boolean", description: "Opt in to the full staleDirectWork array. Defaults false; normal status returns compact staleDirectWorkSummary only." }
|
|
1161
1619
|
}
|
|
1162
1620
|
}
|
|
1163
1621
|
};
|
|
@@ -1177,14 +1635,16 @@ var MESH_ENQUEUE_TASK_TOOL = {
|
|
|
1177
1635
|
inputSchema: {
|
|
1178
1636
|
type: "object",
|
|
1179
1637
|
properties: {
|
|
1180
|
-
message: { type: "string", description: "The task instruction for the agent." }
|
|
1638
|
+
message: { type: "string", description: "The task instruction for the agent." },
|
|
1639
|
+
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." },
|
|
1640
|
+
taskMode: { type: "string", enum: ["code_change", "validation", "live_debug_readonly", "launch_app", "convergence"], description: "CamelCase alias for task_mode." }
|
|
1181
1641
|
},
|
|
1182
1642
|
required: ["message"]
|
|
1183
1643
|
}
|
|
1184
1644
|
};
|
|
1185
1645
|
var MESH_VIEW_QUEUE_TOOL = {
|
|
1186
1646
|
name: "mesh_view_queue",
|
|
1187
|
-
description: "View the mesh work queue with source-of-truth active counts separated from historical completed/failed/cancelled records.",
|
|
1647
|
+
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.",
|
|
1188
1648
|
inputSchema: {
|
|
1189
1649
|
type: "object",
|
|
1190
1650
|
properties: {
|
|
@@ -1237,7 +1697,9 @@ var MESH_SEND_TASK_TOOL = {
|
|
|
1237
1697
|
properties: {
|
|
1238
1698
|
node_id: { type: "string", description: "Target node ID (from mesh_list_nodes)." },
|
|
1239
1699
|
session_id: { type: "string", description: "Agent session ID on the target node." },
|
|
1240
|
-
message: { type: "string", description: "Natural-language task to send to the agent." }
|
|
1700
|
+
message: { type: "string", description: "Natural-language task to send to the agent." },
|
|
1701
|
+
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." },
|
|
1702
|
+
taskMode: { type: "string", enum: ["code_change", "validation", "live_debug_readonly", "launch_app", "convergence"], description: "CamelCase alias for task_mode." }
|
|
1241
1703
|
},
|
|
1242
1704
|
required: ["node_id", "session_id", "message"]
|
|
1243
1705
|
}
|
|
@@ -1295,6 +1757,21 @@ var MESH_GIT_STATUS_TOOL = {
|
|
|
1295
1757
|
required: ["node_id"]
|
|
1296
1758
|
}
|
|
1297
1759
|
};
|
|
1760
|
+
var MESH_FAST_FORWARD_NODE_TOOL = {
|
|
1761
|
+
name: "mesh_fast_forward_node",
|
|
1762
|
+
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.",
|
|
1763
|
+
inputSchema: {
|
|
1764
|
+
type: "object",
|
|
1765
|
+
properties: {
|
|
1766
|
+
node_id: { type: "string", description: "Target node ID." },
|
|
1767
|
+
branch: { type: "string", description: "Optional guard: require the node's current branch to match this branch before planning/executing." },
|
|
1768
|
+
execute: { type: "boolean", description: "When true, apply the fast-forward if all safety gates pass. Defaults false/dry-run." },
|
|
1769
|
+
dry_run: { type: "boolean", description: "Preview only. Defaults true unless execute=true; dry_run=true overrides execute." },
|
|
1770
|
+
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." }
|
|
1771
|
+
},
|
|
1772
|
+
required: ["node_id"]
|
|
1773
|
+
}
|
|
1774
|
+
};
|
|
1298
1775
|
var MESH_CHECKPOINT_TOOL = {
|
|
1299
1776
|
name: "mesh_checkpoint",
|
|
1300
1777
|
description: "Create a git checkpoint (commit) on a mesh node workspace.",
|
|
@@ -1378,7 +1855,7 @@ var MESH_TASK_HISTORY_TOOL = {
|
|
|
1378
1855
|
type: "object",
|
|
1379
1856
|
properties: {
|
|
1380
1857
|
tail: { type: "number", description: "Number of recent entries to return (default: 20)." },
|
|
1381
|
-
kind: { type: "string", description: "Filter by entry kind: task_dispatched, task_completed, task_failed, task_stalled, session_launched, checkpoint_created, node_cloned, node_removed." }
|
|
1858
|
+
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." }
|
|
1382
1859
|
}
|
|
1383
1860
|
}
|
|
1384
1861
|
};
|
|
@@ -1398,7 +1875,7 @@ var MESH_RECONCILE_LEDGER_TOOL = {
|
|
|
1398
1875
|
};
|
|
1399
1876
|
var MESH_REFINE_NODE_TOOL = {
|
|
1400
1877
|
name: "mesh_refine_node",
|
|
1401
|
-
description: "The Refinery:
|
|
1878
|
+
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.",
|
|
1402
1879
|
inputSchema: {
|
|
1403
1880
|
type: "object",
|
|
1404
1881
|
properties: {
|
|
@@ -1407,6 +1884,43 @@ var MESH_REFINE_NODE_TOOL = {
|
|
|
1407
1884
|
required: ["node_id"]
|
|
1408
1885
|
}
|
|
1409
1886
|
};
|
|
1887
|
+
var MESH_REFINE_CONFIG_SCHEMA_TOOL = {
|
|
1888
|
+
name: "mesh_refine_config_schema",
|
|
1889
|
+
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.",
|
|
1890
|
+
inputSchema: { type: "object", properties: {} }
|
|
1891
|
+
};
|
|
1892
|
+
var MESH_VALIDATE_REFINE_CONFIG_TOOL = {
|
|
1893
|
+
name: "mesh_validate_refine_config",
|
|
1894
|
+
description: "Validate the repo mesh/refine config for a node/workspace without running validation commands or merging.",
|
|
1895
|
+
inputSchema: {
|
|
1896
|
+
type: "object",
|
|
1897
|
+
properties: {
|
|
1898
|
+
node_id: { type: "string", description: "Optional node/workspace whose refine config should be loaded. Defaults to the first mesh node." },
|
|
1899
|
+
config: { type: "object", description: "Optional inline config object to validate instead of loading from the repo." }
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
};
|
|
1903
|
+
var MESH_SUGGEST_REFINE_CONFIG_TOOL = {
|
|
1904
|
+
name: "mesh_suggest_refine_config",
|
|
1905
|
+
description: "Suggest a repo mesh/refine config scaffold from project context/package scripts. Suggestions are never executed until saved as explicit refine config.",
|
|
1906
|
+
inputSchema: {
|
|
1907
|
+
type: "object",
|
|
1908
|
+
properties: {
|
|
1909
|
+
node_id: { type: "string", description: "Optional node/workspace used for suggestions. Defaults to the first mesh node." }
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
};
|
|
1913
|
+
var MESH_REFINE_PLAN_TOOL = {
|
|
1914
|
+
name: "mesh_refine_plan",
|
|
1915
|
+
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.",
|
|
1916
|
+
inputSchema: {
|
|
1917
|
+
type: "object",
|
|
1918
|
+
properties: {
|
|
1919
|
+
node_id: { type: "string", description: "Node ID of the worktree node to plan." }
|
|
1920
|
+
},
|
|
1921
|
+
required: ["node_id"]
|
|
1922
|
+
}
|
|
1923
|
+
};
|
|
1410
1924
|
var ALL_MESH_TOOLS = [
|
|
1411
1925
|
MESH_STATUS_TOOL,
|
|
1412
1926
|
MESH_LIST_NODES_TOOL,
|
|
@@ -1419,16 +1933,21 @@ var ALL_MESH_TOOLS = [
|
|
|
1419
1933
|
MESH_READ_DEBUG_TOOL,
|
|
1420
1934
|
MESH_LAUNCH_SESSION_TOOL,
|
|
1421
1935
|
MESH_GIT_STATUS_TOOL,
|
|
1936
|
+
MESH_FAST_FORWARD_NODE_TOOL,
|
|
1422
1937
|
MESH_CHECKPOINT_TOOL,
|
|
1423
1938
|
MESH_APPROVE_TOOL,
|
|
1424
1939
|
MESH_CLONE_NODE_TOOL,
|
|
1425
1940
|
MESH_REMOVE_NODE_TOOL,
|
|
1426
1941
|
MESH_REFINE_NODE_TOOL,
|
|
1942
|
+
MESH_REFINE_CONFIG_SCHEMA_TOOL,
|
|
1943
|
+
MESH_VALIDATE_REFINE_CONFIG_TOOL,
|
|
1944
|
+
MESH_SUGGEST_REFINE_CONFIG_TOOL,
|
|
1945
|
+
MESH_REFINE_PLAN_TOOL,
|
|
1427
1946
|
MESH_CLEANUP_SESSIONS_TOOL,
|
|
1428
1947
|
MESH_TASK_HISTORY_TOOL,
|
|
1429
1948
|
MESH_RECONCILE_LEDGER_TOOL
|
|
1430
1949
|
];
|
|
1431
|
-
async function meshStatus(ctx) {
|
|
1950
|
+
async function meshStatus(ctx, args = {}) {
|
|
1432
1951
|
await refreshMeshFromDaemon(ctx);
|
|
1433
1952
|
const { mesh, transport } = ctx;
|
|
1434
1953
|
const results = [];
|
|
@@ -1437,6 +1956,9 @@ async function meshStatus(ctx) {
|
|
|
1437
1956
|
const entry = {
|
|
1438
1957
|
nodeId: node.id,
|
|
1439
1958
|
workspace: node.workspace,
|
|
1959
|
+
machine: buildNodeMachineIdentity(ctx, node),
|
|
1960
|
+
daemonId: readNodeDaemonId(node),
|
|
1961
|
+
machineId: readNodeMachineId(node),
|
|
1440
1962
|
...getNodeLaunchReadiness(node)
|
|
1441
1963
|
};
|
|
1442
1964
|
try {
|
|
@@ -1446,6 +1968,7 @@ async function meshStatus(ctx) {
|
|
|
1446
1968
|
const uncommittedChanges = countUncommittedChanges(status);
|
|
1447
1969
|
const dirty = isGitStatusDirty(status);
|
|
1448
1970
|
entry.health = status?.isGitRepo ? dirty ? "dirty" : "online" : "degraded";
|
|
1971
|
+
assignFullGitSnapshot(entry, status);
|
|
1449
1972
|
entry.branch = status?.branch;
|
|
1450
1973
|
entry.isDirty = dirty;
|
|
1451
1974
|
entry.uncommittedChanges = uncommittedChanges;
|
|
@@ -1467,6 +1990,7 @@ async function meshStatus(ctx) {
|
|
|
1467
1990
|
const uncommittedChanges = countUncommittedChanges(status);
|
|
1468
1991
|
const dirty = isGitStatusDirty(status);
|
|
1469
1992
|
entry.health = status?.isGitRepo ? dirty ? "dirty" : "online" : "degraded";
|
|
1993
|
+
assignFullGitSnapshot(entry, status);
|
|
1470
1994
|
entry.branch = status?.branch;
|
|
1471
1995
|
entry.isDirty = dirty;
|
|
1472
1996
|
entry.uncommittedChanges = uncommittedChanges;
|
|
@@ -1542,15 +2066,43 @@ async function meshStatus(ctx) {
|
|
|
1542
2066
|
}
|
|
1543
2067
|
const relatedRepos = await collectRelatedRepoStatuses(ctx, node);
|
|
1544
2068
|
if (relatedRepos.length) entry.relatedRepos = relatedRepos;
|
|
2069
|
+
const liveSessions = await collectLiveStatusSessions(ctx, node);
|
|
2070
|
+
if (liveSessions.length > 0) {
|
|
2071
|
+
entry.sessions = liveSessions;
|
|
2072
|
+
}
|
|
1545
2073
|
results.push(entry);
|
|
1546
2074
|
}
|
|
2075
|
+
const ledgerEntries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail: 500 });
|
|
2076
|
+
const activeWorkEvidence = (0, import_daemon_core.buildMeshActiveWork)({
|
|
2077
|
+
meshId: mesh.id,
|
|
2078
|
+
queue: (0, import_daemon_core.getQueue)(mesh.id),
|
|
2079
|
+
ledgerEntries,
|
|
2080
|
+
nodes: results
|
|
2081
|
+
});
|
|
2082
|
+
const pollingGuidance = buildActiveWorkPollingGuidance(activeWorkEvidence.summary);
|
|
2083
|
+
const staleDirectWorkSummary = (0, import_daemon_core.buildCompactStaleDirectWorkSummary)(activeWorkEvidence.staleDirectWork, {
|
|
2084
|
+
note: activeWorkEvidence.staleDirectWorkNote,
|
|
2085
|
+
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."
|
|
2086
|
+
});
|
|
1547
2087
|
const response = {
|
|
1548
2088
|
meshId: mesh.id,
|
|
1549
2089
|
meshName: mesh.name,
|
|
1550
2090
|
repoIdentity: mesh.repoIdentity,
|
|
1551
2091
|
policy: mesh.policy,
|
|
1552
2092
|
refreshedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2093
|
+
sourceOfTruth: {
|
|
2094
|
+
membership: "coordinator_daemon_live_mesh",
|
|
2095
|
+
currentStatus: "live_git_and_session_probes",
|
|
2096
|
+
activeWork: "mesh_queue_file_and_local_ledger",
|
|
2097
|
+
historicalEvidenceOnly: ["recoveryHints", "ledgerSummary"]
|
|
2098
|
+
},
|
|
1553
2099
|
nodes: results,
|
|
2100
|
+
activeWork: activeWorkEvidence.activeWork,
|
|
2101
|
+
staleDirectWorkSummary,
|
|
2102
|
+
...args.includeStaleDirectWorkDetails === true ? { staleDirectWork: activeWorkEvidence.staleDirectWork } : {},
|
|
2103
|
+
terminalDirectWork: activeWorkEvidence.terminalDirectWork,
|
|
2104
|
+
activeWorkSummary: activeWorkEvidence.summary,
|
|
2105
|
+
...pollingGuidance ? { pollingGuidance } : {},
|
|
1554
2106
|
branchConvergenceSummary: summarizeBranchConvergence(results)
|
|
1555
2107
|
};
|
|
1556
2108
|
try {
|
|
@@ -1559,6 +2111,14 @@ async function meshStatus(ctx) {
|
|
|
1559
2111
|
}
|
|
1560
2112
|
try {
|
|
1561
2113
|
const pendingEvents = await drainCoordinatorPendingEvents(ctx);
|
|
2114
|
+
const asyncRefineJobs = (0, import_daemon_core.buildMeshAsyncRefineJobs)({
|
|
2115
|
+
meshId: mesh.id,
|
|
2116
|
+
ledgerEntries,
|
|
2117
|
+
pendingEvents
|
|
2118
|
+
});
|
|
2119
|
+
if (asyncRefineJobs.length > 0) {
|
|
2120
|
+
response.asyncRefineJobs = asyncRefineJobs;
|
|
2121
|
+
}
|
|
1562
2122
|
if (pendingEvents.length > 0) {
|
|
1563
2123
|
response.pendingCoordinatorEvents = pendingEvents;
|
|
1564
2124
|
}
|
|
@@ -1568,12 +2128,17 @@ async function meshStatus(ctx) {
|
|
|
1568
2128
|
}
|
|
1569
2129
|
async function meshTaskHistory(ctx, args) {
|
|
1570
2130
|
const { mesh } = ctx;
|
|
1571
|
-
await drainCoordinatorPendingEvents(ctx);
|
|
2131
|
+
const pendingEvents = await drainCoordinatorPendingEvents(ctx);
|
|
1572
2132
|
const tail = typeof args.tail === "number" && args.tail > 0 ? args.tail : 20;
|
|
1573
2133
|
const kind = typeof args.kind === "string" && args.kind.trim() ? [args.kind.trim()] : void 0;
|
|
1574
2134
|
const entries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail, kind });
|
|
1575
2135
|
const summary = (0, import_daemon_core.getLedgerSummary)(mesh.id);
|
|
1576
|
-
return JSON.stringify({
|
|
2136
|
+
return JSON.stringify({
|
|
2137
|
+
meshId: mesh.id,
|
|
2138
|
+
entries,
|
|
2139
|
+
summary,
|
|
2140
|
+
...pendingEvents.length > 0 ? { pendingCoordinatorEvents: pendingEvents } : {}
|
|
2141
|
+
}, null, 2);
|
|
1577
2142
|
}
|
|
1578
2143
|
async function meshReconcileLedger(ctx, args) {
|
|
1579
2144
|
await refreshMeshFromDaemon(ctx);
|
|
@@ -1663,6 +2228,9 @@ async function meshListNodes(ctx) {
|
|
|
1663
2228
|
nodeId: n.id,
|
|
1664
2229
|
workspace: n.workspace,
|
|
1665
2230
|
repoRoot: n.repoRoot,
|
|
2231
|
+
daemonId: readNodeDaemonId(n),
|
|
2232
|
+
machineId: readNodeMachineId(n),
|
|
2233
|
+
machine: buildNodeMachineIdentity(ctx, n),
|
|
1666
2234
|
isLocalWorktree: n.isLocalWorktree,
|
|
1667
2235
|
policy: n.policy,
|
|
1668
2236
|
relatedRepos: readRelatedRepos(n),
|
|
@@ -1672,12 +2240,13 @@ async function meshListNodes(ctx) {
|
|
|
1672
2240
|
}, null, 2);
|
|
1673
2241
|
}
|
|
1674
2242
|
async function meshEnqueueTask(ctx, args) {
|
|
2243
|
+
const taskMode = readString(args.task_mode) || readString(args.taskMode);
|
|
1675
2244
|
try {
|
|
1676
|
-
const task = (0, import_daemon_core.enqueueTask)(ctx.mesh.id, args.message);
|
|
2245
|
+
const task = (0, import_daemon_core.enqueueTask)(ctx.mesh.id, args.message, { taskMode });
|
|
1677
2246
|
if (isLocalTransport(ctx.transport) && !(ctx.transport instanceof IpcTransport)) {
|
|
1678
2247
|
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
1679
2248
|
});
|
|
1680
|
-
return JSON.stringify({ success: true, taskId: task.id, status: task.status });
|
|
2249
|
+
return JSON.stringify({ success: true, source: "queue", taskId: task.id, status: task.status, taskMode: task.taskMode });
|
|
1681
2250
|
}
|
|
1682
2251
|
if (ctx.transport instanceof IpcTransport) {
|
|
1683
2252
|
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
@@ -1690,11 +2259,24 @@ async function meshEnqueueTask(ctx, args) {
|
|
|
1690
2259
|
ipcDispatchToRemoteAgent(ctx, node, { message: args.message }).then((result) => {
|
|
1691
2260
|
if (result.success) {
|
|
1692
2261
|
try {
|
|
2262
|
+
const providerType = result.providerType;
|
|
2263
|
+
const descriptor = summarizeTaskMessage(args.message);
|
|
1693
2264
|
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
1694
2265
|
kind: "task_dispatched",
|
|
1695
2266
|
nodeId: node.id,
|
|
1696
2267
|
sessionId: result.sessionId,
|
|
1697
|
-
|
|
2268
|
+
providerType,
|
|
2269
|
+
payload: {
|
|
2270
|
+
source: "queue",
|
|
2271
|
+
via: "p2p_direct",
|
|
2272
|
+
taskId: task.id,
|
|
2273
|
+
message: args.message,
|
|
2274
|
+
taskTitle: descriptor.taskTitle,
|
|
2275
|
+
taskSummary: descriptor.taskSummary,
|
|
2276
|
+
...task.taskMode ? { taskMode: task.taskMode } : {},
|
|
2277
|
+
...providerType ? { providerType } : {},
|
|
2278
|
+
targetSessionId: result.sessionId
|
|
2279
|
+
}
|
|
1698
2280
|
});
|
|
1699
2281
|
} catch {
|
|
1700
2282
|
}
|
|
@@ -1705,24 +2287,37 @@ async function meshEnqueueTask(ctx, args) {
|
|
|
1705
2287
|
}
|
|
1706
2288
|
Promise.all(dispatchPromises).catch(() => {
|
|
1707
2289
|
});
|
|
1708
|
-
return JSON.stringify({ success: true, taskId: task.id, status: task.status });
|
|
2290
|
+
return JSON.stringify({ success: true, source: "queue", taskId: task.id, status: task.status, taskMode: task.taskMode });
|
|
1709
2291
|
}
|
|
1710
|
-
return JSON.stringify({ success: true, taskId: task.id, status: task.status });
|
|
2292
|
+
return JSON.stringify({ success: true, source: "queue", taskId: task.id, status: task.status, taskMode: task.taskMode });
|
|
1711
2293
|
} catch (e) {
|
|
1712
|
-
|
|
2294
|
+
const message = e?.message || String(e);
|
|
2295
|
+
if (message.includes("live_debug_readonly_guardrail_violation")) {
|
|
2296
|
+
return JSON.stringify({ success: false, code: "live_debug_readonly_guardrail_violation", taskMode, error: message });
|
|
2297
|
+
}
|
|
2298
|
+
return JSON.stringify({ success: false, error: message });
|
|
1713
2299
|
}
|
|
1714
2300
|
}
|
|
1715
2301
|
async function meshViewQueue(ctx, args) {
|
|
1716
2302
|
try {
|
|
2303
|
+
await refreshMeshFromDaemon(ctx);
|
|
1717
2304
|
const statusFilter = sanitizeQueueStatusFilter(args.status);
|
|
1718
2305
|
const view = normalizeQueueViewMode(args.view);
|
|
1719
|
-
const fullQueue = annotateQueueStaleness((0, import_daemon_core.getQueue)(ctx.mesh.id), ctx.mesh);
|
|
2306
|
+
const fullQueue = prioritizeActiveQueueRows(annotateQueueStaleness((0, import_daemon_core.getQueue)(ctx.mesh.id), ctx.mesh));
|
|
1720
2307
|
const queue = filterQueueForView(fullQueue, view, statusFilter);
|
|
1721
2308
|
const summary = buildQueueStatusSummary(fullQueue);
|
|
1722
2309
|
const visibleSummary = buildQueueStatusSummary(queue);
|
|
1723
2310
|
const maintenance = buildQueueMaintenanceReport(fullQueue);
|
|
2311
|
+
const liveNodes = await collectMeshViewQueueNodesWithLiveSessions(ctx);
|
|
2312
|
+
const activeWorkEvidence = (0, import_daemon_core.buildMeshActiveWork)({
|
|
2313
|
+
meshId: ctx.mesh.id,
|
|
2314
|
+
queue: fullQueue,
|
|
2315
|
+
ledgerEntries: (0, import_daemon_core.readLedgerEntries)(ctx.mesh.id, { tail: 500 }),
|
|
2316
|
+
nodes: liveNodes
|
|
2317
|
+
});
|
|
1724
2318
|
const staleAssignedTasks = maintenance.staleAssignedTasks || [];
|
|
1725
2319
|
const requestedHistoricalRows = queue.some((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
|
|
2320
|
+
const pollingGuidance = buildActiveWorkPollingGuidance(activeWorkEvidence.summary);
|
|
1726
2321
|
return JSON.stringify({
|
|
1727
2322
|
success: true,
|
|
1728
2323
|
sourceOfTruth: {
|
|
@@ -1738,6 +2333,10 @@ async function meshViewQueue(ctx, args) {
|
|
|
1738
2333
|
},
|
|
1739
2334
|
queue,
|
|
1740
2335
|
visibleQueue: queue,
|
|
2336
|
+
activeWork: activeWorkEvidence.activeWork,
|
|
2337
|
+
staleDirectWork: activeWorkEvidence.staleDirectWork,
|
|
2338
|
+
activeWorkSummary: activeWorkEvidence.summary,
|
|
2339
|
+
...pollingGuidance ? { pollingGuidance } : {},
|
|
1741
2340
|
visibleSummary,
|
|
1742
2341
|
summary,
|
|
1743
2342
|
activeCounts: summary.activeCounts,
|
|
@@ -1771,6 +2370,10 @@ async function meshQueueCancel(ctx, args) {
|
|
|
1771
2370
|
if (!taskId) return JSON.stringify({ success: false, error: "task_id required" });
|
|
1772
2371
|
const task = (0, import_daemon_core.cancelTask)(ctx.mesh.id, taskId, { reason: args.reason });
|
|
1773
2372
|
if (!task) return JSON.stringify({ success: false, error: `Queue task '${taskId}' not found` });
|
|
2373
|
+
if (isLocalTransport(ctx.transport)) {
|
|
2374
|
+
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
2375
|
+
});
|
|
2376
|
+
}
|
|
1774
2377
|
return JSON.stringify({ success: true, task }, null, 2);
|
|
1775
2378
|
} catch (e) {
|
|
1776
2379
|
return JSON.stringify({ success: false, error: e.message });
|
|
@@ -1801,10 +2404,60 @@ async function meshQueueRequeue(ctx, args) {
|
|
|
1801
2404
|
}
|
|
1802
2405
|
}
|
|
1803
2406
|
async function meshSendTask(ctx, args) {
|
|
2407
|
+
const requestedTaskMode = readString(args.task_mode) || readString(args.taskMode);
|
|
2408
|
+
const modeValidation = (0, import_daemon_core.validateMeshTaskModeRequest)(requestedTaskMode, args.message);
|
|
2409
|
+
if (!modeValidation.valid) {
|
|
2410
|
+
return JSON.stringify({
|
|
2411
|
+
success: false,
|
|
2412
|
+
code: "live_debug_readonly_guardrail_violation",
|
|
2413
|
+
taskMode: modeValidation.taskMode || requestedTaskMode,
|
|
2414
|
+
violations: modeValidation.violations,
|
|
2415
|
+
allowedOperations: modeValidation.allowedOperations,
|
|
2416
|
+
error: `live_debug_readonly_guardrail_violation: forbidden operations (${modeValidation.violations.join(", ")})`
|
|
2417
|
+
});
|
|
2418
|
+
}
|
|
2419
|
+
const taskMode = modeValidation.taskMode;
|
|
1804
2420
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
1805
2421
|
if (node.policy?.readOnly) {
|
|
1806
2422
|
return JSON.stringify({ error: `Node '${args.node_id}' is read-only` });
|
|
1807
2423
|
}
|
|
2424
|
+
let explicitTargetSession;
|
|
2425
|
+
if (args.session_id && isWorkerTaskMode(taskMode) && (ctx.transport instanceof IpcTransport || isLocalTransport(ctx.transport))) {
|
|
2426
|
+
try {
|
|
2427
|
+
const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
|
|
2428
|
+
const sessions = extractStatusMetadataSessions(statusResult);
|
|
2429
|
+
explicitTargetSession = sessions.find((session) => readSessionRecordId(session) === args.session_id);
|
|
2430
|
+
if (explicitTargetSession && isMeshCoordinatorSessionRecord(explicitTargetSession)) {
|
|
2431
|
+
return JSON.stringify({
|
|
2432
|
+
success: false,
|
|
2433
|
+
recoverable: true,
|
|
2434
|
+
code: "mesh_target_session_is_coordinator",
|
|
2435
|
+
reason: "mesh_target_session_is_coordinator",
|
|
2436
|
+
nodeId: args.node_id,
|
|
2437
|
+
sessionId: args.session_id,
|
|
2438
|
+
taskMode: taskMode || "unspecified",
|
|
2439
|
+
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.`,
|
|
2440
|
+
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.`
|
|
2441
|
+
});
|
|
2442
|
+
}
|
|
2443
|
+
if (explicitTargetSession && isUnmanagedSessionRecord(explicitTargetSession)) {
|
|
2444
|
+
return JSON.stringify({
|
|
2445
|
+
success: false,
|
|
2446
|
+
recoverable: true,
|
|
2447
|
+
code: "mesh_target_session_unmanaged",
|
|
2448
|
+
reason: "mesh_target_session_unmanaged",
|
|
2449
|
+
nodeId: args.node_id,
|
|
2450
|
+
sessionId: args.session_id,
|
|
2451
|
+
taskMode: taskMode || "unspecified",
|
|
2452
|
+
unsafeTranscriptAlias: true,
|
|
2453
|
+
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.`,
|
|
2454
|
+
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.`
|
|
2455
|
+
});
|
|
2456
|
+
}
|
|
2457
|
+
} catch {
|
|
2458
|
+
explicitTargetSession = void 0;
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
1808
2461
|
const duplicate = hasRecentDuplicateDispatch(ctx, args);
|
|
1809
2462
|
if (duplicate.duplicate) {
|
|
1810
2463
|
return JSON.stringify({
|
|
@@ -1828,75 +2481,172 @@ async function meshSendTask(ctx, args) {
|
|
|
1828
2481
|
const res = await ctx.transport.meshEnqueueTask(node.daemonId, {
|
|
1829
2482
|
meshId: ctx.mesh.id,
|
|
1830
2483
|
message: args.message,
|
|
1831
|
-
targetNodeId: args.node_id
|
|
2484
|
+
targetNodeId: args.node_id,
|
|
2485
|
+
...taskMode ? { taskMode } : {}
|
|
1832
2486
|
});
|
|
1833
2487
|
return JSON.stringify(res);
|
|
1834
2488
|
}
|
|
1835
2489
|
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
1836
2490
|
if (ctx.transport instanceof IpcTransport && node.daemonId && !isLocalNode) {
|
|
1837
2491
|
const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id || ""));
|
|
2492
|
+
const taskId = (0, import_node_crypto.randomUUID)();
|
|
1838
2493
|
const result2 = await ipcDispatchToRemoteAgent(ctx, node, {
|
|
1839
2494
|
session_id: args.session_id,
|
|
1840
2495
|
message: args.message,
|
|
1841
|
-
providerType: cached?.providerType
|
|
2496
|
+
providerType: cached?.providerType,
|
|
2497
|
+
verifiedSession: explicitTargetSession
|
|
1842
2498
|
});
|
|
1843
2499
|
if (result2.success) {
|
|
1844
2500
|
const dispatchedSessionId = args.session_id || result2.sessionId;
|
|
1845
2501
|
try {
|
|
2502
|
+
const providerType = result2.providerType || cached?.providerType;
|
|
1846
2503
|
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
1847
2504
|
kind: "task_dispatched",
|
|
1848
2505
|
nodeId: args.node_id,
|
|
1849
2506
|
sessionId: dispatchedSessionId,
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
2507
|
+
providerType,
|
|
2508
|
+
payload: buildDirectTaskPayload(args.message, "p2p_direct", {
|
|
2509
|
+
taskId,
|
|
2510
|
+
taskMode,
|
|
2511
|
+
providerType,
|
|
2512
|
+
targetSessionId: dispatchedSessionId
|
|
2513
|
+
})
|
|
1855
2514
|
});
|
|
1856
2515
|
} catch {
|
|
1857
2516
|
}
|
|
1858
2517
|
}
|
|
1859
|
-
return JSON.stringify({
|
|
2518
|
+
return JSON.stringify({
|
|
2519
|
+
...result2,
|
|
2520
|
+
nodeId: args.node_id,
|
|
2521
|
+
sessionId: result2.success ? args.session_id || result2.sessionId : args.session_id,
|
|
2522
|
+
...result2.success ? { source: "direct", taskId } : {},
|
|
2523
|
+
taskMode,
|
|
2524
|
+
...result2.success && result2.providerType ? { providerType: result2.providerType } : {},
|
|
2525
|
+
dispatched: result2.success === true
|
|
2526
|
+
});
|
|
1860
2527
|
}
|
|
1861
2528
|
if (args.session_id && isLocalTransport(ctx.transport)) {
|
|
1862
2529
|
const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id));
|
|
2530
|
+
let resolvedProviderType = cached?.providerType || "";
|
|
2531
|
+
if (!resolvedProviderType) {
|
|
2532
|
+
let explicitSession = explicitTargetSession;
|
|
2533
|
+
if (!explicitSession) {
|
|
2534
|
+
const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
|
|
2535
|
+
const sessions = extractStatusMetadataSessions(statusResult);
|
|
2536
|
+
explicitSession = sessions.find((session) => readSessionRecordId(session) === args.session_id);
|
|
2537
|
+
}
|
|
2538
|
+
if (!explicitSession) {
|
|
2539
|
+
return JSON.stringify({
|
|
2540
|
+
success: false,
|
|
2541
|
+
recoverable: true,
|
|
2542
|
+
code: "mesh_target_session_not_found",
|
|
2543
|
+
reason: "mesh_target_session_not_found",
|
|
2544
|
+
transport: "local_ipc",
|
|
2545
|
+
retryRecommended: true,
|
|
2546
|
+
nodeId: args.node_id,
|
|
2547
|
+
sessionId: args.session_id,
|
|
2548
|
+
error: `Local session '${args.session_id}' is not present in live status for node '${args.node_id}'.`,
|
|
2549
|
+
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.`
|
|
2550
|
+
});
|
|
2551
|
+
}
|
|
2552
|
+
if (isMeshCoordinatorSessionRecord(explicitSession)) {
|
|
2553
|
+
return JSON.stringify({
|
|
2554
|
+
success: false,
|
|
2555
|
+
recoverable: true,
|
|
2556
|
+
code: "mesh_target_session_is_coordinator",
|
|
2557
|
+
reason: "mesh_target_session_is_coordinator",
|
|
2558
|
+
nodeId: args.node_id,
|
|
2559
|
+
sessionId: args.session_id,
|
|
2560
|
+
taskMode: taskMode || "unspecified",
|
|
2561
|
+
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.`,
|
|
2562
|
+
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.`
|
|
2563
|
+
});
|
|
2564
|
+
}
|
|
2565
|
+
if (isUnmanagedSessionRecord(explicitSession)) {
|
|
2566
|
+
return JSON.stringify({
|
|
2567
|
+
success: false,
|
|
2568
|
+
recoverable: true,
|
|
2569
|
+
code: "mesh_target_session_unmanaged",
|
|
2570
|
+
reason: "mesh_target_session_unmanaged",
|
|
2571
|
+
nodeId: args.node_id,
|
|
2572
|
+
sessionId: args.session_id,
|
|
2573
|
+
taskMode: taskMode || "unspecified",
|
|
2574
|
+
unsafeTranscriptAlias: true,
|
|
2575
|
+
unsafeDelegateTarget: true,
|
|
2576
|
+
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.`,
|
|
2577
|
+
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.`
|
|
2578
|
+
});
|
|
2579
|
+
}
|
|
2580
|
+
resolvedProviderType = resolveSessionProviderType(explicitSession);
|
|
2581
|
+
if (resolvedProviderType) {
|
|
2582
|
+
meshSessionProviderMetadata.set(meshSessionCacheKey(args.node_id, args.session_id), {
|
|
2583
|
+
providerType: resolvedProviderType,
|
|
2584
|
+
providerSessionId: readString(explicitSession?.providerSessionId) || void 0
|
|
2585
|
+
});
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
if (!resolvedProviderType) {
|
|
2589
|
+
return JSON.stringify({
|
|
2590
|
+
success: false,
|
|
2591
|
+
recoverable: true,
|
|
2592
|
+
code: "mesh_target_session_provider_unknown",
|
|
2593
|
+
reason: "mesh_target_session_provider_unknown",
|
|
2594
|
+
transport: "local_ipc",
|
|
2595
|
+
retryRecommended: false,
|
|
2596
|
+
nodeId: args.node_id,
|
|
2597
|
+
sessionId: args.session_id,
|
|
2598
|
+
error: `Local session '${args.session_id}' is live but does not expose providerType/cliType, so agent_command cannot be routed safely.`,
|
|
2599
|
+
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.`
|
|
2600
|
+
});
|
|
2601
|
+
}
|
|
1863
2602
|
const dispatchResult = await commandForNode(ctx, node, "agent_command", {
|
|
1864
2603
|
targetSessionId: args.session_id,
|
|
1865
|
-
|
|
2604
|
+
agentType: resolvedProviderType,
|
|
2605
|
+
cliType: resolvedProviderType,
|
|
2606
|
+
providerType: resolvedProviderType,
|
|
1866
2607
|
action: "send_chat",
|
|
1867
2608
|
message: args.message
|
|
1868
2609
|
});
|
|
1869
2610
|
const dispatchPayload = unwrapCommandPayload(dispatchResult);
|
|
1870
2611
|
if (dispatchPayload?.success === false || dispatchResult?.success === false) {
|
|
2612
|
+
const source = dispatchPayload?.success === false ? dispatchPayload : dispatchResult;
|
|
1871
2613
|
return JSON.stringify({
|
|
2614
|
+
...source && typeof source === "object" ? source : {},
|
|
1872
2615
|
success: false,
|
|
1873
2616
|
nodeId: args.node_id,
|
|
1874
2617
|
sessionId: args.session_id,
|
|
1875
2618
|
error: dispatchPayload?.error || dispatchResult?.error || "agent_command rejected the task"
|
|
1876
2619
|
});
|
|
1877
2620
|
}
|
|
2621
|
+
const taskId = (0, import_node_crypto.randomUUID)();
|
|
1878
2622
|
try {
|
|
1879
2623
|
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
1880
2624
|
kind: "task_dispatched",
|
|
1881
2625
|
nodeId: args.node_id,
|
|
1882
2626
|
sessionId: args.session_id,
|
|
1883
|
-
providerType:
|
|
1884
|
-
payload:
|
|
2627
|
+
providerType: resolvedProviderType,
|
|
2628
|
+
payload: buildDirectTaskPayload(args.message, "local_direct", {
|
|
2629
|
+
taskId,
|
|
2630
|
+
taskMode,
|
|
2631
|
+
providerType: resolvedProviderType,
|
|
2632
|
+
targetSessionId: args.session_id
|
|
2633
|
+
})
|
|
1885
2634
|
});
|
|
1886
2635
|
} catch {
|
|
1887
2636
|
}
|
|
1888
|
-
return JSON.stringify({ success: true, dispatched: true, nodeId: args.node_id, sessionId: args.session_id });
|
|
2637
|
+
return JSON.stringify({ success: true, dispatched: true, source: "direct", taskId, taskMode, providerType: resolvedProviderType, nodeId: args.node_id, sessionId: args.session_id });
|
|
1889
2638
|
}
|
|
1890
2639
|
const task = (0, import_daemon_core.enqueueTask)(ctx.mesh.id, args.message, {
|
|
1891
2640
|
targetNodeId: args.node_id,
|
|
1892
|
-
targetSessionId: args.session_id
|
|
2641
|
+
targetSessionId: args.session_id,
|
|
2642
|
+
taskMode
|
|
1893
2643
|
});
|
|
1894
2644
|
if (isLocalTransport(ctx.transport) || ctx.transport instanceof IpcTransport) {
|
|
1895
2645
|
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
1896
2646
|
});
|
|
1897
2647
|
}
|
|
1898
|
-
const pendingEvents = isLocalTransport(ctx.transport) ? (0, import_daemon_core.drainPendingMeshCoordinatorEvents)() : [];
|
|
1899
|
-
const result = { success: true, nodeId: args.node_id, taskId: task.id, status: task.status };
|
|
2648
|
+
const pendingEvents = isLocalTransport(ctx.transport) ? (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id) : [];
|
|
2649
|
+
const result = { success: true, source: "queue", nodeId: args.node_id, taskId: task.id, status: task.status, taskMode: task.taskMode };
|
|
1900
2650
|
if (pendingEvents.length > 0) {
|
|
1901
2651
|
result.pendingCoordinatorEvents = pendingEvents;
|
|
1902
2652
|
}
|
|
@@ -1920,7 +2670,7 @@ async function meshReadChat(ctx, args) {
|
|
|
1920
2670
|
await drainCoordinatorPendingEvents(ctx, { nodeIds: [args.node_id] });
|
|
1921
2671
|
}
|
|
1922
2672
|
if (isLocalTransport(ctx.transport)) {
|
|
1923
|
-
const cached =
|
|
2673
|
+
const cached = resolveMeshSessionProviderMetadata(ctx, args.node_id, args.session_id);
|
|
1924
2674
|
const providerSessionId = typeof args.provider_session_id === "string" && args.provider_session_id.trim() ? args.provider_session_id.trim() : cached?.providerSessionId;
|
|
1925
2675
|
const result = await commandForNode(ctx, node, "read_chat", {
|
|
1926
2676
|
sessionId: args.session_id,
|
|
@@ -1966,7 +2716,7 @@ async function meshReadChat(ctx, args) {
|
|
|
1966
2716
|
async function meshReadDebug(ctx, args) {
|
|
1967
2717
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
1968
2718
|
if (isLocalTransport(ctx.transport)) {
|
|
1969
|
-
const cached =
|
|
2719
|
+
const cached = resolveMeshSessionProviderMetadata(ctx, args.node_id, args.session_id);
|
|
1970
2720
|
const providerSessionId = typeof args.provider_session_id === "string" && args.provider_session_id.trim() ? args.provider_session_id.trim() : cached?.providerSessionId;
|
|
1971
2721
|
const delivery = args.delivery === "inline" ? void 0 : "daemon_file";
|
|
1972
2722
|
const result = await commandForNode(ctx, node, "get_chat_debug_bundle", {
|
|
@@ -1997,6 +2747,8 @@ async function meshReadDebug(ctx, args) {
|
|
|
1997
2747
|
}
|
|
1998
2748
|
async function meshLaunchSession(ctx, args) {
|
|
1999
2749
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2750
|
+
const bootstrapBlock = getWorktreeBootstrapLaunchBlock(node);
|
|
2751
|
+
if (bootstrapBlock) return JSON.stringify(bootstrapBlock, null, 2);
|
|
2000
2752
|
if (isLocalTransport(ctx.transport)) {
|
|
2001
2753
|
let resolvedProviderType = typeof args.type === "string" && args.type.trim() ? args.type : "";
|
|
2002
2754
|
if (!resolvedProviderType) {
|
|
@@ -2021,6 +2773,10 @@ async function meshLaunchSession(ctx, args) {
|
|
|
2021
2773
|
const coordinatorNode = resolveCoordinatorNode(ctx);
|
|
2022
2774
|
const coordinatorDaemonId = coordinatorNode?.daemonId || ctx.localDaemonId;
|
|
2023
2775
|
const spawnedSessionVisibility = readSpawnedSessionVisibility(ctx.mesh.policy);
|
|
2776
|
+
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
2777
|
+
if (node.daemonId && !isLocalNode && !coordinatorDaemonId) {
|
|
2778
|
+
return JSON.stringify(buildMissingCoordinatorDaemonIdFailure(ctx, node, resolvedProviderType), null, 2);
|
|
2779
|
+
}
|
|
2024
2780
|
let result;
|
|
2025
2781
|
try {
|
|
2026
2782
|
result = await commandForNode(ctx, node, "launch_cli", {
|
|
@@ -2061,7 +2817,6 @@ async function meshLaunchSession(ctx, args) {
|
|
|
2061
2817
|
});
|
|
2062
2818
|
} catch {
|
|
2063
2819
|
}
|
|
2064
|
-
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
2065
2820
|
if (ctx.transport instanceof IpcTransport && node.daemonId && !isLocalNode) {
|
|
2066
2821
|
ctx.transport.meshCommand(node.daemonId, "trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
2067
2822
|
});
|
|
@@ -2086,6 +2841,9 @@ async function meshLaunchSession(ctx, args) {
|
|
|
2086
2841
|
const coordinatorNode = resolveCoordinatorNode(ctx);
|
|
2087
2842
|
const coordinatorDaemonId = coordinatorNode?.daemonId || ctx.localDaemonId;
|
|
2088
2843
|
const spawnedSessionVisibility = readSpawnedSessionVisibility(ctx.mesh.policy);
|
|
2844
|
+
if (!coordinatorDaemonId) {
|
|
2845
|
+
return JSON.stringify(buildMissingCoordinatorDaemonIdFailure(ctx, node, resolvedProviderType), null, 2);
|
|
2846
|
+
}
|
|
2089
2847
|
try {
|
|
2090
2848
|
const res = await ctx.transport.launch(node.daemonId, {
|
|
2091
2849
|
type: resolvedProviderType,
|
|
@@ -2166,6 +2924,51 @@ async function meshGitStatus(ctx, args) {
|
|
|
2166
2924
|
}, null, 2);
|
|
2167
2925
|
}
|
|
2168
2926
|
}
|
|
2927
|
+
async function meshFastForwardNode(ctx, args) {
|
|
2928
|
+
await refreshMeshFromDaemon(ctx);
|
|
2929
|
+
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2930
|
+
const submoduleIgnorePaths = node.policy?.submoduleIgnorePaths || [];
|
|
2931
|
+
if (node.policy?.readOnly) {
|
|
2932
|
+
return JSON.stringify({
|
|
2933
|
+
success: false,
|
|
2934
|
+
code: "node_read_only",
|
|
2935
|
+
nodeId: args.node_id,
|
|
2936
|
+
workspace: node.workspace,
|
|
2937
|
+
allowed: false,
|
|
2938
|
+
willRun: false,
|
|
2939
|
+
executed: false,
|
|
2940
|
+
blockingReasons: ["node_read_only"]
|
|
2941
|
+
}, null, 2);
|
|
2942
|
+
}
|
|
2943
|
+
try {
|
|
2944
|
+
const dryRun = args.dry_run === true || args.execute !== true;
|
|
2945
|
+
const result = await commandForNode(ctx, node, "fast_forward_mesh_node", {
|
|
2946
|
+
meshId: ctx.mesh.id,
|
|
2947
|
+
nodeId: node.id,
|
|
2948
|
+
workspace: node.workspace,
|
|
2949
|
+
branch: typeof args.branch === "string" ? args.branch : void 0,
|
|
2950
|
+
execute: args.execute === true && args.dry_run !== true,
|
|
2951
|
+
dryRun,
|
|
2952
|
+
updateSubmodules: args.update_submodules === true,
|
|
2953
|
+
submoduleIgnorePaths: submoduleIgnorePaths.length > 0 ? submoduleIgnorePaths : void 0
|
|
2954
|
+
});
|
|
2955
|
+
return JSON.stringify(unwrapCommandPayload(result), null, 2);
|
|
2956
|
+
} catch (e) {
|
|
2957
|
+
const failure = buildCoordinatorP2pRelayFailure(e, {
|
|
2958
|
+
command: "fast_forward_mesh_node",
|
|
2959
|
+
targetDaemonId: node.daemonId,
|
|
2960
|
+
nodeId: args.node_id
|
|
2961
|
+
});
|
|
2962
|
+
return JSON.stringify({
|
|
2963
|
+
...failure,
|
|
2964
|
+
workspace: node.workspace,
|
|
2965
|
+
allowed: false,
|
|
2966
|
+
willRun: false,
|
|
2967
|
+
executed: false,
|
|
2968
|
+
blockingReasons: [failure.code || "mesh_fast_forward_unavailable"]
|
|
2969
|
+
}, null, 2);
|
|
2970
|
+
}
|
|
2971
|
+
}
|
|
2169
2972
|
async function meshCheckpoint(ctx, args) {
|
|
2170
2973
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2171
2974
|
if (node.policy?.readOnly) {
|
|
@@ -2181,7 +2984,13 @@ async function meshCheckpoint(ctx, args) {
|
|
|
2181
2984
|
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
2182
2985
|
kind: "checkpoint_created",
|
|
2183
2986
|
nodeId: args.node_id,
|
|
2184
|
-
payload: {
|
|
2987
|
+
payload: {
|
|
2988
|
+
message: args.message,
|
|
2989
|
+
commit: result?.checkpoint?.commit,
|
|
2990
|
+
outcome: result?.checkpoint?.status || (result?.checkpoint?.noop ? "skipped" : void 0),
|
|
2991
|
+
noop: result?.checkpoint?.noop === true,
|
|
2992
|
+
reason: result?.checkpoint?.reason
|
|
2993
|
+
}
|
|
2185
2994
|
});
|
|
2186
2995
|
} catch {
|
|
2187
2996
|
}
|
|
@@ -2197,7 +3006,13 @@ async function meshCheckpoint(ctx, args) {
|
|
|
2197
3006
|
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
2198
3007
|
kind: "checkpoint_created",
|
|
2199
3008
|
nodeId: args.node_id,
|
|
2200
|
-
payload: {
|
|
3009
|
+
payload: {
|
|
3010
|
+
message: args.message,
|
|
3011
|
+
commit: res?.checkpoint?.commit,
|
|
3012
|
+
outcome: res?.checkpoint?.status || (res?.checkpoint?.noop ? "skipped" : void 0),
|
|
3013
|
+
noop: res?.checkpoint?.noop === true,
|
|
3014
|
+
reason: res?.checkpoint?.reason
|
|
3015
|
+
}
|
|
2201
3016
|
});
|
|
2202
3017
|
} catch {
|
|
2203
3018
|
}
|
|
@@ -2251,6 +3066,7 @@ async function meshCloneNode(ctx, args) {
|
|
|
2251
3066
|
if (existingIndex >= 0) ctx.mesh.nodes[existingIndex] = clonePayload.node;
|
|
2252
3067
|
else ctx.mesh.nodes.push(clonePayload.node);
|
|
2253
3068
|
ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3069
|
+
await syncCoordinatorDaemonMeshCache(ctx);
|
|
2254
3070
|
}
|
|
2255
3071
|
return JSON.stringify(result, null, 2);
|
|
2256
3072
|
} else if (!isLocalTransport(ctx.transport) && sourceNode.daemonId) {
|
|
@@ -2268,6 +3084,7 @@ async function meshCloneNode(ctx, args) {
|
|
|
2268
3084
|
if (existingIndex >= 0) ctx.mesh.nodes[existingIndex] = clonePayload.node;
|
|
2269
3085
|
else ctx.mesh.nodes.push(clonePayload.node);
|
|
2270
3086
|
ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3087
|
+
await syncCoordinatorDaemonMeshCache(ctx);
|
|
2271
3088
|
}
|
|
2272
3089
|
return JSON.stringify(res, null, 2);
|
|
2273
3090
|
} catch (e) {
|
|
@@ -2363,6 +3180,43 @@ async function meshRemoveNode(ctx, args) {
|
|
|
2363
3180
|
return JSON.stringify({ error: "Cloud mesh remove_node requires node daemonId" });
|
|
2364
3181
|
}
|
|
2365
3182
|
}
|
|
3183
|
+
function resolveRefineConfigNode(ctx, nodeId) {
|
|
3184
|
+
if (nodeId) return findNode(ctx.mesh, nodeId);
|
|
3185
|
+
const node = ctx.mesh.nodes.find((entry) => !!entry.workspace);
|
|
3186
|
+
if (!node) throw new Error("No mesh node with a workspace is available");
|
|
3187
|
+
return node;
|
|
3188
|
+
}
|
|
3189
|
+
async function meshRefineConfigSchema(ctx) {
|
|
3190
|
+
const node = resolveRefineConfigNode(ctx);
|
|
3191
|
+
const result = await commandForNode(ctx, node, "get_mesh_refine_config_schema", {});
|
|
3192
|
+
return JSON.stringify(result, null, 2);
|
|
3193
|
+
}
|
|
3194
|
+
async function meshValidateRefineConfig(ctx, args) {
|
|
3195
|
+
const node = resolveRefineConfigNode(ctx, args.node_id);
|
|
3196
|
+
const result = await commandForNode(ctx, node, "validate_mesh_refine_config", {
|
|
3197
|
+
workspace: node.workspace,
|
|
3198
|
+
inlineMesh: ctx.mesh,
|
|
3199
|
+
...args.config ? { config: args.config } : {}
|
|
3200
|
+
});
|
|
3201
|
+
return JSON.stringify(result, null, 2);
|
|
3202
|
+
}
|
|
3203
|
+
async function meshSuggestRefineConfig(ctx, args) {
|
|
3204
|
+
const node = resolveRefineConfigNode(ctx, args.node_id);
|
|
3205
|
+
const result = await commandForNode(ctx, node, "suggest_mesh_refine_config", {
|
|
3206
|
+
workspace: node.workspace,
|
|
3207
|
+
inlineMesh: ctx.mesh
|
|
3208
|
+
});
|
|
3209
|
+
return JSON.stringify(result, null, 2);
|
|
3210
|
+
}
|
|
3211
|
+
async function meshRefinePlan(ctx, args) {
|
|
3212
|
+
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
3213
|
+
const result = await commandForNode(ctx, node, "plan_mesh_refine_node", {
|
|
3214
|
+
meshId: ctx.mesh.id,
|
|
3215
|
+
nodeId: args.node_id,
|
|
3216
|
+
inlineMesh: ctx.mesh
|
|
3217
|
+
});
|
|
3218
|
+
return JSON.stringify(result, null, 2);
|
|
3219
|
+
}
|
|
2366
3220
|
async function meshRefineNode(ctx, args) {
|
|
2367
3221
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2368
3222
|
if (isLocalTransport(ctx.transport)) {
|
|
@@ -2371,7 +3225,7 @@ async function meshRefineNode(ctx, args) {
|
|
|
2371
3225
|
nodeId: args.node_id,
|
|
2372
3226
|
inlineMesh: ctx.mesh
|
|
2373
3227
|
});
|
|
2374
|
-
if (result?.success && result.removeResult?.removed !== false) {
|
|
3228
|
+
if (result?.success && result.async !== true && result.removeResult?.removed !== false) {
|
|
2375
3229
|
const idx = ctx.mesh.nodes.findIndex((n) => n.id === args.node_id);
|
|
2376
3230
|
if (idx >= 0) {
|
|
2377
3231
|
ctx.mesh.nodes.splice(idx, 1);
|
|
@@ -2386,7 +3240,7 @@ async function meshRefineNode(ctx, args) {
|
|
|
2386
3240
|
nodeId: args.node_id,
|
|
2387
3241
|
inlineMesh: ctx.mesh
|
|
2388
3242
|
});
|
|
2389
|
-
if (res?.success && res.removeResult?.removed !== false) {
|
|
3243
|
+
if (res?.success && res.async !== true && res.removeResult?.removed !== false) {
|
|
2390
3244
|
const idx = ctx.mesh.nodes.findIndex((n) => n.id === args.node_id);
|
|
2391
3245
|
if (idx >= 0) {
|
|
2392
3246
|
ctx.mesh.nodes.splice(idx, 1);
|
|
@@ -2423,13 +3277,13 @@ var STANDARD_TOOLS = [
|
|
|
2423
3277
|
function buildMcpHelpText() {
|
|
2424
3278
|
const meshTools = ALL_MESH_TOOLS.map((tool) => tool.name);
|
|
2425
3279
|
return `
|
|
2426
|
-
|
|
3280
|
+
ADHDev MCP Server
|
|
2427
3281
|
|
|
2428
3282
|
Usage:
|
|
2429
|
-
adhdev
|
|
2430
|
-
adhdev
|
|
2431
|
-
adhdev
|
|
2432
|
-
adhdev-mcp --
|
|
3283
|
+
adhdev mcp Local mode (requires standalone daemon)
|
|
3284
|
+
adhdev mcp --api-key <key> Cloud mode (ADHDev cloud API)
|
|
3285
|
+
adhdev mcp --mode ipc --repo-mesh <mesh_id> Cloud daemon IPC mesh mode
|
|
3286
|
+
adhdev-mcp --help Compatibility bin (same server, legacy package entrypoint)
|
|
2433
3287
|
|
|
2434
3288
|
Options:
|
|
2435
3289
|
--mode <mode> Transport: local, cloud, or ipc
|
|
@@ -2454,6 +3308,7 @@ Mesh tools: ${meshTools.join(", ")}
|
|
|
2454
3308
|
// src/server.ts
|
|
2455
3309
|
var import_server = require("@modelcontextprotocol/sdk/server/index.js");
|
|
2456
3310
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
3311
|
+
var import_node_os = __toESM(require("os"));
|
|
2457
3312
|
var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
2458
3313
|
|
|
2459
3314
|
// src/transports/local.ts
|
|
@@ -3004,6 +3859,22 @@ function formatChatResult(result, sessionId, format, limit = 50, compact = false
|
|
|
3004
3859
|
}))
|
|
3005
3860
|
}, null, 2);
|
|
3006
3861
|
}
|
|
3862
|
+
if ((format === "text" || format === void 0) && compact && compactPayload) {
|
|
3863
|
+
const lines2 = outputMessages.slice(-limit).map((m) => {
|
|
3864
|
+
const role = m.role === "user" ? "User" : m.role === "assistant" ? "Agent" : m.role;
|
|
3865
|
+
const content = messageContent(m);
|
|
3866
|
+
const truncated = content.length > 500 ? `${content.slice(0, 500)}\u2026` : content;
|
|
3867
|
+
return `[${role}] ${truncated}`;
|
|
3868
|
+
});
|
|
3869
|
+
if (compactPayload.summary) {
|
|
3870
|
+
const truncatedSummary = compactPayload.summary.length > 500 ? `${compactPayload.summary.slice(0, 500)}\u2026` : compactPayload.summary;
|
|
3871
|
+
lines2.push(`[Summary] ${truncatedSummary}`);
|
|
3872
|
+
}
|
|
3873
|
+
if (result?.pollingAdvisory) {
|
|
3874
|
+
lines2.push(`Advisory: ${result.pollingAdvisory.message}`);
|
|
3875
|
+
}
|
|
3876
|
+
return lines2.length > 0 ? lines2.join("\n\n") : "No messages in chat.";
|
|
3877
|
+
}
|
|
3007
3878
|
if (outputMessages.length === 0) {
|
|
3008
3879
|
return result?.pollingAdvisory ? `No messages in chat.
|
|
3009
3880
|
|
|
@@ -3971,6 +4842,7 @@ async function startMcpServer(opts) {
|
|
|
3971
4842
|
requirePreTaskCheckpoint: false,
|
|
3972
4843
|
requirePostTaskCheckpoint: true,
|
|
3973
4844
|
requireApprovalForPush: true,
|
|
4845
|
+
allowAutoPublishSubmoduleMainCommits: false,
|
|
3974
4846
|
requireApprovalForDestructiveGit: true,
|
|
3975
4847
|
dirtyWorkspaceBehavior: "warn",
|
|
3976
4848
|
maxParallelTasks: 2,
|
|
@@ -4027,11 +4899,13 @@ async function startMcpServer(opts) {
|
|
|
4027
4899
|
}
|
|
4028
4900
|
let localDaemonId;
|
|
4029
4901
|
let localMachineId;
|
|
4902
|
+
let coordinatorHostname = import_node_os.default.hostname();
|
|
4030
4903
|
if (transport instanceof LocalTransport || transport instanceof IpcTransport) {
|
|
4031
4904
|
try {
|
|
4032
4905
|
const { loadConfig } = await import("@adhdev/daemon-core");
|
|
4033
4906
|
const cfg = loadConfig();
|
|
4034
|
-
if (cfg.
|
|
4907
|
+
if (cfg.machineId) localMachineId = cfg.machineId;
|
|
4908
|
+
else if (cfg.registeredMachineId) localMachineId = cfg.registeredMachineId;
|
|
4035
4909
|
} catch {
|
|
4036
4910
|
}
|
|
4037
4911
|
}
|
|
@@ -4039,11 +4913,13 @@ async function startMcpServer(opts) {
|
|
|
4039
4913
|
try {
|
|
4040
4914
|
const statusResult = await transport.getStatus();
|
|
4041
4915
|
const instanceId = typeof statusResult?.status?.instanceId === "string" ? statusResult.status.instanceId.trim() : "";
|
|
4916
|
+
const hostname = typeof statusResult?.status?.hostname === "string" ? statusResult.status.hostname.trim() : typeof statusResult?.status?.machine?.hostname === "string" ? statusResult.status.machine.hostname.trim() : "";
|
|
4042
4917
|
if (instanceId) localDaemonId = instanceId;
|
|
4918
|
+
if (hostname) coordinatorHostname = hostname;
|
|
4043
4919
|
} catch {
|
|
4044
4920
|
}
|
|
4045
4921
|
}
|
|
4046
|
-
const meshCtx = { mesh, transport, ...localDaemonId ? { localDaemonId } : {}, ...localMachineId ? { localMachineId } : {} };
|
|
4922
|
+
const meshCtx = { mesh, transport, ...localDaemonId ? { localDaemonId } : {}, ...localMachineId ? { localMachineId } : {}, ...coordinatorHostname ? { coordinatorHostname } : {} };
|
|
4047
4923
|
const coordinatorPrompt = await buildMeshModeCoordinatorPrompt(mesh);
|
|
4048
4924
|
const server2 = new import_server.Server(
|
|
4049
4925
|
{ name: "adhdev-mcp-server", version: "0.9.81" },
|
|
@@ -4072,7 +4948,7 @@ async function startMcpServer(opts) {
|
|
|
4072
4948
|
let text;
|
|
4073
4949
|
switch (name) {
|
|
4074
4950
|
case "mesh_status":
|
|
4075
|
-
text = await meshStatus(meshCtx);
|
|
4951
|
+
text = await meshStatus(meshCtx, a);
|
|
4076
4952
|
break;
|
|
4077
4953
|
case "mesh_list_nodes":
|
|
4078
4954
|
text = await meshListNodes(meshCtx);
|
|
@@ -4104,6 +4980,9 @@ async function startMcpServer(opts) {
|
|
|
4104
4980
|
case "mesh_git_status":
|
|
4105
4981
|
text = await meshGitStatus(meshCtx, a);
|
|
4106
4982
|
break;
|
|
4983
|
+
case "mesh_fast_forward_node":
|
|
4984
|
+
text = await meshFastForwardNode(meshCtx, a);
|
|
4985
|
+
break;
|
|
4107
4986
|
case "mesh_checkpoint":
|
|
4108
4987
|
text = await meshCheckpoint(meshCtx, a);
|
|
4109
4988
|
break;
|
|
@@ -4119,6 +4998,18 @@ async function startMcpServer(opts) {
|
|
|
4119
4998
|
case "mesh_refine_node":
|
|
4120
4999
|
text = await meshRefineNode(meshCtx, a);
|
|
4121
5000
|
break;
|
|
5001
|
+
case "mesh_refine_config_schema":
|
|
5002
|
+
text = await meshRefineConfigSchema(meshCtx);
|
|
5003
|
+
break;
|
|
5004
|
+
case "mesh_validate_refine_config":
|
|
5005
|
+
text = await meshValidateRefineConfig(meshCtx, a);
|
|
5006
|
+
break;
|
|
5007
|
+
case "mesh_suggest_refine_config":
|
|
5008
|
+
text = await meshSuggestRefineConfig(meshCtx, a);
|
|
5009
|
+
break;
|
|
5010
|
+
case "mesh_refine_plan":
|
|
5011
|
+
text = await meshRefinePlan(meshCtx, a);
|
|
5012
|
+
break;
|
|
4122
5013
|
case "mesh_cleanup_sessions":
|
|
4123
5014
|
text = await meshCleanupSessions(meshCtx, a);
|
|
4124
5015
|
break;
|