@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,127 @@
|
|
|
1
|
+
import { formatBeijingPromptTimestamp } from '@bbigbang/protocol';
|
|
2
|
+
const LEGACY_CHANNELS_NOTE_PATH = 'notes/channels.md';
|
|
3
|
+
const CHANNEL_NOTE_INSTRUCTION = 'Durable notes and reset markers for this channel.';
|
|
4
|
+
const LEGACY_CHANNELS_INSTRUCTION = 'Durable summaries and reset markers for joined channels.';
|
|
5
|
+
export function slugifyChannelNoteName(name) {
|
|
6
|
+
const normalized = name
|
|
7
|
+
.trim()
|
|
8
|
+
.toLowerCase()
|
|
9
|
+
.replace(/[^a-z0-9_-]+/g, '-')
|
|
10
|
+
.replace(/-+/g, '-')
|
|
11
|
+
.replace(/^-|-$/g, '');
|
|
12
|
+
return normalized || 'channel';
|
|
13
|
+
}
|
|
14
|
+
export function channelMemoryNotePath(channelName) {
|
|
15
|
+
return `notes/channels/${slugifyChannelNoteName(channelName)}.md`;
|
|
16
|
+
}
|
|
17
|
+
export function buildChannelResetMarker(channelName, clearedAt) {
|
|
18
|
+
return [
|
|
19
|
+
'## History Reset',
|
|
20
|
+
`- Live chat history for #${channelName} was cleared at ${formatBeijingPromptTimestamp(clearedAt)}.`,
|
|
21
|
+
'- Treat older notes in this file as durable memory, not as the currently visible channel transcript.',
|
|
22
|
+
'- If asked what is currently visible in the channel, rely on current chat history or bigbang message read rather than older notes from before this reset.',
|
|
23
|
+
].join('\n');
|
|
24
|
+
}
|
|
25
|
+
function escapeRegex(text) {
|
|
26
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
27
|
+
}
|
|
28
|
+
function buildChannelNoteHeader(channelName) {
|
|
29
|
+
return [
|
|
30
|
+
`# Channel: #${channelName}`,
|
|
31
|
+
'',
|
|
32
|
+
CHANNEL_NOTE_INSTRUCTION,
|
|
33
|
+
].join('\n');
|
|
34
|
+
}
|
|
35
|
+
function buildLegacyChannelsHeader() {
|
|
36
|
+
return [
|
|
37
|
+
'# Channel Summaries',
|
|
38
|
+
'',
|
|
39
|
+
LEGACY_CHANNELS_INSTRUCTION,
|
|
40
|
+
].join('\n');
|
|
41
|
+
}
|
|
42
|
+
function buildLegacyResetEntry(channelName, clearedAt) {
|
|
43
|
+
return [
|
|
44
|
+
`## #${channelName}`,
|
|
45
|
+
`- Live chat history was cleared at ${formatBeijingPromptTimestamp(clearedAt)}.`,
|
|
46
|
+
'- Earlier bullets in this file are durable summaries, not necessarily the currently visible transcript.',
|
|
47
|
+
].join('\n');
|
|
48
|
+
}
|
|
49
|
+
function isNotFoundError(error) {
|
|
50
|
+
const message = String(error?.message ?? error);
|
|
51
|
+
return message.startsWith('not_found:');
|
|
52
|
+
}
|
|
53
|
+
function stripLeadingHeader(content, header) {
|
|
54
|
+
const normalized = content.replace(/\r\n/g, '\n').trim();
|
|
55
|
+
const lines = header.split('\n');
|
|
56
|
+
const titleLine = lines[0] ?? '';
|
|
57
|
+
const introLine = lines.find((line, index) => index > 0 && line.trim().length > 0);
|
|
58
|
+
if (!normalized.startsWith(titleLine))
|
|
59
|
+
return normalized;
|
|
60
|
+
let remaining = normalized.slice(titleLine.length);
|
|
61
|
+
while (true) {
|
|
62
|
+
remaining = remaining.replace(/^(?:[ \t]*\n)+/u, '');
|
|
63
|
+
if (!introLine || !remaining.startsWith(introLine))
|
|
64
|
+
break;
|
|
65
|
+
remaining = remaining.slice(introLine.length);
|
|
66
|
+
}
|
|
67
|
+
return remaining.trim();
|
|
68
|
+
}
|
|
69
|
+
function stripManagedChannelResetBlocks(content, channelName) {
|
|
70
|
+
const channelPattern = new RegExp(String.raw `\n*## History Reset\n- Live chat history for #${escapeRegex(channelName)} was cleared at [^\n]+\.\n- Treat older notes in this file as durable memory, not as the currently visible channel transcript\.\n- If asked what is currently visible in the channel, rely on current chat history or (?:bigbang message read|read_history\(\.\.\.\)) rather than older notes from before this reset\.\n*`, 'g');
|
|
71
|
+
return content.replace(channelPattern, '\n\n').trim();
|
|
72
|
+
}
|
|
73
|
+
function stripManagedLegacyResetBlocks(content, channelName) {
|
|
74
|
+
const legacyPattern = new RegExp(String.raw `\n*## #${escapeRegex(channelName)}\n- Live chat history was cleared at [^\n]+\.\n- Earlier bullets in this file are durable summaries, not necessarily the currently visible transcript\.\n*`, 'g');
|
|
75
|
+
return content.replace(legacyPattern, '\n\n').trim();
|
|
76
|
+
}
|
|
77
|
+
function buildManagedNoteContent(params) {
|
|
78
|
+
const { existingContent, header, marker, stripMarkers } = params;
|
|
79
|
+
const withoutHeader = stripLeadingHeader(existingContent, header);
|
|
80
|
+
const remaining = stripMarkers(withoutHeader);
|
|
81
|
+
return remaining.length > 0 ? `${header}\n\n${marker}\n\n${remaining}\n` : `${header}\n\n${marker}\n`;
|
|
82
|
+
}
|
|
83
|
+
async function upsertMarkerFile(params) {
|
|
84
|
+
const { broker, agent, relativePath, marker, header, stripMarkers } = params;
|
|
85
|
+
if (!agent.nodeId || !agent.workspacePath)
|
|
86
|
+
return;
|
|
87
|
+
try {
|
|
88
|
+
const existing = await broker.readFile(agent.nodeId, agent.workspacePath, relativePath);
|
|
89
|
+
await broker.writeFile(agent.nodeId, agent.workspacePath, relativePath, buildManagedNoteContent({
|
|
90
|
+
existingContent: existing.content,
|
|
91
|
+
header,
|
|
92
|
+
marker,
|
|
93
|
+
stripMarkers,
|
|
94
|
+
}), 'overwrite');
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
if (!isNotFoundError(error))
|
|
98
|
+
throw error;
|
|
99
|
+
await broker.writeFile(agent.nodeId, agent.workspacePath, relativePath, `${header}\n\n${marker}\n`, 'overwrite');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
export async function appendChannelResetMarkers(params) {
|
|
103
|
+
const { broker, agents, channelName, clearedAt } = params;
|
|
104
|
+
const notePath = channelMemoryNotePath(channelName);
|
|
105
|
+
const channelMarker = buildChannelResetMarker(channelName, clearedAt);
|
|
106
|
+
const legacyMarker = buildLegacyResetEntry(channelName, clearedAt);
|
|
107
|
+
for (const agent of agents) {
|
|
108
|
+
if (!agent.nodeId || !agent.workspacePath)
|
|
109
|
+
continue;
|
|
110
|
+
await upsertMarkerFile({
|
|
111
|
+
broker,
|
|
112
|
+
agent,
|
|
113
|
+
relativePath: notePath,
|
|
114
|
+
marker: channelMarker,
|
|
115
|
+
header: buildChannelNoteHeader(channelName),
|
|
116
|
+
stripMarkers: (content) => stripManagedChannelResetBlocks(content, channelName),
|
|
117
|
+
});
|
|
118
|
+
await upsertMarkerFile({
|
|
119
|
+
broker,
|
|
120
|
+
agent,
|
|
121
|
+
relativePath: LEGACY_CHANNELS_NOTE_PATH,
|
|
122
|
+
marker: legacyMarker,
|
|
123
|
+
header: buildLegacyChannelsHeader(),
|
|
124
|
+
stripMarkers: (content) => stripManagedLegacyResetBlocks(content, channelName),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { extractMentionedNames } from '@bbigbang/protocol';
|
|
2
|
+
export function findMentionedAgents(content, agents) {
|
|
3
|
+
const mentionedNames = extractMentionedNames(content);
|
|
4
|
+
if (mentionedNames.length === 0)
|
|
5
|
+
return [];
|
|
6
|
+
const agentByName = new Map(agents.map((agent) => [agent.name.toLowerCase(), agent]));
|
|
7
|
+
return mentionedNames
|
|
8
|
+
.map((name) => agentByName.get(name))
|
|
9
|
+
.filter((agent) => Boolean(agent));
|
|
10
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function allocateNextChannelMessageSeq(db, channelId) {
|
|
2
|
+
const tx = db.transaction((targetChannelId) => {
|
|
3
|
+
db.prepare(`INSERT INTO channel_message_sequences(channel_id, next_seq)
|
|
4
|
+
VALUES(
|
|
5
|
+
?,
|
|
6
|
+
COALESCE((SELECT MAX(seq) FROM channel_messages WHERE channel_id = ?), 0) + 1
|
|
7
|
+
)
|
|
8
|
+
ON CONFLICT(channel_id) DO NOTHING`).run(targetChannelId, targetChannelId);
|
|
9
|
+
const row = db.prepare(`UPDATE channel_message_sequences
|
|
10
|
+
SET next_seq = next_seq + 1
|
|
11
|
+
WHERE channel_id = ?
|
|
12
|
+
RETURNING next_seq - 1 as seq`).get(targetChannelId);
|
|
13
|
+
if (!row) {
|
|
14
|
+
throw new Error(`Failed to allocate message sequence for channel ${targetChannelId}`);
|
|
15
|
+
}
|
|
16
|
+
return row.seq;
|
|
17
|
+
});
|
|
18
|
+
return tx(channelId);
|
|
19
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export function upsertChannelSubscription(db, params) {
|
|
2
|
+
const now = params.lastActiveAt ?? Date.now();
|
|
3
|
+
const subscribedAt = params.subscribedAt ?? now;
|
|
4
|
+
db.prepare(`INSERT INTO channel_subscriptions(channel_id, agent_id, subscribed_at, last_active_at)
|
|
5
|
+
VALUES(?, ?, ?, ?)
|
|
6
|
+
ON CONFLICT(channel_id, agent_id) DO UPDATE SET
|
|
7
|
+
subscribed_at = MIN(channel_subscriptions.subscribed_at, excluded.subscribed_at),
|
|
8
|
+
last_active_at = MAX(channel_subscriptions.last_active_at, excluded.last_active_at)`).run(params.channelId, params.agentId, subscribedAt, now);
|
|
9
|
+
}
|
|
10
|
+
export function listChannelSubscriptions(db, channelId) {
|
|
11
|
+
return db.prepare(`SELECT s.agent_id as agentId,
|
|
12
|
+
a.name as name,
|
|
13
|
+
s.subscribed_at as subscribedAt,
|
|
14
|
+
s.last_active_at as lastActiveAt
|
|
15
|
+
FROM channel_subscriptions s
|
|
16
|
+
JOIN agents a ON a.agent_id = s.agent_id
|
|
17
|
+
WHERE s.channel_id = ?
|
|
18
|
+
ORDER BY s.last_active_at DESC, a.name ASC`).all(channelId);
|
|
19
|
+
}
|
|
20
|
+
export function deleteChannelSubscription(db, channelId, agentId) {
|
|
21
|
+
db.prepare(`DELETE FROM channel_subscriptions
|
|
22
|
+
WHERE channel_id = ? AND agent_id = ?`).run(channelId, agentId);
|
|
23
|
+
}
|
|
24
|
+
export function deleteChannelSubscriptionsForChannel(db, channelId) {
|
|
25
|
+
db.prepare('DELETE FROM channel_subscriptions WHERE channel_id = ?').run(channelId);
|
|
26
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const CLEARED_TASK_ROOT_TARGET_PREFIX = '__cleared_task_root__:';
|
|
2
|
+
export const CLEARED_TASK_ROOT_TARGET_GLOB = `${CLEARED_TASK_ROOT_TARGET_PREFIX}*`;
|
|
3
|
+
export const CHANNEL_TASK_ROOT_PRESERVED_SOURCE = 'channel_task_root_preserved_after_clear';
|
|
4
|
+
export function buildClearedTaskRootArchiveTarget(threadRootId) {
|
|
5
|
+
return `${CLEARED_TASK_ROOT_TARGET_PREFIX}${threadRootId.trim()}`;
|
|
6
|
+
}
|
|
7
|
+
export function isClearedTaskRootTarget(target) {
|
|
8
|
+
const normalized = target?.trim() ?? '';
|
|
9
|
+
return normalized.startsWith(CLEARED_TASK_ROOT_TARGET_PREFIX);
|
|
10
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export const NO_CLAIM_TASKS_FROM_NOTIFICATION_CONSTRAINT = 'Do not run bigbang task claim from this notification to make yourself owner.';
|
|
2
|
+
export const NO_DIRECT_VISIBLE_OUTPUT_CONSTRAINT = 'Do not output user-visible text directly.';
|
|
3
|
+
export const NO_TARGET_OR_CROSS_SURFACE_CONSTRAINT = 'Do not set target or post elsewhere.';
|
|
4
|
+
export const NO_TARGET_CROSS_SURFACE_OR_VISIBLE_OUTPUT_CONSTRAINT = 'Do not set target, post elsewhere, or output user-visible text directly.';
|
|
5
|
+
export function buildCurrentConversationRoutingConstraint(params) {
|
|
6
|
+
const targetAuthority = params?.targetAuthority ?? 'explicit';
|
|
7
|
+
const targetConstraint = targetAuthority === 'platform'
|
|
8
|
+
? 'Do not set target unless the platform explicitly asks for it.'
|
|
9
|
+
: 'Do not set target unless explicitly asked.';
|
|
10
|
+
const parts = [
|
|
11
|
+
targetConstraint,
|
|
12
|
+
'Do not post elsewhere.',
|
|
13
|
+
];
|
|
14
|
+
if (params?.includeVisibleOutputConstraint !== false) {
|
|
15
|
+
parts.push(NO_DIRECT_VISIBLE_OUTPUT_CONSTRAINT);
|
|
16
|
+
}
|
|
17
|
+
return parts.join(' ');
|
|
18
|
+
}
|
|
19
|
+
export function isClosedTaskPeerInboxNoopReply(content) {
|
|
20
|
+
const normalized = content
|
|
21
|
+
.replace(/\s+/g, ' ')
|
|
22
|
+
.replace(/[。.!!??]+$/u, '')
|
|
23
|
+
.trim()
|
|
24
|
+
.toLowerCase();
|
|
25
|
+
if (!normalized || normalized.length > 240)
|
|
26
|
+
return false;
|
|
27
|
+
return [
|
|
28
|
+
/^no (additional|further|more)? ?(thread )?(reply|response|message|action|input|update) (is )?(needed|required|necessary)( from me)?( here)?$/u,
|
|
29
|
+
/^no (additional|further|more)? ?(context|updates?|input|reply|response) (to add|from me)( here)?$/u,
|
|
30
|
+
/^nothing (new |additional |further |more )?to (add|report|reply|send)( here)?$/u,
|
|
31
|
+
/^i have no (new|additional|further|more) (context|updates?|input|reply|response)( to add)?( here)?$/u,
|
|
32
|
+
/^(acknowledged|noted|收到|已知悉|了解)$/u,
|
|
33
|
+
/^(无需|不需要|不用)(再)?(回复|回应|发消息|补充|处理)$/u,
|
|
34
|
+
/^(没有|暂无)(新增|额外|更多)?(信息|补充|上下文|更新|回复)$/u,
|
|
35
|
+
].some((pattern) => pattern.test(normalized));
|
|
36
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { getAgentMessageCheckpoint } from './messageCheckpoints.js';
|
|
2
|
+
import { advanceAgentSurfaceNotificationAck, getAgentSurfaceNotificationState, listAgentSurfaceRosterParticipants, } from './notificationRounds.js';
|
|
3
|
+
import { listRecentTargetParticipants, listTargetParticipants, TARGET_PARTICIPANT_ACTIVE_WINDOW_MS, } from './targetParticipants.js';
|
|
4
|
+
function sortParticipants(participants) {
|
|
5
|
+
return [...participants].sort((left, right) => {
|
|
6
|
+
const leftRoleOrder = left.role === 'owner' ? 0 : 1;
|
|
7
|
+
const rightRoleOrder = right.role === 'owner' ? 0 : 1;
|
|
8
|
+
if (leftRoleOrder !== rightRoleOrder)
|
|
9
|
+
return leftRoleOrder - rightRoleOrder;
|
|
10
|
+
if (left.lastActiveAt !== right.lastActiveAt) {
|
|
11
|
+
return right.lastActiveAt - left.lastActiveAt;
|
|
12
|
+
}
|
|
13
|
+
return left.name.localeCompare(right.name);
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
function mergeParticipants(...lists) {
|
|
17
|
+
const merged = new Map();
|
|
18
|
+
for (const list of lists) {
|
|
19
|
+
for (const participant of list) {
|
|
20
|
+
const existing = merged.get(participant.agentId);
|
|
21
|
+
if (!existing) {
|
|
22
|
+
merged.set(participant.agentId, participant);
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
merged.set(participant.agentId, {
|
|
26
|
+
...existing,
|
|
27
|
+
name: participant.name || existing.name,
|
|
28
|
+
role: existing.role === 'owner' || participant.role === 'owner' ? 'owner' : 'participant',
|
|
29
|
+
joinedAt: Math.min(existing.joinedAt, participant.joinedAt),
|
|
30
|
+
lastActiveAt: Math.max(existing.lastActiveAt, participant.lastActiveAt),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return sortParticipants([...merged.values()]);
|
|
35
|
+
}
|
|
36
|
+
function normalizeRootParticipants(participants) {
|
|
37
|
+
return participants.map((participant) => (participant.role === 'owner'
|
|
38
|
+
? { ...participant, role: 'participant' }
|
|
39
|
+
: participant));
|
|
40
|
+
}
|
|
41
|
+
export function getUnifiedSurfaceUnreadCheckpoint(db, params) {
|
|
42
|
+
const notificationCheckpoint = getAgentSurfaceNotificationState(db, {
|
|
43
|
+
targetAgentId: params.agentId,
|
|
44
|
+
channelId: params.channelId,
|
|
45
|
+
threadRootId: params.threadRootId,
|
|
46
|
+
})?.ackedThroughSeq ?? 0;
|
|
47
|
+
const legacyCheckpoint = getAgentMessageCheckpoint(db, params.agentId, params.channelId, params.threadRootId ?? null);
|
|
48
|
+
return Math.max(notificationCheckpoint, legacyCheckpoint);
|
|
49
|
+
}
|
|
50
|
+
export function advanceAgentSurfaceSeenThroughSeq(db, params) {
|
|
51
|
+
const seenThroughSeq = Math.max(0, Math.floor(params.seenThroughSeq));
|
|
52
|
+
if (seenThroughSeq <= 0)
|
|
53
|
+
return;
|
|
54
|
+
advanceAgentSurfaceNotificationAck(db, {
|
|
55
|
+
targetAgentId: params.agentId,
|
|
56
|
+
channelId: params.channelId,
|
|
57
|
+
threadRootId: params.threadRootId ?? null,
|
|
58
|
+
ackedThroughSeq: seenThroughSeq,
|
|
59
|
+
now: params.now,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
const CHANNEL_ROOT_ROSTER_LIMIT = 20;
|
|
63
|
+
export function listUnifiedSurfaceParticipants(db, params) {
|
|
64
|
+
const isChannelRoot = !params.threadRootId?.trim();
|
|
65
|
+
const targetParticipants = listTargetParticipants(db, {
|
|
66
|
+
channelId: params.channelId,
|
|
67
|
+
threadRootId: params.threadRootId,
|
|
68
|
+
});
|
|
69
|
+
const rosterParticipants = listAgentSurfaceRosterParticipants(db, {
|
|
70
|
+
channelId: params.channelId,
|
|
71
|
+
threadRootId: params.threadRootId,
|
|
72
|
+
now: params.now,
|
|
73
|
+
});
|
|
74
|
+
const merged = mergeParticipants(isChannelRoot ? normalizeRootParticipants(targetParticipants) : targetParticipants, isChannelRoot ? normalizeRootParticipants(rosterParticipants) : rosterParticipants);
|
|
75
|
+
return isChannelRoot ? merged.slice(0, CHANNEL_ROOT_ROSTER_LIMIT) : merged;
|
|
76
|
+
}
|
|
77
|
+
export function listUnifiedRecentSurfaceParticipants(db, params) {
|
|
78
|
+
const now = params.now ?? Date.now();
|
|
79
|
+
const activeSince = params.activeSince ?? (now - TARGET_PARTICIPANT_ACTIVE_WINDOW_MS);
|
|
80
|
+
const isChannelRoot = !params.threadRootId?.trim();
|
|
81
|
+
const recentTargetParticipants = listRecentTargetParticipants(db, {
|
|
82
|
+
channelId: params.channelId,
|
|
83
|
+
threadRootId: params.threadRootId,
|
|
84
|
+
activeSince,
|
|
85
|
+
});
|
|
86
|
+
const rosterParticipants = listAgentSurfaceRosterParticipants(db, {
|
|
87
|
+
channelId: params.channelId,
|
|
88
|
+
threadRootId: params.threadRootId,
|
|
89
|
+
now,
|
|
90
|
+
});
|
|
91
|
+
const merged = mergeParticipants(isChannelRoot ? normalizeRootParticipants(recentTargetParticipants) : recentTargetParticipants, isChannelRoot ? normalizeRootParticipants(rosterParticipants) : rosterParticipants);
|
|
92
|
+
return isChannelRoot ? merged.slice(0, CHANNEL_ROOT_ROSTER_LIMIT) : merged;
|
|
93
|
+
}
|
|
94
|
+
export function captureUnifiedSurfaceParticipantState(db, params) {
|
|
95
|
+
const surfaceThreadKey = params.threadRootId?.trim() ?? '';
|
|
96
|
+
const legacyParticipant = db.prepare(`SELECT role, joined_at as joinedAt, last_active_at as lastActiveAt
|
|
97
|
+
FROM target_participants
|
|
98
|
+
WHERE agent_id = ? AND channel_id = ? AND thread_root_id = ?
|
|
99
|
+
LIMIT 1`).get(params.agentId, params.channelId, surfaceThreadKey);
|
|
100
|
+
const rosterRows = db.prepare(`SELECT source,
|
|
101
|
+
source_key as sourceKey,
|
|
102
|
+
source_agent_id as sourceAgentId,
|
|
103
|
+
expires_at as expiresAt,
|
|
104
|
+
created_at as createdAt,
|
|
105
|
+
updated_at as updatedAt
|
|
106
|
+
FROM agent_surface_roster
|
|
107
|
+
WHERE target_agent_id = ?
|
|
108
|
+
AND channel_id = ?
|
|
109
|
+
AND surface_thread_key = ?
|
|
110
|
+
AND source IN ('explicit_mention', 'surface_owner', 'recent_activity')`).all(params.agentId, params.channelId, surfaceThreadKey);
|
|
111
|
+
return {
|
|
112
|
+
legacyParticipant: legacyParticipant ?? null,
|
|
113
|
+
rosterRows,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
export function restoreUnifiedSurfaceParticipantState(db, params) {
|
|
117
|
+
const surfaceThreadKey = params.threadRootId?.trim() ?? '';
|
|
118
|
+
if (params.snapshot.legacyParticipant) {
|
|
119
|
+
db.prepare(`INSERT INTO target_participants(agent_id, channel_id, thread_root_id, role, joined_at, last_active_at)
|
|
120
|
+
VALUES(?, ?, ?, ?, ?, ?)
|
|
121
|
+
ON CONFLICT(agent_id, channel_id, thread_root_id) DO UPDATE SET
|
|
122
|
+
role = excluded.role,
|
|
123
|
+
joined_at = excluded.joined_at,
|
|
124
|
+
last_active_at = excluded.last_active_at`).run(params.agentId, params.channelId, surfaceThreadKey, params.snapshot.legacyParticipant.role, params.snapshot.legacyParticipant.joinedAt, params.snapshot.legacyParticipant.lastActiveAt);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
db.prepare(`DELETE FROM target_participants
|
|
128
|
+
WHERE agent_id = ? AND channel_id = ? AND thread_root_id = ?`).run(params.agentId, params.channelId, surfaceThreadKey);
|
|
129
|
+
}
|
|
130
|
+
db.prepare(`DELETE FROM agent_surface_roster
|
|
131
|
+
WHERE target_agent_id = ?
|
|
132
|
+
AND channel_id = ?
|
|
133
|
+
AND surface_thread_key = ?
|
|
134
|
+
AND source IN ('explicit_mention', 'surface_owner', 'recent_activity')`).run(params.agentId, params.channelId, surfaceThreadKey);
|
|
135
|
+
for (const row of params.snapshot.rosterRows) {
|
|
136
|
+
db.prepare(`INSERT INTO agent_surface_roster(
|
|
137
|
+
target_agent_id, channel_id, surface_thread_key, source, source_key, source_agent_id, expires_at, created_at, updated_at
|
|
138
|
+
) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(params.agentId, params.channelId, surfaceThreadKey, row.source, row.sourceKey, row.sourceAgentId, row.expiresAt, row.createdAt, row.updatedAt);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
export const CONTEXT_BUNDLE_WEIGHTS = {
|
|
2
|
+
surface: {
|
|
3
|
+
boundTaskMatch: 120,
|
|
4
|
+
linkedTaskMatch: 90,
|
|
5
|
+
activeHandoff: 100,
|
|
6
|
+
recentHandoff: 70,
|
|
7
|
+
sameThreadRoot: 80,
|
|
8
|
+
sameTargetFamily: 60,
|
|
9
|
+
participantOverlap: 24,
|
|
10
|
+
ownerMatch: 18,
|
|
11
|
+
rolePresent: 8,
|
|
12
|
+
unreadActivity: 14,
|
|
13
|
+
queuedActivity: 18,
|
|
14
|
+
recentActivity: 10,
|
|
15
|
+
},
|
|
16
|
+
task: {
|
|
17
|
+
boundTaskMatch: 120,
|
|
18
|
+
linkedTaskMatch: 95,
|
|
19
|
+
activeHandoff: 80,
|
|
20
|
+
recentHandoff: 50,
|
|
21
|
+
sameTargetFamily: 40,
|
|
22
|
+
taskStatusActive: 20,
|
|
23
|
+
taskStatusReview: 16,
|
|
24
|
+
recentActivity: 10,
|
|
25
|
+
},
|
|
26
|
+
handoff: {
|
|
27
|
+
currentConversationHandoff: 120,
|
|
28
|
+
linkedTaskMatch: 90,
|
|
29
|
+
activeHandoff: 70,
|
|
30
|
+
recentHandoff: 30,
|
|
31
|
+
sameTargetFamily: 40,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
export const CONTEXT_BUNDLE_RECENT_ACTIVITY_WINDOW_MS = 6 * 60 * 60 * 1000;
|
|
35
|
+
export function scoreSurfaceCandidate(input) {
|
|
36
|
+
const reasons = [];
|
|
37
|
+
if (input.hasBoundTaskMatch) {
|
|
38
|
+
reasons.push(reason('bound_task_match', 'Shares the current bound task', CONTEXT_BUNDLE_WEIGHTS.surface.boundTaskMatch));
|
|
39
|
+
}
|
|
40
|
+
if (input.linkedTaskMatches.length > 0) {
|
|
41
|
+
reasons.push(reason('linked_task_match', 'Linked to the same task context', CONTEXT_BUNDLE_WEIGHTS.surface.linkedTaskMatch, summarizeList(input.linkedTaskMatches)));
|
|
42
|
+
}
|
|
43
|
+
if (input.hasActiveHandoff) {
|
|
44
|
+
reasons.push(reason('active_handoff', 'Has an open handoff relationship', CONTEXT_BUNDLE_WEIGHTS.surface.activeHandoff));
|
|
45
|
+
}
|
|
46
|
+
if (input.hasRecentHandoff) {
|
|
47
|
+
reasons.push(reason('recent_handoff', 'Recently involved in a handoff', CONTEXT_BUNDLE_WEIGHTS.surface.recentHandoff));
|
|
48
|
+
}
|
|
49
|
+
if (input.isSameThreadRoot) {
|
|
50
|
+
reasons.push(reason('same_thread_root', 'Same thread root family', CONTEXT_BUNDLE_WEIGHTS.surface.sameThreadRoot));
|
|
51
|
+
}
|
|
52
|
+
if (input.isSameTargetFamily) {
|
|
53
|
+
reasons.push(reason('same_target_family', 'Same target family', CONTEXT_BUNDLE_WEIGHTS.surface.sameTargetFamily));
|
|
54
|
+
}
|
|
55
|
+
if (input.participantOverlapCount > 0) {
|
|
56
|
+
reasons.push(reason('participant_overlap', 'Shares recent participants', CONTEXT_BUNDLE_WEIGHTS.surface.participantOverlap, `overlap=${input.participantOverlapCount}`));
|
|
57
|
+
}
|
|
58
|
+
if (input.ownerMatch) {
|
|
59
|
+
reasons.push(reason('owner_match', 'Same owner context', CONTEXT_BUNDLE_WEIGHTS.surface.ownerMatch));
|
|
60
|
+
}
|
|
61
|
+
if (input.hasRole) {
|
|
62
|
+
reasons.push(reason('role_present', 'Has an explicit agent role', CONTEXT_BUNDLE_WEIGHTS.surface.rolePresent));
|
|
63
|
+
}
|
|
64
|
+
if (input.unreadCount > 0) {
|
|
65
|
+
reasons.push(reason('unread_activity', 'Unread activity is waiting', CONTEXT_BUNDLE_WEIGHTS.surface.unreadActivity, `unread=${input.unreadCount}`));
|
|
66
|
+
}
|
|
67
|
+
if (input.queuedPromptCount > 0) {
|
|
68
|
+
reasons.push(reason('queued_activity', 'Queued prompts are waiting', CONTEXT_BUNDLE_WEIGHTS.surface.queuedActivity, `queued=${input.queuedPromptCount}`));
|
|
69
|
+
}
|
|
70
|
+
if (isRecentlyUpdated(input.updatedAt, input.nowMs)) {
|
|
71
|
+
reasons.push(reason('recent_activity', 'Updated recently', CONTEXT_BUNDLE_WEIGHTS.surface.recentActivity));
|
|
72
|
+
}
|
|
73
|
+
return { score: sumReasons(reasons), reasons };
|
|
74
|
+
}
|
|
75
|
+
export function scoreTaskCandidate(input) {
|
|
76
|
+
const reasons = [];
|
|
77
|
+
if (input.hasBoundTaskMatch) {
|
|
78
|
+
reasons.push(reason('bound_task_match', 'Matches the current bound task', CONTEXT_BUNDLE_WEIGHTS.task.boundTaskMatch));
|
|
79
|
+
}
|
|
80
|
+
if (input.linkedTaskMatches.length > 0) {
|
|
81
|
+
reasons.push(reason('linked_task_match', 'Linked to the same task context', CONTEXT_BUNDLE_WEIGHTS.task.linkedTaskMatch, summarizeList(input.linkedTaskMatches)));
|
|
82
|
+
}
|
|
83
|
+
if (input.hasActiveHandoff) {
|
|
84
|
+
reasons.push(reason('active_handoff', 'Referenced by an open handoff', CONTEXT_BUNDLE_WEIGHTS.task.activeHandoff));
|
|
85
|
+
}
|
|
86
|
+
if (input.hasRecentHandoff) {
|
|
87
|
+
reasons.push(reason('recent_handoff', 'Referenced by a recent handoff', CONTEXT_BUNDLE_WEIGHTS.task.recentHandoff));
|
|
88
|
+
}
|
|
89
|
+
if (input.isSameTargetFamily) {
|
|
90
|
+
reasons.push(reason('same_target_family', 'Lives on the same target family', CONTEXT_BUNDLE_WEIGHTS.task.sameTargetFamily));
|
|
91
|
+
}
|
|
92
|
+
if (input.status === 'in_progress') {
|
|
93
|
+
reasons.push(reason('task_status_active', 'Task is actively in progress', CONTEXT_BUNDLE_WEIGHTS.task.taskStatusActive));
|
|
94
|
+
}
|
|
95
|
+
if (input.status === 'in_review') {
|
|
96
|
+
reasons.push(reason('task_status_review', 'Task is waiting in review', CONTEXT_BUNDLE_WEIGHTS.task.taskStatusReview));
|
|
97
|
+
}
|
|
98
|
+
if (isRecentlyUpdated(input.updatedAt, input.nowMs)) {
|
|
99
|
+
reasons.push(reason('recent_activity', 'Updated recently', CONTEXT_BUNDLE_WEIGHTS.task.recentActivity));
|
|
100
|
+
}
|
|
101
|
+
return { score: sumReasons(reasons), reasons };
|
|
102
|
+
}
|
|
103
|
+
export function scoreHandoffCandidate(input) {
|
|
104
|
+
const reasons = [];
|
|
105
|
+
if (input.matchesCurrentConversation) {
|
|
106
|
+
reasons.push(reason('current_conversation_handoff', 'Directly tied to the current conversation', CONTEXT_BUNDLE_WEIGHTS.handoff.currentConversationHandoff));
|
|
107
|
+
}
|
|
108
|
+
if (input.hasRelatedTaskMatch) {
|
|
109
|
+
reasons.push(reason('linked_task_match', 'Shares the current task context', CONTEXT_BUNDLE_WEIGHTS.handoff.linkedTaskMatch));
|
|
110
|
+
}
|
|
111
|
+
if (input.status === 'started' || input.status === 'accepted') {
|
|
112
|
+
reasons.push(reason('active_handoff', 'Handoff is still open', CONTEXT_BUNDLE_WEIGHTS.handoff.activeHandoff));
|
|
113
|
+
}
|
|
114
|
+
else if (isRecentlyUpdated(input.updatedAt, input.nowMs)) {
|
|
115
|
+
reasons.push(reason('recent_handoff', 'Handoff changed recently', CONTEXT_BUNDLE_WEIGHTS.handoff.recentHandoff));
|
|
116
|
+
}
|
|
117
|
+
if (input.isSameTargetFamily) {
|
|
118
|
+
reasons.push(reason('same_target_family', 'Touches the same target family', CONTEXT_BUNDLE_WEIGHTS.handoff.sameTargetFamily));
|
|
119
|
+
}
|
|
120
|
+
return { score: sumReasons(reasons), reasons };
|
|
121
|
+
}
|
|
122
|
+
export function compareRankedItems(left, right) {
|
|
123
|
+
if (right.score !== left.score)
|
|
124
|
+
return right.score - left.score;
|
|
125
|
+
const leftTime = Date.parse(left.updatedAt);
|
|
126
|
+
const rightTime = Date.parse(right.updatedAt);
|
|
127
|
+
if (Number.isFinite(leftTime) && Number.isFinite(rightTime) && rightTime !== leftTime) {
|
|
128
|
+
return rightTime - leftTime;
|
|
129
|
+
}
|
|
130
|
+
return 0;
|
|
131
|
+
}
|
|
132
|
+
export function isRecentlyUpdated(updatedAt, nowMs = Date.now()) {
|
|
133
|
+
const updatedAtMs = Date.parse(updatedAt);
|
|
134
|
+
if (!Number.isFinite(updatedAtMs))
|
|
135
|
+
return false;
|
|
136
|
+
return nowMs - updatedAtMs <= CONTEXT_BUNDLE_RECENT_ACTIVITY_WINDOW_MS;
|
|
137
|
+
}
|
|
138
|
+
function reason(code, label, weight, detail) {
|
|
139
|
+
return {
|
|
140
|
+
code,
|
|
141
|
+
label,
|
|
142
|
+
weight,
|
|
143
|
+
...(detail ? { detail } : {}),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function sumReasons(reasons) {
|
|
147
|
+
return reasons.reduce((sum, item) => sum + item.weight, 0);
|
|
148
|
+
}
|
|
149
|
+
function summarizeList(values) {
|
|
150
|
+
const filtered = values.map((value) => value.trim()).filter(Boolean);
|
|
151
|
+
if (filtered.length === 0)
|
|
152
|
+
return null;
|
|
153
|
+
return filtered.slice(0, 3).join(', ');
|
|
154
|
+
}
|