@taskforcehq/taskforce 0.3.303 → 0.3.305

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 (237) hide show
  1. package/README.md +140 -247
  2. package/dist/Taskforce.module.css +102 -5
  3. package/dist/TaskforceCore.js +635 -238
  4. package/dist/TaskforceCore.test.js +2243 -495
  5. package/dist/annotatedAttachments/service.d.ts +1 -1
  6. package/dist/annotatedAttachments/types.d.ts +1 -1
  7. package/dist/compat/workspaceSyncCompat.js +6 -0
  8. package/dist/components/features/AgentsModule.js +1 -1
  9. package/dist/components/features/AgentsModule.test.js +1 -1
  10. package/dist/components/features/DocumentIndex.d.ts +1 -1
  11. package/dist/components/features/DocumentIndex.js +1 -1
  12. package/dist/components/features/DocumentViewer.d.ts +1 -1
  13. package/dist/components/features/DocumentViewer.js +32 -9
  14. package/dist/components/features/DocumentWorkspace.d.ts +5 -22
  15. package/dist/components/features/DocumentWorkspace.js +12 -155
  16. package/dist/components/features/DocumentWorkspace.test.js +193 -6
  17. package/dist/components/features/TaskSettings.js +87 -41
  18. package/dist/components/features/TaskSettings.test.js +180 -40
  19. package/dist/components/features/documentWorkspaceModel.d.ts +24 -0
  20. package/dist/components/features/documentWorkspaceModel.js +57 -0
  21. package/dist/components/features/useDocumentWorkspaceController.d.ts +34 -0
  22. package/dist/components/features/useDocumentWorkspaceController.js +113 -0
  23. package/dist/components/task/TaskActionHeader.d.ts +2 -1
  24. package/dist/components/task/TaskActionHeader.js +3 -3
  25. package/dist/components/task/TaskActionHeader.test.js +8 -0
  26. package/dist/components/task/TaskCard.js +16 -1
  27. package/dist/components/task/TaskCard.test.js +31 -0
  28. package/dist/components/task/TaskForm.d.ts +9 -8
  29. package/dist/components/task/TaskForm.js +374 -219
  30. package/dist/components/task/TaskForm.test.js +482 -361
  31. package/dist/components/task/TaskKanban.d.ts +1 -1
  32. package/dist/components/task/TaskKanban.js +33 -161
  33. package/dist/components/task/TaskStatusActions.d.ts +6 -1
  34. package/dist/components/task/TaskStatusActions.js +8 -3
  35. package/dist/components/task/TaskStatusActions.test.js +9 -0
  36. package/dist/components/task/taskKanbanDropRules.d.ts +58 -0
  37. package/dist/components/task/taskKanbanDropRules.js +187 -0
  38. package/dist/components/task/taskKanbanDropRules.test.d.ts +1 -0
  39. package/dist/components/task/taskKanbanDropRules.test.js +114 -0
  40. package/dist/components/ui/Modal.d.ts +1 -1
  41. package/dist/components/ui/Modal.js +1 -0
  42. package/dist/components/views/AppShellHeader.js +5 -1
  43. package/dist/components/views/AppShellHeader.test.js +4 -0
  44. package/dist/components/views/PlanComparisonPage.d.ts +3 -2
  45. package/dist/components/views/PlanComparisonPage.js +9 -10
  46. package/dist/components/views/PlanComparisonPage.test.js +55 -41
  47. package/dist/components/views/PlansPage.d.ts +5 -1
  48. package/dist/components/views/PlansPage.js +150 -81
  49. package/dist/components/views/PlansPage.test.js +367 -41
  50. package/dist/components/views/StandaloneLayout.js +135 -54
  51. package/dist/components/views/WidgetView.js +11 -2
  52. package/dist/components/views/panels/FilterToolbar.test.js +6 -4
  53. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +2 -14
  54. package/dist/components/views/standalone/modals/AccountHubModal.js +12 -14
  55. package/dist/components/views/standalone/modals/AccountHubModal.test.js +24 -1
  56. package/dist/components/views/standalone/modals/SyncStatusModal.js +1 -1
  57. package/dist/config/envSchema.js +1 -1
  58. package/dist/core/AiProfileService.d.ts +6 -1
  59. package/dist/core/AiProfileService.js +161 -16
  60. package/dist/core/AiProfileService.test.js +3 -1
  61. package/dist/core/AiProfiles.test.js +200 -0
  62. package/dist/core/AuthTokenService.test.d.ts +1 -0
  63. package/dist/core/AuthTokenService.test.js +78 -0
  64. package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
  65. package/dist/core/EntitlementsPolicy.test.js +75 -13
  66. package/dist/core/PlanEntitlementService.d.ts +9 -1
  67. package/dist/core/PlanEntitlementService.js +144 -19
  68. package/dist/core/PlanVersionPolicy.test.js +19 -26
  69. package/dist/core/SignupMonetizationSettings.test.js +75 -15
  70. package/dist/core/SystemAdmin.test.js +212 -56
  71. package/dist/core/TaskActivityService.d.ts +4 -1
  72. package/dist/core/TaskActivityService.js +45 -0
  73. package/dist/core/TaskAuditTimeline.test.js +52 -1
  74. package/dist/core/TaskTaxonomyValidation.test.js +53 -0
  75. package/dist/core/Taskforce.d.ts +18 -11
  76. package/dist/core/Taskforce.js +93 -12
  77. package/dist/core/WorkspacePlanMode.test.js +73 -8
  78. package/dist/core/shared.d.ts +2 -0
  79. package/dist/core/shared.js +11 -0
  80. package/dist/documentReviews/service.d.ts +1 -1
  81. package/dist/documentReviews/types.d.ts +1 -1
  82. package/dist/hooks/auth/useTaskforceAuthBootstrap.d.ts +48 -0
  83. package/dist/hooks/auth/useTaskforceAuthBootstrap.js +219 -0
  84. package/dist/hooks/sync/auth.d.ts +37 -0
  85. package/dist/hooks/sync/auth.js +63 -0
  86. package/dist/hooks/sync/bootstrap.d.ts +34 -0
  87. package/dist/hooks/sync/bootstrap.js +52 -0
  88. package/dist/hooks/sync/orchestratorShared.d.ts +36 -0
  89. package/dist/hooks/sync/orchestratorShared.js +209 -0
  90. package/dist/hooks/sync/orchestratorShared.test.d.ts +1 -0
  91. package/dist/hooks/sync/orchestratorShared.test.js +49 -0
  92. package/dist/hooks/sync/realtime.d.ts +25 -0
  93. package/dist/hooks/sync/realtime.js +60 -0
  94. package/dist/hooks/sync/recovery.d.ts +69 -0
  95. package/dist/hooks/sync/recovery.js +118 -0
  96. package/dist/hooks/sync/transfers.d.ts +123 -0
  97. package/dist/hooks/sync/transfers.js +435 -0
  98. package/dist/hooks/sync/useDataVersionRefresh.d.ts +16 -0
  99. package/dist/hooks/sync/useDataVersionRefresh.js +73 -0
  100. package/dist/hooks/ui/useResourceExport.d.ts +3 -1
  101. package/dist/hooks/ui/useResourceExport.js +5 -2
  102. package/dist/hooks/ui/useWorkflowTemplates.js +11 -6
  103. package/dist/hooks/useRealtimeSync.js +2 -1
  104. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +8 -0
  105. package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +1 -0
  106. package/dist/hooks/useSyncOrchestrator.context-assets.test.js +228 -0
  107. package/dist/hooks/useSyncOrchestrator.d.ts +12 -8
  108. package/dist/hooks/useSyncOrchestrator.js +388 -879
  109. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +641 -6
  110. package/dist/hooks/useTaskMutations.d.ts +2 -1
  111. package/dist/hooks/useTaskMutations.js +57 -7
  112. package/dist/hooks/useTaskMutations.test.js +176 -0
  113. package/dist/hooks/useTaskforce.d.ts +13 -4
  114. package/dist/hooks/useTaskforce.js +302 -817
  115. package/dist/hooks/useTaskforce.sync-behavior.test.js +78 -0
  116. package/dist/hooks/useWorkspaceSyncController.d.ts +4 -1
  117. package/dist/hooks/useWorkspaceSyncController.js +33 -11
  118. package/dist/hooks/useWorkspaceSyncController.test.js +1 -0
  119. package/dist/hooks/workspace/useTaskforceApiRouting.test.js +17 -0
  120. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +166 -0
  121. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +434 -0
  122. package/dist/hooks/workspace/workspaceLocalState.d.ts +19 -0
  123. package/dist/hooks/workspace/workspaceLocalState.js +100 -0
  124. package/dist/hooks/workspace/workspaceLocalState.test.d.ts +1 -0
  125. package/dist/hooks/workspace/workspaceLocalState.test.js +59 -0
  126. package/dist/localization/locales/en-US.js +2 -2
  127. package/dist/mcp/adminWorkspaceRegistrar.d.ts +2 -0
  128. package/dist/mcp/adminWorkspaceRegistrar.js +154 -0
  129. package/dist/mcp/canonicalAssetHelpers.d.ts +70 -0
  130. package/dist/mcp/canonicalAssetHelpers.js +259 -0
  131. package/dist/mcp/clientIntegrations.d.ts +12 -1
  132. package/dist/mcp/clientIntegrations.js +161 -6
  133. package/dist/mcp/clientIntegrations.test.js +76 -4
  134. package/dist/mcp/collaborationRegistrar.d.ts +2 -0
  135. package/dist/mcp/collaborationRegistrar.js +71 -0
  136. package/dist/mcp/documentAssetRegistrar.d.ts +2 -0
  137. package/dist/mcp/documentAssetRegistrar.js +346 -0
  138. package/dist/mcp/documentHelpers.d.ts +100 -0
  139. package/dist/mcp/documentHelpers.js +161 -0
  140. package/dist/mcp/runtime.js +2455 -3925
  141. package/dist/mcp/runtime.test.js +240 -8
  142. package/dist/mcp/taskAttachmentHelpers.d.ts +41 -0
  143. package/dist/mcp/taskAttachmentHelpers.js +60 -0
  144. package/dist/mcp/taskAttachmentTypes.d.ts +37 -0
  145. package/dist/mcp/taskAttachmentTypes.js +1 -0
  146. package/dist/mcp/taskPlanningRegistrar.d.ts +2 -0
  147. package/dist/mcp/taskPlanningRegistrar.js +418 -0
  148. package/dist/mcp/toolCatalog.d.ts +35 -0
  149. package/dist/mcp/toolCatalog.js +3 -0
  150. package/dist/plugins/vite.js +1 -1
  151. package/dist/runtime/appGateDefinitions.d.ts +1 -1
  152. package/dist/runtime/appGateDefinitions.js +1 -1
  153. package/dist/server/index.d.ts +1 -0
  154. package/dist/server/index.js +46 -3
  155. package/dist/server/index.test.js +12 -1
  156. package/dist/server/routes/admin.d.ts +1 -5
  157. package/dist/server/routes/admin.js +151 -80
  158. package/dist/server/routes/annotatedAttachments.d.ts +1 -1
  159. package/dist/server/routes/annotatedAttachments.js +1 -1
  160. package/dist/server/routes/auth.d.ts +1 -4
  161. package/dist/server/routes/auth.js +59 -64
  162. package/dist/server/routes/authSupport.d.ts +30 -0
  163. package/dist/server/routes/authSupport.js +81 -0
  164. package/dist/server/routes/billing.d.ts +1 -1
  165. package/dist/server/routes/billing.js +122 -255
  166. package/dist/server/routes/billing.test.js +257 -86
  167. package/dist/server/routes/documentReviews.d.ts +1 -1
  168. package/dist/server/routes/documentReviews.js +1 -1
  169. package/dist/server/routes/documents.d.ts +4 -2
  170. package/dist/server/routes/documents.js +73 -7
  171. package/dist/server/routes/primitives.d.ts +11 -0
  172. package/dist/server/routes/primitives.js +73 -0
  173. package/dist/server/routes/resources.d.ts +1 -1
  174. package/dist/server/routes/resources.js +1 -1
  175. package/dist/server/routes/shared.d.ts +31 -1
  176. package/dist/server/routes/shared.js +26 -0
  177. package/dist/server/routes/sync.d.ts +1 -1
  178. package/dist/server/routes/sync.integration.test.js +20 -0
  179. package/dist/server/routes/sync.js +111 -238
  180. package/dist/server/routes/tasks.d.ts +1 -1
  181. package/dist/server/routes/tasks.js +50 -8
  182. package/dist/server/routes/workspaces.d.ts +1 -1
  183. package/dist/server/routes/workspaces.js +1 -1
  184. package/dist/server/routes.d.ts +2 -9
  185. package/dist/server/routes.js +84 -181
  186. package/dist/server/routes.test.js +912 -32
  187. package/dist/shared/taskActor.d.ts +10 -0
  188. package/dist/shared/taskActor.js +1 -0
  189. package/dist/sync/collaborationSyncPayload.js +4 -0
  190. package/dist/sync/collaborationSyncPayload.test.js +8 -0
  191. package/dist/sync/collaborationSyncState.d.ts +3 -1
  192. package/dist/sync/collaborationSyncState.js +19 -0
  193. package/dist/sync/contentSyncState.js +28 -17
  194. package/dist/sync/syncApplyHandlers.d.ts +87 -1
  195. package/dist/sync/syncApplyHandlers.js +370 -8
  196. package/dist/sync/syncApplyHandlers.test.d.ts +1 -0
  197. package/dist/sync/syncApplyHandlers.test.js +379 -0
  198. package/dist/sync/syncService.d.ts +9 -1
  199. package/dist/sync/syncService.js +44 -2
  200. package/dist/sync/syncService.test.js +62 -0
  201. package/dist/sync/workspacePullFeed.d.ts +1 -0
  202. package/dist/sync/workspacePullFeed.js +66 -59
  203. package/dist/sync/workspacePullFeed.test.js +72 -4
  204. package/dist/sync/workspaceSyncModel.d.ts +25 -1
  205. package/dist/sync/workspaceSyncModel.js +77 -3
  206. package/dist/sync/workspaceSyncModel.test.js +153 -0
  207. package/dist/sync/workspaceSyncState.d.ts +2 -0
  208. package/dist/sync/workspaceSyncState.js +1 -0
  209. package/dist/types.d.ts +2 -10
  210. package/dist/ui/assets/AgentsModule-N2MnQBZk.js +1 -0
  211. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-C_TmXZwA.js → AnnotatedAttachmentWorkspace-BG6P_GVW.js} +1 -1
  212. package/dist/ui/assets/DocumentWorkspace-B3nV_Gc5.css +1 -0
  213. package/dist/ui/assets/DocumentWorkspace-DRbDMwh8.js +252 -0
  214. package/dist/ui/assets/{InitiativesModule-4-Rh2K2n.js → InitiativesModule-Dhm7M8e7.js} +1 -1
  215. package/dist/ui/assets/{PlansPage-BlVC_lRq.css → PlansPage-C2dd2n1X.css} +1 -1
  216. package/dist/ui/assets/PlansPage-Cq0zXj3I.js +1 -0
  217. package/dist/ui/assets/TaskSettings-ln0aF7xc.js +9 -0
  218. package/dist/ui/assets/{WorkflowsModule-CGk9s3NJ.js → WorkflowsModule-BcS4afRn.js} +1 -1
  219. package/dist/ui/assets/index-C2-OXZV7.css +1 -0
  220. package/dist/ui/assets/index-DcSI5F86.js +6 -0
  221. package/dist/ui/assets/{vendor-icons-pWocEipT.js → vendor-icons-BSUaRwF8.js} +1 -1
  222. package/dist/ui/index.html +4 -4
  223. package/dist/utils/commercialLifecyclePresentation.d.ts +19 -0
  224. package/dist/utils/commercialLifecyclePresentation.js +196 -0
  225. package/dist/utils/taskActivity.js +4 -0
  226. package/dist/utils/taskActivity.test.js +25 -1
  227. package/package.json +10 -1
  228. package/scripts/check-cloud-mcp.mjs +83 -0
  229. package/scripts/playwright-auth.sh +9 -1
  230. package/scripts/run-billing-performance-smoke.sh +24 -0
  231. package/dist/ui/assets/AgentsModule-CpsTmrIW.js +0 -1
  232. package/dist/ui/assets/DocumentWorkspace-C3GyzrWm.js +0 -250
  233. package/dist/ui/assets/DocumentWorkspace-C_8T8oz-.css +0 -1
  234. package/dist/ui/assets/PlansPage-BP7AYOqr.js +0 -1
  235. package/dist/ui/assets/TaskSettings-BOC5F6Ag.js +0 -8
  236. package/dist/ui/assets/index-CLIMgR6g.js +0 -6
  237. package/dist/ui/assets/index-DneETxcu.css +0 -1
