@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,435 @@
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, workspaceDeletedTaskWatermarksRef, 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
+ workspaceDeletedTaskWatermarksRef.current.delete(taskId);
119
+ workspaceLastPushedWatermarksRef.current.delete(taskId);
120
+ }
121
+ }
122
+ savePersistedSyncWatermarks(currentWorkspaceId, {
123
+ taskWatermarks: workspaceLastPushedWatermarksRef.current,
124
+ initiativeWatermarks: workspaceLastPushedInitiativeWatermarksRef.current,
125
+ workstreamWatermarks: workspaceLastPushedWorkstreamWatermarksRef.current,
126
+ documentWatermarks: workspaceLastPushedDocumentWatermarksRef.current,
127
+ assetWatermarks: workspaceLastPushedAssetWatermarksRef.current,
128
+ documentReviewSessionWatermarks: workspaceLastPushedDocumentReviewSessionWatermarksRef.current,
129
+ documentReviewCommentWatermarks: workspaceLastPushedDocumentReviewCommentWatermarksRef.current,
130
+ annotatedAttachmentSessionWatermarks: workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current,
131
+ taskEventWatermarks: workspaceLastPushedTaskEventWatermarksRef.current,
132
+ taxonomyStateFingerprint
133
+ });
134
+ if (Array.isArray(pushResult.emittedEventIds) && pushResult.emittedEventIds.length > 0) {
135
+ recordSuppressedEventIds(pushResult.emittedEventIds, {
136
+ queueRef: realtimeSuppressedEventQueueRef,
137
+ maxQueueSize: 2048
138
+ });
139
+ }
140
+ if (Array.isArray(pushResult.appliedTaskUpserts) && pushResult.appliedTaskUpserts.length > 0) {
141
+ const applyResult = await applyLocalWorkspaceChanges(currentWorkspaceId, pushResult.appliedTaskUpserts.map((entry) => ({
142
+ op: 'upsert',
143
+ archived: entry.archived === true,
144
+ task: entry.task
145
+ })));
146
+ if (applyResult.ok) {
147
+ for (const entry of pushResult.appliedTaskUpserts) {
148
+ const taskId = String(entry?.task?.id || '').trim();
149
+ const watermark = String(entry?.task?.updatedAt || entry?.task?.createdAt || '').trim();
150
+ if (!taskId || !watermark)
151
+ continue;
152
+ workspaceLastPushedWatermarksRef.current.set(taskId, watermark);
153
+ }
154
+ try {
155
+ await refreshLocalWorkspaceTaskCollections();
156
+ }
157
+ catch {
158
+ logSyncDebug('push_local_task_refresh_failed', {
159
+ workspaceId: currentWorkspaceId,
160
+ taskCount: pushResult.appliedTaskUpserts.length
161
+ });
162
+ }
163
+ }
164
+ else {
165
+ logSyncDebug('push_local_task_apply_failed', {
166
+ workspaceId: currentWorkspaceId,
167
+ taskCount: pushResult.appliedTaskUpserts.length,
168
+ status: applyResult.status,
169
+ failures: applyResult.failures
170
+ });
171
+ }
172
+ }
173
+ clearWorkspaceRetry();
174
+ clearWorkspaceIssue();
175
+ const syncedAt = pushResult.syncedAt || new Date().toISOString();
176
+ workspaceLastPushedSignatureRef.current = buildWorkspaceSyncSignature();
177
+ setWorkspaceLastPushAt(syncedAt);
178
+ setWorkspaceSyncPendingChanges(0);
179
+ setUserGlobalSyncStatus('idle');
180
+ persistWorkspaceSyncPatchSafely({
181
+ phase: 'active',
182
+ lastPushAt: syncedAt,
183
+ lastSyncedAt: syncedAt,
184
+ lastErrorMessage: null
185
+ });
186
+ reportSyncEvent(currentWorkspaceId, {
187
+ eventType: workspaceSyncPhase === 'provision-local' ? 'bootstrap' : 'push',
188
+ status: 'success',
189
+ changeCount: pushResult.changeCount,
190
+ requestMs: pushResult.requestMs
191
+ });
192
+ return true;
193
+ }
194
+ catch (error) {
195
+ const message = 'Workspace sync push failed.';
196
+ setUserGlobalSyncStatus('error');
197
+ setUserGlobalSyncError(message);
198
+ setWorkspaceLastErrorMessage(message);
199
+ setWorkspaceLastErrorAt(new Date().toISOString());
200
+ workspacePendingSignatureRef.current = signature;
201
+ scheduleWorkspaceSyncRetry();
202
+ reportSyncEvent(currentWorkspaceId, {
203
+ eventType: 'push',
204
+ status: 'error',
205
+ errorMessage: `${message} ${String(error?.message || '').trim()}`.trim()
206
+ });
207
+ logSyncDebug('push_failed_exception', {
208
+ workspaceId: currentWorkspaceId,
209
+ elapsedMs: Date.now() - pushStartedAtMs
210
+ });
211
+ return false;
212
+ }
213
+ finally {
214
+ workspacePushInFlightRef.current = false;
215
+ setWorkspaceSyncBusy(workspacePullInFlightRef.current);
216
+ releaseWorkspaceSyncLease(currentWorkspaceId);
217
+ const pendingSignature = workspacePendingSignatureRef.current;
218
+ const latestSignature = buildWorkspaceSyncSignature();
219
+ if (pendingSignature
220
+ && latestSignature
221
+ && latestSignature !== workspaceLastPushedSignatureRef.current
222
+ && !workspacePullInFlightRef.current
223
+ && !isWorkspaceRetryPending()) {
224
+ workspacePendingSignatureRef.current = '';
225
+ window.setTimeout(() => {
226
+ void pushWorkspaceChangesToCloud(latestSignature);
227
+ }, 120);
228
+ }
229
+ }
230
+ };
231
+ const pullWorkspaceChangesFromCloud = async (options) => {
232
+ if (!cloudAuthConfigured || runtimeMode !== 'local' || !workspaceCloudSyncEnabled)
233
+ return false;
234
+ if (!isValidWorkspaceKey(currentWorkspaceId))
235
+ return false;
236
+ if (workspaceRepairModeRef.current === true && options?.repairMode !== true)
237
+ return false;
238
+ if (workspaceSyncPhase === 'provision-local')
239
+ return false;
240
+ if (isWorkspaceRetryPending())
241
+ return false;
242
+ if (!isAuthenticated) {
243
+ const authed = await checkAuthSession();
244
+ if (!authed)
245
+ return false;
246
+ }
247
+ if (workspacePullInFlightRef.current || workspacePushInFlightRef.current)
248
+ return false;
249
+ const leaseAcquired = await acquireWorkspaceSyncLease(currentWorkspaceId, 'pull');
250
+ if (!leaseAcquired) {
251
+ reportWorkspaceWindowContention('pull');
252
+ return false;
253
+ }
254
+ workspacePullInFlightRef.current = true;
255
+ setWorkspaceSyncBusy(true);
256
+ setUserGlobalSyncStatus('syncing');
257
+ setUserGlobalSyncError(null);
258
+ const pullStartedAtMs = Date.now();
259
+ try {
260
+ const ready = await ensureWorkspaceSyncReady();
261
+ if (!ready)
262
+ return false;
263
+ const bootstrap = workspaceSyncPhase === 'attach-cloud';
264
+ const cursor = bootstrap || options?.forceCursorNull === true ? null : workspacePullCursorRef.current;
265
+ const pullResult = await runWorkspacePullSyncService({
266
+ resolveCloudAuthUrl,
267
+ workspaceId: currentWorkspaceId,
268
+ cursor,
269
+ bootstrap,
270
+ ensureCloudWorkspaceReadyForSync,
271
+ maxPages: 20,
272
+ repairMode: options?.repairMode === true || workspaceRepairModeRef.current === true,
273
+ onPagePulled: () => { bootstrapLastProgressAtRef.current = Date.now(); },
274
+ onChangesApplied: () => { bootstrapLastProgressAtRef.current = Date.now(); }
275
+ });
276
+ if (!pullResult.success) {
277
+ if (pullResult.kind === 'pull_http' && pullResult.status === 401) {
278
+ await handleSyncAuthFailure('pull', 401);
279
+ return false;
280
+ }
281
+ if (pullResult.kind === 'apply_auth') {
282
+ await handleSyncAuthFailure('apply', 401);
283
+ return false;
284
+ }
285
+ let message = pullResult.kind === 'apply_payload' || pullResult.kind === 'apply_all_candidates' || pullResult.kind === 'exception'
286
+ ? String(pullResult.error || 'Workspace sync apply failed.')
287
+ : `Workspace sync pull failed (${pullResult.status || 0})`;
288
+ if (pullResult.status === 413) {
289
+ message = 'Local sync failed because the downloaded payload is too large. This usually happens when the workspace contains massive attachments.';
290
+ }
291
+ setUserGlobalSyncStatus('error');
292
+ setUserGlobalSyncError(message);
293
+ setWorkspaceLastErrorMessage(message);
294
+ setWorkspaceLastErrorAt(new Date().toISOString());
295
+ reportSyncEvent(currentWorkspaceId, {
296
+ eventType: pullResult.kind?.startsWith('apply') ? 'apply' : 'pull',
297
+ status: 'error',
298
+ statusCode: pullResult.status,
299
+ errorMessage: message,
300
+ requestMs: pullResult.pullRequestMs,
301
+ details: {
302
+ pages: pullResult.pages,
303
+ applyMs: pullResult.applyMs,
304
+ ...(pullResult.serverDiagnostics ? { serverPullDiagnostics: pullResult.serverDiagnostics } : {})
305
+ }
306
+ });
307
+ if (pullResult.transient || pullResult.hasTransientApplyFailure || isTransientWorkspaceSyncStatus(pullResult.status)) {
308
+ scheduleWorkspaceSyncRetry(pullResult.retryAfterMs);
309
+ }
310
+ else {
311
+ persistWorkspaceSyncPatchSafely({ phase: 'error', lastErrorMessage: message });
312
+ }
313
+ return false;
314
+ }
315
+ if (Array.isArray(pullResult.emittedEventIds)) {
316
+ recordSuppressedEventIds(pullResult.emittedEventIds, { ttlMs: 60_000 });
317
+ }
318
+ if (pullResult.pulledDeleteTaskIds.size > 0) {
319
+ setTasks((prev) => prev.filter((task) => !pullResult.pulledDeleteTaskIds.has(String(task.id || ''))));
320
+ setArchivedTasks((prev) => prev.filter((task) => !pullResult.pulledDeleteTaskIds.has(String(task.id || ''))));
321
+ }
322
+ const decryptFailures = Array.isArray(pullResult.documentDecryptFailures)
323
+ ? pullResult.documentDecryptFailures.filter((value) => String(value || '').trim().length > 0)
324
+ : [];
325
+ if (decryptFailures.length > 0) {
326
+ logSyncDebug('pull_document_decrypt_failures', {
327
+ workspaceId: currentWorkspaceId,
328
+ failureCount: decryptFailures.length
329
+ });
330
+ }
331
+ workspacePullCursorRef.current = pullResult.cursor || null;
332
+ if (pullResult.appliedChanges > 0
333
+ && (pullResult.pulledActiveUpserts
334
+ || pullResult.pulledArchivedUpserts
335
+ || pullResult.pulledTaskEventUpserts)) {
336
+ try {
337
+ await refreshLocalWorkspaceTaskCollections();
338
+ }
339
+ catch {
340
+ logSyncDebug('pull_local_refresh_failed', {
341
+ workspaceId: currentWorkspaceId,
342
+ appliedChanges: pullResult.appliedChanges
343
+ });
344
+ }
345
+ }
346
+ clearWorkspaceRetry();
347
+ clearWorkspaceIssue();
348
+ const syncedAt = pullResult.syncedAt || new Date().toISOString();
349
+ setWorkspaceLastPullAt(syncedAt);
350
+ setWorkspaceSyncPendingChanges(0);
351
+ setUserGlobalSyncStatus('idle');
352
+ if (workspaceSyncPhase === 'attach-cloud') {
353
+ workspaceCaptureAttachBootstrapBaselineRef.current = true;
354
+ workspacePendingSignatureRef.current = '';
355
+ }
356
+ await persistWorkspaceSyncPatchBestEffort({
357
+ phase: 'active',
358
+ pullCursor: workspacePullCursorRef.current,
359
+ lastPullAt: syncedAt,
360
+ lastSyncedAt: syncedAt,
361
+ lastErrorMessage: null
362
+ });
363
+ reportSyncEvent(currentWorkspaceId, {
364
+ eventType: workspaceSyncPhase === 'attach-cloud' ? 'bootstrap' : 'pull',
365
+ status: 'success',
366
+ changeCount: pullResult.appliedChanges,
367
+ requestMs: pullResult.pullRequestMs,
368
+ details: {
369
+ pages: pullResult.pages,
370
+ applyMs: pullResult.applyMs,
371
+ ...(pullResult.serverDiagnostics ? { serverPullDiagnostics: pullResult.serverDiagnostics } : {})
372
+ }
373
+ });
374
+ if (workspaceSyncPhase === 'attach-cloud') {
375
+ await refreshWorkspaceSyncMarkdownDocumentsSnapshot();
376
+ await refreshWorkspaceSyncAssetsSnapshot();
377
+ await refreshWorkspaceSyncDocumentReviewSessionsSnapshot();
378
+ await refreshWorkspaceSyncAnnotatedAttachmentSessionsSnapshot();
379
+ seedWorkspaceSnapshotPushBaseline();
380
+ return true;
381
+ }
382
+ if (!options?.forceCursorNull && !bootstrap && !workspaceStartupFullReconcileRanRef.current) {
383
+ workspaceStartupFullReconcileRanRef.current = true;
384
+ window.setTimeout(() => {
385
+ if (workspaceFullReconcileInFlightRef.current)
386
+ return;
387
+ workspaceFullReconcileInFlightRef.current = true;
388
+ void pullWorkspaceChangesFromCloud({ forceCursorNull: true }).finally(() => {
389
+ workspaceFullReconcileInFlightRef.current = false;
390
+ });
391
+ }, 250);
392
+ }
393
+ const pendingSignature = workspacePendingSignatureRef.current || buildWorkspaceSyncSignature();
394
+ if (pendingSignature && pendingSignature !== workspaceLastPushedSignatureRef.current) {
395
+ workspacePendingSignatureRef.current = '';
396
+ window.setTimeout(() => {
397
+ void pushWorkspaceChangesToCloud(pendingSignature);
398
+ }, 120);
399
+ }
400
+ return true;
401
+ }
402
+ catch (error) {
403
+ const detail = String(error?.message || '').trim();
404
+ const message = detail
405
+ ? `Workspace sync pull failed. ${detail}`
406
+ : 'Workspace sync pull failed.';
407
+ setUserGlobalSyncStatus('error');
408
+ setUserGlobalSyncError(message);
409
+ setWorkspaceLastErrorMessage(message);
410
+ setWorkspaceLastErrorAt(new Date().toISOString());
411
+ scheduleWorkspaceSyncRetry();
412
+ reportSyncEvent(currentWorkspaceId, {
413
+ eventType: 'pull',
414
+ status: 'error',
415
+ errorMessage: message,
416
+ requestMs: Date.now() - pullStartedAtMs
417
+ });
418
+ logSyncDebug('pull_failed_exception', {
419
+ workspaceId: currentWorkspaceId,
420
+ elapsedMs: Date.now() - pullStartedAtMs,
421
+ error: detail || null
422
+ });
423
+ return false;
424
+ }
425
+ finally {
426
+ workspacePullInFlightRef.current = false;
427
+ setWorkspaceSyncBusy(workspacePushInFlightRef.current);
428
+ releaseWorkspaceSyncLease(currentWorkspaceId);
429
+ }
430
+ };
431
+ return {
432
+ pushWorkspaceChangesToCloud,
433
+ pullWorkspaceChangesFromCloud
434
+ };
435
+ }
@@ -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[];
@@ -1,11 +1,12 @@
1
1
  import { useCallback, useEffect, useRef, useState } from 'react';
