@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,224 @@
|
|
|
1
|
+
import { buildThreadShortId, formatBeijingPromptTimestamp, } from '@bbigbang/protocol';
|
|
2
|
+
import { channelMemoryNotePath } from './channelMemoryNotes.js';
|
|
3
|
+
const WORK_LOG_PATH = 'notes/work-log/legacy.md';
|
|
4
|
+
const WORK_LOG_INSTRUCTION = 'Append important completed work, decisions, and follow-ups here.';
|
|
5
|
+
const CHANNEL_NOTE_INSTRUCTION = 'Durable notes and reset markers for this channel.';
|
|
6
|
+
const RECENT_DURABLE_OUTCOMES_START = '<!-- recent-durable-outcomes:start -->';
|
|
7
|
+
const RECENT_DURABLE_OUTCOMES_END = '<!-- recent-durable-outcomes:end -->';
|
|
8
|
+
const TASK_MEMORY_AUDIT_PATH = '.bigbang/task-memory-sync-audit.md';
|
|
9
|
+
const TASK_MEMORY_AUDIT_HEADER = '# Task Memory Sync Audit';
|
|
10
|
+
const TASK_MEMORY_AUDIT_INSTRUCTION = 'Framework-observed task status changes are recorded here for audit/debug only. Agents remain responsible for MEMORY.md and task notes.';
|
|
11
|
+
const TASK_MEMORY_SYNC_MODE_ENV = 'BIGBANG_TASK_MEMORY_SYNC_MODE';
|
|
12
|
+
function isNotFoundError(error) {
|
|
13
|
+
const message = String(error?.message ?? error);
|
|
14
|
+
return message.startsWith('not_found:');
|
|
15
|
+
}
|
|
16
|
+
function normalizeInlineNoteText(value, fallback) {
|
|
17
|
+
const normalized = (value ?? '')
|
|
18
|
+
.replace(/\s+/g, ' ')
|
|
19
|
+
.trim();
|
|
20
|
+
return normalized || fallback;
|
|
21
|
+
}
|
|
22
|
+
function fallbackTaskRef(taskId) {
|
|
23
|
+
return `task_${taskId.replace(/[^a-zA-Z0-9]/g, '').toLowerCase().slice(0, 12) || 'unknown'}`;
|
|
24
|
+
}
|
|
25
|
+
function normalizeManagedNoteSource(content) {
|
|
26
|
+
return content.replace(/\r\n/g, '\n');
|
|
27
|
+
}
|
|
28
|
+
function stripLeadingManagedHeader(params) {
|
|
29
|
+
let remaining = normalizeManagedNoteSource(params.content).trim();
|
|
30
|
+
if (!remaining.startsWith(params.titleLine))
|
|
31
|
+
return remaining;
|
|
32
|
+
remaining = remaining.slice(params.titleLine.length);
|
|
33
|
+
while (true) {
|
|
34
|
+
remaining = remaining.replace(/^(?:[ \t]*\n)+/u, '');
|
|
35
|
+
if (!params.introLine || !remaining.startsWith(params.introLine))
|
|
36
|
+
break;
|
|
37
|
+
remaining = remaining.slice(params.introLine.length);
|
|
38
|
+
}
|
|
39
|
+
return remaining.trim();
|
|
40
|
+
}
|
|
41
|
+
function stripStandaloneInstructionLines(content, instruction) {
|
|
42
|
+
const normalized = normalizeManagedNoteSource(content).trim();
|
|
43
|
+
if (!normalized)
|
|
44
|
+
return '';
|
|
45
|
+
return normalized
|
|
46
|
+
.replace(new RegExp(`(?:^|\\n\\n)${escapeRegex(instruction)}(?=\\n\\n|$)`, 'g'), '')
|
|
47
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
48
|
+
.trim();
|
|
49
|
+
}
|
|
50
|
+
function buildWorkLogEntry(snapshot) {
|
|
51
|
+
return [
|
|
52
|
+
`## ${formatBeijingPromptTimestamp(snapshot.updatedAt)} — ${snapshot.taskRef}`,
|
|
53
|
+
`- target: ${normalizeInlineNoteText(snapshot.targetLabel, snapshot.channelId)}`,
|
|
54
|
+
`- root_message: ${normalizeInlineNoteText(snapshot.rootMessage, 'No root message recorded.')}`,
|
|
55
|
+
`- key decision: Task moved to ${snapshot.status}.`,
|
|
56
|
+
`- risk / follow-up: ${normalizeInlineNoteText(snapshot.residualRisks, 'None recorded.')}`,
|
|
57
|
+
'',
|
|
58
|
+
].join('\n');
|
|
59
|
+
}
|
|
60
|
+
function appendWorkLog(existingContent, snapshot) {
|
|
61
|
+
const header = '# Work Log';
|
|
62
|
+
const body = stripLeadingManagedHeader({
|
|
63
|
+
content: existingContent,
|
|
64
|
+
titleLine: header,
|
|
65
|
+
introLine: WORK_LOG_INSTRUCTION,
|
|
66
|
+
});
|
|
67
|
+
const entry = buildWorkLogEntry(snapshot).trimEnd();
|
|
68
|
+
const dedupedBody = body.replace(new RegExp(`(?:^|\\n\\n)${escapeRegex(entry)}(?=\\n\\n|$)`, 'g'), '').trim();
|
|
69
|
+
const normalizedBody = stripStandaloneInstructionLines(dedupedBody, WORK_LOG_INSTRUCTION);
|
|
70
|
+
const nextBody = [normalizedBody, entry].filter(Boolean).join('\n\n');
|
|
71
|
+
return `${header}\n\n${WORK_LOG_INSTRUCTION}\n\n${nextBody}\n`;
|
|
72
|
+
}
|
|
73
|
+
function upsertChannelOutcomeSection(existingContent, channelName, snapshot) {
|
|
74
|
+
const titleLine = `# Channel: #${channelName}`;
|
|
75
|
+
const header = [
|
|
76
|
+
titleLine,
|
|
77
|
+
'',
|
|
78
|
+
CHANNEL_NOTE_INSTRUCTION,
|
|
79
|
+
].join('\n');
|
|
80
|
+
const content = normalizeManagedNoteSource(existingContent).trim();
|
|
81
|
+
const body = stripLeadingManagedHeader({
|
|
82
|
+
content,
|
|
83
|
+
titleLine,
|
|
84
|
+
introLine: CHANNEL_NOTE_INSTRUCTION,
|
|
85
|
+
});
|
|
86
|
+
const managedPattern = new RegExp(`${escapeRegex(RECENT_DURABLE_OUTCOMES_START)}[\\s\\S]*?${escapeRegex(RECENT_DURABLE_OUTCOMES_END)}\\n*`, 'g');
|
|
87
|
+
const remainder = body.replace(managedPattern, '').trim();
|
|
88
|
+
const entry = `- ${formatBeijingPromptTimestamp(snapshot.updatedAt)} ${snapshot.taskRef} [${snapshot.status}] — ${normalizeInlineNoteText(snapshot.title, 'Untitled task')}`;
|
|
89
|
+
const existingManaged = content.match(new RegExp(`${escapeRegex(RECENT_DURABLE_OUTCOMES_START)}([\\s\\S]*?)${escapeRegex(RECENT_DURABLE_OUTCOMES_END)}`));
|
|
90
|
+
const priorLines = (existingManaged?.[1] ?? '')
|
|
91
|
+
.split('\n')
|
|
92
|
+
.map((line) => line.trim())
|
|
93
|
+
.filter((line) => line.startsWith('- '))
|
|
94
|
+
.filter((line) => !line.includes(`${snapshot.taskRef} [`));
|
|
95
|
+
const managedLines = [
|
|
96
|
+
'## Recent Durable Outcomes',
|
|
97
|
+
RECENT_DURABLE_OUTCOMES_START,
|
|
98
|
+
entry,
|
|
99
|
+
...priorLines.slice(0, 4),
|
|
100
|
+
RECENT_DURABLE_OUTCOMES_END,
|
|
101
|
+
];
|
|
102
|
+
const nextBody = [managedLines.join('\n'), remainder].filter(Boolean).join('\n\n');
|
|
103
|
+
return `${header}\n\n${nextBody}\n`;
|
|
104
|
+
}
|
|
105
|
+
function buildTaskSyncAuditEntry(snapshot) {
|
|
106
|
+
return [
|
|
107
|
+
`## ${formatBeijingPromptTimestamp(snapshot.updatedAt)} — ${snapshot.taskRef}`,
|
|
108
|
+
`- status: ${normalizeInlineNoteText(snapshot.status, 'unknown')}`,
|
|
109
|
+
`- target: ${normalizeInlineNoteText(snapshot.targetLabel, snapshot.channelId)}`,
|
|
110
|
+
`- root_message: ${normalizeInlineNoteText(snapshot.rootMessage, 'No root message recorded.')}`,
|
|
111
|
+
'- note: Framework observed this task status change in audit-only mode. The agent remains responsible for MEMORY.md, notes/tasks.md, and notes/work-log/<week>.md updates.',
|
|
112
|
+
'',
|
|
113
|
+
].join('\n');
|
|
114
|
+
}
|
|
115
|
+
function appendTaskSyncAudit(existingContent, snapshot) {
|
|
116
|
+
const body = stripLeadingManagedHeader({
|
|
117
|
+
content: existingContent,
|
|
118
|
+
titleLine: TASK_MEMORY_AUDIT_HEADER,
|
|
119
|
+
introLine: TASK_MEMORY_AUDIT_INSTRUCTION,
|
|
120
|
+
});
|
|
121
|
+
const entry = buildTaskSyncAuditEntry(snapshot).trimEnd();
|
|
122
|
+
const dedupedBody = body.replace(new RegExp(`(?:^|\\n\\n)${escapeRegex(entry)}(?=\\n\\n|$)`, 'g'), '').trim();
|
|
123
|
+
const normalizedBody = stripStandaloneInstructionLines(dedupedBody, TASK_MEMORY_AUDIT_INSTRUCTION);
|
|
124
|
+
const nextBody = [normalizedBody, entry].filter(Boolean).join('\n\n');
|
|
125
|
+
return `${TASK_MEMORY_AUDIT_HEADER}\n\n${TASK_MEMORY_AUDIT_INSTRUCTION}\n\n${nextBody}\n`;
|
|
126
|
+
}
|
|
127
|
+
function normalizeTaskDurableNoteSyncMode(value) {
|
|
128
|
+
const normalized = (value ?? '').trim().toLowerCase();
|
|
129
|
+
if (!normalized)
|
|
130
|
+
return null;
|
|
131
|
+
if (normalized === 'legacy_memory' || normalized === 'legacy' || normalized === 'memory') {
|
|
132
|
+
return 'legacy_memory';
|
|
133
|
+
}
|
|
134
|
+
if (normalized === 'audit_only' || normalized === 'audit') {
|
|
135
|
+
return 'audit_only';
|
|
136
|
+
}
|
|
137
|
+
if (normalized === 'disabled' || normalized === 'off' || normalized === 'none') {
|
|
138
|
+
return 'disabled';
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
export function resolveTaskDurableNoteSyncMode(value) {
|
|
143
|
+
return normalizeTaskDurableNoteSyncMode(value) ?? 'audit_only';
|
|
144
|
+
}
|
|
145
|
+
function escapeRegex(text) {
|
|
146
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
147
|
+
}
|
|
148
|
+
async function readOptionalFile(broker, agent, relativePath, options) {
|
|
149
|
+
if (!agent.nodeId || !agent.workspacePath)
|
|
150
|
+
return '';
|
|
151
|
+
try {
|
|
152
|
+
const existing = await broker.readFile(agent.nodeId, agent.workspacePath, relativePath, options);
|
|
153
|
+
return existing.content;
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
if (isNotFoundError(error))
|
|
157
|
+
return '';
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function loadTaskMemorySnapshot(db, taskId) {
|
|
162
|
+
const row = db.prepare(`SELECT t.task_id as taskId,
|
|
163
|
+
t.agent_task_ref as taskRef,
|
|
164
|
+
t.title,
|
|
165
|
+
t.status,
|
|
166
|
+
t.channel_id as channelId,
|
|
167
|
+
t.updated_at as updatedAt,
|
|
168
|
+
t.message_id as messageId,
|
|
169
|
+
COALESCE(t.dm_target, cm.target) as messageTarget,
|
|
170
|
+
cm.content as rootMessageContent,
|
|
171
|
+
c.name as channelName
|
|
172
|
+
FROM tasks t
|
|
173
|
+
LEFT JOIN channel_messages cm ON cm.message_id = t.message_id
|
|
174
|
+
LEFT JOIN channels c ON c.channel_id = t.channel_id
|
|
175
|
+
WHERE t.task_id = ?
|
|
176
|
+
LIMIT 1`).get(taskId);
|
|
177
|
+
if (!row)
|
|
178
|
+
return null;
|
|
179
|
+
const targetLabel = row.messageId && row.messageTarget
|
|
180
|
+
? `${row.messageTarget}:${buildThreadShortId(row.messageId)}`
|
|
181
|
+
: row.messageTarget ?? (row.channelName ? `#${row.channelName}` : row.channelId);
|
|
182
|
+
return {
|
|
183
|
+
taskId: row.taskId,
|
|
184
|
+
taskRef: row.taskRef ?? fallbackTaskRef(row.taskId),
|
|
185
|
+
title: row.title,
|
|
186
|
+
status: row.status,
|
|
187
|
+
channelId: row.channelId,
|
|
188
|
+
channelName: row.channelName,
|
|
189
|
+
messageId: row.messageId,
|
|
190
|
+
messageTarget: row.messageTarget,
|
|
191
|
+
targetLabel,
|
|
192
|
+
updatedAt: row.updatedAt,
|
|
193
|
+
rootMessage: row.rootMessageContent?.trim() || null,
|
|
194
|
+
residualRisks: row.status === 'in_review' ? 'Awaiting human review or approval.' : 'None recorded.',
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
export async function syncTaskDurableNotesForAgent(params) {
|
|
198
|
+
const { db, broker, agent, taskId } = params;
|
|
199
|
+
if (!agent?.nodeId || !agent.workspacePath)
|
|
200
|
+
return;
|
|
201
|
+
const snapshot = loadTaskMemorySnapshot(db, taskId);
|
|
202
|
+
if (!snapshot)
|
|
203
|
+
return;
|
|
204
|
+
const mode = resolveTaskDurableNoteSyncMode(params.mode ?? process.env[TASK_MEMORY_SYNC_MODE_ENV]);
|
|
205
|
+
if (mode === 'disabled')
|
|
206
|
+
return;
|
|
207
|
+
if (mode === 'audit_only') {
|
|
208
|
+
const auditContent = await readOptionalFile(broker, agent, TASK_MEMORY_AUDIT_PATH, { scaffold: false });
|
|
209
|
+
await broker.writeFile(agent.nodeId, agent.workspacePath, TASK_MEMORY_AUDIT_PATH, appendTaskSyncAudit(auditContent, snapshot), 'overwrite', { scaffold: false });
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
// legacy_memory keeps the previous framework-managed durable-note behavior
|
|
213
|
+
// as an optional fallback during migration. The agent remains responsible for
|
|
214
|
+
// MEMORY.md and notes/tasks.md in all modes.
|
|
215
|
+
if (snapshot.status === 'done') {
|
|
216
|
+
const workLogContent = await readOptionalFile(broker, agent, WORK_LOG_PATH);
|
|
217
|
+
await broker.writeFile(agent.nodeId, agent.workspacePath, WORK_LOG_PATH, appendWorkLog(workLogContent, snapshot), 'overwrite');
|
|
218
|
+
}
|
|
219
|
+
if (snapshot.channelName && !snapshot.channelId.startsWith('dm:')) {
|
|
220
|
+
const channelNotePath = channelMemoryNotePath(snapshot.channelName);
|
|
221
|
+
const channelContent = await readOptionalFile(broker, agent, channelNotePath);
|
|
222
|
+
await broker.writeFile(agent.nodeId, agent.workspacePath, channelNotePath, upsertChannelOutcomeSection(channelContent, snapshot.channelName, snapshot), 'overwrite');
|
|
223
|
+
}
|
|
224
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function allocateNextTaskNumber(db, channelId) {
|
|
2
|
+
db.prepare(`INSERT INTO channel_task_sequences(channel_id, next_task_number)
|
|
3
|
+
VALUES(
|
|
4
|
+
?,
|
|
5
|
+
COALESCE((SELECT MAX(task_number) FROM tasks WHERE channel_id = ?), 0) + 1
|
|
6
|
+
)
|
|
7
|
+
ON CONFLICT(channel_id) DO NOTHING`).run(channelId, channelId);
|
|
8
|
+
const row = db.prepare(`UPDATE channel_task_sequences
|
|
9
|
+
SET next_task_number = next_task_number + 1
|
|
10
|
+
WHERE channel_id = ?
|
|
11
|
+
RETURNING next_task_number - 1 as taskNumber`).get(channelId);
|
|
12
|
+
if (!row) {
|
|
13
|
+
throw new Error(`Failed to allocate task number for channel ${channelId}`);
|
|
14
|
+
}
|
|
15
|
+
return row.taskNumber;
|
|
16
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 返回 task 的当前 owner agent id。
|
|
3
|
+
* 权威源为 `task_participants.participant_role='owner'`;若缺则回落到 `tasks.claimed_by_agent_id`。
|
|
4
|
+
*/
|
|
5
|
+
export function loadTaskOwnerAgentId(db, taskId) {
|
|
6
|
+
if (!taskId)
|
|
7
|
+
return null;
|
|
8
|
+
const participantRow = db
|
|
9
|
+
.prepare(`SELECT agent_id AS agentId FROM task_participants
|
|
10
|
+
WHERE task_id = ? AND participant_role = 'owner'
|
|
11
|
+
LIMIT 1`)
|
|
12
|
+
.get(taskId);
|
|
13
|
+
if (participantRow?.agentId)
|
|
14
|
+
return participantRow.agentId;
|
|
15
|
+
const taskRow = db
|
|
16
|
+
.prepare(`SELECT claimed_by_agent_id AS claimedByAgentId FROM tasks WHERE task_id = ? LIMIT 1`)
|
|
17
|
+
.get(taskId);
|
|
18
|
+
const fallback = taskRow?.claimedByAgentId?.trim();
|
|
19
|
+
return fallback ? fallback : null;
|
|
20
|
+
}
|
|
21
|
+
export function isAgentTaskOwner(db, taskId, agentId) {
|
|
22
|
+
if (!taskId || !agentId)
|
|
23
|
+
return false;
|
|
24
|
+
return loadTaskOwnerAgentId(db, taskId) === agentId;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 构造 agent 端 "非 owner 不得修改 task" 拒绝响应。
|
|
28
|
+
* 调用方:reply.code(denial.status); return denial.body;
|
|
29
|
+
*/
|
|
30
|
+
export function buildAgentTaskOwnerDenial(params) {
|
|
31
|
+
const taskNumber = typeof params.taskNumber === 'number' && Number.isFinite(params.taskNumber)
|
|
32
|
+
? params.taskNumber
|
|
33
|
+
: null;
|
|
34
|
+
const label = taskNumber !== null ? `#${taskNumber}` : `task ${params.taskId}`;
|
|
35
|
+
return {
|
|
36
|
+
status: 403,
|
|
37
|
+
body: {
|
|
38
|
+
error: `Only the task owner can modify ${label}. Ask the owner or user to change the task instead.`,
|
|
39
|
+
error_code: 'task_owner_required',
|
|
40
|
+
required_action: 'ask_owner_or_user',
|
|
41
|
+
source_target: params.sourceTarget ?? null,
|
|
42
|
+
requested_target: {
|
|
43
|
+
type: 'task',
|
|
44
|
+
taskId: params.taskId,
|
|
45
|
+
taskNumber,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { MAX_TASK_PARTICIPANTS, replaceTaskParticipants, } from './taskParticipants.js';
|
|
2
|
+
/**
|
|
3
|
+
* Single entry point for all task participant writes.
|
|
4
|
+
*
|
|
5
|
+
* Enforces MAX_TASK_PARTICIPANTS (1 owner + bounded collaborators).
|
|
6
|
+
* Handles owner change preservation, collaborator merging, deduplication,
|
|
7
|
+
* and empty-list clearing.
|
|
8
|
+
*/
|
|
9
|
+
export function resolveTaskParticipants(db, params) {
|
|
10
|
+
const ownerId = params.ownerAgentId?.trim() || null;
|
|
11
|
+
// Normalize and deduplicate incoming collaborator IDs, excluding owner
|
|
12
|
+
const incomingCollaboratorIds = Array.from(new Set((params.collaboratorAgentIds ?? [])
|
|
13
|
+
.map((id) => (typeof id === 'string' ? id.trim() : ''))
|
|
14
|
+
.filter((id) => id.length > 0 && id !== ownerId)));
|
|
15
|
+
// Load existing participants when preserving
|
|
16
|
+
let existingCollaboratorIds = [];
|
|
17
|
+
if (params.preserveExistingCollaborators) {
|
|
18
|
+
const rows = db.prepare(`SELECT agent_id as agentId
|
|
19
|
+
FROM task_participants
|
|
20
|
+
WHERE task_id = ?
|
|
21
|
+
AND participant_role = 'collaborator'`).all(params.taskId);
|
|
22
|
+
existingCollaboratorIds = rows
|
|
23
|
+
.map((r) => r.agentId)
|
|
24
|
+
.filter((id) => id && id !== ownerId);
|
|
25
|
+
}
|
|
26
|
+
// Merge: existing + incoming, deduplicated
|
|
27
|
+
const collaboratorAgentIds = params.preserveExistingCollaborators
|
|
28
|
+
? Array.from(new Set([...existingCollaboratorIds, ...incomingCollaboratorIds]))
|
|
29
|
+
: incomingCollaboratorIds;
|
|
30
|
+
// M9 enforcement: bounded collaborators plus owner.
|
|
31
|
+
if (collaboratorAgentIds.length > MAX_TASK_PARTICIPANTS - 1) {
|
|
32
|
+
throw new Error(`Too many collaborators (max ${MAX_TASK_PARTICIPANTS - 1}, plus owner = ${MAX_TASK_PARTICIPANTS} total)`);
|
|
33
|
+
}
|
|
34
|
+
// Write to DB via replaceTaskParticipants
|
|
35
|
+
replaceTaskParticipants(db, {
|
|
36
|
+
taskId: params.taskId,
|
|
37
|
+
ownerAgentId: ownerId,
|
|
38
|
+
collaboratorAgentIds,
|
|
39
|
+
updatedAt: params.updatedAt,
|
|
40
|
+
});
|
|
41
|
+
return { ownerId, collaboratorAgentIds };
|
|
42
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/** Maximum number of task participants (1 owner + up to 19 collaborators = 20 total). */
|
|
2
|
+
export const MAX_TASK_PARTICIPANTS = 20;
|
|
3
|
+
export function upsertTaskParticipant(db, params) {
|
|
4
|
+
const agentId = params.agentId?.trim();
|
|
5
|
+
if (!agentId)
|
|
6
|
+
return;
|
|
7
|
+
const updatedAt = params.updatedAt ?? Date.now();
|
|
8
|
+
db.prepare(`INSERT INTO task_participants(
|
|
9
|
+
task_id,
|
|
10
|
+
agent_id,
|
|
11
|
+
participant_role,
|
|
12
|
+
joined_at,
|
|
13
|
+
last_updated_at
|
|
14
|
+
)
|
|
15
|
+
VALUES(?, ?, ?, ?, ?)
|
|
16
|
+
ON CONFLICT(task_id, agent_id) DO UPDATE SET
|
|
17
|
+
participant_role = CASE
|
|
18
|
+
WHEN excluded.participant_role = 'owner' THEN 'owner'
|
|
19
|
+
ELSE task_participants.participant_role
|
|
20
|
+
END,
|
|
21
|
+
joined_at = MIN(task_participants.joined_at, excluded.joined_at),
|
|
22
|
+
last_updated_at = MAX(task_participants.last_updated_at, excluded.last_updated_at)`).run(params.taskId, agentId, params.role, updatedAt, updatedAt);
|
|
23
|
+
}
|
|
24
|
+
export function listTaskCollaboratorAgentIds(db, params) {
|
|
25
|
+
const rows = db.prepare(`SELECT agent_id as agentId
|
|
26
|
+
FROM task_participants
|
|
27
|
+
WHERE task_id = ?
|
|
28
|
+
AND participant_role = 'collaborator'
|
|
29
|
+
ORDER BY last_updated_at ASC, agent_id ASC`).all(params.taskId);
|
|
30
|
+
return rows
|
|
31
|
+
.map((row) => row.agentId?.trim() ?? '')
|
|
32
|
+
.filter((agentId) => agentId.length > 0);
|
|
33
|
+
}
|
|
34
|
+
export function captureTaskParticipantSnapshot(db, params) {
|
|
35
|
+
const row = db.prepare(`SELECT participant_role as participantRole,
|
|
36
|
+
joined_at as joinedAt,
|
|
37
|
+
last_updated_at as lastUpdatedAt
|
|
38
|
+
FROM task_participants
|
|
39
|
+
WHERE task_id = ? AND agent_id = ?
|
|
40
|
+
LIMIT 1`).get(params.taskId, params.agentId);
|
|
41
|
+
return row ? { exists: true, ...row } : { exists: false };
|
|
42
|
+
}
|
|
43
|
+
export function restoreTaskParticipantSnapshot(db, params) {
|
|
44
|
+
if (!params.snapshot.exists) {
|
|
45
|
+
db.prepare(`DELETE FROM task_participants
|
|
46
|
+
WHERE task_id = ? AND agent_id = ?`).run(params.taskId, params.agentId);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
db.prepare(`INSERT INTO task_participants(
|
|
50
|
+
task_id,
|
|
51
|
+
agent_id,
|
|
52
|
+
participant_role,
|
|
53
|
+
joined_at,
|
|
54
|
+
last_updated_at
|
|
55
|
+
)
|
|
56
|
+
VALUES(?, ?, ?, ?, ?)
|
|
57
|
+
ON CONFLICT(task_id, agent_id) DO UPDATE SET
|
|
58
|
+
participant_role = excluded.participant_role,
|
|
59
|
+
joined_at = excluded.joined_at,
|
|
60
|
+
last_updated_at = excluded.last_updated_at`).run(params.taskId, params.agentId, params.snapshot.participantRole, params.snapshot.joinedAt, params.snapshot.lastUpdatedAt);
|
|
61
|
+
}
|
|
62
|
+
export function clearTaskParticipants(db, params) {
|
|
63
|
+
db.prepare(`DELETE FROM task_participants WHERE task_id = ?`).run(params.taskId);
|
|
64
|
+
}
|
|
65
|
+
export function removeTaskParticipant(db, params) {
|
|
66
|
+
const agentId = params.agentId?.trim();
|
|
67
|
+
if (!agentId)
|
|
68
|
+
return;
|
|
69
|
+
// Shared-task unclaim only removes the releasing owner. Collaborators stay visible
|
|
70
|
+
// so the shared task can still be discovered and potentially re-owned.
|
|
71
|
+
db.prepare(`DELETE FROM task_participants
|
|
72
|
+
WHERE task_id = ? AND agent_id = ?`).run(params.taskId, agentId);
|
|
73
|
+
}
|
|
74
|
+
export function replaceTaskParticipants(db, params) {
|
|
75
|
+
clearTaskParticipants(db, { taskId: params.taskId });
|
|
76
|
+
const ownerAgentId = params.ownerAgentId?.trim() ?? null;
|
|
77
|
+
const collaboratorAgentIds = Array.from(new Set((params.collaboratorAgentIds ?? [])
|
|
78
|
+
.map((agentId) => agentId?.trim() ?? '')
|
|
79
|
+
.filter((agentId) => agentId && agentId !== ownerAgentId)));
|
|
80
|
+
const updatedAt = params.updatedAt ?? Date.now();
|
|
81
|
+
if (ownerAgentId) {
|
|
82
|
+
upsertTaskParticipant(db, {
|
|
83
|
+
taskId: params.taskId,
|
|
84
|
+
agentId: ownerAgentId,
|
|
85
|
+
role: 'owner',
|
|
86
|
+
updatedAt,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
for (const collaboratorAgentId of collaboratorAgentIds) {
|
|
90
|
+
upsertTaskParticipant(db, {
|
|
91
|
+
taskId: params.taskId,
|
|
92
|
+
agentId: collaboratorAgentId,
|
|
93
|
+
role: 'collaborator',
|
|
94
|
+
updatedAt,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const SOURCE_MESSAGE_SECTION_LABEL = 'Source message:';
|
|
2
|
+
function normalizeForSourceComparison(value) {
|
|
3
|
+
return (value ?? '').replace(/\s+/g, ' ').trim();
|
|
4
|
+
}
|
|
5
|
+
export function appendTaskSourceMessageToDescription(params) {
|
|
6
|
+
const description = params.description.trim();
|
|
7
|
+
const sourceMessage = params.sourceMessage?.trim();
|
|
8
|
+
if (!sourceMessage)
|
|
9
|
+
return description;
|
|
10
|
+
const normalizedSource = normalizeForSourceComparison(sourceMessage);
|
|
11
|
+
if (!normalizedSource)
|
|
12
|
+
return description;
|
|
13
|
+
if (normalizeForSourceComparison(params.title) === normalizedSource)
|
|
14
|
+
return description;
|
|
15
|
+
if (normalizeForSourceComparison(description).includes(normalizedSource))
|
|
16
|
+
return description;
|
|
17
|
+
return description
|
|
18
|
+
? `${description}\n\n${SOURCE_MESSAGE_SECTION_LABEL}\n${sourceMessage}`
|
|
19
|
+
: `${SOURCE_MESSAGE_SECTION_LABEL}\n${sourceMessage}`;
|
|
20
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { buildThreadShortId } from '@bbigbang/protocol';
|
|
2
|
+
import { isClearedTaskRootTarget } from './clearedTaskRoots.js';
|
|
3
|
+
export function listVisibleTasksForAgent(db, agentId, options) {
|
|
4
|
+
const requestedStatus = options?.status ?? 'all';
|
|
5
|
+
const requestedScope = options?.scope ?? 'all';
|
|
6
|
+
const params = [agentId, agentId, agentId];
|
|
7
|
+
const conditions = ['(t.claimed_by_agent_id = ? OR tp.agent_id = ?)'];
|
|
8
|
+
if (requestedStatus !== 'all') {
|
|
9
|
+
conditions.push('t.status = ?');
|
|
10
|
+
params.push(requestedStatus);
|
|
11
|
+
}
|
|
12
|
+
if (requestedScope === 'dm') {
|
|
13
|
+
conditions.push(`t.channel_id LIKE 'dm:%'`);
|
|
14
|
+
}
|
|
15
|
+
else if (requestedScope === 'channel') {
|
|
16
|
+
conditions.push(`t.channel_id NOT LIKE 'dm:%'`);
|
|
17
|
+
}
|
|
18
|
+
const rows = db.prepare(`${VISIBLE_TASK_SELECT}
|
|
19
|
+
${VISIBLE_TASK_FROM}
|
|
20
|
+
WHERE ${conditions.join(' AND ')}
|
|
21
|
+
ORDER BY t.updated_at DESC, t.created_at DESC`).all(...params);
|
|
22
|
+
return rows.map(toVisibleTaskView);
|
|
23
|
+
}
|
|
24
|
+
export function getVisibleTaskLookupByRef(db, agentId, taskRef) {
|
|
25
|
+
const row = db.prepare(`${VISIBLE_TASK_SELECT}
|
|
26
|
+
${VISIBLE_TASK_FROM}
|
|
27
|
+
WHERE t.agent_task_ref = ?
|
|
28
|
+
AND (t.claimed_by_agent_id = ? OR tp.agent_id = ?)
|
|
29
|
+
LIMIT 1`).get(agentId, taskRef, agentId, agentId);
|
|
30
|
+
if (!row)
|
|
31
|
+
return null;
|
|
32
|
+
return {
|
|
33
|
+
record: row,
|
|
34
|
+
task: toVisibleTaskView(row),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export function getDeletedTaskLookupByRef(db, agentId, taskRef) {
|
|
38
|
+
const row = db.prepare(`SELECT dt.task_id as taskId,
|
|
39
|
+
dt.agent_task_ref as agentTaskRef,
|
|
40
|
+
dt.channel_id as channelId,
|
|
41
|
+
dt.channel_name as channelName,
|
|
42
|
+
dt.task_number as taskNumber,
|
|
43
|
+
dt.title,
|
|
44
|
+
dt.description,
|
|
45
|
+
dt.status,
|
|
46
|
+
dt.claimed_by_name as claimedByName,
|
|
47
|
+
dt.created_by_name as createdByName,
|
|
48
|
+
dt.message_id as messageId,
|
|
49
|
+
dt.source_target as sourceTarget,
|
|
50
|
+
dt.thread_root_id as threadRootId,
|
|
51
|
+
dt.deleted_by_name as deletedByName,
|
|
52
|
+
dt.created_at as createdAt,
|
|
53
|
+
dt.updated_at as updatedAt,
|
|
54
|
+
dt.deleted_at as deletedAt
|
|
55
|
+
FROM deleted_tasks dt
|
|
56
|
+
WHERE dt.agent_task_ref = ?
|
|
57
|
+
AND (
|
|
58
|
+
dt.claimed_by_agent_id = ?
|
|
59
|
+
OR EXISTS (
|
|
60
|
+
SELECT 1
|
|
61
|
+
FROM deleted_task_participants dtp
|
|
62
|
+
WHERE dtp.task_id = dt.task_id
|
|
63
|
+
AND dtp.agent_id = ?
|
|
64
|
+
)
|
|
65
|
+
)
|
|
66
|
+
LIMIT 1`).get(taskRef, agentId, agentId);
|
|
67
|
+
if (!row)
|
|
68
|
+
return null;
|
|
69
|
+
return {
|
|
70
|
+
record: row,
|
|
71
|
+
deletedTask: toDeletedTaskView(row),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export function listTaskHistoryEventViews(db, taskId, limit) {
|
|
75
|
+
const rows = db.prepare(`SELECT event_id as eventId,
|
|
76
|
+
event_type as eventType,
|
|
77
|
+
actor_type as actorType,
|
|
78
|
+
actor_id as actorId,
|
|
79
|
+
actor_name as actorName,
|
|
80
|
+
from_status as fromStatus,
|
|
81
|
+
to_status as toStatus,
|
|
82
|
+
claimed_by_agent_id_after as claimedByAgentIdAfter,
|
|
83
|
+
claimed_by_name_after as claimedByNameAfter,
|
|
84
|
+
message_id as messageId,
|
|
85
|
+
thread_target as threadTarget,
|
|
86
|
+
created_at as createdAt
|
|
87
|
+
FROM task_events
|
|
88
|
+
WHERE task_id = ?
|
|
89
|
+
ORDER BY created_at DESC
|
|
90
|
+
LIMIT ?`).all(taskId, limit);
|
|
91
|
+
return rows.map((row) => ({
|
|
92
|
+
eventId: row.eventId,
|
|
93
|
+
type: row.eventType,
|
|
94
|
+
actorType: row.actorType,
|
|
95
|
+
actorId: row.actorId,
|
|
96
|
+
actorName: row.actorName,
|
|
97
|
+
fromStatus: row.fromStatus,
|
|
98
|
+
toStatus: row.toStatus,
|
|
99
|
+
claimedByAgentIdAfter: row.claimedByAgentIdAfter,
|
|
100
|
+
claimedByNameAfter: row.claimedByNameAfter,
|
|
101
|
+
messageId: row.messageId,
|
|
102
|
+
threadTarget: row.threadTarget,
|
|
103
|
+
createdAt: new Date(row.createdAt).toISOString(),
|
|
104
|
+
}));
|
|
105
|
+
}
|
|
106
|
+
export function buildTaskSourceTarget(channelId, sourceTarget, channelName) {
|
|
107
|
+
if (isClearedTaskRootTarget(sourceTarget))
|
|
108
|
+
return channelId.startsWith('dm:') ? null : `#${channelName ?? channelId}`;
|
|
109
|
+
if (sourceTarget?.trim())
|
|
110
|
+
return sourceTarget.trim();
|
|
111
|
+
if (channelId.startsWith('dm:'))
|
|
112
|
+
return null;
|
|
113
|
+
return `#${channelName ?? channelId}`;
|
|
114
|
+
}
|
|
115
|
+
export function buildTaskSourceLabel(channelId, sourceTarget, channelName) {
|
|
116
|
+
if (sourceTarget?.startsWith('dm:@'))
|
|
117
|
+
return sourceTarget;
|
|
118
|
+
if (channelName)
|
|
119
|
+
return `#${channelName}`;
|
|
120
|
+
if (channelId.startsWith('dm:'))
|
|
121
|
+
return 'DM';
|
|
122
|
+
if (sourceTarget?.trim())
|
|
123
|
+
return sourceTarget.trim();
|
|
124
|
+
return channelId;
|
|
125
|
+
}
|
|
126
|
+
export function buildTaskThreadTarget(sourceTarget, messageId) {
|
|
127
|
+
if (!sourceTarget || !messageId)
|
|
128
|
+
return null;
|
|
129
|
+
const normalizedTarget = sourceTarget.trim();
|
|
130
|
+
if (!(normalizedTarget.startsWith('dm:@') || normalizedTarget.startsWith('#')))
|
|
131
|
+
return null;
|
|
132
|
+
if (isThreadTargetValue(normalizedTarget))
|
|
133
|
+
return normalizedTarget;
|
|
134
|
+
return `${normalizedTarget}:${buildThreadShortId(messageId)}`;
|
|
135
|
+
}
|
|
136
|
+
const VISIBLE_TASK_SELECT = `SELECT t.task_id as taskId,
|
|
137
|
+
t.agent_task_ref as agentTaskRef,
|
|
138
|
+
t.channel_id as channelId,
|
|
139
|
+
t.task_number as taskNumber,
|
|
140
|
+
t.title,
|
|
141
|
+
t.description,
|
|
142
|
+
t.status,
|
|
143
|
+
t.claimed_by_agent_id as claimedByAgentId,
|
|
144
|
+
t.claimed_by_name as claimedByName,
|
|
145
|
+
t.created_by_agent_id as createdByAgentId,
|
|
146
|
+
t.created_by_name as createdByName,
|
|
147
|
+
t.created_at as createdAt,
|
|
148
|
+
t.updated_at as updatedAt,
|
|
149
|
+
t.message_id as messageId,
|
|
150
|
+
COALESCE(t.dm_target, cm.target) as sourceTarget,
|
|
151
|
+
ch.name as channelName`;
|
|
152
|
+
const VISIBLE_TASK_FROM = `FROM tasks t
|
|
153
|
+
LEFT JOIN task_participants tp
|
|
154
|
+
ON tp.task_id = t.task_id
|
|
155
|
+
AND tp.agent_id = ?
|
|
156
|
+
LEFT JOIN channel_messages cm
|
|
157
|
+
ON cm.message_id = t.message_id
|
|
158
|
+
LEFT JOIN channels ch
|
|
159
|
+
ON ch.channel_id = t.channel_id`;
|
|
160
|
+
function toVisibleTaskView(row) {
|
|
161
|
+
const sourceTarget = buildTaskSourceTarget(row.channelId, row.sourceTarget, row.channelName);
|
|
162
|
+
return {
|
|
163
|
+
taskId: row.taskId,
|
|
164
|
+
agentTaskRef: row.agentTaskRef,
|
|
165
|
+
channelId: row.channelId,
|
|
166
|
+
taskNumber: row.taskNumber,
|
|
167
|
+
title: row.title,
|
|
168
|
+
description: row.description ?? null,
|
|
169
|
+
status: row.status,
|
|
170
|
+
claimedByName: row.claimedByName ?? null,
|
|
171
|
+
createdByName: row.createdByName ?? null,
|
|
172
|
+
messageId: row.messageId ?? null,
|
|
173
|
+
sourceTarget,
|
|
174
|
+
sourceLabel: buildTaskSourceLabel(row.channelId, sourceTarget, row.channelName),
|
|
175
|
+
threadTarget: buildTaskThreadTarget(sourceTarget, row.messageId ?? null),
|
|
176
|
+
createdAt: new Date(row.createdAt).toISOString(),
|
|
177
|
+
updatedAt: new Date(row.updatedAt).toISOString(),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function toDeletedTaskView(row) {
|
|
181
|
+
const sourceTarget = buildTaskSourceTarget(row.channelId, row.sourceTarget, row.channelName);
|
|
182
|
+
const threadTarget = row.threadRootId && sourceTarget ? `${sourceTarget}:${row.threadRootId}` : null;
|
|
183
|
+
return {
|
|
184
|
+
deleted: true,
|
|
185
|
+
taskId: row.taskId,
|
|
186
|
+
agentTaskRef: row.agentTaskRef,
|
|
187
|
+
channelId: row.channelId,
|
|
188
|
+
taskNumber: row.taskNumber,
|
|
189
|
+
title: row.title,
|
|
190
|
+
description: row.description ?? null,
|
|
191
|
+
status: row.status,
|
|
192
|
+
claimedByName: row.claimedByName ?? null,
|
|
193
|
+
createdByName: row.createdByName ?? null,
|
|
194
|
+
messageId: row.messageId ?? null,
|
|
195
|
+
sourceTarget,
|
|
196
|
+
sourceLabel: buildTaskSourceLabel(row.channelId, sourceTarget, row.channelName),
|
|
197
|
+
threadTarget,
|
|
198
|
+
deletedByName: row.deletedByName ?? null,
|
|
199
|
+
createdAt: new Date(row.createdAt).toISOString(),
|
|
200
|
+
updatedAt: new Date(row.updatedAt).toISOString(),
|
|
201
|
+
deletedAt: new Date(row.deletedAt).toISOString(),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function isThreadTargetValue(target) {
|
|
205
|
+
if (target.startsWith('dm:@'))
|
|
206
|
+
return target.split(':').length >= 3;
|
|
207
|
+
if (target.startsWith('#'))
|
|
208
|
+
return target.includes(':');
|
|
209
|
+
return false;
|
|
210
|
+
}
|