@adhdev/daemon-standalone 0.9.82-rc.5 → 0.9.82-rc.51

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.
@@ -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,
@@ -235,6 +245,11 @@ var meshSessionProviderMetadata = /* @__PURE__ */ new Map();
235
245
  function readString(value) {
236
246
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
237
247
  }
248
+ function findNode(mesh, nodeId) {
249
+ const node = mesh.nodes.find((n) => n.id === nodeId);
250
+ if (!node) throw new Error(`Node '${nodeId}' is not a member of mesh '${mesh.name}'`);
251
+ return node;
252
+ }
238
253
  var DUPLICATE_DISPATCH_WINDOW_MS = 6e4;
239
254
  var STALE_ASSIGNED_QUEUE_MS = 30 * 6e4;
240
255
  var OLD_HISTORICAL_QUEUE_RECORD_MS = 7 * 24 * 60 * 6e4;
@@ -246,15 +261,24 @@ async function refreshMeshFromDaemon(ctx) {
246
261
  const result = await ctx.transport.command("get_mesh", { meshId: ctx.mesh.id });
247
262
  if (!result?.success || !Array.isArray(result.mesh?.nodes)) return;
248
263
  const refreshedNodes = result.mesh.nodes.filter((n) => n?.id).map((n) => n);
249
- if (!refreshedNodes.length) return;
250
264
  ctx.mesh.nodes.splice(0, ctx.mesh.nodes.length, ...refreshedNodes);
251
265
  ctx.mesh.updatedAt = result.mesh.updatedAt ?? ctx.mesh.updatedAt;
252
266
  } catch {
253
267
  }
254
268
  }
