@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,685 @@
|
|
|
1
|
+
import { log } from '@bbigbang/runtime-acp';
|
|
2
|
+
import { deleteTaskThreadFollowupsForTask } from './taskThreadFollowups.js';
|
|
3
|
+
import { getThreadBindingForTask } from './threadTaskBindings.js';
|
|
4
|
+
import { NOTIFICATION_REASON_MASK, supersedeNotificationRoundsForThreadRoots, } from './notificationRounds.js';
|
|
5
|
+
import { hasEffectiveAgentRuntimeCapability } from './runtimeCapabilities.js';
|
|
6
|
+
const taskThreadClosingStates = new Map();
|
|
7
|
+
const taskThreadReopenedRunBlocks = new Map();
|
|
8
|
+
function listTaskThreadConversations(db, params) {
|
|
9
|
+
if (params.channelId.startsWith('dm:')) {
|
|
10
|
+
const agentId = params.channelId.slice(3);
|
|
11
|
+
const dmUserId = params.dmUserId?.trim() || null;
|
|
12
|
+
return db.prepare(`SELECT c.id,
|
|
13
|
+
c.session_key as sessionKey,
|
|
14
|
+
c.status,
|
|
15
|
+
c.agent_id as agentId,
|
|
16
|
+
a.name as agentName,
|
|
17
|
+
c.agent_type as agentType,
|
|
18
|
+
c.node_id as nodeId
|
|
19
|
+
FROM conversations c
|
|
20
|
+
LEFT JOIN agents a ON a.agent_id = c.agent_id
|
|
21
|
+
WHERE c.thread_kind = 'direct'
|
|
22
|
+
AND c.thread_root_id = ?
|
|
23
|
+
AND c.agent_id = ?
|
|
24
|
+
AND ${dmUserId ? 'c.user_id = ?' : '1 = 1'}`).all(params.threadRootId, agentId, ...(dmUserId ? [dmUserId] : []));
|
|
25
|
+
}
|
|
26
|
+
return db.prepare(`SELECT c.id,
|
|
27
|
+
c.session_key as sessionKey,
|
|
28
|
+
c.status,
|
|
29
|
+
c.agent_id as agentId,
|
|
30
|
+
a.name as agentName,
|
|
31
|
+
c.agent_type as agentType,
|
|
32
|
+
c.node_id as nodeId
|
|
33
|
+
FROM conversations c
|
|
34
|
+
LEFT JOIN agents a ON a.agent_id = c.agent_id
|
|
35
|
+
WHERE c.thread_kind = 'branch'
|
|
36
|
+
AND c.channel_id = ?
|
|
37
|
+
AND c.thread_root_id = ?`).all(params.channelId, params.threadRootId);
|
|
38
|
+
}
|
|
39
|
+
function setTaskThreadClosingState(conversationId, state) {
|
|
40
|
+
taskThreadClosingStates.set(conversationId, state);
|
|
41
|
+
}
|
|
42
|
+
export function getTaskThreadClosingState(conversationId) {
|
|
43
|
+
if (!conversationId)
|
|
44
|
+
return null;
|
|
45
|
+
return taskThreadClosingStates.get(conversationId) ?? null;
|
|
46
|
+
}
|
|
47
|
+
export function getTaskThreadReopenedRunBlock(conversationId) {
|
|
48
|
+
if (!conversationId)
|
|
49
|
+
return null;
|
|
50
|
+
return taskThreadReopenedRunBlocks.get(conversationId) ?? null;
|
|
51
|
+
}
|
|
52
|
+
function findOpenRunForConversation(db, conversationId) {
|
|
53
|
+
const row = db.prepare(`SELECT r.run_id as runId,
|
|
54
|
+
r.started_at as startedAt
|
|
55
|
+
FROM conversations c
|
|
56
|
+
JOIN runs r
|
|
57
|
+
ON r.session_key = c.session_key
|
|
58
|
+
AND r.ended_at IS NULL
|
|
59
|
+
WHERE c.id = ?
|
|
60
|
+
ORDER BY r.started_at DESC
|
|
61
|
+
LIMIT 1`).get(conversationId);
|
|
62
|
+
return row ?? null;
|
|
63
|
+
}
|
|
64
|
+
function findRunStartedAt(db, conversationId, runId) {
|
|
65
|
+
const row = db.prepare(`SELECT r.started_at as startedAt
|
|
66
|
+
FROM conversations c
|
|
67
|
+
JOIN runs r
|
|
68
|
+
ON r.session_key = c.session_key
|
|
69
|
+
AND r.run_id = ?
|
|
70
|
+
WHERE c.id = ?
|
|
71
|
+
LIMIT 1`).get(runId, conversationId);
|
|
72
|
+
return typeof row?.startedAt === 'number' ? row.startedAt : null;
|
|
73
|
+
}
|
|
74
|
+
function findLatestUserReopenEventAt(db, taskId) {
|
|
75
|
+
const row = db.prepare(`SELECT created_at as createdAt
|
|
76
|
+
FROM task_events
|
|
77
|
+
WHERE task_id = ?
|
|
78
|
+
AND event_type = 'status_changed'
|
|
79
|
+
AND actor_type = 'user'
|
|
80
|
+
AND from_status = 'in_review'
|
|
81
|
+
AND to_status = 'in_progress'
|
|
82
|
+
ORDER BY created_at DESC
|
|
83
|
+
LIMIT 1`).get(taskId);
|
|
84
|
+
return typeof row?.createdAt === 'number' ? row.createdAt : null;
|
|
85
|
+
}
|
|
86
|
+
export function getStaleTaskThreadReopenedRunBlock(db, params) {
|
|
87
|
+
const conversationId = params.conversationId?.trim();
|
|
88
|
+
const taskId = params.taskId?.trim();
|
|
89
|
+
if (!conversationId || !taskId)
|
|
90
|
+
return null;
|
|
91
|
+
const openRun = params.runId?.trim()
|
|
92
|
+
? {
|
|
93
|
+
runId: params.runId.trim(),
|
|
94
|
+
startedAt: findRunStartedAt(db, conversationId, params.runId.trim()),
|
|
95
|
+
}
|
|
96
|
+
: findOpenRunForConversation(db, conversationId);
|
|
97
|
+
if (!openRun?.runId)
|
|
98
|
+
return null;
|
|
99
|
+
const memoryBlock = taskThreadReopenedRunBlocks.get(conversationId);
|
|
100
|
+
if (memoryBlock?.taskId === taskId && memoryBlock.blockedRunId === openRun.runId) {
|
|
101
|
+
return {
|
|
102
|
+
taskId,
|
|
103
|
+
blockedRunId: openRun.runId,
|
|
104
|
+
source: 'memory',
|
|
105
|
+
...(typeof openRun.startedAt === 'number' ? { runStartedAt: openRun.startedAt } : {}),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
if (typeof openRun.startedAt !== 'number')
|
|
109
|
+
return null;
|
|
110
|
+
const reopenEventAt = findLatestUserReopenEventAt(db, taskId);
|
|
111
|
+
if (typeof reopenEventAt !== 'number')
|
|
112
|
+
return null;
|
|
113
|
+
if (openRun.startedAt >= reopenEventAt)
|
|
114
|
+
return null;
|
|
115
|
+
return {
|
|
116
|
+
taskId,
|
|
117
|
+
blockedRunId: openRun.runId,
|
|
118
|
+
source: 'task_event',
|
|
119
|
+
reopenEventAt,
|
|
120
|
+
runStartedAt: openRun.startedAt,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function clearTaskThreadClosingStateForConversation(conversationId) {
|
|
124
|
+
if (!conversationId)
|
|
125
|
+
return;
|
|
126
|
+
taskThreadClosingStates.delete(conversationId);
|
|
127
|
+
}
|
|
128
|
+
function clearTaskThreadReopenedRunBlockForConversation(conversationId, runId) {
|
|
129
|
+
if (!conversationId)
|
|
130
|
+
return;
|
|
131
|
+
if (!runId) {
|
|
132
|
+
taskThreadReopenedRunBlocks.delete(conversationId);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const current = taskThreadReopenedRunBlocks.get(conversationId);
|
|
136
|
+
if (current?.blockedRunId === runId) {
|
|
137
|
+
taskThreadReopenedRunBlocks.delete(conversationId);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
export function clearTaskThreadClosingStateForTask(db, taskId) {
|
|
141
|
+
const binding = getThreadBindingForTask(db, taskId);
|
|
142
|
+
if (!binding)
|
|
143
|
+
return;
|
|
144
|
+
const conversations = listTaskThreadConversations(db, {
|
|
145
|
+
channelId: binding.channelId,
|
|
146
|
+
threadRootId: binding.threadRootId,
|
|
147
|
+
});
|
|
148
|
+
for (const conversation of conversations) {
|
|
149
|
+
clearTaskThreadClosingStateForConversation(conversation.id);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
export function clearTaskThreadReopenedRunBlock(conversationId, runId) {
|
|
153
|
+
clearTaskThreadReopenedRunBlockForConversation(conversationId, runId);
|
|
154
|
+
}
|
|
155
|
+
export function clearTaskThreadReopenedRunBlocksForTask(db, taskId) {
|
|
156
|
+
const binding = getThreadBindingForTask(db, taskId);
|
|
157
|
+
if (!binding)
|
|
158
|
+
return;
|
|
159
|
+
const conversations = listTaskThreadConversations(db, {
|
|
160
|
+
channelId: binding.channelId,
|
|
161
|
+
threadRootId: binding.threadRootId,
|
|
162
|
+
});
|
|
163
|
+
for (const conversation of conversations) {
|
|
164
|
+
clearTaskThreadReopenedRunBlockForConversation(conversation.id);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
export function blockActiveTaskThreadRunsAfterReopen(db, taskId) {
|
|
168
|
+
const binding = getThreadBindingForTask(db, taskId);
|
|
169
|
+
if (!binding)
|
|
170
|
+
return [];
|
|
171
|
+
const conversations = listTaskThreadConversations(db, {
|
|
172
|
+
channelId: binding.channelId,
|
|
173
|
+
threadRootId: binding.threadRootId,
|
|
174
|
+
});
|
|
175
|
+
const blocked = [];
|
|
176
|
+
for (const conversation of conversations) {
|
|
177
|
+
const row = db.prepare(`SELECT run_id as runId
|
|
178
|
+
FROM runs
|
|
179
|
+
WHERE session_key = ?
|
|
180
|
+
AND ended_at IS NULL
|
|
181
|
+
ORDER BY started_at DESC
|
|
182
|
+
LIMIT 1`).get(conversation.sessionKey);
|
|
183
|
+
if (!row?.runId)
|
|
184
|
+
continue;
|
|
185
|
+
taskThreadReopenedRunBlocks.set(conversation.id, {
|
|
186
|
+
taskId,
|
|
187
|
+
blockedRunId: row.runId,
|
|
188
|
+
});
|
|
189
|
+
blocked.push({
|
|
190
|
+
conversationId: conversation.id,
|
|
191
|
+
runId: row.runId,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
return blocked;
|
|
195
|
+
}
|
|
196
|
+
export function markTaskThreadClosingFinalSent(conversationId, runId) {
|
|
197
|
+
const current = getTaskThreadClosingState(conversationId);
|
|
198
|
+
if (!current)
|
|
199
|
+
return false;
|
|
200
|
+
if (!current.allowOneFinal || current.finalSent)
|
|
201
|
+
return false;
|
|
202
|
+
if (!current.ownerRunId || current.ownerRunId !== runId)
|
|
203
|
+
return false;
|
|
204
|
+
taskThreadClosingStates.set(conversationId, {
|
|
205
|
+
...current,
|
|
206
|
+
allowOneFinal: false,
|
|
207
|
+
finalSent: true,
|
|
208
|
+
});
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
function restoreIdleConversationStatus(db, conversation) {
|
|
212
|
+
const hasQueuedPrompt = Boolean(db.prepare(`SELECT 1
|
|
213
|
+
FROM conversation_prompt_queue
|
|
214
|
+
WHERE conversation_id = ?
|
|
215
|
+
AND COALESCE(dispatch_kind, '') != 'legacy_unknown'
|
|
216
|
+
LIMIT 1`).get(conversation.id));
|
|
217
|
+
if (hasQueuedPrompt)
|
|
218
|
+
return false;
|
|
219
|
+
const hasOpenRun = Boolean(db.prepare(`SELECT 1
|
|
220
|
+
FROM runs
|
|
221
|
+
WHERE session_key = ?
|
|
222
|
+
AND ended_at IS NULL
|
|
223
|
+
LIMIT 1`).get(conversation.sessionKey));
|
|
224
|
+
if (hasOpenRun)
|
|
225
|
+
return false;
|
|
226
|
+
const result = db.prepare(`UPDATE conversations
|
|
227
|
+
SET status = 'idle', updated_at = ?
|
|
228
|
+
WHERE id = ?
|
|
229
|
+
AND status IN ('queued', 'active', 'recovering', 'awaiting_approval')`).run(Date.now(), conversation.id);
|
|
230
|
+
return result.changes > 0;
|
|
231
|
+
}
|
|
232
|
+
function findOpenRunId(db, sessionKey) {
|
|
233
|
+
const row = db.prepare(`SELECT run_id as runId
|
|
234
|
+
FROM runs
|
|
235
|
+
WHERE session_key = ?
|
|
236
|
+
AND ended_at IS NULL
|
|
237
|
+
ORDER BY started_at DESC
|
|
238
|
+
LIMIT 1`).get(sessionKey);
|
|
239
|
+
return row?.runId ?? null;
|
|
240
|
+
}
|
|
241
|
+
export function findActiveTaskThreadRunForAgent(db, params) {
|
|
242
|
+
const agentId = params.agentId?.trim();
|
|
243
|
+
if (!agentId)
|
|
244
|
+
return null;
|
|
245
|
+
const binding = getThreadBindingForTask(db, params.taskId);
|
|
246
|
+
if (!binding)
|
|
247
|
+
return null;
|
|
248
|
+
const conversations = listTaskThreadConversations(db, {
|
|
249
|
+
channelId: binding.channelId,
|
|
250
|
+
threadRootId: binding.threadRootId,
|
|
251
|
+
dmUserId: params.dmUserId,
|
|
252
|
+
});
|
|
253
|
+
for (const conversation of conversations) {
|
|
254
|
+
if (conversation.agentId !== agentId)
|
|
255
|
+
continue;
|
|
256
|
+
const runId = findOpenRunId(db, conversation.sessionKey);
|
|
257
|
+
if (!runId)
|
|
258
|
+
continue;
|
|
259
|
+
return {
|
|
260
|
+
conversationId: conversation.id,
|
|
261
|
+
runId,
|
|
262
|
+
agentId: conversation.agentId,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
export function listActiveTaskThreadPeerRuns(db, params) {
|
|
268
|
+
const binding = getThreadBindingForTask(db, params.taskId);
|
|
269
|
+
if (!binding)
|
|
270
|
+
return [];
|
|
271
|
+
const excludedAgentId = params.excludeAgentId?.trim() || null;
|
|
272
|
+
const conversations = listTaskThreadConversations(db, {
|
|
273
|
+
channelId: binding.channelId,
|
|
274
|
+
threadRootId: binding.threadRootId,
|
|
275
|
+
});
|
|
276
|
+
const runs = [];
|
|
277
|
+
for (const conversation of conversations) {
|
|
278
|
+
if (!conversation.agentId)
|
|
279
|
+
continue;
|
|
280
|
+
if (excludedAgentId && conversation.agentId === excludedAgentId)
|
|
281
|
+
continue;
|
|
282
|
+
const runId = findOpenRunId(db, conversation.sessionKey);
|
|
283
|
+
if (!runId)
|
|
284
|
+
continue;
|
|
285
|
+
runs.push({
|
|
286
|
+
conversationId: conversation.id,
|
|
287
|
+
runId,
|
|
288
|
+
agentId: conversation.agentId,
|
|
289
|
+
name: conversation.agentName ?? conversation.agentId,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
return runs;
|
|
293
|
+
}
|
|
294
|
+
export function countQueuedHumanPromptsForTaskThread(db, taskId) {
|
|
295
|
+
const binding = getThreadBindingForTask(db, taskId);
|
|
296
|
+
if (!binding)
|
|
297
|
+
return 0;
|
|
298
|
+
const conversations = listTaskThreadConversations(db, {
|
|
299
|
+
channelId: binding.channelId,
|
|
300
|
+
threadRootId: binding.threadRootId,
|
|
301
|
+
});
|
|
302
|
+
const conversationIds = conversations.map((conversation) => conversation.id);
|
|
303
|
+
if (conversationIds.length === 0)
|
|
304
|
+
return 0;
|
|
305
|
+
const placeholders = conversationIds.map(() => '?').join(', ');
|
|
306
|
+
const row = db.prepare(`SELECT COUNT(*) as count
|
|
307
|
+
FROM conversation_prompt_queue
|
|
308
|
+
WHERE record_as_user_message != 0
|
|
309
|
+
AND COALESCE(dispatch_kind, '') != 'legacy_unknown'
|
|
310
|
+
AND conversation_id IN (${placeholders})`).get(...conversationIds);
|
|
311
|
+
return Number(row?.count ?? 0);
|
|
312
|
+
}
|
|
313
|
+
export function countPendingHumanNotificationRoundsForTaskThread(db, taskId) {
|
|
314
|
+
const binding = getThreadBindingForTask(db, taskId);
|
|
315
|
+
if (!binding)
|
|
316
|
+
return 0;
|
|
317
|
+
const humanChatReasonMask = NOTIFICATION_REASON_MASK.participant_heads_up
|
|
318
|
+
| NOTIFICATION_REASON_MASK.explicit_agent_mention;
|
|
319
|
+
const row = db.prepare(`SELECT COUNT(*) as count
|
|
320
|
+
FROM agent_notification_rounds
|
|
321
|
+
WHERE channel_id = ?
|
|
322
|
+
AND surface_thread_key = ?
|
|
323
|
+
AND status IN ('ready', 'leased', 'delivered')
|
|
324
|
+
AND acked_at IS NULL
|
|
325
|
+
AND COALESCE(from_agent_id, '') LIKE 'user:%'
|
|
326
|
+
AND (reason_mask & ?) != 0`).get(binding.channelId, binding.threadRootId, humanChatReasonMask);
|
|
327
|
+
return Number(row?.count ?? 0);
|
|
328
|
+
}
|
|
329
|
+
export function closeTaskThreadRuntimeState(params) {
|
|
330
|
+
const now = Date.now();
|
|
331
|
+
const binding = getThreadBindingForTask(params.db, params.taskId);
|
|
332
|
+
clearTaskThreadClosingStateForTask(params.db, params.taskId);
|
|
333
|
+
clearTaskThreadReopenedRunBlocksForTask(params.db, params.taskId);
|
|
334
|
+
if (!binding) {
|
|
335
|
+
return {
|
|
336
|
+
closed: false,
|
|
337
|
+
conversationIds: [],
|
|
338
|
+
cancelledRunIds: [],
|
|
339
|
+
clearedQueuedPromptCount: 0,
|
|
340
|
+
clearedAggregateCount: 0,
|
|
341
|
+
clearedCooldownCount: 0,
|
|
342
|
+
clearedFollowupCount: 0,
|
|
343
|
+
idledConversationIds: [],
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
const conversations = listTaskThreadConversations(params.db, {
|
|
347
|
+
channelId: binding.channelId,
|
|
348
|
+
threadRootId: binding.threadRootId,
|
|
349
|
+
});
|
|
350
|
+
const cancelledRunIds = [];
|
|
351
|
+
const idledConversationIds = [];
|
|
352
|
+
let clearedQueuedPromptCount = 0;
|
|
353
|
+
for (const conversation of conversations) {
|
|
354
|
+
const cancelResult = params.conversationManager.cancelConversationRun(conversation.id);
|
|
355
|
+
if (cancelResult.ok && cancelResult.runId) {
|
|
356
|
+
cancelledRunIds.push(cancelResult.runId);
|
|
357
|
+
}
|
|
358
|
+
else if (cancelResult.message && cancelResult.message !== 'No active run to cancel.') {
|
|
359
|
+
log.warn('[task-thread-runtime-closure] failed to cancel active run', {
|
|
360
|
+
taskId: params.taskId,
|
|
361
|
+
conversationId: conversation.id,
|
|
362
|
+
reason: params.reason,
|
|
363
|
+
error: cancelResult.message,
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
clearedQueuedPromptCount += params.db.prepare(`DELETE FROM conversation_prompt_queue
|
|
367
|
+
WHERE conversation_id = ?`).run(conversation.id).changes;
|
|
368
|
+
if (restoreIdleConversationStatus(params.db, conversation)) {
|
|
369
|
+
idledConversationIds.push(conversation.id);
|
|
370
|
+
params.onConversationStatusChange?.(conversation.id, 'idle');
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
const clearedAggregateCount = 0;
|
|
374
|
+
const clearedCooldownCount = 0;
|
|
375
|
+
const clearedFollowupCount = deleteTaskThreadFollowupsForTask(params.db, params.taskId);
|
|
376
|
+
supersedeNotificationRoundsForThreadRoots(params.db, {
|
|
377
|
+
channelId: binding.channelId,
|
|
378
|
+
threadRootIds: binding.threadRootIds,
|
|
379
|
+
now,
|
|
380
|
+
reason: `Task thread closed: ${params.reason}`,
|
|
381
|
+
});
|
|
382
|
+
return {
|
|
383
|
+
closed: true,
|
|
384
|
+
conversationIds: conversations.map((conversation) => conversation.id),
|
|
385
|
+
cancelledRunIds,
|
|
386
|
+
clearedQueuedPromptCount,
|
|
387
|
+
clearedAggregateCount,
|
|
388
|
+
clearedCooldownCount,
|
|
389
|
+
clearedFollowupCount,
|
|
390
|
+
idledConversationIds,
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
export function sealTaskThreadRuntimeState(params) {
|
|
394
|
+
const now = Date.now();
|
|
395
|
+
const binding = getThreadBindingForTask(params.db, params.taskId);
|
|
396
|
+
if (!binding) {
|
|
397
|
+
return {
|
|
398
|
+
sealed: false,
|
|
399
|
+
conversationIds: [],
|
|
400
|
+
preservedActiveRuns: [],
|
|
401
|
+
clearedQueuedPromptCount: 0,
|
|
402
|
+
clearedAggregateCount: 0,
|
|
403
|
+
clearedCooldownCount: 0,
|
|
404
|
+
clearedFollowupCount: 0,
|
|
405
|
+
idledConversationIds: [],
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
const conversations = listTaskThreadConversations(params.db, {
|
|
409
|
+
channelId: binding.channelId,
|
|
410
|
+
threadRootId: binding.threadRootId,
|
|
411
|
+
dmUserId: params.dmUserId,
|
|
412
|
+
});
|
|
413
|
+
const preservedActiveRuns = [];
|
|
414
|
+
const idledConversationIds = [];
|
|
415
|
+
let clearedQueuedPromptCount = 0;
|
|
416
|
+
const ownerConversationId = params.ownerConversationId ?? null;
|
|
417
|
+
const ownerRunId = params.ownerRunId ?? null;
|
|
418
|
+
const ownerAgentId = params.ownerAgentId ?? null;
|
|
419
|
+
for (const conversation of conversations) {
|
|
420
|
+
clearTaskThreadClosingStateForConversation(conversation.id);
|
|
421
|
+
clearTaskThreadReopenedRunBlockForConversation(conversation.id);
|
|
422
|
+
const openRunId = findOpenRunId(params.db, conversation.sessionKey);
|
|
423
|
+
const isOwnerConversation = Boolean(params.reason === 'in_review'
|
|
424
|
+
&& ownerConversationId
|
|
425
|
+
&& ownerRunId
|
|
426
|
+
&& conversation.id === ownerConversationId
|
|
427
|
+
&& openRunId === ownerRunId
|
|
428
|
+
&& (!ownerAgentId || conversation.agentId === ownerAgentId));
|
|
429
|
+
setTaskThreadClosingState(conversation.id, {
|
|
430
|
+
taskId: params.taskId,
|
|
431
|
+
channelId: binding.channelId,
|
|
432
|
+
threadRootId: binding.threadRootId,
|
|
433
|
+
reason: params.reason,
|
|
434
|
+
ownerAgentId,
|
|
435
|
+
ownerRunId,
|
|
436
|
+
allowOneFinal: isOwnerConversation,
|
|
437
|
+
finalSent: false,
|
|
438
|
+
});
|
|
439
|
+
clearedQueuedPromptCount += params.db.prepare(`DELETE FROM conversation_prompt_queue
|
|
440
|
+
WHERE conversation_id = ?`).run(conversation.id).changes;
|
|
441
|
+
if (openRunId) {
|
|
442
|
+
preservedActiveRuns.push({
|
|
443
|
+
conversationId: conversation.id,
|
|
444
|
+
runId: openRunId,
|
|
445
|
+
agentId: conversation.agentId,
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
if (restoreIdleConversationStatus(params.db, conversation)) {
|
|
449
|
+
idledConversationIds.push(conversation.id);
|
|
450
|
+
params.onConversationStatusChange?.(conversation.id, 'idle');
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
const clearedAggregateCount = 0;
|
|
454
|
+
const clearedCooldownCount = 0;
|
|
455
|
+
const clearedFollowupCount = deleteTaskThreadFollowupsForTask(params.db, params.taskId);
|
|
456
|
+
supersedeNotificationRoundsForThreadRoots(params.db, {
|
|
457
|
+
channelId: binding.channelId,
|
|
458
|
+
threadRootIds: binding.threadRootIds,
|
|
459
|
+
now,
|
|
460
|
+
reason: `Task thread sealed: ${params.reason}`,
|
|
461
|
+
});
|
|
462
|
+
return {
|
|
463
|
+
sealed: true,
|
|
464
|
+
conversationIds: conversations.map((conversation) => conversation.id),
|
|
465
|
+
preservedActiveRuns,
|
|
466
|
+
clearedQueuedPromptCount,
|
|
467
|
+
clearedAggregateCount,
|
|
468
|
+
clearedCooldownCount,
|
|
469
|
+
clearedFollowupCount,
|
|
470
|
+
idledConversationIds,
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
export function buildTaskThreadReviewCleanupNotice(params) {
|
|
474
|
+
return [
|
|
475
|
+
'[System notice]',
|
|
476
|
+
`Task #${params.taskNumber} "${params.title}" has moved to in_review.`,
|
|
477
|
+
'This channel task thread is now closed for user-visible chat.',
|
|
478
|
+
'Finish any private cleanup now, including MEMORY.md or notes updates if needed, then stop.',
|
|
479
|
+
'Do not send visible chat, and do not post to this task thread.',
|
|
480
|
+
].join('\n');
|
|
481
|
+
}
|
|
482
|
+
export function softCloseTaskThreadForReview(params) {
|
|
483
|
+
const now = Date.now();
|
|
484
|
+
const binding = getThreadBindingForTask(params.db, params.taskId);
|
|
485
|
+
clearTaskThreadClosingStateForTask(params.db, params.taskId);
|
|
486
|
+
clearTaskThreadReopenedRunBlocksForTask(params.db, params.taskId);
|
|
487
|
+
if (!binding) {
|
|
488
|
+
return {
|
|
489
|
+
closed: false,
|
|
490
|
+
conversationIds: [],
|
|
491
|
+
preservedActiveRuns: [],
|
|
492
|
+
clearedQueuedPromptCount: 0,
|
|
493
|
+
clearedAggregateCount: 0,
|
|
494
|
+
clearedCooldownCount: 0,
|
|
495
|
+
clearedFollowupCount: 0,
|
|
496
|
+
idledConversationIds: [],
|
|
497
|
+
preservedConversationId: null,
|
|
498
|
+
cancelledRunIds: [],
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
const conversations = listTaskThreadConversations(params.db, {
|
|
502
|
+
channelId: binding.channelId,
|
|
503
|
+
threadRootId: binding.threadRootId,
|
|
504
|
+
});
|
|
505
|
+
const preservedActiveRuns = [];
|
|
506
|
+
const cancelledRunIds = [];
|
|
507
|
+
const idledConversationIds = [];
|
|
508
|
+
let clearedQueuedPromptCount = 0;
|
|
509
|
+
const ownerConversationId = params.ownerConversationId ?? null;
|
|
510
|
+
const ownerRunId = params.ownerRunId ?? null;
|
|
511
|
+
const ownerAgentId = params.ownerAgentId ?? null;
|
|
512
|
+
for (const conversation of conversations) {
|
|
513
|
+
const openRunId = findOpenRunId(params.db, conversation.sessionKey);
|
|
514
|
+
const isOwnerConversation = Boolean(ownerConversationId
|
|
515
|
+
&& ownerRunId
|
|
516
|
+
&& conversation.id === ownerConversationId
|
|
517
|
+
&& openRunId === ownerRunId
|
|
518
|
+
&& (!ownerAgentId || conversation.agentId === ownerAgentId));
|
|
519
|
+
setTaskThreadClosingState(conversation.id, {
|
|
520
|
+
taskId: params.taskId,
|
|
521
|
+
channelId: binding.channelId,
|
|
522
|
+
threadRootId: binding.threadRootId,
|
|
523
|
+
reason: 'in_review',
|
|
524
|
+
ownerAgentId,
|
|
525
|
+
ownerRunId,
|
|
526
|
+
allowOneFinal: isOwnerConversation,
|
|
527
|
+
finalSent: false,
|
|
528
|
+
});
|
|
529
|
+
clearedQueuedPromptCount += params.db.prepare(`DELETE FROM conversation_prompt_queue
|
|
530
|
+
WHERE conversation_id = ?`).run(conversation.id).changes;
|
|
531
|
+
if (openRunId && !isOwnerConversation) {
|
|
532
|
+
if (hasEffectiveAgentRuntimeCapability(params.db, {
|
|
533
|
+
nodeId: conversation.nodeId,
|
|
534
|
+
agentType: conversation.agentType,
|
|
535
|
+
capability: 'activeTurnSteer',
|
|
536
|
+
})) {
|
|
537
|
+
preservedActiveRuns.push({
|
|
538
|
+
conversationId: conversation.id,
|
|
539
|
+
runId: openRunId,
|
|
540
|
+
agentId: conversation.agentId,
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
else if (params.conversationManager) {
|
|
544
|
+
const cancelResult = params.conversationManager.cancelConversationRun(conversation.id, openRunId);
|
|
545
|
+
if (cancelResult.ok && cancelResult.runId) {
|
|
546
|
+
cancelledRunIds.push(cancelResult.runId);
|
|
547
|
+
const activeResult = params.db.prepare(`UPDATE conversations
|
|
548
|
+
SET status = 'active', updated_at = ?
|
|
549
|
+
WHERE id = ?
|
|
550
|
+
AND status = 'queued'`).run(Date.now(), conversation.id);
|
|
551
|
+
if (activeResult.changes > 0) {
|
|
552
|
+
params.onConversationStatusChange?.(conversation.id, 'active');
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
else if (cancelResult.message && cancelResult.message !== 'No active run to cancel.') {
|
|
556
|
+
log.warn('[task-thread-runtime-closure] failed to cancel non-steerable active run during review soft-close', {
|
|
557
|
+
taskId: params.taskId,
|
|
558
|
+
conversationId: conversation.id,
|
|
559
|
+
error: cancelResult.message,
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
else {
|
|
564
|
+
log.warn('[task-thread-runtime-closure] cannot preserve non-steerable active run during review soft-close without a conversation manager', {
|
|
565
|
+
taskId: params.taskId,
|
|
566
|
+
conversationId: conversation.id,
|
|
567
|
+
runId: openRunId,
|
|
568
|
+
agentType: conversation.agentType,
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
if (restoreIdleConversationStatus(params.db, conversation)) {
|
|
573
|
+
idledConversationIds.push(conversation.id);
|
|
574
|
+
params.onConversationStatusChange?.(conversation.id, 'idle');
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
const clearedAggregateCount = 0;
|
|
578
|
+
const clearedCooldownCount = 0;
|
|
579
|
+
const clearedFollowupCount = deleteTaskThreadFollowupsForTask(params.db, params.taskId);
|
|
580
|
+
supersedeNotificationRoundsForThreadRoots(params.db, {
|
|
581
|
+
channelId: binding.channelId,
|
|
582
|
+
threadRootIds: binding.threadRootIds,
|
|
583
|
+
now,
|
|
584
|
+
reason: 'Task thread soft-closed for review',
|
|
585
|
+
});
|
|
586
|
+
return {
|
|
587
|
+
closed: true,
|
|
588
|
+
conversationIds: conversations.map((conversation) => conversation.id),
|
|
589
|
+
preservedActiveRuns,
|
|
590
|
+
clearedQueuedPromptCount,
|
|
591
|
+
clearedAggregateCount,
|
|
592
|
+
clearedCooldownCount,
|
|
593
|
+
clearedFollowupCount,
|
|
594
|
+
idledConversationIds,
|
|
595
|
+
preservedConversationId: ownerConversationId,
|
|
596
|
+
cancelledRunIds,
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
export function beginTaskThreadClosing(params) {
|
|
600
|
+
const binding = getThreadBindingForTask(params.db, params.taskId);
|
|
601
|
+
clearTaskThreadClosingStateForTask(params.db, params.taskId);
|
|
602
|
+
clearTaskThreadReopenedRunBlocksForTask(params.db, params.taskId);
|
|
603
|
+
if (!binding) {
|
|
604
|
+
return {
|
|
605
|
+
closed: false,
|
|
606
|
+
conversationIds: [],
|
|
607
|
+
cancelledRunIds: [],
|
|
608
|
+
clearedQueuedPromptCount: 0,
|
|
609
|
+
clearedAggregateCount: 0,
|
|
610
|
+
clearedCooldownCount: 0,
|
|
611
|
+
clearedFollowupCount: 0,
|
|
612
|
+
idledConversationIds: [],
|
|
613
|
+
preservedConversationId: null,
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
const conversations = listTaskThreadConversations(params.db, {
|
|
617
|
+
channelId: binding.channelId,
|
|
618
|
+
threadRootId: binding.threadRootId,
|
|
619
|
+
});
|
|
620
|
+
const ownerConversation = conversations.find((conversation) => conversation.id === params.ownerConversationId);
|
|
621
|
+
if (!ownerConversation) {
|
|
622
|
+
return {
|
|
623
|
+
closed: false,
|
|
624
|
+
conversationIds: conversations.map((conversation) => conversation.id),
|
|
625
|
+
cancelledRunIds: [],
|
|
626
|
+
clearedQueuedPromptCount: 0,
|
|
627
|
+
clearedAggregateCount: 0,
|
|
628
|
+
clearedCooldownCount: 0,
|
|
629
|
+
clearedFollowupCount: 0,
|
|
630
|
+
idledConversationIds: [],
|
|
631
|
+
preservedConversationId: null,
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
const cancelledRunIds = [];
|
|
635
|
+
const idledConversationIds = [];
|
|
636
|
+
let clearedQueuedPromptCount = 0;
|
|
637
|
+
for (const conversation of conversations) {
|
|
638
|
+
const isOwnerConversation = conversation.id === params.ownerConversationId;
|
|
639
|
+
setTaskThreadClosingState(conversation.id, {
|
|
640
|
+
taskId: params.taskId,
|
|
641
|
+
channelId: binding.channelId,
|
|
642
|
+
threadRootId: binding.threadRootId,
|
|
643
|
+
reason: params.reason,
|
|
644
|
+
ownerAgentId: params.ownerAgentId ?? null,
|
|
645
|
+
ownerRunId: params.ownerRunId,
|
|
646
|
+
allowOneFinal: isOwnerConversation,
|
|
647
|
+
finalSent: false,
|
|
648
|
+
});
|
|
649
|
+
clearedQueuedPromptCount += params.db.prepare(`DELETE FROM conversation_prompt_queue
|
|
650
|
+
WHERE conversation_id = ?`).run(conversation.id).changes;
|
|
651
|
+
if (isOwnerConversation) {
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
654
|
+
const cancelResult = params.conversationManager.cancelConversationRun(conversation.id);
|
|
655
|
+
if (cancelResult.ok && cancelResult.runId) {
|
|
656
|
+
cancelledRunIds.push(cancelResult.runId);
|
|
657
|
+
}
|
|
658
|
+
else if (cancelResult.message && cancelResult.message !== 'No active run to cancel.') {
|
|
659
|
+
log.warn('[task-thread-runtime-closure] failed to cancel active run during graceful close', {
|
|
660
|
+
taskId: params.taskId,
|
|
661
|
+
conversationId: conversation.id,
|
|
662
|
+
reason: params.reason,
|
|
663
|
+
error: cancelResult.message,
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
if (restoreIdleConversationStatus(params.db, conversation)) {
|
|
667
|
+
idledConversationIds.push(conversation.id);
|
|
668
|
+
params.onConversationStatusChange?.(conversation.id, 'idle');
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
const clearedAggregateCount = 0;
|
|
672
|
+
const clearedCooldownCount = 0;
|
|
673
|
+
const clearedFollowupCount = deleteTaskThreadFollowupsForTask(params.db, params.taskId);
|
|
674
|
+
return {
|
|
675
|
+
closed: true,
|
|
676
|
+
conversationIds: conversations.map((conversation) => conversation.id),
|
|
677
|
+
cancelledRunIds,
|
|
678
|
+
clearedQueuedPromptCount,
|
|
679
|
+
clearedAggregateCount,
|
|
680
|
+
clearedCooldownCount,
|
|
681
|
+
clearedFollowupCount,
|
|
682
|
+
idledConversationIds,
|
|
683
|
+
preservedConversationId: params.ownerConversationId,
|
|
684
|
+
};
|
|
685
|
+
}
|