@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,255 @@
1
+ import { buildWorkspaceToolPanelActionParamKey, collectPanelLevelUnsupportedNodes, collectPanelTemplateUnsupportedNodes, mapPanelLevelNodes, panelLevelNodeContainsEmptyLayoutContainer, } from '@bbigbang/protocol';
2
+ import { parseWorkspaceToolManifest, resolveWorkspaceToolActionParamBinding } from './workspaceToolManifest.js';
3
+ import { buildToolActionParameterFormSectionForActions } from './workspaceToolPanelProjection.js';
4
+ export function hasRowTemplateGridPromotionSeed(props) {
5
+ return isPlainRecord(props)
6
+ && Array.isArray(props.fields)
7
+ && isPlainRecord(props.template);
8
+ }
9
+ function resolveDirectPromotedPanelActionMode(action) {
10
+ if (action.mode)
11
+ return action.mode;
12
+ return action.command?.trim() || action.rpcCommand?.trim() ? 'platform_exec' : 'notify_agent';
13
+ }
14
+ const DIRECT_TOOL_PROMOTION_UNSUPPORTED_PANEL_LEVEL_NODE_TYPES = new Set([
15
+ 'DatePicker',
16
+ 'MultiSelect',
17
+ 'TagInput',
18
+ 'FileUpload',
19
+ 'Button',
20
+ ]);
21
+ const DIRECT_TOOL_PROMOTION_INTERACTIVE_PANEL_LEVEL_NODE_TYPES = new Set([
22
+ 'TextInput',
23
+ 'TextArea',
24
+ 'NumberInput',
25
+ 'Checkbox',
26
+ 'Select',
27
+ 'MultiSelect',
28
+ 'TagInput',
29
+ 'Slider',
30
+ 'DatePicker',
31
+ 'FileUpload',
32
+ 'Button',
33
+ 'ActionBar',
34
+ ]);
35
+ const DIRECT_TOOL_PROMOTION_UNSUPPORTED_TEMPLATE_NODE_TYPES = new Set([
36
+ 'FormField',
37
+ 'SubmitButton',
38
+ ]);
39
+ function panelLevelNodeRequiresAgentAssistedToolPromotion(node, options) {
40
+ if (!isPlainRecord(node) || typeof node.type !== 'string')
41
+ return false;
42
+ return collectPanelLevelUnsupportedNodes(node, {
43
+ unsupportedTypes: DIRECT_TOOL_PROMOTION_UNSUPPORTED_PANEL_LEVEL_NODE_TYPES,
44
+ interactiveTypes: DIRECT_TOOL_PROMOTION_INTERACTIVE_PANEL_LEVEL_NODE_TYPES,
45
+ allowInteractiveTypes: options?.allowToolParameterNodes === true,
46
+ }).length > 0;
47
+ }
48
+ function panelTemplateNodeRequiresAgentAssistedToolPromotion(node) {
49
+ if (!isPlainRecord(node) || typeof node.type !== 'string')
50
+ return false;
51
+ return collectPanelTemplateUnsupportedNodes(node, {
52
+ unsupportedTypes: DIRECT_TOOL_PROMOTION_UNSUPPORTED_TEMPLATE_NODE_TYPES,
53
+ predicate: (candidate) => ((candidate.type === 'ImageSlot' || candidate.type === 'MediaBlock')
54
+ && candidate.editable === true),
55
+ }).length > 0;
56
+ }
57
+ function panelPropsRequireAgentAssistedToolPromotion(props) {
58
+ return panelLevelNodeRequiresAgentAssistedToolPromotion(props.summary)
59
+ || panelTemplateNodeRequiresAgentAssistedToolPromotion(props.template);
60
+ }
61
+ function buildDirectPromotedParameterBindingIndex(actions) {
62
+ const index = new Map();
63
+ for (const action of actions) {
64
+ for (const field of action.paramsSchema ?? []) {
65
+ const existing = index.get(field.name);
66
+ if (existing && existing !== 'ambiguous') {
67
+ index.set(field.name, 'ambiguous');
68
+ }
69
+ else if (!existing) {
70
+ index.set(field.name, { actionId: action.actionId, field });
71
+ }
72
+ }
73
+ }
74
+ return index;
75
+ }
76
+ function translateDirectPromotedParameterForm(node, actions) {
77
+ if (!node)
78
+ return null;
79
+ const paramIndex = buildDirectPromotedParameterBindingIndex(actions);
80
+ const actionIds = new Set(actions.map((action) => action.actionId));
81
+ const actionsById = new Map(actions.map((action) => [action.actionId, action]));
82
+ return translateDirectPromotedParameterFormNode(node, actions, paramIndex, actionIds, actionsById);
83
+ }
84
+ function translateDirectPromotedParameterFormNode(node, actions, paramIndex, actionIds, actionsById) {
85
+ if (!isPlainRecord(node) || typeof node.type !== 'string')
86
+ return null;
87
+ if (panelLevelNodeContainsEmptyLayoutContainer(node))
88
+ return null;
89
+ let failed = false;
90
+ const translated = mapPanelLevelNodes(node, (candidate) => {
91
+ if (DIRECT_TOOL_PROMOTION_UNSUPPORTED_PANEL_LEVEL_NODE_TYPES.has(candidate.type)) {
92
+ failed = true;
93
+ return null;
94
+ }
95
+ switch (candidate.type) {
96
+ case 'ActionBar': {
97
+ if (Array.isArray(candidate.actionIds) && candidate.actionIds.some((actionId) => typeof actionId !== 'string' || !actionIds.has(actionId))) {
98
+ failed = true;
99
+ return null;
100
+ }
101
+ return undefined;
102
+ }
103
+ case 'TextInput':
104
+ case 'TextArea':
105
+ case 'NumberInput':
106
+ case 'Checkbox':
107
+ case 'Select':
108
+ case 'Slider': {
109
+ if (!isPlainRecord(candidate.props)) {
110
+ failed = true;
111
+ return null;
112
+ }
113
+ const translatedName = translateDirectPromotedParameterControlName(candidate.props.name, candidate.type, actions, paramIndex, actionsById);
114
+ if (!translatedName) {
115
+ failed = true;
116
+ return null;
117
+ }
118
+ return {
119
+ ...candidate,
120
+ props: {
121
+ ...candidate.props,
122
+ name: translatedName,
123
+ },
124
+ };
125
+ }
126
+ default:
127
+ return undefined;
128
+ }
129
+ });
130
+ return failed ? null : translated;
131
+ }
132
+ function translateDirectPromotedParameterControlName(rawName, nodeType, actions, paramIndex, actionsById) {
133
+ if (typeof rawName !== 'string' || !rawName.trim())
134
+ return null;
135
+ const existingBinding = resolveWorkspaceToolActionParamBinding(actions, rawName);
136
+ if (existingBinding) {
137
+ const action = actionsById.get(existingBinding.actionId);
138
+ if (!action)
139
+ return null;
140
+ const field = (action.paramsSchema ?? []).find((candidate) => candidate.name === existingBinding.paramName);
141
+ if (!field)
142
+ return null;
143
+ if (!isToolParameterFormNodeTypeCompatible(nodeType, field.input ?? 'text'))
144
+ return null;
145
+ return buildWorkspaceToolPanelActionParamKey(existingBinding.actionId, existingBinding.paramName);
146
+ }
147
+ const binding = paramIndex.get(rawName);
148
+ if (!binding || binding === 'ambiguous')
149
+ return null;
150
+ if (!isToolParameterFormNodeTypeCompatible(nodeType, binding.field.input ?? 'text'))
151
+ return null;
152
+ return buildWorkspaceToolPanelActionParamKey(binding.actionId, binding.field.name);
153
+ }
154
+ function isToolParameterFormNodeTypeCompatible(nodeType, input) {
155
+ if (input === 'checkbox')
156
+ return nodeType === 'Checkbox';
157
+ if (input === 'number')
158
+ return nodeType === 'NumberInput' || nodeType === 'Slider';
159
+ if (input === 'select')
160
+ return nodeType === 'Select';
161
+ return nodeType === 'TextInput' || nodeType === 'TextArea';
162
+ }
163
+ export function buildDirectPromotedToolManifest(panel) {
164
+ if (panel.component !== 'RowTemplateGrid' || !hasRowTemplateGridPromotionSeed(panel.props))
165
+ return null;
166
+ const props = panel.props;
167
+ if (!props)
168
+ return null;
169
+ if (panelPropsRequireAgentAssistedToolPromotion(props))
170
+ return null;
171
+ const actionIds = new Set();
172
+ const actions = [];
173
+ let hasPlatformExecAction = false;
174
+ for (const action of panel.actions ?? []) {
175
+ if (!action.id?.trim() || !action.label?.trim() || actionIds.has(action.id))
176
+ return null;
177
+ actionIds.add(action.id);
178
+ const mode = resolveDirectPromotedPanelActionMode(action);
179
+ if (mode === 'platform_exec') {
180
+ const command = action.command?.trim() || action.rpcCommand?.trim() || '';
181
+ const toolKind = action.toolKind ?? (action.persistent === true ? 'start' : 'custom');
182
+ if (!command && toolKind !== 'stop')
183
+ return null;
184
+ const cwd = action.cwd?.trim() || action.rpcCwd?.trim() || '';
185
+ hasPlatformExecAction = true;
186
+ actions.push({
187
+ actionId: action.id,
188
+ label: action.label,
189
+ description: action.description,
190
+ kind: toolKind,
191
+ mode: 'platform_exec',
192
+ ...(command ? { command } : {}),
193
+ ...(cwd ? { cwd } : {}),
194
+ ...(typeof action.persistent === 'boolean' ? { persistent: action.persistent } : {}),
195
+ ...(typeof action.maxRunSeconds === 'number' ? { maxRunSeconds: action.maxRunSeconds } : {}),
196
+ ...(typeof action.idleTimeoutSeconds === 'number' ? { idleTimeoutSeconds: action.idleTimeoutSeconds } : {}),
197
+ ...(Array.isArray(action.paramsSchema) ? { paramsSchema: action.paramsSchema } : {}),
198
+ });
199
+ continue;
200
+ }
201
+ actions.push({
202
+ actionId: action.id,
203
+ label: action.label,
204
+ description: action.description,
205
+ kind: 'custom',
206
+ mode: 'notify_agent',
207
+ });
208
+ }
209
+ const translatedParameterForm = props.parameterForm
210
+ ? translateDirectPromotedParameterForm(props.parameterForm, actions)
211
+ : null;
212
+ if (props.parameterForm && !translatedParameterForm)
213
+ return null;
214
+ const fallbackParameterForm = translatedParameterForm
215
+ ?? buildToolActionParameterFormSectionForActions(actions);
216
+ const title = typeof props.title === 'string' && props.title.trim()
217
+ ? props.title.trim()
218
+ : 'Panel Tool';
219
+ const summary = isPlainRecord(panel.result) && typeof panel.result.summary === 'string'
220
+ ? panel.result.summary.trim()
221
+ : '';
222
+ const baseSlug = title
223
+ .trim()
224
+ .toLowerCase()
225
+ .replace(/[^a-z0-9_.-]+/g, '-')
226
+ .replace(/^-+|-+$/g, '')
227
+ .slice(0, 48);
228
+ const shortPanelId = panel.id.replace(/[^a-zA-Z0-9]/g, '').slice(0, 8).toLowerCase() || 'panel';
229
+ const manifest = {
230
+ slug: `${baseSlug || 'panel'}-${shortPanelId}`,
231
+ name: title,
232
+ description: summary || `Static tool promoted from panel ${panel.id}.`,
233
+ ...(hasPlatformExecAction ? { defaultCwd: '.' } : {}),
234
+ actions,
235
+ view: {
236
+ title,
237
+ fields: props.fields,
238
+ mediaSlots: Array.isArray(props.mediaSlots)
239
+ ? props.mediaSlots
240
+ : [],
241
+ ...(fallbackParameterForm ? { parameterForm: fallbackParameterForm } : {}),
242
+ template: props.template,
243
+ },
244
+ };
245
+ try {
246
+ parseWorkspaceToolManifest(JSON.stringify(manifest));
247
+ }
248
+ catch {
249
+ return null;
250
+ }
251
+ return manifest;
252
+ }
253
+ function isPlainRecord(value) {
254
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
255
+ }
@@ -0,0 +1,224 @@
1
+ const CREATE_NEW_TOOL_UNAVAILABLE_MESSAGE = 'Workspace tools can only be created from a /tool conversation or a tool maintenance chat; ordinary surfaces may only republish an existing same-agent tool.';
2
+ const DIFFERENT_LIVE_TOOL_SLUG_MESSAGE = 'A different tool already uses this slug. Rename the linked tool or pick another slug.';
3
+ const TOOL_LINKED_TO_DIFFERENT_SOURCE_PANEL_MESSAGE = 'This tool is already linked to a different source panel.';
4
+ const SOURCE_PANEL_LINKED_TO_DIFFERENT_LIVE_TOOL_MESSAGE = 'This panel is already linked to a different live tool.';
5
+ export function isDeletedLinkedToolPromotionRetryable(promotion) {
6
+ return Boolean(promotion
7
+ && !promotion.toolId
8
+ && promotion.linkedToolId
9
+ && promotion.linkedToolDeletedAt
10
+ && promotion.updatedAt <= promotion.linkedToolDeletedAt);
11
+ }
12
+ export function resolveWorkspaceToolPromotionState(input) {
13
+ if (input.promotion?.toolId) {
14
+ const archived = Boolean(input.panelArchivedAt);
15
+ return {
16
+ state: archived ? 'source_archived_linked_live_tool' : 'linked_live_tool',
17
+ stateReason: archived
18
+ ? 'Source panel is archived after promotion; linked Workspace Tool is live.'
19
+ : 'Panel is linked to a live Workspace Tool.',
20
+ ctaLabel: 'Edit Tool',
21
+ disabledReason: null,
22
+ shouldOpenLinkedTool: true,
23
+ shouldCreateDirectTool: false,
24
+ shouldCreateAssistedPromotion: false,
25
+ shouldReturnPendingPromotion: false,
26
+ shouldRequeueDeletedPromotion: false,
27
+ };
28
+ }
29
+ if (isDeletedLinkedToolPromotionRetryable(input.promotion)) {
30
+ const retryUnavailableReason = input.retryUnavailableReason?.trim();
31
+ if (retryUnavailableReason) {
32
+ return {
33
+ state: 'ineligible',
34
+ stateReason: retryUnavailableReason,
35
+ ctaLabel: 'Retry Tool Upgrade',
36
+ disabledReason: retryUnavailableReason,
37
+ shouldOpenLinkedTool: false,
38
+ shouldCreateDirectTool: false,
39
+ shouldCreateAssistedPromotion: false,
40
+ shouldReturnPendingPromotion: false,
41
+ shouldRequeueDeletedPromotion: false,
42
+ };
43
+ }
44
+ }
45
+ if (isDeletedLinkedToolPromotionRetryable(input.promotion)) {
46
+ return {
47
+ state: 'linked_tool_deleted_retryable',
48
+ stateReason: 'The linked Workspace Tool was deleted; the promotion can be retried in the original conversation.',
49
+ ctaLabel: 'Retry Tool Upgrade',
50
+ disabledReason: null,
51
+ shouldOpenLinkedTool: false,
52
+ shouldCreateDirectTool: false,
53
+ shouldCreateAssistedPromotion: false,
54
+ shouldReturnPendingPromotion: false,
55
+ shouldRequeueDeletedPromotion: true,
56
+ };
57
+ }
58
+ if (input.promotion) {
59
+ return {
60
+ state: 'agent_assisted_pending',
61
+ stateReason: 'A Workspace Tool upgrade conversation already exists for this panel.',
62
+ ctaLabel: 'Continue Upgrade',
63
+ disabledReason: null,
64
+ shouldOpenLinkedTool: false,
65
+ shouldCreateDirectTool: false,
66
+ shouldCreateAssistedPromotion: false,
67
+ shouldReturnPendingPromotion: true,
68
+ shouldRequeueDeletedPromotion: false,
69
+ };
70
+ }
71
+ if (!input.eligible) {
72
+ const reason = input.ineligibleReason?.trim() || 'This panel cannot be upgraded to a tool.';
73
+ return {
74
+ state: 'ineligible',
75
+ stateReason: reason,
76
+ ctaLabel: null,
77
+ disabledReason: reason,
78
+ shouldOpenLinkedTool: false,
79
+ shouldCreateDirectTool: false,
80
+ shouldCreateAssistedPromotion: false,
81
+ shouldReturnPendingPromotion: false,
82
+ shouldRequeueDeletedPromotion: false,
83
+ };
84
+ }
85
+ if (input.directPromotable) {
86
+ return {
87
+ state: 'direct_promotable',
88
+ stateReason: 'Panel can be upgraded directly without an agent run.',
89
+ ctaLabel: 'Upgrade to Tool',
90
+ disabledReason: null,
91
+ shouldOpenLinkedTool: false,
92
+ shouldCreateDirectTool: true,
93
+ shouldCreateAssistedPromotion: false,
94
+ shouldReturnPendingPromotion: false,
95
+ shouldRequeueDeletedPromotion: false,
96
+ };
97
+ }
98
+ return {
99
+ state: 'eligible_unlinked',
100
+ stateReason: 'Panel can be upgraded through an agent-assisted Workspace Tool conversation.',
101
+ ctaLabel: 'Upgrade to Tool',
102
+ disabledReason: null,
103
+ shouldOpenLinkedTool: false,
104
+ shouldCreateDirectTool: false,
105
+ shouldCreateAssistedPromotion: true,
106
+ shouldReturnPendingPromotion: false,
107
+ shouldRequeueDeletedPromotion: false,
108
+ };
109
+ }
110
+ export function resolveWorkspaceToolPublishIdentityState(input) {
111
+ const linkedTool = input.linkedTool ?? null;
112
+ const existingByMaintenanceConversation = input.existingByMaintenanceConversation ?? null;
113
+ const existingByPanelId = input.existingByPanelId ?? null;
114
+ const existingBySlug = input.existingBySlug ?? null;
115
+ const existingByCollaboratorSlug = input.existingByCollaboratorSlug ?? null;
116
+ const liveExistingBySlug = existingBySlug && existingBySlug.deletedAt == null
117
+ ? existingBySlug
118
+ : null;
119
+ const existingIdentity = linkedTool
120
+ ?? existingByMaintenanceConversation
121
+ ?? existingByPanelId
122
+ ?? liveExistingBySlug
123
+ ?? existingByCollaboratorSlug
124
+ ?? null;
125
+ const existing = existingIdentity ?? liveExistingBySlug;
126
+ const restoredPanelId = linkedTool?.sourcePanelId
127
+ ?? existingByMaintenanceConversation?.sourcePanelId
128
+ ?? existingByPanelId?.sourcePanelId
129
+ ?? null;
130
+ const nextSourcePanelId = input.promotionLink?.panelId ?? restoredPanelId ?? null;
131
+ const queueToolId = input.promotionLink?.toolId
132
+ ?? linkedTool?.toolId
133
+ ?? existingByMaintenanceConversation?.toolId
134
+ ?? existingByPanelId?.toolId
135
+ ?? existingBySlug?.toolId
136
+ ?? existingByCollaboratorSlug?.toolId
137
+ ?? null;
138
+ const conflict = resolveWorkspaceToolPublishIdentityConflict({
139
+ allowCreateNewTool: input.allowCreateNewTool,
140
+ promotionLink: input.promotionLink,
141
+ existingIdentity,
142
+ existing,
143
+ existingBySlug: liveExistingBySlug,
144
+ existingByPanelId,
145
+ });
146
+ if (conflict) {
147
+ return {
148
+ state: 'blocked',
149
+ stateReason: conflict.message,
150
+ conflict,
151
+ existingIdentity,
152
+ existing,
153
+ restoredPanelId,
154
+ nextSourcePanelId,
155
+ queueToolId,
156
+ shouldCreateNewTool: false,
157
+ shouldUpdateExistingTool: false,
158
+ };
159
+ }
160
+ if (existingIdentity?.toolId) {
161
+ return {
162
+ state: 'update_existing_tool',
163
+ stateReason: 'Publish updates an existing Workspace Tool identity.',
164
+ conflict: null,
165
+ existingIdentity,
166
+ existing,
167
+ restoredPanelId,
168
+ nextSourcePanelId,
169
+ queueToolId,
170
+ shouldCreateNewTool: false,
171
+ shouldUpdateExistingTool: true,
172
+ };
173
+ }
174
+ return {
175
+ state: 'create_new_tool',
176
+ stateReason: 'Publish creates a new Workspace Tool identity.',
177
+ conflict: null,
178
+ existingIdentity,
179
+ existing,
180
+ restoredPanelId,
181
+ nextSourcePanelId,
182
+ queueToolId,
183
+ shouldCreateNewTool: true,
184
+ shouldUpdateExistingTool: false,
185
+ };
186
+ }
187
+ function resolveWorkspaceToolPublishIdentityConflict(params) {
188
+ if (!params.existingIdentity?.toolId && params.allowCreateNewTool === false) {
189
+ return {
190
+ kind: 'create_new_tool_unavailable',
191
+ message: CREATE_NEW_TOOL_UNAVAILABLE_MESSAGE,
192
+ statusCode: 409,
193
+ };
194
+ }
195
+ if (params.existingIdentity?.toolId
196
+ && params.existingBySlug?.toolId
197
+ && params.existingBySlug.toolId !== params.existingIdentity.toolId) {
198
+ return {
199
+ kind: 'different_live_tool_slug',
200
+ message: DIFFERENT_LIVE_TOOL_SLUG_MESSAGE,
201
+ statusCode: 409,
202
+ };
203
+ }
204
+ if (params.promotionLink?.panelId
205
+ && params.existing?.sourcePanelId
206
+ && params.existing.sourcePanelId !== params.promotionLink.panelId) {
207
+ return {
208
+ kind: 'tool_linked_to_different_source_panel',
209
+ message: TOOL_LINKED_TO_DIFFERENT_SOURCE_PANEL_MESSAGE,
210
+ statusCode: 409,
211
+ };
212
+ }
213
+ if (params.existingIdentity?.toolId
214
+ && params.existingByPanelId
215
+ && params.existingByPanelId.deletedAt == null
216
+ && params.existingByPanelId.toolId !== params.existingIdentity.toolId) {
217
+ return {
218
+ kind: 'source_panel_linked_to_different_live_tool',
219
+ message: SOURCE_PANEL_LINKED_TO_DIFFERENT_LIVE_TOOL_MESSAGE,
220
+ statusCode: 409,
221
+ };
222
+ }
223
+ return null;
224
+ }
@@ -0,0 +1,189 @@
1
+ import { walkPanelLevelNodes, } from '@bbigbang/protocol';
2
+ import { actionRequiresCommand, resolveWorkspaceToolActionMode } from './workspaceToolManifest.js';
3
+ const SHELL_COMPLEXITY_PATTERNS = [
4
+ { code: 'heredoc', pattern: /<<[-~]?\s*['"]?[A-Za-z0-9_]+['"]?/, label: 'heredoc' },
5
+ { code: 'background_process', pattern: /(^|[;&|]\s*)(nohup|setsid)\b|\b(disown|daemonize)\b|--daemon\b|&\s*(?:$|[#;])/, label: 'background/daemon process control' },
6
+ { code: 'pid_file', pattern: /\b(pidfile|pid_file|PIDFILE)\b|\.pid\b|\bkill\s+-?\w*\s+/, label: 'PID file or kill management' },
7
+ ];
8
+ export function buildWorkspaceToolPublishDiagnostics(manifest) {
9
+ const diagnostics = [];
10
+ const commandActions = manifest.actions.filter(isCommandPlatformAction);
11
+ const preferCliActionIds = new Set();
12
+ if (commandActions.length >= 3) {
13
+ for (const action of commandActions)
14
+ preferCliActionIds.add(action.actionId);
15
+ }
16
+ for (const action of commandActions) {
17
+ const command = action.command?.trim() ?? '';
18
+ const complexity = detectShellComplexity(command);
19
+ if (complexity) {
20
+ preferCliActionIds.add(action.actionId);
21
+ diagnostics.push({
22
+ severity: 'warning',
23
+ code: 'complex_command_shell',
24
+ message: `Action "${action.actionId}" uses ${complexity}; prefer moving shell orchestration into a bundle-local CLI wrapper.`,
25
+ actionIds: [action.actionId],
26
+ });
27
+ }
28
+ }
29
+ const persistentStartActions = manifest.actions.filter((action) => (resolveWorkspaceToolActionMode(action) === 'platform_exec'
30
+ && action.kind === 'start'
31
+ && action.persistent === true));
32
+ for (const action of persistentStartActions) {
33
+ if (!hasReliableStatusAction(manifest) || !hasStopAction(manifest)) {
34
+ if (isCommandPlatformAction(action)) {
35
+ preferCliActionIds.add(action.actionId);
36
+ }
37
+ diagnostics.push({
38
+ severity: 'warning',
39
+ code: 'persistent_lifecycle_incomplete',
40
+ message: `Persistent start action "${action.actionId}" should have reliable status and stop actions so the platform can explain and recover long-running processes.`,
41
+ actionIds: [action.actionId],
42
+ });
43
+ }
44
+ }
45
+ const repeatedEntryDiagnostics = buildRepeatedEntrypointDiagnostics(commandActions);
46
+ diagnostics.push(...repeatedEntryDiagnostics);
47
+ for (const diagnostic of repeatedEntryDiagnostics) {
48
+ for (const actionId of diagnostic.actionIds ?? [])
49
+ preferCliActionIds.add(actionId);
50
+ }
51
+ diagnostics.push(...buildAuthoringExperienceDiagnostics(manifest));
52
+ if (preferCliActionIds.size > 0) {
53
+ const actionIds = commandActions
54
+ .map((action) => action.actionId)
55
+ .filter((actionId) => preferCliActionIds.has(actionId));
56
+ diagnostics.unshift({
57
+ severity: 'warning',
58
+ code: 'prefer_cli_entry',
59
+ message: manifest.cli?.entry?.trim()
60
+ ? 'This manifest already declares cli.entry, but some actions still use direct commands. Prefer commandless actions for shared CLI-backed behavior.'
61
+ : 'This tool has a larger, complex, or repeated command-backed control surface. Prefer commandless actions backed by a bundle-local cli.entry wrapper so actions share one stable backend contract.',
62
+ actionIds,
63
+ });
64
+ }
65
+ return dedupeDiagnostics(diagnostics);
66
+ }
67
+ function buildAuthoringExperienceDiagnostics(manifest) {
68
+ const diagnostics = [];
69
+ const paramActions = manifest.actions.filter((action) => (action.paramsSchema?.length ?? 0) > 0);
70
+ const totalParams = paramActions.reduce((sum, action) => sum + (action.paramsSchema?.length ?? 0), 0);
71
+ if (!manifest.view.parameterForm && (paramActions.length >= 2 || totalParams >= 4)) {
72
+ diagnostics.push({
73
+ severity: 'warning',
74
+ code: 'explicit_parameter_form_recommended',
75
+ message: 'This tool has multiple action parameter groups or several parameters. Add view.parameterForm to control grouping, density, and action placement instead of relying on the generated single-column fallback.',
76
+ actionIds: paramActions.map((action) => action.actionId),
77
+ });
78
+ }
79
+ const runtimeActionIds = manifest.actions
80
+ .filter((action) => isRuntimeSummaryCandidate(manifest, action))
81
+ .map((action) => action.actionId);
82
+ if (runtimeActionIds.length > 0 && !summaryContainsRuntimeReadWidgets(manifest)) {
83
+ diagnostics.push({
84
+ severity: 'warning',
85
+ code: 'runtime_summary_widgets_recommended',
86
+ message: 'Long-running or persistent tools should add Tool-only RunStatusCard and RunLogViewer widgets in view.summary so users can see bounded run state and event tails without opening full run history.',
87
+ actionIds: runtimeActionIds,
88
+ });
89
+ }
90
+ return diagnostics;
91
+ }
92
+ function isRuntimeSummaryCandidate(manifest, action) {
93
+ if (manifest.runtime?.mode === 'persistent_runtime')
94
+ return true;
95
+ if (action.persistent === true)
96
+ return true;
97
+ if (typeof action.maxRunSeconds === 'number' && Number.isFinite(action.maxRunSeconds) && action.maxRunSeconds >= 60)
98
+ return true;
99
+ return false;
100
+ }
101
+ function summaryContainsRuntimeReadWidgets(manifest) {
102
+ let hasStatus = false;
103
+ let hasLog = false;
104
+ walkPanelLevelNodes(manifest.view.summary, (node) => {
105
+ if (node.type === 'RunStatusCard')
106
+ hasStatus = true;
107
+ if (node.type === 'RunLogViewer')
108
+ hasLog = true;
109
+ if (hasStatus && hasLog) {
110
+ return false;
111
+ }
112
+ return undefined;
113
+ });
114
+ return hasStatus && hasLog;
115
+ }
116
+ function isCommandPlatformAction(action) {
117
+ return resolveWorkspaceToolActionMode(action) === 'platform_exec'
118
+ && Boolean(action.command?.trim());
119
+ }
120
+ function detectShellComplexity(command) {
121
+ for (const { pattern, label } of SHELL_COMPLEXITY_PATTERNS) {
122
+ if (pattern.test(command))
123
+ return label;
124
+ }
125
+ const chainMatches = command.match(/&&|\|\||;/g) ?? [];
126
+ if (chainMatches.length >= 2)
127
+ return 'multi-step shell chaining';
128
+ if (command.length > 240)
129
+ return 'a long inline command';
130
+ return null;
131
+ }
132
+ function hasReliableStatusAction(manifest) {
133
+ return manifest.actions.some((action) => (resolveWorkspaceToolActionMode(action) === 'platform_exec'
134
+ && action.kind === 'status'
135
+ && (Boolean(action.command?.trim()) || Boolean(manifest.cli?.entry?.trim()))));
136
+ }
137
+ function hasStopAction(manifest) {
138
+ return manifest.actions.some((action) => (resolveWorkspaceToolActionMode(action) === 'platform_exec'
139
+ && action.kind === 'stop'
140
+ && (!actionRequiresCommand(action) || Boolean(action.command?.trim()) || Boolean(manifest.cli?.entry?.trim()))));
141
+ }
142
+ function buildRepeatedEntrypointDiagnostics(commandActions) {
143
+ const actionsByEntry = new Map();
144
+ for (const action of commandActions) {
145
+ const entry = extractScriptEntrypoint(action.command ?? '');
146
+ if (!entry)
147
+ continue;
148
+ const actions = actionsByEntry.get(entry) ?? [];
149
+ actions.push(action);
150
+ actionsByEntry.set(entry, actions);
151
+ }
152
+ const diagnostics = [];
153
+ for (const [entry, actions] of actionsByEntry) {
154
+ if (actions.length < 2)
155
+ continue;
156
+ diagnostics.push({
157
+ severity: 'warning',
158
+ code: 'repeated_entrypoint_commands',
159
+ message: `Multiple actions invoke "${entry}" directly. Prefer one cli.entry wrapper and commandless actions for shared backend behavior.`,
160
+ actionIds: actions.map((action) => action.actionId),
161
+ });
162
+ }
163
+ return diagnostics;
164
+ }
165
+ function extractScriptEntrypoint(command) {
166
+ const normalized = command.trim();
167
+ const interpreterMatch = normalized.match(/(?:^|[\s;&|])(?:python3?|node|tsx|bun)\s+(['"]?)([^'"\s]+)\1/);
168
+ if (interpreterMatch?.[2])
169
+ return stripShellRelativePrefix(interpreterMatch[2]);
170
+ const scriptMatch = normalized.match(/(?:^|[\s;&|])(['"]?)([^'"\s]+\.(?:py|js|mjs|cjs|ts))\1(?:\s|$)/);
171
+ if (scriptMatch?.[2])
172
+ return stripShellRelativePrefix(scriptMatch[2]);
173
+ return null;
174
+ }
175
+ function stripShellRelativePrefix(entry) {
176
+ return entry.replace(/^\.\//, '');
177
+ }
178
+ function dedupeDiagnostics(diagnostics) {
179
+ const seen = new Set();
180
+ const deduped = [];
181
+ for (const diagnostic of diagnostics) {
182
+ const key = `${diagnostic.code}:${(diagnostic.actionIds ?? []).join(',')}:${diagnostic.message}`;
183
+ if (seen.has(key))
184
+ continue;
185
+ seen.add(key);
186
+ deduped.push(diagnostic);
187
+ }
188
+ return deduped;
189
+ }