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