adhdev 0.9.82-rc.2 → 0.9.82-rc.20

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.2",
3
+ "version": "0.9.82-rc.20",
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.2",
50
+ "@adhdev/daemon-core": "0.9.82-rc.20",
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,
@@ -583,10 +619,18 @@ function isIdleSessionRecord(session) {
583
619
  function chooseDispatchableSession(sessions, providerType, meshId, nodeId) {
584
620
  const live = sessions.filter((session) => !isTerminalSessionRecord(session));
585
621
  const matchingProvider = (session) => !providerType || session?.providerType === providerType || session?.cliType === providerType;
622
+ const isMeshOwnedDelegateSession = (session) => {
623
+ const settings = session?.settings;
624
+ const sessionMeshId = typeof settings?.meshNodeFor === "string" ? settings.meshNodeFor.trim() : "";
625
+ const coordinatorDaemonId = typeof settings?.meshCoordinatorDaemonId === "string" ? settings.meshCoordinatorDaemonId.trim() : "";
626
+ const sessionNodeId = typeof settings?.meshNodeId === "string" ? settings.meshNodeId.trim() : "";
627
+ if (sessionMeshId !== meshId || !coordinatorDaemonId) return false;
628
+ return !sessionNodeId || sessionNodeId === nodeId;
629
+ };
586
630
  const meshSessions = live.filter(
587
- (session) => session?.settings?.meshNodeFor === meshId || session?.settings?.meshNodeId === nodeId
631
+ (session) => isMeshOwnedDelegateSession(session)
588
632
  );
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];
633
+ return meshSessions.find((session) => isIdleSessionRecord(session) && matchingProvider(session)) || meshSessions.find(matchingProvider) || void 0;
590
634
  }
