@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.
- package/dist/config.js +380 -0
- package/dist/execution/executionDispatcher.js +3810 -0
- package/dist/main.js +90 -0
- package/dist/nodeEventHistory.js +206 -0
- package/dist/scheduler/dreamLogic.js +50 -0
- package/dist/scheduler/dreamScheduler.js +65 -0
- package/dist/services/agentFileAccessService.js +1913 -0
- package/dist/services/agentRuntimeCleanupBroker.js +62 -0
- package/dist/services/agentSkillsBroker.js +118 -0
- package/dist/services/agentSkillsService.js +83 -0
- package/dist/services/agentWorkspaceBroker.js +937 -0
- package/dist/services/agentWorkspaceService.js +70 -0
- package/dist/services/appVersion.js +14 -0
- package/dist/services/auth.js +586 -0
- package/dist/services/claudeControlBroker.js +154 -0
- package/dist/services/claudeTranscriptBroker.js +100 -0
- package/dist/services/claudeTranscriptService.js +359 -0
- package/dist/services/codexAppServerBroker.js +155 -0
- package/dist/services/codexTranscriptBroker.js +98 -0
- package/dist/services/codexTranscriptService.js +961 -0
- package/dist/services/droidMissionBroker.js +124 -0
- package/dist/services/droidMissionImporter.js +630 -0
- package/dist/services/droidModelOptions.js +165 -0
- package/dist/services/hubServerRegistrationService.js +268 -0
- package/dist/services/libraryManifest.js +43 -0
- package/dist/services/libraryScaffold.js +26 -0
- package/dist/services/libraryService.js +2263 -0
- package/dist/services/memoryService.js +386 -0
- package/dist/services/missionEvidence.js +377 -0
- package/dist/services/missionService.js +2361 -0
- package/dist/services/missionTrace.js +158 -0
- package/dist/services/nativeMissionBriefParser.js +120 -0
- package/dist/services/nativeMissionOrchestrator.js +2045 -0
- package/dist/services/nativeMissionReportGenerator.js +227 -0
- package/dist/services/nativeMissionValidationRunner.js +452 -0
- package/dist/services/nativeMissionWorkerBroker.js +190 -0
- package/dist/services/nodeRegistry.js +34 -0
- package/dist/services/nodeStateReconciler.js +97 -0
- package/dist/services/panelMediaScanner.js +119 -0
- package/dist/services/persistentRuntimeJsonlClient.js +153 -0
- package/dist/services/platformAgentPolicy.js +180 -0
- package/dist/services/platformAgentService.js +2041 -0
- package/dist/services/projectAccessResolver.js +93 -0
- package/dist/services/projectService.js +392 -0
- package/dist/services/resourceSpaceService.js +140 -0
- package/dist/services/scenarioRuntimeService.js +1130 -0
- package/dist/services/suggestedPlannerService.js +868 -0
- package/dist/services/workbenchGitBroker.js +161 -0
- package/dist/services/workbenchGitService.js +69 -0
- package/dist/services/workbenchInspectBroker.js +65 -0
- package/dist/services/workbenchNodePathService.js +79 -0
- package/dist/services/workbenchRegistryService.js +240 -0
- package/dist/services/workbenchRootService.js +181 -0
- package/dist/services/workbenchTerminalBroker.js +378 -0
- package/dist/services/workspaceRunOwnership.js +60 -0
- package/dist/services/workspaceScaffold.js +105 -0
- package/dist/services/workspaceSessionRuntimeService.js +576 -0
- package/dist/services/workspaceSessionService.js +245 -0
- package/dist/services/workspaceToolActionRunner.js +1582 -0
- package/dist/services/workspaceToolErrors.js +10 -0
- package/dist/services/workspaceToolExecutionUtils.js +895 -0
- package/dist/services/workspaceToolLatestStateProjector.js +91 -0
- package/dist/services/workspaceToolManifest.js +572 -0
- package/dist/services/workspaceToolMutationQueue.js +43 -0
- package/dist/services/workspaceToolPanelProjection.js +460 -0
- package/dist/services/workspaceToolPromotion.js +255 -0
- package/dist/services/workspaceToolPromotionState.js +224 -0
- package/dist/services/workspaceToolPublishDiagnostics.js +189 -0
- package/dist/services/workspaceToolPublishIdentityResolver.js +146 -0
- package/dist/services/workspaceToolReadModel.js +378 -0
- package/dist/services/workspaceToolRunLedger.js +239 -0
- package/dist/services/workspaceToolService.js +3067 -0
- package/dist/services/workspaceToolSnapshotPanelSync.js +293 -0
- package/dist/services/workspaceToolTerminalLifecycle.js +283 -0
- package/dist/services/workspaceToolTypes.js +1 -0
- package/dist/services/workspaceToolUploadMaterializer.js +228 -0
- package/dist/web/actionCardRoutes.js +129 -0
- package/dist/web/actionCards.js +469 -0
- package/dist/web/activationContext.js +684 -0
- package/dist/web/agentChannelGuards.js +48 -0
- package/dist/web/agentMentionCooldowns.js +32 -0
- package/dist/web/agentReminders.js +1668 -0
- package/dist/web/agentRuntimePresence.js +197 -0
- package/dist/web/agentSelfState.js +494 -0
- package/dist/web/agentTaskLinks.js +26 -0
- package/dist/web/agentVisibility.js +79 -0
- package/dist/web/assets.js +95 -0
- package/dist/web/channelActivationPrompt.js +395 -0
- package/dist/web/channelMemoryNotes.js +127 -0
- package/dist/web/channelMentions.js +10 -0
- package/dist/web/channelMessageSequences.js +19 -0
- package/dist/web/channelSubscriptions.js +26 -0
- package/dist/web/clearedTaskRoots.js +10 -0
- package/dist/web/collaborationPromptGuidance.js +36 -0
- package/dist/web/collaborationSurfaceState.js +140 -0
- package/dist/web/contextBundleRanking.js +154 -0
- package/dist/web/contextBundleResolver.js +488 -0
- package/dist/web/conversationBuiltinSkillRoots.js +50 -0
- package/dist/web/conversationControls.js +232 -0
- package/dist/web/conversationHandoffs.js +612 -0
- package/dist/web/conversationManager.js +2511 -0
- package/dist/web/conversationSummaries.js +876 -0
- package/dist/web/conversationSurfaceKinds.js +17 -0
- package/dist/web/conversationTargets.js +173 -0
- package/dist/web/directActivationPrompt.js +122 -0
- package/dist/web/directReplyTargets.js +69 -0
- package/dist/web/directThreadResolver.js +129 -0
- package/dist/web/dmTaskHandoffPrompt.js +120 -0
- package/dist/web/dmTaskThreadStatusProjection.js +229 -0
- package/dist/web/ftsQuery.js +33 -0
- package/dist/web/internalAgentRouter.js +11341 -0
- package/dist/web/libraryCuratorScheduler.js +58 -0
- package/dist/web/libraryDocumentPromptGuidance.js +8 -0
- package/dist/web/messageCheckpoints.js +19 -0
- package/dist/web/nodeWsHandler.js +2495 -0
- package/dist/web/notificationRounds.js +1061 -0
- package/dist/web/panelActionMessages.js +108 -0
- package/dist/web/panelActivationPrompt.js +18 -0
- package/dist/web/panelAudit.js +273 -0
- package/dist/web/panelLifecycle.js +222 -0
- package/dist/web/panelMediaPolicy.js +43 -0
- package/dist/web/panelPathPolicy.js +63 -0
- package/dist/web/panelPreviews.js +175 -0
- package/dist/web/panelQueryHandles.js +2749 -0
- package/dist/web/panelRoutes.js +2147 -0
- package/dist/web/panels.js +904 -0
- package/dist/web/peerInboxAggregates.js +1247 -0
- package/dist/web/planApprovalState.js +92 -0
- package/dist/web/platformAgentScheduler.js +66 -0
- package/dist/web/proactiveOpportunities.js +452 -0
- package/dist/web/promptContextSections.js +242 -0
- package/dist/web/promptHistorySanitizer.js +26 -0
- package/dist/web/promptSlashCommands.js +158 -0
- package/dist/web/rollingConversationSummary.js +453 -0
- package/dist/web/routeHelpers.js +11 -0
- package/dist/web/routes/handoff.js +288 -0
- package/dist/web/routes/history.js +345 -0
- package/dist/web/routes/memory.js +258 -0
- package/dist/web/routes/selfState.js +171 -0
- package/dist/web/routes/workspace.js +154 -0
- package/dist/web/runSurfaceWatermarks.js +431 -0
- package/dist/web/runtimeCapabilities.js +48 -0
- package/dist/web/sameAgentHandoffs.js +494 -0
- package/dist/web/server.js +15567 -0
- package/dist/web/sharedCollaborationCapsules.js +163 -0
- package/dist/web/soloSessionRelay.js +42 -0
- package/dist/web/soloWsHandler.js +138 -0
- package/dist/web/suggestedPlannerScheduler.js +56 -0
- package/dist/web/surfaceActivationPolicy.js +108 -0
- package/dist/web/surfaceCollaborators.js +61 -0
- package/dist/web/surfaceSystemStatus.js +263 -0
- package/dist/web/targetParticipants.js +77 -0
- package/dist/web/taskEvents.js +49 -0
- package/dist/web/taskLifecycleMessages.js +165 -0
- package/dist/web/taskLoops.js +732 -0
- package/dist/web/taskMemoryNotes.js +224 -0
- package/dist/web/taskNumbers.js +16 -0
- package/dist/web/taskOwnerGuards.js +49 -0
- package/dist/web/taskParticipantResolver.js +42 -0
- package/dist/web/taskParticipants.js +97 -0
- package/dist/web/taskSourceDetails.js +20 -0
- package/dist/web/taskStateViews.js +210 -0
- package/dist/web/taskStatusTransitions.js +9 -0
- package/dist/web/taskThreadFollowups.js +599 -0
- package/dist/web/taskThreadRuntimeClosure.js +685 -0
- package/dist/web/taskUpdateDelivery.js +104 -0
- package/dist/web/threadReplyContentHeuristics.js +30 -0
- package/dist/web/threadRoots.js +61 -0
- package/dist/web/threadTaskBindings.js +365 -0
- package/dist/web/uiPanelPromptGuidance.js +27 -0
- package/dist/web/workspaceMemoryHints.js +143 -0
- package/dist/web/workspaceToolPromptGuidance.js +30 -0
- package/dist/web/wsHandler.js +397 -0
- package/dist/web/wsSink.js +116 -0
- 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
|
+
}
|