@taskforcehq/taskforce 0.3.305 → 0.3.307

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (440) hide show
  1. package/README.md +0 -2
  2. package/dist/Taskforce.module.css +603 -107
  3. package/dist/TaskforceCore.js +216 -43
  4. package/dist/TaskforceCore.test.js +611 -28
  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 +46 -2
  14. package/dist/compat/workspaceSyncCompat.test.js +38 -1
  15. package/dist/components/activity/EntityActivityTimeline.d.ts +34 -0
  16. package/dist/components/activity/EntityActivityTimeline.js +81 -0
  17. package/dist/components/context/ContextAttachmentManager.d.ts +14 -0
  18. package/dist/components/context/ContextAttachmentManager.js +561 -0
  19. package/dist/components/features/AgentsModule.d.ts +14 -1
  20. package/dist/components/features/AgentsModule.js +366 -18
  21. package/dist/components/features/AgentsModule.test.js +507 -8
  22. package/dist/components/features/AnnotatedAttachmentWorkspace.d.ts +3 -1
  23. package/dist/components/features/AnnotatedAttachmentWorkspace.js +118 -4
  24. package/dist/components/features/AnnotatedAttachmentWorkspace.test.js +52 -0
  25. package/dist/components/features/DocumentIndex.d.ts +2 -2
  26. package/dist/components/features/DocumentIndex.js +3 -4
  27. package/dist/components/features/DocumentViewer.d.ts +7 -1
  28. package/dist/components/features/DocumentViewer.js +62 -4
  29. package/dist/components/features/DocumentWorkspace.d.ts +10 -2
  30. package/dist/components/features/DocumentWorkspace.js +15 -4
  31. package/dist/components/features/DocumentWorkspace.test.js +51 -1
  32. package/dist/components/features/TaskSettings.d.ts +2 -0
  33. package/dist/components/features/TaskSettings.js +83 -78
  34. package/dist/components/features/TaskSettings.test.js +171 -82
  35. package/dist/components/features/documentWorkspaceModel.d.ts +1 -1
  36. package/dist/components/task/TaskActionHeader.js +11 -5
  37. package/dist/components/task/TaskActionHeader.test.js +10 -0
  38. package/dist/components/task/TaskCard.js +24 -29
  39. package/dist/components/task/TaskCard.test.js +38 -3
  40. package/dist/components/task/TaskContextUpload.js +4 -526
  41. package/dist/components/task/TaskContextUpload.test.js +3 -3
  42. package/dist/components/task/TaskForm.js +61 -116
  43. package/dist/components/task/TaskForm.test.js +110 -8
  44. package/dist/components/task/TaskKanban.d.ts +2 -1
  45. package/dist/components/task/TaskKanban.js +94 -21
  46. package/dist/components/task/TaskKanban.test.js +36 -0
  47. package/dist/components/task/TaskList.js +5 -1
  48. package/dist/components/task/TaskList.test.js +4 -0
  49. package/dist/components/ui/EditableAvatarButton.d.ts +18 -0
  50. package/dist/components/ui/EditableAvatarButton.js +18 -0
  51. package/dist/components/ui/HierarchyActionBadge.d.ts +10 -0
  52. package/dist/components/ui/HierarchyActionBadge.js +5 -0
  53. package/dist/components/views/AppShellHeader.d.ts +3 -1
  54. package/dist/components/views/AppShellHeader.js +27 -11
  55. package/dist/components/views/AppShellHeader.test.js +18 -1
  56. package/dist/components/views/PlanComparisonPage.d.ts +9 -1
  57. package/dist/components/views/PlanComparisonPage.js +78 -14
  58. package/dist/components/views/PlanComparisonPage.test.js +257 -0
  59. package/dist/components/views/PlansPage.js +127 -36
  60. package/dist/components/views/PlansPage.test.js +294 -12
  61. package/dist/components/views/StandaloneLayout.js +529 -592
  62. package/dist/components/views/WidgetView.js +10 -8
  63. package/dist/components/views/panels/PlanningDrawer.d.ts +25 -5
  64. package/dist/components/views/panels/PlanningDrawer.js +270 -55
  65. package/dist/components/views/panels/PlanningDrawer.test.d.ts +1 -0
  66. package/dist/components/views/panels/PlanningDrawer.test.js +529 -0
  67. package/dist/components/views/planningScope.d.ts +20 -0
  68. package/dist/components/views/planningScope.js +25 -0
  69. package/dist/components/views/planningScope.test.d.ts +1 -0
  70. package/dist/components/views/planningScope.test.js +60 -0
  71. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +26 -1
  72. package/dist/components/views/standalone/modals/AccountHubModal.js +76 -2
  73. package/dist/components/views/standalone/modals/AccountHubModal.test.js +20 -3
  74. package/dist/components/views/standalone/modals/AvatarImageManagerModal.d.ts +20 -0
  75. package/dist/components/views/standalone/modals/AvatarImageManagerModal.js +127 -0
  76. package/dist/components/views/standalone/modals/EditProfileModal.d.ts +2 -9
  77. package/dist/components/views/standalone/modals/EditProfileModal.js +3 -2
  78. package/dist/components/views/standalone/modals/SyncEnableWarningModal.d.ts +9 -0
  79. package/dist/components/views/standalone/modals/SyncEnableWarningModal.js +6 -0
  80. package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.d.ts +1 -0
  81. package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.js +24 -0
  82. package/dist/components/views/standalone/modals/SyncStatusModal.d.ts +2 -1
  83. package/dist/components/views/standalone/modals/SyncStatusModal.js +2 -2
  84. package/dist/config/envSchema.js +1 -0
  85. package/dist/core/AiProfileService.d.ts +46 -2
  86. package/dist/core/AiProfileService.js +1073 -82
  87. package/dist/core/AiProfileService.test.js +19 -1
  88. package/dist/core/AiProfiles.test.js +1249 -44
  89. package/dist/core/AttachmentLinkService.d.ts +3 -0
  90. package/dist/core/AttachmentLinkService.js +110 -23
  91. package/dist/core/AuthIdentityService.d.ts +107 -0
  92. package/dist/core/AuthIdentityService.js +284 -0
  93. package/dist/core/AuthTokenService.d.ts +4 -0
  94. package/dist/core/AuthTokenService.js +34 -6
  95. package/dist/core/AuthTokenService.test.js +3 -3
  96. package/dist/core/EntitlementsPolicy.test.js +2 -2
  97. package/dist/core/GlobalSettingsService.js +127 -6
  98. package/dist/core/McpTokenService.d.ts +12 -0
  99. package/dist/core/McpTokenService.js +44 -1
  100. package/dist/core/PlanEntitlementService.d.ts +23 -2
  101. package/dist/core/PlanEntitlementService.js +343 -57
  102. package/dist/core/PlanFeatureCatalog.test.d.ts +1 -0
  103. package/dist/core/PlanFeatureCatalog.test.js +84 -0
  104. package/dist/core/PlanVersionPolicy.test.js +35 -2
  105. package/dist/core/PlanningEntities.test.js +418 -4
  106. package/dist/core/SignupMonetizationSettings.test.js +199 -27
  107. package/dist/core/SyncReconciliationService.js +2 -65
  108. package/dist/core/SystemAdmin.test.js +42 -42
  109. package/dist/core/TaskActivityService.d.ts +28 -1
  110. package/dist/core/TaskActivityService.js +227 -46
  111. package/dist/core/TaskActivitySync.test.d.ts +1 -0
  112. package/dist/core/TaskActivitySync.test.js +90 -0
  113. package/dist/core/TaskAttachmentsCanonical.test.js +51 -1
  114. package/dist/core/TaskAuditTimeline.test.js +96 -0
  115. package/dist/core/Taskforce.d.ts +415 -40
  116. package/dist/core/Taskforce.ids.test.js +58 -0
  117. package/dist/core/Taskforce.js +1543 -266
  118. package/dist/core/Taskforce.listTasksSlim.test.d.ts +1 -0
  119. package/dist/core/Taskforce.listTasksSlim.test.js +91 -0
  120. package/dist/core/Taskforce.mcpAuth.test.js +109 -0
  121. package/dist/core/UserProfileAvatarDrafts.test.js +30 -3
  122. package/dist/core/WorkspaceLifecycleService.js +0 -5
  123. package/dist/core/WorkspacePermissions.test.js +25 -1
  124. package/dist/core/WorkspacePlanMode.test.js +117 -16
  125. package/dist/core/shared.d.ts +1 -1
  126. package/dist/core/shared.js +5 -1
  127. package/dist/core/types.d.ts +75 -2
  128. package/dist/hooks/auth/useTaskforceAuthBootstrap.js +28 -2
  129. package/dist/hooks/bootstrapTimeouts.d.ts +4 -0
  130. package/dist/hooks/bootstrapTimeouts.js +8 -0
  131. package/dist/hooks/bootstrapTimeouts.test.d.ts +1 -0
  132. package/dist/hooks/bootstrapTimeouts.test.js +12 -0
  133. package/dist/hooks/sync/bootstrap.js +2 -0
  134. package/dist/hooks/sync/controlPlane.d.ts +37 -0
  135. package/dist/hooks/sync/controlPlane.js +78 -0
  136. package/dist/hooks/sync/controlPlane.test.d.ts +1 -0
  137. package/dist/hooks/sync/controlPlane.test.js +121 -0
  138. package/dist/hooks/sync/lifecyclePolicy.d.ts +45 -0
  139. package/dist/hooks/sync/lifecyclePolicy.js +93 -0
  140. package/dist/hooks/sync/lifecyclePolicy.test.d.ts +1 -0
  141. package/dist/hooks/sync/lifecyclePolicy.test.js +124 -0
  142. package/dist/hooks/sync/orchestratorShared.d.ts +13 -4
  143. package/dist/hooks/sync/orchestratorShared.js +129 -48
  144. package/dist/hooks/sync/orchestratorShared.test.js +50 -1
  145. package/dist/hooks/sync/pullLifecycle.d.ts +31 -0
  146. package/dist/hooks/sync/pullLifecycle.js +24 -0
  147. package/dist/hooks/sync/pullLifecycle.test.d.ts +1 -0
  148. package/dist/hooks/sync/pullLifecycle.test.js +80 -0
  149. package/dist/hooks/sync/recovery.d.ts +16 -2
  150. package/dist/hooks/sync/recovery.js +179 -53
  151. package/dist/hooks/sync/recovery.test.d.ts +1 -0
  152. package/dist/hooks/sync/recovery.test.js +292 -0
  153. package/dist/hooks/sync/transfers.d.ts +16 -3
  154. package/dist/hooks/sync/transfers.js +210 -66
  155. package/dist/hooks/sync/transfers.test.d.ts +1 -0
  156. package/dist/hooks/sync/transfers.test.js +928 -0
  157. package/dist/hooks/sync/useRecentSyncEvents.d.ts +21 -0
  158. package/dist/hooks/sync/useRecentSyncEvents.js +92 -0
  159. package/dist/hooks/sync/useRecentSyncEvents.test.d.ts +1 -0
  160. package/dist/hooks/sync/useRecentSyncEvents.test.js +124 -0
  161. package/dist/hooks/sync/useSyncStatusActions.d.ts +30 -0
  162. package/dist/hooks/sync/useSyncStatusActions.js +88 -0
  163. package/dist/hooks/sync/useSyncStatusActions.test.d.ts +1 -0
  164. package/dist/hooks/sync/useSyncStatusActions.test.js +133 -0
  165. package/dist/hooks/sync/useSyncStatusControls.d.ts +25 -0
  166. package/dist/hooks/sync/useSyncStatusControls.js +60 -0
  167. package/dist/hooks/sync/useSyncStatusControls.test.d.ts +1 -0
  168. package/dist/hooks/sync/useSyncStatusControls.test.js +88 -0
  169. package/dist/hooks/useFilterSort.js +13 -13
  170. package/dist/hooks/useFilterSort.test.js +87 -1
  171. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +12 -0
  172. package/dist/hooks/useSyncOrchestrator.d.ts +9 -2
  173. package/dist/hooks/useSyncOrchestrator.js +409 -183
  174. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +407 -0
  175. package/dist/hooks/useTaskData.js +8 -3
  176. package/dist/hooks/useTaskData.test.js +45 -0
  177. package/dist/hooks/useTaskforce.d.ts +145 -52
  178. package/dist/hooks/useTaskforce.js +592 -253
  179. package/dist/hooks/useTaskforce.runtime-routing.test.d.ts +1 -0
  180. package/dist/hooks/useTaskforce.runtime-routing.test.js +246 -0
  181. package/dist/hooks/useTaskforce.sync-behavior.test.js +2468 -201
  182. package/dist/hooks/useWorkspaceSyncController.d.ts +1 -0
  183. package/dist/hooks/useWorkspaceSyncController.js +13 -2
  184. package/dist/hooks/useWorkspaceSyncController.test.js +48 -0
  185. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +16 -0
  186. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +328 -40
  187. package/dist/localization/locales/en-US.d.ts +0 -1
  188. package/dist/localization/locales/en-US.js +0 -1
  189. package/dist/localization/locales/es-419.js +0 -1
  190. package/dist/localization/locales/pt-BR.js +0 -1
  191. package/dist/mcp/canonicalAssetHelpers.d.ts +16 -4
  192. package/dist/mcp/canonicalAssetHelpers.js +18 -10
  193. package/dist/mcp/canonicalAssetHelpers.test.d.ts +1 -0
  194. package/dist/mcp/canonicalAssetHelpers.test.js +67 -0
  195. package/dist/mcp/clientIntegrations.d.ts +1 -0
  196. package/dist/mcp/clientIntegrations.js +25 -0
  197. package/dist/mcp/clientIntegrations.test.js +14 -3
  198. package/dist/mcp/clientProfileDefaults.js +3 -3
  199. package/dist/mcp/collaborationRegistrar.js +7 -7
  200. package/dist/mcp/documentAssetRegistrar.js +116 -10
  201. package/dist/mcp/runtime.d.ts +3 -0
  202. package/dist/mcp/runtime.js +1099 -185
  203. package/dist/mcp/runtime.test.js +1491 -140
  204. package/dist/mcp/taskAttachmentTypes.d.ts +1 -1
  205. package/dist/mcp/taskPlanningRegistrar.js +48 -0
  206. package/dist/mcp/toolRegistry.d.ts +56 -0
  207. package/dist/mcp/toolRegistry.js +8 -0
  208. package/dist/mcp/toolRegistry.test.js +4 -0
  209. package/dist/migrations/taskSchemaMigrations.d.ts +1 -1
  210. package/dist/migrations/taskSchemaMigrations.js +192 -61
  211. package/dist/migrations/taskSchemaMigrations.test.js +15 -0
  212. package/dist/resources/templates/workflow-sources/workflowDocs.mjs +6 -6
  213. package/dist/resources/templates/workflows/collaborate.yaml +1 -1
  214. package/dist/resources/templates/workflows/evaluate.yaml +2 -2
  215. package/dist/resources/templates/workflows/plan.yaml +1 -1
  216. package/dist/resources/templates/workflows/review.yaml +2 -2
  217. package/dist/server/aiProfileAvatarAssets.d.ts +49 -0
  218. package/dist/server/aiProfileAvatarAssets.js +319 -0
  219. package/dist/server/aiProfileAvatarAssets.test.d.ts +1 -0
  220. package/dist/server/aiProfileAvatarAssets.test.js +213 -0
  221. package/dist/server/annotatedAttachmentsRoutes.test.js +69 -10
  222. package/dist/server/auth/providers/apple.d.ts +33 -0
  223. package/dist/server/auth/providers/apple.js +82 -0
  224. package/dist/server/auth/providers/appleClientSecret.d.ts +37 -0
  225. package/dist/server/auth/providers/appleClientSecret.js +65 -0
  226. package/dist/server/auth/providers/github.d.ts +26 -0
  227. package/dist/server/auth/providers/github.js +86 -0
  228. package/dist/server/auth/providers/google.d.ts +21 -0
  229. package/dist/server/auth/providers/google.js +56 -0
  230. package/dist/server/auth/providers/types.d.ts +21 -0
  231. package/dist/server/auth/providers/types.js +1 -0
  232. package/dist/server/auth.d.ts +2 -0
  233. package/dist/server/auth.js +6 -3
  234. package/dist/server/documentReviewRoutes.test.js +36 -3
  235. package/dist/server/index.cookieProxy.test.js +1 -0
  236. package/dist/server/index.d.ts +12 -0
  237. package/dist/server/index.js +124 -9
  238. package/dist/server/index.rateLimit.test.js +3 -0
  239. package/dist/server/index.test.js +111 -3
  240. package/dist/server/routes/admin.d.ts +3 -0
  241. package/dist/server/routes/admin.js +459 -28
  242. package/dist/server/routes/annotatedAttachments.js +51 -0
  243. package/dist/server/routes/auth.d.ts +26 -2
  244. package/dist/server/routes/auth.js +1170 -71
  245. package/dist/server/routes/authSupport.d.ts +5 -1
  246. package/dist/server/routes/authSupport.js +13 -2
  247. package/dist/server/routes/billing.js +372 -35
  248. package/dist/server/routes/billing.test.js +595 -12
  249. package/dist/server/routes/documents.d.ts +1 -0
  250. package/dist/server/routes/documents.js +182 -70
  251. package/dist/server/routes/shared.d.ts +28 -2
  252. package/dist/server/routes/shared.js +69 -14
  253. package/dist/server/routes/sync.integration.test.js +658 -39
  254. package/dist/server/routes/sync.js +74 -608
  255. package/dist/server/routes/syncAuxRoutes.d.ts +15 -0
  256. package/dist/server/routes/syncAuxRoutes.js +91 -0
  257. package/dist/server/routes/syncAuxRoutes.test.d.ts +1 -0
  258. package/dist/server/routes/syncAuxRoutes.test.js +158 -0
  259. package/dist/server/routes/syncPullApplyRoutes.d.ts +36 -0
  260. package/dist/server/routes/syncPullApplyRoutes.js +204 -0
  261. package/dist/server/routes/syncPushRoutes.d.ts +24 -0
  262. package/dist/server/routes/syncPushRoutes.js +212 -0
  263. package/dist/server/routes/syncRouteGuards.d.ts +36 -0
  264. package/dist/server/routes/syncRouteGuards.js +67 -0
  265. package/dist/server/routes/syncRouteGuards.test.d.ts +1 -0
  266. package/dist/server/routes/syncRouteGuards.test.js +94 -0
  267. package/dist/server/routes/syncRouteTypes.d.ts +13 -0
  268. package/dist/server/routes/syncRouteTypes.js +1 -0
  269. package/dist/server/routes/syncSnapshotRoutes.d.ts +66 -0
  270. package/dist/server/routes/syncSnapshotRoutes.js +182 -0
  271. package/dist/server/routes/tasks.js +55 -10
  272. package/dist/server/routes/workspaces.js +36 -5
  273. package/dist/server/routes.js +147 -11
  274. package/dist/server/routes.test.js +2704 -183
  275. package/dist/services/workflowExportSnapshots.test.js +2 -0
  276. package/dist/shared/aiProfileSeatScope.d.ts +5 -0
  277. package/dist/shared/aiProfileSeatScope.js +21 -0
  278. package/dist/shared/aiProfileSurfaceType.d.ts +1 -1
  279. package/dist/shared/aiProfileSurfaceType.js +10 -8
  280. package/dist/shared/passwordPolicy.d.ts +13 -0
  281. package/dist/shared/passwordPolicy.js +84 -0
  282. package/dist/shared/passwordPolicy.test.d.ts +1 -0
  283. package/dist/shared/passwordPolicy.test.js +34 -0
  284. package/dist/storage/objectStorage.d.ts +1 -0
  285. package/dist/storage/objectStorage.js +2 -1
  286. package/dist/storage/objectStorageClient.d.ts +1 -0
  287. package/dist/storage/objectStorageClient.js +41 -8
  288. package/dist/storage/objectStorageClient.test.js +26 -1
  289. package/dist/storage/objectStorageConfig.js +3 -1
  290. package/dist/storage/objectStorageConfig.test.js +13 -1
  291. package/dist/storage/workspaceAssetStore.d.ts +15 -3
  292. package/dist/storage/workspaceAssetStore.js +132 -44
  293. package/dist/storage/workspaceAssetStore.test.js +93 -6
  294. package/dist/styles/fonts.css +70 -0
  295. package/dist/sync/collaborationSyncPayload.d.ts +6 -1
  296. package/dist/sync/collaborationSyncPayload.js +19 -11
  297. package/dist/sync/collaborationSyncPayload.test.js +14 -1
  298. package/dist/sync/collaborationSyncState.d.ts +3 -3
  299. package/dist/sync/collaborationSyncState.js +19 -12
  300. package/dist/sync/contentSyncPayload.d.ts +5 -1
  301. package/dist/sync/contentSyncPayload.js +9 -0
  302. package/dist/sync/contentSyncPayload.test.js +39 -0
  303. package/dist/sync/contentSyncState.d.ts +9 -9
  304. package/dist/sync/contentSyncState.js +248 -107
  305. package/dist/sync/planningSyncPayload.js +5 -0
  306. package/dist/sync/planningSyncPayload.test.js +24 -0
  307. package/dist/sync/syncApplyHandlers.d.ts +11 -4
  308. package/dist/sync/syncApplyHandlers.js +115 -42
  309. package/dist/sync/syncApplyHandlers.test.js +251 -4
  310. package/dist/sync/syncFieldSemantics.d.ts +12 -0
  311. package/dist/sync/syncFieldSemantics.js +55 -0
  312. package/dist/sync/syncResourceAdapters.d.ts +46 -0
  313. package/dist/sync/syncResourceAdapters.js +79 -0
  314. package/dist/sync/syncService.d.ts +6 -0
  315. package/dist/sync/syncService.js +65 -1
  316. package/dist/sync/syncService.test.js +96 -3
  317. package/dist/sync/taskSyncChangeKey.d.ts +2 -0
  318. package/dist/sync/taskSyncChangeKey.js +143 -0
  319. package/dist/sync/taskSyncPayload.js +3 -8
  320. package/dist/sync/workspacePullFeed.d.ts +4 -0
  321. package/dist/sync/workspacePullFeed.js +132 -72
  322. package/dist/sync/workspacePullFeed.test.js +92 -0
  323. package/dist/sync/workspaceRepair.js +5 -2
  324. package/dist/sync/workspaceSyncModel.d.ts +73 -6
  325. package/dist/sync/workspaceSyncModel.js +342 -73
  326. package/dist/sync/workspaceSyncModel.test.js +559 -14
  327. package/dist/sync/workspaceSyncState.d.ts +17 -0
  328. package/dist/sync/workspaceSyncState.js +2 -1
  329. package/dist/sync/workspaceSyncSurface.js +3 -8
  330. package/dist/types.d.ts +50 -2
  331. package/dist/ui/assets/AgentsModule-2y82_3DO.js +1 -0
  332. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BTLZZCiQ.js +3 -0
  333. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BVKew-mH.css +1 -0
  334. package/dist/ui/assets/ContextAttachmentManager-D2epuNnG.js +3 -0
  335. package/dist/ui/assets/CourierPrime-Bold-BuLj_dpw.woff2 +0 -0
  336. package/dist/ui/assets/CourierPrime-BoldItalic-BrK2pUkZ.woff2 +0 -0
  337. package/dist/ui/assets/CourierPrime-Italic-DFUZK5Ey.woff2 +0 -0
  338. package/dist/ui/assets/CourierPrime-Regular-BsKphzVf.woff2 +0 -0
  339. package/dist/ui/assets/DocumentWorkspace-D4rJKXzd.css +1 -0
  340. package/dist/ui/assets/DocumentWorkspace-sVa-3Do6.js +252 -0
  341. package/dist/ui/assets/EntityActivityTimeline-DyqvvhuF.css +1 -0
  342. package/dist/ui/assets/EntityActivityTimeline-Zods27y_.js +1 -0
  343. package/dist/ui/assets/{InitiativesModule-Dhm7M8e7.js → InitiativesModule-D4XseTwW.js} +1 -1
  344. package/dist/ui/assets/Noir_medium-bxQwKGzB.woff2 +0 -0
  345. package/dist/ui/assets/Noir_regular-ojf2kxlG.woff2 +0 -0
  346. package/dist/ui/assets/PlansPage-D5AcbO0L.js +1 -0
  347. package/dist/ui/assets/PlansPage-Dvqsz5zc.css +1 -0
  348. package/dist/ui/assets/RussoOne-Regular-Cu_qq_qC.woff2 +0 -0
  349. package/dist/ui/assets/SpecialElite-Regular-Di6gSvAS.woff2 +0 -0
  350. package/dist/ui/assets/TaskContextUpload-uC5qx4Bv.js +1 -0
  351. package/dist/ui/assets/TaskSettings-CqvPAsTv.js +9 -0
  352. package/dist/ui/assets/{WorkflowsModule-BcS4afRn.js → WorkflowsModule-oOA9bcdK.js} +4 -4
  353. package/dist/ui/assets/documentReferences-DPZuTgHh.js +1 -0
  354. package/dist/ui/assets/index-607WPo_X.js +4 -0
  355. package/dist/ui/assets/index-CYyodXsg.css +1 -0
  356. package/dist/ui/assets/{vendor-icons-BSUaRwF8.js → vendor-icons-B32hGuF2.js} +1 -1
  357. package/dist/ui/fonts/CourierPrime-Bold.ttf +0 -0
  358. package/dist/ui/fonts/CourierPrime-Bold.woff2 +0 -0
  359. package/dist/ui/fonts/CourierPrime-BoldItalic.ttf +0 -0
  360. package/dist/ui/fonts/CourierPrime-BoldItalic.woff2 +0 -0
  361. package/dist/ui/fonts/CourierPrime-Italic.ttf +0 -0
  362. package/dist/ui/fonts/CourierPrime-Italic.woff2 +0 -0
  363. package/dist/ui/fonts/CourierPrime-Regular.ttf +0 -0
  364. package/dist/ui/fonts/CourierPrime-Regular.woff2 +0 -0
  365. package/dist/ui/fonts/Noir_medium.otf +0 -0
  366. package/dist/ui/fonts/Noir_medium.woff2 +0 -0
  367. package/dist/ui/fonts/Noir_regular.otf +0 -0
  368. package/dist/ui/fonts/Noir_regular.woff2 +0 -0
  369. package/dist/ui/fonts/RussoOne-Regular.woff2 +0 -0
  370. package/dist/ui/fonts/SpecialElite-Regular.ttf +0 -0
  371. package/dist/ui/fonts/SpecialElite-Regular.woff2 +0 -0
  372. package/dist/ui/index.html +3 -3
  373. package/dist/ui/product/agent manager.png +0 -0
  374. package/dist/ui/product/agent_manager_02.png +0 -0
  375. package/dist/utils/accountProfileSummaryCache.d.ts +5 -0
  376. package/dist/utils/aiProfileEvents.d.ts +7 -0
  377. package/dist/utils/aiProfileEvents.js +6 -0
  378. package/dist/utils/assignees.d.ts +8 -0
  379. package/dist/utils/assignees.js +10 -1
  380. package/dist/utils/assignees.test.d.ts +1 -0
  381. package/dist/utils/assignees.test.js +25 -0
  382. package/dist/utils/avatarDisplay.d.ts +1 -0
  383. package/dist/utils/avatarDisplay.js +15 -0
  384. package/dist/utils/avatarUpload.d.ts +27 -0
  385. package/dist/utils/avatarUpload.js +132 -0
  386. package/dist/utils/avatarUpload.test.d.ts +1 -0
  387. package/dist/utils/avatarUpload.test.js +40 -0
  388. package/dist/utils/bootstrapTrace.d.ts +7 -0
  389. package/dist/utils/bootstrapTrace.js +29 -0
  390. package/dist/utils/commercialLifecyclePresentation.js +3 -0
  391. package/dist/utils/contextAssetEvents.d.ts +2 -0
  392. package/dist/utils/contextFiles.d.ts +3 -1
  393. package/dist/utils/contextFiles.js +2 -1
  394. package/dist/utils/syncEventDetails.d.ts +3 -0
  395. package/dist/utils/syncEventDetails.js +64 -0
  396. package/dist/utils/syncEventDetails.test.d.ts +1 -0
  397. package/dist/utils/syncEventDetails.test.js +23 -0
  398. package/dist/utils/syncEventPresentation.d.ts +15 -0
  399. package/dist/utils/syncEventPresentation.js +129 -0
  400. package/dist/utils/syncEventPresentation.test.d.ts +1 -0
  401. package/dist/utils/syncEventPresentation.test.js +64 -0
  402. package/dist/utils/syncStatusPresentation.d.ts +72 -0
  403. package/dist/utils/syncStatusPresentation.js +217 -0
  404. package/dist/utils/syncStatusPresentation.test.d.ts +1 -0
  405. package/dist/utils/syncStatusPresentation.test.js +164 -0
  406. package/dist/utils/taskActivity.d.ts +9 -1
  407. package/dist/utils/taskActivity.js +290 -22
  408. package/dist/utils/taskActivity.test.js +141 -8
  409. package/dist/utils/taskSearch.d.ts +22 -0
  410. package/dist/utils/taskSearch.js +87 -0
  411. package/dist/utils/taskSearch.test.d.ts +1 -0
  412. package/dist/utils/taskSearch.test.js +69 -0
  413. package/dist/utils/taskUtils.js +10 -3
  414. package/dist/utils/taskUtils.test.d.ts +1 -0
  415. package/dist/utils/taskUtils.test.js +19 -0
  416. package/dist/utils/workspaceSyncPresentation.d.ts +4 -0
  417. package/dist/utils/workspaceSyncPresentation.js +68 -2
  418. package/dist/utils/workspaceSyncPresentation.test.js +36 -1
  419. package/package.json +3 -2
  420. package/scripts/export-sqlite-to-postgres.mjs +7 -9
  421. package/scripts/run-e2e-realtime.sh +17 -3
  422. package/scripts/run-smoke.sh +17 -5
  423. package/scripts/run-soak.sh +17 -5
  424. package/src/resources/templates/workflow-sources/workflowDocs.mjs +6 -6
  425. package/src/resources/templates/workflows/collaborate.yaml +1 -1
  426. package/src/resources/templates/workflows/evaluate.yaml +2 -2
  427. package/src/resources/templates/workflows/plan.yaml +1 -1
  428. package/src/resources/templates/workflows/review.yaml +2 -2
  429. package/dist/ui/assets/AgentsModule-N2MnQBZk.js +0 -1
  430. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BG6P_GVW.js +0 -3
  431. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BaS2VwIr.css +0 -1
  432. package/dist/ui/assets/DocumentWorkspace-B3nV_Gc5.css +0 -1
  433. package/dist/ui/assets/DocumentWorkspace-DRbDMwh8.js +0 -252
  434. package/dist/ui/assets/PlansPage-C2dd2n1X.css +0 -1
  435. package/dist/ui/assets/PlansPage-Cq0zXj3I.js +0 -1
  436. package/dist/ui/assets/RussoOne-Regular-C3BxZIj7.ttf +0 -0
  437. package/dist/ui/assets/TaskSettings-ln0aF7xc.js +0 -9
  438. package/dist/ui/assets/index-C2-OXZV7.css +0 -1
  439. package/dist/ui/assets/index-DcSI5F86.js +0 -6
  440. package/scripts/migrate-planning-model.ts +0 -233
