@taskforcehq/taskforce 0.3.302 → 0.3.304

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 (192) hide show
  1. package/dist/Taskforce.module.css +113 -5
  2. package/dist/TaskforceCore.js +560 -208
  3. package/dist/TaskforceCore.test.js +1833 -91
  4. package/dist/annotatedAttachments/service.d.ts +1 -1
  5. package/dist/annotatedAttachments/types.d.ts +1 -1
  6. package/dist/components/features/AgentsModule.js +1 -1
  7. package/dist/components/features/AgentsModule.test.js +1 -1
  8. package/dist/components/features/TaskSettings.js +58 -38
  9. package/dist/components/features/TaskSettings.test.js +139 -40
  10. package/dist/components/task/TaskActionHeader.d.ts +2 -1
  11. package/dist/components/task/TaskActionHeader.js +3 -3
  12. package/dist/components/task/TaskActionHeader.test.js +8 -0
  13. package/dist/components/task/TaskCard.js +16 -1
  14. package/dist/components/task/TaskCard.test.js +31 -0
  15. package/dist/components/task/TaskForm.d.ts +2 -1
  16. package/dist/components/task/TaskForm.js +22 -4
  17. package/dist/components/task/TaskForm.test.js +46 -0
  18. package/dist/components/task/TaskKanban.d.ts +1 -1
  19. package/dist/components/task/TaskKanban.js +31 -160
  20. package/dist/components/task/TaskKanban.test.js +17 -0
  21. package/dist/components/task/TaskStatusActions.d.ts +6 -1
  22. package/dist/components/task/TaskStatusActions.js +8 -3
  23. package/dist/components/task/TaskStatusActions.test.js +9 -0
  24. package/dist/components/task/taskKanbanDropRules.d.ts +58 -0
  25. package/dist/components/task/taskKanbanDropRules.js +187 -0
  26. package/dist/components/task/taskKanbanDropRules.test.d.ts +1 -0
  27. package/dist/components/task/taskKanbanDropRules.test.js +114 -0
  28. package/dist/components/ui/Modal.d.ts +1 -1
  29. package/dist/components/ui/Modal.js +1 -0
  30. package/dist/components/views/AppShellHeader.js +5 -1
  31. package/dist/components/views/AppShellHeader.test.js +4 -0
  32. package/dist/components/views/PlanComparisonPage.d.ts +3 -2
  33. package/dist/components/views/PlanComparisonPage.js +4 -2
  34. package/dist/components/views/PlanComparisonPage.test.js +4 -0
  35. package/dist/components/views/PlansPage.d.ts +7 -1
  36. package/dist/components/views/PlansPage.js +139 -53
  37. package/dist/components/views/PlansPage.test.d.ts +1 -0
  38. package/dist/components/views/PlansPage.test.js +157 -0
  39. package/dist/components/views/StandaloneLayout.d.ts +1 -0
  40. package/dist/components/views/StandaloneLayout.js +216 -186
  41. package/dist/components/views/WidgetView.js +11 -2
  42. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +8 -3
  43. package/dist/components/views/standalone/modals/AccountHubModal.js +16 -2
  44. package/dist/components/views/standalone/modals/AccountHubModal.test.d.ts +1 -0
  45. package/dist/components/views/standalone/modals/AccountHubModal.test.js +26 -0
  46. package/dist/components/views/standalone/modals/SyncStatusModal.js +1 -1
  47. package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
  48. package/dist/core/EntitlementsPolicy.test.js +2 -2
  49. package/dist/core/GlobalSettingsService.js +78 -18
  50. package/dist/core/PlanEntitlementService.d.ts +31 -5
  51. package/dist/core/PlanEntitlementService.js +350 -222
  52. package/dist/core/SignupMonetizationSettings.test.js +208 -49
  53. package/dist/core/SystemAdmin.test.js +186 -96
  54. package/dist/core/TaskActivityService.d.ts +4 -1
  55. package/dist/core/TaskActivityService.js +45 -0
  56. package/dist/core/TaskAuditTimeline.test.js +52 -1
  57. package/dist/core/Taskforce.d.ts +65 -20
  58. package/dist/core/Taskforce.js +261 -51
  59. package/dist/core/Taskforce.mcpAuth.test.js +0 -2
  60. package/dist/core/UserProfileAvatarDrafts.test.js +12 -3
  61. package/dist/core/WorkspacePlanMode.test.js +65 -0
  62. package/dist/core/types.d.ts +11 -12
  63. package/dist/documentReviews/service.d.ts +1 -1
  64. package/dist/documentReviews/types.d.ts +1 -1
  65. package/dist/hooks/auth/useTaskforceAuthBootstrap.d.ts +48 -0
  66. package/dist/hooks/auth/useTaskforceAuthBootstrap.js +219 -0
  67. package/dist/hooks/sync/auth.d.ts +37 -0
  68. package/dist/hooks/sync/auth.js +61 -0
  69. package/dist/hooks/sync/bootstrap.d.ts +34 -0
  70. package/dist/hooks/sync/bootstrap.js +52 -0
  71. package/dist/hooks/sync/orchestratorShared.d.ts +36 -0
  72. package/dist/hooks/sync/orchestratorShared.js +209 -0
  73. package/dist/hooks/sync/orchestratorShared.test.d.ts +1 -0
  74. package/dist/hooks/sync/orchestratorShared.test.js +49 -0
  75. package/dist/hooks/sync/realtime.d.ts +25 -0
  76. package/dist/hooks/sync/realtime.js +60 -0
  77. package/dist/hooks/sync/recovery.d.ts +69 -0
  78. package/dist/hooks/sync/recovery.js +118 -0
  79. package/dist/hooks/sync/transfers.d.ts +122 -0
  80. package/dist/hooks/sync/transfers.js +431 -0
  81. package/dist/hooks/sync/useDataVersionRefresh.d.ts +16 -0
  82. package/dist/hooks/sync/useDataVersionRefresh.js +73 -0
  83. package/dist/hooks/ui/useResourceExport.d.ts +3 -1
  84. package/dist/hooks/ui/useResourceExport.js +5 -2
  85. package/dist/hooks/ui/useWorkflowTemplates.js +11 -6
  86. package/dist/hooks/useRealtimeSync.js +2 -1
  87. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +8 -0
  88. package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +1 -0
  89. package/dist/hooks/useSyncOrchestrator.context-assets.test.js +228 -0
  90. package/dist/hooks/useSyncOrchestrator.d.ts +5 -4
  91. package/dist/hooks/useSyncOrchestrator.js +315 -860
  92. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +665 -8
  93. package/dist/hooks/useTaskMutations.js +45 -6
  94. package/dist/hooks/useTaskMutations.test.js +147 -0
  95. package/dist/hooks/useTaskforce.d.ts +11 -2
  96. package/dist/hooks/useTaskforce.js +290 -812
  97. package/dist/hooks/useWorkspaceSyncController.d.ts +1 -0
  98. package/dist/hooks/useWorkspaceSyncController.js +22 -10
  99. package/dist/hooks/workspace/useTaskforceApiRouting.test.js +17 -0
  100. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +166 -0
  101. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +434 -0
  102. package/dist/hooks/workspace/workspaceLocalState.d.ts +19 -0
  103. package/dist/hooks/workspace/workspaceLocalState.js +100 -0
  104. package/dist/hooks/workspace/workspaceLocalState.test.d.ts +1 -0
  105. package/dist/hooks/workspace/workspaceLocalState.test.js +59 -0
  106. package/dist/localization/locales/en-US.js +2 -2
  107. package/dist/mcp/canonicalAssetHelpers.d.ts +70 -0
  108. package/dist/mcp/canonicalAssetHelpers.js +259 -0
  109. package/dist/mcp/clientIntegrations.d.ts +12 -1
  110. package/dist/mcp/clientIntegrations.js +161 -6
  111. package/dist/mcp/clientIntegrations.test.js +76 -4
  112. package/dist/mcp/documentHelpers.d.ts +100 -0
  113. package/dist/mcp/documentHelpers.js +161 -0
  114. package/dist/mcp/runtime.js +264 -505
  115. package/dist/mcp/runtime.test.js +353 -89
  116. package/dist/mcp/taskAttachmentHelpers.d.ts +41 -0
  117. package/dist/mcp/taskAttachmentHelpers.js +60 -0
  118. package/dist/mcp/taskAttachmentTypes.d.ts +37 -0
  119. package/dist/mcp/taskAttachmentTypes.js +1 -0
  120. package/dist/migrations/billingSchemaParity.test.js +3 -0
  121. package/dist/migrations/taskSchemaMigrations.js +15 -0
  122. package/dist/plugins/vite.js +1 -1
  123. package/dist/runtime/appGateDefinitions.d.ts +1 -1
  124. package/dist/runtime/appGateDefinitions.js +1 -1
  125. package/dist/server/index.cookieProxy.test.js +1 -0
  126. package/dist/server/index.d.ts +1 -0
  127. package/dist/server/index.js +48 -3
  128. package/dist/server/index.test.js +14 -2
  129. package/dist/server/mockStripe.d.ts +1 -0
  130. package/dist/server/mockStripe.js +2 -0
  131. package/dist/server/routes/admin.js +91 -40
  132. package/dist/server/routes/auth.d.ts +1 -0
  133. package/dist/server/routes/auth.js +231 -55
  134. package/dist/server/routes/billing.js +473 -39
  135. package/dist/server/routes/billing.test.js +972 -37
  136. package/dist/server/routes/shared.d.ts +30 -0
  137. package/dist/server/routes/shared.js +31 -3
  138. package/dist/server/routes/sync.integration.test.js +180 -0
  139. package/dist/server/routes/sync.js +125 -242
  140. package/dist/server/routes/tasks.js +49 -7
  141. package/dist/server/routes.js +11 -22
  142. package/dist/server/routes.test.js +530 -81
  143. package/dist/shared/taskActor.d.ts +10 -0
  144. package/dist/shared/taskActor.js +1 -0
  145. package/dist/sync/cloudSyncApi.d.ts +1 -0
  146. package/dist/sync/collaborationSyncPayload.js +4 -0
  147. package/dist/sync/collaborationSyncPayload.test.js +8 -0
  148. package/dist/sync/collaborationSyncState.d.ts +3 -1
  149. package/dist/sync/collaborationSyncState.js +19 -0
  150. package/dist/sync/contentSyncState.d.ts +10 -0
  151. package/dist/sync/contentSyncState.js +277 -14
  152. package/dist/sync/syncApplyHandlers.d.ts +87 -1
  153. package/dist/sync/syncApplyHandlers.js +370 -8
  154. package/dist/sync/syncApplyHandlers.test.d.ts +1 -0
  155. package/dist/sync/syncApplyHandlers.test.js +377 -0
  156. package/dist/sync/syncService.d.ts +9 -1
  157. package/dist/sync/syncService.js +57 -10
  158. package/dist/sync/syncService.test.js +48 -0
  159. package/dist/sync/workspacePullFeed.d.ts +11 -0
  160. package/dist/sync/workspacePullFeed.js +123 -68
  161. package/dist/sync/workspacePullFeed.test.js +180 -0
  162. package/dist/sync/workspaceSyncModel.d.ts +22 -1
  163. package/dist/sync/workspaceSyncModel.js +65 -0
  164. package/dist/sync/workspaceSyncModel.test.js +119 -0
  165. package/dist/types.d.ts +2 -10
  166. package/dist/ui/assets/AgentsModule-BLWVbuKP.js +1 -0
  167. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-Dvi02Y7q.js → AnnotatedAttachmentWorkspace-BlS-cqnl.js} +1 -1
  168. package/dist/ui/assets/{DocumentWorkspace-iys3zfca.js → DocumentWorkspace-BH_6DF6x.js} +1 -1
  169. package/dist/ui/assets/{InitiativesModule-DJvLJI3A.js → InitiativesModule-BjR6iBf9.js} +1 -1
  170. package/dist/ui/assets/{PlansPage-BlVC_lRq.css → PlansPage-C2dd2n1X.css} +1 -1
  171. package/dist/ui/assets/PlansPage-CSDQ4sLa.js +1 -0
  172. package/dist/ui/assets/TaskSettings-CEDy34Jo.js +9 -0
  173. package/dist/ui/assets/{WorkflowsModule-BguHeQaV.js → WorkflowsModule-DnFqdUME.js} +1 -1
  174. package/dist/ui/assets/index-DXUdtH1B.js +6 -0
  175. package/dist/ui/assets/index-Ii0B0rTO.css +1 -0
  176. package/dist/ui/assets/{vendor-icons-C8EED3Oh.js → vendor-icons-I7ZwG1z_.js} +1 -1
  177. package/dist/ui/index.html +4 -4
  178. package/dist/utils/accountProfileSummaryCache.d.ts +31 -0
  179. package/dist/utils/accountProfileSummaryCache.js +97 -0
  180. package/dist/utils/accountProfileSummaryCache.test.d.ts +1 -0
  181. package/dist/utils/accountProfileSummaryCache.test.js +63 -0
  182. package/dist/utils/taskActivity.js +4 -0
  183. package/dist/utils/taskActivity.test.js +25 -1
  184. package/package.json +5 -3
  185. package/scripts/playwright-auth.sh +4 -0
  186. package/dist/ui/assets/AgentsModule-JG5jc3he.js +0 -1
  187. package/dist/ui/assets/PlansPage-BMPlpFpw.js +0 -1
  188. package/dist/ui/assets/TaskSettings-Bqd4cCzW.js +0 -8
  189. package/dist/ui/assets/index-MXeWoebC.css +0 -1
  190. package/dist/ui/assets/index-YJ4k5QNa.js +0 -6
  191. package/dist/utils/billingStatusCache.d.ts +0 -21
  192. package/dist/utils/billingStatusCache.js +0 -70