591
635
  function findNestedPayload(value, predicate) {
592
636
  const seen = /* @__PURE__ */ new Set();
@@ -872,6 +916,10 @@ function summarizeRelatedRepoStatus(repo, status) {
872
916
  workspace: repo.workspace,
873
917
  isGitRepo: status?.isGitRepo === true,
874
918
  branch: status?.branch ?? null,
919
+ upstream: status?.upstream ?? null,
920
+ upstreamStatus: typeof status?.upstreamStatus === "string" ? status.upstreamStatus : status?.upstream ? "unchecked" : "no_upstream",
921
+ upstreamFetchedAt: Number.isFinite(Number(status?.upstreamFetchedAt)) ? Number(status.upstreamFetchedAt) : null,
922
+ upstreamFetchError: typeof status?.upstreamFetchError === "string" ? status.upstreamFetchError : null,
875
923
  ahead: Number.isFinite(Number(status?.ahead)) ? Number(status.ahead) : 0,
876
924
  behind: Number.isFinite(Number(status?.behind)) ? Number(status.behind) : 0,
877
925
  dirty,
@@ -888,7 +936,7 @@ async function collectRelatedRepoStatuses(ctx, node) {
888
936
  const results = [];
889
937
  for (const repo of relatedRepos) {
890
938
  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 });
939
+ 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
940
  const status = extractGitStatus(statusResult);
893
941
  results.push(summarizeRelatedRepoStatus(repo, status));
894
942
  } catch (e) {
@@ -936,11 +984,13 @@ function buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges) {
936
984
  const ahead = readNumeric(status?.ahead);
937
985
  const behind = readNumeric(status?.behind);
938
986
  const upstream = readString(status?.upstream) ?? null;
987
+ const upstreamStatus = readString(status?.upstreamStatus) ?? (upstream ? "unchecked" : "no_upstream");
939
988
  const hasConflicts = status?.hasConflicts === true || Array.isArray(status?.conflictFiles) && status.conflictFiles.length > 0;
940
989
  const base = {
941
990
  defaultBranch,
942
991
  branch,
943
992
  upstream,
993
+ upstreamStatus,
944
994
  ahead,
945
995
  behind,
946
996
  isWorktree: node.isLocalWorktree === true,
@@ -974,6 +1024,15 @@ function buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges) {
974
1024
  };
975
1025
  }
976
1026
  if (branch === defaultBranch) {
1027
+ if (upstream && upstreamStatus !== "fresh") {
1028
+ return {
1029
+ ...base,
1030
+ status: "blocked_review",
1031
+ needsConvergence: true,
1032
+ reason: "default_branch_upstream_unverified",
1033
+ nextStep: `Refresh ${defaultBranch}'s upstream refs or resolve the fetch failure before declaring convergence complete for node '${node.id}'.`
1034
+ };
1035
+ }
977
1036
  if (ahead > 0 || behind > 0) {
978
1037
  return {
979
1038
  ...base,
@@ -1000,6 +1059,15 @@ function buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges) {
1000
1059
  nextStep: `Run mesh_refine_node(node_id: "${node.id}") or explicitly classify this worktree as blocked_review/not_mergeable before ending the task.`
1001
1060
  };
1002
1061
  }
1062
+ if (upstream && upstreamStatus !== "fresh") {
1063
+ return {
1064
+ ...base,
1065
+ status: "blocked_review",
1066
+ needsConvergence: true,
1067
+ reason: "feature_branch_upstream_unverified",
1068
+ nextStep: `Refresh branch '${branch}' upstream refs or resolve the fetch failure before deciding whether it is ready to merge into ${defaultBranch}.`
1069
+ };
1070
+ }
1003
1071
  if (!upstream || ahead > 0 || behind > 0) {
1004
1072
  return {
1005
1073
  ...base,
@@ -1043,6 +1111,71 @@ async function commandForNode(ctx, node, command, args = {}) {
1043
1111
  }
1044
1112
  throw new Error(`Command '${command}' requires daemon IPC/local transport for node '${node.id}'`);
1045
1113
  }
1114
+ function normalizePendingMeshCoordinatorEvents(value) {
1115
+ const payload = unwrapCommandPayload(value);
1116
+ const events = Array.isArray(payload?.events) ? payload.events : Array.isArray(value?.events) ? value.events : [];
1117
+ return events.filter((event) => event && typeof event === "object");
1118
+ }
1119
+ function buildMeshForwardPayloadFromPendingEvent(event) {
1120
+ const metadataEvent = event?.metadataEvent && typeof event.metadataEvent === "object" ? event.metadataEvent : {};
1121
+ return {
1122
+ event: readString(event?.event),
1123
+ meshId: readString(event?.meshId),
1124
+ nodeId: readString(event?.nodeId) || readString(metadataEvent.meshNodeId),
1125
+ workspace: readString(event?.workspace) || readString(metadataEvent.workspace),
1126
+ targetSessionId: readString(metadataEvent.targetSessionId) || readString(metadataEvent.sessionId) || readString(metadataEvent.instanceId),
1127
+ providerType: readString(metadataEvent.providerType),
1128
+ providerSessionId: readString(metadataEvent.providerSessionId),
1129
+ finalSummary: readString(metadataEvent.finalSummary) || readString(metadataEvent.summary),
1130
+ ...metadataEvent.intentional === true ? { intentional: true } : {},
1131
+ ...metadataEvent.intentionalStop === true ? { intentionalStop: true } : {},
1132
+ ...metadataEvent.operatorCleanup === true ? { operatorCleanup: true } : {},
1133
+ ...readString(metadataEvent.reason) ? { reason: readString(metadataEvent.reason) } : {},
1134
+ ...readString(metadataEvent.stopReason) ? { stopReason: readString(metadataEvent.stopReason) } : {},
1135
+ ...readString(metadataEvent.cleanupReason) ? { cleanupReason: readString(metadataEvent.cleanupReason) } : {},
1136
+ ...readString(metadataEvent.source) ? { source: readString(metadataEvent.source) } : {}
1137
+ };
1138
+ }
1139
+ async function drainCoordinatorPendingEvents(ctx, opts) {
1140
+ const requestedNodeIds = opts?.nodeIds?.length ? new Set(opts.nodeIds) : null;
1141
+ const matchesCurrentMesh = (event) => readString(event?.meshId) === ctx.mesh.id;
1142
+ if (ctx.transport instanceof IpcTransport) {
1143
+ const surfacedEvents = [];
1144
+ try {
1145
+ surfacedEvents.push(
1146
+ ...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", {})).filter(matchesCurrentMesh)
1147
+ );
1148
+ } catch {
1149
+ }
1150
+ for (const node of ctx.mesh.nodes) {
1151
+ if (!node.daemonId || isLocalControlPlaneNode(ctx, node)) continue;
1152
+ if (requestedNodeIds && !requestedNodeIds.has(node.id)) continue;
1153
+ try {
1154
+ const remoteEvents = normalizePendingMeshCoordinatorEvents(
1155
+ await ctx.transport.meshCommand(node.daemonId, "get_pending_mesh_events", {})
1156
+ ).filter(matchesCurrentMesh);
1157
+ if (remoteEvents.length === 0) continue;
1158
+ for (const event of remoteEvents) {
1159
+ const payload = buildMeshForwardPayloadFromPendingEvent(event);
1160
+ if (!payload.event || !payload.meshId) continue;
1161
+ await ctx.transport.command("mesh_forward_event", payload);
1162
+ }
1163
+ } catch {
1164
+ }
1165
+ }
1166
+ try {
1167
+ surfacedEvents.push(
1168
+ ...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", {})).filter(matchesCurrentMesh)
1169
+ );
1170
+ } catch {
1171
+ }
1172
+ return surfacedEvents;
1173
+ }
1174
+ if (isLocalTransport(ctx.transport)) {
1175
+ return (0, import_daemon_core.drainPendingMeshCoordinatorEvents)().filter(matchesCurrentMesh);
1176
+ }
1177
+ return [];
1178
+ }
1046
1179
  function isP2pTransportUnavailableError(error) {
1047
1180
  return (0, import_daemon_core.isP2pRelayTransportFailure)(error);
1048
1181
  }
@@ -1344,7 +1477,7 @@ async function meshStatus(ctx) {
1344
1477
  };
1345
1478
  try {
1346
1479
  if (!isLocalTransport(transport) && node.daemonId) {
1347
- const result = await transport.gitStatus(node.daemonId, node.workspace, false);
1480
+ const result = await transport.gitStatus(node.daemonId, node.workspace, false, true);
1348
1481
  const status = extractGitStatus(result);
1349
1482
  const uncommittedChanges = countUncommittedChanges(status);
1350
1483
  const dirty = isGitStatusDirty(status);
@@ -1362,6 +1495,7 @@ async function meshStatus(ctx) {
1362
1495
  const autoDiscover = node.policy?.autoDiscoverSubmodules !== false;
1363
1496
  const statusResult = await commandForNode(ctx, node, "git_status", {
1364
1497
  workspace: node.workspace,
1498
+ refreshUpstream: true,
1365
1499
  includeSubmodules: autoDiscover,
1366
1500
  submoduleIgnorePaths: node.policy?.submoduleIgnorePaths || void 0
1367
1501
  });
@@ -1452,6 +1586,11 @@ async function meshStatus(ctx) {
1452
1586
  repoIdentity: mesh.repoIdentity,
1453
1587
  policy: mesh.policy,
1454
1588
  refreshedAt: (/* @__PURE__ */ new Date()).toISOString(),
1589
+ sourceOfTruth: {
1590
+ membership: "coordinator_daemon_live_mesh",
1591
+ currentStatus: "live_git_and_session_probes",
1592
+ historicalEvidenceOnly: ["recoveryHints", "ledgerSummary"]
1593
+ },
1455
1594
  nodes: results,
1456
1595
  branchConvergenceSummary: summarizeBranchConvergence(results)
1457
1596
  };
@@ -1460,13 +1599,7 @@ async function meshStatus(ctx) {
1460
1599
  } catch {
1461
1600
  }
1462
1601
  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
- }
1602
+ const pendingEvents = await drainCoordinatorPendingEvents(ctx);
1470
1603
  if (pendingEvents.length > 0) {
1471
1604
  response.pendingCoordinatorEvents = pendingEvents;
1472
1605
  }
@@ -1476,6 +1609,7 @@ async function meshStatus(ctx) {
1476
1609
  }
1477
1610
  async function meshTaskHistory(ctx, args) {
1478
1611
  const { mesh } = ctx;
1612
+ await drainCoordinatorPendingEvents(ctx);
1479
1613
  const tail = typeof args.tail === "number" && args.tail > 0 ? args.tail : 20;
1480
1614
  const kind = typeof args.kind === "string" && args.kind.trim() ? [args.kind.trim()] : void 0;
1481
1615
  const entries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail, kind });
@@ -1823,6 +1957,9 @@ async function meshReadChat(ctx, args) {
1823
1957
  if (!node) {
1824
1958
  return JSON.stringify(buildMissingNodeReadChatRecovery(ctx, args), null, 2);
1825
1959
  }
1960
+ if (ctx.transport instanceof IpcTransport || isLocalTransport(ctx.transport)) {
1961
+ await drainCoordinatorPendingEvents(ctx, { nodeIds: [args.node_id] });
1962
+ }
1826
1963
  if (isLocalTransport(ctx.transport)) {
1827
1964
  const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id));
1828
1965
  const providerSessionId = typeof args.provider_session_id === "string" && args.provider_session_id.trim() ? args.provider_session_id.trim() : cached?.providerSessionId;
@@ -2028,7 +2165,7 @@ async function meshGitStatus(ctx, args) {
2028
2165
  const submoduleIgnorePaths = node.policy?.submoduleIgnorePaths || [];
2029
2166
  try {
2030
2167
  if (!isLocalTransport(ctx.transport) && node.daemonId) {
2031
- const result = await ctx.transport.gitStatus(node.daemonId, node.workspace, true);
2168
+ const result = await ctx.transport.gitStatus(node.daemonId, node.workspace, true, true);
2032
2169
  return JSON.stringify({
2033
2170
  nodeId: args.node_id,
2034
2171
  workspace: node.workspace,
@@ -2040,6 +2177,7 @@ async function meshGitStatus(ctx, args) {
2040
2177
  } else if (isLocalTransport(ctx.transport)) {
2041
2178
  const statusResult = await commandForNode(ctx, node, "git_status", {
2042
2179
  workspace: node.workspace,
2180
+ refreshUpstream: true,
2043
2181
  includeSubmodules: autoDiscoverSubmodules,
2044
2182
  submoduleIgnorePaths: submoduleIgnorePaths.length > 0 ? submoduleIgnorePaths : void 0
2045
2183
  });
@@ -2154,6 +2292,7 @@ async function meshCloneNode(ctx, args) {
2154
2292
  if (existingIndex >= 0) ctx.mesh.nodes[existingIndex] = clonePayload.node;
2155
2293
  else ctx.mesh.nodes.push(clonePayload.node);
2156
2294
  ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2295
+ await syncCoordinatorDaemonMeshCache(ctx);
2157
2296
  }
2158
2297
  return JSON.stringify(result, null, 2);
2159
2298
  } else if (!isLocalTransport(ctx.transport) && sourceNode.daemonId) {
@@ -2171,6 +2310,7 @@ async function meshCloneNode(ctx, args) {
2171
2310
  if (existingIndex >= 0) ctx.mesh.nodes[existingIndex] = clonePayload.node;
2172
2311
  else ctx.mesh.nodes.push(clonePayload.node);
2173
2312
  ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2313
+ await syncCoordinatorDaemonMeshCache(ctx);
2174
2314
  }
2175
2315
  return JSON.stringify(res, null, 2);
2176
2316
  } catch (e) {
@@ -2511,8 +2651,8 @@ var CloudTransport = class {
2511
2651
  if (!res.ok) throw new Error(`Approve failed: ${res.status}`);
2512
2652
  return res.json();
2513
2653
  }
2514
- async gitStatus(daemonId, workspace, includeDiff = true) {
2515
- const params = new URLSearchParams({ workspace, includeDiff: String(includeDiff) });
2654
+ async gitStatus(daemonId, workspace, includeDiff = true, refreshUpstream = false) {
2655
+ const params = new URLSearchParams({ workspace, includeDiff: String(includeDiff), refreshUpstream: String(refreshUpstream) });
2516
2656
  const res = await fetch(
2517
2657
  `${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/git-status?${params}`,
2518
2658
  { headers: this.headers() }
@@ -2907,6 +3047,22 @@ function formatChatResult(result, sessionId, format, limit = 50, compact = false
2907
3047
  }))
2908
3048
  }, null, 2);
2909
3049
  }
3050
+ if ((format === "text" || format === void 0) && compact && compactPayload) {
3051
+ const lines2 = outputMessages.slice(-limit).map((m) => {
3052
+ const role = m.role === "user" ? "User" : m.role === "assistant" ? "Agent" : m.role;
3053
+ const content = messageContent(m);
3054
+ const truncated = content.length > 500 ? `${content.slice(0, 500)}\u2026` : content;
3055
+ return `[${role}] ${truncated}`;
3056
+ });
3057
+ if (compactPayload.summary) {
3058
+ const truncatedSummary = compactPayload.summary.length > 500 ? `${compactPayload.summary.slice(0, 500)}\u2026` : compactPayload.summary;
3059
+ lines2.push(`[Summary] ${truncatedSummary}`);
3060
+ }
3061
+ if (result?.pollingAdvisory) {
3062
+ lines2.push(`Advisory: ${result.pollingAdvisory.message}`);
3063
+ }
3064
+ return lines2.length > 0 ? lines2.join("\n\n") : "No messages in chat.";
3065
+ }
2910
3066
  if (outputMessages.length === 0) {
2911
3067
  return result?.pollingAdvisory ? `No messages in chat.
2912
3068