adhdev 0.9.82-rc.21 → 0.9.82-rc.23

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adhdev",
3
- "version": "0.9.82-rc.21",
3
+ "version": "0.9.82-rc.23",
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.21",
50
+ "@adhdev/daemon-core": "0.9.82-rc.23",
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 innerResult = relayResult?.result ?? relayResult;
796
- const statusObj = innerResult?.status ?? innerResult;
797
- const sessions = Array.isArray(statusObj?.sessions) ? statusObj.sessions : [];
798
- const targetSession = chooseDispatchableSession(sessions, resolvedProviderType, ctx.mesh.id, node.id);
799
- if (targetSession?.id || targetSession?.sessionId) {
800
- sessionId = targetSession.id || targetSession.sessionId;
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 = targetSession.providerType || targetSession.cliType || "";
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
- ...cached?.providerType ? { agentType: cached.providerType, cliType: cached.providerType, providerType: cached.providerType } : {},
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: cached?.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,