@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.
Files changed (215) 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.js +9 -1
  38. package/dist/components/views/PlanComparisonPage.test.js +19 -0
  39. package/dist/components/views/PlansPage.js +1 -1
  40. package/dist/components/views/StandaloneLayout.js +256 -69
  41. package/dist/components/views/WidgetView.js +10 -8
  42. package/dist/components/views/panels/PlanningDrawer.d.ts +19 -6
  43. package/dist/components/views/panels/PlanningDrawer.js +259 -51
  44. package/dist/components/views/panels/PlanningDrawer.test.js +392 -4
  45. package/dist/config/envSchema.js +1 -0
  46. package/dist/core/AiProfileService.d.ts +7 -0
  47. package/dist/core/AiProfileService.js +460 -35
  48. package/dist/core/AiProfiles.test.js +648 -2
  49. package/dist/core/AttachmentLinkService.d.ts +3 -0
  50. package/dist/core/AttachmentLinkService.js +58 -13
  51. package/dist/core/GlobalSettingsService.js +12 -0
  52. package/dist/core/McpTokenService.d.ts +12 -0
  53. package/dist/core/McpTokenService.js +44 -1
  54. package/dist/core/PlanningEntities.test.js +366 -4
  55. package/dist/core/TaskActivityService.d.ts +28 -1
  56. package/dist/core/TaskActivityService.js +227 -46
  57. package/dist/core/TaskActivitySync.test.d.ts +1 -0
  58. package/dist/core/TaskActivitySync.test.js +90 -0
  59. package/dist/core/TaskAuditTimeline.test.js +96 -0
  60. package/dist/core/Taskforce.d.ts +190 -26
  61. package/dist/core/Taskforce.ids.test.js +58 -0
  62. package/dist/core/Taskforce.js +775 -71
  63. package/dist/core/Taskforce.mcpAuth.test.js +109 -0
  64. package/dist/core/types.d.ts +7 -0
  65. package/dist/hooks/auth/useTaskforceAuthBootstrap.js +2 -1
  66. package/dist/hooks/bootstrapTimeouts.d.ts +4 -0
  67. package/dist/hooks/bootstrapTimeouts.js +8 -0
  68. package/dist/hooks/bootstrapTimeouts.test.d.ts +1 -0
  69. package/dist/hooks/bootstrapTimeouts.test.js +12 -0
  70. package/dist/hooks/sync/orchestratorShared.d.ts +3 -3
  71. package/dist/hooks/sync/orchestratorShared.js +61 -1
  72. package/dist/hooks/sync/recovery.js +8 -0
  73. package/dist/hooks/sync/recovery.test.js +5 -5
  74. package/dist/hooks/sync/transfers.d.ts +2 -1
  75. package/dist/hooks/sync/transfers.js +36 -9
  76. package/dist/hooks/sync/transfers.test.js +534 -2
  77. package/dist/hooks/useFilterSort.js +13 -13
  78. package/dist/hooks/useFilterSort.test.js +87 -1
  79. package/dist/hooks/useSyncOrchestrator.js +50 -5
  80. package/dist/hooks/useTaskforce.d.ts +85 -50
  81. package/dist/hooks/useTaskforce.js +44 -14
  82. package/dist/hooks/useTaskforce.runtime-routing.test.js +94 -0
  83. package/dist/hooks/useTaskforce.sync-behavior.test.js +99 -0
  84. package/dist/hooks/useWorkspaceSyncController.js +7 -1
  85. package/dist/hooks/useWorkspaceSyncController.test.js +48 -0
  86. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +14 -7
  87. package/dist/mcp/canonicalAssetHelpers.d.ts +16 -4
  88. package/dist/mcp/canonicalAssetHelpers.js +18 -10
  89. package/dist/mcp/canonicalAssetHelpers.test.d.ts +1 -0
  90. package/dist/mcp/canonicalAssetHelpers.test.js +67 -0
  91. package/dist/mcp/clientIntegrations.d.ts +1 -0
  92. package/dist/mcp/clientIntegrations.js +25 -0
  93. package/dist/mcp/clientIntegrations.test.js +14 -3
  94. package/dist/mcp/clientProfileDefaults.js +3 -3
  95. package/dist/mcp/collaborationRegistrar.js +7 -7
  96. package/dist/mcp/documentAssetRegistrar.js +113 -7
  97. package/dist/mcp/runtime.d.ts +1 -0
  98. package/dist/mcp/runtime.js +969 -122
  99. package/dist/mcp/runtime.test.js +1083 -11
  100. package/dist/mcp/taskAttachmentTypes.d.ts +1 -1
  101. package/dist/mcp/taskPlanningRegistrar.js +48 -0
  102. package/dist/mcp/toolRegistry.d.ts +56 -0
  103. package/dist/mcp/toolRegistry.js +8 -0
  104. package/dist/mcp/toolRegistry.test.js +4 -0
  105. package/dist/migrations/taskSchemaMigrations.js +21 -0
  106. package/dist/server/aiProfileAvatarAssets.d.ts +49 -0
  107. package/dist/server/aiProfileAvatarAssets.js +319 -0
  108. package/dist/server/aiProfileAvatarAssets.test.d.ts +1 -0
  109. package/dist/server/aiProfileAvatarAssets.test.js +213 -0
  110. package/dist/server/annotatedAttachmentsRoutes.test.js +66 -7
  111. package/dist/server/index.js +4 -0
  112. package/dist/server/index.test.js +6 -1
  113. package/dist/server/routes/admin.js +187 -9
  114. package/dist/server/routes/annotatedAttachments.js +51 -0
  115. package/dist/server/routes/auth.d.ts +11 -0
  116. package/dist/server/routes/auth.js +189 -27
  117. package/dist/server/routes/authSupport.d.ts +4 -0
  118. package/dist/server/routes/authSupport.js +12 -1
  119. package/dist/server/routes/billing.js +11 -0
  120. package/dist/server/routes/billing.test.js +3 -0
  121. package/dist/server/routes/documents.js +10 -0
  122. package/dist/server/routes/shared.js +0 -10
  123. package/dist/server/routes/sync.integration.test.js +221 -0
  124. package/dist/server/routes/sync.js +13 -3
  125. package/dist/server/routes/syncSnapshotRoutes.js +2 -0
  126. package/dist/server/routes/tasks.js +12 -9
  127. package/dist/server/routes.js +87 -8
  128. package/dist/server/routes.test.js +890 -22
  129. package/dist/shared/aiProfileSurfaceType.d.ts +1 -1
  130. package/dist/shared/aiProfileSurfaceType.js +10 -8
  131. package/dist/storage/objectStorage.d.ts +1 -0
  132. package/dist/storage/objectStorage.js +2 -1
  133. package/dist/storage/objectStorageClient.d.ts +1 -0
  134. package/dist/storage/objectStorageClient.js +41 -8
  135. package/dist/storage/objectStorageClient.test.js +26 -1
  136. package/dist/storage/objectStorageConfig.js +3 -1
  137. package/dist/storage/objectStorageConfig.test.js +13 -1
  138. package/dist/sync/collaborationSyncPayload.js +2 -0
  139. package/dist/sync/collaborationSyncPayload.test.js +1 -0
  140. package/dist/sync/collaborationSyncState.d.ts +3 -3
  141. package/dist/sync/collaborationSyncState.js +19 -12
  142. package/dist/sync/contentSyncPayload.d.ts +5 -1
  143. package/dist/sync/contentSyncPayload.js +9 -0
  144. package/dist/sync/contentSyncPayload.test.js +39 -0
  145. package/dist/sync/contentSyncState.d.ts +5 -1
  146. package/dist/sync/contentSyncState.js +231 -37
  147. package/dist/sync/planningSyncPayload.js +5 -0
  148. package/dist/sync/planningSyncPayload.test.js +24 -0
  149. package/dist/sync/syncApplyHandlers.d.ts +4 -4
  150. package/dist/sync/syncApplyHandlers.js +89 -31
  151. package/dist/sync/syncApplyHandlers.test.js +184 -4
  152. package/dist/sync/syncResourceAdapters.d.ts +2 -0
  153. package/dist/sync/syncResourceAdapters.js +2 -0
  154. package/dist/sync/syncService.d.ts +1 -0
  155. package/dist/sync/syncService.js +13 -1
  156. package/dist/sync/syncService.test.js +39 -0
  157. package/dist/sync/workspacePullFeed.d.ts +4 -0
  158. package/dist/sync/workspacePullFeed.js +129 -70
  159. package/dist/sync/workspacePullFeed.test.js +49 -0
  160. package/dist/sync/workspaceSyncModel.d.ts +28 -6
  161. package/dist/sync/workspaceSyncModel.js +159 -45
  162. package/dist/sync/workspaceSyncModel.test.js +269 -3
  163. package/dist/types.d.ts +33 -2
  164. package/dist/ui/assets/AgentsModule-2y82_3DO.js +1 -0
  165. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BTLZZCiQ.js +3 -0
  166. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BVKew-mH.css +1 -0
  167. package/dist/ui/assets/ContextAttachmentManager-D2epuNnG.js +3 -0
  168. package/dist/ui/assets/DocumentWorkspace-D4rJKXzd.css +1 -0
  169. package/dist/ui/assets/DocumentWorkspace-sVa-3Do6.js +252 -0
  170. package/dist/ui/assets/EntityActivityTimeline-DyqvvhuF.css +1 -0
  171. package/dist/ui/assets/EntityActivityTimeline-Zods27y_.js +1 -0
  172. package/dist/ui/assets/{InitiativesModule-9E6ParrY.js → InitiativesModule-D4XseTwW.js} +1 -1
  173. package/dist/ui/assets/PlansPage-D5AcbO0L.js +1 -0
  174. package/dist/ui/assets/PlansPage-Dvqsz5zc.css +1 -0
  175. package/dist/ui/assets/TaskContextUpload-uC5qx4Bv.js +1 -0
  176. package/dist/ui/assets/TaskSettings-CqvPAsTv.js +9 -0
  177. package/dist/ui/assets/{WorkflowsModule-BA7jcEpH.js → WorkflowsModule-oOA9bcdK.js} +4 -4
  178. package/dist/ui/assets/documentReferences-DPZuTgHh.js +1 -0
  179. package/dist/ui/assets/index-607WPo_X.js +4 -0
  180. package/dist/ui/assets/index-CYyodXsg.css +1 -0
  181. package/dist/ui/assets/{vendor-icons-DuEd_65c.js → vendor-icons-B32hGuF2.js} +1 -1
  182. package/dist/ui/index.html +3 -3
  183. package/dist/ui/product/agent manager.png +0 -0
  184. package/dist/ui/product/agent_manager_02.png +0 -0
  185. package/dist/utils/aiProfileEvents.d.ts +7 -0
  186. package/dist/utils/aiProfileEvents.js +6 -0
  187. package/dist/utils/assignees.d.ts +6 -0
  188. package/dist/utils/assignees.js +8 -1
  189. package/dist/utils/assignees.test.d.ts +1 -0
  190. package/dist/utils/assignees.test.js +25 -0
  191. package/dist/utils/avatarDisplay.d.ts +1 -0
  192. package/dist/utils/avatarDisplay.js +15 -0
  193. package/dist/utils/contextFiles.d.ts +3 -1
  194. package/dist/utils/contextFiles.js +2 -1
  195. package/dist/utils/taskActivity.d.ts +7 -1
  196. package/dist/utils/taskActivity.js +63 -1
  197. package/dist/utils/taskActivity.test.js +23 -1
  198. package/dist/utils/taskSearch.d.ts +22 -0
  199. package/dist/utils/taskSearch.js +87 -0
  200. package/dist/utils/taskSearch.test.d.ts +1 -0
  201. package/dist/utils/taskSearch.test.js +69 -0
  202. package/dist/utils/taskUtils.js +10 -3
  203. package/dist/utils/taskUtils.test.d.ts +1 -0
  204. package/dist/utils/taskUtils.test.js +19 -0
  205. package/package.json +1 -1
  206. package/dist/ui/assets/AgentsModule-DrLawwNl.js +0 -1
  207. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BaS2VwIr.css +0 -1
  208. package/dist/ui/assets/AnnotatedAttachmentWorkspace-Z9_whf7F.js +0 -3
  209. package/dist/ui/assets/DocumentWorkspace-B03MaSkw.js +0 -252
  210. package/dist/ui/assets/DocumentWorkspace-Dhdso07p.css +0 -1
  211. package/dist/ui/assets/PlansPage-CvfnMiWq.css +0 -1
  212. package/dist/ui/assets/PlansPage-DRXscYxH.js +0 -1
  213. package/dist/ui/assets/TaskSettings-DZX4jk7e.js +0 -9
  214. package/dist/ui/assets/index-9eT8e0Lu.css +0 -1
  215. package/dist/ui/assets/index-C6k75jr8.js +0 -6
