@taskforcehq/taskforce 0.3.304 → 0.3.306

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 (383) hide show
  1. package/README.md +140 -247
  2. package/dist/Taskforce.module.css +295 -89
  3. package/dist/TaskforceCore.js +378 -111
  4. package/dist/TaskforceCore.test.js +1221 -107
  5. package/dist/assets/fonts/CourierPrime-Bold.woff2 +0 -0
  6. package/dist/assets/fonts/CourierPrime-BoldItalic.woff2 +0 -0
  7. package/dist/assets/fonts/CourierPrime-Italic.woff2 +0 -0
  8. package/dist/assets/fonts/CourierPrime-Regular.woff2 +0 -0
  9. package/dist/assets/fonts/Noir_medium.woff2 +0 -0
  10. package/dist/assets/fonts/Noir_regular.woff2 +0 -0
  11. package/dist/assets/fonts/RussoOne-Regular.woff2 +0 -0
  12. package/dist/assets/fonts/SpecialElite-Regular.woff2 +0 -0
  13. package/dist/compat/workspaceSyncCompat.js +52 -2
  14. package/dist/compat/workspaceSyncCompat.test.js +38 -1
  15. package/dist/components/context/ContextAttachmentManager.d.ts +14 -0
  16. package/dist/components/context/ContextAttachmentManager.js +549 -0
  17. package/dist/components/features/AgentsModule.d.ts +10 -1
  18. package/dist/components/features/AgentsModule.js +260 -15
  19. package/dist/components/features/AgentsModule.test.js +255 -5
  20. package/dist/components/features/DocumentIndex.d.ts +1 -1
  21. package/dist/components/features/DocumentIndex.js +1 -1
  22. package/dist/components/features/DocumentViewer.d.ts +6 -2
  23. package/dist/components/features/DocumentViewer.js +90 -10
  24. package/dist/components/features/DocumentWorkspace.d.ts +5 -22
  25. package/dist/components/features/DocumentWorkspace.js +12 -155
  26. package/dist/components/features/DocumentWorkspace.test.js +226 -6
  27. package/dist/components/features/TaskSettings.js +29 -3
  28. package/dist/components/features/TaskSettings.test.js +42 -1
  29. package/dist/components/features/documentWorkspaceModel.d.ts +24 -0
  30. package/dist/components/features/documentWorkspaceModel.js +57 -0
  31. package/dist/components/features/useDocumentWorkspaceController.d.ts +34 -0
  32. package/dist/components/features/useDocumentWorkspaceController.js +113 -0
  33. package/dist/components/task/TaskCard.js +19 -28
  34. package/dist/components/task/TaskCard.test.js +38 -3
  35. package/dist/components/task/TaskContextUpload.js +4 -526
  36. package/dist/components/task/TaskForm.d.ts +8 -8
  37. package/dist/components/task/TaskForm.js +362 -230
  38. package/dist/components/task/TaskForm.test.js +558 -381
  39. package/dist/components/task/TaskKanban.d.ts +2 -1
  40. package/dist/components/task/TaskKanban.js +96 -14
  41. package/dist/components/task/TaskKanban.test.js +36 -0
  42. package/dist/components/ui/EditableAvatarButton.d.ts +18 -0
  43. package/dist/components/ui/EditableAvatarButton.js +18 -0
  44. package/dist/components/views/PlanComparisonPage.d.ts +9 -1
  45. package/dist/components/views/PlanComparisonPage.js +70 -17
  46. package/dist/components/views/PlanComparisonPage.test.js +289 -41
  47. package/dist/components/views/PlansPage.js +239 -106
  48. package/dist/components/views/PlansPage.test.js +593 -42
  49. package/dist/components/views/StandaloneLayout.js +310 -554
  50. package/dist/components/views/panels/FilterToolbar.test.js +6 -4
  51. package/dist/components/views/panels/PlanningDrawer.d.ts +8 -1
  52. package/dist/components/views/panels/PlanningDrawer.js +18 -11
  53. package/dist/components/views/panels/PlanningDrawer.test.d.ts +1 -0
  54. package/dist/components/views/panels/PlanningDrawer.test.js +141 -0
  55. package/dist/components/views/planningScope.d.ts +20 -0
  56. package/dist/components/views/planningScope.js +25 -0
  57. package/dist/components/views/planningScope.test.d.ts +1 -0
  58. package/dist/components/views/planningScope.test.js +60 -0
  59. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +27 -14
  60. package/dist/components/views/standalone/modals/AccountHubModal.js +87 -15
  61. package/dist/components/views/standalone/modals/AccountHubModal.test.js +42 -2
  62. package/dist/components/views/standalone/modals/AvatarImageManagerModal.d.ts +20 -0
  63. package/dist/components/views/standalone/modals/AvatarImageManagerModal.js +127 -0
  64. package/dist/components/views/standalone/modals/EditProfileModal.d.ts +2 -9
  65. package/dist/components/views/standalone/modals/EditProfileModal.js +3 -2
  66. package/dist/components/views/standalone/modals/SyncEnableWarningModal.d.ts +9 -0
  67. package/dist/components/views/standalone/modals/SyncEnableWarningModal.js +6 -0
  68. package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.d.ts +1 -0
  69. package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.js +24 -0
  70. package/dist/components/views/standalone/modals/SyncStatusModal.d.ts +2 -1
  71. package/dist/components/views/standalone/modals/SyncStatusModal.js +2 -2
  72. package/dist/config/envSchema.js +1 -1
  73. package/dist/core/AiProfileService.d.ts +45 -3
  74. package/dist/core/AiProfileService.js +774 -63
  75. package/dist/core/AiProfileService.test.js +22 -2
  76. package/dist/core/AiProfiles.test.js +761 -2
  77. package/dist/core/AttachmentLinkService.js +57 -15
  78. package/dist/core/AuthIdentityService.d.ts +107 -0
  79. package/dist/core/AuthIdentityService.js +284 -0
  80. package/dist/core/AuthTokenService.d.ts +4 -0
  81. package/dist/core/AuthTokenService.js +34 -6
  82. package/dist/core/AuthTokenService.test.d.ts +1 -0
  83. package/dist/core/AuthTokenService.test.js +78 -0
  84. package/dist/core/EntitlementsPolicy.test.js +77 -15
  85. package/dist/core/GlobalSettingsService.js +115 -6
  86. package/dist/core/PlanEntitlementService.d.ts +30 -3
  87. package/dist/core/PlanEntitlementService.js +454 -93
  88. package/dist/core/PlanFeatureCatalog.test.d.ts +1 -0
  89. package/dist/core/PlanFeatureCatalog.test.js +84 -0
  90. package/dist/core/PlanVersionPolicy.test.js +54 -28
  91. package/dist/core/PlanningEntities.test.js +52 -0
  92. package/dist/core/SignupMonetizationSettings.test.js +241 -44
  93. package/dist/core/SyncReconciliationService.js +2 -65
  94. package/dist/core/SystemAdmin.test.js +244 -88
  95. package/dist/core/TaskAttachmentsCanonical.test.js +51 -1
  96. package/dist/core/TaskTaxonomyValidation.test.js +53 -0
  97. package/dist/core/Taskforce.d.ts +228 -14
  98. package/dist/core/Taskforce.js +814 -201
  99. package/dist/core/Taskforce.listTasksSlim.test.d.ts +1 -0
  100. package/dist/core/Taskforce.listTasksSlim.test.js +91 -0
  101. package/dist/core/UserProfileAvatarDrafts.test.js +30 -3
  102. package/dist/core/WorkspaceLifecycleService.js +0 -5
  103. package/dist/core/WorkspacePermissions.test.js +25 -1
  104. package/dist/core/WorkspacePlanMode.test.js +128 -27
  105. package/dist/core/shared.d.ts +3 -1
  106. package/dist/core/shared.js +16 -1
  107. package/dist/core/types.d.ts +68 -2
  108. package/dist/hooks/auth/useTaskforceAuthBootstrap.js +26 -1
  109. package/dist/hooks/sync/auth.js +3 -1
  110. package/dist/hooks/sync/bootstrap.js +2 -0
  111. package/dist/hooks/sync/controlPlane.d.ts +37 -0
  112. package/dist/hooks/sync/controlPlane.js +78 -0
  113. package/dist/hooks/sync/controlPlane.test.d.ts +1 -0
  114. package/dist/hooks/sync/controlPlane.test.js +121 -0
  115. package/dist/hooks/sync/lifecyclePolicy.d.ts +45 -0
  116. package/dist/hooks/sync/lifecyclePolicy.js +93 -0
  117. package/dist/hooks/sync/lifecyclePolicy.test.d.ts +1 -0
  118. package/dist/hooks/sync/lifecyclePolicy.test.js +124 -0
  119. package/dist/hooks/sync/orchestratorShared.d.ts +10 -1
  120. package/dist/hooks/sync/orchestratorShared.js +68 -47
  121. package/dist/hooks/sync/orchestratorShared.test.js +50 -1
  122. package/dist/hooks/sync/pullLifecycle.d.ts +31 -0
  123. package/dist/hooks/sync/pullLifecycle.js +24 -0
  124. package/dist/hooks/sync/pullLifecycle.test.d.ts +1 -0
  125. package/dist/hooks/sync/pullLifecycle.test.js +80 -0
  126. package/dist/hooks/sync/recovery.d.ts +16 -2
  127. package/dist/hooks/sync/recovery.js +171 -53
  128. package/dist/hooks/sync/recovery.test.d.ts +1 -0
  129. package/dist/hooks/sync/recovery.test.js +292 -0
  130. package/dist/hooks/sync/transfers.d.ts +16 -3
  131. package/dist/hooks/sync/transfers.js +186 -65
  132. package/dist/hooks/sync/transfers.test.d.ts +1 -0
  133. package/dist/hooks/sync/transfers.test.js +396 -0
  134. package/dist/hooks/sync/useRecentSyncEvents.d.ts +21 -0
  135. package/dist/hooks/sync/useRecentSyncEvents.js +92 -0
  136. package/dist/hooks/sync/useRecentSyncEvents.test.d.ts +1 -0
  137. package/dist/hooks/sync/useRecentSyncEvents.test.js +124 -0
  138. package/dist/hooks/sync/useSyncStatusActions.d.ts +30 -0
  139. package/dist/hooks/sync/useSyncStatusActions.js +88 -0
  140. package/dist/hooks/sync/useSyncStatusActions.test.d.ts +1 -0
  141. package/dist/hooks/sync/useSyncStatusActions.test.js +133 -0
  142. package/dist/hooks/sync/useSyncStatusControls.d.ts +25 -0
  143. package/dist/hooks/sync/useSyncStatusControls.js +60 -0
  144. package/dist/hooks/sync/useSyncStatusControls.test.d.ts +1 -0
  145. package/dist/hooks/sync/useSyncStatusControls.test.js +88 -0
  146. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +12 -0
  147. package/dist/hooks/useSyncOrchestrator.d.ts +16 -6
  148. package/dist/hooks/useSyncOrchestrator.js +424 -175
  149. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +487 -10
  150. package/dist/hooks/useTaskData.js +8 -3
  151. package/dist/hooks/useTaskData.test.js +45 -0
  152. package/dist/hooks/useTaskMutations.d.ts +2 -1
  153. package/dist/hooks/useTaskMutations.js +12 -1
  154. package/dist/hooks/useTaskMutations.test.js +30 -1
  155. package/dist/hooks/useTaskforce.d.ts +65 -4
  156. package/dist/hooks/useTaskforce.js +564 -243
  157. package/dist/hooks/useTaskforce.runtime-routing.test.d.ts +1 -0
  158. package/dist/hooks/useTaskforce.runtime-routing.test.js +152 -0
  159. package/dist/hooks/useTaskforce.sync-behavior.test.js +2454 -208
  160. package/dist/hooks/useWorkspaceSyncController.d.ts +4 -1
  161. package/dist/hooks/useWorkspaceSyncController.js +17 -2
  162. package/dist/hooks/useWorkspaceSyncController.test.js +1 -0
  163. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +16 -0
  164. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +316 -35
  165. package/dist/localization/locales/en-US.d.ts +0 -1
  166. package/dist/localization/locales/en-US.js +0 -1
  167. package/dist/localization/locales/es-419.js +0 -1
  168. package/dist/localization/locales/pt-BR.js +0 -1
  169. package/dist/mcp/adminWorkspaceRegistrar.d.ts +2 -0
  170. package/dist/mcp/adminWorkspaceRegistrar.js +154 -0
  171. package/dist/mcp/collaborationRegistrar.d.ts +2 -0
  172. package/dist/mcp/collaborationRegistrar.js +71 -0
  173. package/dist/mcp/documentAssetRegistrar.d.ts +2 -0
  174. package/dist/mcp/documentAssetRegistrar.js +346 -0
  175. package/dist/mcp/runtime.d.ts +2 -0
  176. package/dist/mcp/runtime.js +2433 -3534
  177. package/dist/mcp/runtime.test.js +440 -2
  178. package/dist/mcp/taskPlanningRegistrar.d.ts +2 -0
  179. package/dist/mcp/taskPlanningRegistrar.js +418 -0
  180. package/dist/mcp/toolCatalog.d.ts +35 -0
  181. package/dist/mcp/toolCatalog.js +3 -0
  182. package/dist/migrations/taskSchemaMigrations.d.ts +1 -1
  183. package/dist/migrations/taskSchemaMigrations.js +171 -61
  184. package/dist/migrations/taskSchemaMigrations.test.js +15 -0
  185. package/dist/resources/templates/workflow-sources/workflowDocs.mjs +6 -6
  186. package/dist/resources/templates/workflows/collaborate.yaml +1 -1
  187. package/dist/resources/templates/workflows/evaluate.yaml +2 -2
  188. package/dist/resources/templates/workflows/plan.yaml +1 -1
  189. package/dist/resources/templates/workflows/review.yaml +2 -2
  190. package/dist/server/annotatedAttachmentsRoutes.test.js +3 -3
  191. package/dist/server/auth/providers/apple.d.ts +33 -0
  192. package/dist/server/auth/providers/apple.js +82 -0
  193. package/dist/server/auth/providers/appleClientSecret.d.ts +37 -0
  194. package/dist/server/auth/providers/appleClientSecret.js +65 -0
  195. package/dist/server/auth/providers/github.d.ts +26 -0
  196. package/dist/server/auth/providers/github.js +86 -0
  197. package/dist/server/auth/providers/google.d.ts +21 -0
  198. package/dist/server/auth/providers/google.js +56 -0
  199. package/dist/server/auth/providers/types.d.ts +21 -0
  200. package/dist/server/auth/providers/types.js +1 -0
  201. package/dist/server/auth.d.ts +2 -0
  202. package/dist/server/auth.js +6 -3
  203. package/dist/server/documentReviewRoutes.test.js +36 -3
  204. package/dist/server/index.cookieProxy.test.js +1 -0
  205. package/dist/server/index.d.ts +12 -0
  206. package/dist/server/index.js +120 -9
  207. package/dist/server/index.rateLimit.test.js +3 -0
  208. package/dist/server/index.test.js +106 -3
  209. package/dist/server/routes/admin.d.ts +4 -5
  210. package/dist/server/routes/admin.js +410 -80
  211. package/dist/server/routes/annotatedAttachments.d.ts +1 -1
  212. package/dist/server/routes/annotatedAttachments.js +1 -1
  213. package/dist/server/routes/auth.d.ts +16 -6
  214. package/dist/server/routes/auth.js +1015 -118
  215. package/dist/server/routes/authSupport.d.ts +30 -0
  216. package/dist/server/routes/authSupport.js +81 -0
  217. package/dist/server/routes/billing.d.ts +1 -1
  218. package/dist/server/routes/billing.js +276 -34
  219. package/dist/server/routes/billing.test.js +760 -52
  220. package/dist/server/routes/documentReviews.d.ts +1 -1
  221. package/dist/server/routes/documentReviews.js +1 -1
  222. package/dist/server/routes/documents.d.ts +5 -2
  223. package/dist/server/routes/documents.js +242 -74
  224. package/dist/server/routes/primitives.d.ts +11 -0
  225. package/dist/server/routes/primitives.js +73 -0
  226. package/dist/server/routes/resources.d.ts +1 -1
  227. package/dist/server/routes/resources.js +1 -1
  228. package/dist/server/routes/shared.d.ts +29 -3
  229. package/dist/server/routes/shared.js +69 -4
  230. package/dist/server/routes/sync.d.ts +1 -1
  231. package/dist/server/routes/sync.integration.test.js +437 -39
  232. package/dist/server/routes/sync.js +62 -606
  233. package/dist/server/routes/syncAuxRoutes.d.ts +15 -0
  234. package/dist/server/routes/syncAuxRoutes.js +91 -0
  235. package/dist/server/routes/syncAuxRoutes.test.d.ts +1 -0
  236. package/dist/server/routes/syncAuxRoutes.test.js +158 -0
  237. package/dist/server/routes/syncPullApplyRoutes.d.ts +36 -0
  238. package/dist/server/routes/syncPullApplyRoutes.js +204 -0
  239. package/dist/server/routes/syncPushRoutes.d.ts +24 -0
  240. package/dist/server/routes/syncPushRoutes.js +212 -0
  241. package/dist/server/routes/syncRouteGuards.d.ts +36 -0
  242. package/dist/server/routes/syncRouteGuards.js +67 -0
  243. package/dist/server/routes/syncRouteGuards.test.d.ts +1 -0
  244. package/dist/server/routes/syncRouteGuards.test.js +94 -0
  245. package/dist/server/routes/syncRouteTypes.d.ts +13 -0
  246. package/dist/server/routes/syncRouteTypes.js +1 -0
  247. package/dist/server/routes/syncSnapshotRoutes.d.ts +66 -0
  248. package/dist/server/routes/syncSnapshotRoutes.js +180 -0
  249. package/dist/server/routes/tasks.d.ts +1 -1
  250. package/dist/server/routes/tasks.js +44 -2
  251. package/dist/server/routes/workspaces.d.ts +1 -1
  252. package/dist/server/routes/workspaces.js +37 -6
  253. package/dist/server/routes.d.ts +2 -9
  254. package/dist/server/routes.js +136 -165
  255. package/dist/server/routes.test.js +2457 -162
  256. package/dist/services/workflowExportSnapshots.test.js +2 -0
  257. package/dist/shared/aiProfileSeatScope.d.ts +5 -0
  258. package/dist/shared/aiProfileSeatScope.js +21 -0
  259. package/dist/shared/passwordPolicy.d.ts +13 -0
  260. package/dist/shared/passwordPolicy.js +84 -0
  261. package/dist/shared/passwordPolicy.test.d.ts +1 -0
  262. package/dist/shared/passwordPolicy.test.js +34 -0
  263. package/dist/storage/workspaceAssetStore.d.ts +15 -3
  264. package/dist/storage/workspaceAssetStore.js +132 -44
  265. package/dist/storage/workspaceAssetStore.test.js +93 -6
  266. package/dist/styles/fonts.css +70 -0
  267. package/dist/sync/collaborationSyncPayload.d.ts +6 -1
  268. package/dist/sync/collaborationSyncPayload.js +17 -11
  269. package/dist/sync/collaborationSyncPayload.test.js +13 -1
  270. package/dist/sync/contentSyncState.d.ts +4 -8
  271. package/dist/sync/contentSyncState.js +33 -77
  272. package/dist/sync/syncApplyHandlers.d.ts +7 -0
  273. package/dist/sync/syncApplyHandlers.js +28 -13
  274. package/dist/sync/syncApplyHandlers.test.js +72 -3
  275. package/dist/sync/syncFieldSemantics.d.ts +12 -0
  276. package/dist/sync/syncFieldSemantics.js +55 -0
  277. package/dist/sync/syncResourceAdapters.d.ts +44 -0
  278. package/dist/sync/syncResourceAdapters.js +77 -0
  279. package/dist/sync/syncService.d.ts +6 -0
  280. package/dist/sync/syncService.js +60 -0
  281. package/dist/sync/syncService.test.js +71 -3
  282. package/dist/sync/taskSyncChangeKey.d.ts +2 -0
  283. package/dist/sync/taskSyncChangeKey.js +143 -0
  284. package/dist/sync/taskSyncPayload.js +3 -8
  285. package/dist/sync/workspacePullFeed.js +4 -3
  286. package/dist/sync/workspacePullFeed.test.js +43 -0
  287. package/dist/sync/workspaceRepair.js +5 -2
  288. package/dist/sync/workspaceSyncModel.d.ts +48 -0
  289. package/dist/sync/workspaceSyncModel.js +195 -31
  290. package/dist/sync/workspaceSyncModel.test.js +325 -12
  291. package/dist/sync/workspaceSyncState.d.ts +19 -0
  292. package/dist/sync/workspaceSyncState.js +3 -1
  293. package/dist/sync/workspaceSyncSurface.js +3 -8
  294. package/dist/types.d.ts +17 -0
  295. package/dist/ui/assets/AgentsModule-DrLawwNl.js +1 -0
  296. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-BlS-cqnl.js → AnnotatedAttachmentWorkspace-Z9_whf7F.js} +1 -1
  297. package/dist/ui/assets/CourierPrime-Bold-BuLj_dpw.woff2 +0 -0
  298. package/dist/ui/assets/CourierPrime-BoldItalic-BrK2pUkZ.woff2 +0 -0
  299. package/dist/ui/assets/CourierPrime-Italic-DFUZK5Ey.woff2 +0 -0
  300. package/dist/ui/assets/CourierPrime-Regular-BsKphzVf.woff2 +0 -0
  301. package/dist/ui/assets/DocumentWorkspace-B03MaSkw.js +252 -0
  302. package/dist/ui/assets/DocumentWorkspace-Dhdso07p.css +1 -0
  303. package/dist/ui/assets/{InitiativesModule-BjR6iBf9.js → InitiativesModule-9E6ParrY.js} +1 -1
  304. package/dist/ui/assets/Noir_medium-bxQwKGzB.woff2 +0 -0
  305. package/dist/ui/assets/Noir_regular-ojf2kxlG.woff2 +0 -0
  306. package/dist/ui/assets/PlansPage-CvfnMiWq.css +1 -0
  307. package/dist/ui/assets/PlansPage-DRXscYxH.js +1 -0
  308. package/dist/ui/assets/RussoOne-Regular-Cu_qq_qC.woff2 +0 -0
  309. package/dist/ui/assets/SpecialElite-Regular-Di6gSvAS.woff2 +0 -0
  310. package/dist/ui/assets/TaskSettings-DZX4jk7e.js +9 -0
  311. package/dist/ui/assets/{WorkflowsModule-DnFqdUME.js → WorkflowsModule-BA7jcEpH.js} +1 -1
  312. package/dist/ui/assets/index-9eT8e0Lu.css +1 -0
  313. package/dist/ui/assets/index-C6k75jr8.js +6 -0
  314. package/dist/ui/assets/{vendor-icons-I7ZwG1z_.js → vendor-icons-DuEd_65c.js} +1 -1
  315. package/dist/ui/fonts/CourierPrime-Bold.ttf +0 -0
  316. package/dist/ui/fonts/CourierPrime-Bold.woff2 +0 -0
  317. package/dist/ui/fonts/CourierPrime-BoldItalic.ttf +0 -0
  318. package/dist/ui/fonts/CourierPrime-BoldItalic.woff2 +0 -0
  319. package/dist/ui/fonts/CourierPrime-Italic.ttf +0 -0
  320. package/dist/ui/fonts/CourierPrime-Italic.woff2 +0 -0
  321. package/dist/ui/fonts/CourierPrime-Regular.ttf +0 -0
  322. package/dist/ui/fonts/CourierPrime-Regular.woff2 +0 -0
  323. package/dist/ui/fonts/Noir_medium.otf +0 -0
  324. package/dist/ui/fonts/Noir_medium.woff2 +0 -0
  325. package/dist/ui/fonts/Noir_regular.otf +0 -0
  326. package/dist/ui/fonts/Noir_regular.woff2 +0 -0
  327. package/dist/ui/fonts/RussoOne-Regular.woff2 +0 -0
  328. package/dist/ui/fonts/SpecialElite-Regular.ttf +0 -0
  329. package/dist/ui/fonts/SpecialElite-Regular.woff2 +0 -0
  330. package/dist/ui/index.html +3 -3
  331. package/dist/utils/accountProfileSummaryCache.d.ts +5 -0
  332. package/dist/utils/assignees.d.ts +2 -0
  333. package/dist/utils/assignees.js +2 -0
  334. package/dist/utils/avatarUpload.d.ts +27 -0
  335. package/dist/utils/avatarUpload.js +132 -0
  336. package/dist/utils/avatarUpload.test.d.ts +1 -0
  337. package/dist/utils/avatarUpload.test.js +40 -0
  338. package/dist/utils/bootstrapTrace.d.ts +7 -0
  339. package/dist/utils/bootstrapTrace.js +29 -0
  340. package/dist/utils/commercialLifecyclePresentation.d.ts +19 -0
  341. package/dist/utils/commercialLifecyclePresentation.js +199 -0
  342. package/dist/utils/contextAssetEvents.d.ts +2 -0
  343. package/dist/utils/syncEventDetails.d.ts +3 -0
  344. package/dist/utils/syncEventDetails.js +64 -0
  345. package/dist/utils/syncEventDetails.test.d.ts +1 -0
  346. package/dist/utils/syncEventDetails.test.js +23 -0
  347. package/dist/utils/syncEventPresentation.d.ts +15 -0
  348. package/dist/utils/syncEventPresentation.js +129 -0
  349. package/dist/utils/syncEventPresentation.test.d.ts +1 -0
  350. package/dist/utils/syncEventPresentation.test.js +64 -0
  351. package/dist/utils/syncStatusPresentation.d.ts +72 -0
  352. package/dist/utils/syncStatusPresentation.js +217 -0
  353. package/dist/utils/syncStatusPresentation.test.d.ts +1 -0
  354. package/dist/utils/syncStatusPresentation.test.js +164 -0
  355. package/dist/utils/taskActivity.d.ts +2 -0
  356. package/dist/utils/taskActivity.js +228 -22
  357. package/dist/utils/taskActivity.test.js +119 -8
  358. package/dist/utils/workspaceSyncPresentation.d.ts +4 -0
  359. package/dist/utils/workspaceSyncPresentation.js +68 -2
  360. package/dist/utils/workspaceSyncPresentation.test.js +36 -1
  361. package/package.json +10 -2
  362. package/scripts/check-cloud-mcp.mjs +83 -0
  363. package/scripts/export-sqlite-to-postgres.mjs +7 -9
  364. package/scripts/playwright-auth.sh +5 -1
  365. package/scripts/run-billing-performance-smoke.sh +24 -0
  366. package/scripts/run-e2e-realtime.sh +17 -3
  367. package/scripts/run-smoke.sh +17 -5
  368. package/scripts/run-soak.sh +17 -5
  369. package/src/resources/templates/workflow-sources/workflowDocs.mjs +6 -6
  370. package/src/resources/templates/workflows/collaborate.yaml +1 -1
  371. package/src/resources/templates/workflows/evaluate.yaml +2 -2
  372. package/src/resources/templates/workflows/plan.yaml +1 -1
  373. package/src/resources/templates/workflows/review.yaml +2 -2
  374. package/dist/ui/assets/AgentsModule-BLWVbuKP.js +0 -1
  375. package/dist/ui/assets/DocumentWorkspace-BH_6DF6x.js +0 -250
  376. package/dist/ui/assets/DocumentWorkspace-C_8T8oz-.css +0 -1
  377. package/dist/ui/assets/PlansPage-C2dd2n1X.css +0 -1
  378. package/dist/ui/assets/PlansPage-CSDQ4sLa.js +0 -1
  379. package/dist/ui/assets/RussoOne-Regular-C3BxZIj7.ttf +0 -0
  380. package/dist/ui/assets/TaskSettings-CEDy34Jo.js +0 -9
  381. package/dist/ui/assets/index-DXUdtH1B.js +0 -6
  382. package/dist/ui/assets/index-Ii0B0rTO.css +0 -1
  383. package/scripts/migrate-planning-model.ts +0 -233
