@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,197 @@
1
+ function toIso(timestamp) {
2
+ return typeof timestamp === 'number' && Number.isFinite(timestamp) && timestamp > 0
3
+ ? new Date(timestamp).toISOString()
4
+ : null;
5
+ }
6
+ function parseSurfaceKind(value) {
7
+ if (value === 'dm')
8
+ return 'dm';
9
+ if (value === 'dm_thread')
10
+ return 'dm_thread';
11
+ if (value === 'channel' || value === 'channel_root')
12
+ return 'channel';
13
+ if (value === 'channel_thread' || value === 'task_thread')
14
+ return 'channel_thread';
15
+ return null;
16
+ }
17
+ function compareHosts(a, b) {
18
+ const rank = (state) => {
19
+ switch (state) {
20
+ case 'active': return 0;
21
+ case 'idle': return 1;
22
+ case 'failed': return 2;
23
+ default: return 3;
24
+ }
25
+ };
26
+ const rankDelta = rank(a.state) - rank(b.state);
27
+ if (rankDelta !== 0)
28
+ return rankDelta;
29
+ return a.hostKey.localeCompare(b.hostKey);
30
+ }
31
+ export function collectAgentRuntimePresence(db, manager, agent, options) {
32
+ const machine = agent.nodeId ? manager.getMachine(agent.nodeId) : null;
33
+ const currentHostBinding = options?.conversationId ? manager.getConversationHostKey(options.conversationId) : null;
34
+ const hostBindings = manager.getAgentHostKeys(agent.agentId);
35
+ const bindingsOnNode = agent.nodeId
36
+ ? hostBindings.filter((binding) => binding.nodeId === agent.nodeId)
37
+ : [];
38
+ const conversationRows = agent.nodeId
39
+ ? db.prepare(`SELECT id as conversationId,
40
+ reply_target as replyTarget,
41
+ surface_kind as surfaceKind,
42
+ agent_type as agentType
43
+ FROM conversations
44
+ WHERE agent_id = ? AND node_id = ?`).all(agent.agentId, agent.nodeId)
45
+ : [];
46
+ const conversationByHostKey = new Map();
47
+ for (const row of conversationRows) {
48
+ conversationByHostKey.set(`conversation:${row.conversationId}:${row.agentType}`, {
49
+ replyTarget: row.replyTarget,
50
+ surfaceKind: parseSurfaceKind(row.surfaceKind),
51
+ });
52
+ }
53
+ const hostRows = bindingsOnNode.length > 0
54
+ ? db.prepare(`SELECT host_key as hostKey,
55
+ state,
56
+ current_run_id as currentRunId,
57
+ has_pending_approval as hasPendingApproval,
58
+ inbox_size as inboxSize,
59
+ pending_dispatch_count as pendingDispatchCount,
60
+ resumable,
61
+ last_wake_at as lastWakeAt,
62
+ last_sleep_at as lastSleepAt,
63
+ last_error as lastError
64
+ FROM node_host_snapshots
65
+ WHERE node_id = ? AND host_key IN (${bindingsOnNode.map(() => '?').join(', ')})`).all(agent.nodeId, ...bindingsOnNode.map((binding) => binding.hostKey))
66
+ : [];
67
+ const hosts = hostRows
68
+ .map((row) => {
69
+ const conversation = conversationByHostKey.get(row.hostKey);
70
+ const host = {
71
+ hostKey: row.hostKey,
72
+ replyTarget: conversation?.replyTarget ?? null,
73
+ surfaceKind: conversation?.surfaceKind ?? null,
74
+ state: row.state,
75
+ currentRunId: row.currentRunId,
76
+ hasPendingApproval: row.hasPendingApproval === 1,
77
+ inboxSize: row.inboxSize,
78
+ pendingDispatchCount: row.pendingDispatchCount,
79
+ resumable: row.resumable === 1,
80
+ lastWakeAt: toIso(row.lastWakeAt),
81
+ lastSleepAt: toIso(row.lastSleepAt),
82
+ lastError: row.lastError,
83
+ };
84
+ return host;
85
+ })
86
+ .sort(compareHosts);
87
+ const currentHostKey = currentHostBinding && currentHostBinding.nodeId === agent.nodeId
88
+ ? currentHostBinding.hostKey
89
+ : null;
90
+ const currentHost = currentHostKey ? hosts.find((host) => host.hostKey === currentHostKey) ?? null : null;
91
+ const otherHosts = hosts.filter((host) => host.hostKey !== currentHostKey).slice(0, 4);
92
+ const runtimeRow = agent.nodeId
93
+ ? db.prepare(`SELECT workspace_root as workspaceRoot,
94
+ terminal_backend_available as terminalBackendAvailable,
95
+ runtime_drivers_json as runtimeDriversJson,
96
+ capabilities_json as capabilitiesJson
97
+ FROM node_runtime_snapshots
98
+ WHERE node_id = ?`).get(agent.nodeId)
99
+ : undefined;
100
+ let runtimeDrivers = [];
101
+ if (runtimeRow?.runtimeDriversJson) {
102
+ try {
103
+ const parsed = JSON.parse(runtimeRow.runtimeDriversJson);
104
+ if (Array.isArray(parsed))
105
+ runtimeDrivers = parsed;
106
+ }
107
+ catch {
108
+ runtimeDrivers = [];
109
+ }
110
+ }
111
+ let capabilities = null;
112
+ if (runtimeRow?.capabilitiesJson) {
113
+ try {
114
+ const parsed = JSON.parse(runtimeRow.capabilitiesJson);
115
+ if (parsed && typeof parsed === 'object')
116
+ capabilities = parsed;
117
+ }
118
+ catch {
119
+ capabilities = null;
120
+ }
121
+ }
122
+ return {
123
+ generatedAt: new Date().toISOString(),
124
+ node: {
125
+ nodeId: agent.nodeId ?? null,
126
+ machineName: machine?.name ?? null,
127
+ hostname: machine?.hostname ?? null,
128
+ status: agent.nodeId ? (machine?.status ?? 'offline') : 'unassigned',
129
+ version: machine?.version ?? null,
130
+ lastSeen: toIso(machine?.lastSeen ?? null),
131
+ },
132
+ runtime: {
133
+ workspaceRoot: runtimeRow?.workspaceRoot ?? null,
134
+ terminalBackendAvailable: runtimeRow ? Boolean(runtimeRow.terminalBackendAvailable) : null,
135
+ runtimeDrivers,
136
+ capabilities,
137
+ },
138
+ summary: {
139
+ totalHosts: hosts.length,
140
+ activeHosts: hosts.filter((host) => host.state === 'active').length,
141
+ idleHosts: hosts.filter((host) => host.state === 'idle').length,
142
+ failedHosts: hosts.filter((host) => host.state === 'failed').length,
143
+ hostsWithPendingApproval: hosts.filter((host) => host.hasPendingApproval).length,
144
+ resumableHosts: hosts.filter((host) => host.resumable).length,
145
+ },
146
+ currentHost,
147
+ otherHosts,
148
+ };
149
+ }
150
+ export function buildAgentRuntimePresenceReport(presence) {
151
+ const driverNames = presence.runtime.runtimeDrivers.map((driver) => driver.agentType).join(', ') || 'none';
152
+ const capabilityParts = presence.runtime.capabilities
153
+ ? Object.entries(presence.runtime.capabilities)
154
+ .filter(([, enabled]) => Boolean(enabled))
155
+ .map(([name]) => name)
156
+ : [];
157
+ const lines = [
158
+ '[Runtime presence]',
159
+ `node: ${presence.node.nodeId ?? 'none'}${presence.node.machineName ? ` (${presence.node.machineName})` : ''} [${presence.node.status}]${presence.node.version ? ` version=${presence.node.version}` : ''}`,
160
+ `runtime: workspace_root=${presence.runtime.workspaceRoot ?? 'unknown'} | terminals=${presence.runtime.terminalBackendAvailable == null ? 'unknown' : (presence.runtime.terminalBackendAvailable ? 'yes' : 'no')} | drivers=${driverNames}`,
161
+ `hosts: total=${presence.summary.totalHosts}, active=${presence.summary.activeHosts}, idle=${presence.summary.idleHosts}, failed=${presence.summary.failedHosts}, approval=${presence.summary.hostsWithPendingApproval}, resumable=${presence.summary.resumableHosts}`,
162
+ ];
163
+ if (capabilityParts.length > 0) {
164
+ lines.push(`capabilities: ${capabilityParts.join(', ')}`);
165
+ }
166
+ if (presence.currentHost) {
167
+ const current = presence.currentHost;
168
+ const extras = [
169
+ `pending_dispatch=${current.pendingDispatchCount}`,
170
+ `inbox=${current.inboxSize}`,
171
+ `approval=${current.hasPendingApproval ? 'yes' : 'no'}`,
172
+ `resumable=${current.resumable ? 'yes' : 'no'}`,
173
+ ];
174
+ if (current.lastError)
175
+ extras.push(`last_error=${current.lastError}`);
176
+ lines.push(`current_host: ${(current.replyTarget ?? current.hostKey)} [${current.state}] | ${extras.join(' | ')}`);
177
+ }
178
+ else {
179
+ lines.push('current_host: none');
180
+ }
181
+ if (presence.otherHosts.length === 0) {
182
+ lines.push('other_hosts: none');
183
+ }
184
+ else {
185
+ lines.push('other_hosts:');
186
+ for (const host of presence.otherHosts) {
187
+ const extras = [
188
+ `pending_dispatch=${host.pendingDispatchCount}`,
189
+ `approval=${host.hasPendingApproval ? 'yes' : 'no'}`,
190
+ ];
191
+ if (host.lastError)
192
+ extras.push(`last_error=${host.lastError}`);
193
+ lines.push(`- ${(host.replyTarget ?? host.hostKey)} [${host.state}] | ${extras.join(' | ')}`);
194
+ }
195
+ }
196
+ return lines.join('\n');
197
+ }
@@ -0,0 +1,494 @@
1
+ import { buildThreadShortId, } from '@bbigbang/protocol';
2
+ import { formatConversationHandoff, listRecentConversationHandoffs } from './conversationHandoffs.js';
3
+ import { AgentVisibility } from './agentVisibility.js';
4
+ import { parseSurfaceKind } from './conversationSurfaceKinds.js';
5
+ import { getAgentMessageCheckpoint } from './messageCheckpoints.js';
6
+ import { TASK_LIFECYCLE_SOURCE } from './taskLifecycleMessages.js';
7
+ import { buildTaskSourceTarget, buildTaskThreadTarget } from './taskStateViews.js';
8
+ export function collectAgentSelfState(db, agent, options) {
9
+ const visibility = new AgentVisibility(db);
10
+ const joinedChannels = loadJoinedChannels(db, agent);
11
+ const conversationRows = db.prepare(`SELECT c.id as conversationId,
12
+ c.title,
13
+ c.status,
14
+ c.channel_id as channelId,
15
+ ch.name as channelName,
16
+ c.thread_kind as threadKind,
17
+ c.is_primary_thread as isPrimaryThread,
18
+ c.thread_root_id as threadRootId,
19
+ c.surface_kind as surfaceKind,
20
+ c.reply_target as replyTarget,
21
+ c.user_id as userId,
22
+ c.updated_at as updatedAt,
23
+ COALESCE(q.queueCount, 0) as queuedPromptCount,
24
+ ar.runId as activeRunId
25
+ FROM conversations c
26
+ LEFT JOIN channels ch ON ch.channel_id = c.channel_id
27
+ LEFT JOIN (
28
+ SELECT conversation_id, COUNT(*) as queueCount
29
+ FROM conversation_prompt_queue
30
+ WHERE COALESCE(dispatch_kind, '') != 'legacy_unknown'
31
+ GROUP BY conversation_id
32
+ ) q ON q.conversation_id = c.id
33
+ LEFT JOIN (
34
+ SELECT c2.id as conversationId, MIN(r.run_id) as runId
35
+ FROM conversations c2
36
+ JOIN runs r ON r.session_key = c2.session_key
37
+ WHERE r.ended_at IS NULL
38
+ GROUP BY c2.id
39
+ ) ar ON ar.conversationId = c.id
40
+ WHERE c.agent_id = ?
41
+ ORDER BY c.updated_at DESC, c.created_at DESC`).all(agent.agentId);
42
+ const visibleChannelIds = new Set(agent.channelIds ?? []);
43
+ const visibleTaskChannelIds = Array.from(new Set([`dm:${agent.agentId}`, ...visibleChannelIds]));
44
+ const visibleTaskChannelPlaceholders = visibleTaskChannelIds.map(() => '?').join(', ');
45
+ const conversations = conversationRows
46
+ .filter((row) => row.channelId.startsWith('dm:') || visibleChannelIds.has(row.channelId))
47
+ .map((row) => toConversationState(db, agent.agentId, row))
48
+ .sort(compareConversations(options?.conversationId ?? null));
49
+ const currentConversation = options?.conversationId
50
+ ? conversations.find((conversation) => conversation.conversationId === options.conversationId) ?? null
51
+ : null;
52
+ const taskLimit = Math.max(1, Math.min(options?.maxTasks ?? 6, 12));
53
+ const taskSummaryRow = db.prepare(`SELECT COUNT(DISTINCT t.task_id) as involvedTasks,
54
+ COUNT(DISTINCT CASE WHEN t.claimed_by_agent_id = ? THEN t.task_id END) as claimedTasks,
55
+ COUNT(DISTINCT CASE WHEN t.status = 'in_review' THEN t.task_id END) as inReviewTasks
56
+ FROM tasks t
57
+ LEFT JOIN task_participants tp
58
+ ON tp.task_id = t.task_id
59
+ AND tp.agent_id = ?
60
+ WHERE (
61
+ t.claimed_by_agent_id = ?
62
+ AND t.channel_id IN (${visibleTaskChannelPlaceholders})
63
+ )
64
+ OR (
65
+ tp.agent_id IS NOT NULL
66
+ AND t.channel_id IN (${visibleTaskChannelPlaceholders})
67
+ )`).get(agent.agentId, agent.agentId, agent.agentId, ...visibleTaskChannelIds, ...visibleTaskChannelIds);
68
+ const bufferedTaskLimit = Math.min(taskLimit * 2, 24);
69
+ const taskRows = db.prepare(`SELECT t.task_id as taskId,
70
+ t.agent_task_ref as agentTaskRef,
71
+ t.channel_id as channelId,
72
+ t.task_number as taskNumber,
73
+ t.title,
74
+ t.status,
75
+ t.updated_at as updatedAt,
76
+ t.message_id as messageId,
77
+ COALESCE(t.dm_target, cm.target) as sourceTarget,
78
+ ch.name as channelName,
79
+ CASE WHEN tp.agent_id IS NULL THEN 0 ELSE 1 END as participant,
80
+ t.claimed_by_agent_id as claimedByAgentId
81
+ FROM tasks t
82
+ LEFT JOIN task_participants tp
83
+ ON tp.task_id = t.task_id
84
+ AND tp.agent_id = ?
85
+ LEFT JOIN channel_messages cm
86
+ ON cm.message_id = t.message_id
87
+ LEFT JOIN channels ch
88
+ ON ch.channel_id = t.channel_id
89
+ WHERE (
90
+ t.claimed_by_agent_id = ?
91
+ AND t.channel_id IN (${visibleTaskChannelPlaceholders})
92
+ )
93
+ OR (
94
+ tp.agent_id IS NOT NULL
95
+ AND t.channel_id IN (${visibleTaskChannelPlaceholders})
96
+ )
97
+ ORDER BY t.updated_at DESC, t.created_at DESC
98
+ LIMIT ?`).all(agent.agentId, agent.agentId, ...visibleTaskChannelIds, ...visibleTaskChannelIds, bufferedTaskLimit);
99
+ const recentTasks = taskRows.slice(0, taskLimit).map((row) => ({
100
+ taskId: row.taskId,
101
+ agentTaskRef: row.agentTaskRef,
102
+ taskNumber: row.taskNumber,
103
+ title: row.title,
104
+ status: row.status,
105
+ sourceTarget: buildTaskSourceTarget(row.channelId, row.sourceTarget, row.channelName),
106
+ threadTarget: buildTaskThreadTarget(buildTaskSourceTarget(row.channelId, row.sourceTarget, row.channelName), row.messageId),
107
+ claimedByMe: row.claimedByAgentId === agent.agentId,
108
+ participant: row.participant === 1,
109
+ updatedAt: new Date(row.updatedAt).toISOString(),
110
+ }));
111
+ const recentHandoffs = listRecentConversationHandoffs(db, agent.agentId, 5)
112
+ .filter((handoff) => visibility.canSeeHandoff(agent, {
113
+ handoffId: handoff.handoffId,
114
+ mode: handoff.mode,
115
+ status: handoff.status,
116
+ sourceConversationId: handoff.sourceConversationId,
117
+ targetConversationId: handoff.targetConversationId,
118
+ targetReplyTarget: handoff.targetReplyTarget,
119
+ sourceTarget: handoff.payload.sourceTarget,
120
+ goal: handoff.payload.goal,
121
+ error: handoff.payload.error,
122
+ updatedAt: handoff.updatedAt,
123
+ }))
124
+ .map((handoff) => ({
125
+ handoffId: handoff.handoffId,
126
+ mode: handoff.mode,
127
+ status: handoff.status,
128
+ sourceConversationId: handoff.sourceConversationId,
129
+ sourceTarget: handoff.payload.sourceTarget,
130
+ targetConversationId: handoff.targetConversationId,
131
+ targetReplyTarget: handoff.targetReplyTarget,
132
+ goal: handoff.payload.goal,
133
+ error: handoff.payload.error,
134
+ updatedAt: handoff.updatedAt,
135
+ summaryText: formatConversationHandoff(handoff),
136
+ }));
137
+ const summary = {
138
+ totalConversations: conversations.length,
139
+ activeConversations: conversations.filter((conversation) => conversation.status === 'active').length,
140
+ queuedConversations: conversations.filter((conversation) => conversation.status === 'queued').length,
141
+ recoveringConversations: conversations.filter((conversation) => conversation.status === 'recovering').length,
142
+ awaitingApprovalConversations: conversations.filter((conversation) => conversation.status === 'awaiting_approval').length,
143
+ failedConversations: conversations.filter((conversation) => conversation.status === 'failed').length,
144
+ totalUnreadMessages: conversations.reduce((sum, conversation) => sum + conversation.unreadCount, 0),
145
+ surfacesWithUnread: conversations.filter((conversation) => conversation.unreadCount > 0).length,
146
+ pendingPrompts: conversations.reduce((sum, conversation) => sum + conversation.queuedPromptCount, 0),
147
+ claimedTasks: Number(taskSummaryRow?.claimedTasks ?? 0),
148
+ involvedTasks: Number(taskSummaryRow?.involvedTasks ?? 0),
149
+ inReviewTasks: Number(taskSummaryRow?.inReviewTasks ?? 0),
150
+ };
151
+ return {
152
+ generatedAt: new Date().toISOString(),
153
+ agent: {
154
+ agentId: agent.agentId,
155
+ name: agent.name,
156
+ agentType: agent.agentType,
157
+ nodeId: agent.nodeId ?? null,
158
+ workspacePath: agent.workspacePath,
159
+ homeChannelId: agent.channelId,
160
+ joinedChannels,
161
+ },
162
+ summary,
163
+ currentConversation,
164
+ conversations,
165
+ recentTasks,
166
+ recentHandoffs,
167
+ };
168
+ }
169
+ export function buildAgentGlobalSnapshot(state) {
170
+ const current = state.currentConversation;
171
+ const currentSurface = current
172
+ ? `${current.replyTarget} [${current.surfaceKind}, status=${current.status}, unread=${current.unreadCount}, queued_prompts=${current.queuedPromptCount}]`
173
+ : 'unknown';
174
+ const otherLiveSurfaces = state.conversations
175
+ .filter((conversation) => !current || conversation.conversationId !== current.conversationId)
176
+ .filter((conversation) => conversation.status !== 'idle' || conversation.unreadCount > 0 || conversation.queuedPromptCount > 0)
177
+ .slice(0, 4);
178
+ const lines = [
179
+ '[Agent global snapshot]',
180
+ `agent: @${state.agent.name}`,
181
+ `current_surface: ${currentSurface}`,
182
+ `global: conversations=${state.summary.totalConversations}, active=${state.summary.activeConversations}, queued=${state.summary.queuedConversations}, awaiting_approval=${state.summary.awaitingApprovalConversations}, unread_messages=${state.summary.totalUnreadMessages} on ${state.summary.surfacesWithUnread} surfaces, pending_prompts=${state.summary.pendingPrompts}`,
183
+ `tasks: claimed=${state.summary.claimedTasks}, involved=${state.summary.involvedTasks}, in_review=${state.summary.inReviewTasks}`,
184
+ ];
185
+ if (otherLiveSurfaces.length > 0) {
186
+ lines.push('other_live_surfaces:');
187
+ for (const conversation of otherLiveSurfaces) {
188
+ lines.push(`- ${conversation.replyTarget} [${conversation.status}, unread=${conversation.unreadCount}, queued_prompts=${conversation.queuedPromptCount}]`);
189
+ }
190
+ }
191
+ else {
192
+ lines.push('other_live_surfaces: none');
193
+ }
194
+ if (state.recentHandoffs.length > 0) {
195
+ lines.push('recent_handoffs:');
196
+ for (const handoff of state.recentHandoffs.slice(0, 3)) {
197
+ lines.push(`- ${handoff.summaryText}`);
198
+ }
199
+ }
200
+ else {
201
+ lines.push('recent_handoffs: none');
202
+ }
203
+ return lines.join('\n');
204
+ }
205
+ export function buildAgentSelfStateReport(state, options) {
206
+ const current = state.currentConversation;
207
+ const joinedChannels = formatJoinedChannels(state.agent.joinedChannels.map((channel) => channel.target));
208
+ const currentSurface = current
209
+ ? formatSelfStateSurface(current)
210
+ : 'none';
211
+ const lines = [
212
+ '[Agent self state]',
213
+ `agent: @${state.agent.name} (${state.agent.agentType}) | current: ${currentSurface}`,
214
+ `joined_channels: ${joinedChannels.text}`,
215
+ `global: conv=${state.summary.totalConversations}, active=${state.summary.activeConversations}, queued=${state.summary.queuedConversations}, recovering=${state.summary.recoveringConversations}, approval=${state.summary.awaitingApprovalConversations}, failed=${state.summary.failedConversations}, new_activity=${state.summary.surfacesWithUnread}, pending=${state.summary.pendingPrompts} | tasks claimed=${state.summary.claimedTasks}, involved=${state.summary.involvedTasks}, review=${state.summary.inReviewTasks}`,
216
+ ];
217
+ if (options?.currentContext) {
218
+ if (options.currentContext.myRole)
219
+ lines.push(`role_here: ${options.currentContext.myRole}`);
220
+ if (options.currentContext.boundTask) {
221
+ lines.push(`bound_task: ${formatTaskIdentity(options.currentContext.boundTask.agentTaskRef, options.currentContext.boundTask.taskNumber)} [${options.currentContext.boundTask.status}]`);
222
+ }
223
+ if (options.currentContext.ownerName)
224
+ lines.push(`owner_here: @${options.currentContext.ownerName}`);
225
+ if (options.currentContext.participants.length > 0) {
226
+ lines.push(`participants_here: ${options.currentContext.participants.map((name) => `@${name}`).join(', ')}`);
227
+ }
228
+ if (options.currentContext.blockers.length > 0) {
229
+ lines.push(`blockers_here: ${truncateSelfStateValue(options.currentContext.blockers.join('; '), 96)}`);
230
+ }
231
+ if (options.currentContext.activeHandoff) {
232
+ lines.push(`active_handoff_here: ${options.currentContext.activeHandoff.handoffId} ${options.currentContext.activeHandoff.mode} [${options.currentContext.activeHandoff.status}] -> ${options.currentContext.activeHandoff.targetReplyTarget}`);
233
+ }
234
+ }
235
+ const currentConversationId = current?.conversationId ?? null;
236
+ const otherConversations = state.conversations
237
+ .filter((conversation) => conversation.conversationId !== currentConversationId);
238
+ const surfaces = otherConversations.slice(0, 4);
239
+ lines.push('other_surfaces:');
240
+ if (surfaces.length === 0) {
241
+ lines.push('- none');
242
+ }
243
+ else {
244
+ for (const conversation of surfaces) {
245
+ const context = options?.conversationContexts?.[conversation.conversationId];
246
+ const extras = [];
247
+ if (context?.myRole)
248
+ extras.push(`role=${context.myRole}`);
249
+ if (conversation.unreadCount > 0)
250
+ extras.push(`unread=${conversation.unreadCount}`);
251
+ if (context?.boundTask) {
252
+ extras.push(`task=${formatTaskIdentity(context.boundTask.agentTaskRef, context.boundTask.taskNumber)} [${context.boundTask.status}]`);
253
+ }
254
+ if (context?.blockers.length)
255
+ extras.push(`blockers=${truncateSelfStateValue(context.blockers.join('; '), 80)}`);
256
+ if (context?.activeHandoff) {
257
+ extras.push(`handoff=${context.activeHandoff.handoffId} ${context.activeHandoff.mode} [${context.activeHandoff.status}]`);
258
+ }
259
+ const teaser = buildSelfStateTeaser(context);
260
+ if (teaser)
261
+ extras.push(`teaser=${truncateSelfStateValue(teaser, 64)}`);
262
+ lines.push(`- ${formatSelfStateSurface(conversation)}${extras.length ? ` | ${extras.join(' | ')}` : ''}`);
263
+ }
264
+ const hiddenSurfaces = otherConversations.slice(surfaces.length);
265
+ const hiddenSurfaceCount = hiddenSurfaces.length;
266
+ if (hiddenSurfaceCount > 0) {
267
+ lines.push(`- hidden=${hiddenSurfaceCount}${formatHiddenSurfaceBreakdown(hiddenSurfaces)}`);
268
+ }
269
+ }
270
+ const currentBoundTaskId = options?.currentContext?.boundTask?.taskId ?? null;
271
+ const recentTasks = state.recentTasks
272
+ .filter((task) => task.taskId !== currentBoundTaskId)
273
+ .slice(0, 3);
274
+ const totalOtherTaskCount = Math.max(0, state.summary.involvedTasks - (currentBoundTaskId ? 1 : 0));
275
+ if (state.recentTasks.length === 0) {
276
+ lines.push('tasks: none');
277
+ }
278
+ else if (recentTasks.length > 0) {
279
+ lines.push('other_tasks:');
280
+ for (const task of recentTasks) {
281
+ const identity = formatTaskIdentity(task.agentTaskRef, task.taskNumber);
282
+ const source = task.threadTarget ?? task.sourceTarget ?? 'unknown';
283
+ const ownership = task.claimedByMe ? 'claimed' : (task.participant ? 'participant' : 'linked');
284
+ lines.push(`- ${identity} [${task.status}, ${ownership}] ${truncateSelfStateValue(task.title, 72)} @ ${source}`);
285
+ }
286
+ if (totalOtherTaskCount > recentTasks.length) {
287
+ lines.push(`- ... ${totalOtherTaskCount - recentTasks.length} more task(s) hidden`);
288
+ }
289
+ }
290
+ else {
291
+ lines.push(totalOtherTaskCount > 0 ? `other_tasks: ${totalOtherTaskCount} hidden` : 'other_tasks: none');
292
+ }
293
+ const recentHandoffs = state.recentHandoffs
294
+ .filter((handoff) => handoff.status !== 'completed')
295
+ .slice(0, 3);
296
+ if (recentHandoffs.length === 0) {
297
+ lines.push('recent_handoffs: none');
298
+ }
299
+ else {
300
+ lines.push('recent_handoffs:');
301
+ for (const handoff of recentHandoffs) {
302
+ lines.push(`- ${handoff.handoffId} ${truncateSelfStateValue(handoff.summaryText, 120)}`);
303
+ }
304
+ if (state.recentHandoffs.filter((handoff) => handoff.status !== 'completed').length > recentHandoffs.length) {
305
+ lines.push(`- ... ${state.recentHandoffs.filter((handoff) => handoff.status !== 'completed').length - recentHandoffs.length} more handoff(s) hidden`);
306
+ }
307
+ }
308
+ return lines.join('\n');
309
+ }
310
+ function formatSelfStateSurface(conversation) {
311
+ return `${conversation.replyTarget} [${conversation.surfaceKind}, ${conversation.status}]`;
312
+ }
313
+ function buildSelfStateTeaser(context) {
314
+ if (!context)
315
+ return null;
316
+ const candidates = [
317
+ context.boundTask?.title,
318
+ context.goal,
319
+ context.lastMessage?.preview,
320
+ ];
321
+ for (const candidate of candidates) {
322
+ const trimmed = candidate?.trim();
323
+ if (trimmed)
324
+ return trimmed;
325
+ }
326
+ return null;
327
+ }
328
+ function formatHiddenSurfaceBreakdown(conversations) {
329
+ if (conversations.length === 0)
330
+ return '';
331
+ const order = ['active', 'queued', 'recovering', 'awaiting_approval', 'failed', 'idle'];
332
+ const counts = new Map();
333
+ for (const conversation of conversations) {
334
+ counts.set(conversation.status, (counts.get(conversation.status) ?? 0) + 1);
335
+ }
336
+ const parts = order
337
+ .map((status) => {
338
+ const count = counts.get(status) ?? 0;
339
+ return count > 0 ? `${status}=${count}` : null;
340
+ })
341
+ .filter((part) => Boolean(part));
342
+ return parts.length > 0 ? `(${parts.join(',')})` : '';
343
+ }
344
+ function formatTaskIdentity(agentTaskRef, taskNumber) {
345
+ if (agentTaskRef && taskNumber != null)
346
+ return `${agentTaskRef} · #t${taskNumber}`;
347
+ if (agentTaskRef)
348
+ return agentTaskRef;
349
+ if (taskNumber != null)
350
+ return `#t${taskNumber}`;
351
+ return 'task';
352
+ }
353
+ function truncateSelfStateValue(value, maxLength = 72) {
354
+ const trimmed = value?.trim() ?? '';
355
+ if (!trimmed)
356
+ return '';
357
+ if (trimmed.length <= maxLength)
358
+ return trimmed;
359
+ return `${trimmed.slice(0, Math.max(0, maxLength - 3)).trimEnd()}...`;
360
+ }
361
+ function formatJoinedChannels(targets, maxChannels = 6) {
362
+ if (targets.length === 0)
363
+ return { text: '(none)', truncated: false };
364
+ const visibleTargets = targets.slice(0, maxChannels);
365
+ const hiddenCount = Math.max(0, targets.length - visibleTargets.length);
366
+ return {
367
+ text: hiddenCount > 0
368
+ ? `${visibleTargets.join(', ')} ... +${hiddenCount} more`
369
+ : visibleTargets.join(', '),
370
+ truncated: hiddenCount > 0,
371
+ };
372
+ }
373
+ function loadJoinedChannels(db, agent) {
374
+ const rows = db.prepare(`SELECT c.channel_id as channelId, ch.name as channelName
375
+ FROM agent_channel_memberships c
376
+ LEFT JOIN channels ch ON ch.channel_id = c.channel_id
377
+ WHERE c.agent_id = ?
378
+ ORDER BY CASE WHEN c.channel_id = ? THEN 0 ELSE 1 END, c.joined_at ASC`).all(agent.agentId, agent.channelId);
379
+ return rows.map((row) => ({
380
+ channelId: row.channelId,
381
+ name: row.channelName ?? null,
382
+ target: row.channelId.startsWith('dm:') ? row.channelId : `#${row.channelName ?? row.channelId}`,
383
+ }));
384
+ }
385
+ function toConversationState(db, agentId, row) {
386
+ const replyTarget = resolveConversationTarget(row);
387
+ const messageChannelId = row.threadKind === 'direct' ? `dm:${agentId}` : row.channelId;
388
+ return {
389
+ conversationId: row.conversationId,
390
+ title: row.title,
391
+ status: row.status,
392
+ channelId: messageChannelId,
393
+ channelName: row.channelName,
394
+ replyTarget,
395
+ surfaceKind: parseSurfaceKind(row.surfaceKind, {
396
+ threadKind: row.threadKind,
397
+ threadRootId: row.threadRootId,
398
+ }),
399
+ threadKind: row.threadKind,
400
+ isPrimaryThread: row.isPrimaryThread === 1,
401
+ threadRootId: row.threadRootId,
402
+ unreadCount: countUnreadMessages(db, agentId, row, messageChannelId, replyTarget),
403
+ queuedPromptCount: Number(row.queuedPromptCount ?? 0),
404
+ hasActiveRun: Boolean(row.activeRunId),
405
+ updatedAt: new Date(row.updatedAt).toISOString(),
406
+ };
407
+ }
408
+ function resolveConversationTarget(row) {
409
+ const trimmedReplyTarget = row.replyTarget?.trim();
410
+ if (trimmedReplyTarget)
411
+ return trimmedReplyTarget;
412
+ if (row.channelId.startsWith('dm:')) {
413
+ const peer = row.userId?.trim() ? `dm:@${row.userId.trim()}` : 'dm:@unknown';
414
+ if (row.threadRootId)
415
+ return `${peer}:${buildThreadShortId(row.threadRootId)}`;
416
+ return peer;
417
+ }
418
+ const baseTarget = `#${row.channelName ?? row.channelId}`;
419
+ if (row.threadRootId)
420
+ return `${baseTarget}:${buildThreadShortId(row.threadRootId)}`;
421
+ return baseTarget;
422
+ }
423
+ function countUnreadMessages(db, agentId, row, messageChannelId, replyTarget) {
424
+ const checkpoint = getAgentMessageCheckpoint(db, agentId, messageChannelId, row.threadRootId);
425
+ if (row.threadRootId) {
426
+ if ((row.threadKind === 'direct' || replyTarget.startsWith('dm:@')) && replyTarget.startsWith('dm:@')) {
427
+ const result = db.prepare(`SELECT COUNT(*) as count
428
+ FROM channel_messages
429
+ WHERE channel_id = ?
430
+ AND seq > ?
431
+ AND COALESCE(sender_id, '') != ?
432
+ AND COALESCE(message_source, '') != ?
433
+ AND (
434
+ thread_root_id = ?
435
+ OR (thread_root_id IS NULL AND target = ?)
436
+ )`).get(messageChannelId, checkpoint, agentId, TASK_LIFECYCLE_SOURCE, row.threadRootId, replyTarget);
437
+ return Number(result.count ?? 0);
438
+ }
439
+ const result = db.prepare(`SELECT COUNT(*) as count
440
+ FROM channel_messages
441
+ WHERE channel_id = ?
442
+ AND seq > ?
443
+ AND COALESCE(sender_id, '') != ?
444
+ AND COALESCE(message_source, '') != ?
445
+ AND thread_root_id = ?`).get(messageChannelId, checkpoint, agentId, TASK_LIFECYCLE_SOURCE, row.threadRootId);
446
+ return Number(result.count ?? 0);
447
+ }
448
+ const result = db.prepare(replyTarget.startsWith('dm:@')
449
+ ? `SELECT COUNT(*) as count
450
+ FROM channel_messages
451
+ WHERE channel_id = ?
452
+ AND seq > ?
453
+ AND COALESCE(sender_id, '') != ?
454
+ AND COALESCE(message_source, '') != ?
455
+ AND thread_root_id IS NULL
456
+ AND target = ?`
457
+ : `SELECT COUNT(*) as count
458
+ FROM channel_messages
459
+ WHERE channel_id = ?
460
+ AND seq > ?
461
+ AND COALESCE(sender_id, '') != ?
462
+ AND COALESCE(message_source, '') != ?
463
+ AND thread_root_id IS NULL`).get(...(replyTarget.startsWith('dm:@')
464
+ ? [messageChannelId, checkpoint, agentId, TASK_LIFECYCLE_SOURCE, replyTarget]
465
+ : [messageChannelId, checkpoint, agentId, TASK_LIFECYCLE_SOURCE]));
466
+ return Number(result.count ?? 0);
467
+ }
468
+ function compareConversations(currentConversationId) {
469
+ const statusWeight = {
470
+ active: 0,
471
+ recovering: 1,
472
+ awaiting_approval: 2,
473
+ queued: 3,
474
+ failed: 4,
475
+ idle: 5,
476
+ };
477
+ return (left, right) => {
478
+ if (currentConversationId) {
479
+ if (left.conversationId === currentConversationId && right.conversationId !== currentConversationId)
480
+ return -1;
481
+ if (right.conversationId === currentConversationId && left.conversationId !== currentConversationId)
482
+ return 1;
483
+ }
484
+ const leftWeight = statusWeight[left.status] ?? 99;
485
+ const rightWeight = statusWeight[right.status] ?? 99;
486
+ if (leftWeight !== rightWeight)
487
+ return leftWeight - rightWeight;
488
+ if (left.unreadCount !== right.unreadCount)
489
+ return right.unreadCount - left.unreadCount;
490
+ if (left.queuedPromptCount !== right.queuedPromptCount)
491
+ return right.queuedPromptCount - left.queuedPromptCount;
492
+ return right.updatedAt.localeCompare(left.updatedAt);
493
+ };
494
+ }