2
- export function useResourceExport({ storagePath }) {
2
+ export function useResourceExport({ storagePath, shouldDeferProtectedApiCalls, shouldBlockProtectedApiCalls }) {
3
3
  const [exportEnvironment, setExportEnvironment] = useState('antigravity');
4
4
  const [availableEnvironments, setAvailableEnvironments] = useState([]);
5
5
  const [exportWorkflowsPath, setExportWorkflowsPath] = useState('.agent/workflows');
6
6
  const [exportingResource, setExportingResource] = useState(null);
7
7
  const [exportResult, setExportResult] = useState(null);
8
8
  const exportResultTimerRef = useRef(null);
9
+ const shouldSkipProtectedApiCalls = shouldDeferProtectedApiCalls || shouldBlockProtectedApiCalls;
9
10
  useEffect(() => {
10
11
  const env = availableEnvironments.find((item) => item.id === exportEnvironment);
11
12
  if (env) {
@@ -23,13 +24,15 @@ export function useResourceExport({ storagePath }) {
23
24
  }, 5000);
24
25
  }, []);
25
26
  const loadAvailableEnvironments = useCallback(async () => {
27
+ if (shouldSkipProtectedApiCalls)
28
+ return;
26
29
  const envRes = await fetch('/api/taskforce/environments');
27
30
  if (!envRes.ok)
28
31
  return;
29
32
  const envData = await envRes.json().catch(() => ({}));
30
33
  if (envData.environments)
31
34
  setAvailableEnvironments(envData.environments);
32
- }, []);
35
+ }, [shouldSkipProtectedApiCalls]);
33
36
  const handleExportWorkflows = useCallback(async (environment, selected) => {
34
37
  setExportingResource('workflows');
35
38
  setExportResult(null);
@@ -2,8 +2,9 @@ import { useCallback, useState } from 'react';
2
2
  export function useWorkflowTemplates({ shouldDeferProtectedApiCalls, shouldBlockProtectedApiCalls }) {
3
3
  const [availableWorkflows, setAvailableWorkflows] = useState([]);
4
4
  const [initiativeTemplates, setInitiativeTemplates] = useState([]);
5
+ const shouldSkipProtectedApiCalls = shouldDeferProtectedApiCalls || shouldBlockProtectedApiCalls;
5
6
  const fetchWorkflows = useCallback(async () => {
6
- if (shouldDeferProtectedApiCalls || shouldBlockProtectedApiCalls)
7
+ if (shouldSkipProtectedApiCalls)
7
8
  return;
8
9
  try {
9
10
  const res = await fetch('/api/taskforce/workflow-templates');
@@ -15,8 +16,10 @@ export function useWorkflowTemplates({ shouldDeferProtectedApiCalls, shouldBlock
15
16
  catch {
16
17
  // Ignore error
17
18
  }
18
- }, [shouldDeferProtectedApiCalls, shouldBlockProtectedApiCalls]);
19
+ }, [shouldSkipProtectedApiCalls]);
19
20
  const fetchInitiativeTemplates = useCallback(async () => {
21
+ if (shouldSkipProtectedApiCalls)
22
+ return [];
20
23
  try {
21
24
  const res = await fetch('/api/taskforce/initiative-templates');
22
25
  if (!res.ok)
@@ -29,9 +32,9 @@ export function useWorkflowTemplates({ shouldDeferProtectedApiCalls, shouldBlock
29
32
  catch {
30
33
  return [];
31
34
  }
32
- }, []);
35
+ }, [shouldSkipProtectedApiCalls]);
33
36
  const fetchWorkflowTemplate = useCallback(async (name) => {
34
- if (!name)
37
+ if (!name || shouldSkipProtectedApiCalls)
35
38
  return null;
36
39
  const paths = [
37
40
  `/api/taskforce/workflow-template/${encodeURIComponent(name)}`,
@@ -51,8 +54,10 @@ export function useWorkflowTemplates({ shouldDeferProtectedApiCalls, shouldBlock
51
54
  catch {
52
55
  return null;
53
56
  }
54
- }, []);
57
+ }, [shouldSkipProtectedApiCalls]);
55
58
  const fetchWorkflowOverrideNames = useCallback(async () => {
59
+ if (shouldSkipProtectedApiCalls)
60
+ return [];
56
61
  try {
57
62
  const res = await fetch('/api/taskforce/workflow-editor/overrides');
58
63
  if (!res.ok)
@@ -63,7 +68,7 @@ export function useWorkflowTemplates({ shouldDeferProtectedApiCalls, shouldBlock
63
68
  catch {
64
69
  return [];
65
70
  }
66
- }, []);
71
+ }, [shouldSkipProtectedApiCalls]);
67
72
  const saveWorkflowTemplateDraft = useCallback(async (name, draft) => {
68
73
  try {
69
74
  const res = await fetch('/api/taskforce/workflow-editor/save', {
@@ -168,7 +168,8 @@ export function useRealtimeSync(input) {
168
168
  if (event.type === 'taskforce:update') {
169
169
  queueSignal({
170
170
  type: 'taskforce:update',
171
- workspaceId: normalizedWorkspaceId
171
+ workspaceId: normalizedWorkspaceId,
172
+ eventId: typeof event.eventId === 'string' ? event.eventId.trim() : undefined
172
173
  });
173
174
  return;
174
175
  }
@@ -10,6 +10,10 @@ describe('normalizeWorkspaceSyncAiProfileSnapshotEntry', () => {
10
10
  username: 'codex',
11
11
  icon: 'Bot',
12
12
  color: '#0ea5e9',
13
+ description: 'Terminal-first coding agent',
14
+ role: 'Implementer',
15
+ provider: 'OpenAI',
16
+ model: 'gpt-5.4',
13
17
  surface_type: 'coding_tool',
14
18
  provider_metadata: { source: 'test' },
15
19
  created_at: '2026-03-15T18:00:00.000Z',
@@ -24,6 +28,10 @@ describe('normalizeWorkspaceSyncAiProfileSnapshotEntry', () => {
24
28
  username: 'codex',
25
29
  icon: 'Bot',
26
30
  color: '#0ea5e9',
31
+ description: 'Terminal-first coding agent',
32
+ role: 'Implementer',
33
+ provider: 'OpenAI',
34
+ model: 'gpt-5.4',
27
35
  surfaceType: 'coding_tool',
28
36
  providerMetadata: { source: 'test' },
29
37
  createdAt: '2026-03-15T18:00:00.000Z',