@@ -1,19 +1,20 @@
1
1
  import * as path from 'path';
2
- import { parseJsonBody } from '../routes.js';
2
+ import { parseJsonBody } from './primitives.js';
3
3
  import { normalizeTaskDataRelativePath, ObjectStorageClient } from '../../storage/objectStorageClient.js';
4
4
  import { WorkspaceAssetStore, } from '../../storage/workspaceAssetStore.js';
5
5
  import { resolveSessionCookieAttributesForRequest } from '../auth.js';
6
- import { decryptDocument, ENCRYPTION_PREFIX } from '../../sync/encryptionService.js';
7
6
  import { isAiProfileSurfaceType } from '../../shared/aiProfileSurfaceType.js';
8
- import { serializeTaskForSync, } from '../../sync/taskSyncPayload.js';
7
+ import { isAiProfileSeatScope } from '../../shared/aiProfileSeatScope.js';
9
8
  import { serializeBinaryAssetSyncPayload, serializeDocumentSyncPayload, } from '../../sync/contentSyncPayload.js';
10
9
  import { createContentSyncState } from '../../sync/contentSyncState.js';
11
10
  import { normalizeAiProfileSyncPayload, normalizeAnnotatedAttachmentSessionSyncPayload, normalizeDocumentReviewCommentSyncPayload, normalizeDocumentReviewSessionSyncPayload, } from '../../sync/collaborationSyncPayload.js';
