@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,453 @@
1
+ import { clearAcpSessionId, getSession, log } from '@bbigbang/runtime-acp';
2
+ const COMPACT_ROTATION_THRESHOLD = 5;
3
+ const SUMMARY_MODEL = 'gpt-5.4';
4
+ const SUMMARY_REASONING_EFFORT = 'medium';
5
+ const SUMMARY_BATCH_LIMIT = 80;
6
+ const MAX_SUMMARY_BATCHES_PER_SETTLEMENT = 4;
7
+ const ROTATION_PENDING_STALE_MS = 10 * 60_000;
8
+ const rotationInFlight = new Set();
9
+ export function recordCodexCompactEvent(db, conversationId, event) {
10
+ const row = loadRotationCandidate(db, conversationId);
11
+ if (!row || !isLongLivedCodexRootSurface(row))
12
+ return null;
13
+ const currentThreadId = getSession(db, row.sessionKey)?.acpSessionId;
14
+ if (!currentThreadId || event.threadId !== currentThreadId)
15
+ return null;
16
+ const existing = db.prepare(`SELECT compact_count_since_rotation as compactCount,
17
+ compact_seen_keys_json as seenKeysJson
18
+ FROM conversation_runtime_rotation_state
19
+ WHERE conversation_id = ?`).get(conversationId);
20
+ const seenKeys = parseStringArray(existing?.seenKeysJson);
21
+ if (seenKeys.includes(event.eventKey))
22
+ return existing?.compactCount ?? 0;
23
+ const nextSeenKeys = [...seenKeys, event.eventKey].slice(-200);
24
+ const nextCount = (existing?.compactCount ?? 0) + 1;
25
+ const now = Date.now();
26
+ db.prepare(`INSERT INTO conversation_runtime_rotation_state(
27
+ conversation_id, agent_id, compact_count_since_rotation, compact_seen_keys_json,
28
+ runtime_epoch, last_compact_at, updated_at
29
+ )
30
+ VALUES(?, ?, ?, ?, 0, ?, ?)
31
+ ON CONFLICT(conversation_id) DO UPDATE SET
32
+ agent_id = excluded.agent_id,
33
+ compact_count_since_rotation = excluded.compact_count_since_rotation,
34
+ compact_seen_keys_json = excluded.compact_seen_keys_json,
35
+ last_compact_at = excluded.last_compact_at,
36
+ updated_at = excluded.updated_at`).run(conversationId, row.agentId, nextCount, JSON.stringify(nextSeenKeys), event.createdAt ?? now, now);
37
+ return nextCount;
38
+ }
39
+ export function markCodexRotationPendingIfDue(db, conversationId) {
40
+ const row = loadRotationCandidate(db, conversationId);
41
+ if (!row || !isLongLivedCodexRootSurface(row))
42
+ return false;
43
+ const state = db.prepare(`SELECT compact_count_since_rotation as compactCount
44
+ FROM conversation_runtime_rotation_state
45
+ WHERE conversation_id = ?`).get(row.conversationId);
46
+ if ((state?.compactCount ?? 0) <= COMPACT_ROTATION_THRESHOLD)
47
+ return false;
48
+ markSummaryRequested(db, row);
49
+ return true;
50
+ }
51
+ export async function maybeRotateCodexConversationAfterSettlement(params) {
52
+ if (!params.nodeRegistry || !params.codexAppServerBroker || !params.agentRuntimeCleanupBroker)
53
+ return false;
54
+ const row = loadRotationCandidate(params.db, params.conversationId);
55
+ if (!row || !isLongLivedCodexRootSurface(row))
56
+ return false;
57
+ if ((row.status !== 'idle' && row.status !== 'queued') || !row.nodeId)
58
+ return false;
59
+ const state = params.db.prepare(`SELECT compact_count_since_rotation as compactCount
60
+ FROM conversation_runtime_rotation_state
61
+ WHERE conversation_id = ?`).get(row.conversationId);
62
+ if ((state?.compactCount ?? 0) <= COMPACT_ROTATION_THRESHOLD)
63
+ return false;
64
+ if (rotationInFlight.has(row.conversationId))
65
+ return false;
66
+ if (!tryBeginRotation(params.db, row))
67
+ return false;
68
+ rotationInFlight.add(row.conversationId);
69
+ try {
70
+ let ready = false;
71
+ try {
72
+ ready = await refreshRollingSummary(params, row);
73
+ }
74
+ finally {
75
+ if (!ready)
76
+ clearRotationPending(params.db, row.conversationId);
77
+ }
78
+ if (!ready)
79
+ return false;
80
+ const stillIdle = params.db.prepare(`SELECT status FROM conversations WHERE id = ?`).get(row.conversationId);
81
+ if (stillIdle?.status !== 'idle' && stillIdle?.status !== 'queued') {
82
+ clearRotationPending(params.db, row.conversationId);
83
+ return false;
84
+ }
85
+ try {
86
+ await params.agentRuntimeCleanupBroker.cleanupAgentRuntime({
87
+ nodeId: row.nodeId,
88
+ agentId: row.agentId,
89
+ hostKeys: [`conversation:${row.conversationId}:${row.agentType}`],
90
+ sessionKeys: [row.sessionKey],
91
+ });
92
+ }
93
+ catch (error) {
94
+ clearRotationPending(params.db, row.conversationId);
95
+ log.warn('[rolling-summary] failed to close codex app-server host before rotation', {
96
+ conversationId: row.conversationId,
97
+ agentId: row.agentId,
98
+ error: String(error?.message ?? error),
99
+ });
100
+ return false;
101
+ }
102
+ clearAcpSessionId(params.db, row.sessionKey);
103
+ const now = Date.now();
104
+ params.db.prepare(`UPDATE conversation_runtime_rotation_state
105
+ SET compact_count_since_rotation = 0,
106
+ compact_seen_keys_json = '[]',
107
+ runtime_epoch = runtime_epoch + 1,
108
+ last_rotation_at = ?,
109
+ last_rotation_reason = ?,
110
+ summary_requested_at = NULL,
111
+ updated_at = ?
112
+ WHERE conversation_id = ?`).run(now, 'compact_count_gt_5', now, row.conversationId);
113
+ log.info('[rolling-summary] rotated codex app-server conversation', {
114
+ conversationId: row.conversationId,
115
+ agentId: row.agentId,
116
+ });
117
+ return true;
118
+ }
119
+ finally {
120
+ rotationInFlight.delete(row.conversationId);
121
+ }
122
+ }
123
+ export function isCodexRuntimeRotationPending(db, conversationId) {
124
+ const row = db.prepare(`SELECT summary_requested_at as summaryRequestedAt
125
+ FROM conversation_runtime_rotation_state
126
+ WHERE conversation_id = ?`).get(conversationId);
127
+ if (typeof row?.summaryRequestedAt !== 'number')
128
+ return false;
129
+ return Date.now() - row.summaryRequestedAt < ROTATION_PENDING_STALE_MS;
130
+ }
131
+ export function buildRollingSummaryPromptSection(db, conversationId) {
132
+ const body = buildRollingSummarySectionBody(db, conversationId);
133
+ if (!body)
134
+ return null;
135
+ return ['[Conversation rolling summary]', body].join('\n');
136
+ }
137
+ export function buildRollingSummarySectionBody(db, conversationId) {
138
+ const stored = loadStoredRollingSummary(db, conversationId);
139
+ if (!stored)
140
+ return null;
141
+ const payload = parseSummaryPayload(stored.summaryJson);
142
+ const summaryText = typeof payload.summaryText === 'string' ? payload.summaryText.trim() : '';
143
+ if (!summaryText)
144
+ return null;
145
+ const lines = [
146
+ '[Conversation rolling summary]',
147
+ `covered_through_seq: ${stored.coveredThroughSeq}`,
148
+ summaryText,
149
+ ];
150
+ for (const key of ['keyFacts', 'openThreads', 'userPreferences', 'decisions', 'risks']) {
151
+ const values = parseStringArray(payload[key]);
152
+ if (values.length > 0) {
153
+ lines.push(`${key}:`);
154
+ for (const value of values.slice(0, 12)) {
155
+ lines.push(`- ${value}`);
156
+ }
157
+ }
158
+ }
159
+ return lines.join('\n');
160
+ }
161
+ function tryBeginRotation(db, row) {
162
+ const existing = db.prepare(`SELECT summary_requested_at as summaryRequestedAt
163
+ FROM conversation_runtime_rotation_state
164
+ WHERE conversation_id = ?`).get(row.conversationId);
165
+ if (typeof existing?.summaryRequestedAt === 'number'
166
+ && Date.now() - existing.summaryRequestedAt < ROTATION_PENDING_STALE_MS) {
167
+ return true;
168
+ }
169
+ markSummaryRequested(db, row);
170
+ return true;
171
+ }
172
+ function clearRotationPending(db, conversationId) {
173
+ const now = Date.now();
174
+ db.prepare(`UPDATE conversation_runtime_rotation_state
175
+ SET summary_requested_at = NULL,
176
+ updated_at = ?
177
+ WHERE conversation_id = ?`).run(now, conversationId);
178
+ }
179
+ export function clearRollingSummaryStateForConversation(db, conversationId) {
180
+ db.prepare('DELETE FROM conversation_rolling_summaries WHERE conversation_id = ?').run(conversationId);
181
+ db.prepare('DELETE FROM conversation_runtime_rotation_state WHERE conversation_id = ?').run(conversationId);
182
+ }
183
+ export function clearRollingSummaryStateForAgent(db, agentId) {
184
+ db.prepare('DELETE FROM conversation_rolling_summaries WHERE agent_id = ?').run(agentId);
185
+ db.prepare('DELETE FROM conversation_runtime_rotation_state WHERE agent_id = ?').run(agentId);
186
+ }
187
+ async function refreshRollingSummary(params, row) {
188
+ const latestSeq = loadLatestSurfaceSeq(params.db, row);
189
+ if (latestSeq == null)
190
+ return false;
191
+ let stored = loadStoredRollingSummary(params.db, row.conversationId);
192
+ let coveredThroughSeq = stored?.coveredThroughSeq ?? 0;
193
+ let batches = 0;
194
+ while (coveredThroughSeq < latestSeq && batches < MAX_SUMMARY_BATCHES_PER_SETTLEMENT) {
195
+ const messages = loadSurfaceMessagesAfter(params.db, row, coveredThroughSeq, SUMMARY_BATCH_LIMIT);
196
+ if (messages.length === 0)
197
+ break;
198
+ const maxSeq = messages.at(-1).seq;
199
+ markSummaryRequested(params.db, row);
200
+ try {
201
+ const content = await params.codexAppServerBroker.summarizeConversation(row.nodeId, {
202
+ envVars: parseEnvVars(row.envVarsJson),
203
+ prompt: buildSummaryPrompt({
204
+ conversation: row,
205
+ previousSummaryJson: stored?.summaryJson ?? null,
206
+ previousCoveredThroughSeq: coveredThroughSeq,
207
+ messages,
208
+ maxSeq,
209
+ }),
210
+ });
211
+ const summaryJson = normalizeSummaryResponse(content, maxSeq);
212
+ upsertRollingSummary(params.db, row, summaryJson, maxSeq, messages.length);
213
+ stored = { summaryJson: JSON.stringify(summaryJson), coveredThroughSeq: maxSeq };
214
+ coveredThroughSeq = maxSeq;
215
+ batches += 1;
216
+ }
217
+ catch (error) {
218
+ markSummaryFailed(params.db, row, error);
219
+ return false;
220
+ }
221
+ }
222
+ return coveredThroughSeq >= latestSeq;
223
+ }
224
+ function loadRotationCandidate(db, conversationId) {
225
+ const row = db.prepare(`SELECT id as conversationId,
226
+ session_key as sessionKey,
227
+ agent_id as agentId,
228
+ agent_type as agentType,
229
+ channel_id as channelId,
230
+ reply_target as replyTarget,
231
+ thread_kind as threadKind,
232
+ thread_root_id as threadRootId,
233
+ is_primary_thread as isPrimaryThread,
234
+ status,
235
+ node_id as nodeId,
236
+ workspace_path as workspacePath,
237
+ env_vars as envVarsJson
238
+ FROM conversations
239
+ WHERE id = ?
240
+ LIMIT 1`).get(conversationId);
241
+ return row ?? null;
242
+ }
243
+ function isLongLivedCodexRootSurface(row) {
244
+ if (row.agentType !== 'codex_app_server')
245
+ return false;
246
+ if (row.threadRootId)
247
+ return false;
248
+ if (row.threadKind === 'direct') {
249
+ return row.isPrimaryThread === 1
250
+ && row.channelId.startsWith('dm:')
251
+ && row.replyTarget.startsWith('dm:@');
252
+ }
253
+ return row.threadKind === 'branch' && !row.channelId.startsWith('dm:');
254
+ }
255
+ function loadStoredRollingSummary(db, conversationId) {
256
+ const row = db.prepare(`SELECT summary_json as summaryJson,
257
+ covered_through_seq as coveredThroughSeq
258
+ FROM conversation_rolling_summaries
259
+ WHERE conversation_id = ?
260
+ AND status = 'ready'
261
+ LIMIT 1`).get(conversationId);
262
+ return row ?? null;
263
+ }
264
+ function loadLatestSurfaceSeq(db, row) {
265
+ const query = row.threadKind === 'direct'
266
+ ? `SELECT MAX(seq) as seq
267
+ FROM channel_messages
268
+ WHERE channel_id = ?
269
+ AND thread_root_id IS NULL
270
+ AND target = ?`
271
+ : `SELECT MAX(seq) as seq
272
+ FROM channel_messages
273
+ WHERE channel_id = ?
274
+ AND thread_root_id IS NULL`;
275
+ const result = row.threadKind === 'direct'
276
+ ? db.prepare(query).get(row.channelId, row.replyTarget)
277
+ : db.prepare(query).get(row.channelId);
278
+ return typeof result?.seq === 'number' ? result.seq : null;
279
+ }
280
+ function loadSurfaceMessagesAfter(db, row, afterSeq, limit) {
281
+ if (row.threadKind === 'direct') {
282
+ return db.prepare(`SELECT message_id as messageId,
283
+ seq,
284
+ sender_name as senderName,
285
+ sender_type as senderType,
286
+ target,
287
+ content,
288
+ created_at as createdAt
289
+ FROM channel_messages
290
+ WHERE channel_id = ?
291
+ AND thread_root_id IS NULL
292
+ AND target = ?
293
+ AND seq > ?
294
+ ORDER BY seq ASC
295
+ LIMIT ?`).all(row.channelId, row.replyTarget, afterSeq, limit);
296
+ }
297
+ return db.prepare(`SELECT message_id as messageId,
298
+ seq,
299
+ sender_name as senderName,
300
+ sender_type as senderType,
301
+ target,
302
+ content,
303
+ created_at as createdAt
304
+ FROM channel_messages
305
+ WHERE channel_id = ?
306
+ AND thread_root_id IS NULL
307
+ AND seq > ?
308
+ ORDER BY seq ASC
309
+ LIMIT ?`).all(row.channelId, afterSeq, limit);
310
+ }
311
+ function markSummaryRequested(db, row) {
312
+ const now = Date.now();
313
+ db.prepare(`INSERT INTO conversation_runtime_rotation_state(
314
+ conversation_id, agent_id, compact_count_since_rotation,
315
+ compact_seen_keys_json, runtime_epoch, summary_requested_at, updated_at
316
+ )
317
+ VALUES(?, ?, 0, '[]', 0, ?, ?)
318
+ ON CONFLICT(conversation_id) DO UPDATE SET
319
+ summary_requested_at = excluded.summary_requested_at,
320
+ updated_at = excluded.updated_at`).run(row.conversationId, row.agentId, now, now);
321
+ }
322
+ function markSummaryFailed(db, row, error) {
323
+ const now = Date.now();
324
+ const message = String(error?.message ?? error);
325
+ db.prepare(`INSERT INTO conversation_rolling_summaries(
326
+ conversation_id, agent_id, channel_id, reply_target, thread_kind, thread_root_id,
327
+ summary_json, covered_through_seq, source_message_count, model, reasoning_effort,
328
+ status, error, created_at, updated_at, completed_at
329
+ )
330
+ VALUES(?, ?, ?, ?, ?, ?, '{}', 0, 0, ?, ?, 'failed', ?, ?, ?, ?)
331
+ ON CONFLICT(conversation_id) DO UPDATE SET
332
+ status = CASE
333
+ WHEN conversation_rolling_summaries.status = 'ready' THEN 'ready'
334
+ ELSE 'failed'
335
+ END,
336
+ error = excluded.error,
337
+ updated_at = excluded.updated_at,
338
+ completed_at = excluded.completed_at`).run(row.conversationId, row.agentId, row.channelId, row.replyTarget, row.threadKind, row.threadRootId, SUMMARY_MODEL, SUMMARY_REASONING_EFFORT, message, now, now, now);
339
+ }
340
+ function upsertRollingSummary(db, row, summaryJson, coveredThroughSeq, sourceMessageCount) {
341
+ const now = Date.now();
342
+ db.prepare(`INSERT INTO conversation_rolling_summaries(
343
+ conversation_id, agent_id, channel_id, reply_target, thread_kind, thread_root_id,
344
+ summary_json, covered_through_seq, source_message_count, model, reasoning_effort,
345
+ status, error, created_at, updated_at, completed_at
346
+ )
347
+ VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'ready', NULL, ?, ?, ?)
348
+ ON CONFLICT(conversation_id) DO UPDATE SET
349
+ agent_id = excluded.agent_id,
350
+ channel_id = excluded.channel_id,
351
+ reply_target = excluded.reply_target,
352
+ thread_kind = excluded.thread_kind,
353
+ thread_root_id = excluded.thread_root_id,
354
+ summary_json = excluded.summary_json,
355
+ covered_through_seq = excluded.covered_through_seq,
356
+ source_message_count = excluded.source_message_count,
357
+ model = excluded.model,
358
+ reasoning_effort = excluded.reasoning_effort,
359
+ status = 'ready',
360
+ error = NULL,
361
+ updated_at = excluded.updated_at,
362
+ completed_at = excluded.completed_at`).run(row.conversationId, row.agentId, row.channelId, row.replyTarget, row.threadKind, row.threadRootId, JSON.stringify(summaryJson), coveredThroughSeq, sourceMessageCount, SUMMARY_MODEL, SUMMARY_REASONING_EFFORT, now, now, now);
363
+ }
364
+ function buildSummaryPrompt(params) {
365
+ const messagesJson = params.messages.map((message) => ({
366
+ seq: message.seq,
367
+ senderName: message.senderName,
368
+ senderType: message.senderType,
369
+ target: message.target,
370
+ content: message.content,
371
+ createdAt: new Date(message.createdAt).toISOString(),
372
+ }));
373
+ return [
374
+ 'You are generating an internal rolling summary for a long-lived agent conversation.',
375
+ 'Return only valid JSON. Do not include markdown fences or commentary.',
376
+ 'Schema: {"summaryText": string, "keyFacts": string[], "openThreads": string[], "userPreferences": string[], "decisions": string[], "risks": string[], "coveredThroughSeq": number}.',
377
+ 'Preserve durable facts, current goals, unresolved requests, user preferences, decisions, and risks. Remove chatter and duplicated wording.',
378
+ `Conversation: ${params.conversation.replyTarget}`,
379
+ `Previous coveredThroughSeq: ${params.previousCoveredThroughSeq}`,
380
+ `New coveredThroughSeq must be: ${params.maxSeq}`,
381
+ `Previous summary JSON: ${params.previousSummaryJson ?? 'null'}`,
382
+ `New messages JSON: ${JSON.stringify(messagesJson)}`,
383
+ ].join('\n\n');
384
+ }
385
+ function normalizeSummaryResponse(content, coveredThroughSeq) {
386
+ const parsed = parseSummaryPayload(extractJsonObject(content));
387
+ const summaryText = typeof parsed.summaryText === 'string' ? parsed.summaryText.trim() : '';
388
+ if (!summaryText) {
389
+ throw new Error('Codex summary response did not include summaryText.');
390
+ }
391
+ return {
392
+ summaryText,
393
+ keyFacts: parseStringArray(parsed.keyFacts),
394
+ openThreads: parseStringArray(parsed.openThreads),
395
+ userPreferences: parseStringArray(parsed.userPreferences),
396
+ decisions: parseStringArray(parsed.decisions),
397
+ risks: parseStringArray(parsed.risks),
398
+ coveredThroughSeq,
399
+ };
400
+ }
401
+ function extractJsonObject(content) {
402
+ const trimmed = content.trim();
403
+ if (trimmed.startsWith('{') && trimmed.endsWith('}'))
404
+ return trimmed;
405
+ const match = trimmed.match(/\{[\s\S]*\}/u);
406
+ if (!match)
407
+ throw new Error('Codex summary response did not contain JSON.');
408
+ return match[0];
409
+ }
410
+ function parseSummaryPayload(value) {
411
+ if (typeof value === 'string') {
412
+ const parsed = JSON.parse(value);
413
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
414
+ ? parsed
415
+ : {};
416
+ }
417
+ return value && typeof value === 'object' && !Array.isArray(value)
418
+ ? value
419
+ : {};
420
+ }
421
+ function parseStringArray(value) {
422
+ if (typeof value === 'string') {
423
+ try {
424
+ return parseStringArray(JSON.parse(value));
425
+ }
426
+ catch {
427
+ return [];
428
+ }
429
+ }
430
+ if (!Array.isArray(value))
431
+ return [];
432
+ return value
433
+ .map((item) => typeof item === 'string' ? item.trim() : '')
434
+ .filter(Boolean);
435
+ }
436
+ function parseEnvVars(value) {
437
+ if (!value)
438
+ return undefined;
439
+ try {
440
+ const parsed = JSON.parse(value);
441
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
442
+ return undefined;
443
+ const env = {};
444
+ for (const [key, raw] of Object.entries(parsed)) {
445
+ if (typeof raw === 'string')
446
+ env[key] = raw;
447
+ }
448
+ return Object.keys(env).length > 0 ? env : undefined;
449
+ }
450
+ catch {
451
+ return undefined;
452
+ }
453
+ }
@@ -0,0 +1,11 @@
1
+ export function findActiveConversationRunId(db, conversationId) {
2
+ const row = db
3
+ .prepare(`SELECT r.run_id as runId
4
+ FROM conversations c
5
+ JOIN runs r ON r.session_key = c.session_key
6
+ WHERE c.id = ? AND r.ended_at IS NULL
7
+ ORDER BY r.started_at DESC, r.rowid DESC
8
+ LIMIT 1`)
9
+ .get(conversationId);
10
+ return row?.runId ?? null;
11
+ }