adhdev 0.9.82-rc.3 → 0.9.82-rc.31
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/cli/index.js +1654 -717
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +1173 -327
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/vendor/mcp-server/index.js +324 -31
- package/vendor/mcp-server/index.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adhdev",
|
|
3
|
-
"version": "0.9.82-rc.
|
|
3
|
+
"version": "0.9.82-rc.31",
|
|
4
4
|
"description": "ADHDev — Agent Dashboard Hub for Dev. Remote-control AI coding agents from anywhere.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"node": ">=18"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@adhdev/daemon-core": "0.9.82-rc.
|
|
50
|
+
"@adhdev/daemon-core": "0.9.82-rc.31",
|
|
51
51
|
"@adhdev/ghostty-vt-node": "*",
|
|
52
52
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
53
53
|
"@xterm/addon-serialize": "^0.14.0",
|
|
@@ -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,
|
|
@@ -246,15 +256,24 @@ async function refreshMeshFromDaemon(ctx) {
|
|
|
246
256
|
const result = await ctx.transport.command("get_mesh", { meshId: ctx.mesh.id });
|
|
247
257
|
if (!result?.success || !Array.isArray(result.mesh?.nodes)) return;
|
|
248
258
|
const refreshedNodes = result.mesh.nodes.filter((n) => n?.id).map((n) => n);
|
|
249
|
-
if (!refreshedNodes.length) return;
|
|
250
259
|
ctx.mesh.nodes.splice(0, ctx.mesh.nodes.length, ...refreshedNodes);
|
|
251
260
|
ctx.mesh.updatedAt = result.mesh.updatedAt ?? ctx.mesh.updatedAt;
|
|
252
261
|
} catch {
|
|
253
262
|
}
|
|
254
263
|
}
|
|
264
|
+
async function syncCoordinatorDaemonMeshCache(ctx) {
|
|
265
|
+
if (!(ctx.transport instanceof IpcTransport)) return;
|
|
266
|
+
try {
|
|
267
|
+
await ctx.transport.command("get_mesh", {
|
|
268
|
+
meshId: ctx.mesh.id,
|
|
269
|
+
inlineMesh: ctx.mesh
|
|
270
|
+
});
|
|
271
|
+
} catch {
|
|
272
|
+
}
|
|
273
|
+
}
|
|
255
274
|
async function findNodeWithRefresh(ctx, nodeId) {
|
|
256
275
|
const hit = ctx.mesh.nodes.find((n) => n.id === nodeId);
|
|
257
|
-
if (hit) return hit;
|
|
276
|
+
if (hit && !hit.isLocalWorktree) return hit;
|
|
258
277
|
await refreshMeshFromDaemon(ctx);
|
|
259
278
|
const refreshed = ctx.mesh.nodes.find((n) => n.id === nodeId);
|
|
260
279
|
if (!refreshed) throw new Error(`Node '${nodeId}' is not a member of mesh '${ctx.mesh.name}'`);
|
|
@@ -262,7 +281,7 @@ async function findNodeWithRefresh(ctx, nodeId) {
|
|
|
262
281
|
}
|
|
263
282
|
async function findOptionalNodeWithRefresh(ctx, nodeId) {
|
|
264
283
|
const hit = ctx.mesh.nodes.find((n) => n.id === nodeId);
|
|
265
|
-
if (hit) return hit;
|
|
284
|
+
if (hit && !hit.isLocalWorktree) return hit;
|
|
266
285
|
await refreshMeshFromDaemon(ctx);
|
|
267
286
|
return ctx.mesh.nodes.find((n) => n.id === nodeId) ?? null;
|
|
268
287
|
}
|
|
@@ -314,9 +333,26 @@ function buildMissingNodeReadChatRecovery(ctx, args) {
|
|
|
314
333
|
readDebugLocator: readString(lastTerminal?.payload?.readDebugLocator) || readString(lastTerminal?.payload?.debugBundlePath)
|
|
315
334
|
};
|
|
316
335
|
if (finalSummary) {
|
|
336
|
+
if (args.compact === true) {
|
|
337
|
+
return {
|
|
338
|
+
...compactChatPayload({
|
|
339
|
+
success: true,
|
|
340
|
+
status: "idle",
|
|
341
|
+
providerSessionId,
|
|
342
|
+
summary: finalSummary,
|
|
343
|
+
messages: [{ role: "assistant", content: finalSummary, isHistorical: true }]
|
|
344
|
+
}, {
|
|
345
|
+
nodeId: args.node_id,
|
|
346
|
+
sessionId: args.session_id,
|
|
347
|
+
limit: args.tail ?? 10
|
|
348
|
+
}),
|
|
349
|
+
recoveredFromLedger: true,
|
|
350
|
+
ledger
|
|
351
|
+
};
|
|
352
|
+
}
|
|
317
353
|
return {
|
|
318
354
|
success: true,
|
|
319
|
-
compact:
|
|
355
|
+
compact: false,
|
|
320
356
|
recoveredFromLedger: true,
|
|
321
357
|
nodeId: args.node_id,
|
|
322
358
|
sessionId: args.session_id,
|
|
@@ -368,6 +404,14 @@ function buildMissingNodeReadChatRecovery(ctx, args) {
|
|
|
368
404
|
function readSessionRecordId(session) {
|
|
369
405
|
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
406
|
}
|
|
407
|
+
function extractStatusMetadataSessions(value) {
|
|
408
|
+
const payload = unwrapCommandPayload(value);
|
|
409
|
+
const status = payload?.status && typeof payload.status === "object" ? payload.status : payload;
|
|
410
|
+
return Array.isArray(status?.sessions) ? status.sessions : [];
|
|
411
|
+
}
|
|
412
|
+
function resolveSessionProviderType(session) {
|
|
413
|
+
return readString(session?.providerType) || readString(session?.cliType) || readString(session?.agentType) || "";
|
|
414
|
+
}
|
|
371
415
|
function addSessionRecord(target, session) {
|
|
372
416
|
if (!session || typeof session !== "object" || isTerminalSessionRecord(session)) return;
|
|
373
417
|
const sessionId = readSessionRecordId(session);
|
|
@@ -580,13 +624,58 @@ function isIdleSessionRecord(session) {
|
|
|
580
624
|
const chatStatus = typeof session?.activeChat?.status === "string" ? session.activeChat.status.toLowerCase() : "";
|
|
581
625
|
return status === "idle" || chatStatus === "waiting_input";
|
|
582
626
|
}
|
|
627
|
+
function isMeshOwnedDelegateSession(session, meshId, nodeId) {
|
|
628
|
+
const settings = session?.settings;
|
|
629
|
+
const sessionMeshId = typeof settings?.meshNodeFor === "string" ? settings.meshNodeFor.trim() : "";
|
|
630
|
+
const coordinatorDaemonId = typeof settings?.meshCoordinatorDaemonId === "string" ? settings.meshCoordinatorDaemonId.trim() : "";
|
|
631
|
+
const sessionNodeId = typeof settings?.meshNodeId === "string" ? settings.meshNodeId.trim() : "";
|
|
632
|
+
if (sessionMeshId !== meshId || !coordinatorDaemonId) return false;
|
|
633
|
+
return !sessionNodeId || sessionNodeId === nodeId;
|
|
634
|
+
}
|
|
583
635
|
function chooseDispatchableSession(sessions, providerType, meshId, nodeId) {
|
|
584
636
|
const live = sessions.filter((session) => !isTerminalSessionRecord(session));
|
|
585
637
|
const matchingProvider = (session) => !providerType || session?.providerType === providerType || session?.cliType === providerType;
|
|
586
638
|
const meshSessions = live.filter(
|
|
587
|
-
(session) => session
|
|
639
|
+
(session) => isMeshOwnedDelegateSession(session, meshId, nodeId)
|
|
588
640
|
);
|
|
589
|
-
return meshSessions.find((session) => isIdleSessionRecord(session) && matchingProvider(session)) || meshSessions.find(matchingProvider) ||
|
|
641
|
+
return meshSessions.find((session) => isIdleSessionRecord(session) && matchingProvider(session)) || meshSessions.find(matchingProvider) || void 0;
|
|
642
|
+
}
|
|
643
|
+
function buildRelayUnsafeRemoteSessionFailure(ctx, node, sessionId, providerType) {
|
|
644
|
+
return {
|
|
645
|
+
success: false,
|
|
646
|
+
recoverable: true,
|
|
647
|
+
code: "mesh_delegate_session_missing_relay_metadata",
|
|
648
|
+
reason: "mesh_delegate_session_missing_relay_metadata",
|
|
649
|
+
transport: "mesh_transport",
|
|
650
|
+
retryRecommended: true,
|
|
651
|
+
meshId: ctx.mesh.id,
|
|
652
|
+
nodeId: node.id,
|
|
653
|
+
daemonId: node.daemonId,
|
|
654
|
+
workspace: node.workspace,
|
|
655
|
+
sessionId,
|
|
656
|
+
...providerType ? { resolvedProviderType: providerType } : {},
|
|
657
|
+
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.`,
|
|
658
|
+
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.`,
|
|
659
|
+
noFallbackReason: "Blindly reusing a remote session without mesh relay metadata would silently drop task_completed / generating_completed events."
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
function buildMissingCoordinatorDaemonIdFailure(ctx, node, providerType) {
|
|
663
|
+
return {
|
|
664
|
+
success: false,
|
|
665
|
+
recoverable: true,
|
|
666
|
+
code: "mesh_coordinator_daemon_unknown",
|
|
667
|
+
reason: "mesh_coordinator_daemon_unknown",
|
|
668
|
+
transport: "mesh_transport",
|
|
669
|
+
retryRecommended: true,
|
|
670
|
+
meshId: ctx.mesh.id,
|
|
671
|
+
nodeId: node.id,
|
|
672
|
+
daemonId: node.daemonId,
|
|
673
|
+
workspace: node.workspace,
|
|
674
|
+
...providerType ? { resolvedProviderType: providerType } : {},
|
|
675
|
+
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.`,
|
|
676
|
+
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.",
|
|
677
|
+
noFallbackReason: "Launching without meshCoordinatorDaemonId would create a worker session that can finish work but cannot emit task_completed / generating_completed back to the coordinator."
|
|
678
|
+
};
|
|
590
679
|
}
|
|
591
680
|
function findNestedPayload(value, predicate) {
|
|
592
681
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -745,20 +834,63 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
|
|
|
745
834
|
let sessionId = args.session_id?.trim() || "";
|
|
746
835
|
const providerPriorityList = Array.isArray(node.policy?.providerPriority) ? node.policy.providerPriority : [];
|
|
747
836
|
let resolvedProviderType = args.providerType?.trim() || providerPriorityList[0] || "";
|
|
748
|
-
if (!sessionId) {
|
|
837
|
+
if (!sessionId || args.session_id) {
|
|
749
838
|
try {
|
|
750
839
|
const relayResult = await transport.meshCommand(daemonId, "get_status_metadata", {});
|
|
751
|
-
const
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
840
|
+
const sessions = extractStatusMetadataSessions(relayResult);
|
|
841
|
+
if (sessionId) {
|
|
842
|
+
const explicitSession = sessions.find((session) => readSessionRecordId(session) === sessionId);
|
|
843
|
+
if (!explicitSession) {
|
|
844
|
+
return {
|
|
845
|
+
success: false,
|
|
846
|
+
recoverable: true,
|
|
847
|
+
code: "mesh_target_session_not_found",
|
|
848
|
+
reason: "mesh_target_session_not_found",
|
|
849
|
+
transport: "mesh_transport",
|
|
850
|
+
retryRecommended: true,
|
|
851
|
+
meshId: ctx.mesh.id,
|
|
852
|
+
nodeId: node.id,
|
|
853
|
+
daemonId,
|
|
854
|
+
workspace: node.workspace,
|
|
855
|
+
sessionId,
|
|
856
|
+
...resolvedProviderType ? { resolvedProviderType } : {},
|
|
857
|
+
error: `Remote session '${sessionId}' is not present in the live status for node '${node.id}'.`,
|
|
858
|
+
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.`
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
if (!isMeshOwnedDelegateSession(explicitSession, ctx.mesh.id, node.id)) {
|
|
862
|
+
return buildRelayUnsafeRemoteSessionFailure(
|
|
863
|
+
ctx,
|
|
864
|
+
node,
|
|
865
|
+
sessionId,
|
|
866
|
+
resolvedProviderType || resolveSessionProviderType(explicitSession) || void 0
|
|
867
|
+
);
|
|
868
|
+
}
|
|
757
869
|
if (!resolvedProviderType) {
|
|
758
|
-
resolvedProviderType =
|
|
870
|
+
resolvedProviderType = resolveSessionProviderType(explicitSession);
|
|
871
|
+
}
|
|
872
|
+
} else {
|
|
873
|
+
const targetSession = chooseDispatchableSession(sessions, resolvedProviderType, ctx.mesh.id, node.id);
|
|
874
|
+
if (targetSession?.id || targetSession?.sessionId) {
|
|
875
|
+
sessionId = targetSession.id || targetSession.sessionId;
|
|
876
|
+
if (!resolvedProviderType) {
|
|
877
|
+
resolvedProviderType = resolveSessionProviderType(targetSession);
|
|
878
|
+
}
|
|
759
879
|
}
|
|
760
880
|
}
|
|
761
881
|
} catch (e) {
|
|
882
|
+
if (sessionId) {
|
|
883
|
+
return {
|
|
884
|
+
...buildCoordinatorP2pRelayFailure(e, {
|
|
885
|
+
command: "get_status_metadata",
|
|
886
|
+
targetDaemonId: daemonId,
|
|
887
|
+
nodeId: node.id,
|
|
888
|
+
sessionId
|
|
889
|
+
}),
|
|
890
|
+
success: false,
|
|
891
|
+
error: `Cannot verify remote session '${sessionId}' before dispatch: ${e?.message || String(e)}`
|
|
892
|
+
};
|
|
893
|
+
}
|
|
762
894
|
}
|
|
763
895
|
}
|
|
764
896
|
if (!resolvedProviderType) {
|
|
@@ -872,6 +1004,10 @@ function summarizeRelatedRepoStatus(repo, status) {
|
|
|
872
1004
|
workspace: repo.workspace,
|
|
873
1005
|
isGitRepo: status?.isGitRepo === true,
|
|
874
1006
|
branch: status?.branch ?? null,
|
|
1007
|
+
upstream: status?.upstream ?? null,
|
|
1008
|
+
upstreamStatus: typeof status?.upstreamStatus === "string" ? status.upstreamStatus : status?.upstream ? "unchecked" : "no_upstream",
|
|
1009
|
+
upstreamFetchedAt: Number.isFinite(Number(status?.upstreamFetchedAt)) ? Number(status.upstreamFetchedAt) : null,
|
|
1010
|
+
upstreamFetchError: typeof status?.upstreamFetchError === "string" ? status.upstreamFetchError : null,
|
|
875
1011
|
ahead: Number.isFinite(Number(status?.ahead)) ? Number(status.ahead) : 0,
|
|
876
1012
|
behind: Number.isFinite(Number(status?.behind)) ? Number(status.behind) : 0,
|
|
877
1013
|
dirty,
|
|
@@ -888,7 +1024,7 @@ async function collectRelatedRepoStatuses(ctx, node) {
|
|
|
888
1024
|
const results = [];
|
|
889
1025
|
for (const repo of relatedRepos) {
|
|
890
1026
|
try {
|
|
891
|
-
const statusResult = !isLocalTransport(ctx.transport) && node.daemonId ? await ctx.transport.gitStatus(node.daemonId, repo.workspace, false) : await commandForNode(ctx, node, "git_status", { workspace: repo.workspace });
|
|
1027
|
+
const statusResult = !isLocalTransport(ctx.transport) && node.daemonId ? await ctx.transport.gitStatus(node.daemonId, repo.workspace, false, true) : await commandForNode(ctx, node, "git_status", { workspace: repo.workspace, refreshUpstream: true });
|
|
892
1028
|
const status = extractGitStatus(statusResult);
|
|
893
1029
|
results.push(summarizeRelatedRepoStatus(repo, status));
|
|
894
1030
|
} catch (e) {
|
|
@@ -936,11 +1072,13 @@ function buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges) {
|
|
|
936
1072
|
const ahead = readNumeric(status?.ahead);
|
|
937
1073
|
const behind = readNumeric(status?.behind);
|
|
938
1074
|
const upstream = readString(status?.upstream) ?? null;
|
|
1075
|
+
const upstreamStatus = readString(status?.upstreamStatus) ?? (upstream ? "unchecked" : "no_upstream");
|
|
939
1076
|
const hasConflicts = status?.hasConflicts === true || Array.isArray(status?.conflictFiles) && status.conflictFiles.length > 0;
|
|
940
1077
|
const base = {
|
|
941
1078
|
defaultBranch,
|
|
942
1079
|
branch,
|
|
943
1080
|
upstream,
|
|
1081
|
+
upstreamStatus,
|
|
944
1082
|
ahead,
|
|
945
1083
|
behind,
|
|
946
1084
|
isWorktree: node.isLocalWorktree === true,
|
|
@@ -974,6 +1112,15 @@ function buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges) {
|
|
|
974
1112
|
};
|
|
975
1113
|
}
|
|
976
1114
|
if (branch === defaultBranch) {
|
|
1115
|
+
if (upstream && upstreamStatus !== "fresh") {
|
|
1116
|
+
return {
|
|
1117
|
+
...base,
|
|
1118
|
+
status: "blocked_review",
|
|
1119
|
+
needsConvergence: true,
|
|
1120
|
+
reason: "default_branch_upstream_unverified",
|
|
1121
|
+
nextStep: `Refresh ${defaultBranch}'s upstream refs or resolve the fetch failure before declaring convergence complete for node '${node.id}'.`
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
977
1124
|
if (ahead > 0 || behind > 0) {
|
|
978
1125
|
return {
|
|
979
1126
|
...base,
|
|
@@ -1000,6 +1147,15 @@ function buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges) {
|
|
|
1000
1147
|
nextStep: `Run mesh_refine_node(node_id: "${node.id}") or explicitly classify this worktree as blocked_review/not_mergeable before ending the task.`
|
|
1001
1148
|
};
|
|
1002
1149
|
}
|
|
1150
|
+
if (upstream && upstreamStatus !== "fresh") {
|
|
1151
|
+
return {
|
|
1152
|
+
...base,
|
|
1153
|
+
status: "blocked_review",
|
|
1154
|
+
needsConvergence: true,
|
|
1155
|
+
reason: "feature_branch_upstream_unverified",
|
|
1156
|
+
nextStep: `Refresh branch '${branch}' upstream refs or resolve the fetch failure before deciding whether it is ready to merge into ${defaultBranch}.`
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1003
1159
|
if (!upstream || ahead > 0 || behind > 0) {
|
|
1004
1160
|
return {
|
|
1005
1161
|
...base,
|
|
@@ -1043,6 +1199,71 @@ async function commandForNode(ctx, node, command, args = {}) {
|
|
|
1043
1199
|
}
|
|
1044
1200
|
throw new Error(`Command '${command}' requires daemon IPC/local transport for node '${node.id}'`);
|
|
1045
1201
|
}
|
|
1202
|
+
function normalizePendingMeshCoordinatorEvents(value) {
|
|
1203
|
+
const payload = unwrapCommandPayload(value);
|
|
1204
|
+
const events = Array.isArray(payload?.events) ? payload.events : Array.isArray(value?.events) ? value.events : [];
|
|
1205
|
+
return events.filter((event) => event && typeof event === "object");
|
|
1206
|
+
}
|
|
1207
|
+
function buildMeshForwardPayloadFromPendingEvent(event) {
|
|
1208
|
+
const metadataEvent = event?.metadataEvent && typeof event.metadataEvent === "object" ? event.metadataEvent : {};
|
|
1209
|
+
return {
|
|
1210
|
+
event: readString(event?.event),
|
|
1211
|
+
meshId: readString(event?.meshId),
|
|
1212
|
+
nodeId: readString(event?.nodeId) || readString(metadataEvent.meshNodeId),
|
|
1213
|
+
workspace: readString(event?.workspace) || readString(metadataEvent.workspace),
|
|
1214
|
+
targetSessionId: readString(metadataEvent.targetSessionId) || readString(metadataEvent.sessionId) || readString(metadataEvent.instanceId),
|
|
1215
|
+
providerType: readString(metadataEvent.providerType),
|
|
1216
|
+
providerSessionId: readString(metadataEvent.providerSessionId),
|
|
1217
|
+
finalSummary: readString(metadataEvent.finalSummary) || readString(metadataEvent.summary),
|
|
1218
|
+
...metadataEvent.intentional === true ? { intentional: true } : {},
|
|
1219
|
+
...metadataEvent.intentionalStop === true ? { intentionalStop: true } : {},
|
|
1220
|
+
...metadataEvent.operatorCleanup === true ? { operatorCleanup: true } : {},
|
|
1221
|
+
...readString(metadataEvent.reason) ? { reason: readString(metadataEvent.reason) } : {},
|
|
1222
|
+
...readString(metadataEvent.stopReason) ? { stopReason: readString(metadataEvent.stopReason) } : {},
|
|
1223
|
+
...readString(metadataEvent.cleanupReason) ? { cleanupReason: readString(metadataEvent.cleanupReason) } : {},
|
|
1224
|
+
...readString(metadataEvent.source) ? { source: readString(metadataEvent.source) } : {}
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
async function drainCoordinatorPendingEvents(ctx, opts) {
|
|
1228
|
+
const requestedNodeIds = opts?.nodeIds?.length ? new Set(opts.nodeIds) : null;
|
|
1229
|
+
const matchesCurrentMesh = (event) => readString(event?.meshId) === ctx.mesh.id;
|
|
1230
|
+
if (ctx.transport instanceof IpcTransport) {
|
|
1231
|
+
const surfacedEvents = [];
|
|
1232
|
+
try {
|
|
1233
|
+
surfacedEvents.push(
|
|
1234
|
+
...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", { meshId: ctx.mesh.id })).filter(matchesCurrentMesh)
|
|
1235
|
+
);
|
|
1236
|
+
} catch {
|
|
1237
|
+
}
|
|
1238
|
+
for (const node of ctx.mesh.nodes) {
|
|
1239
|
+
if (!node.daemonId || isLocalControlPlaneNode(ctx, node)) continue;
|
|
1240
|
+
if (requestedNodeIds && !requestedNodeIds.has(node.id)) continue;
|
|
1241
|
+
try {
|
|
1242
|
+
const remoteEvents = normalizePendingMeshCoordinatorEvents(
|
|
1243
|
+
await ctx.transport.meshCommand(node.daemonId, "get_pending_mesh_events", { meshId: ctx.mesh.id })
|
|
1244
|
+
).filter(matchesCurrentMesh);
|
|
1245
|
+
if (remoteEvents.length === 0) continue;
|
|
1246
|
+
for (const event of remoteEvents) {
|
|
1247
|
+
const payload = buildMeshForwardPayloadFromPendingEvent(event);
|
|
1248
|
+
if (!payload.event || !payload.meshId) continue;
|
|
1249
|
+
await ctx.transport.command("mesh_forward_event", payload);
|
|
1250
|
+
}
|
|
1251
|
+
} catch {
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
try {
|
|
1255
|
+
surfacedEvents.push(
|
|
1256
|
+
...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", { meshId: ctx.mesh.id })).filter(matchesCurrentMesh)
|
|
1257
|
+
);
|
|
1258
|
+
} catch {
|
|
1259
|
+
}
|
|
1260
|
+
return surfacedEvents;
|
|
1261
|
+
}
|
|
1262
|
+
if (isLocalTransport(ctx.transport)) {
|
|
1263
|
+
return (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id).filter(matchesCurrentMesh);
|
|
1264
|
+
}
|
|
1265
|
+
return [];
|
|
1266
|
+
}
|
|
1046
1267
|
function isP2pTransportUnavailableError(error) {
|
|
1047
1268
|
return (0, import_daemon_core.isP2pRelayTransportFailure)(error);
|
|
1048
1269
|
}
|
|
@@ -1344,7 +1565,7 @@ async function meshStatus(ctx) {
|
|
|
1344
1565
|
};
|
|
1345
1566
|
try {
|
|
1346
1567
|
if (!isLocalTransport(transport) && node.daemonId) {
|
|
1347
|
-
const result = await transport.gitStatus(node.daemonId, node.workspace, false);
|
|
1568
|
+
const result = await transport.gitStatus(node.daemonId, node.workspace, false, true);
|
|
1348
1569
|
const status = extractGitStatus(result);
|
|
1349
1570
|
const uncommittedChanges = countUncommittedChanges(status);
|
|
1350
1571
|
const dirty = isGitStatusDirty(status);
|
|
@@ -1362,6 +1583,7 @@ async function meshStatus(ctx) {
|
|
|
1362
1583
|
const autoDiscover = node.policy?.autoDiscoverSubmodules !== false;
|
|
1363
1584
|
const statusResult = await commandForNode(ctx, node, "git_status", {
|
|
1364
1585
|
workspace: node.workspace,
|
|
1586
|
+
refreshUpstream: true,
|
|
1365
1587
|
includeSubmodules: autoDiscover,
|
|
1366
1588
|
submoduleIgnorePaths: node.policy?.submoduleIgnorePaths || void 0
|
|
1367
1589
|
});
|
|
@@ -1452,6 +1674,11 @@ async function meshStatus(ctx) {
|
|
|
1452
1674
|
repoIdentity: mesh.repoIdentity,
|
|
1453
1675
|
policy: mesh.policy,
|
|
1454
1676
|
refreshedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1677
|
+
sourceOfTruth: {
|
|
1678
|
+
membership: "coordinator_daemon_live_mesh",
|
|
1679
|
+
currentStatus: "live_git_and_session_probes",
|
|
1680
|
+
historicalEvidenceOnly: ["recoveryHints", "ledgerSummary"]
|
|
1681
|
+
},
|
|
1455
1682
|
nodes: results,
|
|
1456
1683
|
branchConvergenceSummary: summarizeBranchConvergence(results)
|
|
1457
1684
|
};
|
|
@@ -1460,13 +1687,7 @@ async function meshStatus(ctx) {
|
|
|
1460
1687
|
} catch {
|
|
1461
1688
|
}
|
|
1462
1689
|
try {
|
|
1463
|
-
|
|
1464
|
-
if (ctx.transport instanceof IpcTransport) {
|
|
1465
|
-
const eventsResult = await ctx.transport.command("get_pending_mesh_events", {});
|
|
1466
|
-
pendingEvents = Array.isArray(eventsResult?.events) ? eventsResult.events : [];
|
|
1467
|
-
} else if (isLocalTransport(ctx.transport)) {
|
|
1468
|
-
pendingEvents = (0, import_daemon_core.drainPendingMeshCoordinatorEvents)();
|
|
1469
|
-
}
|
|
1690
|
+
const pendingEvents = await drainCoordinatorPendingEvents(ctx);
|
|
1470
1691
|
if (pendingEvents.length > 0) {
|
|
1471
1692
|
response.pendingCoordinatorEvents = pendingEvents;
|
|
1472
1693
|
}
|
|
@@ -1476,6 +1697,7 @@ async function meshStatus(ctx) {
|
|
|
1476
1697
|
}
|
|
1477
1698
|
async function meshTaskHistory(ctx, args) {
|
|
1478
1699
|
const { mesh } = ctx;
|
|
1700
|
+
await drainCoordinatorPendingEvents(ctx);
|
|
1479
1701
|
const tail = typeof args.tail === "number" && args.tail > 0 ? args.tail : 20;
|
|
1480
1702
|
const kind = typeof args.kind === "string" && args.kind.trim() ? [args.kind.trim()] : void 0;
|
|
1481
1703
|
const entries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail, kind });
|
|
@@ -1767,9 +1989,52 @@ async function meshSendTask(ctx, args) {
|
|
|
1767
1989
|
}
|
|
1768
1990
|
if (args.session_id && isLocalTransport(ctx.transport)) {
|
|
1769
1991
|
const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id));
|
|
1992
|
+
let resolvedProviderType = cached?.providerType || "";
|
|
1993
|
+
if (!resolvedProviderType) {
|
|
1994
|
+
const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
|
|
1995
|
+
const sessions = extractStatusMetadataSessions(statusResult);
|
|
1996
|
+
const explicitSession = sessions.find((session) => readSessionRecordId(session) === args.session_id);
|
|
1997
|
+
if (!explicitSession) {
|
|
1998
|
+
return JSON.stringify({
|
|
1999
|
+
success: false,
|
|
2000
|
+
recoverable: true,
|
|
2001
|
+
code: "mesh_target_session_not_found",
|
|
2002
|
+
reason: "mesh_target_session_not_found",
|
|
2003
|
+
transport: "local_ipc",
|
|
2004
|
+
retryRecommended: true,
|
|
2005
|
+
nodeId: args.node_id,
|
|
2006
|
+
sessionId: args.session_id,
|
|
2007
|
+
error: `Local session '${args.session_id}' is not present in live status for node '${args.node_id}'.`,
|
|
2008
|
+
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.`
|
|
2009
|
+
});
|
|
2010
|
+
}
|
|
2011
|
+
resolvedProviderType = resolveSessionProviderType(explicitSession);
|
|
2012
|
+
if (resolvedProviderType) {
|
|
2013
|
+
meshSessionProviderMetadata.set(meshSessionCacheKey(args.node_id, args.session_id), {
|
|
2014
|
+
providerType: resolvedProviderType,
|
|
2015
|
+
providerSessionId: readString(explicitSession?.providerSessionId) || void 0
|
|
2016
|
+
});
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
if (!resolvedProviderType) {
|
|
2020
|
+
return JSON.stringify({
|
|
2021
|
+
success: false,
|
|
2022
|
+
recoverable: true,
|
|
2023
|
+
code: "mesh_target_session_provider_unknown",
|
|
2024
|
+
reason: "mesh_target_session_provider_unknown",
|
|
2025
|
+
transport: "local_ipc",
|
|
2026
|
+
retryRecommended: false,
|
|
2027
|
+
nodeId: args.node_id,
|
|
2028
|
+
sessionId: args.session_id,
|
|
2029
|
+
error: `Local session '${args.session_id}' is live but does not expose providerType/cliType, so agent_command cannot be routed safely.`,
|
|
2030
|
+
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.`
|
|
2031
|
+
});
|
|
2032
|
+
}
|
|
1770
2033
|
const dispatchResult = await commandForNode(ctx, node, "agent_command", {
|
|
1771
2034
|
targetSessionId: args.session_id,
|
|
1772
|
-
|
|
2035
|
+
agentType: resolvedProviderType,
|
|
2036
|
+
cliType: resolvedProviderType,
|
|
2037
|
+
providerType: resolvedProviderType,
|
|
1773
2038
|
action: "send_chat",
|
|
1774
2039
|
message: args.message
|
|
1775
2040
|
});
|
|
@@ -1787,7 +2052,7 @@ async function meshSendTask(ctx, args) {
|
|
|
1787
2052
|
kind: "task_dispatched",
|
|
1788
2053
|
nodeId: args.node_id,
|
|
1789
2054
|
sessionId: args.session_id,
|
|
1790
|
-
providerType:
|
|
2055
|
+
providerType: resolvedProviderType,
|
|
1791
2056
|
payload: { message: args.message, via: "local_direct" }
|
|
1792
2057
|
});
|
|
1793
2058
|
} catch {
|
|
@@ -1802,7 +2067,7 @@ async function meshSendTask(ctx, args) {
|
|
|
1802
2067
|
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
1803
2068
|
});
|
|
1804
2069
|
}
|
|
1805
|
-
const pendingEvents = isLocalTransport(ctx.transport) ? (0, import_daemon_core.drainPendingMeshCoordinatorEvents)() : [];
|
|
2070
|
+
const pendingEvents = isLocalTransport(ctx.transport) ? (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id) : [];
|
|
1806
2071
|
const result = { success: true, nodeId: args.node_id, taskId: task.id, status: task.status };
|
|
1807
2072
|
if (pendingEvents.length > 0) {
|
|
1808
2073
|
result.pendingCoordinatorEvents = pendingEvents;
|
|
@@ -1823,6 +2088,9 @@ async function meshReadChat(ctx, args) {
|
|
|
1823
2088
|
if (!node) {
|
|
1824
2089
|
return JSON.stringify(buildMissingNodeReadChatRecovery(ctx, args), null, 2);
|
|
1825
2090
|
}
|
|
2091
|
+
if (ctx.transport instanceof IpcTransport || isLocalTransport(ctx.transport)) {
|
|
2092
|
+
await drainCoordinatorPendingEvents(ctx, { nodeIds: [args.node_id] });
|
|
2093
|
+
}
|
|
1826
2094
|
if (isLocalTransport(ctx.transport)) {
|
|
1827
2095
|
const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id));
|
|
1828
2096
|
const providerSessionId = typeof args.provider_session_id === "string" && args.provider_session_id.trim() ? args.provider_session_id.trim() : cached?.providerSessionId;
|
|
@@ -1925,6 +2193,10 @@ async function meshLaunchSession(ctx, args) {
|
|
|
1925
2193
|
const coordinatorNode = resolveCoordinatorNode(ctx);
|
|
1926
2194
|
const coordinatorDaemonId = coordinatorNode?.daemonId || ctx.localDaemonId;
|
|
1927
2195
|
const spawnedSessionVisibility = readSpawnedSessionVisibility(ctx.mesh.policy);
|
|
2196
|
+
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
2197
|
+
if (node.daemonId && !isLocalNode && !coordinatorDaemonId) {
|
|
2198
|
+
return JSON.stringify(buildMissingCoordinatorDaemonIdFailure(ctx, node, resolvedProviderType), null, 2);
|
|
2199
|
+
}
|
|
1928
2200
|
let result;
|
|
1929
2201
|
try {
|
|
1930
2202
|
result = await commandForNode(ctx, node, "launch_cli", {
|
|
@@ -1965,7 +2237,6 @@ async function meshLaunchSession(ctx, args) {
|
|
|
1965
2237
|
});
|
|
1966
2238
|
} catch {
|
|
1967
2239
|
}
|
|
1968
|
-
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
1969
2240
|
if (ctx.transport instanceof IpcTransport && node.daemonId && !isLocalNode) {
|
|
1970
2241
|
ctx.transport.meshCommand(node.daemonId, "trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
1971
2242
|
});
|
|
@@ -1990,6 +2261,9 @@ async function meshLaunchSession(ctx, args) {
|
|
|
1990
2261
|
const coordinatorNode = resolveCoordinatorNode(ctx);
|
|
1991
2262
|
const coordinatorDaemonId = coordinatorNode?.daemonId || ctx.localDaemonId;
|
|
1992
2263
|
const spawnedSessionVisibility = readSpawnedSessionVisibility(ctx.mesh.policy);
|
|
2264
|
+
if (!coordinatorDaemonId) {
|
|
2265
|
+
return JSON.stringify(buildMissingCoordinatorDaemonIdFailure(ctx, node, resolvedProviderType), null, 2);
|
|
2266
|
+
}
|
|
1993
2267
|
try {
|
|
1994
2268
|
const res = await ctx.transport.launch(node.daemonId, {
|
|
1995
2269
|
type: resolvedProviderType,
|
|
@@ -2028,7 +2302,7 @@ async function meshGitStatus(ctx, args) {
|
|
|
2028
2302
|
const submoduleIgnorePaths = node.policy?.submoduleIgnorePaths || [];
|
|
2029
2303
|
try {
|
|
2030
2304
|
if (!isLocalTransport(ctx.transport) && node.daemonId) {
|
|
2031
|
-
const result = await ctx.transport.gitStatus(node.daemonId, node.workspace, true);
|
|
2305
|
+
const result = await ctx.transport.gitStatus(node.daemonId, node.workspace, true, true);
|
|
2032
2306
|
return JSON.stringify({
|
|
2033
2307
|
nodeId: args.node_id,
|
|
2034
2308
|
workspace: node.workspace,
|
|
@@ -2040,6 +2314,7 @@ async function meshGitStatus(ctx, args) {
|
|
|
2040
2314
|
} else if (isLocalTransport(ctx.transport)) {
|
|
2041
2315
|
const statusResult = await commandForNode(ctx, node, "git_status", {
|
|
2042
2316
|
workspace: node.workspace,
|
|
2317
|
+
refreshUpstream: true,
|
|
2043
2318
|
includeSubmodules: autoDiscoverSubmodules,
|
|
2044
2319
|
submoduleIgnorePaths: submoduleIgnorePaths.length > 0 ? submoduleIgnorePaths : void 0
|
|
2045
2320
|
});
|
|
@@ -2154,6 +2429,7 @@ async function meshCloneNode(ctx, args) {
|
|
|
2154
2429
|
if (existingIndex >= 0) ctx.mesh.nodes[existingIndex] = clonePayload.node;
|
|
2155
2430
|
else ctx.mesh.nodes.push(clonePayload.node);
|
|
2156
2431
|
ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2432
|
+
await syncCoordinatorDaemonMeshCache(ctx);
|
|
2157
2433
|
}
|
|
2158
2434
|
return JSON.stringify(result, null, 2);
|
|
2159
2435
|
} else if (!isLocalTransport(ctx.transport) && sourceNode.daemonId) {
|
|
@@ -2171,6 +2447,7 @@ async function meshCloneNode(ctx, args) {
|
|
|
2171
2447
|
if (existingIndex >= 0) ctx.mesh.nodes[existingIndex] = clonePayload.node;
|
|
2172
2448
|
else ctx.mesh.nodes.push(clonePayload.node);
|
|
2173
2449
|
ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2450
|
+
await syncCoordinatorDaemonMeshCache(ctx);
|
|
2174
2451
|
}
|
|
2175
2452
|
return JSON.stringify(res, null, 2);
|
|
2176
2453
|
} catch (e) {
|
|
@@ -2511,8 +2788,8 @@ var CloudTransport = class {
|
|
|
2511
2788
|
if (!res.ok) throw new Error(`Approve failed: ${res.status}`);
|
|
2512
2789
|
return res.json();
|
|
2513
2790
|
}
|
|
2514
|
-
async gitStatus(daemonId, workspace, includeDiff = true) {
|
|
2515
|
-
const params = new URLSearchParams({ workspace, includeDiff: String(includeDiff) });
|
|
2791
|
+
async gitStatus(daemonId, workspace, includeDiff = true, refreshUpstream = false) {
|
|
2792
|
+
const params = new URLSearchParams({ workspace, includeDiff: String(includeDiff), refreshUpstream: String(refreshUpstream) });
|
|
2516
2793
|
const res = await fetch(
|
|
2517
2794
|
`${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/git-status?${params}`,
|
|
2518
2795
|
{ headers: this.headers() }
|
|
@@ -2907,6 +3184,22 @@ function formatChatResult(result, sessionId, format, limit = 50, compact = false
|
|
|
2907
3184
|
}))
|
|
2908
3185
|
}, null, 2);
|
|
2909
3186
|
}
|
|
3187
|
+
if ((format === "text" || format === void 0) && compact && compactPayload) {
|
|
3188
|
+
const lines2 = outputMessages.slice(-limit).map((m) => {
|
|
3189
|
+
const role = m.role === "user" ? "User" : m.role === "assistant" ? "Agent" : m.role;
|
|
3190
|
+
const content = messageContent(m);
|
|
3191
|
+
const truncated = content.length > 500 ? `${content.slice(0, 500)}\u2026` : content;
|
|
3192
|
+
return `[${role}] ${truncated}`;
|
|
3193
|
+
});
|
|
3194
|
+
if (compactPayload.summary) {
|
|
3195
|
+
const truncatedSummary = compactPayload.summary.length > 500 ? `${compactPayload.summary.slice(0, 500)}\u2026` : compactPayload.summary;
|
|
3196
|
+
lines2.push(`[Summary] ${truncatedSummary}`);
|
|
3197
|
+
}
|
|
3198
|
+
if (result?.pollingAdvisory) {
|
|
3199
|
+
lines2.push(`Advisory: ${result.pollingAdvisory.message}`);
|
|
3200
|
+
}
|
|
3201
|
+
return lines2.length > 0 ? lines2.join("\n\n") : "No messages in chat.";
|
|
3202
|
+
}
|
|
2910
3203
|
if (outputMessages.length === 0) {
|
|
2911
3204
|
return result?.pollingAdvisory ? `No messages in chat.
|
|
2912
3205
|
|