@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.
- package/README.md +140 -247
- package/dist/Taskforce.module.css +102 -5
- package/dist/TaskforceCore.js +635 -238
- package/dist/TaskforceCore.test.js +2243 -495
- package/dist/annotatedAttachments/service.d.ts +1 -1
- package/dist/annotatedAttachments/types.d.ts +1 -1
- package/dist/compat/workspaceSyncCompat.js +6 -0
- package/dist/components/features/AgentsModule.js +1 -1
- package/dist/components/features/AgentsModule.test.js +1 -1
- package/dist/components/features/DocumentIndex.d.ts +1 -1
- package/dist/components/features/DocumentIndex.js +1 -1
- package/dist/components/features/DocumentViewer.d.ts +1 -1
- package/dist/components/features/DocumentViewer.js +32 -9
- package/dist/components/features/DocumentWorkspace.d.ts +5 -22
- package/dist/components/features/DocumentWorkspace.js +12 -155
- package/dist/components/features/DocumentWorkspace.test.js +193 -6
- package/dist/components/features/TaskSettings.js +87 -41
- package/dist/components/features/TaskSettings.test.js +180 -40
- package/dist/components/features/documentWorkspaceModel.d.ts +24 -0
- package/dist/components/features/documentWorkspaceModel.js +57 -0
- package/dist/components/features/useDocumentWorkspaceController.d.ts +34 -0
- package/dist/components/features/useDocumentWorkspaceController.js +113 -0
- package/dist/components/task/TaskActionHeader.d.ts +2 -1
- package/dist/components/task/TaskActionHeader.js +3 -3
- package/dist/components/task/TaskActionHeader.test.js +8 -0
- package/dist/components/task/TaskCard.js +16 -1
- package/dist/components/task/TaskCard.test.js +31 -0
- package/dist/components/task/TaskForm.d.ts +9 -8
- package/dist/components/task/TaskForm.js +374 -219
- package/dist/components/task/TaskForm.test.js +482 -361
- package/dist/components/task/TaskKanban.d.ts +1 -1
- package/dist/components/task/TaskKanban.js +33 -161
- package/dist/components/task/TaskStatusActions.d.ts +6 -1
- package/dist/components/task/TaskStatusActions.js +8 -3
- package/dist/components/task/TaskStatusActions.test.js +9 -0
- package/dist/components/task/taskKanbanDropRules.d.ts +58 -0
- package/dist/components/task/taskKanbanDropRules.js +187 -0
- package/dist/components/task/taskKanbanDropRules.test.d.ts +1 -0
- package/dist/components/task/taskKanbanDropRules.test.js +114 -0
- package/dist/components/ui/Modal.d.ts +1 -1
- package/dist/components/ui/Modal.js +1 -0
- package/dist/components/views/AppShellHeader.js +5 -1
- package/dist/components/views/AppShellHeader.test.js +4 -0
- package/dist/components/views/PlanComparisonPage.d.ts +3 -2
- package/dist/components/views/PlanComparisonPage.js +9 -10
- package/dist/components/views/PlanComparisonPage.test.js +55 -41
- package/dist/components/views/PlansPage.d.ts +5 -1
- package/dist/components/views/PlansPage.js +150 -81
- package/dist/components/views/PlansPage.test.js +367 -41
- package/dist/components/views/StandaloneLayout.js +135 -54
- package/dist/components/views/WidgetView.js +11 -2
- package/dist/components/views/panels/FilterToolbar.test.js +6 -4
- package/dist/components/views/standalone/modals/AccountHubModal.d.ts +2 -14
- package/dist/components/views/standalone/modals/AccountHubModal.js +12 -14
- package/dist/components/views/standalone/modals/AccountHubModal.test.js +24 -1
- package/dist/components/views/standalone/modals/SyncStatusModal.js +1 -1
- package/dist/config/envSchema.js +1 -1
- package/dist/core/AiProfileService.d.ts +6 -1
- package/dist/core/AiProfileService.js +161 -16
- package/dist/core/AiProfileService.test.js +3 -1
- package/dist/core/AiProfiles.test.js +200 -0
- package/dist/core/AuthTokenService.test.d.ts +1 -0
- package/dist/core/AuthTokenService.test.js +78 -0
- package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
- package/dist/core/EntitlementsPolicy.test.js +75 -13
- package/dist/core/PlanEntitlementService.d.ts +9 -1
- package/dist/core/PlanEntitlementService.js +144 -19
- package/dist/core/PlanVersionPolicy.test.js +19 -26
- package/dist/core/SignupMonetizationSettings.test.js +75 -15
- package/dist/core/SystemAdmin.test.js +212 -56
- package/dist/core/TaskActivityService.d.ts +4 -1
- package/dist/core/TaskActivityService.js +45 -0
- package/dist/core/TaskAuditTimeline.test.js +52 -1
- package/dist/core/TaskTaxonomyValidation.test.js +53 -0
- package/dist/core/Taskforce.d.ts +18 -11
- package/dist/core/Taskforce.js +93 -12
- package/dist/core/WorkspacePlanMode.test.js +73 -8
- package/dist/core/shared.d.ts +2 -0
- package/dist/core/shared.js +11 -0
- package/dist/documentReviews/service.d.ts +1 -1
- package/dist/documentReviews/types.d.ts +1 -1
- package/dist/hooks/auth/useTaskforceAuthBootstrap.d.ts +48 -0
- package/dist/hooks/auth/useTaskforceAuthBootstrap.js +219 -0
- package/dist/hooks/sync/auth.d.ts +37 -0
- package/dist/hooks/sync/auth.js +63 -0
- package/dist/hooks/sync/bootstrap.d.ts +34 -0
- package/dist/hooks/sync/bootstrap.js +52 -0
- package/dist/hooks/sync/orchestratorShared.d.ts +36 -0
- package/dist/hooks/sync/orchestratorShared.js +209 -0
- package/dist/hooks/sync/orchestratorShared.test.d.ts +1 -0
- package/dist/hooks/sync/orchestratorShared.test.js +49 -0
- package/dist/hooks/sync/realtime.d.ts +25 -0
- package/dist/hooks/sync/realtime.js +60 -0
- package/dist/hooks/sync/recovery.d.ts +69 -0
- package/dist/hooks/sync/recovery.js +118 -0
- package/dist/hooks/sync/transfers.d.ts +123 -0
- package/dist/hooks/sync/transfers.js +435 -0
- package/dist/hooks/sync/useDataVersionRefresh.d.ts +16 -0
- package/dist/hooks/sync/useDataVersionRefresh.js +73 -0
- package/dist/hooks/ui/useResourceExport.d.ts +3 -1
- package/dist/hooks/ui/useResourceExport.js +5 -2
- package/dist/hooks/ui/useWorkflowTemplates.js +11 -6
- package/dist/hooks/useRealtimeSync.js +2 -1
- package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +8 -0
- package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +1 -0
- package/dist/hooks/useSyncOrchestrator.context-assets.test.js +228 -0
- package/dist/hooks/useSyncOrchestrator.d.ts +12 -8
- package/dist/hooks/useSyncOrchestrator.js +388 -879
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +641 -6
- package/dist/hooks/useTaskMutations.d.ts +2 -1
- package/dist/hooks/useTaskMutations.js +57 -7
- package/dist/hooks/useTaskMutations.test.js +176 -0
- package/dist/hooks/useTaskforce.d.ts +13 -4
- package/dist/hooks/useTaskforce.js +302 -817
- package/dist/hooks/useTaskforce.sync-behavior.test.js +78 -0
- package/dist/hooks/useWorkspaceSyncController.d.ts +4 -1
- package/dist/hooks/useWorkspaceSyncController.js +33 -11
- package/dist/hooks/useWorkspaceSyncController.test.js +1 -0
- package/dist/hooks/workspace/useTaskforceApiRouting.test.js +17 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +166 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +434 -0
- package/dist/hooks/workspace/workspaceLocalState.d.ts +19 -0
- package/dist/hooks/workspace/workspaceLocalState.js +100 -0
- package/dist/hooks/workspace/workspaceLocalState.test.d.ts +1 -0
- package/dist/hooks/workspace/workspaceLocalState.test.js +59 -0
- package/dist/localization/locales/en-US.js +2 -2
- package/dist/mcp/adminWorkspaceRegistrar.d.ts +2 -0
- package/dist/mcp/adminWorkspaceRegistrar.js +154 -0
- package/dist/mcp/canonicalAssetHelpers.d.ts +70 -0
- package/dist/mcp/canonicalAssetHelpers.js +259 -0
- package/dist/mcp/clientIntegrations.d.ts +12 -1
- package/dist/mcp/clientIntegrations.js +161 -6
- package/dist/mcp/clientIntegrations.test.js +76 -4
- package/dist/mcp/collaborationRegistrar.d.ts +2 -0
- package/dist/mcp/collaborationRegistrar.js +71 -0
- package/dist/mcp/documentAssetRegistrar.d.ts +2 -0
- package/dist/mcp/documentAssetRegistrar.js +346 -0
- package/dist/mcp/documentHelpers.d.ts +100 -0
- package/dist/mcp/documentHelpers.js +161 -0
- package/dist/mcp/runtime.js +2455 -3925
- package/dist/mcp/runtime.test.js +240 -8
- package/dist/mcp/taskAttachmentHelpers.d.ts +41 -0
- package/dist/mcp/taskAttachmentHelpers.js +60 -0
- package/dist/mcp/taskAttachmentTypes.d.ts +37 -0
- package/dist/mcp/taskAttachmentTypes.js +1 -0
- package/dist/mcp/taskPlanningRegistrar.d.ts +2 -0
- package/dist/mcp/taskPlanningRegistrar.js +418 -0
- package/dist/mcp/toolCatalog.d.ts +35 -0
- package/dist/mcp/toolCatalog.js +3 -0
- package/dist/plugins/vite.js +1 -1
- package/dist/runtime/appGateDefinitions.d.ts +1 -1
- package/dist/runtime/appGateDefinitions.js +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +46 -3
- package/dist/server/index.test.js +12 -1
- package/dist/server/routes/admin.d.ts +1 -5
- package/dist/server/routes/admin.js +151 -80
- package/dist/server/routes/annotatedAttachments.d.ts +1 -1
- package/dist/server/routes/annotatedAttachments.js +1 -1
- package/dist/server/routes/auth.d.ts +1 -4
- package/dist/server/routes/auth.js +59 -64
- package/dist/server/routes/authSupport.d.ts +30 -0
- package/dist/server/routes/authSupport.js +81 -0
- package/dist/server/routes/billing.d.ts +1 -1
- package/dist/server/routes/billing.js +122 -255
- package/dist/server/routes/billing.test.js +257 -86
- package/dist/server/routes/documentReviews.d.ts +1 -1
- package/dist/server/routes/documentReviews.js +1 -1
- package/dist/server/routes/documents.d.ts +4 -2
- package/dist/server/routes/documents.js +73 -7
- package/dist/server/routes/primitives.d.ts +11 -0
- package/dist/server/routes/primitives.js +73 -0
- package/dist/server/routes/resources.d.ts +1 -1
- package/dist/server/routes/resources.js +1 -1
- package/dist/server/routes/shared.d.ts +31 -1
- package/dist/server/routes/shared.js +26 -0
- package/dist/server/routes/sync.d.ts +1 -1
- package/dist/server/routes/sync.integration.test.js +20 -0
- package/dist/server/routes/sync.js +111 -238
- package/dist/server/routes/tasks.d.ts +1 -1
- package/dist/server/routes/tasks.js +50 -8
- package/dist/server/routes/workspaces.d.ts +1 -1
- package/dist/server/routes/workspaces.js +1 -1
- package/dist/server/routes.d.ts +2 -9
- package/dist/server/routes.js +84 -181
- package/dist/server/routes.test.js +912 -32
- package/dist/shared/taskActor.d.ts +10 -0
- package/dist/shared/taskActor.js +1 -0
- package/dist/sync/collaborationSyncPayload.js +4 -0
- package/dist/sync/collaborationSyncPayload.test.js +8 -0
- package/dist/sync/collaborationSyncState.d.ts +3 -1
- package/dist/sync/collaborationSyncState.js +19 -0
- package/dist/sync/contentSyncState.js +28 -17
- package/dist/sync/syncApplyHandlers.d.ts +87 -1
- package/dist/sync/syncApplyHandlers.js +370 -8
- package/dist/sync/syncApplyHandlers.test.d.ts +1 -0
- package/dist/sync/syncApplyHandlers.test.js +379 -0
- package/dist/sync/syncService.d.ts +9 -1
- package/dist/sync/syncService.js +44 -2
- package/dist/sync/syncService.test.js +62 -0
- package/dist/sync/workspacePullFeed.d.ts +1 -0
- package/dist/sync/workspacePullFeed.js +66 -59
- package/dist/sync/workspacePullFeed.test.js +72 -4
- package/dist/sync/workspaceSyncModel.d.ts +25 -1
- package/dist/sync/workspaceSyncModel.js +77 -3
- package/dist/sync/workspaceSyncModel.test.js +153 -0
- package/dist/sync/workspaceSyncState.d.ts +2 -0
- package/dist/sync/workspaceSyncState.js +1 -0
- package/dist/types.d.ts +2 -10
- package/dist/ui/assets/AgentsModule-N2MnQBZk.js +1 -0
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-C_TmXZwA.js → AnnotatedAttachmentWorkspace-BG6P_GVW.js} +1 -1
- package/dist/ui/assets/DocumentWorkspace-B3nV_Gc5.css +1 -0
- package/dist/ui/assets/DocumentWorkspace-DRbDMwh8.js +252 -0
- package/dist/ui/assets/{InitiativesModule-4-Rh2K2n.js → InitiativesModule-Dhm7M8e7.js} +1 -1
- package/dist/ui/assets/{PlansPage-BlVC_lRq.css → PlansPage-C2dd2n1X.css} +1 -1
- package/dist/ui/assets/PlansPage-Cq0zXj3I.js +1 -0
- package/dist/ui/assets/TaskSettings-ln0aF7xc.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-CGk9s3NJ.js → WorkflowsModule-BcS4afRn.js} +1 -1
- package/dist/ui/assets/index-C2-OXZV7.css +1 -0
- package/dist/ui/assets/index-DcSI5F86.js +6 -0
- package/dist/ui/assets/{vendor-icons-pWocEipT.js → vendor-icons-BSUaRwF8.js} +1 -1
- package/dist/ui/index.html +4 -4
- package/dist/utils/commercialLifecyclePresentation.d.ts +19 -0
- package/dist/utils/commercialLifecyclePresentation.js +196 -0
- package/dist/utils/taskActivity.js +4 -0
- package/dist/utils/taskActivity.test.js +25 -1
- package/package.json +10 -1
- package/scripts/check-cloud-mcp.mjs +83 -0
- package/scripts/playwright-auth.sh +9 -1
- package/scripts/run-billing-performance-smoke.sh +24 -0
- package/dist/ui/assets/AgentsModule-CpsTmrIW.js +0 -1
- package/dist/ui/assets/DocumentWorkspace-C3GyzrWm.js +0 -250
- package/dist/ui/assets/DocumentWorkspace-C_8T8oz-.css +0 -1
- package/dist/ui/assets/PlansPage-BP7AYOqr.js +0 -1
- package/dist/ui/assets/TaskSettings-BOC5F6Ag.js +0 -8
- package/dist/ui/assets/index-CLIMgR6g.js +0 -6
- 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 {};
|