@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
@@ -29,6 +29,9 @@ function sortTasksForSync(tasks) {
29
29
  return leftId.localeCompare(rightId);
30
30
  });
31
31
  }
32
+ function clonePlanningAttachmentsForSync(value) {
33
+ return Array.isArray(value) ? value : undefined;
34
+ }
32
35
  export function buildWorkspaceSyncPayloadModel(input) {
33
36
  const payloadBuiltAt = new Date().toISOString();
34
37
  const pendingDeleteIds = new Set(Array.from(input.pendingDeletedTaskIds)
@@ -142,6 +145,7 @@ export function buildWorkspaceSyncPayloadModel(input) {
142
145
  createdAt: String(initiative?.createdAt || '').trim(),
143
146
  updatedAt: String(initiative?.updatedAt || initiative?.createdAt || '').trim(),
144
147
  order: Number.isFinite(Number(initiative?.order)) ? Math.floor(Number(initiative.order)) : null,
148
+ attachments: clonePlanningAttachmentsForSync(initiative?.attachments),
145
149
  isArchived: Boolean(initiative?.isArchived)
146
150
  }
147
151
  }))
@@ -184,6 +188,7 @@ export function buildWorkspaceSyncPayloadModel(input) {
184
188
  createdAt: String(workstream?.createdAt || '').trim(),
185
189
  updatedAt: String(workstream?.updatedAt || workstream?.createdAt || '').trim(),
186
190
  order: Number.isFinite(Number(workstream?.order)) ? Math.floor(Number(workstream.order)) : null,
191
+ attachments: clonePlanningAttachmentsForSync(workstream?.attachments),
187
192
  isArchived: Boolean(workstream?.isArchived)
188
193
  }
189
194
  }))
