@taskforcehq/taskforce 0.3.306 → 0.3.307
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/README.md +0 -2
- package/dist/Taskforce.module.css +317 -27
- package/dist/TaskforceCore.js +1 -0
- package/dist/TaskforceCore.test.js +178 -0
- package/dist/components/activity/EntityActivityTimeline.d.ts +34 -0
- package/dist/components/activity/EntityActivityTimeline.js +81 -0
- package/dist/components/context/ContextAttachmentManager.js +15 -3
- package/dist/components/features/AgentsModule.d.ts +5 -1
- package/dist/components/features/AgentsModule.js +142 -39
- package/dist/components/features/AgentsModule.test.js +260 -11
- package/dist/components/features/AnnotatedAttachmentWorkspace.d.ts +3 -1
- package/dist/components/features/AnnotatedAttachmentWorkspace.js +118 -4
- package/dist/components/features/AnnotatedAttachmentWorkspace.test.js +52 -0
- package/dist/components/features/DocumentIndex.d.ts +2 -2
- package/dist/components/features/DocumentIndex.js +3 -4
- package/dist/components/features/DocumentViewer.d.ts +3 -1
- package/dist/components/features/DocumentViewer.js +6 -5
- package/dist/components/features/DocumentWorkspace.d.ts +10 -2
- package/dist/components/features/DocumentWorkspace.js +15 -4
- package/dist/components/features/DocumentWorkspace.test.js +20 -3
- package/dist/components/features/TaskSettings.d.ts +2 -0
- package/dist/components/features/TaskSettings.js +83 -78
- package/dist/components/features/TaskSettings.test.js +171 -82
- package/dist/components/task/TaskActionHeader.js +11 -5
- package/dist/components/task/TaskActionHeader.test.js +10 -0
- package/dist/components/task/TaskCard.js +6 -2
- package/dist/components/task/TaskContextUpload.test.js +3 -3
- package/dist/components/task/TaskForm.js +14 -64
- package/dist/components/task/TaskKanban.js +0 -8
- package/dist/components/task/TaskList.js +5 -1
- package/dist/components/task/TaskList.test.js +4 -0
- package/dist/components/ui/HierarchyActionBadge.d.ts +10 -0
- package/dist/components/ui/HierarchyActionBadge.js +5 -0
- package/dist/components/views/AppShellHeader.d.ts +3 -1
- package/dist/components/views/AppShellHeader.js +27 -11
- package/dist/components/views/AppShellHeader.test.js +18 -1
- package/dist/components/views/PlanComparisonPage.js +9 -1
- package/dist/components/views/PlanComparisonPage.test.js +19 -0
- package/dist/components/views/PlansPage.js +1 -1
- package/dist/components/views/StandaloneLayout.js +256 -69
- package/dist/components/views/WidgetView.js +10 -8
- package/dist/components/views/panels/PlanningDrawer.d.ts +19 -6
- package/dist/components/views/panels/PlanningDrawer.js +259 -51
- package/dist/components/views/panels/PlanningDrawer.test.js +392 -4
- package/dist/config/envSchema.js +1 -0
- package/dist/core/AiProfileService.d.ts +7 -0
- package/dist/core/AiProfileService.js +460 -35
- package/dist/core/AiProfiles.test.js +648 -2
- package/dist/core/AttachmentLinkService.d.ts +3 -0
- package/dist/core/AttachmentLinkService.js +58 -13
- package/dist/core/GlobalSettingsService.js +12 -0
- package/dist/core/McpTokenService.d.ts +12 -0
- package/dist/core/McpTokenService.js +44 -1
- package/dist/core/PlanningEntities.test.js +366 -4
- package/dist/core/TaskActivityService.d.ts +28 -1
- package/dist/core/TaskActivityService.js +227 -46
- package/dist/core/TaskActivitySync.test.d.ts +1 -0
- package/dist/core/TaskActivitySync.test.js +90 -0
- package/dist/core/TaskAuditTimeline.test.js +96 -0
- package/dist/core/Taskforce.d.ts +190 -26
- package/dist/core/Taskforce.ids.test.js +58 -0
- package/dist/core/Taskforce.js +775 -71
- package/dist/core/Taskforce.mcpAuth.test.js +109 -0
- package/dist/core/types.d.ts +7 -0
- package/dist/hooks/auth/useTaskforceAuthBootstrap.js +2 -1
- package/dist/hooks/bootstrapTimeouts.d.ts +4 -0
- package/dist/hooks/bootstrapTimeouts.js +8 -0
- package/dist/hooks/bootstrapTimeouts.test.d.ts +1 -0
- package/dist/hooks/bootstrapTimeouts.test.js +12 -0
- package/dist/hooks/sync/orchestratorShared.d.ts +3 -3
- package/dist/hooks/sync/orchestratorShared.js +61 -1
- package/dist/hooks/sync/recovery.js +8 -0
- package/dist/hooks/sync/recovery.test.js +5 -5
- package/dist/hooks/sync/transfers.d.ts +2 -1
- package/dist/hooks/sync/transfers.js +36 -9
- package/dist/hooks/sync/transfers.test.js +534 -2
- package/dist/hooks/useFilterSort.js +13 -13
- package/dist/hooks/useFilterSort.test.js +87 -1
- package/dist/hooks/useSyncOrchestrator.js +50 -5
- package/dist/hooks/useTaskforce.d.ts +85 -50
- package/dist/hooks/useTaskforce.js +44 -14
- package/dist/hooks/useTaskforce.runtime-routing.test.js +94 -0
- package/dist/hooks/useTaskforce.sync-behavior.test.js +99 -0
- package/dist/hooks/useWorkspaceSyncController.js +7 -1
- package/dist/hooks/useWorkspaceSyncController.test.js +48 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +14 -7
- package/dist/mcp/canonicalAssetHelpers.d.ts +16 -4
- package/dist/mcp/canonicalAssetHelpers.js +18 -10
- package/dist/mcp/canonicalAssetHelpers.test.d.ts +1 -0
- package/dist/mcp/canonicalAssetHelpers.test.js +67 -0
- package/dist/mcp/clientIntegrations.d.ts +1 -0
- package/dist/mcp/clientIntegrations.js +25 -0
- package/dist/mcp/clientIntegrations.test.js +14 -3
- package/dist/mcp/clientProfileDefaults.js +3 -3
- package/dist/mcp/collaborationRegistrar.js +7 -7
- package/dist/mcp/documentAssetRegistrar.js +113 -7
- package/dist/mcp/runtime.d.ts +1 -0
- package/dist/mcp/runtime.js +969 -122
- package/dist/mcp/runtime.test.js +1083 -11
- package/dist/mcp/taskAttachmentTypes.d.ts +1 -1
- package/dist/mcp/taskPlanningRegistrar.js +48 -0
- package/dist/mcp/toolRegistry.d.ts +56 -0
- package/dist/mcp/toolRegistry.js +8 -0
- package/dist/mcp/toolRegistry.test.js +4 -0
- package/dist/migrations/taskSchemaMigrations.js +21 -0
- package/dist/server/aiProfileAvatarAssets.d.ts +49 -0
- package/dist/server/aiProfileAvatarAssets.js +319 -0
- package/dist/server/aiProfileAvatarAssets.test.d.ts +1 -0
- package/dist/server/aiProfileAvatarAssets.test.js +213 -0
- package/dist/server/annotatedAttachmentsRoutes.test.js +66 -7
- package/dist/server/index.js +4 -0
- package/dist/server/index.test.js +6 -1
- package/dist/server/routes/admin.js +187 -9
- package/dist/server/routes/annotatedAttachments.js +51 -0
- package/dist/server/routes/auth.d.ts +11 -0
- package/dist/server/routes/auth.js +189 -27
- package/dist/server/routes/authSupport.d.ts +4 -0
- package/dist/server/routes/authSupport.js +12 -1
- package/dist/server/routes/billing.js +11 -0
- package/dist/server/routes/billing.test.js +3 -0
- package/dist/server/routes/documents.js +10 -0
- package/dist/server/routes/shared.js +0 -10
- package/dist/server/routes/sync.integration.test.js +221 -0
- package/dist/server/routes/sync.js +13 -3
- package/dist/server/routes/syncSnapshotRoutes.js +2 -0
- package/dist/server/routes/tasks.js +12 -9
- package/dist/server/routes.js +87 -8
- package/dist/server/routes.test.js +890 -22
- package/dist/shared/aiProfileSurfaceType.d.ts +1 -1
- package/dist/shared/aiProfileSurfaceType.js +10 -8
- package/dist/storage/objectStorage.d.ts +1 -0
- package/dist/storage/objectStorage.js +2 -1
- package/dist/storage/objectStorageClient.d.ts +1 -0
- package/dist/storage/objectStorageClient.js +41 -8
- package/dist/storage/objectStorageClient.test.js +26 -1
- package/dist/storage/objectStorageConfig.js +3 -1
- package/dist/storage/objectStorageConfig.test.js +13 -1
- package/dist/sync/collaborationSyncPayload.js +2 -0
- package/dist/sync/collaborationSyncPayload.test.js +1 -0
- package/dist/sync/collaborationSyncState.d.ts +3 -3
- package/dist/sync/collaborationSyncState.js +19 -12
- package/dist/sync/contentSyncPayload.d.ts +5 -1
- package/dist/sync/contentSyncPayload.js +9 -0
- package/dist/sync/contentSyncPayload.test.js +39 -0
- package/dist/sync/contentSyncState.d.ts +5 -1
- package/dist/sync/contentSyncState.js +231 -37
- package/dist/sync/planningSyncPayload.js +5 -0
- package/dist/sync/planningSyncPayload.test.js +24 -0
- package/dist/sync/syncApplyHandlers.d.ts +4 -4
- package/dist/sync/syncApplyHandlers.js +89 -31
- package/dist/sync/syncApplyHandlers.test.js +184 -4
- package/dist/sync/syncResourceAdapters.d.ts +2 -0
- package/dist/sync/syncResourceAdapters.js +2 -0
- package/dist/sync/syncService.d.ts +1 -0
- package/dist/sync/syncService.js +13 -1
- package/dist/sync/syncService.test.js +39 -0
- package/dist/sync/workspacePullFeed.d.ts +4 -0
- package/dist/sync/workspacePullFeed.js +129 -70
- package/dist/sync/workspacePullFeed.test.js +49 -0
- package/dist/sync/workspaceSyncModel.d.ts +28 -6
- package/dist/sync/workspaceSyncModel.js +159 -45
- package/dist/sync/workspaceSyncModel.test.js +269 -3
- package/dist/types.d.ts +33 -2
- package/dist/ui/assets/AgentsModule-2y82_3DO.js +1 -0
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-BTLZZCiQ.js +3 -0
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-BVKew-mH.css +1 -0
- package/dist/ui/assets/ContextAttachmentManager-D2epuNnG.js +3 -0
- package/dist/ui/assets/DocumentWorkspace-D4rJKXzd.css +1 -0
- package/dist/ui/assets/DocumentWorkspace-sVa-3Do6.js +252 -0
- package/dist/ui/assets/EntityActivityTimeline-DyqvvhuF.css +1 -0
- package/dist/ui/assets/EntityActivityTimeline-Zods27y_.js +1 -0
- package/dist/ui/assets/{InitiativesModule-9E6ParrY.js → InitiativesModule-D4XseTwW.js} +1 -1
- package/dist/ui/assets/PlansPage-D5AcbO0L.js +1 -0
- package/dist/ui/assets/PlansPage-Dvqsz5zc.css +1 -0
- package/dist/ui/assets/TaskContextUpload-uC5qx4Bv.js +1 -0
- package/dist/ui/assets/TaskSettings-CqvPAsTv.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-BA7jcEpH.js → WorkflowsModule-oOA9bcdK.js} +4 -4
- package/dist/ui/assets/documentReferences-DPZuTgHh.js +1 -0
- package/dist/ui/assets/index-607WPo_X.js +4 -0
- package/dist/ui/assets/index-CYyodXsg.css +1 -0
- package/dist/ui/assets/{vendor-icons-DuEd_65c.js → vendor-icons-B32hGuF2.js} +1 -1
- package/dist/ui/index.html +3 -3
- package/dist/ui/product/agent manager.png +0 -0
- package/dist/ui/product/agent_manager_02.png +0 -0
- package/dist/utils/aiProfileEvents.d.ts +7 -0
- package/dist/utils/aiProfileEvents.js +6 -0
- package/dist/utils/assignees.d.ts +6 -0
- package/dist/utils/assignees.js +8 -1
- package/dist/utils/assignees.test.d.ts +1 -0
- package/dist/utils/assignees.test.js +25 -0
- package/dist/utils/avatarDisplay.d.ts +1 -0
- package/dist/utils/avatarDisplay.js +15 -0
- package/dist/utils/contextFiles.d.ts +3 -1
- package/dist/utils/contextFiles.js +2 -1
- package/dist/utils/taskActivity.d.ts +7 -1
- package/dist/utils/taskActivity.js +63 -1
- package/dist/utils/taskActivity.test.js +23 -1
- package/dist/utils/taskSearch.d.ts +22 -0
- package/dist/utils/taskSearch.js +87 -0
- package/dist/utils/taskSearch.test.d.ts +1 -0
- package/dist/utils/taskSearch.test.js +69 -0
- package/dist/utils/taskUtils.js +10 -3
- package/dist/utils/taskUtils.test.d.ts +1 -0
- package/dist/utils/taskUtils.test.js +19 -0
- package/package.json +1 -1
- package/dist/ui/assets/AgentsModule-DrLawwNl.js +0 -1
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-BaS2VwIr.css +0 -1
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-Z9_whf7F.js +0 -3
- package/dist/ui/assets/DocumentWorkspace-B03MaSkw.js +0 -252
- package/dist/ui/assets/DocumentWorkspace-Dhdso07p.css +0 -1
- package/dist/ui/assets/PlansPage-CvfnMiWq.css +0 -1
- package/dist/ui/assets/PlansPage-DRXscYxH.js +0 -1
- package/dist/ui/assets/TaskSettings-DZX4jk7e.js +0 -9
- package/dist/ui/assets/index-9eT8e0Lu.css +0 -1
- package/dist/ui/assets/index-C6k75jr8.js +0 -6
|
@@ -87,6 +87,18 @@ export function registerTaskPlanningTools(registerTool, executeTool) {
|
|
|
87
87
|
description: stringProperty('Optional initiative description.'),
|
|
88
88
|
ownerId: stringProperty(`${context.assigneeHint} Call list_assignees to inspect the current eligible IDs.`),
|
|
89
89
|
order: numberProperty('Optional sort order among initiatives.'),
|
|
90
|
+
attachments: arrayProperty('Optional attachment descriptors to store on the initiative. Prefer save_initiative_attachment for generated content or files.', {
|
|
91
|
+
type: 'object',
|
|
92
|
+
properties: {
|
|
93
|
+
path: { type: 'string' },
|
|
94
|
+
fsPath: { type: 'string' },
|
|
95
|
+
caption: { type: 'string' },
|
|
96
|
+
displayName: { type: 'string' },
|
|
97
|
+
originalFilename: { type: 'string' },
|
|
98
|
+
assetId: { type: 'string' },
|
|
99
|
+
},
|
|
100
|
+
required: ['path'],
|
|
101
|
+
}),
|
|
90
102
|
},
|
|
91
103
|
required: ['title'],
|
|
92
104
|
},
|
|
@@ -100,6 +112,18 @@ export function registerTaskPlanningTools(registerTool, executeTool) {
|
|
|
100
112
|
description: stringProperty('Updated initiative description.'),
|
|
101
113
|
ownerId: stringProperty(`${context.assigneeHint} Call list_assignees to inspect the current eligible IDs.`),
|
|
102
114
|
order: numberProperty('Optional sort order among initiatives.'),
|
|
115
|
+
attachments: arrayProperty('Replacement attachment descriptors for the initiative. Prefer save_initiative_attachment and delete_initiative_attachment for generated content or files.', {
|
|
116
|
+
type: 'object',
|
|
117
|
+
properties: {
|
|
118
|
+
path: { type: 'string' },
|
|
119
|
+
fsPath: { type: 'string' },
|
|
120
|
+
caption: { type: 'string' },
|
|
121
|
+
displayName: { type: 'string' },
|
|
122
|
+
originalFilename: { type: 'string' },
|
|
123
|
+
assetId: { type: 'string' },
|
|
124
|
+
},
|
|
125
|
+
required: ['path'],
|
|
126
|
+
}),
|
|
103
127
|
},
|
|
104
128
|
required: ['id'],
|
|
105
129
|
},
|
|
@@ -144,6 +168,18 @@ export function registerTaskPlanningTools(registerTool, executeTool) {
|
|
|
144
168
|
ownerId: stringProperty(`${context.assigneeHint} Call list_assignees to inspect the current eligible IDs.`),
|
|
145
169
|
initiativeId: stringProperty('Optional initiative ID or initiative reference such as IN-123.'),
|
|
146
170
|
order: numberProperty('Optional sort order among workstreams.'),
|
|
171
|
+
attachments: arrayProperty('Optional attachment descriptors to store on the workstream. Prefer save_workstream_attachment for generated content or files.', {
|
|
172
|
+
type: 'object',
|
|
173
|
+
properties: {
|
|
174
|
+
path: { type: 'string' },
|
|
175
|
+
fsPath: { type: 'string' },
|
|
176
|
+
caption: { type: 'string' },
|
|
177
|
+
displayName: { type: 'string' },
|
|
178
|
+
originalFilename: { type: 'string' },
|
|
179
|
+
assetId: { type: 'string' },
|
|
180
|
+
},
|
|
181
|
+
required: ['path'],
|
|
182
|
+
}),
|
|
147
183
|
},
|
|
148
184
|
required: ['title'],
|
|
149
185
|
},
|
|
@@ -158,6 +194,18 @@ export function registerTaskPlanningTools(registerTool, executeTool) {
|
|
|
158
194
|
ownerId: stringProperty(`${context.assigneeHint} Call list_assignees to inspect the current eligible IDs.`),
|
|
159
195
|
initiativeId: stringProperty('Optional initiative ID or initiative reference such as IN-123. Pass an empty string to unlink.'),
|
|
160
196
|
order: numberProperty('Optional sort order among workstreams.'),
|
|
197
|
+
attachments: arrayProperty('Replacement attachment descriptors for the workstream. Prefer save_workstream_attachment and delete_workstream_attachment for generated content or files.', {
|
|
198
|
+
type: 'object',
|
|
199
|
+
properties: {
|
|
200
|
+
path: { type: 'string' },
|
|
201
|
+
fsPath: { type: 'string' },
|
|
202
|
+
caption: { type: 'string' },
|
|
203
|
+
displayName: { type: 'string' },
|
|
204
|
+
originalFilename: { type: 'string' },
|
|
205
|
+
assetId: { type: 'string' },
|
|
206
|
+
},
|
|
207
|
+
required: ['path'],
|
|
208
|
+
}),
|
|
161
209
|
},
|
|
162
210
|
required: ['id'],
|
|
163
211
|
},
|
|
@@ -185,6 +185,62 @@ export declare const MCP_TOOL_REGISTRY: Readonly<{
|
|
|
185
185
|
};
|
|
186
186
|
readonly mutating: false;
|
|
187
187
|
};
|
|
188
|
+
readonly get_workstream_attachments: {
|
|
189
|
+
readonly permission: {
|
|
190
|
+
readonly kind: "scope";
|
|
191
|
+
readonly scope: "tasks:read";
|
|
192
|
+
};
|
|
193
|
+
readonly mutating: false;
|
|
194
|
+
};
|
|
195
|
+
readonly read_workstream_attachment: {
|
|
196
|
+
readonly permission: {
|
|
197
|
+
readonly kind: "scope";
|
|
198
|
+
readonly scope: "tasks:read";
|
|
199
|
+
};
|
|
200
|
+
readonly mutating: false;
|
|
201
|
+
};
|
|
202
|
+
readonly save_workstream_attachment: {
|
|
203
|
+
readonly permission: {
|
|
204
|
+
readonly kind: "scope";
|
|
205
|
+
readonly scope: "tasks:write";
|
|
206
|
+
};
|
|
207
|
+
readonly mutating: true;
|
|
208
|
+
};
|
|
209
|
+
readonly delete_workstream_attachment: {
|
|
210
|
+
readonly permission: {
|
|
211
|
+
readonly kind: "scope";
|
|
212
|
+
readonly scope: "tasks:write";
|
|
213
|
+
};
|
|
214
|
+
readonly mutating: true;
|
|
215
|
+
};
|
|
216
|
+
readonly get_initiative_attachments: {
|
|
217
|
+
readonly permission: {
|
|
218
|
+
readonly kind: "scope";
|
|
219
|
+
readonly scope: "tasks:read";
|
|
220
|
+
};
|
|
221
|
+
readonly mutating: false;
|
|
222
|
+
};
|
|
223
|
+
readonly read_initiative_attachment: {
|
|
224
|
+
readonly permission: {
|
|
225
|
+
readonly kind: "scope";
|
|
226
|
+
readonly scope: "tasks:read";
|
|
227
|
+
};
|
|
228
|
+
readonly mutating: false;
|
|
229
|
+
};
|
|
230
|
+
readonly save_initiative_attachment: {
|
|
231
|
+
readonly permission: {
|
|
232
|
+
readonly kind: "scope";
|
|
233
|
+
readonly scope: "tasks:write";
|
|
234
|
+
};
|
|
235
|
+
readonly mutating: true;
|
|
236
|
+
};
|
|
237
|
+
readonly delete_initiative_attachment: {
|
|
238
|
+
readonly permission: {
|
|
239
|
+
readonly kind: "scope";
|
|
240
|
+
readonly scope: "tasks:write";
|
|
241
|
+
};
|
|
242
|
+
readonly mutating: true;
|
|
243
|
+
};
|
|
188
244
|
readonly search_images: {
|
|
189
245
|
readonly permission: {
|
|
190
246
|
readonly kind: "scope";
|
package/dist/mcp/toolRegistry.js
CHANGED
|
@@ -26,6 +26,14 @@ export const MCP_TOOL_REGISTRY = Object.freeze({
|
|
|
26
26
|
replace_task_checklist: { permission: { kind: 'scope', scope: 'tasks:write' }, mutating: true },
|
|
27
27
|
get_task_attachments: { permission: { kind: 'scope', scope: 'tasks:read' }, mutating: false },
|
|
28
28
|
read_task_attachment: { permission: { kind: 'scope', scope: 'tasks:read' }, mutating: false },
|
|
29
|
+
get_workstream_attachments: { permission: { kind: 'scope', scope: 'tasks:read' }, mutating: false },
|
|
30
|
+
read_workstream_attachment: { permission: { kind: 'scope', scope: 'tasks:read' }, mutating: false },
|
|
31
|
+
save_workstream_attachment: { permission: { kind: 'scope', scope: 'tasks:write' }, mutating: true },
|
|
32
|
+
delete_workstream_attachment: { permission: { kind: 'scope', scope: 'tasks:write' }, mutating: true },
|
|
33
|
+
get_initiative_attachments: { permission: { kind: 'scope', scope: 'tasks:read' }, mutating: false },
|
|
34
|
+
read_initiative_attachment: { permission: { kind: 'scope', scope: 'tasks:read' }, mutating: false },
|
|
35
|
+
save_initiative_attachment: { permission: { kind: 'scope', scope: 'tasks:write' }, mutating: true },
|
|
36
|
+
delete_initiative_attachment: { permission: { kind: 'scope', scope: 'tasks:write' }, mutating: true },
|
|
29
37
|
search_images: { permission: { kind: 'scope', scope: 'tasks:read' }, mutating: false },
|
|
30
38
|
get_image: { permission: { kind: 'scope', scope: 'tasks:read' }, mutating: false },
|
|
31
39
|
search_documents: { permission: { kind: 'scope', scope: 'tasks:read' }, mutating: false },
|
|
@@ -11,6 +11,8 @@ describe('MCP tool registry', () => {
|
|
|
11
11
|
expect(MCP_CLOUD_TOOL_SCOPE_REQUIREMENTS.search_documents).toBe('tasks:read');
|
|
12
12
|
expect(MCP_CLOUD_TOOL_SCOPE_REQUIREMENTS.read_task_attachment).toBe('tasks:read');
|
|
13
13
|
expect(MCP_CLOUD_TOOL_SCOPE_REQUIREMENTS.get_task_attachments).toBe('tasks:read');
|
|
14
|
+
expect(MCP_CLOUD_TOOL_SCOPE_REQUIREMENTS.read_workstream_attachment).toBe('tasks:read');
|
|
15
|
+
expect(MCP_CLOUD_TOOL_SCOPE_REQUIREMENTS.get_initiative_attachments).toBe('tasks:read');
|
|
14
16
|
expect(MCP_CLOUD_TOOL_SCOPE_REQUIREMENTS.export_resources).toBeUndefined();
|
|
15
17
|
});
|
|
16
18
|
it('derives mutating tool names from the shared registry', () => {
|
|
@@ -20,6 +22,8 @@ describe('MCP tool registry', () => {
|
|
|
20
22
|
expect(MCP_WRITE_TOOL_NAMES).toEqual(expected);
|
|
21
23
|
expect(MCP_WRITE_TOOL_NAMES.has('read_task_attachment')).toBe(false);
|
|
22
24
|
expect(MCP_WRITE_TOOL_NAMES.has('save_task_attachment')).toBe(true);
|
|
25
|
+
expect(MCP_WRITE_TOOL_NAMES.has('save_workstream_attachment')).toBe(true);
|
|
26
|
+
expect(MCP_WRITE_TOOL_NAMES.has('delete_initiative_attachment')).toBe(true);
|
|
23
27
|
expect(MCP_WRITE_TOOL_NAMES.has('finish_task')).toBe(true);
|
|
24
28
|
expect(MCP_WRITE_TOOL_NAMES.has('review_document')).toBe(true);
|
|
25
29
|
});
|
|
@@ -198,6 +198,25 @@ export function ensureWorkspaceSchema(db) {
|
|
|
198
198
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
|
199
199
|
);
|
|
200
200
|
|
|
201
|
+
CREATE TABLE IF NOT EXISTS task_attachment_upload_drafts (
|
|
202
|
+
draft_id TEXT PRIMARY KEY,
|
|
203
|
+
tenant_id TEXT NOT NULL DEFAULT 'default',
|
|
204
|
+
workspace_id TEXT NOT NULL,
|
|
205
|
+
task_id TEXT NOT NULL,
|
|
206
|
+
actor_ref TEXT NOT NULL,
|
|
207
|
+
storage_key TEXT NOT NULL,
|
|
208
|
+
mime_type TEXT NOT NULL,
|
|
209
|
+
original_filename TEXT NOT NULL,
|
|
210
|
+
size_bytes INTEGER NOT NULL DEFAULT 0,
|
|
211
|
+
storage_provider TEXT NOT NULL DEFAULT 'local',
|
|
212
|
+
consumed_at TEXT,
|
|
213
|
+
discarded_at TEXT,
|
|
214
|
+
created_at TEXT NOT NULL,
|
|
215
|
+
updated_at TEXT NOT NULL,
|
|
216
|
+
FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE,
|
|
217
|
+
FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE
|
|
218
|
+
);
|
|
219
|
+
|
|
201
220
|
CREATE TABLE IF NOT EXISTS auth_tokens (
|
|
202
221
|
id TEXT PRIMARY KEY,
|
|
203
222
|
tenant_id TEXT NOT NULL DEFAULT 'default',
|
|
@@ -388,6 +407,8 @@ export function ensureWorkspaceSchema(db) {
|
|
|
388
407
|
CREATE INDEX IF NOT EXISTS idx_auth_tokens_user_purpose ON auth_tokens(user_id, purpose);
|
|
389
408
|
CREATE INDEX IF NOT EXISTS idx_auth_tokens_workspace_purpose ON auth_tokens(workspace_id, purpose);
|
|
390
409
|
CREATE INDEX IF NOT EXISTS idx_auth_tokens_expires ON auth_tokens(expires_at);
|
|
410
|
+
CREATE INDEX IF NOT EXISTS idx_task_attachment_upload_drafts_workspace_task ON task_attachment_upload_drafts(workspace_id, task_id);
|
|
411
|
+
CREATE INDEX IF NOT EXISTS idx_task_attachment_upload_drafts_actor_created ON task_attachment_upload_drafts(actor_ref, created_at);
|
|
391
412
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_mcp_access_tokens_prefix ON mcp_access_tokens(token_prefix);
|
|
392
413
|
CREATE INDEX IF NOT EXISTS idx_mcp_access_tokens_workspace_user ON mcp_access_tokens(workspace_id, user_id);
|
|
393
414
|
CREATE INDEX IF NOT EXISTS idx_mcp_access_tokens_status ON mcp_access_tokens(status);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { TaskforceCore } from '../core/Taskforce.js';
|
|
2
|
+
import type { ObjectStorageClient } from '../storage/objectStorageClient.js';
|
|
3
|
+
import type { WorkspaceAssetStore } from '../storage/workspaceAssetStore.js';
|
|
4
|
+
type AiAvatarSlot = 'display' | 'source';
|
|
5
|
+
export interface AiProfileAvatarPayload {
|
|
6
|
+
buffer: Buffer;
|
|
7
|
+
mimeType: string;
|
|
8
|
+
originalName?: string | null;
|
|
9
|
+
}
|
|
10
|
+
export interface AiProfileAvatarAssetDeps {
|
|
11
|
+
core: TaskforceCore;
|
|
12
|
+
workspaceAssetStore: WorkspaceAssetStore | null;
|
|
13
|
+
getObjectStorageClient: () => ObjectStorageClient | null;
|
|
14
|
+
ensureWithinDir: (candidatePath: string, rootDir: string) => string | null;
|
|
15
|
+
markAssetDeleted: (pathValue: string) => unknown;
|
|
16
|
+
appendStorageTelemetry: (event: string, payload?: {
|
|
17
|
+
path?: string;
|
|
18
|
+
status?: string;
|
|
19
|
+
provider?: string;
|
|
20
|
+
}) => void;
|
|
21
|
+
}
|
|
22
|
+
export interface SaveAiProfileAvatarInput {
|
|
23
|
+
workspaceId: string;
|
|
24
|
+
profileId: string;
|
|
25
|
+
display: AiProfileAvatarPayload;
|
|
26
|
+
source?: AiProfileAvatarPayload | null;
|
|
27
|
+
actorId?: string | null;
|
|
28
|
+
}
|
|
29
|
+
export declare function buildAiProfileAvatarStorageKey(input: {
|
|
30
|
+
workspaceId: string;
|
|
31
|
+
profileId: string;
|
|
32
|
+
slot: AiAvatarSlot;
|
|
33
|
+
mimeType: string;
|
|
34
|
+
}): string;
|
|
35
|
+
export declare function storageKeyFromContextUrl(url: unknown): string | null;
|
|
36
|
+
export declare function saveAiProfileAvatarAssets(deps: AiProfileAvatarAssetDeps, input: SaveAiProfileAvatarInput): Promise<{
|
|
37
|
+
profile: import("../core/Taskforce.js").AiProfileRecord;
|
|
38
|
+
displayAsset: import("../storage/workspaceAssetStore.js").WorkspaceAssetEntry;
|
|
39
|
+
sourceAsset: import("../storage/workspaceAssetStore.js").WorkspaceAssetEntry;
|
|
40
|
+
displayStorageKey: string;
|
|
41
|
+
sourceStorageKey: string;
|
|
42
|
+
}>;
|
|
43
|
+
export declare function clearAiProfileAvatarAssets(deps: AiProfileAvatarAssetDeps, input: {
|
|
44
|
+
workspaceId: string;
|
|
45
|
+
profileId: string;
|
|
46
|
+
}): Promise<{
|
|
47
|
+
profile: import("../core/Taskforce.js").AiProfileRecord;
|
|
48
|
+
}>;
|
|
49
|
+
export {};
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { normalizeTaskDataRelativePath } from '../storage/objectStorageClient.js';
|
|
5
|
+
const SUPPORTED_IMAGE_MIME_TYPES = new Set([
|
|
6
|
+
'image/gif',
|
|
7
|
+
'image/jpeg',
|
|
8
|
+
'image/png',
|
|
9
|
+
'image/webp',
|
|
10
|
+
]);
|
|
11
|
+
const AVATAR_MAX_BYTES = 5 * 1024 * 1024;
|
|
12
|
+
function normalizeId(value) {
|
|
13
|
+
return String(value || '').trim();
|
|
14
|
+
}
|
|
15
|
+
function sanitizeStorageSegment(value, fallback) {
|
|
16
|
+
const normalized = normalizeId(value)
|
|
17
|
+
.replace(/[^a-zA-Z0-9._-]/g, '-')
|
|
18
|
+
.replace(/-+/g, '-')
|
|
19
|
+
.replace(/^-+|-+$/g, '');
|
|
20
|
+
return normalized || fallback;
|
|
21
|
+
}
|
|
22
|
+
function normalizeImageMimeType(value) {
|
|
23
|
+
return normalizeId(value).toLowerCase();
|
|
24
|
+
}
|
|
25
|
+
function extensionForMimeType(mimeType) {
|
|
26
|
+
if (mimeType === 'image/gif')
|
|
27
|
+
return 'gif';
|
|
28
|
+
if (mimeType === 'image/jpeg')
|
|
29
|
+
return 'jpg';
|
|
30
|
+
if (mimeType === 'image/png')
|
|
31
|
+
return 'png';
|
|
32
|
+
return 'webp';
|
|
33
|
+
}
|
|
34
|
+
function stableAssetId(workspaceId, profileId, slot) {
|
|
35
|
+
const digest = createHash('sha256')
|
|
36
|
+
.update(`${workspaceId}:${profileId}:${slot}`)
|
|
37
|
+
.digest('hex')
|
|
38
|
+
.slice(0, 32);
|
|
39
|
+
return `asset-${digest}`;
|
|
40
|
+
}
|
|
41
|
+
export function buildAiProfileAvatarStorageKey(input) {
|
|
42
|
+
const workspaceId = sanitizeStorageSegment(input.workspaceId, 'default');
|
|
43
|
+
const profileId = sanitizeStorageSegment(input.profileId, 'ai-profile');
|
|
44
|
+
const ext = extensionForMimeType(input.mimeType);
|
|
45
|
+
return `workspaces/${workspaceId}/assets/images/ai-profiles/${profileId}/avatar/${input.slot}.${ext}`;
|
|
46
|
+
}
|
|
47
|
+
function contextUrlForStorageKey(storageKey) {
|
|
48
|
+
return `/api/taskforce/context/${encodeURIComponent(storageKey)}`;
|
|
49
|
+
}
|
|
50
|
+
export function storageKeyFromContextUrl(url) {
|
|
51
|
+
const raw = normalizeId(url);
|
|
52
|
+
if (!raw)
|
|
53
|
+
return null;
|
|
54
|
+
const contextPrefix = '/api/taskforce/context/';
|
|
55
|
+
if (!raw.startsWith(contextPrefix))
|
|
56
|
+
return null;
|
|
57
|
+
const encodedPath = raw.slice(contextPrefix.length).split('?')[0] || '';
|
|
58
|
+
try {
|
|
59
|
+
return normalizeTaskDataRelativePath(decodeURIComponent(encodedPath));
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function validatePayload(payload, label) {
|
|
66
|
+
const mimeType = normalizeImageMimeType(payload.mimeType);
|
|
67
|
+
if (!SUPPORTED_IMAGE_MIME_TYPES.has(mimeType)) {
|
|
68
|
+
throw new Error(`${label} must be a PNG, JPEG, WebP, or GIF image.`);
|
|
69
|
+
}
|
|
70
|
+
if (!Buffer.isBuffer(payload.buffer) || payload.buffer.length === 0) {
|
|
71
|
+
throw new Error(`${label} is empty.`);
|
|
72
|
+
}
|
|
73
|
+
if (payload.buffer.length > AVATAR_MAX_BYTES) {
|
|
74
|
+
throw new Error(`${label} must be 5 MB or smaller.`);
|
|
75
|
+
}
|
|
76
|
+
return mimeType;
|
|
77
|
+
}
|
|
78
|
+
function storageKeyBelongsToAiProfileAvatar(workspaceId, profileId, storageKey) {
|
|
79
|
+
const workspaceIdSafe = sanitizeStorageSegment(workspaceId, 'default');
|
|
80
|
+
const profileIdSafe = sanitizeStorageSegment(profileId, 'ai-profile');
|
|
81
|
+
return storageKey.startsWith(`workspaces/${workspaceIdSafe}/assets/images/ai-profiles/${profileIdSafe}/avatar/`);
|
|
82
|
+
}
|
|
83
|
+
async function writeAvatarObject(deps, storageKey, payload, provider) {
|
|
84
|
+
if (provider === 'r2') {
|
|
85
|
+
const objectStorageClient = deps.getObjectStorageClient();
|
|
86
|
+
if (!objectStorageClient)
|
|
87
|
+
throw new Error('Object storage is not configured.');
|
|
88
|
+
await objectStorageClient.putObject(storageKey, payload.buffer, payload.mimeType);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const { basePath } = deps.core.getPaths();
|
|
92
|
+
const destination = deps.ensureWithinDir(path.join(basePath, storageKey), basePath);
|
|
93
|
+
if (!destination)
|
|
94
|
+
throw new Error('Invalid AI profile avatar storage path.');
|
|
95
|
+
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
96
|
+
fs.writeFileSync(destination, payload.buffer);
|
|
97
|
+
}
|
|
98
|
+
async function deleteAvatarObject(deps, storageKey, provider) {
|
|
99
|
+
try {
|
|
100
|
+
deps.markAssetDeleted(storageKey);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// Best-effort legacy asset index cleanup should not make the canonical
|
|
104
|
+
// workspace asset/profile update fail after the new slots are written.
|
|
105
|
+
}
|
|
106
|
+
if (provider === 'r2') {
|
|
107
|
+
const objectStorageClient = deps.getObjectStorageClient();
|
|
108
|
+
if (objectStorageClient) {
|
|
109
|
+
await objectStorageClient.deleteObject(storageKey).catch(() => false);
|
|
110
|
+
}
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const { basePath } = deps.core.getPaths();
|
|
114
|
+
const destination = deps.ensureWithinDir(path.join(basePath, storageKey), basePath);
|
|
115
|
+
if (destination && fs.existsSync(destination)) {
|
|
116
|
+
fs.rmSync(destination, { force: true });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function upsertAvatarAsset(input) {
|
|
120
|
+
if (!input.deps.workspaceAssetStore) {
|
|
121
|
+
throw new Error('Workspace asset storage is required for AI profile avatars.');
|
|
122
|
+
}
|
|
123
|
+
const contentSha256 = createHash('sha256').update(input.payload.buffer).digest('hex');
|
|
124
|
+
const existing = input.deps.workspaceAssetStore.getByStorageKey(input.storageKey, input.workspaceId);
|
|
125
|
+
return input.deps.workspaceAssetStore.upsertAsset({
|
|
126
|
+
assetId: existing?.assetId || stableAssetId(input.workspaceId, input.profileId, input.slot),
|
|
127
|
+
workspaceId: input.workspaceId,
|
|
128
|
+
kind: 'image',
|
|
129
|
+
logicalName: `AI profile avatar ${input.slot}`,
|
|
130
|
+
originalFilename: normalizeId(input.payload.originalName) || `${input.slot}.${extensionForMimeType(input.payload.mimeType)}`,
|
|
131
|
+
mimeType: input.payload.mimeType,
|
|
132
|
+
storageProvider: input.provider,
|
|
133
|
+
storageKey: input.storageKey,
|
|
134
|
+
contentSha256,
|
|
135
|
+
sizeBytes: input.payload.buffer.length,
|
|
136
|
+
documentId: null,
|
|
137
|
+
version: null,
|
|
138
|
+
isLatest: true,
|
|
139
|
+
scanStatus: 'clean',
|
|
140
|
+
scanEngine: null,
|
|
141
|
+
scanDetails: null,
|
|
142
|
+
createdByUserId: existing?.createdByUserId || normalizeId(input.actorId) || null,
|
|
143
|
+
createdAt: existing?.createdAt || input.nowIso,
|
|
144
|
+
updatedAt: input.nowIso,
|
|
145
|
+
deletedAt: null,
|
|
146
|
+
purgeAfter: null,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
async function cleanupAiProfileAvatarAssets(input) {
|
|
150
|
+
const keep = new Set(Array.from(input.keepStorageKeys || []).filter(Boolean));
|
|
151
|
+
const candidates = new Map();
|
|
152
|
+
for (const storageKey of input.explicitStorageKeys || []) {
|
|
153
|
+
const normalized = normalizeTaskDataRelativePath(normalizeId(storageKey));
|
|
154
|
+
if (!normalized || keep.has(normalized))
|
|
155
|
+
continue;
|
|
156
|
+
if (storageKeyBelongsToAiProfileAvatar(input.workspaceId, input.profileId, normalized)) {
|
|
157
|
+
candidates.set(normalized, input.providerFallback);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (input.deps.workspaceAssetStore) {
|
|
161
|
+
const assets = input.deps.workspaceAssetStore.listAllByWorkspace(input.workspaceId, {
|
|
162
|
+
includeDeleted: false,
|
|
163
|
+
kind: 'image',
|
|
164
|
+
});
|
|
165
|
+
for (const asset of assets) {
|
|
166
|
+
if (!storageKeyBelongsToAiProfileAvatar(input.workspaceId, input.profileId, asset.storageKey))
|
|
167
|
+
continue;
|
|
168
|
+
if (keep.has(asset.storageKey))
|
|
169
|
+
continue;
|
|
170
|
+
candidates.set(asset.storageKey, asset.storageProvider === 'r2' ? 'r2' : 'local');
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
for (const [storageKey, provider] of candidates) {
|
|
174
|
+
try {
|
|
175
|
+
const asset = input.deps.workspaceAssetStore?.getByStorageKey(storageKey, input.workspaceId);
|
|
176
|
+
if (asset && !asset.deletedAt) {
|
|
177
|
+
input.deps.workspaceAssetStore?.markDeleted(asset.assetId, input.workspaceId);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
input.deps.appendStorageTelemetry('ai_avatar.cleanup', {
|
|
182
|
+
path: storageKey,
|
|
183
|
+
provider,
|
|
184
|
+
status: 'metadata-delete-failed',
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
await deleteAvatarObject(input.deps, storageKey, provider);
|
|
189
|
+
input.deps.appendStorageTelemetry('ai_avatar.cleanup', {
|
|
190
|
+
path: storageKey,
|
|
191
|
+
provider,
|
|
192
|
+
status: 'deleted',
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
input.deps.appendStorageTelemetry('ai_avatar.cleanup', {
|
|
197
|
+
path: storageKey,
|
|
198
|
+
provider,
|
|
199
|
+
status: 'object-delete-failed',
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
export async function saveAiProfileAvatarAssets(deps, input) {
|
|
205
|
+
const workspaceId = normalizeId(input.workspaceId);
|
|
206
|
+
const profileId = normalizeId(input.profileId);
|
|
207
|
+
if (!workspaceId)
|
|
208
|
+
throw new Error('workspaceId is required.');
|
|
209
|
+
if (!profileId)
|
|
210
|
+
throw new Error('profileId is required.');
|
|
211
|
+
const existingProfile = deps.core.listAiProfiles(workspaceId)
|
|
212
|
+
.find((profile) => profile.id === profileId);
|
|
213
|
+
if (!existingProfile)
|
|
214
|
+
throw new Error(`Profile ${profileId} not found.`);
|
|
215
|
+
const displayMimeType = validatePayload(input.display, 'Display avatar');
|
|
216
|
+
const sourcePayload = input.source || input.display;
|
|
217
|
+
const sourceMimeType = validatePayload(sourcePayload, 'Source avatar');
|
|
218
|
+
const displayPayload = { ...input.display, mimeType: displayMimeType };
|
|
219
|
+
const normalizedSourcePayload = { ...sourcePayload, mimeType: sourceMimeType };
|
|
220
|
+
const displayStorageKey = buildAiProfileAvatarStorageKey({
|
|
221
|
+
workspaceId,
|
|
222
|
+
profileId,
|
|
223
|
+
slot: 'display',
|
|
224
|
+
mimeType: displayMimeType,
|
|
225
|
+
});
|
|
226
|
+
const sourceStorageKey = buildAiProfileAvatarStorageKey({
|
|
227
|
+
workspaceId,
|
|
228
|
+
profileId,
|
|
229
|
+
slot: 'source',
|
|
230
|
+
mimeType: sourceMimeType,
|
|
231
|
+
});
|
|
232
|
+
const objectStorageClient = deps.getObjectStorageClient();
|
|
233
|
+
const provider = objectStorageClient ? 'r2' : 'local';
|
|
234
|
+
const nowIso = new Date().toISOString();
|
|
235
|
+
await writeAvatarObject(deps, displayStorageKey, displayPayload, provider);
|
|
236
|
+
await writeAvatarObject(deps, sourceStorageKey, normalizedSourcePayload, provider);
|
|
237
|
+
const displayAsset = upsertAvatarAsset({
|
|
238
|
+
deps,
|
|
239
|
+
workspaceId,
|
|
240
|
+
profileId,
|
|
241
|
+
slot: 'display',
|
|
242
|
+
storageKey: displayStorageKey,
|
|
243
|
+
payload: displayPayload,
|
|
244
|
+
provider,
|
|
245
|
+
actorId: input.actorId,
|
|
246
|
+
nowIso,
|
|
247
|
+
});
|
|
248
|
+
const sourceAsset = upsertAvatarAsset({
|
|
249
|
+
deps,
|
|
250
|
+
workspaceId,
|
|
251
|
+
profileId,
|
|
252
|
+
slot: 'source',
|
|
253
|
+
storageKey: sourceStorageKey,
|
|
254
|
+
payload: normalizedSourcePayload,
|
|
255
|
+
provider,
|
|
256
|
+
actorId: input.actorId,
|
|
257
|
+
nowIso,
|
|
258
|
+
});
|
|
259
|
+
deps.core.notifyDocumentMetadataMutation(workspaceId, [displayStorageKey, sourceStorageKey], 'upsert');
|
|
260
|
+
deps.appendStorageTelemetry('ai_avatar.upload', {
|
|
261
|
+
path: displayStorageKey,
|
|
262
|
+
provider,
|
|
263
|
+
status: 'stored',
|
|
264
|
+
});
|
|
265
|
+
const profile = deps.core.updateAiProfileAvatar({
|
|
266
|
+
workspaceId,
|
|
267
|
+
profileId,
|
|
268
|
+
avatarUrl: contextUrlForStorageKey(displayStorageKey),
|
|
269
|
+
avatarSourceUrl: contextUrlForStorageKey(sourceStorageKey),
|
|
270
|
+
});
|
|
271
|
+
await cleanupAiProfileAvatarAssets({
|
|
272
|
+
deps,
|
|
273
|
+
workspaceId,
|
|
274
|
+
profileId,
|
|
275
|
+
keepStorageKeys: [displayStorageKey, sourceStorageKey],
|
|
276
|
+
explicitStorageKeys: [
|
|
277
|
+
storageKeyFromContextUrl(existingProfile.avatarUrl),
|
|
278
|
+
storageKeyFromContextUrl(existingProfile.avatarSourceUrl),
|
|
279
|
+
],
|
|
280
|
+
providerFallback: provider,
|
|
281
|
+
});
|
|
282
|
+
return {
|
|
283
|
+
profile,
|
|
284
|
+
displayAsset,
|
|
285
|
+
sourceAsset,
|
|
286
|
+
displayStorageKey,
|
|
287
|
+
sourceStorageKey,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
export async function clearAiProfileAvatarAssets(deps, input) {
|
|
291
|
+
const workspaceId = normalizeId(input.workspaceId);
|
|
292
|
+
const profileId = normalizeId(input.profileId);
|
|
293
|
+
if (!workspaceId)
|
|
294
|
+
throw new Error('workspaceId is required.');
|
|
295
|
+
if (!profileId)
|
|
296
|
+
throw new Error('profileId is required.');
|
|
297
|
+
const existingProfile = deps.core.listAiProfiles(workspaceId)
|
|
298
|
+
.find((profile) => profile.id === profileId);
|
|
299
|
+
const objectStorageClient = deps.getObjectStorageClient();
|
|
300
|
+
const provider = objectStorageClient ? 'r2' : 'local';
|
|
301
|
+
const profile = deps.core.updateAiProfileAvatar({
|
|
302
|
+
workspaceId,
|
|
303
|
+
profileId,
|
|
304
|
+
avatarUrl: null,
|
|
305
|
+
avatarSourceUrl: null,
|
|
306
|
+
});
|
|
307
|
+
await cleanupAiProfileAvatarAssets({
|
|
308
|
+
deps,
|
|
309
|
+
workspaceId,
|
|
310
|
+
profileId,
|
|
311
|
+
keepStorageKeys: [],
|
|
312
|
+
explicitStorageKeys: [
|
|
313
|
+
storageKeyFromContextUrl(existingProfile?.avatarUrl),
|
|
314
|
+
storageKeyFromContextUrl(existingProfile?.avatarSourceUrl),
|
|
315
|
+
],
|
|
316
|
+
providerFallback: provider,
|
|
317
|
+
});
|
|
318
|
+
return { profile };
|
|
319
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|