@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,163 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { createPromptContextSection } from './promptContextSections.js';
3
+ export const SHARED_COLLABORATION_MAX_VISIBLE_AGENT_MESSAGES = 50;
4
+ const ORIGIN_EXCERPT_MAX_CHARS = 480;
5
+ function normalizeThreadKey(threadRootId) {
6
+ return threadRootId?.trim() ?? '';
7
+ }
8
+ function normalizeParticipantAgentIds(agentIds) {
9
+ return [...new Set(agentIds.map((agentId) => agentId.trim()).filter(Boolean))].sort();
10
+ }
11
+ function truncateOriginExcerpt(content) {
12
+ const normalized = content.replace(/\s+/g, ' ').trim();
13
+ if (normalized.length <= ORIGIN_EXCERPT_MAX_CHARS)
14
+ return normalized;
15
+ return `${normalized.slice(0, ORIGIN_EXCERPT_MAX_CHARS - 15).trimEnd()} ... [truncated]`;
16
+ }
17
+ function parseParticipantAgentIds(value) {
18
+ try {
19
+ const parsed = JSON.parse(value);
20
+ return Array.isArray(parsed)
21
+ ? normalizeParticipantAgentIds(parsed.filter((item) => typeof item === 'string'))
22
+ : [];
23
+ }
24
+ catch {
25
+ return [];
26
+ }
27
+ }
28
+ export function supersedeActiveSharedCollaborationCapsule(db, params) {
29
+ db.prepare(`UPDATE shared_collaboration_capsules
30
+ SET state = 'superseded',
31
+ superseded_at = ?,
32
+ superseded_by_message_id = ?,
33
+ superseded_by_seq = ?,
34
+ updated_at = ?
35
+ WHERE channel_id = ?
36
+ AND thread_root_id = ?
37
+ AND state = 'active'`).run(params.now, params.supersededByMessageId, params.supersededBySeq, params.now, params.channelId, normalizeThreadKey(params.threadRootId));
38
+ }
39
+ export function createSharedCollaborationCapsuleForUserMessage(db, params) {
40
+ const participantAgentIds = normalizeParticipantAgentIds(params.participantAgentIds);
41
+ if (participantAgentIds.length < 2)
42
+ return null;
43
+ const capsuleId = randomUUID();
44
+ const threadRootId = normalizeThreadKey(params.threadRootId);
45
+ const maxVisibleAgentMessages = Math.max(1, Math.floor(params.maxVisibleAgentMessages ?? SHARED_COLLABORATION_MAX_VISIBLE_AGENT_MESSAGES));
46
+ const originContentExcerpt = truncateOriginExcerpt(params.originContent);
47
+ db.prepare(`INSERT INTO shared_collaboration_capsules(
48
+ capsule_id, channel_id, thread_root_id, origin_message_id, origin_seq,
49
+ origin_content_excerpt, participant_agent_ids_json, state,
50
+ max_visible_agent_messages, created_at, updated_at
51
+ )
52
+ VALUES(?, ?, ?, ?, ?, ?, ?, 'active', ?, ?, ?)`).run(capsuleId, params.channelId, threadRootId, params.originMessageId, params.originSeq, originContentExcerpt, JSON.stringify(participantAgentIds), maxVisibleAgentMessages, params.now, params.now);
53
+ return {
54
+ capsuleId,
55
+ channelId: params.channelId,
56
+ threadRootId,
57
+ originMessageId: params.originMessageId,
58
+ originSeq: params.originSeq,
59
+ originContentExcerpt,
60
+ participantAgentIds,
61
+ state: 'active',
62
+ contributionCount: 0,
63
+ maxVisibleAgentMessages,
64
+ };
65
+ }
66
+ export function recordUserMessageSharedCollaborationCapsule(db, params) {
67
+ return db.transaction(() => {
68
+ supersedeActiveSharedCollaborationCapsule(db, {
69
+ channelId: params.channelId,
70
+ threadRootId: params.threadRootId,
71
+ supersededByMessageId: params.messageId,
72
+ supersededBySeq: params.seq,
73
+ now: params.now,
74
+ });
75
+ return createSharedCollaborationCapsuleForUserMessage(db, {
76
+ channelId: params.channelId,
77
+ threadRootId: params.threadRootId,
78
+ originMessageId: params.messageId,
79
+ originSeq: params.seq,
80
+ originContent: params.content,
81
+ participantAgentIds: params.mentionedAgentIds,
82
+ now: params.now,
83
+ });
84
+ })();
85
+ }
86
+ function countVisibleAgentContributions(db, capsule) {
87
+ if (capsule.participantAgentIds.length === 0)
88
+ return 0;
89
+ const placeholders = capsule.participantAgentIds.map(() => '?').join(', ');
90
+ const row = db.prepare(`SELECT COUNT(*) as count
91
+ FROM channel_messages
92
+ WHERE channel_id = ?
93
+ AND COALESCE(thread_root_id, '') = ?
94
+ AND seq > ?
95
+ AND sender_type = 'agent'
96
+ AND sender_id IN (${placeholders})
97
+ AND (message_kind IS NULL OR message_kind <> 'system_hidden')`).get(capsule.channelId, capsule.threadRootId, capsule.originSeq, ...capsule.participantAgentIds);
98
+ return Math.max(0, Math.floor(row?.count ?? 0));
99
+ }
100
+ export function loadActiveSharedCollaborationCapsuleForPeerUpdate(db, params) {
101
+ const threadRootId = normalizeThreadKey(params.threadRootId);
102
+ const row = db.prepare(`SELECT capsule_id as capsuleId,
103
+ channel_id as channelId,
104
+ thread_root_id as threadRootId,
105
+ origin_message_id as originMessageId,
106
+ origin_seq as originSeq,
107
+ origin_content_excerpt as originContentExcerpt,
108
+ participant_agent_ids_json as participantAgentIdsJson,
109
+ max_visible_agent_messages as maxVisibleAgentMessages
110
+ FROM shared_collaboration_capsules
111
+ WHERE channel_id = ?
112
+ AND thread_root_id = ?
113
+ AND state = 'active'
114
+ LIMIT 1`).get(params.channelId, threadRootId);
115
+ if (!row)
116
+ return null;
117
+ const participantAgentIds = parseParticipantAgentIds(row.participantAgentIdsJson);
118
+ if (!participantAgentIds.includes(params.targetAgentId))
119
+ return null;
120
+ const contributionCount = countVisibleAgentContributions(db, {
121
+ channelId: row.channelId,
122
+ threadRootId: row.threadRootId,
123
+ originSeq: row.originSeq,
124
+ participantAgentIds,
125
+ });
126
+ const maxVisibleAgentMessages = Math.max(1, Math.floor(row.maxVisibleAgentMessages));
127
+ if (contributionCount >= maxVisibleAgentMessages) {
128
+ db.prepare(`UPDATE shared_collaboration_capsules
129
+ SET state = 'capped',
130
+ updated_at = ?
131
+ WHERE capsule_id = ?
132
+ AND state = 'active'`).run(params.now, row.capsuleId);
133
+ return null;
134
+ }
135
+ return {
136
+ capsuleId: row.capsuleId,
137
+ channelId: row.channelId,
138
+ threadRootId: row.threadRootId,
139
+ originMessageId: row.originMessageId,
140
+ originSeq: row.originSeq,
141
+ originContentExcerpt: row.originContentExcerpt,
142
+ participantAgentIds,
143
+ state: 'active',
144
+ contributionCount,
145
+ maxVisibleAgentMessages,
146
+ };
147
+ }
148
+ export function buildSharedCollaborationCapsuleSection(params) {
149
+ const participantLine = params.participantNames.length > 0
150
+ ? params.participantNames.map((name) => `@${name}`).join(', ')
151
+ : params.capsule.participantAgentIds.join(', ');
152
+ const includeRoot = params.includeRootHint === true ? ', include_root=true' : '';
153
+ return createPromptContextSection('shared_collaboration_capsule', [
154
+ '[Shared collaboration capsule]',
155
+ `capsule: ${params.capsule.capsuleId}`,
156
+ `origin: seq=${params.capsule.originSeq} msg=${params.capsule.originMessageId}`,
157
+ `participants: ${participantLine}`,
158
+ `visible_agent_contributions: ${params.capsule.contributionCount}/${params.capsule.maxVisibleAgentMessages}`,
159
+ 'original_request_excerpt:',
160
+ params.capsule.originContentExcerpt,
161
+ `Full origin/request context: bigbang message read --channel "${params.replyTarget}" --around "${params.capsule.originMessageId}" --limit 20${includeRoot ? ' --include-root' : ''}`,
162
+ ].join('\n'));
163
+ }
@@ -0,0 +1,42 @@
1
+ export class SoloSessionRelay {
2
+ clientsBySessionId = new Map();
3
+ registerClient(soloSessionId, agentId, socket) {
4
+ const existing = this.clientsBySessionId.get(soloSessionId);
5
+ if (existing) {
6
+ if (existing.socket !== socket)
7
+ return 'collision';
8
+ if (existing.agentId !== agentId)
9
+ return 'agent_mismatch';
10
+ return 'ok';
11
+ }
12
+ this.clientsBySessionId.set(soloSessionId, { agentId, socket });
13
+ return 'ok';
14
+ }
15
+ unregisterClient(soloSessionId) {
16
+ this.clientsBySessionId.delete(soloSessionId);
17
+ }
18
+ getAgentId(soloSessionId) {
19
+ return this.clientsBySessionId.get(soloSessionId)?.agentId ?? null;
20
+ }
21
+ relayNodeMessage(msg) {
22
+ if (msg.type !== 'solo.run.event'
23
+ && msg.type !== 'solo.run.end'
24
+ && msg.type !== 'solo.permission.request') {
25
+ return false;
26
+ }
27
+ const binding = this.clientsBySessionId.get(msg.soloSessionId);
28
+ if (!binding || binding.socket.readyState !== 1) {
29
+ return false;
30
+ }
31
+ binding.socket.send(JSON.stringify(msg));
32
+ return true;
33
+ }
34
+ sendToClient(soloSessionId, msg) {
35
+ const binding = this.clientsBySessionId.get(soloSessionId);
36
+ if (!binding || binding.socket.readyState !== 1) {
37
+ return false;
38
+ }
39
+ binding.socket.send(JSON.stringify(msg));
40
+ return true;
41
+ }
42
+ }
@@ -0,0 +1,138 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { buildSoloSystemPrompt } from '@bbigbang/memory';
3
+ export function handleSoloWebSocket(socket, agent, nodeRegistry, soloRelay) {
4
+ const agentId = agent.agentId;
5
+ const nodeId = agent.nodeId?.trim();
6
+ if (!nodeId) {
7
+ socket.send(JSON.stringify({ type: 'error', message: 'Agent has no connected node.' }));
8
+ socket.close();
9
+ return;
10
+ }
11
+ const connectedSessions = new Set();
12
+ const sendError = (message) => {
13
+ if (socket.readyState === 1) {
14
+ socket.send(JSON.stringify({ type: 'error', message }));
15
+ }
16
+ };
17
+ const forwardToNode = (msg) => {
18
+ const sent = nodeRegistry.send(nodeId, msg);
19
+ if (!sent) {
20
+ sendError('Agent node is not connected.');
21
+ }
22
+ return sent;
23
+ };
24
+ socket.on('message', (raw) => {
25
+ let parsed;
26
+ try {
27
+ parsed = JSON.parse(String(raw));
28
+ }
29
+ catch {
30
+ sendError('Invalid message payload.');
31
+ return;
32
+ }
33
+ const soloSessionId = parsed.soloSessionId?.trim();
34
+ if (!soloSessionId) {
35
+ sendError('soloSessionId is required.');
36
+ return;
37
+ }
38
+ if (!connectedSessions.has(soloSessionId)) {
39
+ const registered = soloRelay.registerClient(soloSessionId, agentId, socket);
40
+ if (registered === 'collision') {
41
+ sendError('soloSessionId is already bound to another client.');
42
+ return;
43
+ }
44
+ if (registered === 'agent_mismatch') {
45
+ sendError('soloSessionId is already bound to a different agent.');
46
+ return;
47
+ }
48
+ connectedSessions.add(soloSessionId);
49
+ }
50
+ switch (parsed.type) {
51
+ case 'solo.prompt': {
52
+ const text = parsed.text?.trim();
53
+ if (!text) {
54
+ sendError('Prompt text is required.');
55
+ return;
56
+ }
57
+ if (agent.agentType !== 'codex_app_server') {
58
+ sendError('Solo mode is only supported for codex_app_server agents.');
59
+ return;
60
+ }
61
+ const workspacePath = agent.workspacePath?.trim();
62
+ if (!workspacePath) {
63
+ sendError('Agent workspace is not configured.');
64
+ return;
65
+ }
66
+ const dispatch = {
67
+ type: 'solo.run.dispatch',
68
+ soloSessionId,
69
+ agentId,
70
+ agentType: agent.agentType,
71
+ workspacePath,
72
+ agentName: agent.name,
73
+ model: agent.model,
74
+ reasoningEffort: agent.reasoningEffort,
75
+ codexMode: agent.codexMode,
76
+ codexServiceTier: agent.codexServiceTier,
77
+ envVars: agent.envVars,
78
+ disabledToolKinds: agent.disabledToolKinds,
79
+ prompt: text,
80
+ systemPromptText: buildSoloSystemPrompt({
81
+ name: agent.name,
82
+ bio: agent.description,
83
+ description: agent.systemPrompt,
84
+ workspacePath,
85
+ }),
86
+ };
87
+ forwardToNode(dispatch);
88
+ break;
89
+ }
90
+ case 'solo.cancel':
91
+ forwardToNode({ type: 'solo.cancel', soloSessionId });
92
+ break;
93
+ case 'solo.steer': {
94
+ const steerText = parsed.text?.trim();
95
+ if (!steerText) {
96
+ sendError('Steer text is required.');
97
+ return;
98
+ }
99
+ forwardToNode({ type: 'solo.steer', soloSessionId, prompt: steerText });
100
+ break;
101
+ }
102
+ case 'solo.approval.response': {
103
+ if (!parsed.requestId?.trim() || !parsed.decision) {
104
+ sendError('requestId and decision are required.');
105
+ return;
106
+ }
107
+ forwardToNode({
108
+ type: 'solo.permission.response',
109
+ soloSessionId,
110
+ requestId: parsed.requestId,
111
+ decision: parsed.decision,
112
+ selectedActionId: parsed.selectedActionId,
113
+ responseText: parsed.responseText,
114
+ answers: parsed.answers,
115
+ });
116
+ break;
117
+ }
118
+ default:
119
+ sendError('Unsupported solo message type.');
120
+ }
121
+ });
122
+ socket.on('error', (error) => {
123
+ // The close handler owns cleanup; this log keeps socket-level failures visible.
124
+ console.warn('[solo-ws] socket error', error);
125
+ });
126
+ socket.on('close', () => {
127
+ for (const soloSessionId of connectedSessions) {
128
+ soloRelay.unregisterClient(soloSessionId);
129
+ if (nodeId) {
130
+ nodeRegistry.send(nodeId, { type: 'solo.close', soloSessionId });
131
+ }
132
+ }
133
+ connectedSessions.clear();
134
+ });
135
+ }
136
+ export function createSoloSessionId() {
137
+ return randomUUID();
138
+ }
@@ -0,0 +1,56 @@
1
+ import { getSuggestedPlannerTerminalError, } from '../services/suggestedPlannerService.js';
2
+ export const SUGGESTED_PLANNER_POLL_INTERVAL_MS = 60_000;
3
+ export function startSuggestedPlannerScheduler(params) {
4
+ const intervalMs = Math.max(5_000, params.intervalMs ?? SUGGESTED_PLANNER_POLL_INTERVAL_MS);
5
+ let closed = false;
6
+ let running = false;
7
+ let timer = null;
8
+ const tick = async () => {
9
+ if (closed || running)
10
+ return;
11
+ running = true;
12
+ try {
13
+ const now = Date.now();
14
+ for (const config of params.suggestedPlannerService.listDueConfigs(now)) {
15
+ const username = params.suggestedPlannerService.getUsernameForUserId(config.userId);
16
+ if (!username)
17
+ continue;
18
+ const runningPlan = params.db.prepare(`SELECT run_id as runId FROM suggested_planner_runs
19
+ WHERE user_id = ? AND status = 'running' LIMIT 1`).get(config.userId);
20
+ if (runningPlan)
21
+ continue;
22
+ await params.suggestedPlannerService.dispatchPlanRun({
23
+ userId: config.userId,
24
+ username,
25
+ });
26
+ }
27
+ }
28
+ finally {
29
+ running = false;
30
+ }
31
+ };
32
+ timer = setInterval(() => {
33
+ void tick();
34
+ }, intervalMs);
35
+ timer.unref?.();
36
+ return {
37
+ tick,
38
+ stop: () => {
39
+ closed = true;
40
+ if (!timer)
41
+ return;
42
+ clearInterval(timer);
43
+ timer = null;
44
+ },
45
+ };
46
+ }
47
+ export async function handleSuggestedPlannerRunFinished(params) {
48
+ const row = params.db.prepare(`SELECT user_id as userId FROM suggested_planner_runs WHERE run_id = ? AND status = 'running'`).get(params.runId);
49
+ if (!row)
50
+ return;
51
+ await params.suggestedPlannerService.finalizePlanRun({
52
+ runId: params.runId,
53
+ userId: row.userId,
54
+ error: getSuggestedPlannerTerminalError(params.stopReason, params.error),
55
+ });
56
+ }
@@ -0,0 +1,108 @@
1
+ import { isThreadTarget, resolveChannelTaskBoardPresencePolicy, resolveDirectTaskThreadPresencePolicy, } from './workspaceMemoryHints.js';
2
+ const STRONG_SIGNAL_ORDER = [
3
+ 'open_handoff',
4
+ 'handoff_required',
5
+ 'bound_task_match',
6
+ 'task_thread',
7
+ 'explicit_handoff_target',
8
+ 'active_dm_task_thread_linkage',
9
+ 'handoff_recovery_bootstrap',
10
+ ];
11
+ const RELEVANT_CONTEXT_REASON_SIGNAL_MAP = {
12
+ active_handoff: 'open_handoff',
13
+ current_conversation_handoff: 'open_handoff',
14
+ bound_task_match: 'bound_task_match',
15
+ };
16
+ export function historyLimitForSurfaceActivationTier(tier) {
17
+ switch (tier) {
18
+ case 'L1':
19
+ return 0;
20
+ case 'L2-lite':
21
+ return 2;
22
+ case 'L2':
23
+ return 2;
24
+ case 'L2-task':
25
+ case 'L3':
26
+ return 4;
27
+ default:
28
+ return 0;
29
+ }
30
+ }
31
+ export function collectSurfaceActivationStrongSignals(flags) {
32
+ if (!flags)
33
+ return [];
34
+ return STRONG_SIGNAL_ORDER.filter((signal) => flags[signal]);
35
+ }
36
+ export function extractRelevantContextStrongSignals(bundle) {
37
+ const top = bundle?.items[0];
38
+ if (!top)
39
+ return [];
40
+ const flags = {};
41
+ for (const reason of top.reasons) {
42
+ const signal = RELEVANT_CONTEXT_REASON_SIGNAL_MAP[reason.code];
43
+ if (signal)
44
+ flags[signal] = true;
45
+ }
46
+ return collectSurfaceActivationStrongSignals(flags);
47
+ }
48
+ export function shouldPreinjectRelevantContextHint(params) {
49
+ return !isThreadTarget(params.target) && params.strongSignals.length > 0;
50
+ }
51
+ export function resolveChannelSurfaceActivationPolicy(params) {
52
+ const threadSurface = isThreadTarget(params.target);
53
+ const strongSignals = collectSurfaceActivationStrongSignals({
54
+ ...params.strongSignalFlags,
55
+ bound_task_match: params.hasBoundTask ?? false,
56
+ task_thread: Boolean(params.hasBoundTask && threadSurface),
57
+ });
58
+ const activationTier = threadSurface
59
+ ? (params.hasBoundTask ? 'L2-task' : 'L2')
60
+ : (strongSignals.length > 0 ? 'L2-lite' : 'L1');
61
+ const resumeTier = threadSurface ? activationTier : 'L2-lite';
62
+ return {
63
+ surfaceKind: 'channel',
64
+ activationTier,
65
+ resumeTier,
66
+ strongSignals,
67
+ taskBoardPresence: resolveChannelTaskBoardPresencePolicy({
68
+ target: params.target,
69
+ hasBoundTask: params.hasBoundTask,
70
+ includeOpenTasks: params.includeOpenTasks,
71
+ }),
72
+ includeParticipantsByDefault: params.hasBoundTask
73
+ ? true
74
+ : threadSurface
75
+ ? true
76
+ : params.reason === 'agent_mention'
77
+ || (params.reason === 'channel_activity' && (params.participantsCount ?? 0) > 0)
78
+ || (params.participantsCount ?? 0) > 1,
79
+ includeOpenTasksByDefault: Boolean(params.includeOpenTasks),
80
+ optionalPayloadTiers: params.includeOpenTasks ? ['L3'] : [],
81
+ };
82
+ }
83
+ export function resolveDirectSurfaceActivationPolicy(params) {
84
+ const threadSurface = isThreadTarget(params.target);
85
+ const strongSignals = collectSurfaceActivationStrongSignals(params.strongSignalFlags);
86
+ const activationTier = threadSurface
87
+ ? 'L2'
88
+ : (strongSignals.length > 0 ? 'L2-lite' : 'L1');
89
+ const resumeTier = threadSurface ? 'L2' : 'L2-lite';
90
+ const optionalPayloadTiers = [];
91
+ if (params.includeDmActiveTaskThreads)
92
+ optionalPayloadTiers.push('L3');
93
+ if (params.promptIncludeDmContextSnapshot || params.includeDmContextSnapshot)
94
+ optionalPayloadTiers.push('L3');
95
+ return {
96
+ surfaceKind: 'direct',
97
+ activationTier,
98
+ resumeTier,
99
+ strongSignals,
100
+ dmTaskThreadPresence: resolveDirectTaskThreadPresencePolicy({
101
+ target: params.target,
102
+ includeDmActiveTaskThreads: params.includeDmActiveTaskThreads,
103
+ }),
104
+ includeDmSnapshotByDefault: Boolean(params.includeDmContextSnapshot),
105
+ promptIncludeDmSnapshotByDefault: Boolean(params.promptIncludeDmContextSnapshot ?? params.includeDmContextSnapshot),
106
+ optionalPayloadTiers: Array.from(new Set(optionalPayloadTiers)),
107
+ };
108
+ }
@@ -0,0 +1,61 @@
1
+ export function ensureSurfaceOwner(db, params) {
2
+ upsertSurfaceCollaborator(db, {
3
+ ...params,
4
+ role: 'owner',
5
+ });
6
+ }
7
+ export function upsertSurfaceCollaborator(db, params) {
8
+ const now = params.createdAt ?? Date.now();
9
+ db.prepare(`INSERT INTO surface_collaborators(
10
+ surface_type,
11
+ surface_id,
12
+ agent_id,
13
+ role,
14
+ added_by,
15
+ created_at
16
+ )
17
+ VALUES(?, ?, ?, ?, ?, ?)
18
+ ON CONFLICT(surface_type, surface_id, agent_id)
19
+ DO UPDATE SET
20
+ role = CASE
21
+ WHEN surface_collaborators.role = 'owner' THEN 'owner'
22
+ ELSE excluded.role
23
+ END,
24
+ added_by = COALESCE(surface_collaborators.added_by, excluded.added_by)`).run(params.surfaceType, params.surfaceId, params.agentId, params.role, params.addedBy ?? null, now);
25
+ }
26
+ export function getSurfaceCollaboratorRole(db, surfaceType, surfaceId, agentId) {
27
+ const row = db.prepare(`SELECT role
28
+ FROM surface_collaborators
29
+ WHERE surface_type = ?
30
+ AND surface_id = ?
31
+ AND agent_id = ?
32
+ LIMIT 1`).get(surfaceType, surfaceId, agentId);
33
+ return row?.role ?? null;
34
+ }
35
+ export function isSurfaceCollaborator(db, surfaceType, surfaceId, agentId) {
36
+ return getSurfaceCollaboratorRole(db, surfaceType, surfaceId, agentId) !== null;
37
+ }
38
+ export function isSurfaceOwner(db, surfaceType, surfaceId, agentId) {
39
+ return getSurfaceCollaboratorRole(db, surfaceType, surfaceId, agentId) === 'owner';
40
+ }
41
+ export function listSurfaceCollaborators(db, surfaceType, surfaceId) {
42
+ const rows = db.prepare(`SELECT surface_type as surfaceType,
43
+ surface_id as surfaceId,
44
+ agent_id as agentId,
45
+ role,
46
+ added_by as addedBy,
47
+ created_at as createdAt
48
+ FROM surface_collaborators
49
+ WHERE surface_type = ?
50
+ AND surface_id = ?
51
+ ORDER BY CASE role WHEN 'owner' THEN 0 ELSE 1 END, created_at ASC, agent_id ASC`).all(surfaceType, surfaceId);
52
+ return rows;
53
+ }
54
+ export function removeSurfaceCollaborator(db, surfaceType, surfaceId, agentId) {
55
+ const result = db.prepare(`DELETE FROM surface_collaborators
56
+ WHERE surface_type = ?
57
+ AND surface_id = ?
58
+ AND agent_id = ?
59
+ AND role != 'owner'`).run(surfaceType, surfaceId, agentId);
60
+ return result.changes > 0;
61
+ }