269
+ async function syncCoordinatorDaemonMeshCache(ctx) {
270
+ if (!(ctx.transport instanceof IpcTransport)) return;
271
+ try {
272
+ await ctx.transport.command("get_mesh", {
273
+ meshId: ctx.mesh.id,
274
+ inlineMesh: ctx.mesh
275
+ });
276
+ } catch {
277
+ }
278
+ }
255
279
  async function findNodeWithRefresh(ctx, nodeId) {
256
280
  const hit = ctx.mesh.nodes.find((n) => n.id === nodeId);
257
- if (hit) return hit;
281
+ if (hit && !hit.isLocalWorktree) return hit;
258
282
  await refreshMeshFromDaemon(ctx);
259
283
  const refreshed = ctx.mesh.nodes.find((n) => n.id === nodeId);
260
284
  if (!refreshed) throw new Error(`Node '${nodeId}' is not a member of mesh '${ctx.mesh.name}'`);
@@ -262,7 +286,7 @@ async function findNodeWithRefresh(ctx, nodeId) {
262
286
  }
263
287
  async function findOptionalNodeWithRefresh(ctx, nodeId) {
264
288
  const hit = ctx.mesh.nodes.find((n) => n.id === nodeId);
265
- if (hit) return hit;
289
+ if (hit && !hit.isLocalWorktree) return hit;
266
290
  await refreshMeshFromDaemon(ctx);
267
291
  return ctx.mesh.nodes.find((n) => n.id === nodeId) ?? null;
268
292
  }
@@ -314,9 +338,26 @@ function buildMissingNodeReadChatRecovery(ctx, args) {
314
338
  readDebugLocator: readString(lastTerminal?.payload?.readDebugLocator) || readString(lastTerminal?.payload?.debugBundlePath)
315
339
  };
316
340
  if (finalSummary) {
341
+ if (args.compact === true) {
342
+ return {
343
+ ...compactChatPayload({
344
+ success: true,
345
+ status: "idle",
346
+ providerSessionId,
347
+ summary: finalSummary,
348
+ messages: [{ role: "assistant", content: finalSummary, isHistorical: true }]
349
+ }, {
350
+ nodeId: args.node_id,
351
+ sessionId: args.session_id,
352
+ limit: args.tail ?? 10
353
+ }),
354
+ recoveredFromLedger: true,
355
+ ledger
356
+ };
357
+ }
317
358
  return {
318
359
  success: true,
319
- compact: args.compact === true,
360
+ compact: false,
320
361
  recoveredFromLedger: true,
321
362
  nodeId: args.node_id,
322
363
  sessionId: args.session_id,
@@ -368,6 +409,14 @@ function buildMissingNodeReadChatRecovery(ctx, args) {
368
409
  function readSessionRecordId(session) {
369
410
  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
411
  }
412
+ function extractStatusMetadataSessions(value) {
413
+ const payload = unwrapCommandPayload(value);
414
+ const status = payload?.status && typeof payload.status === "object" ? payload.status : payload;
415
+ return Array.isArray(status?.sessions) ? status.sessions : [];
416
+ }
417
+ function resolveSessionProviderType(session) {
418
+ return readString(session?.providerType) || readString(session?.cliType) || readString(session?.agentType) || "";
419
+ }
371
420
  function addSessionRecord(target, session) {
372
421
  if (!session || typeof session !== "object" || isTerminalSessionRecord(session)) return;
373
422
  const sessionId = readSessionRecordId(session);
@@ -580,22 +629,59 @@ function isIdleSessionRecord(session) {
580
629
  const chatStatus = typeof session?.activeChat?.status === "string" ? session.activeChat.status.toLowerCase() : "";
581
630
  return status === "idle" || chatStatus === "waiting_input";
582
631
  }
632
+ function isMeshOwnedDelegateSession(session, meshId, nodeId) {
633
+ const settings = session?.settings;
634
+ const sessionMeshId = typeof settings?.meshNodeFor === "string" ? settings.meshNodeFor.trim() : "";
635
+ const coordinatorDaemonId = typeof settings?.meshCoordinatorDaemonId === "string" ? settings.meshCoordinatorDaemonId.trim() : "";
636
+ const sessionNodeId = typeof settings?.meshNodeId === "string" ? settings.meshNodeId.trim() : "";
637
+ if (sessionMeshId !== meshId || !coordinatorDaemonId) return false;
638
+ return !sessionNodeId || sessionNodeId === nodeId;
639
+ }
583
640
  function chooseDispatchableSession(sessions, providerType, meshId, nodeId) {
584
641
  const live = sessions.filter((session) => !isTerminalSessionRecord(session));
585
642
  const matchingProvider = (session) => !providerType || session?.providerType === providerType || session?.cliType === providerType;
586
- const isMeshOwnedDelegateSession = (session) => {
587
- const settings = session?.settings;
588
- const sessionMeshId = typeof settings?.meshNodeFor === "string" ? settings.meshNodeFor.trim() : "";
589
- const coordinatorDaemonId = typeof settings?.meshCoordinatorDaemonId === "string" ? settings.meshCoordinatorDaemonId.trim() : "";
590
- const sessionNodeId = typeof settings?.meshNodeId === "string" ? settings.meshNodeId.trim() : "";
591
- if (sessionMeshId !== meshId || !coordinatorDaemonId) return false;
592
- return !sessionNodeId || sessionNodeId === nodeId;
593
- };
594
643
  const meshSessions = live.filter(
595
- (session) => isMeshOwnedDelegateSession(session)
644
+ (session) => isMeshOwnedDelegateSession(session, meshId, nodeId)
596
645
  );
597
646
  return meshSessions.find((session) => isIdleSessionRecord(session) && matchingProvider(session)) || meshSessions.find(matchingProvider) || void 0;
598
647
  }
648
+ function buildRelayUnsafeRemoteSessionFailure(ctx, node, sessionId, providerType) {
649
+ return {
650
+ success: false,
651
+ recoverable: true,
652
+ code: "mesh_delegate_session_missing_relay_metadata",
653
+ reason: "mesh_delegate_session_missing_relay_metadata",
654
+ transport: "mesh_transport",
655
+ retryRecommended: true,
656
+ meshId: ctx.mesh.id,
657
+ nodeId: node.id,
658
+ daemonId: node.daemonId,
659
+ workspace: node.workspace,
660
+ sessionId,
661
+ ...providerType ? { resolvedProviderType: providerType } : {},
662
+ 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.`,
663
+ 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.`,
664
+ noFallbackReason: "Blindly reusing a remote session without mesh relay metadata would silently drop task_completed / generating_completed events."
665
+ };
666
+ }
667
+ function buildMissingCoordinatorDaemonIdFailure(ctx, node, providerType) {
668
+ return {
669
+ success: false,
670
+ recoverable: true,
671
+ code: "mesh_coordinator_daemon_unknown",
672
+ reason: "mesh_coordinator_daemon_unknown",
673
+ transport: "mesh_transport",
674
+ retryRecommended: true,
675
+ meshId: ctx.mesh.id,
676
+ nodeId: node.id,
677
+ daemonId: node.daemonId,
678
+ workspace: node.workspace,
679
+ ...providerType ? { resolvedProviderType: providerType } : {},
680
+ 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.`,
681
+ 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.",
682
+ noFallbackReason: "Launching without meshCoordinatorDaemonId would create a worker session that can finish work but cannot emit task_completed / generating_completed back to the coordinator."
683
+ };
684
+ }
599
685
  function findNestedPayload(value, predicate) {
600
686
  const seen = /* @__PURE__ */ new Set();
601
687
  const stack = [{ payload: value, depth: 0 }];
@@ -623,12 +709,16 @@ function extractGitDiff(value) {
623
709
  }
624
710
  function extractSubmodules(value, ignorePaths) {
625
711
  const payload = unwrapCommandPayload(value);
626
- const subs = payload?.submodules ?? value?.submodules;
712
+ const subs = payload?.status?.submodules ?? payload?.submodules ?? value?.status?.submodules ?? value?.submodules;
627
713
  if (!Array.isArray(subs)) return void 0;
628
714
  if (ignorePaths.length === 0) return subs;
629
715
  const ignoreSet = new Set(ignorePaths);
630
716
  return subs.filter((s) => s?.path && !ignoreSet.has(s.path));
631
717
  }
718
+ function assignFullGitSnapshot(entry, status) {
719
+ if (!status || typeof status !== "object" || Array.isArray(status)) return;
720
+ entry.git = status;
721
+ }
632
722
  function extractLaunchPayload(value) {
633
723
  return findNestedPayload(value, (payload) => Boolean(payload?.sessionId || payload?.id || payload?.runtimeSessionId));
634
724
  }
@@ -753,20 +843,63 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
753
843
  let sessionId = args.session_id?.trim() || "";
754
844
  const providerPriorityList = Array.isArray(node.policy?.providerPriority) ? node.policy.providerPriority : [];
755
845
  let resolvedProviderType = args.providerType?.trim() || providerPriorityList[0] || "";
756
- if (!sessionId) {
846
+ if (!sessionId || args.session_id) {
757
847
  try {
758
848
  const relayResult = await transport.meshCommand(daemonId, "get_status_metadata", {});
759
- const innerResult = relayResult?.result ?? relayResult;
760
- const statusObj = innerResult?.status ?? innerResult;
761
- const sessions = Array.isArray(statusObj?.sessions) ? statusObj.sessions : [];
762
- const targetSession = chooseDispatchableSession(sessions, resolvedProviderType, ctx.mesh.id, node.id);
763
- if (targetSession?.id || targetSession?.sessionId) {
764
- sessionId = targetSession.id || targetSession.sessionId;
849
+ const sessions = extractStatusMetadataSessions(relayResult);
850
+ if (sessionId) {
851
+ const explicitSession = sessions.find((session) => readSessionRecordId(session) === sessionId);
852
+ if (!explicitSession) {
853
+ return {
854
+ success: false,
855
+ recoverable: true,
856
+ code: "mesh_target_session_not_found",
857
+ reason: "mesh_target_session_not_found",
858
+ transport: "mesh_transport",
859
+ retryRecommended: true,
860
+ meshId: ctx.mesh.id,
861
+ nodeId: node.id,
862
+ daemonId,
863
+ workspace: node.workspace,
864
+ sessionId,
865
+ ...resolvedProviderType ? { resolvedProviderType } : {},
866
+ error: `Remote session '${sessionId}' is not present in the live status for node '${node.id}'.`,
867
+ 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.`
868
+ };
869
+ }
870
+ if (!isMeshOwnedDelegateSession(explicitSession, ctx.mesh.id, node.id)) {
871
+ return buildRelayUnsafeRemoteSessionFailure(
872
+ ctx,
873
+ node,
874
+ sessionId,
875
+ resolvedProviderType || resolveSessionProviderType(explicitSession) || void 0
876
+ );
877
+ }
765
878
  if (!resolvedProviderType) {
766
- resolvedProviderType = targetSession.providerType || targetSession.cliType || "";
879
+ resolvedProviderType = resolveSessionProviderType(explicitSession);
880
+ }
881
+ } else {
882
+ const targetSession = chooseDispatchableSession(sessions, resolvedProviderType, ctx.mesh.id, node.id);
883
+ if (targetSession?.id || targetSession?.sessionId) {
884
+ sessionId = targetSession.id || targetSession.sessionId;
885
+ if (!resolvedProviderType) {
886
+ resolvedProviderType = resolveSessionProviderType(targetSession);
887
+ }
767
888
  }
768
889
  }
769
890
  } catch (e) {
891
+ if (sessionId) {
892
+ return {
893
+ ...buildCoordinatorP2pRelayFailure(e, {
894
+ command: "get_status_metadata",
895
+ targetDaemonId: daemonId,
896
+ nodeId: node.id,
897
+ sessionId
898
+ }),
899
+ success: false,
900
+ error: `Cannot verify remote session '${sessionId}' before dispatch: ${e?.message || String(e)}`
901
+ };
902
+ }
770
903
  }
771
904
  }
772
905
  if (!resolvedProviderType) {
@@ -880,6 +1013,10 @@ function summarizeRelatedRepoStatus(repo, status) {
880
1013
  workspace: repo.workspace,
881
1014
  isGitRepo: status?.isGitRepo === true,
882
1015
  branch: status?.branch ?? null,
1016
+ upstream: status?.upstream ?? null,
1017
+ upstreamStatus: typeof status?.upstreamStatus === "string" ? status.upstreamStatus : status?.upstream ? "unchecked" : "no_upstream",
1018
+ upstreamFetchedAt: Number.isFinite(Number(status?.upstreamFetchedAt)) ? Number(status.upstreamFetchedAt) : null,
1019
+ upstreamFetchError: typeof status?.upstreamFetchError === "string" ? status.upstreamFetchError : null,
883
1020
  ahead: Number.isFinite(Number(status?.ahead)) ? Number(status.ahead) : 0,
884
1021
  behind: Number.isFinite(Number(status?.behind)) ? Number(status.behind) : 0,
885
1022
  dirty,
@@ -896,7 +1033,7 @@ async function collectRelatedRepoStatuses(ctx, node) {
896
1033
  const results = [];
897
1034
  for (const repo of relatedRepos) {
898
1035
  try {
899
- 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 });
1036
+ 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 });
900
1037
  const status = extractGitStatus(statusResult);
901
1038
  results.push(summarizeRelatedRepoStatus(repo, status));
902
1039
  } catch (e) {
@@ -944,11 +1081,13 @@ function buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges) {
944
1081
  const ahead = readNumeric(status?.ahead);
945
1082
  const behind = readNumeric(status?.behind);
946
1083
  const upstream = readString(status?.upstream) ?? null;
1084
+ const upstreamStatus = readString(status?.upstreamStatus) ?? (upstream ? "unchecked" : "no_upstream");
947
1085
  const hasConflicts = status?.hasConflicts === true || Array.isArray(status?.conflictFiles) && status.conflictFiles.length > 0;
948
1086
  const base = {
949
1087
  defaultBranch,
950
1088
  branch,
951
1089
  upstream,
1090
+ upstreamStatus,
952
1091
  ahead,
953
1092
  behind,
954
1093
  isWorktree: node.isLocalWorktree === true,
@@ -982,6 +1121,15 @@ function buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges) {
982
1121
  };
983
1122
  }
984
1123
  if (branch === defaultBranch) {
1124
+ if (upstream && upstreamStatus !== "fresh") {
1125
+ return {
1126
+ ...base,
1127
+ status: "blocked_review",
1128
+ needsConvergence: true,
1129
+ reason: "default_branch_upstream_unverified",
1130
+ nextStep: `Refresh ${defaultBranch}'s upstream refs or resolve the fetch failure before declaring convergence complete for node '${node.id}'.`
1131
+ };
1132
+ }
985
1133
  if (ahead > 0 || behind > 0) {
986
1134
  return {
987
1135
  ...base,
@@ -1008,6 +1156,15 @@ function buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges) {
1008
1156
  nextStep: `Run mesh_refine_node(node_id: "${node.id}") or explicitly classify this worktree as blocked_review/not_mergeable before ending the task.`
1009
1157
  };
1010
1158
  }
1159
+ if (upstream && upstreamStatus !== "fresh") {
1160
+ return {
1161
+ ...base,
1162
+ status: "blocked_review",
1163
+ needsConvergence: true,
1164
+ reason: "feature_branch_upstream_unverified",
1165
+ nextStep: `Refresh branch '${branch}' upstream refs or resolve the fetch failure before deciding whether it is ready to merge into ${defaultBranch}.`
1166
+ };
1167
+ }
1011
1168
  if (!upstream || ahead > 0 || behind > 0) {
1012
1169
  return {
1013
1170
  ...base,
@@ -1051,6 +1208,71 @@ async function commandForNode(ctx, node, command, args = {}) {
1051
1208
  }
1052
1209
  throw new Error(`Command '${command}' requires daemon IPC/local transport for node '${node.id}'`);
1053
1210
  }
1211
+ function normalizePendingMeshCoordinatorEvents(value) {
1212
+ const payload = unwrapCommandPayload(value);
1213
+ const events = Array.isArray(payload?.events) ? payload.events : Array.isArray(value?.events) ? value.events : [];
1214
+ return events.filter((event) => event && typeof event === "object");
1215
+ }
1216
+ function buildMeshForwardPayloadFromPendingEvent(event) {
1217
+ const metadataEvent = event?.metadataEvent && typeof event.metadataEvent === "object" ? event.metadataEvent : {};
1218
+ return {
1219
+ event: readString(event?.event),
1220
+ meshId: readString(event?.meshId),
1221
+ nodeId: readString(event?.nodeId) || readString(metadataEvent.meshNodeId),
1222
+ workspace: readString(event?.workspace) || readString(metadataEvent.workspace),
1223
+ targetSessionId: readString(metadataEvent.targetSessionId) || readString(metadataEvent.sessionId) || readString(metadataEvent.instanceId),
1224
+ providerType: readString(metadataEvent.providerType),
1225
+ providerSessionId: readString(metadataEvent.providerSessionId),
1226
+ finalSummary: readString(metadataEvent.finalSummary) || readString(metadataEvent.summary),
1227
+ ...metadataEvent.intentional === true ? { intentional: true } : {},
1228
+ ...metadataEvent.intentionalStop === true ? { intentionalStop: true } : {},
1229
+ ...metadataEvent.operatorCleanup === true ? { operatorCleanup: true } : {},
1230
+ ...readString(metadataEvent.reason) ? { reason: readString(metadataEvent.reason) } : {},
1231
+ ...readString(metadataEvent.stopReason) ? { stopReason: readString(metadataEvent.stopReason) } : {},
1232
+ ...readString(metadataEvent.cleanupReason) ? { cleanupReason: readString(metadataEvent.cleanupReason) } : {},
1233
+ ...readString(metadataEvent.source) ? { source: readString(metadataEvent.source) } : {}
1234
+ };
1235
+ }
1236
+ async function drainCoordinatorPendingEvents(ctx, opts) {
1237
+ const requestedNodeIds = opts?.nodeIds?.length ? new Set(opts.nodeIds) : null;
1238
+ const matchesCurrentMesh = (event) => readString(event?.meshId) === ctx.mesh.id;
1239
+ if (ctx.transport instanceof IpcTransport) {
1240
+ const surfacedEvents = [];
1241
+ try {
1242
+ surfacedEvents.push(
1243
+ ...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", { meshId: ctx.mesh.id })).filter(matchesCurrentMesh)
1244
+ );
1245
+ } catch {
1246
+ }
1247
+ for (const node of ctx.mesh.nodes) {
1248
+ if (!node.daemonId || isLocalControlPlaneNode(ctx, node)) continue;
1249
+ if (requestedNodeIds && !requestedNodeIds.has(node.id)) continue;
1250
+ try {
1251
+ const remoteEvents = normalizePendingMeshCoordinatorEvents(
1252
+ await ctx.transport.meshCommand(node.daemonId, "get_pending_mesh_events", { meshId: ctx.mesh.id })
1253
+ ).filter(matchesCurrentMesh);
1254
+ if (remoteEvents.length === 0) continue;
1255
+ for (const event of remoteEvents) {
1256
+ const payload = buildMeshForwardPayloadFromPendingEvent(event);
1257
+ if (!payload.event || !payload.meshId) continue;
1258
+ await ctx.transport.command("mesh_forward_event", payload);
1259
+ }
1260
+ } catch {
1261
+ }
1262
+ }
1263
+ try {
1264
+ surfacedEvents.push(
1265
+ ...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", { meshId: ctx.mesh.id })).filter(matchesCurrentMesh)
1266
+ );
1267
+ } catch {
1268
+ }
1269
+ return surfacedEvents;
1270
+ }
1271
+ if (isLocalTransport(ctx.transport)) {
1272
+ return (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id).filter(matchesCurrentMesh);
1273
+ }
1274
+ return [];
1275
+ }
1054
1276
  function isP2pTransportUnavailableError(error) {
1055
1277
  return (0, import_daemon_core.isP2pRelayTransportFailure)(error);
1056
1278
  }
@@ -1318,6 +1540,43 @@ var MESH_REFINE_NODE_TOOL = {
1318
1540
  required: ["node_id"]
1319
1541
  }
1320
1542
  };
1543
+ var MESH_REFINE_CONFIG_SCHEMA_TOOL = {
1544
+ name: "mesh_refine_config_schema",
1545
+ description: "Return the Repo Mesh Refinery config JSON schema and supported repo-local config locations. This is the validation source of truth; heuristic command detection is suggestions-only.",
1546
+ inputSchema: { type: "object", properties: {} }
1547
+ };
1548
+ var MESH_VALIDATE_REFINE_CONFIG_TOOL = {
1549
+ name: "mesh_validate_refine_config",
1550
+ description: "Validate the repo mesh/refine config for a node/workspace without running validation commands or merging.",
1551
+ inputSchema: {
1552
+ type: "object",
1553
+ properties: {
1554
+ node_id: { type: "string", description: "Optional node/workspace whose refine config should be loaded. Defaults to the first mesh node." },
1555
+ config: { type: "object", description: "Optional inline config object to validate instead of loading from the repo." }
1556
+ }
1557
+ }
1558
+ };
1559
+ var MESH_SUGGEST_REFINE_CONFIG_TOOL = {
1560
+ name: "mesh_suggest_refine_config",
1561
+ description: "Suggest a repo mesh/refine config scaffold from project context/package scripts. Suggestions are never executed until saved as explicit refine config.",
1562
+ inputSchema: {
1563
+ type: "object",
1564
+ properties: {
1565
+ node_id: { type: "string", description: "Optional node/workspace used for suggestions. Defaults to the first mesh node." }
1566
+ }
1567
+ }
1568
+ };
1569
+ var MESH_REFINE_PLAN_TOOL = {
1570
+ name: "mesh_refine_plan",
1571
+ description: "Dry-run Refinery plan for a worktree node: reports config source, validation commands, suggestions/unavailable reason, and merge/cleanup intent without executing validation or git merge.",
1572
+ inputSchema: {
1573
+ type: "object",
1574
+ properties: {
1575
+ node_id: { type: "string", description: "Node ID of the worktree node to plan." }
1576
+ },
1577
+ required: ["node_id"]
1578
+ }
1579
+ };
1321
1580
  var ALL_MESH_TOOLS = [
1322
1581
  MESH_STATUS_TOOL,
1323
1582
  MESH_LIST_NODES_TOOL,
@@ -1335,6 +1594,10 @@ var ALL_MESH_TOOLS = [
1335
1594
  MESH_CLONE_NODE_TOOL,
1336
1595
  MESH_REMOVE_NODE_TOOL,
1337
1596
  MESH_REFINE_NODE_TOOL,
1597
+ MESH_REFINE_CONFIG_SCHEMA_TOOL,
1598
+ MESH_VALIDATE_REFINE_CONFIG_TOOL,
1599
+ MESH_SUGGEST_REFINE_CONFIG_TOOL,
1600
+ MESH_REFINE_PLAN_TOOL,
1338
1601
  MESH_CLEANUP_SESSIONS_TOOL,
1339
1602
  MESH_TASK_HISTORY_TOOL,
1340
1603
  MESH_RECONCILE_LEDGER_TOOL
@@ -1352,11 +1615,12 @@ async function meshStatus(ctx) {
1352
1615
  };
1353
1616
  try {
1354
1617
  if (!isLocalTransport(transport) && node.daemonId) {
1355
- const result = await transport.gitStatus(node.daemonId, node.workspace, false);
1618
+ const result = await transport.gitStatus(node.daemonId, node.workspace, false, true);
1356
1619
  const status = extractGitStatus(result);
1357
1620
  const uncommittedChanges = countUncommittedChanges(status);
1358
1621
  const dirty = isGitStatusDirty(status);
1359
1622
  entry.health = status?.isGitRepo ? dirty ? "dirty" : "online" : "degraded";
1623
+ assignFullGitSnapshot(entry, status);
1360
1624
  entry.branch = status?.branch;
1361
1625
  entry.isDirty = dirty;
1362
1626
  entry.uncommittedChanges = uncommittedChanges;
@@ -1370,6 +1634,7 @@ async function meshStatus(ctx) {
1370
1634
  const autoDiscover = node.policy?.autoDiscoverSubmodules !== false;
1371
1635
  const statusResult = await commandForNode(ctx, node, "git_status", {
1372
1636
  workspace: node.workspace,
1637
+ refreshUpstream: true,
1373
1638
  includeSubmodules: autoDiscover,
1374
1639
  submoduleIgnorePaths: node.policy?.submoduleIgnorePaths || void 0
1375
1640
  });
@@ -1377,6 +1642,7 @@ async function meshStatus(ctx) {
1377
1642
  const uncommittedChanges = countUncommittedChanges(status);
1378
1643
  const dirty = isGitStatusDirty(status);
1379
1644
  entry.health = status?.isGitRepo ? dirty ? "dirty" : "online" : "degraded";
1645
+ assignFullGitSnapshot(entry, status);
1380
1646
  entry.branch = status?.branch;
1381
1647
  entry.isDirty = dirty;
1382
1648
  entry.uncommittedChanges = uncommittedChanges;
@@ -1460,6 +1726,11 @@ async function meshStatus(ctx) {
1460
1726
  repoIdentity: mesh.repoIdentity,
1461
1727
  policy: mesh.policy,
1462
1728
  refreshedAt: (/* @__PURE__ */ new Date()).toISOString(),
1729
+ sourceOfTruth: {
1730
+ membership: "coordinator_daemon_live_mesh",
1731
+ currentStatus: "live_git_and_session_probes",
1732
+ historicalEvidenceOnly: ["recoveryHints", "ledgerSummary"]
1733
+ },
1463
1734
  nodes: results,
1464
1735
  branchConvergenceSummary: summarizeBranchConvergence(results)
1465
1736
  };
@@ -1468,13 +1739,7 @@ async function meshStatus(ctx) {
1468
1739
  } catch {
1469
1740
  }
1470
1741
  try {
1471
- let pendingEvents = [];
1472
- if (ctx.transport instanceof IpcTransport) {
1473
- const eventsResult = await ctx.transport.command("get_pending_mesh_events", {});
1474
- pendingEvents = Array.isArray(eventsResult?.events) ? eventsResult.events : [];
1475
- } else if (isLocalTransport(ctx.transport)) {
1476
- pendingEvents = (0, import_daemon_core.drainPendingMeshCoordinatorEvents)();
1477
- }
1742
+ const pendingEvents = await drainCoordinatorPendingEvents(ctx);
1478
1743
  if (pendingEvents.length > 0) {
1479
1744
  response.pendingCoordinatorEvents = pendingEvents;
1480
1745
  }
@@ -1484,6 +1749,7 @@ async function meshStatus(ctx) {
1484
1749
  }
1485
1750
  async function meshTaskHistory(ctx, args) {
1486
1751
  const { mesh } = ctx;
1752
+ await drainCoordinatorPendingEvents(ctx);
1487
1753
  const tail = typeof args.tail === "number" && args.tail > 0 ? args.tail : 20;
1488
1754
  const kind = typeof args.kind === "string" && args.kind.trim() ? [args.kind.trim()] : void 0;
1489
1755
  const entries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail, kind });
@@ -1775,9 +2041,52 @@ async function meshSendTask(ctx, args) {
1775
2041
  }
1776
2042
  if (args.session_id && isLocalTransport(ctx.transport)) {
1777
2043
  const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id));
2044
+ let resolvedProviderType = cached?.providerType || "";
2045
+ if (!resolvedProviderType) {
2046
+ const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
2047
+ const sessions = extractStatusMetadataSessions(statusResult);
2048
+ const explicitSession = sessions.find((session) => readSessionRecordId(session) === args.session_id);
2049
+ if (!explicitSession) {
2050
+ return JSON.stringify({
2051
+ success: false,
2052
+ recoverable: true,
2053
+ code: "mesh_target_session_not_found",
2054
+ reason: "mesh_target_session_not_found",
2055
+ transport: "local_ipc",
2056
+ retryRecommended: true,
2057
+ nodeId: args.node_id,
2058
+ sessionId: args.session_id,
2059
+ error: `Local session '${args.session_id}' is not present in live status for node '${args.node_id}'.`,
2060
+ 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.`
2061
+ });
2062
+ }
2063
+ resolvedProviderType = resolveSessionProviderType(explicitSession);
2064
+ if (resolvedProviderType) {
2065
+ meshSessionProviderMetadata.set(meshSessionCacheKey(args.node_id, args.session_id), {
2066
+ providerType: resolvedProviderType,
2067
+ providerSessionId: readString(explicitSession?.providerSessionId) || void 0
2068
+ });
2069
+ }
2070
+ }
2071
+ if (!resolvedProviderType) {
2072
+ return JSON.stringify({
2073
+ success: false,
2074
+ recoverable: true,
2075
+ code: "mesh_target_session_provider_unknown",
2076
+ reason: "mesh_target_session_provider_unknown",
2077
+ transport: "local_ipc",
2078
+ retryRecommended: false,
2079
+ nodeId: args.node_id,
2080
+ sessionId: args.session_id,
2081
+ error: `Local session '${args.session_id}' is live but does not expose providerType/cliType, so agent_command cannot be routed safely.`,
2082
+ 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.`
2083
+ });
2084
+ }
1778
2085
  const dispatchResult = await commandForNode(ctx, node, "agent_command", {
1779
2086
  targetSessionId: args.session_id,
1780
- ...cached?.providerType ? { agentType: cached.providerType, cliType: cached.providerType, providerType: cached.providerType } : {},
2087
+ agentType: resolvedProviderType,
2088
+ cliType: resolvedProviderType,
2089
+ providerType: resolvedProviderType,
1781
2090
  action: "send_chat",
1782
2091
  message: args.message
1783
2092
  });
@@ -1795,7 +2104,7 @@ async function meshSendTask(ctx, args) {
1795
2104
  kind: "task_dispatched",
1796
2105
  nodeId: args.node_id,
1797
2106
  sessionId: args.session_id,
1798
- providerType: cached?.providerType,
2107
+ providerType: resolvedProviderType,
1799
2108
  payload: { message: args.message, via: "local_direct" }
1800
2109
  });
1801
2110
  } catch {
@@ -1810,7 +2119,7 @@ async function meshSendTask(ctx, args) {
1810
2119
  ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
1811
2120
  });
1812
2121
  }
1813
- const pendingEvents = isLocalTransport(ctx.transport) ? (0, import_daemon_core.drainPendingMeshCoordinatorEvents)() : [];
2122
+ const pendingEvents = isLocalTransport(ctx.transport) ? (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id) : [];
1814
2123
  const result = { success: true, nodeId: args.node_id, taskId: task.id, status: task.status };
1815
2124
  if (pendingEvents.length > 0) {
1816
2125
  result.pendingCoordinatorEvents = pendingEvents;
@@ -1831,6 +2140,9 @@ async function meshReadChat(ctx, args) {
1831
2140
  if (!node) {
1832
2141
  return JSON.stringify(buildMissingNodeReadChatRecovery(ctx, args), null, 2);
1833
2142
  }
2143
+ if (ctx.transport instanceof IpcTransport || isLocalTransport(ctx.transport)) {
2144
+ await drainCoordinatorPendingEvents(ctx, { nodeIds: [args.node_id] });
2145
+ }
1834
2146
  if (isLocalTransport(ctx.transport)) {
1835
2147
  const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id));
1836
2148
  const providerSessionId = typeof args.provider_session_id === "string" && args.provider_session_id.trim() ? args.provider_session_id.trim() : cached?.providerSessionId;
@@ -1933,6 +2245,10 @@ async function meshLaunchSession(ctx, args) {
1933
2245
  const coordinatorNode = resolveCoordinatorNode(ctx);
1934
2246
  const coordinatorDaemonId = coordinatorNode?.daemonId || ctx.localDaemonId;
1935
2247
  const spawnedSessionVisibility = readSpawnedSessionVisibility(ctx.mesh.policy);
2248
+ const isLocalNode = isLocalControlPlaneNode(ctx, node);
2249
+ if (node.daemonId && !isLocalNode && !coordinatorDaemonId) {
2250
+ return JSON.stringify(buildMissingCoordinatorDaemonIdFailure(ctx, node, resolvedProviderType), null, 2);
2251
+ }
1936
2252
  let result;
1937
2253
  try {
1938
2254
  result = await commandForNode(ctx, node, "launch_cli", {
@@ -1973,7 +2289,6 @@ async function meshLaunchSession(ctx, args) {
1973
2289
  });
1974
2290
  } catch {
1975
2291
  }
1976
- const isLocalNode = isLocalControlPlaneNode(ctx, node);
1977
2292
  if (ctx.transport instanceof IpcTransport && node.daemonId && !isLocalNode) {
1978
2293
  ctx.transport.meshCommand(node.daemonId, "trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
1979
2294
  });
@@ -1998,6 +2313,9 @@ async function meshLaunchSession(ctx, args) {
1998
2313
  const coordinatorNode = resolveCoordinatorNode(ctx);
1999
2314
  const coordinatorDaemonId = coordinatorNode?.daemonId || ctx.localDaemonId;
2000
2315
  const spawnedSessionVisibility = readSpawnedSessionVisibility(ctx.mesh.policy);
2316
+ if (!coordinatorDaemonId) {
2317
+ return JSON.stringify(buildMissingCoordinatorDaemonIdFailure(ctx, node, resolvedProviderType), null, 2);
2318
+ }
2001
2319
  try {
2002
2320
  const res = await ctx.transport.launch(node.daemonId, {
2003
2321
  type: resolvedProviderType,
@@ -2036,7 +2354,7 @@ async function meshGitStatus(ctx, args) {
2036
2354
  const submoduleIgnorePaths = node.policy?.submoduleIgnorePaths || [];
2037
2355
  try {
2038
2356
  if (!isLocalTransport(ctx.transport) && node.daemonId) {
2039
- const result = await ctx.transport.gitStatus(node.daemonId, node.workspace, true);
2357
+ const result = await ctx.transport.gitStatus(node.daemonId, node.workspace, true, true);
2040
2358
  return JSON.stringify({
2041
2359
  nodeId: args.node_id,
2042
2360
  workspace: node.workspace,
@@ -2048,6 +2366,7 @@ async function meshGitStatus(ctx, args) {
2048
2366
  } else if (isLocalTransport(ctx.transport)) {
2049
2367
  const statusResult = await commandForNode(ctx, node, "git_status", {
2050
2368
  workspace: node.workspace,
2369
+ refreshUpstream: true,
2051
2370
  includeSubmodules: autoDiscoverSubmodules,
2052
2371
  submoduleIgnorePaths: submoduleIgnorePaths.length > 0 ? submoduleIgnorePaths : void 0
2053
2372
  });
@@ -2162,6 +2481,7 @@ async function meshCloneNode(ctx, args) {
2162
2481
  if (existingIndex >= 0) ctx.mesh.nodes[existingIndex] = clonePayload.node;
2163
2482
  else ctx.mesh.nodes.push(clonePayload.node);
2164
2483
  ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2484
+ await syncCoordinatorDaemonMeshCache(ctx);
2165
2485
  }
2166
2486
  return JSON.stringify(result, null, 2);
2167
2487
  } else if (!isLocalTransport(ctx.transport) && sourceNode.daemonId) {
@@ -2179,6 +2499,7 @@ async function meshCloneNode(ctx, args) {
2179
2499
  if (existingIndex >= 0) ctx.mesh.nodes[existingIndex] = clonePayload.node;
2180
2500
  else ctx.mesh.nodes.push(clonePayload.node);
2181
2501
  ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2502
+ await syncCoordinatorDaemonMeshCache(ctx);
2182
2503
  }
2183
2504
  return JSON.stringify(res, null, 2);
2184
2505
  } catch (e) {
@@ -2274,6 +2595,43 @@ async function meshRemoveNode(ctx, args) {
2274
2595
  return JSON.stringify({ error: "Cloud mesh remove_node requires node daemonId" });
2275
2596
  }
2276
2597
  }
2598
+ function resolveRefineConfigNode(ctx, nodeId) {
2599
+ if (nodeId) return findNode(ctx.mesh, nodeId);
2600
+ const node = ctx.mesh.nodes.find((entry) => !!entry.workspace);
2601
+ if (!node) throw new Error("No mesh node with a workspace is available");
2602
+ return node;
2603
+ }
2604
+ async function meshRefineConfigSchema(ctx) {
2605
+ const node = resolveRefineConfigNode(ctx);
2606
+ const result = await commandForNode(ctx, node, "get_mesh_refine_config_schema", {});
2607
+ return JSON.stringify(result, null, 2);
2608
+ }
2609
+ async function meshValidateRefineConfig(ctx, args) {
2610
+ const node = resolveRefineConfigNode(ctx, args.node_id);
2611
+ const result = await commandForNode(ctx, node, "validate_mesh_refine_config", {
2612
+ workspace: node.workspace,
2613
+ inlineMesh: ctx.mesh,
2614
+ ...args.config ? { config: args.config } : {}
2615
+ });
2616
+ return JSON.stringify(result, null, 2);
2617
+ }
2618
+ async function meshSuggestRefineConfig(ctx, args) {
2619
+ const node = resolveRefineConfigNode(ctx, args.node_id);
2620
+ const result = await commandForNode(ctx, node, "suggest_mesh_refine_config", {
2621
+ workspace: node.workspace,
2622
+ inlineMesh: ctx.mesh
2623
+ });
2624
+ return JSON.stringify(result, null, 2);
2625
+ }
2626
+ async function meshRefinePlan(ctx, args) {
2627
+ const node = await findNodeWithRefresh(ctx, args.node_id);
2628
+ const result = await commandForNode(ctx, node, "plan_mesh_refine_node", {
2629
+ meshId: ctx.mesh.id,
2630
+ nodeId: args.node_id,
2631
+ inlineMesh: ctx.mesh
2632
+ });
2633
+ return JSON.stringify(result, null, 2);
2634
+ }
2277
2635
  async function meshRefineNode(ctx, args) {
2278
2636
  const node = await findNodeWithRefresh(ctx, args.node_id);
2279
2637
  if (isLocalTransport(ctx.transport)) {
@@ -2519,8 +2877,8 @@ var CloudTransport = class {
2519
2877
  if (!res.ok) throw new Error(`Approve failed: ${res.status}`);
2520
2878
  return res.json();
2521
2879
  }
2522
- async gitStatus(daemonId, workspace, includeDiff = true) {
2523
- const params = new URLSearchParams({ workspace, includeDiff: String(includeDiff) });
2880
+ async gitStatus(daemonId, workspace, includeDiff = true, refreshUpstream = false) {
2881
+ const params = new URLSearchParams({ workspace, includeDiff: String(includeDiff), refreshUpstream: String(refreshUpstream) });
2524
2882
  const res = await fetch(
2525
2883
  `${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/git-status?${params}`,
2526
2884
  { headers: this.headers() }
@@ -2915,6 +3273,22 @@ function formatChatResult(result, sessionId, format, limit = 50, compact = false
2915
3273
  }))
2916
3274
  }, null, 2);
2917
3275
  }
3276
+ if ((format === "text" || format === void 0) && compact && compactPayload) {
3277
+ const lines2 = outputMessages.slice(-limit).map((m) => {
3278
+ const role = m.role === "user" ? "User" : m.role === "assistant" ? "Agent" : m.role;
3279
+ const content = messageContent(m);
3280
+ const truncated = content.length > 500 ? `${content.slice(0, 500)}\u2026` : content;
3281
+ return `[${role}] ${truncated}`;
3282
+ });
3283
+ if (compactPayload.summary) {
3284
+ const truncatedSummary = compactPayload.summary.length > 500 ? `${compactPayload.summary.slice(0, 500)}\u2026` : compactPayload.summary;
3285
+ lines2.push(`[Summary] ${truncatedSummary}`);
3286
+ }
3287
+ if (result?.pollingAdvisory) {
3288
+ lines2.push(`Advisory: ${result.pollingAdvisory.message}`);
3289
+ }
3290
+ return lines2.length > 0 ? lines2.join("\n\n") : "No messages in chat.";
3291
+ }
2918
3292
  if (outputMessages.length === 0) {
2919
3293
  return result?.pollingAdvisory ? `No messages in chat.
2920
3294
 
@@ -4030,6 +4404,18 @@ async function startMcpServer(opts) {
4030
4404
  case "mesh_refine_node":
4031
4405
  text = await meshRefineNode(meshCtx, a);
4032
4406
  break;
4407
+ case "mesh_refine_config_schema":
4408
+ text = await meshRefineConfigSchema(meshCtx);
4409
+ break;
4410
+ case "mesh_validate_refine_config":
4411
+ text = await meshValidateRefineConfig(meshCtx, a);
4412
+ break;
4413
+ case "mesh_suggest_refine_config":
4414
+ text = await meshSuggestRefineConfig(meshCtx, a);
4415
+ break;
4416
+ case "mesh_refine_plan":
4417
+ text = await meshRefinePlan(meshCtx, a);
4418
+ break;
4033
4419
  case "mesh_cleanup_sessions":
4034
4420
  text = await meshCleanupSessions(meshCtx, a);
4035
4421
  break;