12
11
  import { createCollaborationSyncState } from '../../sync/collaborationSyncState.js';
13
- import { buildWorkspacePullFeed } from '../../sync/workspacePullFeed.js';
14
12
  import { normalizeInitiativeSyncPayload, normalizeTaxonomySyncPayload, normalizeWorkstreamSyncPayload, } from '../../sync/planningSyncPayload.js';
15
13
  import { applyAiProfileAndMemberSyncChanges, applyCollaborationSyncChanges, applyContentSyncChanges, applyPlanningSyncChanges, applyPreparedTaskSyncSnapshot, applyTaskActivitySyncChanges, applyTaskSyncDeletes, classifyWorkspaceSyncChanges, prepareTaskSyncSnapshotApply, } from '../../sync/syncApplyHandlers.js';
16
- import { applyWorkspaceRepair } from '../../sync/workspaceRepair.js';
14
+ import { registerWorkspaceSyncAuxRoutes } from './syncAuxRoutes.js';
15
+ import { registerWorkspacePullApplySyncRoutes } from './syncPullApplyRoutes.js';
16
+ import { registerWorkspacePushSyncRoutes } from './syncPushRoutes.js';
17
+ import { registerWorkspaceSnapshotSyncRoutes } from './syncSnapshotRoutes.js';
17
18
  function decodeWorkspaceSyncCursor(raw) {
18
19
  const value = String(raw || '').trim();
19
20
  if (!value)
@@ -59,6 +60,20 @@ function normalizeIncomingAiProfileSurfaceType(value, context) {
59
60
  }
60
61
  return normalized;
61
62
  }
