@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,4 +1,6 @@
1
- import { describe, expect, it, vi } from 'vitest';
1
+ import fs from 'node:fs';
2
+ import { createHash } from 'node:crypto';
3
+ import { describe, expect, it, vi, afterEach } from 'vitest';
2
4
  import { applyCollaborationSyncChanges, applyContentSyncChanges, applyPreparedTaskSyncSnapshot, applyTaskSyncDeletes, classifyWorkspaceSyncChanges, prepareTaskSyncSnapshotApply, } from './syncApplyHandlers.js';
3
5
  function createCoreFixture(overrides = {}) {
4
6
  return {
@@ -29,6 +31,9 @@ function createCoreFixture(overrides = {}) {
29
31
  };
30
32
  }
31
33
  describe('syncApplyHandlers', () => {
34
+ afterEach(() => {
35
+ vi.restoreAllMocks();
36
+ });
32
37
  it('classifies task and workstream changes, then prepares and applies the task snapshot pipeline', () => {
33
38
  const diagnostics = [];
34
39
  const workspaceId = 'workspace-1';
@@ -76,15 +81,18 @@ describe('syncApplyHandlers', () => {
76
81
  {
77
82
  op: 'delete',
78
83
  taskId: 'task-1',
84
+ deletedAt: '2026-04-10T11:00:00.000Z',
79
85
  },
80
86
  {
81
87
  op: 'delete',
82
88
  taskId: 'task-stale',
89
+ deletedAt: '2026-04-10T08:00:00.000Z',
83
90
  },
84
91
  ],
85
92
  normalizeWorkspaceSyncDocumentPath: (rawPath) => typeof rawPath === 'string' ? rawPath : null,
86
93
  normalizeTaskAssetPath: (rawPath) => typeof rawPath === 'string' ? rawPath : null,
87
94
  normalizeIncomingAiProfileSurfaceType: () => null,
95
+ normalizeIncomingAiProfileSeatScope: () => null,
88
96
  normalizeTaskEventSyncPayload: () => null,
89
97
  recordSyncDiagnostic: (targetWorkspaceId, eventType, message, details) => {
90
98
  diagnostics.push({ workspaceId: targetWorkspaceId, eventType, message, details });
@@ -229,7 +237,7 @@ describe('syncApplyHandlers', () => {
229
237
  expect(core.notifyDocumentMetadataMutation).toHaveBeenNthCalledWith(1, 'workspace-1', ['docs/spec.md', 'assets/proof.png'], 'upsert');
230
238
  expect(core.notifyDocumentMetadataMutation).toHaveBeenNthCalledWith(2, 'workspace-1', ['docs/stale.md', 'assets/stale.png'], 'delete');
231
239
  });
232
- it('only allows reference reassignment for content writes when the content changed or repair is explicit', async () => {
240
+ it('allows reference reassignment when metadata drift requires normal sync convergence', async () => {
233
241
  const core = createCoreFixture({
234
242
  getDocumentWatermark: vi.fn().mockReturnValue(null),
235
243
  });
@@ -278,9 +286,70 @@ describe('syncApplyHandlers', () => {
278
286
  }),
279
287
  recordSyncDiagnostic: vi.fn(),
280
288
  });
281
- expect(writeWorkspaceSyncDocument).toHaveBeenCalledWith('/tmp/taskforce-sync-content', expect.objectContaining({ path: 'docs/spec.md' }), 'workspace-1', { allowReferenceNumberReassignment: false });
289
+ expect(writeWorkspaceSyncDocument).toHaveBeenCalledWith('/tmp/taskforce-sync-content', expect.objectContaining({ path: 'docs/spec.md' }), 'workspace-1', { allowReferenceNumberReassignment: true });
282
290
  expect(writeWorkspaceSyncBinaryAsset).toHaveBeenCalledWith('/tmp/taskforce-sync-content', expect.objectContaining({ path: 'assets/proof.png' }), 'workspace-1', { allowReferenceNumberReassignment: true });
283
291
  });
292
+ it('reuses persisted content hashes instead of re-reading unchanged local files', async () => {
293
+ const existingDocContent = '# Spec';
294
+ const existingAssetBytes = Buffer.from('png-bytes');
295
+ const core = createCoreFixture({
296
+ getDocumentWatermark: vi.fn().mockReturnValue({
297
+ updatedAt: '2026-04-10T10:00:00.000Z',
298
+ contentHash: createHash('sha256').update(existingDocContent).digest('hex'),
299
+ }),
300
+ });
301
+ const writeWorkspaceSyncDocument = vi.fn().mockResolvedValue(undefined);
302
+ const writeWorkspaceSyncBinaryAsset = vi.fn().mockResolvedValue(undefined);
303
+ const readSpy = vi.spyOn(fs, 'readFileSync').mockImplementation(((targetPath) => {
304
+ const value = String(targetPath);
305
+ if (value.endsWith('spec.md'))
306
+ return existingDocContent;
307
+ return existingAssetBytes;
308
+ }));
309
+ await applyContentSyncChanges({
310
+ core,
311
+ workspaceId: 'workspace-1',
312
+ incomingDocuments: [
313
+ {
314
+ path: 'docs/spec.md',
315
+ updatedAt: '2026-04-10T10:00:00.000Z',
316
+ content: existingDocContent,
317
+ assetId: 'asset-doc-1',
318
+ },
319
+ ],
320
+ incomingDocumentDeletes: new Map(),
321
+ incomingAssets: [
322
+ {
323
+ path: 'assets/proof.png',
324
+ updatedAt: '2026-04-10T10:05:00.000Z',
325
+ contentBase64: existingAssetBytes.toString('base64'),
326
+ assetId: 'asset-image-1',
327
+ kind: 'image',
328
+ mimeType: 'image/png',
329
+ },
330
+ ],
331
+ incomingAssetDeletes: new Map(),
332
+ basePath: '/tmp/taskforce-sync-content',
333
+ hasObjectStorageClient: () => true,
334
+ hasDocumentSyncMetadataDrift: () => false,
335
+ hasBinaryAssetSyncMetadataDrift: () => false,
336
+ getExistingDocument: () => {
337
+ throw new Error('document fallback should not be used when persisted metadata exists');
338
+ },
339
+ writeWorkspaceSyncDocument,
340
+ deleteWorkspaceSyncDocument: vi.fn(),
341
+ writeWorkspaceSyncBinaryAsset,
342
+ deleteWorkspaceSyncBinaryAsset: vi.fn(),
343
+ getExistingBinaryAsset: () => ({
344
+ updatedAt: '2026-04-10T10:05:00.000Z',
345
+ contentSha256: createHash('sha256').update(existingAssetBytes).digest('hex'),
346
+ }),
347
+ recordSyncDiagnostic: vi.fn(),
348
+ });
349
+ expect(readSpy).not.toHaveBeenCalled();
350
+ expect(writeWorkspaceSyncDocument).not.toHaveBeenCalled();
351
+ expect(writeWorkspaceSyncBinaryAsset).not.toHaveBeenCalled();
352
+ });
284
353
  it('applies collaboration upserts and deletes through the collaboration handlers', () => {
285
354
  const core = createCoreFixture();
286
355
  applyCollaborationSyncChanges({
@@ -0,0 +1,12 @@
1
+ export declare function hasOwnSyncField(source: unknown, field: string): boolean;
2
+ export declare function normalizeSyncOptionalString(value: unknown): string | undefined;
3
+ export declare function normalizeSyncNullableString(value: unknown): string | null;
4
+ export declare function normalizeSyncOptionalId(value: unknown): string | undefined;
5
+ export declare function normalizeSyncNullableId(value: unknown): string | null;
6
+ export declare function normalizeSyncNullableIsoString(value: unknown): string | null;
7
+ export declare function normalizeSyncSameOriginAssetUrl(value: unknown): string | null;
8
+ export declare function readSyncOptionalStringField(source: Record<string, unknown>, field: string): string | undefined;
9
+ export declare function readSyncNullableStringField(source: Record<string, unknown>, field: string): string | null | undefined;
10
+ export declare function readSyncNullableIdField(source: Record<string, unknown>, field: string): string | null | undefined;
11
+ export declare function readSyncNullableIsoStringField(source: Record<string, unknown>, field: string): string | null | undefined;
12
+ export declare function readSyncSameOriginAssetUrlField(source: Record<string, unknown>, field: string): string | null | undefined;
@@ -0,0 +1,55 @@
1
+ export function hasOwnSyncField(source, field) {
2
+ return Boolean(source) && typeof source === 'object' && Object.prototype.hasOwnProperty.call(source, field);
3
+ }
4
+ export function normalizeSyncOptionalString(value) {
5
+ if (value === undefined || value === null)
6
+ return undefined;
7
+ const normalized = String(value).trim();
8
+ return normalized.length > 0 ? normalized : undefined;
9
+ }
10
+ export function normalizeSyncNullableString(value) {
11
+ if (value === undefined || value === null)
12
+ return null;
13
+ const normalized = String(value).trim();
14
+ return normalized.length > 0 ? normalized : null;
15
+ }
16
+ export function normalizeSyncOptionalId(value) {
17
+ return normalizeSyncOptionalString(value);
18
+ }
19
+ export function normalizeSyncNullableId(value) {
20
+ return normalizeSyncNullableString(value);
21
+ }
22
+ export function normalizeSyncNullableIsoString(value) {
23
+ return normalizeSyncNullableString(value);
24
+ }
25
+ export function normalizeSyncSameOriginAssetUrl(value) {
26
+ const normalized = normalizeSyncNullableString(value);
27
+ if (!normalized)
28
+ return null;
29
+ return normalized.startsWith('/api/taskforce/context/') ? normalized : null;
30
+ }
31
+ export function readSyncOptionalStringField(source, field) {
32
+ if (!hasOwnSyncField(source, field))
33
+ return undefined;
34
+ return normalizeSyncOptionalString(source[field]);
35
+ }
36
+ export function readSyncNullableStringField(source, field) {
37
+ if (!hasOwnSyncField(source, field))
38
+ return undefined;
39
+ return normalizeSyncNullableString(source[field]);
40
+ }
41
+ export function readSyncNullableIdField(source, field) {
42
+ if (!hasOwnSyncField(source, field))
43
+ return undefined;
44
+ return normalizeSyncNullableId(source[field]);
45
+ }
46
+ export function readSyncNullableIsoStringField(source, field) {
47
+ if (!hasOwnSyncField(source, field))
48
+ return undefined;
49
+ return normalizeSyncNullableIsoString(source[field]);
50
+ }
51
+ export function readSyncSameOriginAssetUrlField(source, field) {
52
+ if (!hasOwnSyncField(source, field))
53
+ return undefined;
54
+ return normalizeSyncSameOriginAssetUrl(source[field]);
55
+ }
@@ -0,0 +1,44 @@
1
+ import type { TaskItem } from '../types.js';
2
+ export interface SyncAiProfileLike {
3
+ id?: string | null;
4
+ workspaceId?: string | null;
5
+ profileToken?: string | null;
6
+ name?: string | null;
7
+ username?: string | null;
8
+ icon?: string | null;
9
+ color?: string | null;
10
+ avatarUrl?: string | null;
11
+ avatarSourceUrl?: string | null;
12
+ description?: string | null;
13
+ role?: string | null;
14
+ provider?: string | null;
15
+ model?: string | null;
16
+ surfaceType?: string | null;
17
+ seatScope?: string | null;
18
+ providerMetadata?: Record<string, unknown> | null;
19
+ archivedAt?: string | null;
20
+ archivedBy?: string | null;
21
+ archivedReason?: string | null;
22
+ mergedIntoProfileId?: string | null;
23
+ rosterStateUpdatedAt?: string | null;
24
+ createdAt?: string | null;
25
+ updatedAt?: string | null;
26
+ lastActiveAt?: string | null;
27
+ }
28
+ export interface SyncResourceAdapter<T> {
29
+ kind: string;
30
+ getId: (resource: T) => string;
31
+ getWatermark: (resource: T) => string;
32
+ getChangeKey: (resource: T) => string;
33
+ }
34
+ export interface SyncResourceBaselineState {
35
+ watermarks?: Map<string, string> | null;
36
+ changeKeys?: Map<string, string> | null;
37
+ }
38
+ export declare function getAiProfileSyncWatermark(profile: Partial<SyncAiProfileLike> | null | undefined): string;
39
+ export declare function getAiProfileSyncChangeKey(profile: Partial<SyncAiProfileLike> | null | undefined): string;
40
+ export declare const taskSyncResourceAdapter: SyncResourceAdapter<Partial<TaskItem> & {
41
+ isArchived?: boolean | null;
42
+ }>;
43
+ export declare const aiProfileSyncResourceAdapter: SyncResourceAdapter<Partial<SyncAiProfileLike>>;
44
+ export declare function shouldSyncResource<T>(adapter: SyncResourceAdapter<T>, resource: T, baseline?: SyncResourceBaselineState | null): boolean;
@@ -0,0 +1,77 @@
1
+ import { buildTaskSyncChangeKey } from './taskSyncChangeKey.js';
2
+ import { normalizeSyncNullableIsoString, normalizeSyncNullableString, normalizeSyncOptionalId, normalizeSyncSameOriginAssetUrl, } from './syncFieldSemantics.js';
3
+ function normalizeSyncTimestampMax(...values) {
4
+ return values.reduce((latest, value) => {
5
+ const normalized = normalizeSyncNullableIsoString(value) || '';
6
+ if (!latest)
7
+ return normalized;
8
+ if (!normalized)
9
+ return latest;
10
+ return normalized > latest ? normalized : latest;
11
+ }, '');
12
+ }
13
+ export function getAiProfileSyncWatermark(profile) {
14
+ const source = profile || {};
15
+ return normalizeSyncTimestampMax(source.createdAt, source.updatedAt, source.lastActiveAt, source.archivedAt, source.rosterStateUpdatedAt);
16
+ }
17
+ export function getAiProfileSyncChangeKey(profile) {
18
+ const source = profile || {};
19
+ return JSON.stringify({
20
+ id: String(source.id || '').trim(),
21
+ workspaceId: String(source.workspaceId || '').trim(),
22
+ profileToken: String(source.profileToken || '').trim(),
23
+ name: String(source.name || '').trim(),
24
+ username: String(source.username || '').trim(),
25
+ icon: String(source.icon || 'Bot').trim() || 'Bot',
26
+ color: String(source.color || '#8b5cf6').trim() || '#8b5cf6',
27
+ avatarUrl: normalizeSyncSameOriginAssetUrl(source.avatarUrl) || '',
28
+ avatarSourceUrl: normalizeSyncSameOriginAssetUrl(source.avatarSourceUrl) || '',
29
+ description: normalizeSyncNullableString(source.description) || '',
30
+ role: normalizeSyncNullableString(source.role) || '',
31
+ provider: normalizeSyncNullableString(source.provider) || '',
32
+ model: normalizeSyncNullableString(source.model) || '',
33
+ surfaceType: normalizeSyncNullableString(source.surfaceType) || '',
34
+ seatScope: normalizeSyncNullableString(source.seatScope) || '',
35
+ providerMetadata: source.providerMetadata && typeof source.providerMetadata === 'object' && !Array.isArray(source.providerMetadata)
36
+ ? source.providerMetadata
37
+ : null,
38
+ archivedAt: normalizeSyncNullableIsoString(source.archivedAt) || '',
39
+ archivedBy: normalizeSyncOptionalId(source.archivedBy) || '',
40
+ archivedReason: normalizeSyncNullableString(source.archivedReason) || '',
41
+ mergedIntoProfileId: normalizeSyncOptionalId(source.mergedIntoProfileId) || '',
42
+ rosterStateUpdatedAt: normalizeSyncNullableIsoString(source.rosterStateUpdatedAt) || '',
43
+ createdAt: normalizeSyncNullableIsoString(source.createdAt) || '',
44
+ updatedAt: normalizeSyncNullableIsoString(source.updatedAt) || '',
45
+ lastActiveAt: normalizeSyncNullableIsoString(source.lastActiveAt) || '',
46
+ });
47
+ }
48
+ export const taskSyncResourceAdapter = {
49
+ kind: 'task',
50
+ getId: (task) => String(task?.id || '').trim(),
51
+ getWatermark: (task) => String(task?.updatedAt || task?.createdAt || '').trim(),
52
+ getChangeKey: (task) => buildTaskSyncChangeKey(task, Boolean(task?.isArchived))
53
+ };
54
+ export const aiProfileSyncResourceAdapter = {
55
+ kind: 'ai-profile',
56
+ getId: (profile) => String(profile?.id || '').trim(),
57
+ getWatermark: (profile) => getAiProfileSyncWatermark(profile),
58
+ getChangeKey: (profile) => getAiProfileSyncChangeKey(profile)
59
+ };
60
+ export function shouldSyncResource(adapter, resource, baseline) {
61
+ const id = adapter.getId(resource);
62
+ if (!id)
63
+ return false;
64
+ const currentChangeKey = adapter.getChangeKey(resource);
65
+ const previousWatermarks = baseline?.watermarks;
66
+ if (!previousWatermarks || previousWatermarks.size === 0)
67
+ return true;
68
+ if (!previousWatermarks.has(id))
69
+ return true;
70
+ const previousChangeKeys = baseline?.changeKeys;
71
+ const previousChangeKey = previousChangeKeys?.get(id);
72
+ if (typeof previousChangeKey === 'string') {
73
+ return previousChangeKey !== currentChangeKey;
74
+ }
75
+ const previousWatermark = previousWatermarks.get(id) || '';
76
+ return previousWatermark !== currentChangeKey;
77
+ }
@@ -7,6 +7,7 @@ export interface EnsureWorkspaceReadyInput {
7
7
  resolveCloudAuthUrl: (path: string) => string;
8
8
  workspaceId: string;
9
9
  workspaceName: string;
10
+ allowProvisionOnMismatch?: boolean;
10
11
  }
11
12
  export interface EnsureWorkspaceReadyResult {
12
13
  success: boolean;
@@ -55,6 +56,8 @@ export interface WorkspacePushSyncResult {
55
56
  changeCount: number;
56
57
  deleteCount: number;
57
58
  currentTaskIds: Set<string>;
59
+ currentTaskWatermarks?: Map<string, string>;
60
+ currentTaskChangeKeys?: Map<string, string>;
58
61
  deleteTaskIds: Set<string>;
59
62
  currentInitiativeIds: Set<string>;
60
63
  currentInitiativeWatermarks: Map<string, string>;
@@ -62,6 +65,7 @@ export interface WorkspacePushSyncResult {
62
65
  currentWorkstreamWatermarks: Map<string, string>;
63
66
  currentAiProfileIds: Set<string>;
64
67
  currentAiProfileWatermarks: Map<string, string>;
68
+ currentAiProfileChangeKeys?: Map<string, string>;
65
69
  currentDocumentPaths: Set<string>;
66
70
  deleteDocumentPaths: Set<string>;
67
71
  currentDocumentWatermarks: Map<string, string>;
@@ -80,6 +84,7 @@ export interface WorkspacePushSyncResult {
80
84
  currentTaskEventWatermarks: Map<string, string>;
81
85
  /** Watermarks for every task upsert that was successfully pushed. */
82
86
  pushedWatermarks: Map<string, string>;
87
+ pushedTaskChangeKeys?: Map<string, string>;
83
88
  /** Server-emitted realtime mutation event ids for this push apply. */
84
89
  emittedEventIds: string[];
85
90
  /** Authoritative task snapshots returned immediately by the push route for incoming task upserts. */
@@ -105,6 +110,7 @@ export interface WorkspacePullSyncResult {
105
110
  success: boolean;
106
111
  kind?: WorkspacePullFailureKind;
107
112
  status?: number;
113
+ errorCode?: string;
108
114
  retryAfterMs?: number;
109
115
  transient?: boolean;
110
116
  failures?: string[];
@@ -15,6 +15,14 @@ function resolveWorkspaceAccessConflictMessage(data, fallbackStatus) {
15
15
  }
16
16
  return rawError || `Workspace provisioning failed (${fallbackStatus})`;
17
17
  }
18
+ function isMissingCloudWorkspaceSelection(data, status) {
19
+ if (status !== 400)
20
+ return false;
21
+ const code = String(data.code || '').trim().toUpperCase();
22
+ const rawError = String(data.error || '').trim().toLowerCase();
23
+ return code === 'WORKSPACE_ID_REQUIRED'
24
+ && rawError.includes('no cloud workspace is selected');
25
+ }
18
26
  export async function ensureCloudWorkspaceReadyForSyncService(input) {
19
27
  if (!input.cloudAuthConfigured)
20
28
  return { success: false, error: 'Cloud auth endpoint is not configured.' };
@@ -44,7 +52,48 @@ export async function ensureCloudWorkspaceReadyForSyncService(input) {
44
52
  error: String(data.error || 'Workspace sync rate limited. Please retry later.')
45
53
  };
46
54
  }
55
+ if (isMissingCloudWorkspaceSelection(data, handshakeResult.status)) {
56
+ const provisionResult = await provisionWorkspace(input.resolveCloudAuthUrl, {
57
+ workspaceId: input.workspaceId,
58
+ name: input.workspaceName || input.workspaceId
59
+ });
60
+ if (!provisionResult.ok) {
61
+ const provisionError = asObjectRecord(provisionResult.data);
62
+ return {
63
+ success: false,
64
+ statusCode: provisionResult.status,
65
+ transient: isTransientWorkspaceSyncStatus(provisionResult.status),
66
+ retryAfterMs: provisionResult.retryAfterMs,
67
+ error: resolveWorkspaceAccessConflictMessage(provisionError, provisionResult.status)
68
+ };
69
+ }
70
+ handshakeResult = await handshakeWorkspace(input.resolveCloudAuthUrl, input.workspaceId);
71
+ if (!handshakeResult.ok) {
72
+ const retryData = asObjectRecord(handshakeResult.data);
73
+ if (handshakeResult.status === 401) {
74
+ return { success: false, statusCode: 401, transient: false, error: String(retryData.error || 'Authentication required for workspace sync.') };
75
+ }
76
+ if (handshakeResult.status === 429) {
77
+ return { success: false, statusCode: 429, retryAfterMs: handshakeResult.retryAfterMs, transient: true, error: String(retryData.error || 'Workspace sync rate limited. Please retry later.') };
78
+ }
79
+ return {
80
+ success: false,
81
+ statusCode: handshakeResult.status,
82
+ transient: isTransientWorkspaceSyncStatus(handshakeResult.status),
83
+ error: String(retryData.error || `Workspace sync handshake failed (${handshakeResult.status})`)
84
+ };
85
+ }
86
+ return { success: true, provisioned: true };
87
+ }
47
88
  if (handshakeResult.status === 409 && data.code === 'WORKSPACE_ID_MISMATCH') {
89
+ if (input.allowProvisionOnMismatch === false) {
90
+ return {
91
+ success: false,
92
+ statusCode: 409,
93
+ transient: false,
94
+ error: String(data.error || 'Selected cloud workspace could not be attached to this local workspace.')
95
+ };
96
+ }
48
97
  const provisionResult = await provisionWorkspace(input.resolveCloudAuthUrl, {
49
98
  workspaceId: input.workspaceId,
50
99
  name: input.workspaceName || input.workspaceId
@@ -165,6 +214,8 @@ export async function runWorkspacePushSyncService(input) {
165
214
  changeCount,
166
215
  deleteCount,
167
216
  currentTaskIds: input.payload.currentTaskIds,
217
+ currentTaskWatermarks: input.payload.currentTaskWatermarks ?? new Map(),
218
+ currentTaskChangeKeys: input.payload.currentTaskChangeKeys ?? new Map(),
168
219
  deleteTaskIds: input.payload.deleteTaskIds,
169
220
  currentInitiativeIds: input.payload.currentInitiativeIds,
170
221
  currentInitiativeWatermarks: input.payload.currentInitiativeWatermarks,
@@ -172,6 +223,7 @@ export async function runWorkspacePushSyncService(input) {
172
223
  currentWorkstreamWatermarks: input.payload.currentWorkstreamWatermarks,
173
224
  currentAiProfileIds: input.payload.currentAiProfileIds,
174
225
  currentAiProfileWatermarks: input.payload.currentAiProfileWatermarks,
226
+ currentAiProfileChangeKeys: input.payload.currentAiProfileChangeKeys ?? new Map(),
175
227
  currentDocumentPaths: input.payload.currentDocumentPaths,
176
228
  deleteDocumentPaths: input.payload.deleteDocumentPaths,
177
229
  currentDocumentWatermarks: input.payload.currentDocumentWatermarks,
@@ -189,6 +241,7 @@ export async function runWorkspacePushSyncService(input) {
189
241
  currentAnnotatedAttachmentSessionWatermarks: input.payload.currentAnnotatedAttachmentSessionWatermarks,
190
242
  currentTaskEventWatermarks: input.payload.currentTaskEventWatermarks,
191
243
  pushedWatermarks: new Map(),
244
+ pushedTaskChangeKeys: new Map(),
192
245
  emittedEventIds: [],
193
246
  appliedTaskUpserts: []
194
247
  };
@@ -223,6 +276,8 @@ export async function runWorkspacePushSyncService(input) {
223
276
  changeCount,
224
277
  deleteCount,
225
278
  currentTaskIds: input.payload.currentTaskIds,
279
+ currentTaskWatermarks: input.payload.currentTaskWatermarks ?? new Map(),
280
+ currentTaskChangeKeys: input.payload.currentTaskChangeKeys ?? new Map(),
226
281
  deleteTaskIds: input.payload.deleteTaskIds,
227
282
  currentInitiativeIds: input.payload.currentInitiativeIds,
228
283
  currentInitiativeWatermarks: input.payload.currentInitiativeWatermarks,
@@ -230,6 +285,7 @@ export async function runWorkspacePushSyncService(input) {
230
285
  currentWorkstreamWatermarks: input.payload.currentWorkstreamWatermarks,
231
286
  currentAiProfileIds: input.payload.currentAiProfileIds,
232
287
  currentAiProfileWatermarks: input.payload.currentAiProfileWatermarks,
288
+ currentAiProfileChangeKeys: input.payload.currentAiProfileChangeKeys ?? new Map(),
233
289
  currentDocumentPaths: input.payload.currentDocumentPaths,
234
290
  deleteDocumentPaths: input.payload.deleteDocumentPaths,
235
291
  currentDocumentWatermarks: input.payload.currentDocumentWatermarks,
@@ -247,6 +303,7 @@ export async function runWorkspacePushSyncService(input) {
247
303
  currentAnnotatedAttachmentSessionWatermarks: input.payload.currentAnnotatedAttachmentSessionWatermarks,
248
304
  currentTaskEventWatermarks: input.payload.currentTaskEventWatermarks,
249
305
  pushedWatermarks: input.payload.pushedWatermarks,
306
+ pushedTaskChangeKeys: input.payload.pushedTaskChangeKeys ?? new Map(),
250
307
  emittedEventIds,
251
308
  appliedTaskUpserts
252
309
  };
@@ -288,10 +345,13 @@ export async function runWorkspacePullSyncService(input) {
288
345
  }
289
346
  }
290
347
  if (!pullRes.ok) {
348
+ const pullErrorData = asObjectRecord(pullRes.data);
291
349
  return {
292
350
  success: false,
293
351
  kind: 'pull_http',
294
352
  status: pullRes.status,
353
+ errorCode: typeof pullErrorData.code === 'string' ? pullErrorData.code : undefined,
354
+ error: typeof pullErrorData.error === 'string' ? pullErrorData.error : undefined,
295
355
  retryAfterMs: pullRes.retryAfterMs,
296
356
  transient: isTransientWorkspaceSyncStatus(pullRes.status),
297
357
  cursor,
@@ -443,7 +443,7 @@ describe('syncService', () => {
443
443
  status: 200,
444
444
  data: {
445
445
  success: true,
446
- changes: [{ op: 'upsert', task: { id: 't-parent', title: 'Parent', status: 'done', updatedAt: '2026-03-15T20:27:08.000Z' } }],
446
+ changes: [{ op: 'upsert', task: { id: 'task-apply-error', title: 'Apply error', status: 'done', updatedAt: '2026-03-15T20:27:08.000Z' } }],
447
447
  nextCursor: null,
448
448
  hasMore: false
449
449
  }
@@ -452,7 +452,7 @@ describe('syncService', () => {
452
452
  ok: false,
453
453
  status: 400,
454
454
  failures: ['/api/taskforce/sync/workspace/apply-local:400'],
455
- data: { error: 'Parent tasks cannot be marked done manually; completion is derived from descendants' }
455
+ data: { error: 'Task apply rejected by local validation' }
456
456
  });
457
457
  const res = await runWorkspacePullSyncService({
458
458
  resolveCloudAuthUrl: mockResolveCloudAuthUrl,
@@ -463,7 +463,7 @@ describe('syncService', () => {
463
463
  expect(res.success).toBe(false);
464
464
  expect(res.kind).toBe('apply_all_candidates');
465
465
  expect(res.status).toBe(400);
466
- expect(res.error).toBe('Parent tasks cannot be marked done manually; completion is derived from descendants');
466
+ expect(res.error).toBe('Task apply rejected by local validation');
467
467
  });
468
468
  it('surfaces document decryption failures without failing pull', async () => {
469
469
  const previousKey = process.env.TASKFORCE_SYNC_KEY;
@@ -702,6 +702,60 @@ describe('syncService', () => {
702
702
  expect(res.statusCode).toBe(0);
703
703
  expect(res.transient).toBe(true);
704
704
  });
705
+ it('recovers from missing cloud workspace selection by provisioning and retrying handshake', async () => {
706
+ vi.mocked(handshakeWorkspace)
707
+ .mockResolvedValueOnce({
708
+ ok: false,
709
+ status: 400,
710
+ data: {
711
+ code: 'WORKSPACE_ID_REQUIRED',
712
+ error: 'No cloud workspace is selected. Complete workspace setup first.'
713
+ }
714
+ })
715
+ .mockResolvedValueOnce({
716
+ ok: true,
717
+ status: 200,
718
+ data: {}
719
+ });
720
+ vi.mocked(provisionWorkspace).mockResolvedValueOnce({
721
+ ok: true,
722
+ status: 200,
723
+ data: {
724
+ success: true,
725
+ existing: true
726
+ }
727
+ });
728
+ const res = await ensureCloudWorkspaceReadyForSyncService(baseHandshakeInput);
729
+ expect(res.success).toBe(true);
730
+ expect(res.provisioned).toBe(true);
731
+ expect(provisionWorkspace).toHaveBeenCalledWith(mockResolveCloudAuthUrl, {
732
+ workspaceId: 'ws-test',
733
+ name: 'Test Workspace'
734
+ });
735
+ expect(handshakeWorkspace).toHaveBeenCalledTimes(2);
736
+ });
737
+ it('surfaces provisioning failure when recovering missing cloud workspace selection', async () => {
738
+ vi.mocked(handshakeWorkspace).mockResolvedValueOnce({
739
+ ok: false,
740
+ status: 400,
741
+ data: {
742
+ code: 'WORKSPACE_ID_REQUIRED',
743
+ error: 'No cloud workspace is selected. Complete workspace setup first.'
744
+ }
745
+ });
746
+ vi.mocked(provisionWorkspace).mockResolvedValueOnce({
747
+ ok: false,
748
+ status: 409,
749
+ data: {
750
+ code: 'WORKSPACE_ID_ALREADY_EXISTS',
751
+ error: 'Workspace ID already exists'
752
+ }
753
+ });
754
+ const res = await ensureCloudWorkspaceReadyForSyncService(baseHandshakeInput);
755
+ expect(res.success).toBe(false);
756
+ expect(res.statusCode).toBe(409);
757
+ expect(res.error).toContain('already linked to another cloud account');
758
+ });
705
759
  it('returns failure when cloudAuthConfigured is false', async () => {
706
760
  const res = await ensureCloudWorkspaceReadyForSyncService({
707
761
  ...baseHandshakeInput,
@@ -743,6 +797,20 @@ describe('syncService', () => {
743
797
  expect(res.error).toContain('already linked to another cloud account');
744
798
  expect(res.error).toContain('grant you access');
745
799
  });
800
+ it('does not provision a new cloud workspace during attach-cloud mismatch handling', async () => {
801
+ vi.mocked(handshakeWorkspace).mockResolvedValueOnce({
802
+ ok: false,
803
+ status: 409,
804
+ data: { code: 'WORKSPACE_ID_MISMATCH', error: 'Workspace mismatch' }
805
+ });
806
+ const res = await ensureCloudWorkspaceReadyForSyncService({
807
+ ...baseHandshakeInput,
808
+ allowProvisionOnMismatch: false
809
+ });
810
+ expect(res.success).toBe(false);
811
+ expect(res.statusCode).toBe(409);
812
+ expect(provisionWorkspace).not.toHaveBeenCalled();
813
+ });
746
814
  });
747
815
  describe('syncUserGlobalSettingsService transient metadata', () => {
748
816
  beforeEach(() => {
@@ -0,0 +1,2 @@
1
+ import type { TaskItem } from '../types.js';
2
+ export declare function buildTaskSyncChangeKey(task: Partial<TaskItem> | null | undefined, archivedValue: boolean): string;