@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,228 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { log } from '@bbigbang/runtime-acp';
|
|
4
|
+
import { WorkspaceToolServiceError } from './workspaceToolErrors.js';
|
|
5
|
+
import { isPlainRecord } from './workspaceToolExecutionUtils.js';
|
|
6
|
+
const WORKSPACE_TOOL_ACTION_UPLOAD_MAX_BYTES = 5 * 1024 * 1024;
|
|
7
|
+
export class WorkspaceToolUploadMaterializer {
|
|
8
|
+
deps;
|
|
9
|
+
constructor(deps) {
|
|
10
|
+
this.deps = deps;
|
|
11
|
+
}
|
|
12
|
+
async materializeFileActionParams(params) {
|
|
13
|
+
const fileFields = (params.action.paramsSchema ?? []).filter((field) => (field.input ?? 'text') === 'file');
|
|
14
|
+
if (fileFields.length === 0)
|
|
15
|
+
return params.actionParams;
|
|
16
|
+
const next = { ...params.actionParams };
|
|
17
|
+
for (const field of fileFields) {
|
|
18
|
+
if (!Object.prototype.hasOwnProperty.call(next, field.name))
|
|
19
|
+
continue;
|
|
20
|
+
const value = next[field.name];
|
|
21
|
+
if (field.multiple === true) {
|
|
22
|
+
if (!Array.isArray(value)) {
|
|
23
|
+
throw new WorkspaceToolServiceError(`Parameter "${field.name}" must be an array of uploaded file refs.`, 400);
|
|
24
|
+
}
|
|
25
|
+
next[field.name] = await Promise.all(value.map((item, index) => this.materializeUploadedFileParam({
|
|
26
|
+
fieldName: field.name,
|
|
27
|
+
value: item,
|
|
28
|
+
accept: field.accept,
|
|
29
|
+
maxBytes: field.maxBytes,
|
|
30
|
+
runId: params.runId,
|
|
31
|
+
toolId: params.toolId,
|
|
32
|
+
viewerUserId: params.viewerUserId,
|
|
33
|
+
agentNodeId: params.agentNodeId,
|
|
34
|
+
workspaceRoot: params.workspaceRoot,
|
|
35
|
+
index,
|
|
36
|
+
})));
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
next[field.name] = await this.materializeUploadedFileParam({
|
|
40
|
+
fieldName: field.name,
|
|
41
|
+
value,
|
|
42
|
+
accept: field.accept,
|
|
43
|
+
maxBytes: field.maxBytes,
|
|
44
|
+
runId: params.runId,
|
|
45
|
+
toolId: params.toolId,
|
|
46
|
+
viewerUserId: params.viewerUserId,
|
|
47
|
+
agentNodeId: params.agentNodeId,
|
|
48
|
+
workspaceRoot: params.workspaceRoot,
|
|
49
|
+
index: 0,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return next;
|
|
54
|
+
}
|
|
55
|
+
collectMaterializedUploadPaths(value) {
|
|
56
|
+
const paths = new Set();
|
|
57
|
+
const visit = (item) => {
|
|
58
|
+
if (Array.isArray(item)) {
|
|
59
|
+
for (const child of item)
|
|
60
|
+
visit(child);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (!isPlainRecord(item))
|
|
64
|
+
return;
|
|
65
|
+
if (item.type === 'workspace_tool_upload') {
|
|
66
|
+
const candidate = typeof item.workspacePath === 'string'
|
|
67
|
+
? item.workspacePath
|
|
68
|
+
: typeof item.path === 'string'
|
|
69
|
+
? item.path
|
|
70
|
+
: '';
|
|
71
|
+
const normalized = joinWorkspaceRelativePath(candidate);
|
|
72
|
+
if (normalized.startsWith('.agent-tools-runtime/') && normalized.includes('/uploads/')) {
|
|
73
|
+
paths.add(normalized);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
for (const child of Object.values(item))
|
|
77
|
+
visit(child);
|
|
78
|
+
};
|
|
79
|
+
visit(value);
|
|
80
|
+
return [...paths];
|
|
81
|
+
}
|
|
82
|
+
async cleanupMaterializedUploadsIfUnrecorded(params) {
|
|
83
|
+
if (params.paths.length === 0 || this.deps.hasRunRow(params.runId))
|
|
84
|
+
return;
|
|
85
|
+
for (const relativePath of params.paths) {
|
|
86
|
+
try {
|
|
87
|
+
await this.deps.deleteWorkspaceFile(params.agentNodeId, params.workspaceRoot, relativePath);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
if (!String(error?.message ?? error).toLowerCase().includes('not found')) {
|
|
91
|
+
log.warn('[workspace-tools] failed to cleanup unrecorded upload staging file', {
|
|
92
|
+
runId: params.runId,
|
|
93
|
+
relativePath,
|
|
94
|
+
error: String(error?.message ?? error),
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async invokeWithMaterializedUploads(params, fn) {
|
|
101
|
+
try {
|
|
102
|
+
return await fn();
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
await this.cleanupMaterializedUploadsIfUnrecorded({
|
|
106
|
+
runId: params.runId,
|
|
107
|
+
agentNodeId: params.agentNodeId,
|
|
108
|
+
workspaceRoot: params.workspaceRoot,
|
|
109
|
+
paths: params.stagedUploadPaths,
|
|
110
|
+
});
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async materializeUploadedFileParam(params) {
|
|
115
|
+
if (!isPlainRecord(params.value)) {
|
|
116
|
+
throw new WorkspaceToolServiceError(`Parameter "${params.fieldName}" must be an uploaded file ref.`, 400);
|
|
117
|
+
}
|
|
118
|
+
const assetId = normalizeUploadedAssetId(params.value.assetId)
|
|
119
|
+
?? normalizeUploadedAssetId(params.value.attachmentId)
|
|
120
|
+
?? normalizeUploadedAssetId(extractAttachmentIdFromUri(params.value.uri));
|
|
121
|
+
if (!assetId) {
|
|
122
|
+
throw new WorkspaceToolServiceError(`Parameter "${params.fieldName}" must reference an uploaded asset.`, 400);
|
|
123
|
+
}
|
|
124
|
+
const row = this.loadWorkspaceToolUploadAsset(assetId);
|
|
125
|
+
if (!row) {
|
|
126
|
+
throw new WorkspaceToolServiceError(`Uploaded asset "${assetId}" was not found.`, 404);
|
|
127
|
+
}
|
|
128
|
+
if (row.userId && row.userId !== params.viewerUserId && !this.deps.isAdminUser(params.viewerUserId)) {
|
|
129
|
+
throw new WorkspaceToolServiceError(`Uploaded asset "${assetId}" is not owned by the invoking user.`, 403);
|
|
130
|
+
}
|
|
131
|
+
if (!row.userId && !this.deps.isAdminUser(params.viewerUserId)) {
|
|
132
|
+
throw new WorkspaceToolServiceError(`Uploaded asset "${assetId}" is not available for tool action upload.`, 403);
|
|
133
|
+
}
|
|
134
|
+
if (!row.storagePath || !fs.existsSync(row.storagePath)) {
|
|
135
|
+
throw new WorkspaceToolServiceError(`Uploaded asset "${assetId}" is missing from storage.`, 404);
|
|
136
|
+
}
|
|
137
|
+
const maxBytes = params.maxBytes ?? WORKSPACE_TOOL_ACTION_UPLOAD_MAX_BYTES;
|
|
138
|
+
if (row.sizeBytes > maxBytes) {
|
|
139
|
+
throw new WorkspaceToolServiceError(`Parameter "${params.fieldName}" file exceeds ${maxBytes} bytes.`, 400);
|
|
140
|
+
}
|
|
141
|
+
const filename = row.originalFilename?.trim() || row.filename || 'upload.bin';
|
|
142
|
+
if (!uploadedAssetMatchesAccept({ accept: params.accept, filename, mimeType: row.mimeType })) {
|
|
143
|
+
throw new WorkspaceToolServiceError(`Parameter "${params.fieldName}" file does not match accepted type "${params.accept}".`, 400);
|
|
144
|
+
}
|
|
145
|
+
const safeFilename = sanitizeWorkspaceToolUploadFilename(filename);
|
|
146
|
+
const targetRelativePath = joinWorkspaceRelativePath('.agent-tools-runtime', params.toolId, 'uploads', params.runId, params.fieldName, `${String(params.index).padStart(3, '0')}-${assetId}--${safeFilename}`);
|
|
147
|
+
const fileBuffer = fs.readFileSync(row.storagePath);
|
|
148
|
+
await this.deps.writeWorkspaceFile(params.agentNodeId, params.workspaceRoot, targetRelativePath, fileBuffer.toString('base64'), 'overwrite', {
|
|
149
|
+
scaffold: false,
|
|
150
|
+
contentEncoding: 'base64',
|
|
151
|
+
});
|
|
152
|
+
return {
|
|
153
|
+
type: 'workspace_tool_upload',
|
|
154
|
+
assetId,
|
|
155
|
+
attachmentId: assetId,
|
|
156
|
+
filename,
|
|
157
|
+
sizeBytes: row.sizeBytes,
|
|
158
|
+
...(row.mimeType ? { mimeType: row.mimeType } : {}),
|
|
159
|
+
...(row.kind ? { kind: row.kind } : {}),
|
|
160
|
+
...(row.scopeType ? { scopeType: row.scopeType } : {}),
|
|
161
|
+
...(row.scopeId ? { scopeId: row.scopeId } : {}),
|
|
162
|
+
workspacePath: targetRelativePath,
|
|
163
|
+
path: targetRelativePath,
|
|
164
|
+
uri: `workspace:${targetRelativePath}`,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
loadWorkspaceToolUploadAsset(assetId) {
|
|
168
|
+
return this.deps.db.prepare(`SELECT id,
|
|
169
|
+
filename,
|
|
170
|
+
original_filename as originalFilename,
|
|
171
|
+
mime_type as mimeType,
|
|
172
|
+
size_bytes as sizeBytes,
|
|
173
|
+
storage_path as storagePath,
|
|
174
|
+
user_id as userId,
|
|
175
|
+
kind,
|
|
176
|
+
scope_type as scopeType,
|
|
177
|
+
scope_id as scopeId
|
|
178
|
+
FROM attachments
|
|
179
|
+
WHERE id = ?`).get(assetId) ?? null;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
export function joinWorkspaceRelativePath(...parts) {
|
|
183
|
+
return parts
|
|
184
|
+
.flatMap((part) => part.split('/'))
|
|
185
|
+
.filter((part) => part.length > 0 && part !== '.')
|
|
186
|
+
.join('/');
|
|
187
|
+
}
|
|
188
|
+
export function sanitizeWorkspaceToolUploadFilename(filename) {
|
|
189
|
+
const base = path.basename(filename).trim() || 'upload.bin';
|
|
190
|
+
return base
|
|
191
|
+
.replace(/[^A-Za-z0-9._-]+/g, '_')
|
|
192
|
+
.replace(/_+/g, '_')
|
|
193
|
+
.replace(/^_+|_+$/g, '')
|
|
194
|
+
|| 'upload.bin';
|
|
195
|
+
}
|
|
196
|
+
export function normalizeUploadedAssetId(value) {
|
|
197
|
+
if (typeof value !== 'string')
|
|
198
|
+
return null;
|
|
199
|
+
const normalized = value.trim();
|
|
200
|
+
return /^[a-f0-9-]{36}$/i.test(normalized) ? normalized.toLowerCase() : null;
|
|
201
|
+
}
|
|
202
|
+
export function extractAttachmentIdFromUri(value) {
|
|
203
|
+
if (typeof value !== 'string')
|
|
204
|
+
return null;
|
|
205
|
+
const normalized = value.trim();
|
|
206
|
+
if (!normalized.startsWith('attachment:'))
|
|
207
|
+
return null;
|
|
208
|
+
return normalized.slice('attachment:'.length).trim();
|
|
209
|
+
}
|
|
210
|
+
export function uploadedAssetMatchesAccept(params) {
|
|
211
|
+
const acceptTokens = (params.accept ?? '')
|
|
212
|
+
.split(',')
|
|
213
|
+
.map((token) => token.trim().toLowerCase())
|
|
214
|
+
.filter(Boolean);
|
|
215
|
+
if (acceptTokens.length === 0)
|
|
216
|
+
return true;
|
|
217
|
+
const mimeType = (params.mimeType ?? '').trim().toLowerCase();
|
|
218
|
+
const filename = params.filename.trim().toLowerCase();
|
|
219
|
+
return acceptTokens.some((token) => {
|
|
220
|
+
if (token.startsWith('.'))
|
|
221
|
+
return filename.endsWith(token);
|
|
222
|
+
if (token.endsWith('/*')) {
|
|
223
|
+
const prefix = token.slice(0, -1);
|
|
224
|
+
return Boolean(mimeType) && mimeType.startsWith(prefix);
|
|
225
|
+
}
|
|
226
|
+
return Boolean(mimeType) && mimeType === token;
|
|
227
|
+
});
|
|
228
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { getUserChannelAccess, validateSession } from '../services/auth.js';
|
|
2
|
+
import { ActionCardError, cancelActionCard, confirmActionCard, expireDueActionCards, getActionCardById, } from './actionCards.js';
|
|
3
|
+
function getRequestUser(req, db) {
|
|
4
|
+
const authHeader = typeof req.headers.authorization === 'string' ? req.headers.authorization : '';
|
|
5
|
+
const token = authHeader.startsWith('Bearer ') ? authHeader.slice(7).trim() : '';
|
|
6
|
+
return token ? validateSession(db, token) : null;
|
|
7
|
+
}
|
|
8
|
+
function requireAdmin(req, reply, db) {
|
|
9
|
+
const user = getRequestUser(req, db);
|
|
10
|
+
if (!user) {
|
|
11
|
+
reply.code(401);
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
if (!user.isAdmin) {
|
|
15
|
+
reply.code(403);
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
return user;
|
|
19
|
+
}
|
|
20
|
+
function canUserActOnActionCard(db, user, card) {
|
|
21
|
+
if (user.isAdmin)
|
|
22
|
+
return true;
|
|
23
|
+
if (card.actionType !== 'channel:add_member' || !card.originChannelId)
|
|
24
|
+
return false;
|
|
25
|
+
const targetChannelId = typeof card.payload.channelId === 'string' ? card.payload.channelId.trim() : '';
|
|
26
|
+
if (!targetChannelId)
|
|
27
|
+
return false;
|
|
28
|
+
const allowedChannels = new Set(getUserChannelAccess(db, user.id));
|
|
29
|
+
return allowedChannels.has(card.originChannelId) && allowedChannels.has(targetChannelId);
|
|
30
|
+
}
|
|
31
|
+
function requireActionCardActor(req, reply, db, actionCardId) {
|
|
32
|
+
const user = getRequestUser(req, db);
|
|
33
|
+
if (!user) {
|
|
34
|
+
reply.code(401);
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
const card = getActionCardById(db, actionCardId);
|
|
38
|
+
if (!card) {
|
|
39
|
+
reply.code(404);
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
if (!canUserActOnActionCard(db, user, card)) {
|
|
43
|
+
reply.code(403);
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
return user;
|
|
47
|
+
}
|
|
48
|
+
function handleActionCardError(error, reply) {
|
|
49
|
+
const statusCode = error instanceof ActionCardError ? error.statusCode : 500;
|
|
50
|
+
reply.code(statusCode);
|
|
51
|
+
return {
|
|
52
|
+
error: String(error?.message ?? error),
|
|
53
|
+
...(error instanceof ActionCardError && error.errorCode
|
|
54
|
+
? { error_code: error.errorCode, errorCode: error.errorCode }
|
|
55
|
+
: {}),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function publicCard(card) {
|
|
59
|
+
return {
|
|
60
|
+
id: card.id,
|
|
61
|
+
actionCardId: card.id,
|
|
62
|
+
action_card_id: card.id,
|
|
63
|
+
actionType: card.actionType,
|
|
64
|
+
action_type: card.actionType,
|
|
65
|
+
payload: card.payload,
|
|
66
|
+
originConversationId: card.originConversationId,
|
|
67
|
+
origin_conversation_id: card.originConversationId,
|
|
68
|
+
proposerAgentId: card.proposerAgentId,
|
|
69
|
+
proposer_agent_id: card.proposerAgentId,
|
|
70
|
+
runId: card.runId,
|
|
71
|
+
run_id: card.runId,
|
|
72
|
+
turnId: card.turnId,
|
|
73
|
+
turn_id: card.turnId,
|
|
74
|
+
traceId: card.traceId,
|
|
75
|
+
trace_id: card.traceId,
|
|
76
|
+
reason: card.reason,
|
|
77
|
+
status: card.status,
|
|
78
|
+
expiresAt: card.expiresAt,
|
|
79
|
+
expires_at: card.expiresAt,
|
|
80
|
+
result: card.result,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export function registerActionCardRoutes(app, db, conversationManager, broadcastToChannel) {
|
|
84
|
+
app.get('/api/action-cards/:id', async (req, reply) => {
|
|
85
|
+
const user = requireAdmin(req, reply, db);
|
|
86
|
+
if (!user)
|
|
87
|
+
return { error: 'Admin access required' };
|
|
88
|
+
expireDueActionCards(db);
|
|
89
|
+
const card = getActionCardById(db, req.params.id);
|
|
90
|
+
if (!card) {
|
|
91
|
+
reply.code(404);
|
|
92
|
+
return { error: 'Action card not found', error_code: 'ACTION_CARD_NOT_FOUND', errorCode: 'ACTION_CARD_NOT_FOUND' };
|
|
93
|
+
}
|
|
94
|
+
return { ok: true, card: publicCard(card) };
|
|
95
|
+
});
|
|
96
|
+
app.post('/api/action-cards/:id/confirm', async (req, reply) => {
|
|
97
|
+
const user = requireActionCardActor(req, reply, db, req.params.id);
|
|
98
|
+
if (!user)
|
|
99
|
+
return { error: 'Action card access required' };
|
|
100
|
+
try {
|
|
101
|
+
return confirmActionCard(db, {
|
|
102
|
+
id: req.params.id,
|
|
103
|
+
confirmedBy: user.id,
|
|
104
|
+
confirmedByName: user.username,
|
|
105
|
+
conversationManager,
|
|
106
|
+
broadcastToChannel,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
return handleActionCardError(error, reply);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
app.post('/api/action-cards/:id/cancel', async (req, reply) => {
|
|
114
|
+
const user = requireActionCardActor(req, reply, db, req.params.id);
|
|
115
|
+
if (!user)
|
|
116
|
+
return { error: 'Action card access required' };
|
|
117
|
+
try {
|
|
118
|
+
return cancelActionCard(db, {
|
|
119
|
+
id: req.params.id,
|
|
120
|
+
cancelledBy: user.id,
|
|
121
|
+
cancelledByName: user.username,
|
|
122
|
+
broadcastToChannel,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
return handleActionCardError(error, reply);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|