@@ -218,6 +223,10 @@ export function buildWorkspaceSyncPayloadModel(input) {
218
223
  color: String(profile.color || '#8b5cf6').trim() || '#8b5cf6',
219
224
  avatarUrl: normalizeSyncSameOriginAssetUrl(profile.avatarUrl),
220
225
  avatarSourceUrl: normalizeSyncSameOriginAssetUrl(profile.avatarSourceUrl),
226
+ avatarRevision: Number.isFinite(Number(profile.avatarRevision)) ? Math.max(0, Math.floor(Number(profile.avatarRevision))) : 0,
227
+ avatarUpdatedAt: typeof profile.avatarUpdatedAt === 'string' && profile.avatarUpdatedAt.trim().length > 0
228
+ ? profile.avatarUpdatedAt.trim()
229
+ : null,
221
230
  description: typeof profile.description === 'string' ? profile.description.trim() || null : null,
222
231
  role: typeof profile.role === 'string' ? profile.role.trim() || null : null,
223
232
  provider: typeof profile.provider === 'string' ? profile.provider.trim() || null : null,
@@ -277,6 +286,10 @@ export function buildWorkspaceSyncPayloadModel(input) {
277
286
  referenceNumber: Number.isFinite(Number(doc.referenceNumber)) ? Math.max(1, Math.floor(Number(doc.referenceNumber))) : null,
278
287
  version: Number.isFinite(Number(doc.version)) ? Math.max(1, Math.floor(Number(doc.version))) : null,
279
288
  taskId: typeof doc.taskId === 'string' && doc.taskId.trim().length > 0 ? doc.taskId.trim() : null,
289
+ linkTargetType: doc.linkTargetType === 'initiative' || doc.linkTargetType === 'workstream' || doc.linkTargetType === 'task'
290
+ ? doc.linkTargetType
291
+ : null,
292
+ linkTargetId: typeof doc.linkTargetId === 'string' && doc.linkTargetId.trim().length > 0 ? doc.linkTargetId.trim() : null,
280
293
  logicalName: typeof doc.logicalName === 'string' && doc.logicalName.trim().length > 0 ? doc.logicalName.trim() : null,
281
294
  caption: typeof doc.caption === 'string' && doc.caption.trim().length > 0 ? doc.caption.trim() : null,
282
295
  originalFilename: typeof doc.originalFilename === 'string' && doc.originalFilename.trim().length > 0 ? doc.originalFilename.trim() : null,
@@ -309,6 +322,10 @@ export function buildWorkspaceSyncPayloadModel(input) {
309
322
  mimeType: typeof asset.mimeType === 'string' && asset.mimeType.trim().length > 0 ? asset.mimeType.trim() : 'application/octet-stream',
310
323
  referenceNumber: Number.isFinite(Number(asset.referenceNumber)) ? Math.max(1, Math.floor(Number(asset.referenceNumber))) : null,
311
324
  taskId: typeof asset.taskId === 'string' && asset.taskId.trim().length > 0 ? asset.taskId.trim() : null,
325
+ linkTargetType: asset.linkTargetType === 'initiative' || asset.linkTargetType === 'workstream' || asset.linkTargetType === 'task'
326
+ ? asset.linkTargetType
327
+ : null,
328
+ linkTargetId: typeof asset.linkTargetId === 'string' && asset.linkTargetId.trim().length > 0 ? asset.linkTargetId.trim() : null,
312
329
  logicalName: typeof asset.logicalName === 'string' && asset.logicalName.trim().length > 0 ? asset.logicalName.trim() : null,
313
330
  caption: typeof asset.caption === 'string' && asset.caption.trim().length > 0 ? asset.caption.trim() : null,
314
331
  originalFilename: typeof asset.originalFilename === 'string' && asset.originalFilename.trim().length > 0 ? asset.originalFilename.trim() : null,
@@ -325,21 +342,19 @@ export function buildWorkspaceSyncPayloadModel(input) {
325
342
  return previousAssetWatermarks.get(asset.path) !== asset.updatedAt;
326
343
  });
327
344
  const reviewSessions = Array.isArray(input.documentReviewSessions) ? input.documentReviewSessions : [];
345
+ const allDocumentReviewSessionIds = new Set(reviewSessions
346
+ .map((session) => String(session?.id || '').trim())
347
+ .filter((sessionId) => sessionId.length > 0));
328
348
  const currentDocumentReviewSessionIds = new Set(reviewSessions
329
349
  .filter((session) => String(session?.deletedAt || '').trim().length === 0)
330
350
  .map((session) => String(session?.id || '').trim())
331
351
  .filter((sessionId) => sessionId.length > 0));
332
- const deleteDocumentReviewSessionIds = new Set([
333
- ...Array.from(input.lastPushedDocumentReviewSessionIds || new Set())
334
- .map((sessionId) => String(sessionId || '').trim())
335
- .filter((sessionId) => sessionId.length > 0 && !currentDocumentReviewSessionIds.has(sessionId)),
336
- ...reviewSessions
337
- .filter((session) => String(session?.deletedAt || '').trim().length > 0)
338
- .map((session) => String(session?.id || '').trim())
339
- .filter((sessionId) => sessionId.length > 0)
340
- ]);
352
+ const previousDocumentReviewSessionIds = input.lastPushedDocumentReviewSessionIds || new Set();
341
353
  const currentDocumentReviewSessionWatermarks = new Map(reviewSessions
342
- .map((session) => [String(session?.id || '').trim(), String(session?.updatedAt || session?.createdAt || '').trim()])
354
+ .map((session) => [
355
+ String(session?.id || '').trim(),
356
+ String(session?.deletedAt || session?.updatedAt || session?.createdAt || '').trim()
357
+ ])
343
358
  .filter(([sessionId]) => sessionId.length > 0));
344
359
  const previousDocumentReviewSessionWatermarks = input.lastPushedDocumentReviewSessionWatermarks;
345
360
  const shouldSendAllDocumentReviewSessions = !previousDocumentReviewSessionWatermarks || previousDocumentReviewSessionWatermarks.size === 0;
@@ -367,22 +382,46 @@ export function buildWorkspaceSyncPayloadModel(input) {
367
382
  return true;
368
383
  return previousDocumentReviewSessionWatermarks.get(change.session.id) !== change.session.updatedAt;
369
384
  });
385
+ const deletedReviewSessionChanges = reviewSessions
386
+ .filter((session) => String(session?.deletedAt || '').trim().length > 0)
387
+ .map((session) => ({
388
+ sessionId: String(session?.id || '').trim(),
389
+ deletedAt: String(session?.deletedAt || session?.updatedAt || session?.createdAt || payloadBuiltAt).trim() || payloadBuiltAt
390
+ }))
391
+ .filter((change) => change.sessionId.length > 0)
392
+ .filter((change) => {
393
+ if (shouldSendAllDocumentReviewSessions)
394
+ return true;
395
+ return previousDocumentReviewSessionWatermarks.get(change.sessionId) !== change.deletedAt;
396
+ });
397
+ const missingDocumentReviewSessionDeleteIds = Array.from(previousDocumentReviewSessionIds)
398
+ .map((sessionId) => String(sessionId || '').trim())
399
+ .filter((sessionId) => sessionId.length > 0 && !allDocumentReviewSessionIds.has(sessionId));
400
+ const documentReviewSessionDeleteChanges = [
401
+ ...missingDocumentReviewSessionDeleteIds.map((sessionId) => ({
402
+ sessionId,
403
+ deletedAt: payloadBuiltAt
404
+ })),
405
+ ...deletedReviewSessionChanges
406
+ ];
407
+ const deleteDocumentReviewSessionIds = new Set([
408
+ ...missingDocumentReviewSessionDeleteIds,
409
+ ...deletedReviewSessionChanges.map((change) => change.sessionId)
410
+ ]);
370
411
  const reviewComments = Array.isArray(input.documentReviewComments) ? input.documentReviewComments : [];
412
+ const allDocumentReviewCommentIds = new Set(reviewComments
413
+ .map((comment) => String(comment?.id || '').trim())
414
+ .filter((commentId) => commentId.length > 0));
371
415
  const currentDocumentReviewCommentIds = new Set(reviewComments
372
416
  .filter((comment) => String(comment?.deletedAt || '').trim().length === 0)
373
417
  .map((comment) => String(comment?.id || '').trim())
374
418
  .filter((commentId) => commentId.length > 0));
375
- const deleteDocumentReviewCommentIds = new Set([
376
- ...Array.from(input.lastPushedDocumentReviewCommentIds || new Set())
377
- .map((commentId) => String(commentId || '').trim())
378
- .filter((commentId) => commentId.length > 0 && !currentDocumentReviewCommentIds.has(commentId)),
379
- ...reviewComments
380
- .filter((comment) => String(comment?.deletedAt || '').trim().length > 0)
381
- .map((comment) => String(comment?.id || '').trim())
382
- .filter((commentId) => commentId.length > 0)
383
- ]);
419
+ const previousDocumentReviewCommentIds = input.lastPushedDocumentReviewCommentIds || new Set();
384
420
  const currentDocumentReviewCommentWatermarks = new Map(reviewComments
385
- .map((comment) => [String(comment?.id || '').trim(), String(comment?.updatedAt || comment?.createdAt || '').trim()])
421
+ .map((comment) => [
422
+ String(comment?.id || '').trim(),
423
+ String(comment?.deletedAt || comment?.updatedAt || comment?.createdAt || '').trim()
424
+ ])
386
425
  .filter(([commentId]) => commentId.length > 0));
387
426
  const previousDocumentReviewCommentWatermarks = input.lastPushedDocumentReviewCommentWatermarks;
388
427
  const shouldSendAllDocumentReviewComments = !previousDocumentReviewCommentWatermarks || previousDocumentReviewCommentWatermarks.size === 0;
@@ -407,22 +446,46 @@ export function buildWorkspaceSyncPayloadModel(input) {
407
446
  return true;
408
447
  return previousDocumentReviewCommentWatermarks.get(change.comment.id) !== change.comment.updatedAt;
409
448
  });
