@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,26 @@
1
+ export function upsertAgentTaskLink(db, params) {
2
+ const agentId = params.agentId?.trim();
3
+ if (!agentId)
4
+ return;
5
+ const createdRelation = params.created ? 1 : 0;
6
+ const assignedRelation = params.assigned ? 1 : 0;
7
+ // This table is kept as a compatibility/visibility index.
8
+ // Formal shared-role semantics should live in task_participants.
9
+ if (!createdRelation && !assignedRelation && !params.shared)
10
+ return;
11
+ const linkedAt = params.linkedAt ?? Date.now();
12
+ db.prepare(`INSERT INTO agent_task_links(
13
+ agent_id,
14
+ task_id,
15
+ created_relation,
16
+ assigned_relation,
17
+ first_linked_at,
18
+ last_linked_at
19
+ )
20
+ VALUES(?, ?, ?, ?, ?, ?)
21
+ ON CONFLICT(agent_id, task_id) DO UPDATE SET
22
+ created_relation = MAX(agent_task_links.created_relation, excluded.created_relation),
23
+ assigned_relation = MAX(agent_task_links.assigned_relation, excluded.assigned_relation),
24
+ first_linked_at = MIN(agent_task_links.first_linked_at, excluded.first_linked_at),
25
+ last_linked_at = MAX(agent_task_links.last_linked_at, excluded.last_linked_at)`).run(agentId, params.taskId, createdRelation, assignedRelation, linkedAt, linkedAt);
26
+ }
@@ -0,0 +1,79 @@
1
+ import { ensureConversationIdForReplyTarget, findConversationIdForReadTarget, resolveChannelFromTarget, } from './conversationTargets.js';
2
+ function buildJoinedChannelTargets(db, agent) {
3
+ const rows = db.prepare(`SELECT c.channel_id as channelId, ch.name as channelName
4
+ FROM agent_channel_memberships c
5
+ LEFT JOIN channels ch ON ch.channel_id = c.channel_id
6
+ WHERE c.agent_id = ?`).all(agent.agentId);
7
+ return new Set(rows.map((row) => (row.channelId.startsWith('dm:')
8
+ ? row.channelId
9
+ : `#${row.channelName ?? row.channelId}`)));
10
+ }
11
+ export class AgentVisibility {
12
+ db;
13
+ constructor(db) {
14
+ this.db = db;
15
+ }
16
+ canSeeConversation(agent, conversationId) {
17
+ const conversation = this.db.prepare(`SELECT channel_id as channelId, agent_id as agentId
18
+ FROM conversations
19
+ WHERE id = ?
20
+ LIMIT 1`).get(conversationId);
21
+ if (!conversation || conversation.agentId !== agent.agentId)
22
+ return false;
23
+ if (conversation.channelId.startsWith('dm:'))
24
+ return conversation.channelId === `dm:${agent.agentId}`;
25
+ return (agent.channelIds ?? []).includes(conversation.channelId);
26
+ }
27
+ canSeeTarget(agent, target) {
28
+ return this.resolveVisibleChannelId(agent, target) !== null;
29
+ }
30
+ canSeeHandoff(agent, handoff) {
31
+ const sourceVisible = this.canSeeConversation(agent, handoff.sourceConversationId)
32
+ || this.canSeeTarget(agent, handoff.sourceTarget);
33
+ const targetVisible = (handoff.targetConversationId ? this.canSeeConversation(agent, handoff.targetConversationId) : false)
34
+ || this.canSeeTarget(agent, handoff.targetReplyTarget);
35
+ return sourceVisible && targetVisible;
36
+ }
37
+ sanitizeSummaryForSelfState(agent, summary) {
38
+ if (!summary.activeHandoff || this.canSeeHandoff(agent, summary.activeHandoff)) {
39
+ return summary;
40
+ }
41
+ return {
42
+ ...summary,
43
+ blockers: summary.blockers.filter((blocker) => !blocker.startsWith('Handoff in progress to ')),
44
+ activeHandoff: null,
45
+ };
46
+ }
47
+ resolveVisibleChannelId(agent, target) {
48
+ const normalizedTarget = String(target ?? '').trim();
49
+ if (!normalizedTarget)
50
+ return null;
51
+ if (normalizedTarget.startsWith('dm:')) {
52
+ const resolvedDmChannelId = resolveChannelFromTarget(normalizedTarget, this.db);
53
+ const ownDmChannelId = `dm:${agent.agentId}`;
54
+ if (resolvedDmChannelId && resolvedDmChannelId !== ownDmChannelId) {
55
+ return null;
56
+ }
57
+ return ownDmChannelId;
58
+ }
59
+ if (!normalizedTarget.startsWith('#'))
60
+ return null;
61
+ const channelId = resolveChannelFromTarget(normalizedTarget, this.db);
62
+ if (!channelId)
63
+ return null;
64
+ return (agent.channelIds ?? []).includes(channelId) ? channelId : null;
65
+ }
66
+ resolveReadableConversationId(agent, target) {
67
+ if (!this.canSeeTarget(agent, target))
68
+ return null;
69
+ return findConversationIdForReadTarget(this.db, agent.agentId, target);
70
+ }
71
+ ensureVisibleConversationId(agent, conversationManager, target) {
72
+ if (!this.canSeeTarget(agent, target))
73
+ return null;
74
+ return ensureConversationIdForReplyTarget(this.db, conversationManager, agent.agentId, target);
75
+ }
76
+ joinedChannelTargets(agent) {
77
+ return buildJoinedChannelTargets(this.db, agent);
78
+ }
79
+ }
@@ -0,0 +1,95 @@
1
+ import path from 'node:path';
2
+ import { pathToFileURL } from 'node:url';
3
+ const CODE_EXTENSIONS = new Set([
4
+ '.c', '.cc', '.cpp', '.cs', '.css', '.go', '.h', '.hpp', '.html', '.java',
5
+ '.js', '.jsonc', '.jsx', '.kt', '.lua', '.mjs', '.php', '.py', '.rb', '.rs',
6
+ '.scss', '.sh', '.sql', '.swift', '.toml', '.ts', '.tsx', '.vue', '.xml',
7
+ '.yaml', '.yml',
8
+ ]);
9
+ const TEXT_EXTENSIONS = new Set([
10
+ '.csv', '.ini', '.log', '.md', '.rst', '.text', '.toml', '.txt',
11
+ ]);
12
+ export function inferAssetKind(filename, mimeType) {
13
+ const normalizedMime = (mimeType ?? '').trim().toLowerCase();
14
+ const ext = path.extname(filename).toLowerCase();
15
+ if (normalizedMime.startsWith('image/'))
16
+ return 'image';
17
+ if (normalizedMime === 'application/json' || ext === '.json')
18
+ return 'json';
19
+ if (normalizedMime.startsWith('text/')) {
20
+ return CODE_EXTENSIONS.has(ext) ? 'code' : 'text';
21
+ }
22
+ if (CODE_EXTENSIONS.has(ext)) {
23
+ return ext === '.json' ? 'json' : 'code';
24
+ }
25
+ if (TEXT_EXTENSIONS.has(ext))
26
+ return 'text';
27
+ return 'binary';
28
+ }
29
+ export function sanitizeAssetFilename(filename) {
30
+ const base = path.basename(filename).trim() || 'file';
31
+ return base
32
+ .replace(/[^A-Za-z0-9._-]+/g, '_')
33
+ .replace(/_+/g, '_')
34
+ .replace(/^_+|_+$/g, '')
35
+ || 'file';
36
+ }
37
+ export function deriveAssetScope(row) {
38
+ if (row.scopeType && row.scopeId) {
39
+ return { scopeType: row.scopeType, scopeId: row.scopeId };
40
+ }
41
+ if (row.channelId) {
42
+ return {
43
+ scopeType: row.channelId.startsWith('dm:') ? 'direct_conversation' : 'channel',
44
+ scopeId: row.channelId,
45
+ };
46
+ }
47
+ if (row.agentId) {
48
+ return { scopeType: 'agent_upload', scopeId: row.agentId };
49
+ }
50
+ return { scopeType: 'agent_upload', scopeId: 'unscoped' };
51
+ }
52
+ export function buildAssetStoredFilename(assetId, filename) {
53
+ return `${assetId}--${sanitizeAssetFilename(filename)}`;
54
+ }
55
+ function encodeScopePathSegment(value) {
56
+ return encodeURIComponent(value).replace(/%/g, '_');
57
+ }
58
+ export function buildAssetBucket(scopeType, scopeId) {
59
+ const safeScopeId = encodeScopePathSegment(scopeId);
60
+ switch (scopeType) {
61
+ case 'direct_conversation':
62
+ return ['direct', safeScopeId];
63
+ case 'channel':
64
+ return ['channels', safeScopeId];
65
+ case 'thread':
66
+ return ['threads', safeScopeId];
67
+ case 'agent_upload':
68
+ default:
69
+ return ['agent-uploads', safeScopeId];
70
+ }
71
+ }
72
+ export function buildCanonicalAssetStoragePath(params) {
73
+ return path.join(params.assetsRoot, ...buildAssetBucket(params.scopeType, params.scopeId), buildAssetStoredFilename(params.assetId, params.filename));
74
+ }
75
+ export function buildPreferredLocalAssetRelativePath(row) {
76
+ const { scopeType, scopeId } = deriveAssetScope(row);
77
+ return path.join('.platform-assets', ...buildAssetBucket(scopeType, scopeId), buildAssetStoredFilename(row.id, row.originalFilename ?? row.filename));
78
+ }
79
+ export function buildAssetFileRef(row) {
80
+ const { scopeType, scopeId } = deriveAssetScope(row);
81
+ const filename = row.originalFilename ?? row.filename;
82
+ return {
83
+ uri: `attachment:${row.id}`,
84
+ ...(row.mimeType ? { mimeType: row.mimeType } : {}),
85
+ assetId: row.id,
86
+ filename,
87
+ sizeBytes: row.sizeBytes,
88
+ assetKind: inferAssetKind(filename, row.mimeType),
89
+ scopeType,
90
+ scopeId,
91
+ canonicalUri: pathToFileURL(row.storagePath).toString(),
92
+ canonicalPath: row.storagePath,
93
+ preferredLocalPath: buildPreferredLocalAssetRelativePath(row),
94
+ };
95
+ }
@@ -0,0 +1,395 @@
1
+ import { formatBeijingPromptTimestamp } from '@bbigbang/protocol';
2
+ import { sanitizePromptHistoryContent } from './promptHistorySanitizer.js';
3
+ import { historyLimitForSurfaceActivationTier, resolveChannelSurfaceActivationPolicy, } from './surfaceActivationPolicy.js';
4
+ import { buildWorkspaceMemoryHintSection, getCurrentISOWeek, resolveChannelTaskBoardPresencePolicy, } from './workspaceMemoryHints.js';
5
+ import { createPromptContextSection, renderPromptContextSections, } from './promptContextSections.js';
6
+ import { buildCurrentConversationRoutingConstraint, NO_CLAIM_TASKS_FROM_NOTIFICATION_CONSTRAINT, } from './collaborationPromptGuidance.js';
7
+ const MESSAGE_SEPARATOR = '\n\n---\n\n';
8
+ const TASK_KICKOFF_RE = /^@(\S+) you have been assigned task #(\d+): (.+)$/u;
9
+ const COLLABORATOR_BOOTSTRAP_RE = /(?:^|\s)(?:explicitly\s+)?added you as a collaborator on task #\d+:/iu;
10
+ function removeSharedCollaboratorLine(content) {
11
+ return content.replace(/\n\nShared collaborators: [^\n]+(?=\n\nTask brief \/ goal \/ done criteria:\n)/u, '');
12
+ }
13
+ function parseTaskKickoff(content) {
14
+ const normalized = removeSharedCollaboratorLine(content);
15
+ const lines = normalized.split('\n');
16
+ const first = lines[0]?.match(TASK_KICKOFF_RE);
17
+ if (!first)
18
+ return null;
19
+ const taskBriefIndex = lines.findIndex((line) => line.trim() === 'Task brief / goal / done criteria:');
20
+ if (taskBriefIndex < 0)
21
+ return null;
22
+ const endInstructionIndex = lines.findIndex((line, index) => (index > taskBriefIndex
23
+ && line.trim() === 'Please start working from this thread, post progress updates here, and move the task to in_review when the implementation is ready.'));
24
+ const bodyEnd = endInstructionIndex >= 0 ? endInstructionIndex : lines.length;
25
+ const bodyLines = lines.slice(taskBriefIndex + 1, bodyEnd);
26
+ const attachmentIndex = bodyLines.findIndex((line) => /^\[Task attachments?\]$/u.test(line.trim()));
27
+ const descriptionLines = attachmentIndex >= 0 ? bodyLines.slice(0, attachmentIndex) : bodyLines;
28
+ const attachmentLines = attachmentIndex >= 0 ? bodyLines.slice(attachmentIndex) : [];
29
+ return {
30
+ ownerName: first[1] ?? '',
31
+ taskNumber: first[2] ?? '',
32
+ title: first[3] ?? '',
33
+ description: descriptionLines.join('\n').trim(),
34
+ attachmentSection: attachmentLines.join('\n').trim(),
35
+ };
36
+ }
37
+ function isCollaboratorSupportBootstrap(content) {
38
+ const firstLine = content.split('\n')[0]?.trim() ?? '';
39
+ return COLLABORATOR_BOOTSTRAP_RE.test(firstLine);
40
+ }
41
+ export function shouldUseOwnerAwarenessForChannelActivation(params) {
42
+ if ((params.targetRole ?? 'owner') !== 'owner')
43
+ return false;
44
+ if (params.reason !== 'thread_reply')
45
+ return false;
46
+ const targetAgentId = params.targetAgentId?.trim();
47
+ if (!targetAgentId)
48
+ return false;
49
+ const rawMentionedAgentIds = new Set((params.explicitlyMentionedAgentIds ?? [])
50
+ .map((agentId) => agentId.trim())
51
+ .filter((agentId) => agentId.length > 0));
52
+ if (rawMentionedAgentIds.has(targetAgentId))
53
+ return false;
54
+ const awarenessMentionedAgentIds = new Set((params.ownerAwarenessMentionedAgentIds ?? params.explicitlyMentionedAgentIds ?? [])
55
+ .map((agentId) => agentId.trim())
56
+ .filter((agentId) => agentId.length > 0 && agentId !== targetAgentId));
57
+ return awarenessMentionedAgentIds.size > 0;
58
+ }
59
+ export function shouldSuppressReplyContractForChannelActivation(params) {
60
+ const targetRole = params.targetRole ?? 'owner';
61
+ if (targetRole === 'participant')
62
+ return true;
63
+ return shouldUseOwnerAwarenessForChannelActivation(params);
64
+ }
65
+ function sanitizeTriggeredMessageBody(content, targetRole = 'owner') {
66
+ const kickoff = parseTaskKickoff(content);
67
+ if (!kickoff)
68
+ return content;
69
+ if (targetRole !== 'participant') {
70
+ return removeSharedCollaboratorLine(content);
71
+ }
72
+ return [
73
+ `You were added as a collaborator/supporting agent on task #${kickoff.taskNumber}: ${kickoff.title}`,
74
+ `Task owner / assignee: @${kickoff.ownerName}`,
75
+ '',
76
+ 'Task brief / goal / done criteria:',
77
+ kickoff.description || '[missing task brief]',
78
+ ...(kickoff.attachmentSection ? ['', kickoff.attachmentSection] : []),
79
+ '',
80
+ '[Collaborator support guidance]',
81
+ 'Default: absorb this task context. You may stay quiet if you have no new support context.',
82
+ 'If useful, send one concise support update in this current conversation with `bigbang message send --kind progress`: artifact paths, parameters, caveats, validation notes, or missing inputs.',
83
+ buildCurrentConversationRoutingConstraint(),
84
+ 'Do not take over primary execution, send the substantive final result, or move task status unless ownership is explicitly transferred.',
85
+ NO_CLAIM_TASKS_FROM_NOTIFICATION_CONSTRAINT,
86
+ ].join('\n');
87
+ }
88
+ function buildParticipantPromptGuidance(params) {
89
+ const isKickoff = parseTaskKickoff(params.content) !== null;
90
+ if (isKickoff)
91
+ return [];
92
+ if (isCollaboratorSupportBootstrap(params.content)) {
93
+ return [
94
+ '',
95
+ '[Collaborator support guidance]',
96
+ 'Default: absorb this task context. You may stay quiet if you have no new support context.',
97
+ 'If useful, send one concise support update in this current conversation with `bigbang message send --kind progress`.',
98
+ buildCurrentConversationRoutingConstraint(),
99
+ 'Do not take over primary execution, send the substantive final result, or move task status unless ownership is explicitly transferred.',
100
+ NO_CLAIM_TASKS_FROM_NOTIFICATION_CONSTRAINT,
101
+ ];
102
+ }
103
+ if (params.reason === 'thread_reply') {
104
+ return [
105
+ '',
106
+ '[Participant awareness guidance]',
107
+ 'This was delivered because you are a participant on this thread, not because you were directly asked to act.',
108
+ 'Default: absorb this update into your task/thread state; you may stay quiet.',
109
+ 'Reply only if you have key support context, a correction, missing input, or a useful handoff for the owner/user.',
110
+ `If you reply, use \`bigbang message send --kind progress\` in this current conversation. ${buildCurrentConversationRoutingConstraint()}`,
111
+ NO_CLAIM_TASKS_FROM_NOTIFICATION_CONSTRAINT,
112
+ ];
113
+ }
114
+ if (params.reason === 'mention' || params.reason === 'agent_mention') {
115
+ if (params.isChannelRoot) {
116
+ return [
117
+ '',
118
+ '[Mention reply contract]',
119
+ 'You were directly mentioned as a participant on this channel root.',
120
+ 'If a visible response is useful, reply with `bigbang message send --kind progress` in this current conversation.',
121
+ buildCurrentConversationRoutingConstraint(),
122
+ ];
123
+ }
124
+ return [
125
+ '',
126
+ '[Collaborator reply constraints]',
127
+ 'You are a collaborator/supporting participant, not the task owner.',
128
+ 'If your help is needed, reply with `bigbang message send --kind progress` in this current conversation.',
129
+ buildCurrentConversationRoutingConstraint(),
130
+ 'Do not send the substantive final result or change task status unless ownership is explicitly transferred.',
131
+ NO_CLAIM_TASKS_FROM_NOTIFICATION_CONSTRAINT,
132
+ ];
133
+ }
134
+ if (params.reason === 'channel_activity') {
135
+ return [
136
+ '',
137
+ '[Participant channel awareness guidance]',
138
+ 'This was delivered because you are a current participant on this channel surface, not because you were directly asked to act.',
139
+ 'Default: absorb this update; you may stay quiet.',
140
+ 'Reply only if you have key support context, a correction, missing input, or a useful handoff for the channel.',
141
+ `If you reply, use \`bigbang message send --kind progress\` in this current conversation. ${buildCurrentConversationRoutingConstraint()}`,
142
+ NO_CLAIM_TASKS_FROM_NOTIFICATION_CONSTRAINT,
143
+ ];
144
+ }
145
+ return [];
146
+ }
147
+ function isChannelRootTarget(target) {
148
+ const trimmed = target.trim();
149
+ return trimmed.startsWith('#') && !trimmed.includes(':');
150
+ }
151
+ function buildOwnerPromptGuidance(params) {
152
+ const isKickoff = parseTaskKickoff(params.content) !== null;
153
+ if (isKickoff)
154
+ return [];
155
+ if (shouldUseOwnerAwarenessForChannelActivation({
156
+ targetRole: 'owner',
157
+ reason: params.reason,
158
+ targetAgentId: params.targetAgentId,
159
+ explicitlyMentionedAgentIds: params.explicitlyMentionedAgentIds,
160
+ ownerAwarenessMentionedAgentIds: params.ownerAwarenessMentionedAgentIds,
161
+ })) {
162
+ return [
163
+ '',
164
+ '[Owner awareness guidance]',
165
+ 'This follow-up explicitly mentioned another collaborator; you are receiving it as the task owner for awareness.',
166
+ 'Default for this awareness turn: absorb this update and let the directly mentioned collaborator respond first; you may stay quiet. Do not send final or move task status just because you are the owner.',
167
+ `If you need to coordinate, correct course, or provide missing owner context, use \`bigbang message send --kind progress\` in this current conversation. ${buildCurrentConversationRoutingConstraint()}`,
168
+ 'Use the normal owner final/status path only from a later owner-directed activation after the directly mentioned collaborators have replied in the visible thread and the thread is ready for owner wrap-up. Do not close or advance task status from this same awareness activation.',
169
+ ];
170
+ }
171
+ return [];
172
+ }
173
+ export function buildChannelActivationPrompt(params) {
174
+ const targetRole = params.targetRole ?? 'owner';
175
+ const isParticipantKickoff = targetRole === 'participant' && parseTaskKickoff(params.content) !== null;
176
+ const isParticipantSupportBootstrap = targetRole === 'participant' && isCollaboratorSupportBootstrap(params.content);
177
+ const reasonText = isParticipantKickoff
178
+ ? `You were added as a collaborator/supporting agent in #${params.channelName} by ${params.senderName}.`
179
+ : isParticipantSupportBootstrap
180
+ ? `You were added as a collaborator/supporting agent in #${params.channelName} by ${params.senderName}.`
181
+ : params.reason === 'mention'
182
+ ? `You were @mentioned in #${params.channelName} by ${params.senderName}.`
183
+ : params.reason === 'agent_mention'
184
+ ? `Another agent (@${params.senderName}) mentioned you in #${params.channelName}.`
185
+ : params.reason === 'thread_reply'
186
+ ? params.replyCount && params.replyCount > 1
187
+ ? `Your collaborative thread in #${params.channelName} received ${params.replyCount} replies from ${params.senderName} within the last 10 seconds.`
188
+ : `Your collaborative thread in #${params.channelName} received a reply from ${params.senderName}.`
189
+ : `There is new channel activity in #${params.channelName} from ${params.senderName}.`;
190
+ const replyTarget = params.replyTarget ?? params.target;
191
+ const isChannelRoot = isChannelRootTarget(replyTarget);
192
+ const today = params.today ?? getCurrentISOWeek();
193
+ const memoryHintSection = buildWorkspaceMemoryHintSection(params.memoryHints, today);
194
+ const triggeredBody = sanitizeTriggeredMessageBody(params.content, targetRole);
195
+ const lines = [
196
+ `[System: ${reasonText}]`,
197
+ '',
198
+ '[Current conversation target]',
199
+ `reply_target: ${replyTarget}`,
200
+ '',
201
+ ...(memoryHintSection ? [memoryHintSection, ''] : []),
202
+ '[Triggered message metadata]',
203
+ `target: ${params.target}`,
204
+ `sender: @${params.senderName}`,
205
+ '',
206
+ '[Triggered message body]',
207
+ triggeredBody,
208
+ ...(targetRole === 'participant'
209
+ ? buildParticipantPromptGuidance({
210
+ reason: params.reason,
211
+ content: params.content,
212
+ isChannelRoot,
213
+ })
214
+ : buildOwnerPromptGuidance({
215
+ reason: params.reason,
216
+ content: params.content,
217
+ targetAgentId: params.targetAgentId,
218
+ explicitlyMentionedAgentIds: params.explicitlyMentionedAgentIds,
219
+ ownerAwarenessMentionedAgentIds: params.ownerAwarenessMentionedAgentIds,
220
+ })),
221
+ ];
222
+ return lines.join('\n');
223
+ }
224
+ /**
225
+ * Builds the activation context text (recent messages, thread root, unread summary) to be
226
+ * injected as part of contextText — only on fresh ACP sessions, not on every turn.
227
+ * Returns an empty string if there is nothing to include.
228
+ */
229
+ export function buildChannelActivationContextText(params, options) {
230
+ return renderPromptContextSections(buildChannelActivationContextSections(params, options));
231
+ }
232
+ export function buildChannelActivationContextSections(params, options) {
233
+ const parts = [];
234
+ const defaultPolicy = resolveChannelSurfaceActivationPolicy({
235
+ target: params.target,
236
+ reason: 'channel_activity',
237
+ hasBoundTask: Boolean(params.boundTask),
238
+ });
239
+ const historyLimit = Math.max(0, options?.historyLimit ?? historyLimitForSurfaceActivationTier(defaultPolicy.activationTier));
240
+ if (params.rootMessage) {
241
+ const rootSection = createPromptContextSection('thread_root_message', `[Thread root message]\n${formatPromptMessage(params.rootMessage)}`);
242
+ if (rootSection) {
243
+ parts.push(rootSection);
244
+ }
245
+ }
246
+ parts.push(...buildExactTargetHistoryContextSections(params, { maxMessages: historyLimit }));
247
+ if (options?.includeParticipants !== false && params.participants && params.participants.length > 0) {
248
+ const participantsSection = createPromptContextSection('participants', `[Active participants on this target]\n${params.participants.map((participant) => {
249
+ const role = participant.role === 'owner' ? 'owner' : 'participant';
250
+ return `@${participant.name} (${role})`;
251
+ }).join('\n')}`);
252
+ if (participantsSection) {
253
+ parts.push(participantsSection);
254
+ }
255
+ }
256
+ if (params.boundTask) {
257
+ const assignee = params.boundTask.claimedByName ? ` @${params.boundTask.claimedByName}` : ' unassigned';
258
+ const brief = params.boundTask.description?.trim()
259
+ ? `\nTask brief / goal / done criteria:\n${params.boundTask.description.trim()}`
260
+ : '\nTask brief / goal / done criteria: missing';
261
+ const boundTaskSection = createPromptContextSection('bound_task_thread', `[Bound task-message for this thread]\n#${params.boundTask.taskNumber} [${params.boundTask.status}]${assignee} — ${params.boundTask.title}${brief}\nThis thread is the shared work surface for that task-message. If you are not the owner/assignee, default to coordination and progress updates only in this thread. Only the owner/assignee should send the substantive final result and move the task to in_review. If ownership or takeover needs to change, hand off to the root channel first. If you already own this task, do not claim it again in this thread. If you are a participant/collaborator, do not run bigbang task claim from this thread to make yourself owner. Do not create or claim a different task from this thread; hand off to the root channel first if new tracked work is needed. After the task moves to in_review, this thread will close. Do not append a second completion-summary message after the substantive result.`);
262
+ if (boundTaskSection) {
263
+ parts.push(boundTaskSection);
264
+ }
265
+ }
266
+ const openTaskMode = options?.openTaskMode
267
+ ?? resolveChannelTaskBoardPresencePolicy({
268
+ target: params.target,
269
+ hasBoundTask: Boolean(params.boundTask),
270
+ includeOpenTasks: options?.includeOpenTasks,
271
+ }).renderMode;
272
+ const visibleOpenTasks = openTaskMode !== 'full'
273
+ ? []
274
+ : (params.openTasks ?? []).slice(0, Math.max(1, options?.maxOpenTasks ?? params.openTasks?.length ?? 1));
275
+ const openTaskCount = Math.max(0, params.openTaskCount ?? params.openTasks?.length ?? 0);
276
+ if (visibleOpenTasks.length > 0) {
277
+ const taskBoardSummarySection = createPromptContextSection('task_board_summary', `[Task-message board summary]\n${visibleOpenTasks.map((task) => {
278
+ const assignee = task.claimedByName ? ` @${task.claimedByName}` : ' unassigned';
279
+ return `#${task.taskNumber} [${task.status}]${assignee} — ${task.title}`;
280
+ }).join('\n')}`);
281
+ if (taskBoardSummarySection) {
282
+ parts.push(taskBoardSummarySection);
283
+ }
284
+ }
285
+ else if (openTaskMode === 'hint' && openTaskCount > 0) {
286
+ const taskBoardHintSection = createPromptContextSection('task_board_hint', `[Task-message board hint]\nopen_tasks_present: ${openTaskCount}\nUse bigbang task list or bigbang task my to rediscover related task work before creating new work if this looks related.`);
287
+ if (taskBoardHintSection) {
288
+ parts.push(taskBoardHintSection);
289
+ }
290
+ }
291
+ return parts;
292
+ }
293
+ export function buildExactTargetHistoryContextText(params, options) {
294
+ return renderPromptContextSections(buildExactTargetHistoryContextSections(params, options));
295
+ }
296
+ export function buildExactTargetHistoryContextSections(params, options) {
297
+ const parts = [];
298
+ const sanitizedRecentMessages = (params.recentMessages ?? [])
299
+ .map((message) => {
300
+ const content = sanitizePromptHistoryContent(message.content, message.senderType);
301
+ return content
302
+ ? { ...message, content }
303
+ : null;
304
+ })
305
+ .filter((message) => Boolean(message));
306
+ const maxMessages = options?.maxMessages == null
307
+ ? sanitizedRecentMessages.length
308
+ : Math.max(0, options.maxMessages);
309
+ const visibleRecentMessages = maxMessages === 0
310
+ ? []
311
+ : sanitizedRecentMessages.slice(-maxMessages);
312
+ if (visibleRecentMessages.length > 0) {
313
+ const recentMessagesSection = createPromptContextSection('recent_messages', `[Recent messages on this exact target]
314
+ ${visibleRecentMessages.map(formatPromptMessage).join(MESSAGE_SEPARATOR)}`);
315
+ if (recentMessagesSection) {
316
+ parts.push(recentMessagesSection);
317
+ }
318
+ }
319
+ const hiddenRecentCount = Math.max(0, sanitizedRecentMessages.length - visibleRecentMessages.length);
320
+ const olderUnreadCount = Math.max(0, params.olderUnreadCount ?? params.unreadCount ?? 0);
321
+ const recentWindowStartSeq = sanitizedRecentMessages[0]?.seq ?? params.recentWindowStartSeq ?? params.oldestVisibleSeq;
322
+ const oldestVisibleSeq = visibleRecentMessages[0]?.seq;
323
+ if (oldestVisibleSeq != null) {
324
+ const historyCursorSection = createPromptContextSection('history_cursor', `[History cursor]
325
+ oldest_visible_seq: ${oldestVisibleSeq}
326
+ `);
327
+ if (historyCursorSection) {
328
+ parts.push(historyCursorSection);
329
+ }
330
+ }
331
+ if (hiddenRecentCount > 0 && oldestVisibleSeq != null) {
332
+ const label = hiddenRecentCount === 1 ? '1 earlier recent message' : `${hiddenRecentCount} earlier recent messages`;
333
+ const recentSummarySection = createPromptContextSection('recent_summary', `[Recent summary]
334
+ ${label} on this exact target were not included above. Use bigbang message read --channel "${params.target}" --before ${oldestVisibleSeq} if you need them.`);
335
+ if (recentSummarySection) {
336
+ parts.push(recentSummarySection);
337
+ }
338
+ }
339
+ else if (hiddenRecentCount > 0 && recentWindowStartSeq != null) {
340
+ const label = hiddenRecentCount === 1 ? '1 recent message' : `${hiddenRecentCount} recent messages`;
341
+ const readHint = recentWindowStartSeq > 1
342
+ ? ` Use bigbang message read --channel "${params.target}" --after ${recentWindowStartSeq - 1} if you need them.`
343
+ : ` Use bigbang message read --channel "${params.target}" --around ${recentWindowStartSeq} if you need them.`;
344
+ const recentSummarySection = createPromptContextSection('recent_summary', `[Recent summary]
345
+ ${label} on this exact target were omitted above.${readHint}`);
346
+ if (recentSummarySection) {
347
+ parts.push(recentSummarySection);
348
+ }
349
+ }
350
+ if (olderUnreadCount > 0 && recentWindowStartSeq != null) {
351
+ const label = olderUnreadCount === 1 ? '1 unread older message' : `${olderUnreadCount} unread older messages`;
352
+ const verb = olderUnreadCount === 1 ? 'exists' : 'exist';
353
+ const inboxPointerSection = createPromptContextSection('activation_pointer', `[Inbox]
354
+ ${label} ${verb} before seq ${recentWindowStartSeq} on this target. Run bigbang inbox check to inspect pending surfaces, then use bigbang message check or bigbang message read when you need content.`);
355
+ if (inboxPointerSection) {
356
+ parts.push(inboxPointerSection);
357
+ }
358
+ }
359
+ return parts;
360
+ }
361
+ export function buildAttachmentReferenceContextText(attachmentIds) {
362
+ if (!attachmentIds?.length)
363
+ return '';
364
+ return [
365
+ `[Message attachment${attachmentIds.length > 1 ? 's' : ''}]`,
366
+ ...attachmentIds.map((attachmentId) => `attachment_id: ${attachmentId}`),
367
+ `Use the provided local path when available, or bigbang attachment view --attachment-id "<one of the IDs above>" to inspect the attached file${attachmentIds.length > 1 ? 's' : ''}.`,
368
+ ].join('\n');
369
+ }
370
+ function formatPromptMessage(message) {
371
+ const visibleContent = sanitizePromptHistoryContent(message.content, message.senderType);
372
+ const firstLine = [
373
+ `target: ${message.target}`,
374
+ `seq: ${message.seq}`,
375
+ ].join(' ');
376
+ const secondLineParts = [
377
+ `time: ${formatBeijingPromptTimestamp(message.createdAt)}`,
378
+ `sender: @${message.senderName}`,
379
+ ];
380
+ if (message.senderType === 'agent')
381
+ secondLineParts.push('sender_type: agent');
382
+ const parts = [
383
+ '[Message metadata]',
384
+ firstLine,
385
+ secondLineParts.join(' '),
386
+ '',
387
+ '[Message body]',
388
+ visibleContent,
389
+ ];
390
+ const attachmentContext = buildAttachmentReferenceContextText(message.attachmentIds);
391
+ if (attachmentContext) {
392
+ parts.push('', attachmentContext);
393
+ }
394
+ return parts.join('\n');
395
+ }