adhdev 0.9.82-rc.4 → 0.9.82-rc.40

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.4",
3
+ "version": "0.9.82-rc.40",
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.4",
50
+ "@adhdev/daemon-core": "0.9.82-rc.40",
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: args.compact === true,
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?.settings?.meshNodeFor === meshId || session?.settings?.meshNodeId === nodeId
639
+ (session) => isMeshOwnedDelegateSession(session, meshId, nodeId)
588
640
  );
589
- return meshSessions.find((session) => isIdleSessionRecord(session) && matchingProvider(session)) || meshSessions.find(matchingProvider) || live.find((session) => isIdleSessionRecord(session) && matchingProvider(session)) || live.find(matchingProvider) || live.find(isIdleSessionRecord) || live[0];
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();
@@ -615,12 +704,16 @@ function extractGitDiff(value) {
615
704
  }
616
705
  function extractSubmodules(value, ignorePaths) {
617
706
  const payload = unwrapCommandPayload(value);
618
- const subs = payload?.submodules ?? value?.submodules;
707
+ const subs = payload?.status?.submodules ?? payload?.submodules ?? value?.status?.submodules ?? value?.submodules;
619
708
  if (!Array.isArray(subs)) return void 0;
620
709
  if (ignorePaths.length === 0) return subs;
621
710
  const ignoreSet = new Set(ignorePaths);
622
711
  return subs.filter((s) => s?.path && !ignoreSet.has(s.path));
623
712
  }
713
+ function assignFullGitSnapshot(entry, status) {
714
+ if (!status || typeof status !== "object" || Array.isArray(status)) return;
715
+ entry.git = status;
716
+ }
624
717
  function extractLaunchPayload(value) {
625
718
  return findNestedPayload(value, (payload) => Boolean(payload?.sessionId || payload?.id || payload?.runtimeSessionId));
626
719
  }
@@ -745,20 +838,63 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
745
838
  let sessionId = args.session_id?.trim() || "";
746
839
  const providerPriorityList = Array.isArray(node.policy?.providerPriority) ? node.policy.providerPriority : [];
747
840
  let resolvedProviderType = args.providerType?.trim() || providerPriorityList[0] || "";