@@ -0,0 +1,122 @@
1
+ import type { Dispatch, MutableRefObject, SetStateAction } from 'react';
2
+ import { type EnsureWorkspaceReadyResult } from '../../sync/syncService';
3
+ import type { WorkspaceSyncPayload } from '../../sync/workspaceSyncModel';
4
+ import type { WorkspaceSyncPhase } from '../../sync/workspaceSyncState';
5
+ interface CreateWorkspaceTransferCoordinatorArgs {
6
+ cloudAuthConfigured: boolean;
7
+ runtimeMode: 'local' | 'cloud';
8
+ workspaceCloudSyncEnabled: boolean;
9
+ currentWorkspaceId: string;
10
+ workspaceSyncPhase: WorkspaceSyncPhase;
11
+ taxonomyStateFingerprint: string;
12
+ isAuthenticated: boolean;
13
+ checkAuthSession: () => Promise<boolean>;
14
+ resolveCloudAuthUrl: (path: string) => string;
15
+ ensureCloudWorkspaceReadyForSync: () => Promise<EnsureWorkspaceReadyResult>;
16
+ ensureWorkspaceSyncReady: () => Promise<boolean>;
17
+ handleSyncAuthFailure: (source: 'push' | 'pull' | 'apply' | 'handshake', statusCode: number) => Promise<void>;
18
+ clearWorkspaceRetry: () => void;
19
+ clearWorkspaceIssue: () => void;
20
+ isWorkspaceRetryPending: () => boolean;
21
+ scheduleWorkspaceSyncRetry: (minDelayMs?: number) => void;
22
+ persistWorkspaceSyncPatchSafely: (patch: {
23
+ phase: 'error';
24
+ lastErrorMessage: string;
25
+ } | {
26
+ phase: 'active';
27
+ lastPushAt?: string;
28
+ lastPullAt?: string;
29
+ lastSyncedAt: string;
30
+ pullCursor?: string | null;
31
+ lastErrorMessage: null;
32
+ }) => void;
33
+ persistWorkspaceSyncPatchBestEffort: (patch: {
34
+ phase: 'active';
35
+ pullCursor: string | null;
36
+ lastPullAt: string;
37
+ lastSyncedAt: string;
38
+ lastErrorMessage: null;
39
+ }) => Promise<unknown>;
40
+ reportSyncEvent: (workspaceId: string, event: {
41
+ eventType: 'pull' | 'handshake' | 'bootstrap' | 'apply' | 'push';
42
+ status: 'success' | 'error';
43
+ statusCode?: number;
44
+ errorMessage?: string;
45
+ changeCount?: number;
46
+ requestMs?: number;
47
+ details?: Record<string, unknown>;
48
+ }) => void;
49
+ acquireWorkspaceSyncLease: (workspaceId: string, owner: 'pull' | 'push' | 'repair') => Promise<boolean>;
50
+ releaseWorkspaceSyncLease: (workspaceId: string) => void;
51
+ reportWorkspaceWindowContention: (operation: 'pull' | 'push' | 'repair') => void;
52
+ refreshWorkspaceSyncMarkdownDocumentsSnapshot: () => Promise<void>;
53
+ refreshWorkspaceSyncAiProfilesSnapshot: () => Promise<void>;
54
+ refreshWorkspaceSyncAssetsSnapshot: () => Promise<void>;
55
+ refreshWorkspaceSyncDocumentReviewSessionsSnapshot: () => Promise<void>;
56
+ refreshWorkspaceSyncAnnotatedAttachmentSessionsSnapshot: () => Promise<void>;
57
+ refreshLocalWorkspaceTaskCollections: () => Promise<void>;
58
+ buildWorkspaceSyncPayload: (options?: {
59
+ forceAllAiProfiles?: boolean;
60
+ }) => WorkspaceSyncPayload;
61
+ buildWorkspaceSyncSignature: () => string;
62
+ seedWorkspaceSnapshotPushBaseline: () => void;
63
+ shouldSuppressAttachBootstrapPush: (signature: string) => boolean;
64
+ recordSuppressedEventIds: (eventIds: Iterable<string>, options?: {
65
+ queueRef?: MutableRefObject<string[]>;
66
+ maxQueueSize?: number;
67
+ ttlMs?: number;
68
+ }) => void;
69
+ workspaceRepairModeRef: MutableRefObject<boolean>;
70
+ workspaceForceFullAiProfilePushRef: MutableRefObject<boolean>;
71
+ workspacePushInFlightRef: MutableRefObject<boolean>;
72
+ workspacePullInFlightRef: MutableRefObject<boolean>;
73
+ workspaceLastPushedSignatureRef: MutableRefObject<string>;
74
+ workspacePendingSignatureRef: MutableRefObject<string>;
75
+ workspaceLastPushedTaskIdsRef: MutableRefObject<Set<string>>;
76
+ workspaceDeletedTaskIdsRef: MutableRefObject<Set<string>>;
77
+ workspacePullCursorRef: MutableRefObject<string | null>;
78
+ workspaceLastPushedWatermarksRef: MutableRefObject<Map<string, string>>;
79
+ workspaceLastPushedInitiativeIdsRef: MutableRefObject<Set<string>>;
80
+ workspaceLastPushedInitiativeWatermarksRef: MutableRefObject<Map<string, string>>;
81
+ workspaceLastPushedWorkstreamIdsRef: MutableRefObject<Set<string>>;
82
+ workspaceLastPushedWorkstreamWatermarksRef: MutableRefObject<Map<string, string>>;
83
+ workspaceLastPushedAiProfileIdsRef: MutableRefObject<Set<string>>;
84
+ workspaceLastPushedAiProfileWatermarksRef: MutableRefObject<Map<string, string>>;
85
+ workspaceLastPushedDocumentPathsRef: MutableRefObject<Set<string>>;
86
+ workspaceLastPushedDocumentWatermarksRef: MutableRefObject<Map<string, string>>;
87
+ workspaceLastPushedAssetPathsRef: MutableRefObject<Set<string>>;
88
+ workspaceLastPushedAssetWatermarksRef: MutableRefObject<Map<string, string>>;
89
+ workspaceLastPushedDocumentReviewSessionIdsRef: MutableRefObject<Set<string>>;
90
+ workspaceLastPushedDocumentReviewSessionWatermarksRef: MutableRefObject<Map<string, string>>;
91
+ workspaceLastPushedDocumentReviewCommentIdsRef: MutableRefObject<Set<string>>;
92
+ workspaceLastPushedDocumentReviewCommentWatermarksRef: MutableRefObject<Map<string, string>>;
93
+ workspaceLastPushedAnnotatedAttachmentSessionIdsRef: MutableRefObject<Set<string>>;
94
+ workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef: MutableRefObject<Map<string, string>>;
95
+ workspaceLastPushedTaskEventWatermarksRef: MutableRefObject<Map<string, string>>;
96
+ workspaceCaptureAttachBootstrapBaselineRef: MutableRefObject<boolean>;
97
+ workspaceStartupFullReconcileRanRef: MutableRefObject<boolean>;
98
+ workspaceFullReconcileInFlightRef: MutableRefObject<boolean>;
99
+ bootstrapLastProgressAtRef: MutableRefObject<number | null>;
100
+ realtimeSuppressedEventQueueRef: MutableRefObject<string[]>;
101
+ setWorkspaceSyncBusy: Dispatch<SetStateAction<boolean>>;
102
+ setUserGlobalSyncStatus: Dispatch<SetStateAction<'disconnected' | 'idle' | 'syncing' | 'error'>>;
103
+ setUserGlobalSyncError: Dispatch<SetStateAction<string | null>>;
104
+ setWorkspaceLastErrorMessage: Dispatch<SetStateAction<string | null>>;
105
+ setWorkspaceLastErrorAt: Dispatch<SetStateAction<string | null>>;
106
+ setWorkspaceLastPullAt: Dispatch<SetStateAction<string | null>>;
107
+ setWorkspaceLastPushAt: Dispatch<SetStateAction<string | null>>;
108
+ setWorkspaceSyncPendingChanges: Dispatch<SetStateAction<number>>;
109
+ setTasks: Dispatch<SetStateAction<any[]>>;
110
+ setArchivedTasks: Dispatch<SetStateAction<any[]>>;
111
+ }
112
+ export declare function createWorkspaceTransferCoordinator({ cloudAuthConfigured, runtimeMode, workspaceCloudSyncEnabled, currentWorkspaceId, workspaceSyncPhase, taxonomyStateFingerprint, isAuthenticated, checkAuthSession, resolveCloudAuthUrl, ensureCloudWorkspaceReadyForSync, ensureWorkspaceSyncReady, handleSyncAuthFailure, clearWorkspaceRetry, clearWorkspaceIssue, isWorkspaceRetryPending, scheduleWorkspaceSyncRetry, persistWorkspaceSyncPatchSafely, persistWorkspaceSyncPatchBestEffort, reportSyncEvent, acquireWorkspaceSyncLease, releaseWorkspaceSyncLease, reportWorkspaceWindowContention, refreshWorkspaceSyncMarkdownDocumentsSnapshot, refreshWorkspaceSyncAiProfilesSnapshot, refreshWorkspaceSyncAssetsSnapshot, refreshWorkspaceSyncDocumentReviewSessionsSnapshot, refreshWorkspaceSyncAnnotatedAttachmentSessionsSnapshot, refreshLocalWorkspaceTaskCollections, buildWorkspaceSyncPayload, buildWorkspaceSyncSignature, seedWorkspaceSnapshotPushBaseline, shouldSuppressAttachBootstrapPush, recordSuppressedEventIds, workspaceRepairModeRef, workspaceForceFullAiProfilePushRef, workspacePushInFlightRef, workspacePullInFlightRef, workspaceLastPushedSignatureRef, workspacePendingSignatureRef, workspaceLastPushedTaskIdsRef, workspaceDeletedTaskIdsRef, workspacePullCursorRef, workspaceLastPushedWatermarksRef, workspaceLastPushedInitiativeIdsRef, workspaceLastPushedInitiativeWatermarksRef, workspaceLastPushedWorkstreamIdsRef, workspaceLastPushedWorkstreamWatermarksRef, workspaceLastPushedAiProfileIdsRef, workspaceLastPushedAiProfileWatermarksRef, workspaceLastPushedDocumentPathsRef, workspaceLastPushedDocumentWatermarksRef, workspaceLastPushedAssetPathsRef, workspaceLastPushedAssetWatermarksRef, workspaceLastPushedDocumentReviewSessionIdsRef, workspaceLastPushedDocumentReviewSessionWatermarksRef, workspaceLastPushedDocumentReviewCommentIdsRef, workspaceLastPushedDocumentReviewCommentWatermarksRef, workspaceLastPushedAnnotatedAttachmentSessionIdsRef, workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef, workspaceLastPushedTaskEventWatermarksRef, workspaceCaptureAttachBootstrapBaselineRef, workspaceStartupFullReconcileRanRef, workspaceFullReconcileInFlightRef, bootstrapLastProgressAtRef, realtimeSuppressedEventQueueRef, setWorkspaceSyncBusy, setUserGlobalSyncStatus, setUserGlobalSyncError, setWorkspaceLastErrorMessage, setWorkspaceLastErrorAt, setWorkspaceLastPullAt, setWorkspaceLastPushAt, setWorkspaceSyncPendingChanges, setTasks, setArchivedTasks }: CreateWorkspaceTransferCoordinatorArgs): {
113
+ pushWorkspaceChangesToCloud: (signature: string, options?: {
114
+ forceAllAiProfiles?: boolean;
115
+ repairMode?: boolean;
116
+ }) => Promise<boolean>;
117
+ pullWorkspaceChangesFromCloud: (options?: {
118
+ repairMode?: boolean;
119
+ forceCursorNull?: boolean;
120
+ }) => Promise<boolean>;
121
+ };
122
+ export {};
@@ -0,0 +1,431 @@
1
+ import { applyLocalWorkspaceChanges } from '../../sync/cloudSyncApi';
2
+ import { isTransientWorkspaceSyncStatus, runWorkspacePullSyncService, runWorkspacePushSyncService } from '../../sync/syncService';
3
+ import { logSyncDebug, savePersistedSyncWatermarks, isValidWorkspaceKey } from './orchestratorShared';
4
+ export function createWorkspaceTransferCoordinator({ cloudAuthConfigured, runtimeMode, workspaceCloudSyncEnabled, currentWorkspaceId, workspaceSyncPhase, taxonomyStateFingerprint, isAuthenticated, checkAuthSession, resolveCloudAuthUrl, ensureCloudWorkspaceReadyForSync, ensureWorkspaceSyncReady, handleSyncAuthFailure, clearWorkspaceRetry, clearWorkspaceIssue, isWorkspaceRetryPending, scheduleWorkspaceSyncRetry, persistWorkspaceSyncPatchSafely, persistWorkspaceSyncPatchBestEffort, reportSyncEvent, acquireWorkspaceSyncLease, releaseWorkspaceSyncLease, reportWorkspaceWindowContention, refreshWorkspaceSyncMarkdownDocumentsSnapshot, refreshWorkspaceSyncAiProfilesSnapshot, refreshWorkspaceSyncAssetsSnapshot, refreshWorkspaceSyncDocumentReviewSessionsSnapshot, refreshWorkspaceSyncAnnotatedAttachmentSessionsSnapshot, refreshLocalWorkspaceTaskCollections, buildWorkspaceSyncPayload, buildWorkspaceSyncSignature, seedWorkspaceSnapshotPushBaseline, shouldSuppressAttachBootstrapPush, recordSuppressedEventIds, workspaceRepairModeRef, workspaceForceFullAiProfilePushRef, workspacePushInFlightRef, workspacePullInFlightRef, workspaceLastPushedSignatureRef, workspacePendingSignatureRef, workspaceLastPushedTaskIdsRef, workspaceDeletedTaskIdsRef, workspacePullCursorRef, workspaceLastPushedWatermarksRef, workspaceLastPushedInitiativeIdsRef, workspaceLastPushedInitiativeWatermarksRef, workspaceLastPushedWorkstreamIdsRef, workspaceLastPushedWorkstreamWatermarksRef, workspaceLastPushedAiProfileIdsRef, workspaceLastPushedAiProfileWatermarksRef, workspaceLastPushedDocumentPathsRef, workspaceLastPushedDocumentWatermarksRef, workspaceLastPushedAssetPathsRef, workspaceLastPushedAssetWatermarksRef, workspaceLastPushedDocumentReviewSessionIdsRef, workspaceLastPushedDocumentReviewSessionWatermarksRef, workspaceLastPushedDocumentReviewCommentIdsRef, workspaceLastPushedDocumentReviewCommentWatermarksRef, workspaceLastPushedAnnotatedAttachmentSessionIdsRef, workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef, workspaceLastPushedTaskEventWatermarksRef, workspaceCaptureAttachBootstrapBaselineRef, workspaceStartupFullReconcileRanRef, workspaceFullReconcileInFlightRef, bootstrapLastProgressAtRef, realtimeSuppressedEventQueueRef, setWorkspaceSyncBusy, setUserGlobalSyncStatus, setUserGlobalSyncError, setWorkspaceLastErrorMessage, setWorkspaceLastErrorAt, setWorkspaceLastPullAt, setWorkspaceLastPushAt, setWorkspaceSyncPendingChanges, setTasks, setArchivedTasks }) {
5
+ const pushWorkspaceChangesToCloud = async (signature, options) => {
6
+ if (!cloudAuthConfigured || runtimeMode !== 'local' || !workspaceCloudSyncEnabled)
7
+ return false;
8
+ if (!isValidWorkspaceKey(currentWorkspaceId))
9
+ return false;
10
+ if (workspaceRepairModeRef.current === true && options?.repairMode !== true) {
11
+ workspacePendingSignatureRef.current = signature;
12
+ return false;
13
+ }
14
+ if (workspaceSyncPhase === 'attach-cloud') {
15
+ workspacePendingSignatureRef.current = signature;
16
+ return false;
17
+ }
18
+ if (!isAuthenticated) {
19
+ const authed = await checkAuthSession();
20
+ if (!authed)
21
+ return false;
22
+ }
23
+ if (shouldSuppressAttachBootstrapPush(signature)) {
24
+ return false;
25
+ }
26
+ if (workspacePushInFlightRef.current || workspacePullInFlightRef.current) {
27
+ workspacePendingSignatureRef.current = signature;
28
+ return false;
29
+ }
30
+ const leaseAcquired = await acquireWorkspaceSyncLease(currentWorkspaceId, 'push');
31
+ if (!leaseAcquired) {
32
+ workspacePendingSignatureRef.current = signature;
33
+ reportWorkspaceWindowContention('push');
34
+ return false;
35
+ }
36
+ workspacePushInFlightRef.current = true;
37
+ setWorkspaceSyncBusy(true);
38
+ setUserGlobalSyncStatus('syncing');
39
+ setUserGlobalSyncError(null);
40
+ const pushStartedAtMs = Date.now();
41
+ const forceAllAiProfiles = options?.forceAllAiProfiles === true || workspaceForceFullAiProfilePushRef.current === true;
42
+ try {
43
+ await refreshWorkspaceSyncMarkdownDocumentsSnapshot();
44
+ await refreshWorkspaceSyncAiProfilesSnapshot();
45
+ await refreshWorkspaceSyncAssetsSnapshot();
46
+ await refreshWorkspaceSyncDocumentReviewSessionsSnapshot();
47
+ const ready = await ensureWorkspaceSyncReady();
48
+ if (!ready)
49
+ return false;
50
+ const payload = buildWorkspaceSyncPayload({ forceAllAiProfiles });
51
+ setWorkspaceSyncPendingChanges(payload.changes.length);
52
+ const pushResult = await runWorkspacePushSyncService({
53
+ resolveCloudAuthUrl,
54
+ workspaceId: currentWorkspaceId,
55
+ payload,
56
+ ensureCloudWorkspaceReadyForSync,
57
+ repairMode: options?.repairMode === true || workspaceRepairModeRef.current === true
58
+ });
59
+ if (!pushResult.success) {
60
+ if (pushResult.status === 401) {
61
+ await handleSyncAuthFailure('push', 401);
62
+ return false;
63
+ }
64
+ let message = pushResult.error
65
+ ? `Workspace sync push failed (${pushResult.status || 0}): ${pushResult.error}`
66
+ : `Workspace sync push failed (${pushResult.status || 0})`;
67
+ if (pushResult.status === 413) {
68
+ message = 'Workspace sync failed because the payload is too large. This usually happens when one or more attachments exceed the project limit.';
69
+ }
70
+ setUserGlobalSyncStatus('error');
71
+ setUserGlobalSyncError(message);
72
+ setWorkspaceLastErrorMessage(message);
73
+ setWorkspaceLastErrorAt(new Date().toISOString());
74
+ reportSyncEvent(currentWorkspaceId, {
75
+ eventType: 'push',
76
+ status: 'error',
77
+ statusCode: pushResult.status,
78
+ errorMessage: message,
79
+ requestMs: pushResult.requestMs
80
+ });
81
+ workspacePendingSignatureRef.current = signature;
82
+ if (pushResult.transient || isTransientWorkspaceSyncStatus(pushResult.status)) {
83
+ scheduleWorkspaceSyncRetry(pushResult.retryAfterMs);
84
+ }
85
+ else if (workspaceSyncPhase === 'active') {
86
+ persistWorkspaceSyncPatchSafely({ phase: 'error', lastErrorMessage: message });
87
+ }
88
+ return false;
89
+ }
90
+ workspaceLastPushedSignatureRef.current = signature;
91
+ workspaceLastPushedTaskIdsRef.current = pushResult.currentTaskIds;
92
+ workspaceLastPushedInitiativeIdsRef.current = new Set(pushResult.currentInitiativeIds);
93
+ workspaceLastPushedInitiativeWatermarksRef.current = new Map(pushResult.currentInitiativeWatermarks);
94
+ workspaceLastPushedWorkstreamIdsRef.current = new Set(pushResult.currentWorkstreamIds);
95
+ workspaceLastPushedWorkstreamWatermarksRef.current = new Map(pushResult.currentWorkstreamWatermarks);
96
+ workspaceLastPushedAiProfileIdsRef.current = new Set(pushResult.currentAiProfileIds);
97
+ workspaceLastPushedAiProfileWatermarksRef.current = new Map(pushResult.currentAiProfileWatermarks);
98
+ workspaceLastPushedDocumentPathsRef.current = new Set(pushResult.currentDocumentPaths);
99
+ workspaceLastPushedDocumentWatermarksRef.current = new Map(pushResult.currentDocumentWatermarks);
100
+ workspaceLastPushedAssetPathsRef.current = new Set(pushResult.currentAssetPaths);
101
+ workspaceLastPushedAssetWatermarksRef.current = new Map(pushResult.currentAssetWatermarks);
102
+ workspaceLastPushedDocumentReviewSessionIdsRef.current = new Set(pushResult.currentDocumentReviewSessionIds);
103
+ workspaceLastPushedDocumentReviewSessionWatermarksRef.current = new Map(pushResult.currentDocumentReviewSessionWatermarks);
104
+ workspaceLastPushedDocumentReviewCommentIdsRef.current = new Set(pushResult.currentDocumentReviewCommentIds);
105
+ workspaceLastPushedDocumentReviewCommentWatermarksRef.current = new Map(pushResult.currentDocumentReviewCommentWatermarks);
106
+ workspaceLastPushedAnnotatedAttachmentSessionIdsRef.current = new Set(pushResult.currentAnnotatedAttachmentSessionIds);
107
+ workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current = new Map(pushResult.currentAnnotatedAttachmentSessionWatermarks);
108
+ workspaceLastPushedTaskEventWatermarksRef.current = new Map(pushResult.currentTaskEventWatermarks);
109
+ if (forceAllAiProfiles) {
110
+ workspaceForceFullAiProfilePushRef.current = false;
111
+ }
112
+ for (const [taskId, watermark] of pushResult.pushedWatermarks) {
113
+ workspaceLastPushedWatermarksRef.current.set(taskId, watermark);
114
+ }
115
+ if (pushResult.deleteTaskIds.size > 0) {
116
+ for (const taskId of pushResult.deleteTaskIds) {
117
+ workspaceDeletedTaskIdsRef.current.delete(taskId);
118
+ workspaceLastPushedWatermarksRef.current.delete(taskId);
119
+ }
120
+ }
121
+ savePersistedSyncWatermarks(currentWorkspaceId, {
122
+ taskWatermarks: workspaceLastPushedWatermarksRef.current,
123
+ initiativeWatermarks: workspaceLastPushedInitiativeWatermarksRef.current,
124
+ workstreamWatermarks: workspaceLastPushedWorkstreamWatermarksRef.current,
125
+ documentWatermarks: workspaceLastPushedDocumentWatermarksRef.current,
126
+ assetWatermarks: workspaceLastPushedAssetWatermarksRef.current,
127
+ documentReviewSessionWatermarks: workspaceLastPushedDocumentReviewSessionWatermarksRef.current,
128
+ documentReviewCommentWatermarks: workspaceLastPushedDocumentReviewCommentWatermarksRef.current,
129
+ annotatedAttachmentSessionWatermarks: workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current,
130
+ taskEventWatermarks: workspaceLastPushedTaskEventWatermarksRef.current,
131
+ taxonomyStateFingerprint
132
+ });
133
+ if (Array.isArray(pushResult.emittedEventIds) && pushResult.emittedEventIds.length > 0) {
134
+ recordSuppressedEventIds(pushResult.emittedEventIds, {
135
+ queueRef: realtimeSuppressedEventQueueRef,
136
+ maxQueueSize: 2048
137
+ });
138
+ }
139
+ if (Array.isArray(pushResult.appliedTaskUpserts) && pushResult.appliedTaskUpserts.length > 0) {
140
+ const applyResult = await applyLocalWorkspaceChanges(currentWorkspaceId, pushResult.appliedTaskUpserts.map((entry) => ({
141
+ op: 'upsert',
142
+ archived: entry.archived === true,
143
+ task: entry.task
144
+ })));
145
+ if (applyResult.ok) {
146
+ for (const entry of pushResult.appliedTaskUpserts) {
147
+ const taskId = String(entry?.task?.id || '').trim();
148
+ const watermark = String(entry?.task?.updatedAt || entry?.task?.createdAt || '').trim();
149
+ if (!taskId || !watermark)
150
+ continue;
151
+ workspaceLastPushedWatermarksRef.current.set(taskId, watermark);
152
+ }
153
+ try {
154
+ await refreshLocalWorkspaceTaskCollections();
155
+ }
156
+ catch {
157
+ logSyncDebug('push_local_task_refresh_failed', {
158
+ workspaceId: currentWorkspaceId,
159
+ taskCount: pushResult.appliedTaskUpserts.length
160
+ });
161
+ }
162
+ }
163
+ else {
164
+ logSyncDebug('push_local_task_apply_failed', {
165
+ workspaceId: currentWorkspaceId,
166
+ taskCount: pushResult.appliedTaskUpserts.length,
167
+ status: applyResult.status,
168
+ failures: applyResult.failures
169
+ });
170
+ }
171
+ }
172
+ clearWorkspaceRetry();
173
+ clearWorkspaceIssue();
174
+ const syncedAt = pushResult.syncedAt || new Date().toISOString();
175
+ workspaceLastPushedSignatureRef.current = buildWorkspaceSyncSignature();
176
+ setWorkspaceLastPushAt(syncedAt);
177
+ setWorkspaceSyncPendingChanges(0);
178
+ setUserGlobalSyncStatus('idle');
179
+ persistWorkspaceSyncPatchSafely({
180
+ phase: 'active',
181
+ lastPushAt: syncedAt,
182
+ lastSyncedAt: syncedAt,
183
+ lastErrorMessage: null
184
+ });
185
+ reportSyncEvent(currentWorkspaceId, {
186
+ eventType: workspaceSyncPhase === 'provision-local' ? 'bootstrap' : 'push',
187
+ status: 'success',
188
+ changeCount: pushResult.changeCount,
189
+ requestMs: pushResult.requestMs
190
+ });
191
+ return true;
192
+ }
193
+ catch (error) {
194
+ const message = 'Workspace sync push failed.';
195
+ setUserGlobalSyncStatus('error');
196
+ setUserGlobalSyncError(message);
197
+ setWorkspaceLastErrorMessage(message);
198
+ setWorkspaceLastErrorAt(new Date().toISOString());
199
+ workspacePendingSignatureRef.current = signature;
200
+ scheduleWorkspaceSyncRetry();
201
+ reportSyncEvent(currentWorkspaceId, {
202
+ eventType: 'push',
203
+ status: 'error',
204
+ errorMessage: `${message} ${String(error?.message || '').trim()}`.trim()
205
+ });
206
+ logSyncDebug('push_failed_exception', {
207
+ workspaceId: currentWorkspaceId,
208
+ elapsedMs: Date.now() - pushStartedAtMs
209
+ });
210
+ return false;
211
+ }
212
+ finally {
213
+ workspacePushInFlightRef.current = false;
214
+ setWorkspaceSyncBusy(workspacePullInFlightRef.current);
215
+ releaseWorkspaceSyncLease(currentWorkspaceId);
216
+ const pendingSignature = workspacePendingSignatureRef.current;
217
+ if (pendingSignature
218
+ && pendingSignature !== workspaceLastPushedSignatureRef.current
219
+ && !workspacePullInFlightRef.current) {
220
+ workspacePendingSignatureRef.current = '';
221
+ window.setTimeout(() => {
222
+ void pushWorkspaceChangesToCloud(pendingSignature);
223
+ }, 120);
224
+ }
225
+ }
226
+ };
227
+ const pullWorkspaceChangesFromCloud = async (options) => {
228
+ if (!cloudAuthConfigured || runtimeMode !== 'local' || !workspaceCloudSyncEnabled)
229
+ return false;
230
+ if (!isValidWorkspaceKey(currentWorkspaceId))
231
+ return false;
232
+ if (workspaceRepairModeRef.current === true && options?.repairMode !== true)
233
+ return false;
234
+ if (workspaceSyncPhase === 'provision-local')
235
+ return false;
236
+ if (isWorkspaceRetryPending())
237
+ return false;
238
+ if (!isAuthenticated) {
239
+ const authed = await checkAuthSession();
240
+ if (!authed)
241
+ return false;
242
+ }
243
+ if (workspacePullInFlightRef.current || workspacePushInFlightRef.current)
244
+ return false;
245
+ const leaseAcquired = await acquireWorkspaceSyncLease(currentWorkspaceId, 'pull');
246
+ if (!leaseAcquired) {
247
+ reportWorkspaceWindowContention('pull');
248
+ return false;
249
+ }
250
+ workspacePullInFlightRef.current = true;
251
+ setWorkspaceSyncBusy(true);
252
+ setUserGlobalSyncStatus('syncing');
253
+ setUserGlobalSyncError(null);
254
+ const pullStartedAtMs = Date.now();
255
+ try {
256
+ const ready = await ensureWorkspaceSyncReady();
257
+ if (!ready)
258
+ return false;
259
+ const bootstrap = workspaceSyncPhase === 'attach-cloud';
260
+ const cursor = bootstrap || options?.forceCursorNull === true ? null : workspacePullCursorRef.current;
261
+ const pullResult = await runWorkspacePullSyncService({
262
+ resolveCloudAuthUrl,
263
+ workspaceId: currentWorkspaceId,
264
+ cursor,
265
+ bootstrap,
266
+ ensureCloudWorkspaceReadyForSync,
267
+ maxPages: 20,
268
+ repairMode: options?.repairMode === true || workspaceRepairModeRef.current === true,
269
+ onPagePulled: () => { bootstrapLastProgressAtRef.current = Date.now(); },
270
+ onChangesApplied: () => { bootstrapLastProgressAtRef.current = Date.now(); }
271
+ });
272
+ if (!pullResult.success) {
273
+ if (pullResult.kind === 'pull_http' && pullResult.status === 401) {
274
+ await handleSyncAuthFailure('pull', 401);
275
+ return false;
276
+ }
277
+ if (pullResult.kind === 'apply_auth') {
278
+ await handleSyncAuthFailure('apply', 401);
279
+ return false;
280
+ }
281
+ let message = pullResult.kind === 'apply_payload' || pullResult.kind === 'apply_all_candidates' || pullResult.kind === 'exception'
282
+ ? String(pullResult.error || 'Workspace sync apply failed.')
283
+ : `Workspace sync pull failed (${pullResult.status || 0})`;
284
+ if (pullResult.status === 413) {
285
+ message = 'Local sync failed because the downloaded payload is too large. This usually happens when the workspace contains massive attachments.';
286
+ }
287
+ setUserGlobalSyncStatus('error');
288
+ setUserGlobalSyncError(message);
289
+ setWorkspaceLastErrorMessage(message);
290
+ setWorkspaceLastErrorAt(new Date().toISOString());
291
+ reportSyncEvent(currentWorkspaceId, {
292
+ eventType: pullResult.kind?.startsWith('apply') ? 'apply' : 'pull',
293
+ status: 'error',
294
+ statusCode: pullResult.status,
295
+ errorMessage: message,
296
+ requestMs: pullResult.pullRequestMs,
297
+ details: {
298
+ pages: pullResult.pages,
299
+ applyMs: pullResult.applyMs,
300
+ ...(pullResult.serverDiagnostics ? { serverPullDiagnostics: pullResult.serverDiagnostics } : {})
301
+ }
302
+ });
303
+ if (pullResult.transient || pullResult.hasTransientApplyFailure || isTransientWorkspaceSyncStatus(pullResult.status)) {
304
+ scheduleWorkspaceSyncRetry(pullResult.retryAfterMs);
305
+ }
306
+ else {
307
+ persistWorkspaceSyncPatchSafely({ phase: 'error', lastErrorMessage: message });
308
+ }
309
+ return false;
310
+ }
311
+ if (Array.isArray(pullResult.emittedEventIds)) {
312
+ recordSuppressedEventIds(pullResult.emittedEventIds, { ttlMs: 60_000 });
313
+ }
314
+ if (pullResult.pulledDeleteTaskIds.size > 0) {
315
+ setTasks((prev) => prev.filter((task) => !pullResult.pulledDeleteTaskIds.has(String(task.id || ''))));
316
+ setArchivedTasks((prev) => prev.filter((task) => !pullResult.pulledDeleteTaskIds.has(String(task.id || ''))));
317
+ }
318
+ const decryptFailures = Array.isArray(pullResult.documentDecryptFailures)
319
+ ? pullResult.documentDecryptFailures.filter((value) => String(value || '').trim().length > 0)
320
+ : [];
321
+ if (decryptFailures.length > 0) {
322
+ logSyncDebug('pull_document_decrypt_failures', {
323
+ workspaceId: currentWorkspaceId,
324
+ failureCount: decryptFailures.length
325
+ });
326
+ }
327
+ workspacePullCursorRef.current = pullResult.cursor || null;
328
+ if (pullResult.appliedChanges > 0
329
+ && (pullResult.pulledActiveUpserts
330
+ || pullResult.pulledArchivedUpserts
331
+ || pullResult.pulledTaskEventUpserts)) {
332
+ try {
333
+ await refreshLocalWorkspaceTaskCollections();
334
+ }
335
+ catch {
336
+ logSyncDebug('pull_local_refresh_failed', {
337
+ workspaceId: currentWorkspaceId,
338
+ appliedChanges: pullResult.appliedChanges
339
+ });
340
+ }
341
+ }
342
+ clearWorkspaceRetry();
343
+ clearWorkspaceIssue();
344
+ const syncedAt = pullResult.syncedAt || new Date().toISOString();
345
+ setWorkspaceLastPullAt(syncedAt);
346
+ setWorkspaceSyncPendingChanges(0);
347
+ setUserGlobalSyncStatus('idle');
348
+ if (workspaceSyncPhase === 'attach-cloud') {
349
+ workspaceCaptureAttachBootstrapBaselineRef.current = true;
350
+ workspacePendingSignatureRef.current = '';
351
+ }
352
+ await persistWorkspaceSyncPatchBestEffort({
353
+ phase: 'active',
354
+ pullCursor: workspacePullCursorRef.current,
355
+ lastPullAt: syncedAt,
356
+ lastSyncedAt: syncedAt,
357
+ lastErrorMessage: null
358
+ });
359
+ reportSyncEvent(currentWorkspaceId, {
360
+ eventType: workspaceSyncPhase === 'attach-cloud' ? 'bootstrap' : 'pull',
361
+ status: 'success',
362
+ changeCount: pullResult.appliedChanges,
363
+ requestMs: pullResult.pullRequestMs,
364
+ details: {
365
+ pages: pullResult.pages,
366
+ applyMs: pullResult.applyMs,
367
+ ...(pullResult.serverDiagnostics ? { serverPullDiagnostics: pullResult.serverDiagnostics } : {})
368
+ }
369
+ });
370
+ if (workspaceSyncPhase === 'attach-cloud') {
371
+ await refreshWorkspaceSyncMarkdownDocumentsSnapshot();
372
+ await refreshWorkspaceSyncAssetsSnapshot();
373
+ await refreshWorkspaceSyncDocumentReviewSessionsSnapshot();
374
+ await refreshWorkspaceSyncAnnotatedAttachmentSessionsSnapshot();
375
+ seedWorkspaceSnapshotPushBaseline();
376
+ return true;
377
+ }
378
+ if (!options?.forceCursorNull && !bootstrap && !workspaceStartupFullReconcileRanRef.current) {
379
+ workspaceStartupFullReconcileRanRef.current = true;
380
+ window.setTimeout(() => {
381
+ if (workspaceFullReconcileInFlightRef.current)
382
+ return;
383
+ workspaceFullReconcileInFlightRef.current = true;
384
+ void pullWorkspaceChangesFromCloud({ forceCursorNull: true }).finally(() => {
385
+ workspaceFullReconcileInFlightRef.current = false;
386
+ });
387
+ }, 250);
388
+ }
389
+ const pendingSignature = workspacePendingSignatureRef.current || buildWorkspaceSyncSignature();
390
+ if (pendingSignature && pendingSignature !== workspaceLastPushedSignatureRef.current) {
391
+ workspacePendingSignatureRef.current = '';
392
+ window.setTimeout(() => {
393
+ void pushWorkspaceChangesToCloud(pendingSignature);
394
+ }, 120);
395
+ }
396
+ return true;
397
+ }
398
+ catch (error) {
399
+ const detail = String(error?.message || '').trim();
400
+ const message = detail
401
+ ? `Workspace sync pull failed. ${detail}`
402
+ : 'Workspace sync pull failed.';
403
+ setUserGlobalSyncStatus('error');
404
+ setUserGlobalSyncError(message);
405
+ setWorkspaceLastErrorMessage(message);
406
+ setWorkspaceLastErrorAt(new Date().toISOString());
407
+ scheduleWorkspaceSyncRetry();
408
+ reportSyncEvent(currentWorkspaceId, {
409
+ eventType: 'pull',
410
+ status: 'error',
411
+ errorMessage: message,
412
+ requestMs: Date.now() - pullStartedAtMs
413
+ });
414
+ logSyncDebug('pull_failed_exception', {
415
+ workspaceId: currentWorkspaceId,
416
+ elapsedMs: Date.now() - pullStartedAtMs,
417
+ error: detail || null
418
+ });
419
+ return false;
420
+ }
421
+ finally {
422
+ workspacePullInFlightRef.current = false;
423
+ setWorkspaceSyncBusy(workspacePushInFlightRef.current);
424
+ releaseWorkspaceSyncLease(currentWorkspaceId);
425
+ }
426
+ };
427
+ return {
428
+ pushWorkspaceChangesToCloud,
429
+ pullWorkspaceChangesFromCloud
430
+ };
431
+ }
@@ -0,0 +1,16 @@
1
+ import { type MutableRefObject } from 'react';
2
+ interface UseDataVersionRefreshArgs {
3
+ isOpen: boolean;
4
+ authBlocked: boolean;
5
+ authRequiredForApi: boolean;
6
+ isAuthenticated: boolean;
7
+ canCallProtectedApi: boolean;
8
+ shouldGateProtectedApiCalls: boolean;
9
+ authSessionResolvedRef: MutableRefObject<boolean>;
10
+ authRequiredForApiRef: MutableRefObject<boolean>;
11
+ isAuthenticatedRef: MutableRefObject<boolean>;
12
+ dataVersionRef: MutableRefObject<number | null>;
13
+ refreshTaskCollectionsForDataVersion: () => Promise<void>;
14
+ }
15
+ export declare function useDataVersionRefresh({ isOpen, authBlocked, authRequiredForApi, isAuthenticated, canCallProtectedApi, shouldGateProtectedApiCalls, authSessionResolvedRef, authRequiredForApiRef, isAuthenticatedRef, dataVersionRef, refreshTaskCollectionsForDataVersion }: UseDataVersionRefreshArgs): void;
16
+ export {};
@@ -0,0 +1,73 @@
1
+ import { useEffect } from 'react';
2
+ import { shouldSkipDataVersionPolling, shouldSkipDataVersionTick } from '../auth/useAuthGuardPolicy';
3
+ export function useDataVersionRefresh({ isOpen, authBlocked, authRequiredForApi, isAuthenticated, canCallProtectedApi, shouldGateProtectedApiCalls, authSessionResolvedRef, authRequiredForApiRef, isAuthenticatedRef, dataVersionRef, refreshTaskCollectionsForDataVersion }) {
4
+ useEffect(() => {
5
+ if (typeof window === 'undefined' || typeof document === 'undefined')
6
+ return;
7
+ if (shouldSkipDataVersionPolling({
8
+ isOpen,
9
+ authBlocked,
10
+ authRequiredForApi,
11
+ isAuthenticated,
12
+ canCallProtectedApi
13
+ }))
14
+ return;
15
+ let cancelled = false;
16
+ const checkDataVersion = async () => {
17
+ if (cancelled || document.visibilityState !== 'visible')
18
+ return;
19
+ if (shouldSkipDataVersionTick({
20
+ shouldGateProtectedApiCalls,
21
+ authSessionResolved: authSessionResolvedRef.current,
22
+ authRequiredForApi: authRequiredForApiRef.current,
23
+ isAuthenticated: isAuthenticatedRef.current
24
+ }))
25
+ return;
26
+ try {
27
+ const res = await fetch('/api/taskforce/data-version');
28
+ if (!res.ok)
29
+ return;
30
+ const data = await res.json();
31
+ const version = Number(data?.dataVersion);
32
+ if (!Number.isFinite(version))
33
+ return;
34
+ if (dataVersionRef.current === null) {
35
+ dataVersionRef.current = version;
36
+ return;
37
+ }
38
+ if (version !== dataVersionRef.current) {
39
+ dataVersionRef.current = version;
40
+ await refreshTaskCollectionsForDataVersion();
41
+ }
42
+ }
43
+ catch {
44
+ // best effort only
45
+ }
46
+ };
47
+ const onVisibilityChange = () => {
48
+ if (document.visibilityState === 'visible') {
49
+ void checkDataVersion();
50
+ }
51
+ };
52
+ const intervalId = window.setInterval(() => { void checkDataVersion(); }, 3000);
53
+ document.addEventListener('visibilitychange', onVisibilityChange);
54
+ void checkDataVersion();
55
+ return () => {
56
+ cancelled = true;
57
+ window.clearInterval(intervalId);
58
+ document.removeEventListener('visibilitychange', onVisibilityChange);
59
+ };
60
+ }, [
61
+ isOpen,
62
+ authBlocked,
63
+ authRequiredForApi,
64
+ isAuthenticated,
65
+ canCallProtectedApi,
66
+ shouldGateProtectedApiCalls,
67
+ authSessionResolvedRef,
68
+ authRequiredForApiRef,
69
+ isAuthenticatedRef,
70
+ dataVersionRef,
71
+ refreshTaskCollectionsForDataVersion
72
+ ]);
73
+ }
@@ -1,8 +1,10 @@
1
1
  import type { Environment } from '../../types';
2
2
  interface UseResourceExportInput {
3
3
  storagePath: string;
4
+ shouldDeferProtectedApiCalls: boolean;
5
+ shouldBlockProtectedApiCalls: boolean;
4
6
  }
5
- export declare function useResourceExport({ storagePath }: UseResourceExportInput): {
7
+ export declare function useResourceExport({ storagePath, shouldDeferProtectedApiCalls, shouldBlockProtectedApiCalls }: UseResourceExportInput): {
6
8
  exportEnvironment: string;
7
9
  setExportEnvironment: import("react").Dispatch<import("react").SetStateAction<string>>;
8
10
  availableEnvironments: Environment[];