@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.
- package/dist/config.js +380 -0
- package/dist/execution/executionDispatcher.js +3810 -0
- package/dist/main.js +90 -0
- package/dist/nodeEventHistory.js +206 -0
- package/dist/scheduler/dreamLogic.js +50 -0
- package/dist/scheduler/dreamScheduler.js +65 -0
- package/dist/services/agentFileAccessService.js +1913 -0
- package/dist/services/agentRuntimeCleanupBroker.js +62 -0
- package/dist/services/agentSkillsBroker.js +118 -0
- package/dist/services/agentSkillsService.js +83 -0
- package/dist/services/agentWorkspaceBroker.js +937 -0
- package/dist/services/agentWorkspaceService.js +70 -0
- package/dist/services/appVersion.js +14 -0
- package/dist/services/auth.js +586 -0
- package/dist/services/claudeControlBroker.js +154 -0
- package/dist/services/claudeTranscriptBroker.js +100 -0
- package/dist/services/claudeTranscriptService.js +359 -0
- package/dist/services/codexAppServerBroker.js +155 -0
- package/dist/services/codexTranscriptBroker.js +98 -0
- package/dist/services/codexTranscriptService.js +961 -0
- package/dist/services/droidMissionBroker.js +124 -0
- package/dist/services/droidMissionImporter.js +630 -0
- package/dist/services/droidModelOptions.js +165 -0
- package/dist/services/hubServerRegistrationService.js +268 -0
- package/dist/services/libraryManifest.js +43 -0
- package/dist/services/libraryScaffold.js +26 -0
- package/dist/services/libraryService.js +2263 -0
- package/dist/services/memoryService.js +386 -0
- package/dist/services/missionEvidence.js +377 -0
- package/dist/services/missionService.js +2361 -0
- package/dist/services/missionTrace.js +158 -0
- package/dist/services/nativeMissionBriefParser.js +120 -0
- package/dist/services/nativeMissionOrchestrator.js +2045 -0
- package/dist/services/nativeMissionReportGenerator.js +227 -0
- package/dist/services/nativeMissionValidationRunner.js +452 -0
- package/dist/services/nativeMissionWorkerBroker.js +190 -0
- package/dist/services/nodeRegistry.js +34 -0
- package/dist/services/nodeStateReconciler.js +97 -0
- package/dist/services/panelMediaScanner.js +119 -0
- package/dist/services/persistentRuntimeJsonlClient.js +153 -0
- package/dist/services/platformAgentPolicy.js +180 -0
- package/dist/services/platformAgentService.js +2041 -0
- package/dist/services/projectAccessResolver.js +93 -0
- package/dist/services/projectService.js +392 -0
- package/dist/services/resourceSpaceService.js +140 -0
- package/dist/services/scenarioRuntimeService.js +1130 -0
- package/dist/services/suggestedPlannerService.js +868 -0
- package/dist/services/workbenchGitBroker.js +161 -0
- package/dist/services/workbenchGitService.js +69 -0
- package/dist/services/workbenchInspectBroker.js +65 -0
- package/dist/services/workbenchNodePathService.js +79 -0
- package/dist/services/workbenchRegistryService.js +240 -0
- package/dist/services/workbenchRootService.js +181 -0
- package/dist/services/workbenchTerminalBroker.js +378 -0
- package/dist/services/workspaceRunOwnership.js +60 -0
- package/dist/services/workspaceScaffold.js +105 -0
- package/dist/services/workspaceSessionRuntimeService.js +576 -0
- package/dist/services/workspaceSessionService.js +245 -0
- package/dist/services/workspaceToolActionRunner.js +1582 -0
- package/dist/services/workspaceToolErrors.js +10 -0
- package/dist/services/workspaceToolExecutionUtils.js +895 -0
- package/dist/services/workspaceToolLatestStateProjector.js +91 -0
- package/dist/services/workspaceToolManifest.js +572 -0
- package/dist/services/workspaceToolMutationQueue.js +43 -0
- package/dist/services/workspaceToolPanelProjection.js +460 -0
- package/dist/services/workspaceToolPromotion.js +255 -0
- package/dist/services/workspaceToolPromotionState.js +224 -0
- package/dist/services/workspaceToolPublishDiagnostics.js +189 -0
- package/dist/services/workspaceToolPublishIdentityResolver.js +146 -0
- package/dist/services/workspaceToolReadModel.js +378 -0
- package/dist/services/workspaceToolRunLedger.js +239 -0
- package/dist/services/workspaceToolService.js +3067 -0
- package/dist/services/workspaceToolSnapshotPanelSync.js +293 -0
- package/dist/services/workspaceToolTerminalLifecycle.js +283 -0
- package/dist/services/workspaceToolTypes.js +1 -0
- package/dist/services/workspaceToolUploadMaterializer.js +228 -0
- package/dist/web/actionCardRoutes.js +129 -0
- package/dist/web/actionCards.js +469 -0
- package/dist/web/activationContext.js +684 -0
- package/dist/web/agentChannelGuards.js +48 -0
- package/dist/web/agentMentionCooldowns.js +32 -0
- package/dist/web/agentReminders.js +1668 -0
- package/dist/web/agentRuntimePresence.js +197 -0
- package/dist/web/agentSelfState.js +494 -0
- package/dist/web/agentTaskLinks.js +26 -0
- package/dist/web/agentVisibility.js +79 -0
- package/dist/web/assets.js +95 -0
- package/dist/web/channelActivationPrompt.js +395 -0
- package/dist/web/channelMemoryNotes.js +127 -0
- package/dist/web/channelMentions.js +10 -0
- package/dist/web/channelMessageSequences.js +19 -0
- package/dist/web/channelSubscriptions.js +26 -0
- package/dist/web/clearedTaskRoots.js +10 -0
- package/dist/web/collaborationPromptGuidance.js +36 -0
- package/dist/web/collaborationSurfaceState.js +140 -0
- package/dist/web/contextBundleRanking.js +154 -0
- package/dist/web/contextBundleResolver.js +488 -0
- package/dist/web/conversationBuiltinSkillRoots.js +50 -0
- package/dist/web/conversationControls.js +232 -0
- package/dist/web/conversationHandoffs.js +612 -0
- package/dist/web/conversationManager.js +2511 -0
- package/dist/web/conversationSummaries.js +876 -0
- package/dist/web/conversationSurfaceKinds.js +17 -0
- package/dist/web/conversationTargets.js +173 -0
- package/dist/web/directActivationPrompt.js +122 -0
- package/dist/web/directReplyTargets.js +69 -0
- package/dist/web/directThreadResolver.js +129 -0
- package/dist/web/dmTaskHandoffPrompt.js +120 -0
- package/dist/web/dmTaskThreadStatusProjection.js +229 -0
- package/dist/web/ftsQuery.js +33 -0
- package/dist/web/internalAgentRouter.js +11341 -0
- package/dist/web/libraryCuratorScheduler.js +58 -0
- package/dist/web/libraryDocumentPromptGuidance.js +8 -0
- package/dist/web/messageCheckpoints.js +19 -0
- package/dist/web/nodeWsHandler.js +2495 -0
- package/dist/web/notificationRounds.js +1061 -0
- package/dist/web/panelActionMessages.js +108 -0
- package/dist/web/panelActivationPrompt.js +18 -0
- package/dist/web/panelAudit.js +273 -0
- package/dist/web/panelLifecycle.js +222 -0
- package/dist/web/panelMediaPolicy.js +43 -0
- package/dist/web/panelPathPolicy.js +63 -0
- package/dist/web/panelPreviews.js +175 -0
- package/dist/web/panelQueryHandles.js +2749 -0
- package/dist/web/panelRoutes.js +2147 -0
- package/dist/web/panels.js +904 -0
- package/dist/web/peerInboxAggregates.js +1247 -0
- package/dist/web/planApprovalState.js +92 -0
- package/dist/web/platformAgentScheduler.js +66 -0
- package/dist/web/proactiveOpportunities.js +452 -0
- package/dist/web/promptContextSections.js +242 -0
- package/dist/web/promptHistorySanitizer.js +26 -0
- package/dist/web/promptSlashCommands.js +158 -0
- package/dist/web/rollingConversationSummary.js +453 -0
- package/dist/web/routeHelpers.js +11 -0
- package/dist/web/routes/handoff.js +288 -0
- package/dist/web/routes/history.js +345 -0
- package/dist/web/routes/memory.js +258 -0
- package/dist/web/routes/selfState.js +171 -0
- package/dist/web/routes/workspace.js +154 -0
- package/dist/web/runSurfaceWatermarks.js +431 -0
- package/dist/web/runtimeCapabilities.js +48 -0
- package/dist/web/sameAgentHandoffs.js +494 -0
- package/dist/web/server.js +15567 -0
- package/dist/web/sharedCollaborationCapsules.js +163 -0
- package/dist/web/soloSessionRelay.js +42 -0
- package/dist/web/soloWsHandler.js +138 -0
- package/dist/web/suggestedPlannerScheduler.js +56 -0
- package/dist/web/surfaceActivationPolicy.js +108 -0
- package/dist/web/surfaceCollaborators.js +61 -0
- package/dist/web/surfaceSystemStatus.js +263 -0
- package/dist/web/targetParticipants.js +77 -0
- package/dist/web/taskEvents.js +49 -0
- package/dist/web/taskLifecycleMessages.js +165 -0
- package/dist/web/taskLoops.js +732 -0
- package/dist/web/taskMemoryNotes.js +224 -0
- package/dist/web/taskNumbers.js +16 -0
- package/dist/web/taskOwnerGuards.js +49 -0
- package/dist/web/taskParticipantResolver.js +42 -0
- package/dist/web/taskParticipants.js +97 -0
- package/dist/web/taskSourceDetails.js +20 -0
- package/dist/web/taskStateViews.js +210 -0
- package/dist/web/taskStatusTransitions.js +9 -0
- package/dist/web/taskThreadFollowups.js +599 -0
- package/dist/web/taskThreadRuntimeClosure.js +685 -0
- package/dist/web/taskUpdateDelivery.js +104 -0
- package/dist/web/threadReplyContentHeuristics.js +30 -0
- package/dist/web/threadRoots.js +61 -0
- package/dist/web/threadTaskBindings.js +365 -0
- package/dist/web/uiPanelPromptGuidance.js +27 -0
- package/dist/web/workspaceMemoryHints.js +143 -0
- package/dist/web/workspaceToolPromptGuidance.js +30 -0
- package/dist/web/wsHandler.js +397 -0
- package/dist/web/wsSink.js +116 -0
- 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
|
+
}
|