@adhdev/daemon-standalone 0.9.82-rc.7 → 0.9.82-rc.71
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 +24875 -21162
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/public/assets/index-Bso1b8Lh.css +1 -0
- package/public/assets/index-R9GDvGJ8.js +98 -0
- package/public/assets/{terminal-DCVDkSIp.js → terminal-NZFQPQy6.js} +4 -4
- package/public/assets/{vendor-piMzuE8Y.js → vendor-C7br1-G2.js} +29 -29
- package/public/index.html +3 -3
- package/vendor/mcp-server/index.js +647 -70
- package/vendor/mcp-server/index.js.map +1 -1
- package/public/assets/index-8xD0Z30r.js +0 -98
- package/public/assets/index-B8fg8KB1.css +0 -1
|
@@ -35,9 +35,19 @@ __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: 6e4,
|
|
47
|
+
agent_command: 3e4,
|
|
48
|
+
git_status: 45e3,
|
|
49
|
+
mesh_status: 12e4
|
|
50
|
+
};
|
|
41
51
|
var IpcTransport = class {
|
|
42
52
|
port;
|
|
43
53
|
path;
|
|
@@ -85,7 +95,7 @@ var IpcTransport = class {
|
|
|
85
95
|
}
|
|
86
96
|
fn();
|
|
87
97
|
};
|
|
88
|
-
const timeoutMs = type
|
|
98
|
+
const timeoutMs = IPC_COMMAND_TIMEOUTS_MS[type] ?? DEFAULT_IPC_COMMAND_TIMEOUT_MS;
|
|
89
99
|
const timeout = setTimeout(() => {
|
|
90
100
|
finish(() => reject(new Error(`Daemon IPC command '${type}' timed out after ${Math.round(timeoutMs / 1e3)}s`)));
|
|
91
101
|
}, timeoutMs);
|
|
@@ -143,6 +153,10 @@ function isLocalTransport(transport) {
|
|
|
143
153
|
}
|
|
144
154
|
|
|
145
155
|
// src/tools/chat-compact.ts
|
|
156
|
+
function isAssistantLike(message) {
|
|
157
|
+
const role = String(message?.role ?? "").toLowerCase();
|
|
158
|
+
return role === "assistant" || role === "agent";
|
|
159
|
+
}
|
|
146
160
|
function messageContent(message) {
|
|
147
161
|
const content = message?.content;
|
|
148
162
|
if (typeof content === "string") return content;
|
|
@@ -161,16 +175,22 @@ function isCoordinatorVisibleMessage(message) {
|
|
|
161
175
|
if (meta?.internal === true || meta?.debug === true || meta?.control === true || meta?.userVisible === false || meta?.user_visible === false) return false;
|
|
162
176
|
return role === "user" || role === "assistant" || role === "agent";
|
|
163
177
|
}
|
|
178
|
+
function buildCompactMessageTail(visibleMessages, opts) {
|
|
179
|
+
const summary = typeof opts.summary === "string" ? opts.summary.trim() : "";
|
|
180
|
+
const shouldOmitSummaryMessage = !!summary && !!opts.finalAssistant && isAssistantLike(opts.finalAssistant) && messageContent(opts.finalAssistant).trim() === summary;
|
|
181
|
+
const sourceMessages = shouldOmitSummaryMessage ? visibleMessages.filter((message) => message !== opts.finalAssistant) : visibleMessages;
|
|
182
|
+
return sourceMessages.slice(-opts.limit);
|
|
183
|
+
}
|
|
164
184
|
function compactChatPayload(payload, opts = {}) {
|
|
165
185
|
const rawMessages = Array.isArray(payload?.messages) ? payload.messages : [];
|
|
166
186
|
const visible = rawMessages.filter(isCoordinatorVisibleMessage);
|
|
167
187
|
const limit = Math.max(1, Math.min(opts.limit ?? 10, 10));
|
|
168
|
-
const messages = visible.slice(-limit);
|
|
169
188
|
const finalAssistant = [...visible].reverse().find((message) => {
|
|
170
189
|
const role = String(message?.role ?? "").toLowerCase();
|
|
171
190
|
return (role === "assistant" || role === "agent") && messageContent(message).trim();
|
|
172
191
|
});
|
|
173
192
|
const summary = typeof payload?.summary === "string" && payload.summary.trim() ? payload.summary.trim() : messageContent(finalAssistant).trim();
|
|
193
|
+
const messages = buildCompactMessageTail(visible, { summary, finalAssistant, limit });
|
|
174
194
|
return {
|
|
175
195
|
success: payload?.success !== false,
|
|
176
196
|
compact: true,
|
|
@@ -235,6 +255,30 @@ var meshSessionProviderMetadata = /* @__PURE__ */ new Map();
|
|
|
235
255
|
function readString(value) {
|
|
236
256
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
237
257
|
}
|
|
258
|
+
function summarizeTaskMessage(message) {
|
|
259
|
+
const taskSummary = message.replace(/\s+/g, " ").trim();
|
|
260
|
+
const taskTitle = taskSummary.length > 96 ? `${taskSummary.slice(0, 93)}...` : taskSummary;
|
|
261
|
+
return { taskTitle: taskTitle || "(untitled task)", taskSummary };
|
|
262
|
+
}
|
|
263
|
+
function buildDirectTaskPayload(message, via, opts) {
|
|
264
|
+
const descriptor = summarizeTaskMessage(message);
|
|
265
|
+
return {
|
|
266
|
+
source: "direct",
|
|
267
|
+
via,
|
|
268
|
+
taskId: opts.taskId,
|
|
269
|
+
message,
|
|
270
|
+
taskTitle: descriptor.taskTitle,
|
|
271
|
+
taskSummary: descriptor.taskSummary,
|
|
272
|
+
...opts.taskMode ? { taskMode: opts.taskMode } : {},
|
|
273
|
+
...opts.providerType ? { providerType: opts.providerType } : {},
|
|
274
|
+
...opts.targetSessionId ? { targetSessionId: opts.targetSessionId } : {}
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function findNode(mesh, nodeId) {
|
|
278
|
+
const node = mesh.nodes.find((n) => n.id === nodeId);
|
|
279
|
+
if (!node) throw new Error(`Node '${nodeId}' is not a member of mesh '${mesh.name}'`);
|
|
280
|
+
return node;
|
|
281
|
+
}
|
|
238
282
|
var DUPLICATE_DISPATCH_WINDOW_MS = 6e4;
|
|
239
283
|
var STALE_ASSIGNED_QUEUE_MS = 30 * 6e4;
|
|
240
284
|
var OLD_HISTORICAL_QUEUE_RECORD_MS = 7 * 24 * 60 * 6e4;
|
|
@@ -246,15 +290,24 @@ async function refreshMeshFromDaemon(ctx) {
|
|
|
246
290
|
const result = await ctx.transport.command("get_mesh", { meshId: ctx.mesh.id });
|
|
247
291
|
if (!result?.success || !Array.isArray(result.mesh?.nodes)) return;
|
|
248
292
|
const refreshedNodes = result.mesh.nodes.filter((n) => n?.id).map((n) => n);
|
|
249
|
-
if (!refreshedNodes.length) return;
|
|
250
293
|
ctx.mesh.nodes.splice(0, ctx.mesh.nodes.length, ...refreshedNodes);
|
|
251
294
|
ctx.mesh.updatedAt = result.mesh.updatedAt ?? ctx.mesh.updatedAt;
|
|
252
295
|
} catch {
|
|
253
296
|
}
|
|
254
297
|
}
|
|
298
|
+
async function syncCoordinatorDaemonMeshCache(ctx) {
|
|
299
|
+
if (!(ctx.transport instanceof IpcTransport)) return;
|
|
300
|
+
try {
|
|
301
|
+
await ctx.transport.command("get_mesh", {
|
|
302
|
+
meshId: ctx.mesh.id,
|
|
303
|
+
inlineMesh: ctx.mesh
|
|
304
|
+
});
|
|
305
|
+
} catch {
|
|
306
|
+
}
|
|
307
|
+
}
|
|
255
308
|
async function findNodeWithRefresh(ctx, nodeId) {
|
|
256
309
|
const hit = ctx.mesh.nodes.find((n) => n.id === nodeId);
|
|
257
|
-
if (hit) return hit;
|
|
310
|
+
if (hit && !hit.isLocalWorktree) return hit;
|
|
258
311
|
await refreshMeshFromDaemon(ctx);
|
|
259
312
|
const refreshed = ctx.mesh.nodes.find((n) => n.id === nodeId);
|
|
260
313
|
if (!refreshed) throw new Error(`Node '${nodeId}' is not a member of mesh '${ctx.mesh.name}'`);
|
|
@@ -262,7 +315,7 @@ async function findNodeWithRefresh(ctx, nodeId) {
|
|
|
262
315
|
}
|
|
263
316
|
async function findOptionalNodeWithRefresh(ctx, nodeId) {
|
|
264
317
|
const hit = ctx.mesh.nodes.find((n) => n.id === nodeId);
|
|
265
|
-
if (hit) return hit;
|
|
318
|
+
if (hit && !hit.isLocalWorktree) return hit;
|
|
266
319
|
await refreshMeshFromDaemon(ctx);
|
|
267
320
|
return ctx.mesh.nodes.find((n) => n.id === nodeId) ?? null;
|
|
268
321
|
}
|
|
@@ -314,9 +367,26 @@ function buildMissingNodeReadChatRecovery(ctx, args) {
|
|
|
314
367
|
readDebugLocator: readString(lastTerminal?.payload?.readDebugLocator) || readString(lastTerminal?.payload?.debugBundlePath)
|
|
315
368
|
};
|
|
316
369
|
if (finalSummary) {
|
|
370
|
+
if (args.compact === true) {
|
|
371
|
+
return {
|
|
372
|
+
...compactChatPayload({
|
|
373
|
+
success: true,
|
|
374
|
+
status: "idle",
|
|
375
|
+
providerSessionId,
|
|
376
|
+
summary: finalSummary,
|
|
377
|
+
messages: [{ role: "assistant", content: finalSummary, isHistorical: true }]
|
|
378
|
+
}, {
|
|
379
|
+
nodeId: args.node_id,
|
|
380
|
+
sessionId: args.session_id,
|
|
381
|
+
limit: args.tail ?? 10
|
|
382
|
+
}),
|
|
383
|
+
recoveredFromLedger: true,
|
|
384
|
+
ledger
|
|
385
|
+
};
|
|
386
|
+
}
|
|
317
387
|
return {
|
|
318
388
|
success: true,
|
|
319
|
-
compact:
|
|
389
|
+
compact: false,
|
|
320
390
|
recoveredFromLedger: true,
|
|
321
391
|
nodeId: args.node_id,
|
|
322
392
|
sessionId: args.session_id,
|
|
@@ -368,6 +438,14 @@ function buildMissingNodeReadChatRecovery(ctx, args) {
|
|
|
368
438
|
function readSessionRecordId(session) {
|
|
369
439
|
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
440
|
}
|
|
441
|
+
function extractStatusMetadataSessions(value) {
|
|
442
|
+
const payload = unwrapCommandPayload(value);
|
|
443
|
+
const status = payload?.status && typeof payload.status === "object" ? payload.status : payload;
|
|
444
|
+
return Array.isArray(status?.sessions) ? status.sessions : [];
|
|
445
|
+
}
|
|
446
|
+
function resolveSessionProviderType(session) {
|
|
447
|
+
return readString(session?.providerType) || readString(session?.cliType) || readString(session?.agentType) || "";
|
|
448
|
+
}
|
|
371
449
|
function addSessionRecord(target, session) {
|
|
372
450
|
if (!session || typeof session !== "object" || isTerminalSessionRecord(session)) return;
|
|
373
451
|
const sessionId = readSessionRecordId(session);
|
|
@@ -475,6 +553,18 @@ function filterQueueForView(queue, view, statuses) {
|
|
|
475
553
|
if (view === "historical") return queue.filter((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
|
|
476
554
|
return queue;
|
|
477
555
|
}
|
|
556
|
+
function prioritizeActiveQueueRows(queue) {
|
|
557
|
+
const active = [];
|
|
558
|
+
const historical = [];
|
|
559
|
+
const other = [];
|
|
560
|
+
for (const task of queue) {
|
|
561
|
+
const status = String(task?.status || "");
|
|
562
|
+
if (ACTIVE_QUEUE_STATUSES.has(status)) active.push(task);
|
|
563
|
+
else if (HISTORICAL_QUEUE_STATUSES.has(status)) historical.push(task);
|
|
564
|
+
else other.push(task);
|
|
565
|
+
}
|
|
566
|
+
return [...active, ...other, ...historical];
|
|
567
|
+
}
|
|
478
568
|
function slimQueueTask(task) {
|
|
479
569
|
return {
|
|
480
570
|
id: task?.id,
|
|
@@ -580,22 +670,59 @@ function isIdleSessionRecord(session) {
|
|
|
580
670
|
const chatStatus = typeof session?.activeChat?.status === "string" ? session.activeChat.status.toLowerCase() : "";
|
|
581
671
|
return status === "idle" || chatStatus === "waiting_input";
|
|
582
672
|
}
|
|
673
|
+
function isMeshOwnedDelegateSession(session, meshId, nodeId) {
|
|
674
|
+
const settings = session?.settings;
|
|
675
|
+
const sessionMeshId = typeof settings?.meshNodeFor === "string" ? settings.meshNodeFor.trim() : "";
|
|
676
|
+
const coordinatorDaemonId = typeof settings?.meshCoordinatorDaemonId === "string" ? settings.meshCoordinatorDaemonId.trim() : "";
|
|
677
|
+
const sessionNodeId = typeof settings?.meshNodeId === "string" ? settings.meshNodeId.trim() : "";
|
|
678
|
+
if (sessionMeshId !== meshId || !coordinatorDaemonId) return false;
|
|
679
|
+
return !sessionNodeId || sessionNodeId === nodeId;
|
|
680
|
+
}
|
|
583
681
|
function chooseDispatchableSession(sessions, providerType, meshId, nodeId) {
|
|
584
682
|
const live = sessions.filter((session) => !isTerminalSessionRecord(session));
|
|
585
683
|
const matchingProvider = (session) => !providerType || session?.providerType === providerType || session?.cliType === providerType;
|
|
586
|
-
const isMeshOwnedDelegateSession = (session) => {
|
|
587
|
-
const settings = session?.settings;
|
|
588
|
-
const sessionMeshId = typeof settings?.meshNodeFor === "string" ? settings.meshNodeFor.trim() : "";
|
|
589
|
-
const coordinatorDaemonId = typeof settings?.meshCoordinatorDaemonId === "string" ? settings.meshCoordinatorDaemonId.trim() : "";
|
|
590
|
-
const sessionNodeId = typeof settings?.meshNodeId === "string" ? settings.meshNodeId.trim() : "";
|
|
591
|
-
if (sessionMeshId !== meshId || !coordinatorDaemonId) return false;
|
|
592
|
-
return !sessionNodeId || sessionNodeId === nodeId;
|
|
593
|
-
};
|
|
594
684
|
const meshSessions = live.filter(
|
|
595
|
-
(session) => isMeshOwnedDelegateSession(session)
|
|
685
|
+
(session) => isMeshOwnedDelegateSession(session, meshId, nodeId)
|
|
596
686
|
);
|
|
597
687
|
return meshSessions.find((session) => isIdleSessionRecord(session) && matchingProvider(session)) || meshSessions.find(matchingProvider) || void 0;
|
|
598
688
|
}
|
|
689
|
+
function buildRelayUnsafeRemoteSessionFailure(ctx, node, sessionId, providerType) {
|
|
690
|
+
return {
|
|
691
|
+
success: false,
|
|
692
|
+
recoverable: true,
|
|
693
|
+
code: "mesh_delegate_session_missing_relay_metadata",
|
|
694
|
+
reason: "mesh_delegate_session_missing_relay_metadata",
|
|
695
|
+
transport: "mesh_transport",
|
|
696
|
+
retryRecommended: true,
|
|
697
|
+
meshId: ctx.mesh.id,
|
|
698
|
+
nodeId: node.id,
|
|
699
|
+
daemonId: node.daemonId,
|
|
700
|
+
workspace: node.workspace,
|
|
701
|
+
sessionId,
|
|
702
|
+
...providerType ? { resolvedProviderType: providerType } : {},
|
|
703
|
+
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.`,
|
|
704
|
+
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.`,
|
|
705
|
+
noFallbackReason: "Blindly reusing a remote session without mesh relay metadata would silently drop task_completed / generating_completed events."
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
function buildMissingCoordinatorDaemonIdFailure(ctx, node, providerType) {
|
|
709
|
+
return {
|
|
710
|
+
success: false,
|
|
711
|
+
recoverable: true,
|
|
712
|
+
code: "mesh_coordinator_daemon_unknown",
|
|
713
|
+
reason: "mesh_coordinator_daemon_unknown",
|
|
714
|
+
transport: "mesh_transport",
|
|
715
|
+
retryRecommended: true,
|
|
716
|
+
meshId: ctx.mesh.id,
|
|
717
|
+
nodeId: node.id,
|
|
718
|
+
daemonId: node.daemonId,
|
|
719
|
+
workspace: node.workspace,
|
|
720
|
+
...providerType ? { resolvedProviderType: providerType } : {},
|
|
721
|
+
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.`,
|
|
722
|
+
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.",
|
|
723
|
+
noFallbackReason: "Launching without meshCoordinatorDaemonId would create a worker session that can finish work but cannot emit task_completed / generating_completed back to the coordinator."
|
|
724
|
+
};
|
|
725
|
+
}
|
|
599
726
|
function findNestedPayload(value, predicate) {
|
|
600
727
|
const seen = /* @__PURE__ */ new Set();
|
|
601
728
|
const stack = [{ payload: value, depth: 0 }];
|
|
@@ -623,12 +750,16 @@ function extractGitDiff(value) {
|
|
|
623
750
|
}
|
|
624
751
|
function extractSubmodules(value, ignorePaths) {
|
|
625
752
|
const payload = unwrapCommandPayload(value);
|
|
626
|
-
const subs = payload?.submodules ?? value?.submodules;
|
|
753
|
+
const subs = payload?.status?.submodules ?? payload?.submodules ?? value?.status?.submodules ?? value?.submodules;
|
|
627
754
|
if (!Array.isArray(subs)) return void 0;
|
|
628
755
|
if (ignorePaths.length === 0) return subs;
|
|
629
756
|
const ignoreSet = new Set(ignorePaths);
|
|
630
757
|
return subs.filter((s) => s?.path && !ignoreSet.has(s.path));
|
|
631
758
|
}
|
|
759
|
+
function assignFullGitSnapshot(entry, status) {
|
|
760
|
+
if (!status || typeof status !== "object" || Array.isArray(status)) return;
|
|
761
|
+
entry.git = status;
|
|
762
|
+
}
|
|
632
763
|
function extractLaunchPayload(value) {
|
|
633
764
|
return findNestedPayload(value, (payload) => Boolean(payload?.sessionId || payload?.id || payload?.runtimeSessionId));
|
|
634
765
|
}
|
|
@@ -753,20 +884,63 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
|
|
|
753
884
|
let sessionId = args.session_id?.trim() || "";
|
|
754
885
|
const providerPriorityList = Array.isArray(node.policy?.providerPriority) ? node.policy.providerPriority : [];
|
|
755
886
|
let resolvedProviderType = args.providerType?.trim() || providerPriorityList[0] || "";
|
|
756
|
-
if (!sessionId) {
|
|
887
|
+
if (!sessionId || args.session_id) {
|
|
757
888
|
try {
|
|
758
889
|
const relayResult = await transport.meshCommand(daemonId, "get_status_metadata", {});
|
|
759
|
-
const
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
890
|
+
const sessions = extractStatusMetadataSessions(relayResult);
|
|
891
|
+
if (sessionId) {
|
|
892
|
+
const explicitSession = sessions.find((session) => readSessionRecordId(session) === sessionId);
|
|
893
|
+
if (!explicitSession) {
|
|
894
|
+
return {
|
|
895
|
+
success: false,
|
|
896
|
+
recoverable: true,
|
|
897
|
+
code: "mesh_target_session_not_found",
|
|
898
|
+
reason: "mesh_target_session_not_found",
|
|
899
|
+
transport: "mesh_transport",
|
|
900
|
+
retryRecommended: true,
|
|
901
|
+
meshId: ctx.mesh.id,
|
|
902
|
+
nodeId: node.id,
|
|
903
|
+
daemonId,
|
|
904
|
+
workspace: node.workspace,
|
|
905
|
+
sessionId,
|
|
906
|
+
...resolvedProviderType ? { resolvedProviderType } : {},
|
|
907
|
+
error: `Remote session '${sessionId}' is not present in the live status for node '${node.id}'.`,
|
|
908
|
+
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.`
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
if (!isMeshOwnedDelegateSession(explicitSession, ctx.mesh.id, node.id)) {
|
|
912
|
+
return buildRelayUnsafeRemoteSessionFailure(
|
|
913
|
+
ctx,
|
|
914
|
+
node,
|
|
915
|
+
sessionId,
|
|
916
|
+
resolvedProviderType || resolveSessionProviderType(explicitSession) || void 0
|
|
917
|
+
);
|
|
918
|
+
}
|
|
765
919
|
if (!resolvedProviderType) {
|
|
766
|
-
resolvedProviderType =
|
|
920
|
+
resolvedProviderType = resolveSessionProviderType(explicitSession);
|
|
921
|
+
}
|
|
922
|
+
} else {
|
|
923
|
+
const targetSession = chooseDispatchableSession(sessions, resolvedProviderType, ctx.mesh.id, node.id);
|
|
924
|
+
if (targetSession?.id || targetSession?.sessionId) {
|
|
925
|
+
sessionId = targetSession.id || targetSession.sessionId;
|
|
926
|
+
if (!resolvedProviderType) {
|
|
927
|
+
resolvedProviderType = resolveSessionProviderType(targetSession);
|
|
928
|
+
}
|
|
767
929
|
}
|
|
768
930
|
}
|
|
769
931
|
} catch (e) {
|
|
932
|
+
if (sessionId) {
|
|
933
|
+
return {
|
|
934
|
+
...buildCoordinatorP2pRelayFailure(e, {
|
|
935
|
+
command: "get_status_metadata",
|
|
936
|
+
targetDaemonId: daemonId,
|
|
937
|
+
nodeId: node.id,
|
|
938
|
+
sessionId
|
|
939
|
+
}),
|
|
940
|
+
success: false,
|
|
941
|
+
error: `Cannot verify remote session '${sessionId}' before dispatch: ${e?.message || String(e)}`
|
|
942
|
+
};
|
|
943
|
+
}
|
|
770
944
|
}
|
|
771
945
|
}
|
|
772
946
|
if (!resolvedProviderType) {
|
|
@@ -796,7 +970,7 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
|
|
|
796
970
|
error: `P2P dispatch failed: ${errorMessage}`
|
|
797
971
|
};
|
|
798
972
|
}
|
|
799
|
-
return { success: true, dispatched: true, sessionId: sessionId || resolvedProviderType };
|
|
973
|
+
return { success: true, dispatched: true, sessionId: sessionId || resolvedProviderType, providerType: resolvedProviderType };
|
|
800
974
|
} catch (e) {
|
|
801
975
|
const errorMessage = e?.message || String(e);
|
|
802
976
|
return {
|
|
@@ -880,6 +1054,10 @@ function summarizeRelatedRepoStatus(repo, status) {
|
|
|
880
1054
|
workspace: repo.workspace,
|
|
881
1055
|
isGitRepo: status?.isGitRepo === true,
|
|
882
1056
|
branch: status?.branch ?? null,
|
|
1057
|
+
upstream: status?.upstream ?? null,
|
|
1058
|
+
upstreamStatus: typeof status?.upstreamStatus === "string" ? status.upstreamStatus : status?.upstream ? "unchecked" : "no_upstream",
|
|
1059
|
+
upstreamFetchedAt: Number.isFinite(Number(status?.upstreamFetchedAt)) ? Number(status.upstreamFetchedAt) : null,
|
|
1060
|
+
upstreamFetchError: typeof status?.upstreamFetchError === "string" ? status.upstreamFetchError : null,
|
|
883
1061
|
ahead: Number.isFinite(Number(status?.ahead)) ? Number(status.ahead) : 0,
|
|
884
1062
|
behind: Number.isFinite(Number(status?.behind)) ? Number(status.behind) : 0,
|
|
885
1063
|
dirty,
|
|
@@ -896,7 +1074,7 @@ async function collectRelatedRepoStatuses(ctx, node) {
|
|
|
896
1074
|
const results = [];
|
|
897
1075
|
for (const repo of relatedRepos) {
|
|
898
1076
|
try {
|
|
899
|
-
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 });
|
|
1077
|
+
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 });
|
|
900
1078
|
const status = extractGitStatus(statusResult);
|
|
901
1079
|
results.push(summarizeRelatedRepoStatus(repo, status));
|
|
902
1080
|
} catch (e) {
|
|
@@ -944,11 +1122,13 @@ function buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges) {
|
|
|
944
1122
|
const ahead = readNumeric(status?.ahead);
|
|
945
1123
|
const behind = readNumeric(status?.behind);
|
|
946
1124
|
const upstream = readString(status?.upstream) ?? null;
|
|
1125
|
+
const upstreamStatus = readString(status?.upstreamStatus) ?? (upstream ? "unchecked" : "no_upstream");
|
|
947
1126
|
const hasConflicts = status?.hasConflicts === true || Array.isArray(status?.conflictFiles) && status.conflictFiles.length > 0;
|
|
948
1127
|
const base = {
|
|
949
1128
|
defaultBranch,
|
|
950
1129
|
branch,
|
|
951
1130
|
upstream,
|
|
1131
|
+
upstreamStatus,
|
|
952
1132
|
ahead,
|
|
953
1133
|
behind,
|
|
954
1134
|
isWorktree: node.isLocalWorktree === true,
|
|
@@ -982,6 +1162,15 @@ function buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges) {
|
|
|
982
1162
|
};
|
|
983
1163
|
}
|
|
984
1164
|
if (branch === defaultBranch) {
|
|
1165
|
+
if (upstream && upstreamStatus !== "fresh") {
|
|
1166
|
+
return {
|
|
1167
|
+
...base,
|
|
1168
|
+
status: "blocked_review",
|
|
1169
|
+
needsConvergence: true,
|
|
1170
|
+
reason: "default_branch_upstream_unverified",
|
|
1171
|
+
nextStep: `Refresh ${defaultBranch}'s upstream refs or resolve the fetch failure before declaring convergence complete for node '${node.id}'.`
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
985
1174
|
if (ahead > 0 || behind > 0) {
|
|
986
1175
|
return {
|
|
987
1176
|
...base,
|
|
@@ -1008,6 +1197,15 @@ function buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges) {
|
|
|
1008
1197
|
nextStep: `Run mesh_refine_node(node_id: "${node.id}") or explicitly classify this worktree as blocked_review/not_mergeable before ending the task.`
|
|
1009
1198
|
};
|
|
1010
1199
|
}
|
|
1200
|
+
if (upstream && upstreamStatus !== "fresh") {
|
|
1201
|
+
return {
|
|
1202
|
+
...base,
|
|
1203
|
+
status: "blocked_review",
|
|
1204
|
+
needsConvergence: true,
|
|
1205
|
+
reason: "feature_branch_upstream_unverified",
|
|
1206
|
+
nextStep: `Refresh branch '${branch}' upstream refs or resolve the fetch failure before deciding whether it is ready to merge into ${defaultBranch}.`
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1011
1209
|
if (!upstream || ahead > 0 || behind > 0) {
|
|
1012
1210
|
return {
|
|
1013
1211
|
...base,
|
|
@@ -1051,6 +1249,79 @@ async function commandForNode(ctx, node, command, args = {}) {
|
|
|
1051
1249
|
}
|
|
1052
1250
|
throw new Error(`Command '${command}' requires daemon IPC/local transport for node '${node.id}'`);
|
|
1053
1251
|
}
|
|
1252
|
+
function normalizePendingMeshCoordinatorEvents(value) {
|
|
1253
|
+
const payload = unwrapCommandPayload(value);
|
|
1254
|
+
const events = Array.isArray(payload?.events) ? payload.events : Array.isArray(value?.events) ? value.events : [];
|
|
1255
|
+
return events.filter((event) => event && typeof event === "object");
|
|
1256
|
+
}
|
|
1257
|
+
function buildMeshForwardPayloadFromPendingEvent(event) {
|
|
1258
|
+
const metadataEvent = event?.metadataEvent && typeof event.metadataEvent === "object" ? event.metadataEvent : {};
|
|
1259
|
+
return {
|
|
1260
|
+
event: readString(event?.event),
|
|
1261
|
+
meshId: readString(event?.meshId),
|
|
1262
|
+
nodeId: readString(event?.nodeId) || readString(metadataEvent.meshNodeId),
|
|
1263
|
+
workspace: readString(event?.workspace) || readString(metadataEvent.workspace),
|
|
1264
|
+
targetSessionId: readString(metadataEvent.targetSessionId) || readString(metadataEvent.sessionId) || readString(metadataEvent.instanceId),
|
|
1265
|
+
providerType: readString(metadataEvent.providerType),
|
|
1266
|
+
providerSessionId: readString(metadataEvent.providerSessionId),
|
|
1267
|
+
finalSummary: readString(metadataEvent.finalSummary) || readString(metadataEvent.summary),
|
|
1268
|
+
jobId: readString(metadataEvent.jobId),
|
|
1269
|
+
interactionId: readString(metadataEvent.interactionId),
|
|
1270
|
+
status: readString(metadataEvent.status),
|
|
1271
|
+
targetDaemonId: readString(metadataEvent.targetDaemonId),
|
|
1272
|
+
startedAt: readString(metadataEvent.startedAt),
|
|
1273
|
+
completedAt: readString(metadataEvent.completedAt),
|
|
1274
|
+
retryOfJobId: readString(metadataEvent.retryOfJobId),
|
|
1275
|
+
...metadataEvent.result && typeof metadataEvent.result === "object" && !Array.isArray(metadataEvent.result) ? { result: metadataEvent.result } : {},
|
|
1276
|
+
...metadataEvent.intentional === true ? { intentional: true } : {},
|
|
1277
|
+
...metadataEvent.intentionalStop === true ? { intentionalStop: true } : {},
|
|
1278
|
+
...metadataEvent.operatorCleanup === true ? { operatorCleanup: true } : {},
|
|
1279
|
+
...readString(metadataEvent.reason) ? { reason: readString(metadataEvent.reason) } : {},
|
|
1280
|
+
...readString(metadataEvent.stopReason) ? { stopReason: readString(metadataEvent.stopReason) } : {},
|
|
1281
|
+
...readString(metadataEvent.cleanupReason) ? { cleanupReason: readString(metadataEvent.cleanupReason) } : {},
|
|
1282
|
+
...readString(metadataEvent.source) ? { source: readString(metadataEvent.source) } : {}
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
async function drainCoordinatorPendingEvents(ctx, opts) {
|
|
1286
|
+
const requestedNodeIds = opts?.nodeIds?.length ? new Set(opts.nodeIds) : null;
|
|
1287
|
+
const matchesCurrentMesh = (event) => readString(event?.meshId) === ctx.mesh.id;
|
|
1288
|
+
if (ctx.transport instanceof IpcTransport) {
|
|
1289
|
+
const surfacedEvents = [];
|
|
1290
|
+
try {
|
|
1291
|
+
surfacedEvents.push(
|
|
1292
|
+
...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", { meshId: ctx.mesh.id })).filter(matchesCurrentMesh)
|
|
1293
|
+
);
|
|
1294
|
+
} catch {
|
|
1295
|
+
}
|
|
1296
|
+
for (const node of ctx.mesh.nodes) {
|
|
1297
|
+
if (!node.daemonId || isLocalControlPlaneNode(ctx, node)) continue;
|
|
1298
|
+
if (requestedNodeIds && !requestedNodeIds.has(node.id)) continue;
|
|
1299
|
+
try {
|
|
1300
|
+
const remoteEvents = normalizePendingMeshCoordinatorEvents(
|
|
1301
|
+
await ctx.transport.meshCommand(node.daemonId, "get_pending_mesh_events", { meshId: ctx.mesh.id })
|
|
1302
|
+
).filter(matchesCurrentMesh);
|
|
1303
|
+
if (remoteEvents.length === 0) continue;
|
|
1304
|
+
for (const event of remoteEvents) {
|
|
1305
|
+
const payload = buildMeshForwardPayloadFromPendingEvent(event);
|
|
1306
|
+
if (!payload.event || !payload.meshId) continue;
|
|
1307
|
+
await ctx.transport.command("mesh_forward_event", payload);
|
|
1308
|
+
}
|
|
1309
|
+
} catch {
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
try {
|
|
1313
|
+
surfacedEvents.push(
|
|
1314
|
+
...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", { meshId: ctx.mesh.id })).filter(matchesCurrentMesh)
|
|
1315
|
+
);
|
|
1316
|
+
} catch {
|
|
1317
|
+
}
|
|
1318
|
+
return surfacedEvents;
|
|
1319
|
+
}
|
|
1320
|
+
if (isLocalTransport(ctx.transport)) {
|
|
1321
|
+
return (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id).filter(matchesCurrentMesh);
|
|
1322
|
+
}
|
|
1323
|
+
return [];
|
|
1324
|
+
}
|
|
1054
1325
|
function isP2pTransportUnavailableError(error) {
|
|
1055
1326
|
return (0, import_daemon_core.isP2pRelayTransportFailure)(error);
|
|
1056
1327
|
}
|
|
@@ -1088,7 +1359,9 @@ var MESH_ENQUEUE_TASK_TOOL = {
|
|
|
1088
1359
|
inputSchema: {
|
|
1089
1360
|
type: "object",
|
|
1090
1361
|
properties: {
|
|
1091
|
-
message: { type: "string", description: "The task instruction for the agent." }
|
|
1362
|
+
message: { type: "string", description: "The task instruction for the agent." },
|
|
1363
|
+
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." },
|
|
1364
|
+
taskMode: { type: "string", enum: ["code_change", "validation", "live_debug_readonly", "launch_app", "convergence"], description: "CamelCase alias for task_mode." }
|
|
1092
1365
|
},
|
|
1093
1366
|
required: ["message"]
|
|
1094
1367
|
}
|
|
@@ -1148,7 +1421,9 @@ var MESH_SEND_TASK_TOOL = {
|
|
|
1148
1421
|
properties: {
|
|
1149
1422
|
node_id: { type: "string", description: "Target node ID (from mesh_list_nodes)." },
|
|
1150
1423
|
session_id: { type: "string", description: "Agent session ID on the target node." },
|
|
1151
|
-
message: { type: "string", description: "Natural-language task to send to the agent." }
|
|
1424
|
+
message: { type: "string", description: "Natural-language task to send to the agent." },
|
|
1425
|
+
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." },
|
|
1426
|
+
taskMode: { type: "string", enum: ["code_change", "validation", "live_debug_readonly", "launch_app", "convergence"], description: "CamelCase alias for task_mode." }
|
|
1152
1427
|
},
|
|
1153
1428
|
required: ["node_id", "session_id", "message"]
|
|
1154
1429
|
}
|
|
@@ -1206,6 +1481,21 @@ var MESH_GIT_STATUS_TOOL = {
|
|
|
1206
1481
|
required: ["node_id"]
|
|
1207
1482
|
}
|
|
1208
1483
|
};
|
|
1484
|
+
var MESH_FAST_FORWARD_NODE_TOOL = {
|
|
1485
|
+
name: "mesh_fast_forward_node",
|
|
1486
|
+
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.",
|
|
1487
|
+
inputSchema: {
|
|
1488
|
+
type: "object",
|
|
1489
|
+
properties: {
|
|
1490
|
+
node_id: { type: "string", description: "Target node ID." },
|
|
1491
|
+
branch: { type: "string", description: "Optional guard: require the node's current branch to match this branch before planning/executing." },
|
|
1492
|
+
execute: { type: "boolean", description: "When true, apply the fast-forward if all safety gates pass. Defaults false/dry-run." },
|
|
1493
|
+
dry_run: { type: "boolean", description: "Preview only. Defaults true unless execute=true; dry_run=true overrides execute." },
|
|
1494
|
+
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." }
|
|
1495
|
+
},
|
|
1496
|
+
required: ["node_id"]
|
|
1497
|
+
}
|
|
1498
|
+
};
|
|
1209
1499
|
var MESH_CHECKPOINT_TOOL = {
|
|
1210
1500
|
name: "mesh_checkpoint",
|
|
1211
1501
|
description: "Create a git checkpoint (commit) on a mesh node workspace.",
|
|
@@ -1289,7 +1579,7 @@ var MESH_TASK_HISTORY_TOOL = {
|
|
|
1289
1579
|
type: "object",
|
|
1290
1580
|
properties: {
|
|
1291
1581
|
tail: { type: "number", description: "Number of recent entries to return (default: 20)." },
|
|
1292
|
-
kind: { type: "string", description: "Filter by entry kind: task_dispatched, task_completed, task_failed, task_stalled, session_launched, checkpoint_created, node_cloned, node_removed." }
|
|
1582
|
+
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." }
|
|
1293
1583
|
}
|
|
1294
1584
|
}
|
|
1295
1585
|
};
|
|
@@ -1309,7 +1599,7 @@ var MESH_RECONCILE_LEDGER_TOOL = {
|
|
|
1309
1599
|
};
|
|
1310
1600
|
var MESH_REFINE_NODE_TOOL = {
|
|
1311
1601
|
name: "mesh_refine_node",
|
|
1312
|
-
description: "The Refinery:
|
|
1602
|
+
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.",
|
|
1313
1603
|
inputSchema: {
|
|
1314
1604
|
type: "object",
|
|
1315
1605
|
properties: {
|
|
@@ -1318,6 +1608,43 @@ var MESH_REFINE_NODE_TOOL = {
|
|
|
1318
1608
|
required: ["node_id"]
|
|
1319
1609
|
}
|
|
1320
1610
|
};
|
|
1611
|
+
var MESH_REFINE_CONFIG_SCHEMA_TOOL = {
|
|
1612
|
+
name: "mesh_refine_config_schema",
|
|
1613
|
+
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.",
|
|
1614
|
+
inputSchema: { type: "object", properties: {} }
|
|
1615
|
+
};
|
|
1616
|
+
var MESH_VALIDATE_REFINE_CONFIG_TOOL = {
|
|
1617
|
+
name: "mesh_validate_refine_config",
|
|
1618
|
+
description: "Validate the repo mesh/refine config for a node/workspace without running validation commands or merging.",
|
|
1619
|
+
inputSchema: {
|
|
1620
|
+
type: "object",
|
|
1621
|
+
properties: {
|
|
1622
|
+
node_id: { type: "string", description: "Optional node/workspace whose refine config should be loaded. Defaults to the first mesh node." },
|
|
1623
|
+
config: { type: "object", description: "Optional inline config object to validate instead of loading from the repo." }
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
};
|
|
1627
|
+
var MESH_SUGGEST_REFINE_CONFIG_TOOL = {
|
|
1628
|
+
name: "mesh_suggest_refine_config",
|
|
1629
|
+
description: "Suggest a repo mesh/refine config scaffold from project context/package scripts. Suggestions are never executed until saved as explicit refine config.",
|
|
1630
|
+
inputSchema: {
|
|
1631
|
+
type: "object",
|
|
1632
|
+
properties: {
|
|
1633
|
+
node_id: { type: "string", description: "Optional node/workspace used for suggestions. Defaults to the first mesh node." }
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
};
|
|
1637
|
+
var MESH_REFINE_PLAN_TOOL = {
|
|
1638
|
+
name: "mesh_refine_plan",
|
|
1639
|
+
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.",
|
|
1640
|
+
inputSchema: {
|
|
1641
|
+
type: "object",
|
|
1642
|
+
properties: {
|
|
1643
|
+
node_id: { type: "string", description: "Node ID of the worktree node to plan." }
|
|
1644
|
+
},
|
|
1645
|
+
required: ["node_id"]
|
|
1646
|
+
}
|
|
1647
|
+
};
|
|
1321
1648
|
var ALL_MESH_TOOLS = [
|
|
1322
1649
|
MESH_STATUS_TOOL,
|
|
1323
1650
|
MESH_LIST_NODES_TOOL,
|
|
@@ -1330,11 +1657,16 @@ var ALL_MESH_TOOLS = [
|
|
|
1330
1657
|
MESH_READ_DEBUG_TOOL,
|
|
1331
1658
|
MESH_LAUNCH_SESSION_TOOL,
|
|
1332
1659
|
MESH_GIT_STATUS_TOOL,
|
|
1660
|
+
MESH_FAST_FORWARD_NODE_TOOL,
|
|
1333
1661
|
MESH_CHECKPOINT_TOOL,
|
|
1334
1662
|
MESH_APPROVE_TOOL,
|
|
1335
1663
|
MESH_CLONE_NODE_TOOL,
|
|
1336
1664
|
MESH_REMOVE_NODE_TOOL,
|
|
1337
1665
|
MESH_REFINE_NODE_TOOL,
|
|
1666
|
+
MESH_REFINE_CONFIG_SCHEMA_TOOL,
|
|
1667
|
+
MESH_VALIDATE_REFINE_CONFIG_TOOL,
|
|
1668
|
+
MESH_SUGGEST_REFINE_CONFIG_TOOL,
|
|
1669
|
+
MESH_REFINE_PLAN_TOOL,
|
|
1338
1670
|
MESH_CLEANUP_SESSIONS_TOOL,
|
|
1339
1671
|
MESH_TASK_HISTORY_TOOL,
|
|
1340
1672
|
MESH_RECONCILE_LEDGER_TOOL
|
|
@@ -1352,11 +1684,12 @@ async function meshStatus(ctx) {
|
|
|
1352
1684
|
};
|
|
1353
1685
|
try {
|
|
1354
1686
|
if (!isLocalTransport(transport) && node.daemonId) {
|
|
1355
|
-
const result = await transport.gitStatus(node.daemonId, node.workspace, false);
|
|
1687
|
+
const result = await transport.gitStatus(node.daemonId, node.workspace, false, true);
|
|
1356
1688
|
const status = extractGitStatus(result);
|
|
1357
1689
|
const uncommittedChanges = countUncommittedChanges(status);
|
|
1358
1690
|
const dirty = isGitStatusDirty(status);
|
|
1359
1691
|
entry.health = status?.isGitRepo ? dirty ? "dirty" : "online" : "degraded";
|
|
1692
|
+
assignFullGitSnapshot(entry, status);
|
|
1360
1693
|
entry.branch = status?.branch;
|
|
1361
1694
|
entry.isDirty = dirty;
|
|
1362
1695
|
entry.uncommittedChanges = uncommittedChanges;
|
|
@@ -1370,6 +1703,7 @@ async function meshStatus(ctx) {
|
|
|
1370
1703
|
const autoDiscover = node.policy?.autoDiscoverSubmodules !== false;
|
|
1371
1704
|
const statusResult = await commandForNode(ctx, node, "git_status", {
|
|
1372
1705
|
workspace: node.workspace,
|
|
1706
|
+
refreshUpstream: true,
|
|
1373
1707
|
includeSubmodules: autoDiscover,
|
|
1374
1708
|
submoduleIgnorePaths: node.policy?.submoduleIgnorePaths || void 0
|
|
1375
1709
|
});
|
|
@@ -1377,6 +1711,7 @@ async function meshStatus(ctx) {
|
|
|
1377
1711
|
const uncommittedChanges = countUncommittedChanges(status);
|
|
1378
1712
|
const dirty = isGitStatusDirty(status);
|
|
1379
1713
|
entry.health = status?.isGitRepo ? dirty ? "dirty" : "online" : "degraded";
|
|
1714
|
+
assignFullGitSnapshot(entry, status);
|
|
1380
1715
|
entry.branch = status?.branch;
|
|
1381
1716
|
entry.isDirty = dirty;
|
|
1382
1717
|
entry.uncommittedChanges = uncommittedChanges;
|
|
@@ -1454,13 +1789,27 @@ async function meshStatus(ctx) {
|
|
|
1454
1789
|
if (relatedRepos.length) entry.relatedRepos = relatedRepos;
|
|
1455
1790
|
results.push(entry);
|
|
1456
1791
|
}
|
|
1792
|
+
const activeWorkEvidence = (0, import_daemon_core.buildMeshActiveWork)({
|
|
1793
|
+
meshId: mesh.id,
|
|
1794
|
+
queue: (0, import_daemon_core.getQueue)(mesh.id),
|
|
1795
|
+
ledgerEntries: (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail: 500 }),
|
|
1796
|
+
nodes: mesh.nodes
|
|
1797
|
+
});
|
|
1457
1798
|
const response = {
|
|
1458
1799
|
meshId: mesh.id,
|
|
1459
1800
|
meshName: mesh.name,
|
|
1460
1801
|
repoIdentity: mesh.repoIdentity,
|
|
1461
1802
|
policy: mesh.policy,
|
|
1462
1803
|
refreshedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1804
|
+
sourceOfTruth: {
|
|
1805
|
+
membership: "coordinator_daemon_live_mesh",
|
|
1806
|
+
currentStatus: "live_git_and_session_probes",
|
|
1807
|
+
activeWork: "mesh_queue_file_and_local_ledger",
|
|
1808
|
+
historicalEvidenceOnly: ["recoveryHints", "ledgerSummary"]
|
|
1809
|
+
},
|
|
1463
1810
|
nodes: results,
|
|
1811
|
+
activeWork: activeWorkEvidence.activeWork,
|
|
1812
|
+
activeWorkSummary: activeWorkEvidence.summary,
|
|
1464
1813
|
branchConvergenceSummary: summarizeBranchConvergence(results)
|
|
1465
1814
|
};
|
|
1466
1815
|
try {
|
|
@@ -1468,13 +1817,7 @@ async function meshStatus(ctx) {
|
|
|
1468
1817
|
} catch {
|
|
1469
1818
|
}
|
|
1470
1819
|
try {
|
|
1471
|
-
|
|
1472
|
-
if (ctx.transport instanceof IpcTransport) {
|
|
1473
|
-
const eventsResult = await ctx.transport.command("get_pending_mesh_events", {});
|
|
1474
|
-
pendingEvents = Array.isArray(eventsResult?.events) ? eventsResult.events : [];
|
|
1475
|
-
} else if (isLocalTransport(ctx.transport)) {
|
|
1476
|
-
pendingEvents = (0, import_daemon_core.drainPendingMeshCoordinatorEvents)();
|
|
1477
|
-
}
|
|
1820
|
+
const pendingEvents = await drainCoordinatorPendingEvents(ctx);
|
|
1478
1821
|
if (pendingEvents.length > 0) {
|
|
1479
1822
|
response.pendingCoordinatorEvents = pendingEvents;
|
|
1480
1823
|
}
|
|
@@ -1484,6 +1827,7 @@ async function meshStatus(ctx) {
|
|
|
1484
1827
|
}
|
|
1485
1828
|
async function meshTaskHistory(ctx, args) {
|
|
1486
1829
|
const { mesh } = ctx;
|
|
1830
|
+
await drainCoordinatorPendingEvents(ctx);
|
|
1487
1831
|
const tail = typeof args.tail === "number" && args.tail > 0 ? args.tail : 20;
|
|
1488
1832
|
const kind = typeof args.kind === "string" && args.kind.trim() ? [args.kind.trim()] : void 0;
|
|
1489
1833
|
const entries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail, kind });
|
|
@@ -1587,12 +1931,13 @@ async function meshListNodes(ctx) {
|
|
|
1587
1931
|
}, null, 2);
|
|
1588
1932
|
}
|
|
1589
1933
|
async function meshEnqueueTask(ctx, args) {
|
|
1934
|
+
const taskMode = readString(args.task_mode) || readString(args.taskMode);
|
|
1590
1935
|
try {
|
|
1591
|
-
const task = (0, import_daemon_core.enqueueTask)(ctx.mesh.id, args.message);
|
|
1936
|
+
const task = (0, import_daemon_core.enqueueTask)(ctx.mesh.id, args.message, { taskMode });
|
|
1592
1937
|
if (isLocalTransport(ctx.transport) && !(ctx.transport instanceof IpcTransport)) {
|
|
1593
1938
|
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
1594
1939
|
});
|
|
1595
|
-
return JSON.stringify({ success: true, taskId: task.id, status: task.status });
|
|
1940
|
+
return JSON.stringify({ success: true, source: "queue", taskId: task.id, status: task.status, taskMode: task.taskMode });
|
|
1596
1941
|
}
|
|
1597
1942
|
if (ctx.transport instanceof IpcTransport) {
|
|
1598
1943
|
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
@@ -1605,11 +1950,24 @@ async function meshEnqueueTask(ctx, args) {
|
|
|
1605
1950
|
ipcDispatchToRemoteAgent(ctx, node, { message: args.message }).then((result) => {
|
|
1606
1951
|
if (result.success) {
|
|
1607
1952
|
try {
|
|
1953
|
+
const providerType = result.providerType;
|
|
1954
|
+
const descriptor = summarizeTaskMessage(args.message);
|
|
1608
1955
|
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
1609
1956
|
kind: "task_dispatched",
|
|
1610
1957
|
nodeId: node.id,
|
|
1611
1958
|
sessionId: result.sessionId,
|
|
1612
|
-
|
|
1959
|
+
providerType,
|
|
1960
|
+
payload: {
|
|
1961
|
+
source: "queue",
|
|
1962
|
+
via: "p2p_direct",
|
|
1963
|
+
taskId: task.id,
|
|
1964
|
+
message: args.message,
|
|
1965
|
+
taskTitle: descriptor.taskTitle,
|
|
1966
|
+
taskSummary: descriptor.taskSummary,
|
|
1967
|
+
...task.taskMode ? { taskMode: task.taskMode } : {},
|
|
1968
|
+
...providerType ? { providerType } : {},
|
|
1969
|
+
targetSessionId: result.sessionId
|
|
1970
|
+
}
|
|
1613
1971
|
});
|
|
1614
1972
|
} catch {
|
|
1615
1973
|
}
|
|
@@ -1620,22 +1978,32 @@ async function meshEnqueueTask(ctx, args) {
|
|
|
1620
1978
|
}
|
|
1621
1979
|
Promise.all(dispatchPromises).catch(() => {
|
|
1622
1980
|
});
|
|
1623
|
-
return JSON.stringify({ success: true, taskId: task.id, status: task.status });
|
|
1981
|
+
return JSON.stringify({ success: true, source: "queue", taskId: task.id, status: task.status, taskMode: task.taskMode });
|
|
1624
1982
|
}
|
|
1625
|
-
return JSON.stringify({ success: true, taskId: task.id, status: task.status });
|
|
1983
|
+
return JSON.stringify({ success: true, source: "queue", taskId: task.id, status: task.status, taskMode: task.taskMode });
|
|
1626
1984
|
} catch (e) {
|
|
1627
|
-
|
|
1985
|
+
const message = e?.message || String(e);
|
|
1986
|
+
if (message.includes("live_debug_readonly_guardrail_violation")) {
|
|
1987
|
+
return JSON.stringify({ success: false, code: "live_debug_readonly_guardrail_violation", taskMode, error: message });
|
|
1988
|
+
}
|
|
1989
|
+
return JSON.stringify({ success: false, error: message });
|
|
1628
1990
|
}
|
|
1629
1991
|
}
|
|
1630
1992
|
async function meshViewQueue(ctx, args) {
|
|
1631
1993
|
try {
|
|
1632
1994
|
const statusFilter = sanitizeQueueStatusFilter(args.status);
|
|
1633
1995
|
const view = normalizeQueueViewMode(args.view);
|
|
1634
|
-
const fullQueue = annotateQueueStaleness((0, import_daemon_core.getQueue)(ctx.mesh.id), ctx.mesh);
|
|
1996
|
+
const fullQueue = prioritizeActiveQueueRows(annotateQueueStaleness((0, import_daemon_core.getQueue)(ctx.mesh.id), ctx.mesh));
|
|
1635
1997
|
const queue = filterQueueForView(fullQueue, view, statusFilter);
|
|
1636
1998
|
const summary = buildQueueStatusSummary(fullQueue);
|
|
1637
1999
|
const visibleSummary = buildQueueStatusSummary(queue);
|
|
1638
2000
|
const maintenance = buildQueueMaintenanceReport(fullQueue);
|
|
2001
|
+
const activeWorkEvidence = (0, import_daemon_core.buildMeshActiveWork)({
|
|
2002
|
+
meshId: ctx.mesh.id,
|
|
2003
|
+
queue: fullQueue,
|
|
2004
|
+
ledgerEntries: (0, import_daemon_core.readLedgerEntries)(ctx.mesh.id, { tail: 500 }),
|
|
2005
|
+
nodes: ctx.mesh.nodes
|
|
2006
|
+
});
|
|
1639
2007
|
const staleAssignedTasks = maintenance.staleAssignedTasks || [];
|
|
1640
2008
|
const requestedHistoricalRows = queue.some((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
|
|
1641
2009
|
return JSON.stringify({
|
|
@@ -1653,6 +2021,8 @@ async function meshViewQueue(ctx, args) {
|
|
|
1653
2021
|
},
|
|
1654
2022
|
queue,
|
|
1655
2023
|
visibleQueue: queue,
|
|
2024
|
+
activeWork: activeWorkEvidence.activeWork,
|
|
2025
|
+
activeWorkSummary: activeWorkEvidence.summary,
|
|
1656
2026
|
visibleSummary,
|
|
1657
2027
|
summary,
|
|
1658
2028
|
activeCounts: summary.activeCounts,
|
|
@@ -1686,6 +2056,10 @@ async function meshQueueCancel(ctx, args) {
|
|
|
1686
2056
|
if (!taskId) return JSON.stringify({ success: false, error: "task_id required" });
|
|
1687
2057
|
const task = (0, import_daemon_core.cancelTask)(ctx.mesh.id, taskId, { reason: args.reason });
|
|
1688
2058
|
if (!task) return JSON.stringify({ success: false, error: `Queue task '${taskId}' not found` });
|
|
2059
|
+
if (isLocalTransport(ctx.transport)) {
|
|
2060
|
+
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
2061
|
+
});
|
|
2062
|
+
}
|
|
1689
2063
|
return JSON.stringify({ success: true, task }, null, 2);
|
|
1690
2064
|
} catch (e) {
|
|
1691
2065
|
return JSON.stringify({ success: false, error: e.message });
|
|
@@ -1716,6 +2090,19 @@ async function meshQueueRequeue(ctx, args) {
|
|
|
1716
2090
|
}
|
|
1717
2091
|
}
|
|
1718
2092
|
async function meshSendTask(ctx, args) {
|
|
2093
|
+
const requestedTaskMode = readString(args.task_mode) || readString(args.taskMode);
|
|
2094
|
+
const modeValidation = (0, import_daemon_core.validateMeshTaskModeRequest)(requestedTaskMode, args.message);
|
|
2095
|
+
if (!modeValidation.valid) {
|
|
2096
|
+
return JSON.stringify({
|
|
2097
|
+
success: false,
|
|
2098
|
+
code: "live_debug_readonly_guardrail_violation",
|
|
2099
|
+
taskMode: modeValidation.taskMode || requestedTaskMode,
|
|
2100
|
+
violations: modeValidation.violations,
|
|
2101
|
+
allowedOperations: modeValidation.allowedOperations,
|
|
2102
|
+
error: `live_debug_readonly_guardrail_violation: forbidden operations (${modeValidation.violations.join(", ")})`
|
|
2103
|
+
});
|
|
2104
|
+
}
|
|
2105
|
+
const taskMode = modeValidation.taskMode;
|
|
1719
2106
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
1720
2107
|
if (node.policy?.readOnly) {
|
|
1721
2108
|
return JSON.stringify({ error: `Node '${args.node_id}' is read-only` });
|
|
@@ -1743,13 +2130,15 @@ async function meshSendTask(ctx, args) {
|
|
|
1743
2130
|
const res = await ctx.transport.meshEnqueueTask(node.daemonId, {
|
|
1744
2131
|
meshId: ctx.mesh.id,
|
|
1745
2132
|
message: args.message,
|
|
1746
|
-
targetNodeId: args.node_id
|
|
2133
|
+
targetNodeId: args.node_id,
|
|
2134
|
+
...taskMode ? { taskMode } : {}
|
|
1747
2135
|
});
|
|
1748
2136
|
return JSON.stringify(res);
|
|
1749
2137
|
}
|
|
1750
2138
|
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
1751
2139
|
if (ctx.transport instanceof IpcTransport && node.daemonId && !isLocalNode) {
|
|
1752
2140
|
const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id || ""));
|
|
2141
|
+
const taskId = (0, import_node_crypto.randomUUID)();
|
|
1753
2142
|
const result2 = await ipcDispatchToRemoteAgent(ctx, node, {
|
|
1754
2143
|
session_id: args.session_id,
|
|
1755
2144
|
message: args.message,
|
|
@@ -1758,60 +2147,123 @@ async function meshSendTask(ctx, args) {
|
|
|
1758
2147
|
if (result2.success) {
|
|
1759
2148
|
const dispatchedSessionId = args.session_id || result2.sessionId;
|
|
1760
2149
|
try {
|
|
2150
|
+
const providerType = result2.providerType || cached?.providerType;
|
|
1761
2151
|
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
1762
2152
|
kind: "task_dispatched",
|
|
1763
2153
|
nodeId: args.node_id,
|
|
1764
2154
|
sessionId: dispatchedSessionId,
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
2155
|
+
providerType,
|
|
2156
|
+
payload: buildDirectTaskPayload(args.message, "p2p_direct", {
|
|
2157
|
+
taskId,
|
|
2158
|
+
taskMode,
|
|
2159
|
+
providerType,
|
|
2160
|
+
targetSessionId: dispatchedSessionId
|
|
2161
|
+
})
|
|
1770
2162
|
});
|
|
1771
2163
|
} catch {
|
|
1772
2164
|
}
|
|
1773
2165
|
}
|
|
1774
|
-
return JSON.stringify({
|
|
2166
|
+
return JSON.stringify({
|
|
2167
|
+
...result2,
|
|
2168
|
+
nodeId: args.node_id,
|
|
2169
|
+
sessionId: result2.success ? args.session_id || result2.sessionId : args.session_id,
|
|
2170
|
+
...result2.success ? { source: "direct", taskId } : {},
|
|
2171
|
+
taskMode,
|
|
2172
|
+
...result2.success && result2.providerType ? { providerType: result2.providerType } : {},
|
|
2173
|
+
dispatched: result2.success === true
|
|
2174
|
+
});
|
|
1775
2175
|
}
|
|
1776
2176
|
if (args.session_id && isLocalTransport(ctx.transport)) {
|
|
1777
2177
|
const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id));
|
|
2178
|
+
let resolvedProviderType = cached?.providerType || "";
|
|
2179
|
+
if (!resolvedProviderType) {
|
|
2180
|
+
const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
|
|
2181
|
+
const sessions = extractStatusMetadataSessions(statusResult);
|
|
2182
|
+
const explicitSession = sessions.find((session) => readSessionRecordId(session) === args.session_id);
|
|
2183
|
+
if (!explicitSession) {
|
|
2184
|
+
return JSON.stringify({
|
|
2185
|
+
success: false,
|
|
2186
|
+
recoverable: true,
|
|
2187
|
+
code: "mesh_target_session_not_found",
|
|
2188
|
+
reason: "mesh_target_session_not_found",
|
|
2189
|
+
transport: "local_ipc",
|
|
2190
|
+
retryRecommended: true,
|
|
2191
|
+
nodeId: args.node_id,
|
|
2192
|
+
sessionId: args.session_id,
|
|
2193
|
+
error: `Local session '${args.session_id}' is not present in live status for node '${args.node_id}'.`,
|
|
2194
|
+
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.`
|
|
2195
|
+
});
|
|
2196
|
+
}
|
|
2197
|
+
resolvedProviderType = resolveSessionProviderType(explicitSession);
|
|
2198
|
+
if (resolvedProviderType) {
|
|
2199
|
+
meshSessionProviderMetadata.set(meshSessionCacheKey(args.node_id, args.session_id), {
|
|
2200
|
+
providerType: resolvedProviderType,
|
|
2201
|
+
providerSessionId: readString(explicitSession?.providerSessionId) || void 0
|
|
2202
|
+
});
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
if (!resolvedProviderType) {
|
|
2206
|
+
return JSON.stringify({
|
|
2207
|
+
success: false,
|
|
2208
|
+
recoverable: true,
|
|
2209
|
+
code: "mesh_target_session_provider_unknown",
|
|
2210
|
+
reason: "mesh_target_session_provider_unknown",
|
|
2211
|
+
transport: "local_ipc",
|
|
2212
|
+
retryRecommended: false,
|
|
2213
|
+
nodeId: args.node_id,
|
|
2214
|
+
sessionId: args.session_id,
|
|
2215
|
+
error: `Local session '${args.session_id}' is live but does not expose providerType/cliType, so agent_command cannot be routed safely.`,
|
|
2216
|
+
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.`
|
|
2217
|
+
});
|
|
2218
|
+
}
|
|
1778
2219
|
const dispatchResult = await commandForNode(ctx, node, "agent_command", {
|
|
1779
2220
|
targetSessionId: args.session_id,
|
|
1780
|
-
|
|
2221
|
+
agentType: resolvedProviderType,
|
|
2222
|
+
cliType: resolvedProviderType,
|
|
2223
|
+
providerType: resolvedProviderType,
|
|
1781
2224
|
action: "send_chat",
|
|
1782
2225
|
message: args.message
|
|
1783
2226
|
});
|
|
1784
2227
|
const dispatchPayload = unwrapCommandPayload(dispatchResult);
|
|
1785
2228
|
if (dispatchPayload?.success === false || dispatchResult?.success === false) {
|
|
2229
|
+
const source = dispatchPayload?.success === false ? dispatchPayload : dispatchResult;
|
|
1786
2230
|
return JSON.stringify({
|
|
2231
|
+
...source && typeof source === "object" ? source : {},
|
|
1787
2232
|
success: false,
|
|
1788
2233
|
nodeId: args.node_id,
|
|
1789
2234
|
sessionId: args.session_id,
|
|
1790
2235
|
error: dispatchPayload?.error || dispatchResult?.error || "agent_command rejected the task"
|
|
1791
2236
|
});
|
|
1792
2237
|
}
|
|
2238
|
+
const taskId = (0, import_node_crypto.randomUUID)();
|
|
1793
2239
|
try {
|
|
1794
2240
|
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
1795
2241
|
kind: "task_dispatched",
|
|
1796
2242
|
nodeId: args.node_id,
|
|
1797
2243
|
sessionId: args.session_id,
|
|
1798
|
-
providerType:
|
|
1799
|
-
payload:
|
|
2244
|
+
providerType: resolvedProviderType,
|
|
2245
|
+
payload: buildDirectTaskPayload(args.message, "local_direct", {
|
|
2246
|
+
taskId,
|
|
2247
|
+
taskMode,
|
|
2248
|
+
providerType: resolvedProviderType,
|
|
2249
|
+
targetSessionId: args.session_id
|
|
2250
|
+
})
|
|
1800
2251
|
});
|
|
1801
2252
|
} catch {
|
|
1802
2253
|
}
|
|
1803
|
-
return JSON.stringify({ success: true, dispatched: true, nodeId: args.node_id, sessionId: args.session_id });
|
|
2254
|
+
return JSON.stringify({ success: true, dispatched: true, source: "direct", taskId, taskMode, providerType: resolvedProviderType, nodeId: args.node_id, sessionId: args.session_id });
|
|
1804
2255
|
}
|
|
1805
2256
|
const task = (0, import_daemon_core.enqueueTask)(ctx.mesh.id, args.message, {
|
|
1806
2257
|
targetNodeId: args.node_id,
|
|
1807
|
-
targetSessionId: args.session_id
|
|
2258
|
+
targetSessionId: args.session_id,
|
|
2259
|
+
taskMode
|
|
1808
2260
|
});
|
|
1809
2261
|
if (isLocalTransport(ctx.transport) || ctx.transport instanceof IpcTransport) {
|
|
1810
2262
|
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
1811
2263
|
});
|
|
1812
2264
|
}
|
|
1813
|
-
const pendingEvents = isLocalTransport(ctx.transport) ? (0, import_daemon_core.drainPendingMeshCoordinatorEvents)() : [];
|
|
1814
|
-
const result = { success: true, nodeId: args.node_id, taskId: task.id, status: task.status };
|
|
2265
|
+
const pendingEvents = isLocalTransport(ctx.transport) ? (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id) : [];
|
|
2266
|
+
const result = { success: true, source: "queue", nodeId: args.node_id, taskId: task.id, status: task.status, taskMode: task.taskMode };
|
|
1815
2267
|
if (pendingEvents.length > 0) {
|
|
1816
2268
|
result.pendingCoordinatorEvents = pendingEvents;
|
|
1817
2269
|
}
|
|
@@ -1831,6 +2283,9 @@ async function meshReadChat(ctx, args) {
|
|
|
1831
2283
|
if (!node) {
|
|
1832
2284
|
return JSON.stringify(buildMissingNodeReadChatRecovery(ctx, args), null, 2);
|
|
1833
2285
|
}
|
|
2286
|
+
if (ctx.transport instanceof IpcTransport || isLocalTransport(ctx.transport)) {
|
|
2287
|
+
await drainCoordinatorPendingEvents(ctx, { nodeIds: [args.node_id] });
|
|
2288
|
+
}
|
|
1834
2289
|
if (isLocalTransport(ctx.transport)) {
|
|
1835
2290
|
const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id));
|
|
1836
2291
|
const providerSessionId = typeof args.provider_session_id === "string" && args.provider_session_id.trim() ? args.provider_session_id.trim() : cached?.providerSessionId;
|
|
@@ -1933,6 +2388,10 @@ async function meshLaunchSession(ctx, args) {
|
|
|
1933
2388
|
const coordinatorNode = resolveCoordinatorNode(ctx);
|
|
1934
2389
|
const coordinatorDaemonId = coordinatorNode?.daemonId || ctx.localDaemonId;
|
|
1935
2390
|
const spawnedSessionVisibility = readSpawnedSessionVisibility(ctx.mesh.policy);
|
|
2391
|
+
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
2392
|
+
if (node.daemonId && !isLocalNode && !coordinatorDaemonId) {
|
|
2393
|
+
return JSON.stringify(buildMissingCoordinatorDaemonIdFailure(ctx, node, resolvedProviderType), null, 2);
|
|
2394
|
+
}
|
|
1936
2395
|
let result;
|
|
1937
2396
|
try {
|
|
1938
2397
|
result = await commandForNode(ctx, node, "launch_cli", {
|
|
@@ -1973,7 +2432,6 @@ async function meshLaunchSession(ctx, args) {
|
|
|
1973
2432
|
});
|
|
1974
2433
|
} catch {
|
|
1975
2434
|
}
|
|
1976
|
-
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
1977
2435
|
if (ctx.transport instanceof IpcTransport && node.daemonId && !isLocalNode) {
|
|
1978
2436
|
ctx.transport.meshCommand(node.daemonId, "trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
1979
2437
|
});
|
|
@@ -1998,6 +2456,9 @@ async function meshLaunchSession(ctx, args) {
|
|
|
1998
2456
|
const coordinatorNode = resolveCoordinatorNode(ctx);
|
|
1999
2457
|
const coordinatorDaemonId = coordinatorNode?.daemonId || ctx.localDaemonId;
|
|
2000
2458
|
const spawnedSessionVisibility = readSpawnedSessionVisibility(ctx.mesh.policy);
|
|
2459
|
+
if (!coordinatorDaemonId) {
|
|
2460
|
+
return JSON.stringify(buildMissingCoordinatorDaemonIdFailure(ctx, node, resolvedProviderType), null, 2);
|
|
2461
|
+
}
|
|
2001
2462
|
try {
|
|
2002
2463
|
const res = await ctx.transport.launch(node.daemonId, {
|
|
2003
2464
|
type: resolvedProviderType,
|
|
@@ -2036,7 +2497,7 @@ async function meshGitStatus(ctx, args) {
|
|
|
2036
2497
|
const submoduleIgnorePaths = node.policy?.submoduleIgnorePaths || [];
|
|
2037
2498
|
try {
|
|
2038
2499
|
if (!isLocalTransport(ctx.transport) && node.daemonId) {
|
|
2039
|
-
const result = await ctx.transport.gitStatus(node.daemonId, node.workspace, true);
|
|
2500
|
+
const result = await ctx.transport.gitStatus(node.daemonId, node.workspace, true, true);
|
|
2040
2501
|
return JSON.stringify({
|
|
2041
2502
|
nodeId: args.node_id,
|
|
2042
2503
|
workspace: node.workspace,
|
|
@@ -2048,6 +2509,7 @@ async function meshGitStatus(ctx, args) {
|
|
|
2048
2509
|
} else if (isLocalTransport(ctx.transport)) {
|
|
2049
2510
|
const statusResult = await commandForNode(ctx, node, "git_status", {
|
|
2050
2511
|
workspace: node.workspace,
|
|
2512
|
+
refreshUpstream: true,
|
|
2051
2513
|
includeSubmodules: autoDiscoverSubmodules,
|
|
2052
2514
|
submoduleIgnorePaths: submoduleIgnorePaths.length > 0 ? submoduleIgnorePaths : void 0
|
|
2053
2515
|
});
|
|
@@ -2077,6 +2539,51 @@ async function meshGitStatus(ctx, args) {
|
|
|
2077
2539
|
}, null, 2);
|
|
2078
2540
|
}
|
|
2079
2541
|
}
|
|
2542
|
+
async function meshFastForwardNode(ctx, args) {
|
|
2543
|
+
await refreshMeshFromDaemon(ctx);
|
|
2544
|
+
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2545
|
+
const submoduleIgnorePaths = node.policy?.submoduleIgnorePaths || [];
|
|
2546
|
+
if (node.policy?.readOnly) {
|
|
2547
|
+
return JSON.stringify({
|
|
2548
|
+
success: false,
|
|
2549
|
+
code: "node_read_only",
|
|
2550
|
+
nodeId: args.node_id,
|
|
2551
|
+
workspace: node.workspace,
|
|
2552
|
+
allowed: false,
|
|
2553
|
+
willRun: false,
|
|
2554
|
+
executed: false,
|
|
2555
|
+
blockingReasons: ["node_read_only"]
|
|
2556
|
+
}, null, 2);
|
|
2557
|
+
}
|
|
2558
|
+
try {
|
|
2559
|
+
const dryRun = args.dry_run === true || args.execute !== true;
|
|
2560
|
+
const result = await commandForNode(ctx, node, "fast_forward_mesh_node", {
|
|
2561
|
+
meshId: ctx.mesh.id,
|
|
2562
|
+
nodeId: node.id,
|
|
2563
|
+
workspace: node.workspace,
|
|
2564
|
+
branch: typeof args.branch === "string" ? args.branch : void 0,
|
|
2565
|
+
execute: args.execute === true && args.dry_run !== true,
|
|
2566
|
+
dryRun,
|
|
2567
|
+
updateSubmodules: args.update_submodules === true,
|
|
2568
|
+
submoduleIgnorePaths: submoduleIgnorePaths.length > 0 ? submoduleIgnorePaths : void 0
|
|
2569
|
+
});
|
|
2570
|
+
return JSON.stringify(unwrapCommandPayload(result), null, 2);
|
|
2571
|
+
} catch (e) {
|
|
2572
|
+
const failure = buildCoordinatorP2pRelayFailure(e, {
|
|
2573
|
+
command: "fast_forward_mesh_node",
|
|
2574
|
+
targetDaemonId: node.daemonId,
|
|
2575
|
+
nodeId: args.node_id
|
|
2576
|
+
});
|
|
2577
|
+
return JSON.stringify({
|
|
2578
|
+
...failure,
|
|
2579
|
+
workspace: node.workspace,
|
|
2580
|
+
allowed: false,
|
|
2581
|
+
willRun: false,
|
|
2582
|
+
executed: false,
|
|
2583
|
+
blockingReasons: [failure.code || "mesh_fast_forward_unavailable"]
|
|
2584
|
+
}, null, 2);
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2080
2587
|
async function meshCheckpoint(ctx, args) {
|
|
2081
2588
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2082
2589
|
if (node.policy?.readOnly) {
|
|
@@ -2162,6 +2669,7 @@ async function meshCloneNode(ctx, args) {
|
|
|
2162
2669
|
if (existingIndex >= 0) ctx.mesh.nodes[existingIndex] = clonePayload.node;
|
|
2163
2670
|
else ctx.mesh.nodes.push(clonePayload.node);
|
|
2164
2671
|
ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2672
|
+
await syncCoordinatorDaemonMeshCache(ctx);
|
|
2165
2673
|
}
|
|
2166
2674
|
return JSON.stringify(result, null, 2);
|
|
2167
2675
|
} else if (!isLocalTransport(ctx.transport) && sourceNode.daemonId) {
|
|
@@ -2179,6 +2687,7 @@ async function meshCloneNode(ctx, args) {
|
|
|
2179
2687
|
if (existingIndex >= 0) ctx.mesh.nodes[existingIndex] = clonePayload.node;
|
|
2180
2688
|
else ctx.mesh.nodes.push(clonePayload.node);
|
|
2181
2689
|
ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2690
|
+
await syncCoordinatorDaemonMeshCache(ctx);
|
|
2182
2691
|
}
|
|
2183
2692
|
return JSON.stringify(res, null, 2);
|
|
2184
2693
|
} catch (e) {
|
|
@@ -2274,6 +2783,43 @@ async function meshRemoveNode(ctx, args) {
|
|
|
2274
2783
|
return JSON.stringify({ error: "Cloud mesh remove_node requires node daemonId" });
|
|
2275
2784
|
}
|
|
2276
2785
|
}
|
|
2786
|
+
function resolveRefineConfigNode(ctx, nodeId) {
|
|
2787
|
+
if (nodeId) return findNode(ctx.mesh, nodeId);
|
|
2788
|
+
const node = ctx.mesh.nodes.find((entry) => !!entry.workspace);
|
|
2789
|
+
if (!node) throw new Error("No mesh node with a workspace is available");
|
|
2790
|
+
return node;
|
|
2791
|
+
}
|
|
2792
|
+
async function meshRefineConfigSchema(ctx) {
|
|
2793
|
+
const node = resolveRefineConfigNode(ctx);
|
|
2794
|
+
const result = await commandForNode(ctx, node, "get_mesh_refine_config_schema", {});
|
|
2795
|
+
return JSON.stringify(result, null, 2);
|
|
2796
|
+
}
|
|
2797
|
+
async function meshValidateRefineConfig(ctx, args) {
|
|
2798
|
+
const node = resolveRefineConfigNode(ctx, args.node_id);
|
|
2799
|
+
const result = await commandForNode(ctx, node, "validate_mesh_refine_config", {
|
|
2800
|
+
workspace: node.workspace,
|
|
2801
|
+
inlineMesh: ctx.mesh,
|
|
2802
|
+
...args.config ? { config: args.config } : {}
|
|
2803
|
+
});
|
|
2804
|
+
return JSON.stringify(result, null, 2);
|
|
2805
|
+
}
|
|
2806
|
+
async function meshSuggestRefineConfig(ctx, args) {
|
|
2807
|
+
const node = resolveRefineConfigNode(ctx, args.node_id);
|
|
2808
|
+
const result = await commandForNode(ctx, node, "suggest_mesh_refine_config", {
|
|
2809
|
+
workspace: node.workspace,
|
|
2810
|
+
inlineMesh: ctx.mesh
|
|
2811
|
+
});
|
|
2812
|
+
return JSON.stringify(result, null, 2);
|
|
2813
|
+
}
|
|
2814
|
+
async function meshRefinePlan(ctx, args) {
|
|
2815
|
+
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2816
|
+
const result = await commandForNode(ctx, node, "plan_mesh_refine_node", {
|
|
2817
|
+
meshId: ctx.mesh.id,
|
|
2818
|
+
nodeId: args.node_id,
|
|
2819
|
+
inlineMesh: ctx.mesh
|
|
2820
|
+
});
|
|
2821
|
+
return JSON.stringify(result, null, 2);
|
|
2822
|
+
}
|
|
2277
2823
|
async function meshRefineNode(ctx, args) {
|
|
2278
2824
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2279
2825
|
if (isLocalTransport(ctx.transport)) {
|
|
@@ -2282,7 +2828,7 @@ async function meshRefineNode(ctx, args) {
|
|
|
2282
2828
|
nodeId: args.node_id,
|
|
2283
2829
|
inlineMesh: ctx.mesh
|
|
2284
2830
|
});
|
|
2285
|
-
if (result?.success && result.removeResult?.removed !== false) {
|
|
2831
|
+
if (result?.success && result.async !== true && result.removeResult?.removed !== false) {
|
|
2286
2832
|
const idx = ctx.mesh.nodes.findIndex((n) => n.id === args.node_id);
|
|
2287
2833
|
if (idx >= 0) {
|
|
2288
2834
|
ctx.mesh.nodes.splice(idx, 1);
|
|
@@ -2297,7 +2843,7 @@ async function meshRefineNode(ctx, args) {
|
|
|
2297
2843
|
nodeId: args.node_id,
|
|
2298
2844
|
inlineMesh: ctx.mesh
|
|
2299
2845
|
});
|
|
2300
|
-
if (res?.success && res.removeResult?.removed !== false) {
|
|
2846
|
+
if (res?.success && res.async !== true && res.removeResult?.removed !== false) {
|
|
2301
2847
|
const idx = ctx.mesh.nodes.findIndex((n) => n.id === args.node_id);
|
|
2302
2848
|
if (idx >= 0) {
|
|
2303
2849
|
ctx.mesh.nodes.splice(idx, 1);
|
|
@@ -2334,13 +2880,13 @@ var STANDARD_TOOLS = [
|
|
|
2334
2880
|
function buildMcpHelpText() {
|
|
2335
2881
|
const meshTools = ALL_MESH_TOOLS.map((tool) => tool.name);
|
|
2336
2882
|
return `
|
|
2337
|
-
|
|
2883
|
+
ADHDev MCP Server
|
|
2338
2884
|
|
|
2339
2885
|
Usage:
|
|
2340
|
-
adhdev
|
|
2341
|
-
adhdev
|
|
2342
|
-
adhdev
|
|
2343
|
-
adhdev-mcp --
|
|
2886
|
+
adhdev mcp Local mode (requires standalone daemon)
|
|
2887
|
+
adhdev mcp --api-key <key> Cloud mode (ADHDev cloud API)
|
|
2888
|
+
adhdev mcp --mode ipc --repo-mesh <mesh_id> Cloud daemon IPC mesh mode
|
|
2889
|
+
adhdev-mcp --help Compatibility bin (same server, legacy package entrypoint)
|
|
2344
2890
|
|
|
2345
2891
|
Options:
|
|
2346
2892
|
--mode <mode> Transport: local, cloud, or ipc
|
|
@@ -2519,8 +3065,8 @@ var CloudTransport = class {
|
|
|
2519
3065
|
if (!res.ok) throw new Error(`Approve failed: ${res.status}`);
|
|
2520
3066
|
return res.json();
|
|
2521
3067
|
}
|
|
2522
|
-
async gitStatus(daemonId, workspace, includeDiff = true) {
|
|
2523
|
-
const params = new URLSearchParams({ workspace, includeDiff: String(includeDiff) });
|
|
3068
|
+
async gitStatus(daemonId, workspace, includeDiff = true, refreshUpstream = false) {
|
|
3069
|
+
const params = new URLSearchParams({ workspace, includeDiff: String(includeDiff), refreshUpstream: String(refreshUpstream) });
|
|
2524
3070
|
const res = await fetch(
|
|
2525
3071
|
`${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/git-status?${params}`,
|
|
2526
3072
|
{ headers: this.headers() }
|
|
@@ -2915,6 +3461,22 @@ function formatChatResult(result, sessionId, format, limit = 50, compact = false
|
|
|
2915
3461
|
}))
|
|
2916
3462
|
}, null, 2);
|
|
2917
3463
|
}
|
|
3464
|
+
if ((format === "text" || format === void 0) && compact && compactPayload) {
|
|
3465
|
+
const lines2 = outputMessages.slice(-limit).map((m) => {
|
|
3466
|
+
const role = m.role === "user" ? "User" : m.role === "assistant" ? "Agent" : m.role;
|
|
3467
|
+
const content = messageContent(m);
|
|
3468
|
+
const truncated = content.length > 500 ? `${content.slice(0, 500)}\u2026` : content;
|
|
3469
|
+
return `[${role}] ${truncated}`;
|
|
3470
|
+
});
|
|
3471
|
+
if (compactPayload.summary) {
|
|
3472
|
+
const truncatedSummary = compactPayload.summary.length > 500 ? `${compactPayload.summary.slice(0, 500)}\u2026` : compactPayload.summary;
|
|
3473
|
+
lines2.push(`[Summary] ${truncatedSummary}`);
|
|
3474
|
+
}
|
|
3475
|
+
if (result?.pollingAdvisory) {
|
|
3476
|
+
lines2.push(`Advisory: ${result.pollingAdvisory.message}`);
|
|
3477
|
+
}
|
|
3478
|
+
return lines2.length > 0 ? lines2.join("\n\n") : "No messages in chat.";
|
|
3479
|
+
}
|
|
2918
3480
|
if (outputMessages.length === 0) {
|
|
2919
3481
|
return result?.pollingAdvisory ? `No messages in chat.
|
|
2920
3482
|
|
|
@@ -4015,6 +4577,9 @@ async function startMcpServer(opts) {
|
|
|
4015
4577
|
case "mesh_git_status":
|
|
4016
4578
|
text = await meshGitStatus(meshCtx, a);
|
|
4017
4579
|
break;
|
|
4580
|
+
case "mesh_fast_forward_node":
|
|
4581
|
+
text = await meshFastForwardNode(meshCtx, a);
|
|
4582
|
+
break;
|
|
4018
4583
|
case "mesh_checkpoint":
|
|
4019
4584
|
text = await meshCheckpoint(meshCtx, a);
|
|
4020
4585
|
break;
|
|
@@ -4030,6 +4595,18 @@ async function startMcpServer(opts) {
|
|
|
4030
4595
|
case "mesh_refine_node":
|
|
4031
4596
|
text = await meshRefineNode(meshCtx, a);
|
|
4032
4597
|
break;
|
|
4598
|
+
case "mesh_refine_config_schema":
|
|
4599
|
+
text = await meshRefineConfigSchema(meshCtx);
|
|
4600
|
+
break;
|
|
4601
|
+
case "mesh_validate_refine_config":
|
|
4602
|
+
text = await meshValidateRefineConfig(meshCtx, a);
|
|
4603
|
+
break;
|
|
4604
|
+
case "mesh_suggest_refine_config":
|
|
4605
|
+
text = await meshSuggestRefineConfig(meshCtx, a);
|
|
4606
|
+
break;
|
|
4607
|
+
case "mesh_refine_plan":
|
|
4608
|
+
text = await meshRefinePlan(meshCtx, a);
|
|
4609
|
+
break;
|
|
4033
4610
|
case "mesh_cleanup_sessions":
|
|
4034
4611
|
text = await meshCleanupSessions(meshCtx, a);
|
|
4035
4612
|
break;
|