@bbigbang/core 0.1.0

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.
Files changed (175) hide show
  1. package/dist/config.js +380 -0
  2. package/dist/execution/executionDispatcher.js +3810 -0
  3. package/dist/main.js +90 -0
  4. package/dist/nodeEventHistory.js +206 -0
  5. package/dist/scheduler/dreamLogic.js +50 -0
  6. package/dist/scheduler/dreamScheduler.js +65 -0
  7. package/dist/services/agentFileAccessService.js +1913 -0
  8. package/dist/services/agentRuntimeCleanupBroker.js +62 -0
  9. package/dist/services/agentSkillsBroker.js +118 -0
  10. package/dist/services/agentSkillsService.js +83 -0
  11. package/dist/services/agentWorkspaceBroker.js +937 -0
  12. package/dist/services/agentWorkspaceService.js +70 -0
  13. package/dist/services/appVersion.js +14 -0
  14. package/dist/services/auth.js +586 -0
  15. package/dist/services/claudeControlBroker.js +154 -0
  16. package/dist/services/claudeTranscriptBroker.js +100 -0
  17. package/dist/services/claudeTranscriptService.js +359 -0
  18. package/dist/services/codexAppServerBroker.js +155 -0
  19. package/dist/services/codexTranscriptBroker.js +98 -0
  20. package/dist/services/codexTranscriptService.js +961 -0
  21. package/dist/services/droidMissionBroker.js +124 -0
  22. package/dist/services/droidMissionImporter.js +630 -0
  23. package/dist/services/droidModelOptions.js +165 -0
  24. package/dist/services/hubServerRegistrationService.js +268 -0
  25. package/dist/services/libraryManifest.js +43 -0
  26. package/dist/services/libraryScaffold.js +26 -0
  27. package/dist/services/libraryService.js +2263 -0
  28. package/dist/services/memoryService.js +386 -0
  29. package/dist/services/missionEvidence.js +377 -0
  30. package/dist/services/missionService.js +2361 -0
  31. package/dist/services/missionTrace.js +158 -0
  32. package/dist/services/nativeMissionBriefParser.js +120 -0
  33. package/dist/services/nativeMissionOrchestrator.js +2045 -0
  34. package/dist/services/nativeMissionReportGenerator.js +227 -0
  35. package/dist/services/nativeMissionValidationRunner.js +452 -0
  36. package/dist/services/nativeMissionWorkerBroker.js +190 -0
  37. package/dist/services/nodeRegistry.js +34 -0
  38. package/dist/services/nodeStateReconciler.js +97 -0
  39. package/dist/services/panelMediaScanner.js +119 -0
  40. package/dist/services/persistentRuntimeJsonlClient.js +153 -0
  41. package/dist/services/platformAgentPolicy.js +180 -0
  42. package/dist/services/platformAgentService.js +2041 -0
  43. package/dist/services/projectAccessResolver.js +93 -0
  44. package/dist/services/projectService.js +392 -0
  45. package/dist/services/resourceSpaceService.js +140 -0
  46. package/dist/services/scenarioRuntimeService.js +1130 -0
  47. package/dist/services/suggestedPlannerService.js +868 -0
  48. package/dist/services/workbenchGitBroker.js +161 -0
  49. package/dist/services/workbenchGitService.js +69 -0
  50. package/dist/services/workbenchInspectBroker.js +65 -0
  51. package/dist/services/workbenchNodePathService.js +79 -0
  52. package/dist/services/workbenchRegistryService.js +240 -0
  53. package/dist/services/workbenchRootService.js +181 -0
  54. package/dist/services/workbenchTerminalBroker.js +378 -0
  55. package/dist/services/workspaceRunOwnership.js +60 -0
  56. package/dist/services/workspaceScaffold.js +105 -0
  57. package/dist/services/workspaceSessionRuntimeService.js +576 -0
  58. package/dist/services/workspaceSessionService.js +245 -0
  59. package/dist/services/workspaceToolActionRunner.js +1582 -0
  60. package/dist/services/workspaceToolErrors.js +10 -0
  61. package/dist/services/workspaceToolExecutionUtils.js +895 -0
  62. package/dist/services/workspaceToolLatestStateProjector.js +91 -0
  63. package/dist/services/workspaceToolManifest.js +572 -0
  64. package/dist/services/workspaceToolMutationQueue.js +43 -0
  65. package/dist/services/workspaceToolPanelProjection.js +460 -0
  66. package/dist/services/workspaceToolPromotion.js +255 -0
  67. package/dist/services/workspaceToolPromotionState.js +224 -0
  68. package/dist/services/workspaceToolPublishDiagnostics.js +189 -0
  69. package/dist/services/workspaceToolPublishIdentityResolver.js +146 -0
  70. package/dist/services/workspaceToolReadModel.js +378 -0
  71. package/dist/services/workspaceToolRunLedger.js +239 -0
  72. package/dist/services/workspaceToolService.js +3067 -0
  73. package/dist/services/workspaceToolSnapshotPanelSync.js +293 -0
  74. package/dist/services/workspaceToolTerminalLifecycle.js +283 -0
  75. package/dist/services/workspaceToolTypes.js +1 -0
  76. package/dist/services/workspaceToolUploadMaterializer.js +228 -0
  77. package/dist/web/actionCardRoutes.js +129 -0
  78. package/dist/web/actionCards.js +469 -0
  79. package/dist/web/activationContext.js +684 -0
  80. package/dist/web/agentChannelGuards.js +48 -0
  81. package/dist/web/agentMentionCooldowns.js +32 -0
  82. package/dist/web/agentReminders.js +1668 -0
  83. package/dist/web/agentRuntimePresence.js +197 -0
  84. package/dist/web/agentSelfState.js +494 -0
  85. package/dist/web/agentTaskLinks.js +26 -0
  86. package/dist/web/agentVisibility.js +79 -0
  87. package/dist/web/assets.js +95 -0
  88. package/dist/web/channelActivationPrompt.js +395 -0
  89. package/dist/web/channelMemoryNotes.js +127 -0
  90. package/dist/web/channelMentions.js +10 -0
  91. package/dist/web/channelMessageSequences.js +19 -0
  92. package/dist/web/channelSubscriptions.js +26 -0
  93. package/dist/web/clearedTaskRoots.js +10 -0
  94. package/dist/web/collaborationPromptGuidance.js +36 -0
  95. package/dist/web/collaborationSurfaceState.js +140 -0
  96. package/dist/web/contextBundleRanking.js +154 -0
  97. package/dist/web/contextBundleResolver.js +488 -0
  98. package/dist/web/conversationBuiltinSkillRoots.js +50 -0
  99. package/dist/web/conversationControls.js +232 -0
  100. package/dist/web/conversationHandoffs.js +612 -0
  101. package/dist/web/conversationManager.js +2511 -0
  102. package/dist/web/conversationSummaries.js +876 -0
  103. package/dist/web/conversationSurfaceKinds.js +17 -0
  104. package/dist/web/conversationTargets.js +173 -0
  105. package/dist/web/directActivationPrompt.js +122 -0
  106. package/dist/web/directReplyTargets.js +69 -0
  107. package/dist/web/directThreadResolver.js +129 -0
  108. package/dist/web/dmTaskHandoffPrompt.js +120 -0
  109. package/dist/web/dmTaskThreadStatusProjection.js +229 -0
  110. package/dist/web/ftsQuery.js +33 -0
  111. package/dist/web/internalAgentRouter.js +11341 -0
  112. package/dist/web/libraryCuratorScheduler.js +58 -0
  113. package/dist/web/libraryDocumentPromptGuidance.js +8 -0
  114. package/dist/web/messageCheckpoints.js +19 -0
  115. package/dist/web/nodeWsHandler.js +2495 -0
  116. package/dist/web/notificationRounds.js +1061 -0
  117. package/dist/web/panelActionMessages.js +108 -0
  118. package/dist/web/panelActivationPrompt.js +18 -0
  119. package/dist/web/panelAudit.js +273 -0
  120. package/dist/web/panelLifecycle.js +222 -0
  121. package/dist/web/panelMediaPolicy.js +43 -0
  122. package/dist/web/panelPathPolicy.js +63 -0
  123. package/dist/web/panelPreviews.js +175 -0
  124. package/dist/web/panelQueryHandles.js +2749 -0
  125. package/dist/web/panelRoutes.js +2147 -0
  126. package/dist/web/panels.js +904 -0
  127. package/dist/web/peerInboxAggregates.js +1247 -0
  128. package/dist/web/planApprovalState.js +92 -0
  129. package/dist/web/platformAgentScheduler.js +66 -0
  130. package/dist/web/proactiveOpportunities.js +452 -0
  131. package/dist/web/promptContextSections.js +242 -0
  132. package/dist/web/promptHistorySanitizer.js +26 -0
  133. package/dist/web/promptSlashCommands.js +158 -0
  134. package/dist/web/rollingConversationSummary.js +453 -0
  135. package/dist/web/routeHelpers.js +11 -0
  136. package/dist/web/routes/handoff.js +288 -0
  137. package/dist/web/routes/history.js +345 -0
  138. package/dist/web/routes/memory.js +258 -0
  139. package/dist/web/routes/selfState.js +171 -0
  140. package/dist/web/routes/workspace.js +154 -0
  141. package/dist/web/runSurfaceWatermarks.js +431 -0
  142. package/dist/web/runtimeCapabilities.js +48 -0
  143. package/dist/web/sameAgentHandoffs.js +494 -0
  144. package/dist/web/server.js +15567 -0
  145. package/dist/web/sharedCollaborationCapsules.js +163 -0
  146. package/dist/web/soloSessionRelay.js +42 -0
  147. package/dist/web/soloWsHandler.js +138 -0
  148. package/dist/web/suggestedPlannerScheduler.js +56 -0
  149. package/dist/web/surfaceActivationPolicy.js +108 -0
  150. package/dist/web/surfaceCollaborators.js +61 -0
  151. package/dist/web/surfaceSystemStatus.js +263 -0
  152. package/dist/web/targetParticipants.js +77 -0
  153. package/dist/web/taskEvents.js +49 -0
  154. package/dist/web/taskLifecycleMessages.js +165 -0
  155. package/dist/web/taskLoops.js +732 -0
  156. package/dist/web/taskMemoryNotes.js +224 -0
  157. package/dist/web/taskNumbers.js +16 -0
  158. package/dist/web/taskOwnerGuards.js +49 -0
  159. package/dist/web/taskParticipantResolver.js +42 -0
  160. package/dist/web/taskParticipants.js +97 -0
  161. package/dist/web/taskSourceDetails.js +20 -0
  162. package/dist/web/taskStateViews.js +210 -0
  163. package/dist/web/taskStatusTransitions.js +9 -0
  164. package/dist/web/taskThreadFollowups.js +599 -0
  165. package/dist/web/taskThreadRuntimeClosure.js +685 -0
  166. package/dist/web/taskUpdateDelivery.js +104 -0
  167. package/dist/web/threadReplyContentHeuristics.js +30 -0
  168. package/dist/web/threadRoots.js +61 -0
  169. package/dist/web/threadTaskBindings.js +365 -0
  170. package/dist/web/uiPanelPromptGuidance.js +27 -0
  171. package/dist/web/workspaceMemoryHints.js +143 -0
  172. package/dist/web/workspaceToolPromptGuidance.js +30 -0
  173. package/dist/web/wsHandler.js +397 -0
  174. package/dist/web/wsSink.js +116 -0
  175. package/package.json +54 -0
