@bbigbang/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/dist/config.js +380 -0
  2. package/dist/execution/executionDispatcher.js +3810 -0
  3. package/dist/main.js +90 -0
  4. package/dist/nodeEventHistory.js +206 -0
  5. package/dist/scheduler/dreamLogic.js +50 -0
  6. package/dist/scheduler/dreamScheduler.js +65 -0
  7. package/dist/services/agentFileAccessService.js +1913 -0
  8. package/dist/services/agentRuntimeCleanupBroker.js +62 -0
  9. package/dist/services/agentSkillsBroker.js +118 -0
  10. package/dist/services/agentSkillsService.js +83 -0
  11. package/dist/services/agentWorkspaceBroker.js +937 -0
  12. package/dist/services/agentWorkspaceService.js +70 -0
  13. package/dist/services/appVersion.js +14 -0
  14. package/dist/services/auth.js +586 -0
  15. package/dist/services/claudeControlBroker.js +154 -0
  16. package/dist/services/claudeTranscriptBroker.js +100 -0
  17. package/dist/services/claudeTranscriptService.js +359 -0
  18. package/dist/services/codexAppServerBroker.js +155 -0
  19. package/dist/services/codexTranscriptBroker.js +98 -0
  20. package/dist/services/codexTranscriptService.js +961 -0
  21. package/dist/services/droidMissionBroker.js +124 -0
  22. package/dist/services/droidMissionImporter.js +630 -0
  23. package/dist/services/droidModelOptions.js +165 -0
  24. package/dist/services/hubServerRegistrationService.js +268 -0
  25. package/dist/services/libraryManifest.js +43 -0
  26. package/dist/services/libraryScaffold.js +26 -0
  27. package/dist/services/libraryService.js +2263 -0
  28. package/dist/services/memoryService.js +386 -0
  29. package/dist/services/missionEvidence.js +377 -0
  30. package/dist/services/missionService.js +2361 -0
  31. package/dist/services/missionTrace.js +158 -0
  32. package/dist/services/nativeMissionBriefParser.js +120 -0
  33. package/dist/services/nativeMissionOrchestrator.js +2045 -0
  34. package/dist/services/nativeMissionReportGenerator.js +227 -0
  35. package/dist/services/nativeMissionValidationRunner.js +452 -0
  36. package/dist/services/nativeMissionWorkerBroker.js +190 -0
  37. package/dist/services/nodeRegistry.js +34 -0
  38. package/dist/services/nodeStateReconciler.js +97 -0
  39. package/dist/services/panelMediaScanner.js +119 -0
  40. package/dist/services/persistentRuntimeJsonlClient.js +153 -0
  41. package/dist/services/platformAgentPolicy.js +180 -0
  42. package/dist/services/platformAgentService.js +2041 -0
  43. package/dist/services/projectAccessResolver.js +93 -0
  44. package/dist/services/projectService.js +392 -0
  45. package/dist/services/resourceSpaceService.js +140 -0
  46. package/dist/services/scenarioRuntimeService.js +1130 -0
  47. package/dist/services/suggestedPlannerService.js +868 -0
  48. package/dist/services/workbenchGitBroker.js +161 -0
  49. package/dist/services/workbenchGitService.js +69 -0
  50. package/dist/services/workbenchInspectBroker.js +65 -0
  51. package/dist/services/workbenchNodePathService.js +79 -0
  52. package/dist/services/workbenchRegistryService.js +240 -0
  53. package/dist/services/workbenchRootService.js +181 -0
  54. package/dist/services/workbenchTerminalBroker.js +378 -0
  55. package/dist/services/workspaceRunOwnership.js +60 -0
  56. package/dist/services/workspaceScaffold.js +105 -0
  57. package/dist/services/workspaceSessionRuntimeService.js +576 -0
  58. package/dist/services/workspaceSessionService.js +245 -0
  59. package/dist/services/workspaceToolActionRunner.js +1582 -0
  60. package/dist/services/workspaceToolErrors.js +10 -0
  61. package/dist/services/workspaceToolExecutionUtils.js +895 -0
  62. package/dist/services/workspaceToolLatestStateProjector.js +91 -0
  63. package/dist/services/workspaceToolManifest.js +572 -0
  64. package/dist/services/workspaceToolMutationQueue.js +43 -0
  65. package/dist/services/workspaceToolPanelProjection.js +460 -0
  66. package/dist/services/workspaceToolPromotion.js +255 -0
  67. package/dist/services/workspaceToolPromotionState.js +224 -0
  68. package/dist/services/workspaceToolPublishDiagnostics.js +189 -0
  69. package/dist/services/workspaceToolPublishIdentityResolver.js +146 -0
  70. package/dist/services/workspaceToolReadModel.js +378 -0
  71. package/dist/services/workspaceToolRunLedger.js +239 -0
  72. package/dist/services/workspaceToolService.js +3067 -0
  73. package/dist/services/workspaceToolSnapshotPanelSync.js +293 -0
  74. package/dist/services/workspaceToolTerminalLifecycle.js +283 -0
  75. package/dist/services/workspaceToolTypes.js +1 -0
  76. package/dist/services/workspaceToolUploadMaterializer.js +228 -0
  77. package/dist/web/actionCardRoutes.js +129 -0
  78. package/dist/web/actionCards.js +469 -0
  79. package/dist/web/activationContext.js +684 -0
  80. package/dist/web/agentChannelGuards.js +48 -0
  81. package/dist/web/agentMentionCooldowns.js +32 -0
  82. package/dist/web/agentReminders.js +1668 -0
  83. package/dist/web/agentRuntimePresence.js +197 -0
  84. package/dist/web/agentSelfState.js +494 -0
  85. package/dist/web/agentTaskLinks.js +26 -0
  86. package/dist/web/agentVisibility.js +79 -0
  87. package/dist/web/assets.js +95 -0
  88. package/dist/web/channelActivationPrompt.js +395 -0
  89. package/dist/web/channelMemoryNotes.js +127 -0
  90. package/dist/web/channelMentions.js +10 -0
  91. package/dist/web/channelMessageSequences.js +19 -0
  92. package/dist/web/channelSubscriptions.js +26 -0
  93. package/dist/web/clearedTaskRoots.js +10 -0
  94. package/dist/web/collaborationPromptGuidance.js +36 -0
  95. package/dist/web/collaborationSurfaceState.js +140 -0
  96. package/dist/web/contextBundleRanking.js +154 -0
  97. package/dist/web/contextBundleResolver.js +488 -0
  98. package/dist/web/conversationBuiltinSkillRoots.js +50 -0
  99. package/dist/web/conversationControls.js +232 -0
  100. package/dist/web/conversationHandoffs.js +612 -0
  101. package/dist/web/conversationManager.js +2511 -0
  102. package/dist/web/conversationSummaries.js +876 -0
  103. package/dist/web/conversationSurfaceKinds.js +17 -0
  104. package/dist/web/conversationTargets.js +173 -0
  105. package/dist/web/directActivationPrompt.js +122 -0
  106. package/dist/web/directReplyTargets.js +69 -0
  107. package/dist/web/directThreadResolver.js +129 -0
  108. package/dist/web/dmTaskHandoffPrompt.js +120 -0
  109. package/dist/web/dmTaskThreadStatusProjection.js +229 -0
  110. package/dist/web/ftsQuery.js +33 -0
  111. package/dist/web/internalAgentRouter.js +11341 -0
  112. package/dist/web/libraryCuratorScheduler.js +58 -0
  113. package/dist/web/libraryDocumentPromptGuidance.js +8 -0
  114. package/dist/web/messageCheckpoints.js +19 -0
  115. package/dist/web/nodeWsHandler.js +2495 -0
  116. package/dist/web/notificationRounds.js +1061 -0
  117. package/dist/web/panelActionMessages.js +108 -0
  118. package/dist/web/panelActivationPrompt.js +18 -0
  119. package/dist/web/panelAudit.js +273 -0
  120. package/dist/web/panelLifecycle.js +222 -0
  121. package/dist/web/panelMediaPolicy.js +43 -0
  122. package/dist/web/panelPathPolicy.js +63 -0
  123. package/dist/web/panelPreviews.js +175 -0
  124. package/dist/web/panelQueryHandles.js +2749 -0
  125. package/dist/web/panelRoutes.js +2147 -0
  126. package/dist/web/panels.js +904 -0
  127. package/dist/web/peerInboxAggregates.js +1247 -0
  128. package/dist/web/planApprovalState.js +92 -0
  129. package/dist/web/platformAgentScheduler.js +66 -0
  130. package/dist/web/proactiveOpportunities.js +452 -0
  131. package/dist/web/promptContextSections.js +242 -0
  132. package/dist/web/promptHistorySanitizer.js +26 -0
  133. package/dist/web/promptSlashCommands.js +158 -0
  134. package/dist/web/rollingConversationSummary.js +453 -0
  135. package/dist/web/routeHelpers.js +11 -0
  136. package/dist/web/routes/handoff.js +288 -0
  137. package/dist/web/routes/history.js +345 -0
  138. package/dist/web/routes/memory.js +258 -0
  139. package/dist/web/routes/selfState.js +171 -0
  140. package/dist/web/routes/workspace.js +154 -0
  141. package/dist/web/runSurfaceWatermarks.js +431 -0
  142. package/dist/web/runtimeCapabilities.js +48 -0
  143. package/dist/web/sameAgentHandoffs.js +494 -0
  144. package/dist/web/server.js +15567 -0
  145. package/dist/web/sharedCollaborationCapsules.js +163 -0
  146. package/dist/web/soloSessionRelay.js +42 -0
  147. package/dist/web/soloWsHandler.js +138 -0
  148. package/dist/web/suggestedPlannerScheduler.js +56 -0
  149. package/dist/web/surfaceActivationPolicy.js +108 -0
  150. package/dist/web/surfaceCollaborators.js +61 -0
  151. package/dist/web/surfaceSystemStatus.js +263 -0
  152. package/dist/web/targetParticipants.js +77 -0
  153. package/dist/web/taskEvents.js +49 -0
  154. package/dist/web/taskLifecycleMessages.js +165 -0
  155. package/dist/web/taskLoops.js +732 -0
  156. package/dist/web/taskMemoryNotes.js +224 -0
  157. package/dist/web/taskNumbers.js +16 -0
  158. package/dist/web/taskOwnerGuards.js +49 -0
  159. package/dist/web/taskParticipantResolver.js +42 -0
  160. package/dist/web/taskParticipants.js +97 -0
  161. package/dist/web/taskSourceDetails.js +20 -0
  162. package/dist/web/taskStateViews.js +210 -0
  163. package/dist/web/taskStatusTransitions.js +9 -0
  164. package/dist/web/taskThreadFollowups.js +599 -0
  165. package/dist/web/taskThreadRuntimeClosure.js +685 -0
  166. package/dist/web/taskUpdateDelivery.js +104 -0
  167. package/dist/web/threadReplyContentHeuristics.js +30 -0
  168. package/dist/web/threadRoots.js +61 -0
  169. package/dist/web/threadTaskBindings.js +365 -0
  170. package/dist/web/uiPanelPromptGuidance.js +27 -0
  171. package/dist/web/workspaceMemoryHints.js +143 -0
  172. package/dist/web/workspaceToolPromptGuidance.js +30 -0
  173. package/dist/web/wsHandler.js +397 -0
  174. package/dist/web/wsSink.js +116 -0
  175. package/package.json +54 -0
