adhdev 0.9.82-rc.21 → 0.9.82-rc.22
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 +116 -11
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +116 -11
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/vendor/mcp-server/index.js +157 -20
- 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.22",
|
|
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.22",
|
|
51
51
|
"@adhdev/ghostty-vt-node": "*",
|
|
52
52
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
53
53
|
"@xterm/addon-serialize": "^0.14.0",
|
|
@@ -404,6 +404,14 @@ function buildMissingNodeReadChatRecovery(ctx, args) {
|
|
|
404
404
|
function readSessionRecordId(session) {
|
|
405
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);
|
|
406
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
|
+
}
|
|
407
415
|
function addSessionRecord(target, session) {
|
|
408
416
|
if (!session || typeof session !== "object" || isTerminalSessionRecord(session)) return;
|
|
409
417
|
const sessionId = readSessionRecordId(session);
|
|
@@ -616,22 +624,59 @@ function isIdleSessionRecord(session) {
|
|
|
616
624
|
const chatStatus = typeof session?.activeChat?.status === "string" ? session.activeChat.status.toLowerCase() : "";
|
|
617
625
|
return status === "idle" || chatStatus === "waiting_input";
|
|
618
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
|
+
}
|
|
619
635
|
function chooseDispatchableSession(sessions, providerType, meshId, nodeId) {
|
|
620
636
|
const live = sessions.filter((session) => !isTerminalSessionRecord(session));
|
|
621
637
|
const matchingProvider = (session) => !providerType || session?.providerType === providerType || session?.cliType === providerType;
|
|
622
|
-
const isMeshOwnedDelegateSession = (session) => {
|
|
623
|
-
const settings = session?.settings;
|
|
624
|
-
const sessionMeshId = typeof settings?.meshNodeFor === "string" ? settings.meshNodeFor.trim() : "";
|
|
625
|
-
const coordinatorDaemonId = typeof settings?.meshCoordinatorDaemonId === "string" ? settings.meshCoordinatorDaemonId.trim() : "";
|
|
626
|
-
const sessionNodeId = typeof settings?.meshNodeId === "string" ? settings.meshNodeId.trim() : "";
|
|
627
|
-
if (sessionMeshId !== meshId || !coordinatorDaemonId) return false;
|
|
628
|
-
return !sessionNodeId || sessionNodeId === nodeId;
|
|
629
|
-
};
|
|
630
638
|
const meshSessions = live.filter(
|
|
631
|
-
(session) => isMeshOwnedDelegateSession(session)
|
|
639
|
+
(session) => isMeshOwnedDelegateSession(session, meshId, nodeId)
|
|
632
640
|
);
|
|
633
641
|
return meshSessions.find((session) => isIdleSessionRecord(session) && matchingProvider(session)) || meshSessions.find(matchingProvider) || void 0;
|
|
634
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
|
+
};
|
|
679
|
+
}
|
|
635
680
|
function findNestedPayload(value, predicate) {
|
|
636
681
|
const seen = /* @__PURE__ */ new Set();
|
|
637
682
|
const stack = [{ payload: value, depth: 0 }];
|
|
@@ -789,20 +834,63 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
|
|
|
789
834
|
let sessionId = args.session_id?.trim() || "";
|
|
790
835
|
const providerPriorityList = Array.isArray(node.policy?.providerPriority) ? node.policy.providerPriority : [];
|
|
791
836
|
let resolvedProviderType = args.providerType?.trim() || providerPriorityList[0] || "";
|
|
792
|
-
if (!sessionId) {
|
|
837
|
+
if (!sessionId || args.session_id) {
|
|
793
838
|
try {
|
|
794
839
|
const relayResult = await transport.meshCommand(daemonId, "get_status_metadata", {});
|
|
795
|
-
const
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
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
|
+
}
|
|
801
869
|
if (!resolvedProviderType) {
|
|
802
|
-
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
|
+
}
|
|
803
879
|
}
|
|
804
880
|
}
|
|
805
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
|
+
}
|
|
806
894
|
}
|
|
807
895
|
}
|
|
808
896
|
if (!resolvedProviderType) {
|
|
@@ -1901,9 +1989,52 @@ async function meshSendTask(ctx, args) {
|
|
|
1901
1989
|
}
|
|
1902
1990
|
if (args.session_id && isLocalTransport(ctx.transport)) {
|
|
1903
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
|
+
}
|
|
1904
2033
|
const dispatchResult = await commandForNode(ctx, node, "agent_command", {
|
|
1905
2034
|
targetSessionId: args.session_id,
|
|
1906
|
-
|
|
2035
|
+
agentType: resolvedProviderType,
|
|
2036
|
+
cliType: resolvedProviderType,
|
|
2037
|
+
providerType: resolvedProviderType,
|
|
1907
2038
|
action: "send_chat",
|
|
1908
2039
|
message: args.message
|
|
1909
2040
|
});
|
|
@@ -1921,7 +2052,7 @@ async function meshSendTask(ctx, args) {
|
|
|
1921
2052
|
kind: "task_dispatched",
|
|
1922
2053
|
nodeId: args.node_id,
|
|
1923
2054
|
sessionId: args.session_id,
|
|
1924
|
-
providerType:
|
|
2055
|
+
providerType: resolvedProviderType,
|
|
1925
2056
|
payload: { message: args.message, via: "local_direct" }
|
|
1926
2057
|
});
|
|
1927
2058
|
} catch {
|
|
@@ -2062,6 +2193,10 @@ async function meshLaunchSession(ctx, args) {
|
|
|
2062
2193
|
const coordinatorNode = resolveCoordinatorNode(ctx);
|
|
2063
2194
|
const coordinatorDaemonId = coordinatorNode?.daemonId || ctx.localDaemonId;
|
|
2064
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
|
+
}
|
|
2065
2200
|
let result;
|
|
2066
2201
|
try {
|
|
2067
2202
|
result = await commandForNode(ctx, node, "launch_cli", {
|
|
@@ -2102,7 +2237,6 @@ async function meshLaunchSession(ctx, args) {
|
|
|
2102
2237
|
});
|
|
2103
2238
|
} catch {
|
|
2104
2239
|
}
|
|
2105
|
-
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
2106
2240
|
if (ctx.transport instanceof IpcTransport && node.daemonId && !isLocalNode) {
|
|
2107
2241
|
ctx.transport.meshCommand(node.daemonId, "trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
2108
2242
|
});
|
|
@@ -2127,6 +2261,9 @@ async function meshLaunchSession(ctx, args) {
|
|
|
2127
2261
|
const coordinatorNode = resolveCoordinatorNode(ctx);
|
|
2128
2262
|
const coordinatorDaemonId = coordinatorNode?.daemonId || ctx.localDaemonId;
|
|
2129
2263
|
const spawnedSessionVisibility = readSpawnedSessionVisibility(ctx.mesh.policy);
|
|
2264
|
+
if (!coordinatorDaemonId) {
|
|
2265
|
+
return JSON.stringify(buildMissingCoordinatorDaemonIdFailure(ctx, node, resolvedProviderType), null, 2);
|
|
2266
|
+
}
|
|
2130
2267
|
try {
|
|
2131
2268
|
const res = await ctx.transport.launch(node.daemonId, {
|
|
2132
2269
|
type: resolvedProviderType,
|