@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
@@ -22,13 +22,33 @@ describe('TaskforceCore planning entity persistence', () => {
22
22
  const initiative = core.createInitiative({
23
23
  title: 'Launch initiative',
24
24
  comments: [{ id: 'comment-1', author: 'user', text: 'Kickoff note', timestamp: '2026-01-01T00:00:00.000Z' }],
25
- attachments: [{ path: '/tmp/launch-brief.md', caption: 'Launch brief', timestamp: '2026-01-01T00:00:00.000Z' }],
25
+ attachments: [{
26
+ path: '/tmp/launch-brief.md',
27
+ fsPath: 'workspaces/workspace-1/assets/documents/asset-launch-brief.md',
28
+ caption: 'Launch brief',
29
+ displayName: 'Launch Brief',
30
+ originalFilename: 'launch-brief.md',
31
+ assetId: 'asset-launch-brief',
32
+ referenceNumber: 12,
33
+ referenceLabel: 'D-12',
34
+ timestamp: '2026-01-01T00:00:00.000Z',
35
+ }],
26
36
  });
27
37
  const workstream = core.createWorkstream({
28
38
  title: 'Billing workstream',
29
39
  initiativeId: initiative.id,
30
40
  comments: [{ id: 'comment-2', author: 'user', text: 'Billing note', timestamp: '2026-01-01T00:00:00.000Z' }],
31
- attachments: [{ path: '/tmp/billing-brief.md', caption: 'Billing brief', timestamp: '2026-01-01T00:00:00.000Z' }],
41
+ attachments: [{
42
+ path: '/tmp/billing-brief.md',
43
+ fsPath: 'workspaces/workspace-1/assets/documents/asset-billing-brief.md',
44
+ caption: 'Billing brief',
45
+ displayName: 'Billing Brief',
46
+ originalFilename: 'billing-brief.md',
47
+ assetId: 'asset-billing-brief',
48
+ referenceNumber: 13,
49
+ referenceLabel: 'D-13',
50
+ timestamp: '2026-01-01T00:00:00.000Z',
51
+ }],
32
52
  });
33
53
  const task = core.createTask({
34
54
  title: 'Adjust billing copy',
@@ -43,11 +63,29 @@ describe('TaskforceCore planning entity persistence', () => {
43
63
  const persistedTask = core.getTask(task.id);
44
64
  expect(persistedInitiative?.referenceLabel).toBe('IN-1');
45
65
  expect(persistedInitiative?.comments?.[0]?.text).toBe('Kickoff note');
46
- expect(persistedInitiative?.attachments?.[0]).toMatchObject({ path: '/tmp/launch-brief.md' });
66
+ expect(persistedInitiative?.attachments?.[0]).toMatchObject({
67
+ path: '/tmp/launch-brief.md',
68
+ fsPath: 'workspaces/workspace-1/assets/documents/asset-launch-brief.md',
69
+ caption: 'Launch brief',
70
+ displayName: 'Launch Brief',
71
+ originalFilename: 'launch-brief.md',
72
+ assetId: 'asset-launch-brief',
73
+ referenceNumber: 12,
74
+ referenceLabel: 'D-12',
75
+ });
47
76
  expect(persistedWorkstream?.referenceLabel).toBe('WS-1');
48
77
  expect(persistedWorkstream?.initiativeId).toBe(initiative.id);
49
78
  expect(persistedWorkstream?.comments?.[0]?.text).toBe('Billing note');
50
- expect(persistedWorkstream?.attachments?.[0]).toMatchObject({ path: '/tmp/billing-brief.md' });
79
+ expect(persistedWorkstream?.attachments?.[0]).toMatchObject({
80
+ path: '/tmp/billing-brief.md',
81
+ fsPath: 'workspaces/workspace-1/assets/documents/asset-billing-brief.md',
82
+ caption: 'Billing brief',
83
+ displayName: 'Billing Brief',
84
+ originalFilename: 'billing-brief.md',
85
+ assetId: 'asset-billing-brief',
86
+ referenceNumber: 13,
87
+ referenceLabel: 'D-13',
88
+ });
51
89
  expect(persistedTask?.workstreamId).toBe(workstream.id);
52
90
  expect(persistedTask?.checklistItems?.map((item) => item.title)).toEqual([
53
91
  'Update docs',
@@ -92,6 +130,328 @@ describe('TaskforceCore planning entity persistence', () => {
92
130
  expect(clearedTask?.workstreamId).toBeNull();
93
131
  core.close();
94
132
  });
133
+ it('syncs canonical attachment links for initiatives and workstreams like task attachments do', () => {
134
+ const core = createCore();
135
+ const workspaceId = 'default';
136
+ const assetStore = core.getWorkspaceAssetStore();
137
+ assetStore.upsertAsset({
138
+ assetId: 'asset-initiative-doc',
139
+ workspaceId,
140
+ kind: 'document',
141
+ logicalName: 'Initiative context',
142
+ originalFilename: 'initiative-context.md',
143
+ mimeType: 'text/markdown',
144
+ storageProvider: 'local',
145
+ storageKey: 'workspaces/default/assets/documents/initiative-context.md',
146
+ contentSha256: 'sha-init',
147
+ sizeBytes: 120,
148
+ documentId: 'doc-initiative-context',
149
+ version: 1,
150
+ isLatest: 1,
151
+ });
152
+ assetStore.upsertAsset({
153
+ assetId: 'asset-workstream-doc',
154
+ workspaceId,
155
+ kind: 'document',
156
+ logicalName: 'Workstream context',
157
+ originalFilename: 'workstream-context.md',
158
+ mimeType: 'text/markdown',
159
+ storageProvider: 'local',
160
+ storageKey: 'workspaces/default/assets/documents/workstream-context.md',
161
+ contentSha256: 'sha-workstream',
162
+ sizeBytes: 140,
163
+ documentId: 'doc-workstream-context',
164
+ version: 1,
165
+ isLatest: 1,
166
+ });
167
+ const initiative = core.createInitiative({
168
+ title: 'Platform',
169
+ attachments: [{
170
+ path: '/api/taskforce/documents/asset-initiative-doc/content',
171
+ fsPath: 'workspaces/default/assets/documents/initiative-context.md',
172
+ caption: 'Initiative context',
173
+ assetId: 'asset-initiative-doc',
174
+ timestamp: '2026-01-01T00:00:00.000Z',
175
+ }],
176
+ }, workspaceId);
177
+ const workstream = core.createWorkstream({
178
+ title: 'Sync',
179
+ initiativeId: initiative.id,
180
+ attachments: [{
181
+ path: '/api/taskforce/documents/asset-workstream-doc/content',
182
+ fsPath: 'workspaces/default/assets/documents/workstream-context.md',
183
+ caption: 'Workstream context',
184
+ assetId: 'asset-workstream-doc',
185
+ timestamp: '2026-01-01T00:00:00.000Z',
186
+ }],
187
+ }, workspaceId);
188
+ expect(assetStore.listLinksForTarget('initiative', initiative.id, workspaceId)).toEqual([
189
+ expect.objectContaining({
190
+ assetId: 'asset-initiative-doc',
191
+ targetType: 'initiative',
192
+ targetId: initiative.id,
193
+ role: 'attachment',
194
+ })
195
+ ]);
196
+ expect(assetStore.listLinksForTarget('workstream', workstream.id, workspaceId)).toEqual([
197
+ expect.objectContaining({
198
+ assetId: 'asset-workstream-doc',
199
+ targetType: 'workstream',
200
+ targetId: workstream.id,
201
+ role: 'attachment',
202
+ })
203
+ ]);
204
+ core.updateInitiative(initiative.id, { attachments: [] }, workspaceId);
205
+ core.updateWorkstream(workstream.id, { attachments: [] }, workspaceId);
206
+ expect(assetStore.listLinksForTarget('initiative', initiative.id, workspaceId)).toEqual([]);
207
+ expect(assetStore.listLinksForTarget('initiative', initiative.id, workspaceId, { includeDeleted: true })).toEqual([
208
+ expect.objectContaining({
209
+ assetId: 'asset-initiative-doc',
210
+ deletedAt: expect.any(String),
211
+ })
212
+ ]);
213
+ expect(assetStore.listLinksForTarget('workstream', workstream.id, workspaceId)).toEqual([]);
214
+ expect(assetStore.listLinksForTarget('workstream', workstream.id, workspaceId, { includeDeleted: true })).toEqual([
215
+ expect.objectContaining({
216
+ assetId: 'asset-workstream-doc',
217
+ deletedAt: expect.any(String),
218
+ })
219
+ ]);
220
+ core.close();
221
+ });
222
+ it('syncs sparse canonical document attachment links by asset id for planning entities', () => {
223
+ const core = createCore();
224
+ const workspaceId = 'default';
225
+ const assetStore = core.getWorkspaceAssetStore();
226
+ assetStore.upsertAsset({
227
+ assetId: 'asset-sparse-initiative-doc',
228
+ workspaceId,
229
+ kind: 'document',
230
+ logicalName: 'Sparse initiative context',
231
+ originalFilename: 'sparse-initiative.md',
232
+ mimeType: 'text/markdown',
233
+ storageProvider: 'local',
234
+ storageKey: 'workspaces/default/assets/documents/sparse-initiative.md',
235
+ contentSha256: 'sha-sparse-initiative',
236
+ sizeBytes: 120,
237
+ documentId: 'doc-sparse-initiative',
238
+ version: 1,
239
+ isLatest: 1,
240
+ });
241
+ assetStore.upsertAsset({
242
+ assetId: 'asset-sparse-workstream-doc',
243
+ workspaceId,
244
+ kind: 'document',
245
+ logicalName: 'Sparse workstream context',
246
+ originalFilename: 'sparse-workstream.md',
247
+ mimeType: 'text/markdown',
248
+ storageProvider: 'local',
249
+ storageKey: 'workspaces/default/assets/documents/sparse-workstream.md',
250
+ contentSha256: 'sha-sparse-workstream',
251
+ sizeBytes: 140,
252
+ documentId: 'doc-sparse-workstream',
253
+ version: 1,
254
+ isLatest: 1,
255
+ });
256
+ const initiative = core.createInitiative({
257
+ title: 'Platform',
258
+ attachments: [{
259
+ path: '/api/taskforce/documents/asset-sparse-initiative-doc/content',
260
+ assetId: 'asset-sparse-initiative-doc',
261
+ caption: 'Sparse initiative context',
262
+ referenceLabel: 'D-101',
263
+ timestamp: '2026-01-01T00:00:00.000Z',
264
+ }],
265
+ }, workspaceId);
266
+ const workstream = core.createWorkstream({
267
+ title: 'Sync',
268
+ initiativeId: initiative.id,
269
+ attachments: [{
270
+ path: '/api/taskforce/documents/asset-sparse-workstream-doc/content',
271
+ assetId: 'asset-sparse-workstream-doc',
272
+ caption: 'Sparse workstream context',
273
+ referenceLabel: 'D-102',
274
+ timestamp: '2026-01-01T00:00:00.000Z',
275
+ }],
276
+ }, workspaceId);
277
+ expect(assetStore.listLinksForTarget('initiative', initiative.id, workspaceId)).toEqual([
278
+ expect.objectContaining({
279
+ assetId: 'asset-sparse-initiative-doc',
280
+ targetType: 'initiative',
281
+ targetId: initiative.id,
282
+ role: 'attachment',
283
+ })
284
+ ]);
285
+ expect(assetStore.listLinksForTarget('workstream', workstream.id, workspaceId)).toEqual([
286
+ expect.objectContaining({
287
+ assetId: 'asset-sparse-workstream-doc',
288
+ targetType: 'workstream',
289
+ targetId: workstream.id,
290
+ role: 'attachment',
291
+ })
292
+ ]);
293
+ core.close();
294
+ });
295
+ it('preserves planning attachments when sync upserts omit attachment fields', () => {
296
+ const core = createCore();
297
+ const workspaceId = 'default';
298
+ const assetStore = core.getWorkspaceAssetStore();
299
+ assetStore.upsertAsset({
300
+ assetId: 'asset-preserved-initiative-doc',
301
+ workspaceId,
302
+ kind: 'document',
303
+ logicalName: 'Preserved initiative context',
304
+ originalFilename: 'preserved-initiative.md',
305
+ mimeType: 'text/markdown',
306
+ storageProvider: 'local',
307
+ storageKey: 'workspaces/default/assets/documents/preserved-initiative.md',
308
+ contentSha256: 'sha-preserved-initiative',
309
+ sizeBytes: 120,
310
+ documentId: 'doc-preserved-initiative',
311
+ version: 1,
312
+ isLatest: 1,
313
+ });
314
+ assetStore.upsertAsset({
315
+ assetId: 'asset-preserved-workstream-doc',
316
+ workspaceId,
317
+ kind: 'document',
318
+ logicalName: 'Preserved workstream context',
319
+ originalFilename: 'preserved-workstream.md',
320
+ mimeType: 'text/markdown',
321
+ storageProvider: 'local',
322
+ storageKey: 'workspaces/default/assets/documents/preserved-workstream.md',
323
+ contentSha256: 'sha-preserved-workstream',
324
+ sizeBytes: 140,
325
+ documentId: 'doc-preserved-workstream',
326
+ version: 1,
327
+ isLatest: 1,
328
+ });
329
+ const initiative = core.createInitiative({
330
+ title: 'Platform',
331
+ attachments: [{
332
+ path: '/api/taskforce/documents/asset-preserved-initiative-doc/content',
333
+ assetId: 'asset-preserved-initiative-doc',
334
+ caption: 'Preserved initiative context',
335
+ timestamp: '2026-01-01T00:00:00.000Z',
336
+ }],
337
+ }, workspaceId);
338
+ const workstream = core.createWorkstream({
339
+ title: 'Sync',
340
+ initiativeId: initiative.id,
341
+ attachments: [{
342
+ path: '/api/taskforce/documents/asset-preserved-workstream-doc/content',
343
+ assetId: 'asset-preserved-workstream-doc',
344
+ caption: 'Preserved workstream context',
345
+ timestamp: '2026-01-01T00:00:00.000Z',
346
+ }],
347
+ }, workspaceId);
348
+ core.upsertInitiativeForSync({
349
+ id: initiative.id,
350
+ referenceNumber: initiative.referenceNumber,
351
+ title: 'Platform updated elsewhere',
352
+ description: initiative.description,
353
+ ownerId: initiative.ownerId,
354
+ createdAt: initiative.createdAt,
355
+ updatedAt: '2026-01-01T00:05:00.000Z',
356
+ order: initiative.order,
357
+ isArchived: false,
358
+ }, workspaceId);
359
+ core.upsertWorkstreamForSync({
360
+ id: workstream.id,
361
+ referenceNumber: workstream.referenceNumber,
362
+ initiativeId: initiative.id,
363
+ title: 'Sync updated elsewhere',
364
+ description: workstream.description,
365
+ ownerId: workstream.ownerId,
366
+ createdAt: workstream.createdAt,
367
+ updatedAt: '2026-01-01T00:05:00.000Z',
368
+ order: workstream.order,
369
+ isArchived: false,
370
+ }, workspaceId);
371
+ expect(core.getInitiative(initiative.id, workspaceId)?.attachments).toEqual([
372
+ expect.objectContaining({
373
+ assetId: 'asset-preserved-initiative-doc',
374
+ caption: 'Preserved initiative context',
375
+ })
376
+ ]);
377
+ expect(core.getWorkstream(workstream.id, workspaceId)?.attachments).toEqual([
378
+ expect.objectContaining({
379
+ assetId: 'asset-preserved-workstream-doc',
380
+ caption: 'Preserved workstream context',
381
+ })
382
+ ]);
383
+ expect(assetStore.listLinksForTarget('initiative', initiative.id, workspaceId)).toEqual([
384
+ expect.objectContaining({
385
+ assetId: 'asset-preserved-initiative-doc',
386
+ deletedAt: null,
387
+ })
388
+ ]);
389
+ expect(assetStore.listLinksForTarget('workstream', workstream.id, workspaceId)).toEqual([
390
+ expect.objectContaining({
391
+ assetId: 'asset-preserved-workstream-doc',
392
+ deletedAt: null,
393
+ })
394
+ ]);
395
+ core.close();
396
+ });
397
+ it('rejects stale planning sync upserts so older empty snapshots cannot clear newer attachments', () => {
398
+ const core = createCore();
399
+ const workspaceId = 'default';
400
+ const initiative = core.createInitiative({
401
+ title: 'Cloud initiative',
402
+ updatedAt: '2026-01-01T00:10:00.000Z',
403
+ attachments: [{
404
+ path: '/api/taskforce/documents/asset-newer-initiative-doc/content',
405
+ assetId: 'asset-newer-initiative-doc',
406
+ caption: 'Newer initiative doc',
407
+ timestamp: '2026-01-01T00:10:00.000Z',
408
+ }],
409
+ }, workspaceId);
410
+ const workstream = core.createWorkstream({
411
+ title: 'Cloud workstream',
412
+ initiativeId: initiative.id,
413
+ updatedAt: '2026-01-01T00:10:00.000Z',
414
+ attachments: [{
415
+ path: '/api/taskforce/documents/asset-newer-workstream-doc/content',
416
+ assetId: 'asset-newer-workstream-doc',
417
+ caption: 'Newer workstream doc',
418
+ timestamp: '2026-01-01T00:10:00.000Z',
419
+ }],
420
+ }, workspaceId);
421
+ core.upsertInitiativeForSync({
422
+ ...initiative,
423
+ title: 'Stale initiative snapshot',
424
+ updatedAt: '2026-01-01T00:05:00.000Z',
425
+ attachments: [],
426
+ }, workspaceId);
427
+ core.upsertWorkstreamForSync({
428
+ ...workstream,
429
+ title: 'Stale workstream snapshot',
430
+ updatedAt: '2026-01-01T00:05:00.000Z',
431
+ attachments: [],
432
+ }, workspaceId);
433
+ expect(core.getInitiative(initiative.id, workspaceId)).toEqual(expect.objectContaining({
434
+ title: 'Cloud initiative',
435
+ updatedAt: '2026-01-01T00:10:00.000Z',
436
+ attachments: [
437
+ expect.objectContaining({
438
+ assetId: 'asset-newer-initiative-doc',
439
+ caption: 'Newer initiative doc',
440
+ })
441
+ ],
442
+ }));
443
+ expect(core.getWorkstream(workstream.id, workspaceId)).toEqual(expect.objectContaining({
444
+ title: 'Cloud workstream',
445
+ updatedAt: '2026-01-01T00:10:00.000Z',
446
+ attachments: [
447
+ expect.objectContaining({
448
+ assetId: 'asset-newer-workstream-doc',
449
+ caption: 'Newer workstream doc',
450
+ })
451
+ ],
452
+ }));
453
+ core.close();
454
+ });
95
455
  it('builds lightweight planning bootstrap summaries without hydrating full task details', () => {
96
456
  const core = createCore();
97
457
  const initiative = core.createInitiative({ title: 'Launch initiative' });
@@ -127,6 +487,8 @@ describe('TaskforceCore planning entity persistence', () => {
127
487
  const billingSummary = bootstrap.workstreamTaskSummaries.find((entry) => entry.workstreamId === billingWorkstream.id);
128
488
  expect(bootstrap.initiatives.map((entry) => entry.id)).toEqual([initiative.id]);
129
489
  expect(bootstrap.workstreams.map((entry) => entry.id)).toEqual([deliveryWorkstream.id, billingWorkstream.id]);
490
+ expect(bootstrap.initiatives.every((entry) => entry.activity === undefined && entry.entityEvents === undefined)).toBe(true);
491
+ expect(bootstrap.workstreams.every((entry) => entry.activity === undefined && entry.entityEvents === undefined)).toBe(true);
130
492
  expect(deliverySummary).toMatchObject({
131
493
  workstreamId: deliveryWorkstream.id,
132
494
  taskCount: 2,
@@ -1,6 +1,6 @@
1
1
  import type { DatabaseAdapter } from '../storage/databaseAdapter.js';
2
2
  import type { TaskActor } from '../shared/taskActor.js';
3
- import type { Comment, TaskActivityItem, TaskEvent, TaskEventDetails } from './Taskforce.js';
3
+ import type { ActivityEntityType, Comment, EntityActivityItem, EntityEvent, TaskActivityItem, TaskEvent, TaskEventDetails } from './Taskforce.js';
4
4
  export interface TaskActivityServiceDeps {
5
5
  db: DatabaseAdapter;
6
6
  tenantId: string;
@@ -9,9 +9,34 @@ export interface TaskActivityServiceDeps {
9
9
  resolveWorkspaceActor: (workspaceId: string, actorRef: unknown) => TaskActor | null;
10
10
  }
11
11
  export declare function listWorkspaceTaskEventsService(deps: TaskActivityServiceDeps, workspaceId: string): TaskEvent[];
12
+ export declare function listWorkspaceEntityEventsService(deps: TaskActivityServiceDeps, workspaceId: string): EntityEvent[];
13
+ export declare function listWorkspaceEntityEventsForSyncService(deps: TaskActivityServiceDeps, workspaceId: string): EntityEvent[];
14
+ export declare function listWorkspaceTaskEventsForSyncService(deps: TaskActivityServiceDeps, workspaceId: string): TaskEvent[];
15
+ export declare function getTaskEventForSyncService(deps: TaskActivityServiceDeps, input: {
16
+ taskId: string;
17
+ workspaceId: string;
18
+ eventId: string;
19
+ }): TaskEvent | null;
20
+ export declare function getEntityEventForSyncService(deps: TaskActivityServiceDeps, input: {
21
+ workspaceId: string;
22
+ eventId: string;
23
+ }): EntityEvent | null;
12
24
  export declare function replaceTaskCommentsService(deps: TaskActivityServiceDeps, taskId: string, workspaceId: string, comments: Comment[]): void;
25
+ export declare function listEntityEventsInternalService(deps: TaskActivityServiceDeps, entityType: ActivityEntityType, entityId: string, workspaceId: string): EntityEvent[];
13
26
  export declare function listTaskEventsInternalService(deps: TaskActivityServiceDeps, taskId: string, workspaceId: string): TaskEvent[];
27
+ export declare function buildEntityActivityService(comments: Comment[], entityEvents: EntityEvent[], options?: {
28
+ hiddenActions?: string[];
29
+ }): EntityActivityItem[];
14
30
  export declare function buildTaskActivityService(comments: Comment[], taskEvents: TaskEvent[]): TaskActivityItem[];
31
+ export declare function addEntityEventService(deps: TaskActivityServiceDeps, input: {
32
+ entityType: ActivityEntityType;
33
+ entityId: string;
34
+ workspaceId: string;
35
+ action: string;
36
+ actor: unknown;
37
+ createdAt?: string;
38
+ details?: TaskEventDetails;
39
+ }): void;
15
40
  export declare function addTaskEventService(deps: TaskActivityServiceDeps, input: {
16
41
  taskId: string;
17
42
  workspaceId: string;
@@ -20,5 +45,7 @@ export declare function addTaskEventService(deps: TaskActivityServiceDeps, input
20
45
  createdAt?: string;
21
46
  details?: TaskEventDetails;
22
47
  }): void;
48
+ export declare function replaceEntityEventsService(deps: TaskActivityServiceDeps, entityType: ActivityEntityType, entityId: string, workspaceId: string, entityEvents: EntityEvent[]): void;
23
49
  export declare function replaceTaskEventsService(deps: TaskActivityServiceDeps, taskId: string, workspaceId: string, taskEvents: TaskEvent[]): void;
50
+ export declare function upsertEntityEventForSyncService(deps: TaskActivityServiceDeps, event: EntityEvent): EntityEvent;
24
51
  export declare function upsertTaskEventForSyncService(deps: TaskActivityServiceDeps, event: TaskEvent): TaskEvent;