@@ -26,57 +26,19 @@ import { useWorkspaceSyncController } from './useWorkspaceSyncController';
26
26
  import { normalizeTaskforceTheme } from '../utils/theme';
27
27
  import { useRealtimeSync } from './useRealtimeSync';
28
28
  import { TASKFORCE_CONTEXT_ASSETS_MUTATED_EVENT } from '../utils/contextAssetEvents';
29
+ import { TASKFORCE_AI_PROFILES_MUTATED_EVENT } from '../utils/aiProfileEvents';
29
30
  import { setLocale as setAppLocale } from '../localization';
30
31
  import { normalizeTaskFromApi } from '../utils/taskNormalization.js';
32
+ import { normalizeSyncEventDetails } from '../utils/syncEventDetails';
31
33
  import { createSyncAuthCoordinator } from './sync/auth';
32
34
  import { createBootstrapTimeoutMonitor, syncBootstrapPhaseTracking } from './sync/bootstrap';
33
- import { buildTaxonomyStateFingerprint, flattenDocumentReviewComments, flattenTaskEvents, isValidWorkspaceKey, logSyncDebug, normalizeWorkspaceSyncAiProfileSnapshotEntry, readPersistedSyncWatermarks, readUserGlobalSyncMeta, WORKSPACE_CONCURRENT_WINDOW_MESSAGE, WORKSPACE_FULL_RECONCILE_INTERVAL_MS, writeUserGlobalSyncMeta } from './sync/orchestratorShared';
35
+ import { resolvePullSyncReadiness, resolvePushSyncReadiness, resolveRetrySyncReadiness } from './sync/controlPlane';
36
+ import { resolveWorkspaceAutomaticPullPolicy, resolveWorkspaceSyncPresentation, shouldRunWorkspaceStartupRepair, shouldScheduleWorkspaceReactivePush, shouldStartWorkspacePeriodicFullReconcile, shouldStartWorkspacePushWatchdog, } from './sync/lifecyclePolicy';
37
+ import { buildPersistedSyncWatermarksSnapshot, buildTaxonomyStateFingerprint, flattenDocumentReviewComments, flattenTaskEvents, isValidWorkspaceKey, logSyncDebug, normalizeWorkspaceSyncAiProfileSnapshotEntry, parsePersistedSyncWatermarksSnapshot, readPersistedSyncWatermarks, readUserGlobalSyncMeta, savePersistedSyncWatermarks, WORKSPACE_CONCURRENT_WINDOW_MESSAGE, WORKSPACE_FULL_RECONCILE_INTERVAL_MS, writeUserGlobalSyncMeta } from './sync/orchestratorShared';
34
38
  import { createRealtimeSyncBridge } from './sync/realtime';