748
- if (!sessionId) {
841
+ if (!sessionId || args.session_id) {
749
842
  try {
750
843
  const relayResult = await transport.meshCommand(daemonId, "get_status_metadata", {});
751
- const innerResult = relayResult?.result ?? relayResult;
752
- const statusObj = innerResult?.status ?? innerResult;
753
- const sessions = Array.isArray(statusObj?.sessions) ? statusObj.sessions : [];
754
- const targetSession = chooseDispatchableSession(sessions, resolvedProviderType, ctx.mesh.id, node.id);
755
- if (targetSession?.id || targetSession?.sessionId) {
756
- sessionId = targetSession.id || targetSession.sessionId;
844
+ const sessions = extractStatusMetadataSessions(relayResult);
845
+ if (sessionId) {
846
+ const explicitSession = sessions.find((session) => readSessionRecordId(session) === sessionId);
847
+ if (!explicitSession) {
848
+ return {
849
+ success: false,
850
+ recoverable: true,
851
+ code: "mesh_target_session_not_found",
852
+ reason: "mesh_target_session_not_found",
853
+ transport: "mesh_transport",
854
+ retryRecommended: true,
855
+ meshId: ctx.mesh.id,
856
+ nodeId: node.id,
857
+ daemonId,
858
+ workspace: node.workspace,
859
+ sessionId,
860
+ ...resolvedProviderType ? { resolvedProviderType } : {},
861
+ error: `Remote session '${sessionId}' is not present in the live status for node '${node.id}'.`,
862
+ 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.`
863
+ };
864
+ }
865
+ if (!isMeshOwnedDelegateSession(explicitSession, ctx.mesh.id, node.id)) {
866
+ return buildRelayUnsafeRemoteSessionFailure(
867
+ ctx,
868
+ node,
869
+ sessionId,
870
+ resolvedProviderType || resolveSessionProviderType(explicitSession) || void 0
871
+ );
872
+ }
757
873
  if (!resolvedProviderType) {
758
- resolvedProviderType = targetSession.providerType || targetSession.cliType || "";
874
+ resolvedProviderType = resolveSessionProviderType(explicitSession);
875
+ }
876
+ } else {
877
+ const targetSession = chooseDispatchableSession(sessions, resolvedProviderType, ctx.mesh.id, node.id);
878
+ if (targetSession?.id || targetSession?.sessionId) {
879
+ sessionId = targetSession.id || targetSession.sessionId;
880
+ if (!resolvedProviderType) {
881
+ resolvedProviderType = resolveSessionProviderType(targetSession);
882
+ }
759
883
  }
760
884
  }
761
885
  } catch (e) {
886
+ if (sessionId) {
887
+ return {
888
+ ...buildCoordinatorP2pRelayFailure(e, {
889
+ command: "get_status_metadata",
890
+ targetDaemonId: daemonId,
891
+ nodeId: node.id,
892
+ sessionId
893
+ }),
894
+ success: false,
895
+ error: `Cannot verify remote session '${sessionId}' before dispatch: ${e?.message || String(e)}`
896
+ };
897
+ }
762
898
  }
763
899
  }
764
900
  if (!resolvedProviderType) {
@@ -872,6 +1008,10 @@ function summarizeRelatedRepoStatus(repo, status) {
872
1008
  workspace: repo.workspace,
873
1009
  isGitRepo: status?.isGitRepo === true,
874
1010
  branch: status?.branch ?? null,
1011
+ upstream: status?.upstream ?? null,
1012
+ upstreamStatus: typeof status?.upstreamStatus === "string" ? status.upstreamStatus : status?.upstream ? "unchecked" : "no_upstream",
1013
+ upstreamFetchedAt: Number.isFinite(Number(status?.upstreamFetchedAt)) ? Number(status.upstreamFetchedAt) : null,
1014
+ upstreamFetchError: typeof status?.upstreamFetchError === "string" ? status.upstreamFetchError : null,
875
1015
  ahead: Number.isFinite(Number(status?.ahead)) ? Number(status.ahead) : 0,
876
1016
  behind: Number.isFinite(Number(status?.behind)) ? Number(status.behind) : 0,
877
1017
  dirty,
@@ -888,7 +1028,7 @@ async function collectRelatedRepoStatuses(ctx, node) {
888
1028
  const results = [];
889
1029
  for (const repo of relatedRepos) {
890
1030
  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 });
1031
+ 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
1032
  const status = extractGitStatus(statusResult);
893
1033
  results.push(summarizeRelatedRepoStatus(repo, status));
894
1034
  } catch (e) {
@@ -936,11 +1076,13 @@ function buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges) {
936
1076
  const ahead = readNumeric(status?.ahead);
937
1077
  const behind = readNumeric(status?.behind);
938
1078
  const upstream = readString(status?.upstream) ?? null;
1079
+ const upstreamStatus = readString(status?.upstreamStatus) ?? (upstream ? "unchecked" : "no_upstream");
939
1080
  const hasConflicts = status?.hasConflicts === true || Array.isArray(status?.conflictFiles) && status.conflictFiles.length > 0;
940
1081
  const base = {
941
1082
  defaultBranch,
942
1083
  branch,
943
1084
  upstream,
1085
+ upstreamStatus,
944
1086
  ahead,
945
1087
  behind,
946
1088
  isWorktree: node.isLocalWorktree === true,
@@ -974,6 +1116,15 @@ function buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges) {
974
1116
  };
975
1117
  }
976
1118
  if (branch === defaultBranch) {
1119
+ if (upstream && upstreamStatus !== "fresh") {
1120
+ return {
1121
+ ...base,
1122
+ status: "blocked_review",
1123
+ needsConvergence: true,
1124
+ reason: "default_branch_upstream_unverified",
1125
+ nextStep: `Refresh ${defaultBranch}'s upstream refs or resolve the fetch failure before declaring convergence complete for node '${node.id}'.`
1126
+ };
1127
+ }
977
1128
  if (ahead > 0 || behind > 0) {
978
1129
  return {
979
1130
  ...base,
@@ -1000,6 +1151,15 @@ function buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges) {
1000
1151
  nextStep: `Run mesh_refine_node(node_id: "${node.id}") or explicitly classify this worktree as blocked_review/not_mergeable before ending the task.`
1001
1152
  };
1002
1153
  }
1154
+ if (upstream && upstreamStatus !== "fresh") {
1155
+ return {
1156
+ ...base,
1157
+ status: "blocked_review",
1158
+ needsConvergence: true,
1159
+ reason: "feature_branch_upstream_unverified",
1160
+ nextStep: `Refresh branch '${branch}' upstream refs or resolve the fetch failure before deciding whether it is ready to merge into ${defaultBranch}.`
1161
+ };
1162
+ }
1003
1163
  if (!upstream || ahead > 0 || behind > 0) {
1004
1164
  return {
1005
1165
  ...base,
@@ -1043,6 +1203,71 @@ async function commandForNode(ctx, node, command, args = {}) {
1043
1203
  }
1044
1204
  throw new Error(`Command '${command}' requires daemon IPC/local transport for node '${node.id}'`);
1045
1205
  }
1206
+ function normalizePendingMeshCoordinatorEvents(value) {
1207
+ const payload = unwrapCommandPayload(value);
1208
+ const events = Array.isArray(payload?.events) ? payload.events : Array.isArray(value?.events) ? value.events : [];
1209
+ return events.filter((event) => event && typeof event === "object");
1210
+ }
1211
+ function buildMeshForwardPayloadFromPendingEvent(event) {
1212
+ const metadataEvent = event?.metadataEvent && typeof event.metadataEvent === "object" ? event.metadataEvent : {};
1213
+ return {
1214
+ event: readString(event?.event),
1215
+ meshId: readString(event?.meshId),
1216
+ nodeId: readString(event?.nodeId) || readString(metadataEvent.meshNodeId),
1217
+ workspace: readString(event?.workspace) || readString(metadataEvent.workspace),
1218
+ targetSessionId: readString(metadataEvent.targetSessionId) || readString(metadataEvent.sessionId) || readString(metadataEvent.instanceId),
1219
+ providerType: readString(metadataEvent.providerType),
1220
+ providerSessionId: readString(metadataEvent.providerSessionId),
1221
+ finalSummary: readString(metadataEvent.finalSummary) || readString(metadataEvent.summary),
1222
+ ...metadataEvent.intentional === true ? { intentional: true } : {},
1223
+ ...metadataEvent.intentionalStop === true ? { intentionalStop: true } : {},
1224
+ ...metadataEvent.operatorCleanup === true ? { operatorCleanup: true } : {},
1225
+ ...readString(metadataEvent.reason) ? { reason: readString(metadataEvent.reason) } : {},
1226
+ ...readString(metadataEvent.stopReason) ? { stopReason: readString(metadataEvent.stopReason) } : {},
1227
+ ...readString(metadataEvent.cleanupReason) ? { cleanupReason: readString(metadataEvent.cleanupReason) } : {},
1228
+ ...readString(metadataEvent.source) ? { source: readString(metadataEvent.source) } : {}
1229
+ };
1230
+ }
1231
+ async function drainCoordinatorPendingEvents(ctx, opts) {
1232
+ const requestedNodeIds = opts?.nodeIds?.length ? new Set(opts.nodeIds) : null;
1233
+ const matchesCurrentMesh = (event) => readString(event?.meshId) === ctx.mesh.id;
1234
+ if (ctx.transport instanceof IpcTransport) {
1235
+ const surfacedEvents = [];
1236
+ try {
1237
+ surfacedEvents.push(
1238
+ ...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", { meshId: ctx.mesh.id })).filter(matchesCurrentMesh)
1239
+ );
1240
+ } catch {
1241
+ }
1242
+ for (const node of ctx.mesh.nodes) {
1243
+ if (!node.daemonId || isLocalControlPlaneNode(ctx, node)) continue;
1244
+ if (requestedNodeIds && !requestedNodeIds.has(node.id)) continue;
1245
+ try {
1246
+ const remoteEvents = normalizePendingMeshCoordinatorEvents(
1247
+ await ctx.transport.meshCommand(node.daemonId, "get_pending_mesh_events", { meshId: ctx.mesh.id })
1248
+ ).filter(matchesCurrentMesh);
1249
+ if (remoteEvents.length === 0) continue;
1250
+ for (const event of remoteEvents) {
1251
+ const payload = buildMeshForwardPayloadFromPendingEvent(event);
1252
+ if (!payload.event || !payload.meshId) continue;
1253
+ await ctx.transport.command("mesh_forward_event", payload);
1254
+ }
1255
+ } catch {
1256
+ }
1257
+ }
1258
+ try {
1259
+ surfacedEvents.push(
1260
+ ...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", { meshId: ctx.mesh.id })).filter(matchesCurrentMesh)
1261
+ );
1262
+ } catch {
1263
+ }
1264
+ return surfacedEvents;
1265
+ }
1266
+ if (isLocalTransport(ctx.transport)) {
1267
+ return (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id).filter(matchesCurrentMesh);
1268
+ }
1269
+ return [];
1270
+ }
1046
1271
  function isP2pTransportUnavailableError(error) {
1047
1272
  return (0, import_daemon_core.isP2pRelayTransportFailure)(error);
1048
1273
  }
@@ -1344,11 +1569,12 @@ async function meshStatus(ctx) {
1344
1569
  };
1345
1570
  try {
1346
1571
  if (!isLocalTransport(transport) && node.daemonId) {
1347
- const result = await transport.gitStatus(node.daemonId, node.workspace, false);
1572
+ const result = await transport.gitStatus(node.daemonId, node.workspace, false, true);
1348
1573
  const status = extractGitStatus(result);
1349
1574
  const uncommittedChanges = countUncommittedChanges(status);
1350
1575
  const dirty = isGitStatusDirty(status);
1351
1576
  entry.health = status?.isGitRepo ? dirty ? "dirty" : "online" : "degraded";
1577
+ assignFullGitSnapshot(entry, status);
1352
1578
  entry.branch = status?.branch;
1353
1579
  entry.isDirty = dirty;
1354
1580
  entry.uncommittedChanges = uncommittedChanges;
@@ -1362,6 +1588,7 @@ async function meshStatus(ctx) {
1362
1588
  const autoDiscover = node.policy?.autoDiscoverSubmodules !== false;
1363
1589
  const statusResult = await commandForNode(ctx, node, "git_status", {
1364
1590
  workspace: node.workspace,
1591
+ refreshUpstream: true,
1365
1592
  includeSubmodules: autoDiscover,
1366
1593
  submoduleIgnorePaths: node.policy?.submoduleIgnorePaths || void 0
1367
1594
  });
@@ -1369,6 +1596,7 @@ async function meshStatus(ctx) {
1369
1596
  const uncommittedChanges = countUncommittedChanges(status);
1370
1597
  const dirty = isGitStatusDirty(status);
1371
1598
  entry.health = status?.isGitRepo ? dirty ? "dirty" : "online" : "degraded";
1599
+ assignFullGitSnapshot(entry, status);
1372
1600
  entry.branch = status?.branch;
1373
1601
  entry.isDirty = dirty;
1374
1602
  entry.uncommittedChanges = uncommittedChanges;
@@ -1452,6 +1680,11 @@ async function meshStatus(ctx) {
1452
1680
  repoIdentity: mesh.repoIdentity,
1453
1681
  policy: mesh.policy,
1454
1682
  refreshedAt: (/* @__PURE__ */ new Date()).toISOString(),
1683
+ sourceOfTruth: {
1684
+ membership: "coordinator_daemon_live_mesh",
1685
+ currentStatus: "live_git_and_session_probes",
1686
+ historicalEvidenceOnly: ["recoveryHints", "ledgerSummary"]
1687
+ },
1455
1688
  nodes: results,
1456
1689
  branchConvergenceSummary: summarizeBranchConvergence(results)
1457
1690
  };
@@ -1460,13 +1693,7 @@ async function meshStatus(ctx) {
1460
1693
  } catch {
1461
1694
  }
1462
1695
  try {
1463
- let pendingEvents = [];
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
- }
1696
+ const pendingEvents = await drainCoordinatorPendingEvents(ctx);
1470
1697
  if (pendingEvents.length > 0) {
1471
1698
  response.pendingCoordinatorEvents = pendingEvents;
1472
1699
  }
@@ -1476,6 +1703,7 @@ async function meshStatus(ctx) {
1476
1703
  }
1477
1704
  async function meshTaskHistory(ctx, args) {
1478
1705
  const { mesh } = ctx;
1706
+ await drainCoordinatorPendingEvents(ctx);
1479
1707
  const tail = typeof args.tail === "number" && args.tail > 0 ? args.tail : 20;
1480
1708
  const kind = typeof args.kind === "string" && args.kind.trim() ? [args.kind.trim()] : void 0;
1481
1709
  const entries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail, kind });
@@ -1767,9 +1995,52 @@ async function meshSendTask(ctx, args) {
1767
1995
  }
1768
1996
  if (args.session_id && isLocalTransport(ctx.transport)) {
1769
1997
  const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id));
1998
+ let resolvedProviderType = cached?.providerType || "";
1999
+ if (!resolvedProviderType) {
2000
+ const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
2001
+ const sessions = extractStatusMetadataSessions(statusResult);
2002
+ const explicitSession = sessions.find((session) => readSessionRecordId(session) === args.session_id);
2003
+ if (!explicitSession) {
2004
+ return JSON.stringify({
2005
+ success: false,
2006
+ recoverable: true,
2007
+ code: "mesh_target_session_not_found",
2008
+ reason: "mesh_target_session_not_found",
2009
+ transport: "local_ipc",
2010
+ retryRecommended: true,
2011
+ nodeId: args.node_id,
2012
+ sessionId: args.session_id,
2013
+ error: `Local session '${args.session_id}' is not present in live status for node '${args.node_id}'.`,
2014
+ 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.`
2015
+ });
2016
+ }
2017
+ resolvedProviderType = resolveSessionProviderType(explicitSession);
2018
+ if (resolvedProviderType) {
2019
+ meshSessionProviderMetadata.set(meshSessionCacheKey(args.node_id, args.session_id), {
2020
+ providerType: resolvedProviderType,
2021
+ providerSessionId: readString(explicitSession?.providerSessionId) || void 0
2022
+ });
2023
+ }
2024
+ }
2025
+ if (!resolvedProviderType) {
2026
+ return JSON.stringify({
2027
+ success: false,
2028
+ recoverable: true,
2029
+ code: "mesh_target_session_provider_unknown",
2030
+ reason: "mesh_target_session_provider_unknown",
2031
+ transport: "local_ipc",
2032
+ retryRecommended: false,
2033
+ nodeId: args.node_id,
2034
+ sessionId: args.session_id,
2035
+ error: `Local session '${args.session_id}' is live but does not expose providerType/cliType, so agent_command cannot be routed safely.`,
2036
+ 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.`
2037
+ });
2038
+ }
1770
2039
  const dispatchResult = await commandForNode(ctx, node, "agent_command", {
1771
2040
  targetSessionId: args.session_id,
1772
- ...cached?.providerType ? { agentType: cached.providerType, cliType: cached.providerType, providerType: cached.providerType } : {},
2041
+ agentType: resolvedProviderType,
2042
+ cliType: resolvedProviderType,
2043
+ providerType: resolvedProviderType,
1773
2044
  action: "send_chat",
1774
2045
  message: args.message
1775
2046
  });
@@ -1787,7 +2058,7 @@ async function meshSendTask(ctx, args) {
1787
2058
  kind: "task_dispatched",
1788
2059
  nodeId: args.node_id,
1789
2060
  sessionId: args.session_id,
1790
- providerType: cached?.providerType,
2061
+ providerType: resolvedProviderType,
1791
2062
  payload: { message: args.message, via: "local_direct" }
1792
2063
  });
1793
2064
  } catch {
@@ -1802,7 +2073,7 @@ async function meshSendTask(ctx, args) {
1802
2073
  ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
1803
2074
  });
1804
2075
  }
1805
- const pendingEvents = isLocalTransport(ctx.transport) ? (0, import_daemon_core.drainPendingMeshCoordinatorEvents)() : [];
2076
+ const pendingEvents = isLocalTransport(ctx.transport) ? (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id) : [];
1806
2077
  const result = { success: true, nodeId: args.node_id, taskId: task.id, status: task.status };
1807
2078
  if (pendingEvents.length > 0) {
1808
2079
  result.pendingCoordinatorEvents = pendingEvents;
@@ -1823,6 +2094,9 @@ async function meshReadChat(ctx, args) {
1823
2094
  if (!node) {
1824
2095
  return JSON.stringify(buildMissingNodeReadChatRecovery(ctx, args), null, 2);
1825
2096
  }
2097
+ if (ctx.transport instanceof IpcTransport || isLocalTransport(ctx.transport)) {
2098
+ await drainCoordinatorPendingEvents(ctx, { nodeIds: [args.node_id] });
2099
+ }
1826
2100
  if (isLocalTransport(ctx.transport)) {
1827
2101
  const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id));
1828
2102
  const providerSessionId = typeof args.provider_session_id === "string" && args.provider_session_id.trim() ? args.provider_session_id.trim() : cached?.providerSessionId;
@@ -1925,6 +2199,10 @@ async function meshLaunchSession(ctx, args) {
1925
2199
  const coordinatorNode = resolveCoordinatorNode(ctx);
1926
2200
  const coordinatorDaemonId = coordinatorNode?.daemonId || ctx.localDaemonId;
1927
2201
  const spawnedSessionVisibility = readSpawnedSessionVisibility(ctx.mesh.policy);
2202
+ const isLocalNode = isLocalControlPlaneNode(ctx, node);
2203
+ if (node.daemonId && !isLocalNode && !coordinatorDaemonId) {
2204
+ return JSON.stringify(buildMissingCoordinatorDaemonIdFailure(ctx, node, resolvedProviderType), null, 2);
2205
+ }
1928
2206
  let result;
1929
2207
  try {
1930
2208
  result = await commandForNode(ctx, node, "launch_cli", {
@@ -1965,7 +2243,6 @@ async function meshLaunchSession(ctx, args) {
1965
2243
  });
1966
2244
  } catch {
1967
2245
  }
1968
- const isLocalNode = isLocalControlPlaneNode(ctx, node);
1969
2246
  if (ctx.transport instanceof IpcTransport && node.daemonId && !isLocalNode) {
1970
2247
  ctx.transport.meshCommand(node.daemonId, "trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
1971
2248
  });
@@ -1990,6 +2267,9 @@ async function meshLaunchSession(ctx, args) {
1990
2267
  const coordinatorNode = resolveCoordinatorNode(ctx);
1991
2268
  const coordinatorDaemonId = coordinatorNode?.daemonId || ctx.localDaemonId;
1992
2269
  const spawnedSessionVisibility = readSpawnedSessionVisibility(ctx.mesh.policy);
2270
+ if (!coordinatorDaemonId) {
2271
+ return JSON.stringify(buildMissingCoordinatorDaemonIdFailure(ctx, node, resolvedProviderType), null, 2);
2272
+ }
1993
2273
  try {
1994
2274
  const res = await ctx.transport.launch(node.daemonId, {
1995
2275
  type: resolvedProviderType,
@@ -2028,7 +2308,7 @@ async function meshGitStatus(ctx, args) {
2028
2308
  const submoduleIgnorePaths = node.policy?.submoduleIgnorePaths || [];
2029
2309
  try {
2030
2310
  if (!isLocalTransport(ctx.transport) && node.daemonId) {
2031
- const result = await ctx.transport.gitStatus(node.daemonId, node.workspace, true);
2311
+ const result = await ctx.transport.gitStatus(node.daemonId, node.workspace, true, true);
2032
2312
  return JSON.stringify({
2033
2313
  nodeId: args.node_id,
2034
2314
  workspace: node.workspace,
@@ -2040,6 +2320,7 @@ async function meshGitStatus(ctx, args) {
2040
2320
  } else if (isLocalTransport(ctx.transport)) {
2041
2321
  const statusResult = await commandForNode(ctx, node, "git_status", {
2042
2322
  workspace: node.workspace,
2323
+ refreshUpstream: true,
2043
2324
  includeSubmodules: autoDiscoverSubmodules,
2044
2325
  submoduleIgnorePaths: submoduleIgnorePaths.length > 0 ? submoduleIgnorePaths : void 0
2045
2326
  });
@@ -2154,6 +2435,7 @@ async function meshCloneNode(ctx, args) {
2154
2435
  if (existingIndex >= 0) ctx.mesh.nodes[existingIndex] = clonePayload.node;
2155
2436
  else ctx.mesh.nodes.push(clonePayload.node);
2156
2437
  ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2438
+ await syncCoordinatorDaemonMeshCache(ctx);
2157
2439
  }
2158
2440
  return JSON.stringify(result, null, 2);
2159
2441
  } else if (!isLocalTransport(ctx.transport) && sourceNode.daemonId) {
@@ -2171,6 +2453,7 @@ async function meshCloneNode(ctx, args) {
2171
2453
  if (existingIndex >= 0) ctx.mesh.nodes[existingIndex] = clonePayload.node;
2172
2454
  else ctx.mesh.nodes.push(clonePayload.node);
2173
2455
  ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2456
+ await syncCoordinatorDaemonMeshCache(ctx);
2174
2457
  }
2175
2458
  return JSON.stringify(res, null, 2);
2176
2459
  } catch (e) {
@@ -2511,8 +2794,8 @@ var CloudTransport = class {
2511
2794
  if (!res.ok) throw new Error(`Approve failed: ${res.status}`);
2512
2795
  return res.json();
2513
2796
  }
2514
- async gitStatus(daemonId, workspace, includeDiff = true) {
2515
- const params = new URLSearchParams({ workspace, includeDiff: String(includeDiff) });
2797
+ async gitStatus(daemonId, workspace, includeDiff = true, refreshUpstream = false) {
2798
+ const params = new URLSearchParams({ workspace, includeDiff: String(includeDiff), refreshUpstream: String(refreshUpstream) });
2516
2799
  const res = await fetch(
2517
2800
  `${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/git-status?${params}`,
2518
2801
  { headers: this.headers() }
@@ -2907,6 +3190,22 @@ function formatChatResult(result, sessionId, format, limit = 50, compact = false
2907
3190
  }))
2908
3191
  }, null, 2);
2909
3192
  }
3193
+ if ((format === "text" || format === void 0) && compact && compactPayload) {
3194
+ const lines2 = outputMessages.slice(-limit).map((m) => {
3195
+ const role = m.role === "user" ? "User" : m.role === "assistant" ? "Agent" : m.role;
3196
+ const content = messageContent(m);
3197
+ const truncated = content.length > 500 ? `${content.slice(0, 500)}\u2026` : content;
3198
+ return `[${role}] ${truncated}`;
3199
+ });
3200
+ if (compactPayload.summary) {
3201
+ const truncatedSummary = compactPayload.summary.length > 500 ? `${compactPayload.summary.slice(0, 500)}\u2026` : compactPayload.summary;
3202
+ lines2.push(`[Summary] ${truncatedSummary}`);
3203
+ }
3204
+ if (result?.pollingAdvisory) {
3205
+ lines2.push(`Advisory: ${result.pollingAdvisory.message}`);
3206
+ }
3207
+ return lines2.length > 0 ? lines2.join("\n\n") : "No messages in chat.";
3208
+ }
2910
3209
  if (outputMessages.length === 0) {
2911
3210
  return result?.pollingAdvisory ? `No messages in chat.
2912
3211