@adhdev/daemon-standalone 0.9.82-rc.6 → 0.9.82-rc.60
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 +23506 -20606
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/public/assets/index-Bso1b8Lh.css +1 -0
- package/public/assets/index-CsR4qRd4.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 +604 -60
- package/vendor/mcp-server/index.js.map +1 -1
- package/public/assets/index-B8fg8KB1.css +0 -1
- package/public/assets/index-BG1OOql2.js +0 -98
|
@@ -35,6 +35,9 @@ __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";
|
|
@@ -143,6 +146,10 @@ function isLocalTransport(transport) {
|
|
|
143
146
|
}
|
|
144
147
|
|
|
145
148
|
// src/tools/chat-compact.ts
|
|
149
|
+
function isAssistantLike(message) {
|
|
150
|
+
const role = String(message?.role ?? "").toLowerCase();
|
|
151
|
+
return role === "assistant" || role === "agent";
|
|
152
|
+
}
|
|
146
153
|
function messageContent(message) {
|
|
147
154
|
const content = message?.content;
|
|
148
155
|
if (typeof content === "string") return content;
|
|
@@ -161,16 +168,22 @@ function isCoordinatorVisibleMessage(message) {
|
|
|
161
168
|
if (meta?.internal === true || meta?.debug === true || meta?.control === true || meta?.userVisible === false || meta?.user_visible === false) return false;
|
|
162
169
|
return role === "user" || role === "assistant" || role === "agent";
|
|
163
170
|
}
|
|
171
|
+
function buildCompactMessageTail(visibleMessages, opts) {
|
|
172
|
+
const summary = typeof opts.summary === "string" ? opts.summary.trim() : "";
|
|
173
|
+
const shouldOmitSummaryMessage = !!summary && !!opts.finalAssistant && isAssistantLike(opts.finalAssistant) && messageContent(opts.finalAssistant).trim() === summary;
|
|
174
|
+
const sourceMessages = shouldOmitSummaryMessage ? visibleMessages.filter((message) => message !== opts.finalAssistant) : visibleMessages;
|
|
175
|
+
return sourceMessages.slice(-opts.limit);
|
|
176
|
+
}
|
|
164
177
|
function compactChatPayload(payload, opts = {}) {
|
|
165
178
|
const rawMessages = Array.isArray(payload?.messages) ? payload.messages : [];
|
|
166
179
|
const visible = rawMessages.filter(isCoordinatorVisibleMessage);
|
|
167
180
|
const limit = Math.max(1, Math.min(opts.limit ?? 10, 10));
|
|
168
|
-
const messages = visible.slice(-limit);
|
|
169
181
|
const finalAssistant = [...visible].reverse().find((message) => {
|
|
170
182
|
const role = String(message?.role ?? "").toLowerCase();
|
|
171
183
|
return (role === "assistant" || role === "agent") && messageContent(message).trim();
|
|
172
184
|
});
|
|
173
185
|
const summary = typeof payload?.summary === "string" && payload.summary.trim() ? payload.summary.trim() : messageContent(finalAssistant).trim();
|
|
186
|
+
const messages = buildCompactMessageTail(visible, { summary, finalAssistant, limit });
|
|
174
187
|
return {
|
|
175
188
|
success: payload?.success !== false,
|
|
176
189
|
compact: true,
|
|
@@ -235,6 +248,30 @@ var meshSessionProviderMetadata = /* @__PURE__ */ new Map();
|
|
|
235
248
|
function readString(value) {
|
|
236
249
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
237
250
|
}
|
|
251
|
+
function summarizeTaskMessage(message) {
|
|
252
|
+
const taskSummary = message.replace(/\s+/g, " ").trim();
|
|
253
|
+
const taskTitle = taskSummary.length > 96 ? `${taskSummary.slice(0, 93)}...` : taskSummary;
|
|
254
|
+
return { taskTitle: taskTitle || "(untitled task)", taskSummary };
|
|
255
|
+
}
|
|
256
|
+
function buildDirectTaskPayload(message, via, opts) {
|
|
257
|
+
const descriptor = summarizeTaskMessage(message);
|
|
258
|
+
return {
|
|
259
|
+
source: "direct",
|
|
260
|
+
via,
|
|
261
|
+
taskId: opts.taskId,
|
|
262
|
+
message,
|
|
263
|
+
taskTitle: descriptor.taskTitle,
|
|
264
|
+
taskSummary: descriptor.taskSummary,
|
|
265
|
+
...opts.taskMode ? { taskMode: opts.taskMode } : {},
|
|
266
|
+
...opts.providerType ? { providerType: opts.providerType } : {},
|
|
267
|
+
...opts.targetSessionId ? { targetSessionId: opts.targetSessionId } : {}
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
function findNode(mesh, nodeId) {
|
|
271
|
+
const node = mesh.nodes.find((n) => n.id === nodeId);
|
|
272
|
+
if (!node) throw new Error(`Node '${nodeId}' is not a member of mesh '${mesh.name}'`);
|
|
273
|
+
return node;
|
|
274
|
+
}
|
|
238
275
|
var DUPLICATE_DISPATCH_WINDOW_MS = 6e4;
|
|
239
276
|
var STALE_ASSIGNED_QUEUE_MS = 30 * 6e4;
|
|
240
277
|
var OLD_HISTORICAL_QUEUE_RECORD_MS = 7 * 24 * 60 * 6e4;
|
|
@@ -246,15 +283,24 @@ async function refreshMeshFromDaemon(ctx) {
|
|
|
246
283
|
const result = await ctx.transport.command("get_mesh", { meshId: ctx.mesh.id });
|
|
247
284
|
if (!result?.success || !Array.isArray(result.mesh?.nodes)) return;
|
|
248
285
|
const refreshedNodes = result.mesh.nodes.filter((n) => n?.id).map((n) => n);
|
|
249
|
-
if (!refreshedNodes.length) return;
|
|
250
286
|
ctx.mesh.nodes.splice(0, ctx.mesh.nodes.length, ...refreshedNodes);
|
|
251
287
|
ctx.mesh.updatedAt = result.mesh.updatedAt ?? ctx.mesh.updatedAt;
|
|
252
288
|
} catch {
|
|
253
289
|
}
|
|
254
290
|
}
|
|
291
|
+
async function syncCoordinatorDaemonMeshCache(ctx) {
|
|
292
|
+
if (!(ctx.transport instanceof IpcTransport)) return;
|
|
293
|
+
try {
|
|
294
|
+
await ctx.transport.command("get_mesh", {
|
|
295
|
+
meshId: ctx.mesh.id,
|
|
296
|
+
inlineMesh: ctx.mesh
|
|
297
|
+
});
|
|
298
|
+
} catch {
|
|
299
|
+
}
|
|
300
|
+
}
|
|
255
301
|
async function findNodeWithRefresh(ctx, nodeId) {
|
|
256
302
|
const hit = ctx.mesh.nodes.find((n) => n.id === nodeId);
|
|
257
|
-
if (hit) return hit;
|
|
303
|
+
if (hit && !hit.isLocalWorktree) return hit;
|
|
258
304
|
await refreshMeshFromDaemon(ctx);
|
|
259
305
|
const refreshed = ctx.mesh.nodes.find((n) => n.id === nodeId);
|
|
260
306
|
if (!refreshed) throw new Error(`Node '${nodeId}' is not a member of mesh '${ctx.mesh.name}'`);
|
|
@@ -262,7 +308,7 @@ async function findNodeWithRefresh(ctx, nodeId) {
|
|
|
262
308
|
}
|
|
263
309
|
async function findOptionalNodeWithRefresh(ctx, nodeId) {
|
|
264
310
|
const hit = ctx.mesh.nodes.find((n) => n.id === nodeId);
|
|
265
|
-
if (hit) return hit;
|
|
311
|
+
if (hit && !hit.isLocalWorktree) return hit;
|
|
266
312
|
await refreshMeshFromDaemon(ctx);
|
|
267
313
|
return ctx.mesh.nodes.find((n) => n.id === nodeId) ?? null;
|
|
268
314
|
}
|
|
@@ -314,9 +360,26 @@ function buildMissingNodeReadChatRecovery(ctx, args) {
|
|
|
314
360
|
readDebugLocator: readString(lastTerminal?.payload?.readDebugLocator) || readString(lastTerminal?.payload?.debugBundlePath)
|
|
315
361
|
};
|
|
316
362
|
if (finalSummary) {
|
|
363
|
+
if (args.compact === true) {
|
|
364
|
+
return {
|
|
365
|
+
...compactChatPayload({
|
|
366
|
+
success: true,
|
|
367
|
+
status: "idle",
|
|
368
|
+
providerSessionId,
|
|
369
|
+
summary: finalSummary,
|
|
370
|
+
messages: [{ role: "assistant", content: finalSummary, isHistorical: true }]
|
|
371
|
+
}, {
|
|
372
|
+
nodeId: args.node_id,
|
|
373
|
+
sessionId: args.session_id,
|
|
374
|
+
limit: args.tail ?? 10
|
|
375
|
+
}),
|
|
376
|
+
recoveredFromLedger: true,
|
|
377
|
+
ledger
|
|
378
|
+
};
|
|
379
|
+
}
|
|
317
380
|
return {
|
|
318
381
|
success: true,
|
|
319
|
-
compact:
|
|
382
|
+
compact: false,
|
|
320
383
|
recoveredFromLedger: true,
|
|
321
384
|
nodeId: args.node_id,
|
|
322
385
|
sessionId: args.session_id,
|
|
@@ -368,6 +431,14 @@ function buildMissingNodeReadChatRecovery(ctx, args) {
|
|
|
368
431
|
function readSessionRecordId(session) {
|
|
369
432
|
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
433
|
}
|
|
434
|
+
function extractStatusMetadataSessions(value) {
|
|
435
|
+
const payload = unwrapCommandPayload(value);
|
|
436
|
+
const status = payload?.status && typeof payload.status === "object" ? payload.status : payload;
|
|
437
|
+
return Array.isArray(status?.sessions) ? status.sessions : [];
|
|
438
|
+
}
|
|
439
|
+
function resolveSessionProviderType(session) {
|
|
440
|
+
return readString(session?.providerType) || readString(session?.cliType) || readString(session?.agentType) || "";
|
|
441
|
+
}
|
|
371
442
|
function addSessionRecord(target, session) {
|
|
372
443
|
if (!session || typeof session !== "object" || isTerminalSessionRecord(session)) return;
|
|
373
444
|
const sessionId = readSessionRecordId(session);
|
|
@@ -580,22 +651,59 @@ function isIdleSessionRecord(session) {
|
|
|
580
651
|
const chatStatus = typeof session?.activeChat?.status === "string" ? session.activeChat.status.toLowerCase() : "";
|
|
581
652
|
return status === "idle" || chatStatus === "waiting_input";
|
|
582
653
|
}
|
|
654
|
+
function isMeshOwnedDelegateSession(session, meshId, nodeId) {
|
|
655
|
+
const settings = session?.settings;
|
|
656
|
+
const sessionMeshId = typeof settings?.meshNodeFor === "string" ? settings.meshNodeFor.trim() : "";
|
|
657
|
+
const coordinatorDaemonId = typeof settings?.meshCoordinatorDaemonId === "string" ? settings.meshCoordinatorDaemonId.trim() : "";
|
|
658
|
+
const sessionNodeId = typeof settings?.meshNodeId === "string" ? settings.meshNodeId.trim() : "";
|
|
659
|
+
if (sessionMeshId !== meshId || !coordinatorDaemonId) return false;
|
|
660
|
+
return !sessionNodeId || sessionNodeId === nodeId;
|
|
661
|
+
}
|
|
583
662
|
function chooseDispatchableSession(sessions, providerType, meshId, nodeId) {
|
|
584
663
|
const live = sessions.filter((session) => !isTerminalSessionRecord(session));
|
|
585
664
|
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
665
|
const meshSessions = live.filter(
|
|
595
|
-
(session) => isMeshOwnedDelegateSession(session)
|
|
666
|
+
(session) => isMeshOwnedDelegateSession(session, meshId, nodeId)
|
|
596
667
|
);
|
|
597
668
|
return meshSessions.find((session) => isIdleSessionRecord(session) && matchingProvider(session)) || meshSessions.find(matchingProvider) || void 0;
|
|
598
669
|
}
|
|
670
|
+
function buildRelayUnsafeRemoteSessionFailure(ctx, node, sessionId, providerType) {
|
|
671
|
+
return {
|
|
672
|
+
success: false,
|
|
673
|
+
recoverable: true,
|
|
674
|
+
code: "mesh_delegate_session_missing_relay_metadata",
|
|
675
|
+
reason: "mesh_delegate_session_missing_relay_metadata",
|
|
676
|
+
transport: "mesh_transport",
|
|
677
|
+
retryRecommended: true,
|
|
678
|
+
meshId: ctx.mesh.id,
|
|
679
|
+
nodeId: node.id,
|
|
680
|
+
daemonId: node.daemonId,
|
|
681
|
+
workspace: node.workspace,
|
|
682
|
+
sessionId,
|
|
683
|
+
...providerType ? { resolvedProviderType: providerType } : {},
|
|
684
|
+
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.`,
|
|
685
|
+
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.`,
|
|
686
|
+
noFallbackReason: "Blindly reusing a remote session without mesh relay metadata would silently drop task_completed / generating_completed events."
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
function buildMissingCoordinatorDaemonIdFailure(ctx, node, providerType) {
|
|
690
|
+
return {
|
|
691
|
+
success: false,
|
|
692
|
+
recoverable: true,
|
|
693
|
+
code: "mesh_coordinator_daemon_unknown",
|
|
694
|
+
reason: "mesh_coordinator_daemon_unknown",
|
|
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
|
+
...providerType ? { resolvedProviderType: providerType } : {},
|
|
702
|
+
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.`,
|
|
703
|
+
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.",
|
|
704
|
+
noFallbackReason: "Launching without meshCoordinatorDaemonId would create a worker session that can finish work but cannot emit task_completed / generating_completed back to the coordinator."
|
|
705
|
+
};
|
|
706
|
+
}
|
|
599
707
|
function findNestedPayload(value, predicate) {
|
|
600
708
|
const seen = /* @__PURE__ */ new Set();
|
|
601
709
|
const stack = [{ payload: value, depth: 0 }];
|
|
@@ -623,12 +731,16 @@ function extractGitDiff(value) {
|
|
|
623
731
|
}
|
|
624
732
|
function extractSubmodules(value, ignorePaths) {
|
|
625
733
|
const payload = unwrapCommandPayload(value);
|
|
626
|
-
const subs = payload?.submodules ?? value?.submodules;
|
|
734
|
+
const subs = payload?.status?.submodules ?? payload?.submodules ?? value?.status?.submodules ?? value?.submodules;
|
|
627
735
|
if (!Array.isArray(subs)) return void 0;
|
|
628
736
|
if (ignorePaths.length === 0) return subs;
|
|
629
737
|
const ignoreSet = new Set(ignorePaths);
|
|
630
738
|
return subs.filter((s) => s?.path && !ignoreSet.has(s.path));
|
|
631
739
|
}
|
|
740
|
+
function assignFullGitSnapshot(entry, status) {
|
|
741
|
+
if (!status || typeof status !== "object" || Array.isArray(status)) return;
|
|
742
|
+
entry.git = status;
|
|
743
|
+
}
|
|
632
744
|
function extractLaunchPayload(value) {
|
|
633
745
|
return findNestedPayload(value, (payload) => Boolean(payload?.sessionId || payload?.id || payload?.runtimeSessionId));
|
|
634
746
|
}
|
|
@@ -753,20 +865,63 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
|
|
|
753
865
|
let sessionId = args.session_id?.trim() || "";
|
|
754
866
|
const providerPriorityList = Array.isArray(node.policy?.providerPriority) ? node.policy.providerPriority : [];
|
|
755
867
|
let resolvedProviderType = args.providerType?.trim() || providerPriorityList[0] || "";
|
|
756
|
-
if (!sessionId) {
|
|
868
|
+
if (!sessionId || args.session_id) {
|
|
757
869
|
try {
|
|
758
870
|
const relayResult = await transport.meshCommand(daemonId, "get_status_metadata", {});
|
|
759
|
-
const
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
871
|
+
const sessions = extractStatusMetadataSessions(relayResult);
|
|
872
|
+
if (sessionId) {
|
|
873
|
+
const explicitSession = sessions.find((session) => readSessionRecordId(session) === sessionId);
|
|
874
|
+
if (!explicitSession) {
|
|
875
|
+
return {
|
|
876
|
+
success: false,
|
|
877
|
+
recoverable: true,
|
|
878
|
+
code: "mesh_target_session_not_found",
|
|
879
|
+
reason: "mesh_target_session_not_found",
|
|
880
|
+
transport: "mesh_transport",
|
|
881
|
+
retryRecommended: true,
|
|
882
|
+
meshId: ctx.mesh.id,
|
|
883
|
+
nodeId: node.id,
|
|
884
|
+
daemonId,
|
|
885
|
+
workspace: node.workspace,
|
|
886
|
+
sessionId,
|
|
887
|
+
...resolvedProviderType ? { resolvedProviderType } : {},
|
|
888
|
+
error: `Remote session '${sessionId}' is not present in the live status for node '${node.id}'.`,
|
|
889
|
+
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.`
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
if (!isMeshOwnedDelegateSession(explicitSession, ctx.mesh.id, node.id)) {
|
|
893
|
+
return buildRelayUnsafeRemoteSessionFailure(
|
|
894
|
+
ctx,
|
|
895
|
+
node,
|
|
896
|
+
sessionId,
|
|
897
|
+
resolvedProviderType || resolveSessionProviderType(explicitSession) || void 0
|
|
898
|
+
);
|
|
899
|
+
}
|
|
765
900
|
if (!resolvedProviderType) {
|
|
766
|
-
resolvedProviderType =
|
|
901
|
+
resolvedProviderType = resolveSessionProviderType(explicitSession);
|
|
902
|
+
}
|
|
903
|
+
} else {
|
|
904
|
+
const targetSession = chooseDispatchableSession(sessions, resolvedProviderType, ctx.mesh.id, node.id);
|
|
905
|
+
if (targetSession?.id || targetSession?.sessionId) {
|
|
906
|
+
sessionId = targetSession.id || targetSession.sessionId;
|
|
907
|
+
if (!resolvedProviderType) {
|
|
908
|
+
resolvedProviderType = resolveSessionProviderType(targetSession);
|
|
909
|
+
}
|
|
767
910
|
}
|
|
768
911
|
}
|
|
769
912
|
} catch (e) {
|
|
913
|
+
if (sessionId) {
|
|
914
|
+
return {
|
|
915
|
+
...buildCoordinatorP2pRelayFailure(e, {
|
|
916
|
+
command: "get_status_metadata",
|
|
917
|
+
targetDaemonId: daemonId,
|
|
918
|
+
nodeId: node.id,
|
|
919
|
+
sessionId
|
|
920
|
+
}),
|
|
921
|
+
success: false,
|
|
922
|
+
error: `Cannot verify remote session '${sessionId}' before dispatch: ${e?.message || String(e)}`
|
|
923
|
+
};
|
|
924
|
+
}
|
|
770
925
|
}
|
|
771
926
|
}
|
|
772
927
|
if (!resolvedProviderType) {
|
|
@@ -796,7 +951,7 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
|
|
|
796
951
|
error: `P2P dispatch failed: ${errorMessage}`
|
|
797
952
|
};
|
|
798
953
|
}
|
|
799
|
-
return { success: true, dispatched: true, sessionId: sessionId || resolvedProviderType };
|
|
954
|
+
return { success: true, dispatched: true, sessionId: sessionId || resolvedProviderType, providerType: resolvedProviderType };
|
|
800
955
|
} catch (e) {
|
|
801
956
|
const errorMessage = e?.message || String(e);
|
|
802
957
|
return {
|
|
@@ -880,6 +1035,10 @@ function summarizeRelatedRepoStatus(repo, status) {
|
|
|
880
1035
|
workspace: repo.workspace,
|
|
881
1036
|
isGitRepo: status?.isGitRepo === true,
|
|
882
1037
|
branch: status?.branch ?? null,
|
|
1038
|
+
upstream: status?.upstream ?? null,
|
|
1039
|
+
upstreamStatus: typeof status?.upstreamStatus === "string" ? status.upstreamStatus : status?.upstream ? "unchecked" : "no_upstream",
|
|
1040
|
+
upstreamFetchedAt: Number.isFinite(Number(status?.upstreamFetchedAt)) ? Number(status.upstreamFetchedAt) : null,
|
|
1041
|
+
upstreamFetchError: typeof status?.upstreamFetchError === "string" ? status.upstreamFetchError : null,
|
|
883
1042
|
ahead: Number.isFinite(Number(status?.ahead)) ? Number(status.ahead) : 0,
|
|
884
1043
|
behind: Number.isFinite(Number(status?.behind)) ? Number(status.behind) : 0,
|
|
885
1044
|
dirty,
|
|
@@ -896,7 +1055,7 @@ async function collectRelatedRepoStatuses(ctx, node) {
|
|
|
896
1055
|
const results = [];
|
|
897
1056
|
for (const repo of relatedRepos) {
|
|
898
1057
|
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 });
|
|
1058
|
+
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
1059
|
const status = extractGitStatus(statusResult);
|
|
901
1060
|
results.push(summarizeRelatedRepoStatus(repo, status));
|
|
902
1061
|
} catch (e) {
|
|
@@ -944,11 +1103,13 @@ function buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges) {
|
|
|
944
1103
|
const ahead = readNumeric(status?.ahead);
|
|
945
1104
|
const behind = readNumeric(status?.behind);
|
|
946
1105
|
const upstream = readString(status?.upstream) ?? null;
|
|
1106
|
+
const upstreamStatus = readString(status?.upstreamStatus) ?? (upstream ? "unchecked" : "no_upstream");
|
|
947
1107
|
const hasConflicts = status?.hasConflicts === true || Array.isArray(status?.conflictFiles) && status.conflictFiles.length > 0;
|
|
948
1108
|
const base = {
|
|
949
1109
|
defaultBranch,
|
|
950
1110
|
branch,
|
|
951
1111
|
upstream,
|
|
1112
|
+
upstreamStatus,
|
|
952
1113
|
ahead,
|
|
953
1114
|
behind,
|
|
954
1115
|
isWorktree: node.isLocalWorktree === true,
|
|
@@ -982,6 +1143,15 @@ function buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges) {
|
|
|
982
1143
|
};
|
|
983
1144
|
}
|
|
984
1145
|
if (branch === defaultBranch) {
|
|
1146
|
+
if (upstream && upstreamStatus !== "fresh") {
|
|
1147
|
+
return {
|
|
1148
|
+
...base,
|
|
1149
|
+
status: "blocked_review",
|
|
1150
|
+
needsConvergence: true,
|
|
1151
|
+
reason: "default_branch_upstream_unverified",
|
|
1152
|
+
nextStep: `Refresh ${defaultBranch}'s upstream refs or resolve the fetch failure before declaring convergence complete for node '${node.id}'.`
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
985
1155
|
if (ahead > 0 || behind > 0) {
|
|
986
1156
|
return {
|
|
987
1157
|
...base,
|
|
@@ -1008,6 +1178,15 @@ function buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges) {
|
|
|
1008
1178
|
nextStep: `Run mesh_refine_node(node_id: "${node.id}") or explicitly classify this worktree as blocked_review/not_mergeable before ending the task.`
|
|
1009
1179
|
};
|
|
1010
1180
|
}
|
|
1181
|
+
if (upstream && upstreamStatus !== "fresh") {
|
|
1182
|
+
return {
|
|
1183
|
+
...base,
|
|
1184
|
+
status: "blocked_review",
|
|
1185
|
+
needsConvergence: true,
|
|
1186
|
+
reason: "feature_branch_upstream_unverified",
|
|
1187
|
+
nextStep: `Refresh branch '${branch}' upstream refs or resolve the fetch failure before deciding whether it is ready to merge into ${defaultBranch}.`
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1011
1190
|
if (!upstream || ahead > 0 || behind > 0) {
|
|
1012
1191
|
return {
|
|
1013
1192
|
...base,
|
|
@@ -1051,6 +1230,71 @@ async function commandForNode(ctx, node, command, args = {}) {
|
|
|
1051
1230
|
}
|
|
1052
1231
|
throw new Error(`Command '${command}' requires daemon IPC/local transport for node '${node.id}'`);
|
|
1053
1232
|
}
|
|
1233
|
+
function normalizePendingMeshCoordinatorEvents(value) {
|
|
1234
|
+
const payload = unwrapCommandPayload(value);
|
|
1235
|
+
const events = Array.isArray(payload?.events) ? payload.events : Array.isArray(value?.events) ? value.events : [];
|
|
1236
|
+
return events.filter((event) => event && typeof event === "object");
|
|
1237
|
+
}
|
|
1238
|
+
function buildMeshForwardPayloadFromPendingEvent(event) {
|
|
1239
|
+
const metadataEvent = event?.metadataEvent && typeof event.metadataEvent === "object" ? event.metadataEvent : {};
|
|
1240
|
+
return {
|
|
1241
|
+
event: readString(event?.event),
|
|
1242
|
+
meshId: readString(event?.meshId),
|
|
1243
|
+
nodeId: readString(event?.nodeId) || readString(metadataEvent.meshNodeId),
|
|
1244
|
+
workspace: readString(event?.workspace) || readString(metadataEvent.workspace),
|
|
1245
|
+
targetSessionId: readString(metadataEvent.targetSessionId) || readString(metadataEvent.sessionId) || readString(metadataEvent.instanceId),
|
|
1246
|
+
providerType: readString(metadataEvent.providerType),
|
|
1247
|
+
providerSessionId: readString(metadataEvent.providerSessionId),
|
|
1248
|
+
finalSummary: readString(metadataEvent.finalSummary) || readString(metadataEvent.summary),
|
|
1249
|
+
...metadataEvent.intentional === true ? { intentional: true } : {},
|
|
1250
|
+
...metadataEvent.intentionalStop === true ? { intentionalStop: true } : {},
|
|
1251
|
+
...metadataEvent.operatorCleanup === true ? { operatorCleanup: true } : {},
|
|
1252
|
+
...readString(metadataEvent.reason) ? { reason: readString(metadataEvent.reason) } : {},
|
|
1253
|
+
...readString(metadataEvent.stopReason) ? { stopReason: readString(metadataEvent.stopReason) } : {},
|
|
1254
|
+
...readString(metadataEvent.cleanupReason) ? { cleanupReason: readString(metadataEvent.cleanupReason) } : {},
|
|
1255
|
+
...readString(metadataEvent.source) ? { source: readString(metadataEvent.source) } : {}
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1258
|
+
async function drainCoordinatorPendingEvents(ctx, opts) {
|
|
1259
|
+
const requestedNodeIds = opts?.nodeIds?.length ? new Set(opts.nodeIds) : null;
|
|
1260
|
+
const matchesCurrentMesh = (event) => readString(event?.meshId) === ctx.mesh.id;
|
|
1261
|
+
if (ctx.transport instanceof IpcTransport) {
|
|
1262
|
+
const surfacedEvents = [];
|
|
1263
|
+
try {
|
|
1264
|
+
surfacedEvents.push(
|
|
1265
|
+
...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", { meshId: ctx.mesh.id })).filter(matchesCurrentMesh)
|
|
1266
|
+
);
|
|
1267
|
+
} catch {
|
|
1268
|
+
}
|
|
1269
|
+
for (const node of ctx.mesh.nodes) {
|
|
1270
|
+
if (!node.daemonId || isLocalControlPlaneNode(ctx, node)) continue;
|
|
1271
|
+
if (requestedNodeIds && !requestedNodeIds.has(node.id)) continue;
|
|
1272
|
+
try {
|
|
1273
|
+
const remoteEvents = normalizePendingMeshCoordinatorEvents(
|
|
1274
|
+
await ctx.transport.meshCommand(node.daemonId, "get_pending_mesh_events", { meshId: ctx.mesh.id })
|
|
1275
|
+
).filter(matchesCurrentMesh);
|
|
1276
|
+
if (remoteEvents.length === 0) continue;
|
|
1277
|
+
for (const event of remoteEvents) {
|
|
1278
|
+
const payload = buildMeshForwardPayloadFromPendingEvent(event);
|
|
1279
|
+
if (!payload.event || !payload.meshId) continue;
|
|
1280
|
+
await ctx.transport.command("mesh_forward_event", payload);
|
|
1281
|
+
}
|
|
1282
|
+
} catch {
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
try {
|
|
1286
|
+
surfacedEvents.push(
|
|
1287
|
+
...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", { meshId: ctx.mesh.id })).filter(matchesCurrentMesh)
|
|
1288
|
+
);
|
|
1289
|
+
} catch {
|
|
1290
|
+
}
|
|
1291
|
+
return surfacedEvents;
|
|
1292
|
+
}
|
|
1293
|
+
if (isLocalTransport(ctx.transport)) {
|
|
1294
|
+
return (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id).filter(matchesCurrentMesh);
|
|
1295
|
+
}
|
|
1296
|
+
return [];
|
|
1297
|
+
}
|
|
1054
1298
|
function isP2pTransportUnavailableError(error) {
|
|
1055
1299
|
return (0, import_daemon_core.isP2pRelayTransportFailure)(error);
|
|
1056
1300
|
}
|
|
@@ -1088,7 +1332,9 @@ var MESH_ENQUEUE_TASK_TOOL = {
|
|
|
1088
1332
|
inputSchema: {
|
|
1089
1333
|
type: "object",
|
|
1090
1334
|
properties: {
|
|
1091
|
-
message: { type: "string", description: "The task instruction for the agent." }
|
|
1335
|
+
message: { type: "string", description: "The task instruction for the agent." },
|
|
1336
|
+
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." },
|
|
1337
|
+
taskMode: { type: "string", enum: ["code_change", "validation", "live_debug_readonly", "launch_app", "convergence"], description: "CamelCase alias for task_mode." }
|
|
1092
1338
|
},
|
|
1093
1339
|
required: ["message"]
|
|
1094
1340
|
}
|
|
@@ -1148,7 +1394,9 @@ var MESH_SEND_TASK_TOOL = {
|
|
|
1148
1394
|
properties: {
|
|
1149
1395
|
node_id: { type: "string", description: "Target node ID (from mesh_list_nodes)." },
|
|
1150
1396
|
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." }
|
|
1397
|
+
message: { type: "string", description: "Natural-language task to send to the agent." },
|
|
1398
|
+
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." },
|
|
1399
|
+
taskMode: { type: "string", enum: ["code_change", "validation", "live_debug_readonly", "launch_app", "convergence"], description: "CamelCase alias for task_mode." }
|
|
1152
1400
|
},
|
|
1153
1401
|
required: ["node_id", "session_id", "message"]
|
|
1154
1402
|
}
|
|
@@ -1206,6 +1454,21 @@ var MESH_GIT_STATUS_TOOL = {
|
|
|
1206
1454
|
required: ["node_id"]
|
|
1207
1455
|
}
|
|
1208
1456
|
};
|
|
1457
|
+
var MESH_FAST_FORWARD_NODE_TOOL = {
|
|
1458
|
+
name: "mesh_fast_forward_node",
|
|
1459
|
+
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.",
|
|
1460
|
+
inputSchema: {
|
|
1461
|
+
type: "object",
|
|
1462
|
+
properties: {
|
|
1463
|
+
node_id: { type: "string", description: "Target node ID." },
|
|
1464
|
+
branch: { type: "string", description: "Optional guard: require the node's current branch to match this branch before planning/executing." },
|
|
1465
|
+
execute: { type: "boolean", description: "When true, apply the fast-forward if all safety gates pass. Defaults false/dry-run." },
|
|
1466
|
+
dry_run: { type: "boolean", description: "Preview only. Defaults true unless execute=true; dry_run=true overrides execute." },
|
|
1467
|
+
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." }
|
|
1468
|
+
},
|
|
1469
|
+
required: ["node_id"]
|
|
1470
|
+
}
|
|
1471
|
+
};
|
|
1209
1472
|
var MESH_CHECKPOINT_TOOL = {
|
|
1210
1473
|
name: "mesh_checkpoint",
|
|
1211
1474
|
description: "Create a git checkpoint (commit) on a mesh node workspace.",
|
|
@@ -1289,7 +1552,7 @@ var MESH_TASK_HISTORY_TOOL = {
|
|
|
1289
1552
|
type: "object",
|
|
1290
1553
|
properties: {
|
|
1291
1554
|
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." }
|
|
1555
|
+
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
1556
|
}
|
|
1294
1557
|
}
|
|
1295
1558
|
};
|
|
@@ -1318,6 +1581,43 @@ var MESH_REFINE_NODE_TOOL = {
|
|
|
1318
1581
|
required: ["node_id"]
|
|
1319
1582
|
}
|
|
1320
1583
|
};
|
|
1584
|
+
var MESH_REFINE_CONFIG_SCHEMA_TOOL = {
|
|
1585
|
+
name: "mesh_refine_config_schema",
|
|
1586
|
+
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.",
|
|
1587
|
+
inputSchema: { type: "object", properties: {} }
|
|
1588
|
+
};
|
|
1589
|
+
var MESH_VALIDATE_REFINE_CONFIG_TOOL = {
|
|
1590
|
+
name: "mesh_validate_refine_config",
|
|
1591
|
+
description: "Validate the repo mesh/refine config for a node/workspace without running validation commands or merging.",
|
|
1592
|
+
inputSchema: {
|
|
1593
|
+
type: "object",
|
|
1594
|
+
properties: {
|
|
1595
|
+
node_id: { type: "string", description: "Optional node/workspace whose refine config should be loaded. Defaults to the first mesh node." },
|
|
1596
|
+
config: { type: "object", description: "Optional inline config object to validate instead of loading from the repo." }
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
};
|
|
1600
|
+
var MESH_SUGGEST_REFINE_CONFIG_TOOL = {
|
|
1601
|
+
name: "mesh_suggest_refine_config",
|
|
1602
|
+
description: "Suggest a repo mesh/refine config scaffold from project context/package scripts. Suggestions are never executed until saved as explicit refine config.",
|
|
1603
|
+
inputSchema: {
|
|
1604
|
+
type: "object",
|
|
1605
|
+
properties: {
|
|
1606
|
+
node_id: { type: "string", description: "Optional node/workspace used for suggestions. Defaults to the first mesh node." }
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
};
|
|
1610
|
+
var MESH_REFINE_PLAN_TOOL = {
|
|
1611
|
+
name: "mesh_refine_plan",
|
|
1612
|
+
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.",
|
|
1613
|
+
inputSchema: {
|
|
1614
|
+
type: "object",
|
|
1615
|
+
properties: {
|
|
1616
|
+
node_id: { type: "string", description: "Node ID of the worktree node to plan." }
|
|
1617
|
+
},
|
|
1618
|
+
required: ["node_id"]
|
|
1619
|
+
}
|
|
1620
|
+
};
|
|
1321
1621
|
var ALL_MESH_TOOLS = [
|
|
1322
1622
|
MESH_STATUS_TOOL,
|
|
1323
1623
|
MESH_LIST_NODES_TOOL,
|
|
@@ -1330,11 +1630,16 @@ var ALL_MESH_TOOLS = [
|
|
|
1330
1630
|
MESH_READ_DEBUG_TOOL,
|
|
1331
1631
|
MESH_LAUNCH_SESSION_TOOL,
|
|
1332
1632
|
MESH_GIT_STATUS_TOOL,
|
|
1633
|
+
MESH_FAST_FORWARD_NODE_TOOL,
|
|
1333
1634
|
MESH_CHECKPOINT_TOOL,
|
|
1334
1635
|
MESH_APPROVE_TOOL,
|
|
1335
1636
|
MESH_CLONE_NODE_TOOL,
|
|
1336
1637
|
MESH_REMOVE_NODE_TOOL,
|
|
1337
1638
|
MESH_REFINE_NODE_TOOL,
|
|
1639
|
+
MESH_REFINE_CONFIG_SCHEMA_TOOL,
|
|
1640
|
+
MESH_VALIDATE_REFINE_CONFIG_TOOL,
|
|
1641
|
+
MESH_SUGGEST_REFINE_CONFIG_TOOL,
|
|
1642
|
+
MESH_REFINE_PLAN_TOOL,
|
|
1338
1643
|
MESH_CLEANUP_SESSIONS_TOOL,
|
|
1339
1644
|
MESH_TASK_HISTORY_TOOL,
|
|
1340
1645
|
MESH_RECONCILE_LEDGER_TOOL
|
|
@@ -1352,11 +1657,12 @@ async function meshStatus(ctx) {
|
|
|
1352
1657
|
};
|
|
1353
1658
|
try {
|
|
1354
1659
|
if (!isLocalTransport(transport) && node.daemonId) {
|
|
1355
|
-
const result = await transport.gitStatus(node.daemonId, node.workspace, false);
|
|
1660
|
+
const result = await transport.gitStatus(node.daemonId, node.workspace, false, true);
|
|
1356
1661
|
const status = extractGitStatus(result);
|
|
1357
1662
|
const uncommittedChanges = countUncommittedChanges(status);
|
|
1358
1663
|
const dirty = isGitStatusDirty(status);
|
|
1359
1664
|
entry.health = status?.isGitRepo ? dirty ? "dirty" : "online" : "degraded";
|
|
1665
|
+
assignFullGitSnapshot(entry, status);
|
|
1360
1666
|
entry.branch = status?.branch;
|
|
1361
1667
|
entry.isDirty = dirty;
|
|
1362
1668
|
entry.uncommittedChanges = uncommittedChanges;
|
|
@@ -1370,6 +1676,7 @@ async function meshStatus(ctx) {
|
|
|
1370
1676
|
const autoDiscover = node.policy?.autoDiscoverSubmodules !== false;
|
|
1371
1677
|
const statusResult = await commandForNode(ctx, node, "git_status", {
|
|
1372
1678
|
workspace: node.workspace,
|
|
1679
|
+
refreshUpstream: true,
|
|
1373
1680
|
includeSubmodules: autoDiscover,
|
|
1374
1681
|
submoduleIgnorePaths: node.policy?.submoduleIgnorePaths || void 0
|
|
1375
1682
|
});
|
|
@@ -1377,6 +1684,7 @@ async function meshStatus(ctx) {
|
|
|
1377
1684
|
const uncommittedChanges = countUncommittedChanges(status);
|
|
1378
1685
|
const dirty = isGitStatusDirty(status);
|
|
1379
1686
|
entry.health = status?.isGitRepo ? dirty ? "dirty" : "online" : "degraded";
|
|
1687
|
+
assignFullGitSnapshot(entry, status);
|
|
1380
1688
|
entry.branch = status?.branch;
|
|
1381
1689
|
entry.isDirty = dirty;
|
|
1382
1690
|
entry.uncommittedChanges = uncommittedChanges;
|
|
@@ -1454,13 +1762,27 @@ async function meshStatus(ctx) {
|
|
|
1454
1762
|
if (relatedRepos.length) entry.relatedRepos = relatedRepos;
|
|
1455
1763
|
results.push(entry);
|
|
1456
1764
|
}
|
|
1765
|
+
const activeWorkEvidence = (0, import_daemon_core.buildMeshActiveWork)({
|
|
1766
|
+
meshId: mesh.id,
|
|
1767
|
+
queue: (0, import_daemon_core.getQueue)(mesh.id),
|
|
1768
|
+
ledgerEntries: (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail: 500 }),
|
|
1769
|
+
nodes: mesh.nodes
|
|
1770
|
+
});
|
|
1457
1771
|
const response = {
|
|
1458
1772
|
meshId: mesh.id,
|
|
1459
1773
|
meshName: mesh.name,
|
|
1460
1774
|
repoIdentity: mesh.repoIdentity,
|
|
1461
1775
|
policy: mesh.policy,
|
|
1462
1776
|
refreshedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1777
|
+
sourceOfTruth: {
|
|
1778
|
+
membership: "coordinator_daemon_live_mesh",
|
|
1779
|
+
currentStatus: "live_git_and_session_probes",
|
|
1780
|
+
activeWork: "mesh_queue_file_and_local_ledger",
|
|
1781
|
+
historicalEvidenceOnly: ["recoveryHints", "ledgerSummary"]
|
|
1782
|
+
},
|
|
1463
1783
|
nodes: results,
|
|
1784
|
+
activeWork: activeWorkEvidence.activeWork,
|
|
1785
|
+
activeWorkSummary: activeWorkEvidence.summary,
|
|
1464
1786
|
branchConvergenceSummary: summarizeBranchConvergence(results)
|
|
1465
1787
|
};
|
|
1466
1788
|
try {
|
|
@@ -1468,13 +1790,7 @@ async function meshStatus(ctx) {
|
|
|
1468
1790
|
} catch {
|
|
1469
1791
|
}
|
|
1470
1792
|
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
|
-
}
|
|
1793
|
+
const pendingEvents = await drainCoordinatorPendingEvents(ctx);
|
|
1478
1794
|
if (pendingEvents.length > 0) {
|
|
1479
1795
|
response.pendingCoordinatorEvents = pendingEvents;
|
|
1480
1796
|
}
|
|
@@ -1484,6 +1800,7 @@ async function meshStatus(ctx) {
|
|
|
1484
1800
|
}
|
|
1485
1801
|
async function meshTaskHistory(ctx, args) {
|
|
1486
1802
|
const { mesh } = ctx;
|
|
1803
|
+
await drainCoordinatorPendingEvents(ctx);
|
|
1487
1804
|
const tail = typeof args.tail === "number" && args.tail > 0 ? args.tail : 20;
|
|
1488
1805
|
const kind = typeof args.kind === "string" && args.kind.trim() ? [args.kind.trim()] : void 0;
|
|
1489
1806
|
const entries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail, kind });
|
|
@@ -1587,12 +1904,13 @@ async function meshListNodes(ctx) {
|
|
|
1587
1904
|
}, null, 2);
|
|
1588
1905
|
}
|
|
1589
1906
|
async function meshEnqueueTask(ctx, args) {
|
|
1907
|
+
const taskMode = readString(args.task_mode) || readString(args.taskMode);
|
|
1590
1908
|
try {
|
|
1591
|
-
const task = (0, import_daemon_core.enqueueTask)(ctx.mesh.id, args.message);
|
|
1909
|
+
const task = (0, import_daemon_core.enqueueTask)(ctx.mesh.id, args.message, { taskMode });
|
|
1592
1910
|
if (isLocalTransport(ctx.transport) && !(ctx.transport instanceof IpcTransport)) {
|
|
1593
1911
|
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
1594
1912
|
});
|
|
1595
|
-
return JSON.stringify({ success: true, taskId: task.id, status: task.status });
|
|
1913
|
+
return JSON.stringify({ success: true, source: "queue", taskId: task.id, status: task.status, taskMode: task.taskMode });
|
|
1596
1914
|
}
|
|
1597
1915
|
if (ctx.transport instanceof IpcTransport) {
|
|
1598
1916
|
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
@@ -1605,11 +1923,24 @@ async function meshEnqueueTask(ctx, args) {
|
|
|
1605
1923
|
ipcDispatchToRemoteAgent(ctx, node, { message: args.message }).then((result) => {
|
|
1606
1924
|
if (result.success) {
|
|
1607
1925
|
try {
|
|
1926
|
+
const providerType = result.providerType;
|
|
1927
|
+
const descriptor = summarizeTaskMessage(args.message);
|
|
1608
1928
|
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
1609
1929
|
kind: "task_dispatched",
|
|
1610
1930
|
nodeId: node.id,
|
|
1611
1931
|
sessionId: result.sessionId,
|
|
1612
|
-
|
|
1932
|
+
providerType,
|
|
1933
|
+
payload: {
|
|
1934
|
+
source: "queue",
|
|
1935
|
+
via: "p2p_direct",
|
|
1936
|
+
taskId: task.id,
|
|
1937
|
+
message: args.message,
|
|
1938
|
+
taskTitle: descriptor.taskTitle,
|
|
1939
|
+
taskSummary: descriptor.taskSummary,
|
|
1940
|
+
...task.taskMode ? { taskMode: task.taskMode } : {},
|
|
1941
|
+
...providerType ? { providerType } : {},
|
|
1942
|
+
targetSessionId: result.sessionId
|
|
1943
|
+
}
|
|
1613
1944
|
});
|
|
1614
1945
|
} catch {
|
|
1615
1946
|
}
|
|
@@ -1620,11 +1951,15 @@ async function meshEnqueueTask(ctx, args) {
|
|
|
1620
1951
|
}
|
|
1621
1952
|
Promise.all(dispatchPromises).catch(() => {
|
|
1622
1953
|
});
|
|
1623
|
-
return JSON.stringify({ success: true, taskId: task.id, status: task.status });
|
|
1954
|
+
return JSON.stringify({ success: true, source: "queue", taskId: task.id, status: task.status, taskMode: task.taskMode });
|
|
1624
1955
|
}
|
|
1625
|
-
return JSON.stringify({ success: true, taskId: task.id, status: task.status });
|
|
1956
|
+
return JSON.stringify({ success: true, source: "queue", taskId: task.id, status: task.status, taskMode: task.taskMode });
|
|
1626
1957
|
} catch (e) {
|
|
1627
|
-
|
|
1958
|
+
const message = e?.message || String(e);
|
|
1959
|
+
if (message.includes("live_debug_readonly_guardrail_violation")) {
|
|
1960
|
+
return JSON.stringify({ success: false, code: "live_debug_readonly_guardrail_violation", taskMode, error: message });
|
|
1961
|
+
}
|
|
1962
|
+
return JSON.stringify({ success: false, error: message });
|
|
1628
1963
|
}
|
|
1629
1964
|
}
|
|
1630
1965
|
async function meshViewQueue(ctx, args) {
|
|
@@ -1636,6 +1971,12 @@ async function meshViewQueue(ctx, args) {
|
|
|
1636
1971
|
const summary = buildQueueStatusSummary(fullQueue);
|
|
1637
1972
|
const visibleSummary = buildQueueStatusSummary(queue);
|
|
1638
1973
|
const maintenance = buildQueueMaintenanceReport(fullQueue);
|
|
1974
|
+
const activeWorkEvidence = (0, import_daemon_core.buildMeshActiveWork)({
|
|
1975
|
+
meshId: ctx.mesh.id,
|
|
1976
|
+
queue: fullQueue,
|
|
1977
|
+
ledgerEntries: (0, import_daemon_core.readLedgerEntries)(ctx.mesh.id, { tail: 500 }),
|
|
1978
|
+
nodes: ctx.mesh.nodes
|
|
1979
|
+
});
|
|
1639
1980
|
const staleAssignedTasks = maintenance.staleAssignedTasks || [];
|
|
1640
1981
|
const requestedHistoricalRows = queue.some((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
|
|
1641
1982
|
return JSON.stringify({
|
|
@@ -1653,6 +1994,8 @@ async function meshViewQueue(ctx, args) {
|
|
|
1653
1994
|
},
|
|
1654
1995
|
queue,
|
|
1655
1996
|
visibleQueue: queue,
|
|
1997
|
+
activeWork: activeWorkEvidence.activeWork,
|
|
1998
|
+
activeWorkSummary: activeWorkEvidence.summary,
|
|
1656
1999
|
visibleSummary,
|
|
1657
2000
|
summary,
|
|
1658
2001
|
activeCounts: summary.activeCounts,
|
|
@@ -1716,6 +2059,19 @@ async function meshQueueRequeue(ctx, args) {
|
|
|
1716
2059
|
}
|
|
1717
2060
|
}
|
|
1718
2061
|
async function meshSendTask(ctx, args) {
|
|
2062
|
+
const requestedTaskMode = readString(args.task_mode) || readString(args.taskMode);
|
|
2063
|
+
const modeValidation = (0, import_daemon_core.validateMeshTaskModeRequest)(requestedTaskMode, args.message);
|
|
2064
|
+
if (!modeValidation.valid) {
|
|
2065
|
+
return JSON.stringify({
|
|
2066
|
+
success: false,
|
|
2067
|
+
code: "live_debug_readonly_guardrail_violation",
|
|
2068
|
+
taskMode: modeValidation.taskMode || requestedTaskMode,
|
|
2069
|
+
violations: modeValidation.violations,
|
|
2070
|
+
allowedOperations: modeValidation.allowedOperations,
|
|
2071
|
+
error: `live_debug_readonly_guardrail_violation: forbidden operations (${modeValidation.violations.join(", ")})`
|
|
2072
|
+
});
|
|
2073
|
+
}
|
|
2074
|
+
const taskMode = modeValidation.taskMode;
|
|
1719
2075
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
1720
2076
|
if (node.policy?.readOnly) {
|
|
1721
2077
|
return JSON.stringify({ error: `Node '${args.node_id}' is read-only` });
|
|
@@ -1743,13 +2099,15 @@ async function meshSendTask(ctx, args) {
|
|
|
1743
2099
|
const res = await ctx.transport.meshEnqueueTask(node.daemonId, {
|
|
1744
2100
|
meshId: ctx.mesh.id,
|
|
1745
2101
|
message: args.message,
|
|
1746
|
-
targetNodeId: args.node_id
|
|
2102
|
+
targetNodeId: args.node_id,
|
|
2103
|
+
...taskMode ? { taskMode } : {}
|
|
1747
2104
|
});
|
|
1748
2105
|
return JSON.stringify(res);
|
|
1749
2106
|
}
|
|
1750
2107
|
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
1751
2108
|
if (ctx.transport instanceof IpcTransport && node.daemonId && !isLocalNode) {
|
|
1752
2109
|
const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id || ""));
|
|
2110
|
+
const taskId = (0, import_node_crypto.randomUUID)();
|
|
1753
2111
|
const result2 = await ipcDispatchToRemoteAgent(ctx, node, {
|
|
1754
2112
|
session_id: args.session_id,
|
|
1755
2113
|
message: args.message,
|
|
@@ -1758,26 +2116,80 @@ async function meshSendTask(ctx, args) {
|
|
|
1758
2116
|
if (result2.success) {
|
|
1759
2117
|
const dispatchedSessionId = args.session_id || result2.sessionId;
|
|
1760
2118
|
try {
|
|
2119
|
+
const providerType = result2.providerType || cached?.providerType;
|
|
1761
2120
|
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
1762
2121
|
kind: "task_dispatched",
|
|
1763
2122
|
nodeId: args.node_id,
|
|
1764
2123
|
sessionId: dispatchedSessionId,
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
2124
|
+
providerType,
|
|
2125
|
+
payload: buildDirectTaskPayload(args.message, "p2p_direct", {
|
|
2126
|
+
taskId,
|
|
2127
|
+
taskMode,
|
|
2128
|
+
providerType,
|
|
2129
|
+
targetSessionId: dispatchedSessionId
|
|
2130
|
+
})
|
|
1770
2131
|
});
|
|
1771
2132
|
} catch {
|
|
1772
2133
|
}
|
|
1773
2134
|
}
|
|
1774
|
-
return JSON.stringify({
|
|
2135
|
+
return JSON.stringify({
|
|
2136
|
+
...result2,
|
|
2137
|
+
nodeId: args.node_id,
|
|
2138
|
+
sessionId: result2.success ? args.session_id || result2.sessionId : args.session_id,
|
|
2139
|
+
...result2.success ? { source: "direct", taskId } : {},
|
|
2140
|
+
taskMode,
|
|
2141
|
+
...result2.success && result2.providerType ? { providerType: result2.providerType } : {},
|
|
2142
|
+
dispatched: result2.success === true
|
|
2143
|
+
});
|
|
1775
2144
|
}
|
|
1776
2145
|
if (args.session_id && isLocalTransport(ctx.transport)) {
|
|
1777
2146
|
const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id));
|
|
2147
|
+
let resolvedProviderType = cached?.providerType || "";
|
|
2148
|
+
if (!resolvedProviderType) {
|
|
2149
|
+
const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
|
|
2150
|
+
const sessions = extractStatusMetadataSessions(statusResult);
|
|
2151
|
+
const explicitSession = sessions.find((session) => readSessionRecordId(session) === args.session_id);
|
|
2152
|
+
if (!explicitSession) {
|
|
2153
|
+
return JSON.stringify({
|
|
2154
|
+
success: false,
|
|
2155
|
+
recoverable: true,
|
|
2156
|
+
code: "mesh_target_session_not_found",
|
|
2157
|
+
reason: "mesh_target_session_not_found",
|
|
2158
|
+
transport: "local_ipc",
|
|
2159
|
+
retryRecommended: true,
|
|
2160
|
+
nodeId: args.node_id,
|
|
2161
|
+
sessionId: args.session_id,
|
|
2162
|
+
error: `Local session '${args.session_id}' is not present in live status for node '${args.node_id}'.`,
|
|
2163
|
+
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.`
|
|
2164
|
+
});
|
|
2165
|
+
}
|
|
2166
|
+
resolvedProviderType = resolveSessionProviderType(explicitSession);
|
|
2167
|
+
if (resolvedProviderType) {
|
|
2168
|
+
meshSessionProviderMetadata.set(meshSessionCacheKey(args.node_id, args.session_id), {
|
|
2169
|
+
providerType: resolvedProviderType,
|
|
2170
|
+
providerSessionId: readString(explicitSession?.providerSessionId) || void 0
|
|
2171
|
+
});
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
if (!resolvedProviderType) {
|
|
2175
|
+
return JSON.stringify({
|
|
2176
|
+
success: false,
|
|
2177
|
+
recoverable: true,
|
|
2178
|
+
code: "mesh_target_session_provider_unknown",
|
|
2179
|
+
reason: "mesh_target_session_provider_unknown",
|
|
2180
|
+
transport: "local_ipc",
|
|
2181
|
+
retryRecommended: false,
|
|
2182
|
+
nodeId: args.node_id,
|
|
2183
|
+
sessionId: args.session_id,
|
|
2184
|
+
error: `Local session '${args.session_id}' is live but does not expose providerType/cliType, so agent_command cannot be routed safely.`,
|
|
2185
|
+
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.`
|
|
2186
|
+
});
|
|
2187
|
+
}
|
|
1778
2188
|
const dispatchResult = await commandForNode(ctx, node, "agent_command", {
|
|
1779
2189
|
targetSessionId: args.session_id,
|
|
1780
|
-
|
|
2190
|
+
agentType: resolvedProviderType,
|
|
2191
|
+
cliType: resolvedProviderType,
|
|
2192
|
+
providerType: resolvedProviderType,
|
|
1781
2193
|
action: "send_chat",
|
|
1782
2194
|
message: args.message
|
|
1783
2195
|
});
|
|
@@ -1790,28 +2202,35 @@ async function meshSendTask(ctx, args) {
|
|
|
1790
2202
|
error: dispatchPayload?.error || dispatchResult?.error || "agent_command rejected the task"
|
|
1791
2203
|
});
|
|
1792
2204
|
}
|
|
2205
|
+
const taskId = (0, import_node_crypto.randomUUID)();
|
|
1793
2206
|
try {
|
|
1794
2207
|
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
1795
2208
|
kind: "task_dispatched",
|
|
1796
2209
|
nodeId: args.node_id,
|
|
1797
2210
|
sessionId: args.session_id,
|
|
1798
|
-
providerType:
|
|
1799
|
-
payload:
|
|
2211
|
+
providerType: resolvedProviderType,
|
|
2212
|
+
payload: buildDirectTaskPayload(args.message, "local_direct", {
|
|
2213
|
+
taskId,
|
|
2214
|
+
taskMode,
|
|
2215
|
+
providerType: resolvedProviderType,
|
|
2216
|
+
targetSessionId: args.session_id
|
|
2217
|
+
})
|
|
1800
2218
|
});
|
|
1801
2219
|
} catch {
|
|
1802
2220
|
}
|
|
1803
|
-
return JSON.stringify({ success: true, dispatched: true, nodeId: args.node_id, sessionId: args.session_id });
|
|
2221
|
+
return JSON.stringify({ success: true, dispatched: true, source: "direct", taskId, taskMode, providerType: resolvedProviderType, nodeId: args.node_id, sessionId: args.session_id });
|
|
1804
2222
|
}
|
|
1805
2223
|
const task = (0, import_daemon_core.enqueueTask)(ctx.mesh.id, args.message, {
|
|
1806
2224
|
targetNodeId: args.node_id,
|
|
1807
|
-
targetSessionId: args.session_id
|
|
2225
|
+
targetSessionId: args.session_id,
|
|
2226
|
+
taskMode
|
|
1808
2227
|
});
|
|
1809
2228
|
if (isLocalTransport(ctx.transport) || ctx.transport instanceof IpcTransport) {
|
|
1810
2229
|
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
1811
2230
|
});
|
|
1812
2231
|
}
|
|
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 };
|
|
2232
|
+
const pendingEvents = isLocalTransport(ctx.transport) ? (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id) : [];
|
|
2233
|
+
const result = { success: true, source: "queue", nodeId: args.node_id, taskId: task.id, status: task.status, taskMode: task.taskMode };
|
|
1815
2234
|
if (pendingEvents.length > 0) {
|
|
1816
2235
|
result.pendingCoordinatorEvents = pendingEvents;
|
|
1817
2236
|
}
|
|
@@ -1831,6 +2250,9 @@ async function meshReadChat(ctx, args) {
|
|
|
1831
2250
|
if (!node) {
|
|
1832
2251
|
return JSON.stringify(buildMissingNodeReadChatRecovery(ctx, args), null, 2);
|
|
1833
2252
|
}
|
|
2253
|
+
if (ctx.transport instanceof IpcTransport || isLocalTransport(ctx.transport)) {
|
|
2254
|
+
await drainCoordinatorPendingEvents(ctx, { nodeIds: [args.node_id] });
|
|
2255
|
+
}
|
|
1834
2256
|
if (isLocalTransport(ctx.transport)) {
|
|
1835
2257
|
const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id));
|
|
1836
2258
|
const providerSessionId = typeof args.provider_session_id === "string" && args.provider_session_id.trim() ? args.provider_session_id.trim() : cached?.providerSessionId;
|
|
@@ -1933,6 +2355,10 @@ async function meshLaunchSession(ctx, args) {
|
|
|
1933
2355
|
const coordinatorNode = resolveCoordinatorNode(ctx);
|
|
1934
2356
|
const coordinatorDaemonId = coordinatorNode?.daemonId || ctx.localDaemonId;
|
|
1935
2357
|
const spawnedSessionVisibility = readSpawnedSessionVisibility(ctx.mesh.policy);
|
|
2358
|
+
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
2359
|
+
if (node.daemonId && !isLocalNode && !coordinatorDaemonId) {
|
|
2360
|
+
return JSON.stringify(buildMissingCoordinatorDaemonIdFailure(ctx, node, resolvedProviderType), null, 2);
|
|
2361
|
+
}
|
|
1936
2362
|
let result;
|
|
1937
2363
|
try {
|
|
1938
2364
|
result = await commandForNode(ctx, node, "launch_cli", {
|
|
@@ -1973,7 +2399,6 @@ async function meshLaunchSession(ctx, args) {
|
|
|
1973
2399
|
});
|
|
1974
2400
|
} catch {
|
|
1975
2401
|
}
|
|
1976
|
-
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
1977
2402
|
if (ctx.transport instanceof IpcTransport && node.daemonId && !isLocalNode) {
|
|
1978
2403
|
ctx.transport.meshCommand(node.daemonId, "trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
1979
2404
|
});
|
|
@@ -1998,6 +2423,9 @@ async function meshLaunchSession(ctx, args) {
|
|
|
1998
2423
|
const coordinatorNode = resolveCoordinatorNode(ctx);
|
|
1999
2424
|
const coordinatorDaemonId = coordinatorNode?.daemonId || ctx.localDaemonId;
|
|
2000
2425
|
const spawnedSessionVisibility = readSpawnedSessionVisibility(ctx.mesh.policy);
|
|
2426
|
+
if (!coordinatorDaemonId) {
|
|
2427
|
+
return JSON.stringify(buildMissingCoordinatorDaemonIdFailure(ctx, node, resolvedProviderType), null, 2);
|
|
2428
|
+
}
|
|
2001
2429
|
try {
|
|
2002
2430
|
const res = await ctx.transport.launch(node.daemonId, {
|
|
2003
2431
|
type: resolvedProviderType,
|
|
@@ -2036,7 +2464,7 @@ async function meshGitStatus(ctx, args) {
|
|
|
2036
2464
|
const submoduleIgnorePaths = node.policy?.submoduleIgnorePaths || [];
|
|
2037
2465
|
try {
|
|
2038
2466
|
if (!isLocalTransport(ctx.transport) && node.daemonId) {
|
|
2039
|
-
const result = await ctx.transport.gitStatus(node.daemonId, node.workspace, true);
|
|
2467
|
+
const result = await ctx.transport.gitStatus(node.daemonId, node.workspace, true, true);
|
|
2040
2468
|
return JSON.stringify({
|
|
2041
2469
|
nodeId: args.node_id,
|
|
2042
2470
|
workspace: node.workspace,
|
|
@@ -2048,6 +2476,7 @@ async function meshGitStatus(ctx, args) {
|
|
|
2048
2476
|
} else if (isLocalTransport(ctx.transport)) {
|
|
2049
2477
|
const statusResult = await commandForNode(ctx, node, "git_status", {
|
|
2050
2478
|
workspace: node.workspace,
|
|
2479
|
+
refreshUpstream: true,
|
|
2051
2480
|
includeSubmodules: autoDiscoverSubmodules,
|
|
2052
2481
|
submoduleIgnorePaths: submoduleIgnorePaths.length > 0 ? submoduleIgnorePaths : void 0
|
|
2053
2482
|
});
|
|
@@ -2077,6 +2506,51 @@ async function meshGitStatus(ctx, args) {
|
|
|
2077
2506
|
}, null, 2);
|
|
2078
2507
|
}
|
|
2079
2508
|
}
|
|
2509
|
+
async function meshFastForwardNode(ctx, args) {
|
|
2510
|
+
await refreshMeshFromDaemon(ctx);
|
|
2511
|
+
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2512
|
+
const submoduleIgnorePaths = node.policy?.submoduleIgnorePaths || [];
|
|
2513
|
+
if (node.policy?.readOnly) {
|
|
2514
|
+
return JSON.stringify({
|
|
2515
|
+
success: false,
|
|
2516
|
+
code: "node_read_only",
|
|
2517
|
+
nodeId: args.node_id,
|
|
2518
|
+
workspace: node.workspace,
|
|
2519
|
+
allowed: false,
|
|
2520
|
+
willRun: false,
|
|
2521
|
+
executed: false,
|
|
2522
|
+
blockingReasons: ["node_read_only"]
|
|
2523
|
+
}, null, 2);
|
|
2524
|
+
}
|
|
2525
|
+
try {
|
|
2526
|
+
const dryRun = args.dry_run === true || args.execute !== true;
|
|
2527
|
+
const result = await commandForNode(ctx, node, "fast_forward_mesh_node", {
|
|
2528
|
+
meshId: ctx.mesh.id,
|
|
2529
|
+
nodeId: node.id,
|
|
2530
|
+
workspace: node.workspace,
|
|
2531
|
+
branch: typeof args.branch === "string" ? args.branch : void 0,
|
|
2532
|
+
execute: args.execute === true && args.dry_run !== true,
|
|
2533
|
+
dryRun,
|
|
2534
|
+
updateSubmodules: args.update_submodules === true,
|
|
2535
|
+
submoduleIgnorePaths: submoduleIgnorePaths.length > 0 ? submoduleIgnorePaths : void 0
|
|
2536
|
+
});
|
|
2537
|
+
return JSON.stringify(unwrapCommandPayload(result), null, 2);
|
|
2538
|
+
} catch (e) {
|
|
2539
|
+
const failure = buildCoordinatorP2pRelayFailure(e, {
|
|
2540
|
+
command: "fast_forward_mesh_node",
|
|
2541
|
+
targetDaemonId: node.daemonId,
|
|
2542
|
+
nodeId: args.node_id
|
|
2543
|
+
});
|
|
2544
|
+
return JSON.stringify({
|
|
2545
|
+
...failure,
|
|
2546
|
+
workspace: node.workspace,
|
|
2547
|
+
allowed: false,
|
|
2548
|
+
willRun: false,
|
|
2549
|
+
executed: false,
|
|
2550
|
+
blockingReasons: [failure.code || "mesh_fast_forward_unavailable"]
|
|
2551
|
+
}, null, 2);
|
|
2552
|
+
}
|
|
2553
|
+
}
|
|
2080
2554
|
async function meshCheckpoint(ctx, args) {
|
|
2081
2555
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2082
2556
|
if (node.policy?.readOnly) {
|
|
@@ -2162,6 +2636,7 @@ async function meshCloneNode(ctx, args) {
|
|
|
2162
2636
|
if (existingIndex >= 0) ctx.mesh.nodes[existingIndex] = clonePayload.node;
|
|
2163
2637
|
else ctx.mesh.nodes.push(clonePayload.node);
|
|
2164
2638
|
ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2639
|
+
await syncCoordinatorDaemonMeshCache(ctx);
|
|
2165
2640
|
}
|
|
2166
2641
|
return JSON.stringify(result, null, 2);
|
|
2167
2642
|
} else if (!isLocalTransport(ctx.transport) && sourceNode.daemonId) {
|
|
@@ -2179,6 +2654,7 @@ async function meshCloneNode(ctx, args) {
|
|
|
2179
2654
|
if (existingIndex >= 0) ctx.mesh.nodes[existingIndex] = clonePayload.node;
|
|
2180
2655
|
else ctx.mesh.nodes.push(clonePayload.node);
|
|
2181
2656
|
ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2657
|
+
await syncCoordinatorDaemonMeshCache(ctx);
|
|
2182
2658
|
}
|
|
2183
2659
|
return JSON.stringify(res, null, 2);
|
|
2184
2660
|
} catch (e) {
|
|
@@ -2274,6 +2750,43 @@ async function meshRemoveNode(ctx, args) {
|
|
|
2274
2750
|
return JSON.stringify({ error: "Cloud mesh remove_node requires node daemonId" });
|
|
2275
2751
|
}
|
|
2276
2752
|
}
|
|
2753
|
+
function resolveRefineConfigNode(ctx, nodeId) {
|
|
2754
|
+
if (nodeId) return findNode(ctx.mesh, nodeId);
|
|
2755
|
+
const node = ctx.mesh.nodes.find((entry) => !!entry.workspace);
|
|
2756
|
+
if (!node) throw new Error("No mesh node with a workspace is available");
|
|
2757
|
+
return node;
|
|
2758
|
+
}
|
|
2759
|
+
async function meshRefineConfigSchema(ctx) {
|
|
2760
|
+
const node = resolveRefineConfigNode(ctx);
|
|
2761
|
+
const result = await commandForNode(ctx, node, "get_mesh_refine_config_schema", {});
|
|
2762
|
+
return JSON.stringify(result, null, 2);
|
|
2763
|
+
}
|
|
2764
|
+
async function meshValidateRefineConfig(ctx, args) {
|
|
2765
|
+
const node = resolveRefineConfigNode(ctx, args.node_id);
|
|
2766
|
+
const result = await commandForNode(ctx, node, "validate_mesh_refine_config", {
|
|
2767
|
+
workspace: node.workspace,
|
|
2768
|
+
inlineMesh: ctx.mesh,
|
|
2769
|
+
...args.config ? { config: args.config } : {}
|
|
2770
|
+
});
|
|
2771
|
+
return JSON.stringify(result, null, 2);
|
|
2772
|
+
}
|
|
2773
|
+
async function meshSuggestRefineConfig(ctx, args) {
|
|
2774
|
+
const node = resolveRefineConfigNode(ctx, args.node_id);
|
|
2775
|
+
const result = await commandForNode(ctx, node, "suggest_mesh_refine_config", {
|
|
2776
|
+
workspace: node.workspace,
|
|
2777
|
+
inlineMesh: ctx.mesh
|
|
2778
|
+
});
|
|
2779
|
+
return JSON.stringify(result, null, 2);
|
|
2780
|
+
}
|
|
2781
|
+
async function meshRefinePlan(ctx, args) {
|
|
2782
|
+
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2783
|
+
const result = await commandForNode(ctx, node, "plan_mesh_refine_node", {
|
|
2784
|
+
meshId: ctx.mesh.id,
|
|
2785
|
+
nodeId: args.node_id,
|
|
2786
|
+
inlineMesh: ctx.mesh
|
|
2787
|
+
});
|
|
2788
|
+
return JSON.stringify(result, null, 2);
|
|
2789
|
+
}
|
|
2277
2790
|
async function meshRefineNode(ctx, args) {
|
|
2278
2791
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2279
2792
|
if (isLocalTransport(ctx.transport)) {
|
|
@@ -2519,8 +3032,8 @@ var CloudTransport = class {
|
|
|
2519
3032
|
if (!res.ok) throw new Error(`Approve failed: ${res.status}`);
|
|
2520
3033
|
return res.json();
|
|
2521
3034
|
}
|
|
2522
|
-
async gitStatus(daemonId, workspace, includeDiff = true) {
|
|
2523
|
-
const params = new URLSearchParams({ workspace, includeDiff: String(includeDiff) });
|
|
3035
|
+
async gitStatus(daemonId, workspace, includeDiff = true, refreshUpstream = false) {
|
|
3036
|
+
const params = new URLSearchParams({ workspace, includeDiff: String(includeDiff), refreshUpstream: String(refreshUpstream) });
|
|
2524
3037
|
const res = await fetch(
|
|
2525
3038
|
`${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/git-status?${params}`,
|
|
2526
3039
|
{ headers: this.headers() }
|
|
@@ -2915,6 +3428,22 @@ function formatChatResult(result, sessionId, format, limit = 50, compact = false
|
|
|
2915
3428
|
}))
|
|
2916
3429
|
}, null, 2);
|
|
2917
3430
|
}
|
|
3431
|
+
if ((format === "text" || format === void 0) && compact && compactPayload) {
|
|
3432
|
+
const lines2 = outputMessages.slice(-limit).map((m) => {
|
|
3433
|
+
const role = m.role === "user" ? "User" : m.role === "assistant" ? "Agent" : m.role;
|
|
3434
|
+
const content = messageContent(m);
|
|
3435
|
+
const truncated = content.length > 500 ? `${content.slice(0, 500)}\u2026` : content;
|
|
3436
|
+
return `[${role}] ${truncated}`;
|
|
3437
|
+
});
|
|
3438
|
+
if (compactPayload.summary) {
|
|
3439
|
+
const truncatedSummary = compactPayload.summary.length > 500 ? `${compactPayload.summary.slice(0, 500)}\u2026` : compactPayload.summary;
|
|
3440
|
+
lines2.push(`[Summary] ${truncatedSummary}`);
|
|
3441
|
+
}
|
|
3442
|
+
if (result?.pollingAdvisory) {
|
|
3443
|
+
lines2.push(`Advisory: ${result.pollingAdvisory.message}`);
|
|
3444
|
+
}
|
|
3445
|
+
return lines2.length > 0 ? lines2.join("\n\n") : "No messages in chat.";
|
|
3446
|
+
}
|
|
2918
3447
|
if (outputMessages.length === 0) {
|
|
2919
3448
|
return result?.pollingAdvisory ? `No messages in chat.
|
|
2920
3449
|
|
|
@@ -4015,6 +4544,9 @@ async function startMcpServer(opts) {
|
|
|
4015
4544
|
case "mesh_git_status":
|
|
4016
4545
|
text = await meshGitStatus(meshCtx, a);
|
|
4017
4546
|
break;
|
|
4547
|
+
case "mesh_fast_forward_node":
|
|
4548
|
+
text = await meshFastForwardNode(meshCtx, a);
|
|
4549
|
+
break;
|
|
4018
4550
|
case "mesh_checkpoint":
|
|
4019
4551
|
text = await meshCheckpoint(meshCtx, a);
|
|
4020
4552
|
break;
|
|
@@ -4030,6 +4562,18 @@ async function startMcpServer(opts) {
|
|
|
4030
4562
|
case "mesh_refine_node":
|
|
4031
4563
|
text = await meshRefineNode(meshCtx, a);
|
|
4032
4564
|
break;
|
|
4565
|
+
case "mesh_refine_config_schema":
|
|
4566
|
+
text = await meshRefineConfigSchema(meshCtx);
|
|
4567
|
+
break;
|
|
4568
|
+
case "mesh_validate_refine_config":
|
|
4569
|
+
text = await meshValidateRefineConfig(meshCtx, a);
|
|
4570
|
+
break;
|
|
4571
|
+
case "mesh_suggest_refine_config":
|
|
4572
|
+
text = await meshSuggestRefineConfig(meshCtx, a);
|
|
4573
|
+
break;
|
|
4574
|
+
case "mesh_refine_plan":
|
|
4575
|
+
text = await meshRefinePlan(meshCtx, a);
|
|
4576
|
+
break;
|
|
4033
4577
|
case "mesh_cleanup_sessions":
|
|
4034
4578
|
text = await meshCleanupSessions(meshCtx, a);
|
|
4035
4579
|
break;
|