35
39
  import { createSyncRecoveryCoordinator } from './sync/recovery';
36
40
  import { createWorkspaceTransferCoordinator } from './sync/transfers';
37
- // === SECTION: Pure utility functions ===
38
- function summarizeSyncPhase(enabled, phase, busy, retryAt, lastErrorMessage) {
39
- if (!enabled)
40
- return 'Sync is turned off for this workspace.';
41
- if (phase === 'provision-local') {
42
- return busy
43
- ? 'Uploading this workspace to cloud for the first time.'
44
- : 'Preparing the first upload to cloud.';
45
- }
46
- if (phase === 'attach-cloud') {
47
- return busy
48
- ? 'Pulling the cloud workspace into this device.'
49
- : 'Waiting for the first cloud pull into this device.';
50
- }
51
- if (retryAt) {
52
- return 'Sync hit a temporary problem and will retry automatically.';
53
- }
54
- if (lastErrorMessage) {
55
- return 'Sync needs attention before it can continue.';
56
- }
57
- if (busy) {
58
- return 'Sync is currently running.';
59
- }
60
- return 'Sync is healthy.';
61
- }
62
- function recommendSyncAction(enabled, phase, retryAt, lastErrorMessage) {
63
- if (!enabled) {
64
- return 'Turn on sync when you are ready to back up this workspace.';
65
- }
66
- if (phase === 'provision-local') {
67
- return 'Keep this window open while the first upload finishes. If it seems stalled, press Sync.';
68
- }
69
- if (phase === 'attach-cloud') {
70
- return 'Keep this window open for the first cloud pull. If it does not move, press Repair.';
71
- }
72
- if (retryAt) {
73
- return 'Wait for the automatic retry, or press Sync to retry now.';
74
- }
75
- if (lastErrorMessage) {
76
- return 'Press Sync to retry. If the same error keeps returning, press Repair.';
77
- }
78
- return 'No action needed.';
79
- }
41
+ import { aiProfileSyncResourceAdapter, taskSyncResourceAdapter } from '../sync/syncResourceAdapters.js';
80
42
  const EMPTY_WORKSPACE_SYNC_REFERENCE_SNAPSHOT_STATUS = {
81
43
  documents: false,
82
44
  aiProfiles: false,
@@ -109,6 +71,7 @@ export function useSyncOrchestrator(input) {
109
71
  const workspaceInitiativesRef = useRef(initiatives || []);
110
72
  const workspaceWorkstreamsRef = useRef(workstreams || []);
111
73
  const workspaceSyncMarkdownDocumentsRef = useRef([]);
74
+ const workspaceSyncKnownDocumentPathsRef = useRef(new Set());
112
75
  const [workspaceSyncAiProfiles, setWorkspaceSyncAiProfiles] = useState([]);
113
76
  const [workspaceSyncAiProfilesFingerprint, setWorkspaceSyncAiProfilesFingerprint] = useState('');
114
77
  const [workspaceSyncAiProfilesLastFetchAt, setWorkspaceSyncAiProfilesLastFetchAt] = useState(null);
@@ -119,6 +82,7 @@ export function useSyncOrchestrator(input) {
119
82
  const [workspaceSyncAssets, setWorkspaceSyncAssets] = useState([]);
120
83
  const [workspaceSyncAssetsFingerprint, setWorkspaceSyncAssetsFingerprint] = useState('');
121
84
  const workspaceSyncAssetsRef = useRef([]);
85
+ const workspaceSyncKnownAssetPathsRef = useRef(new Set());
122
86
  const [workspaceSyncDocumentReviewSessions, setWorkspaceSyncDocumentReviewSessions] = useState([]);
123
87
  const [workspaceSyncDocumentReviewSessionsFingerprint, setWorkspaceSyncDocumentReviewSessionsFingerprint] = useState('');
124
88
  const workspaceSyncDocumentReviewSessionsRef = useRef([]);
@@ -137,6 +101,7 @@ export function useSyncOrchestrator(input) {
137
101
  });
138
102
  const userSettingsSyncInFlightRef = useRef(false);
139
103
  const userSettingsRetryTimeoutRef = useRef(null);
104
+ const workspaceBaselineResetReasonRef = useRef('missing-baseline');
140
105
  const userSettingsRetryAttemptRef = useRef(0);
141
106
  const applyingRemoteUserSettingsRef = useRef(false);
142
107
  const initialUserSettingsSyncCompletedRef = useRef(new Set());
@@ -150,6 +115,7 @@ export function useSyncOrchestrator(input) {
150
115
  const realtimePullDebounceRef = useRef(null);
151
116
  const realtimeSuppressedEventIdsRef = useRef(new Set());
152
117
  const realtimeSuppressedEventQueueRef = useRef([]);
118
+ const workspacePendingPushReplayRef = useRef(null);
153
119
  const retryWorkspaceSyncRef = useRef(() => Promise.resolve(false));
154
120
  const syncUserGlobalSettingsRef = useRef(async () => { });
155
121
  const bootstrapPhaseStartedAtRef = useRef(null);
@@ -178,73 +144,6 @@ export function useSyncOrchestrator(input) {
178
144
  void syncUserGlobalSettingsRef.current({ preferCloudOnFirstSync: false });
179
145
  }, delayMs);
180
146
  }, [clearUserSettingsRetry]);
