@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,161 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ export class WorkbenchGitBroker {
3
+ statusPending = new Map();
4
+ diffPending = new Map();
5
+ actionPending = new Map();
6
+ nodeRegistry;
7
+ statusTimeoutMs;
8
+ diffTimeoutMs;
9
+ actionTimeoutMs;
10
+ constructor(params) {
11
+ this.nodeRegistry = params.nodeRegistry;
12
+ const fallbackTimeoutMs = params.timeoutMs;
13
+ this.statusTimeoutMs = params.statusTimeoutMs ?? fallbackTimeoutMs ?? 5_000;
14
+ this.diffTimeoutMs = params.diffTimeoutMs ?? fallbackTimeoutMs ?? 15_000;
15
+ this.actionTimeoutMs = params.actionTimeoutMs ?? fallbackTimeoutMs ?? 60_000;
16
+ }
17
+ runGitAction(nodeId, workspaceRoot, action, commitMessage) {
18
+ const requestId = randomUUID();
19
+ return new Promise((resolve, reject) => {
20
+ const timer = setTimeout(() => {
21
+ this.actionPending.delete(requestId);
22
+ reject(new Error('Workspace git action request timed out.'));
23
+ }, this.actionTimeoutMs);
24
+ this.actionPending.set(requestId, {
25
+ nodeId,
26
+ resolve,
27
+ reject,
28
+ timer,
29
+ });
30
+ const sent = this.nodeRegistry.send(nodeId, {
31
+ type: 'workspace.git_action.request',
32
+ requestId,
33
+ workspaceRoot,
34
+ action,
35
+ commitMessage,
36
+ });
37
+ if (!sent) {
38
+ clearTimeout(timer);
39
+ this.actionPending.delete(requestId);
40
+ reject(new Error('Agent node is offline.'));
41
+ }
42
+ });
43
+ }
44
+ getGitStatus(nodeId, workspaceRoot) {
45
+ const requestId = randomUUID();
46
+ return new Promise((resolve, reject) => {
47
+ const timer = setTimeout(() => {
48
+ this.statusPending.delete(requestId);
49
+ reject(new Error('Workspace git status request timed out.'));
50
+ }, this.statusTimeoutMs);
51
+ this.statusPending.set(requestId, {
52
+ nodeId,
53
+ resolve,
54
+ reject,
55
+ timer,
56
+ });
57
+ const sent = this.nodeRegistry.send(nodeId, {
58
+ type: 'workspace.git_status.request',
59
+ requestId,
60
+ workspaceRoot,
61
+ });
62
+ if (!sent) {
63
+ clearTimeout(timer);
64
+ this.statusPending.delete(requestId);
65
+ reject(new Error('Agent node is offline.'));
66
+ }
67
+ });
68
+ }
69
+ getGitDiff(nodeId, workspaceRoot, mode) {
70
+ const requestId = randomUUID();
71
+ return new Promise((resolve, reject) => {
72
+ const timer = setTimeout(() => {
73
+ this.diffPending.delete(requestId);
74
+ reject(new Error('Workspace git diff request timed out.'));
75
+ }, this.diffTimeoutMs);
76
+ this.diffPending.set(requestId, {
77
+ nodeId,
78
+ resolve,
79
+ reject,
80
+ timer,
81
+ });
82
+ const sent = this.nodeRegistry.send(nodeId, {
83
+ type: 'workspace.git_diff.request',
84
+ requestId,
85
+ workspaceRoot,
86
+ mode,
87
+ });
88
+ if (!sent) {
89
+ clearTimeout(timer);
90
+ this.diffPending.delete(requestId);
91
+ reject(new Error('Agent node is offline.'));
92
+ }
93
+ });
94
+ }
95
+ handleGitStatusResponse(msg) {
96
+ const pending = this.statusPending.get(msg.requestId);
97
+ if (!pending)
98
+ return;
99
+ this.statusPending.delete(msg.requestId);
100
+ clearTimeout(pending.timer);
101
+ if (msg.error || !msg.status) {
102
+ pending.reject(new Error(this.formatErrorMessage(msg.errorCode, msg.error, 'Workspace git status failed.')));
103
+ return;
104
+ }
105
+ pending.resolve(msg.status);
106
+ }
107
+ handleGitDiffResponse(msg) {
108
+ const pending = this.diffPending.get(msg.requestId);
109
+ if (!pending)
110
+ return;
111
+ this.diffPending.delete(msg.requestId);
112
+ clearTimeout(pending.timer);
113
+ if (msg.error || !msg.diff) {
114
+ pending.reject(new Error(this.formatErrorMessage(msg.errorCode, msg.error, 'Workspace git diff failed.')));
115
+ return;
116
+ }
117
+ pending.resolve(msg.diff);
118
+ }
119
+ handleGitActionResponse(msg) {
120
+ const pending = this.actionPending.get(msg.requestId);
121
+ if (!pending)
122
+ return;
123
+ this.actionPending.delete(msg.requestId);
124
+ clearTimeout(pending.timer);
125
+ if (msg.error || !msg.result) {
126
+ pending.reject(new Error(this.formatErrorMessage(msg.errorCode, msg.error, 'Workspace git action failed.')));
127
+ return;
128
+ }
129
+ pending.resolve(msg.result);
130
+ }
131
+ rejectPendingForNode(nodeId) {
132
+ for (const [requestId, pending] of this.statusPending.entries()) {
133
+ if (pending.nodeId !== nodeId)
134
+ continue;
135
+ clearTimeout(pending.timer);
136
+ pending.reject(new Error(`Agent node disconnected: ${nodeId}`));
137
+ this.statusPending.delete(requestId);
138
+ }
139
+ for (const [requestId, pending] of this.diffPending.entries()) {
140
+ if (pending.nodeId !== nodeId)
141
+ continue;
142
+ clearTimeout(pending.timer);
143
+ pending.reject(new Error(`Agent node disconnected: ${nodeId}`));
144
+ this.diffPending.delete(requestId);
145
+ }
146
+ for (const [requestId, pending] of this.actionPending.entries()) {
147
+ if (pending.nodeId !== nodeId)
148
+ continue;
149
+ clearTimeout(pending.timer);
150
+ pending.reject(new Error(`Agent node disconnected: ${nodeId}`));
151
+ this.actionPending.delete(requestId);
152
+ }
153
+ }
154
+ formatErrorMessage(errorCode, error, fallback) {
155
+ if (error)
156
+ return errorCode ? `${errorCode}:${error}` : error;
157
+ if (errorCode)
158
+ return `${errorCode}:${fallback}`;
159
+ return fallback;
160
+ }
161
+ }
@@ -0,0 +1,69 @@
1
+ export class WorkbenchGitServiceError extends Error {
2
+ statusCode;
3
+ constructor(statusCode, message) {
4
+ super(message);
5
+ this.statusCode = statusCode;
6
+ }
7
+ }
8
+ export class WorkbenchGitService {
9
+ broker;
10
+ projectAccessResolver;
11
+ constructor(params) {
12
+ this.broker = params.broker;
13
+ this.projectAccessResolver = params.projectAccessResolver;
14
+ }
15
+ async getStatus(root) {
16
+ ensureProjectRoot(root);
17
+ try {
18
+ return await this.projectAccessResolver.withResolvedNode(root.projectId, async (nodeId) => this.broker.getGitStatus(nodeId, root.rootPath));
19
+ }
20
+ catch (error) {
21
+ throw mapWorkbenchGitError(error);
22
+ }
23
+ }
24
+ async getDiff(root, mode) {
25
+ ensureProjectRoot(root);
26
+ try {
27
+ return await this.projectAccessResolver.withResolvedNode(root.projectId, async (nodeId) => this.broker.getGitDiff(nodeId, root.rootPath, mode));
28
+ }
29
+ catch (error) {
30
+ throw mapWorkbenchGitError(error);
31
+ }
32
+ }
33
+ async runAction(root, action, commitMessage) {
34
+ ensureProjectRoot(root);
35
+ try {
36
+ return await this.projectAccessResolver.withResolvedNode(root.projectId, async (nodeId) => this.broker.runGitAction(nodeId, root.rootPath, action, commitMessage));
37
+ }
38
+ catch (error) {
39
+ throw mapWorkbenchGitError(error);
40
+ }
41
+ }
42
+ }
43
+ function ensureProjectRoot(root) {
44
+ if (root.kind !== 'project_space') {
45
+ throw new WorkbenchGitServiceError(409, 'Git changes are only available for project roots.');
46
+ }
47
+ if (!root.projectId) {
48
+ throw new WorkbenchGitServiceError(409, 'Project root is missing its project identity.');
49
+ }
50
+ }
51
+ function mapWorkbenchGitError(error) {
52
+ const message = String(error?.message ?? error);
53
+ if (message === 'Agent node is offline.' || message.startsWith('Agent node disconnected:')) {
54
+ return new WorkbenchGitServiceError(409, message);
55
+ }
56
+ if (message.includes('request timed out.')) {
57
+ return new WorkbenchGitServiceError(504, message);
58
+ }
59
+ if (message.startsWith('not_found:')) {
60
+ return new WorkbenchGitServiceError(404, message.slice('not_found:'.length));
61
+ }
62
+ if (message.startsWith('not_directory:')) {
63
+ return new WorkbenchGitServiceError(400, message.slice('not_directory:'.length));
64
+ }
65
+ if (message.startsWith('io_error:')) {
66
+ return new WorkbenchGitServiceError(500, message.slice('io_error:'.length));
67
+ }
68
+ return new WorkbenchGitServiceError(500, message);
69
+ }
@@ -0,0 +1,65 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ export class WorkbenchInspectBroker {
3
+ pending = new Map();
4
+ nodeRegistry;
5
+ timeoutMs;
6
+ constructor(params) {
7
+ this.nodeRegistry = params.nodeRegistry;
8
+ this.timeoutMs = params.timeoutMs ?? 1_500;
9
+ }
10
+ inspectWorkspace(nodeId, workspaceRoot) {
11
+ const requestId = randomUUID();
12
+ return new Promise((resolve, reject) => {
13
+ const timer = setTimeout(() => {
14
+ this.pending.delete(requestId);
15
+ reject(new Error('Workspace inspect request timed out.'));
16
+ }, this.timeoutMs);
17
+ this.pending.set(requestId, {
18
+ nodeId,
19
+ resolve,
20
+ reject,
21
+ timer,
22
+ });
23
+ const sent = this.nodeRegistry.send(nodeId, {
24
+ type: 'workspace.inspect.request',
25
+ requestId,
26
+ workspaceRoot,
27
+ });
28
+ if (!sent) {
29
+ clearTimeout(timer);
30
+ this.pending.delete(requestId);
31
+ reject(new Error('Agent node is offline.'));
32
+ }
33
+ });
34
+ }
35
+ handleInspectResponse(msg) {
36
+ const pending = this.pending.get(msg.requestId);
37
+ if (!pending)
38
+ return;
39
+ this.pending.delete(msg.requestId);
40
+ clearTimeout(pending.timer);
41
+ if (msg.error || !msg.inspect) {
42
+ pending.reject(new Error(this.formatErrorMessage(msg.errorCode, msg.error)));
43
+ return;
44
+ }
45
+ pending.resolve(msg.inspect);
46
+ }
47
+ rejectPendingForNode(nodeId) {
48
+ for (const [requestId, pending] of this.pending.entries()) {
49
+ if (pending.nodeId !== nodeId)
50
+ continue;
51
+ clearTimeout(pending.timer);
52
+ pending.reject(new Error(`Agent node disconnected: ${nodeId}`));
53
+ this.pending.delete(requestId);
54
+ }
55
+ }
56
+ formatErrorMessage(errorCode, error) {
57
+ if (error)
58
+ return error;
59
+ if (errorCode === 'not_found')
60
+ return 'Workspace root not found.';
61
+ if (errorCode === 'not_directory')
62
+ return 'Workspace root is not a directory.';
63
+ return 'Workspace inspect failed.';
64
+ }
65
+ }
@@ -0,0 +1,79 @@
1
+ export class WorkbenchNodePathServiceError extends Error {
2
+ statusCode;
3
+ constructor(statusCode, message) {
4
+ super(message);
5
+ this.statusCode = statusCode;
6
+ }
7
+ }
8
+ export class WorkbenchNodePathService {
9
+ broker;
10
+ projectAccessResolver;
11
+ constructor(params) {
12
+ this.broker = params.broker;
13
+ this.projectAccessResolver = params.projectAccessResolver;
14
+ }
15
+ async listTree(nodeId, rootPath, relativePath, options = {}) {
16
+ if (!nodeId)
17
+ throw new WorkbenchNodePathServiceError(409, 'Project root is not bound to a remote node.');
18
+ try {
19
+ return await this.broker.listDirectory(nodeId, rootPath, relativePath, { scaffold: false, ...options });
20
+ }
21
+ catch (error) {
22
+ throw mapWorkspaceError(error);
23
+ }
24
+ }
25
+ async readFile(nodeId, rootPath, relativePath, options = {}) {
26
+ if (!nodeId)
27
+ throw new WorkbenchNodePathServiceError(409, 'Project root is not bound to a remote node.');
28
+ try {
29
+ return await this.broker.readFile(nodeId, rootPath, relativePath, { scaffold: false, ...options });
30
+ }
31
+ catch (error) {
32
+ throw mapWorkspaceError(error);
33
+ }
34
+ }
35
+ async listProjectTree(projectId, rootPath, relativePath, options = {}) {
36
+ try {
37
+ return await this.projectAccessResolver.withResolvedNode(projectId, async (nodeId) => this.broker.listDirectory(nodeId, rootPath, relativePath, { scaffold: false, ...options }));
38
+ }
39
+ catch (error) {
40
+ throw mapWorkspaceError(error);
41
+ }
42
+ }
43
+ async readProjectFile(projectId, rootPath, relativePath, options = {}) {
44
+ try {
45
+ return await this.projectAccessResolver.withResolvedNode(projectId, async (nodeId) => this.broker.readFile(nodeId, rootPath, relativePath, { scaffold: false, ...options }));
46
+ }
47
+ catch (error) {
48
+ throw mapWorkspaceError(error);
49
+ }
50
+ }
51
+ }
52
+ function mapWorkspaceError(error) {
53
+ const message = String(error?.message ?? error);
54
+ if (message === 'Agent node is offline.') {
55
+ return new WorkbenchNodePathServiceError(409, message);
56
+ }
57
+ if (message === 'Workspace request timed out.') {
58
+ return new WorkbenchNodePathServiceError(504, message);
59
+ }
60
+ if (message.startsWith('not_found:')) {
61
+ return new WorkbenchNodePathServiceError(404, message.slice('not_found:'.length));
62
+ }
63
+ if (message.startsWith('path_outside_workspace:')) {
64
+ return new WorkbenchNodePathServiceError(400, message.slice('path_outside_workspace:'.length));
65
+ }
66
+ if (message.startsWith('invalid_request:')) {
67
+ return new WorkbenchNodePathServiceError(400, message.slice('invalid_request:'.length));
68
+ }
69
+ if (message.startsWith('binary_file:')) {
70
+ return new WorkbenchNodePathServiceError(415, message.slice('binary_file:'.length));
71
+ }
72
+ if (message.startsWith('file_too_large:')) {
73
+ return new WorkbenchNodePathServiceError(413, message.slice('file_too_large:'.length));
74
+ }
75
+ if (message.startsWith('not_directory:') || message.startsWith('not_file:')) {
76
+ return new WorkbenchNodePathServiceError(400, message.slice(message.indexOf(':') + 1));
77
+ }
78
+ return new WorkbenchNodePathServiceError(500, message);
79
+ }
@@ -0,0 +1,240 @@
1
+ import { log } from '@bbigbang/runtime-acp';
2
+ import { canonicalizeWorkbenchRootPath, getWorkbenchPathBasename, } from './workbenchRootService.js';
3
+ export class WorkbenchRegistryService {
4
+ db;
5
+ workbenchRootService;
6
+ inspectBroker;
7
+ constructor(params) {
8
+ this.db = params.db;
9
+ this.workbenchRootService = params.workbenchRootService;
10
+ this.inspectBroker = params.inspectBroker;
11
+ }
12
+ async listProjects() {
13
+ const persisted = this.loadPersistedWorkspaceRecords();
14
+ const projectRoots = this.workbenchRootService
15
+ .listRoots()
16
+ .filter((root) => (root.kind === 'project_space'
17
+ && !!root.projectId));
18
+ const snapshots = await Promise.all(projectRoots.map((root) => this.buildProjectSnapshot(root, persisted.get(root.projectId) ?? null)));
19
+ const projectsById = new Map();
20
+ for (const snapshot of snapshots) {
21
+ const projectId = snapshot.workspace.projectId ?? snapshot.workspace.workspaceId;
22
+ const existing = projectsById.get(projectId);
23
+ if (existing) {
24
+ existing.workspaces.push(snapshot.workspace);
25
+ existing.workspaces.sort((left, right) => left.rootPath.localeCompare(right.rootPath));
26
+ if (!existing.remoteUrl && snapshot.remoteUrl) {
27
+ existing.remoteUrl = snapshot.remoteUrl;
28
+ }
29
+ if (snapshot.workspace.workspaceKind === 'local_checkout'
30
+ && existing.primaryRootPath !== snapshot.primaryRootPath) {
31
+ existing.primaryRootPath = snapshot.primaryRootPath;
32
+ }
33
+ continue;
34
+ }
35
+ projectsById.set(projectId, {
36
+ projectId,
37
+ displayName: snapshot.displayName,
38
+ projectKind: snapshot.projectKind,
39
+ primaryRootPath: snapshot.primaryRootPath,
40
+ remoteUrl: snapshot.remoteUrl,
41
+ workspaces: [snapshot.workspace],
42
+ });
43
+ }
44
+ const projects = [...projectsById.values()]
45
+ .sort((left, right) => left.displayName.localeCompare(right.displayName));
46
+ this.persistProjects(projects);
47
+ return projects;
48
+ }
49
+ async buildProjectSnapshot(root, persisted) {
50
+ const inspect = await this.inspectProjectRoot(root);
51
+ const canonicalRootPath = canonicalizeWorkbenchRootPath(root.rootPath);
52
+ const displayName = inspect
53
+ ? resolveProjectDisplayName(canonicalRootPath, inspect)
54
+ : (persisted?.workspaceDisplayName ?? (getWorkbenchPathBasename(canonicalRootPath) || canonicalRootPath));
55
+ return {
56
+ displayName: inspect ? displayName : (persisted?.projectDisplayName ?? displayName),
57
+ projectKind: inspect?.isGit ? 'git' : (persisted?.projectKind ?? 'directory'),
58
+ primaryRootPath: persisted?.primaryRootPath ? canonicalizeWorkbenchRootPath(persisted.primaryRootPath) : canonicalRootPath,
59
+ remoteUrl: inspect?.remoteUrl ?? persisted?.projectRemoteUrl ?? null,
60
+ workspace: {
61
+ workspaceId: root.workbenchRootId,
62
+ workbenchRootId: root.workbenchRootId,
63
+ displayName,
64
+ rootPath: canonicalRootPath,
65
+ workspaceKind: inspect?.workspaceKind ?? persisted?.workspaceKind ?? 'directory',
66
+ branchName: inspect?.branchName ?? persisted?.branchName ?? null,
67
+ remoteUrl: inspect?.remoteUrl ?? persisted?.workspaceRemoteUrl ?? null,
68
+ nodeId: root.nodeId,
69
+ projectId: root.projectId,
70
+ projectBackendType: root.projectBackendType,
71
+ agentIds: [...(root.agentIds ?? [])].sort((left, right) => left.localeCompare(right)),
72
+ writable: root.writable,
73
+ terminalSupported: root.terminalSupported,
74
+ terminalDisabledReason: root.terminalDisabledReason ?? null,
75
+ },
76
+ };
77
+ }
78
+ async inspectProjectRoot(root) {
79
+ if (!root.nodeId)
80
+ return null;
81
+ try {
82
+ return await this.inspectBroker.inspectWorkspace(root.nodeId, root.rootPath);
83
+ }
84
+ catch (error) {
85
+ log.debug('[workbench-registry] project inspect unavailable', {
86
+ rootId: root.workbenchRootId,
87
+ nodeId: root.nodeId,
88
+ rootPath: root.rootPath,
89
+ error: String(error?.message ?? error),
90
+ });
91
+ return null;
92
+ }
93
+ }
94
+ loadPersistedWorkspaceRecords() {
95
+ const rows = this.db.prepare(`SELECT ww.workbench_root_id as workbenchRootId,
96
+ ww.display_name as workspaceDisplayName,
97
+ ww.root_path as workspaceRootPath,
98
+ ww.workspace_kind as workspaceKind,
99
+ ww.branch_name as branchName,
100
+ ww.remote_url as workspaceRemoteUrl,
101
+ wp.project_id as projectId,
102
+ wp.display_name as projectDisplayName,
103
+ wp.project_kind as projectKind,
104
+ wp.primary_root_path as primaryRootPath,
105
+ wp.remote_url as projectRemoteUrl
106
+ FROM workbench_workspaces ww
107
+ JOIN workbench_projects wp ON wp.project_id = ww.project_id
108
+ WHERE ww.archived_at IS NULL
109
+ AND wp.archived_at IS NULL`).all();
110
+ return new Map(rows.map((row) => [row.projectId, row]));
111
+ }
112
+ persistProjects(projects) {
113
+ const now = Date.now();
114
+ const upsertProject = this.db.prepare(`INSERT INTO workbench_projects(
115
+ project_id,
116
+ project_kind,
117
+ display_name,
118
+ primary_root_path,
119
+ remote_url,
120
+ created_at,
121
+ updated_at,
122
+ archived_at
123
+ ) VALUES(?, ?, ?, ?, ?, ?, ?, NULL)
124
+ ON CONFLICT(project_id) DO UPDATE SET
125
+ project_kind = excluded.project_kind,
126
+ display_name = excluded.display_name,
127
+ primary_root_path = excluded.primary_root_path,
128
+ remote_url = excluded.remote_url,
129
+ updated_at = excluded.updated_at,
130
+ archived_at = NULL`);
131
+ const upsertWorkspace = this.db.prepare(`INSERT INTO workbench_workspaces(
132
+ workspace_id,
133
+ workbench_root_id,
134
+ project_id,
135
+ agent_id,
136
+ root_path,
137
+ display_name,
138
+ workspace_kind,
139
+ branch_name,
140
+ remote_url,
141
+ created_at,
142
+ updated_at,
143
+ archived_at
144
+ ) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL)
145
+ ON CONFLICT(workspace_id) DO UPDATE SET
146
+ workbench_root_id = excluded.workbench_root_id,
147
+ project_id = excluded.project_id,
148
+ agent_id = excluded.agent_id,
149
+ root_path = excluded.root_path,
150
+ display_name = excluded.display_name,
151
+ workspace_kind = excluded.workspace_kind,
152
+ branch_name = excluded.branch_name,
153
+ remote_url = excluded.remote_url,
154
+ updated_at = excluded.updated_at,
155
+ archived_at = NULL`);
156
+ const clearWorkspaceAgents = this.db.prepare(`DELETE FROM workbench_workspace_agents WHERE workspace_id = ?`);
157
+ const insertWorkspaceAgent = this.db.prepare(`INSERT OR REPLACE INTO workbench_workspace_agents(
158
+ workspace_id,
159
+ agent_id,
160
+ created_at,
161
+ updated_at
162
+ ) VALUES(?, ?, ?, ?)`);
163
+ const archiveStaleProjects = this.db.prepare(`UPDATE workbench_projects
164
+ SET archived_at = ?, updated_at = ?
165
+ WHERE archived_at IS NULL`);
166
+ const archiveStaleWorkspaces = this.db.prepare(`UPDATE workbench_workspaces
167
+ SET archived_at = ?, updated_at = ?
168
+ WHERE archived_at IS NULL`);
169
+ const unarchiveProject = this.db.prepare(`UPDATE workbench_projects
170
+ SET archived_at = NULL, updated_at = ?
171
+ WHERE project_id = ?`);
172
+ const unarchiveWorkspace = this.db.prepare(`UPDATE workbench_workspaces
173
+ SET archived_at = NULL, updated_at = ?
174
+ WHERE workspace_id = ?`);
175
+ const deleteArchivedWorkspaceAgents = this.db.prepare(`DELETE FROM workbench_workspace_agents
176
+ WHERE workspace_id IN (
177
+ SELECT workspace_id
178
+ FROM workbench_workspaces
179
+ WHERE archived_at IS NOT NULL
180
+ )`);
181
+ this.db.transaction(() => {
182
+ archiveStaleProjects.run(now, now);
183
+ archiveStaleWorkspaces.run(now, now);
184
+ for (const project of projects) {
185
+ upsertProject.run(project.projectId, project.projectKind, project.displayName, project.primaryRootPath, project.remoteUrl, now, now);
186
+ unarchiveProject.run(now, project.projectId);
187
+ for (const workspace of project.workspaces) {
188
+ const primaryAgentId = workspace.agentIds?.[0] ?? workspace.agentId ?? '';
189
+ upsertWorkspace.run(workspace.workspaceId, workspace.workbenchRootId, project.projectId, primaryAgentId, workspace.rootPath, workspace.displayName, workspace.workspaceKind, workspace.branchName, workspace.remoteUrl, now, now);
190
+ unarchiveWorkspace.run(now, workspace.workspaceId);
191
+ clearWorkspaceAgents.run(workspace.workspaceId);
192
+ for (const agentId of workspace.agentIds ?? (workspace.agentId ? [workspace.agentId] : [])) {
193
+ insertWorkspaceAgent.run(workspace.workspaceId, agentId, now, now);
194
+ }
195
+ }
196
+ }
197
+ deleteArchivedWorkspaceAgents.run();
198
+ })();
199
+ }
200
+ }
201
+ function resolveProjectDisplayName(rootPath, inspect) {
202
+ const remoteDisplayName = inspect.remoteUrl ? normalizeRemoteDisplayName(inspect.remoteUrl) : null;
203
+ if (remoteDisplayName)
204
+ return remoteDisplayName;
205
+ if (inspect.isGit && inspect.repoRoot) {
206
+ const repoRoot = canonicalizeWorkbenchRootPath(inspect.repoRoot);
207
+ return getWorkbenchPathBasename(repoRoot) || repoRoot;
208
+ }
209
+ return getWorkbenchPathBasename(rootPath) || rootPath;
210
+ }
211
+ function normalizeRemoteDisplayName(remoteUrl) {
212
+ const trimmed = remoteUrl.trim();
213
+ if (!trimmed)
214
+ return null;
215
+ const scpMatch = trimmed.match(/^[^@]+@([^:]+):(.+)$/);
216
+ if (scpMatch) {
217
+ return buildRemoteDisplayName(normalizeRemotePath(scpMatch[2]));
218
+ }
219
+ try {
220
+ const parsed = new URL(trimmed);
221
+ return buildRemoteDisplayName(normalizeRemotePath(parsed.pathname));
222
+ }
223
+ catch {
224
+ return null;
225
+ }
226
+ }
227
+ function normalizeRemotePath(value) {
228
+ return value
229
+ .trim()
230
+ .replace(/^\/+/, '')
231
+ .replace(/\.git$/i, '')
232
+ .replace(/\/+$/, '');
233
+ }
234
+ function buildRemoteDisplayName(remotePath) {
235
+ const segments = remotePath.split('/').filter(Boolean);
236
+ if (segments.length >= 2) {
237
+ return `${segments[segments.length - 2]}/${segments[segments.length - 1]}`;
238
+ }
239
+ return segments[0] ?? null;
240
+ }