63
+ function normalizeIncomingAiProfileSeatScope(value, context) {
64
+ if (value === undefined || value === null)
65
+ return null;
66
+ const normalized = String(value || '').trim();
67
+ if (!normalized)
68
+ return null;
69
+ if (!isAiProfileSeatScope(normalized)) {
70
+ throw createSyncRouteError(`Invalid ai profile seatScope "${normalized}"`, {
71
+ ...context,
72
+ seatScope: normalized
73
+ }, { code: 'AI_PROFILE_SEAT_SCOPE_INVALID', statusCode: 400 });
74
+ }
75
+ return normalized;
76
+ }
62
77
  function normalizeTaskEventSyncPayload(raw, workspaceId, sourceWatermark) {
63
78
  if (!raw || typeof raw !== 'object' || Array.isArray(raw))
64
79
  return null;
@@ -155,7 +170,7 @@ export function registerSyncRoutes(deps) {
155
170
  const assertSyncedAiProfilesPersisted = (workspaceId, incomingAiProfiles) => {
156
171
  if (incomingAiProfiles.size === 0)
157
172
  return;
158
- const persistedIds = new Set(core.listAiProfiles(workspaceId)
173
+ const persistedIds = new Set(core.listAiProfiles(workspaceId, { includeArchived: true })
159
174
  .map((profile) => String(profile.id || '').trim())
160
175
  .filter(Boolean));
161
176
  for (const profile of incomingAiProfiles.values()) {
@@ -240,6 +255,7 @@ export function registerSyncRoutes(deps) {
240
255
  normalizeWorkspaceSyncDocumentPath,
241
256
  normalizeTaskAssetPath: (rawPath) => normalizeTaskDataRelativePath(String(rawPath || '').trim()),
242
257
  normalizeIncomingAiProfileSurfaceType,
258
+ normalizeIncomingAiProfileSeatScope,
243
259
  normalizeTaskEventSyncPayload,
244
260
  recordSyncDiagnostic,
245
261
  });
@@ -624,7 +640,7 @@ export function registerSyncRoutes(deps) {
624
640
  workstream
625
641
  })),
626
642
  ...incomingAiProfiles
627
- .map((profile) => normalizeAiProfileSyncPayload(profile, cloudWorkspaceId, normalizeIncomingAiProfileSurfaceType, 'push.aiProfileUpsert'))
643
+ .map((profile) => normalizeAiProfileSyncPayload(profile, cloudWorkspaceId, normalizeIncomingAiProfileSurfaceType, normalizeIncomingAiProfileSeatScope, 'push.aiProfileUpsert'))
628
644
  .filter((profile) => Boolean(profile && profile.id.length > 0))
629
645
  .map((profile) => ({
630
646
  op: 'ai-profile-upsert',
@@ -722,607 +738,47 @@ export function registerSyncRoutes(deps) {
722
738
  }));
723
739
  }
724
740
  });
