adhdev 0.9.82-rc.3 → 0.9.82-rc.30

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.3",
3
+ "version": "0.9.82-rc.30",
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.3",
50
+ "@adhdev/daemon-core": "0.9.82-rc.30",
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();
@@ -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 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;
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 = 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
+ }
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
- 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
- }
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
- ...cached?.providerType ? { agentType: cached.providerType, cliType: cached.providerType, providerType: cached.providerType } : {},
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: cached?.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