181
- useEffect(() => {
182
- workspaceSyncMarkdownDocumentsFingerprintRef.current = '';
183
- workspaceSyncAiProfilesFingerprintRef.current = '';
184
- workspaceSyncAssetsFingerprintRef.current = '';
185
- workspaceSyncDocumentReviewSessionsFingerprintRef.current = '';
186
- workspaceSyncAnnotatedAttachmentSessionsFingerprintRef.current = '';
187
- workspaceAttachBootstrapBaselineSignatureRef.current = '';
188
- workspaceCaptureAttachBootstrapBaselineRef.current = false;
189
- setWorkspaceSyncMarkdownDocuments([]);
190
- setWorkspaceSyncMarkdownDocumentsFingerprint('');
191
- workspaceSyncAiProfilesRef.current = [];
192
- setWorkspaceSyncAiProfiles([]);
193
- setWorkspaceSyncAiProfilesFingerprint('');
194
- setWorkspaceSyncAssets([]);
195
- setWorkspaceSyncAssetsFingerprint('');
196
- workspaceSyncDocumentReviewSessionsRef.current = [];
197
- workspaceSyncDocumentReviewCommentsRef.current = [];
198
- setWorkspaceSyncDocumentReviewSessions([]);
199
- setWorkspaceSyncDocumentReviewSessionsFingerprint('');
200
- workspaceSyncAnnotatedAttachmentSessionsRef.current = [];
201
- setWorkspaceSyncAnnotatedAttachmentSessions([]);
202
- setWorkspaceSyncAnnotatedAttachmentSessionsFingerprint('');
203
- setWorkspaceSyncReferenceSnapshotStatus(EMPTY_WORKSPACE_SYNC_REFERENCE_SNAPSHOT_STATUS);
204
- setWorkspaceLastErrorMessage(null);
205
- setWorkspaceLastErrorAt(null);
206
- setWorkspaceSyncPendingChanges(0);
207
- realtimeSuppressedEventIdsRef.current = new Set();
208
- realtimeSuppressedEventQueueRef.current = [];
209
- // Reset watermarks for the new workspace, then restore from localStorage if recent
210
- // enough. This prevents a full resync on every page reload while still catching any
211
- // changes that happened while the app was closed.
212
- workspaceLastPushedWatermarksRef.current = new Map();
213
- workspaceLastPushedInitiativeIdsRef.current = new Set();
214
- workspaceLastPushedInitiativeWatermarksRef.current = new Map();
215
- workspaceLastPushedWorkstreamIdsRef.current = new Set();
216
- workspaceLastPushedWorkstreamWatermarksRef.current = new Map();
217
- workspaceLastPushedAiProfileIdsRef.current = new Set();
218
- workspaceLastPushedAiProfileWatermarksRef.current = new Map();
219
- workspaceLastPushedDocumentPathsRef.current = new Set();
220
- workspaceLastPushedDocumentWatermarksRef.current = new Map();
221
- workspaceLastPushedAssetPathsRef.current = new Set();
222
- workspaceLastPushedAssetWatermarksRef.current = new Map();
223
- workspaceLastPushedDocumentReviewSessionIdsRef.current = new Set();
224
- workspaceLastPushedDocumentReviewSessionWatermarksRef.current = new Map();
225
- workspaceLastPushedDocumentReviewCommentIdsRef.current = new Set();
226
- workspaceLastPushedDocumentReviewCommentWatermarksRef.current = new Map();
227
- workspaceLastPushedAnnotatedAttachmentSessionIdsRef.current = new Set();
228
- workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current = new Map();
229
- workspaceLastPushedTaskEventWatermarksRef.current = new Map();
230
- workspaceLastPushedAnnotatedAttachmentSessionIdsRef.current = new Set();
231
- workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current = new Map();
232
- workspaceLastPushedTaskEventWatermarksRef.current = new Map();
233
- workspaceStartupFullReconcileRanRef.current = false;
234
- workspaceFullReconcileInFlightRef.current = false;
235
- const persisted = readPersistedSyncWatermarks(currentWorkspaceId, taxonomyStateFingerprint);
236
- if (persisted) {
237
- workspaceLastPushedWatermarksRef.current = persisted.taskWatermarks;
238
- workspaceLastPushedInitiativeWatermarksRef.current = persisted.initiativeWatermarks;
239
- workspaceLastPushedWorkstreamWatermarksRef.current = persisted.workstreamWatermarks;
240
- workspaceLastPushedDocumentWatermarksRef.current = persisted.documentWatermarks;
241
- workspaceLastPushedAssetWatermarksRef.current = persisted.assetWatermarks;
242
- workspaceLastPushedDocumentReviewSessionWatermarksRef.current = persisted.documentReviewSessionWatermarks;
243
- workspaceLastPushedDocumentReviewCommentWatermarksRef.current = persisted.documentReviewCommentWatermarks;
244
- workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current = persisted.annotatedAttachmentSessionWatermarks;
245
- workspaceLastPushedTaskEventWatermarksRef.current = persisted.taskEventWatermarks;
246
- }
247
- }, [currentWorkspaceId, taxonomyStateFingerprint]);
248
147
  const markWorkspaceSyncReferenceSnapshotReady = useCallback((key) => {
249
148
  setWorkspaceSyncReferenceSnapshotStatus((current) => (current[key]
250
149
  ? current
@@ -254,7 +153,7 @@ export function useSyncOrchestrator(input) {
254
153
  }));
255
154
  }, []);
256
155
  const workspaceSyncReferenceSnapshotsReady = Object.values(workspaceSyncReferenceSnapshotStatus).every(Boolean);
