@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,293 @@
1
+ import { deletePanelAttachmentArtifactRows, unlinkPanelAttachmentStoragePaths, } from '../web/panelLifecycle.js';
2
+ import { getPanelById, insertPanel, insertPanelRows, listAllPanelRows, } from '../web/panels.js';
3
+ import { ensureSurfaceOwner } from '../web/surfaceCollaborators.js';
4
+ import { isPlainRecord, parseStateRowJson, } from './workspaceToolExecutionUtils.js';
5
+ import { parseWorkspaceToolManifest } from './workspaceToolManifest.js';
6
+ import { buildToolSnapshotPanelProjection, buildToolSnapshotSchemaFingerprint, deriveWorkspaceToolRuntimeState, workspaceToolRuntimeStateToPanelStatus, } from './workspaceToolPanelProjection.js';
7
+ export class WorkspaceToolSnapshotPanelSyncer {
8
+ params;
9
+ constructor(params) {
10
+ this.params = params;
11
+ }
12
+ updateLatestState(params, options) {
13
+ const now = Date.now();
14
+ const staleCachedStoragePaths = [];
15
+ // Transaction boundary: the tool latest_state_json update and the
16
+ // tool-scoped snapshot panel rewrite commit or roll back together.
17
+ // The broadcast callback and stale media unlink happen only after commit.
18
+ const tx = this.db.transaction(() => {
19
+ this.db.prepare(`UPDATE workspace_tools
20
+ SET latest_state_json = ?,
21
+ updated_at = ?
22
+ WHERE tool_id = ?`).run(params.state ? JSON.stringify(params.state) : null, now, params.toolId);
23
+ const toolRow = this.db.prepare(`SELECT tool_id as toolId,
24
+ user_id as userId,
25
+ agent_id as agentId,
26
+ source_panel_id as sourcePanelId,
27
+ panel_id as panelId,
28
+ active_terminal_id as activeTerminalId,
29
+ maintenance_conversation_id as maintenanceConversationId,
30
+ allow_shared_exec as allowSharedExec,
31
+ manifest_json as manifestJson
32
+ FROM workspace_tools
33
+ WHERE tool_id = ?
34
+ AND deleted_at IS NULL
35
+ LIMIT 1`).get(params.toolId);
36
+ const snapshotConversationCandidate = toolRow?.maintenanceConversationId ?? params.maintenanceConversationId ?? null;
37
+ const snapshotConversationId = snapshotConversationCandidate
38
+ && this.conversations.getConversation(snapshotConversationCandidate)
39
+ ? snapshotConversationCandidate
40
+ : null;
41
+ if (toolRow?.maintenanceConversationId && !snapshotConversationId) {
42
+ this.db.prepare(`UPDATE workspace_tools
43
+ SET maintenance_conversation_id = NULL,
44
+ updated_at = ?
45
+ WHERE tool_id = ?
46
+ AND maintenance_conversation_id = ?`).run(now, toolRow.toolId, toolRow.maintenanceConversationId);
47
+ }
48
+ if (toolRow && snapshotConversationId) {
49
+ const agent = this.conversations.getAgent(toolRow.agentId);
50
+ const manifest = parseWorkspaceToolManifest(toolRow.manifestJson);
51
+ const snapshotSync = this.syncPanel({
52
+ panelId: toolRow.panelId,
53
+ sourcePanelId: toolRow.sourcePanelId,
54
+ toolId: toolRow.toolId,
55
+ userId: toolRow.userId,
56
+ agentId: toolRow.agentId,
57
+ conversationId: snapshotConversationId,
58
+ nodeId: agent?.nodeId ?? null,
59
+ manifest,
60
+ latestState: params.state,
61
+ allowSharedExec: toolRow.allowSharedExec === 1,
62
+ status: this.resolveDefaultToolPanelStatus(toolRow.toolId, toolRow.activeTerminalId),
63
+ });
64
+ if (snapshotSync.panelId !== toolRow.panelId) {
65
+ this.db.prepare(`UPDATE workspace_tools
66
+ SET panel_id = ?,
67
+ updated_at = ?
68
+ WHERE tool_id = ?`).run(snapshotSync.panelId, now, toolRow.toolId);
69
+ }
70
+ staleCachedStoragePaths.push(...snapshotSync.staleCachedStoragePaths);
71
+ }
72
+ });
73
+ tx();
74
+ options?.onCommitted?.();
75
+ unlinkPanelAttachmentStoragePaths(staleCachedStoragePaths);
76
+ }
77
+ syncForTool(toolId, status) {
78
+ const toolRow = this.db.prepare(`SELECT tool_id as toolId,
79
+ user_id as userId,
80
+ agent_id as agentId,
81
+ source_panel_id as sourcePanelId,
82
+ panel_id as panelId,
83
+ active_terminal_id as activeTerminalId,
84
+ maintenance_conversation_id as maintenanceConversationId,
85
+ allow_shared_exec as allowSharedExec,
86
+ manifest_json as manifestJson,
87
+ latest_state_json as latestStateJson
88
+ FROM workspace_tools
89
+ WHERE tool_id = ?
90
+ AND deleted_at IS NULL
91
+ LIMIT 1`).get(toolId);
92
+ if (!toolRow)
93
+ return;
94
+ if (!toolRow.panelId && !toolRow.maintenanceConversationId)
95
+ return;
96
+ if (toolRow.maintenanceConversationId && !this.conversations.getConversation(toolRow.maintenanceConversationId)) {
97
+ this.db.prepare(`UPDATE workspace_tools
98
+ SET maintenance_conversation_id = NULL,
99
+ updated_at = ?
100
+ WHERE tool_id = ?
101
+ AND maintenance_conversation_id = ?`).run(Date.now(), toolRow.toolId, toolRow.maintenanceConversationId);
102
+ if (!toolRow.panelId)
103
+ return;
104
+ toolRow.maintenanceConversationId = null;
105
+ }
106
+ const agent = this.conversations.getAgent(toolRow.agentId);
107
+ const manifest = parseWorkspaceToolManifest(toolRow.manifestJson);
108
+ const snapshotSync = this.syncPanel({
109
+ panelId: toolRow.panelId,
110
+ sourcePanelId: toolRow.sourcePanelId,
111
+ toolId: toolRow.toolId,
112
+ userId: toolRow.userId,
113
+ agentId: toolRow.agentId,
114
+ conversationId: toolRow.maintenanceConversationId ?? '',
115
+ nodeId: agent?.nodeId ?? null,
116
+ manifest,
117
+ latestState: parseStateRowJson(toolRow.latestStateJson ?? null),
118
+ allowSharedExec: toolRow.allowSharedExec === 1,
119
+ status: status ?? this.resolveDefaultToolPanelStatus(toolRow.toolId, toolRow.activeTerminalId),
120
+ });
121
+ if (snapshotSync.panelId !== toolRow.panelId) {
122
+ this.db.prepare(`UPDATE workspace_tools
123
+ SET panel_id = ?,
124
+ updated_at = ?
125
+ WHERE tool_id = ?`).run(snapshotSync.panelId, Date.now(), toolRow.toolId);
126
+ }
127
+ unlinkPanelAttachmentStoragePaths(snapshotSync.staleCachedStoragePaths);
128
+ }
129
+ syncPanel(spec) {
130
+ const now = Date.now();
131
+ const existing = spec.panelId ? getPanelById(this.db, spec.panelId) : null;
132
+ const sourcePanel = spec.sourcePanelId
133
+ ? getPanelById(this.db, spec.sourcePanelId)
134
+ : null;
135
+ const sourceProps = sourcePanel?.component === 'RowTemplateGrid' && !sourcePanel.archivedAt && isPlainRecord(sourcePanel.props)
136
+ ? sourcePanel.props
137
+ : spec.sourcePanelId && existing?.component === 'RowTemplateGrid' && isPlainRecord(existing.props)
138
+ ? existing.props
139
+ : null;
140
+ const liveSourceRows = sourcePanel && !sourcePanel.archivedAt && sourceProps
141
+ ? listAllPanelRows(this.db, sourcePanel.id).map((sourceRow) => ({
142
+ rowId: sourceRow.rowId,
143
+ fields: sourceRow.fields,
144
+ media: sourceRow.media,
145
+ nodeId: sourceRow.nodeId ?? spec.nodeId,
146
+ cachedAssetIds: {},
147
+ }))
148
+ : [];
149
+ const preservedSnapshotRows = liveSourceRows.length === 0 && spec.sourcePanelId && existing?.component === 'RowTemplateGrid'
150
+ ? listAllPanelRows(this.db, existing.id)
151
+ .filter((existingRow) => existingRow.rowId !== 'tool-state')
152
+ .map((existingRow) => ({
153
+ rowId: existingRow.rowId,
154
+ fields: existingRow.fields,
155
+ media: existingRow.media,
156
+ nodeId: existingRow.nodeId ?? spec.nodeId,
157
+ cachedAssetIds: {},
158
+ }))
159
+ : [];
160
+ const projection = buildToolSnapshotPanelProjection({
161
+ sourcePanelId: spec.sourcePanelId,
162
+ nodeId: spec.nodeId,
163
+ manifest: spec.manifest,
164
+ latestState: spec.latestState,
165
+ allowSharedExec: spec.allowSharedExec,
166
+ status: spec.status,
167
+ sourceProps,
168
+ sourceRows: liveSourceRows,
169
+ preservedRows: preservedSnapshotRows,
170
+ });
171
+ if (!existing || existing.archivedAt) {
172
+ const panelId = insertPanel(this.db, {
173
+ id: existing?.archivedAt ? undefined : (spec.panelId ?? undefined),
174
+ agentId: spec.agentId,
175
+ conversationId: spec.conversationId,
176
+ ownerUserId: spec.userId,
177
+ scopeType: 'tool',
178
+ scopeId: spec.toolId,
179
+ component: 'RowTemplateGrid',
180
+ props: projection.props,
181
+ actions: projection.actions,
182
+ rowCount: projection.rowCount,
183
+ status: spec.status ?? 'idle',
184
+ result: projection.result,
185
+ createdAt: now,
186
+ updatedAt: now,
187
+ });
188
+ ensureSurfaceOwner(this.db, {
189
+ surfaceType: 'panel',
190
+ surfaceId: panelId,
191
+ agentId: spec.agentId,
192
+ createdAt: now,
193
+ });
194
+ if (projection.rows.length > 0) {
195
+ insertPanelRows(this.db, projection.rows.map((row, rowIndex) => ({
196
+ panelId,
197
+ rowIndex,
198
+ rowId: row.rowId,
199
+ fields: row.fields,
200
+ media: row.media,
201
+ nodeId: row.nodeId,
202
+ cachedAssetIds: row.cachedAssetIds ?? {},
203
+ })));
204
+ }
205
+ return { panelId, staleCachedStoragePaths: [] };
206
+ }
207
+ const previousSchemaFingerprint = buildToolSnapshotSchemaFingerprint(existing.props ?? {});
208
+ const staleCachedAssetIds = listPanelCachedAssetIds(this.db, existing.id);
209
+ const staleCachedStoragePaths = this.db.transaction(() => {
210
+ this.db.prepare(`UPDATE panels
211
+ SET conversation_id = ?,
212
+ owner_user_id = ?,
213
+ scope_type = 'tool',
214
+ scope_id = ?,
215
+ component = 'RowTemplateGrid',
216
+ props_json = ?,
217
+ actions_json = ?,
218
+ row_count = ?,
219
+ status = ?,
220
+ progress_json = NULL,
221
+ result_json = ?,
222
+ updated_at = ?,
223
+ version = version + 1
224
+ WHERE id = ?`).run(spec.conversationId, spec.userId, spec.toolId, JSON.stringify(projection.props), JSON.stringify(projection.actions), projection.rowCount, spec.status ?? existing.status, projection.result ? JSON.stringify(projection.result) : null, now, existing.id);
225
+ ensureSurfaceOwner(this.db, {
226
+ surfaceType: 'panel',
227
+ surfaceId: existing.id,
228
+ agentId: spec.agentId,
229
+ createdAt: now,
230
+ });
231
+ if (previousSchemaFingerprint !== projection.schemaFingerprint) {
232
+ this.db.prepare(`DELETE FROM panel_state WHERE panel_id = ?`).run(existing.id);
233
+ this.db.prepare(`DELETE FROM panel_shared_state WHERE panel_id = ?`).run(existing.id);
234
+ }
235
+ const storagePaths = deletePanelAttachmentArtifactRows(this.db, {
236
+ panelId: existing.id,
237
+ attachmentIds: staleCachedAssetIds,
238
+ });
239
+ this.db.prepare('DELETE FROM panel_rows WHERE panel_id = ?').run(existing.id);
240
+ if (projection.rows.length > 0) {
241
+ insertPanelRows(this.db, projection.rows.map((row, rowIndex) => ({
242
+ panelId: existing.id,
243
+ rowIndex,
244
+ rowId: row.rowId,
245
+ fields: row.fields,
246
+ media: row.media,
247
+ nodeId: row.nodeId,
248
+ cachedAssetIds: row.cachedAssetIds ?? {},
249
+ })));
250
+ }
251
+ return storagePaths;
252
+ })();
253
+ return { panelId: existing.id, staleCachedStoragePaths };
254
+ }
255
+ get db() {
256
+ return this.params.db;
257
+ }
258
+ get conversations() {
259
+ return this.params.conversations;
260
+ }
261
+ resolveDefaultToolPanelStatus(toolId, activeTerminalId) {
262
+ const lastRunRow = this.db.prepare(`SELECT status
263
+ FROM workspace_tool_runs
264
+ WHERE tool_id = ?
265
+ ORDER BY started_at DESC
266
+ LIMIT 1`).get(toolId);
267
+ return workspaceToolRuntimeStateToPanelStatus(deriveWorkspaceToolRuntimeState({
268
+ activeTerminalId,
269
+ lastRunStatus: lastRunRow?.status ?? null,
270
+ }));
271
+ }
272
+ }
273
+ function listPanelCachedAssetIds(db, panelId) {
274
+ const rows = db.prepare(`SELECT cached_asset_ids_json as cachedAssetIdsJson
275
+ FROM panel_rows
276
+ WHERE panel_id = ?`).all(panelId);
277
+ const assetIds = new Set();
278
+ for (const row of rows) {
279
+ try {
280
+ const parsed = JSON.parse(row.cachedAssetIdsJson ?? '{}');
281
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
282
+ continue;
283
+ for (const value of Object.values(parsed)) {
284
+ if (typeof value === 'string' && value.trim())
285
+ assetIds.add(value);
286
+ }
287
+ }
288
+ catch {
289
+ // Malformed cache metadata should not block snapshot refresh.
290
+ }
291
+ }
292
+ return [...assetIds];
293
+ }
@@ -0,0 +1,283 @@
1
+ import { log } from '@bbigbang/runtime-acp';
2
+ import { parseWorkspaceToolManifest } from './workspaceToolManifest.js';
3
+ import { deriveWorkspaceToolRuntimeState, workspaceToolRuntimeStateToPanelStatus, } from './workspaceToolPanelProjection.js';
4
+ import { WorkspaceToolServiceError } from './workspaceToolErrors.js';
5
+ const DEFAULT_PERSISTENT_MAX_RUN_SECONDS = 24 * 60 * 60;
6
+ const PERSISTENT_RUNTIME_ACTION_TIMEOUT_MS = 120_000;
7
+ const DEFAULT_PERSISTENT_RUNTIME_IDLE_TTL_SECONDS = 900;
8
+ const DEFAULT_WORKSPACE_TOOL_MAX_ACTIVE_PERSISTENT_RUNS_PER_AGENT = 3;
9
+ export class WorkspaceToolTerminalLifecycle {
10
+ deps;
11
+ activeTerminalBuffers;
12
+ activePersistentTimeouts = new Map();
13
+ activePersistentIdleTimeouts = new Map();
14
+ activePersistentStartupCaptureTimeouts = new Map();
15
+ terminatingPersistentTerminals = new Set();
16
+ maxActivePersistentRunsPerAgent;
17
+ constructor(deps, activeTerminalBuffers) {
18
+ this.deps = deps;
19
+ this.activeTerminalBuffers = activeTerminalBuffers;
20
+ this.maxActivePersistentRunsPerAgent =
21
+ typeof deps.maxActivePersistentRunsPerAgent === 'number'
22
+ && Number.isInteger(deps.maxActivePersistentRunsPerAgent)
23
+ && deps.maxActivePersistentRunsPerAgent > 0
24
+ ? deps.maxActivePersistentRunsPerAgent
25
+ : DEFAULT_WORKSPACE_TOOL_MAX_ACTIVE_PERSISTENT_RUNS_PER_AGENT;
26
+ }
27
+ getContext(terminalId) {
28
+ return this.activeTerminalBuffers.get(terminalId);
29
+ }
30
+ hasContext(terminalId) {
31
+ return this.activeTerminalBuffers.has(terminalId);
32
+ }
33
+ setContext(terminalId, ctx) {
34
+ this.activeTerminalBuffers.set(terminalId, ctx);
35
+ }
36
+ deleteContext(terminalId) {
37
+ this.activeTerminalBuffers.delete(terminalId);
38
+ }
39
+ isTerminating(terminalId) {
40
+ return this.terminatingPersistentTerminals.has(terminalId);
41
+ }
42
+ assertAgentPersistentRunQuota(agentId) {
43
+ const row = this.deps.db.prepare(`SELECT COUNT(*) as count
44
+ FROM workspace_tools
45
+ WHERE agent_id = ?
46
+ AND deleted_at IS NULL
47
+ AND active_terminal_id IS NOT NULL`).get(agentId);
48
+ if (row.count >= this.maxActivePersistentRunsPerAgent) {
49
+ throw new WorkspaceToolServiceError(`Agent already has ${this.maxActivePersistentRunsPerAgent} active persistent workspace-tool terminals. Stop one before starting another.`, 409);
50
+ }
51
+ }
52
+ finishActivePersistentRun(toolId, terminalId, completion) {
53
+ const run = this.deps.db.prepare(`SELECT run_id as runId
54
+ FROM workspace_tool_runs
55
+ WHERE tool_id = ?
56
+ AND terminal_id = ?
57
+ AND status = 'running'
58
+ ORDER BY started_at DESC
59
+ LIMIT 1`).get(toolId, terminalId);
60
+ this.deps.db.prepare(`UPDATE workspace_tools
61
+ SET active_terminal_id = NULL,
62
+ updated_at = ?
63
+ WHERE tool_id = ?
64
+ AND active_terminal_id = ?`).run(Date.now(), toolId, terminalId);
65
+ this.deps.onBeforeFinish?.(terminalId, run?.runId ?? null);
66
+ this.activeTerminalBuffers.delete(terminalId);
67
+ this.clearPersistentRunTimeout(terminalId);
68
+ this.clearPersistentIdleTimeout(terminalId);
69
+ this.clearPersistentStartupCaptureTimeout(terminalId);
70
+ if (run) {
71
+ this.deps.runLedger.completeRun(run.runId, {
72
+ status: completion.status,
73
+ endedAt: Date.now(),
74
+ exitCode: completion.exitCode ?? null,
75
+ signal: completion.signal ?? null,
76
+ });
77
+ }
78
+ this.syncStatusBestEffort(toolId, completion);
79
+ }
80
+ reattachActivePersistentTerminal(row, nodeId) {
81
+ let manifest;
82
+ try {
83
+ manifest = parseWorkspaceToolManifest(row.manifestJson);
84
+ }
85
+ catch (error) {
86
+ log.warn('[workspace-tools] active terminal reconcile skipped invalid manifest', {
87
+ toolId: row.toolId,
88
+ terminalId: row.activeTerminalId,
89
+ error: String(error?.message ?? error),
90
+ });
91
+ return false;
92
+ }
93
+ const alreadyBuffered = this.activeTerminalBuffers.has(row.activeTerminalId);
94
+ if (!alreadyBuffered) {
95
+ this.activeTerminalBuffers.set(row.activeTerminalId, {
96
+ toolId: row.toolId,
97
+ runId: row.runningRunId,
98
+ userId: row.userId,
99
+ agentId: row.agentId,
100
+ agentNodeId: nodeId,
101
+ maintenanceConversationId: row.maintenanceConversationId,
102
+ manifest,
103
+ lineBuffer: '',
104
+ });
105
+ }
106
+ const action = row.runningActionId
107
+ ? manifest.actions.find((candidate) => candidate.actionId === row.runningActionId)
108
+ : undefined;
109
+ if (manifest.runtime?.mode !== 'persistent_runtime') {
110
+ this.schedulePersistentRunTimeout({
111
+ toolId: row.toolId,
112
+ agentNodeId: nodeId,
113
+ terminalId: row.activeTerminalId,
114
+ maxRunSeconds: action?.maxRunSeconds,
115
+ startedAt: row.runStartedAt ?? undefined,
116
+ });
117
+ }
118
+ else if (row.runningRunId) {
119
+ this.schedulePersistentRunTimeout({
120
+ toolId: row.toolId,
121
+ agentNodeId: nodeId,
122
+ terminalId: row.activeTerminalId,
123
+ maxRunSeconds: action?.maxRunSeconds ?? (PERSISTENT_RUNTIME_ACTION_TIMEOUT_MS / 1000),
124
+ startedAt: row.runStartedAt ?? undefined,
125
+ });
126
+ }
127
+ else {
128
+ this.clearPersistentRunTimeout(row.activeTerminalId);
129
+ }
130
+ const ctx = this.activeTerminalBuffers.get(row.activeTerminalId);
131
+ if (ctx) {
132
+ ctx.idleTimeoutSeconds = manifest.runtime?.mode === 'persistent_runtime'
133
+ ? manifest.runtime.idleTtlSeconds ?? DEFAULT_PERSISTENT_RUNTIME_IDLE_TTL_SECONDS
134
+ : action?.idleTimeoutSeconds;
135
+ this.resetPersistentIdleTimeout(row.activeTerminalId);
136
+ }
137
+ return !alreadyBuffered;
138
+ }
139
+ async handleTerminalTermination(event) {
140
+ this.terminatingPersistentTerminals.add(event.terminalId);
141
+ try {
142
+ const row = this.deps.db.prepare(`SELECT tool_id as toolId,
143
+ user_id as userId
144
+ FROM workspace_tools
145
+ WHERE active_terminal_id = ?
146
+ AND deleted_at IS NULL
147
+ LIMIT 1`).get(event.terminalId);
148
+ if (!row)
149
+ return;
150
+ await this.deps.enqueueToolMutation(row.toolId, async () => {
151
+ const current = this.deps.db.prepare(`SELECT active_terminal_id as activeTerminalId
152
+ FROM workspace_tools
153
+ WHERE tool_id = ?`).get(row.toolId);
154
+ if (!current?.activeTerminalId || current.activeTerminalId !== event.terminalId) {
155
+ return;
156
+ }
157
+ const status = event.exitCode === 0
158
+ ? 'completed'
159
+ : event.disposed && event.exitCode == null
160
+ ? 'cancelled'
161
+ : 'failed';
162
+ this.finishActivePersistentRun(row.toolId, event.terminalId, {
163
+ status,
164
+ exitCode: event.exitCode ?? null,
165
+ signal: event.signal ?? null,
166
+ });
167
+ });
168
+ }
169
+ finally {
170
+ this.terminatingPersistentTerminals.delete(event.terminalId);
171
+ }
172
+ }
173
+ schedulePersistentRunTimeout(params) {
174
+ this.clearPersistentRunTimeout(params.terminalId);
175
+ const seconds = params.maxRunSeconds ?? DEFAULT_PERSISTENT_MAX_RUN_SECONDS;
176
+ const elapsedMs = params.startedAt ? Math.max(0, Date.now() - params.startedAt) : 0;
177
+ const timeoutMs = Math.max(0, (seconds * 1000) - elapsedMs);
178
+ const handle = setTimeout(() => {
179
+ void Promise.resolve(this.handlePersistentRunTimeout({
180
+ toolId: params.toolId,
181
+ agentNodeId: params.agentNodeId,
182
+ terminalId: params.terminalId,
183
+ signal: 'timeout',
184
+ })).catch(() => {
185
+ // Timeouts are best-effort; the runner logs its own failures.
186
+ });
187
+ }, timeoutMs);
188
+ handle.unref?.();
189
+ this.activePersistentTimeouts.set(params.terminalId, handle);
190
+ }
191
+ resetPersistentIdleTimeout(terminalId) {
192
+ const ctx = this.activeTerminalBuffers.get(terminalId);
193
+ if (!ctx?.idleTimeoutSeconds) {
194
+ this.clearPersistentIdleTimeout(terminalId);
195
+ return;
196
+ }
197
+ this.schedulePersistentIdleTimeout({
198
+ toolId: ctx.toolId,
199
+ agentNodeId: ctx.agentNodeId,
200
+ terminalId,
201
+ idleTimeoutSeconds: ctx.idleTimeoutSeconds,
202
+ });
203
+ }
204
+ schedulePersistentIdleTimeout(params) {
205
+ this.clearPersistentIdleTimeout(params.terminalId);
206
+ const handle = setTimeout(() => {
207
+ void Promise.resolve(this.handlePersistentRunTimeout({
208
+ toolId: params.toolId,
209
+ agentNodeId: params.agentNodeId,
210
+ terminalId: params.terminalId,
211
+ signal: 'idle_timeout',
212
+ })).catch(() => {
213
+ // Timeouts are best-effort; the runner logs its own failures.
214
+ });
215
+ }, params.idleTimeoutSeconds * 1000);
216
+ handle.unref?.();
217
+ this.activePersistentIdleTimeouts.set(params.terminalId, handle);
218
+ }
219
+ clearPersistentRunTimeout(terminalId) {
220
+ const handle = this.activePersistentTimeouts.get(terminalId);
221
+ if (!handle)
222
+ return;
223
+ clearTimeout(handle);
224
+ this.activePersistentTimeouts.delete(terminalId);
225
+ }
226
+ clearPersistentIdleTimeout(terminalId) {
227
+ const handle = this.activePersistentIdleTimeouts.get(terminalId);
228
+ if (!handle)
229
+ return;
230
+ clearTimeout(handle);
231
+ this.activePersistentIdleTimeouts.delete(terminalId);
232
+ }
233
+ clearPersistentStartupCaptureTimeout(terminalId) {
234
+ const handle = this.activePersistentStartupCaptureTimeouts.get(terminalId);
235
+ if (!handle)
236
+ return;
237
+ clearTimeout(handle);
238
+ this.activePersistentStartupCaptureTimeouts.delete(terminalId);
239
+ }
240
+ schedulePersistentStartupSnapshotFallback(params) {
241
+ this.clearPersistentStartupCaptureTimeout(params.terminalId);
242
+ const timer = setTimeout(() => {
243
+ this.activePersistentStartupCaptureTimeouts.delete(params.terminalId);
244
+ void Promise.resolve(params.onFallback()).catch(() => {
245
+ // Fallback is best-effort.
246
+ });
247
+ }, params.delayMs);
248
+ timer.unref?.();
249
+ this.activePersistentStartupCaptureTimeouts.set(params.terminalId, timer);
250
+ }
251
+ async handlePersistentRunTimeout(params) {
252
+ await this.deps.enqueueToolMutation(params.toolId, async () => {
253
+ const current = this.deps.db.prepare(`SELECT active_terminal_id as activeTerminalId
254
+ FROM workspace_tools
255
+ WHERE tool_id = ?
256
+ AND deleted_at IS NULL`).get(params.toolId);
257
+ if (!current?.activeTerminalId || current.activeTerminalId !== params.terminalId) {
258
+ this.clearPersistentRunTimeout(params.terminalId);
259
+ this.clearPersistentIdleTimeout(params.terminalId);
260
+ return;
261
+ }
262
+ this.clearPersistentRunTimeout(params.terminalId);
263
+ this.clearPersistentIdleTimeout(params.terminalId);
264
+ await this.deps.terminalBroker.closeTerminal(params.agentNodeId, params.terminalId);
265
+ this.finishActivePersistentRun(params.toolId, params.terminalId, {
266
+ status: 'timed_out',
267
+ signal: params.signal ?? 'timeout',
268
+ });
269
+ });
270
+ }
271
+ syncStatusBestEffort(toolId, completion) {
272
+ this.deps.latestStateProjector.syncStatusBestEffort(toolId, workspaceToolRuntimeStateToPanelStatus(deriveWorkspaceToolRuntimeState({
273
+ activeTerminalId: null,
274
+ lastRunStatus: completion.status,
275
+ })));
276
+ }
277
+ }
278
+ export function persistentRuntimeActionTimeoutMs(action) {
279
+ return action.maxRunSeconds ? action.maxRunSeconds * 1000 : PERSISTENT_RUNTIME_ACTION_TIMEOUT_MS;
280
+ }
281
+ export function defaultPersistentRuntimeIdleTtlSeconds() {
282
+ return DEFAULT_PERSISTENT_RUNTIME_IDLE_TTL_SECONDS;
283
+ }
@@ -0,0 +1 @@
1
+ export {};