@adhdev/daemon-standalone 0.9.82-rc.9 → 0.9.82-rc.90
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 +25362 -21190
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/public/assets/index-BFF2C2J9.js +99 -0
- package/public/assets/index-Bso1b8Lh.css +1 -0
- package/public/assets/{terminal-CfAvHQvJ.js → terminal-D46M5EWH.js} +3 -3
- package/public/index.html +2 -2
- package/vendor/mcp-server/index.js +705 -70
- package/vendor/mcp-server/index.js.map +1 -1
- package/public/assets/index-C6PWDloP.js +0 -98
- package/public/assets/index-DC6YELcC.css +0 -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,
|
|
@@ -235,6 +270,30 @@ var meshSessionProviderMetadata = /* @__PURE__ */ new Map();
|
|
|
235
270
|
function readString(value) {
|
|
236
271
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
237
272
|
}
|
|
273
|
+
function summarizeTaskMessage(message) {
|
|
274
|
+
const taskSummary = message.replace(/\s+/g, " ").trim();
|
|
275
|
+
const taskTitle = taskSummary.length > 96 ? `${taskSummary.slice(0, 93)}...` : taskSummary;
|
|
276
|
+
return { taskTitle: taskTitle || "(untitled task)", taskSummary };
|
|
277
|
+
}
|
|
278
|
+
function buildDirectTaskPayload(message, via, opts) {
|
|
279
|
+
const descriptor = summarizeTaskMessage(message);
|
|
280
|
+
return {
|
|
281
|
+
source: "direct",
|
|
282
|
+
via,
|
|
283
|
+
taskId: opts.taskId,
|
|
284
|
+
message,
|
|
285
|
+
taskTitle: descriptor.taskTitle,
|
|
286
|
+
taskSummary: descriptor.taskSummary,
|
|
287
|
+
...opts.taskMode ? { taskMode: opts.taskMode } : {},
|
|
288
|
+
...opts.providerType ? { providerType: opts.providerType } : {},
|
|
289
|
+
...opts.targetSessionId ? { targetSessionId: opts.targetSessionId } : {}
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
function findNode(mesh, nodeId) {
|
|
293
|
+
const node = mesh.nodes.find((n) => n.id === nodeId);
|
|
294
|
+
if (!node) throw new Error(`Node '${nodeId}' is not a member of mesh '${mesh.name}'`);
|
|
295
|
+
return node;
|
|
296
|
+
}
|
|
238
297
|
var DUPLICATE_DISPATCH_WINDOW_MS = 6e4;
|
|
239
298
|
var STALE_ASSIGNED_QUEUE_MS = 30 * 6e4;
|
|
240
299
|
var OLD_HISTORICAL_QUEUE_RECORD_MS = 7 * 24 * 60 * 6e4;
|
|
@@ -246,15 +305,24 @@ async function refreshMeshFromDaemon(ctx) {
|
|
|
246
305
|
const result = await ctx.transport.command("get_mesh", { meshId: ctx.mesh.id });
|
|
247
306
|
if (!result?.success || !Array.isArray(result.mesh?.nodes)) return;
|
|
248
307
|
const refreshedNodes = result.mesh.nodes.filter((n) => n?.id).map((n) => n);
|
|
249
|
-
if (!refreshedNodes.length) return;
|
|
250
308
|
ctx.mesh.nodes.splice(0, ctx.mesh.nodes.length, ...refreshedNodes);
|
|
251
309
|
ctx.mesh.updatedAt = result.mesh.updatedAt ?? ctx.mesh.updatedAt;
|
|
252
310
|
} catch {
|
|
253
311
|
}
|
|
254
312
|
}
|
|
313
|
+
async function syncCoordinatorDaemonMeshCache(ctx) {
|
|
314
|
+
if (!(ctx.transport instanceof IpcTransport)) return;
|
|
315
|
+
try {
|
|
316
|
+
await ctx.transport.command("get_mesh", {
|
|
317
|
+
meshId: ctx.mesh.id,
|
|
318
|
+
inlineMesh: ctx.mesh
|
|
319
|
+
});
|
|
320
|
+
} catch {
|
|
321
|
+
}
|
|
322
|
+
}
|
|
255
323
|
async function findNodeWithRefresh(ctx, nodeId) {
|
|
256
324
|
const hit = ctx.mesh.nodes.find((n) => n.id === nodeId);
|
|
257
|
-
if (hit) return hit;
|
|
325
|
+
if (hit && !hit.isLocalWorktree) return hit;
|
|
258
326
|
await refreshMeshFromDaemon(ctx);
|
|
259
327
|
const refreshed = ctx.mesh.nodes.find((n) => n.id === nodeId);
|
|
260
328
|
if (!refreshed) throw new Error(`Node '${nodeId}' is not a member of mesh '${ctx.mesh.name}'`);
|
|
@@ -262,7 +330,7 @@ async function findNodeWithRefresh(ctx, nodeId) {
|
|
|
262
330
|
}
|
|
263
331
|
async function findOptionalNodeWithRefresh(ctx, nodeId) {
|
|
264
332
|
const hit = ctx.mesh.nodes.find((n) => n.id === nodeId);
|
|
265
|
-
if (hit) return hit;
|
|
333
|
+
if (hit && !hit.isLocalWorktree) return hit;
|
|
266
334
|
await refreshMeshFromDaemon(ctx);
|
|
267
335
|
return ctx.mesh.nodes.find((n) => n.id === nodeId) ?? null;
|
|
268
336
|
}
|
|
@@ -314,9 +382,26 @@ function buildMissingNodeReadChatRecovery(ctx, args) {
|
|
|
314
382
|
readDebugLocator: readString(lastTerminal?.payload?.readDebugLocator) || readString(lastTerminal?.payload?.debugBundlePath)
|
|
315
383
|
};
|
|
316
384
|
if (finalSummary) {
|
|
385
|
+
if (args.compact === true) {
|
|
386
|
+
return {
|
|
387
|
+
...compactChatPayload({
|
|
388
|
+
success: true,
|
|
389
|
+
status: "idle",
|
|
390
|
+
providerSessionId,
|
|
391
|
+
summary: finalSummary,
|
|
392
|
+
messages: [{ role: "assistant", content: finalSummary, isHistorical: true }]
|
|
393
|
+
}, {
|
|
394
|
+
nodeId: args.node_id,
|
|
395
|
+
sessionId: args.session_id,
|
|
396
|
+
limit: args.tail ?? 10
|
|
397
|
+
}),
|
|
398
|
+
recoveredFromLedger: true,
|
|
399
|
+
ledger
|
|
400
|
+
};
|
|
401
|
+
}
|
|
317
402
|
return {
|
|
318
403
|
success: true,
|
|
319
|
-
compact:
|
|
404
|
+
compact: false,
|
|
320
405
|
recoveredFromLedger: true,
|
|
321
406
|
nodeId: args.node_id,
|
|
322
407
|
sessionId: args.session_id,
|
|
@@ -368,6 +453,14 @@ function buildMissingNodeReadChatRecovery(ctx, args) {
|
|
|
368
453
|
function readSessionRecordId(session) {
|
|
369
454
|
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
455
|
}
|
|
456
|
+
function extractStatusMetadataSessions(value) {
|
|
457
|
+
const payload = unwrapCommandPayload(value);
|
|
458
|
+
const status = payload?.status && typeof payload.status === "object" ? payload.status : payload;
|
|
459
|
+
return Array.isArray(status?.sessions) ? status.sessions : [];
|
|
460
|
+
}
|
|
461
|
+
function resolveSessionProviderType(session) {
|
|
462
|
+
return readString(session?.providerType) || readString(session?.cliType) || readString(session?.agentType) || "";
|
|
463
|
+
}
|
|
371
464
|
function addSessionRecord(target, session) {
|
|
372
465
|
if (!session || typeof session !== "object" || isTerminalSessionRecord(session)) return;
|
|
373
466
|
const sessionId = readSessionRecordId(session);
|
|
@@ -436,18 +529,26 @@ function queueAssignmentStaleReason(task, liveness) {
|
|
|
436
529
|
}
|
|
437
530
|
function buildQueueStatusSummary(queue) {
|
|
438
531
|
const counts = { pending: 0, assigned: 0, completed: 0, failed: 0, cancelled: 0 };
|
|
532
|
+
let staleAssigned = 0;
|
|
439
533
|
for (const task of queue) {
|
|
440
534
|
const status = typeof task?.status === "string" ? task.status : void 0;
|
|
441
535
|
if (status && Object.prototype.hasOwnProperty.call(counts, status)) {
|
|
442
536
|
counts[status] += 1;
|
|
443
537
|
}
|
|
538
|
+
if (status === "assigned" && task?.staleAssigned === true) staleAssigned += 1;
|
|
444
539
|
}
|
|
540
|
+
const liveAssigned = Math.max(0, counts.assigned - staleAssigned);
|
|
445
541
|
return {
|
|
446
542
|
totalCount: queue.length,
|
|
447
|
-
activeCount: counts.pending +
|
|
543
|
+
activeCount: counts.pending + liveAssigned,
|
|
448
544
|
historicalCount: counts.completed + counts.failed + counts.cancelled,
|
|
449
545
|
counts,
|
|
450
546
|
activeCounts: {
|
|
547
|
+
pending: counts.pending,
|
|
548
|
+
assigned: liveAssigned
|
|
549
|
+
},
|
|
550
|
+
staleAssignedCount: staleAssigned,
|
|
551
|
+
rawActiveCounts: {
|
|
451
552
|
pending: counts.pending,
|
|
452
553
|
assigned: counts.assigned
|
|
453
554
|
},
|
|
@@ -475,6 +576,18 @@ function filterQueueForView(queue, view, statuses) {
|
|
|
475
576
|
if (view === "historical") return queue.filter((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
|
|
476
577
|
return queue;
|
|
477
578
|
}
|
|
579
|
+
function prioritizeActiveQueueRows(queue) {
|
|
580
|
+
const active = [];
|
|
581
|
+
const historical = [];
|
|
582
|
+
const other = [];
|
|
583
|
+
for (const task of queue) {
|
|
584
|
+
const status = String(task?.status || "");
|
|
585
|
+
if (ACTIVE_QUEUE_STATUSES.has(status)) active.push(task);
|
|
586
|
+
else if (HISTORICAL_QUEUE_STATUSES.has(status)) historical.push(task);
|
|
587
|
+
else other.push(task);
|
|
588
|
+
}
|
|
589
|
+
return [...active, ...other, ...historical];
|
|
590
|
+
}
|
|
478
591
|
function slimQueueTask(task) {
|
|
479
592
|
return {
|
|
480
593
|
id: task?.id,
|
|
@@ -580,22 +693,59 @@ function isIdleSessionRecord(session) {
|
|
|
580
693
|
const chatStatus = typeof session?.activeChat?.status === "string" ? session.activeChat.status.toLowerCase() : "";
|
|
581
694
|
return status === "idle" || chatStatus === "waiting_input";
|
|
582
695
|
}
|
|
696
|
+
function isMeshOwnedDelegateSession(session, meshId, nodeId) {
|
|
697
|
+
const settings = session?.settings;
|
|
698
|
+
const sessionMeshId = typeof settings?.meshNodeFor === "string" ? settings.meshNodeFor.trim() : "";
|
|
699
|
+
const coordinatorDaemonId = typeof settings?.meshCoordinatorDaemonId === "string" ? settings.meshCoordinatorDaemonId.trim() : "";
|
|
700
|
+
const sessionNodeId = typeof settings?.meshNodeId === "string" ? settings.meshNodeId.trim() : "";
|
|
701
|
+
if (sessionMeshId !== meshId || !coordinatorDaemonId) return false;
|
|
702
|
+
return !sessionNodeId || sessionNodeId === nodeId;
|
|
703
|
+
}
|
|
583
704
|
function chooseDispatchableSession(sessions, providerType, meshId, nodeId) {
|
|
584
705
|
const live = sessions.filter((session) => !isTerminalSessionRecord(session));
|
|
585
706
|
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
707
|
const meshSessions = live.filter(
|
|
595
|
-
(session) => isMeshOwnedDelegateSession(session)
|
|
708
|
+
(session) => isMeshOwnedDelegateSession(session, meshId, nodeId)
|
|
596
709
|
);
|
|
597
710
|
return meshSessions.find((session) => isIdleSessionRecord(session) && matchingProvider(session)) || meshSessions.find(matchingProvider) || void 0;
|
|
598
711
|
}
|
|
712
|
+
function buildRelayUnsafeRemoteSessionFailure(ctx, node, sessionId, providerType) {
|
|
713
|
+
return {
|
|
714
|
+
success: false,
|
|
715
|
+
recoverable: true,
|
|
716
|
+
code: "mesh_delegate_session_missing_relay_metadata",
|
|
717
|
+
reason: "mesh_delegate_session_missing_relay_metadata",
|
|
718
|
+
transport: "mesh_transport",
|
|
719
|
+
retryRecommended: true,
|
|
720
|
+
meshId: ctx.mesh.id,
|
|
721
|
+
nodeId: node.id,
|
|
722
|
+
daemonId: node.daemonId,
|
|
723
|
+
workspace: node.workspace,
|
|
724
|
+
sessionId,
|
|
725
|
+
...providerType ? { resolvedProviderType: providerType } : {},
|
|
726
|
+
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.`,
|
|
727
|
+
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.`,
|
|
728
|
+
noFallbackReason: "Blindly reusing a remote session without mesh relay metadata would silently drop task_completed / generating_completed events."
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
function buildMissingCoordinatorDaemonIdFailure(ctx, node, providerType) {
|
|
732
|
+
return {
|
|
733
|
+
success: false,
|
|
734
|
+
recoverable: true,
|
|
735
|
+
code: "mesh_coordinator_daemon_unknown",
|
|
736
|
+
reason: "mesh_coordinator_daemon_unknown",
|
|
737
|
+
transport: "mesh_transport",
|
|
738
|
+
retryRecommended: true,
|
|
739
|
+
meshId: ctx.mesh.id,
|
|
740
|
+
nodeId: node.id,
|
|
741
|
+
daemonId: node.daemonId,
|
|
742
|
+
workspace: node.workspace,
|
|
743
|
+
...providerType ? { resolvedProviderType: providerType } : {},
|
|
744
|
+
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.`,
|
|
745
|
+
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.",
|
|
746
|
+
noFallbackReason: "Launching without meshCoordinatorDaemonId would create a worker session that can finish work but cannot emit task_completed / generating_completed back to the coordinator."
|
|
747
|
+
};
|
|
748
|
+
}
|
|
599
749
|
function findNestedPayload(value, predicate) {
|
|
600
750
|
const seen = /* @__PURE__ */ new Set();
|
|
601
751
|
const stack = [{ payload: value, depth: 0 }];
|
|
@@ -623,12 +773,16 @@ function extractGitDiff(value) {
|
|
|
623
773
|
}
|
|
624
774
|
function extractSubmodules(value, ignorePaths) {
|
|
625
775
|
const payload = unwrapCommandPayload(value);
|
|
626
|
-
const subs = payload?.submodules ?? value?.submodules;
|
|
776
|
+
const subs = payload?.status?.submodules ?? payload?.submodules ?? value?.status?.submodules ?? value?.submodules;
|
|
627
777
|
if (!Array.isArray(subs)) return void 0;
|
|
628
778
|
if (ignorePaths.length === 0) return subs;
|
|
629
779
|
const ignoreSet = new Set(ignorePaths);
|
|
630
780
|
return subs.filter((s) => s?.path && !ignoreSet.has(s.path));
|
|
631
781
|
}
|
|
782
|
+
function assignFullGitSnapshot(entry, status) {
|
|
783
|
+
if (!status || typeof status !== "object" || Array.isArray(status)) return;
|
|
784
|
+
entry.git = status;
|
|
785
|
+
}
|
|
632
786
|
function extractLaunchPayload(value) {
|
|
633
787
|
return findNestedPayload(value, (payload) => Boolean(payload?.sessionId || payload?.id || payload?.runtimeSessionId));
|
|
634
788
|
}
|
|
@@ -753,20 +907,63 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
|
|
|
753
907
|
let sessionId = args.session_id?.trim() || "";
|
|
754
908
|
const providerPriorityList = Array.isArray(node.policy?.providerPriority) ? node.policy.providerPriority : [];
|
|
755
909
|
let resolvedProviderType = args.providerType?.trim() || providerPriorityList[0] || "";
|
|
756
|
-
if (!sessionId) {
|
|
910
|
+
if (!sessionId || args.session_id) {
|
|
757
911
|
try {
|
|
758
912
|
const relayResult = await transport.meshCommand(daemonId, "get_status_metadata", {});
|
|
759
|
-
const
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
913
|
+
const sessions = extractStatusMetadataSessions(relayResult);
|
|
914
|
+
if (sessionId) {
|
|
915
|
+
const explicitSession = sessions.find((session) => readSessionRecordId(session) === sessionId);
|
|
916
|
+
if (!explicitSession) {
|
|
917
|
+
return {
|
|
918
|
+
success: false,
|
|
919
|
+
recoverable: true,
|
|
920
|
+
code: "mesh_target_session_not_found",
|
|
921
|
+
reason: "mesh_target_session_not_found",
|
|
922
|
+
transport: "mesh_transport",
|
|
923
|
+
retryRecommended: true,
|
|
924
|
+
meshId: ctx.mesh.id,
|
|
925
|
+
nodeId: node.id,
|
|
926
|
+
daemonId,
|
|
927
|
+
workspace: node.workspace,
|
|
928
|
+
sessionId,
|
|
929
|
+
...resolvedProviderType ? { resolvedProviderType } : {},
|
|
930
|
+
error: `Remote session '${sessionId}' is not present in the live status for node '${node.id}'.`,
|
|
931
|
+
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.`
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
if (!isMeshOwnedDelegateSession(explicitSession, ctx.mesh.id, node.id)) {
|
|
935
|
+
return buildRelayUnsafeRemoteSessionFailure(
|
|
936
|
+
ctx,
|
|
937
|
+
node,
|
|
938
|
+
sessionId,
|
|
939
|
+
resolvedProviderType || resolveSessionProviderType(explicitSession) || void 0
|
|
940
|
+
);
|
|
941
|
+
}
|
|
765
942
|
if (!resolvedProviderType) {
|
|
766
|
-
resolvedProviderType =
|
|
943
|
+
resolvedProviderType = resolveSessionProviderType(explicitSession);
|
|
944
|
+
}
|
|
945
|
+
} else {
|
|
946
|
+
const targetSession = chooseDispatchableSession(sessions, resolvedProviderType, ctx.mesh.id, node.id);
|
|
947
|
+
if (targetSession?.id || targetSession?.sessionId) {
|
|
948
|
+
sessionId = targetSession.id || targetSession.sessionId;
|
|
949
|
+
if (!resolvedProviderType) {
|
|
950
|
+
resolvedProviderType = resolveSessionProviderType(targetSession);
|
|
951
|
+
}
|
|
767
952
|
}
|
|
768
953
|
}
|
|
769
954
|
} catch (e) {
|
|
955
|
+
if (sessionId) {
|
|
956
|
+
return {
|
|
957
|
+
...buildCoordinatorP2pRelayFailure(e, {
|
|
958
|
+
command: "get_status_metadata",
|
|
959
|
+
targetDaemonId: daemonId,
|
|
960
|
+
nodeId: node.id,
|
|
961
|
+
sessionId
|
|
962
|
+
}),
|
|
963
|
+
success: false,
|
|
964
|
+
error: `Cannot verify remote session '${sessionId}' before dispatch: ${e?.message || String(e)}`
|
|
965
|
+
};
|
|
966
|
+
}
|
|
770
967
|
}
|
|
771
968
|
}
|
|
772
969
|
if (!resolvedProviderType) {
|
|
@@ -796,7 +993,7 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
|
|
|
796
993
|
error: `P2P dispatch failed: ${errorMessage}`
|
|
797
994
|
};
|
|
798
995
|
}
|
|
799
|
-
return { success: true, dispatched: true, sessionId: sessionId || resolvedProviderType };
|
|
996
|
+
return { success: true, dispatched: true, sessionId: sessionId || resolvedProviderType, providerType: resolvedProviderType };
|
|
800
997
|
} catch (e) {
|
|
801
998
|
const errorMessage = e?.message || String(e);
|
|
802
999
|
return {
|
|
@@ -831,6 +1028,34 @@ function readNodeMachineId(node) {
|
|
|
831
1028
|
function readNodeDaemonId(node) {
|
|
832
1029
|
return readString(node.daemonId) || readString(node.daemon_id);
|
|
833
1030
|
}
|
|
1031
|
+
function normalizeHostname(value) {
|
|
1032
|
+
const hostname = readString(value);
|
|
1033
|
+
if (!hostname) return void 0;
|
|
1034
|
+
return hostname.toLowerCase().replace(/\.$/, "");
|
|
1035
|
+
}
|
|
1036
|
+
function readNodeHostname(node) {
|
|
1037
|
+
return readString(node.hostname) || readString(node.machineHostname) || readString(node.machine_hostname) || readString(node.machineName) || readString(node.machine_name) || readString(node.lastProbe?.hostname) || readString(node.last_probe?.hostname) || readString(node.lastProbe?.machine?.hostname) || readString(node.last_probe?.machine?.hostname);
|
|
1038
|
+
}
|
|
1039
|
+
function buildNodeMachineIdentity(ctx, node) {
|
|
1040
|
+
const machineId = readNodeMachineId(node);
|
|
1041
|
+
const daemonId = readNodeDaemonId(node);
|
|
1042
|
+
const hostname = readNodeHostname(node);
|
|
1043
|
+
const coordinatorHostname = readString(ctx.coordinatorHostname);
|
|
1044
|
+
const hostnameMatches = Boolean(
|
|
1045
|
+
normalizeHostname(hostname) && normalizeHostname(coordinatorHostname) && normalizeHostname(hostname) === normalizeHostname(coordinatorHostname)
|
|
1046
|
+
);
|
|
1047
|
+
const sameMachine = isLocalControlPlaneNode(ctx, node) || hostnameMatches;
|
|
1048
|
+
return {
|
|
1049
|
+
daemonId,
|
|
1050
|
+
machineId,
|
|
1051
|
+
hostname,
|
|
1052
|
+
machineName: hostname,
|
|
1053
|
+
coordinatorHostname,
|
|
1054
|
+
sameMachine,
|
|
1055
|
+
locality: sameMachine ? "same_machine" : "remote_or_unknown",
|
|
1056
|
+
localityReason: sameMachine ? isLocalControlPlaneNode(ctx, node) ? "matched coordinator daemon or machine id" : "matched coordinator hostname" : "no coordinator daemon, machine id, or hostname match"
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
834
1059
|
function isDirectLocalNode(ctx, node) {
|
|
835
1060
|
const machineId = readNodeMachineId(node);
|
|
836
1061
|
const daemonId = readNodeDaemonId(node);
|
|
@@ -938,6 +1163,14 @@ function getNodeLaunchReadiness(node) {
|
|
|
938
1163
|
launchBlockedMessage: missingProviderPriorityMessage(node.id)
|
|
939
1164
|
};
|
|
940
1165
|
}
|
|
1166
|
+
async function collectLiveStatusSessions(ctx, node) {
|
|
1167
|
+
try {
|
|
1168
|
+
const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
|
|
1169
|
+
return extractStatusMetadataSessions(statusResult);
|
|
1170
|
+
} catch {
|
|
1171
|
+
return [];
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
941
1174
|
function readNumeric(value, fallback = 0) {
|
|
942
1175
|
const parsed = Number(value);
|
|
943
1176
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
@@ -1073,7 +1306,81 @@ async function commandForNode(ctx, node, command, args = {}) {
|
|
|
1073
1306
|
if (isLocalTransport(ctx.transport)) {
|
|
1074
1307
|
return ctx.transport.command(command, args);
|
|
1075
1308
|
}
|
|
1076
|
-
|
|
1309
|
+
const identity = buildNodeMachineIdentity(ctx, node);
|
|
1310
|
+
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})`);
|
|
1311
|
+
}
|
|
1312
|
+
function normalizePendingMeshCoordinatorEvents(value) {
|
|
1313
|
+
const payload = unwrapCommandPayload(value);
|
|
1314
|
+
const events = Array.isArray(payload?.events) ? payload.events : Array.isArray(value?.events) ? value.events : [];
|
|
1315
|
+
return events.filter((event) => event && typeof event === "object");
|
|
1316
|
+
}
|
|
1317
|
+
function buildMeshForwardPayloadFromPendingEvent(event) {
|
|
1318
|
+
const metadataEvent = event?.metadataEvent && typeof event.metadataEvent === "object" ? event.metadataEvent : {};
|
|
1319
|
+
return {
|
|
1320
|
+
event: readString(event?.event),
|
|
1321
|
+
meshId: readString(event?.meshId),
|
|
1322
|
+
nodeId: readString(event?.nodeId) || readString(metadataEvent.meshNodeId),
|
|
1323
|
+
workspace: readString(event?.workspace) || readString(metadataEvent.workspace),
|
|
1324
|
+
targetSessionId: readString(metadataEvent.targetSessionId) || readString(metadataEvent.sessionId) || readString(metadataEvent.instanceId),
|
|
1325
|
+
providerType: readString(metadataEvent.providerType),
|
|
1326
|
+
providerSessionId: readString(metadataEvent.providerSessionId),
|
|
1327
|
+
finalSummary: readString(metadataEvent.finalSummary) || readString(metadataEvent.summary),
|
|
1328
|
+
jobId: readString(metadataEvent.jobId),
|
|
1329
|
+
interactionId: readString(metadataEvent.interactionId),
|
|
1330
|
+
status: readString(metadataEvent.status),
|
|
1331
|
+
targetDaemonId: readString(metadataEvent.targetDaemonId),
|
|
1332
|
+
startedAt: readString(metadataEvent.startedAt),
|
|
1333
|
+
completedAt: readString(metadataEvent.completedAt),
|
|
1334
|
+
retryOfJobId: readString(metadataEvent.retryOfJobId),
|
|
1335
|
+
...metadataEvent.result && typeof metadataEvent.result === "object" && !Array.isArray(metadataEvent.result) ? { result: metadataEvent.result } : {},
|
|
1336
|
+
...metadataEvent.intentional === true ? { intentional: true } : {},
|
|
1337
|
+
...metadataEvent.intentionalStop === true ? { intentionalStop: true } : {},
|
|
1338
|
+
...metadataEvent.operatorCleanup === true ? { operatorCleanup: true } : {},
|
|
1339
|
+
...readString(metadataEvent.reason) ? { reason: readString(metadataEvent.reason) } : {},
|
|
1340
|
+
...readString(metadataEvent.stopReason) ? { stopReason: readString(metadataEvent.stopReason) } : {},
|
|
1341
|
+
...readString(metadataEvent.cleanupReason) ? { cleanupReason: readString(metadataEvent.cleanupReason) } : {},
|
|
1342
|
+
...readString(metadataEvent.source) ? { source: readString(metadataEvent.source) } : {}
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1345
|
+
async function drainCoordinatorPendingEvents(ctx, opts) {
|
|
1346
|
+
const requestedNodeIds = opts?.nodeIds?.length ? new Set(opts.nodeIds) : null;
|
|
1347
|
+
const matchesCurrentMesh = (event) => readString(event?.meshId) === ctx.mesh.id;
|
|
1348
|
+
if (ctx.transport instanceof IpcTransport) {
|
|
1349
|
+
const surfacedEvents = [];
|
|
1350
|
+
try {
|
|
1351
|
+
surfacedEvents.push(
|
|
1352
|
+
...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", { meshId: ctx.mesh.id })).filter(matchesCurrentMesh)
|
|
1353
|
+
);
|
|
1354
|
+
} catch {
|
|
1355
|
+
}
|
|
1356
|
+
for (const node of ctx.mesh.nodes) {
|
|
1357
|
+
if (!node.daemonId || isLocalControlPlaneNode(ctx, node)) continue;
|
|
1358
|
+
if (requestedNodeIds && !requestedNodeIds.has(node.id)) continue;
|
|
1359
|
+
try {
|
|
1360
|
+
const remoteEvents = normalizePendingMeshCoordinatorEvents(
|
|
1361
|
+
await ctx.transport.meshCommand(node.daemonId, "get_pending_mesh_events", { meshId: ctx.mesh.id })
|
|
1362
|
+
).filter(matchesCurrentMesh);
|
|
1363
|
+
if (remoteEvents.length === 0) continue;
|
|
1364
|
+
for (const event of remoteEvents) {
|
|
1365
|
+
const payload = buildMeshForwardPayloadFromPendingEvent(event);
|
|
1366
|
+
if (!payload.event || !payload.meshId) continue;
|
|
1367
|
+
await ctx.transport.command("mesh_forward_event", payload);
|
|
1368
|
+
}
|
|
1369
|
+
} catch {
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
try {
|
|
1373
|
+
surfacedEvents.push(
|
|
1374
|
+
...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", { meshId: ctx.mesh.id })).filter(matchesCurrentMesh)
|
|
1375
|
+
);
|
|
1376
|
+
} catch {
|
|
1377
|
+
}
|
|
1378
|
+
return surfacedEvents;
|
|
1379
|
+
}
|
|
1380
|
+
if (isLocalTransport(ctx.transport)) {
|
|
1381
|
+
return (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id).filter(matchesCurrentMesh);
|
|
1382
|
+
}
|
|
1383
|
+
return [];
|
|
1077
1384
|
}
|
|
1078
1385
|
function isP2pTransportUnavailableError(error) {
|
|
1079
1386
|
return (0, import_daemon_core.isP2pRelayTransportFailure)(error);
|
|
@@ -1112,7 +1419,9 @@ var MESH_ENQUEUE_TASK_TOOL = {
|
|
|
1112
1419
|
inputSchema: {
|
|
1113
1420
|
type: "object",
|
|
1114
1421
|
properties: {
|
|
1115
|
-
message: { type: "string", description: "The task instruction for the agent." }
|
|
1422
|
+
message: { type: "string", description: "The task instruction for the agent." },
|
|
1423
|
+
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." },
|
|
1424
|
+
taskMode: { type: "string", enum: ["code_change", "validation", "live_debug_readonly", "launch_app", "convergence"], description: "CamelCase alias for task_mode." }
|
|
1116
1425
|
},
|
|
1117
1426
|
required: ["message"]
|
|
1118
1427
|
}
|
|
@@ -1172,7 +1481,9 @@ var MESH_SEND_TASK_TOOL = {
|
|
|
1172
1481
|
properties: {
|
|
1173
1482
|
node_id: { type: "string", description: "Target node ID (from mesh_list_nodes)." },
|
|
1174
1483
|
session_id: { type: "string", description: "Agent session ID on the target node." },
|
|
1175
|
-
message: { type: "string", description: "Natural-language task to send to the agent." }
|
|
1484
|
+
message: { type: "string", description: "Natural-language task to send to the agent." },
|
|
1485
|
+
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." },
|
|
1486
|
+
taskMode: { type: "string", enum: ["code_change", "validation", "live_debug_readonly", "launch_app", "convergence"], description: "CamelCase alias for task_mode." }
|
|
1176
1487
|
},
|
|
1177
1488
|
required: ["node_id", "session_id", "message"]
|
|
1178
1489
|
}
|
|
@@ -1230,6 +1541,21 @@ var MESH_GIT_STATUS_TOOL = {
|
|
|
1230
1541
|
required: ["node_id"]
|
|
1231
1542
|
}
|
|
1232
1543
|
};
|
|
1544
|
+
var MESH_FAST_FORWARD_NODE_TOOL = {
|
|
1545
|
+
name: "mesh_fast_forward_node",
|
|
1546
|
+
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.",
|
|
1547
|
+
inputSchema: {
|
|
1548
|
+
type: "object",
|
|
1549
|
+
properties: {
|
|
1550
|
+
node_id: { type: "string", description: "Target node ID." },
|
|
1551
|
+
branch: { type: "string", description: "Optional guard: require the node's current branch to match this branch before planning/executing." },
|
|
1552
|
+
execute: { type: "boolean", description: "When true, apply the fast-forward if all safety gates pass. Defaults false/dry-run." },
|
|
1553
|
+
dry_run: { type: "boolean", description: "Preview only. Defaults true unless execute=true; dry_run=true overrides execute." },
|
|
1554
|
+
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." }
|
|
1555
|
+
},
|
|
1556
|
+
required: ["node_id"]
|
|
1557
|
+
}
|
|
1558
|
+
};
|
|
1233
1559
|
var MESH_CHECKPOINT_TOOL = {
|
|
1234
1560
|
name: "mesh_checkpoint",
|
|
1235
1561
|
description: "Create a git checkpoint (commit) on a mesh node workspace.",
|
|
@@ -1313,7 +1639,7 @@ var MESH_TASK_HISTORY_TOOL = {
|
|
|
1313
1639
|
type: "object",
|
|
1314
1640
|
properties: {
|
|
1315
1641
|
tail: { type: "number", description: "Number of recent entries to return (default: 20)." },
|
|
1316
|
-
kind: { type: "string", description: "Filter by entry kind: task_dispatched, task_completed, task_failed, task_stalled, session_launched, checkpoint_created, node_cloned, node_removed." }
|
|
1642
|
+
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." }
|
|
1317
1643
|
}
|
|
1318
1644
|
}
|
|
1319
1645
|
};
|
|
@@ -1333,7 +1659,7 @@ var MESH_RECONCILE_LEDGER_TOOL = {
|
|
|
1333
1659
|
};
|
|
1334
1660
|
var MESH_REFINE_NODE_TOOL = {
|
|
1335
1661
|
name: "mesh_refine_node",
|
|
1336
|
-
description: "The Refinery:
|
|
1662
|
+
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.",
|
|
1337
1663
|
inputSchema: {
|
|
1338
1664
|
type: "object",
|
|
1339
1665
|
properties: {
|
|
@@ -1342,6 +1668,43 @@ var MESH_REFINE_NODE_TOOL = {
|
|
|
1342
1668
|
required: ["node_id"]
|
|
1343
1669
|
}
|
|
1344
1670
|
};
|
|
1671
|
+
var MESH_REFINE_CONFIG_SCHEMA_TOOL = {
|
|
1672
|
+
name: "mesh_refine_config_schema",
|
|
1673
|
+
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.",
|
|
1674
|
+
inputSchema: { type: "object", properties: {} }
|
|
1675
|
+
};
|
|
1676
|
+
var MESH_VALIDATE_REFINE_CONFIG_TOOL = {
|
|
1677
|
+
name: "mesh_validate_refine_config",
|
|
1678
|
+
description: "Validate the repo mesh/refine config for a node/workspace without running validation commands or merging.",
|
|
1679
|
+
inputSchema: {
|
|
1680
|
+
type: "object",
|
|
1681
|
+
properties: {
|
|
1682
|
+
node_id: { type: "string", description: "Optional node/workspace whose refine config should be loaded. Defaults to the first mesh node." },
|
|
1683
|
+
config: { type: "object", description: "Optional inline config object to validate instead of loading from the repo." }
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
};
|
|
1687
|
+
var MESH_SUGGEST_REFINE_CONFIG_TOOL = {
|
|
1688
|
+
name: "mesh_suggest_refine_config",
|
|
1689
|
+
description: "Suggest a repo mesh/refine config scaffold from project context/package scripts. Suggestions are never executed until saved as explicit refine config.",
|
|
1690
|
+
inputSchema: {
|
|
1691
|
+
type: "object",
|
|
1692
|
+
properties: {
|
|
1693
|
+
node_id: { type: "string", description: "Optional node/workspace used for suggestions. Defaults to the first mesh node." }
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
};
|
|
1697
|
+
var MESH_REFINE_PLAN_TOOL = {
|
|
1698
|
+
name: "mesh_refine_plan",
|
|
1699
|
+
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.",
|
|
1700
|
+
inputSchema: {
|
|
1701
|
+
type: "object",
|
|
1702
|
+
properties: {
|
|
1703
|
+
node_id: { type: "string", description: "Node ID of the worktree node to plan." }
|
|
1704
|
+
},
|
|
1705
|
+
required: ["node_id"]
|
|
1706
|
+
}
|
|
1707
|
+
};
|
|
1345
1708
|
var ALL_MESH_TOOLS = [
|
|
1346
1709
|
MESH_STATUS_TOOL,
|
|
1347
1710
|
MESH_LIST_NODES_TOOL,
|
|
@@ -1354,11 +1717,16 @@ var ALL_MESH_TOOLS = [
|
|
|
1354
1717
|
MESH_READ_DEBUG_TOOL,
|
|
1355
1718
|
MESH_LAUNCH_SESSION_TOOL,
|
|
1356
1719
|
MESH_GIT_STATUS_TOOL,
|
|
1720
|
+
MESH_FAST_FORWARD_NODE_TOOL,
|
|
1357
1721
|
MESH_CHECKPOINT_TOOL,
|
|
1358
1722
|
MESH_APPROVE_TOOL,
|
|
1359
1723
|
MESH_CLONE_NODE_TOOL,
|
|
1360
1724
|
MESH_REMOVE_NODE_TOOL,
|
|
1361
1725
|
MESH_REFINE_NODE_TOOL,
|
|
1726
|
+
MESH_REFINE_CONFIG_SCHEMA_TOOL,
|
|
1727
|
+
MESH_VALIDATE_REFINE_CONFIG_TOOL,
|
|
1728
|
+
MESH_SUGGEST_REFINE_CONFIG_TOOL,
|
|
1729
|
+
MESH_REFINE_PLAN_TOOL,
|
|
1362
1730
|
MESH_CLEANUP_SESSIONS_TOOL,
|
|
1363
1731
|
MESH_TASK_HISTORY_TOOL,
|
|
1364
1732
|
MESH_RECONCILE_LEDGER_TOOL
|
|
@@ -1372,6 +1740,9 @@ async function meshStatus(ctx) {
|
|
|
1372
1740
|
const entry = {
|
|
1373
1741
|
nodeId: node.id,
|
|
1374
1742
|
workspace: node.workspace,
|
|
1743
|
+
machine: buildNodeMachineIdentity(ctx, node),
|
|
1744
|
+
daemonId: readNodeDaemonId(node),
|
|
1745
|
+
machineId: readNodeMachineId(node),
|
|
1375
1746
|
...getNodeLaunchReadiness(node)
|
|
1376
1747
|
};
|
|
1377
1748
|
try {
|
|
@@ -1381,6 +1752,7 @@ async function meshStatus(ctx) {
|
|
|
1381
1752
|
const uncommittedChanges = countUncommittedChanges(status);
|
|
1382
1753
|
const dirty = isGitStatusDirty(status);
|
|
1383
1754
|
entry.health = status?.isGitRepo ? dirty ? "dirty" : "online" : "degraded";
|
|
1755
|
+
assignFullGitSnapshot(entry, status);
|
|
1384
1756
|
entry.branch = status?.branch;
|
|
1385
1757
|
entry.isDirty = dirty;
|
|
1386
1758
|
entry.uncommittedChanges = uncommittedChanges;
|
|
@@ -1402,6 +1774,7 @@ async function meshStatus(ctx) {
|
|
|
1402
1774
|
const uncommittedChanges = countUncommittedChanges(status);
|
|
1403
1775
|
const dirty = isGitStatusDirty(status);
|
|
1404
1776
|
entry.health = status?.isGitRepo ? dirty ? "dirty" : "online" : "degraded";
|
|
1777
|
+
assignFullGitSnapshot(entry, status);
|
|
1405
1778
|
entry.branch = status?.branch;
|
|
1406
1779
|
entry.isDirty = dirty;
|
|
1407
1780
|
entry.uncommittedChanges = uncommittedChanges;
|
|
@@ -1477,15 +1850,35 @@ async function meshStatus(ctx) {
|
|
|
1477
1850
|
}
|
|
1478
1851
|
const relatedRepos = await collectRelatedRepoStatuses(ctx, node);
|
|
1479
1852
|
if (relatedRepos.length) entry.relatedRepos = relatedRepos;
|
|
1853
|
+
const liveSessions = await collectLiveStatusSessions(ctx, node);
|
|
1854
|
+
if (liveSessions.length > 0) {
|
|
1855
|
+
entry.sessions = liveSessions;
|
|
1856
|
+
}
|
|
1480
1857
|
results.push(entry);
|
|
1481
1858
|
}
|
|
1859
|
+
const activeWorkEvidence = (0, import_daemon_core.buildMeshActiveWork)({
|
|
1860
|
+
meshId: mesh.id,
|
|
1861
|
+
queue: (0, import_daemon_core.getQueue)(mesh.id),
|
|
1862
|
+
ledgerEntries: (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail: 500 }),
|
|
1863
|
+
nodes: results
|
|
1864
|
+
});
|
|
1482
1865
|
const response = {
|
|
1483
1866
|
meshId: mesh.id,
|
|
1484
1867
|
meshName: mesh.name,
|
|
1485
1868
|
repoIdentity: mesh.repoIdentity,
|
|
1486
1869
|
policy: mesh.policy,
|
|
1487
1870
|
refreshedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1871
|
+
sourceOfTruth: {
|
|
1872
|
+
membership: "coordinator_daemon_live_mesh",
|
|
1873
|
+
currentStatus: "live_git_and_session_probes",
|
|
1874
|
+
activeWork: "mesh_queue_file_and_local_ledger",
|
|
1875
|
+
historicalEvidenceOnly: ["recoveryHints", "ledgerSummary"]
|
|
1876
|
+
},
|
|
1488
1877
|
nodes: results,
|
|
1878
|
+
activeWork: activeWorkEvidence.activeWork,
|
|
1879
|
+
staleDirectWork: activeWorkEvidence.staleDirectWork,
|
|
1880
|
+
terminalDirectWork: activeWorkEvidence.terminalDirectWork,
|
|
1881
|
+
activeWorkSummary: activeWorkEvidence.summary,
|
|
1489
1882
|
branchConvergenceSummary: summarizeBranchConvergence(results)
|
|
1490
1883
|
};
|
|
1491
1884
|
try {
|
|
@@ -1493,13 +1886,7 @@ async function meshStatus(ctx) {
|
|
|
1493
1886
|
} catch {
|
|
1494
1887
|
}
|
|
1495
1888
|
try {
|
|
1496
|
-
|
|
1497
|
-
if (ctx.transport instanceof IpcTransport) {
|
|
1498
|
-
const eventsResult = await ctx.transport.command("get_pending_mesh_events", {});
|
|
1499
|
-
pendingEvents = Array.isArray(eventsResult?.events) ? eventsResult.events : [];
|
|
1500
|
-
} else if (isLocalTransport(ctx.transport)) {
|
|
1501
|
-
pendingEvents = (0, import_daemon_core.drainPendingMeshCoordinatorEvents)();
|
|
1502
|
-
}
|
|
1889
|
+
const pendingEvents = await drainCoordinatorPendingEvents(ctx);
|
|
1503
1890
|
if (pendingEvents.length > 0) {
|
|
1504
1891
|
response.pendingCoordinatorEvents = pendingEvents;
|
|
1505
1892
|
}
|
|
@@ -1509,11 +1896,17 @@ async function meshStatus(ctx) {
|
|
|
1509
1896
|
}
|
|
1510
1897
|
async function meshTaskHistory(ctx, args) {
|
|
1511
1898
|
const { mesh } = ctx;
|
|
1899
|
+
const pendingEvents = await drainCoordinatorPendingEvents(ctx);
|
|
1512
1900
|
const tail = typeof args.tail === "number" && args.tail > 0 ? args.tail : 20;
|
|
1513
1901
|
const kind = typeof args.kind === "string" && args.kind.trim() ? [args.kind.trim()] : void 0;
|
|
1514
1902
|
const entries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail, kind });
|
|
1515
1903
|
const summary = (0, import_daemon_core.getLedgerSummary)(mesh.id);
|
|
1516
|
-
return JSON.stringify({
|
|
1904
|
+
return JSON.stringify({
|
|
1905
|
+
meshId: mesh.id,
|
|
1906
|
+
entries,
|
|
1907
|
+
summary,
|
|
1908
|
+
...pendingEvents.length > 0 ? { pendingCoordinatorEvents: pendingEvents } : {}
|
|
1909
|
+
}, null, 2);
|
|
1517
1910
|
}
|
|
1518
1911
|
async function meshReconcileLedger(ctx, args) {
|
|
1519
1912
|
await refreshMeshFromDaemon(ctx);
|
|
@@ -1603,6 +1996,9 @@ async function meshListNodes(ctx) {
|
|
|
1603
1996
|
nodeId: n.id,
|
|
1604
1997
|
workspace: n.workspace,
|
|
1605
1998
|
repoRoot: n.repoRoot,
|
|
1999
|
+
daemonId: readNodeDaemonId(n),
|
|
2000
|
+
machineId: readNodeMachineId(n),
|
|
2001
|
+
machine: buildNodeMachineIdentity(ctx, n),
|
|
1606
2002
|
isLocalWorktree: n.isLocalWorktree,
|
|
1607
2003
|
policy: n.policy,
|
|
1608
2004
|
relatedRepos: readRelatedRepos(n),
|
|
@@ -1612,12 +2008,13 @@ async function meshListNodes(ctx) {
|
|
|
1612
2008
|
}, null, 2);
|
|
1613
2009
|
}
|
|
1614
2010
|
async function meshEnqueueTask(ctx, args) {
|
|
2011
|
+
const taskMode = readString(args.task_mode) || readString(args.taskMode);
|
|
1615
2012
|
try {
|
|
1616
|
-
const task = (0, import_daemon_core.enqueueTask)(ctx.mesh.id, args.message);
|
|
2013
|
+
const task = (0, import_daemon_core.enqueueTask)(ctx.mesh.id, args.message, { taskMode });
|
|
1617
2014
|
if (isLocalTransport(ctx.transport) && !(ctx.transport instanceof IpcTransport)) {
|
|
1618
2015
|
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
1619
2016
|
});
|
|
1620
|
-
return JSON.stringify({ success: true, taskId: task.id, status: task.status });
|
|
2017
|
+
return JSON.stringify({ success: true, source: "queue", taskId: task.id, status: task.status, taskMode: task.taskMode });
|
|
1621
2018
|
}
|
|
1622
2019
|
if (ctx.transport instanceof IpcTransport) {
|
|
1623
2020
|
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
@@ -1630,11 +2027,24 @@ async function meshEnqueueTask(ctx, args) {
|
|
|
1630
2027
|
ipcDispatchToRemoteAgent(ctx, node, { message: args.message }).then((result) => {
|
|
1631
2028
|
if (result.success) {
|
|
1632
2029
|
try {
|
|
2030
|
+
const providerType = result.providerType;
|
|
2031
|
+
const descriptor = summarizeTaskMessage(args.message);
|
|
1633
2032
|
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
1634
2033
|
kind: "task_dispatched",
|
|
1635
2034
|
nodeId: node.id,
|
|
1636
2035
|
sessionId: result.sessionId,
|
|
1637
|
-
|
|
2036
|
+
providerType,
|
|
2037
|
+
payload: {
|
|
2038
|
+
source: "queue",
|
|
2039
|
+
via: "p2p_direct",
|
|
2040
|
+
taskId: task.id,
|
|
2041
|
+
message: args.message,
|
|
2042
|
+
taskTitle: descriptor.taskTitle,
|
|
2043
|
+
taskSummary: descriptor.taskSummary,
|
|
2044
|
+
...task.taskMode ? { taskMode: task.taskMode } : {},
|
|
2045
|
+
...providerType ? { providerType } : {},
|
|
2046
|
+
targetSessionId: result.sessionId
|
|
2047
|
+
}
|
|
1638
2048
|
});
|
|
1639
2049
|
} catch {
|
|
1640
2050
|
}
|
|
@@ -1645,22 +2055,33 @@ async function meshEnqueueTask(ctx, args) {
|
|
|
1645
2055
|
}
|
|
1646
2056
|
Promise.all(dispatchPromises).catch(() => {
|
|
1647
2057
|
});
|
|
1648
|
-
return JSON.stringify({ success: true, taskId: task.id, status: task.status });
|
|
2058
|
+
return JSON.stringify({ success: true, source: "queue", taskId: task.id, status: task.status, taskMode: task.taskMode });
|
|
1649
2059
|
}
|
|
1650
|
-
return JSON.stringify({ success: true, taskId: task.id, status: task.status });
|
|
2060
|
+
return JSON.stringify({ success: true, source: "queue", taskId: task.id, status: task.status, taskMode: task.taskMode });
|
|
1651
2061
|
} catch (e) {
|
|
1652
|
-
|
|
2062
|
+
const message = e?.message || String(e);
|
|
2063
|
+
if (message.includes("live_debug_readonly_guardrail_violation")) {
|
|
2064
|
+
return JSON.stringify({ success: false, code: "live_debug_readonly_guardrail_violation", taskMode, error: message });
|
|
2065
|
+
}
|
|
2066
|
+
return JSON.stringify({ success: false, error: message });
|
|
1653
2067
|
}
|
|
1654
2068
|
}
|
|
1655
2069
|
async function meshViewQueue(ctx, args) {
|
|
1656
2070
|
try {
|
|
2071
|
+
await refreshMeshFromDaemon(ctx);
|
|
1657
2072
|
const statusFilter = sanitizeQueueStatusFilter(args.status);
|
|
1658
2073
|
const view = normalizeQueueViewMode(args.view);
|
|
1659
|
-
const fullQueue = annotateQueueStaleness((0, import_daemon_core.getQueue)(ctx.mesh.id), ctx.mesh);
|
|
2074
|
+
const fullQueue = prioritizeActiveQueueRows(annotateQueueStaleness((0, import_daemon_core.getQueue)(ctx.mesh.id), ctx.mesh));
|
|
1660
2075
|
const queue = filterQueueForView(fullQueue, view, statusFilter);
|
|
1661
2076
|
const summary = buildQueueStatusSummary(fullQueue);
|
|
1662
2077
|
const visibleSummary = buildQueueStatusSummary(queue);
|
|
1663
2078
|
const maintenance = buildQueueMaintenanceReport(fullQueue);
|
|
2079
|
+
const activeWorkEvidence = (0, import_daemon_core.buildMeshActiveWork)({
|
|
2080
|
+
meshId: ctx.mesh.id,
|
|
2081
|
+
queue: fullQueue,
|
|
2082
|
+
ledgerEntries: (0, import_daemon_core.readLedgerEntries)(ctx.mesh.id, { tail: 500 }),
|
|
2083
|
+
nodes: ctx.mesh.nodes
|
|
2084
|
+
});
|
|
1664
2085
|
const staleAssignedTasks = maintenance.staleAssignedTasks || [];
|
|
1665
2086
|
const requestedHistoricalRows = queue.some((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
|
|
1666
2087
|
return JSON.stringify({
|
|
@@ -1678,6 +2099,9 @@ async function meshViewQueue(ctx, args) {
|
|
|
1678
2099
|
},
|
|
1679
2100
|
queue,
|
|
1680
2101
|
visibleQueue: queue,
|
|
2102
|
+
activeWork: activeWorkEvidence.activeWork,
|
|
2103
|
+
staleDirectWork: activeWorkEvidence.staleDirectWork,
|
|
2104
|
+
activeWorkSummary: activeWorkEvidence.summary,
|
|
1681
2105
|
visibleSummary,
|
|
1682
2106
|
summary,
|
|
1683
2107
|
activeCounts: summary.activeCounts,
|
|
@@ -1711,6 +2135,10 @@ async function meshQueueCancel(ctx, args) {
|
|
|
1711
2135
|
if (!taskId) return JSON.stringify({ success: false, error: "task_id required" });
|
|
1712
2136
|
const task = (0, import_daemon_core.cancelTask)(ctx.mesh.id, taskId, { reason: args.reason });
|
|
1713
2137
|
if (!task) return JSON.stringify({ success: false, error: `Queue task '${taskId}' not found` });
|
|
2138
|
+
if (isLocalTransport(ctx.transport)) {
|
|
2139
|
+
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
2140
|
+
});
|
|
2141
|
+
}
|
|
1714
2142
|
return JSON.stringify({ success: true, task }, null, 2);
|
|
1715
2143
|
} catch (e) {
|
|
1716
2144
|
return JSON.stringify({ success: false, error: e.message });
|
|
@@ -1741,6 +2169,19 @@ async function meshQueueRequeue(ctx, args) {
|
|
|
1741
2169
|
}
|
|
1742
2170
|
}
|
|
1743
2171
|
async function meshSendTask(ctx, args) {
|
|
2172
|
+
const requestedTaskMode = readString(args.task_mode) || readString(args.taskMode);
|
|
2173
|
+
const modeValidation = (0, import_daemon_core.validateMeshTaskModeRequest)(requestedTaskMode, args.message);
|
|
2174
|
+
if (!modeValidation.valid) {
|
|
2175
|
+
return JSON.stringify({
|
|
2176
|
+
success: false,
|
|
2177
|
+
code: "live_debug_readonly_guardrail_violation",
|
|
2178
|
+
taskMode: modeValidation.taskMode || requestedTaskMode,
|
|
2179
|
+
violations: modeValidation.violations,
|
|
2180
|
+
allowedOperations: modeValidation.allowedOperations,
|
|
2181
|
+
error: `live_debug_readonly_guardrail_violation: forbidden operations (${modeValidation.violations.join(", ")})`
|
|
2182
|
+
});
|
|
2183
|
+
}
|
|
2184
|
+
const taskMode = modeValidation.taskMode;
|
|
1744
2185
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
1745
2186
|
if (node.policy?.readOnly) {
|
|
1746
2187
|
return JSON.stringify({ error: `Node '${args.node_id}' is read-only` });
|
|
@@ -1768,13 +2209,15 @@ async function meshSendTask(ctx, args) {
|
|
|
1768
2209
|
const res = await ctx.transport.meshEnqueueTask(node.daemonId, {
|
|
1769
2210
|
meshId: ctx.mesh.id,
|
|
1770
2211
|
message: args.message,
|
|
1771
|
-
targetNodeId: args.node_id
|
|
2212
|
+
targetNodeId: args.node_id,
|
|
2213
|
+
...taskMode ? { taskMode } : {}
|
|
1772
2214
|
});
|
|
1773
2215
|
return JSON.stringify(res);
|
|
1774
2216
|
}
|
|
1775
2217
|
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
1776
2218
|
if (ctx.transport instanceof IpcTransport && node.daemonId && !isLocalNode) {
|
|
1777
2219
|
const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id || ""));
|
|
2220
|
+
const taskId = (0, import_node_crypto.randomUUID)();
|
|
1778
2221
|
const result2 = await ipcDispatchToRemoteAgent(ctx, node, {
|
|
1779
2222
|
session_id: args.session_id,
|
|
1780
2223
|
message: args.message,
|
|
@@ -1783,60 +2226,123 @@ async function meshSendTask(ctx, args) {
|
|
|
1783
2226
|
if (result2.success) {
|
|
1784
2227
|
const dispatchedSessionId = args.session_id || result2.sessionId;
|
|
1785
2228
|
try {
|
|
2229
|
+
const providerType = result2.providerType || cached?.providerType;
|
|
1786
2230
|
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
1787
2231
|
kind: "task_dispatched",
|
|
1788
2232
|
nodeId: args.node_id,
|
|
1789
2233
|
sessionId: dispatchedSessionId,
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
2234
|
+
providerType,
|
|
2235
|
+
payload: buildDirectTaskPayload(args.message, "p2p_direct", {
|
|
2236
|
+
taskId,
|
|
2237
|
+
taskMode,
|
|
2238
|
+
providerType,
|
|
2239
|
+
targetSessionId: dispatchedSessionId
|
|
2240
|
+
})
|
|
1795
2241
|
});
|
|
1796
2242
|
} catch {
|
|
1797
2243
|
}
|
|
1798
2244
|
}
|
|
1799
|
-
return JSON.stringify({
|
|
2245
|
+
return JSON.stringify({
|
|
2246
|
+
...result2,
|
|
2247
|
+
nodeId: args.node_id,
|
|
2248
|
+
sessionId: result2.success ? args.session_id || result2.sessionId : args.session_id,
|
|
2249
|
+
...result2.success ? { source: "direct", taskId } : {},
|
|
2250
|
+
taskMode,
|
|
2251
|
+
...result2.success && result2.providerType ? { providerType: result2.providerType } : {},
|
|
2252
|
+
dispatched: result2.success === true
|
|
2253
|
+
});
|
|
1800
2254
|
}
|
|
1801
2255
|
if (args.session_id && isLocalTransport(ctx.transport)) {
|
|
1802
2256
|
const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id));
|
|
2257
|
+
let resolvedProviderType = cached?.providerType || "";
|
|
2258
|
+
if (!resolvedProviderType) {
|
|
2259
|
+
const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
|
|
2260
|
+
const sessions = extractStatusMetadataSessions(statusResult);
|
|
2261
|
+
const explicitSession = sessions.find((session) => readSessionRecordId(session) === args.session_id);
|
|
2262
|
+
if (!explicitSession) {
|
|
2263
|
+
return JSON.stringify({
|
|
2264
|
+
success: false,
|
|
2265
|
+
recoverable: true,
|
|
2266
|
+
code: "mesh_target_session_not_found",
|
|
2267
|
+
reason: "mesh_target_session_not_found",
|
|
2268
|
+
transport: "local_ipc",
|
|
2269
|
+
retryRecommended: true,
|
|
2270
|
+
nodeId: args.node_id,
|
|
2271
|
+
sessionId: args.session_id,
|
|
2272
|
+
error: `Local session '${args.session_id}' is not present in live status for node '${args.node_id}'.`,
|
|
2273
|
+
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.`
|
|
2274
|
+
});
|
|
2275
|
+
}
|
|
2276
|
+
resolvedProviderType = resolveSessionProviderType(explicitSession);
|
|
2277
|
+
if (resolvedProviderType) {
|
|
2278
|
+
meshSessionProviderMetadata.set(meshSessionCacheKey(args.node_id, args.session_id), {
|
|
2279
|
+
providerType: resolvedProviderType,
|
|
2280
|
+
providerSessionId: readString(explicitSession?.providerSessionId) || void 0
|
|
2281
|
+
});
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
if (!resolvedProviderType) {
|
|
2285
|
+
return JSON.stringify({
|
|
2286
|
+
success: false,
|
|
2287
|
+
recoverable: true,
|
|
2288
|
+
code: "mesh_target_session_provider_unknown",
|
|
2289
|
+
reason: "mesh_target_session_provider_unknown",
|
|
2290
|
+
transport: "local_ipc",
|
|
2291
|
+
retryRecommended: false,
|
|
2292
|
+
nodeId: args.node_id,
|
|
2293
|
+
sessionId: args.session_id,
|
|
2294
|
+
error: `Local session '${args.session_id}' is live but does not expose providerType/cliType, so agent_command cannot be routed safely.`,
|
|
2295
|
+
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.`
|
|
2296
|
+
});
|
|
2297
|
+
}
|
|
1803
2298
|
const dispatchResult = await commandForNode(ctx, node, "agent_command", {
|
|
1804
2299
|
targetSessionId: args.session_id,
|
|
1805
|
-
|
|
2300
|
+
agentType: resolvedProviderType,
|
|
2301
|
+
cliType: resolvedProviderType,
|
|
2302
|
+
providerType: resolvedProviderType,
|
|
1806
2303
|
action: "send_chat",
|
|
1807
2304
|
message: args.message
|
|
1808
2305
|
});
|
|
1809
2306
|
const dispatchPayload = unwrapCommandPayload(dispatchResult);
|
|
1810
2307
|
if (dispatchPayload?.success === false || dispatchResult?.success === false) {
|
|
2308
|
+
const source = dispatchPayload?.success === false ? dispatchPayload : dispatchResult;
|
|
1811
2309
|
return JSON.stringify({
|
|
2310
|
+
...source && typeof source === "object" ? source : {},
|
|
1812
2311
|
success: false,
|
|
1813
2312
|
nodeId: args.node_id,
|
|
1814
2313
|
sessionId: args.session_id,
|
|
1815
2314
|
error: dispatchPayload?.error || dispatchResult?.error || "agent_command rejected the task"
|
|
1816
2315
|
});
|
|
1817
2316
|
}
|
|
2317
|
+
const taskId = (0, import_node_crypto.randomUUID)();
|
|
1818
2318
|
try {
|
|
1819
2319
|
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
1820
2320
|
kind: "task_dispatched",
|
|
1821
2321
|
nodeId: args.node_id,
|
|
1822
2322
|
sessionId: args.session_id,
|
|
1823
|
-
providerType:
|
|
1824
|
-
payload:
|
|
2323
|
+
providerType: resolvedProviderType,
|
|
2324
|
+
payload: buildDirectTaskPayload(args.message, "local_direct", {
|
|
2325
|
+
taskId,
|
|
2326
|
+
taskMode,
|
|
2327
|
+
providerType: resolvedProviderType,
|
|
2328
|
+
targetSessionId: args.session_id
|
|
2329
|
+
})
|
|
1825
2330
|
});
|
|
1826
2331
|
} catch {
|
|
1827
2332
|
}
|
|
1828
|
-
return JSON.stringify({ success: true, dispatched: true, nodeId: args.node_id, sessionId: args.session_id });
|
|
2333
|
+
return JSON.stringify({ success: true, dispatched: true, source: "direct", taskId, taskMode, providerType: resolvedProviderType, nodeId: args.node_id, sessionId: args.session_id });
|
|
1829
2334
|
}
|
|
1830
2335
|
const task = (0, import_daemon_core.enqueueTask)(ctx.mesh.id, args.message, {
|
|
1831
2336
|
targetNodeId: args.node_id,
|
|
1832
|
-
targetSessionId: args.session_id
|
|
2337
|
+
targetSessionId: args.session_id,
|
|
2338
|
+
taskMode
|
|
1833
2339
|
});
|
|
1834
2340
|
if (isLocalTransport(ctx.transport) || ctx.transport instanceof IpcTransport) {
|
|
1835
2341
|
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
1836
2342
|
});
|
|
1837
2343
|
}
|
|
1838
|
-
const pendingEvents = isLocalTransport(ctx.transport) ? (0, import_daemon_core.drainPendingMeshCoordinatorEvents)() : [];
|
|
1839
|
-
const result = { success: true, nodeId: args.node_id, taskId: task.id, status: task.status };
|
|
2344
|
+
const pendingEvents = isLocalTransport(ctx.transport) ? (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id) : [];
|
|
2345
|
+
const result = { success: true, source: "queue", nodeId: args.node_id, taskId: task.id, status: task.status, taskMode: task.taskMode };
|
|
1840
2346
|
if (pendingEvents.length > 0) {
|
|
1841
2347
|
result.pendingCoordinatorEvents = pendingEvents;
|
|
1842
2348
|
}
|
|
@@ -1856,6 +2362,9 @@ async function meshReadChat(ctx, args) {
|
|
|
1856
2362
|
if (!node) {
|
|
1857
2363
|
return JSON.stringify(buildMissingNodeReadChatRecovery(ctx, args), null, 2);
|
|
1858
2364
|
}
|
|
2365
|
+
if (ctx.transport instanceof IpcTransport || isLocalTransport(ctx.transport)) {
|
|
2366
|
+
await drainCoordinatorPendingEvents(ctx, { nodeIds: [args.node_id] });
|
|
2367
|
+
}
|
|
1859
2368
|
if (isLocalTransport(ctx.transport)) {
|
|
1860
2369
|
const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id));
|
|
1861
2370
|
const providerSessionId = typeof args.provider_session_id === "string" && args.provider_session_id.trim() ? args.provider_session_id.trim() : cached?.providerSessionId;
|
|
@@ -1958,6 +2467,10 @@ async function meshLaunchSession(ctx, args) {
|
|
|
1958
2467
|
const coordinatorNode = resolveCoordinatorNode(ctx);
|
|
1959
2468
|
const coordinatorDaemonId = coordinatorNode?.daemonId || ctx.localDaemonId;
|
|
1960
2469
|
const spawnedSessionVisibility = readSpawnedSessionVisibility(ctx.mesh.policy);
|
|
2470
|
+
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
2471
|
+
if (node.daemonId && !isLocalNode && !coordinatorDaemonId) {
|
|
2472
|
+
return JSON.stringify(buildMissingCoordinatorDaemonIdFailure(ctx, node, resolvedProviderType), null, 2);
|
|
2473
|
+
}
|
|
1961
2474
|
let result;
|
|
1962
2475
|
try {
|
|
1963
2476
|
result = await commandForNode(ctx, node, "launch_cli", {
|
|
@@ -1998,7 +2511,6 @@ async function meshLaunchSession(ctx, args) {
|
|
|
1998
2511
|
});
|
|
1999
2512
|
} catch {
|
|
2000
2513
|
}
|
|
2001
|
-
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
2002
2514
|
if (ctx.transport instanceof IpcTransport && node.daemonId && !isLocalNode) {
|
|
2003
2515
|
ctx.transport.meshCommand(node.daemonId, "trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
2004
2516
|
});
|
|
@@ -2023,6 +2535,9 @@ async function meshLaunchSession(ctx, args) {
|
|
|
2023
2535
|
const coordinatorNode = resolveCoordinatorNode(ctx);
|
|
2024
2536
|
const coordinatorDaemonId = coordinatorNode?.daemonId || ctx.localDaemonId;
|
|
2025
2537
|
const spawnedSessionVisibility = readSpawnedSessionVisibility(ctx.mesh.policy);
|
|
2538
|
+
if (!coordinatorDaemonId) {
|
|
2539
|
+
return JSON.stringify(buildMissingCoordinatorDaemonIdFailure(ctx, node, resolvedProviderType), null, 2);
|
|
2540
|
+
}
|
|
2026
2541
|
try {
|
|
2027
2542
|
const res = await ctx.transport.launch(node.daemonId, {
|
|
2028
2543
|
type: resolvedProviderType,
|
|
@@ -2103,6 +2618,51 @@ async function meshGitStatus(ctx, args) {
|
|
|
2103
2618
|
}, null, 2);
|
|
2104
2619
|
}
|
|
2105
2620
|
}
|
|
2621
|
+
async function meshFastForwardNode(ctx, args) {
|
|
2622
|
+
await refreshMeshFromDaemon(ctx);
|
|
2623
|
+
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2624
|
+
const submoduleIgnorePaths = node.policy?.submoduleIgnorePaths || [];
|
|
2625
|
+
if (node.policy?.readOnly) {
|
|
2626
|
+
return JSON.stringify({
|
|
2627
|
+
success: false,
|
|
2628
|
+
code: "node_read_only",
|
|
2629
|
+
nodeId: args.node_id,
|
|
2630
|
+
workspace: node.workspace,
|
|
2631
|
+
allowed: false,
|
|
2632
|
+
willRun: false,
|
|
2633
|
+
executed: false,
|
|
2634
|
+
blockingReasons: ["node_read_only"]
|
|
2635
|
+
}, null, 2);
|
|
2636
|
+
}
|
|
2637
|
+
try {
|
|
2638
|
+
const dryRun = args.dry_run === true || args.execute !== true;
|
|
2639
|
+
const result = await commandForNode(ctx, node, "fast_forward_mesh_node", {
|
|
2640
|
+
meshId: ctx.mesh.id,
|
|
2641
|
+
nodeId: node.id,
|
|
2642
|
+
workspace: node.workspace,
|
|
2643
|
+
branch: typeof args.branch === "string" ? args.branch : void 0,
|
|
2644
|
+
execute: args.execute === true && args.dry_run !== true,
|
|
2645
|
+
dryRun,
|
|
2646
|
+
updateSubmodules: args.update_submodules === true,
|
|
2647
|
+
submoduleIgnorePaths: submoduleIgnorePaths.length > 0 ? submoduleIgnorePaths : void 0
|
|
2648
|
+
});
|
|
2649
|
+
return JSON.stringify(unwrapCommandPayload(result), null, 2);
|
|
2650
|
+
} catch (e) {
|
|
2651
|
+
const failure = buildCoordinatorP2pRelayFailure(e, {
|
|
2652
|
+
command: "fast_forward_mesh_node",
|
|
2653
|
+
targetDaemonId: node.daemonId,
|
|
2654
|
+
nodeId: args.node_id
|
|
2655
|
+
});
|
|
2656
|
+
return JSON.stringify({
|
|
2657
|
+
...failure,
|
|
2658
|
+
workspace: node.workspace,
|
|
2659
|
+
allowed: false,
|
|
2660
|
+
willRun: false,
|
|
2661
|
+
executed: false,
|
|
2662
|
+
blockingReasons: [failure.code || "mesh_fast_forward_unavailable"]
|
|
2663
|
+
}, null, 2);
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2106
2666
|
async function meshCheckpoint(ctx, args) {
|
|
2107
2667
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2108
2668
|
if (node.policy?.readOnly) {
|
|
@@ -2188,6 +2748,7 @@ async function meshCloneNode(ctx, args) {
|
|
|
2188
2748
|
if (existingIndex >= 0) ctx.mesh.nodes[existingIndex] = clonePayload.node;
|
|
2189
2749
|
else ctx.mesh.nodes.push(clonePayload.node);
|
|
2190
2750
|
ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2751
|
+
await syncCoordinatorDaemonMeshCache(ctx);
|
|
2191
2752
|
}
|
|
2192
2753
|
return JSON.stringify(result, null, 2);
|
|
2193
2754
|
} else if (!isLocalTransport(ctx.transport) && sourceNode.daemonId) {
|
|
@@ -2205,6 +2766,7 @@ async function meshCloneNode(ctx, args) {
|
|
|
2205
2766
|
if (existingIndex >= 0) ctx.mesh.nodes[existingIndex] = clonePayload.node;
|
|
2206
2767
|
else ctx.mesh.nodes.push(clonePayload.node);
|
|
2207
2768
|
ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2769
|
+
await syncCoordinatorDaemonMeshCache(ctx);
|
|
2208
2770
|
}
|
|
2209
2771
|
return JSON.stringify(res, null, 2);
|
|
2210
2772
|
} catch (e) {
|
|
@@ -2300,6 +2862,43 @@ async function meshRemoveNode(ctx, args) {
|
|
|
2300
2862
|
return JSON.stringify({ error: "Cloud mesh remove_node requires node daemonId" });
|
|
2301
2863
|
}
|
|
2302
2864
|
}
|
|
2865
|
+
function resolveRefineConfigNode(ctx, nodeId) {
|
|
2866
|
+
if (nodeId) return findNode(ctx.mesh, nodeId);
|
|
2867
|
+
const node = ctx.mesh.nodes.find((entry) => !!entry.workspace);
|
|
2868
|
+
if (!node) throw new Error("No mesh node with a workspace is available");
|
|
2869
|
+
return node;
|
|
2870
|
+
}
|
|
2871
|
+
async function meshRefineConfigSchema(ctx) {
|
|
2872
|
+
const node = resolveRefineConfigNode(ctx);
|
|
2873
|
+
const result = await commandForNode(ctx, node, "get_mesh_refine_config_schema", {});
|
|
2874
|
+
return JSON.stringify(result, null, 2);
|
|
2875
|
+
}
|
|
2876
|
+
async function meshValidateRefineConfig(ctx, args) {
|
|
2877
|
+
const node = resolveRefineConfigNode(ctx, args.node_id);
|
|
2878
|
+
const result = await commandForNode(ctx, node, "validate_mesh_refine_config", {
|
|
2879
|
+
workspace: node.workspace,
|
|
2880
|
+
inlineMesh: ctx.mesh,
|
|
2881
|
+
...args.config ? { config: args.config } : {}
|
|
2882
|
+
});
|
|
2883
|
+
return JSON.stringify(result, null, 2);
|
|
2884
|
+
}
|
|
2885
|
+
async function meshSuggestRefineConfig(ctx, args) {
|
|
2886
|
+
const node = resolveRefineConfigNode(ctx, args.node_id);
|
|
2887
|
+
const result = await commandForNode(ctx, node, "suggest_mesh_refine_config", {
|
|
2888
|
+
workspace: node.workspace,
|
|
2889
|
+
inlineMesh: ctx.mesh
|
|
2890
|
+
});
|
|
2891
|
+
return JSON.stringify(result, null, 2);
|
|
2892
|
+
}
|
|
2893
|
+
async function meshRefinePlan(ctx, args) {
|
|
2894
|
+
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2895
|
+
const result = await commandForNode(ctx, node, "plan_mesh_refine_node", {
|
|
2896
|
+
meshId: ctx.mesh.id,
|
|
2897
|
+
nodeId: args.node_id,
|
|
2898
|
+
inlineMesh: ctx.mesh
|
|
2899
|
+
});
|
|
2900
|
+
return JSON.stringify(result, null, 2);
|
|
2901
|
+
}
|
|
2303
2902
|
async function meshRefineNode(ctx, args) {
|
|
2304
2903
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2305
2904
|
if (isLocalTransport(ctx.transport)) {
|
|
@@ -2308,7 +2907,7 @@ async function meshRefineNode(ctx, args) {
|
|
|
2308
2907
|
nodeId: args.node_id,
|
|
2309
2908
|
inlineMesh: ctx.mesh
|
|
2310
2909
|
});
|
|
2311
|
-
if (result?.success && result.removeResult?.removed !== false) {
|
|
2910
|
+
if (result?.success && result.async !== true && result.removeResult?.removed !== false) {
|
|
2312
2911
|
const idx = ctx.mesh.nodes.findIndex((n) => n.id === args.node_id);
|
|
2313
2912
|
if (idx >= 0) {
|
|
2314
2913
|
ctx.mesh.nodes.splice(idx, 1);
|
|
@@ -2323,7 +2922,7 @@ async function meshRefineNode(ctx, args) {
|
|
|
2323
2922
|
nodeId: args.node_id,
|
|
2324
2923
|
inlineMesh: ctx.mesh
|
|
2325
2924
|
});
|
|
2326
|
-
if (res?.success && res.removeResult?.removed !== false) {
|
|
2925
|
+
if (res?.success && res.async !== true && res.removeResult?.removed !== false) {
|
|
2327
2926
|
const idx = ctx.mesh.nodes.findIndex((n) => n.id === args.node_id);
|
|
2328
2927
|
if (idx >= 0) {
|
|
2329
2928
|
ctx.mesh.nodes.splice(idx, 1);
|
|
@@ -2360,13 +2959,13 @@ var STANDARD_TOOLS = [
|
|
|
2360
2959
|
function buildMcpHelpText() {
|
|
2361
2960
|
const meshTools = ALL_MESH_TOOLS.map((tool) => tool.name);
|
|
2362
2961
|
return `
|
|
2363
|
-
|
|
2962
|
+
ADHDev MCP Server
|
|
2364
2963
|
|
|
2365
2964
|
Usage:
|
|
2366
|
-
adhdev
|
|
2367
|
-
adhdev
|
|
2368
|
-
adhdev
|
|
2369
|
-
adhdev-mcp --
|
|
2965
|
+
adhdev mcp Local mode (requires standalone daemon)
|
|
2966
|
+
adhdev mcp --api-key <key> Cloud mode (ADHDev cloud API)
|
|
2967
|
+
adhdev mcp --mode ipc --repo-mesh <mesh_id> Cloud daemon IPC mesh mode
|
|
2968
|
+
adhdev-mcp --help Compatibility bin (same server, legacy package entrypoint)
|
|
2370
2969
|
|
|
2371
2970
|
Options:
|
|
2372
2971
|
--mode <mode> Transport: local, cloud, or ipc
|
|
@@ -2391,6 +2990,7 @@ Mesh tools: ${meshTools.join(", ")}
|
|
|
2391
2990
|
// src/server.ts
|
|
2392
2991
|
var import_server = require("@modelcontextprotocol/sdk/server/index.js");
|
|
2393
2992
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
2993
|
+
var import_node_os = __toESM(require("os"));
|
|
2394
2994
|
var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
2395
2995
|
|
|
2396
2996
|
// src/transports/local.ts
|
|
@@ -2941,6 +3541,22 @@ function formatChatResult(result, sessionId, format, limit = 50, compact = false
|
|
|
2941
3541
|
}))
|
|
2942
3542
|
}, null, 2);
|
|
2943
3543
|
}
|
|
3544
|
+
if ((format === "text" || format === void 0) && compact && compactPayload) {
|
|
3545
|
+
const lines2 = outputMessages.slice(-limit).map((m) => {
|
|
3546
|
+
const role = m.role === "user" ? "User" : m.role === "assistant" ? "Agent" : m.role;
|
|
3547
|
+
const content = messageContent(m);
|
|
3548
|
+
const truncated = content.length > 500 ? `${content.slice(0, 500)}\u2026` : content;
|
|
3549
|
+
return `[${role}] ${truncated}`;
|
|
3550
|
+
});
|
|
3551
|
+
if (compactPayload.summary) {
|
|
3552
|
+
const truncatedSummary = compactPayload.summary.length > 500 ? `${compactPayload.summary.slice(0, 500)}\u2026` : compactPayload.summary;
|
|
3553
|
+
lines2.push(`[Summary] ${truncatedSummary}`);
|
|
3554
|
+
}
|
|
3555
|
+
if (result?.pollingAdvisory) {
|
|
3556
|
+
lines2.push(`Advisory: ${result.pollingAdvisory.message}`);
|
|
3557
|
+
}
|
|
3558
|
+
return lines2.length > 0 ? lines2.join("\n\n") : "No messages in chat.";
|
|
3559
|
+
}
|
|
2944
3560
|
if (outputMessages.length === 0) {
|
|
2945
3561
|
return result?.pollingAdvisory ? `No messages in chat.
|
|
2946
3562
|
|
|
@@ -3908,6 +4524,7 @@ async function startMcpServer(opts) {
|
|
|
3908
4524
|
requirePreTaskCheckpoint: false,
|
|
3909
4525
|
requirePostTaskCheckpoint: true,
|
|
3910
4526
|
requireApprovalForPush: true,
|
|
4527
|
+
allowAutoPublishSubmoduleMainCommits: false,
|
|
3911
4528
|
requireApprovalForDestructiveGit: true,
|
|
3912
4529
|
dirtyWorkspaceBehavior: "warn",
|
|
3913
4530
|
maxParallelTasks: 2,
|
|
@@ -3964,6 +4581,7 @@ async function startMcpServer(opts) {
|
|
|
3964
4581
|
}
|
|
3965
4582
|
let localDaemonId;
|
|
3966
4583
|
let localMachineId;
|
|
4584
|
+
let coordinatorHostname = import_node_os.default.hostname();
|
|
3967
4585
|
if (transport instanceof LocalTransport || transport instanceof IpcTransport) {
|
|
3968
4586
|
try {
|
|
3969
4587
|
const { loadConfig } = await import("@adhdev/daemon-core");
|
|
@@ -3976,11 +4594,13 @@ async function startMcpServer(opts) {
|
|
|
3976
4594
|
try {
|
|
3977
4595
|
const statusResult = await transport.getStatus();
|
|
3978
4596
|
const instanceId = typeof statusResult?.status?.instanceId === "string" ? statusResult.status.instanceId.trim() : "";
|
|
4597
|
+
const hostname = typeof statusResult?.status?.hostname === "string" ? statusResult.status.hostname.trim() : typeof statusResult?.status?.machine?.hostname === "string" ? statusResult.status.machine.hostname.trim() : "";
|
|
3979
4598
|
if (instanceId) localDaemonId = instanceId;
|
|
4599
|
+
if (hostname) coordinatorHostname = hostname;
|
|
3980
4600
|
} catch {
|
|
3981
4601
|
}
|
|
3982
4602
|
}
|
|
3983
|
-
const meshCtx = { mesh, transport, ...localDaemonId ? { localDaemonId } : {}, ...localMachineId ? { localMachineId } : {} };
|
|
4603
|
+
const meshCtx = { mesh, transport, ...localDaemonId ? { localDaemonId } : {}, ...localMachineId ? { localMachineId } : {}, ...coordinatorHostname ? { coordinatorHostname } : {} };
|
|
3984
4604
|
const coordinatorPrompt = await buildMeshModeCoordinatorPrompt(mesh);
|
|
3985
4605
|
const server2 = new import_server.Server(
|
|
3986
4606
|
{ name: "adhdev-mcp-server", version: "0.9.81" },
|
|
@@ -4041,6 +4661,9 @@ async function startMcpServer(opts) {
|
|
|
4041
4661
|
case "mesh_git_status":
|
|
4042
4662
|
text = await meshGitStatus(meshCtx, a);
|
|
4043
4663
|
break;
|
|
4664
|
+
case "mesh_fast_forward_node":
|
|
4665
|
+
text = await meshFastForwardNode(meshCtx, a);
|
|
4666
|
+
break;
|
|
4044
4667
|
case "mesh_checkpoint":
|
|
4045
4668
|
text = await meshCheckpoint(meshCtx, a);
|
|
4046
4669
|
break;
|
|
@@ -4056,6 +4679,18 @@ async function startMcpServer(opts) {
|
|
|
4056
4679
|
case "mesh_refine_node":
|
|
4057
4680
|
text = await meshRefineNode(meshCtx, a);
|
|
4058
4681
|
break;
|
|
4682
|
+
case "mesh_refine_config_schema":
|
|
4683
|
+
text = await meshRefineConfigSchema(meshCtx);
|
|
4684
|
+
break;
|
|
4685
|
+
case "mesh_validate_refine_config":
|
|
4686
|
+
text = await meshValidateRefineConfig(meshCtx, a);
|
|
4687
|
+
break;
|
|
4688
|
+
case "mesh_suggest_refine_config":
|
|
4689
|
+
text = await meshSuggestRefineConfig(meshCtx, a);
|
|
4690
|
+
break;
|
|
4691
|
+
case "mesh_refine_plan":
|
|
4692
|
+
text = await meshRefinePlan(meshCtx, a);
|
|
4693
|
+
break;
|
|
4059
4694
|
case "mesh_cleanup_sessions":
|
|
4060
4695
|
text = await meshCleanupSessions(meshCtx, a);
|
|
4061
4696
|
break;
|