@adhdev/daemon-standalone 0.9.82-rc.5 → 0.9.82-rc.51
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 +22461 -20426
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/public/assets/index-s07lLC64.js +98 -0
- package/public/assets/index-sfey5a4c.css +1 -0
- package/public/assets/{terminal-DCVDkSIp.js → terminal-D46M5EWH.js} +4 -4
- package/public/assets/{vendor-piMzuE8Y.js → vendor-CgiI0UIA.js} +29 -29
- package/public/index.html +3 -3
- package/vendor/mcp-server/index.js +425 -39
- 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
|
@@ -143,6 +143,10 @@ function isLocalTransport(transport) {
|
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
// src/tools/chat-compact.ts
|
|
146
|
+
function isAssistantLike(message) {
|
|
147
|
+
const role = String(message?.role ?? "").toLowerCase();
|
|
148
|
+
return role === "assistant" || role === "agent";
|
|
149
|
+
}
|
|
146
150
|
function messageContent(message) {
|
|
147
151
|
const content = message?.content;
|
|
148
152
|
if (typeof content === "string") return content;
|
|
@@ -161,16 +165,22 @@ function isCoordinatorVisibleMessage(message) {
|
|
|
161
165
|
if (meta?.internal === true || meta?.debug === true || meta?.control === true || meta?.userVisible === false || meta?.user_visible === false) return false;
|
|
162
166
|
return role === "user" || role === "assistant" || role === "agent";
|
|
163
167
|
}
|
|
168
|
+
function buildCompactMessageTail(visibleMessages, opts) {
|
|
169
|
+
const summary = typeof opts.summary === "string" ? opts.summary.trim() : "";
|
|
170
|
+
const shouldOmitSummaryMessage = !!summary && !!opts.finalAssistant && isAssistantLike(opts.finalAssistant) && messageContent(opts.finalAssistant).trim() === summary;
|
|
171
|
+
const sourceMessages = shouldOmitSummaryMessage ? visibleMessages.filter((message) => message !== opts.finalAssistant) : visibleMessages;
|
|
172
|
+
return sourceMessages.slice(-opts.limit);
|
|
173
|
+
}
|
|
164
174
|
function compactChatPayload(payload, opts = {}) {
|
|
165
175
|
const rawMessages = Array.isArray(payload?.messages) ? payload.messages : [];
|
|
166
176
|
const visible = rawMessages.filter(isCoordinatorVisibleMessage);
|
|
167
177
|
const limit = Math.max(1, Math.min(opts.limit ?? 10, 10));
|
|
168
|
-
const messages = visible.slice(-limit);
|
|
169
178
|
const finalAssistant = [...visible].reverse().find((message) => {
|
|
170
179
|
const role = String(message?.role ?? "").toLowerCase();
|
|
171
180
|
return (role === "assistant" || role === "agent") && messageContent(message).trim();
|
|
172
181
|
});
|
|
173
182
|
const summary = typeof payload?.summary === "string" && payload.summary.trim() ? payload.summary.trim() : messageContent(finalAssistant).trim();
|
|
183
|
+
const messages = buildCompactMessageTail(visible, { summary, finalAssistant, limit });
|
|
174
184
|
return {
|
|
175
185
|
success: payload?.success !== false,
|
|
176
186
|
compact: true,
|
|
@@ -235,6 +245,11 @@ var meshSessionProviderMetadata = /* @__PURE__ */ new Map();
|
|
|
235
245
|
function readString(value) {
|
|
236
246
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
237
247
|
}
|
|
248
|
+
function findNode(mesh, nodeId) {
|
|
249
|
+
const node = mesh.nodes.find((n) => n.id === nodeId);
|
|
250
|
+
if (!node) throw new Error(`Node '${nodeId}' is not a member of mesh '${mesh.name}'`);
|
|
251
|
+
return node;
|
|
252
|
+
}
|
|
238
253
|
var DUPLICATE_DISPATCH_WINDOW_MS = 6e4;
|
|
239
254
|
var STALE_ASSIGNED_QUEUE_MS = 30 * 6e4;
|
|
240
255
|
var OLD_HISTORICAL_QUEUE_RECORD_MS = 7 * 24 * 60 * 6e4;
|
|
@@ -246,15 +261,24 @@ async function refreshMeshFromDaemon(ctx) {
|
|
|
246
261
|
const result = await ctx.transport.command("get_mesh", { meshId: ctx.mesh.id });
|
|
247
262
|
if (!result?.success || !Array.isArray(result.mesh?.nodes)) return;
|
|
248
263
|
const refreshedNodes = result.mesh.nodes.filter((n) => n?.id).map((n) => n);
|
|
249
|
-
if (!refreshedNodes.length) return;
|
|
250
264
|
ctx.mesh.nodes.splice(0, ctx.mesh.nodes.length, ...refreshedNodes);
|
|
251
265
|
ctx.mesh.updatedAt = result.mesh.updatedAt ?? ctx.mesh.updatedAt;
|
|
252
266
|
} catch {
|
|
253
267
|
}
|
|
254
268
|
}
|
|
269
|
+
async function syncCoordinatorDaemonMeshCache(ctx) {
|
|
270
|
+
if (!(ctx.transport instanceof IpcTransport)) return;
|
|
271
|
+
try {
|
|
272
|
+
await ctx.transport.command("get_mesh", {
|
|
273
|
+
meshId: ctx.mesh.id,
|
|
274
|
+
inlineMesh: ctx.mesh
|
|
275
|
+
});
|
|
276
|
+
} catch {
|
|
277
|
+
}
|
|
278
|
+
}
|
|
255
279
|
async function findNodeWithRefresh(ctx, nodeId) {
|
|
256
280
|
const hit = ctx.mesh.nodes.find((n) => n.id === nodeId);
|
|
257
|
-
if (hit) return hit;
|
|
281
|
+
if (hit && !hit.isLocalWorktree) return hit;
|
|
258
282
|
await refreshMeshFromDaemon(ctx);
|
|
259
283
|
const refreshed = ctx.mesh.nodes.find((n) => n.id === nodeId);
|
|
260
284
|
if (!refreshed) throw new Error(`Node '${nodeId}' is not a member of mesh '${ctx.mesh.name}'`);
|
|
@@ -262,7 +286,7 @@ async function findNodeWithRefresh(ctx, nodeId) {
|
|
|
262
286
|
}
|
|
263
287
|
async function findOptionalNodeWithRefresh(ctx, nodeId) {
|
|
264
288
|
const hit = ctx.mesh.nodes.find((n) => n.id === nodeId);
|
|
265
|
-
if (hit) return hit;
|
|
289
|
+
if (hit && !hit.isLocalWorktree) return hit;
|
|
266
290
|
await refreshMeshFromDaemon(ctx);
|
|
267
291
|
return ctx.mesh.nodes.find((n) => n.id === nodeId) ?? null;
|
|
268
292
|
}
|
|
@@ -314,9 +338,26 @@ function buildMissingNodeReadChatRecovery(ctx, args) {
|
|
|
314
338
|
readDebugLocator: readString(lastTerminal?.payload?.readDebugLocator) || readString(lastTerminal?.payload?.debugBundlePath)
|
|
315
339
|
};
|
|
316
340
|
if (finalSummary) {
|
|
341
|
+
if (args.compact === true) {
|
|
342
|
+
return {
|
|
343
|
+
...compactChatPayload({
|
|
344
|
+
success: true,
|
|
345
|
+
status: "idle",
|
|
346
|
+
providerSessionId,
|
|
347
|
+
summary: finalSummary,
|
|
348
|
+
messages: [{ role: "assistant", content: finalSummary, isHistorical: true }]
|
|
349
|
+
}, {
|
|
350
|
+
nodeId: args.node_id,
|
|
351
|
+
sessionId: args.session_id,
|
|
352
|
+
limit: args.tail ?? 10
|
|
353
|
+
}),
|
|
354
|
+
recoveredFromLedger: true,
|
|
355
|
+
ledger
|
|
356
|
+
};
|
|
357
|
+
}
|
|
317
358
|
return {
|
|
318
359
|
success: true,
|
|
319
|
-
compact:
|
|
360
|
+
compact: false,
|
|
320
361
|
recoveredFromLedger: true,
|
|
321
362
|
nodeId: args.node_id,
|
|
322
363
|
sessionId: args.session_id,
|
|
@@ -368,6 +409,14 @@ function buildMissingNodeReadChatRecovery(ctx, args) {
|
|
|
368
409
|
function readSessionRecordId(session) {
|
|
369
410
|
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
411
|
}
|
|
412
|
+
function extractStatusMetadataSessions(value) {
|
|
413
|
+
const payload = unwrapCommandPayload(value);
|
|
414
|
+
const status = payload?.status && typeof payload.status === "object" ? payload.status : payload;
|
|
415
|
+
return Array.isArray(status?.sessions) ? status.sessions : [];
|
|
416
|
+
}
|
|
417
|
+
function resolveSessionProviderType(session) {
|
|
418
|
+
return readString(session?.providerType) || readString(session?.cliType) || readString(session?.agentType) || "";
|
|
419
|
+
}
|
|
371
420
|
function addSessionRecord(target, session) {
|
|
372
421
|
if (!session || typeof session !== "object" || isTerminalSessionRecord(session)) return;
|
|
373
422
|
const sessionId = readSessionRecordId(session);
|
|
@@ -580,22 +629,59 @@ function isIdleSessionRecord(session) {
|
|
|
580
629
|
const chatStatus = typeof session?.activeChat?.status === "string" ? session.activeChat.status.toLowerCase() : "";
|
|
581
630
|
return status === "idle" || chatStatus === "waiting_input";
|
|
582
631
|
}
|
|
632
|
+
function isMeshOwnedDelegateSession(session, meshId, nodeId) {
|
|
633
|
+
const settings = session?.settings;
|
|
634
|
+
const sessionMeshId = typeof settings?.meshNodeFor === "string" ? settings.meshNodeFor.trim() : "";
|
|
635
|
+
const coordinatorDaemonId = typeof settings?.meshCoordinatorDaemonId === "string" ? settings.meshCoordinatorDaemonId.trim() : "";
|
|
636
|
+
const sessionNodeId = typeof settings?.meshNodeId === "string" ? settings.meshNodeId.trim() : "";
|
|
637
|
+
if (sessionMeshId !== meshId || !coordinatorDaemonId) return false;
|
|
638
|
+
return !sessionNodeId || sessionNodeId === nodeId;
|
|
639
|
+
}
|
|
583
640
|
function chooseDispatchableSession(sessions, providerType, meshId, nodeId) {
|
|
584
641
|
const live = sessions.filter((session) => !isTerminalSessionRecord(session));
|
|
585
642
|
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
643
|
const meshSessions = live.filter(
|
|
595
|
-
(session) => isMeshOwnedDelegateSession(session)
|
|
644
|
+
(session) => isMeshOwnedDelegateSession(session, meshId, nodeId)
|
|
596
645
|
);
|
|
597
646
|
return meshSessions.find((session) => isIdleSessionRecord(session) && matchingProvider(session)) || meshSessions.find(matchingProvider) || void 0;
|
|
598
647
|
}
|
|
648
|
+
function buildRelayUnsafeRemoteSessionFailure(ctx, node, sessionId, providerType) {
|
|
649
|
+
return {
|
|
650
|
+
success: false,
|
|
651
|
+
recoverable: true,
|
|
652
|
+
code: "mesh_delegate_session_missing_relay_metadata",
|
|
653
|
+
reason: "mesh_delegate_session_missing_relay_metadata",
|
|
654
|
+
transport: "mesh_transport",
|
|
655
|
+
retryRecommended: true,
|
|
656
|
+
meshId: ctx.mesh.id,
|
|
657
|
+
nodeId: node.id,
|
|
658
|
+
daemonId: node.daemonId,
|
|
659
|
+
workspace: node.workspace,
|
|
660
|
+
sessionId,
|
|
661
|
+
...providerType ? { resolvedProviderType: providerType } : {},
|
|
662
|
+
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.`,
|
|
663
|
+
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.`,
|
|
664
|
+
noFallbackReason: "Blindly reusing a remote session without mesh relay metadata would silently drop task_completed / generating_completed events."
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
function buildMissingCoordinatorDaemonIdFailure(ctx, node, providerType) {
|
|
668
|
+
return {
|
|
669
|
+
success: false,
|
|
670
|
+
recoverable: true,
|
|
671
|
+
code: "mesh_coordinator_daemon_unknown",
|
|
672
|
+
reason: "mesh_coordinator_daemon_unknown",
|
|
673
|
+
transport: "mesh_transport",
|
|
674
|
+
retryRecommended: true,
|
|
675
|
+
meshId: ctx.mesh.id,
|
|
676
|
+
nodeId: node.id,
|
|
677
|
+
daemonId: node.daemonId,
|
|
678
|
+
workspace: node.workspace,
|
|
679
|
+
...providerType ? { resolvedProviderType: providerType } : {},
|
|
680
|
+
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.`,
|
|
681
|
+
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.",
|
|
682
|
+
noFallbackReason: "Launching without meshCoordinatorDaemonId would create a worker session that can finish work but cannot emit task_completed / generating_completed back to the coordinator."
|
|
683
|
+
};
|
|
684
|
+
}
|
|
599
685
|
function findNestedPayload(value, predicate) {
|
|
600
686
|
const seen = /* @__PURE__ */ new Set();
|
|
601
687
|
const stack = [{ payload: value, depth: 0 }];
|
|
@@ -623,12 +709,16 @@ function extractGitDiff(value) {
|
|
|
623
709
|
}
|
|
624
710
|
function extractSubmodules(value, ignorePaths) {
|
|
625
711
|
const payload = unwrapCommandPayload(value);
|
|
626
|
-
const subs = payload?.submodules ?? value?.submodules;
|
|
712
|
+
const subs = payload?.status?.submodules ?? payload?.submodules ?? value?.status?.submodules ?? value?.submodules;
|
|
627
713
|
if (!Array.isArray(subs)) return void 0;
|
|
628
714
|
if (ignorePaths.length === 0) return subs;
|
|
629
715
|
const ignoreSet = new Set(ignorePaths);
|
|
630
716
|
return subs.filter((s) => s?.path && !ignoreSet.has(s.path));
|
|
631
717
|
}
|
|
718
|
+
function assignFullGitSnapshot(entry, status) {
|
|
719
|
+
if (!status || typeof status !== "object" || Array.isArray(status)) return;
|
|
720
|
+
entry.git = status;
|
|
721
|
+
}
|
|
632
722
|
function extractLaunchPayload(value) {
|
|
633
723
|
return findNestedPayload(value, (payload) => Boolean(payload?.sessionId || payload?.id || payload?.runtimeSessionId));
|
|
634
724
|
}
|
|
@@ -753,20 +843,63 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
|
|
|
753
843
|
let sessionId = args.session_id?.trim() || "";
|
|
754
844
|
const providerPriorityList = Array.isArray(node.policy?.providerPriority) ? node.policy.providerPriority : [];
|
|
755
845
|
let resolvedProviderType = args.providerType?.trim() || providerPriorityList[0] || "";
|
|
756
|
-
if (!sessionId) {
|
|
846
|
+
if (!sessionId || args.session_id) {
|
|
757
847
|
try {
|
|
758
848
|
const relayResult = await transport.meshCommand(daemonId, "get_status_metadata", {});
|
|
759
|
-
const
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
849
|
+
const sessions = extractStatusMetadataSessions(relayResult);
|
|
850
|
+
if (sessionId) {
|
|
851
|
+
const explicitSession = sessions.find((session) => readSessionRecordId(session) === sessionId);
|
|
852
|
+
if (!explicitSession) {
|
|
853
|
+
return {
|
|
854
|
+
success: false,
|
|
855
|
+
recoverable: true,
|
|
856
|
+
code: "mesh_target_session_not_found",
|
|
857
|
+
reason: "mesh_target_session_not_found",
|
|
858
|
+
transport: "mesh_transport",
|
|
859
|
+
retryRecommended: true,
|
|
860
|
+
meshId: ctx.mesh.id,
|
|
861
|
+
nodeId: node.id,
|
|
862
|
+
daemonId,
|
|
863
|
+
workspace: node.workspace,
|
|
864
|
+
sessionId,
|
|
865
|
+
...resolvedProviderType ? { resolvedProviderType } : {},
|
|
866
|
+
error: `Remote session '${sessionId}' is not present in the live status for node '${node.id}'.`,
|
|
867
|
+
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.`
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
if (!isMeshOwnedDelegateSession(explicitSession, ctx.mesh.id, node.id)) {
|
|
871
|
+
return buildRelayUnsafeRemoteSessionFailure(
|
|
872
|
+
ctx,
|
|
873
|
+
node,
|
|
874
|
+
sessionId,
|
|
875
|
+
resolvedProviderType || resolveSessionProviderType(explicitSession) || void 0
|
|
876
|
+
);
|
|
877
|
+
}
|
|
765
878
|
if (!resolvedProviderType) {
|
|
766
|
-
resolvedProviderType =
|
|
879
|
+
resolvedProviderType = resolveSessionProviderType(explicitSession);
|
|
880
|
+
}
|
|
881
|
+
} else {
|
|
882
|
+
const targetSession = chooseDispatchableSession(sessions, resolvedProviderType, ctx.mesh.id, node.id);
|
|
883
|
+
if (targetSession?.id || targetSession?.sessionId) {
|
|
884
|
+
sessionId = targetSession.id || targetSession.sessionId;
|
|
885
|
+
if (!resolvedProviderType) {
|
|
886
|
+
resolvedProviderType = resolveSessionProviderType(targetSession);
|
|
887
|
+
}
|
|
767
888
|
}
|
|
768
889
|
}
|
|
769
890
|
} catch (e) {
|
|
891
|
+
if (sessionId) {
|
|
892
|
+
return {
|
|
893
|
+
...buildCoordinatorP2pRelayFailure(e, {
|
|
894
|
+
command: "get_status_metadata",
|
|
895
|
+
targetDaemonId: daemonId,
|
|
896
|
+
nodeId: node.id,
|
|
897
|
+
sessionId
|
|
898
|
+
}),
|
|
899
|
+
success: false,
|
|
900
|
+
error: `Cannot verify remote session '${sessionId}' before dispatch: ${e?.message || String(e)}`
|
|
901
|
+
};
|
|
902
|
+
}
|
|
770
903
|
}
|
|
771
904
|
}
|
|
772
905
|
if (!resolvedProviderType) {
|
|
@@ -880,6 +1013,10 @@ function summarizeRelatedRepoStatus(repo, status) {
|
|
|
880
1013
|
workspace: repo.workspace,
|
|
881
1014
|
isGitRepo: status?.isGitRepo === true,
|
|
882
1015
|
branch: status?.branch ?? null,
|
|
1016
|
+
upstream: status?.upstream ?? null,
|
|
1017
|
+
upstreamStatus: typeof status?.upstreamStatus === "string" ? status.upstreamStatus : status?.upstream ? "unchecked" : "no_upstream",
|
|
1018
|
+
upstreamFetchedAt: Number.isFinite(Number(status?.upstreamFetchedAt)) ? Number(status.upstreamFetchedAt) : null,
|
|
1019
|
+
upstreamFetchError: typeof status?.upstreamFetchError === "string" ? status.upstreamFetchError : null,
|
|
883
1020
|
ahead: Number.isFinite(Number(status?.ahead)) ? Number(status.ahead) : 0,
|
|
884
1021
|
behind: Number.isFinite(Number(status?.behind)) ? Number(status.behind) : 0,
|
|
885
1022
|
dirty,
|
|
@@ -896,7 +1033,7 @@ async function collectRelatedRepoStatuses(ctx, node) {
|
|
|
896
1033
|
const results = [];
|
|
897
1034
|
for (const repo of relatedRepos) {
|
|
898
1035
|
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 });
|
|
1036
|
+
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
1037
|
const status = extractGitStatus(statusResult);
|
|
901
1038
|
results.push(summarizeRelatedRepoStatus(repo, status));
|
|
902
1039
|
} catch (e) {
|
|
@@ -944,11 +1081,13 @@ function buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges) {
|
|
|
944
1081
|
const ahead = readNumeric(status?.ahead);
|
|
945
1082
|
const behind = readNumeric(status?.behind);
|
|
946
1083
|
const upstream = readString(status?.upstream) ?? null;
|
|
1084
|
+
const upstreamStatus = readString(status?.upstreamStatus) ?? (upstream ? "unchecked" : "no_upstream");
|
|
947
1085
|
const hasConflicts = status?.hasConflicts === true || Array.isArray(status?.conflictFiles) && status.conflictFiles.length > 0;
|
|
948
1086
|
const base = {
|
|
949
1087
|
defaultBranch,
|
|
950
1088
|
branch,
|
|
951
1089
|
upstream,
|
|
1090
|
+
upstreamStatus,
|
|
952
1091
|
ahead,
|
|
953
1092
|
behind,
|
|
954
1093
|
isWorktree: node.isLocalWorktree === true,
|
|
@@ -982,6 +1121,15 @@ function buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges) {
|
|
|
982
1121
|
};
|
|
983
1122
|
}
|
|
984
1123
|
if (branch === defaultBranch) {
|
|
1124
|
+
if (upstream && upstreamStatus !== "fresh") {
|
|
1125
|
+
return {
|
|
1126
|
+
...base,
|
|
1127
|
+
status: "blocked_review",
|
|
1128
|
+
needsConvergence: true,
|
|
1129
|
+
reason: "default_branch_upstream_unverified",
|
|
1130
|
+
nextStep: `Refresh ${defaultBranch}'s upstream refs or resolve the fetch failure before declaring convergence complete for node '${node.id}'.`
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
985
1133
|
if (ahead > 0 || behind > 0) {
|
|
986
1134
|
return {
|
|
987
1135
|
...base,
|
|
@@ -1008,6 +1156,15 @@ function buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges) {
|
|
|
1008
1156
|
nextStep: `Run mesh_refine_node(node_id: "${node.id}") or explicitly classify this worktree as blocked_review/not_mergeable before ending the task.`
|
|
1009
1157
|
};
|
|
1010
1158
|
}
|
|
1159
|
+
if (upstream && upstreamStatus !== "fresh") {
|
|
1160
|
+
return {
|
|
1161
|
+
...base,
|
|
1162
|
+
status: "blocked_review",
|
|
1163
|
+
needsConvergence: true,
|
|
1164
|
+
reason: "feature_branch_upstream_unverified",
|
|
1165
|
+
nextStep: `Refresh branch '${branch}' upstream refs or resolve the fetch failure before deciding whether it is ready to merge into ${defaultBranch}.`
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1011
1168
|
if (!upstream || ahead > 0 || behind > 0) {
|
|
1012
1169
|
return {
|
|
1013
1170
|
...base,
|
|
@@ -1051,6 +1208,71 @@ async function commandForNode(ctx, node, command, args = {}) {
|
|
|
1051
1208
|
}
|
|
1052
1209
|
throw new Error(`Command '${command}' requires daemon IPC/local transport for node '${node.id}'`);
|
|
1053
1210
|
}
|
|
1211
|
+
function normalizePendingMeshCoordinatorEvents(value) {
|
|
1212
|
+
const payload = unwrapCommandPayload(value);
|
|
1213
|
+
const events = Array.isArray(payload?.events) ? payload.events : Array.isArray(value?.events) ? value.events : [];
|
|
1214
|
+
return events.filter((event) => event && typeof event === "object");
|
|
1215
|
+
}
|
|
1216
|
+
function buildMeshForwardPayloadFromPendingEvent(event) {
|
|
1217
|
+
const metadataEvent = event?.metadataEvent && typeof event.metadataEvent === "object" ? event.metadataEvent : {};
|
|
1218
|
+
return {
|
|
1219
|
+
event: readString(event?.event),
|
|
1220
|
+
meshId: readString(event?.meshId),
|
|
1221
|
+
nodeId: readString(event?.nodeId) || readString(metadataEvent.meshNodeId),
|
|
1222
|
+
workspace: readString(event?.workspace) || readString(metadataEvent.workspace),
|
|
1223
|
+
targetSessionId: readString(metadataEvent.targetSessionId) || readString(metadataEvent.sessionId) || readString(metadataEvent.instanceId),
|
|
1224
|
+
providerType: readString(metadataEvent.providerType),
|
|
1225
|
+
providerSessionId: readString(metadataEvent.providerSessionId),
|
|
1226
|
+
finalSummary: readString(metadataEvent.finalSummary) || readString(metadataEvent.summary),
|
|
1227
|
+
...metadataEvent.intentional === true ? { intentional: true } : {},
|
|
1228
|
+
...metadataEvent.intentionalStop === true ? { intentionalStop: true } : {},
|
|
1229
|
+
...metadataEvent.operatorCleanup === true ? { operatorCleanup: true } : {},
|
|
1230
|
+
...readString(metadataEvent.reason) ? { reason: readString(metadataEvent.reason) } : {},
|
|
1231
|
+
...readString(metadataEvent.stopReason) ? { stopReason: readString(metadataEvent.stopReason) } : {},
|
|
1232
|
+
...readString(metadataEvent.cleanupReason) ? { cleanupReason: readString(metadataEvent.cleanupReason) } : {},
|
|
1233
|
+
...readString(metadataEvent.source) ? { source: readString(metadataEvent.source) } : {}
|
|
1234
|
+
};
|
|
1235
|
+
}
|
|
1236
|
+
async function drainCoordinatorPendingEvents(ctx, opts) {
|
|
1237
|
+
const requestedNodeIds = opts?.nodeIds?.length ? new Set(opts.nodeIds) : null;
|
|
1238
|
+
const matchesCurrentMesh = (event) => readString(event?.meshId) === ctx.mesh.id;
|
|
1239
|
+
if (ctx.transport instanceof IpcTransport) {
|
|
1240
|
+
const surfacedEvents = [];
|
|
1241
|
+
try {
|
|
1242
|
+
surfacedEvents.push(
|
|
1243
|
+
...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", { meshId: ctx.mesh.id })).filter(matchesCurrentMesh)
|
|
1244
|
+
);
|
|
1245
|
+
} catch {
|
|
1246
|
+
}
|
|
1247
|
+
for (const node of ctx.mesh.nodes) {
|
|
1248
|
+
if (!node.daemonId || isLocalControlPlaneNode(ctx, node)) continue;
|
|
1249
|
+
if (requestedNodeIds && !requestedNodeIds.has(node.id)) continue;
|
|
1250
|
+
try {
|
|
1251
|
+
const remoteEvents = normalizePendingMeshCoordinatorEvents(
|
|
1252
|
+
await ctx.transport.meshCommand(node.daemonId, "get_pending_mesh_events", { meshId: ctx.mesh.id })
|
|
1253
|
+
).filter(matchesCurrentMesh);
|
|
1254
|
+
if (remoteEvents.length === 0) continue;
|
|
1255
|
+
for (const event of remoteEvents) {
|
|
1256
|
+
const payload = buildMeshForwardPayloadFromPendingEvent(event);
|
|
1257
|
+
if (!payload.event || !payload.meshId) continue;
|
|
1258
|
+
await ctx.transport.command("mesh_forward_event", payload);
|
|
1259
|
+
}
|
|
1260
|
+
} catch {
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
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
|
+
return surfacedEvents;
|
|
1270
|
+
}
|
|
1271
|
+
if (isLocalTransport(ctx.transport)) {
|
|
1272
|
+
return (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id).filter(matchesCurrentMesh);
|
|
1273
|
+
}
|
|
1274
|
+
return [];
|
|
1275
|
+
}
|
|
1054
1276
|
function isP2pTransportUnavailableError(error) {
|
|
1055
1277
|
return (0, import_daemon_core.isP2pRelayTransportFailure)(error);
|
|
1056
1278
|
}
|
|
@@ -1318,6 +1540,43 @@ var MESH_REFINE_NODE_TOOL = {
|
|
|
1318
1540
|
required: ["node_id"]
|
|
1319
1541
|
}
|
|
1320
1542
|
};
|
|
1543
|
+
var MESH_REFINE_CONFIG_SCHEMA_TOOL = {
|
|
1544
|
+
name: "mesh_refine_config_schema",
|
|
1545
|
+
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.",
|
|
1546
|
+
inputSchema: { type: "object", properties: {} }
|
|
1547
|
+
};
|
|
1548
|
+
var MESH_VALIDATE_REFINE_CONFIG_TOOL = {
|
|
1549
|
+
name: "mesh_validate_refine_config",
|
|
1550
|
+
description: "Validate the repo mesh/refine config for a node/workspace without running validation commands or merging.",
|
|
1551
|
+
inputSchema: {
|
|
1552
|
+
type: "object",
|
|
1553
|
+
properties: {
|
|
1554
|
+
node_id: { type: "string", description: "Optional node/workspace whose refine config should be loaded. Defaults to the first mesh node." },
|
|
1555
|
+
config: { type: "object", description: "Optional inline config object to validate instead of loading from the repo." }
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
};
|
|
1559
|
+
var MESH_SUGGEST_REFINE_CONFIG_TOOL = {
|
|
1560
|
+
name: "mesh_suggest_refine_config",
|
|
1561
|
+
description: "Suggest a repo mesh/refine config scaffold from project context/package scripts. Suggestions are never executed until saved as explicit refine config.",
|
|
1562
|
+
inputSchema: {
|
|
1563
|
+
type: "object",
|
|
1564
|
+
properties: {
|
|
1565
|
+
node_id: { type: "string", description: "Optional node/workspace used for suggestions. Defaults to the first mesh node." }
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
};
|
|
1569
|
+
var MESH_REFINE_PLAN_TOOL = {
|
|
1570
|
+
name: "mesh_refine_plan",
|
|
1571
|
+
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.",
|
|
1572
|
+
inputSchema: {
|
|
1573
|
+
type: "object",
|
|
1574
|
+
properties: {
|
|
1575
|
+
node_id: { type: "string", description: "Node ID of the worktree node to plan." }
|
|
1576
|
+
},
|
|
1577
|
+
required: ["node_id"]
|
|
1578
|
+
}
|
|
1579
|
+
};
|
|
1321
1580
|
var ALL_MESH_TOOLS = [
|
|
1322
1581
|
MESH_STATUS_TOOL,
|
|
1323
1582
|
MESH_LIST_NODES_TOOL,
|
|
@@ -1335,6 +1594,10 @@ var ALL_MESH_TOOLS = [
|
|
|
1335
1594
|
MESH_CLONE_NODE_TOOL,
|
|
1336
1595
|
MESH_REMOVE_NODE_TOOL,
|
|
1337
1596
|
MESH_REFINE_NODE_TOOL,
|
|
1597
|
+
MESH_REFINE_CONFIG_SCHEMA_TOOL,
|
|
1598
|
+
MESH_VALIDATE_REFINE_CONFIG_TOOL,
|
|
1599
|
+
MESH_SUGGEST_REFINE_CONFIG_TOOL,
|
|
1600
|
+
MESH_REFINE_PLAN_TOOL,
|
|
1338
1601
|
MESH_CLEANUP_SESSIONS_TOOL,
|
|
1339
1602
|
MESH_TASK_HISTORY_TOOL,
|
|
1340
1603
|
MESH_RECONCILE_LEDGER_TOOL
|
|
@@ -1352,11 +1615,12 @@ async function meshStatus(ctx) {
|
|
|
1352
1615
|
};
|
|
1353
1616
|
try {
|
|
1354
1617
|
if (!isLocalTransport(transport) && node.daemonId) {
|
|
1355
|
-
const result = await transport.gitStatus(node.daemonId, node.workspace, false);
|
|
1618
|
+
const result = await transport.gitStatus(node.daemonId, node.workspace, false, true);
|
|
1356
1619
|
const status = extractGitStatus(result);
|
|
1357
1620
|
const uncommittedChanges = countUncommittedChanges(status);
|
|
1358
1621
|
const dirty = isGitStatusDirty(status);
|
|
1359
1622
|
entry.health = status?.isGitRepo ? dirty ? "dirty" : "online" : "degraded";
|
|
1623
|
+
assignFullGitSnapshot(entry, status);
|
|
1360
1624
|
entry.branch = status?.branch;
|
|
1361
1625
|
entry.isDirty = dirty;
|
|
1362
1626
|
entry.uncommittedChanges = uncommittedChanges;
|
|
@@ -1370,6 +1634,7 @@ async function meshStatus(ctx) {
|
|
|
1370
1634
|
const autoDiscover = node.policy?.autoDiscoverSubmodules !== false;
|
|
1371
1635
|
const statusResult = await commandForNode(ctx, node, "git_status", {
|
|
1372
1636
|
workspace: node.workspace,
|
|
1637
|
+
refreshUpstream: true,
|
|
1373
1638
|
includeSubmodules: autoDiscover,
|
|
1374
1639
|
submoduleIgnorePaths: node.policy?.submoduleIgnorePaths || void 0
|
|
1375
1640
|
});
|
|
@@ -1377,6 +1642,7 @@ async function meshStatus(ctx) {
|
|
|
1377
1642
|
const uncommittedChanges = countUncommittedChanges(status);
|
|
1378
1643
|
const dirty = isGitStatusDirty(status);
|
|
1379
1644
|
entry.health = status?.isGitRepo ? dirty ? "dirty" : "online" : "degraded";
|
|
1645
|
+
assignFullGitSnapshot(entry, status);
|
|
1380
1646
|
entry.branch = status?.branch;
|
|
1381
1647
|
entry.isDirty = dirty;
|
|
1382
1648
|
entry.uncommittedChanges = uncommittedChanges;
|
|
@@ -1460,6 +1726,11 @@ async function meshStatus(ctx) {
|
|
|
1460
1726
|
repoIdentity: mesh.repoIdentity,
|
|
1461
1727
|
policy: mesh.policy,
|
|
1462
1728
|
refreshedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1729
|
+
sourceOfTruth: {
|
|
1730
|
+
membership: "coordinator_daemon_live_mesh",
|
|
1731
|
+
currentStatus: "live_git_and_session_probes",
|
|
1732
|
+
historicalEvidenceOnly: ["recoveryHints", "ledgerSummary"]
|
|
1733
|
+
},
|
|
1463
1734
|
nodes: results,
|
|
1464
1735
|
branchConvergenceSummary: summarizeBranchConvergence(results)
|
|
1465
1736
|
};
|
|
@@ -1468,13 +1739,7 @@ async function meshStatus(ctx) {
|
|
|
1468
1739
|
} catch {
|
|
1469
1740
|
}
|
|
1470
1741
|
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
|
-
}
|
|
1742
|
+
const pendingEvents = await drainCoordinatorPendingEvents(ctx);
|
|
1478
1743
|
if (pendingEvents.length > 0) {
|
|
1479
1744
|
response.pendingCoordinatorEvents = pendingEvents;
|
|
1480
1745
|
}
|
|
@@ -1484,6 +1749,7 @@ async function meshStatus(ctx) {
|
|
|
1484
1749
|
}
|
|
1485
1750
|
async function meshTaskHistory(ctx, args) {
|
|
1486
1751
|
const { mesh } = ctx;
|
|
1752
|
+
await drainCoordinatorPendingEvents(ctx);
|
|
1487
1753
|
const tail = typeof args.tail === "number" && args.tail > 0 ? args.tail : 20;
|
|
1488
1754
|
const kind = typeof args.kind === "string" && args.kind.trim() ? [args.kind.trim()] : void 0;
|
|
1489
1755
|
const entries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail, kind });
|
|
@@ -1775,9 +2041,52 @@ async function meshSendTask(ctx, args) {
|
|
|
1775
2041
|
}
|
|
1776
2042
|
if (args.session_id && isLocalTransport(ctx.transport)) {
|
|
1777
2043
|
const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id));
|
|
2044
|
+
let resolvedProviderType = cached?.providerType || "";
|
|
2045
|
+
if (!resolvedProviderType) {
|
|
2046
|
+
const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
|
|
2047
|
+
const sessions = extractStatusMetadataSessions(statusResult);
|
|
2048
|
+
const explicitSession = sessions.find((session) => readSessionRecordId(session) === args.session_id);
|
|
2049
|
+
if (!explicitSession) {
|
|
2050
|
+
return JSON.stringify({
|
|
2051
|
+
success: false,
|
|
2052
|
+
recoverable: true,
|
|
2053
|
+
code: "mesh_target_session_not_found",
|
|
2054
|
+
reason: "mesh_target_session_not_found",
|
|
2055
|
+
transport: "local_ipc",
|
|
2056
|
+
retryRecommended: true,
|
|
2057
|
+
nodeId: args.node_id,
|
|
2058
|
+
sessionId: args.session_id,
|
|
2059
|
+
error: `Local session '${args.session_id}' is not present in live status for node '${args.node_id}'.`,
|
|
2060
|
+
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.`
|
|
2061
|
+
});
|
|
2062
|
+
}
|
|
2063
|
+
resolvedProviderType = resolveSessionProviderType(explicitSession);
|
|
2064
|
+
if (resolvedProviderType) {
|
|
2065
|
+
meshSessionProviderMetadata.set(meshSessionCacheKey(args.node_id, args.session_id), {
|
|
2066
|
+
providerType: resolvedProviderType,
|
|
2067
|
+
providerSessionId: readString(explicitSession?.providerSessionId) || void 0
|
|
2068
|
+
});
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
if (!resolvedProviderType) {
|
|
2072
|
+
return JSON.stringify({
|
|
2073
|
+
success: false,
|
|
2074
|
+
recoverable: true,
|
|
2075
|
+
code: "mesh_target_session_provider_unknown",
|
|
2076
|
+
reason: "mesh_target_session_provider_unknown",
|
|
2077
|
+
transport: "local_ipc",
|
|
2078
|
+
retryRecommended: false,
|
|
2079
|
+
nodeId: args.node_id,
|
|
2080
|
+
sessionId: args.session_id,
|
|
2081
|
+
error: `Local session '${args.session_id}' is live but does not expose providerType/cliType, so agent_command cannot be routed safely.`,
|
|
2082
|
+
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.`
|
|
2083
|
+
});
|
|
2084
|
+
}
|
|
1778
2085
|
const dispatchResult = await commandForNode(ctx, node, "agent_command", {
|
|
1779
2086
|
targetSessionId: args.session_id,
|
|
1780
|
-
|
|
2087
|
+
agentType: resolvedProviderType,
|
|
2088
|
+
cliType: resolvedProviderType,
|
|
2089
|
+
providerType: resolvedProviderType,
|
|
1781
2090
|
action: "send_chat",
|
|
1782
2091
|
message: args.message
|
|
1783
2092
|
});
|
|
@@ -1795,7 +2104,7 @@ async function meshSendTask(ctx, args) {
|
|
|
1795
2104
|
kind: "task_dispatched",
|
|
1796
2105
|
nodeId: args.node_id,
|
|
1797
2106
|
sessionId: args.session_id,
|
|
1798
|
-
providerType:
|
|
2107
|
+
providerType: resolvedProviderType,
|
|
1799
2108
|
payload: { message: args.message, via: "local_direct" }
|
|
1800
2109
|
});
|
|
1801
2110
|
} catch {
|
|
@@ -1810,7 +2119,7 @@ async function meshSendTask(ctx, args) {
|
|
|
1810
2119
|
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
1811
2120
|
});
|
|
1812
2121
|
}
|
|
1813
|
-
const pendingEvents = isLocalTransport(ctx.transport) ? (0, import_daemon_core.drainPendingMeshCoordinatorEvents)() : [];
|
|
2122
|
+
const pendingEvents = isLocalTransport(ctx.transport) ? (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id) : [];
|
|
1814
2123
|
const result = { success: true, nodeId: args.node_id, taskId: task.id, status: task.status };
|
|
1815
2124
|
if (pendingEvents.length > 0) {
|
|
1816
2125
|
result.pendingCoordinatorEvents = pendingEvents;
|
|
@@ -1831,6 +2140,9 @@ async function meshReadChat(ctx, args) {
|
|
|
1831
2140
|
if (!node) {
|
|
1832
2141
|
return JSON.stringify(buildMissingNodeReadChatRecovery(ctx, args), null, 2);
|
|
1833
2142
|
}
|
|
2143
|
+
if (ctx.transport instanceof IpcTransport || isLocalTransport(ctx.transport)) {
|
|
2144
|
+
await drainCoordinatorPendingEvents(ctx, { nodeIds: [args.node_id] });
|
|
2145
|
+
}
|
|
1834
2146
|
if (isLocalTransport(ctx.transport)) {
|
|
1835
2147
|
const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id));
|
|
1836
2148
|
const providerSessionId = typeof args.provider_session_id === "string" && args.provider_session_id.trim() ? args.provider_session_id.trim() : cached?.providerSessionId;
|
|
@@ -1933,6 +2245,10 @@ async function meshLaunchSession(ctx, args) {
|
|
|
1933
2245
|
const coordinatorNode = resolveCoordinatorNode(ctx);
|
|
1934
2246
|
const coordinatorDaemonId = coordinatorNode?.daemonId || ctx.localDaemonId;
|
|
1935
2247
|
const spawnedSessionVisibility = readSpawnedSessionVisibility(ctx.mesh.policy);
|
|
2248
|
+
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
2249
|
+
if (node.daemonId && !isLocalNode && !coordinatorDaemonId) {
|
|
2250
|
+
return JSON.stringify(buildMissingCoordinatorDaemonIdFailure(ctx, node, resolvedProviderType), null, 2);
|
|
2251
|
+
}
|
|
1936
2252
|
let result;
|
|
1937
2253
|
try {
|
|
1938
2254
|
result = await commandForNode(ctx, node, "launch_cli", {
|
|
@@ -1973,7 +2289,6 @@ async function meshLaunchSession(ctx, args) {
|
|
|
1973
2289
|
});
|
|
1974
2290
|
} catch {
|
|
1975
2291
|
}
|
|
1976
|
-
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
1977
2292
|
if (ctx.transport instanceof IpcTransport && node.daemonId && !isLocalNode) {
|
|
1978
2293
|
ctx.transport.meshCommand(node.daemonId, "trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
1979
2294
|
});
|
|
@@ -1998,6 +2313,9 @@ async function meshLaunchSession(ctx, args) {
|
|
|
1998
2313
|
const coordinatorNode = resolveCoordinatorNode(ctx);
|
|
1999
2314
|
const coordinatorDaemonId = coordinatorNode?.daemonId || ctx.localDaemonId;
|
|
2000
2315
|
const spawnedSessionVisibility = readSpawnedSessionVisibility(ctx.mesh.policy);
|
|
2316
|
+
if (!coordinatorDaemonId) {
|
|
2317
|
+
return JSON.stringify(buildMissingCoordinatorDaemonIdFailure(ctx, node, resolvedProviderType), null, 2);
|
|
2318
|
+
}
|
|
2001
2319
|
try {
|
|
2002
2320
|
const res = await ctx.transport.launch(node.daemonId, {
|
|
2003
2321
|
type: resolvedProviderType,
|
|
@@ -2036,7 +2354,7 @@ async function meshGitStatus(ctx, args) {
|
|
|
2036
2354
|
const submoduleIgnorePaths = node.policy?.submoduleIgnorePaths || [];
|
|
2037
2355
|
try {
|
|
2038
2356
|
if (!isLocalTransport(ctx.transport) && node.daemonId) {
|
|
2039
|
-
const result = await ctx.transport.gitStatus(node.daemonId, node.workspace, true);
|
|
2357
|
+
const result = await ctx.transport.gitStatus(node.daemonId, node.workspace, true, true);
|
|
2040
2358
|
return JSON.stringify({
|
|
2041
2359
|
nodeId: args.node_id,
|
|
2042
2360
|
workspace: node.workspace,
|
|
@@ -2048,6 +2366,7 @@ async function meshGitStatus(ctx, args) {
|
|
|
2048
2366
|
} else if (isLocalTransport(ctx.transport)) {
|
|
2049
2367
|
const statusResult = await commandForNode(ctx, node, "git_status", {
|
|
2050
2368
|
workspace: node.workspace,
|
|
2369
|
+
refreshUpstream: true,
|
|
2051
2370
|
includeSubmodules: autoDiscoverSubmodules,
|
|
2052
2371
|
submoduleIgnorePaths: submoduleIgnorePaths.length > 0 ? submoduleIgnorePaths : void 0
|
|
2053
2372
|
});
|
|
@@ -2162,6 +2481,7 @@ async function meshCloneNode(ctx, args) {
|
|
|
2162
2481
|
if (existingIndex >= 0) ctx.mesh.nodes[existingIndex] = clonePayload.node;
|
|
2163
2482
|
else ctx.mesh.nodes.push(clonePayload.node);
|
|
2164
2483
|
ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2484
|
+
await syncCoordinatorDaemonMeshCache(ctx);
|
|
2165
2485
|
}
|
|
2166
2486
|
return JSON.stringify(result, null, 2);
|
|
2167
2487
|
} else if (!isLocalTransport(ctx.transport) && sourceNode.daemonId) {
|
|
@@ -2179,6 +2499,7 @@ async function meshCloneNode(ctx, args) {
|
|
|
2179
2499
|
if (existingIndex >= 0) ctx.mesh.nodes[existingIndex] = clonePayload.node;
|
|
2180
2500
|
else ctx.mesh.nodes.push(clonePayload.node);
|
|
2181
2501
|
ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2502
|
+
await syncCoordinatorDaemonMeshCache(ctx);
|
|
2182
2503
|
}
|
|
2183
2504
|
return JSON.stringify(res, null, 2);
|
|
2184
2505
|
} catch (e) {
|
|
@@ -2274,6 +2595,43 @@ async function meshRemoveNode(ctx, args) {
|
|
|
2274
2595
|
return JSON.stringify({ error: "Cloud mesh remove_node requires node daemonId" });
|
|
2275
2596
|
}
|
|
2276
2597
|
}
|
|
2598
|
+
function resolveRefineConfigNode(ctx, nodeId) {
|
|
2599
|
+
if (nodeId) return findNode(ctx.mesh, nodeId);
|
|
2600
|
+
const node = ctx.mesh.nodes.find((entry) => !!entry.workspace);
|
|
2601
|
+
if (!node) throw new Error("No mesh node with a workspace is available");
|
|
2602
|
+
return node;
|
|
2603
|
+
}
|
|
2604
|
+
async function meshRefineConfigSchema(ctx) {
|
|
2605
|
+
const node = resolveRefineConfigNode(ctx);
|
|
2606
|
+
const result = await commandForNode(ctx, node, "get_mesh_refine_config_schema", {});
|
|
2607
|
+
return JSON.stringify(result, null, 2);
|
|
2608
|
+
}
|
|
2609
|
+
async function meshValidateRefineConfig(ctx, args) {
|
|
2610
|
+
const node = resolveRefineConfigNode(ctx, args.node_id);
|
|
2611
|
+
const result = await commandForNode(ctx, node, "validate_mesh_refine_config", {
|
|
2612
|
+
workspace: node.workspace,
|
|
2613
|
+
inlineMesh: ctx.mesh,
|
|
2614
|
+
...args.config ? { config: args.config } : {}
|
|
2615
|
+
});
|
|
2616
|
+
return JSON.stringify(result, null, 2);
|
|
2617
|
+
}
|
|
2618
|
+
async function meshSuggestRefineConfig(ctx, args) {
|
|
2619
|
+
const node = resolveRefineConfigNode(ctx, args.node_id);
|
|
2620
|
+
const result = await commandForNode(ctx, node, "suggest_mesh_refine_config", {
|
|
2621
|
+
workspace: node.workspace,
|
|
2622
|
+
inlineMesh: ctx.mesh
|
|
2623
|
+
});
|
|
2624
|
+
return JSON.stringify(result, null, 2);
|
|
2625
|
+
}
|
|
2626
|
+
async function meshRefinePlan(ctx, args) {
|
|
2627
|
+
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2628
|
+
const result = await commandForNode(ctx, node, "plan_mesh_refine_node", {
|
|
2629
|
+
meshId: ctx.mesh.id,
|
|
2630
|
+
nodeId: args.node_id,
|
|
2631
|
+
inlineMesh: ctx.mesh
|
|
2632
|
+
});
|
|
2633
|
+
return JSON.stringify(result, null, 2);
|
|
2634
|
+
}
|
|
2277
2635
|
async function meshRefineNode(ctx, args) {
|
|
2278
2636
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2279
2637
|
if (isLocalTransport(ctx.transport)) {
|
|
@@ -2519,8 +2877,8 @@ var CloudTransport = class {
|
|
|
2519
2877
|
if (!res.ok) throw new Error(`Approve failed: ${res.status}`);
|
|
2520
2878
|
return res.json();
|
|
2521
2879
|
}
|
|
2522
|
-
async gitStatus(daemonId, workspace, includeDiff = true) {
|
|
2523
|
-
const params = new URLSearchParams({ workspace, includeDiff: String(includeDiff) });
|
|
2880
|
+
async gitStatus(daemonId, workspace, includeDiff = true, refreshUpstream = false) {
|
|
2881
|
+
const params = new URLSearchParams({ workspace, includeDiff: String(includeDiff), refreshUpstream: String(refreshUpstream) });
|
|
2524
2882
|
const res = await fetch(
|
|
2525
2883
|
`${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/git-status?${params}`,
|
|
2526
2884
|
{ headers: this.headers() }
|
|
@@ -2915,6 +3273,22 @@ function formatChatResult(result, sessionId, format, limit = 50, compact = false
|
|
|
2915
3273
|
}))
|
|
2916
3274
|
}, null, 2);
|
|
2917
3275
|
}
|
|
3276
|
+
if ((format === "text" || format === void 0) && compact && compactPayload) {
|
|
3277
|
+
const lines2 = outputMessages.slice(-limit).map((m) => {
|
|
3278
|
+
const role = m.role === "user" ? "User" : m.role === "assistant" ? "Agent" : m.role;
|
|
3279
|
+
const content = messageContent(m);
|
|
3280
|
+
const truncated = content.length > 500 ? `${content.slice(0, 500)}\u2026` : content;
|
|
3281
|
+
return `[${role}] ${truncated}`;
|
|
3282
|
+
});
|
|
3283
|
+
if (compactPayload.summary) {
|
|
3284
|
+
const truncatedSummary = compactPayload.summary.length > 500 ? `${compactPayload.summary.slice(0, 500)}\u2026` : compactPayload.summary;
|
|
3285
|
+
lines2.push(`[Summary] ${truncatedSummary}`);
|
|
3286
|
+
}
|
|
3287
|
+
if (result?.pollingAdvisory) {
|
|
3288
|
+
lines2.push(`Advisory: ${result.pollingAdvisory.message}`);
|
|
3289
|
+
}
|
|
3290
|
+
return lines2.length > 0 ? lines2.join("\n\n") : "No messages in chat.";
|
|
3291
|
+
}
|
|
2918
3292
|
if (outputMessages.length === 0) {
|
|
2919
3293
|
return result?.pollingAdvisory ? `No messages in chat.
|
|
2920
3294
|
|
|
@@ -4030,6 +4404,18 @@ async function startMcpServer(opts) {
|
|
|
4030
4404
|
case "mesh_refine_node":
|
|
4031
4405
|
text = await meshRefineNode(meshCtx, a);
|
|
4032
4406
|
break;
|
|
4407
|
+
case "mesh_refine_config_schema":
|
|
4408
|
+
text = await meshRefineConfigSchema(meshCtx);
|
|
4409
|
+
break;
|
|
4410
|
+
case "mesh_validate_refine_config":
|
|
4411
|
+
text = await meshValidateRefineConfig(meshCtx, a);
|
|
4412
|
+
break;
|
|
4413
|
+
case "mesh_suggest_refine_config":
|
|
4414
|
+
text = await meshSuggestRefineConfig(meshCtx, a);
|
|
4415
|
+
break;
|
|
4416
|
+
case "mesh_refine_plan":
|
|
4417
|
+
text = await meshRefinePlan(meshCtx, a);
|
|
4418
|
+
break;
|
|
4033
4419
|
case "mesh_cleanup_sessions":
|
|
4034
4420
|
text = await meshCleanupSessions(meshCtx, a);
|
|
4035
4421
|
break;
|