@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,469 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { allocateNextChannelMessageSeq } from './channelMessageSequences.js';
3
+ import { insertPanel } from './panels.js';
4
+ export const ACTION_CARD_REASON_MAX_LENGTH = 500;
5
+ export class ActionCardError extends Error {
6
+ statusCode;
7
+ errorCode;
8
+ constructor(statusCode, message, errorCode) {
9
+ super(message);
10
+ this.statusCode = statusCode;
11
+ this.errorCode = errorCode;
12
+ }
13
+ }
14
+ function parseJsonObject(raw) {
15
+ if (!raw)
16
+ return null;
17
+ try {
18
+ const parsed = JSON.parse(raw);
19
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
20
+ ? parsed
21
+ : null;
22
+ }
23
+ catch {
24
+ return null;
25
+ }
26
+ }
27
+ function normalizeStatus(value) {
28
+ return value === 'confirmed'
29
+ || value === 'cancelled'
30
+ || value === 'expired'
31
+ || value === 'failed'
32
+ ? value
33
+ : 'pending';
34
+ }
35
+ function normalizeActionType(value) {
36
+ if (value === 'channel:create' || value === 'agent:create' || value === 'channel:add_member')
37
+ return value;
38
+ throw new ActionCardError(400, `Unsupported action type: ${value}`, 'UNSUPPORTED_ACTION_TYPE');
39
+ }
40
+ function rowToActionCard(row) {
41
+ return {
42
+ id: row.id,
43
+ actionType: normalizeActionType(row.actionType),
44
+ payload: parseJsonObject(row.payloadJson) ?? {},
45
+ originConversationId: row.originConversationId,
46
+ originChannelId: row.originChannelId,
47
+ originThreadRootId: row.originThreadRootId,
48
+ proposerAgentId: row.proposerAgentId,
49
+ runId: row.runId,
50
+ turnId: row.turnId,
51
+ traceId: row.traceId,
52
+ reason: row.reason,
53
+ status: normalizeStatus(row.status),
54
+ expiresAt: row.expiresAt,
55
+ confirmedBy: row.confirmedBy,
56
+ result: parseJsonObject(row.resultJson),
57
+ createdAt: row.createdAt,
58
+ updatedAt: row.updatedAt,
59
+ confirmedAt: row.confirmedAt,
60
+ cancelledAt: row.cancelledAt,
61
+ failedAt: row.failedAt,
62
+ };
63
+ }
64
+ export function getActionCardById(db, id) {
65
+ const row = db.prepare(`SELECT id, action_type as actionType, payload_json as payloadJson,
66
+ origin_conversation_id as originConversationId,
67
+ origin_channel_id as originChannelId,
68
+ origin_thread_root_id as originThreadRootId,
69
+ proposer_agent_id as proposerAgentId,
70
+ run_id as runId,
71
+ turn_id as turnId,
72
+ trace_id as traceId,
73
+ reason,
74
+ status, expires_at as expiresAt,
75
+ confirmed_by as confirmedBy,
76
+ result_json as resultJson,
77
+ created_at as createdAt,
78
+ updated_at as updatedAt,
79
+ confirmed_at as confirmedAt,
80
+ cancelled_at as cancelledAt,
81
+ failed_at as failedAt
82
+ FROM action_cards
83
+ WHERE id = ?
84
+ LIMIT 1`).get(id);
85
+ return row ? rowToActionCard(row) : null;
86
+ }
87
+ function requireRecord(value, label) {
88
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
89
+ throw new ActionCardError(400, `${label} must be a JSON object`, 'INVALID_ACTION_PAYLOAD');
90
+ }
91
+ return value;
92
+ }
93
+ function optionalString(value) {
94
+ return typeof value === 'string' && value.trim() ? value.trim() : undefined;
95
+ }
96
+ function normalizeReason(value) {
97
+ const normalized = value?.trim() ?? '';
98
+ if (!normalized)
99
+ return null;
100
+ return normalized.slice(0, ACTION_CARD_REASON_MAX_LENGTH);
101
+ }
102
+ function requireStringField(payload, field) {
103
+ const value = optionalString(payload[field]);
104
+ if (!value)
105
+ throw new ActionCardError(400, `${field} is required`, 'INVALID_ACTION_PAYLOAD');
106
+ return value;
107
+ }
108
+ function normalizeStringArray(value, field) {
109
+ if (value == null)
110
+ return undefined;
111
+ if (!Array.isArray(value))
112
+ throw new ActionCardError(400, `${field} must be an array`, 'INVALID_ACTION_PAYLOAD');
113
+ const normalized = value
114
+ .map((item) => (typeof item === 'string' ? item.trim() : ''))
115
+ .filter(Boolean);
116
+ return normalized.length ? normalized : undefined;
117
+ }
118
+ function normalizeChannelCreatePayload(payload) {
119
+ return {
120
+ name: requireStringField(payload, 'name'),
121
+ ...(optionalString(payload.workspacePath) ? { workspacePath: optionalString(payload.workspacePath) } : {}),
122
+ ...(optionalString(payload.description) ? { description: optionalString(payload.description) } : {}),
123
+ ...(normalizeStringArray(payload.agentIds, 'agentIds') ? { agentIds: normalizeStringArray(payload.agentIds, 'agentIds') } : {}),
124
+ ...(payload.collaborationMode === 'mention_only' ? { collaborationMode: 'mention_only' } : {}),
125
+ };
126
+ }
127
+ function normalizeAgentCreatePayload(payload) {
128
+ const rawAgentType = optionalString(payload.agentType);
129
+ if (rawAgentType
130
+ && rawAgentType !== 'claude_acp'
131
+ && rawAgentType !== 'claude_sdk'
132
+ && rawAgentType !== 'codex_acp'
133
+ && rawAgentType !== 'codex_app_server') {
134
+ throw new ActionCardError(400, `Unsupported agentType: ${rawAgentType}`, 'INVALID_ACTION_PAYLOAD');
135
+ }
136
+ const agentType = rawAgentType;
137
+ return {
138
+ name: requireStringField(payload, 'name'),
139
+ ...(agentType ? { agentType } : {}),
140
+ ...(optionalString(payload.model) ? { model: optionalString(payload.model) } : {}),
141
+ ...(optionalString(payload.reasoningEffort) ? { reasoningEffort: optionalString(payload.reasoningEffort) } : {}),
142
+ ...(optionalString(payload.channelId) ? { channelId: optionalString(payload.channelId) } : {}),
143
+ ...(optionalString(payload.systemPrompt) ? { systemPrompt: optionalString(payload.systemPrompt) } : {}),
144
+ ...(optionalString(payload.description) ? { description: optionalString(payload.description) } : {}),
145
+ ...(optionalString(payload.nodeId) ? { nodeId: optionalString(payload.nodeId) } : {}),
146
+ ...(optionalString(payload.workspacePath) ? { workspacePath: optionalString(payload.workspacePath) } : {}),
147
+ ...(optionalString(payload.projectPath) ? { projectPath: optionalString(payload.projectPath) } : {}),
148
+ ...(normalizeStringArray(payload.skillRoots, 'skillRoots') ? { skillRoots: normalizeStringArray(payload.skillRoots, 'skillRoots') } : {}),
149
+ };
150
+ }
151
+ function normalizeAddMemberPayload(payload) {
152
+ return {
153
+ channelId: requireStringField(payload, 'channelId'),
154
+ agentId: requireStringField(payload, 'agentId'),
155
+ };
156
+ }
157
+ function normalizeActionPayload(actionType, payload) {
158
+ switch (actionType) {
159
+ case 'channel:create':
160
+ return normalizeChannelCreatePayload(payload);
161
+ case 'agent:create':
162
+ return normalizeAgentCreatePayload(payload);
163
+ case 'channel:add_member':
164
+ return normalizeAddMemberPayload(payload);
165
+ }
166
+ }
167
+ function summarizeAction(actionType, payload) {
168
+ if (actionType === 'channel:create')
169
+ return `Create channel "${String(payload.name)}"`;
170
+ if (actionType === 'agent:create')
171
+ return `Create agent "${String(payload.name)}"`;
172
+ return `Add agent ${String(payload.agentId)} to channel ${String(payload.channelId)}`;
173
+ }
174
+ function toPublicActionCard(card) {
175
+ return {
176
+ id: card.id,
177
+ actionCardId: card.id,
178
+ action_card_id: card.id,
179
+ actionType: card.actionType,
180
+ action_type: card.actionType,
181
+ payload: card.payload,
182
+ originConversationId: card.originConversationId,
183
+ origin_conversation_id: card.originConversationId,
184
+ originChannelId: card.originChannelId,
185
+ origin_channel_id: card.originChannelId,
186
+ originThreadRootId: card.originThreadRootId,
187
+ origin_thread_root_id: card.originThreadRootId,
188
+ proposerAgentId: card.proposerAgentId,
189
+ proposer_agent_id: card.proposerAgentId,
190
+ runId: card.runId,
191
+ run_id: card.runId,
192
+ turnId: card.turnId,
193
+ turn_id: card.turnId,
194
+ traceId: card.traceId,
195
+ trace_id: card.traceId,
196
+ reason: card.reason,
197
+ status: card.status,
198
+ expiresAt: card.expiresAt,
199
+ expires_at: card.expiresAt,
200
+ confirmedBy: card.confirmedBy,
201
+ confirmed_by: card.confirmedBy,
202
+ result: card.result,
203
+ createdAt: card.createdAt,
204
+ created_at: card.createdAt,
205
+ updatedAt: card.updatedAt,
206
+ updated_at: card.updatedAt,
207
+ };
208
+ }
209
+ function insertActionCardPanel(db, card) {
210
+ const panelId = `action-card-${card.id}`;
211
+ try {
212
+ return insertPanel(db, {
213
+ id: panelId,
214
+ agentId: card.proposerAgentId,
215
+ conversationId: card.originConversationId,
216
+ handle: panelId,
217
+ scopeType: card.originChannelId
218
+ ? (card.originThreadRootId ? 'thread' : 'channel')
219
+ : 'direct',
220
+ scopeId: card.originChannelId,
221
+ component: 'KeyValuePanel',
222
+ props: {
223
+ title: 'Action confirmation',
224
+ summary: summarizeAction(card.actionType, card.payload),
225
+ actionCardId: card.id,
226
+ actionType: card.actionType,
227
+ status: card.status,
228
+ expiresAt: new Date(card.expiresAt).toISOString(),
229
+ reason: card.reason,
230
+ runId: card.runId,
231
+ turnId: card.turnId,
232
+ traceId: card.traceId,
233
+ payload: card.payload,
234
+ confirmEndpoint: `/api/action-cards/${card.id}/confirm`,
235
+ cancelEndpoint: `/api/action-cards/${card.id}/cancel`,
236
+ },
237
+ actions: [
238
+ { id: 'confirm', label: 'Confirm', variant: 'primary', mode: 'platform_exec' },
239
+ { id: 'cancel', label: 'Cancel', variant: 'secondary', mode: 'platform_exec' },
240
+ ],
241
+ rowCount: 0,
242
+ status: 'idle',
243
+ createdByRunId: card.runId,
244
+ createdAt: card.createdAt,
245
+ updatedAt: card.updatedAt,
246
+ });
247
+ }
248
+ catch {
249
+ return null;
250
+ }
251
+ }
252
+ export function createActionCard(db, params) {
253
+ const actionType = normalizeActionType(params.actionType);
254
+ const rawPayload = requireRecord(params.payload, 'payload');
255
+ const payload = normalizeActionPayload(actionType, rawPayload);
256
+ const conversation = params.conversationManager.getConversation(params.originConversationId);
257
+ if (!conversation || conversation.agentId !== params.proposerAgentId) {
258
+ throw new ActionCardError(403, 'Conversation is not available for this agent', 'CONVERSATION_NOT_AVAILABLE');
259
+ }
260
+ const now = Date.now();
261
+ const expiresAt = params.expiresAt && params.expiresAt > now
262
+ ? params.expiresAt
263
+ : now + 24 * 60 * 60 * 1000;
264
+ const reason = normalizeReason(params.reason);
265
+ const id = randomUUID();
266
+ db.prepare(`INSERT INTO action_cards(
267
+ id, action_type, payload_json, origin_conversation_id, origin_channel_id,
268
+ origin_thread_root_id, proposer_agent_id, run_id, turn_id, trace_id, reason, status, expires_at,
269
+ created_at, updated_at
270
+ )
271
+ VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', ?, ?, ?)`).run(id, actionType, JSON.stringify(payload), conversation.id, conversation.channelId ?? null, conversation.threadRootId ?? null, params.proposerAgentId, params.runId ?? null, params.turnId ?? null, params.traceId ?? null, reason, expiresAt, now, now);
272
+ const card = getActionCardById(db, id);
273
+ if (!card)
274
+ throw new ActionCardError(500, 'Action card was not persisted');
275
+ const panelId = insertActionCardPanel(db, card);
276
+ return {
277
+ card: {
278
+ ...toPublicActionCard(card),
279
+ panelId,
280
+ panel_id: panelId,
281
+ summary: summarizeAction(actionType, payload),
282
+ },
283
+ panelId,
284
+ };
285
+ }
286
+ export function expireDueActionCards(db, now = Date.now()) {
287
+ const result = db.prepare(`UPDATE action_cards
288
+ SET status = 'expired',
289
+ updated_at = ?,
290
+ failed_at = COALESCE(failed_at, ?)
291
+ WHERE status = 'pending'
292
+ AND expires_at <= ?`).run(now, now, now);
293
+ return Number(result.changes ?? 0);
294
+ }
295
+ function writeActionCardResultMessage(db, params) {
296
+ if (!params.card.originChannelId)
297
+ return null;
298
+ const channelId = params.card.originChannelId;
299
+ const channel = db.prepare(`SELECT name FROM channels WHERE channel_id = ? LIMIT 1`).get(channelId);
300
+ const target = channel
301
+ ? `#${channel.name}${params.card.originThreadRootId ? `:${params.card.originThreadRootId}` : ''}`
302
+ : (params.card.originThreadRootId ? `#${channelId}:${params.card.originThreadRootId}` : `#${channelId}`);
303
+ const createdAt = Date.now();
304
+ const messageId = randomUUID();
305
+ const seq = allocateNextChannelMessageSeq(db, channelId);
306
+ const content = params.status === 'confirmed'
307
+ ? `Action confirmed by ${params.actorName}: ${summarizeAction(params.card.actionType, params.card.payload)}.`
308
+ : `Action ${params.status} by ${params.actorName}: ${summarizeAction(params.card.actionType, params.card.payload)}.`;
309
+ db.prepare(`INSERT INTO channel_messages(
310
+ message_id, channel_id, sender_id, sender_name, sender_type, target, content,
311
+ seq, created_at, thread_root_id, message_kind, message_source
312
+ )
313
+ VALUES(?, ?, 'system', 'Bigbang', 'system', ?, ?, ?, ?, ?, 'system', 'action_card')`).run(messageId, channelId, target, content, seq, createdAt, params.card.originThreadRootId);
314
+ params.broadcastToChannel?.(channelId, {
315
+ type: 'channel.message',
316
+ message: {
317
+ id: messageId,
318
+ senderName: 'Bigbang',
319
+ senderType: 'system',
320
+ content,
321
+ createdAt: new Date(createdAt).toISOString(),
322
+ seq,
323
+ messageKind: 'system',
324
+ messageSource: 'action_card',
325
+ ...(params.card.originThreadRootId ? { threadRootId: params.card.originThreadRootId } : {}),
326
+ },
327
+ });
328
+ return messageId;
329
+ }
330
+ function executeActionCard(card, conversationManager) {
331
+ switch (card.actionType) {
332
+ case 'channel:create': {
333
+ const payload = normalizeChannelCreatePayload(card.payload);
334
+ const channel = conversationManager.createChannel({
335
+ name: payload.name,
336
+ workspacePath: payload.workspacePath,
337
+ description: payload.description,
338
+ collaborationMode: payload.collaborationMode,
339
+ });
340
+ for (const agentId of payload.agentIds ?? []) {
341
+ if (conversationManager.getAgent(agentId))
342
+ conversationManager.joinChannel(agentId, channel.channelId);
343
+ }
344
+ return { channel: conversationManager.getChannel(channel.channelId) ?? channel };
345
+ }
346
+ case 'agent:create': {
347
+ const agent = conversationManager.createAgent(normalizeAgentCreatePayload(card.payload));
348
+ return { agent };
349
+ }
350
+ case 'channel:add_member': {
351
+ const payload = normalizeAddMemberPayload(card.payload);
352
+ const channel = conversationManager.getChannel(payload.channelId);
353
+ if (!channel)
354
+ throw new ActionCardError(404, 'Channel not found', 'CHANNEL_NOT_FOUND');
355
+ const agent = conversationManager.getAgent(payload.agentId);
356
+ if (!agent)
357
+ throw new ActionCardError(404, 'Agent not found', 'AGENT_NOT_FOUND');
358
+ conversationManager.joinChannel(payload.agentId, payload.channelId);
359
+ return { channel: conversationManager.getChannel(payload.channelId), agent };
360
+ }
361
+ }
362
+ }
363
+ function updateActionCardFailed(db, cardId, message, now) {
364
+ db.prepare(`UPDATE action_cards
365
+ SET status = 'failed', result_json = ?, updated_at = ?, failed_at = ?
366
+ WHERE id = ? AND status = 'pending'`).run(JSON.stringify({ error: message }), now, now, cardId);
367
+ }
368
+ export function confirmActionCard(db, params) {
369
+ const tx = db.transaction(() => {
370
+ const card = getActionCardById(db, params.id);
371
+ if (!card)
372
+ throw new ActionCardError(404, 'Action card not found', 'ACTION_CARD_NOT_FOUND');
373
+ const now = Date.now();
374
+ if (card.status !== 'pending') {
375
+ throw new ActionCardError(409, `Action card is ${card.status}`, 'ACTION_CARD_NOT_PENDING');
376
+ }
377
+ if (card.expiresAt <= now) {
378
+ const updated = db.prepare(`UPDATE action_cards
379
+ SET status = 'expired', updated_at = ?, failed_at = ?
380
+ WHERE id = ? AND status = 'pending'`).run(now, now, card.id);
381
+ if (updated.changes !== 1) {
382
+ throw new ActionCardError(409, 'Action card is no longer pending', 'ACTION_CARD_NOT_PENDING');
383
+ }
384
+ writeActionCardResultMessage(db, {
385
+ card: { ...card, status: 'expired' },
386
+ status: 'expired',
387
+ actorName: params.confirmedByName,
388
+ broadcastToChannel: params.broadcastToChannel,
389
+ });
390
+ return new ActionCardError(409, 'Action card expired', 'ACTION_CARD_EXPIRED');
391
+ }
392
+ try {
393
+ const result = executeActionCard(card, params.conversationManager);
394
+ const updated = db.prepare(`UPDATE action_cards
395
+ SET status = 'confirmed', confirmed_by = ?, result_json = ?,
396
+ updated_at = ?, confirmed_at = ?
397
+ WHERE id = ? AND status = 'pending'`).run(params.confirmedBy, JSON.stringify(result), now, now, card.id);
398
+ if (updated.changes !== 1) {
399
+ throw new ActionCardError(409, 'Action card is no longer pending', 'ACTION_CARD_NOT_PENDING');
400
+ }
401
+ const confirmedCard = getActionCardById(db, card.id) ?? { ...card, status: 'confirmed', result };
402
+ const resultMessageId = writeActionCardResultMessage(db, {
403
+ card: confirmedCard,
404
+ status: 'confirmed',
405
+ actorName: params.confirmedByName,
406
+ result,
407
+ broadcastToChannel: params.broadcastToChannel,
408
+ });
409
+ return {
410
+ ok: true,
411
+ card: toPublicActionCard(confirmedCard),
412
+ result,
413
+ resultMessageId,
414
+ result_message_id: resultMessageId,
415
+ };
416
+ }
417
+ catch (error) {
418
+ if (error instanceof ActionCardError && error.errorCode === 'ACTION_CARD_NOT_PENDING') {
419
+ throw error;
420
+ }
421
+ const statusCode = error instanceof ActionCardError ? error.statusCode : 500;
422
+ const message = String(error?.message ?? error);
423
+ updateActionCardFailed(db, card.id, message, now);
424
+ writeActionCardResultMessage(db, {
425
+ card: { ...card, status: 'failed', result: { error: message } },
426
+ status: 'failed',
427
+ actorName: params.confirmedByName,
428
+ broadcastToChannel: params.broadcastToChannel,
429
+ });
430
+ return new ActionCardError(statusCode, message, error instanceof ActionCardError ? error.errorCode : 'ACTION_CARD_EXECUTION_FAILED');
431
+ }
432
+ });
433
+ const result = tx();
434
+ if (result instanceof ActionCardError)
435
+ throw result;
436
+ return result;
437
+ }
438
+ export function cancelActionCard(db, params) {
439
+ const tx = db.transaction(() => {
440
+ expireDueActionCards(db);
441
+ const card = getActionCardById(db, params.id);
442
+ if (!card)
443
+ throw new ActionCardError(404, 'Action card not found', 'ACTION_CARD_NOT_FOUND');
444
+ if (card.status !== 'pending') {
445
+ throw new ActionCardError(409, `Action card is ${card.status}`, 'ACTION_CARD_NOT_PENDING');
446
+ }
447
+ const now = Date.now();
448
+ const updated = db.prepare(`UPDATE action_cards
449
+ SET status = 'cancelled', confirmed_by = ?, updated_at = ?, cancelled_at = ?
450
+ WHERE id = ? AND status = 'pending'`).run(params.cancelledBy, now, now, card.id);
451
+ if (updated.changes !== 1) {
452
+ throw new ActionCardError(409, 'Action card is no longer pending', 'ACTION_CARD_NOT_PENDING');
453
+ }
454
+ const cancelledCard = getActionCardById(db, card.id) ?? { ...card, status: 'cancelled' };
455
+ const resultMessageId = writeActionCardResultMessage(db, {
456
+ card: cancelledCard,
457
+ status: 'cancelled',
458
+ actorName: params.cancelledByName,
459
+ broadcastToChannel: params.broadcastToChannel,
460
+ });
461
+ return {
462
+ ok: true,
463
+ card: toPublicActionCard(cancelledCard),
464
+ resultMessageId,
465
+ result_message_id: resultMessageId,
466
+ };
467
+ });
468
+ return tx();
469
+ }