725
- // GET /api/taskforce/sync/workspace/documents - Snapshot markdown documents only.
726
- // Binary image/file assets travel through the sibling /assets snapshot and asset-* sync ops.
727
- addRoute('GET', '/api/taskforce/sync/workspace/documents', async (req, res) => {
728
- const authConfig = context.authConfig;
729
- if (authConfig?.runtimeMode === 'cloud') {
730
- const access = resolveRequestAccess(req, authConfig);
731
- if (!access.authenticated || !access.userId || access.userId === 'anonymous') {
732
- res.writeHead(401, { 'Content-Type': 'application/json' });
733
- res.end(JSON.stringify({ success: false, error: 'Authentication required.' }));
734
- return;
735
- }
736
- }
737
- const workspaceId = requestWorkspaceId(req) || 'default';
738
- const basePath = core.getPaths().basePath;
739
- const documents = await contentSyncState.listWorkspaceSyncDocumentsSnapshot(basePath, workspaceId);
740
- const fingerprint = JSON.stringify(documents.map((doc) => [
741
- doc.path,
742
- doc.updatedAt,
743
- doc.content.length,
744
- doc.assetId || '',
745
- doc.documentId || '',
746
- String(doc.version ?? ''),
747
- doc.taskId || '',
748
- doc.linkRole || ''
749
- ].join(':')));
750
- res.writeHead(200, { 'Content-Type': 'application/json' });
751
- res.end(JSON.stringify({
752
- success: true,
753
- documents,
754
- fingerprint
755
- }));
756
- });
757
- // GET /api/taskforce/sync/workspace/ai-profiles - Snapshot workspace AI profiles for local->cloud sync.
758
- addRoute('GET', '/api/taskforce/sync/workspace/ai-profiles', async (req, res) => {
759
- const authConfig = context.authConfig;
760
- if (authConfig?.runtimeMode === 'cloud') {
761
- const access = resolveRequestAccess(req, authConfig);
762
- if (!access.authenticated || !access.userId || access.userId === 'anonymous') {
763
- res.writeHead(401, { 'Content-Type': 'application/json' });
764
- res.end(JSON.stringify({ success: false, error: 'Authentication required.' }));
765
- return;
766
- }
767
- }
768
- const workspaceId = requestWorkspaceId(req) || 'default';
769
- const aiProfiles = core.listAiProfiles(workspaceId);
770
- const fingerprint = JSON.stringify(aiProfiles.map((profile) => [
771
- profile.id,
772
- profile.updatedAt,
773
- profile.name,
774
- profile.username,
775
- profile.profileToken,
776
- profile.surfaceType ?? ''
777
- ].join(':')));
778
- res.writeHead(200, { 'Content-Type': 'application/json' });
779
- res.end(JSON.stringify({
780
- success: true,
781
- aiProfiles,
782
- fingerprint
783
- }));
784
- });
785
- // GET /api/taskforce/sync/workspace/document-reviews - Snapshot document review sessions for local->cloud sync.
786
- addRoute('GET', '/api/taskforce/sync/workspace/document-reviews', async (req, res) => {
787
- const authConfig = context.authConfig;
788
- if (authConfig?.runtimeMode === 'cloud') {
789
- const access = resolveRequestAccess(req, authConfig);
790
- if (!access.authenticated || !access.userId || access.userId === 'anonymous') {
791
- res.writeHead(401, { 'Content-Type': 'application/json' });
792
- res.end(JSON.stringify({ success: false, error: 'Authentication required.' }));
793
- return;
794
- }
795
- }
796
- const workspaceId = requestWorkspaceId(req) || 'default';
797
- const sessions = collaborationSyncState.listWorkspaceSyncDocumentReviewSessionsSnapshot(workspaceId);
798
- const comments = collaborationSyncState.listWorkspaceSyncDocumentReviewCommentsSnapshot(workspaceId);
799
- const fingerprint = JSON.stringify([
800
- ...sessions.map((session) => [
801
- 'session',
802
- session.id,
803
- session.updatedAt,
804
- session.status,
805
- session.comments.length
806
- ].join(':')),
807
- ...comments.map((comment) => [
808
- 'comment',
809
- comment.id,
810
- comment.sessionId,
811
- comment.updatedAt,
812
- comment.deletedAt || ''
813
- ].join(':'))
814
- ]);
815
- res.writeHead(200, { 'Content-Type': 'application/json' });
816
- res.end(JSON.stringify({
817
- success: true,
818
- sessions,
819
- comments,
820
- fingerprint
821
- }));
822
- });
823
- // GET /api/taskforce/sync/workspace/annotated-attachments - Snapshot Image Notes sessions for local->cloud sync.
824
- addRoute('GET', '/api/taskforce/sync/workspace/annotated-attachments', async (req, res) => {
825
- const authConfig = context.authConfig;
826
- if (authConfig?.runtimeMode === 'cloud') {
827
- const access = resolveRequestAccess(req, authConfig);
828
- if (!access.authenticated || !access.userId || access.userId === 'anonymous') {
829
- res.writeHead(401, { 'Content-Type': 'application/json' });
830
- res.end(JSON.stringify({ success: false, error: 'Authentication required.' }));
831
- return;
832
- }
833
- }
834
- const workspaceId = requestWorkspaceId(req) || 'default';
835
- const sessions = collaborationSyncState.listWorkspaceSyncAnnotatedAttachmentSessionsSnapshot(workspaceId);
836
- const fingerprint = JSON.stringify(sessions.map((session) => [
837
- session.id,
838
- session.updatedAt,
839
- session.taskId,
840
- session.baseImageAssetId,
841
- session.annotations.length,
842
- session.deletedAt || ''
843
- ].join(':')));
844
- res.writeHead(200, { 'Content-Type': 'application/json' });
845
- res.end(JSON.stringify({
846
- success: true,
847
- sessions,
848
- fingerprint
849
- }));
850
- });
851
- // GET /api/taskforce/sync/workspace/assets - Snapshot non-markdown local assets.
852
- // Markdown remains on the /documents snapshot so document encryption/version semantics stay isolated.
853
- addRoute('GET', '/api/taskforce/sync/workspace/assets', async (req, res) => {
854
- const authConfig = context.authConfig;
855
- if (authConfig?.runtimeMode === 'cloud') {
856
- const access = resolveRequestAccess(req, authConfig);
857
- if (!access.authenticated || !access.userId || access.userId === 'anonymous') {
858
- res.writeHead(401, { 'Content-Type': 'application/json' });
859
- res.end(JSON.stringify({ success: false, error: 'Authentication required.' }));
860
- return;
861
- }
862
- }
863
- const workspaceId = requestWorkspaceId(req) || 'default';
864
- const basePath = core.getPaths().basePath;
865
- const assets = await contentSyncState.listWorkspaceSyncBinaryAssetsSnapshot(basePath, workspaceId);
866
- const fingerprint = JSON.stringify(assets.map((asset) => [
867
- asset.path,
868
- asset.updatedAt,
869
- asset.contentBase64.length,
870
- asset.assetId || '',
871
- asset.kind,
872
- asset.mimeType,
873
- asset.taskId || '',
874
- asset.originalFilename || '',
875
- asset.linkRole || ''
876
- ].join(':')));
877
- res.writeHead(200, { 'Content-Type': 'application/json' });
878
- res.end(JSON.stringify({
879
- success: true,
880
- assets,
881
- fingerprint
882
- }));
883
- });
884
- // GET /api/taskforce/sync/workspace/pull - Incremental workspace task deltas with cursor paging
885
- addRoute('GET', '/api/taskforce/sync/workspace/pull', async (req, res) => {
886
- const authConfig = context.authConfig;
887
- if (!authConfig || !authConfig.enabled) {
888
- res.writeHead(400, { 'Content-Type': 'application/json' });
889
- res.end(JSON.stringify({ success: false, error: 'Sync is unavailable when auth is disabled.' }));
890
- return;
891
- }
892
- const access = resolveRequestAccess(req, authConfig);
893
- if (!access.authenticated || !access.userId || access.userId === 'anonymous') {
894
- auditAuthAction(req, 'sync-workspace-pull-unauthorized', {});
895
- res.writeHead(401, { 'Content-Type': 'application/json' });
896
- res.end(JSON.stringify({ success: false, error: 'Authentication required.' }));
897
- return;
898
- }
899
- const url = new URL(req.url || '/api/taskforce/sync/workspace/pull', 'http://localhost');
900
- const requestedWorkspaceId = String(url.searchParams.get('workspaceId') || '').trim();
901
- if (!requestedWorkspaceId) {
902
- auditAuthAction(req, 'sync-workspace-pull-missing-workspace', { code: 'WORKSPACE_ID_REQUIRED' });
903
- res.writeHead(400, { 'Content-Type': 'application/json' });
904
- res.end(JSON.stringify({
905
- success: false,
906
- error: 'workspaceId is required.',
907
- code: 'WORKSPACE_ID_REQUIRED',
908
- workspaceId: null
909
- }));
910
- return;
911
- }
912
- const allowed = core.listUserWorkspaces(access.userId).find((workspace) => workspace.id === requestedWorkspaceId);
913
- if (!allowed) {
914
- auditAuthAction(req, 'sync-workspace-pull-forbidden', {
915
- requestedWorkspaceId
916
- });
917
- res.writeHead(403, { 'Content-Type': 'application/json' });
918
- res.end(JSON.stringify({
919
- success: false,
920
- error: 'You do not have access to this workspace.',
921
- code: 'WORKSPACE_ACCESS_DENIED',
922
- workspaceId: requestedWorkspaceId
923
- }));
924
- return;
925
- }
926
- const targetWorkspaceId = requestedWorkspaceId;
927
- const cursor = decodeWorkspaceSyncCursor(url.searchParams.get('cursor'));
928
- const requestedLimit = Number.parseInt(String(url.searchParams.get('limit') || ''), 10);
929
- const pageSize = Number.isFinite(requestedLimit) && requestedLimit > 0
930
- ? Math.max(1, Math.min(500, Math.floor(requestedLimit)))
931
- : 200;
932
- const { workspaceMembers, changes, nextCursor, hasMore, diagnostics } = await buildWorkspacePullFeed({
933
- core,
934
- workspaceId: targetWorkspaceId,
935
- cursor,
936
- pageSize,
937
- encodeWorkspaceSyncCursor,
938
- contentSyncState,
939
- collaborationSyncState,
940
- });
941
- res.writeHead(200, { 'Content-Type': 'application/json' });
942
- res.end(JSON.stringify({
943
- success: true,
944
- workspaceId: targetWorkspaceId,
945
- workspaceMembers,
946
- changes,
947
- nextCursor,
948
- hasMore,
949
- diagnostics
950
- }));
951
- auditAuthAction(req, 'sync-workspace-pull-success', {
952
- workspaceId: targetWorkspaceId,
953
- returnedChanges: changes.length,
954
- hasMore,
955
- diagnostics
956
- });
957
- });
958
- // POST /api/taskforce/sync/workspace/apply-local - Apply pulled cloud deltas into local workspace store
959
- addRoute('POST', '/api/taskforce/sync/workspace/apply-local', async (req, res) => {
960
- const startedAtMs = Date.now();
961
- const authConfig = context.authConfig;
962
- if (authConfig?.runtimeMode === 'cloud') {
963
- res.writeHead(400, { 'Content-Type': 'application/json' });
964
- res.end(JSON.stringify({ success: false, error: 'Local apply endpoint is only available in local runtime.' }));
965
- return;
966
- }
967
- const body = await parseJsonBody(req, 50 * 1024 * 1024);
968
- const bodyWorkspaceId = String(body?.workspaceId || '').trim();
969
- const headerWorkspaceId = String(Array.isArray(req.headers['x-taskforce-workspace-id'])
970
- ? req.headers['x-taskforce-workspace-id'][0]
971
- : req.headers['x-taskforce-workspace-id'] || '').trim();
972
- let workspaceId = bodyWorkspaceId || headerWorkspaceId || requestWorkspaceId(req);
973
- if (authConfig?.enabled) {
974
- const access = resolveRequestAccess(req, authConfig);
975
- if (!access.authenticated || !access.userId || access.userId === 'anonymous') {
976
- auditAuthAction(req, 'sync-workspace-apply-unauthorized', {});
977
- res.writeHead(401, { 'Content-Type': 'application/json' });
978
- res.end(JSON.stringify({ success: false, error: 'Authentication required.' }));
979
- return;
980
- }
981
- if (workspaceId && workspaceId !== 'default' && workspaceId !== 'null') {
982
- const allowed = core.listUserWorkspaces(access.userId).find((workspace) => workspace.id === workspaceId);
983
- if (!allowed) {
984
- auditAuthAction(req, 'sync-workspace-apply-forbidden', {
985
- requestedWorkspaceId: workspaceId
986
- });
987
- res.writeHead(403, { 'Content-Type': 'application/json' });
988
- res.end(JSON.stringify({
989
- success: false,
990
- error: 'You do not have access to this workspace.',
991
- code: 'WORKSPACE_ACCESS_DENIED',
992
- workspaceId
993
- }));
994
- return;
995
- }
996
- }
997
- else {
998
- const accessWorkspaceId = String(access.workspaceId || '').trim();
999
- if (accessWorkspaceId && accessWorkspaceId !== 'default') {
1000
- workspaceId = accessWorkspaceId;
1001
- }
1002
- }
1003
- }
1004
- if (!workspaceId || workspaceId === 'default' || workspaceId === 'null') {
1005
- res.writeHead(400, { 'Content-Type': 'application/json' });
1006
- res.end(JSON.stringify({
1007
- success: false,
1008
- error: 'Workspace ID required.',
1009
- code: 'WORKSPACE_ID_REQUIRED'
1010
- }));
1011
- return;
1012
- }
1013
- const incomingChanges = Array.isArray(body?.changes) ? body.changes : [];
1014
- const workspaceMembers = Array.isArray(body?.workspaceMembers)
1015
- ? body.workspaceMembers
1016
- .map((entry) => normalizeWorkspaceMemberSyncSnapshot(workspaceId, entry && typeof entry === 'object' ? entry : null))
1017
- .filter((entry) => Boolean(entry))
1018
- : undefined;
1019
- const allowReferenceNumberReassignment = body?.repair === true;
1020
- try {
1021
- const { result, emittedEventIds } = await dispatchWorkspaceSyncChanges(workspaceId, incomingChanges, {
1022
- allowReferenceNumberReassignment,
1023
- ...(Array.isArray(workspaceMembers) ? { workspaceMembers } : {})
1024
- });
1025
- const durationMs = Date.now() - startedAtMs;
1026
- core.recordSyncEvent(workspaceId, {
1027
- eventType: 'apply',
1028
- status: 'success',
1029
- changeCount: incomingChanges.length,
1030
- requestMs: durationMs
1031
- });
1032
- res.writeHead(200, { 'Content-Type': 'application/json' });
1033
- res.end(JSON.stringify({
1034
- success: true,
1035
- workspaceId,
1036
- result,
1037
- emittedEventIds,
1038
- durationMs
1039
- }));
1040
- }
1041
- catch (error) {
1042
- const statusCode = resolveErrorStatusCode(error, 400);
1043
- const durationMs = Date.now() - startedAtMs;
1044
- core.recordSyncEvent(workspaceId, {
1045
- eventType: 'apply',
1046
- status: 'error',
1047
- statusCode,
1048
- errorMessage: String(error?.message || 'Failed to apply local workspace deltas'),
1049
- changeCount: incomingChanges.length,
1050
- requestMs: durationMs
1051
- });
1052
- res.writeHead(statusCode, { 'Content-Type': 'application/json' });
1053
- res.end(JSON.stringify({
1054
- success: false,
1055
- error: String(error?.message || 'Failed to apply local workspace deltas'),
1056
- code: error?.code,
1057
- details: error?.details || undefined,
1058
- durationMs
1059
- }));
1060
- }
1061
- });
1062
- // POST /api/taskforce/sync/workspace/push - Incremental workspace deltas from local to cloud
1063
- addRoute('POST', '/api/taskforce/sync/workspace/push', async (req, res) => {
1064
- const startedAtMs = Date.now();
1065
- const authConfig = context.authConfig;
1066
- if (!authConfig || !authConfig.enabled) {
1067
- res.writeHead(400, { 'Content-Type': 'application/json' });
1068
- res.end(JSON.stringify({ success: false, error: 'Sync is unavailable when auth is disabled.' }));
1069
- return;
1070
- }
1071
- const access = resolveRequestAccess(req, authConfig);
1072
- if (!access.authenticated || !access.userId || access.userId === 'anonymous') {
1073
- auditAuthAction(req, 'sync-workspace-push-unauthorized', {});
1074
- res.writeHead(401, { 'Content-Type': 'application/json' });
1075
- res.end(JSON.stringify({ success: false, error: 'Authentication required.' }));
1076
- return;
1077
- }
1078
- const body = await parseJsonBody(req, 50 * 1024 * 1024);
1079
- const requestedWorkspaceId = String(body?.workspaceId || '').trim();
1080
- if (!requestedWorkspaceId) {
1081
- auditAuthAction(req, 'sync-workspace-push-missing-workspace', { code: 'WORKSPACE_ID_REQUIRED' });
1082
- res.writeHead(400, { 'Content-Type': 'application/json' });
1083
- res.end(JSON.stringify({
1084
- success: false,
1085
- error: 'workspaceId is required.',
1086
- code: 'WORKSPACE_ID_REQUIRED',
1087
- workspaceId: null,
1088
- durationMs: Date.now() - startedAtMs
1089
- }));
1090
- return;
1091
- }
1092
- const allowed = core.listUserWorkspaces(access.userId).find((workspace) => workspace.id === requestedWorkspaceId);
1093
- if (!allowed) {
1094
- auditAuthAction(req, 'sync-workspace-push-forbidden', {
1095
- requestedWorkspaceId
1096
- });
1097
- res.writeHead(403, { 'Content-Type': 'application/json' });
1098
- res.end(JSON.stringify({
1099
- success: false,
1100
- error: 'You do not have access to this workspace.',
1101
- code: 'WORKSPACE_ACCESS_DENIED',
1102
- workspaceId: requestedWorkspaceId
1103
- }));
1104
- return;
1105
- }
1106
- const targetWorkspaceId = requestedWorkspaceId;
1107
- const idempotencyKey = String(body?.idempotencyKey || '').trim();
1108
- if (idempotencyKey) {
1109
- const replay = readPushIdempotency(targetWorkspaceId, idempotencyKey);
1110
- if (replay) {
1111
- auditAuthAction(req, 'sync-workspace-push-idempotent-replay', {
1112
- workspaceId: targetWorkspaceId,
1113
- idempotencyKey
1114
- });
1115
- res.writeHead(200, { 'Content-Type': 'application/json' });
1116
- res.end(replay);
1117
- return;
1118
- }
1119
- }
1120
- let incomingChanges = Array.isArray(body?.changes) ? body.changes : [];
1121
- const incomingTaskUpsertIds = incomingChanges
1122
- .filter((change) => change?.op === 'upsert')
1123
- .map((change) => String(change.task?.id || '').trim())
1124
- .filter((taskId) => taskId.length > 0);
1125
- const allowReferenceNumberReassignment = body?.repair === true;
1126
- // Decrypt asset and document content if TASKFORCE_SYNC_KEY is configured on this server.
1127
- // The local runtime encrypts content before pushing; the cloud must decrypt before writing to storage.
1128
- if (typeof process !== 'undefined' && process.env?.TASKFORCE_SYNC_KEY) {
1129
- const syncKey = process.env.TASKFORCE_SYNC_KEY;
1130
- const decryptedChanges = [];
1131
- for (const change of incomingChanges) {
1132
- if (change.op === 'asset-upsert' && typeof change.contentBase64 === 'string' && change.contentBase64.startsWith(ENCRYPTION_PREFIX)) {
1133
- try {
1134
- decryptedChanges.push({ ...change, contentBase64: await decryptDocument(change.contentBase64, syncKey) });
1135
- }
1136
- catch {
1137
- decryptedChanges.push(change);
1138
- }
1139
- }
1140
- else if (change.op === 'document-upsert' && typeof change.content === 'string' && change.content.startsWith(ENCRYPTION_PREFIX)) {
1141
- try {
1142
- decryptedChanges.push({ ...change, content: await decryptDocument(change.content, syncKey) });
1143
- }
1144
- catch {
1145
- decryptedChanges.push(change);
1146
- }
1147
- }
1148
- else {
1149
- decryptedChanges.push(change);
1150
- }
1151
- }
1152
- incomingChanges = decryptedChanges;
1153
- }
1154
- const receivedAtBaseMs = Date.now();
1155
- incomingChanges = incomingChanges.map((change, index) => {
1156
- if (change.op !== 'upsert')
1157
- return change;
1158
- const task = change.task;
1159
- const rawReferenceNumber = task.referenceNumber;
1160
- const incomingReferenceNumber = rawReferenceNumber === undefined || rawReferenceNumber === null
1161
- ? null
1162
- : (Number.isFinite(Number(rawReferenceNumber))
1163
- ? Math.max(1, Math.floor(Number(rawReferenceNumber)))
1164
- : null);
1165
- if (incomingReferenceNumber)
1166
- return change;
1167
- return {
1168
- ...change,
1169
- task: {
1170
- ...task,
1171
- updatedAt: new Date(receivedAtBaseMs + index).toISOString()
1172
- }
1173
- };
1174
- });
1175
- try {
1176
- const { result, deleteCount, emittedEventIds } = await dispatchWorkspaceSyncChanges(targetWorkspaceId, incomingChanges, {
1177
- allowReferenceNumberReassignment
1178
- });
1179
- const appliedTaskUpserts = incomingTaskUpsertIds
1180
- .map((taskId) => core.getTask(taskId, targetWorkspaceId))
1181
- .filter((task) => Boolean(task))
1182
- .map((task) => ({
1183
- archived: Boolean(task.isArchived),
1184
- task: serializeTaskForSync(task)
1185
- }));
1186
- const durationMs = Date.now() - startedAtMs;
1187
- const payload = JSON.stringify({
1188
- success: true,
1189
- workspaceId: targetWorkspaceId,
1190
- idempotencyKey: idempotencyKey || null,
1191
- emittedEventIds,
1192
- appliedTaskUpserts,
1193
- result,
1194
- durationMs
1195
- });
1196
- if (idempotencyKey) {
1197
- writePushIdempotency(targetWorkspaceId, idempotencyKey, payload);
1198
- }
1199
- core.recordSyncEvent(targetWorkspaceId, {
1200
- eventType: 'push',
1201
- status: 'success',
1202
- changeCount: incomingChanges.length,
1203
- requestMs: durationMs
1204
- });
1205
- res.writeHead(200, { 'Content-Type': 'application/json' });
1206
- res.end(payload);
1207
- auditAuthAction(req, 'sync-workspace-push-success', {
1208
- workspaceId: targetWorkspaceId,
1209
- idempotencyKey: idempotencyKey || null,
1210
- changeCount: incomingChanges.length,
1211
- deleteCount: deleteCount,
1212
- durationMs
1213
- });
1214
- }
1215
- catch (error) {
1216
- const statusCode = resolveErrorStatusCode(error, 400);
1217
- const durationMs = Date.now() - startedAtMs;
1218
- core.recordSyncEvent(targetWorkspaceId, {
1219
- eventType: 'push',
1220
- status: 'error',
1221
- statusCode,
1222
- errorMessage: String(error?.message || 'Failed to apply workspace deltas'),
1223
- changeCount: incomingChanges.length,
1224
- requestMs: durationMs
1225
- });
1226
- auditAuthAction(req, 'sync-workspace-push-failed', {
1227
- workspaceId: targetWorkspaceId,
1228
- code: error?.code || null,
1229
- statusCode,
1230
- details: error?.details || null,
1231
- durationMs
1232
- });
1233
- res.writeHead(statusCode, { 'Content-Type': 'application/json' });
1234
- res.end(JSON.stringify({
1235
- success: false,
1236
- error: String(error?.message || 'Failed to apply workspace deltas'),
1237
- code: error?.code,
1238
- details: error?.details || undefined,
1239
- durationMs
1240
- }));
1241
- }
741
+ registerWorkspaceSnapshotSyncRoutes({
742
+ addRoute,
743
+ core,
744
+ context,
745
+ resolveRequestAccess,
746
+ requestWorkspaceId,
747
+ contentSyncState,
748
+ collaborationSyncState
1242
749
  });
1243
- // GET /api/taskforce/sync/events - Query recent sync events (local runtime only)
1244
- addRoute('GET', '/api/taskforce/sync/events', async (req, res) => {
1245
- const authConfig = context.authConfig;
1246
- const access = authConfig?.enabled ? resolveRequestAccess(req, authConfig) : null;
1247
- if (access && (!access.authenticated || !access.userId || access.userId === 'anonymous')) {
1248
- res.writeHead(401, { 'Content-Type': 'application/json' });
1249
- res.end(JSON.stringify({ success: false, error: 'Authentication required.' }));
1250
- return;
1251
- }
1252
- const url = new URL(req.url || '/', 'http://localhost');
1253
- const workspaceId = String(url.searchParams.get('workspace_id') || access?.workspaceId || '').trim() || 'default';
1254
- const limit = Math.min(500, Math.max(1, Number(url.searchParams.get('limit') || '100')));
1255
- const events = core.listSyncEvents(workspaceId, limit);
1256
- res.writeHead(200, { 'Content-Type': 'application/json' });
1257
- res.end(JSON.stringify({ success: true, events }));
750
+ registerWorkspacePullApplySyncRoutes({
751
+ addRoute,
752
+ core,
753
+ context,
754
+ auditAuthAction,
755
+ resolveErrorStatusCode,
756
+ requestWorkspaceId,
757
+ resolveRequestAccess,
758
+ contentSyncState,
759
+ collaborationSyncState,
760
+ decodeWorkspaceSyncCursor,
761
+ encodeWorkspaceSyncCursor,
762
+ normalizeWorkspaceMemberSyncSnapshot,
763
+ dispatchWorkspaceSyncChanges
1258
764
  });
1259
- // POST /api/taskforce/sync/events - Record a client-side sync event (pull results, handshake outcomes)
1260
- addRoute('POST', '/api/taskforce/sync/events', async (req, res) => {
1261
- const authConfig = context.authConfig;
1262
- const access = authConfig?.enabled ? resolveRequestAccess(req, authConfig) : null;
1263
- if (access && (!access.authenticated || !access.userId || access.userId === 'anonymous')) {
1264
- res.writeHead(401, { 'Content-Type': 'application/json' });
1265
- res.end(JSON.stringify({ success: false, error: 'Authentication required.' }));
1266
- return;
1267
- }
1268
- const body = await parseJsonBody(req);
1269
- const allowedEventTypes = ['pull', 'handshake', 'bootstrap', 'apply', 'push'];
1270
- const eventType = String(body?.eventType || '').trim().toLowerCase();
1271
- if (!allowedEventTypes.includes(eventType)) {
1272
- res.writeHead(400, { 'Content-Type': 'application/json' });
1273
- res.end(JSON.stringify({ success: false, error: 'Invalid eventType.' }));
1274
- return;
1275
- }
1276
- const workspaceId = String(body?.workspaceId || access?.workspaceId || '').trim() || 'default';
1277
- core.recordSyncEvent(workspaceId, {
1278
- eventType: eventType,
1279
- status: String(body?.status || '') === 'error' ? 'error' : 'success',
1280
- statusCode: typeof body?.statusCode === 'number' ? body.statusCode : undefined,
1281
- errorMessage: typeof body?.errorMessage === 'string' ? body.errorMessage.slice(0, 500) : undefined,
1282
- changeCount: typeof body?.changeCount === 'number' ? body.changeCount : undefined,
1283
- requestMs: typeof body?.requestMs === 'number' ? body.requestMs : undefined,
1284
- details: body?.details && typeof body.details === 'object' && !Array.isArray(body.details)
1285
- ? body.details
1286
- : undefined
1287
- });
1288
- res.writeHead(200, { 'Content-Type': 'application/json' });
1289
- res.end(JSON.stringify({ success: true }));
765
+ registerWorkspacePushSyncRoutes({
766
+ addRoute,
767
+ core,
768
+ context,
769
+ auditAuthAction,
770
+ resolveErrorStatusCode,
771
+ resolveRequestAccess,
772
+ readPushIdempotency,
773
+ writePushIdempotency,
774
+ dispatchWorkspaceSyncChanges
1290
775
  });
1291
- // POST /api/taskforce/sync/workspace/repair-startup - Auto-repair missing asset/document
1292
- // metadata on workspace open. Only safe fixups: re-registers DB records for files that
1293
- // exist on disk but have no (or a stale deleted) metadata entry. Never deletes files.
1294
- // Called once by the sync orchestrator when the workspace becomes active.
1295
- addRoute('POST', '/api/taskforce/sync/workspace/repair-startup', async (req, res) => {
1296
- const authConfig = context.authConfig;
1297
- const access = authConfig?.enabled ? resolveRequestAccess(req, authConfig) : null;
1298
- if (access && (!access.authenticated || !access.userId || access.userId === 'anonymous')) {
1299
- res.writeHead(401, { 'Content-Type': 'application/json' });
1300
- res.end(JSON.stringify({ success: false, error: 'Authentication required.' }));
1301
- return;
1302
- }
1303
- const body = await parseJsonBody(req);
1304
- const workspaceId = String(body?.workspaceId || requestWorkspaceId(req) || '').trim() || 'default';
1305
- if (!workspaceAssetStore) {
1306
- res.writeHead(200, { 'Content-Type': 'application/json' });
1307
- res.end(JSON.stringify({ success: true, applied: 0, skipped: 0, failed: 0, detail: 'Asset store unavailable.' }));
1308
- return;
1309
- }
1310
- const { basePath } = core.getPaths();
1311
- const result = applyWorkspaceRepair({
1312
- workspaceId,
1313
- basePath,
1314
- tasks: core.listTasks(workspaceId)?.tasks || [],
1315
- archivedTasks: core.listArchive(workspaceId)?.archived || [],
1316
- deletedTasks: core.listWorkspaceSyncDeletes(workspaceId) || [],
1317
- workspaceAssetStore,
1318
- upsertDocumentWatermark: (targetWorkspaceId, docPath, contentHash, updatedAt) => {
1319
- core.upsertDocumentWatermark(targetWorkspaceId, docPath, contentHash, updatedAt);
1320
- }
1321
- });
1322
- const applied = result.actionsApplied.filter((a) => a.status === 'applied').length;
1323
- const failed = result.actionsApplied.filter((a) => a.status === 'failed').length;
1324
- const skipped = result.actionsApplied.filter((a) => a.status === 'skipped').length;
1325
- res.writeHead(200, { 'Content-Type': 'application/json' });
1326
- res.end(JSON.stringify({ success: true, applied, skipped, failed }));
776
+ registerWorkspaceSyncAuxRoutes({
777
+ addRoute,
778
+ core,
779
+ context,
780
+ resolveRequestAccess,
781
+ requestWorkspaceId,
782
+ workspaceAssetStore
1327
783
  });
1328
784
  }