@@ -0,0 +1,219 @@
1
+ import { useCallback, useEffect } from 'react';
2
+ import { createDefaultAssigneeOptions } from '../../utils/assignees';
3
+ import { persistLocalMutationActorUserId, readLocalMutationActorUserId } from '../../utils/taskforceApiClient';
4
+ export function useTaskforceAuthBootstrap({ runtimeConfigReady, shouldProbeCloudAuth, authSessionResolved, resolveCloudAuthUrl, authOnlyCloudMode, authRequiredError, runtimeMode, workspaceSelectionScope, markBootstrapPhase, markBootstrapStalled, setRuntimeMode, setAuthRequiredForApi, setIsAuthenticated, setAuthUserId, setAuthWorkspaceId, setAuthUserEmail, setAuthUserDisplayName, setAuthUserAvatarUrl, setUserGlobalSyncStatus, setUserGlobalSyncError, setHasBetaAccess, setAvailableWorkspaces, setHydratedWorkspaceRole, setAssigneeOptions, setAuthBlocked, setAuthSessionResolved, setError, applyResolvedWorkspaceId, readPersistedWorkspaceId, authSessionRequestRef, authSessionEpochRef, authSessionLastCheckedAtRef, authSessionLastResultRef, isLoopbackHost, setAuthStateReadyRefs }) {
5
+ const checkAuthSession = useCallback(async (options) => {
6
+ const force = options?.force === true;
7
+ markBootstrapPhase('auth');
8
+ if (!runtimeConfigReady) {
9
+ return authSessionLastResultRef.current;
10
+ }
11
+ if (!shouldProbeCloudAuth) {
12
+ setRuntimeMode('local');
13
+ setAuthRequiredForApi(false);
14
+ setIsAuthenticated(false);
15
+ setAuthUserId('anonymous');
16
+ setAuthWorkspaceId('');
17
+ persistLocalMutationActorUserId(null);
18
+ setAuthUserEmail('');
19
+ setAuthUserDisplayName('');
20
+ setAuthUserAvatarUrl('');
21
+ setUserGlobalSyncStatus('disconnected');
22
+ setUserGlobalSyncError(null);
23
+ setHasBetaAccess(true);
24
+ setAvailableWorkspaces([]);
25
+ setHydratedWorkspaceRole(null);
26
+ setAssigneeOptions(createDefaultAssigneeOptions());
27
+ setAuthBlocked(false);
28
+ setAuthSessionResolved(true);
29
+ setAuthStateReadyRefs(false, true, false);
30
+ authSessionLastResultRef.current = false;
31
+ authSessionLastCheckedAtRef.current = Date.now();
32
+ return false;
33
+ }
34
+ const sessionUrl = resolveCloudAuthUrl('/api/taskforce/auth/session');
35
+ const now = Date.now();
36
+ if (!force && authSessionRequestRef.current)
37
+ return authSessionRequestRef.current;
38
+ if (!force && authSessionResolved && (now - authSessionLastCheckedAtRef.current) < 1500) {
39
+ return authSessionLastResultRef.current;
40
+ }
41
+ const requestEpoch = ++authSessionEpochRef.current;
42
+ let request = null;
43
+ request = (async () => {
44
+ const abortController = new AbortController();
45
+ const timeoutId = typeof window !== 'undefined'
46
+ ? window.setTimeout(() => abortController.abort(), 8000)
47
+ : null;
48
+ const isStaleRequest = () => authSessionEpochRef.current !== requestEpoch;
49
+ try {
50
+ const res = await fetch(sessionUrl, {
51
+ method: 'GET',
52
+ credentials: 'include',
53
+ mode: 'cors',
54
+ signal: abortController.signal
55
+ });
56
+ if (isStaleRequest()) {
57
+ return authSessionLastResultRef.current;
58
+ }
59
+ if (res.status === 429) {
60
+ setAuthSessionResolved(true);
61
+ setAuthStateReadyRefs(authSessionLastResultRef.current, true, false);
62
+ return authSessionLastResultRef.current;
63
+ }
64
+ if (!res.ok) {
65
+ if (isStaleRequest()) {
66
+ return authSessionLastResultRef.current;
67
+ }
68
+ if (isLoopbackHost)
69
+ persistLocalMutationActorUserId(null);
70
+ setAuthSessionResolved(true);
71
+ setAuthStateReadyRefs(false, true, false);
72
+ authSessionLastResultRef.current = false;
73
+ return false;
74
+ }
75
+ const data = await res.json();
76
+ if (isStaleRequest()) {
77
+ return authSessionLastResultRef.current;
78
+ }
79
+ const required = Boolean(data.authRequiredForApi);
80
+ const authenticated = Boolean(data.authenticated);
81
+ const betaAccess = authenticated ? (data.betaAccess !== false) : true;
82
+ const workspaceId = typeof data.workspaceId === 'string' && data.workspaceId.trim().length > 0
83
+ ? data.workspaceId.trim()
84
+ : '';
85
+ const userId = typeof data.userId === 'string' && data.userId.trim().length > 0
86
+ ? data.userId.trim()
87
+ : 'anonymous';
88
+ const userEmail = typeof data.email === 'string' && data.email.trim().length > 0
89
+ ? data.email.trim().toLowerCase()
90
+ : '';
91
+ const userDisplayName = typeof data.displayName === 'string' ? data.displayName.trim() : '';
92
+ const userAvatarUrl = typeof data.avatarUrl === 'string' ? data.avatarUrl.trim() : '';
93
+ const isLocalRuntime = runtimeMode === 'local';
94
+ const keepLocalWorkspaceContext = runtimeMode === 'local' || authOnlyCloudMode;
95
+ const nextAuthRequiredForApi = authOnlyCloudMode ? false : (isLocalRuntime ? false : required);
96
+ setAuthRequiredForApi(nextAuthRequiredForApi);
97
+ setIsAuthenticated(authenticated);
98
+ setAuthUserId(authenticated ? userId : 'anonymous');
99
+ setAuthWorkspaceId(authenticated ? workspaceId : '');
100
+ if (isLoopbackHost)
101
+ persistLocalMutationActorUserId(authenticated ? userId : null);
102
+ setAuthUserEmail(authenticated ? userEmail : '');
103
+ setAuthUserDisplayName(authenticated ? userDisplayName : '');
104
+ setAuthUserAvatarUrl(authenticated ? userAvatarUrl : '');
105
+ if (authenticated) {
106
+ setError((prev) => prev === authRequiredError ? '' : prev);
107
+ }
108
+ setUserGlobalSyncStatus(authenticated ? 'idle' : 'disconnected');
109
+ setUserGlobalSyncError(null);
110
+ setHasBetaAccess(betaAccess);
111
+ if (!keepLocalWorkspaceContext) {
112
+ applyResolvedWorkspaceId(workspaceId || readPersistedWorkspaceId(workspaceSelectionScope) || 'default', { preserveNonDefaultDefault: true });
113
+ }
114
+ setAuthBlocked(authOnlyCloudMode ? false : (isLocalRuntime ? false : (required && !authenticated)));
115
+ setAuthSessionResolved(true);
116
+ setAuthStateReadyRefs(authenticated, true, nextAuthRequiredForApi);
117
+ authSessionLastResultRef.current = authenticated;
118
+ if (authenticated)
119
+ markBootstrapPhase('ready');
120
+ return authenticated;
121
+ }
122
+ catch {
123
+ if (isStaleRequest()) {
124
+ return authSessionLastResultRef.current;
125
+ }
126
+ if (isLoopbackHost)
127
+ persistLocalMutationActorUserId(null);
128
+ setAuthSessionResolved(true);
129
+ setAuthStateReadyRefs(false, true, false);
130
+ authSessionLastResultRef.current = false;
131
+ markBootstrapStalled('Unable to verify your session.');
132
+ return authSessionLastResultRef.current;
133
+ }
134
+ finally {
135
+ if (timeoutId !== null && typeof window !== 'undefined') {
136
+ window.clearTimeout(timeoutId);
137
+ }
138
+ if (!isStaleRequest()) {
139
+ authSessionLastCheckedAtRef.current = Date.now();
140
+ }
141
+ if (request && authSessionRequestRef.current === request) {
142
+ authSessionRequestRef.current = null;
143
+ }
144
+ }
145
+ })();
146
+ authSessionRequestRef.current = request;
147
+ return request;
148
+ }, [
149
+ runtimeConfigReady,
150
+ shouldProbeCloudAuth,
151
+ authSessionResolved,
152
+ resolveCloudAuthUrl,
153
+ authOnlyCloudMode,
154
+ authRequiredError,
155
+ runtimeMode,
156
+ workspaceSelectionScope,
157
+ markBootstrapPhase,
158
+ markBootstrapStalled,
159
+ setRuntimeMode,
160
+ setAuthRequiredForApi,
161
+ setIsAuthenticated,
162
+ setAuthUserId,
163
+ setAuthWorkspaceId,
164
+ setAuthUserEmail,
165
+ setAuthUserDisplayName,
166
+ setAuthUserAvatarUrl,
167
+ setUserGlobalSyncStatus,
168
+ setUserGlobalSyncError,
169
+ setHasBetaAccess,
170
+ setAvailableWorkspaces,
171
+ setHydratedWorkspaceRole,
172
+ setAssigneeOptions,
173
+ setAuthBlocked,
174
+ setAuthSessionResolved,
175
+ setError,
176
+ applyResolvedWorkspaceId,
177
+ readPersistedWorkspaceId,
178
+ authSessionRequestRef,
179
+ authSessionEpochRef,
180
+ authSessionLastCheckedAtRef,
181
+ authSessionLastResultRef,
182
+ isLoopbackHost,
183
+ setAuthStateReadyRefs
184
+ ]);
185
+ const optimisticLocalAuthUserId = runtimeMode === 'local' && isLoopbackHost && shouldProbeCloudAuth
186
+ ? readLocalMutationActorUserId()
187
+ : '';
188
+ useEffect(() => {
189
+ if (optimisticLocalAuthUserId) {
190
+ setIsAuthenticated(true);
191
+ setAuthSessionResolved(true);
192
+ setAuthUserId(optimisticLocalAuthUserId);
193
+ setAuthStateReadyRefs(true, true, false);
194
+ authSessionLastResultRef.current = true;
195
+ }
196
+ }, [optimisticLocalAuthUserId, setAuthSessionResolved, setAuthStateReadyRefs, setAuthUserId, setIsAuthenticated, authSessionLastResultRef]);
197
+ useEffect(() => {
198
+ if (runtimeMode !== 'local' || !shouldProbeCloudAuth)
199
+ return;
200
+ if (typeof window === 'undefined')
201
+ return;
202
+ const recheck = () => { void checkAuthSession(); };
203
+ const onVisibilityChange = () => {
204
+ if (document.visibilityState === 'visible')
205
+ recheck();
206
+ };
207
+ window.addEventListener('focus', recheck);
208
+ window.addEventListener('online', recheck);
209
+ document.addEventListener('visibilitychange', onVisibilityChange);
210
+ const intervalId = window.setInterval(recheck, 30_000);
211
+ return () => {
212
+ window.removeEventListener('focus', recheck);
213
+ window.removeEventListener('online', recheck);
214
+ document.removeEventListener('visibilitychange', onVisibilityChange);
215
+ window.clearInterval(intervalId);
216
+ };
217
+ }, [runtimeMode, shouldProbeCloudAuth, checkAuthSession]);
218
+ return { checkAuthSession };
219
+ }
@@ -0,0 +1,37 @@
1
+ import type { WorkspaceSyncPhase } from '../../sync/workspaceSyncState';
2
+ interface CreateSyncAuthCoordinatorArgs {
3
+ currentWorkspaceId: string;
4
+ workspaceSyncPhase: WorkspaceSyncPhase;
5
+ clearWorkspaceRetry: () => void;
6
+ setWorkspaceSyncBusy: (value: boolean) => void;
7
+ setUserGlobalSyncStatus: (value: 'error') => void;
8
+ setUserGlobalSyncError: (value: string) => void;
9
+ setWorkspaceLastErrorMessage: (value: string) => void;
10
+ setWorkspaceLastErrorAt: (value: string) => void;
11
+ reportSyncEvent: (workspaceId: string, event: {
12
+ eventType: 'pull' | 'handshake' | 'bootstrap' | 'apply' | 'push';
13
+ status: 'success' | 'error';
14
+ statusCode?: number;
15
+ errorMessage?: string;
16
+ }) => void;
17
+ setAuthBlocked: (value: boolean) => void;
18
+ setIsAuthenticated: (value: boolean) => void;
19
+ checkAuthSession: () => Promise<boolean>;
20
+ ensureCloudWorkspaceReadyForSync: () => Promise<{
21
+ success: boolean;
22
+ statusCode?: number;
23
+ error?: string;
24
+ transient?: boolean;
25
+ retryAfterMs?: number;
26
+ }>;
27
+ scheduleWorkspaceSyncRetry: (minDelayMs?: number) => void;
28
+ persistWorkspaceSyncPatchSafely: (patch: {
29
+ phase: 'error';
30
+ lastErrorMessage: string;
31
+ }) => void;
32
+ }
33
+ export declare function createSyncAuthCoordinator({ currentWorkspaceId, workspaceSyncPhase, clearWorkspaceRetry, setWorkspaceSyncBusy, setUserGlobalSyncStatus, setUserGlobalSyncError, setWorkspaceLastErrorMessage, setWorkspaceLastErrorAt, reportSyncEvent, setAuthBlocked, setIsAuthenticated, checkAuthSession, ensureCloudWorkspaceReadyForSync, scheduleWorkspaceSyncRetry, persistWorkspaceSyncPatchSafely }: CreateSyncAuthCoordinatorArgs): {
34
+ handleSyncAuthFailure: (source: "push" | "pull" | "apply" | "handshake", statusCode: number) => Promise<void>;
35
+ ensureWorkspaceSyncReady: () => Promise<boolean>;
36
+ };
37
+ export {};
@@ -0,0 +1,63 @@
1
+ import { isTransientWorkspaceSyncStatus } from '../../sync/syncService';
2
+ import { isValidWorkspaceKey } from './orchestratorShared';
3
+ export function createSyncAuthCoordinator({ currentWorkspaceId, workspaceSyncPhase, clearWorkspaceRetry, setWorkspaceSyncBusy, setUserGlobalSyncStatus, setUserGlobalSyncError, setWorkspaceLastErrorMessage, setWorkspaceLastErrorAt, reportSyncEvent, setAuthBlocked, setIsAuthenticated, checkAuthSession, ensureCloudWorkspaceReadyForSync, scheduleWorkspaceSyncRetry, persistWorkspaceSyncPatchSafely }) {
4
+ const handleSyncAuthFailure = async (source, statusCode) => {
5
+ clearWorkspaceRetry();
6
+ setWorkspaceSyncBusy(false);
7
+ setUserGlobalSyncStatus('error');
8
+ const message = 'Session expired. Sign in again to resume cloud sync.';
9
+ setUserGlobalSyncError(message);
10
+ setWorkspaceLastErrorMessage(message);
11
+ setWorkspaceLastErrorAt(new Date().toISOString());
12
+ reportSyncEvent(currentWorkspaceId, {
13
+ eventType: source === 'handshake' ? 'handshake' : source,
14
+ status: 'error',
15
+ statusCode,
16
+ errorMessage: message
17
+ });
18
+ setAuthBlocked(true);
19
+ setIsAuthenticated(false);
20
+ await checkAuthSession();
21
+ };
22
+ const ensureWorkspaceSyncReady = async () => {
23
+ if (!isValidWorkspaceKey(currentWorkspaceId)) {
24
+ const message = 'Workspace sync blocked: local workspace ID is unresolved.';
25
+ setWorkspaceLastErrorMessage(message);
26
+ setWorkspaceLastErrorAt(new Date().toISOString());
27
+ setUserGlobalSyncError(message);
28
+ setUserGlobalSyncStatus('error');
29
+ return false;
30
+ }
31
+ const ensured = await ensureCloudWorkspaceReadyForSync();
32
+ if (ensured.success)
33
+ return true;
34
+ if (ensured.statusCode === 401) {
35
+ await handleSyncAuthFailure('handshake', 401);
36
+ return false;
37
+ }
38
+ const message = ensured.error || 'Workspace sync handshake failed.';
39
+ setWorkspaceLastErrorMessage(message);
40
+ setWorkspaceLastErrorAt(new Date().toISOString());
41
+ setUserGlobalSyncError(message);
42
+ setUserGlobalSyncStatus('error');
43
+ reportSyncEvent(currentWorkspaceId, {
44
+ eventType: 'handshake',
45
+ status: 'error',
46
+ statusCode: ensured.statusCode,
47
+ errorMessage: message
48
+ });
49
+ if (ensured.transient || isTransientWorkspaceSyncStatus(ensured.statusCode)) {
50
+ scheduleWorkspaceSyncRetry(ensured.retryAfterMs);
51
+ }
52
+ else if (workspaceSyncPhase === 'active'
53
+ || workspaceSyncPhase === 'attach-cloud'
54
+ || workspaceSyncPhase === 'provision-local') {
55
+ persistWorkspaceSyncPatchSafely({ phase: 'error', lastErrorMessage: message });
56
+ }
57
+ return false;
58
+ };
59
+ return {
60
+ handleSyncAuthFailure,
61
+ ensureWorkspaceSyncReady
62
+ };
63
+ }
@@ -0,0 +1,34 @@
1
+ import type { MutableRefObject } from 'react';
2
+ import type { WorkspaceSyncPhase } from '../../sync/workspaceSyncState';
3
+ interface BootstrapPhaseTrackingArgs {
4
+ workspaceSyncPhase: WorkspaceSyncPhase;
5
+ lastBootstrapPhaseRef: MutableRefObject<WorkspaceSyncPhase>;
6
+ bootstrapPhaseStartedAtRef: MutableRefObject<number | null>;
7
+ bootstrapLastProgressAtRef: MutableRefObject<number | null>;
8
+ }
9
+ export declare function syncBootstrapPhaseTracking({ workspaceSyncPhase, lastBootstrapPhaseRef, bootstrapPhaseStartedAtRef, bootstrapLastProgressAtRef }: BootstrapPhaseTrackingArgs): void;
10
+ interface BootstrapTimeoutMonitorArgs {
11
+ currentWorkspaceId: string;
12
+ workspaceCloudSyncEnabled: boolean;
13
+ workspaceSyncPhase: WorkspaceSyncPhase;
14
+ workspacePullInFlightRef: MutableRefObject<boolean>;
15
+ workspacePushInFlightRef: MutableRefObject<boolean>;
16
+ bootstrapPhaseStartedAtRef: MutableRefObject<number | null>;
17
+ bootstrapLastProgressAtRef: MutableRefObject<number | null>;
18
+ setWorkspaceLastErrorMessage: (value: string) => void;
19
+ setWorkspaceLastErrorAt: (value: string) => void;
20
+ setUserGlobalSyncStatus: (value: 'error') => void;
21
+ setUserGlobalSyncError: (value: string) => void;
22
+ setWorkspaceSyncBusy: (value: boolean) => void;
23
+ persistWorkspaceSyncPatchSafely: (patch: {
24
+ phase: 'error';
25
+ lastErrorMessage: string;
26
+ }) => void;
27
+ }
28
+ export declare function createBootstrapTimeoutMonitor({ currentWorkspaceId, workspaceCloudSyncEnabled, workspaceSyncPhase, workspacePullInFlightRef, workspacePushInFlightRef, bootstrapPhaseStartedAtRef, bootstrapLastProgressAtRef, setWorkspaceLastErrorMessage, setWorkspaceLastErrorAt, setUserGlobalSyncStatus, setUserGlobalSyncError, setWorkspaceSyncBusy, persistWorkspaceSyncPatchSafely }: BootstrapTimeoutMonitorArgs): (() => void) | undefined;
29
+ export declare function resolveRepairPhase(args: {
30
+ workspaceSyncPhase: WorkspaceSyncPhase;
31
+ lastBootstrapPhase: WorkspaceSyncPhase;
32
+ workspaceLastPullAt: string | null;
33
+ }): WorkspaceSyncPhase;
34
+ export {};
@@ -0,0 +1,52 @@
1
+ import { WORKSPACE_BOOTSTRAP_TIMEOUT_MS, logSyncDebug } from './orchestratorShared';
2
+ export function syncBootstrapPhaseTracking({ workspaceSyncPhase, lastBootstrapPhaseRef, bootstrapPhaseStartedAtRef, bootstrapLastProgressAtRef }) {
3
+ if (workspaceSyncPhase === 'attach-cloud' || workspaceSyncPhase === 'provision-local') {
4
+ lastBootstrapPhaseRef.current = workspaceSyncPhase;
5
+ if (bootstrapPhaseStartedAtRef.current === null) {
6
+ bootstrapPhaseStartedAtRef.current = Date.now();
7
+ }
8
+ bootstrapLastProgressAtRef.current = null;
9
+ return;
10
+ }
11
+ bootstrapPhaseStartedAtRef.current = null;
12
+ bootstrapLastProgressAtRef.current = null;
13
+ }
14
+ export function createBootstrapTimeoutMonitor({ currentWorkspaceId, workspaceCloudSyncEnabled, workspaceSyncPhase, workspacePullInFlightRef, workspacePushInFlightRef, bootstrapPhaseStartedAtRef, bootstrapLastProgressAtRef, setWorkspaceLastErrorMessage, setWorkspaceLastErrorAt, setUserGlobalSyncStatus, setUserGlobalSyncError, setWorkspaceSyncBusy, persistWorkspaceSyncPatchSafely }) {
15
+ if (!workspaceCloudSyncEnabled)
16
+ return undefined;
17
+ if (workspaceSyncPhase !== 'attach-cloud' && workspaceSyncPhase !== 'provision-local')
18
+ return undefined;
19
+ const intervalId = window.setInterval(() => {
20
+ if (workspaceSyncPhase !== 'attach-cloud' && workspaceSyncPhase !== 'provision-local')
21
+ return;
22
+ if (workspacePullInFlightRef.current || workspacePushInFlightRef.current)
23
+ return;
24
+ const lastActivity = Math.max(bootstrapPhaseStartedAtRef.current ?? 0, bootstrapLastProgressAtRef.current ?? 0);
25
+ if (lastActivity > 0 && (Date.now() - lastActivity) < WORKSPACE_BOOTSTRAP_TIMEOUT_MS)
26
+ return;
27
+ const phaseLabel = workspaceSyncPhase === 'attach-cloud' ? 'initial cloud pull' : 'initial cloud upload';
28
+ const message = `Workspace sync ${phaseLabel} stalled. Press Repair to restart sync for this workspace.`;
29
+ setWorkspaceLastErrorMessage(message);
30
+ setWorkspaceLastErrorAt(new Date().toISOString());
31
+ setUserGlobalSyncStatus('error');
32
+ setUserGlobalSyncError(message);
33
+ setWorkspaceSyncBusy(false);
34
+ persistWorkspaceSyncPatchSafely({ phase: 'error', lastErrorMessage: message });
35
+ logSyncDebug('workspace_sync_bootstrap_timeout', {
36
+ workspaceId: currentWorkspaceId,
37
+ phase: workspaceSyncPhase
38
+ });
39
+ }, 10_000);
40
+ return () => window.clearInterval(intervalId);
41
+ }
42
+ export function resolveRepairPhase(args) {
43
+ const { workspaceSyncPhase, lastBootstrapPhase, workspaceLastPullAt } = args;
44
+ if (workspaceSyncPhase === 'provision-local')
45
+ return 'provision-local';
46
+ if (workspaceSyncPhase === 'attach-cloud')
47
+ return 'attach-cloud';
48
+ if (lastBootstrapPhase === 'provision-local' || lastBootstrapPhase === 'attach-cloud') {
49
+ return lastBootstrapPhase;
50
+ }
51
+ return workspaceLastPullAt ? 'attach-cloud' : 'provision-local';
52
+ }
@@ -0,0 +1,36 @@
1
+ import type { WorkspaceTaxonomyState } from '../../shared/taxonomyState.js';
2
+ import type { WorkspaceSyncAiProfileSnapshot, WorkspaceSyncDocumentReviewCommentSnapshot, WorkspaceSyncDocumentReviewSessionSnapshot, WorkspaceSyncTaskEventSnapshot } from '../../sync/workspaceSyncModel';
3
+ import type { TaskItem } from '../../types';
4
+ export declare const USER_GLOBAL_SYNC_META_KEY = "taskforce.userGlobalSyncMeta.v1";
5
+ export declare const SYNC_DEBUG_LOG_KEY = "taskforce.syncDebug.v1";
6
+ export declare const WORKSPACE_BOOTSTRAP_TIMEOUT_MS = 120000;
7
+ export declare const WORKSPACE_SYNC_WATERMARKS_KEY_PREFIX = "taskforce.syncWatermarks.v3";
8
+ export declare const WORKSPACE_FULL_RECONCILE_INTERVAL_MS: number;
9
+ export declare const WORKSPACE_CONCURRENT_WINDOW_MESSAGE = "Another local window is already syncing this workspace. Wait a few seconds, or press Repair in that window.";
10
+ export declare function isValidWorkspaceKey(workspaceId: string): boolean;
11
+ export declare function logSyncDebug(event: string, details?: Record<string, unknown>): void;
12
+ export declare function normalizeWorkspaceSyncAiProfileSnapshotEntry(entry: any): WorkspaceSyncAiProfileSnapshot;
13
+ export declare function readUserGlobalSyncMeta(): Record<string, {
14
+ updatedAt: string;
15
+ }>;
16
+ export declare function writeUserGlobalSyncMeta(next: Record<string, {
17
+ updatedAt: string;
18
+ }>): void;
19
+ export declare function buildTaxonomyStateFingerprint(taxonomyState?: WorkspaceTaxonomyState): string;
20
+ export declare function flattenDocumentReviewComments(sessions: WorkspaceSyncDocumentReviewSessionSnapshot[]): WorkspaceSyncDocumentReviewCommentSnapshot[];
21
+ export declare function flattenTaskEvents(tasks: TaskItem[], archivedTasks: TaskItem[]): WorkspaceSyncTaskEventSnapshot[];
22
+ export interface PersistedSyncWatermarks {
23
+ taskWatermarks: Map<string, string>;
24
+ initiativeWatermarks: Map<string, string>;
25
+ workstreamWatermarks: Map<string, string>;
26
+ documentWatermarks: Map<string, string>;
27
+ assetWatermarks: Map<string, string>;
28
+ documentReviewSessionWatermarks: Map<string, string>;
29
+ documentReviewCommentWatermarks: Map<string, string>;
30
+ annotatedAttachmentSessionWatermarks: Map<string, string>;
31
+ taskEventWatermarks: Map<string, string>;
32
+ }
33
+ export declare function readPersistedSyncWatermarks(workspaceId: string, taxonomyStateFingerprint?: string): PersistedSyncWatermarks | null;
34
+ export declare function savePersistedSyncWatermarks(workspaceId: string, data: PersistedSyncWatermarks & {
35
+ taxonomyStateFingerprint?: string;
36
+ }): void;
@@ -0,0 +1,209 @@
1
+ import { parseAiProfileSurfaceType } from '../../shared/aiProfileSurfaceType.js';
2
+ export const USER_GLOBAL_SYNC_META_KEY = 'taskforce.userGlobalSyncMeta.v1';
3
+ export const SYNC_DEBUG_LOG_KEY = 'taskforce.syncDebug.v1';
4
+ export const WORKSPACE_BOOTSTRAP_TIMEOUT_MS = 120_000;
5
+ export const WORKSPACE_SYNC_WATERMARKS_KEY_PREFIX = 'taskforce.syncWatermarks.v3';
6
+ export const WORKSPACE_FULL_RECONCILE_INTERVAL_MS = 5 * 60 * 1000;
7
+ export const WORKSPACE_CONCURRENT_WINDOW_MESSAGE = 'Another local window is already syncing this workspace. Wait a few seconds, or press Repair in that window.';
8
+ export function isValidWorkspaceKey(workspaceId) {
9
+ const workspaceKey = String(workspaceId || '').trim();
10
+ return workspaceKey.length > 0 && workspaceKey.toLowerCase() !== 'default';
11
+ }
12
+ function isSyncDebugLoggingEnabled() {
13
+ if (typeof window === 'undefined')
14
+ return false;
15
+ try {
16
+ const raw = String(window.localStorage.getItem(SYNC_DEBUG_LOG_KEY) || '').trim().toLowerCase();
17
+ return raw === '1' || raw === 'true' || raw === 'on' || raw === 'yes';
18
+ }
19
+ catch {
20
+ return false;
21
+ }
22
+ }
23
+ export function logSyncDebug(event, details) {
24
+ if (!isSyncDebugLoggingEnabled())
25
+ return;
26
+ const payload = details && typeof details === 'object' ? details : {};
27
+ console.info('[Taskforce Sync]', event, payload);
28
+ }
29
+ export function normalizeWorkspaceSyncAiProfileSnapshotEntry(entry) {
30
+ const metadata = entry?.providerMetadata ?? entry?.provider_metadata ?? null;
31
+ return {
32
+ id: String(entry?.id || '').trim(),
33
+ workspaceId: String(entry?.workspaceId || entry?.workspace_id || '').trim(),
34
+ profileToken: String(entry?.profileToken || entry?.profile_token || '').trim(),
35
+ name: String(entry?.name || '').trim(),
36
+ username: String(entry?.username || '').trim(),
37
+ icon: String(entry?.icon || 'Bot').trim() || 'Bot',
38
+ color: String(entry?.color || '#8b5cf6').trim() || '#8b5cf6',
39
+ description: typeof entry?.description === 'string' && entry.description.trim().length > 0
40
+ ? entry.description.trim()
41
+ : null,
42
+ role: typeof entry?.role === 'string' && entry.role.trim().length > 0
43
+ ? entry.role.trim()
44
+ : null,
45
+ provider: typeof entry?.provider === 'string' && entry.provider.trim().length > 0
46
+ ? entry.provider.trim()
47
+ : null,
48
+ model: typeof entry?.model === 'string' && entry.model.trim().length > 0
49
+ ? entry.model.trim()
50
+ : null,
51
+ surfaceType: parseAiProfileSurfaceType(entry?.surfaceType ?? entry?.surface_type),
52
+ providerMetadata: metadata && typeof metadata === 'object' && !Array.isArray(metadata)
53
+ ? metadata
54
+ : null,
55
+ createdAt: String(entry?.createdAt || entry?.created_at || '').trim(),
56
+ updatedAt: String(entry?.updatedAt || entry?.updated_at || entry?.createdAt || entry?.created_at || '').trim(),
57
+ lastActiveAt: typeof entry?.lastActiveAt === 'string' && entry.lastActiveAt.trim().length > 0
58
+ ? entry.lastActiveAt.trim()
59
+ : typeof entry?.last_active_at === 'string' && entry.last_active_at.trim().length > 0
60
+ ? entry.last_active_at.trim()
61
+ : null
62
+ };
63
+ }
64
+ export function readUserGlobalSyncMeta() {
65
+ if (typeof window === 'undefined')
66
+ return {};
67
+ try {
68
+ const raw = window.localStorage.getItem(USER_GLOBAL_SYNC_META_KEY);
69
+ if (!raw)
70
+ return {};
71
+ const parsed = JSON.parse(raw);
72
+ return parsed && typeof parsed === 'object'
73
+ ? parsed
74
+ : {};
75
+ }
76
+ catch {
77
+ return {};
78
+ }
79
+ }
80
+ export function writeUserGlobalSyncMeta(next) {
81
+ if (typeof window === 'undefined')
82
+ return;
83
+ try {
84
+ window.localStorage.setItem(USER_GLOBAL_SYNC_META_KEY, JSON.stringify(next));
85
+ }
86
+ catch {
87
+ // best-effort only
88
+ }
89
+ }
90
+ export function buildTaxonomyStateFingerprint(taxonomyState) {
91
+ try {
92
+ return JSON.stringify(taxonomyState && typeof taxonomyState === 'object' ? taxonomyState : null);
93
+ }
94
+ catch {
95
+ return 'null';
96
+ }
97
+ }
98
+ export function flattenDocumentReviewComments(sessions) {
99
+ return sessions.flatMap((session) => {
100
+ const sessionId = String(session?.id || '').trim();
101
+ if (!sessionId || !Array.isArray(session?.comments))
102
+ return [];
103
+ return session.comments
104
+ .map((comment) => ({
105
+ id: String(comment?.id || '').trim(),
106
+ sessionId,
107
+ body: String(comment?.body || ''),
108
+ anchor: comment?.anchor ?? null,
109
+ order: Number.isFinite(Number(comment?.order)) ? Math.max(0, Math.floor(Number(comment.order))) : 0,
110
+ authorActorId: typeof comment?.authorActorId === 'string' && comment.authorActorId.trim().length > 0 ? comment.authorActorId.trim() : null,
111
+ createdAt: String(comment?.createdAt || '').trim(),
112
+ updatedAt: String(comment?.updatedAt || comment?.createdAt || '').trim(),
113
+ deletedAt: typeof comment?.deletedAt === 'string' && comment.deletedAt.trim().length > 0 ? comment.deletedAt.trim() : null,
114
+ }))
115
+ .filter((comment) => comment.id.length > 0 && comment.sessionId.length > 0);
116
+ });
117
+ }
118
+ export function flattenTaskEvents(tasks, archivedTasks) {
119
+ const deduped = new Map();
120
+ for (const task of [...(tasks || []), ...(archivedTasks || [])]) {
121
+ const taskEvents = Array.isArray(task?.taskEvents) ? task.taskEvents : [];
122
+ for (const event of taskEvents) {
123
+ const id = String(event?.id || '').trim();
124
+ const taskId = String(event?.taskId || task?.id || '').trim();
125
+ const workspaceId = String(event?.workspaceId || '').trim();
126
+ const createdAt = String(event?.createdAt || '').trim();
127
+ if (!id || !taskId || !workspaceId || !createdAt)
128
+ continue;
129
+ const nextEvent = {
130
+ id,
131
+ taskId,
132
+ workspaceId,
133
+ action: String(event?.action || '').trim(),
134
+ actor: String(event?.actor || 'system').trim() || 'system',
135
+ actorType: event?.actorType === 'ai' || event?.actorType === 'human' || event?.actorType === 'system'
136
+ ? event.actorType
137
+ : 'system',
138
+ details: event?.details,
139
+ createdAt,
140
+ };
141
+ const existing = deduped.get(id);
142
+ if (!existing || nextEvent.createdAt > existing.createdAt) {
143
+ deduped.set(id, nextEvent);
144
+ }
145
+ }
146
+ }
147
+ return Array.from(deduped.values()).sort((left, right) => {
148
+ if (left.createdAt !== right.createdAt)
149
+ return left.createdAt.localeCompare(right.createdAt);
150
+ return left.id.localeCompare(right.id);
151
+ });
152
+ }
153
+ export function readPersistedSyncWatermarks(workspaceId, taxonomyStateFingerprint) {
154
+ if (typeof window === 'undefined')
155
+ return null;
156
+ try {
157
+ const key = `${WORKSPACE_SYNC_WATERMARKS_KEY_PREFIX}.${workspaceId}`;
158
+ const raw = window.localStorage.getItem(key);
159
+ if (!raw)
160
+ return null;
161
+ const parsed = JSON.parse(raw);
162
+ if (!parsed || (parsed.v !== 2 && parsed.v !== 3 && parsed.v !== 4 && parsed.v !== 5 && parsed.v !== 6 && parsed.v !== 7 && parsed.v !== 8))
163
+ return null;
164
+ const savedTaxonomyStateFingerprint = typeof parsed.taxonomyStateFingerprint === 'string'
165
+ ? parsed.taxonomyStateFingerprint
166
+ : '';
167
+ const taskWatermarks = (!taxonomyStateFingerprint || !savedTaxonomyStateFingerprint || savedTaxonomyStateFingerprint === taxonomyStateFingerprint)
168
+ ? new Map(Array.isArray(parsed.taskWatermarks) ? parsed.taskWatermarks : [])
169
+ : new Map();
170
+ return {
171
+ taskWatermarks,
172
+ initiativeWatermarks: new Map(Array.isArray(parsed.initiativeWatermarks) ? parsed.initiativeWatermarks : []),
173
+ workstreamWatermarks: new Map(Array.isArray(parsed.workstreamWatermarks) ? parsed.workstreamWatermarks : []),
174
+ documentWatermarks: new Map(Array.isArray(parsed.documentWatermarks) ? parsed.documentWatermarks : []),
175
+ assetWatermarks: new Map(Array.isArray(parsed.assetWatermarks) ? parsed.assetWatermarks : []),
176
+ documentReviewSessionWatermarks: new Map(Array.isArray(parsed.documentReviewSessionWatermarks) ? parsed.documentReviewSessionWatermarks : []),
177
+ documentReviewCommentWatermarks: new Map(Array.isArray(parsed.documentReviewCommentWatermarks) ? parsed.documentReviewCommentWatermarks : []),
178
+ annotatedAttachmentSessionWatermarks: new Map(Array.isArray(parsed.annotatedAttachmentSessionWatermarks) ? parsed.annotatedAttachmentSessionWatermarks : []),
179
+ taskEventWatermarks: new Map(Array.isArray(parsed.taskEventWatermarks) ? parsed.taskEventWatermarks : []),
180
+ };
181
+ }
182
+ catch {
183
+ return null;
184
+ }
185
+ }
186
+ export function savePersistedSyncWatermarks(workspaceId, data) {
187
+ if (typeof window === 'undefined')
188
+ return;
189
+ try {
190
+ const key = `${WORKSPACE_SYNC_WATERMARKS_KEY_PREFIX}.${workspaceId}`;
191
+ window.localStorage.setItem(key, JSON.stringify({
192
+ v: 8,
193
+ savedAt: new Date().toISOString(),
194
+ taskWatermarks: Array.from(data.taskWatermarks.entries()),
195
+ initiativeWatermarks: Array.from(data.initiativeWatermarks.entries()),
196
+ workstreamWatermarks: Array.from(data.workstreamWatermarks.entries()),
197
+ documentWatermarks: Array.from(data.documentWatermarks.entries()),
198
+ assetWatermarks: Array.from(data.assetWatermarks.entries()),
199
+ documentReviewSessionWatermarks: Array.from(data.documentReviewSessionWatermarks.entries()),
200
+ documentReviewCommentWatermarks: Array.from(data.documentReviewCommentWatermarks.entries()),
201
+ annotatedAttachmentSessionWatermarks: Array.from(data.annotatedAttachmentSessionWatermarks.entries()),
202
+ taskEventWatermarks: Array.from(data.taskEventWatermarks.entries()),
203
+ taxonomyStateFingerprint: typeof data.taxonomyStateFingerprint === 'string' ? data.taxonomyStateFingerprint : ''
204
+ }));
205
+ }
206
+ catch {
207
+ // localStorage may be full or unavailable; non-fatal
208
+ }
209
+ }
@@ -0,0 +1 @@
1
+ export {};