@taskforcehq/taskforce 0.3.306 → 0.3.308

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (232) hide show
  1. package/README.md +0 -2
  2. package/dist/Taskforce.module.css +317 -27
  3. package/dist/TaskforceCore.js +1 -0
  4. package/dist/TaskforceCore.test.js +178 -0
  5. package/dist/components/activity/EntityActivityTimeline.d.ts +34 -0
  6. package/dist/components/activity/EntityActivityTimeline.js +81 -0
  7. package/dist/components/context/ContextAttachmentManager.js +15 -3
  8. package/dist/components/features/AgentsModule.d.ts +5 -1
  9. package/dist/components/features/AgentsModule.js +142 -39
  10. package/dist/components/features/AgentsModule.test.js +260 -11
  11. package/dist/components/features/AnnotatedAttachmentWorkspace.d.ts +3 -1
  12. package/dist/components/features/AnnotatedAttachmentWorkspace.js +118 -4
  13. package/dist/components/features/AnnotatedAttachmentWorkspace.test.js +52 -0
  14. package/dist/components/features/DocumentIndex.d.ts +2 -2
  15. package/dist/components/features/DocumentIndex.js +3 -4
  16. package/dist/components/features/DocumentViewer.d.ts +3 -1
  17. package/dist/components/features/DocumentViewer.js +6 -5
  18. package/dist/components/features/DocumentWorkspace.d.ts +10 -2
  19. package/dist/components/features/DocumentWorkspace.js +15 -4
  20. package/dist/components/features/DocumentWorkspace.test.js +20 -3
  21. package/dist/components/features/TaskSettings.d.ts +2 -0
  22. package/dist/components/features/TaskSettings.js +83 -78
  23. package/dist/components/features/TaskSettings.test.js +171 -82
  24. package/dist/components/task/TaskActionHeader.js +11 -5
  25. package/dist/components/task/TaskActionHeader.test.js +10 -0
  26. package/dist/components/task/TaskCard.js +6 -2
  27. package/dist/components/task/TaskContextUpload.test.js +3 -3
  28. package/dist/components/task/TaskForm.js +14 -64
  29. package/dist/components/task/TaskKanban.js +0 -8
  30. package/dist/components/task/TaskList.js +5 -1
  31. package/dist/components/task/TaskList.test.js +4 -0
  32. package/dist/components/ui/HierarchyActionBadge.d.ts +10 -0
  33. package/dist/components/ui/HierarchyActionBadge.js +5 -0
  34. package/dist/components/views/AppShellHeader.d.ts +3 -1
  35. package/dist/components/views/AppShellHeader.js +27 -11
  36. package/dist/components/views/AppShellHeader.test.js +18 -1
  37. package/dist/components/views/PlanComparisonPage.d.ts +20 -15
  38. package/dist/components/views/PlanComparisonPage.js +221 -82
  39. package/dist/components/views/PlanComparisonPage.test.js +268 -14
  40. package/dist/components/views/PlansPage.js +16 -10
  41. package/dist/components/views/PlansPage.test.js +4 -2
  42. package/dist/components/views/StandaloneLayout.js +256 -69
  43. package/dist/components/views/WidgetView.js +10 -8
  44. package/dist/components/views/panels/PlanningDrawer.d.ts +19 -6
  45. package/dist/components/views/panels/PlanningDrawer.js +259 -51
  46. package/dist/components/views/panels/PlanningDrawer.test.js +392 -4
  47. package/dist/config/envSchema.js +1 -0
  48. package/dist/core/AiProfileService.d.ts +7 -0
  49. package/dist/core/AiProfileService.js +460 -35
  50. package/dist/core/AiProfiles.test.js +648 -2
  51. package/dist/core/AttachmentLinkService.d.ts +3 -0
  52. package/dist/core/AttachmentLinkService.js +58 -13
  53. package/dist/core/GlobalSettingsService.js +30 -0
  54. package/dist/core/GlobalSettingsService.test.d.ts +1 -0
  55. package/dist/core/GlobalSettingsService.test.js +45 -0
  56. package/dist/core/McpTokenService.d.ts +12 -0
  57. package/dist/core/McpTokenService.js +44 -1
  58. package/dist/core/PlanEntitlementService.d.ts +20 -0
  59. package/dist/core/PlanEntitlementService.js +215 -19
  60. package/dist/core/PlanFeatureCatalog.test.js +55 -0
  61. package/dist/core/PlanningEntities.test.js +366 -4
  62. package/dist/core/TaskActivityService.d.ts +28 -1
  63. package/dist/core/TaskActivityService.js +227 -46
  64. package/dist/core/TaskActivitySync.test.d.ts +1 -0
  65. package/dist/core/TaskActivitySync.test.js +90 -0
  66. package/dist/core/TaskAuditTimeline.test.js +96 -0
  67. package/dist/core/Taskforce.d.ts +204 -26
  68. package/dist/core/Taskforce.ids.test.js +58 -0
  69. package/dist/core/Taskforce.js +781 -71
  70. package/dist/core/Taskforce.mcpAuth.test.js +109 -0
  71. package/dist/core/types.d.ts +10 -0
  72. package/dist/hooks/auth/useTaskforceAuthBootstrap.js +2 -1
  73. package/dist/hooks/bootstrapTimeouts.d.ts +4 -0
  74. package/dist/hooks/bootstrapTimeouts.js +8 -0
  75. package/dist/hooks/bootstrapTimeouts.test.d.ts +1 -0
  76. package/dist/hooks/bootstrapTimeouts.test.js +12 -0
  77. package/dist/hooks/sync/orchestratorShared.d.ts +3 -3
  78. package/dist/hooks/sync/orchestratorShared.js +61 -1
  79. package/dist/hooks/sync/recovery.js +8 -0
  80. package/dist/hooks/sync/recovery.test.js +5 -5
  81. package/dist/hooks/sync/transfers.d.ts +2 -1
  82. package/dist/hooks/sync/transfers.js +36 -9
  83. package/dist/hooks/sync/transfers.test.js +534 -2
  84. package/dist/hooks/useFilterSort.js +13 -13
  85. package/dist/hooks/useFilterSort.test.js +87 -1
  86. package/dist/hooks/useSyncOrchestrator.js +50 -5
  87. package/dist/hooks/useTaskforce.d.ts +85 -50
  88. package/dist/hooks/useTaskforce.js +44 -14
  89. package/dist/hooks/useTaskforce.runtime-routing.test.js +94 -0
  90. package/dist/hooks/useTaskforce.sync-behavior.test.js +99 -0
  91. package/dist/hooks/useWorkspaceSyncController.js +7 -1
  92. package/dist/hooks/useWorkspaceSyncController.test.js +48 -0
  93. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +14 -7
  94. package/dist/mcp/canonicalAssetHelpers.d.ts +16 -4
  95. package/dist/mcp/canonicalAssetHelpers.js +18 -10
  96. package/dist/mcp/canonicalAssetHelpers.test.d.ts +1 -0
  97. package/dist/mcp/canonicalAssetHelpers.test.js +67 -0
  98. package/dist/mcp/clientIntegrations.d.ts +1 -0
  99. package/dist/mcp/clientIntegrations.js +25 -0
  100. package/dist/mcp/clientIntegrations.test.js +14 -3
  101. package/dist/mcp/clientProfileDefaults.js +3 -3
  102. package/dist/mcp/collaborationRegistrar.js +7 -7
  103. package/dist/mcp/documentAssetRegistrar.js +113 -7
  104. package/dist/mcp/runtime.d.ts +1 -0
  105. package/dist/mcp/runtime.js +969 -122
  106. package/dist/mcp/runtime.test.js +1083 -11
  107. package/dist/mcp/taskAttachmentTypes.d.ts +1 -1
  108. package/dist/mcp/taskPlanningRegistrar.js +48 -0
  109. package/dist/mcp/toolRegistry.d.ts +56 -0
  110. package/dist/mcp/toolRegistry.js +8 -0
  111. package/dist/mcp/toolRegistry.test.js +4 -0
  112. package/dist/migrations/taskSchemaMigrations.js +58 -15
  113. package/dist/server/aiProfileAvatarAssets.d.ts +49 -0
  114. package/dist/server/aiProfileAvatarAssets.js +319 -0
  115. package/dist/server/aiProfileAvatarAssets.test.d.ts +1 -0
  116. package/dist/server/aiProfileAvatarAssets.test.js +213 -0
  117. package/dist/server/annotatedAttachmentsRoutes.test.js +66 -7
  118. package/dist/server/index.js +4 -0
  119. package/dist/server/index.test.js +6 -1
  120. package/dist/server/routes/admin.js +328 -9
  121. package/dist/server/routes/annotatedAttachments.js +51 -0
  122. package/dist/server/routes/auth.d.ts +11 -0
  123. package/dist/server/routes/auth.js +189 -27
  124. package/dist/server/routes/authSupport.d.ts +5 -0
  125. package/dist/server/routes/authSupport.js +13 -1
  126. package/dist/server/routes/billing.d.ts +1 -0
  127. package/dist/server/routes/billing.js +116 -63
  128. package/dist/server/routes/billing.test.js +162 -11
  129. package/dist/server/routes/documents.js +10 -0
  130. package/dist/server/routes/shared.js +0 -10
  131. package/dist/server/routes/sync.integration.test.js +221 -0
  132. package/dist/server/routes/sync.js +13 -3
  133. package/dist/server/routes/syncSnapshotRoutes.js +2 -0
  134. package/dist/server/routes/tasks.js +12 -9
  135. package/dist/server/routes.js +87 -8
  136. package/dist/server/routes.test.js +972 -23
  137. package/dist/shared/aiProfileSurfaceType.d.ts +1 -1
  138. package/dist/shared/aiProfileSurfaceType.js +10 -8
  139. package/dist/storage/objectStorage.d.ts +1 -0
  140. package/dist/storage/objectStorage.js +2 -1
  141. package/dist/storage/objectStorageClient.d.ts +1 -0
  142. package/dist/storage/objectStorageClient.js +41 -8
  143. package/dist/storage/objectStorageClient.test.js +26 -1
  144. package/dist/storage/objectStorageConfig.js +3 -1
  145. package/dist/storage/objectStorageConfig.test.js +13 -1
  146. package/dist/sync/collaborationSyncPayload.js +2 -0
  147. package/dist/sync/collaborationSyncPayload.test.js +1 -0
  148. package/dist/sync/collaborationSyncState.d.ts +3 -3
  149. package/dist/sync/collaborationSyncState.js +19 -12
  150. package/dist/sync/contentSyncPayload.d.ts +5 -1
  151. package/dist/sync/contentSyncPayload.js +9 -0
  152. package/dist/sync/contentSyncPayload.test.js +39 -0
  153. package/dist/sync/contentSyncState.d.ts +5 -1
  154. package/dist/sync/contentSyncState.js +231 -37
  155. package/dist/sync/planningSyncPayload.js +5 -0
  156. package/dist/sync/planningSyncPayload.test.js +24 -0
  157. package/dist/sync/syncApplyHandlers.d.ts +4 -4
  158. package/dist/sync/syncApplyHandlers.js +89 -31
  159. package/dist/sync/syncApplyHandlers.test.js +184 -4
  160. package/dist/sync/syncResourceAdapters.d.ts +2 -0
  161. package/dist/sync/syncResourceAdapters.js +2 -0
  162. package/dist/sync/syncService.d.ts +1 -0
  163. package/dist/sync/syncService.js +13 -1
  164. package/dist/sync/syncService.test.js +39 -0
  165. package/dist/sync/workspacePullFeed.d.ts +4 -0
  166. package/dist/sync/workspacePullFeed.js +129 -70
  167. package/dist/sync/workspacePullFeed.test.js +49 -0
  168. package/dist/sync/workspaceSyncModel.d.ts +28 -6
  169. package/dist/sync/workspaceSyncModel.js +159 -45
  170. package/dist/sync/workspaceSyncModel.test.js +269 -3
  171. package/dist/types.d.ts +33 -2
  172. package/dist/ui/agent-logos/ChatGPT.png +0 -0
  173. package/dist/ui/agent-logos/Gemini CLI.jpeg +0 -0
  174. package/dist/ui/agent-logos/antigravity.jpeg +0 -0
  175. package/dist/ui/agent-logos/claude-code.jpeg +0 -0
  176. package/dist/ui/agent-logos/codex.jpeg +0 -0
  177. package/dist/ui/agent-logos/cursor.png +0 -0
  178. package/dist/ui/agent-logos/openclaw.jpeg +0 -0
  179. package/dist/ui/agent-logos/windsurf.png +0 -0
  180. package/dist/ui/assets/AgentsModule-BS4Pi_nC.js +1 -0
  181. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BVKew-mH.css +1 -0
  182. package/dist/ui/assets/AnnotatedAttachmentWorkspace-CCckzWYZ.js +3 -0
  183. package/dist/ui/assets/ContextAttachmentManager-CfG_ER7C.js +3 -0
  184. package/dist/ui/assets/DocumentWorkspace-BVaWPA25.js +252 -0
  185. package/dist/ui/assets/DocumentWorkspace-D4rJKXzd.css +1 -0
  186. package/dist/ui/assets/EntityActivityTimeline-DSj0ThaK.js +1 -0
  187. package/dist/ui/assets/EntityActivityTimeline-DyqvvhuF.css +1 -0
  188. package/dist/ui/assets/{InitiativesModule-9E6ParrY.js → InitiativesModule-AxMkWtxH.js} +1 -1
  189. package/dist/ui/assets/PlansPage-B_z-D6Nh.css +1 -0
  190. package/dist/ui/assets/PlansPage-Ndt7mYfo.js +1 -0
  191. package/dist/ui/assets/TaskContextUpload-Dw4rTF4i.js +1 -0
  192. package/dist/ui/assets/TaskSettings-f2ga42_V.js +9 -0
  193. package/dist/ui/assets/{WorkflowsModule-BA7jcEpH.js → WorkflowsModule-E4UL3mov.js} +4 -4
  194. package/dist/ui/assets/documentReferences-BOUcJm6-.js +1 -0
  195. package/dist/ui/assets/index-BvAbqx5Q.css +1 -0
  196. package/dist/ui/assets/index-MAHKvFqp.js +4 -0
  197. package/dist/ui/assets/{vendor-icons-DuEd_65c.js → vendor-icons-B32hGuF2.js} +1 -1
  198. package/dist/ui/index.html +3 -3
  199. package/dist/ui/og-image.png +0 -0
  200. package/dist/ui/product/agent manager.png +0 -0
  201. package/dist/ui/product/agent_manager_02.png +0 -0
  202. package/dist/utils/aiProfileEvents.d.ts +7 -0
  203. package/dist/utils/aiProfileEvents.js +6 -0
  204. package/dist/utils/assignees.d.ts +6 -0
  205. package/dist/utils/assignees.js +8 -1
  206. package/dist/utils/assignees.test.d.ts +1 -0
  207. package/dist/utils/assignees.test.js +25 -0
  208. package/dist/utils/avatarDisplay.d.ts +1 -0
  209. package/dist/utils/avatarDisplay.js +15 -0
  210. package/dist/utils/contextFiles.d.ts +3 -1
  211. package/dist/utils/contextFiles.js +2 -1
  212. package/dist/utils/taskActivity.d.ts +7 -1
  213. package/dist/utils/taskActivity.js +63 -1
  214. package/dist/utils/taskActivity.test.js +23 -1
  215. package/dist/utils/taskSearch.d.ts +22 -0
  216. package/dist/utils/taskSearch.js +87 -0
  217. package/dist/utils/taskSearch.test.d.ts +1 -0
  218. package/dist/utils/taskSearch.test.js +69 -0
  219. package/dist/utils/taskUtils.js +10 -3
  220. package/dist/utils/taskUtils.test.d.ts +1 -0
  221. package/dist/utils/taskUtils.test.js +19 -0
  222. package/package.json +1 -1
  223. package/dist/ui/assets/AgentsModule-DrLawwNl.js +0 -1
  224. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BaS2VwIr.css +0 -1
  225. package/dist/ui/assets/AnnotatedAttachmentWorkspace-Z9_whf7F.js +0 -3
  226. package/dist/ui/assets/DocumentWorkspace-B03MaSkw.js +0 -252
  227. package/dist/ui/assets/DocumentWorkspace-Dhdso07p.css +0 -1
  228. package/dist/ui/assets/PlansPage-CvfnMiWq.css +0 -1
  229. package/dist/ui/assets/PlansPage-DRXscYxH.js +0 -1
  230. package/dist/ui/assets/TaskSettings-DZX4jk7e.js +0 -9
  231. package/dist/ui/assets/index-9eT8e0Lu.css +0 -1
  232. package/dist/ui/assets/index-C6k75jr8.js +0 -6