@@ -0,0 +1,1247 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { buildChannelSurfaceActivationEnvelope } from './activationContext.js';
3
+ import { enqueueAndDeliverNotificationRound, isClosedTaskStatus, peerInboxMessagesWereCreatedBeforeTaskClose, } from './notificationRounds.js';
4
+ import { hasEffectiveAgentRuntimeCapability } from './runtimeCapabilities.js';
5
+ import { findMentionedAgents } from './channelMentions.js';
6
+ import { getBoundTaskForThread, listCurrentTaskPeerUpdateParticipants, listThreadCollaborationParticipants, } from './threadTaskBindings.js';
7
+ import { listTaskCollaboratorAgentIds } from './taskParticipants.js';
8
+ import { listUnifiedRecentSurfaceParticipants } from './collaborationSurfaceState.js';
9
+ import { findThreadRootMessageId } from './threadRoots.js';
10
+ import { buildCurrentConversationRoutingConstraint, NO_TARGET_CROSS_SURFACE_OR_VISIBLE_OUTPUT_CONSTRAINT, NO_TARGET_OR_CROSS_SURFACE_CONSTRAINT, } from './collaborationPromptGuidance.js';
11
+ import { buildSharedCollaborationCapsuleSection, loadActiveSharedCollaborationCapsuleForPeerUpdate, } from './sharedCollaborationCapsules.js';
12
+ export const PEER_INBOX_BATCH_WINDOW_MS = 60_000;
13
+ export const PEER_INBOX_BATCH_FLUSH_COUNT = 5;
14
+ export const PEER_INBOX_AGGREGATE_POLL_INTERVAL_MS = 1_000;
15
+ export const PEER_INBOX_FAILURE_RETRY_MS = 30_000;
16
+ function normalizeThreadKey(threadRootId) {
17
+ return threadRootId?.trim() ?? '';
18
+ }
19
+ function normalizePeerDelivery(value) {
20
+ return value === 'immediate' ? 'immediate' : 'batch';
21
+ }
22
+ function loadAggregateByKey(db, params) {
23
+ return db.prepare(`SELECT aggregate_id as aggregateId,
24
+ surface_kind as surfaceKind,
25
+ channel_id as channelId,
26
+ surface_thread_key as surfaceThreadKey,
27
+ target_agent_id as targetAgentId,
28
+ conversation_id as conversationId,
29
+ first_message_id as firstMessageId,
30
+ last_message_id as lastMessageId,
31
+ first_seq as firstSeq,
32
+ last_seq as lastSeq,
33
+ message_count as messageCount,
34
+ flush_policy as flushPolicy,
35
+ deliver_after as deliverAfter,
36
+ retry_count as retryCount,
37
+ last_failure_notice_at as lastFailureNoticeAt,
38
+ created_at as createdAt,
39
+ updated_at as updatedAt
40
+ FROM peer_notification_aggregates
41
+ WHERE surface_kind = ?
42
+ AND channel_id = ?
43
+ AND surface_thread_key = ?
44
+ AND target_agent_id = ?
45
+ LIMIT 1`).get(params.surfaceKind, params.channelId, params.surfaceThreadKey, params.targetAgentId);
46
+ }
47
+ export function loadPeerInboxAggregate(db, aggregateId) {
48
+ return db.prepare(`SELECT aggregate_id as aggregateId,
49
+ surface_kind as surfaceKind,
50
+ channel_id as channelId,
51
+ surface_thread_key as surfaceThreadKey,
52
+ target_agent_id as targetAgentId,
53
+ conversation_id as conversationId,
54
+ first_message_id as firstMessageId,
55
+ last_message_id as lastMessageId,
56
+ first_seq as firstSeq,
57
+ last_seq as lastSeq,
58
+ message_count as messageCount,
59
+ flush_policy as flushPolicy,
60
+ deliver_after as deliverAfter,
61
+ retry_count as retryCount,
62
+ last_failure_notice_at as lastFailureNoticeAt,
63
+ created_at as createdAt,
64
+ updated_at as updatedAt
65
+ FROM peer_notification_aggregates
66
+ WHERE aggregate_id = ?
67
+ LIMIT 1`).get(aggregateId);
68
+ }
69
+ function computeAggregateStats(db, aggregateId) {
70
+ const row = db.prepare(`SELECT COUNT(*) as messageCount,
71
+ MIN(seq) as firstSeq,
72
+ MAX(seq) as lastSeq,
73
+ MAX(CASE WHEN peer_delivery = 'immediate' THEN 1 ELSE 0 END) as hasImmediate,
74
+ MIN(inserted_at) as firstInsertedAt
75
+ FROM peer_notification_aggregate_items
76
+ WHERE aggregate_id = ?`).get(aggregateId);
77
+ if (!row.messageCount || row.firstSeq == null || row.lastSeq == null || row.firstInsertedAt == null) {
78
+ throw new Error(`Peer inbox aggregate ${aggregateId} has no items.`);
79
+ }
80
+ const edgeRows = db.prepare(`SELECT seq, message_id as messageId
81
+ FROM peer_notification_aggregate_items
82
+ WHERE aggregate_id = ?
83
+ AND seq IN (?, ?)
84
+ ORDER BY seq ASC, inserted_at ASC`).all(aggregateId, row.firstSeq, row.lastSeq);
85
+ const firstMessageId = edgeRows.find((edge) => edge.seq === row.firstSeq)?.messageId;
86
+ const lastMessageId = [...edgeRows].reverse().find((edge) => edge.seq === row.lastSeq)?.messageId;
87
+ if (!firstMessageId || !lastMessageId) {
88
+ throw new Error(`Peer inbox aggregate ${aggregateId} is missing edge message ids.`);
89
+ }
90
+ return {
91
+ messageCount: row.messageCount,
92
+ firstSeq: row.firstSeq,
93
+ lastSeq: row.lastSeq,
94
+ firstMessageId,
95
+ lastMessageId,
96
+ hasImmediate: row.hasImmediate,
97
+ firstInsertedAt: row.firstInsertedAt,
98
+ };
99
+ }
100
+ export function schedulePeerInboxAggregate(params) {
101
+ const now = params.now ?? Date.now();
102
+ const surfaceThreadKey = normalizeThreadKey(params.threadRootId);
103
+ const peerDelivery = normalizePeerDelivery(params.peerDelivery);
104
+ const batchWindowMs = Math.max(1_000, params.batchWindowMs ?? PEER_INBOX_BATCH_WINDOW_MS);
105
+ const flushCount = Math.max(1, params.flushCount ?? PEER_INBOX_BATCH_FLUSH_COUNT);
106
+ return params.db.transaction(() => {
107
+ let existing = loadAggregateByKey(params.db, {
108
+ surfaceKind: params.surfaceKind,
109
+ channelId: params.channelId,
110
+ surfaceThreadKey,
111
+ targetAgentId: params.targetAgentId,
112
+ });
113
+ if (existing && existing.conversationId !== params.conversationId) {
114
+ params.db.prepare('DELETE FROM peer_notification_aggregates WHERE aggregate_id = ?').run(existing.aggregateId);
115
+ existing = undefined;
116
+ }
117
+ const aggregateId = existing?.aggregateId ?? randomUUID();
118
+ if (!existing) {
119
+ const deliverAfter = peerDelivery === 'immediate' ? now : now + batchWindowMs;
120
+ params.db.prepare(`INSERT INTO peer_notification_aggregates(
121
+ aggregate_id, surface_kind, channel_id, surface_thread_key, target_agent_id,
122
+ conversation_id, first_message_id, last_message_id, first_seq, last_seq,
123
+ message_count, flush_policy, deliver_after, retry_count, last_failure_notice_at,
124
+ created_at, updated_at
125
+ )
126
+ VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?, 0, NULL, ?, ?)`).run(aggregateId, params.surfaceKind, params.channelId, surfaceThreadKey, params.targetAgentId, params.conversationId, params.messageId, params.messageId, params.seq, params.seq, peerDelivery, deliverAfter, now, now);
127
+ }
128
+ const insertResult = params.db.prepare(`INSERT OR IGNORE INTO peer_notification_aggregate_items(
129
+ aggregate_id, message_id, seq, sender_id, sender_name, sender_type,
130
+ message_kind, peer_delivery, created_at, inserted_at
131
+ )
132
+ VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(aggregateId, params.messageId, params.seq, params.senderId, params.senderName, params.senderType ?? 'agent', params.messageKind ?? null, peerDelivery, params.messageCreatedAt ?? now, now);
133
+ if (insertResult.changes === 0 && existing) {
134
+ if (peerDelivery !== 'immediate') {
135
+ return existing;
136
+ }
137
+ params.db.prepare(`UPDATE peer_notification_aggregate_items
138
+ SET peer_delivery = 'immediate'
139
+ WHERE aggregate_id = ?
140
+ AND message_id = ?
141
+ AND peer_delivery <> 'immediate'`).run(aggregateId, params.messageId);
142
+ }
143
+ const stats = computeAggregateStats(params.db, aggregateId);
144
+ const nextFlushPolicy = stats.hasImmediate ? 'immediate' : 'batch';
145
+ const nextDeliverAfter = stats.hasImmediate || stats.messageCount >= flushCount
146
+ ? now
147
+ : Math.min(existing?.deliverAfter ?? now + batchWindowMs, now + batchWindowMs);
148
+ params.db.prepare(`UPDATE peer_notification_aggregates
149
+ SET conversation_id = ?,
150
+ first_message_id = ?,
151
+ last_message_id = ?,
152
+ first_seq = ?,
153
+ last_seq = ?,
154
+ message_count = ?,
155
+ flush_policy = ?,
156
+ deliver_after = ?,
157
+ updated_at = ?
158
+ WHERE aggregate_id = ?`).run(params.conversationId, stats.firstMessageId, stats.lastMessageId, stats.firstSeq, stats.lastSeq, stats.messageCount, nextFlushPolicy, nextDeliverAfter, now, aggregateId);
159
+ const aggregate = loadPeerInboxAggregate(params.db, aggregateId);
160
+ if (!aggregate) {
161
+ throw new Error(`Peer inbox aggregate ${aggregateId} disappeared after update.`);
162
+ }
163
+ return aggregate;
164
+ })();
165
+ }
166
+ export function loadPeerInboxAggregateItems(db, aggregateId) {
167
+ return db.prepare(`SELECT i.aggregate_id as aggregateId,
168
+ i.message_id as messageId,
169
+ i.seq,
170
+ i.sender_id as senderId,
171
+ i.sender_name as senderName,
172
+ i.sender_type as senderType,
173
+ i.message_kind as messageKind,
174
+ i.peer_delivery as peerDelivery,
175
+ i.created_at as createdAt,
176
+ i.inserted_at as insertedAt,
177
+ cm.content as content,
178
+ cm.target as target
179
+ FROM peer_notification_aggregate_items i
180
+ LEFT JOIN channel_messages cm ON cm.message_id = i.message_id
181
+ WHERE i.aggregate_id = ?
182
+ ORDER BY i.seq ASC, i.inserted_at ASC`).all(aggregateId);
183
+ }
184
+ function loadPeerInboxSelfContextItems(db, params) {
185
+ const threadClause = params.threadRootId
186
+ ? 'cm.thread_root_id = ?'
187
+ : '(cm.thread_root_id IS NULL OR cm.thread_root_id = \'\')';
188
+ const args = params.threadRootId
189
+ ? [params.channelId, params.threadRootId, params.targetAgentId, params.firstSeq, params.lastSeq]
190
+ : [params.channelId, params.targetAgentId, params.firstSeq, params.lastSeq];
191
+ return db.prepare(`SELECT ? as aggregateId,
192
+ cm.message_id as messageId,
193
+ cm.seq,
194
+ cm.sender_id as senderId,
195
+ cm.sender_name as senderName,
196
+ cm.sender_type as senderType,
197
+ cm.message_kind as messageKind,
198
+ CASE WHEN cm.peer_delivery = 'immediate' THEN 'immediate' ELSE 'batch' END as peerDelivery,
199
+ cm.created_at as createdAt,
200
+ cm.created_at as insertedAt,
201
+ cm.content as content,
202
+ cm.target as target,
203
+ 'self' as contextRole
204
+ FROM channel_messages cm
205
+ WHERE cm.channel_id = ?
206
+ AND ${threadClause}
207
+ AND cm.sender_id = ?
208
+ AND cm.sender_type = 'agent'
209
+ AND NOT (
210
+ COALESCE(cm.message_source, '') = 'delta_fallback'
211
+ AND EXISTS (
212
+ SELECT 1
213
+ FROM agents hidden_delta_sender
214
+ WHERE hidden_delta_sender.agent_id = cm.sender_id
215
+ AND hidden_delta_sender.agent_type = 'codex_app_server'
216
+ )
217
+ )
218
+ AND cm.seq BETWEEN ? AND ?
219
+ ORDER BY cm.seq ASC, cm.created_at ASC`).all(params.aggregateId, ...args);
220
+ }
221
+ function mergePeerInboxPromptItems(params) {
222
+ const byMessageId = new Map();
223
+ for (const item of [...params.deliveryItems, ...params.selfContextItems]) {
224
+ if (!byMessageId.has(item.messageId)) {
225
+ byMessageId.set(item.messageId, item);
226
+ }
227
+ }
228
+ return [...byMessageId.values()].sort((a, b) => (a.seq - b.seq
229
+ || a.insertedAt - b.insertedAt
230
+ || a.messageId.localeCompare(b.messageId)));
231
+ }
232
+ function deletePeerInboxAggregate(db, aggregateId) {
233
+ db.prepare('DELETE FROM peer_notification_aggregates WHERE aggregate_id = ?').run(aggregateId);
234
+ }
235
+ function deletePeerInboxAggregatesByIds(db, aggregateIds) {
236
+ const ids = [...new Set(aggregateIds.map((id) => id.trim()).filter(Boolean))];
237
+ if (ids.length === 0)
238
+ return 0;
239
+ const placeholders = ids.map(() => '?').join(', ');
240
+ db.prepare(`DELETE FROM peer_notification_aggregate_items
241
+ WHERE aggregate_id IN (${placeholders})`).run(...ids);
242
+ const result = db.prepare(`DELETE FROM peer_notification_aggregates
243
+ WHERE aggregate_id IN (${placeholders})`).run(...ids);
244
+ return result.changes;
245
+ }
246
+ function refreshPeerInboxAggregateStatsOrDelete(db, aggregateId, now) {
247
+ const current = loadPeerInboxAggregate(db, aggregateId);
248
+ const remaining = db.prepare(`SELECT COUNT(*) as count
249
+ FROM peer_notification_aggregate_items
250
+ WHERE aggregate_id = ?`).get(aggregateId);
251
+ if (remaining.count === 0) {
252
+ deletePeerInboxAggregate(db, aggregateId);
253
+ return false;
254
+ }
255
+ const stats = computeAggregateStats(db, aggregateId);
256
+ const nextFlushPolicy = stats.hasImmediate ? 'immediate' : 'batch';
257
+ const computedDeliverAfter = stats.hasImmediate || stats.messageCount >= PEER_INBOX_BATCH_FLUSH_COUNT
258
+ ? now
259
+ : Math.max(now, stats.firstInsertedAt + PEER_INBOX_BATCH_WINDOW_MS);
260
+ const shouldPreserveLaterDelivery = Boolean(current)
261
+ && current.deliverAfter > computedDeliverAfter
262
+ && (current.retryCount > 0 || nextFlushPolicy === 'batch');
263
+ const nextDeliverAfter = shouldPreserveLaterDelivery
264
+ ? current.deliverAfter
265
+ : computedDeliverAfter;
266
+ db.prepare(`UPDATE peer_notification_aggregates
267
+ SET first_message_id = ?,
268
+ last_message_id = ?,
269
+ first_seq = ?,
270
+ last_seq = ?,
271
+ message_count = ?,
272
+ flush_policy = ?,
273
+ deliver_after = ?,
274
+ updated_at = ?
275
+ WHERE aggregate_id = ?`).run(stats.firstMessageId, stats.lastMessageId, stats.firstSeq, stats.lastSeq, stats.messageCount, nextFlushPolicy, nextDeliverAfter, now, aggregateId);
276
+ return true;
277
+ }
278
+ function findExistingPeerNotificationRoundForAggregate(db, params) {
279
+ const expectedItemMessageIds = [...new Set(params.itemMessageIds)].sort();
280
+ const rows = db.prepare(`SELECT status,
281
+ acked_at as ackedAt,
282
+ delivery_payload_json as deliveryPayloadJson
283
+ FROM agent_notification_rounds
284
+ WHERE target_agent_id = ?
285
+ AND channel_id = ?
286
+ AND surface_thread_key = ?
287
+ ORDER BY updated_at DESC, created_at DESC`).all(params.targetAgentId, params.channelId, params.surfaceThreadKey);
288
+ for (const row of rows) {
289
+ let parsed;
290
+ try {
291
+ parsed = JSON.parse(row.deliveryPayloadJson);
292
+ }
293
+ catch {
294
+ continue;
295
+ }
296
+ const peerInboxAggregate = parsed?.activationMetadata?.peerInboxAggregate;
297
+ if (peerInboxAggregate?.aggregateId !== params.aggregateId) {
298
+ continue;
299
+ }
300
+ const itemMessageIds = Array.isArray(peerInboxAggregate.itemMessageIds)
301
+ ? peerInboxAggregate.itemMessageIds.filter((id) => typeof id === 'string').sort()
302
+ : [];
303
+ const isSamePeerPayload = itemMessageIds.length === expectedItemMessageIds.length
304
+ && itemMessageIds.every((id, index) => id === expectedItemMessageIds[index])
305
+ && peerInboxAggregate.firstSeq === params.firstSeq
306
+ && peerInboxAggregate.lastSeq === params.lastSeq
307
+ && peerInboxAggregate.messageCount === params.messageCount
308
+ && peerInboxAggregate.flushPolicy === params.flushPolicy;
309
+ if (!isSamePeerPayload) {
310
+ continue;
311
+ }
312
+ if (row.status === 'acknowledged' || row.ackedAt != null) {
313
+ return 'acknowledged';
314
+ }
315
+ if (row.status === 'superseded') {
316
+ return 'superseded';
317
+ }
318
+ if (row.status === 'ready' || row.status === 'leased' || row.status === 'delivered') {
319
+ return 'pending';
320
+ }
321
+ }
322
+ return null;
323
+ }
324
+ export function deletePeerInboxAggregatesForAgent(db, agentId) {
325
+ const now = Date.now();
326
+ const targetRows = db.prepare(`SELECT aggregate_id as aggregateId
327
+ FROM peer_notification_aggregates
328
+ WHERE target_agent_id = ?`).all(agentId);
329
+ const deletedTargetCount = deletePeerInboxAggregatesByIds(db, targetRows.map((row) => row.aggregateId));
330
+ const senderRows = db.prepare(`SELECT DISTINCT aggregate_id as aggregateId
331
+ FROM peer_notification_aggregate_items
332
+ WHERE sender_id = ?`).all(agentId);
333
+ if (senderRows.length === 0) {
334
+ return deletedTargetCount;
335
+ }
336
+ const senderAggregateIds = senderRows.map((row) => row.aggregateId);
337
+ const placeholders = senderAggregateIds.map(() => '?').join(', ');
338
+ db.prepare(`DELETE FROM peer_notification_aggregate_items
339
+ WHERE sender_id = ?
340
+ AND aggregate_id IN (${placeholders})`).run(agentId, ...senderAggregateIds);
341
+ let emptiedSenderCount = 0;
342
+ for (const aggregateId of senderAggregateIds) {
343
+ if (!refreshPeerInboxAggregateStatsOrDelete(db, aggregateId, now)) {
344
+ emptiedSenderCount += 1;
345
+ }
346
+ }
347
+ return deletedTargetCount + emptiedSenderCount;
348
+ }
349
+ export function deletePeerInboxAggregatesForChannel(db, channelId) {
350
+ const rows = db.prepare(`SELECT aggregate_id as aggregateId
351
+ FROM peer_notification_aggregates
352
+ WHERE channel_id = ?`).all(channelId);
353
+ return deletePeerInboxAggregatesByIds(db, rows.map((row) => row.aggregateId));
354
+ }
355
+ function finalizeDeliveredPeerInboxAggregate(db, params) {
356
+ db.transaction(() => {
357
+ const messageIds = [...new Set(params.deliveredMessageIds.map((id) => id.trim()).filter(Boolean))];
358
+ if (messageIds.length > 0) {
359
+ const placeholders = messageIds.map(() => '?').join(', ');
360
+ db.prepare(`DELETE FROM peer_notification_aggregate_items
361
+ WHERE aggregate_id = ?
362
+ AND message_id IN (${placeholders})`).run(params.aggregateId, ...messageIds);
363
+ }
364
+ refreshPeerInboxAggregateStatsOrDelete(db, params.aggregateId, params.now);
365
+ })();
366
+ }
367
+ function reschedulePeerInboxAggregateAfterFailure(db, params) {
368
+ db.prepare(`UPDATE peer_notification_aggregates
369
+ SET deliver_after = ?,
370
+ retry_count = ?,
371
+ last_failure_notice_at = CASE WHEN ? THEN ? ELSE last_failure_notice_at END,
372
+ updated_at = ?
373
+ WHERE aggregate_id = ?`).run(params.now + PEER_INBOX_FAILURE_RETRY_MS, params.retryCount + 1, params.broadcastedNotice ? 1 : 0, params.now, params.now, params.aggregateId);
374
+ }
375
+ function shouldBroadcastFailureNotice(row, now) {
376
+ return row.lastFailureNoticeAt == null
377
+ || now - row.lastFailureNoticeAt >= PEER_INBOX_FAILURE_RETRY_MS;
378
+ }
379
+ function buildFailureNoticeMessage(row, targetAgentName) {
380
+ return row.surfaceKind === 'channel_root'
381
+ ? `@${targetAgentName} could not be notified about the new channel activity.`
382
+ : `@${targetAgentName} could not be notified about the new thread reply.`;
383
+ }
384
+ function maybeBroadcastFailureNotice(params) {
385
+ const shouldBroadcastNotice = shouldBroadcastFailureNotice(params.row, params.now);
386
+ if (shouldBroadcastNotice) {
387
+ params.broadcastToChannel?.(params.row.channelId, {
388
+ type: 'channel.notice',
389
+ notice: {
390
+ message: buildFailureNoticeMessage(params.row, params.targetAgentName),
391
+ createdAt: new Date(params.now).toISOString(),
392
+ },
393
+ });
394
+ }
395
+ return shouldBroadcastNotice;
396
+ }
397
+ function loadDuePeerInboxAggregates(db, now) {
398
+ return db.prepare(`SELECT aggregate_id as aggregateId,
399
+ surface_kind as surfaceKind,
400
+ channel_id as channelId,
401
+ surface_thread_key as surfaceThreadKey,
402
+ target_agent_id as targetAgentId,
403
+ conversation_id as conversationId,
404
+ first_message_id as firstMessageId,
405
+ last_message_id as lastMessageId,
406
+ first_seq as firstSeq,
407
+ last_seq as lastSeq,
408
+ message_count as messageCount,
409
+ flush_policy as flushPolicy,
410
+ deliver_after as deliverAfter,
411
+ retry_count as retryCount,
412
+ last_failure_notice_at as lastFailureNoticeAt,
413
+ created_at as createdAt,
414
+ updated_at as updatedAt
415
+ FROM peer_notification_aggregates
416
+ WHERE deliver_after <= ?
417
+ ORDER BY deliver_after ASC, created_at ASC`).all(now);
418
+ }
419
+ function isCurrentTaskPeerTarget(db, params) {
420
+ return listCurrentTaskPeerUpdateParticipants(db, {
421
+ taskId: params.taskId,
422
+ ownerAgentId: params.ownerAgentId,
423
+ }).some((participant) => participant.agentId === params.targetAgentId);
424
+ }
425
+ function assessClosedTaskPeerInboxAggregate(params) {
426
+ if (!params.boundTask || !isClosedTaskStatus(params.boundTask.status)) {
427
+ return { ok: true };
428
+ }
429
+ const isPreClosePeerInbox = peerInboxMessagesWereCreatedBeforeTaskClose(params.db, {
430
+ channelId: params.row.channelId,
431
+ surfaceThreadKey: params.row.surfaceThreadKey,
432
+ firstSeq: params.row.firstSeq,
433
+ lastSeq: params.row.lastSeq,
434
+ taskId: params.boundTask.taskId,
435
+ taskStatus: params.boundTask.status,
436
+ });
437
+ return isPreClosePeerInbox
438
+ ? { ok: true }
439
+ : { ok: false, terminal: true, reason: `Task thread is ${params.boundTask.status}` };
440
+ }
441
+ function isTaskCloseSummaryPeerInboxAggregate(params) {
442
+ if (!params.boundTask || !isClosedTaskStatus(params.boundTask.status))
443
+ return false;
444
+ const assigneeId = params.boundTask.assigneeId;
445
+ if (!assigneeId)
446
+ return false;
447
+ return params.items.length > 0 && params.items.every((item) => (item.messageKind === 'final'
448
+ && item.senderId === assigneeId));
449
+ }
450
+ function shouldAttachSharedCollaborationCapsule(params) {
451
+ if (params.boundTask && isClosedTaskStatus(params.boundTask.status))
452
+ return false;
453
+ const participantIds = new Set(params.capsuleParticipantAgentIds);
454
+ const senderIds = [...new Set(params.deliveryItems
455
+ .filter((item) => item.contextRole !== 'self')
456
+ .map((item) => item.senderId)
457
+ .filter(Boolean))];
458
+ return senderIds.length > 0 && senderIds.every((senderId) => participantIds.has(senderId));
459
+ }
460
+ function hasNewerOwnerDirectedActivationAfterAggregate(params) {
461
+ const threadRootId = params.row.surfaceThreadKey.trim();
462
+ if (!threadRootId)
463
+ return false;
464
+ const rows = params.db.prepare(`SELECT sender_id as senderId,
465
+ sender_type as senderType,
466
+ content,
467
+ created_at as createdAt
468
+ FROM channel_messages
469
+ WHERE channel_id = ?
470
+ AND thread_root_id = ?
471
+ AND seq > ?
472
+ ORDER BY seq ASC`).all(params.row.channelId, threadRootId, params.row.lastSeq);
473
+ return rows.some((row) => {
474
+ const mentionsOwner = findMentionedAgents(row.content ?? '', params.channelAgents)
475
+ .some((agent) => agent.agentId === params.ownerAgentId);
476
+ if (!mentionsOwner)
477
+ return false;
478
+ if (row.senderType === 'user')
479
+ return true;
480
+ if (row.senderType !== 'agent')
481
+ return false;
482
+ const notification = params.db.prepare(`SELECT 1
483
+ FROM agent_mention_cooldowns
484
+ WHERE channel_id = ?
485
+ AND thread_root_id = ?
486
+ AND from_agent_id = ?
487
+ AND to_agent_id = ?
488
+ AND last_notified_at >= ?
489
+ LIMIT 1`).get(params.row.channelId, threadRootId, row.senderId, params.ownerAgentId, row.createdAt);
490
+ return Boolean(notification);
491
+ });
492
+ }
493
+ function buildOwnerAwarenessForPeerInboxAggregate(params) {
494
+ const boundTask = params.boundTask;
495
+ if (!boundTask)
496
+ return undefined;
497
+ const ownerAgentId = boundTask.assigneeId?.trim();
498
+ if (!ownerAgentId || params.row.targetAgentId !== ownerAgentId)
499
+ return undefined;
500
+ const taskCollaboratorAgentIds = new Set(listTaskCollaboratorAgentIds(params.db, {
501
+ taskId: boundTask.taskId,
502
+ }));
503
+ if (taskCollaboratorAgentIds.size === 0)
504
+ return undefined;
505
+ const channelAgents = params.conversationManager.listAgents(params.row.channelId);
506
+ const mentionedAgentIds = new Set();
507
+ let triggerSeq = null;
508
+ for (const item of params.items) {
509
+ const mentionedAgents = findMentionedAgents(item.content ?? '', channelAgents);
510
+ if (mentionedAgents.some((agent) => agent.agentId === ownerAgentId)) {
511
+ return undefined;
512
+ }
513
+ const mentionedCollaboratorIds = mentionedAgents
514
+ .map((agent) => agent.agentId)
515
+ .filter((agentId) => taskCollaboratorAgentIds.has(agentId) && agentId !== ownerAgentId);
516
+ if (mentionedCollaboratorIds.length === 0)
517
+ continue;
518
+ triggerSeq = triggerSeq == null ? item.seq : Math.min(triggerSeq, item.seq);
519
+ for (const agentId of mentionedCollaboratorIds) {
520
+ mentionedAgentIds.add(agentId);
521
+ }
522
+ }
523
+ if (mentionedAgentIds.size === 0 || triggerSeq == null)
524
+ return undefined;
525
+ if (hasNewerOwnerDirectedActivationAfterAggregate({
526
+ db: params.db,
527
+ row: params.row,
528
+ ownerAgentId,
529
+ channelAgents,
530
+ })) {
531
+ return undefined;
532
+ }
533
+ return {
534
+ mode: 'collaborator_only_thread_reply',
535
+ triggerSeq,
536
+ mentionedAgentIds: Array.from(mentionedAgentIds),
537
+ };
538
+ }
539
+ function isCurrentPeerInboxTarget(db, params) {
540
+ const participants = params.surfaceKind === 'channel_root'
541
+ ? listUnifiedRecentSurfaceParticipants(db, {
542
+ channelId: params.channelId,
543
+ threadRootId: null,
544
+ now: params.now,
545
+ })
546
+ : listThreadCollaborationParticipants(db, {
547
+ channelId: params.channelId,
548
+ threadRootId: params.threadRootId ?? '',
549
+ });
550
+ if (participants.some((participant) => participant.agentId === params.targetAgentId)) {
551
+ return true;
552
+ }
553
+ if (params.surfaceKind !== 'channel_thread' || !params.threadRootId) {
554
+ return false;
555
+ }
556
+ const rootMessageId = findThreadRootMessageId(db, params.channelId, params.threadRootId);
557
+ if (!rootMessageId) {
558
+ return false;
559
+ }
560
+ const rootMessage = db.prepare(`SELECT sender_id as senderId,
561
+ sender_type as senderType
562
+ FROM channel_messages
563
+ WHERE channel_id = ?
564
+ AND message_id = ?
565
+ LIMIT 1`).get(params.channelId, rootMessageId);
566
+ return rootMessage?.senderType === 'agent' && rootMessage.senderId === params.targetAgentId;
567
+ }
568
+ export async function processDuePeerInboxAggregates(params) {
569
+ const now = params.now ?? Date.now();
570
+ const rows = loadDuePeerInboxAggregates(params.db, now);
571
+ for (const row of rows) {
572
+ const conversation = params.conversationManager.getConversation(row.conversationId);
573
+ if (!conversation?.replyTarget) {
574
+ deletePeerInboxAggregate(params.db, row.aggregateId);
575
+ continue;
576
+ }
577
+ const channelRow = params.db.prepare(`SELECT name
578
+ FROM channels
579
+ WHERE channel_id = ?
580
+ LIMIT 1`).get(row.channelId);
581
+ if (!channelRow?.name) {
582
+ deletePeerInboxAggregate(params.db, row.aggregateId);
583
+ continue;
584
+ }
585
+ const threadRootId = row.surfaceThreadKey || null;
586
+ if (row.surfaceKind === 'channel_thread' && !threadRootId) {
587
+ deletePeerInboxAggregate(params.db, row.aggregateId);
588
+ continue;
589
+ }
590
+ const boundTask = threadRootId
591
+ ? getBoundTaskForThread(params.db, {
592
+ channelId: row.channelId,
593
+ threadRootId,
594
+ })
595
+ : null;
596
+ if (boundTask) {
597
+ if (!isCurrentTaskPeerTarget(params.db, {
598
+ taskId: boundTask.taskId,
599
+ ownerAgentId: boundTask.assigneeId,
600
+ targetAgentId: row.targetAgentId,
601
+ })) {
602
+ deletePeerInboxAggregate(params.db, row.aggregateId);
603
+ continue;
604
+ }
605
+ }
606
+ else if (!isCurrentPeerInboxTarget(params.db, {
607
+ surfaceKind: row.surfaceKind,
608
+ channelId: row.channelId,
609
+ threadRootId,
610
+ targetAgentId: row.targetAgentId,
611
+ now,
612
+ })) {
613
+ deletePeerInboxAggregate(params.db, row.aggregateId);
614
+ continue;
615
+ }
616
+ const deliveryItems = loadPeerInboxAggregateItems(params.db, row.aggregateId);
617
+ if (deliveryItems.length === 0) {
618
+ deletePeerInboxAggregate(params.db, row.aggregateId);
619
+ continue;
620
+ }
621
+ const promptItems = mergePeerInboxPromptItems({
622
+ deliveryItems,
623
+ selfContextItems: loadPeerInboxSelfContextItems(params.db, {
624
+ aggregateId: row.aggregateId,
625
+ channelId: row.channelId,
626
+ threadRootId,
627
+ targetAgentId: row.targetAgentId,
628
+ firstSeq: row.firstSeq,
629
+ lastSeq: row.lastSeq,
630
+ }),
631
+ });
632
+ const closedTaskPolicy = assessClosedTaskPeerInboxAggregate({
633
+ db: params.db,
634
+ row,
635
+ boundTask: boundTask ?? null,
636
+ });
637
+ if (!closedTaskPolicy.ok) {
638
+ deletePeerInboxAggregate(params.db, row.aggregateId);
639
+ continue;
640
+ }
641
+ const isCloseSummaryAggregate = isTaskCloseSummaryPeerInboxAggregate({
642
+ boundTask: boundTask ?? null,
643
+ items: deliveryItems,
644
+ });
645
+ const ownerAwareness = buildOwnerAwarenessForPeerInboxAggregate({
646
+ db: params.db,
647
+ conversationManager: params.conversationManager,
648
+ row,
649
+ boundTask: boundTask ?? null,
650
+ items: deliveryItems,
651
+ });
652
+ const targetAgent = params.conversationManager.getAgent(row.targetAgentId);
653
+ if (!targetAgent) {
654
+ deletePeerInboxAggregate(params.db, row.aggregateId);
655
+ continue;
656
+ }
657
+ const targetSupportsActiveTurnSteer = hasEffectiveAgentRuntimeCapability(params.db, {
658
+ nodeId: targetAgent.nodeId,
659
+ agentType: targetAgent.agentType,
660
+ capability: 'activeTurnSteer',
661
+ });
662
+ const reason = row.surfaceKind === 'channel_root' ? 'channel_activity' : 'thread_reply';
663
+ const envelope = buildChannelSurfaceActivationEnvelope(params.db, {
664
+ agentId: row.targetAgentId,
665
+ channelId: row.channelId,
666
+ replyTarget: conversation.replyTarget,
667
+ triggerSeq: row.lastSeq,
668
+ threadRootId,
669
+ channelName: channelRow.name,
670
+ reason,
671
+ });
672
+ const channelLabel = threadRootId ? `#${channelRow.name}:${threadRootId}` : `#${channelRow.name}`;
673
+ const loadedSharedCollaborationCapsule = !isCloseSummaryAggregate && !ownerAwareness
674
+ ? loadActiveSharedCollaborationCapsuleForPeerUpdate(params.db, {
675
+ channelId: row.channelId,
676
+ threadRootId,
677
+ targetAgentId: row.targetAgentId,
678
+ now,
679
+ })
680
+ : null;
681
+ const sharedCollaborationCapsule = loadedSharedCollaborationCapsule && shouldAttachSharedCollaborationCapsule({
682
+ capsuleParticipantAgentIds: loadedSharedCollaborationCapsule.participantAgentIds,
683
+ deliveryItems,
684
+ boundTask: boundTask ?? null,
685
+ })
686
+ ? loadedSharedCollaborationCapsule
687
+ : null;
688
+ const sharedCollaborationSection = sharedCollaborationCapsule
689
+ ? buildSharedCollaborationCapsuleSection({
690
+ capsule: sharedCollaborationCapsule,
691
+ participantNames: sharedCollaborationCapsule.participantAgentIds.map((agentId) => {
692
+ const participant = params.conversationManager.getAgent(agentId);
693
+ return participant?.name ?? agentId;
694
+ }),
695
+ replyTarget: conversation.replyTarget,
696
+ includeRootHint: Boolean(threadRootId),
697
+ })
698
+ : null;
699
+ const promptText = renderPeerInboxFlushPrompt({
700
+ aggregate: promptItems.length === row.messageCount ? row : { ...row, messageCount: promptItems.length },
701
+ items: promptItems,
702
+ channelLabel,
703
+ replyTarget: conversation.replyTarget,
704
+ includeRootHint: Boolean(threadRootId),
705
+ closedTaskAwareness: isCloseSummaryAggregate,
706
+ sharedCollaborationSectionText: sharedCollaborationSection?.text,
707
+ });
708
+ const activationMetadata = {
709
+ suppressReplyContract: true,
710
+ peerInboxAggregate: {
711
+ aggregateId: row.aggregateId,
712
+ itemMessageIds: deliveryItems.map((item) => item.messageId),
713
+ firstSeq: row.firstSeq,
714
+ lastSeq: row.lastSeq,
715
+ messageCount: row.messageCount,
716
+ flushPolicy: row.flushPolicy,
717
+ ...(isCloseSummaryAggregate ? { closeSummary: true } : {}),
718
+ },
719
+ ...(boundTask && threadRootId ? {
720
+ taskPeerUpdate: {
721
+ taskId: boundTask.taskId,
722
+ channelId: row.channelId,
723
+ threadRootId,
724
+ fromAgentId: deliveryItems[0]?.senderId ?? '',
725
+ firstMessageId: row.firstMessageId,
726
+ lastMessageId: row.lastMessageId,
727
+ messageCount: row.messageCount,
728
+ },
729
+ } : {}),
730
+ ...(ownerAwareness ? { ownerAwareness } : {}),
731
+ ...(sharedCollaborationCapsule ? {
732
+ sharedCollaboration: {
733
+ capsuleId: sharedCollaborationCapsule.capsuleId,
734
+ channelId: sharedCollaborationCapsule.channelId,
735
+ threadRootId: sharedCollaborationCapsule.threadRootId,
736
+ originMessageId: sharedCollaborationCapsule.originMessageId,
737
+ originSeq: sharedCollaborationCapsule.originSeq,
738
+ contributionCount: sharedCollaborationCapsule.contributionCount,
739
+ maxVisibleAgentMessages: sharedCollaborationCapsule.maxVisibleAgentMessages,
740
+ },
741
+ } : {}),
742
+ };
743
+ const allowActiveSteer = row.flushPolicy === 'immediate' || Boolean(boundTask);
744
+ const submitPeerInboxPrompt = async (activeSteer) => {
745
+ await params.conversationManager.submitPrompt(conversation.id, promptText, {
746
+ recordAsUserMessage: false,
747
+ activationContextText: envelope.activationContextText,
748
+ activationContextSections: envelope.activationContextSections,
749
+ activationContextMode: 'resume_only',
750
+ resumeContextText: envelope.resumeContextText,
751
+ resumeContextSections: envelope.resumeContextSections,
752
+ replayOverlapRecentMessages: envelope.replayOverlapRecentMessages,
753
+ activationMetadata,
754
+ allowActiveSteer: activeSteer,
755
+ suppressReplyContract: true,
756
+ });
757
+ };
758
+ if (targetSupportsActiveTurnSteer) {
759
+ try {
760
+ const result = await enqueueAndDeliverNotificationRound({
761
+ db: params.db,
762
+ conversationManager: params.conversationManager,
763
+ targetAgentId: row.targetAgentId,
764
+ channelId: row.channelId,
765
+ threadRootId,
766
+ conversationId: conversation.id,
767
+ reason: 'participant_heads_up',
768
+ toSeqInclusive: row.lastSeq,
769
+ fromAgentId: null,
770
+ senderName: 'peer updates',
771
+ firstMessageId: row.firstMessageId,
772
+ lastMessageId: row.lastMessageId,
773
+ messageCount: row.messageCount,
774
+ deliveryPayload: {
775
+ promptText,
776
+ activationContextText: envelope.activationContextText,
777
+ activationContextSections: envelope.activationContextSections,
778
+ activationContextMode: 'resume_only',
779
+ resumeContextText: envelope.resumeContextText,
780
+ resumeContextSections: envelope.resumeContextSections,
781
+ replayOverlapRecentMessages: envelope.replayOverlapRecentMessages,
782
+ activationMetadata,
783
+ allowActiveSteer,
784
+ suppressReplyContract: true,
785
+ },
786
+ now,
787
+ });
788
+ if (result.acceptedPayload && result.delivered > 0) {
789
+ finalizeDeliveredPeerInboxAggregate(params.db, {
790
+ aggregateId: row.aggregateId,
791
+ deliveredMessageIds: deliveryItems.map((item) => item.messageId),
792
+ now,
793
+ });
794
+ continue;
795
+ }
796
+ if (!result.acceptedPayload) {
797
+ const existingPeerRoundState = findExistingPeerNotificationRoundForAggregate(params.db, {
798
+ aggregateId: row.aggregateId,
799
+ itemMessageIds: deliveryItems.map((item) => item.messageId),
800
+ firstSeq: row.firstSeq,
801
+ lastSeq: row.lastSeq,
802
+ messageCount: row.messageCount,
803
+ flushPolicy: row.flushPolicy,
804
+ targetAgentId: row.targetAgentId,
805
+ channelId: row.channelId,
806
+ surfaceThreadKey: row.surfaceThreadKey,
807
+ });
808
+ if (existingPeerRoundState === 'acknowledged') {
809
+ finalizeDeliveredPeerInboxAggregate(params.db, {
810
+ aggregateId: row.aggregateId,
811
+ deliveredMessageIds: deliveryItems.map((item) => item.messageId),
812
+ now,
813
+ });
814
+ continue;
815
+ }
816
+ if (existingPeerRoundState === 'superseded') {
817
+ deletePeerInboxAggregate(params.db, row.aggregateId);
818
+ continue;
819
+ }
820
+ if (existingPeerRoundState === 'pending') {
821
+ reschedulePeerInboxAggregateAfterFailure(params.db, {
822
+ aggregateId: row.aggregateId,
823
+ retryCount: row.retryCount,
824
+ now,
825
+ broadcastedNotice: false,
826
+ });
827
+ continue;
828
+ }
829
+ await submitPeerInboxPrompt(allowActiveSteer);
830
+ finalizeDeliveredPeerInboxAggregate(params.db, {
831
+ aggregateId: row.aggregateId,
832
+ deliveredMessageIds: deliveryItems.map((item) => item.messageId),
833
+ now,
834
+ });
835
+ continue;
836
+ }
837
+ const latestPeerRoundState = findExistingPeerNotificationRoundForAggregate(params.db, {
838
+ aggregateId: row.aggregateId,
839
+ itemMessageIds: deliveryItems.map((item) => item.messageId),
840
+ firstSeq: row.firstSeq,
841
+ lastSeq: row.lastSeq,
842
+ messageCount: row.messageCount,
843
+ flushPolicy: row.flushPolicy,
844
+ targetAgentId: row.targetAgentId,
845
+ channelId: row.channelId,
846
+ surfaceThreadKey: row.surfaceThreadKey,
847
+ });
848
+ if (latestPeerRoundState === 'superseded') {
849
+ deletePeerInboxAggregate(params.db, row.aggregateId);
850
+ continue;
851
+ }
852
+ const shouldBroadcastNotice = maybeBroadcastFailureNotice({
853
+ row,
854
+ targetAgentName: targetAgent.name,
855
+ now,
856
+ broadcastToChannel: params.broadcastToChannel,
857
+ });
858
+ reschedulePeerInboxAggregateAfterFailure(params.db, {
859
+ aggregateId: row.aggregateId,
860
+ retryCount: row.retryCount,
861
+ now,
862
+ broadcastedNotice: shouldBroadcastNotice,
863
+ });
864
+ }
865
+ catch {
866
+ const shouldBroadcastNotice = maybeBroadcastFailureNotice({
867
+ row,
868
+ targetAgentName: targetAgent.name,
869
+ now,
870
+ broadcastToChannel: params.broadcastToChannel,
871
+ });
872
+ reschedulePeerInboxAggregateAfterFailure(params.db, {
873
+ aggregateId: row.aggregateId,
874
+ retryCount: row.retryCount,
875
+ now,
876
+ broadcastedNotice: shouldBroadcastNotice,
877
+ });
878
+ }
879
+ continue;
880
+ }
881
+ try {
882
+ await submitPeerInboxPrompt(false);
883
+ finalizeDeliveredPeerInboxAggregate(params.db, {
884
+ aggregateId: row.aggregateId,
885
+ deliveredMessageIds: deliveryItems.map((item) => item.messageId),
886
+ now,
887
+ });
888
+ }
889
+ catch {
890
+ const shouldBroadcastNotice = maybeBroadcastFailureNotice({
891
+ row,
892
+ targetAgentName: targetAgent.name,
893
+ now,
894
+ broadcastToChannel: params.broadcastToChannel,
895
+ });
896
+ reschedulePeerInboxAggregateAfterFailure(params.db, {
897
+ aggregateId: row.aggregateId,
898
+ retryCount: row.retryCount,
899
+ now,
900
+ broadcastedNotice: shouldBroadcastNotice,
901
+ });
902
+ }
903
+ }
904
+ }
905
+ export function startPeerInboxAggregateService(params) {
906
+ const intervalMs = Math.max(1_000, params.intervalMs ?? PEER_INBOX_AGGREGATE_POLL_INTERVAL_MS);
907
+ let timer = null;
908
+ let running = false;
909
+ const tick = async () => {
910
+ if (running)
911
+ return;
912
+ running = true;
913
+ try {
914
+ await processDuePeerInboxAggregates({
915
+ db: params.db,
916
+ conversationManager: params.conversationManager,
917
+ broadcastToChannel: params.broadcastToChannel,
918
+ });
919
+ }
920
+ finally {
921
+ running = false;
922
+ }
923
+ };
924
+ timer = setInterval(() => {
925
+ void tick();
926
+ }, intervalMs);
927
+ timer.unref?.();
928
+ return {
929
+ tick,
930
+ stop: () => {
931
+ if (!timer)
932
+ return;
933
+ clearInterval(timer);
934
+ timer = null;
935
+ },
936
+ };
937
+ }
938
+ function formatTimestamp(value) {
939
+ return new Date(value).toISOString();
940
+ }
941
+ function firstLine(value) {
942
+ return value.split(/\r?\n/u).find((line) => line.trim().length > 0)?.trim() ?? '';
943
+ }
944
+ function truncateOneLine(value, maxChars) {
945
+ const line = firstLine(value).replace(/\s+/g, ' ');
946
+ if (maxChars <= 0) {
947
+ return '';
948
+ }
949
+ if (line.length <= maxChars) {
950
+ return line;
951
+ }
952
+ return `${line.slice(0, Math.max(0, maxChars - 3))}...`;
953
+ }
954
+ function truncateMiddle(value, maxChars) {
955
+ if (value.length <= maxChars) {
956
+ return value;
957
+ }
958
+ if (maxChars <= 3) {
959
+ return value.slice(0, maxChars);
960
+ }
961
+ const left = Math.ceil((maxChars - 3) / 2);
962
+ const right = Math.floor((maxChars - 3) / 2);
963
+ return `${value.slice(0, left)}...${value.slice(value.length - right)}`;
964
+ }
965
+ function readHistoryInstruction(params) {
966
+ return `bigbang message read --channel "${params.replyTarget}" --around "${params.messageId}" --limit 20${params.includeRoot ? ' --include-root' : ''}`;
967
+ }
968
+ export function renderPeerInboxFlushPrompt(params) {
969
+ const maxPromptChars = Math.max(1_000, params.maxPromptChars ?? 24_000);
970
+ const maxMessageChars = Math.max(200, params.maxMessageChars ?? 4_000);
971
+ const channelLabel = truncateMiddle(params.channelLabel, 160);
972
+ const replyTargetHeader = params.replyTarget.length <= 180
973
+ ? params.replyTarget
974
+ : '[exact target is in bigbang message read --channel ... below]';
975
+ const header = [
976
+ `[System: Peer updates in ${channelLabel}]`,
977
+ `delivery: ${params.aggregate.flushPolicy}`,
978
+ `message_count: ${params.aggregate.messageCount}`,
979
+ `seq_range: ${params.aggregate.firstSeq}-${params.aggregate.lastSeq}`,
980
+ '',
981
+ '[Current conversation target]',
982
+ `reply_target: ${replyTargetHeader}`,
983
+ '',
984
+ ...(params.sharedCollaborationSectionText ? [params.sharedCollaborationSectionText.trim(), ''] : []),
985
+ '[Peer update batch]',
986
+ ];
987
+ const footer = params.closedTaskAwareness === true
988
+ ? [
989
+ '',
990
+ '[Peer update reply contract]',
991
+ 'Default: absorb this closed-task update for awareness/audit only; do not send a visible reply.',
992
+ 'The task is already in review or done. Do not send visible chat for acknowledgements, summaries, already-replied/no-duplicate notices, or no-op text.',
993
+ 'If the task is in_review and you must add a substantive correction, reply only with `bigbang message send --kind progress` in this current conversation.',
994
+ `If the task is done, or you only have acknowledgement/no-op text, update memory/notes privately if useful, then stop. ${NO_TARGET_CROSS_SURFACE_OR_VISIBLE_OUTPUT_CONSTRAINT}`,
995
+ ]
996
+ : params.sharedCollaborationSectionText
997
+ ? [
998
+ '',
999
+ '[Shared continuation reply contract]',
1000
+ 'Default: continue only if you can add a new, non-duplicate increment toward the active shared collaboration request; otherwise stay silent.',
1001
+ 'If you continue, send exactly one `bigbang message send --kind progress` in this current conversation.',
1002
+ 'Do not acknowledge, summarize, say you already replied, say you will not duplicate, or post no-op text. Do not use kind="final" from this peer update.',
1003
+ buildCurrentConversationRoutingConstraint({ targetAuthority: 'platform' }),
1004
+ ]
1005
+ : [
1006
+ '',
1007
+ '[Peer update reply contract]',
1008
+ 'Default: absorb these updates; do not send a visible reply.',
1009
+ 'Reply only for new info not already present: correction, missing input, handoff, or user-visible result.',
1010
+ 'Do not send visible chat for acknowledgements, summaries, already-replied/no-duplicate notices, or no-op text.',
1011
+ 'If needed, reply only with `bigbang message send --kind progress`; final only for a true final result.',
1012
+ buildCurrentConversationRoutingConstraint({ targetAuthority: 'platform' }),
1013
+ ];
1014
+ const renderBlock = (item, index, forceStub = false) => {
1015
+ const content = item.content ?? '[Message content is no longer available in channel history.]';
1016
+ const senderLabel = item.contextRole === 'self' ? `@${item.senderName} (you)` : `@${item.senderName}`;
1017
+ const metadata = `${index + 1}. seq=${item.seq} msg=${item.messageId} time=${formatTimestamp(item.createdAt)} sender=${senderLabel} kind=${item.messageKind ?? 'untyped'} delivery=${item.peerDelivery}`;
1018
+ if (forceStub) {
1019
+ return [
1020
+ metadata,
1021
+ ' [Content omitted inline due to prompt budget.]',
1022
+ ` first_line=${truncateOneLine(content, 120)}`,
1023
+ ` ${readHistoryInstruction({
1024
+ replyTarget: params.replyTarget,
1025
+ messageId: item.messageId,
1026
+ includeRoot: params.includeRootHint === true,
1027
+ })}`,
1028
+ ].join('\n');
1029
+ }
1030
+ let body = content;
1031
+ if (body.length > maxMessageChars) {
1032
+ body = `${body.slice(0, maxMessageChars)}\n[truncated]\n${readHistoryInstruction({
1033
+ replyTarget: params.replyTarget,
1034
+ messageId: item.messageId,
1035
+ includeRoot: params.includeRootHint === true,
1036
+ })}`;
1037
+ }
1038
+ return `${metadata}\n ${body.replace(/\n/g, '\n ')}`;
1039
+ };
1040
+ const priorityItems = params.items
1041
+ .map((item, index) => ({ item, index }))
1042
+ .sort((left, right) => {
1043
+ const deliveryDelta = (right.item.peerDelivery === 'immediate' ? 1 : 0)
1044
+ - (left.item.peerDelivery === 'immediate' ? 1 : 0);
1045
+ if (deliveryDelta !== 0) {
1046
+ return deliveryDelta;
1047
+ }
1048
+ const seqDelta = right.item.seq - left.item.seq;
1049
+ if (seqDelta !== 0) {
1050
+ return seqDelta;
1051
+ }
1052
+ return right.item.insertedAt - left.item.insertedAt;
1053
+ });
1054
+ const selected = new Set();
1055
+ const omittedLinesFor = (omitted, budget) => {
1056
+ if (omitted.length === 0) {
1057
+ return [];
1058
+ }
1059
+ const omittedSeqs = omitted.map(({ item }) => item.seq);
1060
+ const firstOmitted = omitted[0]?.item;
1061
+ const lastOmitted = omitted[omitted.length - 1]?.item;
1062
+ const headerLine = `[Omitted ${omitted.length} message(s); fetch exact content with bigbang message read.]`;
1063
+ const senderLabel = (item) => (item.contextRole === 'self' ? `@${item.senderName} (you)` : `@${item.senderName}`);
1064
+ const selfOmitted = omitted.find(({ item }) => item.contextRole === 'self')?.item ?? null;
1065
+ const selfOmittedSuffix = selfOmitted ? ` self_omitted=${senderLabel(selfOmitted)}` : '';
1066
+ const rangeLine = firstOmitted && lastOmitted
1067
+ ? `omitted_message_seq_range=${Math.min(...omittedSeqs)}..${Math.max(...omittedSeqs)} count=${omitted.length} first_msg=${firstOmitted.messageId} last_msg=${lastOmitted.messageId}${selfOmittedSuffix}`
1068
+ : null;
1069
+ const compactRangeLine = firstOmitted && lastOmitted
1070
+ ? `omitted_message_seq_range=${Math.min(...omittedSeqs)}..${Math.max(...omittedSeqs)} count=${omitted.length}${selfOmittedSuffix}`
1071
+ : null;
1072
+ const compactIds = omitted.map(({ item }) => `seq=${item.seq}:msg=${item.messageId}`).join(',');
1073
+ const compactPrefix = 'omitted_message_refs=';
1074
+ const compactLine = `${compactPrefix}${compactIds}`;
1075
+ const shortCompactLine = `[Omitted ${omitted.length}] refs=${omitted.map(({ item }) => `${item.seq}:${item.messageId}`).join(',')}`;
1076
+ if (budget <= headerLine.length) {
1077
+ if (shortCompactLine.length <= budget) {
1078
+ return [shortCompactLine];
1079
+ }
1080
+ if (compactLine.length <= budget) {
1081
+ return [compactLine];
1082
+ }
1083
+ if (rangeLine && rangeLine.length <= budget) {
1084
+ return [rangeLine];
1085
+ }
1086
+ if (compactRangeLine && compactRangeLine.length <= budget) {
1087
+ return [compactRangeLine];
1088
+ }
1089
+ return [];
1090
+ }
1091
+ const lines = [headerLine];
1092
+ if (headerLine.length + 1 + compactLine.length <= budget) {
1093
+ lines.push(compactLine);
1094
+ return lines;
1095
+ }
1096
+ if (shortCompactLine.length <= budget) {
1097
+ return [shortCompactLine];
1098
+ }
1099
+ if (compactLine.length <= budget) {
1100
+ return [compactLine];
1101
+ }
1102
+ if (rangeLine && headerLine.length + 1 + rangeLine.length <= budget) {
1103
+ lines.push(rangeLine);
1104
+ return lines;
1105
+ }
1106
+ if (compactRangeLine && headerLine.length + 1 + compactRangeLine.length <= budget) {
1107
+ lines.push(compactRangeLine);
1108
+ return lines;
1109
+ }
1110
+ const metadataOnlyLines = omitted.map(({ item }) => {
1111
+ return `seq=${item.seq} msg=${item.messageId} sender=${senderLabel(item)}`;
1112
+ });
1113
+ if (metadataOnlyLines.join('\n').length + headerLine.length + 1 <= budget) {
1114
+ const remainingBudget = budget - headerLine.length - 1 - metadataOnlyLines.reduce((total, line) => total + line.length + 1, 0);
1115
+ const firstLineBudget = Math.max(0, Math.floor(remainingBudget / omitted.length) - ' first_line='.length);
1116
+ for (const { item } of omitted) {
1117
+ const content = item.content ?? '[Message content is no longer available in channel history.]';
1118
+ const suffix = firstLineBudget > 0 ? ` first_line=${truncateOneLine(content, firstLineBudget)}` : '';
1119
+ lines.push(`seq=${item.seq} msg=${item.messageId} sender=${senderLabel(item)}${suffix}`);
1120
+ }
1121
+ return lines;
1122
+ }
1123
+ if (rangeLine && rangeLine.length <= budget) {
1124
+ return [rangeLine];
1125
+ }
1126
+ if (compactRangeLine && compactRangeLine.length <= budget) {
1127
+ return [compactRangeLine];
1128
+ }
1129
+ return [];
1130
+ };
1131
+ const assemble = (selectedIds, forcedBlocks = []) => {
1132
+ const bodyBlocks = params.items
1133
+ .map((item, index) => ({ item, index }))
1134
+ .filter(({ item }) => selectedIds.has(item.messageId))
1135
+ .map(({ item, index }) => renderBlock(item, index));
1136
+ const omitted = params.items
1137
+ .filter((item) => !selectedIds.has(item.messageId))
1138
+ .map((item) => ({ item }));
1139
+ const body = [...forcedBlocks, ...bodyBlocks];
1140
+ const usedWithoutOmitted = [...header, ...body, ...footer].join('\n').length;
1141
+ const omittedBudget = Math.max(0, maxPromptChars - usedWithoutOmitted - 2);
1142
+ const omittedLines = omittedLinesFor(omitted, omittedBudget);
1143
+ return [...header, ...body, ...omittedLines, ...footer].join('\n');
1144
+ };
1145
+ if (priorityItems.length === 0) {
1146
+ return assemble(selected);
1147
+ }
1148
+ const topPriority = priorityItems[0];
1149
+ const topFullCandidate = new Set([topPriority.item.messageId]);
1150
+ let forcedTopBlock = null;
1151
+ if (assemble(topFullCandidate).length <= maxPromptChars) {
1152
+ selected.add(topPriority.item.messageId);
1153
+ }
1154
+ else {
1155
+ forcedTopBlock = renderBlock(topPriority.item, topPriority.index, true);
1156
+ if (assemble(new Set(), [forcedTopBlock]).length > maxPromptChars) {
1157
+ forcedTopBlock = [
1158
+ `${topPriority.index + 1}. seq=${topPriority.item.seq} sender=@${topPriority.item.senderName} delivery=${topPriority.item.peerDelivery}`,
1159
+ readHistoryInstruction({
1160
+ replyTarget: params.replyTarget,
1161
+ messageId: topPriority.item.messageId,
1162
+ includeRoot: params.includeRootHint === true,
1163
+ }),
1164
+ ].join('\n');
1165
+ }
1166
+ }
1167
+ for (const { item } of priorityItems.slice(1)) {
1168
+ const candidate = new Set(selected);
1169
+ candidate.add(item.messageId);
1170
+ if (assemble(candidate, forcedTopBlock ? [forcedTopBlock] : []).length <= maxPromptChars) {
1171
+ selected.add(item.messageId);
1172
+ }
1173
+ }
1174
+ const compactFooter = params.closedTaskAwareness === true
1175
+ ? [
1176
+ '',
1177
+ '[Peer update reply contract]',
1178
+ 'Default: absorb this closed-task update for awareness/audit only; do not send a visible reply.',
1179
+ 'Do not send visible chat for acknowledgements, summaries, already-replied/no-duplicate notices, or no-op text.',
1180
+ `If the task is in_review and a substantive correction is required, reply only with \`bigbang message send --kind progress\` in this current conversation. If done or no-op, stop. ${NO_TARGET_OR_CROSS_SURFACE_CONSTRAINT}`,
1181
+ ]
1182
+ : params.sharedCollaborationSectionText
1183
+ ? [
1184
+ '',
1185
+ '[Shared continuation reply contract]',
1186
+ 'Default: add one new non-duplicate progress only if useful; otherwise stay silent.',
1187
+ `Use exactly one \`bigbang message send --kind progress\`. No final, acknowledgements, already-replied/no-duplicate notices, or no-op summaries. ${buildCurrentConversationRoutingConstraint({ includeVisibleOutputConstraint: false })}`,
1188
+ ]
1189
+ : [
1190
+ '',
1191
+ '[Peer update reply contract]',
1192
+ 'Default: absorb; no reply.',
1193
+ 'If needed, reply only with `bigbang message send` for new info not already present: correction, missing input, handoff, or result.',
1194
+ `No acknowledgements, already-replied/no-duplicate notices, or no-op summaries. ${buildCurrentConversationRoutingConstraint({ includeVisibleOutputConstraint: false })}`,
1195
+ ];
1196
+ const compactHeader = [
1197
+ `[System: Peer updates in ${truncateMiddle(params.channelLabel, 80)}]`,
1198
+ `delivery: ${params.aggregate.flushPolicy}`,
1199
+ `message_count: ${params.aggregate.messageCount}`,
1200
+ `seq_range: ${params.aggregate.firstSeq}-${params.aggregate.lastSeq}`,
1201
+ '',
1202
+ '[Current conversation target]',
1203
+ 'reply_target: [same surface as bigbang message read --channel ... below]',
1204
+ '',
1205
+ ...(params.sharedCollaborationSectionText ? [params.sharedCollaborationSectionText.trim(), ''] : []),
1206
+ '[Peer update batch]',
1207
+ ];
1208
+ const minimalStub = [
1209
+ `${topPriority.index + 1}. seq=${topPriority.item.seq} sender=@${topPriority.item.senderName}${topPriority.item.contextRole === 'self' ? ' (you)' : ''} delivery=${topPriority.item.peerDelivery}`,
1210
+ readHistoryInstruction({
1211
+ replyTarget: params.replyTarget,
1212
+ messageId: topPriority.item.messageId,
1213
+ includeRoot: params.includeRootHint === true,
1214
+ }),
1215
+ ];
1216
+ const compactPromptWithOmittedRefs = () => {
1217
+ const omitted = params.items
1218
+ .filter((item) => item.messageId !== topPriority.item.messageId)
1219
+ .map((item) => ({ item }));
1220
+ if (omitted.length === 0) {
1221
+ return null;
1222
+ }
1223
+ const baseLength = [...compactHeader, ...minimalStub, ...compactFooter].join('\n').length;
1224
+ const omittedBudget = Math.max(0, maxPromptChars - baseLength - 2);
1225
+ const omittedLines = omittedLinesFor(omitted, omittedBudget);
1226
+ if (omittedLines.length === 0) {
1227
+ return null;
1228
+ }
1229
+ const compactPrompt = [...compactHeader, ...minimalStub, ...omittedLines, ...compactFooter].join('\n');
1230
+ return compactPrompt.length <= maxPromptChars ? compactPrompt : null;
1231
+ };
1232
+ const prompt = assemble(selected, forcedTopBlock ? [forcedTopBlock] : []);
1233
+ const omittedInPrompt = params.items.some((item) => !selected.has(item.messageId));
1234
+ if (omittedInPrompt
1235
+ && !prompt.includes('omitted_message_seq_range=')
1236
+ && !prompt.includes('omitted_message_refs=')
1237
+ && !prompt.includes('[Omitted')) {
1238
+ const compactPrompt = compactPromptWithOmittedRefs();
1239
+ if (compactPrompt) {
1240
+ return compactPrompt;
1241
+ }
1242
+ }
1243
+ if (prompt.length <= maxPromptChars) {
1244
+ return prompt;
1245
+ }
1246
+ return compactPromptWithOmittedRefs() ?? [...compactHeader, ...minimalStub, ...compactFooter].join('\n');
1247
+ }