@@ -5,6 +5,31 @@ export async function buildWorkspacePullFeed(input) {
5
5
  const { core, workspaceId, cursor, pageSize, encodeWorkspaceSyncCursor, contentSyncState, collaborationSyncState } = input;
6
6
  const syncCursor = cursor ? { watermark: cursor.u, id: cursor.id } : null;
7
7
  const dbFetchLimit = pageSize * 2 + 10;
8
+ const snapshotErrors = [];
9
+ const captureSnapshotError = (domain, error) => {
10
+ snapshotErrors.push({
11
+ domain,
12
+ error: String(error?.message || error || 'Unknown snapshot error')
13
+ });
14
+ };
15
+ const readSnapshot = (domain, fallback, reader) => {
16
+ try {
17
+ return reader();
18
+ }
19
+ catch (error) {
20
+ captureSnapshotError(domain, error);
21
+ return fallback;
22
+ }
23
+ };
24
+ const readSnapshotAsync = async (domain, fallback, reader) => {
25
+ try {
26
+ return await reader();
27
+ }
28
+ catch (error) {
29
+ captureSnapshotError(domain, error);
30
+ return fallback;
31
+ }
32
+ };
8
33
  const taskRows = core.listTasksForSync(workspaceId, syncCursor, dbFetchLimit);
9
34
  const upsertEntries = taskRows
10
35
  .map((entry) => {
@@ -28,7 +53,7 @@ export async function buildWorkspacePullFeed(input) {
28
53
  taskId: String(entry.taskId || '').trim()
29
54
  }))
30
55
  .filter((entry) => Number.isFinite(Date.parse(entry.watermark)) && entry.sortId.length > 0);
31
- const taxonomySnapshot = core.getTaxonomiesSyncSnapshot(workspaceId);
56
+ const taxonomySnapshot = readSnapshot('taxonomy', null, () => core.getTaxonomiesSyncSnapshot(workspaceId));
32
57
  const taxonomyEntries = (taxonomySnapshot
33
58
  && Array.isArray(taxonomySnapshot.taxonomies)
34
59
  && Number.isFinite(Date.parse(String(taxonomySnapshot.updatedAt || '').trim())))
@@ -40,7 +65,7 @@ export async function buildWorkspacePullFeed(input) {
40
65
  ...(taxonomySnapshot.taxonomyState ? { taxonomyState: taxonomySnapshot.taxonomyState } : {})
41
66
  }]