449
+ const deletedReviewCommentChanges = reviewComments
450
+ .filter((comment) => String(comment?.deletedAt || '').trim().length > 0)
451
+ .map((comment) => ({
452
+ commentId: String(comment?.id || '').trim(),
453
+ deletedAt: String(comment?.deletedAt || comment?.updatedAt || comment?.createdAt || payloadBuiltAt).trim() || payloadBuiltAt
454
+ }))
455
+ .filter((change) => change.commentId.length > 0)
456
+ .filter((change) => {
457
+ if (shouldSendAllDocumentReviewComments)
458
+ return true;
459
+ return previousDocumentReviewCommentWatermarks.get(change.commentId) !== change.deletedAt;
460
+ });
461
+ const missingDocumentReviewCommentDeleteIds = Array.from(previousDocumentReviewCommentIds)
462
+ .map((commentId) => String(commentId || '').trim())
463
+ .filter((commentId) => commentId.length > 0 && !allDocumentReviewCommentIds.has(commentId));
464
+ const documentReviewCommentDeleteChanges = [
465
+ ...missingDocumentReviewCommentDeleteIds.map((commentId) => ({
466
+ commentId,
467
+ deletedAt: payloadBuiltAt
468
+ })),
469
+ ...deletedReviewCommentChanges
470
+ ];
471
+ const deleteDocumentReviewCommentIds = new Set([
472
+ ...missingDocumentReviewCommentDeleteIds,
473
+ ...deletedReviewCommentChanges.map((change) => change.commentId)
474
+ ]);
410
475
  const annotatedSessions = Array.isArray(input.annotatedAttachmentSessions) ? input.annotatedAttachmentSessions : [];