@@ -0,0 +1,494 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { log } from '@bbigbang/runtime-acp';
3
+ import { buildChannelSurfaceActivationEnvelope, buildDirectSurfaceActivationEnvelope } from './activationContext.js';
4
+ import { getUnifiedSurfaceUnreadCheckpoint } from './collaborationSurfaceState.js';
5
+ import { resolveDefaultReplyTarget } from './conversationTargets.js';
6
+ import { advanceAgentSurfaceNotificationAck } from './notificationRounds.js';
7
+ import { buildContinueThereSourceNoticePrompt, buildConversationHandoffCapsuleText, buildConversationHandoffPrompt, createConversationHandoff, markConversationHandoffSourceCancelled, } from './conversationHandoffs.js';
8
+ import { createPromptContextSection, } from './promptContextSections.js';
9
+ import { combinePromptSections } from './workspaceMemoryHints.js';
10
+ function resolveHandoffTargetSurfaceFromConversation(params) {
11
+ const conversation = params.conversationManager.getConversation(params.targetConversationId);
12
+ if (!conversation?.agentId)
13
+ return null;
14
+ const replyTarget = (params.fallbackReplyTarget ?? conversation.replyTarget ?? '').trim();
15
+ const channelId = conversation.threadKind === 'direct'
16
+ ? `dm:${conversation.agentId}`
17
+ : conversation.channelId;
18
+ if (!replyTarget || !channelId)
19
+ return null;
20
+ return {
21
+ agentId: conversation.agentId,
22
+ channelId,
23
+ replyTarget,
24
+ threadRootId: conversation.threadRootId ?? null,
25
+ };
26
+ }
27
+ function loadHandoffTargetSurfaceCheckpointSeedSeq(db, surface) {
28
+ if (getUnifiedSurfaceUnreadCheckpoint(db, {
29
+ agentId: surface.agentId,
30
+ channelId: surface.channelId,
31
+ threadRootId: surface.threadRootId,
32
+ }) > 0) {
33
+ return null;
34
+ }
35
+ const isDirectThreadTarget = surface.threadRootId !== null && surface.replyTarget.startsWith('dm:@');
36
+ const threadClause = surface.threadRootId == null
37
+ ? 'thread_root_id IS NULL'
38
+ : isDirectThreadTarget
39
+ ? '(thread_root_id = ? OR thread_root_id IS NULL)'
40
+ : 'thread_root_id = ?';
41
+ const threadArgs = surface.threadRootId == null ? [] : [surface.threadRootId];
42
+ const row = db.prepare(`SELECT COALESCE(MAX(seq), 0) as maxSeq
43
+ FROM channel_messages
44
+ WHERE channel_id = ?
45
+ AND ${threadClause}`).get(surface.channelId, ...threadArgs);
46
+ const maxSeq = Math.max(0, Math.floor(Number(row?.maxSeq ?? 0)));
47
+ return maxSeq > 0 ? maxSeq : null;
48
+ }
49
+ function persistHandoffTargetSurfaceCheckpointSeedIfUnset(db, surface, seedSeq) {
50
+ if (!surface || seedSeq == null)
51
+ return;
52
+ const normalizedSeedSeq = Math.max(0, Math.floor(seedSeq));
53
+ if (normalizedSeedSeq <= 0)
54
+ return;
55
+ if (getUnifiedSurfaceUnreadCheckpoint(db, {
56
+ agentId: surface.agentId,
57
+ channelId: surface.channelId,
58
+ threadRootId: surface.threadRootId,
59
+ }) > 0) {
60
+ return;
61
+ }
62
+ advanceAgentSurfaceNotificationAck(db, {
63
+ targetAgentId: surface.agentId,
64
+ channelId: surface.channelId,
65
+ threadRootId: surface.threadRootId,
66
+ ackedThroughSeq: normalizedSeedSeq,
67
+ });
68
+ }
69
+ export async function performSameAgentHandoff(params) {
70
+ const sourceDisposition = params.sourceDisposition ?? (params.mode === 'continue_there' ? 'cancel_current_run' : 'keep_running');
71
+ const failureSourceDisposition = params.failureSourceDisposition ?? 'keep_running';
72
+ const workflowKind = params.workflowKind ?? 'generic';
73
+ const targetSurface = resolveHandoffTargetSurfaceFromConversation({
74
+ conversationManager: params.conversationManager,
75
+ targetConversationId: params.targetConversationId,
76
+ fallbackReplyTarget: params.targetReplyTarget,
77
+ });
78
+ const safeInvokeLifecycleCallback = (callbackName, callback) => {
79
+ if (!callback)
80
+ return;
81
+ try {
82
+ callback();
83
+ }
84
+ catch (error) {
85
+ log.warn('[same-agent-handoff] lifecycle callback failed', {
86
+ callbackName,
87
+ sourceConversationId: params.sourceConversationId,
88
+ targetConversationId: params.targetConversationId,
89
+ targetReplyTarget: params.targetReplyTarget,
90
+ workflowKind,
91
+ mode: params.mode,
92
+ error: String(error?.message ?? error),
93
+ });
94
+ }
95
+ };
96
+ const cancelSourceRun = async (disposition, options) => {
97
+ if (disposition !== 'cancel_current_run' || !params.sourceConversationRunId) {
98
+ return { cancelRequested: false };
99
+ }
100
+ if (options?.sendContinueThereNotice && params.mode === 'continue_there') {
101
+ await params.conversationManager.sendBestEffortSystemNoticeToActiveRun({
102
+ conversationId: params.sourceConversationId,
103
+ runId: params.sourceConversationRunId,
104
+ promptText: buildContinueThereSourceNoticePrompt({
105
+ sourceReplyTarget: params.sourceReplyTarget,
106
+ targetReplyTarget: params.targetReplyTarget,
107
+ relatedTaskRef: params.payload.relatedTaskRef,
108
+ }),
109
+ });
110
+ }
111
+ const cancelResult = params.conversationManager.cancelConversationRun(params.sourceConversationId, params.sourceConversationRunId, { strictExpectedRunId: true });
112
+ if (!cancelResult.ok || !cancelResult.runId) {
113
+ return { cancelRequested: false };
114
+ }
115
+ params.db.prepare(`DELETE FROM conversation_prompt_queue
116
+ WHERE conversation_id = ?
117
+ AND record_as_user_message = 0`).run(params.sourceConversationId);
118
+ return {
119
+ cancelRequested: true,
120
+ cancelRunId: cancelResult.runId,
121
+ };
122
+ };
123
+ const persistSourceCancellation = (record, cancelOutcome) => (cancelOutcome.cancelRunId
124
+ ? markConversationHandoffSourceCancelled(params.db, {
125
+ handoffId: record.handoffId,
126
+ cancelRunId: cancelOutcome.cancelRunId,
127
+ }) ?? record
128
+ : record);
129
+ const handoffCapsuleText = buildConversationHandoffCapsuleText({
130
+ mode: params.mode,
131
+ targetReplyTarget: params.targetReplyTarget,
132
+ payload: params.payload,
133
+ });
134
+ if (!params.dispatch) {
135
+ const failed = createConversationHandoff(params.db, {
136
+ agentId: params.agentId,
137
+ sourceConversationId: params.sourceConversationId,
138
+ targetConversationId: params.targetConversationId,
139
+ targetReplyTarget: params.targetReplyTarget,
140
+ workflowKind,
141
+ sourceDisposition,
142
+ taskId: params.taskId ?? null,
143
+ taskNumber: params.taskNumber ?? null,
144
+ mode: params.mode,
145
+ status: 'failed',
146
+ payload: {
147
+ ...params.payload,
148
+ error: params.dispatchBuildErrorText ?? `Failed to build handoff dispatch for ${params.targetReplyTarget}`,
149
+ },
150
+ });
151
+ const cancelOutcome = await cancelSourceRun(failureSourceDisposition);
152
+ const finalRecord = persistSourceCancellation(failed, cancelOutcome);
153
+ safeInvokeLifecycleCallback('onFailed', () => {
154
+ params.onFailed?.({
155
+ record: finalRecord,
156
+ cancelRequested: cancelOutcome.cancelRequested,
157
+ ...(cancelOutcome.cancelRunId ? { cancelRunId: cancelOutcome.cancelRunId } : {}),
158
+ sourceConversationRunId: params.sourceConversationRunId,
159
+ });
160
+ });
161
+ return {
162
+ status: 'failed',
163
+ handoffId: failed.handoffId,
164
+ queued: false,
165
+ targetConversationId: params.targetConversationId,
166
+ targetReplyTarget: params.targetReplyTarget,
167
+ cancelRequested: cancelOutcome.cancelRequested,
168
+ ...(cancelOutcome.cancelRunId ? { cancelRunId: cancelOutcome.cancelRunId } : {}),
169
+ error: failed.payload.error ?? params.dispatchBuildErrorText ?? 'Failed to build handoff dispatch',
170
+ };
171
+ }
172
+ const beforeDispatchState = params.getLatestConversationDispatchState(params.targetConversationId);
173
+ const finalizeStarted = async (result) => {
174
+ const record = createConversationHandoff(params.db, {
175
+ agentId: params.agentId,
176
+ sourceConversationId: params.sourceConversationId,
177
+ targetConversationId: params.targetConversationId,
178
+ targetReplyTarget: params.targetReplyTarget,
179
+ workflowKind,
180
+ sourceDisposition,
181
+ taskId: params.taskId ?? null,
182
+ taskNumber: params.taskNumber ?? null,
183
+ mode: params.mode,
184
+ status: 'started',
185
+ payload: {
186
+ ...params.payload,
187
+ ...(result?.runId ? { targetRunId: result.runId } : {}),
188
+ },
189
+ });
190
+ persistHandoffTargetSurfaceCheckpointSeedIfUnset(params.db, targetSurface, params.dispatch?.targetSurfaceCheckpointSeedSeq);
191
+ const cancelOutcome = await cancelSourceRun(sourceDisposition, {
192
+ sendContinueThereNotice: true,
193
+ });
194
+ const finalRecord = persistSourceCancellation(record, cancelOutcome);
195
+ if (result?.queued) {
196
+ safeInvokeLifecycleCallback('onQueued', () => {
197
+ params.onQueued?.(params.targetConversationId);
198
+ });
199
+ }
200
+ safeInvokeLifecycleCallback('onStarted', () => {
201
+ params.onStarted?.({
202
+ record: finalRecord,
203
+ cancelRequested: cancelOutcome.cancelRequested,
204
+ ...(cancelOutcome.cancelRunId ? { cancelRunId: cancelOutcome.cancelRunId } : {}),
205
+ sourceConversationRunId: params.sourceConversationRunId,
206
+ });
207
+ });
208
+ return {
209
+ status: 'started',
210
+ handoffId: record.handoffId,
211
+ queued: result?.queued ?? false,
212
+ targetConversationId: params.targetConversationId,
213
+ targetReplyTarget: params.targetReplyTarget,
214
+ ...(result?.runId ? { targetRunId: result.runId } : {}),
215
+ cancelRequested: cancelOutcome.cancelRequested,
216
+ ...(cancelOutcome.cancelRunId ? { cancelRunId: cancelOutcome.cancelRunId } : {}),
217
+ };
218
+ };
219
+ const handoffCorrelationId = randomUUID();
220
+ const handoffCapsuleSection = createPromptContextSection('handoff_capsule', handoffCapsuleText);
221
+ const dispatchOptionsWithCapsule = {
222
+ ...params.dispatch.options,
223
+ activationContextText: combinePromptSections([handoffCapsuleText, params.dispatch.options.activationContextText]),
224
+ activationContextSections: handoffCapsuleSection
225
+ ? [handoffCapsuleSection, ...(params.dispatch.options.activationContextSections ?? [])]
226
+ : params.dispatch.options.activationContextSections,
227
+ resumeContextText: combinePromptSections([handoffCapsuleText, params.dispatch.options.resumeContextText]),
228
+ resumeContextSections: handoffCapsuleSection
229
+ ? [handoffCapsuleSection, ...(params.dispatch.options.resumeContextSections ?? [])]
230
+ : params.dispatch.options.resumeContextSections,
231
+ activationMetadata: withHandoffDispatchMetadata(params.dispatch.options.activationMetadata, {
232
+ correlationId: handoffCorrelationId,
233
+ sourceConversationId: params.sourceConversationId,
234
+ targetConversationId: params.targetConversationId,
235
+ }),
236
+ };
237
+ try {
238
+ const result = await params.conversationManager.submitPrompt(params.targetConversationId, params.dispatch.promptText, dispatchOptionsWithCapsule);
239
+ return await finalizeStarted(result);
240
+ }
241
+ catch (error) {
242
+ const afterDispatchState = params.getLatestConversationDispatchState(params.targetConversationId);
243
+ const matchedQueuedDispatch = afterDispatchState.queueId !== null
244
+ && afterDispatchState.queueId !== beforeDispatchState.queueId
245
+ && queuedPromptMatchesHandoffCorrelation(params.db, afterDispatchState.queueId, handoffCorrelationId);
246
+ const matchedActiveRun = afterDispatchState.runId !== null
247
+ && afterDispatchState.runId !== beforeDispatchState.runId
248
+ && activeRunMatchesHandoffCorrelation(params.db, afterDispatchState.runId, handoffCorrelationId);
249
+ if (matchedQueuedDispatch || matchedActiveRun) {
250
+ return await finalizeStarted({
251
+ queued: matchedQueuedDispatch,
252
+ ...(matchedActiveRun && afterDispatchState.runId ? { runId: afterDispatchState.runId } : {}),
253
+ });
254
+ }
255
+ const errorText = String(error?.message ?? error);
256
+ const failed = createConversationHandoff(params.db, {
257
+ agentId: params.agentId,
258
+ sourceConversationId: params.sourceConversationId,
259
+ targetConversationId: params.targetConversationId,
260
+ targetReplyTarget: params.targetReplyTarget,
261
+ workflowKind,
262
+ sourceDisposition,
263
+ taskId: params.taskId ?? null,
264
+ taskNumber: params.taskNumber ?? null,
265
+ mode: params.mode,
266
+ status: 'failed',
267
+ payload: {
268
+ ...params.payload,
269
+ error: errorText,
270
+ },
271
+ });
272
+ const cancelOutcome = await cancelSourceRun(failureSourceDisposition);
273
+ const finalRecord = persistSourceCancellation(failed, cancelOutcome);
274
+ safeInvokeLifecycleCallback('onFailed', () => {
275
+ params.onFailed?.({
276
+ record: finalRecord,
277
+ cancelRequested: cancelOutcome.cancelRequested,
278
+ ...(cancelOutcome.cancelRunId ? { cancelRunId: cancelOutcome.cancelRunId } : {}),
279
+ sourceConversationRunId: params.sourceConversationRunId,
280
+ });
281
+ });
282
+ return {
283
+ status: 'failed',
284
+ handoffId: failed.handoffId,
285
+ queued: false,
286
+ targetConversationId: params.targetConversationId,
287
+ targetReplyTarget: params.targetReplyTarget,
288
+ cancelRequested: cancelOutcome.cancelRequested,
289
+ ...(cancelOutcome.cancelRunId ? { cancelRunId: cancelOutcome.cancelRunId } : {}),
290
+ error: errorText,
291
+ };
292
+ }
293
+ }
294
+ export function getLatestConversationDispatchState(db, conversationId) {
295
+ const queuedPrompt = db.prepare(`SELECT queue_id as queueId
296
+ FROM conversation_prompt_queue
297
+ WHERE conversation_id = ?
298
+ ORDER BY queue_id DESC
299
+ LIMIT 1`).get(conversationId);
300
+ const existingRun = db.prepare(`SELECT r.run_id as runId
301
+ FROM runs r
302
+ JOIN conversations c ON c.session_key = r.session_key
303
+ WHERE c.id = ?
304
+ AND r.ended_at IS NULL
305
+ ORDER BY r.started_at DESC
306
+ LIMIT 1`).get(conversationId);
307
+ return {
308
+ queueId: queuedPrompt?.queueId ?? null,
309
+ runId: existingRun?.runId ?? null,
310
+ };
311
+ }
312
+ function withHandoffDispatchMetadata(metadata, handoffDispatch) {
313
+ return {
314
+ ...(metadata ?? {}),
315
+ handoffDispatch,
316
+ };
317
+ }
318
+ function queuedPromptMatchesHandoffCorrelation(db, queueId, correlationId) {
319
+ const row = db.prepare(`SELECT activation_metadata_json as activationMetadataJson
320
+ FROM conversation_prompt_queue
321
+ WHERE queue_id = ?
322
+ LIMIT 1`).get(queueId);
323
+ return activationMetadataMatchesHandoffCorrelation(row?.activationMetadataJson, correlationId);
324
+ }
325
+ function activeRunMatchesHandoffCorrelation(db, runId, correlationId) {
326
+ const row = db.prepare(`SELECT r.ended_at as endedAt,
327
+ rdi.activation_metadata_json as activationMetadataJson
328
+ FROM runs r
329
+ LEFT JOIN run_debug_inputs rdi ON rdi.run_id = r.run_id
330
+ WHERE r.run_id = ?
331
+ LIMIT 1`).get(runId);
332
+ return row?.endedAt == null
333
+ && activationMetadataMatchesHandoffCorrelation(row?.activationMetadataJson, correlationId);
334
+ }
335
+ function activationMetadataMatchesHandoffCorrelation(rawMetadata, correlationId) {
336
+ if (!rawMetadata)
337
+ return false;
338
+ try {
339
+ const parsed = JSON.parse(rawMetadata);
340
+ return parsed?.handoffDispatch?.correlationId === correlationId;
341
+ }
342
+ catch {
343
+ return false;
344
+ }
345
+ }
346
+ function nextSyntheticRunEventSeq(db, runId) {
347
+ const row = db.prepare(`SELECT COALESCE(MAX(seq), 0) as maxSeq
348
+ FROM events
349
+ WHERE run_id = ?`).get(runId);
350
+ return Number(row?.maxSeq ?? 0) + 1;
351
+ }
352
+ function loadLatestChannelSeq(db, channelId) {
353
+ const row = db.prepare(`SELECT COALESCE(MAX(seq), 0) as maxSeq
354
+ FROM channel_messages
355
+ WHERE channel_id = ?`).get(channelId);
356
+ return Number(row?.maxSeq ?? 0);
357
+ }
358
+ export function buildSameAgentHandoffDispatchPlan(params) {
359
+ const conversation = params.conversationManager.getConversation(params.targetConversationId);
360
+ if (!conversation?.agentId)
361
+ return null;
362
+ const replyTarget = (resolveDefaultReplyTarget(params.db, conversation.id, params.humanUserName) ?? conversation.replyTarget ?? '').trim();
363
+ if (!replyTarget)
364
+ return null;
365
+ const channelId = conversation.threadKind === 'direct'
366
+ ? `dm:${conversation.agentId}`
367
+ : conversation.channelId;
368
+ if (!channelId)
369
+ return null;
370
+ const targetSurface = {
371
+ agentId: conversation.agentId,
372
+ channelId,
373
+ replyTarget,
374
+ threadRootId: conversation.threadRootId ?? null,
375
+ };
376
+ const targetSurfaceCheckpointSeedSeq = loadHandoffTargetSurfaceCheckpointSeedSeq(params.db, targetSurface);
377
+ if (conversation.threadKind === 'direct') {
378
+ const directEnvelopeOverrides = params.directEnvelopeOverrides ?? {};
379
+ const envelope = buildDirectSurfaceActivationEnvelope(params.db, {
380
+ agentId: conversation.agentId,
381
+ channelId,
382
+ replyTarget,
383
+ triggerSeq: params.triggerSeqOverride ?? (loadLatestChannelSeq(params.db, channelId) + 1),
384
+ threadRootId: conversation.threadRootId ?? undefined,
385
+ unreadCheckpointSeq: directEnvelopeOverrides.unreadCheckpointSeq ?? targetSurfaceCheckpointSeedSeq ?? undefined,
386
+ ...directEnvelopeOverrides,
387
+ strongSignalFlags: {
388
+ open_handoff: true,
389
+ explicit_handoff_target: true,
390
+ ...directEnvelopeOverrides.strongSignalFlags,
391
+ },
392
+ });
393
+ return {
394
+ promptText: params.promptText,
395
+ targetSurfaceCheckpointSeedSeq,
396
+ options: {
397
+ recordAsUserMessage: false,
398
+ activationContextText: envelope.activationContextText,
399
+ activationContextSections: envelope.activationContextSections,
400
+ resumeContextText: envelope.resumeContextText,
401
+ resumeContextSections: envelope.resumeContextSections,
402
+ replayOverlapRecentMessages: envelope.replayOverlapRecentMessages,
403
+ allowActiveSteer: false,
404
+ ...(params.suppressReplyContract === true ? { suppressReplyContract: true } : {}),
405
+ ...(params.activationMetadata ? { activationMetadata: params.activationMetadata } : {}),
406
+ ...(params.attachments?.length ? { attachments: params.attachments } : {}),
407
+ ...(params.attachmentIds?.length ? { attachmentIds: params.attachmentIds } : {}),
408
+ },
409
+ };
410
+ }
411
+ const channelEnvelopeOverrides = params.channelEnvelopeOverrides ?? {};
412
+ const channel = params.conversationManager.getChannel(conversation.channelId);
413
+ const envelope = buildChannelSurfaceActivationEnvelope(params.db, {
414
+ agentId: conversation.agentId,
415
+ channelId,
416
+ replyTarget,
417
+ triggerSeq: params.triggerSeqOverride ?? (loadLatestChannelSeq(params.db, channelId) + 1),
418
+ threadRootId: conversation.threadRootId ?? undefined,
419
+ channelName: channel?.name,
420
+ reason: 'channel_activity',
421
+ unreadCheckpointSeq: channelEnvelopeOverrides.unreadCheckpointSeq ?? targetSurfaceCheckpointSeedSeq ?? undefined,
422
+ ...channelEnvelopeOverrides,
423
+ strongSignalFlags: {
424
+ open_handoff: true,
425
+ explicit_handoff_target: true,
426
+ ...channelEnvelopeOverrides.strongSignalFlags,
427
+ },
428
+ });
429
+ return {
430
+ promptText: params.promptText,
431
+ targetSurfaceCheckpointSeedSeq,
432
+ options: {
433
+ recordAsUserMessage: false,
434
+ activationContextText: envelope.activationContextText,
435
+ activationContextSections: envelope.activationContextSections,
436
+ resumeContextText: envelope.resumeContextText,
437
+ resumeContextSections: envelope.resumeContextSections,
438
+ replayOverlapRecentMessages: envelope.replayOverlapRecentMessages,
439
+ allowActiveSteer: false,
440
+ ...(params.suppressReplyContract === true ? { suppressReplyContract: true } : {}),
441
+ ...(params.activationMetadata ? { activationMetadata: params.activationMetadata } : {}),
442
+ ...(params.attachments?.length ? { attachments: params.attachments } : {}),
443
+ ...(params.attachmentIds?.length ? { attachmentIds: params.attachmentIds } : {}),
444
+ },
445
+ };
446
+ }
447
+ export async function startConversationHandoff(params) {
448
+ const dispatch = buildSameAgentHandoffDispatchPlan({
449
+ db: params.db,
450
+ conversationManager: params.conversationManager,
451
+ humanUserName: params.humanUserName,
452
+ targetConversationId: params.targetConversationId,
453
+ promptText: buildConversationHandoffPrompt({
454
+ mode: params.mode,
455
+ targetReplyTarget: params.targetReplyTarget,
456
+ payload: params.payload,
457
+ }),
458
+ });
459
+ return performSameAgentHandoff({
460
+ agentId: params.agentId,
461
+ db: params.db,
462
+ conversationManager: params.conversationManager,
463
+ getLatestConversationDispatchState: (conversationId) => getLatestConversationDispatchState(params.db, conversationId),
464
+ sourceConversationId: params.sourceConversationId,
465
+ sourceConversationRunId: params.sourceConversationRunId,
466
+ sourceReplyTarget: params.sourceReplyTarget,
467
+ targetConversationId: params.targetConversationId,
468
+ targetReplyTarget: params.targetReplyTarget,
469
+ mode: params.mode,
470
+ payload: params.payload,
471
+ taskId: params.taskId ?? null,
472
+ taskNumber: params.taskNumber ?? null,
473
+ dispatch,
474
+ dispatchBuildErrorText: `Failed to build handoff dispatch for ${params.targetReplyTarget}`,
475
+ onQueued: (targetConversationId) => {
476
+ params.broadcastConversationStatus?.(targetConversationId, 'queued');
477
+ },
478
+ onStarted: ({ record, cancelRequested, cancelRunId, sourceConversationRunId }) => {
479
+ if (!cancelRequested || !cancelRunId || !sourceConversationRunId)
480
+ return;
481
+ params.db.prepare(`INSERT INTO events(run_id, seq, method, payload_json, created_at)
482
+ VALUES(?, ?, ?, ?, ?)`).run(sourceConversationRunId, nextSyntheticRunEventSeq(params.db, sourceConversationRunId), params.handoffEventMethod ?? 'platform/handoff', JSON.stringify({
483
+ kind: 'conversation_handoff',
484
+ handoffId: record.handoffId,
485
+ mode: params.mode,
486
+ sourceTarget: params.sourceReplyTarget,
487
+ targetReplyTarget: params.targetReplyTarget,
488
+ targetConversationId: params.targetConversationId,
489
+ cancelRunId,
490
+ status: 'started',
491
+ }), Date.now());
492
+ },
493
+ });
494
+ }