@adhdev/daemon-standalone 0.9.82-rc.1 → 0.9.82-rc.100
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 +26055 -21227
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/public/assets/index-Bso1b8Lh.css +1 -0
- package/public/assets/index-DaIkPFUd.js +99 -0
- package/public/assets/{terminal-Cz61jYPm.js → terminal-D46M5EWH.js} +4 -4
- package/public/assets/vendor-CgiI0UIA.js +2745 -0
- package/public/index.html +3 -3
- package/vendor/mcp-server/index.js +883 -75
- package/vendor/mcp-server/index.js.map +1 -1
- package/public/assets/index-01wE493H.css +0 -1
- package/public/assets/index-BU3NAjAr.js +0 -98
- package/public/assets/vendor-CLec0455.js +0 -2723
|
@@ -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. After a terminal signal, call mesh_read_chat once with compact=true, then verify git state if repository changes were expected.",
|
|
279
|
+
message: "Do not repeatedly poll mesh_status/mesh_view_queue/mesh_read_chat while delegated work is generating; these snapshots rarely change until the worker emits a completion/status event."
|
|
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,22 @@ 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 isWorkerTaskMode(taskMode) {
|
|
482
|
+
return taskMode !== "live_debug_readonly";
|
|
483
|
+
}
|
|
371
484
|
function addSessionRecord(target, session) {
|
|
372
485
|
if (!session || typeof session !== "object" || isTerminalSessionRecord(session)) return;
|
|
373
486
|
const sessionId = readSessionRecordId(session);
|
|
@@ -436,18 +549,26 @@ function queueAssignmentStaleReason(task, liveness) {
|
|
|
436
549
|
}
|
|
437
550
|
function buildQueueStatusSummary(queue) {
|
|
438
551
|
const counts = { pending: 0, assigned: 0, completed: 0, failed: 0, cancelled: 0 };
|
|
552
|
+
let staleAssigned = 0;
|
|
439
553
|
for (const task of queue) {
|
|
440
554
|
const status = typeof task?.status === "string" ? task.status : void 0;
|
|
441
555
|
if (status && Object.prototype.hasOwnProperty.call(counts, status)) {
|
|
442
556
|
counts[status] += 1;
|
|
443
557
|
}
|
|
558
|
+
if (status === "assigned" && task?.staleAssigned === true) staleAssigned += 1;
|
|
444
559
|
}
|
|
560
|
+
const liveAssigned = Math.max(0, counts.assigned - staleAssigned);
|
|
445
561
|
return {
|
|
446
562
|
totalCount: queue.length,
|
|
447
|
-
activeCount: counts.pending +
|
|
563
|
+
activeCount: counts.pending + liveAssigned,
|
|
448
564
|
historicalCount: counts.completed + counts.failed + counts.cancelled,
|
|
449
565
|
counts,
|
|
450
566
|
activeCounts: {
|
|
567
|
+
pending: counts.pending,
|
|
568
|
+
assigned: liveAssigned
|
|
569
|
+
},
|
|
570
|
+
staleAssignedCount: staleAssigned,
|
|
571
|
+
rawActiveCounts: {
|
|
451
572
|
pending: counts.pending,
|
|
452
573
|
assigned: counts.assigned
|
|
453
574
|
},
|
|
@@ -475,6 +596,18 @@ function filterQueueForView(queue, view, statuses) {
|
|
|
475
596
|
if (view === "historical") return queue.filter((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
|
|
476
597
|
return queue;
|
|
477
598
|
}
|
|
599
|
+
function prioritizeActiveQueueRows(queue) {
|
|
600
|
+
const active = [];
|
|
601
|
+
const historical = [];
|
|
602
|
+
const other = [];
|
|
603
|
+
for (const task of queue) {
|
|
604
|
+
const status = String(task?.status || "");
|
|
605
|
+
if (ACTIVE_QUEUE_STATUSES.has(status)) active.push(task);
|
|
606
|
+
else if (HISTORICAL_QUEUE_STATUSES.has(status)) historical.push(task);
|
|
607
|
+
else other.push(task);
|
|
608
|
+
}
|
|
609
|
+
return [...active, ...other, ...historical];
|
|
610
|
+
}
|
|
478
611
|
function slimQueueTask(task) {
|
|
479
612
|
return {
|
|
480
613
|
id: task?.id,
|
|
@@ -580,13 +713,58 @@ function isIdleSessionRecord(session) {
|
|
|
580
713
|
const chatStatus = typeof session?.activeChat?.status === "string" ? session.activeChat.status.toLowerCase() : "";
|
|
581
714
|
return status === "idle" || chatStatus === "waiting_input";
|
|
582
715
|
}
|
|
716
|
+
function isMeshOwnedDelegateSession(session, meshId, nodeId) {
|
|
717
|
+
const settings = session?.settings;
|
|
718
|
+
const sessionMeshId = typeof settings?.meshNodeFor === "string" ? settings.meshNodeFor.trim() : "";
|
|
719
|
+
const coordinatorDaemonId = typeof settings?.meshCoordinatorDaemonId === "string" ? settings.meshCoordinatorDaemonId.trim() : "";
|
|
720
|
+
const sessionNodeId = typeof settings?.meshNodeId === "string" ? settings.meshNodeId.trim() : "";
|
|
721
|
+
if (sessionMeshId !== meshId || !coordinatorDaemonId) return false;
|
|
722
|
+
return !sessionNodeId || sessionNodeId === nodeId;
|
|
723
|
+
}
|
|
583
724
|
function chooseDispatchableSession(sessions, providerType, meshId, nodeId) {
|
|
584
725
|
const live = sessions.filter((session) => !isTerminalSessionRecord(session));
|
|
585
726
|
const matchingProvider = (session) => !providerType || session?.providerType === providerType || session?.cliType === providerType;
|
|
586
727
|
const meshSessions = live.filter(
|
|
587
|
-
(session) => session
|
|
728
|
+
(session) => isMeshOwnedDelegateSession(session, meshId, nodeId)
|
|
588
729
|
);
|
|
589
|
-
return meshSessions.find((session) => isIdleSessionRecord(session) && matchingProvider(session)) || meshSessions.find(matchingProvider) ||
|
|
730
|
+
return meshSessions.find((session) => isIdleSessionRecord(session) && matchingProvider(session)) || meshSessions.find(matchingProvider) || void 0;
|
|
731
|
+
}
|
|
732
|
+
function buildRelayUnsafeRemoteSessionFailure(ctx, node, sessionId, providerType) {
|
|
733
|
+
return {
|
|
734
|
+
success: false,
|
|
735
|
+
recoverable: true,
|
|
736
|
+
code: "mesh_delegate_session_missing_relay_metadata",
|
|
737
|
+
reason: "mesh_delegate_session_missing_relay_metadata",
|
|
738
|
+
transport: "mesh_transport",
|
|
739
|
+
retryRecommended: true,
|
|
740
|
+
meshId: ctx.mesh.id,
|
|
741
|
+
nodeId: node.id,
|
|
742
|
+
daemonId: node.daemonId,
|
|
743
|
+
workspace: node.workspace,
|
|
744
|
+
sessionId,
|
|
745
|
+
...providerType ? { resolvedProviderType: providerType } : {},
|
|
746
|
+
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.`,
|
|
747
|
+
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.`,
|
|
748
|
+
noFallbackReason: "Blindly reusing a remote session without mesh relay metadata would silently drop task_completed / generating_completed events."
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
function buildMissingCoordinatorDaemonIdFailure(ctx, node, providerType) {
|
|
752
|
+
return {
|
|
753
|
+
success: false,
|
|
754
|
+
recoverable: true,
|
|
755
|
+
code: "mesh_coordinator_daemon_unknown",
|
|
756
|
+
reason: "mesh_coordinator_daemon_unknown",
|
|
757
|
+
transport: "mesh_transport",
|
|
758
|
+
retryRecommended: true,
|
|
759
|
+
meshId: ctx.mesh.id,
|
|
760
|
+
nodeId: node.id,
|
|
761
|
+
daemonId: node.daemonId,
|
|
762
|
+
workspace: node.workspace,
|
|
763
|
+
...providerType ? { resolvedProviderType: providerType } : {},
|
|
764
|
+
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.`,
|
|
765
|
+
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.",
|
|
766
|
+
noFallbackReason: "Launching without meshCoordinatorDaemonId would create a worker session that can finish work but cannot emit task_completed / generating_completed back to the coordinator."
|
|
767
|
+
};
|
|
590
768
|
}
|
|
591
769
|
function findNestedPayload(value, predicate) {
|
|
592
770
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -615,12 +793,16 @@ function extractGitDiff(value) {
|
|
|
615
793
|
}
|
|
616
794
|
function extractSubmodules(value, ignorePaths) {
|
|
617
795
|
const payload = unwrapCommandPayload(value);
|
|
618
|
-
const subs = payload?.submodules ?? value?.submodules;
|
|
796
|
+
const subs = payload?.status?.submodules ?? payload?.submodules ?? value?.status?.submodules ?? value?.submodules;
|
|
619
797
|
if (!Array.isArray(subs)) return void 0;
|
|
620
798
|
if (ignorePaths.length === 0) return subs;
|
|
621
799
|
const ignoreSet = new Set(ignorePaths);
|
|
622
800
|
return subs.filter((s) => s?.path && !ignoreSet.has(s.path));
|
|
623
801
|
}
|
|
802
|
+
function assignFullGitSnapshot(entry, status) {
|
|
803
|
+
if (!status || typeof status !== "object" || Array.isArray(status)) return;
|
|
804
|
+
entry.git = status;
|
|
805
|
+
}
|
|
624
806
|
function extractLaunchPayload(value) {
|
|
625
807
|
return findNestedPayload(value, (payload) => Boolean(payload?.sessionId || payload?.id || payload?.runtimeSessionId));
|
|
626
808
|
}
|
|
@@ -745,20 +927,76 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
|
|
|
745
927
|
let sessionId = args.session_id?.trim() || "";
|
|
746
928
|
const providerPriorityList = Array.isArray(node.policy?.providerPriority) ? node.policy.providerPriority : [];
|
|
747
929
|
let resolvedProviderType = args.providerType?.trim() || providerPriorityList[0] || "";
|
|
748
|
-
if (
|
|
930
|
+
if (sessionId && args.verifiedSession) {
|
|
931
|
+
const explicitSession = args.verifiedSession;
|
|
932
|
+
if (!isMeshOwnedDelegateSession(explicitSession, ctx.mesh.id, node.id)) {
|
|
933
|
+
return buildRelayUnsafeRemoteSessionFailure(
|
|
934
|
+
ctx,
|
|
935
|
+
node,
|
|
936
|
+
sessionId,
|
|
937
|
+
resolvedProviderType || resolveSessionProviderType(explicitSession) || void 0
|
|
938
|
+
);
|
|
939
|
+
}
|
|
940
|
+
if (!resolvedProviderType) {
|
|
941
|
+
resolvedProviderType = resolveSessionProviderType(explicitSession);
|
|
942
|
+
}
|
|
943
|
+
} else if (!sessionId || args.session_id) {
|
|
749
944
|
try {
|
|
750
945
|
const relayResult = await transport.meshCommand(daemonId, "get_status_metadata", {});
|
|
751
|
-
const
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
946
|
+
const sessions = extractStatusMetadataSessions(relayResult);
|
|
947
|
+
if (sessionId) {
|
|
948
|
+
const explicitSession = sessions.find((session) => readSessionRecordId(session) === sessionId);
|
|
949
|
+
if (!explicitSession) {
|
|
950
|
+
return {
|
|
951
|
+
success: false,
|
|
952
|
+
recoverable: true,
|
|
953
|
+
code: "mesh_target_session_not_found",
|
|
954
|
+
reason: "mesh_target_session_not_found",
|
|
955
|
+
transport: "mesh_transport",
|
|
956
|
+
retryRecommended: true,
|
|
957
|
+
meshId: ctx.mesh.id,
|
|
958
|
+
nodeId: node.id,
|
|
959
|
+
daemonId,
|
|
960
|
+
workspace: node.workspace,
|
|
961
|
+
sessionId,
|
|
962
|
+
...resolvedProviderType ? { resolvedProviderType } : {},
|
|
963
|
+
error: `Remote session '${sessionId}' is not present in the live status for node '${node.id}'.`,
|
|
964
|
+
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.`
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
if (!isMeshOwnedDelegateSession(explicitSession, ctx.mesh.id, node.id)) {
|
|
968
|
+
return buildRelayUnsafeRemoteSessionFailure(
|
|
969
|
+
ctx,
|
|
970
|
+
node,
|
|
971
|
+
sessionId,
|
|
972
|
+
resolvedProviderType || resolveSessionProviderType(explicitSession) || void 0
|
|
973
|
+
);
|
|
974
|
+
}
|
|
757
975
|
if (!resolvedProviderType) {
|
|
758
|
-
resolvedProviderType =
|
|
976
|
+
resolvedProviderType = resolveSessionProviderType(explicitSession);
|
|
977
|
+
}
|
|
978
|
+
} else {
|
|
979
|
+
const targetSession = chooseDispatchableSession(sessions, resolvedProviderType, ctx.mesh.id, node.id);
|
|
980
|
+
if (targetSession?.id || targetSession?.sessionId) {
|
|
981
|
+
sessionId = targetSession.id || targetSession.sessionId;
|
|
982
|
+
if (!resolvedProviderType) {
|
|
983
|
+
resolvedProviderType = resolveSessionProviderType(targetSession);
|
|
984
|
+
}
|
|
759
985
|
}
|
|
760
986
|
}
|
|
761
987
|
} catch (e) {
|
|
988
|
+
if (sessionId) {
|
|
989
|
+
return {
|
|
990
|
+
...buildCoordinatorP2pRelayFailure(e, {
|
|
991
|
+
command: "get_status_metadata",
|
|
992
|
+
targetDaemonId: daemonId,
|
|
993
|
+
nodeId: node.id,
|
|
994
|
+
sessionId
|
|
995
|
+
}),
|
|
996
|
+
success: false,
|
|
997
|
+
error: `Cannot verify remote session '${sessionId}' before dispatch: ${e?.message || String(e)}`
|
|
998
|
+
};
|
|
999
|
+
}
|
|
762
1000
|
}
|
|
763
1001
|
}
|
|
764
1002
|
if (!resolvedProviderType) {
|
|
@@ -788,7 +1026,7 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
|
|
|
788
1026
|
error: `P2P dispatch failed: ${errorMessage}`
|
|
789
1027
|
};
|
|
790
1028
|
}
|
|
791
|
-
return { success: true, dispatched: true, sessionId: sessionId || resolvedProviderType };
|
|
1029
|
+
return { success: true, dispatched: true, sessionId: sessionId || resolvedProviderType, providerType: resolvedProviderType };
|
|
792
1030
|
} catch (e) {
|
|
793
1031
|
const errorMessage = e?.message || String(e);
|
|
794
1032
|
return {
|
|
@@ -818,10 +1056,60 @@ function resolveCoordinatorNode(ctx) {
|
|
|
818
1056
|
return void 0;
|
|
819
1057
|
}
|
|
820
1058
|
function readNodeMachineId(node) {
|
|
821
|
-
return readString(node.machineId) || readString(node.machine_id);
|
|
1059
|
+
return readString(node.machineId) || readString(node.machine_id) || readString(node.machine?.id) || readString(node.machine?.machineId) || readString(node.lastProbe?.machineId) || readString(node.last_probe?.machine_id) || readString(node.lastProbe?.machine?.id) || readString(node.lastProbe?.machine?.machineId) || readString(node.last_probe?.machine?.id) || readString(node.last_probe?.machine?.machine_id);
|
|
822
1060
|
}
|
|
823
1061
|
function readNodeDaemonId(node) {
|
|
824
|
-
return readString(node.daemonId) || readString(node.daemon_id);
|
|
1062
|
+
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);
|
|
1063
|
+
}
|
|
1064
|
+
function normalizeHostname(value) {
|
|
1065
|
+
const hostname = readString(value);
|
|
1066
|
+
if (!hostname) return void 0;
|
|
1067
|
+
return hostname.toLowerCase().replace(/\.$/, "");
|
|
1068
|
+
}
|
|
1069
|
+
function readNodeHostname(node) {
|
|
1070
|
+
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);
|
|
1071
|
+
}
|
|
1072
|
+
function readNodeDisplayMachineName(node) {
|
|
1073
|
+
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);
|
|
1074
|
+
}
|
|
1075
|
+
function compactIdentityEvidence(value) {
|
|
1076
|
+
if (!value) return void 0;
|
|
1077
|
+
return value.length > 24 ? `${value.slice(0, 12)}\u2026${value.slice(-8)}` : value;
|
|
1078
|
+
}
|
|
1079
|
+
function pushIdentityEvidence(evidence, label, value) {
|
|
1080
|
+
const compact = compactIdentityEvidence(value);
|
|
1081
|
+
if (compact) evidence.push(`${label}:${compact}`);
|
|
1082
|
+
}
|
|
1083
|
+
function buildNodeMachineIdentity(ctx, node) {
|
|
1084
|
+
const machineId = readNodeMachineId(node);
|
|
1085
|
+
const daemonId = readNodeDaemonId(node);
|
|
1086
|
+
const hostname = readNodeHostname(node);
|
|
1087
|
+
const machineName = readNodeDisplayMachineName(node);
|
|
1088
|
+
const coordinatorHostname = readString(ctx.coordinatorHostname);
|
|
1089
|
+
const directLocal = isLocalControlPlaneNode(ctx, node);
|
|
1090
|
+
const hostnameMatches = Boolean(
|
|
1091
|
+
normalizeHostname(hostname) && normalizeHostname(coordinatorHostname) && normalizeHostname(hostname) === normalizeHostname(coordinatorHostname)
|
|
1092
|
+
);
|
|
1093
|
+
const sameMachine = directLocal || hostnameMatches;
|
|
1094
|
+
const evidence = [];
|
|
1095
|
+
pushIdentityEvidence(evidence, "machineName", machineName);
|
|
1096
|
+
pushIdentityEvidence(evidence, "hostname", hostname);
|
|
1097
|
+
pushIdentityEvidence(evidence, "machineId", machineId);
|
|
1098
|
+
pushIdentityEvidence(evidence, "daemonId", daemonId);
|
|
1099
|
+
const locality = sameMachine ? "same_machine" : evidence.length > 0 ? "remote_known" : "remote_or_unknown";
|
|
1100
|
+
const localityReason = sameMachine ? directLocal ? "matched coordinator daemon or machine id" : "matched coordinator hostname" : evidence.length > 0 ? `known remote/other machine identity; no local coordinator match (${evidence.join(", ")})` : "no useful machine identity evidence available";
|
|
1101
|
+
return {
|
|
1102
|
+
daemonId,
|
|
1103
|
+
machineId,
|
|
1104
|
+
hostname,
|
|
1105
|
+
machineName,
|
|
1106
|
+
displayName: machineName || hostname || daemonId || machineId,
|
|
1107
|
+
coordinatorHostname,
|
|
1108
|
+
sameMachine,
|
|
1109
|
+
locality,
|
|
1110
|
+
localityReason,
|
|
1111
|
+
identityEvidence: evidence
|
|
1112
|
+
};
|
|
825
1113
|
}
|
|
826
1114
|
function isDirectLocalNode(ctx, node) {
|
|
827
1115
|
const machineId = readNodeMachineId(node);
|
|
@@ -846,6 +1134,54 @@ function isLocalControlPlaneNode(ctx, node) {
|
|
|
846
1134
|
function meshSessionCacheKey(nodeId, runtimeSessionId) {
|
|
847
1135
|
return `${nodeId}:${runtimeSessionId}`;
|
|
848
1136
|
}
|
|
1137
|
+
function rememberMeshSessionProviderMetadata(nodeId, runtimeSessionId, metadata) {
|
|
1138
|
+
const keyNodeId = readString(nodeId);
|
|
1139
|
+
const keySessionId = readString(runtimeSessionId);
|
|
1140
|
+
if (!keyNodeId || !keySessionId) return;
|
|
1141
|
+
const providerType = readString(metadata.providerType);
|
|
1142
|
+
const providerSessionId = readString(metadata.providerSessionId);
|
|
1143
|
+
if (!providerType && !providerSessionId) return;
|
|
1144
|
+
const existing = meshSessionProviderMetadata.get(meshSessionCacheKey(keyNodeId, keySessionId)) || { providerType: "" };
|
|
1145
|
+
meshSessionProviderMetadata.set(meshSessionCacheKey(keyNodeId, keySessionId), {
|
|
1146
|
+
providerType: providerType || existing.providerType,
|
|
1147
|
+
providerSessionId: providerSessionId || existing.providerSessionId
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
function rememberMeshSessionProviderMetadataFromEvent(event) {
|
|
1151
|
+
const metadataEvent = event?.metadataEvent && typeof event.metadataEvent === "object" ? event.metadataEvent : event && typeof event === "object" ? event : {};
|
|
1152
|
+
const nodeId = readString(event?.nodeId) || readString(metadataEvent.nodeId) || readString(metadataEvent.meshNodeId);
|
|
1153
|
+
const sessionId = readString(metadataEvent.targetSessionId) || readString(metadataEvent.sessionId) || readString(metadataEvent.instanceId) || readString(event?.sessionId);
|
|
1154
|
+
rememberMeshSessionProviderMetadata(nodeId, sessionId, {
|
|
1155
|
+
providerType: readString(metadataEvent.providerType) || readString(event?.providerType) || "",
|
|
1156
|
+
providerSessionId: readString(metadataEvent.providerSessionId) || readString(event?.providerSessionId)
|
|
1157
|
+
});
|
|
1158
|
+
}
|
|
1159
|
+
function resolveMeshSessionProviderMetadataFromLedger(ctx, nodeId, runtimeSessionId) {
|
|
1160
|
+
const entries = (0, import_daemon_core.readLedgerEntries)(ctx.mesh.id, { tail: 500 });
|
|
1161
|
+
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
1162
|
+
const entry = entries[i];
|
|
1163
|
+
const payload = entry.payload && typeof entry.payload === "object" && !Array.isArray(entry.payload) ? entry.payload : {};
|
|
1164
|
+
const entryNodeId = readString(entry.nodeId) || readString(payload.nodeId) || readString(payload.meshNodeId);
|
|
1165
|
+
if (entryNodeId && entryNodeId !== nodeId) continue;
|
|
1166
|
+
const entrySessionId = readString(entry.sessionId) || readString(payload.targetSessionId) || readString(payload.sessionId) || readString(payload.instanceId);
|
|
1167
|
+
if (entrySessionId !== runtimeSessionId) continue;
|
|
1168
|
+
const providerType = readString(entry.providerType) || readString(payload.providerType);
|
|
1169
|
+
const completionDiagnostic = payload.completionDiagnostic && typeof payload.completionDiagnostic === "object" && !Array.isArray(payload.completionDiagnostic) ? payload.completionDiagnostic : {};
|
|
1170
|
+
const metadataEvent = payload.metadataEvent && typeof payload.metadataEvent === "object" && !Array.isArray(payload.metadataEvent) ? payload.metadataEvent : {};
|
|
1171
|
+
const providerSessionId = readString(payload.providerSessionId) || readString(completionDiagnostic.providerSessionId) || readString(metadataEvent.providerSessionId);
|
|
1172
|
+
if (providerType || providerSessionId) {
|
|
1173
|
+
return { providerType: providerType || "", providerSessionId };
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
return void 0;
|
|
1177
|
+
}
|
|
1178
|
+
function resolveMeshSessionProviderMetadata(ctx, nodeId, runtimeSessionId) {
|
|
1179
|
+
const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(nodeId, runtimeSessionId));
|
|
1180
|
+
if (cached?.providerType || cached?.providerSessionId) return cached;
|
|
1181
|
+
const fromLedger = resolveMeshSessionProviderMetadataFromLedger(ctx, nodeId, runtimeSessionId);
|
|
1182
|
+
if (fromLedger) rememberMeshSessionProviderMetadata(nodeId, runtimeSessionId, fromLedger);
|
|
1183
|
+
return fromLedger;
|
|
1184
|
+
}
|
|
849
1185
|
function countUncommittedChanges(status) {
|
|
850
1186
|
if (typeof status?.uncommittedChanges === "number") return status.uncommittedChanges;
|
|
851
1187
|
const keys = ["staged", "modified", "untracked", "deleted", "renamed"];
|
|
@@ -872,6 +1208,10 @@ function summarizeRelatedRepoStatus(repo, status) {
|
|
|
872
1208
|
workspace: repo.workspace,
|
|
873
1209
|
isGitRepo: status?.isGitRepo === true,
|
|
874
1210
|
branch: status?.branch ?? null,
|
|
1211
|
+
upstream: status?.upstream ?? null,
|
|
1212
|
+
upstreamStatus: typeof status?.upstreamStatus === "string" ? status.upstreamStatus : status?.upstream ? "unchecked" : "no_upstream",
|
|
1213
|
+
upstreamFetchedAt: Number.isFinite(Number(status?.upstreamFetchedAt)) ? Number(status.upstreamFetchedAt) : null,
|
|
1214
|
+
upstreamFetchError: typeof status?.upstreamFetchError === "string" ? status.upstreamFetchError : null,
|
|
875
1215
|
ahead: Number.isFinite(Number(status?.ahead)) ? Number(status.ahead) : 0,
|
|
876
1216
|
behind: Number.isFinite(Number(status?.behind)) ? Number(status.behind) : 0,
|
|
877
1217
|
dirty,
|
|
@@ -888,7 +1228,7 @@ async function collectRelatedRepoStatuses(ctx, node) {
|
|
|
888
1228
|
const results = [];
|
|
889
1229
|
for (const repo of relatedRepos) {
|
|
890
1230
|
try {
|
|
891
|
-
const statusResult = !isLocalTransport(ctx.transport) && node.daemonId ? await ctx.transport.gitStatus(node.daemonId, repo.workspace, false) : await commandForNode(ctx, node, "git_status", { workspace: repo.workspace });
|
|
1231
|
+
const statusResult = !isLocalTransport(ctx.transport) && node.daemonId ? await ctx.transport.gitStatus(node.daemonId, repo.workspace, false, true) : await commandForNode(ctx, node, "git_status", { workspace: repo.workspace, refreshUpstream: true });
|
|
892
1232
|
const status = extractGitStatus(statusResult);
|
|
893
1233
|
results.push(summarizeRelatedRepoStatus(repo, status));
|
|
894
1234
|
} catch (e) {
|
|
@@ -926,6 +1266,14 @@ function getNodeLaunchReadiness(node) {
|
|
|
926
1266
|
launchBlockedMessage: missingProviderPriorityMessage(node.id)
|
|
927
1267
|
};
|
|
928
1268
|
}
|
|
1269
|
+
async function collectLiveStatusSessions(ctx, node) {
|
|
1270
|
+
try {
|
|
1271
|
+
const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
|
|
1272
|
+
return extractStatusMetadataSessions(statusResult);
|
|
1273
|
+
} catch {
|
|
1274
|
+
return [];
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
929
1277
|
function readNumeric(value, fallback = 0) {
|
|
930
1278
|
const parsed = Number(value);
|
|
931
1279
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
@@ -936,11 +1284,13 @@ function buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges) {
|
|
|
936
1284
|
const ahead = readNumeric(status?.ahead);
|
|
937
1285
|
const behind = readNumeric(status?.behind);
|
|
938
1286
|
const upstream = readString(status?.upstream) ?? null;
|
|
1287
|
+
const upstreamStatus = readString(status?.upstreamStatus) ?? (upstream ? "unchecked" : "no_upstream");
|
|
939
1288
|
const hasConflicts = status?.hasConflicts === true || Array.isArray(status?.conflictFiles) && status.conflictFiles.length > 0;
|
|
940
1289
|
const base = {
|
|
941
1290
|
defaultBranch,
|
|
942
1291
|
branch,
|
|
943
1292
|
upstream,
|
|
1293
|
+
upstreamStatus,
|
|
944
1294
|
ahead,
|
|
945
1295
|
behind,
|
|
946
1296
|
isWorktree: node.isLocalWorktree === true,
|
|
@@ -974,6 +1324,15 @@ function buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges) {
|
|
|
974
1324
|
};
|
|
975
1325
|
}
|
|
976
1326
|
if (branch === defaultBranch) {
|
|
1327
|
+
if (upstream && upstreamStatus !== "fresh") {
|
|
1328
|
+
return {
|
|
1329
|
+
...base,
|
|
1330
|
+
status: "blocked_review",
|
|
1331
|
+
needsConvergence: true,
|
|
1332
|
+
reason: "default_branch_upstream_unverified",
|
|
1333
|
+
nextStep: `Refresh ${defaultBranch}'s upstream refs or resolve the fetch failure before declaring convergence complete for node '${node.id}'.`
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
977
1336
|
if (ahead > 0 || behind > 0) {
|
|
978
1337
|
return {
|
|
979
1338
|
...base,
|
|
@@ -1000,6 +1359,15 @@ function buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges) {
|
|
|
1000
1359
|
nextStep: `Run mesh_refine_node(node_id: "${node.id}") or explicitly classify this worktree as blocked_review/not_mergeable before ending the task.`
|
|
1001
1360
|
};
|
|
1002
1361
|
}
|
|
1362
|
+
if (upstream && upstreamStatus !== "fresh") {
|
|
1363
|
+
return {
|
|
1364
|
+
...base,
|
|
1365
|
+
status: "blocked_review",
|
|
1366
|
+
needsConvergence: true,
|
|
1367
|
+
reason: "feature_branch_upstream_unverified",
|
|
1368
|
+
nextStep: `Refresh branch '${branch}' upstream refs or resolve the fetch failure before deciding whether it is ready to merge into ${defaultBranch}.`
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1003
1371
|
if (!upstream || ahead > 0 || behind > 0) {
|
|
1004
1372
|
return {
|
|
1005
1373
|
...base,
|
|
@@ -1041,7 +1409,86 @@ async function commandForNode(ctx, node, command, args = {}) {
|
|
|
1041
1409
|
if (isLocalTransport(ctx.transport)) {
|
|
1042
1410
|
return ctx.transport.command(command, args);
|
|
1043
1411
|
}
|
|
1044
|
-
|
|
1412
|
+
const identity = buildNodeMachineIdentity(ctx, node);
|
|
1413
|
+
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})`);
|
|
1414
|
+
}
|
|
1415
|
+
function normalizePendingMeshCoordinatorEvents(value) {
|
|
1416
|
+
const payload = unwrapCommandPayload(value);
|
|
1417
|
+
const events = Array.isArray(payload?.events) ? payload.events : Array.isArray(value?.events) ? value.events : [];
|
|
1418
|
+
return events.filter((event) => event && typeof event === "object");
|
|
1419
|
+
}
|
|
1420
|
+
function buildMeshForwardPayloadFromPendingEvent(event) {
|
|
1421
|
+
const metadataEvent = event?.metadataEvent && typeof event.metadataEvent === "object" ? event.metadataEvent : {};
|
|
1422
|
+
return {
|
|
1423
|
+
event: readString(event?.event),
|
|
1424
|
+
meshId: readString(event?.meshId),
|
|
1425
|
+
nodeId: readString(event?.nodeId) || readString(metadataEvent.meshNodeId),
|
|
1426
|
+
workspace: readString(event?.workspace) || readString(metadataEvent.workspace),
|
|
1427
|
+
targetSessionId: readString(metadataEvent.targetSessionId) || readString(metadataEvent.sessionId) || readString(metadataEvent.instanceId),
|
|
1428
|
+
providerType: readString(metadataEvent.providerType),
|
|
1429
|
+
providerSessionId: readString(metadataEvent.providerSessionId),
|
|
1430
|
+
finalSummary: readString(metadataEvent.finalSummary) || readString(metadataEvent.summary),
|
|
1431
|
+
jobId: readString(metadataEvent.jobId),
|
|
1432
|
+
interactionId: readString(metadataEvent.interactionId),
|
|
1433
|
+
status: readString(metadataEvent.status),
|
|
1434
|
+
targetDaemonId: readString(metadataEvent.targetDaemonId),
|
|
1435
|
+
startedAt: readString(metadataEvent.startedAt),
|
|
1436
|
+
completedAt: readString(metadataEvent.completedAt),
|
|
1437
|
+
retryOfJobId: readString(metadataEvent.retryOfJobId),
|
|
1438
|
+
...metadataEvent.result && typeof metadataEvent.result === "object" && !Array.isArray(metadataEvent.result) ? { result: metadataEvent.result } : {},
|
|
1439
|
+
...metadataEvent.intentional === true ? { intentional: true } : {},
|
|
1440
|
+
...metadataEvent.intentionalStop === true ? { intentionalStop: true } : {},
|
|
1441
|
+
...metadataEvent.operatorCleanup === true ? { operatorCleanup: true } : {},
|
|
1442
|
+
...readString(metadataEvent.reason) ? { reason: readString(metadataEvent.reason) } : {},
|
|
1443
|
+
...readString(metadataEvent.stopReason) ? { stopReason: readString(metadataEvent.stopReason) } : {},
|
|
1444
|
+
...readString(metadataEvent.cleanupReason) ? { cleanupReason: readString(metadataEvent.cleanupReason) } : {},
|
|
1445
|
+
...readString(metadataEvent.source) ? { source: readString(metadataEvent.source) } : {}
|
|
1446
|
+
};
|
|
1447
|
+
}
|
|
1448
|
+
async function drainCoordinatorPendingEvents(ctx, opts) {
|
|
1449
|
+
const requestedNodeIds = opts?.nodeIds?.length ? new Set(opts.nodeIds) : null;
|
|
1450
|
+
const matchesCurrentMesh = (event) => readString(event?.meshId) === ctx.mesh.id;
|
|
1451
|
+
if (ctx.transport instanceof IpcTransport) {
|
|
1452
|
+
const surfacedEvents = [];
|
|
1453
|
+
try {
|
|
1454
|
+
surfacedEvents.push(
|
|
1455
|
+
...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", { meshId: ctx.mesh.id })).filter(matchesCurrentMesh)
|
|
1456
|
+
);
|
|
1457
|
+
surfacedEvents.forEach(rememberMeshSessionProviderMetadataFromEvent);
|
|
1458
|
+
} catch {
|
|
1459
|
+
}
|
|
1460
|
+
for (const node of ctx.mesh.nodes) {
|
|
1461
|
+
if (!node.daemonId || isLocalControlPlaneNode(ctx, node)) continue;
|
|
1462
|
+
if (requestedNodeIds && !requestedNodeIds.has(node.id)) continue;
|
|
1463
|
+
try {
|
|
1464
|
+
const remoteEvents = normalizePendingMeshCoordinatorEvents(
|
|
1465
|
+
await ctx.transport.meshCommand(node.daemonId, "get_pending_mesh_events", { meshId: ctx.mesh.id })
|
|
1466
|
+
).filter(matchesCurrentMesh);
|
|
1467
|
+
if (remoteEvents.length === 0) continue;
|
|
1468
|
+
for (const event of remoteEvents) {
|
|
1469
|
+
const payload = buildMeshForwardPayloadFromPendingEvent(event);
|
|
1470
|
+
if (!payload.event || !payload.meshId) continue;
|
|
1471
|
+
await ctx.transport.command("mesh_forward_event", payload);
|
|
1472
|
+
rememberMeshSessionProviderMetadataFromEvent({ ...event, metadataEvent: payload });
|
|
1473
|
+
}
|
|
1474
|
+
} catch {
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
try {
|
|
1478
|
+
surfacedEvents.push(
|
|
1479
|
+
...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", { meshId: ctx.mesh.id })).filter(matchesCurrentMesh)
|
|
1480
|
+
);
|
|
1481
|
+
surfacedEvents.forEach(rememberMeshSessionProviderMetadataFromEvent);
|
|
1482
|
+
} catch {
|
|
1483
|
+
}
|
|
1484
|
+
return surfacedEvents;
|
|
1485
|
+
}
|
|
1486
|
+
if (isLocalTransport(ctx.transport)) {
|
|
1487
|
+
const events = (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id).filter(matchesCurrentMesh);
|
|
1488
|
+
events.forEach(rememberMeshSessionProviderMetadataFromEvent);
|
|
1489
|
+
return events;
|
|
1490
|
+
}
|
|
1491
|
+
return [];
|
|
1045
1492
|
}
|
|
1046
1493
|
function isP2pTransportUnavailableError(error) {
|
|
1047
1494
|
return (0, import_daemon_core.isP2pRelayTransportFailure)(error);
|
|
@@ -1056,7 +1503,7 @@ function buildRemoveNodeArgs(ctx, nodeId, sessionCleanupMode) {
|
|
|
1056
1503
|
}
|
|
1057
1504
|
var MESH_STATUS_TOOL = {
|
|
1058
1505
|
name: "mesh_status",
|
|
1059
|
-
description: "Get the current status of all nodes in the repo mesh \u2014 health, git state, active sessions, recovery hints, and recommended next steps. Use this to decide which node to send work to or how to recover from failures.",
|
|
1506
|
+
description: "Get the current status of all nodes in the repo mesh \u2014 health, git state, active sessions, recovery hints, and recommended next steps. Use this to decide which node to send work to or how to recover from failures. Do not repeatedly call this to wait for generating delegated work; wait for pendingCoordinatorEvents/completion events or an explicit user status request.",
|
|
1060
1507
|
inputSchema: {
|
|
1061
1508
|
type: "object",
|
|
1062
1509
|
properties: {
|
|
@@ -1080,14 +1527,16 @@ var MESH_ENQUEUE_TASK_TOOL = {
|
|
|
1080
1527
|
inputSchema: {
|
|
1081
1528
|
type: "object",
|
|
1082
1529
|
properties: {
|
|
1083
|
-
message: { type: "string", description: "The task instruction for the agent." }
|
|
1530
|
+
message: { type: "string", description: "The task instruction for the agent." },
|
|
1531
|
+
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." },
|
|
1532
|
+
taskMode: { type: "string", enum: ["code_change", "validation", "live_debug_readonly", "launch_app", "convergence"], description: "CamelCase alias for task_mode." }
|
|
1084
1533
|
},
|
|
1085
1534
|
required: ["message"]
|
|
1086
1535
|
}
|
|
1087
1536
|
};
|
|
1088
1537
|
var MESH_VIEW_QUEUE_TOOL = {
|
|
1089
1538
|
name: "mesh_view_queue",
|
|
1090
|
-
description: "View the mesh work queue with source-of-truth active counts separated from historical completed/failed/cancelled records.",
|
|
1539
|
+
description: "View the mesh work queue with source-of-truth active counts separated from historical completed/failed/cancelled records. Do not repeatedly call this to wait for generating assigned work; wait for pendingCoordinatorEvents/completion events or an explicit user status request.",
|
|
1091
1540
|
inputSchema: {
|
|
1092
1541
|
type: "object",
|
|
1093
1542
|
properties: {
|
|
@@ -1140,7 +1589,9 @@ var MESH_SEND_TASK_TOOL = {
|
|
|
1140
1589
|
properties: {
|
|
1141
1590
|
node_id: { type: "string", description: "Target node ID (from mesh_list_nodes)." },
|
|
1142
1591
|
session_id: { type: "string", description: "Agent session ID on the target node." },
|
|
1143
|
-
message: { type: "string", description: "Natural-language task to send to the agent." }
|
|
1592
|
+
message: { type: "string", description: "Natural-language task to send to the agent." },
|
|
1593
|
+
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." },
|
|
1594
|
+
taskMode: { type: "string", enum: ["code_change", "validation", "live_debug_readonly", "launch_app", "convergence"], description: "CamelCase alias for task_mode." }
|
|
1144
1595
|
},
|
|
1145
1596
|
required: ["node_id", "session_id", "message"]
|
|
1146
1597
|
}
|
|
@@ -1198,6 +1649,21 @@ var MESH_GIT_STATUS_TOOL = {
|
|
|
1198
1649
|
required: ["node_id"]
|
|
1199
1650
|
}
|
|
1200
1651
|
};
|
|
1652
|
+
var MESH_FAST_FORWARD_NODE_TOOL = {
|
|
1653
|
+
name: "mesh_fast_forward_node",
|
|
1654
|
+
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.",
|
|
1655
|
+
inputSchema: {
|
|
1656
|
+
type: "object",
|
|
1657
|
+
properties: {
|
|
1658
|
+
node_id: { type: "string", description: "Target node ID." },
|
|
1659
|
+
branch: { type: "string", description: "Optional guard: require the node's current branch to match this branch before planning/executing." },
|
|
1660
|
+
execute: { type: "boolean", description: "When true, apply the fast-forward if all safety gates pass. Defaults false/dry-run." },
|
|
1661
|
+
dry_run: { type: "boolean", description: "Preview only. Defaults true unless execute=true; dry_run=true overrides execute." },
|
|
1662
|
+
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." }
|
|
1663
|
+
},
|
|
1664
|
+
required: ["node_id"]
|
|
1665
|
+
}
|
|
1666
|
+
};
|
|
1201
1667
|
var MESH_CHECKPOINT_TOOL = {
|
|
1202
1668
|
name: "mesh_checkpoint",
|
|
1203
1669
|
description: "Create a git checkpoint (commit) on a mesh node workspace.",
|
|
@@ -1281,7 +1747,7 @@ var MESH_TASK_HISTORY_TOOL = {
|
|
|
1281
1747
|
type: "object",
|
|
1282
1748
|
properties: {
|
|
1283
1749
|
tail: { type: "number", description: "Number of recent entries to return (default: 20)." },
|
|
1284
|
-
kind: { type: "string", description: "Filter by entry kind: task_dispatched, task_completed, task_failed, task_stalled, session_launched, checkpoint_created, node_cloned, node_removed." }
|
|
1750
|
+
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." }
|
|
1285
1751
|
}
|
|
1286
1752
|
}
|
|
1287
1753
|
};
|
|
@@ -1301,7 +1767,7 @@ var MESH_RECONCILE_LEDGER_TOOL = {
|
|
|
1301
1767
|
};
|
|
1302
1768
|
var MESH_REFINE_NODE_TOOL = {
|
|
1303
1769
|
name: "mesh_refine_node",
|
|
1304
|
-
description: "The Refinery:
|
|
1770
|
+
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.",
|
|
1305
1771
|
inputSchema: {
|
|
1306
1772
|
type: "object",
|
|
1307
1773
|
properties: {
|
|
@@ -1310,6 +1776,43 @@ var MESH_REFINE_NODE_TOOL = {
|
|
|
1310
1776
|
required: ["node_id"]
|
|
1311
1777
|
}
|
|
1312
1778
|
};
|
|
1779
|
+
var MESH_REFINE_CONFIG_SCHEMA_TOOL = {
|
|
1780
|
+
name: "mesh_refine_config_schema",
|
|
1781
|
+
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.",
|
|
1782
|
+
inputSchema: { type: "object", properties: {} }
|
|
1783
|
+
};
|
|
1784
|
+
var MESH_VALIDATE_REFINE_CONFIG_TOOL = {
|
|
1785
|
+
name: "mesh_validate_refine_config",
|
|
1786
|
+
description: "Validate the repo mesh/refine config for a node/workspace without running validation commands or merging.",
|
|
1787
|
+
inputSchema: {
|
|
1788
|
+
type: "object",
|
|
1789
|
+
properties: {
|
|
1790
|
+
node_id: { type: "string", description: "Optional node/workspace whose refine config should be loaded. Defaults to the first mesh node." },
|
|
1791
|
+
config: { type: "object", description: "Optional inline config object to validate instead of loading from the repo." }
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
};
|
|
1795
|
+
var MESH_SUGGEST_REFINE_CONFIG_TOOL = {
|
|
1796
|
+
name: "mesh_suggest_refine_config",
|
|
1797
|
+
description: "Suggest a repo mesh/refine config scaffold from project context/package scripts. Suggestions are never executed until saved as explicit refine config.",
|
|
1798
|
+
inputSchema: {
|
|
1799
|
+
type: "object",
|
|
1800
|
+
properties: {
|
|
1801
|
+
node_id: { type: "string", description: "Optional node/workspace used for suggestions. Defaults to the first mesh node." }
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
};
|
|
1805
|
+
var MESH_REFINE_PLAN_TOOL = {
|
|
1806
|
+
name: "mesh_refine_plan",
|
|
1807
|
+
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.",
|
|
1808
|
+
inputSchema: {
|
|
1809
|
+
type: "object",
|
|
1810
|
+
properties: {
|
|
1811
|
+
node_id: { type: "string", description: "Node ID of the worktree node to plan." }
|
|
1812
|
+
},
|
|
1813
|
+
required: ["node_id"]
|
|
1814
|
+
}
|
|
1815
|
+
};
|
|
1313
1816
|
var ALL_MESH_TOOLS = [
|
|
1314
1817
|
MESH_STATUS_TOOL,
|
|
1315
1818
|
MESH_LIST_NODES_TOOL,
|
|
@@ -1322,11 +1825,16 @@ var ALL_MESH_TOOLS = [
|
|
|
1322
1825
|
MESH_READ_DEBUG_TOOL,
|
|
1323
1826
|
MESH_LAUNCH_SESSION_TOOL,
|
|
1324
1827
|
MESH_GIT_STATUS_TOOL,
|
|
1828
|
+
MESH_FAST_FORWARD_NODE_TOOL,
|
|
1325
1829
|
MESH_CHECKPOINT_TOOL,
|
|
1326
1830
|
MESH_APPROVE_TOOL,
|
|
1327
1831
|
MESH_CLONE_NODE_TOOL,
|
|
1328
1832
|
MESH_REMOVE_NODE_TOOL,
|
|
1329
1833
|
MESH_REFINE_NODE_TOOL,
|
|
1834
|
+
MESH_REFINE_CONFIG_SCHEMA_TOOL,
|
|
1835
|
+
MESH_VALIDATE_REFINE_CONFIG_TOOL,
|
|
1836
|
+
MESH_SUGGEST_REFINE_CONFIG_TOOL,
|
|
1837
|
+
MESH_REFINE_PLAN_TOOL,
|
|
1330
1838
|
MESH_CLEANUP_SESSIONS_TOOL,
|
|
1331
1839
|
MESH_TASK_HISTORY_TOOL,
|
|
1332
1840
|
MESH_RECONCILE_LEDGER_TOOL
|
|
@@ -1340,15 +1848,19 @@ async function meshStatus(ctx) {
|
|
|
1340
1848
|
const entry = {
|
|
1341
1849
|
nodeId: node.id,
|
|
1342
1850
|
workspace: node.workspace,
|
|
1851
|
+
machine: buildNodeMachineIdentity(ctx, node),
|
|
1852
|
+
daemonId: readNodeDaemonId(node),
|
|
1853
|
+
machineId: readNodeMachineId(node),
|
|
1343
1854
|
...getNodeLaunchReadiness(node)
|
|
1344
1855
|
};
|
|
1345
1856
|
try {
|
|
1346
1857
|
if (!isLocalTransport(transport) && node.daemonId) {
|
|
1347
|
-
const result = await transport.gitStatus(node.daemonId, node.workspace, false);
|
|
1858
|
+
const result = await transport.gitStatus(node.daemonId, node.workspace, false, true);
|
|
1348
1859
|
const status = extractGitStatus(result);
|
|
1349
1860
|
const uncommittedChanges = countUncommittedChanges(status);
|
|
1350
1861
|
const dirty = isGitStatusDirty(status);
|
|
1351
1862
|
entry.health = status?.isGitRepo ? dirty ? "dirty" : "online" : "degraded";
|
|
1863
|
+
assignFullGitSnapshot(entry, status);
|
|
1352
1864
|
entry.branch = status?.branch;
|
|
1353
1865
|
entry.isDirty = dirty;
|
|
1354
1866
|
entry.uncommittedChanges = uncommittedChanges;
|
|
@@ -1362,6 +1874,7 @@ async function meshStatus(ctx) {
|
|
|
1362
1874
|
const autoDiscover = node.policy?.autoDiscoverSubmodules !== false;
|
|
1363
1875
|
const statusResult = await commandForNode(ctx, node, "git_status", {
|
|
1364
1876
|
workspace: node.workspace,
|
|
1877
|
+
refreshUpstream: true,
|
|
1365
1878
|
includeSubmodules: autoDiscover,
|
|
1366
1879
|
submoduleIgnorePaths: node.policy?.submoduleIgnorePaths || void 0
|
|
1367
1880
|
});
|
|
@@ -1369,6 +1882,7 @@ async function meshStatus(ctx) {
|
|
|
1369
1882
|
const uncommittedChanges = countUncommittedChanges(status);
|
|
1370
1883
|
const dirty = isGitStatusDirty(status);
|
|
1371
1884
|
entry.health = status?.isGitRepo ? dirty ? "dirty" : "online" : "degraded";
|
|
1885
|
+
assignFullGitSnapshot(entry, status);
|
|
1372
1886
|
entry.branch = status?.branch;
|
|
1373
1887
|
entry.isDirty = dirty;
|
|
1374
1888
|
entry.uncommittedChanges = uncommittedChanges;
|
|
@@ -1444,15 +1958,37 @@ async function meshStatus(ctx) {
|
|
|
1444
1958
|
}
|
|
1445
1959
|
const relatedRepos = await collectRelatedRepoStatuses(ctx, node);
|
|
1446
1960
|
if (relatedRepos.length) entry.relatedRepos = relatedRepos;
|
|
1961
|
+
const liveSessions = await collectLiveStatusSessions(ctx, node);
|
|
1962
|
+
if (liveSessions.length > 0) {
|
|
1963
|
+
entry.sessions = liveSessions;
|
|
1964
|
+
}
|
|
1447
1965
|
results.push(entry);
|
|
1448
1966
|
}
|
|
1967
|
+
const activeWorkEvidence = (0, import_daemon_core.buildMeshActiveWork)({
|
|
1968
|
+
meshId: mesh.id,
|
|
1969
|
+
queue: (0, import_daemon_core.getQueue)(mesh.id),
|
|
1970
|
+
ledgerEntries: (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail: 500 }),
|
|
1971
|
+
nodes: results
|
|
1972
|
+
});
|
|
1973
|
+
const pollingGuidance = buildActiveWorkPollingGuidance(activeWorkEvidence.summary);
|
|
1449
1974
|
const response = {
|
|
1450
1975
|
meshId: mesh.id,
|
|
1451
1976
|
meshName: mesh.name,
|
|
1452
1977
|
repoIdentity: mesh.repoIdentity,
|
|
1453
1978
|
policy: mesh.policy,
|
|
1454
1979
|
refreshedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1980
|
+
sourceOfTruth: {
|
|
1981
|
+
membership: "coordinator_daemon_live_mesh",
|
|
1982
|
+
currentStatus: "live_git_and_session_probes",
|
|
1983
|
+
activeWork: "mesh_queue_file_and_local_ledger",
|
|
1984
|
+
historicalEvidenceOnly: ["recoveryHints", "ledgerSummary"]
|
|
1985
|
+
},
|
|
1455
1986
|
nodes: results,
|
|
1987
|
+
activeWork: activeWorkEvidence.activeWork,
|
|
1988
|
+
staleDirectWork: activeWorkEvidence.staleDirectWork,
|
|
1989
|
+
terminalDirectWork: activeWorkEvidence.terminalDirectWork,
|
|
1990
|
+
activeWorkSummary: activeWorkEvidence.summary,
|
|
1991
|
+
...pollingGuidance ? { pollingGuidance } : {},
|
|
1456
1992
|
branchConvergenceSummary: summarizeBranchConvergence(results)
|
|
1457
1993
|
};
|
|
1458
1994
|
try {
|
|
@@ -1460,13 +1996,7 @@ async function meshStatus(ctx) {
|
|
|
1460
1996
|
} catch {
|
|
1461
1997
|
}
|
|
1462
1998
|
try {
|
|
1463
|
-
|
|
1464
|
-
if (ctx.transport instanceof IpcTransport) {
|
|
1465
|
-
const eventsResult = await ctx.transport.command("get_pending_mesh_events", {});
|
|
1466
|
-
pendingEvents = Array.isArray(eventsResult?.events) ? eventsResult.events : [];
|
|
1467
|
-
} else if (isLocalTransport(ctx.transport)) {
|
|
1468
|
-
pendingEvents = (0, import_daemon_core.drainPendingMeshCoordinatorEvents)();
|
|
1469
|
-
}
|
|
1999
|
+
const pendingEvents = await drainCoordinatorPendingEvents(ctx);
|
|
1470
2000
|
if (pendingEvents.length > 0) {
|
|
1471
2001
|
response.pendingCoordinatorEvents = pendingEvents;
|
|
1472
2002
|
}
|
|
@@ -1476,11 +2006,17 @@ async function meshStatus(ctx) {
|
|
|
1476
2006
|
}
|
|
1477
2007
|
async function meshTaskHistory(ctx, args) {
|
|
1478
2008
|
const { mesh } = ctx;
|
|
2009
|
+
const pendingEvents = await drainCoordinatorPendingEvents(ctx);
|
|
1479
2010
|
const tail = typeof args.tail === "number" && args.tail > 0 ? args.tail : 20;
|
|
1480
2011
|
const kind = typeof args.kind === "string" && args.kind.trim() ? [args.kind.trim()] : void 0;
|
|
1481
2012
|
const entries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail, kind });
|
|
1482
2013
|
const summary = (0, import_daemon_core.getLedgerSummary)(mesh.id);
|
|
1483
|
-
return JSON.stringify({
|
|
2014
|
+
return JSON.stringify({
|
|
2015
|
+
meshId: mesh.id,
|
|
2016
|
+
entries,
|
|
2017
|
+
summary,
|
|
2018
|
+
...pendingEvents.length > 0 ? { pendingCoordinatorEvents: pendingEvents } : {}
|
|
2019
|
+
}, null, 2);
|
|
1484
2020
|
}
|
|
1485
2021
|
async function meshReconcileLedger(ctx, args) {
|
|
1486
2022
|
await refreshMeshFromDaemon(ctx);
|
|
@@ -1570,6 +2106,9 @@ async function meshListNodes(ctx) {
|
|
|
1570
2106
|
nodeId: n.id,
|
|
1571
2107
|
workspace: n.workspace,
|
|
1572
2108
|
repoRoot: n.repoRoot,
|
|
2109
|
+
daemonId: readNodeDaemonId(n),
|
|
2110
|
+
machineId: readNodeMachineId(n),
|
|
2111
|
+
machine: buildNodeMachineIdentity(ctx, n),
|
|
1573
2112
|
isLocalWorktree: n.isLocalWorktree,
|
|
1574
2113
|
policy: n.policy,
|
|
1575
2114
|
relatedRepos: readRelatedRepos(n),
|
|
@@ -1579,12 +2118,13 @@ async function meshListNodes(ctx) {
|
|
|
1579
2118
|
}, null, 2);
|
|
1580
2119
|
}
|
|
1581
2120
|
async function meshEnqueueTask(ctx, args) {
|
|
2121
|
+
const taskMode = readString(args.task_mode) || readString(args.taskMode);
|
|
1582
2122
|
try {
|
|
1583
|
-
const task = (0, import_daemon_core.enqueueTask)(ctx.mesh.id, args.message);
|
|
2123
|
+
const task = (0, import_daemon_core.enqueueTask)(ctx.mesh.id, args.message, { taskMode });
|
|
1584
2124
|
if (isLocalTransport(ctx.transport) && !(ctx.transport instanceof IpcTransport)) {
|
|
1585
2125
|
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
1586
2126
|
});
|
|
1587
|
-
return JSON.stringify({ success: true, taskId: task.id, status: task.status });
|
|
2127
|
+
return JSON.stringify({ success: true, source: "queue", taskId: task.id, status: task.status, taskMode: task.taskMode });
|
|
1588
2128
|
}
|
|
1589
2129
|
if (ctx.transport instanceof IpcTransport) {
|
|
1590
2130
|
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
@@ -1597,11 +2137,24 @@ async function meshEnqueueTask(ctx, args) {
|
|
|
1597
2137
|
ipcDispatchToRemoteAgent(ctx, node, { message: args.message }).then((result) => {
|
|
1598
2138
|
if (result.success) {
|
|
1599
2139
|
try {
|
|
2140
|
+
const providerType = result.providerType;
|
|
2141
|
+
const descriptor = summarizeTaskMessage(args.message);
|
|
1600
2142
|
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
1601
2143
|
kind: "task_dispatched",
|
|
1602
2144
|
nodeId: node.id,
|
|
1603
2145
|
sessionId: result.sessionId,
|
|
1604
|
-
|
|
2146
|
+
providerType,
|
|
2147
|
+
payload: {
|
|
2148
|
+
source: "queue",
|
|
2149
|
+
via: "p2p_direct",
|
|
2150
|
+
taskId: task.id,
|
|
2151
|
+
message: args.message,
|
|
2152
|
+
taskTitle: descriptor.taskTitle,
|
|
2153
|
+
taskSummary: descriptor.taskSummary,
|
|
2154
|
+
...task.taskMode ? { taskMode: task.taskMode } : {},
|
|
2155
|
+
...providerType ? { providerType } : {},
|
|
2156
|
+
targetSessionId: result.sessionId
|
|
2157
|
+
}
|
|
1605
2158
|
});
|
|
1606
2159
|
} catch {
|
|
1607
2160
|
}
|
|
@@ -1612,24 +2165,36 @@ async function meshEnqueueTask(ctx, args) {
|
|
|
1612
2165
|
}
|
|
1613
2166
|
Promise.all(dispatchPromises).catch(() => {
|
|
1614
2167
|
});
|
|
1615
|
-
return JSON.stringify({ success: true, taskId: task.id, status: task.status });
|
|
2168
|
+
return JSON.stringify({ success: true, source: "queue", taskId: task.id, status: task.status, taskMode: task.taskMode });
|
|
1616
2169
|
}
|
|
1617
|
-
return JSON.stringify({ success: true, taskId: task.id, status: task.status });
|
|
2170
|
+
return JSON.stringify({ success: true, source: "queue", taskId: task.id, status: task.status, taskMode: task.taskMode });
|
|
1618
2171
|
} catch (e) {
|
|
1619
|
-
|
|
2172
|
+
const message = e?.message || String(e);
|
|
2173
|
+
if (message.includes("live_debug_readonly_guardrail_violation")) {
|
|
2174
|
+
return JSON.stringify({ success: false, code: "live_debug_readonly_guardrail_violation", taskMode, error: message });
|
|
2175
|
+
}
|
|
2176
|
+
return JSON.stringify({ success: false, error: message });
|
|
1620
2177
|
}
|
|
1621
2178
|
}
|
|
1622
2179
|
async function meshViewQueue(ctx, args) {
|
|
1623
2180
|
try {
|
|
2181
|
+
await refreshMeshFromDaemon(ctx);
|
|
1624
2182
|
const statusFilter = sanitizeQueueStatusFilter(args.status);
|
|
1625
2183
|
const view = normalizeQueueViewMode(args.view);
|
|
1626
|
-
const fullQueue = annotateQueueStaleness((0, import_daemon_core.getQueue)(ctx.mesh.id), ctx.mesh);
|
|
2184
|
+
const fullQueue = prioritizeActiveQueueRows(annotateQueueStaleness((0, import_daemon_core.getQueue)(ctx.mesh.id), ctx.mesh));
|
|
1627
2185
|
const queue = filterQueueForView(fullQueue, view, statusFilter);
|
|
1628
2186
|
const summary = buildQueueStatusSummary(fullQueue);
|
|
1629
2187
|
const visibleSummary = buildQueueStatusSummary(queue);
|
|
1630
2188
|
const maintenance = buildQueueMaintenanceReport(fullQueue);
|
|
2189
|
+
const activeWorkEvidence = (0, import_daemon_core.buildMeshActiveWork)({
|
|
2190
|
+
meshId: ctx.mesh.id,
|
|
2191
|
+
queue: fullQueue,
|
|
2192
|
+
ledgerEntries: (0, import_daemon_core.readLedgerEntries)(ctx.mesh.id, { tail: 500 }),
|
|
2193
|
+
nodes: ctx.mesh.nodes
|
|
2194
|
+
});
|
|
1631
2195
|
const staleAssignedTasks = maintenance.staleAssignedTasks || [];
|
|
1632
2196
|
const requestedHistoricalRows = queue.some((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
|
|
2197
|
+
const pollingGuidance = buildActiveWorkPollingGuidance(activeWorkEvidence.summary);
|
|
1633
2198
|
return JSON.stringify({
|
|
1634
2199
|
success: true,
|
|
1635
2200
|
sourceOfTruth: {
|
|
@@ -1645,6 +2210,10 @@ async function meshViewQueue(ctx, args) {
|
|
|
1645
2210
|
},
|
|
1646
2211
|
queue,
|
|
1647
2212
|
visibleQueue: queue,
|
|
2213
|
+
activeWork: activeWorkEvidence.activeWork,
|
|
2214
|
+
staleDirectWork: activeWorkEvidence.staleDirectWork,
|
|
2215
|
+
activeWorkSummary: activeWorkEvidence.summary,
|
|
2216
|
+
...pollingGuidance ? { pollingGuidance } : {},
|
|
1648
2217
|
visibleSummary,
|
|
1649
2218
|
summary,
|
|
1650
2219
|
activeCounts: summary.activeCounts,
|
|
@@ -1678,6 +2247,10 @@ async function meshQueueCancel(ctx, args) {
|
|
|
1678
2247
|
if (!taskId) return JSON.stringify({ success: false, error: "task_id required" });
|
|
1679
2248
|
const task = (0, import_daemon_core.cancelTask)(ctx.mesh.id, taskId, { reason: args.reason });
|
|
1680
2249
|
if (!task) return JSON.stringify({ success: false, error: `Queue task '${taskId}' not found` });
|
|
2250
|
+
if (isLocalTransport(ctx.transport)) {
|
|
2251
|
+
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
2252
|
+
});
|
|
2253
|
+
}
|
|
1681
2254
|
return JSON.stringify({ success: true, task }, null, 2);
|
|
1682
2255
|
} catch (e) {
|
|
1683
2256
|
return JSON.stringify({ success: false, error: e.message });
|
|
@@ -1708,10 +2281,46 @@ async function meshQueueRequeue(ctx, args) {
|
|
|
1708
2281
|
}
|
|
1709
2282
|
}
|
|
1710
2283
|
async function meshSendTask(ctx, args) {
|
|
2284
|
+
const requestedTaskMode = readString(args.task_mode) || readString(args.taskMode);
|
|
2285
|
+
const modeValidation = (0, import_daemon_core.validateMeshTaskModeRequest)(requestedTaskMode, args.message);
|
|
2286
|
+
if (!modeValidation.valid) {
|
|
2287
|
+
return JSON.stringify({
|
|
2288
|
+
success: false,
|
|
2289
|
+
code: "live_debug_readonly_guardrail_violation",
|
|
2290
|
+
taskMode: modeValidation.taskMode || requestedTaskMode,
|
|
2291
|
+
violations: modeValidation.violations,
|
|
2292
|
+
allowedOperations: modeValidation.allowedOperations,
|
|
2293
|
+
error: `live_debug_readonly_guardrail_violation: forbidden operations (${modeValidation.violations.join(", ")})`
|
|
2294
|
+
});
|
|
2295
|
+
}
|
|
2296
|
+
const taskMode = modeValidation.taskMode;
|
|
1711
2297
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
1712
2298
|
if (node.policy?.readOnly) {
|
|
1713
2299
|
return JSON.stringify({ error: `Node '${args.node_id}' is read-only` });
|
|
1714
2300
|
}
|
|
2301
|
+
let explicitTargetSession;
|
|
2302
|
+
if (args.session_id && isWorkerTaskMode(taskMode) && (ctx.transport instanceof IpcTransport || isLocalTransport(ctx.transport))) {
|
|
2303
|
+
try {
|
|
2304
|
+
const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
|
|
2305
|
+
const sessions = extractStatusMetadataSessions(statusResult);
|
|
2306
|
+
explicitTargetSession = sessions.find((session) => readSessionRecordId(session) === args.session_id);
|
|
2307
|
+
if (explicitTargetSession && isMeshCoordinatorSessionRecord(explicitTargetSession)) {
|
|
2308
|
+
return JSON.stringify({
|
|
2309
|
+
success: false,
|
|
2310
|
+
recoverable: true,
|
|
2311
|
+
code: "mesh_target_session_is_coordinator",
|
|
2312
|
+
reason: "mesh_target_session_is_coordinator",
|
|
2313
|
+
nodeId: args.node_id,
|
|
2314
|
+
sessionId: args.session_id,
|
|
2315
|
+
taskMode: taskMode || "unspecified",
|
|
2316
|
+
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.`,
|
|
2317
|
+
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.`
|
|
2318
|
+
});
|
|
2319
|
+
}
|
|
2320
|
+
} catch {
|
|
2321
|
+
explicitTargetSession = void 0;
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
1715
2324
|
const duplicate = hasRecentDuplicateDispatch(ctx, args);
|
|
1716
2325
|
if (duplicate.duplicate) {
|
|
1717
2326
|
return JSON.stringify({
|
|
@@ -1735,75 +2344,144 @@ async function meshSendTask(ctx, args) {
|
|
|
1735
2344
|
const res = await ctx.transport.meshEnqueueTask(node.daemonId, {
|
|
1736
2345
|
meshId: ctx.mesh.id,
|
|
1737
2346
|
message: args.message,
|
|
1738
|
-
targetNodeId: args.node_id
|
|
2347
|
+
targetNodeId: args.node_id,
|
|
2348
|
+
...taskMode ? { taskMode } : {}
|
|
1739
2349
|
});
|
|
1740
2350
|
return JSON.stringify(res);
|
|
1741
2351
|
}
|
|
1742
2352
|
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
1743
2353
|
if (ctx.transport instanceof IpcTransport && node.daemonId && !isLocalNode) {
|
|
1744
2354
|
const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id || ""));
|
|
2355
|
+
const taskId = (0, import_node_crypto.randomUUID)();
|
|
1745
2356
|
const result2 = await ipcDispatchToRemoteAgent(ctx, node, {
|
|
1746
2357
|
session_id: args.session_id,
|
|
1747
2358
|
message: args.message,
|
|
1748
|
-
providerType: cached?.providerType
|
|
2359
|
+
providerType: cached?.providerType,
|
|
2360
|
+
verifiedSession: explicitTargetSession
|
|
1749
2361
|
});
|
|
1750
2362
|
if (result2.success) {
|
|
1751
2363
|
const dispatchedSessionId = args.session_id || result2.sessionId;
|
|
1752
2364
|
try {
|
|
2365
|
+
const providerType = result2.providerType || cached?.providerType;
|
|
1753
2366
|
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
1754
2367
|
kind: "task_dispatched",
|
|
1755
2368
|
nodeId: args.node_id,
|
|
1756
2369
|
sessionId: dispatchedSessionId,
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
2370
|
+
providerType,
|
|
2371
|
+
payload: buildDirectTaskPayload(args.message, "p2p_direct", {
|
|
2372
|
+
taskId,
|
|
2373
|
+
taskMode,
|
|
2374
|
+
providerType,
|
|
2375
|
+
targetSessionId: dispatchedSessionId
|
|
2376
|
+
})
|
|
1762
2377
|
});
|
|
1763
2378
|
} catch {
|
|
1764
2379
|
}
|
|
1765
2380
|
}
|
|
1766
|
-
return JSON.stringify({
|
|
2381
|
+
return JSON.stringify({
|
|
2382
|
+
...result2,
|
|
2383
|
+
nodeId: args.node_id,
|
|
2384
|
+
sessionId: result2.success ? args.session_id || result2.sessionId : args.session_id,
|
|
2385
|
+
...result2.success ? { source: "direct", taskId } : {},
|
|
2386
|
+
taskMode,
|
|
2387
|
+
...result2.success && result2.providerType ? { providerType: result2.providerType } : {},
|
|
2388
|
+
dispatched: result2.success === true
|
|
2389
|
+
});
|
|
1767
2390
|
}
|
|
1768
2391
|
if (args.session_id && isLocalTransport(ctx.transport)) {
|
|
1769
2392
|
const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id));
|
|
2393
|
+
let resolvedProviderType = cached?.providerType || "";
|
|
2394
|
+
if (!resolvedProviderType) {
|
|
2395
|
+
let explicitSession = explicitTargetSession;
|
|
2396
|
+
if (!explicitSession) {
|
|
2397
|
+
const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
|
|
2398
|
+
const sessions = extractStatusMetadataSessions(statusResult);
|
|
2399
|
+
explicitSession = sessions.find((session) => readSessionRecordId(session) === args.session_id);
|
|
2400
|
+
}
|
|
2401
|
+
if (!explicitSession) {
|
|
2402
|
+
return JSON.stringify({
|
|
2403
|
+
success: false,
|
|
2404
|
+
recoverable: true,
|
|
2405
|
+
code: "mesh_target_session_not_found",
|
|
2406
|
+
reason: "mesh_target_session_not_found",
|
|
2407
|
+
transport: "local_ipc",
|
|
2408
|
+
retryRecommended: true,
|
|
2409
|
+
nodeId: args.node_id,
|
|
2410
|
+
sessionId: args.session_id,
|
|
2411
|
+
error: `Local session '${args.session_id}' is not present in live status for node '${args.node_id}'.`,
|
|
2412
|
+
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.`
|
|
2413
|
+
});
|
|
2414
|
+
}
|
|
2415
|
+
resolvedProviderType = resolveSessionProviderType(explicitSession);
|
|
2416
|
+
if (resolvedProviderType) {
|
|
2417
|
+
meshSessionProviderMetadata.set(meshSessionCacheKey(args.node_id, args.session_id), {
|
|
2418
|
+
providerType: resolvedProviderType,
|
|
2419
|
+
providerSessionId: readString(explicitSession?.providerSessionId) || void 0
|
|
2420
|
+
});
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
if (!resolvedProviderType) {
|
|
2424
|
+
return JSON.stringify({
|
|
2425
|
+
success: false,
|
|
2426
|
+
recoverable: true,
|
|
2427
|
+
code: "mesh_target_session_provider_unknown",
|
|
2428
|
+
reason: "mesh_target_session_provider_unknown",
|
|
2429
|
+
transport: "local_ipc",
|
|
2430
|
+
retryRecommended: false,
|
|
2431
|
+
nodeId: args.node_id,
|
|
2432
|
+
sessionId: args.session_id,
|
|
2433
|
+
error: `Local session '${args.session_id}' is live but does not expose providerType/cliType, so agent_command cannot be routed safely.`,
|
|
2434
|
+
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.`
|
|
2435
|
+
});
|
|
2436
|
+
}
|
|
1770
2437
|
const dispatchResult = await commandForNode(ctx, node, "agent_command", {
|
|
1771
2438
|
targetSessionId: args.session_id,
|
|
1772
|
-
|
|
2439
|
+
agentType: resolvedProviderType,
|
|
2440
|
+
cliType: resolvedProviderType,
|
|
2441
|
+
providerType: resolvedProviderType,
|
|
1773
2442
|
action: "send_chat",
|
|
1774
2443
|
message: args.message
|
|
1775
2444
|
});
|
|
1776
2445
|
const dispatchPayload = unwrapCommandPayload(dispatchResult);
|
|
1777
2446
|
if (dispatchPayload?.success === false || dispatchResult?.success === false) {
|
|
2447
|
+
const source = dispatchPayload?.success === false ? dispatchPayload : dispatchResult;
|
|
1778
2448
|
return JSON.stringify({
|
|
2449
|
+
...source && typeof source === "object" ? source : {},
|
|
1779
2450
|
success: false,
|
|
1780
2451
|
nodeId: args.node_id,
|
|
1781
2452
|
sessionId: args.session_id,
|
|
1782
2453
|
error: dispatchPayload?.error || dispatchResult?.error || "agent_command rejected the task"
|
|
1783
2454
|
});
|
|
1784
2455
|
}
|
|
2456
|
+
const taskId = (0, import_node_crypto.randomUUID)();
|
|
1785
2457
|
try {
|
|
1786
2458
|
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
1787
2459
|
kind: "task_dispatched",
|
|
1788
2460
|
nodeId: args.node_id,
|
|
1789
2461
|
sessionId: args.session_id,
|
|
1790
|
-
providerType:
|
|
1791
|
-
payload:
|
|
2462
|
+
providerType: resolvedProviderType,
|
|
2463
|
+
payload: buildDirectTaskPayload(args.message, "local_direct", {
|
|
2464
|
+
taskId,
|
|
2465
|
+
taskMode,
|
|
2466
|
+
providerType: resolvedProviderType,
|
|
2467
|
+
targetSessionId: args.session_id
|
|
2468
|
+
})
|
|
1792
2469
|
});
|
|
1793
2470
|
} catch {
|
|
1794
2471
|
}
|
|
1795
|
-
return JSON.stringify({ success: true, dispatched: true, nodeId: args.node_id, sessionId: args.session_id });
|
|
2472
|
+
return JSON.stringify({ success: true, dispatched: true, source: "direct", taskId, taskMode, providerType: resolvedProviderType, nodeId: args.node_id, sessionId: args.session_id });
|
|
1796
2473
|
}
|
|
1797
2474
|
const task = (0, import_daemon_core.enqueueTask)(ctx.mesh.id, args.message, {
|
|
1798
2475
|
targetNodeId: args.node_id,
|
|
1799
|
-
targetSessionId: args.session_id
|
|
2476
|
+
targetSessionId: args.session_id,
|
|
2477
|
+
taskMode
|
|
1800
2478
|
});
|
|
1801
2479
|
if (isLocalTransport(ctx.transport) || ctx.transport instanceof IpcTransport) {
|
|
1802
2480
|
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
1803
2481
|
});
|
|
1804
2482
|
}
|
|
1805
|
-
const pendingEvents = isLocalTransport(ctx.transport) ? (0, import_daemon_core.drainPendingMeshCoordinatorEvents)() : [];
|
|
1806
|
-
const result = { success: true, nodeId: args.node_id, taskId: task.id, status: task.status };
|
|
2483
|
+
const pendingEvents = isLocalTransport(ctx.transport) ? (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id) : [];
|
|
2484
|
+
const result = { success: true, source: "queue", nodeId: args.node_id, taskId: task.id, status: task.status, taskMode: task.taskMode };
|
|
1807
2485
|
if (pendingEvents.length > 0) {
|
|
1808
2486
|
result.pendingCoordinatorEvents = pendingEvents;
|
|
1809
2487
|
}
|
|
@@ -1823,8 +2501,11 @@ async function meshReadChat(ctx, args) {
|
|
|
1823
2501
|
if (!node) {
|
|
1824
2502
|
return JSON.stringify(buildMissingNodeReadChatRecovery(ctx, args), null, 2);
|
|
1825
2503
|
}
|
|
2504
|
+
if (ctx.transport instanceof IpcTransport || isLocalTransport(ctx.transport)) {
|
|
2505
|
+
await drainCoordinatorPendingEvents(ctx, { nodeIds: [args.node_id] });
|
|
2506
|
+
}
|
|
1826
2507
|
if (isLocalTransport(ctx.transport)) {
|
|
1827
|
-
const cached =
|
|
2508
|
+
const cached = resolveMeshSessionProviderMetadata(ctx, args.node_id, args.session_id);
|
|
1828
2509
|
const providerSessionId = typeof args.provider_session_id === "string" && args.provider_session_id.trim() ? args.provider_session_id.trim() : cached?.providerSessionId;
|
|
1829
2510
|
const result = await commandForNode(ctx, node, "read_chat", {
|
|
1830
2511
|
sessionId: args.session_id,
|
|
@@ -1870,7 +2551,7 @@ async function meshReadChat(ctx, args) {
|
|
|
1870
2551
|
async function meshReadDebug(ctx, args) {
|
|
1871
2552
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
1872
2553
|
if (isLocalTransport(ctx.transport)) {
|
|
1873
|
-
const cached =
|
|
2554
|
+
const cached = resolveMeshSessionProviderMetadata(ctx, args.node_id, args.session_id);
|
|
1874
2555
|
const providerSessionId = typeof args.provider_session_id === "string" && args.provider_session_id.trim() ? args.provider_session_id.trim() : cached?.providerSessionId;
|
|
1875
2556
|
const delivery = args.delivery === "inline" ? void 0 : "daemon_file";
|
|
1876
2557
|
const result = await commandForNode(ctx, node, "get_chat_debug_bundle", {
|
|
@@ -1925,6 +2606,10 @@ async function meshLaunchSession(ctx, args) {
|
|
|
1925
2606
|
const coordinatorNode = resolveCoordinatorNode(ctx);
|
|
1926
2607
|
const coordinatorDaemonId = coordinatorNode?.daemonId || ctx.localDaemonId;
|
|
1927
2608
|
const spawnedSessionVisibility = readSpawnedSessionVisibility(ctx.mesh.policy);
|
|
2609
|
+
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
2610
|
+
if (node.daemonId && !isLocalNode && !coordinatorDaemonId) {
|
|
2611
|
+
return JSON.stringify(buildMissingCoordinatorDaemonIdFailure(ctx, node, resolvedProviderType), null, 2);
|
|
2612
|
+
}
|
|
1928
2613
|
let result;
|
|
1929
2614
|
try {
|
|
1930
2615
|
result = await commandForNode(ctx, node, "launch_cli", {
|
|
@@ -1965,7 +2650,6 @@ async function meshLaunchSession(ctx, args) {
|
|
|
1965
2650
|
});
|
|
1966
2651
|
} catch {
|
|
1967
2652
|
}
|
|
1968
|
-
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
1969
2653
|
if (ctx.transport instanceof IpcTransport && node.daemonId && !isLocalNode) {
|
|
1970
2654
|
ctx.transport.meshCommand(node.daemonId, "trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
1971
2655
|
});
|
|
@@ -1990,6 +2674,9 @@ async function meshLaunchSession(ctx, args) {
|
|
|
1990
2674
|
const coordinatorNode = resolveCoordinatorNode(ctx);
|
|
1991
2675
|
const coordinatorDaemonId = coordinatorNode?.daemonId || ctx.localDaemonId;
|
|
1992
2676
|
const spawnedSessionVisibility = readSpawnedSessionVisibility(ctx.mesh.policy);
|
|
2677
|
+
if (!coordinatorDaemonId) {
|
|
2678
|
+
return JSON.stringify(buildMissingCoordinatorDaemonIdFailure(ctx, node, resolvedProviderType), null, 2);
|
|
2679
|
+
}
|
|
1993
2680
|
try {
|
|
1994
2681
|
const res = await ctx.transport.launch(node.daemonId, {
|
|
1995
2682
|
type: resolvedProviderType,
|
|
@@ -2028,7 +2715,7 @@ async function meshGitStatus(ctx, args) {
|
|
|
2028
2715
|
const submoduleIgnorePaths = node.policy?.submoduleIgnorePaths || [];
|
|
2029
2716
|
try {
|
|
2030
2717
|
if (!isLocalTransport(ctx.transport) && node.daemonId) {
|
|
2031
|
-
const result = await ctx.transport.gitStatus(node.daemonId, node.workspace, true);
|
|
2718
|
+
const result = await ctx.transport.gitStatus(node.daemonId, node.workspace, true, true);
|
|
2032
2719
|
return JSON.stringify({
|
|
2033
2720
|
nodeId: args.node_id,
|
|
2034
2721
|
workspace: node.workspace,
|
|
@@ -2040,6 +2727,7 @@ async function meshGitStatus(ctx, args) {
|
|
|
2040
2727
|
} else if (isLocalTransport(ctx.transport)) {
|
|
2041
2728
|
const statusResult = await commandForNode(ctx, node, "git_status", {
|
|
2042
2729
|
workspace: node.workspace,
|
|
2730
|
+
refreshUpstream: true,
|
|
2043
2731
|
includeSubmodules: autoDiscoverSubmodules,
|
|
2044
2732
|
submoduleIgnorePaths: submoduleIgnorePaths.length > 0 ? submoduleIgnorePaths : void 0
|
|
2045
2733
|
});
|
|
@@ -2069,6 +2757,51 @@ async function meshGitStatus(ctx, args) {
|
|
|
2069
2757
|
}, null, 2);
|
|
2070
2758
|
}
|
|
2071
2759
|
}
|
|
2760
|
+
async function meshFastForwardNode(ctx, args) {
|
|
2761
|
+
await refreshMeshFromDaemon(ctx);
|
|
2762
|
+
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2763
|
+
const submoduleIgnorePaths = node.policy?.submoduleIgnorePaths || [];
|
|
2764
|
+
if (node.policy?.readOnly) {
|
|
2765
|
+
return JSON.stringify({
|
|
2766
|
+
success: false,
|
|
2767
|
+
code: "node_read_only",
|
|
2768
|
+
nodeId: args.node_id,
|
|
2769
|
+
workspace: node.workspace,
|
|
2770
|
+
allowed: false,
|
|
2771
|
+
willRun: false,
|
|
2772
|
+
executed: false,
|
|
2773
|
+
blockingReasons: ["node_read_only"]
|
|
2774
|
+
}, null, 2);
|
|
2775
|
+
}
|
|
2776
|
+
try {
|
|
2777
|
+
const dryRun = args.dry_run === true || args.execute !== true;
|
|
2778
|
+
const result = await commandForNode(ctx, node, "fast_forward_mesh_node", {
|
|
2779
|
+
meshId: ctx.mesh.id,
|
|
2780
|
+
nodeId: node.id,
|
|
2781
|
+
workspace: node.workspace,
|
|
2782
|
+
branch: typeof args.branch === "string" ? args.branch : void 0,
|
|
2783
|
+
execute: args.execute === true && args.dry_run !== true,
|
|
2784
|
+
dryRun,
|
|
2785
|
+
updateSubmodules: args.update_submodules === true,
|
|
2786
|
+
submoduleIgnorePaths: submoduleIgnorePaths.length > 0 ? submoduleIgnorePaths : void 0
|
|
2787
|
+
});
|
|
2788
|
+
return JSON.stringify(unwrapCommandPayload(result), null, 2);
|
|
2789
|
+
} catch (e) {
|
|
2790
|
+
const failure = buildCoordinatorP2pRelayFailure(e, {
|
|
2791
|
+
command: "fast_forward_mesh_node",
|
|
2792
|
+
targetDaemonId: node.daemonId,
|
|
2793
|
+
nodeId: args.node_id
|
|
2794
|
+
});
|
|
2795
|
+
return JSON.stringify({
|
|
2796
|
+
...failure,
|
|
2797
|
+
workspace: node.workspace,
|
|
2798
|
+
allowed: false,
|
|
2799
|
+
willRun: false,
|
|
2800
|
+
executed: false,
|
|
2801
|
+
blockingReasons: [failure.code || "mesh_fast_forward_unavailable"]
|
|
2802
|
+
}, null, 2);
|
|
2803
|
+
}
|
|
2804
|
+
}
|
|
2072
2805
|
async function meshCheckpoint(ctx, args) {
|
|
2073
2806
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2074
2807
|
if (node.policy?.readOnly) {
|
|
@@ -2154,6 +2887,7 @@ async function meshCloneNode(ctx, args) {
|
|
|
2154
2887
|
if (existingIndex >= 0) ctx.mesh.nodes[existingIndex] = clonePayload.node;
|
|
2155
2888
|
else ctx.mesh.nodes.push(clonePayload.node);
|
|
2156
2889
|
ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2890
|
+
await syncCoordinatorDaemonMeshCache(ctx);
|
|
2157
2891
|
}
|
|
2158
2892
|
return JSON.stringify(result, null, 2);
|
|
2159
2893
|
} else if (!isLocalTransport(ctx.transport) && sourceNode.daemonId) {
|
|
@@ -2171,6 +2905,7 @@ async function meshCloneNode(ctx, args) {
|
|
|
2171
2905
|
if (existingIndex >= 0) ctx.mesh.nodes[existingIndex] = clonePayload.node;
|
|
2172
2906
|
else ctx.mesh.nodes.push(clonePayload.node);
|
|
2173
2907
|
ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2908
|
+
await syncCoordinatorDaemonMeshCache(ctx);
|
|
2174
2909
|
}
|
|
2175
2910
|
return JSON.stringify(res, null, 2);
|
|
2176
2911
|
} catch (e) {
|
|
@@ -2266,6 +3001,43 @@ async function meshRemoveNode(ctx, args) {
|
|
|
2266
3001
|
return JSON.stringify({ error: "Cloud mesh remove_node requires node daemonId" });
|
|
2267
3002
|
}
|
|
2268
3003
|
}
|
|
3004
|
+
function resolveRefineConfigNode(ctx, nodeId) {
|
|
3005
|
+
if (nodeId) return findNode(ctx.mesh, nodeId);
|
|
3006
|
+
const node = ctx.mesh.nodes.find((entry) => !!entry.workspace);
|
|
3007
|
+
if (!node) throw new Error("No mesh node with a workspace is available");
|
|
3008
|
+
return node;
|
|
3009
|
+
}
|
|
3010
|
+
async function meshRefineConfigSchema(ctx) {
|
|
3011
|
+
const node = resolveRefineConfigNode(ctx);
|
|
3012
|
+
const result = await commandForNode(ctx, node, "get_mesh_refine_config_schema", {});
|
|
3013
|
+
return JSON.stringify(result, null, 2);
|
|
3014
|
+
}
|
|
3015
|
+
async function meshValidateRefineConfig(ctx, args) {
|
|
3016
|
+
const node = resolveRefineConfigNode(ctx, args.node_id);
|
|
3017
|
+
const result = await commandForNode(ctx, node, "validate_mesh_refine_config", {
|
|
3018
|
+
workspace: node.workspace,
|
|
3019
|
+
inlineMesh: ctx.mesh,
|
|
3020
|
+
...args.config ? { config: args.config } : {}
|
|
3021
|
+
});
|
|
3022
|
+
return JSON.stringify(result, null, 2);
|
|
3023
|
+
}
|
|
3024
|
+
async function meshSuggestRefineConfig(ctx, args) {
|
|
3025
|
+
const node = resolveRefineConfigNode(ctx, args.node_id);
|
|
3026
|
+
const result = await commandForNode(ctx, node, "suggest_mesh_refine_config", {
|
|
3027
|
+
workspace: node.workspace,
|
|
3028
|
+
inlineMesh: ctx.mesh
|
|
3029
|
+
});
|
|
3030
|
+
return JSON.stringify(result, null, 2);
|
|
3031
|
+
}
|
|
3032
|
+
async function meshRefinePlan(ctx, args) {
|
|
3033
|
+
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
3034
|
+
const result = await commandForNode(ctx, node, "plan_mesh_refine_node", {
|
|
3035
|
+
meshId: ctx.mesh.id,
|
|
3036
|
+
nodeId: args.node_id,
|
|
3037
|
+
inlineMesh: ctx.mesh
|
|
3038
|
+
});
|
|
3039
|
+
return JSON.stringify(result, null, 2);
|
|
3040
|
+
}
|
|
2269
3041
|
async function meshRefineNode(ctx, args) {
|
|
2270
3042
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2271
3043
|
if (isLocalTransport(ctx.transport)) {
|
|
@@ -2274,7 +3046,7 @@ async function meshRefineNode(ctx, args) {
|
|
|
2274
3046
|
nodeId: args.node_id,
|
|
2275
3047
|
inlineMesh: ctx.mesh
|
|
2276
3048
|
});
|
|
2277
|
-
if (result?.success && result.removeResult?.removed !== false) {
|
|
3049
|
+
if (result?.success && result.async !== true && result.removeResult?.removed !== false) {
|
|
2278
3050
|
const idx = ctx.mesh.nodes.findIndex((n) => n.id === args.node_id);
|
|
2279
3051
|
if (idx >= 0) {
|
|
2280
3052
|
ctx.mesh.nodes.splice(idx, 1);
|
|
@@ -2289,7 +3061,7 @@ async function meshRefineNode(ctx, args) {
|
|
|
2289
3061
|
nodeId: args.node_id,
|
|
2290
3062
|
inlineMesh: ctx.mesh
|
|
2291
3063
|
});
|
|
2292
|
-
if (res?.success && res.removeResult?.removed !== false) {
|
|
3064
|
+
if (res?.success && res.async !== true && res.removeResult?.removed !== false) {
|
|
2293
3065
|
const idx = ctx.mesh.nodes.findIndex((n) => n.id === args.node_id);
|
|
2294
3066
|
if (idx >= 0) {
|
|
2295
3067
|
ctx.mesh.nodes.splice(idx, 1);
|
|
@@ -2326,13 +3098,13 @@ var STANDARD_TOOLS = [
|
|
|
2326
3098
|
function buildMcpHelpText() {
|
|
2327
3099
|
const meshTools = ALL_MESH_TOOLS.map((tool) => tool.name);
|
|
2328
3100
|
return `
|
|
2329
|
-
|
|
3101
|
+
ADHDev MCP Server
|
|
2330
3102
|
|
|
2331
3103
|
Usage:
|
|
2332
|
-
adhdev
|
|
2333
|
-
adhdev
|
|
2334
|
-
adhdev
|
|
2335
|
-
adhdev-mcp --
|
|
3104
|
+
adhdev mcp Local mode (requires standalone daemon)
|
|
3105
|
+
adhdev mcp --api-key <key> Cloud mode (ADHDev cloud API)
|
|
3106
|
+
adhdev mcp --mode ipc --repo-mesh <mesh_id> Cloud daemon IPC mesh mode
|
|
3107
|
+
adhdev-mcp --help Compatibility bin (same server, legacy package entrypoint)
|
|
2336
3108
|
|
|
2337
3109
|
Options:
|
|
2338
3110
|
--mode <mode> Transport: local, cloud, or ipc
|
|
@@ -2357,6 +3129,7 @@ Mesh tools: ${meshTools.join(", ")}
|
|
|
2357
3129
|
// src/server.ts
|
|
2358
3130
|
var import_server = require("@modelcontextprotocol/sdk/server/index.js");
|
|
2359
3131
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
3132
|
+
var import_node_os = __toESM(require("os"));
|
|
2360
3133
|
var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
2361
3134
|
|
|
2362
3135
|
// src/transports/local.ts
|
|
@@ -2511,8 +3284,8 @@ var CloudTransport = class {
|
|
|
2511
3284
|
if (!res.ok) throw new Error(`Approve failed: ${res.status}`);
|
|
2512
3285
|
return res.json();
|
|
2513
3286
|
}
|
|
2514
|
-
async gitStatus(daemonId, workspace, includeDiff = true) {
|
|
2515
|
-
const params = new URLSearchParams({ workspace, includeDiff: String(includeDiff) });
|
|
3287
|
+
async gitStatus(daemonId, workspace, includeDiff = true, refreshUpstream = false) {
|
|
3288
|
+
const params = new URLSearchParams({ workspace, includeDiff: String(includeDiff), refreshUpstream: String(refreshUpstream) });
|
|
2516
3289
|
const res = await fetch(
|
|
2517
3290
|
`${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/git-status?${params}`,
|
|
2518
3291
|
{ headers: this.headers() }
|
|
@@ -2907,6 +3680,22 @@ function formatChatResult(result, sessionId, format, limit = 50, compact = false
|
|
|
2907
3680
|
}))
|
|
2908
3681
|
}, null, 2);
|
|
2909
3682
|
}
|
|
3683
|
+
if ((format === "text" || format === void 0) && compact && compactPayload) {
|
|
3684
|
+
const lines2 = outputMessages.slice(-limit).map((m) => {
|
|
3685
|
+
const role = m.role === "user" ? "User" : m.role === "assistant" ? "Agent" : m.role;
|
|
3686
|
+
const content = messageContent(m);
|
|
3687
|
+
const truncated = content.length > 500 ? `${content.slice(0, 500)}\u2026` : content;
|
|
3688
|
+
return `[${role}] ${truncated}`;
|
|
3689
|
+
});
|
|
3690
|
+
if (compactPayload.summary) {
|
|
3691
|
+
const truncatedSummary = compactPayload.summary.length > 500 ? `${compactPayload.summary.slice(0, 500)}\u2026` : compactPayload.summary;
|
|
3692
|
+
lines2.push(`[Summary] ${truncatedSummary}`);
|
|
3693
|
+
}
|
|
3694
|
+
if (result?.pollingAdvisory) {
|
|
3695
|
+
lines2.push(`Advisory: ${result.pollingAdvisory.message}`);
|
|
3696
|
+
}
|
|
3697
|
+
return lines2.length > 0 ? lines2.join("\n\n") : "No messages in chat.";
|
|
3698
|
+
}
|
|
2910
3699
|
if (outputMessages.length === 0) {
|
|
2911
3700
|
return result?.pollingAdvisory ? `No messages in chat.
|
|
2912
3701
|
|
|
@@ -3874,6 +4663,7 @@ async function startMcpServer(opts) {
|
|
|
3874
4663
|
requirePreTaskCheckpoint: false,
|
|
3875
4664
|
requirePostTaskCheckpoint: true,
|
|
3876
4665
|
requireApprovalForPush: true,
|
|
4666
|
+
allowAutoPublishSubmoduleMainCommits: false,
|
|
3877
4667
|
requireApprovalForDestructiveGit: true,
|
|
3878
4668
|
dirtyWorkspaceBehavior: "warn",
|
|
3879
4669
|
maxParallelTasks: 2,
|
|
@@ -3930,6 +4720,7 @@ async function startMcpServer(opts) {
|
|
|
3930
4720
|
}
|
|
3931
4721
|
let localDaemonId;
|
|
3932
4722
|
let localMachineId;
|
|
4723
|
+
let coordinatorHostname = import_node_os.default.hostname();
|
|
3933
4724
|
if (transport instanceof LocalTransport || transport instanceof IpcTransport) {
|
|
3934
4725
|
try {
|
|
3935
4726
|
const { loadConfig } = await import("@adhdev/daemon-core");
|
|
@@ -3942,11 +4733,13 @@ async function startMcpServer(opts) {
|
|
|
3942
4733
|
try {
|
|
3943
4734
|
const statusResult = await transport.getStatus();
|
|
3944
4735
|
const instanceId = typeof statusResult?.status?.instanceId === "string" ? statusResult.status.instanceId.trim() : "";
|
|
4736
|
+
const hostname = typeof statusResult?.status?.hostname === "string" ? statusResult.status.hostname.trim() : typeof statusResult?.status?.machine?.hostname === "string" ? statusResult.status.machine.hostname.trim() : "";
|
|
3945
4737
|
if (instanceId) localDaemonId = instanceId;
|
|
4738
|
+
if (hostname) coordinatorHostname = hostname;
|
|
3946
4739
|
} catch {
|
|
3947
4740
|
}
|
|
3948
4741
|
}
|
|
3949
|
-
const meshCtx = { mesh, transport, ...localDaemonId ? { localDaemonId } : {}, ...localMachineId ? { localMachineId } : {} };
|
|
4742
|
+
const meshCtx = { mesh, transport, ...localDaemonId ? { localDaemonId } : {}, ...localMachineId ? { localMachineId } : {}, ...coordinatorHostname ? { coordinatorHostname } : {} };
|
|
3950
4743
|
const coordinatorPrompt = await buildMeshModeCoordinatorPrompt(mesh);
|
|
3951
4744
|
const server2 = new import_server.Server(
|
|
3952
4745
|
{ name: "adhdev-mcp-server", version: "0.9.81" },
|
|
@@ -4007,6 +4800,9 @@ async function startMcpServer(opts) {
|
|
|
4007
4800
|
case "mesh_git_status":
|
|
4008
4801
|
text = await meshGitStatus(meshCtx, a);
|
|
4009
4802
|
break;
|
|
4803
|
+
case "mesh_fast_forward_node":
|
|
4804
|
+
text = await meshFastForwardNode(meshCtx, a);
|
|
4805
|
+
break;
|
|
4010
4806
|
case "mesh_checkpoint":
|
|
4011
4807
|
text = await meshCheckpoint(meshCtx, a);
|
|
4012
4808
|
break;
|
|
@@ -4022,6 +4818,18 @@ async function startMcpServer(opts) {
|
|
|
4022
4818
|
case "mesh_refine_node":
|
|
4023
4819
|
text = await meshRefineNode(meshCtx, a);
|
|
4024
4820
|
break;
|
|
4821
|
+
case "mesh_refine_config_schema":
|
|
4822
|
+
text = await meshRefineConfigSchema(meshCtx);
|
|
4823
|
+
break;
|
|
4824
|
+
case "mesh_validate_refine_config":
|
|
4825
|
+
text = await meshValidateRefineConfig(meshCtx, a);
|
|
4826
|
+
break;
|
|
4827
|
+
case "mesh_suggest_refine_config":
|
|
4828
|
+
text = await meshSuggestRefineConfig(meshCtx, a);
|
|
4829
|
+
break;
|
|
4830
|
+
case "mesh_refine_plan":
|
|
4831
|
+
text = await meshRefinePlan(meshCtx, a);
|
|
4832
|
+
break;
|
|
4025
4833
|
case "mesh_cleanup_sessions":
|
|
4026
4834
|
text = await meshCleanupSessions(meshCtx, a);
|
|
4027
4835
|
break;
|