476
+ const allAnnotatedAttachmentSessionIds = new Set(annotatedSessions
477
+ .map((session) => String(session?.id || '').trim())
478
+ .filter((sessionId) => sessionId.length > 0));
411
479
  const currentAnnotatedAttachmentSessionIds = new Set(annotatedSessions
412
480
  .filter((session) => String(session?.deletedAt || '').trim().length === 0)
413
481
  .map((session) => String(session?.id || '').trim())
414
482
  .filter((sessionId) => sessionId.length > 0));
415
- const deleteAnnotatedAttachmentSessionIds = new Set([
416
- ...Array.from(input.lastPushedAnnotatedAttachmentSessionIds || new Set())
417
- .map((sessionId) => String(sessionId || '').trim())
418
- .filter((sessionId) => sessionId.length > 0 && !currentAnnotatedAttachmentSessionIds.has(sessionId)),
419
- ...annotatedSessions
420
- .filter((session) => String(session?.deletedAt || '').trim().length > 0)
421
- .map((session) => String(session?.id || '').trim())
422
- .filter((sessionId) => sessionId.length > 0)
423
- ]);
483
+ const previousAnnotatedAttachmentSessionIds = input.lastPushedAnnotatedAttachmentSessionIds || new Set();
424
484
  const currentAnnotatedAttachmentSessionWatermarks = new Map(annotatedSessions
425
- .map((session) => [String(session?.id || '').trim(), String(session?.updatedAt || session?.createdAt || '').trim()])
485
+ .map((session) => [
486
+ String(session?.id || '').trim(),
487
+ String(session?.deletedAt || session?.updatedAt || session?.createdAt || '').trim()
488
+ ])
426
489
  .filter(([sessionId]) => sessionId.length > 0));
427
490
  const previousAnnotatedAttachmentSessionWatermarks = input.lastPushedAnnotatedAttachmentSessionWatermarks;