@@ -6,7 +6,7 @@ export type TaskAttachmentRecord = {
6
6
  displayName?: string;
7
7
  originalFilename?: string;
8
8
  timestamp?: string;
9
- assetId?: string;
9
+ assetId?: string | null;
10
10
  referenceNumber?: number | null;
11
11
  referenceLabel?: string | null;
12
12
  taskId?: string | null;
@@ -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";
@@ -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);
@@ -455,6 +476,7 @@ export function ensureBillingSchema(db) {
455
476
  tenant_id TEXT NOT NULL DEFAULT 'default',
456
477
  plan_version_id TEXT NOT NULL,
457
478
  billing_interval TEXT NOT NULL,
479
+ pricing_audience TEXT NOT NULL DEFAULT 'public',
458
480
  stripe_price_id TEXT NOT NULL,
459
481
  pricing_type TEXT NOT NULL DEFAULT 'stripe',
460
482
  active BOOLEAN NOT NULL DEFAULT TRUE,
@@ -462,7 +484,7 @@ export function ensureBillingSchema(db) {
462
484
  currency TEXT,
463
485
  created_at TEXT NOT NULL,
464
486
  updated_at TEXT NOT NULL,
465
- PRIMARY KEY (tenant_id, plan_version_id, billing_interval, stripe_price_id)
487
+ PRIMARY KEY (tenant_id, plan_version_id, billing_interval, pricing_audience, stripe_price_id)
466
488
  );
467
489
 
468
490
  CREATE INDEX IF NOT EXISTS idx_user_billing_tenant ON user_billing(tenant_id);
@@ -480,6 +502,7 @@ export function ensureBillingSchema(db) {
480
502
  const hasUnitAmountColumn = columns.some((c) => c.name === 'unit_amount');
481
503
  const hasCurrencyColumn = columns.some((c) => c.name === 'currency');
482
504
  const hasPricingTypeColumn = columns.some((c) => c.name === 'pricing_type');
505
+ const hasPricingAudienceColumn = columns.some((c) => c.name === 'pricing_audience');
483
506
  if (!hasUnitAmountColumn) {
484
507
  db.exec(`ALTER TABLE billing_price_mappings ADD COLUMN unit_amount INTEGER`);
485
508
  }
@@ -489,30 +512,34 @@ export function ensureBillingSchema(db) {
489
512
  if (!hasPricingTypeColumn) {
490
513
  db.exec(`ALTER TABLE billing_price_mappings ADD COLUMN pricing_type TEXT NOT NULL DEFAULT 'stripe'`);
491
514
  }
515
+ if (!hasPricingAudienceColumn) {
516
+ db.exec(`ALTER TABLE billing_price_mappings ADD COLUMN pricing_audience TEXT NOT NULL DEFAULT 'public'`);
517
+ }
492
518
  ensureBillingPriceMappingsConstraints(db);
493
519
  normalizeUserBillingDefaults(db);
494
520
  }
495
521
  function ensureBillingPriceMappingsConstraints(db) {
496
522
  if (db.provider === 'postgres') {
497
523
  db.exec(`
524
+ ALTER TABLE billing_price_mappings ADD COLUMN IF NOT EXISTS pricing_audience TEXT NOT NULL DEFAULT 'public';
525
+ DROP INDEX IF EXISTS idx_billing_price_mappings_active_interval;
498
526
  ALTER TABLE billing_price_mappings DROP CONSTRAINT IF EXISTS billing_price_mappings_pkey;
499
527
  ALTER TABLE billing_price_mappings
500
528
  ADD CONSTRAINT billing_price_mappings_pkey
501
- PRIMARY KEY (tenant_id, plan_version_id, billing_interval, stripe_price_id);
529
+ PRIMARY KEY (tenant_id, plan_version_id, billing_interval, pricing_audience, stripe_price_id);
502
530
  CREATE UNIQUE INDEX IF NOT EXISTS idx_billing_price_mappings_active_interval
503
- ON billing_price_mappings(tenant_id, plan_version_id, billing_interval)
531
+ ON billing_price_mappings(tenant_id, plan_version_id, billing_interval, pricing_audience)
504
532
  WHERE active = TRUE;
505
533
  `);
506
534
  return;
507
535
  }
508
536
  const indexes = db.prepare(`PRAGMA index_list(billing_price_mappings)`).all();
509
537
  const tableInfo = db.prepare(`PRAGMA table_info(billing_price_mappings)`).all();
510
- const hasActiveIntervalIndex = indexes.some((index) => index.name === 'idx_billing_price_mappings_active_interval');
511
538
  const primaryKeyColumns = tableInfo
512
539
  .filter((column) => Number(column.pk || 0) > 0)
513
540
  .sort((a, b) => Number(a.pk || 0) - Number(b.pk || 0))
514
541
  .map((column) => String(column.name || ''));
515
- const needsPrimaryKeyRepair = primaryKeyColumns.join('|') !== 'tenant_id|plan_version_id|billing_interval|stripe_price_id';
542
+ const needsPrimaryKeyRepair = primaryKeyColumns.join('|') !== 'tenant_id|plan_version_id|billing_interval|pricing_audience|stripe_price_id';
516
543
  if (needsPrimaryKeyRepair) {
517
544
  db.exec(`
518
545
  ALTER TABLE billing_price_mappings RENAME TO billing_price_mappings_legacy;
@@ -521,6 +548,7 @@ function ensureBillingPriceMappingsConstraints(db) {
521
548
  tenant_id TEXT NOT NULL DEFAULT 'default',
522
549
  plan_version_id TEXT NOT NULL,
523
550
  billing_interval TEXT NOT NULL,
551
+ pricing_audience TEXT NOT NULL DEFAULT 'public',
524
552
  stripe_price_id TEXT NOT NULL,
525
553
  pricing_type TEXT NOT NULL DEFAULT 'stripe',
526
554
  active BOOLEAN NOT NULL DEFAULT TRUE,
@@ -528,26 +556,25 @@ function ensureBillingPriceMappingsConstraints(db) {
528
556
  currency TEXT,
529
557
  created_at TEXT NOT NULL,
530
558
  updated_at TEXT NOT NULL,
531
- PRIMARY KEY (tenant_id, plan_version_id, billing_interval, stripe_price_id)
559
+ PRIMARY KEY (tenant_id, plan_version_id, billing_interval, pricing_audience, stripe_price_id)
532
560
  );
533
561
 
534
562
  INSERT INTO billing_price_mappings (
535
- tenant_id, plan_version_id, billing_interval, stripe_price_id, pricing_type, active, unit_amount, currency, created_at, updated_at
563
+ tenant_id, plan_version_id, billing_interval, pricing_audience, stripe_price_id, pricing_type, active, unit_amount, currency, created_at, updated_at
536
564
  )
537
565
  SELECT
538
- tenant_id, plan_version_id, billing_interval, stripe_price_id, COALESCE(pricing_type, 'stripe'), active, unit_amount, currency, created_at, updated_at
566
+ tenant_id, plan_version_id, billing_interval, COALESCE(pricing_audience, 'public'), stripe_price_id, COALESCE(pricing_type, 'stripe'), active, unit_amount, currency, created_at, updated_at
539
567
  FROM billing_price_mappings_legacy;
540
568
 
541
569
  DROP TABLE billing_price_mappings_legacy;
542
570
  `);
543
571
  }
544
- if (!hasActiveIntervalIndex) {
545
- db.exec(`
546
- CREATE UNIQUE INDEX IF NOT EXISTS idx_billing_price_mappings_active_interval
547
- ON billing_price_mappings(tenant_id, plan_version_id, billing_interval)
548
- WHERE active = 1;
549
- `);
550
- }
572
+ db.exec(`
573
+ DROP INDEX IF EXISTS idx_billing_price_mappings_active_interval;
574
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_billing_price_mappings_active_interval
575
+ ON billing_price_mappings(tenant_id, plan_version_id, billing_interval, pricing_audience)
576
+ WHERE active = 1;
577
+ `);
551
578
  }
552
579
  function normalizeUserBillingDefaults(db) {
553
580
  if (db.provider === 'postgres') {
@@ -670,8 +697,12 @@ export function ensureEntitlementsSchema(db) {
670
697
  CREATE TABLE IF NOT EXISTS plan_feature_catalog_overrides (
671
698
  tenant_id TEXT NOT NULL DEFAULT 'default',
672
699
  feature_key TEXT NOT NULL,
700
+ label TEXT,
701
+ description TEXT,
702
+ is_custom INTEGER NOT NULL DEFAULT 0,
673
703
  public_label TEXT,
674
704
  public_description TEXT,
705
+ public_description_visible INTEGER,
675
706
  public_display_order INTEGER,
676
707
  created_at TEXT NOT NULL,
677
708
  updated_at TEXT NOT NULL,
@@ -733,6 +764,18 @@ export function ensureEntitlementsSchema(db) {
733
764
  db.exec(`ALTER TABLE plan_catalog ADD COLUMN description TEXT`);
734
765
  }
735
766
  const planFeatureCatalogOverrideColumns = db.prepare(`PRAGMA table_info(plan_feature_catalog_overrides)`).all();
767
+ if (!planFeatureCatalogOverrideColumns.some((column) => column.name === 'label')) {
768
+ db.exec(`ALTER TABLE plan_feature_catalog_overrides ADD COLUMN label TEXT`);
769
+ }
770
+ if (!planFeatureCatalogOverrideColumns.some((column) => column.name === 'description')) {
771
+ db.exec(`ALTER TABLE plan_feature_catalog_overrides ADD COLUMN description TEXT`);
772
+ }
773
+ if (!planFeatureCatalogOverrideColumns.some((column) => column.name === 'is_custom')) {
774
+ db.exec(`ALTER TABLE plan_feature_catalog_overrides ADD COLUMN is_custom INTEGER NOT NULL DEFAULT 0`);
775
+ }
776
+ if (!planFeatureCatalogOverrideColumns.some((column) => column.name === 'public_description_visible')) {
777
+ db.exec(`ALTER TABLE plan_feature_catalog_overrides ADD COLUMN public_description_visible INTEGER`);
778
+ }
736
779
  if (!planFeatureCatalogOverrideColumns.some((column) => column.name === 'public_display_order')) {
737
780
  db.exec(`ALTER TABLE plan_feature_catalog_overrides ADD COLUMN public_display_order INTEGER`);
738
781
  }
@@ -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 {};