@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,488 @@
1
+ import { buildThreadShortId, } from '@bbigbang/protocol';
2
+ import { listRecentConversationHandoffs } from './conversationHandoffs.js';
3
+ import { getConversationSummary, listConversationSummariesForAgent } from './conversationSummaries.js';
4
+ import { compareRankedItems, scoreHandoffCandidate, scoreSurfaceCandidate, scoreTaskCandidate } from './contextBundleRanking.js';
5
+ const DEFAULT_MAX_SURFACES = 4;
6
+ const DEFAULT_MAX_TASKS = 3;
7
+ const DEFAULT_MAX_HANDOFFS = 3;
8
+ export function buildContextBundle(db, agent, conversationId, options) {
9
+ const currentConversation = getConversationSummary(db, agent, conversationId);
10
+ if (!currentConversation)
11
+ return null;
12
+ const maxSurfaces = clampCount(options?.maxSurfaces, DEFAULT_MAX_SURFACES, 8);
13
+ const maxTasks = clampCount(options?.maxTasks, DEFAULT_MAX_TASKS, 8);
14
+ const maxHandoffs = clampCount(options?.maxHandoffs, DEFAULT_MAX_HANDOFFS, 8);
15
+ const nowMs = Date.now();
16
+ const currentTargetFamily = getTargetFamily(currentConversation.replyTarget);
17
+ const currentThreadRootId = normalizeText(currentConversation.threadRootId);
18
+ const currentOwnerName = normalizeText(currentConversation.summary.ownerName);
19
+ const currentParticipants = new Set(currentConversation.summary.participants.map((name) => name.trim().toLowerCase()).filter(Boolean));
20
+ const currentTaskRefs = collectCurrentTaskRefs(currentConversation);
21
+ const currentTaskIds = collectCurrentTaskIds(currentConversation);
22
+ const currentBoundTaskId = currentConversation.summary.boundTask?.taskId ?? null;
23
+ const handoffs = listRecentConversationHandoffs(db, agent.agentId, Math.max(12, maxHandoffs * 4));
24
+ const surfaces = listConversationSummariesForAgent(db, agent)
25
+ .filter((conversation) => conversation.conversationId !== conversationId);
26
+ const tasks = listAgentTaskCandidates(db, agent.agentId)
27
+ .filter((task) => task.taskId !== currentBoundTaskId);
28
+ const rankedSurfaces = surfaces
29
+ .map((conversation) => buildSurfaceItem({
30
+ conversation,
31
+ currentConversation,
32
+ currentTargetFamily,
33
+ currentThreadRootId,
34
+ currentOwnerName,
35
+ currentParticipants,
36
+ currentTaskRefs,
37
+ currentTaskIds,
38
+ handoffs,
39
+ nowMs,
40
+ }))
41
+ .filter((item) => Boolean(item))
42
+ .sort(compareRankedItems);
43
+ const rankedTasks = tasks
44
+ .map((task) => buildTaskItem({
45
+ task,
46
+ currentTargetFamily,
47
+ currentTaskRefs,
48
+ currentTaskIds,
49
+ handoffs,
50
+ nowMs,
51
+ }))
52
+ .filter((item) => Boolean(item))
53
+ .sort(compareRankedItems);
54
+ const rankedHandoffs = handoffs
55
+ .map((handoff) => buildHandoffItem({
56
+ handoff,
57
+ currentConversationId: conversationId,
58
+ currentTargetFamily,
59
+ currentTaskRefs,
60
+ currentTaskIds,
61
+ nowMs,
62
+ }))
63
+ .filter((item) => Boolean(item))
64
+ .sort(compareRankedItems);
65
+ const selectedItems = [
66
+ ...rankedSurfaces.slice(0, maxSurfaces),
67
+ ...rankedTasks.slice(0, maxTasks),
68
+ ...rankedHandoffs.slice(0, maxHandoffs),
69
+ ].sort(compareRankedItems);
70
+ const omittedItems = [
71
+ ...rankedSurfaces.slice(maxSurfaces),
72
+ ...rankedTasks.slice(maxTasks),
73
+ ...rankedHandoffs.slice(maxHandoffs),
74
+ ];
75
+ const recommended = recommendRelevantContextNextAction(selectedItems);
76
+ return {
77
+ generatedAt: new Date(nowMs).toISOString(),
78
+ currentConversation: {
79
+ conversationId: currentConversation.conversationId,
80
+ replyTarget: currentConversation.replyTarget,
81
+ status: currentConversation.status,
82
+ surfaceKind: currentConversation.surfaceKind,
83
+ boundTaskRef: currentConversation.summary.boundTask?.agentTaskRef ?? null,
84
+ linkedTaskRefs: currentConversation.summary.linkedTaskRefs,
85
+ },
86
+ recommendedNextAction: recommended.action,
87
+ recommendedNextActionReason: recommended.reason,
88
+ suggestedHistoryReads: buildContextBundleHistorySuggestions(selectedItems),
89
+ items: selectedItems,
90
+ totalCount: rankedSurfaces.length + rankedTasks.length + rankedHandoffs.length,
91
+ truncated: omittedItems.length > 0,
92
+ overflowByStatus: summarizeContextBundleOverflowByStatus(omittedItems),
93
+ };
94
+ }
95
+ export function buildRelevantContextHintSection(bundle) {
96
+ const top = bundle?.items[0];
97
+ if (!top || !hasStrongRelevantContextHintSignal(top))
98
+ return '';
99
+ return [
100
+ '[Relevant context hint]',
101
+ 'Treat this as a narrowing hint, not a mandatory route.',
102
+ `Top related item: ${formatRelevantContextHintItem(top)}`,
103
+ `Context signal: ${bundle.recommendedNextActionReason}`,
104
+ 'If you need the full ranked bundle before deciding what to inspect or whether to hand off, run bigbang context bundle --format json.',
105
+ ].join('\n');
106
+ }
107
+ function buildSurfaceItem(params) {
108
+ const candidateTaskRefs = new Set(params.conversation.summary.linkedTaskRefs.map(normalizeTaskRef).filter(Boolean));
109
+ const candidateTaskIds = new Set(params.conversation.summary.linkedTasks.map((task) => task.taskId).filter(Boolean));
110
+ if (params.conversation.summary.boundTask?.taskId)
111
+ candidateTaskIds.add(params.conversation.summary.boundTask.taskId);
112
+ if (params.conversation.summary.boundTask?.agentTaskRef) {
113
+ candidateTaskRefs.add(normalizeTaskRef(params.conversation.summary.boundTask.agentTaskRef));
114
+ }
115
+ const relatedHandoffs = params.handoffs.filter((handoff) => (handoff.sourceConversationId === params.conversation.conversationId
116
+ || handoff.targetConversationId === params.conversation.conversationId));
117
+ const participantOverlapCount = params.conversation.summary.participants.reduce((count, participant) => (params.currentParticipants.has(participant.trim().toLowerCase()) ? count + 1 : count), 0);
118
+ const linkedTaskMatches = intersectSets(params.currentTaskRefs, candidateTaskRefs);
119
+ const hasBoundTaskMatch = hasSetOverlap(params.currentTaskIds, candidateTaskIds);
120
+ const hasActiveHandoff = relatedHandoffs.some((handoff) => isOpenHandoff(handoff) && relatesToCurrent(handoff, params.currentConversation, params.currentTaskRefs, params.currentTaskIds));
121
+ const hasRecentHandoff = relatedHandoffs.some((handoff) => !isOpenHandoff(handoff) && relatesToCurrent(handoff, params.currentConversation, params.currentTaskRefs, params.currentTaskIds));
122
+ const ranked = scoreSurfaceCandidate({
123
+ hasBoundTaskMatch,
124
+ linkedTaskMatches,
125
+ hasActiveHandoff,
126
+ hasRecentHandoff,
127
+ isSameThreadRoot: Boolean(params.currentThreadRootId) && params.currentThreadRootId === normalizeText(params.conversation.threadRootId),
128
+ isSameTargetFamily: getTargetFamily(params.conversation.replyTarget) === params.currentTargetFamily,
129
+ participantOverlapCount,
130
+ ownerMatch: Boolean(params.currentOwnerName) && params.currentOwnerName === normalizeText(params.conversation.summary.ownerName),
131
+ hasRole: Boolean(params.conversation.summary.myRole),
132
+ unreadCount: params.conversation.unreadCount,
133
+ queuedPromptCount: params.conversation.queuedPromptCount,
134
+ updatedAt: params.conversation.updatedAt,
135
+ nowMs: params.nowMs,
136
+ });
137
+ if (ranked.score <= 0)
138
+ return null;
139
+ return {
140
+ kind: 'surface',
141
+ score: ranked.score,
142
+ reasons: ranked.reasons,
143
+ updatedAt: params.conversation.updatedAt,
144
+ conversationId: params.conversation.conversationId,
145
+ replyTarget: params.conversation.replyTarget,
146
+ status: params.conversation.status,
147
+ surfaceKind: params.conversation.surfaceKind,
148
+ threadKind: params.conversation.threadKind,
149
+ threadRootId: params.conversation.threadRootId ?? null,
150
+ unreadCount: params.conversation.unreadCount,
151
+ queuedPromptCount: params.conversation.queuedPromptCount,
152
+ goal: params.conversation.summary.goal,
153
+ nextStep: params.conversation.summary.nextStep,
154
+ myRole: params.conversation.summary.myRole,
155
+ ownerName: params.conversation.summary.ownerName,
156
+ linkedTaskRefs: params.conversation.summary.linkedTaskRefs,
157
+ boundTask: params.conversation.summary.boundTask,
158
+ activeHandoff: params.conversation.summary.activeHandoff,
159
+ lastMessage: params.conversation.summary.lastMessage,
160
+ summaryText: params.conversation.summaryText,
161
+ };
162
+ }
163
+ function buildTaskItem(params) {
164
+ const normalizedTaskRef = normalizeTaskRef(params.task.agentTaskRef);
165
+ const linkedTaskMatches = [
166
+ ...(normalizedTaskRef && params.currentTaskRefs.has(normalizedTaskRef) ? [normalizedTaskRef] : []),
167
+ ...(params.currentTaskIds.has(params.task.taskId) ? [params.task.taskId] : []),
168
+ ];
169
+ const hasBoundTaskMatch = params.currentTaskIds.has(params.task.taskId);
170
+ const relatedHandoffs = params.handoffs.filter((handoff) => (handoffMatchesTaskIdentity(handoff, {
171
+ taskId: params.task.taskId,
172
+ normalizedTaskRef,
173
+ })));
174
+ const threadTarget = buildTaskThreadTarget(params.task.sourceTarget, params.task.messageId);
175
+ const ranked = scoreTaskCandidate({
176
+ hasBoundTaskMatch,
177
+ linkedTaskMatches,
178
+ hasActiveHandoff: relatedHandoffs.some(isOpenHandoff),
179
+ hasRecentHandoff: relatedHandoffs.some((handoff) => !isOpenHandoff(handoff)),
180
+ isSameTargetFamily: getTargetFamily(threadTarget ?? params.task.sourceTarget ?? '') === params.currentTargetFamily,
181
+ status: params.task.status,
182
+ updatedAt: new Date(params.task.updatedAt).toISOString(),
183
+ nowMs: params.nowMs,
184
+ });
185
+ if (ranked.score <= 0)
186
+ return null;
187
+ return {
188
+ kind: 'task',
189
+ score: ranked.score,
190
+ reasons: ranked.reasons,
191
+ updatedAt: new Date(params.task.updatedAt).toISOString(),
192
+ taskId: params.task.taskId,
193
+ agentTaskRef: params.task.agentTaskRef,
194
+ taskNumber: params.task.taskNumber,
195
+ title: params.task.title,
196
+ status: params.task.status,
197
+ sourceTarget: params.task.sourceTarget,
198
+ sourceLabel: buildTaskSourceLabel(params.task.channelId, params.task.sourceTarget, params.task.channelName),
199
+ threadTarget,
200
+ };
201
+ }
202
+ function buildHandoffItem(params) {
203
+ const ranked = scoreHandoffCandidate({
204
+ matchesCurrentConversation: params.handoff.sourceConversationId === params.currentConversationId || params.handoff.targetConversationId === params.currentConversationId,
205
+ hasRelatedTaskMatch: handoffMatchesCurrentTaskContext(params.handoff, params.currentTaskIds, params.currentTaskRefs),
206
+ isSameTargetFamily: getTargetFamily(params.handoff.targetReplyTarget) === params.currentTargetFamily
207
+ || getTargetFamily(params.handoff.payload.sourceTarget ?? '') === params.currentTargetFamily,
208
+ status: params.handoff.status,
209
+ updatedAt: params.handoff.updatedAt,
210
+ nowMs: params.nowMs,
211
+ });
212
+ if (ranked.score <= 0)
213
+ return null;
214
+ return {
215
+ kind: 'handoff',
216
+ score: ranked.score,
217
+ reasons: ranked.reasons,
218
+ updatedAt: params.handoff.updatedAt,
219
+ handoffId: params.handoff.handoffId,
220
+ mode: params.handoff.mode,
221
+ status: params.handoff.status,
222
+ workflowKind: params.handoff.workflowKind,
223
+ sourceDisposition: params.handoff.sourceDisposition,
224
+ sourceConversationId: params.handoff.sourceConversationId,
225
+ sourceTarget: params.handoff.payload.sourceTarget,
226
+ targetConversationId: params.handoff.targetConversationId,
227
+ targetReplyTarget: params.handoff.targetReplyTarget,
228
+ goal: params.handoff.payload.goal,
229
+ relatedTaskRef: params.handoff.payload.relatedTaskRef,
230
+ taskId: params.handoff.taskId,
231
+ taskNumber: params.handoff.taskNumber,
232
+ };
233
+ }
234
+ export function recommendRelevantContextNextAction(items) {
235
+ const top = items[0];
236
+ if (!top) {
237
+ return {
238
+ action: 'stay_on_current_surface',
239
+ reason: 'No higher-signal related surface, task, or handoff was found.',
240
+ };
241
+ }
242
+ if (top.kind === 'handoff') {
243
+ if (top.status === 'started' || top.status === 'accepted') {
244
+ return {
245
+ action: 'check_handoff',
246
+ reason: `Open handoff ${top.handoffId} is the highest-signal related context.`,
247
+ };
248
+ }
249
+ return {
250
+ action: 'inspect_surface',
251
+ reason: `Recent handoff ${top.handoffId} is the highest-signal related context.`,
252
+ };
253
+ }
254
+ if (top.kind === 'task') {
255
+ return {
256
+ action: 'read_task_history',
257
+ reason: `Task ${formatTaskIdentity(top.agentTaskRef, top.taskNumber)} is the highest-signal related context.`,
258
+ };
259
+ }
260
+ const reasonCodes = new Set(top.reasons.map((reason) => reason.code));
261
+ if (reasonCodes.has('active_handoff')) {
262
+ return {
263
+ action: 'check_handoff',
264
+ reason: `${top.replyTarget} has an open handoff relationship with the current work.`,
265
+ };
266
+ }
267
+ if (reasonCodes.has('bound_task_match') || reasonCodes.has('linked_task_match')) {
268
+ if ((reasonCodes.has('queued_activity') || reasonCodes.has('unread_activity')) && isRunnableSurfaceStatus(top.status)) {
269
+ return {
270
+ action: 'handoff_candidate',
271
+ reason: `${top.replyTarget} shares the current task context and already has waiting activity.`,
272
+ };
273
+ }
274
+ return {
275
+ action: 'inspect_surface',
276
+ reason: `${top.replyTarget} shares the current task context.`,
277
+ };
278
+ }
279
+ if (reasonCodes.has('unread_activity') || reasonCodes.has('queued_activity')) {
280
+ return {
281
+ action: 'read_history',
282
+ reason: `${top.replyTarget} has waiting activity to inspect.`,
283
+ };
284
+ }
285
+ return {
286
+ action: 'inspect_surface',
287
+ reason: `${top.replyTarget} is the highest-signal neighboring surface.`,
288
+ };
289
+ }
290
+ export function buildContextBundleHistorySuggestions(items) {
291
+ const suggestions = [];
292
+ const seenTargets = new Set();
293
+ for (const item of items) {
294
+ if (suggestions.length >= 3)
295
+ break;
296
+ if (item.kind === 'surface') {
297
+ if (seenTargets.has(item.replyTarget))
298
+ continue;
299
+ seenTargets.add(item.replyTarget);
300
+ suggestions.push({
301
+ target: item.replyTarget,
302
+ around: item.lastMessage?.messageId ?? null,
303
+ includeRoot: item.surfaceKind === 'dm_thread' || item.surfaceKind === 'channel_thread',
304
+ reason: item.reasons[0]?.label ?? 'Top-ranked related surface',
305
+ });
306
+ continue;
307
+ }
308
+ if (item.kind === 'handoff') {
309
+ if (seenTargets.has(item.targetReplyTarget))
310
+ continue;
311
+ seenTargets.add(item.targetReplyTarget);
312
+ suggestions.push({
313
+ target: item.targetReplyTarget,
314
+ includeRoot: isThreadTarget(item.targetReplyTarget),
315
+ reason: item.reasons[0]?.label ?? 'Top-ranked related handoff target',
316
+ });
317
+ }
318
+ }
319
+ return suggestions;
320
+ }
321
+ export function summarizeContextBundleOverflowByStatus(items) {
322
+ return items.reduce((counts, item) => {
323
+ const statusKey = `${item.kind}:${item.status}`;
324
+ counts[statusKey] = (counts[statusKey] ?? 0) + 1;
325
+ return counts;
326
+ }, {});
327
+ }
328
+ function isRunnableSurfaceStatus(status) {
329
+ return status === 'active' || status === 'queued' || status === 'recovering';
330
+ }
331
+ function isThreadTarget(target) {
332
+ const trimmed = target.trim();
333
+ if (trimmed.startsWith('dm:@'))
334
+ return trimmed.split(':').length >= 3;
335
+ if (trimmed.startsWith('#'))
336
+ return trimmed.includes(':');
337
+ return false;
338
+ }
339
+ function collectCurrentTaskRefs(conversation) {
340
+ const refs = new Set();
341
+ for (const taskRef of conversation.summary.linkedTaskRefs) {
342
+ const normalized = normalizeTaskRef(taskRef);
343
+ if (normalized)
344
+ refs.add(normalized);
345
+ }
346
+ const boundTaskRef = normalizeTaskRef(conversation.summary.boundTask?.agentTaskRef);
347
+ if (boundTaskRef)
348
+ refs.add(boundTaskRef);
349
+ return refs;
350
+ }
351
+ function collectCurrentTaskIds(conversation) {
352
+ const ids = new Set();
353
+ for (const task of conversation.summary.linkedTasks) {
354
+ if (task.taskId)
355
+ ids.add(task.taskId);
356
+ }
357
+ if (conversation.summary.boundTask?.taskId)
358
+ ids.add(conversation.summary.boundTask.taskId);
359
+ return ids;
360
+ }
361
+ function listAgentTaskCandidates(db, agentId) {
362
+ return db.prepare(`SELECT t.task_id as taskId,
363
+ t.agent_task_ref as agentTaskRef,
364
+ t.channel_id as channelId,
365
+ t.task_number as taskNumber,
366
+ t.title,
367
+ t.status,
368
+ t.updated_at as updatedAt,
369
+ t.message_id as messageId,
370
+ COALESCE(t.dm_target, cm.target) as sourceTarget,
371
+ ch.name as channelName
372
+ FROM tasks t
373
+ LEFT JOIN task_participants tp ON tp.task_id = t.task_id AND tp.agent_id = ?
374
+ LEFT JOIN channel_messages cm ON cm.message_id = t.message_id
375
+ LEFT JOIN channels ch ON ch.channel_id = t.channel_id
376
+ WHERE t.claimed_by_agent_id = ? OR tp.agent_id = ?
377
+ ORDER BY t.updated_at DESC, t.created_at DESC
378
+ LIMIT 24`).all(agentId, agentId, agentId);
379
+ }
380
+ function relatesToCurrent(handoff, currentConversation, currentTaskRefs, currentTaskIds) {
381
+ if (handoff.sourceConversationId === currentConversation.conversationId || handoff.targetConversationId === currentConversation.conversationId) {
382
+ return true;
383
+ }
384
+ if (handoffMatchesCurrentTaskContext(handoff, currentTaskIds, currentTaskRefs))
385
+ return true;
386
+ const currentTargetFamily = getTargetFamily(currentConversation.replyTarget);
387
+ return getTargetFamily(handoff.targetReplyTarget) === currentTargetFamily
388
+ || getTargetFamily(handoff.payload.sourceTarget ?? '') === currentTargetFamily;
389
+ }
390
+ function handoffMatchesCurrentTaskContext(handoff, currentTaskIds, currentTaskRefs) {
391
+ if (handoff.taskId)
392
+ return currentTaskIds.has(handoff.taskId);
393
+ const relatedTaskRef = normalizeTaskRef(handoff.payload.relatedTaskRef);
394
+ return relatedTaskRef ? currentTaskRefs.has(relatedTaskRef) : false;
395
+ }
396
+ function handoffMatchesTaskIdentity(handoff, params) {
397
+ if (handoff.taskId)
398
+ return handoff.taskId === params.taskId;
399
+ return Boolean(params.normalizedTaskRef) && normalizeTaskRef(handoff.payload.relatedTaskRef) === params.normalizedTaskRef;
400
+ }
401
+ function buildTaskSourceLabel(channelId, sourceTarget, channelName) {
402
+ if (sourceTarget)
403
+ return sourceTarget;
404
+ if (channelId.startsWith('dm:'))
405
+ return channelId;
406
+ return `#${channelName ?? channelId}`;
407
+ }
408
+ export function buildTaskThreadTarget(sourceTarget, messageId) {
409
+ if (!sourceTarget || !messageId)
410
+ return null;
411
+ const normalizedTarget = sourceTarget.trim();
412
+ if (!(normalizedTarget.startsWith('dm:@') || normalizedTarget.startsWith('#')))
413
+ return null;
414
+ if (isThreadTarget(normalizedTarget))
415
+ return normalizedTarget;
416
+ return `${normalizedTarget}:${buildThreadShortId(messageId)}`;
417
+ }
418
+ function hasStrongRelevantContextHintSignal(item) {
419
+ return item.reasons.some((reason) => (reason.code === 'active_handoff'
420
+ || reason.code === 'bound_task_match'
421
+ || reason.code === 'current_conversation_handoff'));
422
+ }
423
+ function formatRelevantContextHintItem(item) {
424
+ if (item.kind === 'surface') {
425
+ return `surface ${item.replyTarget} [${item.status}]`;
426
+ }
427
+ if (item.kind === 'task') {
428
+ const target = item.threadTarget ?? item.sourceTarget;
429
+ return target
430
+ ? `task ${formatTaskIdentity(item.agentTaskRef, item.taskNumber)} [${item.status}] -> ${target}`
431
+ : `task ${formatTaskIdentity(item.agentTaskRef, item.taskNumber)} [${item.status}]`;
432
+ }
433
+ const source = item.sourceTarget ?? item.sourceConversationId;
434
+ return `handoff ${item.mode} [${item.status}] ${source} -> ${item.targetReplyTarget}`;
435
+ }
436
+ function formatTaskIdentity(agentTaskRef, taskNumber) {
437
+ if (agentTaskRef && taskNumber != null)
438
+ return `${agentTaskRef} · #t${taskNumber}`;
439
+ if (agentTaskRef)
440
+ return agentTaskRef;
441
+ if (taskNumber != null)
442
+ return `#t${taskNumber}`;
443
+ return 'task';
444
+ }
445
+ function clampCount(value, defaultValue, maxValue) {
446
+ if (!Number.isFinite(value))
447
+ return defaultValue;
448
+ return Math.max(1, Math.min(Math.floor(value), maxValue));
449
+ }
450
+ function normalizeTaskRef(taskRef) {
451
+ return taskRef?.trim().toLowerCase() ?? '';
452
+ }
453
+ function normalizeText(value) {
454
+ const trimmed = value?.trim();
455
+ return trimmed ? trimmed.toLowerCase() : null;
456
+ }
457
+ function getTargetFamily(target) {
458
+ const trimmed = target.trim().toLowerCase();
459
+ if (!trimmed)
460
+ return '';
461
+ if (trimmed.startsWith('dm:@')) {
462
+ const parts = trimmed.split(':');
463
+ return parts.length >= 2 ? `${parts[0]}:${parts[1]}` : trimmed;
464
+ }
465
+ if (trimmed.startsWith('#')) {
466
+ const index = trimmed.indexOf(':');
467
+ return index >= 0 ? trimmed.slice(0, index) : trimmed;
468
+ }
469
+ return trimmed;
470
+ }
471
+ function isOpenHandoff(handoff) {
472
+ return handoff.status === 'started' || handoff.status === 'accepted';
473
+ }
474
+ function hasSetOverlap(left, right) {
475
+ for (const value of left) {
476
+ if (right.has(value))
477
+ return true;
478
+ }
479
+ return false;
480
+ }
481
+ function intersectSets(left, right) {
482
+ const values = [];
483
+ for (const value of left) {
484
+ if (right.has(value))
485
+ values.push(value);
486
+ }
487
+ return values;
488
+ }
@@ -0,0 +1,50 @@
1
+ import { BUILTIN_LIBRARY_DOCUMENTS_SKILL_ROOT_SENTINEL, BUILTIN_UI_PANEL_SKILL_ROOT_SENTINEL, BUILTIN_WORKSPACE_TOOL_SKILL_ROOT_SENTINEL, } from '@bbigbang/protocol';
2
+ export const CONVERSATION_BUILTIN_SKILL_ROOTS = [
3
+ BUILTIN_WORKSPACE_TOOL_SKILL_ROOT_SENTINEL,
4
+ BUILTIN_UI_PANEL_SKILL_ROOT_SENTINEL,
5
+ BUILTIN_LIBRARY_DOCUMENTS_SKILL_ROOT_SENTINEL,
6
+ ];
7
+ const BUILTIN_SKILL_ROOT_SET = new Set(CONVERSATION_BUILTIN_SKILL_ROOTS);
8
+ export function isConversationBuiltinSkillRoot(value) {
9
+ return BUILTIN_SKILL_ROOT_SET.has(value);
10
+ }
11
+ export function normalizeConversationBuiltinSkillRoots(values) {
12
+ const enabled = new Set();
13
+ for (const rawValue of values) {
14
+ const value = rawValue?.trim();
15
+ if (!value || !isConversationBuiltinSkillRoot(value))
16
+ continue;
17
+ enabled.add(value);
18
+ }
19
+ return CONVERSATION_BUILTIN_SKILL_ROOTS.filter((value) => enabled.has(value));
20
+ }
21
+ export function parseConversationBuiltinSkillRoots(raw) {
22
+ if (!raw?.trim())
23
+ return [];
24
+ try {
25
+ const parsed = JSON.parse(raw);
26
+ if (!Array.isArray(parsed))
27
+ return [];
28
+ return normalizeConversationBuiltinSkillRoots(parsed.map((value) => (typeof value === 'string' ? value : null)));
29
+ }
30
+ catch {
31
+ return [];
32
+ }
33
+ }
34
+ export function serializeConversationBuiltinSkillRoots(values) {
35
+ return JSON.stringify(normalizeConversationBuiltinSkillRoots(values));
36
+ }
37
+ export function conversationBuiltinSkillRootsForSlashCommand(slashCommandName) {
38
+ const normalized = slashCommandName?.trim().toLowerCase();
39
+ if (normalized === 'tool' || normalized === 'tool:plan') {
40
+ return [
41
+ BUILTIN_WORKSPACE_TOOL_SKILL_ROOT_SENTINEL,
42
+ BUILTIN_UI_PANEL_SKILL_ROOT_SENTINEL,
43
+ ];
44
+ }
45
+ if (normalized === 'panel' || normalized === 'panel:plan')
46
+ return [BUILTIN_UI_PANEL_SKILL_ROOT_SENTINEL];
47
+ if (normalized === 'document')
48
+ return [BUILTIN_LIBRARY_DOCUMENTS_SKILL_ROOT_SENTINEL];
49
+ return [];
50
+ }