428
491
  const shouldSendAllAnnotatedAttachmentSessions = !previousAnnotatedAttachmentSessionWatermarks || previousAnnotatedAttachmentSessionWatermarks.size === 0;
@@ -453,6 +516,32 @@ export function buildWorkspaceSyncPayloadModel(input) {
453
516
  return true;
454
517
  return previousAnnotatedAttachmentSessionWatermarks.get(change.session.id) !== change.session.updatedAt;
455
518
  });
519
+ const deletedAnnotatedAttachmentSessionChanges = annotatedSessions
520
+ .filter((session) => String(session?.deletedAt || '').trim().length > 0)
521
+ .map((session) => ({
522
+ sessionId: String(session?.id || '').trim(),
523
+ deletedAt: String(session?.deletedAt || session?.updatedAt || session?.createdAt || payloadBuiltAt).trim() || payloadBuiltAt
524
+ }))
525
+ .filter((change) => change.sessionId.length > 0)
526
+ .filter((change) => {
527
+ if (shouldSendAllAnnotatedAttachmentSessions)
528
+ return true;
529
+ return previousAnnotatedAttachmentSessionWatermarks.get(change.sessionId) !== change.deletedAt;
530
+ });
531
+ const missingAnnotatedAttachmentSessionDeleteIds = Array.from(previousAnnotatedAttachmentSessionIds)
532
+ .map((sessionId) => String(sessionId || '').trim())
533
+ .filter((sessionId) => sessionId.length > 0 && !allAnnotatedAttachmentSessionIds.has(sessionId));
534
+ const annotatedAttachmentSessionDeleteChanges = [
535
+ ...missingAnnotatedAttachmentSessionDeleteIds.map((sessionId) => ({
536
+ sessionId,
537
+ deletedAt: payloadBuiltAt
538
+ })),
539
+ ...deletedAnnotatedAttachmentSessionChanges
540
+ ];
541
+ const deleteAnnotatedAttachmentSessionIds = new Set([
542
+ ...missingAnnotatedAttachmentSessionDeleteIds,
543
+ ...deletedAnnotatedAttachmentSessionChanges.map((change) => change.sessionId)
544
+ ]);
456
545
  const taskEvents = sortTaskEventsForSync(Array.isArray(input.taskEvents) ? input.taskEvents : []);