257
- const { workspaceRetryAt, isWorkspaceRetryPending, workspacePushInFlightRef, workspacePullInFlightRef, workspaceLastPushedSignatureRef, workspacePendingSignatureRef, workspaceLastPushedTaskIdsRef, workspaceDeletedTaskIdsRef, workspaceDeletedTaskWatermarksRef, workspacePullCursorRef, clearWorkspaceRetry, scheduleWorkspaceRetry, persistWorkspaceSyncPatch, applyWorkspaceSyncPatchLocally, loadWorkspaceSyncState, applyWorkspaceSyncStateSnapshot: applyWorkspaceSyncStateSnapshotBase, saveWorkspaceCloudSyncSettings: saveWorkspaceCloudSyncSettingsBase, acquireWorkspaceSyncLease, releaseWorkspaceSyncLease, forceClearWorkspaceSyncLease } = useWorkspaceSyncController({
156
+ const { workspaceRetryAt, isWorkspaceRetryPending, workspacePushInFlightRef, workspacePullInFlightRef, workspaceLastPushedSignatureRef, workspacePendingSignatureRef, workspaceLastPushedTaskIdsRef, workspaceDeletedTaskIdsRef, workspaceDeletedTaskWatermarksRef, workspacePullCursorRef, clearWorkspaceRetry, scheduleWorkspaceRetry, persistWorkspaceSyncPatch, applyWorkspaceSyncPatchLocally, readWorkspaceSyncStateSnapshot, loadWorkspaceSyncState, applyWorkspaceSyncStateSnapshot: applyWorkspaceSyncStateSnapshotBase, saveWorkspaceCloudSyncSettings: saveWorkspaceCloudSyncSettingsBase, acquireWorkspaceSyncLease, releaseWorkspaceSyncLease, forceClearWorkspaceSyncLease } = useWorkspaceSyncController({
258
157
  currentWorkspaceId,
259
158
  setWorkspaceCloudSyncEnabled,
260
159
  setWorkspaceSyncPhase,
@@ -274,12 +173,14 @@ export function useSyncOrchestrator(input) {
274
173
  applyWorkspaceSyncStateSnapshotBase(nextState);
275
174
  }, [applyWorkspaceSyncStateSnapshotBase]);
276
175
  const workspaceLastPushedWatermarksRef = useRef(new Map());
176
+ const workspaceLastPushedTaskChangeKeysRef = useRef(new Map());
277
177
  const workspaceLastPushedInitiativeIdsRef = useRef(new Set());
278
178
  const workspaceLastPushedInitiativeWatermarksRef = useRef(new Map());
279
179
  const workspaceLastPushedWorkstreamIdsRef = useRef(new Set());
280
180
  const workspaceLastPushedWorkstreamWatermarksRef = useRef(new Map());
281
181
  const workspaceLastPushedAiProfileIdsRef = useRef(new Set());
282
182
  const workspaceLastPushedAiProfileWatermarksRef = useRef(new Map());
183
+ const workspaceLastPushedAiProfileChangeKeysRef = useRef(new Map());
283
184
  const workspaceLastPushedDocumentPathsRef = useRef(new Set());
284
185
  const workspaceLastPushedDocumentWatermarksRef = useRef(new Map());
285
186
  const workspaceLastPushedAssetPathsRef = useRef(new Set());
@@ -291,6 +192,48 @@ export function useSyncOrchestrator(input) {
291
192
  const workspaceLastPushedAnnotatedAttachmentSessionIdsRef = useRef(new Set());
292
193
  const workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef = useRef(new Map());
293
194
  const workspaceLastPushedTaskEventWatermarksRef = useRef(new Map());
195
+ const hydrateWorkspacePushBaselines = useCallback((persisted) => {
196
+ if (!persisted)
197
+ return false;
198
+ workspaceLastPushedTaskIdsRef.current = new Set(persisted.taskIds);
199
+ workspaceLastPushedWatermarksRef.current = persisted.taskWatermarks;
200
+ workspaceLastPushedTaskChangeKeysRef.current = persisted.taskChangeKeys || new Map();
201
+ workspaceLastPushedInitiativeIdsRef.current = new Set(Array.from(persisted.initiativeWatermarks.keys()));
202
+ workspaceLastPushedInitiativeWatermarksRef.current = persisted.initiativeWatermarks;
203
+ workspaceLastPushedWorkstreamIdsRef.current = new Set(Array.from(persisted.workstreamWatermarks.keys()));
204
+ workspaceLastPushedWorkstreamWatermarksRef.current = persisted.workstreamWatermarks;
205
+ workspaceLastPushedAiProfileIdsRef.current = new Set(Array.from(persisted.aiProfileWatermarks.keys()));
206
+ workspaceLastPushedAiProfileWatermarksRef.current = persisted.aiProfileWatermarks;
207
+ workspaceLastPushedAiProfileChangeKeysRef.current = persisted.aiProfileChangeKeys || new Map();
208
+ workspaceLastPushedDocumentPathsRef.current = new Set(Array.from(persisted.documentWatermarks.keys()));
209
+ workspaceLastPushedDocumentWatermarksRef.current = persisted.documentWatermarks;
210
+ workspaceLastPushedAssetPathsRef.current = new Set(Array.from(persisted.assetWatermarks.keys()));
211
+ workspaceLastPushedAssetWatermarksRef.current = persisted.assetWatermarks;
212
+ workspaceLastPushedDocumentReviewSessionIdsRef.current = new Set(Array.from(persisted.documentReviewSessionWatermarks.keys()));
213
+ workspaceLastPushedDocumentReviewSessionWatermarksRef.current = persisted.documentReviewSessionWatermarks;
214
+ workspaceLastPushedDocumentReviewCommentIdsRef.current = new Set(Array.from(persisted.documentReviewCommentWatermarks.keys()));
215
+ workspaceLastPushedDocumentReviewCommentWatermarksRef.current = persisted.documentReviewCommentWatermarks;
216
+ workspaceLastPushedAnnotatedAttachmentSessionIdsRef.current = new Set(Array.from(persisted.annotatedAttachmentSessionWatermarks.keys()));
217
+ workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current = persisted.annotatedAttachmentSessionWatermarks;
218
+ workspaceLastPushedTaskEventWatermarksRef.current = persisted.taskEventWatermarks;
219
+ workspaceBaselineResetReasonRef.current = null;
220
+ return true;
221
+ }, [workspaceLastPushedTaskIdsRef]);
222
+ const buildCurrentPersistedWorkspaceSyncWatermarks = useCallback(() => ({
223
+ taskIds: workspaceLastPushedTaskIdsRef.current,
224
+ taskWatermarks: workspaceLastPushedWatermarksRef.current,
225
+ taskChangeKeys: workspaceLastPushedTaskChangeKeysRef.current,
226
+ initiativeWatermarks: workspaceLastPushedInitiativeWatermarksRef.current,
227
+ workstreamWatermarks: workspaceLastPushedWorkstreamWatermarksRef.current,
228
+ aiProfileWatermarks: workspaceLastPushedAiProfileWatermarksRef.current,
229
+ aiProfileChangeKeys: workspaceLastPushedAiProfileChangeKeysRef.current,
230
+ documentWatermarks: workspaceLastPushedDocumentWatermarksRef.current,
231
+ assetWatermarks: workspaceLastPushedAssetWatermarksRef.current,
232
+ documentReviewSessionWatermarks: workspaceLastPushedDocumentReviewSessionWatermarksRef.current,
233
+ documentReviewCommentWatermarks: workspaceLastPushedDocumentReviewCommentWatermarksRef.current,
234
+ annotatedAttachmentSessionWatermarks: workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current,
235
+ taskEventWatermarks: workspaceLastPushedTaskEventWatermarksRef.current,
236
+ }), []);
294
237
  const workspaceStartupRepairRanRef = useRef(false);
295
238
  const workspaceStartupFullReconcileRanRef = useRef(false);
296
239
  const workspaceRepairModeRef = useRef(false);
@@ -311,36 +254,22 @@ export function useSyncOrchestrator(input) {
311
254
  return workspaceLastPushAt;
312
255
  return null;
313
256
  }, [workspaceLastPullAt, workspaceLastPushAt]);
314
- const workspaceSyncStatus = useMemo(() => {
315
- if (!workspaceCloudSyncEnabled)
316
- return 'off';
317
- if (workspaceSyncBusy || workspaceSyncPhase === 'provision-local' || workspaceSyncPhase === 'attach-cloud') {
318
- return 'syncing';
319
- }
320
- if (workspaceRetryAt || workspaceLastErrorMessage || workspaceSyncPhase === 'error') {
321
- return 'attention';
322
- }
323
- return 'healthy';
324
- }, [
325
- workspaceCloudSyncEnabled,
326
- workspaceSyncBusy,
327
- workspaceSyncPhase,
328
- workspaceRetryAt,
329
- workspaceLastErrorMessage
330
- ]);
331
- const workspaceSyncSummary = useMemo(() => summarizeSyncPhase(workspaceCloudSyncEnabled, workspaceSyncPhase, workspaceSyncBusy, workspaceRetryAt, workspaceLastErrorMessage), [
257
+ const workspaceSyncPresentation = useMemo(() => resolveWorkspaceSyncPresentation({
258
+ enabled: workspaceCloudSyncEnabled,
259
+ phase: workspaceSyncPhase,
260
+ busy: workspaceSyncBusy,
261
+ retryAt: workspaceRetryAt,
262
+ lastErrorMessage: workspaceLastErrorMessage
263
+ }), [
332
264
  workspaceCloudSyncEnabled,
333
265
  workspaceSyncPhase,
334
266
  workspaceSyncBusy,
335
267
  workspaceRetryAt,
336
268
  workspaceLastErrorMessage
337
269
  ]);
338
- const workspaceSyncRecommendedAction = useMemo(() => recommendSyncAction(workspaceCloudSyncEnabled, workspaceSyncPhase, workspaceRetryAt, workspaceLastErrorMessage), [
339
- workspaceCloudSyncEnabled,
340
- workspaceSyncPhase,
341
- workspaceRetryAt,
342
- workspaceLastErrorMessage
343
- ]);
270
+ const workspaceSyncStatus = workspaceSyncPresentation.status;
271
+ const workspaceSyncSummary = workspaceSyncPresentation.summary;
272
+ const workspaceSyncRecommendedAction = workspaceSyncPresentation.recommendedAction;
344
273
  const getLocalUserSyncUpdatedAt = useCallback((userId) => {
345
274
  const key = String(userId || '').trim();
346
275
  if (!key)
@@ -531,6 +460,102 @@ export function useSyncOrchestrator(input) {
531
460
  const persistWorkspaceSyncPatchSafely = useCallback((patch) => {
532
461
  void persistWorkspaceSyncPatchBestEffort(patch).catch(() => { });
533
462
  }, [persistWorkspaceSyncPatchBestEffort]);
463
+ const persistWorkspaceSyncWatermarksSnapshotBestEffort = useCallback(() => {
464
+ const snapshotData = {
465
+ ...buildCurrentPersistedWorkspaceSyncWatermarks(),
466
+ taxonomyStateFingerprint
467
+ };
468
+ const persisted = savePersistedSyncWatermarks(currentWorkspaceId, snapshotData);
469
+ void persistWorkspaceSyncPatchBestEffort({
470
+ pushBaseline: buildPersistedSyncWatermarksSnapshot(snapshotData)
471
+ }).catch(() => { });
472
+ return persisted;
473
+ }, [
474
+ buildCurrentPersistedWorkspaceSyncWatermarks,
475
+ currentWorkspaceId,
476
+ persistWorkspaceSyncPatchBestEffort,
477
+ taxonomyStateFingerprint
478
+ ]);
479
+ useEffect(() => {
480
+ workspaceSyncMarkdownDocumentsFingerprintRef.current = '';
481
+ workspaceSyncAiProfilesFingerprintRef.current = '';
482
+ workspaceSyncAssetsFingerprintRef.current = '';
483
+ workspaceSyncDocumentReviewSessionsFingerprintRef.current = '';
484
+ workspaceSyncAnnotatedAttachmentSessionsFingerprintRef.current = '';
485
+ workspaceAttachBootstrapBaselineSignatureRef.current = '';
486
+ workspaceCaptureAttachBootstrapBaselineRef.current = false;
487
+ setWorkspaceSyncMarkdownDocuments([]);
488
+ setWorkspaceSyncMarkdownDocumentsFingerprint('');
489
+ workspaceSyncAiProfilesRef.current = [];
490
+ setWorkspaceSyncAiProfiles([]);
491
+ setWorkspaceSyncAiProfilesFingerprint('');
492
+ setWorkspaceSyncAssets([]);
493
+ setWorkspaceSyncAssetsFingerprint('');
494
+ workspaceSyncDocumentReviewSessionsRef.current = [];
495
+ workspaceSyncDocumentReviewCommentsRef.current = [];
496
+ setWorkspaceSyncDocumentReviewSessions([]);
497
+ setWorkspaceSyncDocumentReviewSessionsFingerprint('');
498
+ workspaceSyncAnnotatedAttachmentSessionsRef.current = [];
499
+ setWorkspaceSyncAnnotatedAttachmentSessions([]);
500
+ setWorkspaceSyncAnnotatedAttachmentSessionsFingerprint('');
501
+ setWorkspaceSyncReferenceSnapshotStatus(EMPTY_WORKSPACE_SYNC_REFERENCE_SNAPSHOT_STATUS);
502
+ setWorkspaceLastErrorMessage(null);
503
+ setWorkspaceLastErrorAt(null);
504
+ setWorkspaceSyncPendingChanges(0);
505
+ realtimeSuppressedEventIdsRef.current = new Set();
506
+ realtimeSuppressedEventQueueRef.current = [];
507
+ // Reset watermarks for the new workspace, then restore from localStorage if recent
508
+ // enough. This prevents a full resync on every page reload while still catching any
509
+ // changes that happened while the app was closed.
510
+ workspaceLastPushedWatermarksRef.current = new Map();
511
+ workspaceLastPushedTaskChangeKeysRef.current = new Map();
512
+ workspaceLastPushedInitiativeIdsRef.current = new Set();
513
+ workspaceLastPushedInitiativeWatermarksRef.current = new Map();
514
+ workspaceLastPushedWorkstreamIdsRef.current = new Set();
515
+ workspaceLastPushedWorkstreamWatermarksRef.current = new Map();
516
+ workspaceLastPushedAiProfileIdsRef.current = new Set();
517
+ workspaceLastPushedAiProfileWatermarksRef.current = new Map();
518
+ workspaceLastPushedAiProfileChangeKeysRef.current = new Map();
519
+ workspaceLastPushedDocumentPathsRef.current = new Set();
520
+ workspaceLastPushedDocumentWatermarksRef.current = new Map();
521
+ workspaceLastPushedAssetPathsRef.current = new Set();
522
+ workspaceLastPushedAssetWatermarksRef.current = new Map();
523
+ workspaceLastPushedDocumentReviewSessionIdsRef.current = new Set();
524
+ workspaceLastPushedDocumentReviewSessionWatermarksRef.current = new Map();
525
+ workspaceLastPushedDocumentReviewCommentIdsRef.current = new Set();
526
+ workspaceLastPushedDocumentReviewCommentWatermarksRef.current = new Map();
527
+ workspaceLastPushedAnnotatedAttachmentSessionIdsRef.current = new Set();
528
+ workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current = new Map();
529
+ workspaceLastPushedTaskEventWatermarksRef.current = new Map();
530
+ workspaceStartupFullReconcileRanRef.current = false;
531
+ workspaceFullReconcileInFlightRef.current = false;
532
+ const persisted = readPersistedSyncWatermarks(currentWorkspaceId, taxonomyStateFingerprint);
533
+ if (!hydrateWorkspacePushBaselines(persisted)) {
534
+ workspaceBaselineResetReasonRef.current = 'missing-baseline';
535
+ }
536
+ }, [currentWorkspaceId, hydrateWorkspacePushBaselines, taxonomyStateFingerprint]);
537
+ useEffect(() => {
538
+ if (workspaceBaselineResetReasonRef.current !== 'missing-baseline')
539
+ return;
540
+ const persistedLocal = readPersistedSyncWatermarks(currentWorkspaceId, taxonomyStateFingerprint);
541
+ if (hydrateWorkspacePushBaselines(persistedLocal))
542
+ return;
543
+ const persistedRemote = parsePersistedSyncWatermarksSnapshot(readWorkspaceSyncStateSnapshot().pushBaseline, taxonomyStateFingerprint);
544
+ if (!hydrateWorkspacePushBaselines(persistedRemote))
545
+ return;
546
+ savePersistedSyncWatermarks(currentWorkspaceId, {
547
+ ...buildCurrentPersistedWorkspaceSyncWatermarks(),
548
+ taxonomyStateFingerprint
549
+ });
550
+ }, [
551
+ buildCurrentPersistedWorkspaceSyncWatermarks,
552
+ currentWorkspaceId,
553
+ hydrateWorkspacePushBaselines,
554
+ readWorkspaceSyncStateSnapshot,
555
+ taxonomyStateFingerprint,
556
+ workspaceCloudSyncEnabled,
557
+ workspaceSyncPhase
558
+ ]);
534
559
  const scheduleWorkspaceSyncRetry = useCallback((minDelayMs) => {
535
560
  scheduleWorkspaceRetry(() => retryWorkspaceSyncRef.current(), { minDelayMs });
536
561
  }, [scheduleWorkspaceRetry]);
@@ -592,13 +617,16 @@ export function useSyncOrchestrator(input) {
592
617
  workspaceArchivedTasksRef.current = archivedTasks || [];
593
618
  }, [archivedTasks]);
594
619
  useEffect(() => {
620
+ workspaceDeletedTaskIdsRef.current = new Set((deletedTasks || [])
621
+ .map((entry) => String(entry?.taskId || '').trim())
622
+ .filter((taskId) => taskId.length > 0));
595
623
  workspaceDeletedTaskWatermarksRef.current = new Map((deletedTasks || [])
596
624
  .map((entry) => [
597
625
  String(entry?.taskId || '').trim(),
598
626
  String(entry?.deletedAt || '').trim()
599
627
  ])
600
628
  .filter(([taskId, deletedAt]) => taskId.length > 0 && Number.isFinite(Date.parse(deletedAt))));
601
- }, [deletedTasks, workspaceDeletedTaskWatermarksRef]);
629
+ }, [deletedTasks, workspaceDeletedTaskIdsRef, workspaceDeletedTaskWatermarksRef]);
602
630
  useEffect(() => {
603
631
  workspaceInitiativesRef.current = initiatives || [];
604
632
  }, [initiatives]);
@@ -615,7 +643,7 @@ export function useSyncOrchestrator(input) {
615
643
  return buildWorkspaceSyncPayloadModel({
616
644
  tasks: workspaceTasksRef.current,
617
645
  archivedTasks: workspaceArchivedTasksRef.current,
618
- taskEvents: flattenTaskEvents(workspaceTasksRef.current, workspaceArchivedTasksRef.current),
646
+ taskEvents: flattenTaskEvents(workspaceTasksRef.current, workspaceArchivedTasksRef.current, workspaceInitiativesRef.current, workspaceWorkstreamsRef.current),
619
647
  lastPushedTaskIds: workspaceLastPushedTaskIdsRef.current,
620
648
  pendingDeletedTaskIds: workspaceDeletedTaskIdsRef.current,
621
649
  pendingDeletedTaskWatermarks: workspaceDeletedTaskWatermarksRef.current,
@@ -630,8 +658,11 @@ export function useSyncOrchestrator(input) {
630
658
  aiProfiles: workspaceSyncAiProfilesRef.current,
631
659
  lastPushedAiProfileIds: forceAllAiProfiles ? new Set() : workspaceLastPushedAiProfileIdsRef.current,
632
660
  lastPushedAiProfileWatermarks: forceAllAiProfiles ? new Map() : workspaceLastPushedAiProfileWatermarksRef.current,
661
+ lastPushedAiProfileChangeKeys: forceAllAiProfiles ? new Map() : workspaceLastPushedAiProfileChangeKeysRef.current,
633
662
  documents: workspaceSyncMarkdownDocumentsRef.current,
634
663
  assets: workspaceSyncAssetsRef.current,
664
+ currentDocumentPaths: workspaceSyncKnownDocumentPathsRef.current,
665
+ currentAssetPaths: workspaceSyncKnownAssetPathsRef.current,
635
666
  documentReviewSessions: workspaceSyncDocumentReviewSessionsRef.current,
636
667
  documentReviewComments: workspaceSyncDocumentReviewCommentsRef.current,
637
668
  annotatedAttachmentSessions: workspaceSyncAnnotatedAttachmentSessionsRef.current,
@@ -646,25 +677,42 @@ export function useSyncOrchestrator(input) {
646
677
  lastPushedAnnotatedAttachmentSessionIds: workspaceLastPushedAnnotatedAttachmentSessionIdsRef.current,
647
678
  lastPushedAnnotatedAttachmentSessionWatermarks: workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current,
648
679
  lastPushedTaskEventWatermarks: workspaceLastPushedTaskEventWatermarksRef.current,
649
- lastPushedWatermarks: workspaceLastPushedWatermarksRef.current
680
+ baselineResetReason: workspaceBaselineResetReasonRef.current,
681
+ lastPushedWatermarks: workspaceLastPushedWatermarksRef.current,
682
+ lastPushedTaskChangeKeys: workspaceLastPushedTaskChangeKeysRef.current
650
683
  });
651
684
  }, [taxonomies, taxonomyState]);
652
685
  const reportSyncEvent = useCallback((workspaceId, event) => {
653
686
  if (runtimeMode !== 'local')
654
687
  return;
655
- fetch('/api/taskforce/sync/events', {
656
- method: 'POST',
657
- credentials: 'include',
658
- headers: { 'Content-Type': 'application/json' },
659
- body: JSON.stringify({ ...event, workspaceId })
660
- }).catch((error) => {
688
+ try {
689
+ const normalizedEvent = {
690
+ ...event,
691
+ workspaceId,
692
+ details: normalizeSyncEventDetails(event.details)
693
+ };
694
+ void fetch('/api/taskforce/sync/events', {
695
+ method: 'POST',
696
+ credentials: 'include',
697
+ headers: { 'Content-Type': 'application/json' },
698
+ body: JSON.stringify(normalizedEvent)
699
+ }).catch((error) => {
700
+ logSyncDebug('sync_event_post_failed', {
701
+ workspaceId,
702
+ eventType: event.eventType,
703
+ status: event.status,
704
+ error: String(error?.message || error || '')
705
+ });
706
+ });
707
+ }
708
+ catch (error) {
661
709
  logSyncDebug('sync_event_post_failed', {
662
710
  workspaceId,
663
711
  eventType: event.eventType,
664
712
  status: event.status,
665
713
  error: String(error?.message || error || '')
666
714
  });
667
- });
715
+ }
668
716
  }, [runtimeMode]);
669
717
  const buildWorkspaceSyncSignature = useCallback(() => {
670
718
  // Read documents and assets from refs for the same stale-closure reason as
@@ -673,7 +721,7 @@ export function useSyncOrchestrator(input) {
673
721
  workspaceId: currentWorkspaceId,
674
722
  tasks: workspaceTasksRef.current,
675
723
  archivedTasks: workspaceArchivedTasksRef.current,
676
- taskEvents: flattenTaskEvents(workspaceTasksRef.current, workspaceArchivedTasksRef.current),
724
+ taskEvents: flattenTaskEvents(workspaceTasksRef.current, workspaceArchivedTasksRef.current, workspaceInitiativesRef.current, workspaceWorkstreamsRef.current),
677
725
  pendingDeletedTaskIds: workspaceDeletedTaskIdsRef.current,
678
726
  initiatives: workspaceInitiativesRef.current,
679
727
  workstreams: workspaceWorkstreamsRef.current,
@@ -720,17 +768,25 @@ export function useSyncOrchestrator(input) {
720
768
  isArchived: Boolean(workstream.isArchived)
721
769
  }))
722
770
  .sort((left, right) => left.id.localeCompare(right.id)),
723
- taskEvents: flattenTaskEvents(tasks || [], archivedTasks || [])
771
+ taskEvents: flattenTaskEvents(tasks || [], archivedTasks || [], initiatives || [], workstreams || [])
724
772
  .map((event) => ({
725
773
  id: String(event.id || '').trim(),
726
- taskId: String(event.taskId || '').trim(),
774
+ entityType: String(event.entityType || 'task').trim(),
775
+ entityId: String(event.entityId || event.taskId || '').trim(),
727
776
  createdAt: String(event.createdAt || '').trim(),
728
777
  action: String(event.action || '').trim(),
729
778
  }))
730
779
  .sort((left, right) => left.id.localeCompare(right.id)),
780
+ deletedTasks: (deletedTasks || [])
781
+ .map((entry) => ({
782
+ taskId: String(entry?.taskId || '').trim(),
783
+ deletedAt: String(entry?.deletedAt || '').trim()
784
+ }))
785
+ .filter((entry) => entry.taskId.length > 0 && Number.isFinite(Date.parse(entry.deletedAt)))
786
+ .sort((left, right) => left.taskId.localeCompare(right.taskId)),
731
787
  taxonomies: Array.isArray(taxonomies) ? taxonomies : [],
732
788
  taxonomyState: taxonomyState && typeof taxonomyState === 'object' ? taxonomyState : null
733
- }), [tasks, archivedTasks, initiatives, workstreams, taxonomies, taxonomyState]);
789
+ }), [tasks, archivedTasks, deletedTasks, initiatives, workstreams, taxonomies, taxonomyState]);
734
790
  // Fingerprint of per-task attachment counts — changes when the MCP tool saves a new
735
791
  // attachment and the React app polls the updated task. Used to trigger an immediate
736
792
  // asset snapshot refresh without requiring a cross-process DOM event.
@@ -797,6 +853,11 @@ export function useSyncOrchestrator(input) {
797
853
  }))