42
67
  : [];
43
- const initiativeEntries = core.listInitiatives(workspaceId)
68
+ const initiativeEntries = readSnapshot('initiatives', [], () => core.listInitiatives(workspaceId))
44
69
  .map((initiative) => ({
45
70
  kind: 'initiative',
46
71
  watermark: String(initiative.updatedAt || initiative.createdAt || '').trim(),
@@ -48,7 +73,7 @@ export async function buildWorkspacePullFeed(input) {
48
73
  initiative
49
74
  }))
50
75
  .filter((entry) => Number.isFinite(Date.parse(entry.watermark)) && entry.initiative.id.length > 0);
51
- const workstreamEntries = core.listWorkstreams(workspaceId)
76
+ const workstreamEntries = readSnapshot('workstreams', [], () => core.listWorkstreams(workspaceId))
52
77
  .map((workstream) => ({
53
78
  kind: 'workstream',
54
79
  watermark: String(workstream.updatedAt || workstream.createdAt || '').trim(),
@@ -56,7 +81,7 @@ export async function buildWorkspacePullFeed(input) {
56
81
  workstream
57
82
  }))
58
83
  .filter((entry) => Number.isFinite(Date.parse(entry.watermark)) && entry.workstream.id.length > 0);
59
- const aiProfileEntries = core.listAiProfiles(workspaceId, { includeArchived: true })
84
+ const aiProfileEntries = readSnapshot('ai-profiles', [], () => core.listAiProfiles(workspaceId, { includeArchived: true }))
60
85
  .map((profile) => ({
61
86
  kind: 'ai-profile',
62
87
  watermark: aiProfileSyncResourceAdapter.getWatermark(profile),
@@ -65,7 +90,7 @@ export async function buildWorkspacePullFeed(input) {
65
90
  }))
66
91
  .filter((entry) => Number.isFinite(Date.parse(entry.watermark)) && entry.sortId.length > 0);
67
92
  const indexStartedAtMs = Date.now();
68
- const documentIndex = await contentSyncState.listWorkspaceSyncDocumentIndex(core.getPaths().basePath, workspaceId);
93
+ const documentIndex = await readSnapshotAsync('documents.index', [], () => contentSyncState.listWorkspaceSyncDocumentIndex(core.getPaths().basePath, workspaceId));
69
94
  const documentEntries = documentIndex
70
95
  .map((doc) => ({
71
96
  kind: 'document',
@@ -77,13 +102,15 @@ export async function buildWorkspacePullFeed(input) {
77
102
  referenceNumber: doc.referenceNumber,
78
103
  version: doc.version,
79
104
  taskId: doc.taskId,
105
+ linkTargetType: doc.linkTargetType,
106
+ linkTargetId: doc.linkTargetId,
80
107
  logicalName: doc.logicalName,
81
108
  caption: doc.caption,
82
109
  originalFilename: doc.originalFilename,
83
110
  linkRole: (doc.linkRole === 'reference' ? 'reference' : 'attachment')
84
111
  }))
85
112
  .filter((entry) => Number.isFinite(Date.parse(entry.watermark)) && entry.path.length > 0);
86
- const documentDeleteEntries = contentSyncState.listWorkspaceSyncDocumentDeletesSnapshot(workspaceId)
113
+ const documentDeleteEntries = readSnapshot('documents.deletes', [], () => contentSyncState.listWorkspaceSyncDocumentDeletesSnapshot(workspaceId))
87
114
  .map((doc) => ({
88
115
  kind: 'document-delete',
89
116
  watermark: String(doc.deletedAt || '').trim(),
@@ -92,7 +119,7 @@ export async function buildWorkspacePullFeed(input) {
92
119
  assetId: typeof doc.assetId === 'string' && doc.assetId.trim().length > 0 ? doc.assetId.trim() : undefined
93
120
  }))
94
121
  .filter((entry) => Number.isFinite(Date.parse(entry.watermark)) && entry.path.length > 0);
95
- const documentReviewSessions = collaborationSyncState.listWorkspaceSyncDocumentReviewSessionsSnapshot(workspaceId);
122
+ const documentReviewSessions = readSnapshot('document-review-sessions', [], () => collaborationSyncState.listWorkspaceSyncDocumentReviewSessionsSnapshot(workspaceId));
96
123
  const documentReviewSessionEntries = documentReviewSessions
97
124
  .filter((session) => !session.deletedAt)
98
125
  .map((session) => ({
@@ -111,7 +138,7 @@ export async function buildWorkspacePullFeed(input) {
111
138
  sessionId: String(session.id || '').trim()
112
139
  }))
113
140
  .filter((entry) => Number.isFinite(Date.parse(entry.watermark)) && entry.sessionId.length > 0);
114
- const documentReviewComments = collaborationSyncState.listWorkspaceSyncDocumentReviewCommentsSnapshot(workspaceId);
141
+ const documentReviewComments = readSnapshot('document-review-comments', [], () => collaborationSyncState.listWorkspaceSyncDocumentReviewCommentsSnapshot(workspaceId));
115
142
  const documentReviewCommentEntries = documentReviewComments
116
143
  .filter((comment) => !comment.deletedAt)
117
144
  .map((comment) => ({
@@ -130,7 +157,7 @@ export async function buildWorkspacePullFeed(input) {
130
157
  commentId: String(comment.id || '').trim()
131
158
  }))
132
159
  .filter((entry) => Number.isFinite(Date.parse(entry.watermark)) && entry.commentId.length > 0);
133
- const annotatedAttachmentSessions = collaborationSyncState.listWorkspaceSyncAnnotatedAttachmentSessionsSnapshot(workspaceId);
160
+ const annotatedAttachmentSessions = readSnapshot('annotated-attachment-sessions', [], () => collaborationSyncState.listWorkspaceSyncAnnotatedAttachmentSessionsSnapshot(workspaceId));
134
161
  const annotatedAttachmentSessionEntries = annotatedAttachmentSessions
135
162
  .filter((session) => !session.deletedAt)
136
163
  .map((session) => ({
@@ -149,15 +176,38 @@ export async function buildWorkspacePullFeed(input) {
149
176
  sessionId: String(session.id || '').trim()
150
177
  }))
151
178
  .filter((entry) => Number.isFinite(Date.parse(entry.watermark)) && entry.sessionId.length > 0);
152
- const taskEventEntries = collaborationSyncState.listWorkspaceSyncTaskEventsSnapshot(workspaceId)
153
- .map((event) => ({
154
- kind: 'task-event',
155
- watermark: String(event.createdAt || '').trim(),
156
- sortId: `task-event:${String(event.id || '').trim()}`,
157
- event
158
- }))
159
- .filter((entry) => Number.isFinite(Date.parse(entry.watermark)) && entry.event.id.length > 0 && entry.event.taskId.length > 0);
160
- const assetIndex = await contentSyncState.listWorkspaceSyncBinaryAssetIndex(workspaceId);
179
+ const taskEventEntries = readSnapshot('task-events', [], () => collaborationSyncState.listWorkspaceSyncTaskEventsSnapshot(workspaceId))
180
+ .map((event) => {
181
+ const entityType = event.entityType === 'initiative' || event.entityType === 'workstream'
182
+ ? event.entityType
183
+ : 'task';
184
+ const entityId = String(event.entityId || event.taskId || '').trim();
185
+ if (entityType === 'task') {
186
+ return {
187
+ kind: 'task-event',
188
+ watermark: String(event.createdAt || '').trim(),
189
+ sortId: `task-event:${String(event.id || '').trim()}`,
190
+ event: {
191
+ ...event,
192
+ entityType: 'task',
193
+ entityId,
194
+ taskId: String(event.taskId || entityId).trim(),
195
+ },
196
+ };
197
+ }
198
+ return {
199
+ kind: 'entity-event',
200
+ watermark: String(event.createdAt || '').trim(),
201
+ sortId: `entity-event:${String(event.id || '').trim()}`,
202
+ event: {
203
+ ...event,
204
+ entityType,
205
+ entityId,
206
+ },
207
+ };
208
+ })
209
+ .filter((entry) => Number.isFinite(Date.parse(entry.watermark)) && entry.event.id.length > 0 && entry.event.entityId.length > 0 && (entry.kind !== 'task-event' || Boolean(entry.event.taskId)));
210
+ const assetIndex = await readSnapshotAsync('assets.index', [], () => contentSyncState.listWorkspaceSyncBinaryAssetIndex(workspaceId));
161
211
  const assetEntries = assetIndex
162
212
  .map((asset) => ({
163
213
  kind: 'asset',
@@ -169,13 +219,15 @@ export async function buildWorkspacePullFeed(input) {
169
219
  mimeType: asset.mimeType,
170
220
  referenceNumber: asset.referenceNumber,
171
221
  taskId: asset.taskId,
222
+ linkTargetType: asset.linkTargetType,
223
+ linkTargetId: asset.linkTargetId,
172
224
  logicalName: asset.logicalName,
173
225
  caption: asset.caption,
174
226
  originalFilename: asset.originalFilename,
175
227
  linkRole: asset.linkRole
176
228
  }))
177
229
  .filter((entry) => Number.isFinite(Date.parse(entry.watermark)) && entry.path.length > 0);
178
- const assetDeleteEntries = contentSyncState.listWorkspaceSyncBinaryAssetDeletesSnapshot(workspaceId)
230
+ const assetDeleteEntries = readSnapshot('assets.deletes', [], () => contentSyncState.listWorkspaceSyncBinaryAssetDeletesSnapshot(workspaceId))
179
231
  .map((asset) => ({
180
232
  kind: 'asset-delete',
181
233
  watermark: String(asset.deletedAt || '').trim(),
@@ -218,7 +270,7 @@ export async function buildWorkspacePullFeed(input) {
218
270
  return String(a.sortId || '').localeCompare(String(b.sortId || ''));
219
271
  });
220
272
  const page = afterCursor.slice(0, pageSize);
221
- const workspaceMembers = core.listWorkspaceUsers(workspaceId).map((member) => ({
273
+ const workspaceMembers = readSnapshot('workspace-members', [], () => core.listWorkspaceUsers(workspaceId)).map((member) => ({
222
274
  userId: member.userId,
223
275
  workspaceId,
224
276
  email: member.email,
@@ -287,55 +339,61 @@ export async function buildWorkspacePullFeed(input) {
287
339
  ? [{ op: 'annotated-attachment-session-delete', sessionId: entry.sessionId, deletedAt: entry.watermark, watermark: entry.watermark }]
288
340
  : entry.kind === 'task-event'
289
341
  ? [{ op: 'task-event-upsert', event: entry.event, watermark: entry.watermark }]
290
- : entry.kind === 'document'
291
- ? (() => {
292
- const payload = hydratedDocuments.get(entry.path);
293
- if (!payload)
294
- return [];
295
- return [{
296
- op: 'document-upsert',
297
- path: entry.path,
298
- updatedAt: entry.watermark,
299
- content: payload.content,
300
- assetId: payload.assetId,
301
- documentId: payload.documentId,
302
- referenceNumber: payload.referenceNumber,
303
- version: payload.version,
304
- taskId: payload.taskId,
305
- logicalName: payload.logicalName,
306
- caption: payload.caption,
307
- originalFilename: payload.originalFilename,
308
- linkRole: payload.linkRole === 'reference' ? 'reference' : 'attachment',
309
- watermark: entry.watermark
310
- }];
311
- })()
312
- : entry.kind === 'document-delete'
313
- ? [{ op: 'document-delete', path: entry.path, assetId: entry.assetId, deletedAt: entry.watermark, watermark: entry.watermark }]
314
- : entry.kind === 'asset'
315
- ? (() => {
316
- const payload = hydratedAssets.get(entry.path);
317
- if (!payload)
318
- return [];
319
- return [{
320
- op: 'asset-upsert',
321
- path: entry.path,
322
- updatedAt: entry.watermark,
323
- contentBase64: payload.contentBase64,
324
- assetId: payload.assetId,
325
- kind: payload.kind,
326
- mimeType: payload.mimeType,
327
- referenceNumber: payload.referenceNumber,
328
- taskId: payload.taskId,
329
- logicalName: payload.logicalName,
330
- caption: payload.caption,
331
- originalFilename: payload.originalFilename,
332
- linkRole: payload.linkRole,
333
- watermark: entry.watermark
334
- }];
335
- })()
336
- : entry.kind === 'asset-delete'
337
- ? [{ op: 'asset-delete', path: entry.path, assetId: entry.assetId, deletedAt: entry.watermark, watermark: entry.watermark }]
338
- : [{ op: 'upsert', task: entry.task, archived: Boolean(entry.archived), watermark: entry.watermark }]));
342
+ : entry.kind === 'entity-event'
343
+ ? [{ op: 'entity-event-upsert', event: entry.event, watermark: entry.watermark }]
344
+ : entry.kind === 'document'
345
+ ? (() => {
346
+ const payload = hydratedDocuments.get(entry.path);
347
+ if (!payload)
348
+ return [];
349
+ return [{
350
+ op: 'document-upsert',
351
+ path: entry.path,
352
+ updatedAt: entry.watermark,
353
+ content: payload.content,
354
+ assetId: payload.assetId,
355
+ documentId: payload.documentId,
356
+ referenceNumber: payload.referenceNumber,
357
+ version: payload.version,
358
+ taskId: payload.taskId,
359
+ linkTargetType: payload.linkTargetType,
360
+ linkTargetId: payload.linkTargetId,
361
+ logicalName: payload.logicalName,
362
+ caption: payload.caption,
363
+ originalFilename: payload.originalFilename,
364
+ linkRole: payload.linkRole === 'reference' ? 'reference' : 'attachment',
365
+ watermark: entry.watermark
366
+ }];
367
+ })()
368
+ : entry.kind === 'document-delete'
369
+ ? [{ op: 'document-delete', path: entry.path, assetId: entry.assetId, deletedAt: entry.watermark, watermark: entry.watermark }]
370
+ : entry.kind === 'asset'
371
+ ? (() => {
372
+ const payload = hydratedAssets.get(entry.path);
373
+ if (!payload)
374
+ return [];
375
+ return [{
376
+ op: 'asset-upsert',
377
+ path: entry.path,
378
+ updatedAt: entry.watermark,
379
+ contentBase64: payload.contentBase64,
380
+ assetId: payload.assetId,
381
+ kind: payload.kind,
382
+ mimeType: payload.mimeType,
383
+ referenceNumber: payload.referenceNumber,
384
+ taskId: payload.taskId,
385
+ linkTargetType: payload.linkTargetType,
386
+ linkTargetId: payload.linkTargetId,
387
+ logicalName: payload.logicalName,
388
+ caption: payload.caption,
389
+ originalFilename: payload.originalFilename,
390
+ linkRole: payload.linkRole,
391
+ watermark: entry.watermark
392
+ }];
393
+ })()
394
+ : entry.kind === 'asset-delete'
395
+ ? [{ op: 'asset-delete', path: entry.path, assetId: entry.assetId, deletedAt: entry.watermark, watermark: entry.watermark }]
396
+ : [{ op: 'upsert', task: entry.task, archived: Boolean(entry.archived), watermark: entry.watermark }]));
339
397
  const encodeMs = Date.now() - encodeStartedAtMs;
340
398
  return {
341
399
  workspaceMembers,
@@ -351,7 +409,8 @@ export async function buildWorkspacePullFeed(input) {
351
409
  indexMs,
352
410
  hydrationMs,
353
411
  encodeMs,
354
- buildMs: Date.now() - buildStartedAtMs
412
+ buildMs: Date.now() - buildStartedAtMs,
413
+ snapshotErrors
355
414
  }
356
415
  };
357
416
  }
@@ -211,6 +211,55 @@ describe('workspacePullFeed', () => {
211
211
  expect(result.nextCursor).toBe('2026-04-10T10:05:00.000Z|ai-profile:ai-profile-1');
212
212
  expect(result.hasMore).toBe(false);
213
213
  });
214
+ it('degrades optional snapshot failures into diagnostics instead of throwing the pull feed', async () => {
215
+ const core = createCoreMock();
216
+ const content = createContentSyncStateMock();
217
+ const collaboration = createCollaborationSyncStateMock();
218
+ core.listTasksForSync = () => [
219
+ {
220
+ archived: false,
221
+ task: {
222
+ id: 'task-1',
223
+ title: 'Still syncs tasks',
224
+ status: 'task',
225
+ category: 'general',
226
+ type: 'task',
227
+ createdAt: '2026-04-10T10:00:00.000Z',
228
+ }
229
+ }
230
+ ];
231
+ core.listWorkspaceUsers = () => {
232
+ throw new Error('workspace members exploded');
233
+ };
234
+ content.listWorkspaceSyncBinaryAssetIndex = async () => {
235
+ throw new Error('asset index exploded');
236
+ };
237
+ collaboration.listWorkspaceSyncDocumentReviewSessionsSnapshot = () => {
238
+ throw new Error('review sessions exploded');
239
+ };
240
+ const result = await buildWorkspacePullFeed({
241
+ core,
242
+ workspaceId: 'workspace-1',
243
+ cursor: null,
244
+ pageSize: 10,
245
+ encodeWorkspaceSyncCursor: encodeCursor,
246
+ contentSyncState: content,
247
+ collaborationSyncState: collaboration,
248
+ });
249
+ expect(result.changes).toEqual([
250
+ expect.objectContaining({
251
+ op: 'upsert',
252
+ task: expect.objectContaining({ id: 'task-1' })
253
+ })
254
+ ]);
255
+ expect(result.workspaceMembers).toEqual([]);
256
+ expect(result.diagnostics.assetIndexCount).toBe(0);
257
+ expect(result.diagnostics.snapshotErrors).toEqual(expect.arrayContaining([
258
+ expect.objectContaining({ domain: 'assets.index', error: 'asset index exploded' }),
259
+ expect.objectContaining({ domain: 'document-review-sessions', error: 'review sessions exploded' }),
260
+ expect.objectContaining({ domain: 'workspace-members', error: 'workspace members exploded' }),
261
+ ]));
262
+ });
214
263
  it('hydrates document and asset payloads only for entries included in the current page', async () => {
215
264
  const core = createCoreMock();
216
265
  const content = createContentSyncStateMock();
@@ -1,4 +1,4 @@
1
- import type { InitiativeItem, TaskEvent, TaskItem, WorkstreamItem } from '../types';
1
+ import type { AttachmentItem, EntityEvent, InitiativeItem, TaskItem, WorkstreamItem } from '../types';
2
2
  import type { DocumentReviewCommentRecord, DocumentReviewSessionStatus } from '../documentReviews/types.js';
3
3
  import type { AnnotatedAttachmentAnnotation } from '../annotatedAttachments/types.js';
4
4
  import type { WorkspaceTaxonomyState } from '../shared/taxonomyState.js';
@@ -6,16 +6,23 @@ import { type AiProfileSurfaceType } from '../shared/aiProfileSurfaceType.js';
6
6
  import { type AiProfileSeatScope } from '../shared/aiProfileSeatScope.js';
7
7
  import { type TaskSyncPayload } from './taskSyncPayload.js';
8
8
  export type WorkspaceSyncTaskSnapshot = TaskSyncPayload;
9
- export interface WorkspaceSyncTaskEventSnapshot {
9
+ export interface WorkspaceSyncEntityEventSnapshot {
10
10
  id: string;
11
- taskId: string;
12
11
  workspaceId: string;
12
+ entityType: 'task' | 'initiative' | 'workstream';
13
+ entityId: string;
14
+ taskId?: string;
13
15
  action: string;
14
16
  actor: string;
15
17
  actorType: 'system' | 'human' | 'ai';
16
- details?: TaskEvent['details'];
18
+ details?: EntityEvent['details'];
17
19
  createdAt: string;
18
20
  }
21
+ export type WorkspaceSyncTaskEventSnapshot = WorkspaceSyncEntityEventSnapshot & {
22
+ entityType: 'task';
23
+ entityId: string;
24
+ taskId: string;
25
+ };
19
26
  export interface WorkspaceSyncAiProfileSnapshot {
20
27
  id: string;
21
28
  workspaceId: string;
@@ -26,6 +33,8 @@ export interface WorkspaceSyncAiProfileSnapshot {
26
33
  color: string;
27
34
  avatarUrl?: string | null;
28
35
  avatarSourceUrl?: string | null;
36
+ avatarRevision?: number;
37
+ avatarUpdatedAt?: string | null;
29
38
  description?: string | null;
30
39
  role?: string | null;
31
40
  provider?: string | null;
@@ -105,6 +114,7 @@ export interface WorkspaceSyncInitiativeSnapshot {
105
114
  createdAt: string;
106
115
  updatedAt?: string;
107
116
  order?: number | null;
117
+ attachments?: Array<string | AttachmentItem>;
108
118
  isArchived?: boolean;
109
119
  }
110
120
  export interface WorkspaceSyncWorkstreamSnapshot {
@@ -118,6 +128,7 @@ export interface WorkspaceSyncWorkstreamSnapshot {
118
128
  createdAt: string;
119
129
  updatedAt?: string;
120
130
  order?: number | null;
131
+ attachments?: Array<string | AttachmentItem>;
121
132
  isArchived?: boolean;
122
133
  }
123
134
  export type WorkspaceSyncChange = {
@@ -153,6 +164,8 @@ export type WorkspaceSyncChange = {
153
164
  referenceNumber?: number | null;
154
165
  version?: number | null;
155
166
  taskId?: string | null;
167
+ linkTargetType?: 'task' | 'initiative' | 'workstream' | null;
168
+ linkTargetId?: string | null;
156
169
  logicalName?: string | null;
157
170
  caption?: string | null;
158
171
  originalFilename?: string | null;
@@ -172,6 +185,8 @@ export type WorkspaceSyncChange = {
172
185
  mimeType: string;
173
186
  referenceNumber?: number | null;
174
187
  taskId?: string | null;
188
+ linkTargetType?: 'task' | 'initiative' | 'workstream' | null;
189
+ linkTargetId?: string | null;
175
190
  logicalName?: string | null;
176
191
  caption?: string | null;
177
192
  originalFilename?: string | null;
@@ -202,6 +217,9 @@ export type WorkspaceSyncChange = {
202
217
  op: 'annotated-attachment-session-delete';
203
218
  sessionId: string;
204
219
  deletedAt?: string;
220
+ } | {
221
+ op: 'entity-event-upsert';
222
+ event: WorkspaceSyncEntityEventSnapshot;
205
223
  } | {
206
224
  op: 'task-event-upsert';
207
225
  event: WorkspaceSyncTaskEventSnapshot;
@@ -276,6 +294,8 @@ export interface WorkspaceSyncDocumentSnapshot {
276
294
  referenceNumber?: number | null;
277
295
  version?: number | null;
278
296
  taskId?: string | null;
297
+ linkTargetType?: 'task' | 'initiative' | 'workstream' | null;
298
+ linkTargetId?: string | null;
279
299
  logicalName?: string | null;
280
300
  caption?: string | null;
281
301
  originalFilename?: string | null;
@@ -290,6 +310,8 @@ export interface WorkspaceSyncAssetSnapshot {
290
310
  mimeType: string;
291
311
  referenceNumber?: number | null;
292
312
  taskId?: string | null;
313
+ linkTargetType?: 'task' | 'initiative' | 'workstream' | null;
314
+ linkTargetId?: string | null;
293
315
  logicalName?: string | null;
294
316
  caption?: string | null;
295
317
  originalFilename?: string | null;
@@ -313,7 +335,7 @@ export declare function buildWorkspaceSyncPayloadModel(input: {
313
335
  documentReviewSessions?: WorkspaceSyncDocumentReviewSessionSnapshot[];
314
336
  documentReviewComments?: WorkspaceSyncDocumentReviewCommentSnapshot[];
315
337
  annotatedAttachmentSessions?: WorkspaceSyncAnnotatedAttachmentSessionSnapshot[];
316
- taskEvents?: WorkspaceSyncTaskEventSnapshot[];
338
+ taskEvents?: WorkspaceSyncEntityEventSnapshot[];
317
339
  lastPushedInitiativeIds?: Set<string>;
318
340
  lastPushedInitiativeWatermarks?: Map<string, string>;
319
341
  lastPushedWorkstreamIds?: Set<string>;
@@ -357,5 +379,5 @@ export declare function buildWorkspaceSyncSignatureModel(input: {
357
379
  documentReviewSessions?: WorkspaceSyncDocumentReviewSessionSnapshot[];
358
380
  documentReviewComments?: WorkspaceSyncDocumentReviewCommentSnapshot[];
359
381
  annotatedAttachmentSessions?: WorkspaceSyncAnnotatedAttachmentSessionSnapshot[];
360
- taskEvents?: WorkspaceSyncTaskEventSnapshot[];
382
+ taskEvents?: WorkspaceSyncEntityEventSnapshot[];
361
383
  }): string;