457
546
  const currentTaskEventWatermarks = new Map(taskEvents
458
547
  .map((event) => [String(event?.id || '').trim(), String(event?.createdAt || '').trim()])
@@ -460,12 +549,17 @@ export function buildWorkspaceSyncPayloadModel(input) {
460
549
  const previousTaskEventWatermarks = input.lastPushedTaskEventWatermarks;
461
550
  const shouldSendAllTaskEvents = !previousTaskEventWatermarks || previousTaskEventWatermarks.size === 0;
462
551
  const taskEventUpsertChanges = taskEvents
463
- .map((event) => ({
464
- op: 'task-event-upsert',
465
- event: {
552
+ .map((event) => {
553
+ const entityType = event.entityType === 'initiative' || event.entityType === 'workstream'
554
+ ? event.entityType
555
+ : 'task';
556
+ const entityId = String(event.entityId || event.taskId || '').trim();
557
+ const normalizedEvent = {
466
558
  id: String(event.id || '').trim(),
467
- taskId: String(event.taskId || '').trim(),
468
559
  workspaceId: String(event.workspaceId || '').trim(),
560
+ entityType,
561
+ entityId,
562
+ taskId: entityType === 'task' ? String(event.taskId || entityId).trim() : undefined,
469
563
  action: String(event.action || '').trim(),
470
564
  actor: String(event.actor || 'system').trim() || 'system',
471
565
  actorType: event.actorType === 'ai' || event.actorType === 'human' || event.actorType === 'system'
@@ -475,10 +569,28 @@ export function buildWorkspaceSyncPayloadModel(input) {
475
569
  ? event.details
476
570
  : {},
477
571
  createdAt: String(event.createdAt || '').trim(),
572
+ };
573
+ if (entityType === 'task') {
574
+ return {
575
+ op: 'task-event-upsert',
576
+ event: {
577
+ ...normalizedEvent,
578
+ entityType: 'task',
579
+ taskId: String(normalizedEvent.taskId || entityId).trim(),
580
+ }
581
+ };
478
582
  }
479
- }))
583
+ return {
584
+ op: 'entity-event-upsert',
585
+ event: {
586
+ ...normalizedEvent,
587
+ entityType,
588
+ taskId: undefined,
589
+ }
590
+ };
591
+ })
480
592
  .filter((change) => (change.event.id.length > 0
481
- && change.event.taskId.length > 0
593
+ && change.event.entityId.length > 0
482
594
  && change.event.workspaceId.length > 0
483
595
  && change.event.action.length > 0
484
596
  && Number.isFinite(Date.parse(change.event.createdAt))))
@@ -517,22 +629,22 @@ export function buildWorkspaceSyncPayloadModel(input) {
517
629
  deletedAt: payloadBuiltAt
518
630
  })),
519
631
  ...documentReviewSessionUpsertChanges,
520
- ...Array.from(deleteDocumentReviewSessionIds).map((sessionId) => ({
632
+ ...documentReviewSessionDeleteChanges.map((change) => ({
521
633
  op: 'document-review-session-delete',
522
- sessionId,
523
- deletedAt: payloadBuiltAt
634
+ sessionId: change.sessionId,
635
+ deletedAt: change.deletedAt
524
636
  })),
525
637
  ...documentReviewCommentUpsertChanges,
526
- ...Array.from(deleteDocumentReviewCommentIds).map((commentId) => ({
638
+ ...documentReviewCommentDeleteChanges.map((change) => ({
527
639
  op: 'document-review-comment-delete',
528
- commentId,
529
- deletedAt: payloadBuiltAt
640
+ commentId: change.commentId,
641
+ deletedAt: change.deletedAt
530
642
  })),
531
643
  ...annotatedAttachmentSessionUpsertChanges,
532
- ...Array.from(deleteAnnotatedAttachmentSessionIds).map((sessionId) => ({
644
+ ...annotatedAttachmentSessionDeleteChanges.map((change) => ({
533
645
  op: 'annotated-attachment-session-delete',
534
- sessionId,
535
- deletedAt: payloadBuiltAt
646
+ sessionId: change.sessionId,
647
+ deletedAt: change.deletedAt
536
648
  })),
537
649
  ...taskEventUpsertChanges,
538
650
  ...activeChanges,
@@ -694,6 +806,7 @@ export function buildWorkspaceSyncSignatureModel(input) {
694
806
  updatedAt: String(initiative.updatedAt || initiative.createdAt || '').trim(),
695
807
  title: String(initiative.title || '').trim(),
696
808
  ownerId: typeof initiative.ownerId === 'string' ? initiative.ownerId.trim() : '',
809
+ attachments: clonePlanningAttachmentsForSync(initiative.attachments) || [],
697
810
  isArchived: Boolean(initiative.isArchived)
698
811
  }))
699
812
  .filter((initiative) => initiative.id.length > 0)
@@ -705,6 +818,7 @@ export function buildWorkspaceSyncSignatureModel(input) {
705
818
  initiativeId: typeof workstream.initiativeId === 'string' ? workstream.initiativeId.trim() : '',
706
819
  title: String(workstream.title || '').trim(),
707
820
  ownerId: typeof workstream.ownerId === 'string' ? workstream.ownerId.trim() : '',
821
+ attachments: clonePlanningAttachmentsForSync(workstream.attachments) || [],
708
822
  isArchived: Boolean(workstream.isArchived)
709
823
  }))
710
824
  .filter((workstream) => workstream.id.length > 0)