798
854
  .filter((entry) => entry.path.length > 0)
799
855
  : [];
856
+ const knownDocumentPaths = new Set(Array.isArray(data?.knownPaths)
857
+ ? data.knownPaths
858
+ .map((entry) => String(entry || '').trim())
859
+ .filter((entry) => entry.length > 0)
860
+ : docs.map((entry) => entry.path));
800
861
  const fingerprint = typeof data?.fingerprint === 'string'
801
862
  ? data.fingerprint
802
863
  : JSON.stringify(docs.map((entry) => `${entry.path}:${entry.updatedAt}:${entry.content.length}`).sort());
@@ -804,6 +865,7 @@ export function useSyncOrchestrator(input) {
804
865
  return;
805
866
  workspaceSyncMarkdownDocumentsFingerprintRef.current = fingerprint;
806
867
  workspaceSyncMarkdownDocumentsRef.current = docs;
868
+ workspaceSyncKnownDocumentPathsRef.current = knownDocumentPaths;
807
869
  setWorkspaceSyncMarkdownDocuments(docs);
808
870
  setWorkspaceSyncMarkdownDocumentsFingerprint(fingerprint);
809
871
  }
@@ -853,7 +915,16 @@ export function useSyncOrchestrator(input) {
853
915
  setWorkspaceSyncAiProfilesLastFetchAt(new Date().toISOString());
854
916
  const fingerprint = typeof data?.fingerprint === 'string'
855
917
  ? data.fingerprint
856
- : JSON.stringify(aiProfiles.map((entry) => `${entry.id}:${entry.updatedAt}:${entry.name}:${entry.username}`).sort());
918
+ : JSON.stringify(aiProfiles.map((entry) => [
919
+ entry.id,
920
+ entry.updatedAt,
921
+ entry.name,
922
+ entry.username,
923
+ entry.archivedAt ?? '',
924
+ entry.archivedReason ?? '',
925
+ entry.mergedIntoProfileId ?? '',
926
+ entry.rosterStateUpdatedAt ?? ''
927
+ ].join(':')).sort());
857
928
  if (fingerprint === workspaceSyncAiProfilesFingerprintRef.current)
858
929
  return;
859
930
  workspaceSyncAiProfilesFingerprintRef.current = fingerprint;
@@ -910,6 +981,11 @@ export function useSyncOrchestrator(input) {
910
981
  }))
911
982
  .filter((entry) => entry.path.length > 0 && entry.contentBase64.length > 0)
912
983
  : [];
984
+ const knownAssetPaths = new Set(Array.isArray(data?.knownPaths)
985
+ ? data.knownPaths
986
+ .map((entry) => String(entry || '').trim())
987
+ .filter((entry) => entry.length > 0)
988
+ : assets.map((entry) => entry.path));
913
989
  const fingerprint = typeof data?.fingerprint === 'string'
914
990
  ? data.fingerprint
915
991
  : JSON.stringify(assets.map((entry) => `${entry.path}:${entry.updatedAt}:${entry.contentBase64.length}`).sort());
@@ -917,6 +993,7 @@ export function useSyncOrchestrator(input) {
917
993
  return;
918
994
  workspaceSyncAssetsFingerprintRef.current = fingerprint;
919
995
  workspaceSyncAssetsRef.current = assets;
996
+ workspaceSyncKnownAssetPathsRef.current = knownAssetPaths;
920
997
  setWorkspaceSyncAssets(assets);
921
998
  setWorkspaceSyncAssetsFingerprint(fingerprint);
922
999
  }
@@ -930,6 +1007,37 @@ export function useSyncOrchestrator(input) {
930
1007
  markWorkspaceSyncReferenceSnapshotReady('assets');
931
1008
  }
932
1009
  }, [runtimeMode, currentWorkspaceId, cloudAuthConfigured, authSessionResolved, isAuthenticated, markWorkspaceSyncReferenceSnapshotReady]);
1010
+ const refreshWorkspaceSyncPlanningEntitiesSnapshot = useCallback(async () => {
1011
+ if (runtimeMode !== 'local')
1012
+ return;
1013
+ if (!isValidWorkspaceKey(currentWorkspaceId))
1014
+ return;
1015
+ if (cloudAuthConfigured && (!authSessionResolved || !isAuthenticated))
1016
+ return;
1017
+ try {
1018
+ const res = await fetch(`/api/taskforce/planning/bootstrap?workspaceId=${encodeURIComponent(currentWorkspaceId)}`, {
1019
+ method: 'GET',
1020
+ credentials: 'include'
1021
+ });
1022
+ if (!res.ok)
1023
+ return;
1024
+ const data = await res.json().catch(() => ({}));
1025
+ const nextInitiatives = Array.isArray(data?.initiatives)
1026
+ ? data.initiatives.filter((entry) => (Boolean(entry && typeof entry === 'object' && typeof entry.id === 'string')))
1027
+ : [];
1028
+ const nextWorkstreams = Array.isArray(data?.workstreams)
1029
+ ? data.workstreams.filter((entry) => (Boolean(entry && typeof entry === 'object' && typeof entry.id === 'string')))
1030
+ : [];
1031
+ workspaceInitiativesRef.current = nextInitiatives;
1032
+ workspaceWorkstreamsRef.current = nextWorkstreams;
1033
+ }
1034
+ catch (error) {
1035
+ logSyncDebug('planning_snapshot_refresh_failed', {
1036
+ workspaceId: currentWorkspaceId,
1037
+ error: String(error?.message || error || '')
1038
+ });
1039
+ }
1040
+ }, [runtimeMode, currentWorkspaceId, cloudAuthConfigured, authSessionResolved, isAuthenticated]);
933
1041
  const refreshWorkspaceSyncDocumentReviewSessionsSnapshot = useCallback(async () => {
934
1042
  if (runtimeMode !== 'local')
935
1043
  return;
@@ -1059,6 +1167,38 @@ export function useSyncOrchestrator(input) {
1059
1167
  }
1060
1168
  }, [runtimeMode, currentWorkspaceId, cloudAuthConfigured, authSessionResolved, isAuthenticated, markWorkspaceSyncReferenceSnapshotReady]);
