@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,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
|
+
}
|