@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,288 @@
|
|
|
1
|
+
import { AgentVisibility } from '../agentVisibility.js';
|
|
2
|
+
import { formatConversationHandoff, getHandoffState, } from '../conversationHandoffs.js';
|
|
3
|
+
import { formatConversationSummary, refreshConversationSummariesForConversationIds, refreshConversationSummary, } from '../conversationSummaries.js';
|
|
4
|
+
import { resolveChannelFromTarget, resolveDefaultReplyTarget, resolveThreadRootId, } from '../conversationTargets.js';
|
|
5
|
+
import { findActiveConversationRunId } from '../routeHelpers.js';
|
|
6
|
+
import { startConversationHandoff } from '../sameAgentHandoffs.js';
|
|
7
|
+
import { getStaleTaskThreadReopenedRunBlock } from '../taskThreadRuntimeClosure.js';
|
|
8
|
+
import { getBoundTaskForThread } from '../threadTaskBindings.js';
|
|
9
|
+
import { buildRollingSummarySectionBody } from '../rollingConversationSummary.js';
|
|
10
|
+
function normalizeRequiredText(value) {
|
|
11
|
+
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
12
|
+
}
|
|
13
|
+
function normalizeOptionalText(value) {
|
|
14
|
+
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
15
|
+
}
|
|
16
|
+
function normalizeStringArray(value) {
|
|
17
|
+
if (!Array.isArray(value))
|
|
18
|
+
return [];
|
|
19
|
+
return value
|
|
20
|
+
.map((item) => (typeof item === 'string' ? item.trim() : ''))
|
|
21
|
+
.filter(Boolean);
|
|
22
|
+
}
|
|
23
|
+
function getCanonicalBoundTaskRef(boundTask) {
|
|
24
|
+
if (!boundTask)
|
|
25
|
+
return null;
|
|
26
|
+
const prefixedRef = typeof boundTask.agentTaskRef === 'string' ? boundTask.agentTaskRef.trim() : '';
|
|
27
|
+
return prefixedRef || null;
|
|
28
|
+
}
|
|
29
|
+
function annotateSourceSummaryForHandoff(params) {
|
|
30
|
+
if (!params.sourceSummary)
|
|
31
|
+
return null;
|
|
32
|
+
if (params.mode !== 'continue_there')
|
|
33
|
+
return params.sourceSummary;
|
|
34
|
+
const lines = params.sourceSummary.split('\n');
|
|
35
|
+
const statusLineIndex = lines.findIndex((line) => line.startsWith('Status: '));
|
|
36
|
+
if (statusLineIndex >= 0) {
|
|
37
|
+
lines[statusLineIndex] = `${lines[statusLineIndex]} (pre-handoff snapshot)`;
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
lines.unshift('Handoff: continue_there (pre-handoff snapshot)');
|
|
41
|
+
}
|
|
42
|
+
return lines.join('\n');
|
|
43
|
+
}
|
|
44
|
+
export function registerAgentHandoffRoutes(params) {
|
|
45
|
+
const { app, db, conversationManager, humanUserName, broadcastConversationStatus } = params;
|
|
46
|
+
const visibility = new AgentVisibility(db);
|
|
47
|
+
app.post('/api/internal/agent/:agentId/handoff', async (req, reply) => {
|
|
48
|
+
const { agentId } = req.params;
|
|
49
|
+
const agent = conversationManager.getAgent(agentId);
|
|
50
|
+
if (!agent) {
|
|
51
|
+
reply.code(404);
|
|
52
|
+
return { error: 'Agent not found' };
|
|
53
|
+
}
|
|
54
|
+
const sourceConversationId = typeof req.body?.conversationId === 'string' ? req.body.conversationId.trim() : '';
|
|
55
|
+
const requestedTarget = typeof req.body?.target === 'string' ? req.body.target.trim() : '';
|
|
56
|
+
const mode = (typeof req.body?.mode === 'string' ? req.body.mode.trim() : 'delegate_only');
|
|
57
|
+
const goal = normalizeRequiredText(req.body?.goal);
|
|
58
|
+
if (!sourceConversationId) {
|
|
59
|
+
reply.code(400);
|
|
60
|
+
return { error: 'conversationId is required' };
|
|
61
|
+
}
|
|
62
|
+
if (!requestedTarget) {
|
|
63
|
+
reply.code(400);
|
|
64
|
+
return { error: 'target is required' };
|
|
65
|
+
}
|
|
66
|
+
if (!goal) {
|
|
67
|
+
reply.code(400);
|
|
68
|
+
return { error: 'goal is required' };
|
|
69
|
+
}
|
|
70
|
+
if (!['delegate_only', 'continue_there', 'collab'].includes(mode)) {
|
|
71
|
+
reply.code(400);
|
|
72
|
+
return { error: `Invalid mode: ${req.body?.mode}` };
|
|
73
|
+
}
|
|
74
|
+
const sourceConversation = conversationManager.getConversation(sourceConversationId);
|
|
75
|
+
if (!sourceConversation || sourceConversation.agentId !== agentId) {
|
|
76
|
+
reply.code(400);
|
|
77
|
+
return { error: 'conversationId does not belong to this agent' };
|
|
78
|
+
}
|
|
79
|
+
if (requestedTarget.startsWith('#')) {
|
|
80
|
+
const channelId = resolveChannelFromTarget(requestedTarget, db);
|
|
81
|
+
if (!channelId) {
|
|
82
|
+
reply.code(404);
|
|
83
|
+
return { error: 'Target channel not found' };
|
|
84
|
+
}
|
|
85
|
+
if (visibility.resolveVisibleChannelId(agent, requestedTarget) === null) {
|
|
86
|
+
reply.code(403);
|
|
87
|
+
return { error: 'Agent is not a member of this channel' };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else if (!visibility.canSeeTarget(agent, requestedTarget)) {
|
|
91
|
+
reply.code(403);
|
|
92
|
+
return { error: 'Agent cannot access this target' };
|
|
93
|
+
}
|
|
94
|
+
const sourceReplyTarget = (resolveDefaultReplyTarget(db, sourceConversationId, humanUserName) ?? sourceConversation.replyTarget ?? '').trim();
|
|
95
|
+
const sourceConversationRunId = findActiveConversationRunId(db, sourceConversationId);
|
|
96
|
+
const sourceThreadRootId = resolveThreadRootId(sourceReplyTarget) ?? sourceConversation.threadRootId ?? null;
|
|
97
|
+
const sourceChannelId = sourceConversation.threadKind === 'direct'
|
|
98
|
+
? (sourceConversation.agentId ? `dm:${sourceConversation.agentId}` : null)
|
|
99
|
+
: (sourceConversation.channelId ?? null);
|
|
100
|
+
const sourceBoundTask = sourceChannelId && sourceThreadRootId
|
|
101
|
+
? getBoundTaskForThread(db, { channelId: sourceChannelId, threadRootId: sourceThreadRootId })
|
|
102
|
+
: undefined;
|
|
103
|
+
const staleReopenedRun = getStaleTaskThreadReopenedRunBlock(db, {
|
|
104
|
+
conversationId: sourceConversationId,
|
|
105
|
+
runId: sourceConversationRunId,
|
|
106
|
+
taskId: sourceBoundTask?.taskId,
|
|
107
|
+
});
|
|
108
|
+
if (staleReopenedRun) {
|
|
109
|
+
reply.code(409);
|
|
110
|
+
return {
|
|
111
|
+
error: 'This task-thread was reopened by a user follow-up. Stop this old review-closing run instead of handing off stale context.',
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const targetConversationId = visibility.ensureVisibleConversationId(agent, conversationManager, requestedTarget);
|
|
115
|
+
if (!targetConversationId) {
|
|
116
|
+
reply.code(404);
|
|
117
|
+
return { error: 'Conversation not found for the requested target' };
|
|
118
|
+
}
|
|
119
|
+
const targetConversation = conversationManager.getConversation(targetConversationId);
|
|
120
|
+
if (!targetConversation || targetConversation.agentId !== agentId) {
|
|
121
|
+
reply.code(404);
|
|
122
|
+
return { error: 'Target conversation not found' };
|
|
123
|
+
}
|
|
124
|
+
const targetReplyTarget = (resolveDefaultReplyTarget(db, targetConversationId, humanUserName) ?? targetConversation.replyTarget ?? '').trim();
|
|
125
|
+
if (!sourceReplyTarget || !targetReplyTarget) {
|
|
126
|
+
reply.code(400);
|
|
127
|
+
return { error: 'Unable to resolve handoff targets' };
|
|
128
|
+
}
|
|
129
|
+
if (sourceConversationId === targetConversationId || sourceReplyTarget === targetReplyTarget) {
|
|
130
|
+
reply.code(400);
|
|
131
|
+
return { error: 'Source and target must be different work surfaces' };
|
|
132
|
+
}
|
|
133
|
+
if (mode === 'continue_there' && !sourceConversationRunId) {
|
|
134
|
+
reply.code(409);
|
|
135
|
+
return { error: 'continue_there requires an active source run' };
|
|
136
|
+
}
|
|
137
|
+
const sourceSummary = refreshConversationSummary(db, agent, sourceConversationId);
|
|
138
|
+
const sourceSummaryBoundTask = sourceSummary?.summary.boundTask ?? null;
|
|
139
|
+
const handoffTaskId = sourceSummaryBoundTask?.taskId ?? sourceBoundTask?.taskId ?? null;
|
|
140
|
+
const handoffTaskNumber = sourceSummaryBoundTask?.taskNumber ?? sourceBoundTask?.taskNumber ?? null;
|
|
141
|
+
const explicitRelatedTaskRef = normalizeOptionalText(req.body?.context?.related_task_ref);
|
|
142
|
+
const sourceBoundTaskRef = getCanonicalBoundTaskRef(sourceSummaryBoundTask);
|
|
143
|
+
const sourceSummaryText = annotateSourceSummaryForHandoff({
|
|
144
|
+
mode,
|
|
145
|
+
sourceSummary: sourceSummary ? formatConversationSummary(sourceSummary) : null,
|
|
146
|
+
});
|
|
147
|
+
const sourceRollingSummary = buildRollingSummarySectionBody(db, sourceConversationId);
|
|
148
|
+
const payload = {
|
|
149
|
+
goal,
|
|
150
|
+
whyNow: normalizeOptionalText(req.body?.context?.why_now),
|
|
151
|
+
constraints: normalizeStringArray(req.body?.context?.constraints),
|
|
152
|
+
alreadyDone: normalizeStringArray(req.body?.context?.already_done),
|
|
153
|
+
expectedOutput: normalizeOptionalText(req.body?.context?.expected_output),
|
|
154
|
+
sourceTarget: sourceReplyTarget,
|
|
155
|
+
relatedTaskRef: explicitRelatedTaskRef ?? sourceBoundTaskRef,
|
|
156
|
+
optionalRefs: normalizeStringArray(req.body?.context?.optional_refs),
|
|
157
|
+
sourceSummary: sourceSummaryText,
|
|
158
|
+
sourceRollingSummary,
|
|
159
|
+
targetRunId: null,
|
|
160
|
+
cancelRunId: null,
|
|
161
|
+
completionMessageId: null,
|
|
162
|
+
error: null,
|
|
163
|
+
sourceContext: sourceSummary
|
|
164
|
+
? {
|
|
165
|
+
lastUserMessage: sourceSummary.summary.lastUserMessage,
|
|
166
|
+
lastAgentMessage: sourceSummary.summary.lastAgentMessage,
|
|
167
|
+
boundTaskRef: sourceBoundTaskRef,
|
|
168
|
+
boundTaskTitle: sourceSummary.summary.boundTask?.title ?? null,
|
|
169
|
+
ownerName: sourceSummary.summary.ownerName,
|
|
170
|
+
participants: sourceSummary.summary.participants,
|
|
171
|
+
myRole: sourceSummary.summary.myRole,
|
|
172
|
+
}
|
|
173
|
+
: null,
|
|
174
|
+
};
|
|
175
|
+
const handoff = await startConversationHandoff({
|
|
176
|
+
agentId,
|
|
177
|
+
db,
|
|
178
|
+
conversationManager,
|
|
179
|
+
humanUserName,
|
|
180
|
+
sourceConversationId,
|
|
181
|
+
sourceConversationRunId,
|
|
182
|
+
sourceReplyTarget,
|
|
183
|
+
targetConversationId,
|
|
184
|
+
targetReplyTarget,
|
|
185
|
+
mode,
|
|
186
|
+
payload,
|
|
187
|
+
taskId: handoffTaskId,
|
|
188
|
+
taskNumber: handoffTaskNumber,
|
|
189
|
+
broadcastConversationStatus,
|
|
190
|
+
});
|
|
191
|
+
refreshConversationSummariesForConversationIds(db, (targetAgentId) => conversationManager.getAgent(targetAgentId), [sourceConversationId, targetConversationId]);
|
|
192
|
+
return {
|
|
193
|
+
handoff: {
|
|
194
|
+
handoffId: handoff.handoffId,
|
|
195
|
+
agentId,
|
|
196
|
+
status: handoff.status,
|
|
197
|
+
mode,
|
|
198
|
+
sourceConversationId,
|
|
199
|
+
sourceReplyTarget,
|
|
200
|
+
targetConversationId,
|
|
201
|
+
targetReplyTarget,
|
|
202
|
+
workflowKind: 'generic',
|
|
203
|
+
sourceDisposition: mode === 'continue_there' ? 'cancel_current_run' : 'keep_running',
|
|
204
|
+
taskId: handoffTaskId,
|
|
205
|
+
taskNumber: handoffTaskNumber,
|
|
206
|
+
payload,
|
|
207
|
+
queued: handoff.queued,
|
|
208
|
+
cancelRequested: handoff.cancelRequested,
|
|
209
|
+
...(handoff.targetRunId ? { targetRunId: handoff.targetRunId } : {}),
|
|
210
|
+
...(handoff.cancelRunId ? { cancelRunId: handoff.cancelRunId } : {}),
|
|
211
|
+
...(handoff.error ? { error: handoff.error } : {}),
|
|
212
|
+
reportText: formatConversationHandoff({
|
|
213
|
+
handoffId: handoff.handoffId,
|
|
214
|
+
agentId,
|
|
215
|
+
sourceConversationId,
|
|
216
|
+
targetConversationId,
|
|
217
|
+
targetReplyTarget,
|
|
218
|
+
workflowKind: 'generic',
|
|
219
|
+
sourceDisposition: mode === 'continue_there' ? 'cancel_current_run' : 'keep_running',
|
|
220
|
+
taskId: handoffTaskId,
|
|
221
|
+
taskNumber: handoffTaskNumber,
|
|
222
|
+
mode,
|
|
223
|
+
status: handoff.status,
|
|
224
|
+
payload: {
|
|
225
|
+
...payload,
|
|
226
|
+
...(handoff.error ? { error: handoff.error } : {}),
|
|
227
|
+
},
|
|
228
|
+
createdAt: new Date().toISOString(),
|
|
229
|
+
updatedAt: new Date().toISOString(),
|
|
230
|
+
}),
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
});
|
|
234
|
+
app.get('/api/internal/agent/:agentId/handoffs/:handoffId', async (req, reply) => {
|
|
235
|
+
const { agentId, handoffId } = req.params;
|
|
236
|
+
if (!conversationManager.getAgent(agentId)) {
|
|
237
|
+
reply.code(404);
|
|
238
|
+
return { error: 'Agent not found' };
|
|
239
|
+
}
|
|
240
|
+
const state = getHandoffState(db, handoffId);
|
|
241
|
+
if (!state || state.agentId !== agentId) {
|
|
242
|
+
reply.code(404);
|
|
243
|
+
return { error: 'Handoff not found' };
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
handoff: {
|
|
247
|
+
handoffId: state.handoffId,
|
|
248
|
+
agentId: state.agentId,
|
|
249
|
+
sourceConversationId: state.sourceConversationId,
|
|
250
|
+
sourceReplyTarget: state.payload.sourceTarget,
|
|
251
|
+
targetConversationId: state.targetConversationId,
|
|
252
|
+
targetReplyTarget: state.targetReplyTarget,
|
|
253
|
+
workflowKind: state.workflowKind,
|
|
254
|
+
sourceDisposition: state.sourceDisposition,
|
|
255
|
+
taskId: state.taskId,
|
|
256
|
+
taskNumber: state.taskNumber,
|
|
257
|
+
mode: state.mode,
|
|
258
|
+
status: state.status,
|
|
259
|
+
payload: state.payload,
|
|
260
|
+
createdAt: state.createdAt,
|
|
261
|
+
updatedAt: state.updatedAt,
|
|
262
|
+
runs: state.runs.map((r) => ({
|
|
263
|
+
runId: r.runId,
|
|
264
|
+
agentId: r.agentId,
|
|
265
|
+
disposition: r.disposition,
|
|
266
|
+
createdAt: r.createdAt,
|
|
267
|
+
completedAt: r.completedAt,
|
|
268
|
+
})),
|
|
269
|
+
reportText: formatConversationHandoff({
|
|
270
|
+
handoffId: state.handoffId,
|
|
271
|
+
agentId: state.agentId,
|
|
272
|
+
sourceConversationId: state.sourceConversationId,
|
|
273
|
+
targetConversationId: state.targetConversationId,
|
|
274
|
+
targetReplyTarget: state.targetReplyTarget,
|
|
275
|
+
workflowKind: state.workflowKind,
|
|
276
|
+
sourceDisposition: state.sourceDisposition,
|
|
277
|
+
taskId: state.taskId,
|
|
278
|
+
taskNumber: state.taskNumber,
|
|
279
|
+
mode: state.mode,
|
|
280
|
+
status: state.status,
|
|
281
|
+
payload: state.payload,
|
|
282
|
+
createdAt: state.createdAt,
|
|
283
|
+
updatedAt: state.updatedAt,
|
|
284
|
+
}),
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
});
|
|
288
|
+
}
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import { AgentVisibility } from '../agentVisibility.js';
|
|
2
|
+
import { CLEARED_TASK_ROOT_TARGET_GLOB } from '../clearedTaskRoots.js';
|
|
3
|
+
import { findThreadRootMessageId } from '../threadRoots.js';
|
|
4
|
+
import { resolveChannelFromTarget, resolveThreadRootId } from '../conversationTargets.js';
|
|
5
|
+
import { buildFtsMatchQuery } from '../ftsQuery.js';
|
|
6
|
+
import { recordRunSurfaceCatchUpRowsSeen, recordRunSurfaceRowsSeen, resolveRunWatermarkIdentity, } from '../runSurfaceWatermarks.js';
|
|
7
|
+
import { advanceAgentSurfaceSeenThroughSeq } from '../collaborationSurfaceState.js';
|
|
8
|
+
function parseBoundedPositiveInt(value, fallback, max) {
|
|
9
|
+
const parsed = Number(value);
|
|
10
|
+
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
11
|
+
return fallback;
|
|
12
|
+
return Math.min(Math.floor(parsed), max);
|
|
13
|
+
}
|
|
14
|
+
function parseOptionalPositiveInt(value) {
|
|
15
|
+
if (value === undefined)
|
|
16
|
+
return undefined;
|
|
17
|
+
const parsed = Number(value);
|
|
18
|
+
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
19
|
+
return undefined;
|
|
20
|
+
return Math.floor(parsed);
|
|
21
|
+
}
|
|
22
|
+
function buildMessageScope(alias, channelId, threadRootId, target) {
|
|
23
|
+
const params = [channelId];
|
|
24
|
+
if (threadRootId !== null) {
|
|
25
|
+
params.push(threadRootId);
|
|
26
|
+
if (target?.startsWith('dm:@')) {
|
|
27
|
+
params.push(target);
|
|
28
|
+
return {
|
|
29
|
+
clause: `${alias}.channel_id = ? AND (${alias}.thread_root_id = ? OR (${alias}.thread_root_id IS NULL AND ${alias}.target = ?))`,
|
|
30
|
+
params,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
clause: `${alias}.channel_id = ? AND ${alias}.thread_root_id = ?`,
|
|
35
|
+
params,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
if (target?.startsWith('dm:@')) {
|
|
39
|
+
params.push(target);
|
|
40
|
+
return {
|
|
41
|
+
clause: `${alias}.channel_id = ? AND ${alias}.thread_root_id IS NULL AND ${alias}.target = ?`,
|
|
42
|
+
params,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
params.push(CLEARED_TASK_ROOT_TARGET_GLOB);
|
|
46
|
+
return {
|
|
47
|
+
clause: `${alias}.channel_id = ? AND ${alias}.thread_root_id IS NULL AND ${alias}.target NOT GLOB ?`,
|
|
48
|
+
params,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function resolveReadableChannelId(db, visibility, agent, target) {
|
|
52
|
+
const visibleChannelId = visibility.resolveVisibleChannelId(agent, target);
|
|
53
|
+
if (visibleChannelId)
|
|
54
|
+
return { channelId: visibleChannelId };
|
|
55
|
+
if (target.startsWith('#')) {
|
|
56
|
+
const channelId = resolveChannelFromTarget(target, db);
|
|
57
|
+
if (!channelId)
|
|
58
|
+
return { channelId: null, errorCode: 400, error: `Cannot resolve channel: ${target}` };
|
|
59
|
+
return { channelId: null, errorCode: 403, error: 'Agent is not a member of this channel' };
|
|
60
|
+
}
|
|
61
|
+
return { channelId: null, errorCode: 403, error: 'Agent cannot access this target' };
|
|
62
|
+
}
|
|
63
|
+
export function registerAgentHistoryRoutes(app, db, conversationManager) {
|
|
64
|
+
const visibility = new AgentVisibility(db);
|
|
65
|
+
app.get('/api/internal/agent/:agentId/search', async (req, reply) => {
|
|
66
|
+
const { agentId } = req.params;
|
|
67
|
+
const agent = conversationManager.getAgent(agentId);
|
|
68
|
+
if (!agent) {
|
|
69
|
+
reply.code(404);
|
|
70
|
+
return { error: 'Agent not found' };
|
|
71
|
+
}
|
|
72
|
+
const query = typeof req.query.q === 'string' ? req.query.q.trim() : '';
|
|
73
|
+
if (!query) {
|
|
74
|
+
reply.code(400);
|
|
75
|
+
return { error: 'q query parameter is required' };
|
|
76
|
+
}
|
|
77
|
+
const ftsQuery = buildFtsMatchQuery(query, { prefix: true });
|
|
78
|
+
if (!ftsQuery) {
|
|
79
|
+
reply.code(400);
|
|
80
|
+
return { error: 'q query parameter is required' };
|
|
81
|
+
}
|
|
82
|
+
const limit = parseBoundedPositiveInt(req.query.limit, 10, 20);
|
|
83
|
+
const visibleChannelIds = Array.from(new Set([`dm:${agentId}`, ...(agent.channelIds ?? [])]));
|
|
84
|
+
const whereParts = [
|
|
85
|
+
`cm.channel_id IN (${visibleChannelIds.map(() => '?').join(', ')})`,
|
|
86
|
+
'cm.target NOT GLOB ?',
|
|
87
|
+
];
|
|
88
|
+
const params = [ftsQuery, ...visibleChannelIds, CLEARED_TASK_ROOT_TARGET_GLOB];
|
|
89
|
+
const channelTarget = typeof req.query.channel === 'string' ? req.query.channel.trim() : '';
|
|
90
|
+
if (channelTarget) {
|
|
91
|
+
const resolved = resolveReadableChannelId(db, visibility, agent, channelTarget);
|
|
92
|
+
if (!resolved.channelId) {
|
|
93
|
+
reply.code(resolved.errorCode ?? 400);
|
|
94
|
+
return { error: resolved.error ?? `Cannot resolve channel: ${channelTarget}` };
|
|
95
|
+
}
|
|
96
|
+
const threadRootId = resolveThreadRootId(channelTarget);
|
|
97
|
+
if (threadRootId !== null || channelTarget.startsWith('dm:@')) {
|
|
98
|
+
const scope = buildMessageScope('cm', resolved.channelId, threadRootId, channelTarget);
|
|
99
|
+
whereParts.push(scope.clause);
|
|
100
|
+
params.push(...scope.params);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
whereParts.push('cm.channel_id = ?');
|
|
104
|
+
params.push(resolved.channelId);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const rows = db.prepare(`SELECT
|
|
108
|
+
cm.message_id as messageId,
|
|
109
|
+
cm.channel_id as channelId,
|
|
110
|
+
cm.sender_id as senderId,
|
|
111
|
+
cm.sender_name as senderName,
|
|
112
|
+
cm.sender_type as senderType,
|
|
113
|
+
cm.target,
|
|
114
|
+
cm.content,
|
|
115
|
+
cm.seq,
|
|
116
|
+
cm.created_at as createdAt,
|
|
117
|
+
snippet(channel_messages_fts, 4, '[', ']', '...', 12) as snippet
|
|
118
|
+
FROM channel_messages_fts
|
|
119
|
+
JOIN channel_messages cm ON cm.message_id = channel_messages_fts.message_id
|
|
120
|
+
WHERE channel_messages_fts MATCH ?
|
|
121
|
+
AND ${whereParts.join(' AND ')}
|
|
122
|
+
ORDER BY bm25(channel_messages_fts), cm.created_at DESC
|
|
123
|
+
LIMIT ?`).all(...params, limit);
|
|
124
|
+
return {
|
|
125
|
+
results: rows.map((row) => ({
|
|
126
|
+
id: row.messageId,
|
|
127
|
+
target: row.target,
|
|
128
|
+
senderName: row.senderName,
|
|
129
|
+
senderType: row.senderType,
|
|
130
|
+
content: row.content,
|
|
131
|
+
seq: row.seq,
|
|
132
|
+
createdAt: new Date(row.createdAt).toISOString(),
|
|
133
|
+
snippet: row.snippet ?? row.content,
|
|
134
|
+
})),
|
|
135
|
+
};
|
|
136
|
+
});
|
|
137
|
+
app.get('/api/internal/agent/:agentId/history', async (req, reply) => {
|
|
138
|
+
const { agentId } = req.params;
|
|
139
|
+
const agent = conversationManager.getAgent(agentId);
|
|
140
|
+
if (!agent) {
|
|
141
|
+
reply.code(404);
|
|
142
|
+
return { error: 'Agent not found' };
|
|
143
|
+
}
|
|
144
|
+
const { channel, limit: limitStr, around: aroundStr, before: beforeStr, after: afterStr, include_root: includeRootStr, runId: runIdStr, conversationId: conversationIdStr, } = req.query;
|
|
145
|
+
if (!channel) {
|
|
146
|
+
reply.code(400);
|
|
147
|
+
return { error: 'channel query parameter is required' };
|
|
148
|
+
}
|
|
149
|
+
const runId = runIdStr?.trim() || null;
|
|
150
|
+
const conversationId = conversationIdStr?.trim() || null;
|
|
151
|
+
if (conversationId) {
|
|
152
|
+
const conversation = conversationManager.getConversation(conversationId);
|
|
153
|
+
if (!conversation || conversation.agentId !== agentId) {
|
|
154
|
+
reply.code(400);
|
|
155
|
+
return { error: 'conversationId does not belong to this agent' };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const watermarkIdentity = resolveRunWatermarkIdentity(db, agentId, { runId, conversationId });
|
|
159
|
+
if (runId && !watermarkIdentity) {
|
|
160
|
+
reply.code(400);
|
|
161
|
+
return { error: 'runId does not belong to this agent conversation' };
|
|
162
|
+
}
|
|
163
|
+
const resolved = resolveReadableChannelId(db, visibility, agent, channel);
|
|
164
|
+
if (!resolved.channelId) {
|
|
165
|
+
reply.code(resolved.errorCode ?? 400);
|
|
166
|
+
return { error: resolved.error ?? `Cannot resolve channel: ${channel}` };
|
|
167
|
+
}
|
|
168
|
+
const limit = parseBoundedPositiveInt(limitStr, 50, 100);
|
|
169
|
+
const before = parseOptionalPositiveInt(beforeStr);
|
|
170
|
+
const after = parseOptionalPositiveInt(afterStr);
|
|
171
|
+
const around = typeof aroundStr === 'string' ? aroundStr.trim() : '';
|
|
172
|
+
const includeRoot = typeof includeRootStr === 'string' && ['1', 'true', 'yes'].includes(includeRootStr.trim().toLowerCase());
|
|
173
|
+
if (around && (before !== undefined || after !== undefined)) {
|
|
174
|
+
reply.code(400);
|
|
175
|
+
return { error: 'around cannot be combined with before or after' };
|
|
176
|
+
}
|
|
177
|
+
const targetThreadRootId = resolveThreadRootId(channel);
|
|
178
|
+
const scope = buildMessageScope('cm', resolved.channelId, targetThreadRootId, channel);
|
|
179
|
+
const taskJoinSelect = `cm.message_id as messageId, cm.channel_id as channelId, cm.sender_id as senderId,
|
|
180
|
+
cm.sender_name as senderName, cm.sender_type as senderType,
|
|
181
|
+
cm.target, cm.content, cm.seq, cm.created_at as createdAt, cm.thread_root_id as threadRootId,
|
|
182
|
+
t.task_number as taskNumber, t.status as taskStatus, t.claimed_by_name as taskAssigneeName`;
|
|
183
|
+
const taskJoin = `LEFT JOIN tasks t ON t.message_id = cm.message_id`;
|
|
184
|
+
const selectRows = (extraWhere, extraParams, order, rowLimit) => db
|
|
185
|
+
.prepare(`SELECT ${taskJoinSelect}
|
|
186
|
+
FROM channel_messages cm ${taskJoin}
|
|
187
|
+
WHERE ${scope.clause}${extraWhere ? ` AND ${extraWhere}` : ''}
|
|
188
|
+
ORDER BY cm.seq ${order} LIMIT ?`)
|
|
189
|
+
.all(...scope.params, ...extraParams, rowLimit);
|
|
190
|
+
const findAroundAnchorSeq = () => {
|
|
191
|
+
if (!around)
|
|
192
|
+
return null;
|
|
193
|
+
if (/^\d+$/.test(around)) {
|
|
194
|
+
const row = db.prepare(`SELECT cm.seq as seq
|
|
195
|
+
FROM channel_messages cm
|
|
196
|
+
WHERE ${scope.clause} AND cm.seq = ?
|
|
197
|
+
LIMIT 1`).get(...scope.params, Number(around));
|
|
198
|
+
return row?.seq ?? null;
|
|
199
|
+
}
|
|
200
|
+
const row = db.prepare(`SELECT cm.seq as seq
|
|
201
|
+
FROM channel_messages cm
|
|
202
|
+
WHERE ${scope.clause} AND cm.message_id LIKE ?
|
|
203
|
+
ORDER BY cm.seq ASC
|
|
204
|
+
LIMIT 1`).get(...scope.params, `${around}%`);
|
|
205
|
+
return row?.seq ?? null;
|
|
206
|
+
};
|
|
207
|
+
let rows;
|
|
208
|
+
if (around) {
|
|
209
|
+
const anchorSeq = findAroundAnchorSeq();
|
|
210
|
+
if (anchorSeq === null) {
|
|
211
|
+
reply.code(404);
|
|
212
|
+
return { error: `Cannot resolve message around ${around}` };
|
|
213
|
+
}
|
|
214
|
+
const beforeBase = Math.floor((limit - 1) / 2);
|
|
215
|
+
const afterBase = Math.max(limit - beforeBase - 1, 0);
|
|
216
|
+
let beforeRows = selectRows('cm.seq < ?', [anchorSeq], 'DESC', beforeBase);
|
|
217
|
+
let afterRows = selectRows('cm.seq > ?', [anchorSeq], 'ASC', afterBase);
|
|
218
|
+
if (beforeRows.length < beforeBase) {
|
|
219
|
+
afterRows = selectRows('cm.seq > ?', [anchorSeq], 'ASC', afterBase + (beforeBase - beforeRows.length));
|
|
220
|
+
}
|
|
221
|
+
if (afterRows.length < afterBase) {
|
|
222
|
+
beforeRows = selectRows('cm.seq < ?', [anchorSeq], 'DESC', beforeBase + (afterBase - afterRows.length));
|
|
223
|
+
}
|
|
224
|
+
const anchorRows = selectRows('cm.seq = ?', [anchorSeq], 'ASC', 1);
|
|
225
|
+
rows = beforeRows.reverse().concat(anchorRows, afterRows);
|
|
226
|
+
}
|
|
227
|
+
else if (after !== undefined) {
|
|
228
|
+
rows = selectRows('cm.seq > ?', [after], 'ASC', limit);
|
|
229
|
+
}
|
|
230
|
+
else if (before !== undefined) {
|
|
231
|
+
rows = selectRows('cm.seq < ?', [before], 'DESC', limit).reverse();
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
rows = selectRows('', [], 'DESC', limit).reverse();
|
|
235
|
+
}
|
|
236
|
+
if (after !== undefined) {
|
|
237
|
+
recordRunSurfaceCatchUpRowsSeen(db, {
|
|
238
|
+
identity: watermarkIdentity,
|
|
239
|
+
agentId,
|
|
240
|
+
channelId: resolved.channelId,
|
|
241
|
+
threadRootId: targetThreadRootId,
|
|
242
|
+
afterSeq: after,
|
|
243
|
+
rows,
|
|
244
|
+
source: 'read_history',
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
else if (around || before === undefined) {
|
|
248
|
+
recordRunSurfaceRowsSeen(db, {
|
|
249
|
+
identity: watermarkIdentity,
|
|
250
|
+
agentId,
|
|
251
|
+
rows,
|
|
252
|
+
source: 'read_history',
|
|
253
|
+
threadRootIdOverride: targetThreadRootId,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
if (rows.length > 0) {
|
|
257
|
+
const maxSeqBySurface = new Map();
|
|
258
|
+
for (const row of rows) {
|
|
259
|
+
const effectiveThreadRootId = row.threadRootId ?? targetThreadRootId;
|
|
260
|
+
const key = `${row.channelId}::${effectiveThreadRootId ?? ''}`;
|
|
261
|
+
const current = maxSeqBySurface.get(key);
|
|
262
|
+
if (!current || row.seq > current.maxSeq) {
|
|
263
|
+
maxSeqBySurface.set(key, {
|
|
264
|
+
channelId: row.channelId,
|
|
265
|
+
threadRootId: effectiveThreadRootId,
|
|
266
|
+
maxSeq: row.seq,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
for (const surface of maxSeqBySurface.values()) {
|
|
271
|
+
advanceAgentSurfaceSeenThroughSeq(db, {
|
|
272
|
+
agentId,
|
|
273
|
+
channelId: surface.channelId,
|
|
274
|
+
threadRootId: surface.threadRootId,
|
|
275
|
+
seenThroughSeq: surface.maxSeq,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
const hasOlder = rows.length > 0
|
|
280
|
+
? !!db.prepare(`SELECT 1
|
|
281
|
+
FROM channel_messages cm
|
|
282
|
+
WHERE ${scope.clause} AND cm.seq < ?
|
|
283
|
+
LIMIT 1`).get(...scope.params, rows[0].seq)
|
|
284
|
+
: false;
|
|
285
|
+
const hasNewer = rows.length > 0
|
|
286
|
+
? !!db.prepare(`SELECT 1
|
|
287
|
+
FROM channel_messages cm
|
|
288
|
+
WHERE ${scope.clause} AND cm.seq > ?
|
|
289
|
+
LIMIT 1`).get(...scope.params, rows[rows.length - 1].seq)
|
|
290
|
+
: false;
|
|
291
|
+
const messages = rows.map((r) => {
|
|
292
|
+
const ext = r;
|
|
293
|
+
return {
|
|
294
|
+
id: r.messageId,
|
|
295
|
+
target: r.target,
|
|
296
|
+
senderName: r.senderName,
|
|
297
|
+
senderType: r.senderType,
|
|
298
|
+
content: r.content,
|
|
299
|
+
seq: r.seq,
|
|
300
|
+
createdAt: new Date(r.createdAt).toISOString(),
|
|
301
|
+
...(ext.taskNumber != null ? {
|
|
302
|
+
taskNumber: ext.taskNumber,
|
|
303
|
+
taskStatus: ext.taskStatus,
|
|
304
|
+
taskAssigneeName: ext.taskAssigneeName,
|
|
305
|
+
} : {}),
|
|
306
|
+
};
|
|
307
|
+
});
|
|
308
|
+
if (includeRoot && targetThreadRootId) {
|
|
309
|
+
const rootMessageId = findThreadRootMessageId(db, resolved.channelId, targetThreadRootId);
|
|
310
|
+
if (rootMessageId && !messages.some((message) => message.id === rootMessageId)) {
|
|
311
|
+
const rootRow = db.prepare(`SELECT ${taskJoinSelect}
|
|
312
|
+
FROM channel_messages cm ${taskJoin}
|
|
313
|
+
WHERE cm.channel_id = ?
|
|
314
|
+
AND cm.message_id = ?
|
|
315
|
+
AND cm.target NOT GLOB ?
|
|
316
|
+
LIMIT 1`).get(resolved.channelId, rootMessageId, CLEARED_TASK_ROOT_TARGET_GLOB);
|
|
317
|
+
if (rootRow) {
|
|
318
|
+
messages.unshift({
|
|
319
|
+
id: rootRow.messageId,
|
|
320
|
+
target: rootRow.target,
|
|
321
|
+
senderName: rootRow.senderName,
|
|
322
|
+
senderType: rootRow.senderType,
|
|
323
|
+
content: rootRow.content,
|
|
324
|
+
seq: rootRow.seq,
|
|
325
|
+
createdAt: new Date(rootRow.createdAt).toISOString(),
|
|
326
|
+
...(rootRow.taskNumber != null ? {
|
|
327
|
+
taskNumber: rootRow.taskNumber,
|
|
328
|
+
taskStatus: rootRow.taskStatus,
|
|
329
|
+
taskAssigneeName: rootRow.taskAssigneeName,
|
|
330
|
+
} : {}),
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return {
|
|
336
|
+
channel,
|
|
337
|
+
messages,
|
|
338
|
+
hasOlder,
|
|
339
|
+
hasNewer,
|
|
340
|
+
has_older: hasOlder,
|
|
341
|
+
has_newer: hasNewer,
|
|
342
|
+
has_more: hasOlder || hasNewer,
|
|
343
|
+
};
|
|
344
|
+
});
|
|
345
|
+
}
|