@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,876 @@
1
+ import { buildThreadShortId, } from '@bbigbang/protocol';
2
+ import { collectAgentSelfState } from './agentSelfState.js';
3
+ import { getThreadCollaborationSummary } from './threadTaskBindings.js';
4
+ import { getLatestConversationHandoffForConversation } from './conversationHandoffs.js';
5
+ import { listUnifiedRecentSurfaceParticipants } from './collaborationSurfaceState.js';
6
+ import { TARGET_PARTICIPANT_ACTIVE_WINDOW_MS } from './targetParticipants.js';
7
+ import { TASK_LIFECYCLE_SOURCE } from './taskLifecycleMessages.js';
8
+ export function refreshConversationSummariesForAgent(db, agent, options) {
9
+ const state = collectAgentSelfState(db, agent);
10
+ const filtered = options?.status && options.status !== 'all'
11
+ ? state.conversations.filter((conversation) => conversation.status === options.status)
12
+ : state.conversations;
13
+ return filtered.map((conversation) => upsertConversationSummary(db, agent, conversation));
14
+ }
15
+ export function refreshConversationSummary(db, agent, conversationId) {
16
+ const state = collectAgentSelfState(db, agent, { conversationId });
17
+ const conversation = state.conversations.find((item) => item.conversationId === conversationId);
18
+ if (!conversation)
19
+ return null;
20
+ return upsertConversationSummary(db, agent, conversation);
21
+ }
22
+ export function listConversationSummariesForAgent(db, agent, options) {
23
+ const state = collectAgentSelfState(db, agent);
24
+ const filtered = options?.status && options.status !== 'all'
25
+ ? state.conversations.filter((conversation) => conversation.status === options.status)
26
+ : state.conversations;
27
+ return filtered.map((conversation) => getConversationSummaryRecord(db, agent, conversation));
28
+ }
29
+ export function getConversationSummary(db, agent, conversationId) {
30
+ const state = collectAgentSelfState(db, agent, { conversationId });
31
+ const conversation = state.conversations.find((item) => item.conversationId === conversationId);
32
+ if (!conversation)
33
+ return null;
34
+ return getConversationSummaryRecord(db, agent, conversation);
35
+ }
36
+ export function buildConversationSummaryRecords(db, agent, conversations) {
37
+ return conversations.map((conversation) => getConversationSummaryRecord(db, agent, conversation));
38
+ }
39
+ export function buildConversationSurfaceResponse(record, options) {
40
+ const contextKey = options?.contextKey ?? 'context';
41
+ const summary = options?.context ?? record.summary;
42
+ const response = {
43
+ conversationId: record.conversationId,
44
+ replyTarget: record.replyTarget,
45
+ status: record.status,
46
+ surfaceKind: record.surfaceKind,
47
+ threadKind: record.threadKind,
48
+ threadRootId: record.threadRootId,
49
+ unreadCount: record.unreadCount,
50
+ queuedPromptCount: record.queuedPromptCount,
51
+ hasActiveRun: record.hasActiveRun,
52
+ updatedAt: record.updatedAt,
53
+ summaryUpdatedAt: record.summaryUpdatedAt,
54
+ summaryText: formatConversationSummaryText(record.replyTarget, record.status, summary),
55
+ ...(contextKey === 'summary' ? { summary } : { context: summary }),
56
+ };
57
+ if (options?.includeReportText) {
58
+ response.reportText = formatConversationSummary({
59
+ ...record,
60
+ summary,
61
+ });
62
+ }
63
+ return response;
64
+ }
65
+ export function refreshConversationSummariesForConversationIds(db, lookupAgent, conversationIds) {
66
+ const refreshed = [];
67
+ const seen = new Set();
68
+ for (const conversationId of conversationIds) {
69
+ if (!conversationId || seen.has(conversationId))
70
+ continue;
71
+ seen.add(conversationId);
72
+ const row = db.prepare(`SELECT agent_id as agentId
73
+ FROM conversations
74
+ WHERE id = ?
75
+ LIMIT 1`).get(conversationId);
76
+ if (!row?.agentId)
77
+ continue;
78
+ const agent = lookupAgent(row.agentId);
79
+ if (!agent)
80
+ continue;
81
+ const summary = refreshConversationSummary(db, agent, conversationId);
82
+ if (summary)
83
+ refreshed.push(summary);
84
+ }
85
+ return refreshed;
86
+ }
87
+ export function refreshConversationSummariesForMessageScope(db, lookupAgent, params) {
88
+ return refreshConversationSummariesForConversationIds(db, lookupAgent, listConversationIdsForMessageScope(db, params));
89
+ }
90
+ export function refreshConversationSummariesForTask(db, lookupAgent, taskId) {
91
+ const row = db.prepare(`SELECT t.channel_id as channelId,
92
+ t.message_id as messageId,
93
+ COALESCE(t.dm_target, cm.target) as replyTarget
94
+ FROM tasks t
95
+ LEFT JOIN channel_messages cm ON cm.message_id = t.message_id
96
+ WHERE t.task_id = ?
97
+ LIMIT 1`).get(taskId);
98
+ if (!row)
99
+ return [];
100
+ const conversationIds = new Set();
101
+ for (const conversationId of listConversationIdsForMessageScope(db, {
102
+ channelId: row.channelId,
103
+ replyTarget: row.replyTarget,
104
+ threadRootId: null,
105
+ })) {
106
+ conversationIds.add(conversationId);
107
+ }
108
+ if (row.messageId) {
109
+ for (const conversationId of listConversationIdsForMessageScope(db, {
110
+ channelId: row.channelId,
111
+ threadRootId: buildThreadShortId(row.messageId),
112
+ })) {
113
+ conversationIds.add(conversationId);
114
+ }
115
+ }
116
+ return refreshConversationSummariesForConversationIds(db, lookupAgent, conversationIds);
117
+ }
118
+ export function getStoredConversationSummary(db, conversationId) {
119
+ const row = db.prepare(`SELECT conversation_id as conversationId,
120
+ agent_id as agentId,
121
+ reply_target as replyTarget,
122
+ channel_id as channelId,
123
+ thread_kind as threadKind,
124
+ thread_root_id as threadRootId,
125
+ summary_json as summaryJson,
126
+ last_message_id as lastMessageId,
127
+ last_message_seq as lastMessageSeq,
128
+ updated_at as updatedAt
129
+ FROM conversation_summaries
130
+ WHERE conversation_id = ?
131
+ LIMIT 1`).get(conversationId);
132
+ if (!row)
133
+ return null;
134
+ return {
135
+ conversationId: row.conversationId,
136
+ agentId: row.agentId,
137
+ replyTarget: row.replyTarget,
138
+ channelId: row.channelId,
139
+ threadKind: row.threadKind,
140
+ threadRootId: row.threadRootId ?? null,
141
+ summary: parseConversationSummaryPayload(row.summaryJson),
142
+ lastMessageId: row.lastMessageId ?? null,
143
+ lastMessageSeq: row.lastMessageSeq ?? null,
144
+ updatedAt: new Date(row.updatedAt).toISOString(),
145
+ };
146
+ }
147
+ export function formatConversationSummary(record, options) {
148
+ const includeGoal = options?.includeGoal ?? true;
149
+ const includeRecentState = options?.includeRecentState ?? true;
150
+ const includeLinkedTasks = options?.includeLinkedTasks ?? true;
151
+ const includeLastMessage = options?.includeLastMessage ?? true;
152
+ const lines = [
153
+ `Target: ${record.replyTarget}`,
154
+ `Status: ${record.status}`,
155
+ `Surface: ${record.surfaceKind}`,
156
+ `Unread: ${record.unreadCount}`,
157
+ `Queued prompts: ${record.queuedPromptCount}`,
158
+ ];
159
+ if (includeGoal && record.summary.goal)
160
+ lines.push(`Goal: ${record.summary.goal}`);
161
+ if (includeRecentState && record.summary.recentState)
162
+ lines.push(`Recent state: ${record.summary.recentState}`);
163
+ if (record.summary.blockers.length > 0)
164
+ lines.push(`Blockers: ${record.summary.blockers.join('; ')}`);
165
+ if (record.summary.nextStep)
166
+ lines.push(`Next: ${record.summary.nextStep}`);
167
+ if (record.summary.myRole)
168
+ lines.push(`My role: ${record.summary.myRole}`);
169
+ if (record.summary.ownerName)
170
+ lines.push(`Owner: @${record.summary.ownerName}`);
171
+ if (includeLinkedTasks && record.summary.linkedTaskRefs.length > 0) {
172
+ lines.push(`Linked tasks: ${record.summary.linkedTaskRefs.join(', ')}`);
173
+ }
174
+ if (record.summary.boundTask) {
175
+ lines.push(`Bound task: ${formatTaskRef(record.summary.boundTask.agentTaskRef, record.summary.boundTask.taskNumber)} [${record.summary.boundTask.status}]`);
176
+ }
177
+ if (record.summary.participants.length > 0) {
178
+ lines.push(`Participants: ${record.summary.participants.map((name) => `@${name}`).join(', ')}`);
179
+ }
180
+ if (record.summary.activeHandoff) {
181
+ lines.push(`Active handoff: ${record.summary.activeHandoff.mode} [${record.summary.activeHandoff.status}] -> ${record.summary.activeHandoff.targetReplyTarget}`);
182
+ }
183
+ if (includeLastMessage && record.summary.lastMessage) {
184
+ lines.push(`Last message: @${record.summary.lastMessage.senderName} — ${record.summary.lastMessage.preview}`);
185
+ }
186
+ return lines.join('\n');
187
+ }
188
+ function getConversationSummaryRecord(db, agent, conversation) {
189
+ const stored = getStoredConversationSummary(db, conversation.conversationId);
190
+ if (stored && isStoredConversationSummaryFresh(db, agent.agentId, conversation, stored)) {
191
+ return buildConversationSummaryRecordFromStored(conversation, stored);
192
+ }
193
+ return upsertConversationSummary(db, agent, conversation);
194
+ }
195
+ function isStoredConversationSummaryFresh(db, agentId, conversation, stored) {
196
+ if (!stored)
197
+ return false;
198
+ if (stored.agentId !== agentId)
199
+ return false;
200
+ if (stored.replyTarget !== conversation.replyTarget)
201
+ return false;
202
+ if (stored.channelId !== conversation.channelId)
203
+ return false;
204
+ if (stored.threadKind !== conversation.threadKind)
205
+ return false;
206
+ if ((stored.threadRootId ?? null) !== (conversation.threadRootId ?? null))
207
+ return false;
208
+ if (stored.summary.unreadCount !== conversation.unreadCount)
209
+ return false;
210
+ if (stored.summary.queuedPromptCount !== conversation.queuedPromptCount)
211
+ return false;
212
+ const storedUpdatedAt = Date.parse(stored.updatedAt);
213
+ const conversationUpdatedAt = Date.parse(conversation.updatedAt);
214
+ if (Number.isFinite(storedUpdatedAt) && Number.isFinite(conversationUpdatedAt) && conversationUpdatedAt > storedUpdatedAt) {
215
+ return false;
216
+ }
217
+ if (Number.isFinite(storedUpdatedAt) && (Date.now() - storedUpdatedAt) >= TARGET_PARTICIPANT_ACTIVE_WINDOW_MS) {
218
+ return false;
219
+ }
220
+ const latestOpenHandoff = getLatestConversationHandoffForConversation(db, {
221
+ conversationId: conversation.conversationId,
222
+ openOnly: true,
223
+ });
224
+ if ((stored.summary.activeHandoff?.updatedAt ?? null) !== (latestOpenHandoff?.updatedAt ?? null)) {
225
+ return false;
226
+ }
227
+ const collaborationState = loadConversationCollaborationState(db, conversation);
228
+ if ((stored.summary.ownerAgentId ?? null) !== collaborationState.ownerAgentId) {
229
+ return false;
230
+ }
231
+ if ((stored.summary.ownerName ?? null) !== collaborationState.ownerName) {
232
+ return false;
233
+ }
234
+ if (!equalStringArrays(stored.summary.participants, collaborationState.participants)) {
235
+ return false;
236
+ }
237
+ const latestMessage = loadLatestScopeMessage(db, conversation);
238
+ return (stored.lastMessageId ?? null) === (latestMessage?.messageId ?? null)
239
+ && (stored.lastMessageSeq ?? null) === (latestMessage?.seq ?? null);
240
+ }
241
+ function buildConversationSummaryRecordFromStored(conversation, stored) {
242
+ const summary = {
243
+ ...stored.summary,
244
+ unreadCount: conversation.unreadCount,
245
+ queuedPromptCount: conversation.queuedPromptCount,
246
+ };
247
+ return {
248
+ conversationId: conversation.conversationId,
249
+ replyTarget: conversation.replyTarget,
250
+ status: conversation.status,
251
+ surfaceKind: conversation.surfaceKind,
252
+ threadKind: conversation.threadKind,
253
+ threadRootId: conversation.threadRootId,
254
+ unreadCount: conversation.unreadCount,
255
+ queuedPromptCount: conversation.queuedPromptCount,
256
+ hasActiveRun: conversation.hasActiveRun,
257
+ updatedAt: conversation.updatedAt,
258
+ summaryUpdatedAt: stored.updatedAt,
259
+ summary,
260
+ summaryText: formatConversationSummaryText(conversation.replyTarget, conversation.status, summary),
261
+ };
262
+ }
263
+ function listConversationIdsForMessageScope(db, params) {
264
+ const threadRootId = normalizeOptionalText(params.threadRootId);
265
+ if (params.channelId.startsWith('dm:')) {
266
+ if (threadRootId) {
267
+ const rows = db.prepare(`SELECT id
268
+ FROM conversations
269
+ WHERE channel_id = ?
270
+ AND thread_kind = 'direct'
271
+ AND thread_root_id = ?
272
+ ORDER BY updated_at DESC, created_at DESC`).all(params.channelId, threadRootId);
273
+ return rows.map((row) => row.id);
274
+ }
275
+ const replyTarget = normalizeOptionalText(params.replyTarget);
276
+ if (!replyTarget)
277
+ return [];
278
+ const rows = db.prepare(`SELECT id
279
+ FROM conversations
280
+ WHERE channel_id = ?
281
+ AND thread_kind = 'direct'
282
+ AND thread_root_id IS NULL
283
+ AND reply_target = ?
284
+ ORDER BY updated_at DESC, created_at DESC`).all(params.channelId, replyTarget);
285
+ return rows.map((row) => row.id);
286
+ }
287
+ if (threadRootId) {
288
+ const rows = db.prepare(`SELECT id
289
+ FROM conversations
290
+ WHERE channel_id = ?
291
+ AND thread_kind = 'branch'
292
+ AND thread_root_id = ?
293
+ ORDER BY updated_at DESC, created_at DESC`).all(params.channelId, threadRootId);
294
+ return rows.map((row) => row.id);
295
+ }
296
+ const rows = db.prepare(`SELECT id
297
+ FROM conversations
298
+ WHERE channel_id = ?
299
+ AND thread_kind = 'branch'
300
+ AND thread_root_id IS NULL
301
+ ORDER BY updated_at DESC, created_at DESC`).all(params.channelId);
302
+ return rows.map((row) => row.id);
303
+ }
304
+ function upsertConversationSummary(db, agent, conversation) {
305
+ const messages = loadScopeMessages(db, conversation);
306
+ const latestMessage = messages.at(-1) ?? null;
307
+ const lastUserMessage = [...messages].reverse().find((message) => message.senderType === 'user') ?? null;
308
+ const lastAgentMessage = [...messages].reverse().find((message) => message.senderType === 'agent') ?? null;
309
+ const lastSystemMessage = [...messages].reverse().find((message) => message.senderType === 'system') ?? null;
310
+ const linkedTasks = loadLinkedTasks(db, conversation);
311
+ const linkedTaskSummaries = linkedTasks.map(toLinkedTaskSummary);
312
+ const lastRunError = loadLastRunError(db, conversation.conversationId);
313
+ const collaborationState = loadConversationCollaborationState(db, conversation);
314
+ const { threadCollaboration } = collaborationState;
315
+ const targetParticipants = collaborationState.participants;
316
+ const ownerAgentId = collaborationState.ownerAgentId;
317
+ const ownerName = collaborationState.ownerName;
318
+ const boundTask = threadCollaboration.boundTask
319
+ ? (() => {
320
+ const linkedTask = linkedTasks[0] ?? {
321
+ taskId: threadCollaboration.boundTask.taskId,
322
+ agentTaskRef: null,
323
+ taskNumber: threadCollaboration.boundTask.taskNumber,
324
+ title: threadCollaboration.boundTask.title,
325
+ status: threadCollaboration.boundTask.status,
326
+ claimedByName: threadCollaboration.boundTask.assigneeName ?? null,
327
+ description: threadCollaboration.boundTask.description ?? null,
328
+ messageId: null,
329
+ channelId: threadCollaboration.boundTask.channelId,
330
+ sourceTarget: conversation.replyTarget,
331
+ };
332
+ const summary = toLinkedTaskSummary(linkedTask);
333
+ return {
334
+ ...summary,
335
+ threadTarget: summary.threadTarget ?? conversation.replyTarget,
336
+ description: threadCollaboration.boundTask.description ?? null,
337
+ claimedByName: threadCollaboration.boundTask.assigneeName ?? null,
338
+ };
339
+ })()
340
+ : null;
341
+ const activeHandoff = toActiveHandoffSummary(getLatestConversationHandoffForConversation(db, {
342
+ conversationId: conversation.conversationId,
343
+ openOnly: true,
344
+ }));
345
+ const blockers = [];
346
+ if (conversation.status === 'awaiting_approval')
347
+ blockers.push('Awaiting human approval');
348
+ if (conversation.status === 'failed')
349
+ blockers.push(lastRunError ? `Last run failed: ${lastRunError}` : 'Last run failed');
350
+ if (conversation.queuedPromptCount > 0 && conversation.status !== 'active' && conversation.status !== 'recovering') {
351
+ blockers.push(`${conversation.queuedPromptCount} queued prompt(s) pending`);
352
+ }
353
+ if (activeHandoff?.status === 'started' || activeHandoff?.status === 'accepted') {
354
+ blockers.push(`Handoff in progress to ${activeHandoff.targetReplyTarget}`);
355
+ }
356
+ const taskSummary = linkedTasks[0] ?? null;
357
+ const goal = taskSummary
358
+ ? summarizeText(taskSummary.title, 160)
359
+ : summarizeText(lastUserMessage?.content ?? loadThreadRootMessageContent(db, conversation) ?? null, 160);
360
+ const recentState = summarizeText(lastAgentMessage?.content ?? lastSystemMessage?.content ?? null, 180);
361
+ const nextStep = buildNextStep({
362
+ conversation,
363
+ lastUserMessage: lastUserMessage?.content ?? null,
364
+ linkedTask: taskSummary,
365
+ });
366
+ const linkedTaskRefs = linkedTasks.map((task) => formatTaskRef(task.agentTaskRef, task.taskNumber));
367
+ const linkedTaskTitles = linkedTasks.map((task) => summarizeText(task.title, 160)).filter((title) => Boolean(title));
368
+ const myRole = deriveMyRole({
369
+ agent,
370
+ conversation,
371
+ boundTask,
372
+ ownerAgentId,
373
+ ownerName,
374
+ participants: targetParticipants,
375
+ });
376
+ const summary = {
377
+ goal,
378
+ recentState,
379
+ blockers,
380
+ nextStep,
381
+ lastUserMessage: summarizeText(lastUserMessage?.content ?? null, 180),
382
+ lastAgentMessage: summarizeText(lastAgentMessage?.content ?? null, 180),
383
+ linkedTaskRefs,
384
+ linkedTaskTitles,
385
+ unreadCount: conversation.unreadCount,
386
+ queuedPromptCount: conversation.queuedPromptCount,
387
+ lastMessageAt: latestMessage ? new Date(latestMessage.createdAt).toISOString() : null,
388
+ lastMessage: latestMessage
389
+ ? {
390
+ messageId: latestMessage.messageId,
391
+ target: latestMessage.target,
392
+ senderName: latestMessage.senderName,
393
+ senderType: latestMessage.senderType,
394
+ preview: summarizeText(latestMessage.content, 180) ?? '',
395
+ createdAt: new Date(latestMessage.createdAt).toISOString(),
396
+ }
397
+ : null,
398
+ linkedTasks: linkedTaskSummaries,
399
+ boundTask,
400
+ ownerAgentId,
401
+ ownerName,
402
+ participants: targetParticipants,
403
+ myRole,
404
+ activeHandoff,
405
+ };
406
+ const now = Date.now();
407
+ db.prepare(`INSERT INTO conversation_summaries(
408
+ conversation_id,
409
+ agent_id,
410
+ reply_target,
411
+ channel_id,
412
+ thread_kind,
413
+ thread_root_id,
414
+ summary_json,
415
+ last_message_id,
416
+ last_message_seq,
417
+ updated_at
418
+ )
419
+ VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
420
+ ON CONFLICT(conversation_id) DO UPDATE SET
421
+ agent_id = excluded.agent_id,
422
+ reply_target = excluded.reply_target,
423
+ channel_id = excluded.channel_id,
424
+ thread_kind = excluded.thread_kind,
425
+ thread_root_id = excluded.thread_root_id,
426
+ summary_json = excluded.summary_json,
427
+ last_message_id = excluded.last_message_id,
428
+ last_message_seq = excluded.last_message_seq,
429
+ updated_at = excluded.updated_at`).run(conversation.conversationId, agent.agentId, conversation.replyTarget, conversation.channelId, conversation.threadKind, conversation.threadRootId, JSON.stringify(summary), latestMessage?.messageId ?? null, latestMessage?.seq ?? null, now);
430
+ return {
431
+ conversationId: conversation.conversationId,
432
+ replyTarget: conversation.replyTarget,
433
+ status: conversation.status,
434
+ surfaceKind: conversation.surfaceKind,
435
+ threadKind: conversation.threadKind,
436
+ threadRootId: conversation.threadRootId,
437
+ unreadCount: conversation.unreadCount,
438
+ queuedPromptCount: conversation.queuedPromptCount,
439
+ hasActiveRun: conversation.hasActiveRun,
440
+ updatedAt: conversation.updatedAt,
441
+ summaryUpdatedAt: new Date(now).toISOString(),
442
+ summary,
443
+ summaryText: formatConversationSummaryText(conversation.replyTarget, conversation.status, summary),
444
+ };
445
+ }
446
+ function loadConversationCollaborationState(db, conversation) {
447
+ const threadCollaboration = getThreadCollaborationSummary(db, {
448
+ channelId: conversation.channelId,
449
+ threadRootId: conversation.threadRootId,
450
+ });
451
+ const participants = conversation.threadRootId
452
+ ? threadCollaboration.participants
453
+ : listUnifiedRecentSurfaceParticipants(db, {
454
+ channelId: conversation.channelId,
455
+ threadRootId: conversation.threadRootId,
456
+ }).map((participant) => participant.name);
457
+ return {
458
+ threadCollaboration,
459
+ ownerAgentId: threadCollaboration.ownerAgentId ?? null,
460
+ ownerName: threadCollaboration.ownerName ?? null,
461
+ participants,
462
+ };
463
+ }
464
+ function equalStringArrays(left, right) {
465
+ if (left.length !== right.length)
466
+ return false;
467
+ return left.every((value, index) => value === right[index]);
468
+ }
469
+ function loadScopeMessages(db, conversation, limit = 12) {
470
+ if (conversation.threadRootId) {
471
+ if (conversation.replyTarget.startsWith('dm:@')) {
472
+ return db.prepare(`SELECT message_id as messageId,
473
+ sender_name as senderName,
474
+ sender_type as senderType,
475
+ target,
476
+ content,
477
+ seq,
478
+ created_at as createdAt
479
+ FROM channel_messages
480
+ WHERE channel_id = ?
481
+ AND COALESCE(message_source, '') != ?
482
+ AND (
483
+ thread_root_id = ?
484
+ OR (thread_root_id IS NULL AND target = ?)
485
+ )
486
+ ORDER BY seq DESC
487
+ LIMIT ?`).all(conversation.channelId, TASK_LIFECYCLE_SOURCE, conversation.threadRootId, conversation.replyTarget, limit)
488
+ .reverse();
489
+ }
490
+ return db.prepare(`SELECT message_id as messageId,
491
+ sender_name as senderName,
492
+ sender_type as senderType,
493
+ target,
494
+ content,
495
+ seq,
496
+ created_at as createdAt
497
+ FROM channel_messages
498
+ WHERE channel_id = ?
499
+ AND COALESCE(message_source, '') != ?
500
+ AND thread_root_id = ?
501
+ ORDER BY seq DESC
502
+ LIMIT ?`).all(conversation.channelId, TASK_LIFECYCLE_SOURCE, conversation.threadRootId, limit)
503
+ .reverse();
504
+ }
505
+ if (conversation.replyTarget.startsWith('dm:@')) {
506
+ return db.prepare(`SELECT message_id as messageId,
507
+ sender_name as senderName,
508
+ sender_type as senderType,
509
+ target,
510
+ content,
511
+ seq,
512
+ created_at as createdAt
513
+ FROM channel_messages
514
+ WHERE channel_id = ?
515
+ AND COALESCE(message_source, '') != ?
516
+ AND thread_root_id IS NULL
517
+ AND target = ?
518
+ ORDER BY seq DESC
519
+ LIMIT ?`).all(conversation.channelId, TASK_LIFECYCLE_SOURCE, conversation.replyTarget, limit)
520
+ .reverse();
521
+ }
522
+ return db.prepare(`SELECT message_id as messageId,
523
+ sender_name as senderName,
524
+ sender_type as senderType,
525
+ target,
526
+ content,
527
+ seq,
528
+ created_at as createdAt
529
+ FROM channel_messages
530
+ WHERE channel_id = ?
531
+ AND COALESCE(message_source, '') != ?
532
+ AND thread_root_id IS NULL
533
+ ORDER BY seq DESC
534
+ LIMIT ?`).all(conversation.channelId, TASK_LIFECYCLE_SOURCE, limit)
535
+ .reverse();
536
+ }
537
+ function loadLatestScopeMessage(db, conversation) {
538
+ if (conversation.threadRootId) {
539
+ if (conversation.replyTarget.startsWith('dm:@')) {
540
+ const row = db.prepare(`SELECT message_id as messageId,
541
+ seq
542
+ FROM channel_messages
543
+ WHERE channel_id = ?
544
+ AND COALESCE(message_source, '') != ?
545
+ AND (
546
+ thread_root_id = ?
547
+ OR (thread_root_id IS NULL AND target = ?)
548
+ )
549
+ ORDER BY seq DESC
550
+ LIMIT 1`).get(conversation.channelId, TASK_LIFECYCLE_SOURCE, conversation.threadRootId, conversation.replyTarget);
551
+ return row ?? null;
552
+ }
553
+ const row = db.prepare(`SELECT message_id as messageId,
554
+ seq
555
+ FROM channel_messages
556
+ WHERE channel_id = ?
557
+ AND COALESCE(message_source, '') != ?
558
+ AND thread_root_id = ?
559
+ ORDER BY seq DESC
560
+ LIMIT 1`).get(conversation.channelId, TASK_LIFECYCLE_SOURCE, conversation.threadRootId);
561
+ return row ?? null;
562
+ }
563
+ if (conversation.replyTarget.startsWith('dm:@')) {
564
+ const row = db.prepare(`SELECT message_id as messageId,
565
+ seq
566
+ FROM channel_messages
567
+ WHERE channel_id = ?
568
+ AND COALESCE(message_source, '') != ?
569
+ AND thread_root_id IS NULL
570
+ AND target = ?
571
+ ORDER BY seq DESC
572
+ LIMIT 1`).get(conversation.channelId, TASK_LIFECYCLE_SOURCE, conversation.replyTarget);
573
+ return row ?? null;
574
+ }
575
+ const row = db.prepare(`SELECT message_id as messageId,
576
+ seq
577
+ FROM channel_messages
578
+ WHERE channel_id = ?
579
+ AND COALESCE(message_source, '') != ?
580
+ AND thread_root_id IS NULL
581
+ ORDER BY seq DESC
582
+ LIMIT 1`).get(conversation.channelId, TASK_LIFECYCLE_SOURCE);
583
+ return row ?? null;
584
+ }
585
+ function loadThreadRootMessageContent(db, conversation) {
586
+ if (!conversation.threadRootId)
587
+ return null;
588
+ const row = db.prepare(`SELECT content
589
+ FROM channel_messages
590
+ WHERE channel_id = ?
591
+ AND (
592
+ thread_root_id = ?
593
+ OR message_id = ?
594
+ )
595
+ ORDER BY seq ASC
596
+ LIMIT 1`).get(conversation.channelId, conversation.threadRootId, conversation.threadRootId);
597
+ return row?.content ?? null;
598
+ }
599
+ function loadLinkedTasks(db, conversation) {
600
+ if (conversation.threadRootId) {
601
+ const summary = getThreadCollaborationSummary(db, {
602
+ channelId: conversation.channelId,
603
+ threadRootId: conversation.threadRootId,
604
+ });
605
+ if (summary.boundTask) {
606
+ const taskRefRow = db.prepare(`SELECT agent_task_ref as agentTaskRef,
607
+ claimed_by_name as claimedByName,
608
+ description,
609
+ message_id as messageId
610
+ FROM tasks
611
+ WHERE task_id = ?
612
+ LIMIT 1`).get(summary.boundTask.taskId);
613
+ return [{
614
+ taskId: summary.boundTask.taskId,
615
+ agentTaskRef: taskRefRow?.agentTaskRef ?? null,
616
+ taskNumber: summary.boundTask.taskNumber,
617
+ title: summary.boundTask.title,
618
+ status: summary.boundTask.status,
619
+ claimedByName: taskRefRow?.claimedByName ?? null,
620
+ description: taskRefRow?.description ?? null,
621
+ messageId: taskRefRow?.messageId ?? null,
622
+ channelId: conversation.channelId,
623
+ sourceTarget: conversation.replyTarget,
624
+ }];
625
+ }
626
+ }
627
+ return db.prepare(`SELECT t.task_id as taskId,
628
+ t.agent_task_ref as agentTaskRef,
629
+ t.task_number as taskNumber,
630
+ t.title,
631
+ t.status,
632
+ t.claimed_by_name as claimedByName,
633
+ t.description,
634
+ t.message_id as messageId,
635
+ COALESCE(t.dm_target, cm.target) as sourceTarget,
636
+ t.channel_id as channelId,
637
+ ch.name as channelName
638
+ FROM tasks t
639
+ LEFT JOIN channel_messages cm ON cm.message_id = t.message_id
640
+ LEFT JOIN channels ch ON ch.channel_id = t.channel_id
641
+ WHERE t.channel_id = ?
642
+ AND COALESCE(t.dm_target, cm.target) = ?
643
+ ORDER BY t.updated_at DESC, t.created_at DESC
644
+ LIMIT 3`).all(conversation.channelId, conversation.replyTarget);
645
+ }
646
+ function loadLastRunError(db, conversationId) {
647
+ const row = db.prepare(`SELECT r.error
648
+ FROM conversations c
649
+ JOIN runs r ON r.session_key = c.session_key
650
+ WHERE c.id = ?
651
+ AND r.error IS NOT NULL
652
+ AND trim(r.error) != ''
653
+ ORDER BY r.started_at DESC
654
+ LIMIT 1`).get(conversationId);
655
+ return summarizeText(row?.error ?? null, 180);
656
+ }
657
+ function buildTaskThreadTarget(sourceTarget, messageId) {
658
+ const normalizedTarget = normalizeOptionalText(sourceTarget);
659
+ if (!normalizedTarget || !messageId)
660
+ return null;
661
+ if (!(normalizedTarget.startsWith('#') || normalizedTarget.startsWith('dm:@')))
662
+ return null;
663
+ if ((normalizedTarget.startsWith('#') && normalizedTarget.includes(':')) || (normalizedTarget.startsWith('dm:@') && normalizedTarget.split(':').length >= 3)) {
664
+ return normalizedTarget;
665
+ }
666
+ return `${normalizedTarget}:${buildThreadShortId(messageId)}`;
667
+ }
668
+ function toLinkedTaskSummary(task) {
669
+ return {
670
+ taskId: task.taskId,
671
+ agentTaskRef: task.agentTaskRef,
672
+ taskNumber: task.taskNumber,
673
+ title: task.title,
674
+ status: task.status,
675
+ threadTarget: buildTaskThreadTarget(task.sourceTarget ?? (task.channelId?.startsWith('dm:') ? null : `#${task.channelName ?? task.channelId ?? ''}`), task.messageId ?? null),
676
+ };
677
+ }
678
+ function toActiveHandoffSummary(record) {
679
+ if (!record)
680
+ return null;
681
+ return {
682
+ handoffId: record.handoffId,
683
+ mode: record.mode,
684
+ status: record.status,
685
+ sourceConversationId: record.sourceConversationId,
686
+ targetConversationId: record.targetConversationId,
687
+ targetReplyTarget: record.targetReplyTarget,
688
+ sourceTarget: record.payload.sourceTarget,
689
+ goal: record.payload.goal,
690
+ error: record.payload.error,
691
+ updatedAt: record.updatedAt,
692
+ };
693
+ }
694
+ function deriveMyRole(params) {
695
+ if (params.boundTask?.claimedByName === params.agent.name)
696
+ return 'task_owner';
697
+ if (params.boundTask && params.participants.includes(params.agent.name))
698
+ return 'task_participant';
699
+ if (params.ownerAgentId === params.agent.agentId || params.ownerName === params.agent.name)
700
+ return 'owner';
701
+ if (params.participants.includes(params.agent.name))
702
+ return 'participant';
703
+ if (params.conversation.surfaceKind === 'dm' && params.conversation.isPrimaryThread)
704
+ return 'primary_dm_agent';
705
+ if (params.conversation.surfaceKind === 'dm_thread')
706
+ return 'dm_thread_agent';
707
+ if (params.conversation.surfaceKind === 'channel')
708
+ return 'channel_member';
709
+ if (params.conversation.surfaceKind === 'channel_thread')
710
+ return 'thread_member';
711
+ return 'observer';
712
+ }
713
+ function buildNextStep(params) {
714
+ if (params.conversation.status === 'active' || params.conversation.status === 'recovering') {
715
+ return 'Continue work on this surface';
716
+ }
717
+ if (params.conversation.status === 'awaiting_approval') {
718
+ return 'Wait for approval or a new user reply';
719
+ }
720
+ if (params.conversation.queuedPromptCount > 0) {
721
+ return `Process ${params.conversation.queuedPromptCount} queued prompt(s)`;
722
+ }
723
+ if (params.linkedTask && params.linkedTask.status !== 'done') {
724
+ return `Advance ${formatTaskRef(params.linkedTask.agentTaskRef, params.linkedTask.taskNumber)}`;
725
+ }
726
+ if (params.lastUserMessage) {
727
+ return 'Respond to the latest user message';
728
+ }
729
+ return null;
730
+ }
731
+ function formatConversationSummaryText(replyTarget, status, summary) {
732
+ const lines = [
733
+ `[Conversation summary] ${replyTarget}`,
734
+ `status: ${status}`,
735
+ `unread: ${summary.unreadCount}`,
736
+ `queued_prompts: ${summary.queuedPromptCount}`,
737
+ ];
738
+ if (summary.goal)
739
+ lines.push(`goal: ${summary.goal}`);
740
+ if (summary.recentState)
741
+ lines.push(`recent_state: ${summary.recentState}`);
742
+ if (summary.blockers.length > 0)
743
+ lines.push(`blockers: ${summary.blockers.join('; ')}`);
744
+ if (summary.nextStep)
745
+ lines.push(`next_step: ${summary.nextStep}`);
746
+ if (summary.myRole)
747
+ lines.push(`my_role: ${summary.myRole}`);
748
+ if (summary.ownerName)
749
+ lines.push(`owner: @${summary.ownerName}`);
750
+ if (summary.linkedTaskRefs.length > 0)
751
+ lines.push(`linked_tasks: ${summary.linkedTaskRefs.join(', ')}`);
752
+ if (summary.boundTask) {
753
+ lines.push(`bound_task: ${formatTaskRef(summary.boundTask.agentTaskRef, summary.boundTask.taskNumber)} [${summary.boundTask.status}]`);
754
+ }
755
+ if (summary.participants.length > 0) {
756
+ lines.push(`participants: ${summary.participants.map((name) => `@${name}`).join(', ')}`);
757
+ }
758
+ if (summary.activeHandoff) {
759
+ lines.push(`active_handoff: ${summary.activeHandoff.handoffId} ${summary.activeHandoff.mode} [${summary.activeHandoff.status}] -> ${summary.activeHandoff.targetReplyTarget}`);
760
+ }
761
+ if (summary.lastMessage) {
762
+ lines.push(`last_message: @${summary.lastMessage.senderName} ${summary.lastMessage.preview}`);
763
+ }
764
+ return lines.join('\n');
765
+ }
766
+ function formatTaskRef(agentTaskRef, taskNumber) {
767
+ return agentTaskRef?.trim() ? `${agentTaskRef} · #t${taskNumber}` : `#t${taskNumber}`;
768
+ }
769
+ function summarizeText(value, limit) {
770
+ const normalized = String(value ?? '')
771
+ .replace(/\s+/g, ' ')
772
+ .trim();
773
+ if (!normalized)
774
+ return null;
775
+ return normalized.length > limit ? `${normalized.slice(0, limit - 3)}...` : normalized;
776
+ }
777
+ function normalizeOptionalText(value) {
778
+ const normalized = String(value ?? '').trim();
779
+ return normalized ? normalized : null;
780
+ }
781
+ function parseConversationSummaryPayload(summaryJson) {
782
+ try {
783
+ const parsed = JSON.parse(summaryJson);
784
+ return {
785
+ goal: typeof parsed?.goal === 'string' ? parsed.goal : null,
786
+ recentState: typeof parsed?.recentState === 'string' ? parsed.recentState : null,
787
+ blockers: Array.isArray(parsed?.blockers) ? parsed.blockers.filter((item) => typeof item === 'string') : [],
788
+ nextStep: typeof parsed?.nextStep === 'string' ? parsed.nextStep : null,
789
+ lastUserMessage: typeof parsed?.lastUserMessage === 'string' ? parsed.lastUserMessage : null,
790
+ lastAgentMessage: typeof parsed?.lastAgentMessage === 'string' ? parsed.lastAgentMessage : null,
791
+ linkedTaskRefs: Array.isArray(parsed?.linkedTaskRefs) ? parsed.linkedTaskRefs.filter((item) => typeof item === 'string') : [],
792
+ linkedTaskTitles: Array.isArray(parsed?.linkedTaskTitles) ? parsed.linkedTaskTitles.filter((item) => typeof item === 'string') : [],
793
+ unreadCount: Number(parsed?.unreadCount ?? 0) || 0,
794
+ queuedPromptCount: Number(parsed?.queuedPromptCount ?? 0) || 0,
795
+ lastMessageAt: typeof parsed?.lastMessageAt === 'string' ? parsed.lastMessageAt : null,
796
+ lastMessage: parsed?.lastMessage && typeof parsed.lastMessage === 'object' && typeof parsed.lastMessage.messageId === 'string'
797
+ ? {
798
+ messageId: parsed.lastMessage.messageId,
799
+ target: typeof parsed.lastMessage.target === 'string' ? parsed.lastMessage.target : '',
800
+ senderName: typeof parsed.lastMessage.senderName === 'string' ? parsed.lastMessage.senderName : 'unknown',
801
+ senderType: typeof parsed.lastMessage.senderType === 'string' ? parsed.lastMessage.senderType : 'unknown',
802
+ preview: typeof parsed.lastMessage.preview === 'string' ? parsed.lastMessage.preview : '',
803
+ createdAt: typeof parsed.lastMessage.createdAt === 'string' ? parsed.lastMessage.createdAt : '',
804
+ }
805
+ : null,
806
+ linkedTasks: Array.isArray(parsed?.linkedTasks)
807
+ ? parsed.linkedTasks.filter((item) => Boolean(item
808
+ && typeof item === 'object'
809
+ && typeof item.taskId === 'string'
810
+ && typeof item.taskNumber === 'number'
811
+ && typeof item.title === 'string'
812
+ && typeof item.status === 'string')).map((item) => ({
813
+ taskId: item.taskId,
814
+ agentTaskRef: typeof item.agentTaskRef === 'string' ? item.agentTaskRef : null,
815
+ taskNumber: item.taskNumber,
816
+ title: item.title,
817
+ status: item.status,
818
+ threadTarget: typeof item.threadTarget === 'string' ? item.threadTarget : null,
819
+ }))
820
+ : [],
821
+ boundTask: parsed?.boundTask && typeof parsed.boundTask === 'object' && typeof parsed.boundTask.taskId === 'string'
822
+ ? {
823
+ taskId: parsed.boundTask.taskId,
824
+ agentTaskRef: typeof parsed.boundTask.agentTaskRef === 'string' ? parsed.boundTask.agentTaskRef : null,
825
+ taskNumber: Number(parsed.boundTask.taskNumber ?? 0) || 0,
826
+ title: typeof parsed.boundTask.title === 'string' ? parsed.boundTask.title : '',
827
+ status: typeof parsed.boundTask.status === 'string' ? parsed.boundTask.status : 'todo',
828
+ threadTarget: typeof parsed.boundTask.threadTarget === 'string' ? parsed.boundTask.threadTarget : null,
829
+ description: typeof parsed.boundTask.description === 'string' ? parsed.boundTask.description : null,
830
+ claimedByName: typeof parsed.boundTask.claimedByName === 'string' ? parsed.boundTask.claimedByName : null,
831
+ }
832
+ : null,
833
+ ownerAgentId: typeof parsed?.ownerAgentId === 'string' ? parsed.ownerAgentId : null,
834
+ ownerName: typeof parsed?.ownerName === 'string' ? parsed.ownerName : null,
835
+ participants: Array.isArray(parsed?.participants) ? parsed.participants.filter((item) => typeof item === 'string') : [],
836
+ myRole: typeof parsed?.myRole === 'string' ? parsed.myRole : null,
837
+ activeHandoff: parsed?.activeHandoff && typeof parsed.activeHandoff === 'object' && typeof parsed.activeHandoff.handoffId === 'string'
838
+ ? {
839
+ handoffId: parsed.activeHandoff.handoffId,
840
+ mode: parsed.activeHandoff.mode,
841
+ status: parsed.activeHandoff.status,
842
+ sourceConversationId: typeof parsed.activeHandoff.sourceConversationId === 'string' ? parsed.activeHandoff.sourceConversationId : '',
843
+ targetConversationId: typeof parsed.activeHandoff.targetConversationId === 'string' ? parsed.activeHandoff.targetConversationId : null,
844
+ targetReplyTarget: typeof parsed.activeHandoff.targetReplyTarget === 'string' ? parsed.activeHandoff.targetReplyTarget : '',
845
+ sourceTarget: typeof parsed.activeHandoff.sourceTarget === 'string' ? parsed.activeHandoff.sourceTarget : null,
846
+ goal: typeof parsed.activeHandoff.goal === 'string' ? parsed.activeHandoff.goal : '',
847
+ error: typeof parsed.activeHandoff.error === 'string' ? parsed.activeHandoff.error : null,
848
+ updatedAt: typeof parsed.activeHandoff.updatedAt === 'string' ? parsed.activeHandoff.updatedAt : '',
849
+ }
850
+ : null,
851
+ };
852
+ }
853
+ catch {
854
+ return {
855
+ goal: null,
856
+ recentState: null,
857
+ blockers: [],
858
+ nextStep: null,
859
+ lastUserMessage: null,
860
+ lastAgentMessage: null,
861
+ linkedTaskRefs: [],
862
+ linkedTaskTitles: [],
863
+ unreadCount: 0,
864
+ queuedPromptCount: 0,
865
+ lastMessageAt: null,
866
+ lastMessage: null,
867
+ linkedTasks: [],
868
+ boundTask: null,
869
+ ownerAgentId: null,
870
+ ownerName: null,
871
+ participants: [],
872
+ myRole: null,
873
+ activeHandoff: null,
874
+ };
875
+ }
876
+ }