1061
1169
  const seedWorkspaceSnapshotPushBaseline = useCallback(() => {
1170
+ workspaceLastPushedTaskIdsRef.current = new Set([...workspaceTasksRef.current, ...workspaceArchivedTasksRef.current]
1171
+ .map((entry) => String(entry?.id || '').trim())
1172
+ .filter((id) => id.length > 0));
1173
+ workspaceLastPushedWatermarksRef.current = new Map([...workspaceTasksRef.current, ...workspaceArchivedTasksRef.current]
1174
+ .map((entry) => [String(entry?.id || '').trim(), taskSyncResourceAdapter.getWatermark(entry)])
1175
+ .filter(([id]) => id.length > 0));
1176
+ workspaceLastPushedTaskChangeKeysRef.current = new Map([
1177
+ ...workspaceTasksRef.current.map((entry) => [String(entry?.id || '').trim(), taskSyncResourceAdapter.getChangeKey({ ...entry, isArchived: false })]),
1178
+ ...workspaceArchivedTasksRef.current.map((entry) => [String(entry?.id || '').trim(), taskSyncResourceAdapter.getChangeKey({ ...entry, isArchived: true })])
1179
+ ]
1180
+ .filter(([id]) => id.length > 0));
1181
+ workspaceLastPushedInitiativeIdsRef.current = new Set(workspaceInitiativesRef.current
1182
+ .map((entry) => String(entry?.id || '').trim())
1183
+ .filter((id) => id.length > 0));
1184
+ workspaceLastPushedInitiativeWatermarksRef.current = new Map(workspaceInitiativesRef.current
1185
+ .map((entry) => [String(entry?.id || '').trim(), String(entry?.updatedAt || entry?.createdAt || '').trim()])
1186
+ .filter(([id]) => id.length > 0));
1187
+ workspaceLastPushedWorkstreamIdsRef.current = new Set(workspaceWorkstreamsRef.current
1188
+ .map((entry) => String(entry?.id || '').trim())
1189
+ .filter((id) => id.length > 0));
1190
+ workspaceLastPushedWorkstreamWatermarksRef.current = new Map(workspaceWorkstreamsRef.current
1191
+ .map((entry) => [String(entry?.id || '').trim(), String(entry?.updatedAt || entry?.createdAt || '').trim()])
1192
+ .filter(([id]) => id.length > 0));
1193
+ workspaceLastPushedAiProfileIdsRef.current = new Set(workspaceSyncAiProfilesRef.current
1194
+ .map((entry) => String(entry?.id || '').trim())
1195
+ .filter((id) => id.length > 0));
1196
+ workspaceLastPushedAiProfileWatermarksRef.current = new Map(workspaceSyncAiProfilesRef.current
1197
+ .map((entry) => [String(entry?.id || '').trim(), aiProfileSyncResourceAdapter.getWatermark(entry)])
1198
+ .filter(([id]) => id.length > 0));
1199
+ workspaceLastPushedAiProfileChangeKeysRef.current = new Map(workspaceSyncAiProfilesRef.current
1200
+ .map((entry) => [String(entry?.id || '').trim(), aiProfileSyncResourceAdapter.getChangeKey(entry)])
1201
+ .filter(([id]) => id.length > 0));
1062
1202
  workspaceLastPushedDocumentPathsRef.current = new Set(workspaceSyncMarkdownDocumentsRef.current
1063
1203
  .map((entry) => String(entry?.path || '').trim())
1064
1204
  .filter((path) => path.length > 0));
@@ -1089,10 +1229,12 @@ export function useSyncOrchestrator(input) {
1089
1229
  workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current = new Map(workspaceSyncAnnotatedAttachmentSessionsRef.current
1090
1230
  .map((entry) => [String(entry?.id || '').trim(), String(entry?.updatedAt || entry?.createdAt || '').trim()])
1091
1231
  .filter(([id]) => id.length > 0));
1092
- workspaceLastPushedTaskEventWatermarksRef.current = new Map(flattenTaskEvents(workspaceTasksRef.current, workspaceArchivedTasksRef.current)
1232
+ workspaceLastPushedTaskEventWatermarksRef.current = new Map(flattenTaskEvents(workspaceTasksRef.current, workspaceArchivedTasksRef.current, workspaceInitiativesRef.current, workspaceWorkstreamsRef.current)
1093
1233
  .map((entry) => [String(entry?.id || '').trim(), String(entry?.createdAt || '').trim()])
1094
1234
  .filter(([id]) => id.length > 0));
1095
- }, []);
1235
+ const persisted = persistWorkspaceSyncWatermarksSnapshotBestEffort();
1236
+ workspaceBaselineResetReasonRef.current = persisted ? null : 'storage-write-failed';
1237
+ }, [persistWorkspaceSyncWatermarksSnapshotBestEffort]);
1096
1238
  const refreshLocalWorkspaceTaskCollections = useCallback(async () => {
1097
1239
  const [tasksRes, archiveRes] = await Promise.all([
1098
1240
  fetch('/api/taskforce/tasks', {
@@ -1163,7 +1305,6 @@ export function useSyncOrchestrator(input) {
1163
1305
  workspaceCloudSyncEnabled,
1164
1306
  currentWorkspaceId,
1165
1307
  workspaceSyncPhase,
1166
- taxonomyStateFingerprint,
1167
1308
  isAuthenticated,
1168
1309
  checkAuthSession,
1169
1310
  resolveCloudAuthUrl,
@@ -1176,6 +1317,7 @@ export function useSyncOrchestrator(input) {
1176
1317
  scheduleWorkspaceSyncRetry,
1177
1318
  persistWorkspaceSyncPatchSafely,
1178
1319
  persistWorkspaceSyncPatchBestEffort,
1320
+ persistWorkspaceSyncWatermarksSnapshotBestEffort,
1179
1321
  reportSyncEvent,
1180
1322
  acquireWorkspaceSyncLease,
1181
1323
  releaseWorkspaceSyncLease,
@@ -1183,6 +1325,7 @@ export function useSyncOrchestrator(input) {
1183
1325
  refreshWorkspaceSyncMarkdownDocumentsSnapshot,
1184
1326
  refreshWorkspaceSyncAiProfilesSnapshot,
1185
1327
  refreshWorkspaceSyncAssetsSnapshot,
1328
+ refreshWorkspaceSyncPlanningEntitiesSnapshot,
1186
1329
  refreshWorkspaceSyncDocumentReviewSessionsSnapshot,
1187
1330
  refreshWorkspaceSyncAnnotatedAttachmentSessionsSnapshot,
1188
1331
  refreshLocalWorkspaceTaskCollections,
@@ -1195,6 +1338,7 @@ export function useSyncOrchestrator(input) {
1195
1338
  workspaceForceFullAiProfilePushRef,
1196
1339
  workspacePushInFlightRef,
1197
1340
  workspacePullInFlightRef,
1341
+ workspacePendingPushReplayRef,
1198
1342
  workspaceLastPushedSignatureRef,
1199
1343
  workspacePendingSignatureRef,
1200
1344
  workspaceLastPushedTaskIdsRef,
@@ -1202,12 +1346,14 @@ export function useSyncOrchestrator(input) {
1202
1346
  workspaceDeletedTaskWatermarksRef,
1203
1347
  workspacePullCursorRef,
1204
1348
  workspaceLastPushedWatermarksRef,
1349
+ workspaceLastPushedTaskChangeKeysRef,
1205
1350
  workspaceLastPushedInitiativeIdsRef,
1206
1351
  workspaceLastPushedInitiativeWatermarksRef,
1207
1352
  workspaceLastPushedWorkstreamIdsRef,
1208
1353
  workspaceLastPushedWorkstreamWatermarksRef,
1209
1354
  workspaceLastPushedAiProfileIdsRef,
1210
1355
  workspaceLastPushedAiProfileWatermarksRef,
1356
+ workspaceLastPushedAiProfileChangeKeysRef,
1211
1357
  workspaceLastPushedDocumentPathsRef,
1212
1358
  workspaceLastPushedDocumentWatermarksRef,
1213
1359
  workspaceLastPushedAssetPathsRef,
@@ -1219,6 +1365,7 @@ export function useSyncOrchestrator(input) {
1219
1365
  workspaceLastPushedAnnotatedAttachmentSessionIdsRef,
1220
1366
  workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef,
1221
1367
  workspaceLastPushedTaskEventWatermarksRef,
1368
+ workspaceBaselineResetReasonRef,
1222
1369
  workspaceCaptureAttachBootstrapBaselineRef,
1223
1370
  workspaceStartupFullReconcileRanRef,
1224
1371
  workspaceFullReconcileInFlightRef,
@@ -1252,6 +1399,7 @@ export function useSyncOrchestrator(input) {
1252
1399
  scheduleWorkspaceSyncRetry,
1253
1400
  persistWorkspaceSyncPatchSafely,
1254
1401
  persistWorkspaceSyncPatchBestEffort,
1402
+ persistWorkspaceSyncWatermarksSnapshotBestEffort,
1255
1403
  reportSyncEvent,
1256
1404
  acquireWorkspaceSyncLease,
1257
1405
  releaseWorkspaceSyncLease,
@@ -1259,6 +1407,7 @@ export function useSyncOrchestrator(input) {
1259
1407
  refreshWorkspaceSyncMarkdownDocumentsSnapshot,
1260
1408
  refreshWorkspaceSyncAiProfilesSnapshot,
1261
1409
  refreshWorkspaceSyncAssetsSnapshot,
1410
+ refreshWorkspaceSyncPlanningEntitiesSnapshot,
1262
1411
  refreshWorkspaceSyncDocumentReviewSessionsSnapshot,
1263
1412
  refreshWorkspaceSyncAnnotatedAttachmentSessionsSnapshot,
1264
1413
  refreshLocalWorkspaceTaskCollections,
@@ -1339,7 +1488,6 @@ export function useSyncOrchestrator(input) {
1339
1488
  }, [syncUserGlobalSettings]);
1340
1489
  const recoveryCoordinator = useMemo(() => createSyncRecoveryCoordinator({
1341
1490
  currentWorkspaceId,
1342
- taxonomyStateFingerprint,
1343
1491
  workspaceCloudSyncEnabled,
1344
1492
  workspaceSyncPhase,
1345
1493
  workspaceLastPullAt,
@@ -1355,12 +1503,14 @@ export function useSyncOrchestrator(input) {
1355
1503
  workspacePendingSignatureRef,
1356
1504
  workspaceLastPushedSignatureRef,
1357
1505
  workspaceLastPushedWatermarksRef,
1506
+ workspaceLastPushedTaskChangeKeysRef,
1358
1507
  workspaceLastPushedInitiativeIdsRef,
1359
1508
  workspaceLastPushedInitiativeWatermarksRef,
1360
1509
  workspaceLastPushedWorkstreamIdsRef,
1361
1510
  workspaceLastPushedWorkstreamWatermarksRef,
1362
1511
  workspaceLastPushedAiProfileIdsRef,
1363
1512
  workspaceLastPushedAiProfileWatermarksRef,
1513
+ workspaceLastPushedAiProfileChangeKeysRef,
1364
1514
  workspaceLastPushedDocumentPathsRef,
1365
1515
  workspaceLastPushedDocumentWatermarksRef,
1366
1516
  workspaceLastPushedAssetPathsRef,
@@ -1372,6 +1522,7 @@ export function useSyncOrchestrator(input) {
1372
1522
  workspaceLastPushedAnnotatedAttachmentSessionIdsRef,
1373
1523
  workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef,
1374
1524
  workspaceLastPushedTaskEventWatermarksRef,
1525
+ workspaceBaselineResetReasonRef,
1375
1526
  bootstrapPhaseStartedAtRef,
1376
1527
  bootstrapLastProgressAtRef,
1377
1528
  acquireWorkspaceSyncLease,
@@ -1380,7 +1531,9 @@ export function useSyncOrchestrator(input) {
1380
1531
  clearWorkspaceRetry,
1381
1532
  clearWorkspaceIssue,
1382
1533
  reportWorkspaceWindowContention,
1534
+ reportSyncEvent,
1383
1535
  persistWorkspaceSyncPatch,
1536
+ persistWorkspaceSyncWatermarksSnapshotBestEffort,
1384
1537
  buildWorkspaceSyncSignature,
1385
1538
  pullWorkspaceChangesFromCloud,
1386
1539
  pushWorkspaceChangesToCloud,
@@ -1400,7 +1553,9 @@ export function useSyncOrchestrator(input) {
1400
1553
  clearWorkspaceRetry,
1401
1554
  clearWorkspaceIssue,
1402
1555
  reportWorkspaceWindowContention,
1556
+ reportSyncEvent,
1403
1557
  persistWorkspaceSyncPatch,
1558
+ persistWorkspaceSyncWatermarksSnapshotBestEffort,
1404
1559
  buildWorkspaceSyncSignature,
1405
1560
  pullWorkspaceChangesFromCloud,
1406
1561
  pushWorkspaceChangesToCloud,
@@ -1412,6 +1567,39 @@ export function useSyncOrchestrator(input) {
1412
1567
  const { retryWorkspaceCloudSync, resetWorkspaceSyncCursorAndPull } = recoveryCoordinator;
1413
1568
  // === SECTION: Diagnostics & reactive effects ===
1414
1569
  const getWorkspaceSyncDiagnostics = useCallback(() => {
1570
+ const pushReadiness = resolvePushSyncReadiness({
1571
+ cloudAuthConfigured,
1572
+ runtimeMode,
1573
+ workspaceCloudSyncEnabled,
1574
+ currentWorkspaceId,
1575
+ workspaceSyncPhase,
1576
+ isAuthenticated,
1577
+ repairModeActive: workspaceRepairModeRef.current === true,
1578
+ repairModeBypass: false,
1579
+ pushInFlight: workspacePushInFlightRef.current,
1580
+ pullInFlight: workspacePullInFlightRef.current
1581
+ });
1582
+ const retryPending = isWorkspaceRetryPending();
1583
+ const pullReadiness = resolvePullSyncReadiness({
1584
+ cloudAuthConfigured,
1585
+ runtimeMode,
1586
+ workspaceCloudSyncEnabled,
1587
+ currentWorkspaceId,
1588
+ workspaceSyncPhase,
1589
+ isAuthenticated,
1590
+ repairModeActive: workspaceRepairModeRef.current === true,
1591
+ repairModeBypass: false,
1592
+ retryPending,
1593
+ pushInFlight: workspacePushInFlightRef.current,
1594
+ pullInFlight: workspacePullInFlightRef.current
1595
+ });
1596
+ const retryReadiness = resolveRetrySyncReadiness({
1597
+ cloudAuthConfigured,
1598
+ runtimeMode,
1599
+ workspaceCloudSyncEnabled,
1600
+ currentWorkspaceId,
1601
+ isAuthenticated
1602
+ });
1415
1603
  return {
1416
1604
  workspaceId: currentWorkspaceId,
1417
1605
  enabled: workspaceCloudSyncEnabled,
@@ -1425,6 +1613,13 @@ export function useSyncOrchestrator(input) {
1425
1613
  pendingChanges: workspaceSyncPendingChanges,
1426
1614
  lastErrorMessage: workspaceLastErrorMessage,
1427
1615
  recommendedAction: workspaceSyncRecommendedAction,
1616
+ pushBlockedReason: pushReadiness.allowed ? null : pushReadiness.reason,
1617
+ pullBlockedReason: pullReadiness.allowed ? null : pullReadiness.reason,
1618
+ retryBlockedReason: retryReadiness.allowed ? null : retryReadiness.reason,
1619
+ repairActive: workspaceRepairModeRef.current === true,
1620
+ pushInFlight: workspacePushInFlightRef.current === true,
1621
+ pullInFlight: workspacePullInFlightRef.current === true,
1622
+ retryPending,
1428
1623
  documentSnapshotCount: workspaceSyncMarkdownDocuments.length,
1429
1624
  aiProfileSnapshotCount: workspaceSyncAiProfiles.length,
1430
1625
  aiProfileSnapshotRawCount: workspaceSyncAiProfilesRawCount,
@@ -1441,9 +1636,12 @@ export function useSyncOrchestrator(input) {
1441
1636
  forceFullAiProfilePushQueued: workspaceForceFullAiProfilePushRef.current === true
1442
1637
  };
1443
1638
  }, [
1639
+ cloudAuthConfigured,
1444
1640
  currentWorkspaceId,
1641
+ runtimeMode,
1445
1642
  workspaceCloudSyncEnabled,
1446
1643
  workspaceSyncPhase,
1644
+ isAuthenticated,
1447
1645
  workspaceSyncStatus,
1448
1646
  workspaceSyncSummary,
1449
1647
  workspaceLastSuccessfulSyncAt,
@@ -1463,7 +1661,8 @@ export function useSyncOrchestrator(input) {
1463
1661
  workspaceSyncDocumentReviewSessions.length,
1464
1662
  workspaceSyncDocumentReviewSessionsFingerprint,
1465
1663
  workspaceSyncAnnotatedAttachmentSessions.length,
1466
- workspaceSyncAnnotatedAttachmentSessionsFingerprint
1664
+ workspaceSyncAnnotatedAttachmentSessionsFingerprint,
1665
+ isWorkspaceRetryPending
1467
1666
  ]);
1468
1667
  useEffect(() => {
1469
1668
  retryWorkspaceSyncRef.current = retryWorkspaceCloudSync;
@@ -1492,7 +1691,16 @@ export function useSyncOrchestrator(input) {
1492
1691
  return;
1493
1692
  refreshWorkspaceSyncContextAssetSnapshots();
1494
1693
  };
1694
+ const handleAiProfilesMutated = (event) => {
1695
+ const detail = event.detail;
1696
+ const eventWorkspaceId = String(detail?.workspaceId || '').trim() || 'default';
1697
+ if (eventWorkspaceId !== currentWorkspaceId)
1698
+ return;
1699
+ void refreshWorkspaceSyncAiProfilesSnapshot();
1700
+ void refreshWorkspaceSyncAssetsSnapshot();
1701
+ };
1495
1702
  window.addEventListener(TASKFORCE_CONTEXT_ASSETS_MUTATED_EVENT, handleContextAssetsMutated);
1703
+ window.addEventListener(TASKFORCE_AI_PROFILES_MUTATED_EVENT, handleAiProfilesMutated);
1496
1704
  const docsIntervalId = window.setInterval(() => {
1497
1705
  void refreshWorkspaceSyncMarkdownDocumentsSnapshot();
1498
1706
  }, workspaceDocumentsRefreshIntervalMs);
@@ -1510,6 +1718,7 @@ export function useSyncOrchestrator(input) {
1510
1718
  }, workspaceDocumentsRefreshIntervalMs);
1511
1719
  return () => {
1512
1720
  window.removeEventListener(TASKFORCE_CONTEXT_ASSETS_MUTATED_EVENT, handleContextAssetsMutated);
1721
+ window.removeEventListener(TASKFORCE_AI_PROFILES_MUTATED_EVENT, handleAiProfilesMutated);
1513
1722
  window.clearInterval(docsIntervalId);
1514
1723
  window.clearInterval(aiProfilesIntervalId);
1515
1724
  window.clearInterval(assetsIntervalId);
@@ -1545,11 +1754,14 @@ export function useSyncOrchestrator(input) {
1545
1754
  refreshWorkspaceSyncContextAssetSnapshots();
1546
1755
  }, [cloudAuthConfigured, runtimeMode, workspaceCloudSyncEnabled, authSessionResolved, isAuthenticated, taskAttachmentsFingerprint, refreshWorkspaceSyncContextAssetSnapshots]);
1547
1756
  useEffect(() => {
1548
- if (!cloudAuthConfigured || runtimeMode !== 'local' || !workspaceCloudSyncEnabled || !authSessionResolved)
1549
- return;
1550
- if (workspaceSyncPhase === 'attach-cloud')
1551
- return;
1552
- if (!workspaceSyncReferenceSnapshotsReady)
1757
+ if (!shouldScheduleWorkspaceReactivePush({
1758
+ cloudAuthConfigured,
1759
+ runtimeMode,
1760
+ workspaceCloudSyncEnabled,
1761
+ authSessionResolved,
1762
+ workspaceSyncPhase,
1763
+ workspaceSyncReferenceSnapshotsReady
1764
+ }))
1553
1765
  return;
1554
1766
  const signature = buildWorkspaceSyncSignature();
1555
1767
  if (shouldSuppressAttachBootstrapPush(signature))
@@ -1587,11 +1799,14 @@ export function useSyncOrchestrator(input) {
1587
1799
  workspacePullInFlightRef
1588
1800
  ]);
1589
1801
  useEffect(() => {
1590
- if (!cloudAuthConfigured || runtimeMode !== 'local' || !workspaceCloudSyncEnabled || !authSessionResolved)
1591
- return;
1592
- if (workspaceSyncPhase !== 'active')
1593
- return;
1594
- if (!workspaceSyncReferenceSnapshotsReady)
1802
+ if (!shouldStartWorkspacePushWatchdog({
1803
+ cloudAuthConfigured,
1804
+ runtimeMode,
1805
+ workspaceCloudSyncEnabled,
1806
+ authSessionResolved,
1807
+ workspaceSyncPhase,
1808
+ workspaceSyncReferenceSnapshotsReady
1809
+ }))
1595
1810
  return;
1596
1811
  const intervalId = window.setInterval(() => {
1597
1812
  if (workspacePushInFlightRef.current || workspacePullInFlightRef.current)
@@ -1633,14 +1848,18 @@ export function useSyncOrchestrator(input) {
1633
1848
  clearWorkspaceRetry
1634
1849
  ]);
1635
1850
  useEffect(() => {
1636
- if (!cloudAuthConfigured || runtimeMode !== 'local' || !workspaceCloudSyncEnabled || !authSessionResolved)
1637
- return;
1638
- if (workspaceSyncPhase === 'provision-local' || workspaceSyncPhase === 'error')
1639
- return;
1640
- if (isWorkspaceRetryPending())
1851
+ const pullPolicy = resolveWorkspaceAutomaticPullPolicy({
1852
+ cloudAuthConfigured,
1853
+ runtimeMode,
1854
+ workspaceCloudSyncEnabled,
1855
+ authSessionResolved,
1856
+ workspaceSyncPhase,
1857
+ lastPullAt: workspaceLastPullAt,
1858
+ retryPending: isWorkspaceRetryPending()
1859
+ });
1860
+ if (!pullPolicy.shouldStart)
1641
1861
  return;
1642
- const shouldKickOffPull = workspaceSyncPhase === 'attach-cloud' || !workspaceLastPullAt;
1643
- if (shouldKickOffPull) {
1862
+ if (pullPolicy.shouldKickOffImmediately) {
1644
1863
  void pullWorkspaceChangesFromCloud();
1645
1864
  }
1646
1865
  const intervalId = window.setInterval(() => {
@@ -1661,9 +1880,14 @@ export function useSyncOrchestrator(input) {
1661
1880
  workspacePullIntervalMs
1662
1881
  ]);
1663
1882
  useEffect(() => {
1664
- if (!cloudAuthConfigured || runtimeMode !== 'local' || !workspaceCloudSyncEnabled || !authSessionResolved || !isAuthenticated)
1665
- return;
1666
- if (workspaceSyncPhase !== 'active')
1883
+ if (!shouldStartWorkspacePeriodicFullReconcile({
1884
+ cloudAuthConfigured,
1885
+ runtimeMode,
1886
+ workspaceCloudSyncEnabled,
1887
+ authSessionResolved,
1888
+ isAuthenticated,
1889
+ workspaceSyncPhase
1890
+ }))
1667
1891
  return;
1668
1892
  const intervalId = window.setInterval(() => {
1669
1893
  if (workspaceFullReconcileInFlightRef.current)
@@ -1726,13 +1950,15 @@ export function useSyncOrchestrator(input) {
1726
1950
  // missing or stale-tombstoned metadata — a common result of a crash between file write
1727
1951
  // and DB upsert. The repair only adds missing records; it never deletes files.
1728
1952
  useEffect(() => {
1729
- if (!cloudAuthConfigured || runtimeMode !== 'local' || !workspaceCloudSyncEnabled)
1730
- return;
1731
- if (!authSessionResolved || !isAuthenticated)
1732
- return;
1733
- if (workspaceSyncPhase === 'provision-local' || workspaceSyncPhase === 'attach-cloud')
1734
- return;
1735
- if (workspaceStartupRepairRanRef.current)
1953
+ if (!shouldRunWorkspaceStartupRepair({
1954
+ cloudAuthConfigured,
1955
+ runtimeMode,
1956
+ workspaceCloudSyncEnabled,
1957
+ authSessionResolved,
1958
+ isAuthenticated,
1959
+ workspaceSyncPhase,
1960
+ alreadyRan: workspaceStartupRepairRanRef.current
1961
+ }))
1736
1962
  return;
1737
1963
  workspaceStartupRepairRanRef.current = true;
1738
1964
  fetch('/api/taskforce/sync/workspace/repair-startup', {