@taskforcehq/taskforce 0.3.305 → 0.3.306
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/dist/Taskforce.module.css +295 -89
- package/dist/TaskforceCore.js +215 -43
- package/dist/TaskforceCore.test.js +433 -28
- package/dist/assets/fonts/CourierPrime-Bold.woff2 +0 -0
- package/dist/assets/fonts/CourierPrime-BoldItalic.woff2 +0 -0
- package/dist/assets/fonts/CourierPrime-Italic.woff2 +0 -0
- package/dist/assets/fonts/CourierPrime-Regular.woff2 +0 -0
- package/dist/assets/fonts/Noir_medium.woff2 +0 -0
- package/dist/assets/fonts/Noir_regular.woff2 +0 -0
- package/dist/assets/fonts/RussoOne-Regular.woff2 +0 -0
- package/dist/assets/fonts/SpecialElite-Regular.woff2 +0 -0
- package/dist/compat/workspaceSyncCompat.js +46 -2
- package/dist/compat/workspaceSyncCompat.test.js +38 -1
- package/dist/components/context/ContextAttachmentManager.d.ts +14 -0
- package/dist/components/context/ContextAttachmentManager.js +549 -0
- package/dist/components/features/AgentsModule.d.ts +10 -1
- package/dist/components/features/AgentsModule.js +260 -15
- package/dist/components/features/AgentsModule.test.js +255 -5
- package/dist/components/features/DocumentViewer.d.ts +5 -1
- package/dist/components/features/DocumentViewer.js +60 -3
- package/dist/components/features/DocumentWorkspace.js +1 -1
- package/dist/components/features/DocumentWorkspace.test.js +33 -0
- package/dist/components/features/documentWorkspaceModel.d.ts +1 -1
- package/dist/components/task/TaskCard.js +19 -28
- package/dist/components/task/TaskCard.test.js +38 -3
- package/dist/components/task/TaskContextUpload.js +4 -526
- package/dist/components/task/TaskForm.js +85 -90
- package/dist/components/task/TaskForm.test.js +110 -8
- package/dist/components/task/TaskKanban.d.ts +2 -1
- package/dist/components/task/TaskKanban.js +94 -13
- package/dist/components/task/TaskKanban.test.js +36 -0
- package/dist/components/ui/EditableAvatarButton.d.ts +18 -0
- package/dist/components/ui/EditableAvatarButton.js +18 -0
- package/dist/components/views/PlanComparisonPage.d.ts +9 -1
- package/dist/components/views/PlanComparisonPage.js +70 -14
- package/dist/components/views/PlanComparisonPage.test.js +238 -0
- package/dist/components/views/PlansPage.js +127 -36
- package/dist/components/views/PlansPage.test.js +294 -12
- package/dist/components/views/StandaloneLayout.js +283 -533
- package/dist/components/views/panels/PlanningDrawer.d.ts +8 -1
- package/dist/components/views/panels/PlanningDrawer.js +18 -11
- package/dist/components/views/panels/PlanningDrawer.test.d.ts +1 -0
- package/dist/components/views/panels/PlanningDrawer.test.js +141 -0
- package/dist/components/views/planningScope.d.ts +20 -0
- package/dist/components/views/planningScope.js +25 -0
- package/dist/components/views/planningScope.test.d.ts +1 -0
- package/dist/components/views/planningScope.test.js +60 -0
- package/dist/components/views/standalone/modals/AccountHubModal.d.ts +26 -1
- package/dist/components/views/standalone/modals/AccountHubModal.js +76 -2
- package/dist/components/views/standalone/modals/AccountHubModal.test.js +20 -3
- package/dist/components/views/standalone/modals/AvatarImageManagerModal.d.ts +20 -0
- package/dist/components/views/standalone/modals/AvatarImageManagerModal.js +127 -0
- package/dist/components/views/standalone/modals/EditProfileModal.d.ts +2 -9
- package/dist/components/views/standalone/modals/EditProfileModal.js +3 -2
- package/dist/components/views/standalone/modals/SyncEnableWarningModal.d.ts +9 -0
- package/dist/components/views/standalone/modals/SyncEnableWarningModal.js +6 -0
- package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.d.ts +1 -0
- package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.js +24 -0
- package/dist/components/views/standalone/modals/SyncStatusModal.d.ts +2 -1
- package/dist/components/views/standalone/modals/SyncStatusModal.js +2 -2
- package/dist/core/AiProfileService.d.ts +39 -2
- package/dist/core/AiProfileService.js +639 -73
- package/dist/core/AiProfileService.test.js +19 -1
- package/dist/core/AiProfiles.test.js +566 -7
- package/dist/core/AttachmentLinkService.js +57 -15
- package/dist/core/AuthIdentityService.d.ts +107 -0
- package/dist/core/AuthIdentityService.js +284 -0
- package/dist/core/AuthTokenService.d.ts +4 -0
- package/dist/core/AuthTokenService.js +34 -6
- package/dist/core/AuthTokenService.test.js +3 -3
- package/dist/core/EntitlementsPolicy.test.js +2 -2
- package/dist/core/GlobalSettingsService.js +115 -6
- package/dist/core/PlanEntitlementService.d.ts +23 -2
- package/dist/core/PlanEntitlementService.js +343 -57
- package/dist/core/PlanFeatureCatalog.test.d.ts +1 -0
- package/dist/core/PlanFeatureCatalog.test.js +84 -0
- package/dist/core/PlanVersionPolicy.test.js +35 -2
- package/dist/core/PlanningEntities.test.js +52 -0
- package/dist/core/SignupMonetizationSettings.test.js +199 -27
- package/dist/core/SyncReconciliationService.js +2 -65
- package/dist/core/SystemAdmin.test.js +42 -42
- package/dist/core/TaskAttachmentsCanonical.test.js +51 -1
- package/dist/core/Taskforce.d.ts +225 -14
- package/dist/core/Taskforce.js +765 -192
- package/dist/core/Taskforce.listTasksSlim.test.d.ts +1 -0
- package/dist/core/Taskforce.listTasksSlim.test.js +91 -0
- package/dist/core/UserProfileAvatarDrafts.test.js +30 -3
- package/dist/core/WorkspaceLifecycleService.js +0 -5
- package/dist/core/WorkspacePermissions.test.js +25 -1
- package/dist/core/WorkspacePlanMode.test.js +117 -16
- package/dist/core/shared.d.ts +1 -1
- package/dist/core/shared.js +5 -1
- package/dist/core/types.d.ts +68 -2
- package/dist/hooks/auth/useTaskforceAuthBootstrap.js +26 -1
- package/dist/hooks/sync/bootstrap.js +2 -0
- package/dist/hooks/sync/controlPlane.d.ts +37 -0
- package/dist/hooks/sync/controlPlane.js +78 -0
- package/dist/hooks/sync/controlPlane.test.d.ts +1 -0
- package/dist/hooks/sync/controlPlane.test.js +121 -0
- package/dist/hooks/sync/lifecyclePolicy.d.ts +45 -0
- package/dist/hooks/sync/lifecyclePolicy.js +93 -0
- package/dist/hooks/sync/lifecyclePolicy.test.d.ts +1 -0
- package/dist/hooks/sync/lifecyclePolicy.test.js +124 -0
- package/dist/hooks/sync/orchestratorShared.d.ts +10 -1
- package/dist/hooks/sync/orchestratorShared.js +68 -47
- package/dist/hooks/sync/orchestratorShared.test.js +50 -1
- package/dist/hooks/sync/pullLifecycle.d.ts +31 -0
- package/dist/hooks/sync/pullLifecycle.js +24 -0
- package/dist/hooks/sync/pullLifecycle.test.d.ts +1 -0
- package/dist/hooks/sync/pullLifecycle.test.js +80 -0
- package/dist/hooks/sync/recovery.d.ts +16 -2
- package/dist/hooks/sync/recovery.js +171 -53
- package/dist/hooks/sync/recovery.test.d.ts +1 -0
- package/dist/hooks/sync/recovery.test.js +292 -0
- package/dist/hooks/sync/transfers.d.ts +15 -3
- package/dist/hooks/sync/transfers.js +179 -62
- package/dist/hooks/sync/transfers.test.d.ts +1 -0
- package/dist/hooks/sync/transfers.test.js +396 -0
- package/dist/hooks/sync/useRecentSyncEvents.d.ts +21 -0
- package/dist/hooks/sync/useRecentSyncEvents.js +92 -0
- package/dist/hooks/sync/useRecentSyncEvents.test.d.ts +1 -0
- package/dist/hooks/sync/useRecentSyncEvents.test.js +124 -0
- package/dist/hooks/sync/useSyncStatusActions.d.ts +30 -0
- package/dist/hooks/sync/useSyncStatusActions.js +88 -0
- package/dist/hooks/sync/useSyncStatusActions.test.d.ts +1 -0
- package/dist/hooks/sync/useSyncStatusActions.test.js +133 -0
- package/dist/hooks/sync/useSyncStatusControls.d.ts +25 -0
- package/dist/hooks/sync/useSyncStatusControls.js +60 -0
- package/dist/hooks/sync/useSyncStatusControls.test.d.ts +1 -0
- package/dist/hooks/sync/useSyncStatusControls.test.js +88 -0
- package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +12 -0
- package/dist/hooks/useSyncOrchestrator.d.ts +9 -2
- package/dist/hooks/useSyncOrchestrator.js +359 -178
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +407 -0
- package/dist/hooks/useTaskData.js +8 -3
- package/dist/hooks/useTaskData.test.js +45 -0
- package/dist/hooks/useTaskforce.d.ts +60 -2
- package/dist/hooks/useTaskforce.js +549 -240
- package/dist/hooks/useTaskforce.runtime-routing.test.d.ts +1 -0
- package/dist/hooks/useTaskforce.runtime-routing.test.js +152 -0
- package/dist/hooks/useTaskforce.sync-behavior.test.js +2380 -212
- package/dist/hooks/useWorkspaceSyncController.d.ts +1 -0
- package/dist/hooks/useWorkspaceSyncController.js +6 -1
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +16 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +316 -35
- package/dist/localization/locales/en-US.d.ts +0 -1
- package/dist/localization/locales/en-US.js +0 -1
- package/dist/localization/locales/es-419.js +0 -1
- package/dist/localization/locales/pt-BR.js +0 -1
- package/dist/mcp/documentAssetRegistrar.js +3 -3
- package/dist/mcp/runtime.d.ts +2 -0
- package/dist/mcp/runtime.js +131 -64
- package/dist/mcp/runtime.test.js +291 -12
- package/dist/migrations/taskSchemaMigrations.d.ts +1 -1
- package/dist/migrations/taskSchemaMigrations.js +171 -61
- package/dist/migrations/taskSchemaMigrations.test.js +15 -0
- package/dist/resources/templates/workflow-sources/workflowDocs.mjs +6 -6
- package/dist/resources/templates/workflows/collaborate.yaml +1 -1
- package/dist/resources/templates/workflows/evaluate.yaml +2 -2
- package/dist/resources/templates/workflows/plan.yaml +1 -1
- package/dist/resources/templates/workflows/review.yaml +2 -2
- package/dist/server/annotatedAttachmentsRoutes.test.js +3 -3
- package/dist/server/auth/providers/apple.d.ts +33 -0
- package/dist/server/auth/providers/apple.js +82 -0
- package/dist/server/auth/providers/appleClientSecret.d.ts +37 -0
- package/dist/server/auth/providers/appleClientSecret.js +65 -0
- package/dist/server/auth/providers/github.d.ts +26 -0
- package/dist/server/auth/providers/github.js +86 -0
- package/dist/server/auth/providers/google.d.ts +21 -0
- package/dist/server/auth/providers/google.js +56 -0
- package/dist/server/auth/providers/types.d.ts +21 -0
- package/dist/server/auth/providers/types.js +1 -0
- package/dist/server/auth.d.ts +2 -0
- package/dist/server/auth.js +6 -3
- package/dist/server/documentReviewRoutes.test.js +36 -3
- package/dist/server/index.cookieProxy.test.js +1 -0
- package/dist/server/index.d.ts +12 -0
- package/dist/server/index.js +120 -9
- package/dist/server/index.rateLimit.test.js +3 -0
- package/dist/server/index.test.js +106 -3
- package/dist/server/routes/admin.d.ts +3 -0
- package/dist/server/routes/admin.js +272 -19
- package/dist/server/routes/auth.d.ts +15 -2
- package/dist/server/routes/auth.js +994 -57
- package/dist/server/routes/authSupport.d.ts +1 -1
- package/dist/server/routes/authSupport.js +1 -1
- package/dist/server/routes/billing.js +361 -35
- package/dist/server/routes/billing.test.js +592 -12
- package/dist/server/routes/documents.d.ts +1 -0
- package/dist/server/routes/documents.js +172 -70
- package/dist/server/routes/shared.d.ts +28 -2
- package/dist/server/routes/shared.js +69 -4
- package/dist/server/routes/sync.integration.test.js +437 -39
- package/dist/server/routes/sync.js +61 -605
- package/dist/server/routes/syncAuxRoutes.d.ts +15 -0
- package/dist/server/routes/syncAuxRoutes.js +91 -0
- package/dist/server/routes/syncAuxRoutes.test.d.ts +1 -0
- package/dist/server/routes/syncAuxRoutes.test.js +158 -0
- package/dist/server/routes/syncPullApplyRoutes.d.ts +36 -0
- package/dist/server/routes/syncPullApplyRoutes.js +204 -0
- package/dist/server/routes/syncPushRoutes.d.ts +24 -0
- package/dist/server/routes/syncPushRoutes.js +212 -0
- package/dist/server/routes/syncRouteGuards.d.ts +36 -0
- package/dist/server/routes/syncRouteGuards.js +67 -0
- package/dist/server/routes/syncRouteGuards.test.d.ts +1 -0
- package/dist/server/routes/syncRouteGuards.test.js +94 -0
- package/dist/server/routes/syncRouteTypes.d.ts +13 -0
- package/dist/server/routes/syncRouteTypes.js +1 -0
- package/dist/server/routes/syncSnapshotRoutes.d.ts +66 -0
- package/dist/server/routes/syncSnapshotRoutes.js +180 -0
- package/dist/server/routes/tasks.js +43 -1
- package/dist/server/routes/workspaces.js +36 -5
- package/dist/server/routes.js +60 -3
- package/dist/server/routes.test.js +1824 -171
- package/dist/services/workflowExportSnapshots.test.js +2 -0
- package/dist/shared/aiProfileSeatScope.d.ts +5 -0
- package/dist/shared/aiProfileSeatScope.js +21 -0
- package/dist/shared/passwordPolicy.d.ts +13 -0
- package/dist/shared/passwordPolicy.js +84 -0
- package/dist/shared/passwordPolicy.test.d.ts +1 -0
- package/dist/shared/passwordPolicy.test.js +34 -0
- package/dist/storage/workspaceAssetStore.d.ts +15 -3
- package/dist/storage/workspaceAssetStore.js +132 -44
- package/dist/storage/workspaceAssetStore.test.js +93 -6
- package/dist/styles/fonts.css +70 -0
- package/dist/sync/collaborationSyncPayload.d.ts +6 -1
- package/dist/sync/collaborationSyncPayload.js +17 -11
- package/dist/sync/collaborationSyncPayload.test.js +13 -1
- package/dist/sync/contentSyncState.d.ts +4 -8
- package/dist/sync/contentSyncState.js +17 -70
- package/dist/sync/syncApplyHandlers.d.ts +7 -0
- package/dist/sync/syncApplyHandlers.js +28 -13
- package/dist/sync/syncApplyHandlers.test.js +70 -3
- package/dist/sync/syncFieldSemantics.d.ts +12 -0
- package/dist/sync/syncFieldSemantics.js +55 -0
- package/dist/sync/syncResourceAdapters.d.ts +44 -0
- package/dist/sync/syncResourceAdapters.js +77 -0
- package/dist/sync/syncService.d.ts +5 -0
- package/dist/sync/syncService.js +52 -0
- package/dist/sync/syncService.test.js +57 -3
- package/dist/sync/taskSyncChangeKey.d.ts +2 -0
- package/dist/sync/taskSyncChangeKey.js +143 -0
- package/dist/sync/taskSyncPayload.js +3 -8
- package/dist/sync/workspacePullFeed.js +4 -3
- package/dist/sync/workspacePullFeed.test.js +43 -0
- package/dist/sync/workspaceRepair.js +5 -2
- package/dist/sync/workspaceSyncModel.d.ts +45 -0
- package/dist/sync/workspaceSyncModel.js +183 -28
- package/dist/sync/workspaceSyncModel.test.js +291 -12
- package/dist/sync/workspaceSyncState.d.ts +17 -0
- package/dist/sync/workspaceSyncState.js +2 -1
- package/dist/sync/workspaceSyncSurface.js +3 -8
- package/dist/types.d.ts +17 -0
- package/dist/ui/assets/AgentsModule-DrLawwNl.js +1 -0
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-BG6P_GVW.js → AnnotatedAttachmentWorkspace-Z9_whf7F.js} +1 -1
- package/dist/ui/assets/CourierPrime-Bold-BuLj_dpw.woff2 +0 -0
- package/dist/ui/assets/CourierPrime-BoldItalic-BrK2pUkZ.woff2 +0 -0
- package/dist/ui/assets/CourierPrime-Italic-DFUZK5Ey.woff2 +0 -0
- package/dist/ui/assets/CourierPrime-Regular-BsKphzVf.woff2 +0 -0
- package/dist/ui/assets/DocumentWorkspace-B03MaSkw.js +252 -0
- package/dist/ui/assets/DocumentWorkspace-Dhdso07p.css +1 -0
- package/dist/ui/assets/{InitiativesModule-Dhm7M8e7.js → InitiativesModule-9E6ParrY.js} +1 -1
- package/dist/ui/assets/Noir_medium-bxQwKGzB.woff2 +0 -0
- package/dist/ui/assets/Noir_regular-ojf2kxlG.woff2 +0 -0
- package/dist/ui/assets/PlansPage-CvfnMiWq.css +1 -0
- package/dist/ui/assets/PlansPage-DRXscYxH.js +1 -0
- package/dist/ui/assets/RussoOne-Regular-Cu_qq_qC.woff2 +0 -0
- package/dist/ui/assets/SpecialElite-Regular-Di6gSvAS.woff2 +0 -0
- package/dist/ui/assets/{TaskSettings-ln0aF7xc.js → TaskSettings-DZX4jk7e.js} +1 -1
- package/dist/ui/assets/{WorkflowsModule-BcS4afRn.js → WorkflowsModule-BA7jcEpH.js} +1 -1
- package/dist/ui/assets/index-9eT8e0Lu.css +1 -0
- package/dist/ui/assets/index-C6k75jr8.js +6 -0
- package/dist/ui/assets/{vendor-icons-BSUaRwF8.js → vendor-icons-DuEd_65c.js} +1 -1
- package/dist/ui/fonts/CourierPrime-Bold.ttf +0 -0
- package/dist/ui/fonts/CourierPrime-Bold.woff2 +0 -0
- package/dist/ui/fonts/CourierPrime-BoldItalic.ttf +0 -0
- package/dist/ui/fonts/CourierPrime-BoldItalic.woff2 +0 -0
- package/dist/ui/fonts/CourierPrime-Italic.ttf +0 -0
- package/dist/ui/fonts/CourierPrime-Italic.woff2 +0 -0
- package/dist/ui/fonts/CourierPrime-Regular.ttf +0 -0
- package/dist/ui/fonts/CourierPrime-Regular.woff2 +0 -0
- package/dist/ui/fonts/Noir_medium.otf +0 -0
- package/dist/ui/fonts/Noir_medium.woff2 +0 -0
- package/dist/ui/fonts/Noir_regular.otf +0 -0
- package/dist/ui/fonts/Noir_regular.woff2 +0 -0
- package/dist/ui/fonts/RussoOne-Regular.woff2 +0 -0
- package/dist/ui/fonts/SpecialElite-Regular.ttf +0 -0
- package/dist/ui/fonts/SpecialElite-Regular.woff2 +0 -0
- package/dist/ui/index.html +3 -3
- package/dist/utils/accountProfileSummaryCache.d.ts +5 -0
- package/dist/utils/assignees.d.ts +2 -0
- package/dist/utils/assignees.js +2 -0
- package/dist/utils/avatarUpload.d.ts +27 -0
- package/dist/utils/avatarUpload.js +132 -0
- package/dist/utils/avatarUpload.test.d.ts +1 -0
- package/dist/utils/avatarUpload.test.js +40 -0
- package/dist/utils/bootstrapTrace.d.ts +7 -0
- package/dist/utils/bootstrapTrace.js +29 -0
- package/dist/utils/commercialLifecyclePresentation.js +3 -0
- package/dist/utils/contextAssetEvents.d.ts +2 -0
- package/dist/utils/syncEventDetails.d.ts +3 -0
- package/dist/utils/syncEventDetails.js +64 -0
- package/dist/utils/syncEventDetails.test.d.ts +1 -0
- package/dist/utils/syncEventDetails.test.js +23 -0
- package/dist/utils/syncEventPresentation.d.ts +15 -0
- package/dist/utils/syncEventPresentation.js +129 -0
- package/dist/utils/syncEventPresentation.test.d.ts +1 -0
- package/dist/utils/syncEventPresentation.test.js +64 -0
- package/dist/utils/syncStatusPresentation.d.ts +72 -0
- package/dist/utils/syncStatusPresentation.js +217 -0
- package/dist/utils/syncStatusPresentation.test.d.ts +1 -0
- package/dist/utils/syncStatusPresentation.test.js +164 -0
- package/dist/utils/taskActivity.d.ts +2 -0
- package/dist/utils/taskActivity.js +228 -22
- package/dist/utils/taskActivity.test.js +119 -8
- package/dist/utils/workspaceSyncPresentation.d.ts +4 -0
- package/dist/utils/workspaceSyncPresentation.js +68 -2
- package/dist/utils/workspaceSyncPresentation.test.js +36 -1
- package/package.json +3 -2
- package/scripts/export-sqlite-to-postgres.mjs +7 -9
- package/scripts/run-e2e-realtime.sh +17 -3
- package/scripts/run-smoke.sh +17 -5
- package/scripts/run-soak.sh +17 -5
- package/src/resources/templates/workflow-sources/workflowDocs.mjs +6 -6
- package/src/resources/templates/workflows/collaborate.yaml +1 -1
- package/src/resources/templates/workflows/evaluate.yaml +2 -2
- package/src/resources/templates/workflows/plan.yaml +1 -1
- package/src/resources/templates/workflows/review.yaml +2 -2
- package/dist/ui/assets/AgentsModule-N2MnQBZk.js +0 -1
- package/dist/ui/assets/DocumentWorkspace-B3nV_Gc5.css +0 -1
- package/dist/ui/assets/DocumentWorkspace-DRbDMwh8.js +0 -252
- package/dist/ui/assets/PlansPage-C2dd2n1X.css +0 -1
- package/dist/ui/assets/PlansPage-Cq0zXj3I.js +0 -1
- package/dist/ui/assets/RussoOne-Regular-C3BxZIj7.ttf +0 -0
- package/dist/ui/assets/index-C2-OXZV7.css +0 -1
- package/dist/ui/assets/index-DcSI5F86.js +0 -6
- package/scripts/migrate-planning-model.ts +0 -233
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { useCallback, useEffect } from 'react';
|
|
2
2
|
import { createDefaultAssigneeOptions } from '../../utils/assignees';
|
|
3
|
+
import { logBootstrapDebug } from '../workspace/workspaceLocalState';
|
|
3
4
|
import { persistLocalMutationActorUserId, readLocalMutationActorUserId } from '../../utils/taskforceApiClient';
|
|
5
|
+
function getBootstrapTimestamp() {
|
|
6
|
+
return typeof performance !== 'undefined' ? performance.now() : Date.now();
|
|
7
|
+
}
|
|
4
8
|
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
9
|
const checkAuthSession = useCallback(async (options) => {
|
|
6
10
|
const force = options?.force === true;
|
|
@@ -41,11 +45,20 @@ export function useTaskforceAuthBootstrap({ runtimeConfigReady, shouldProbeCloud
|
|
|
41
45
|
const requestEpoch = ++authSessionEpochRef.current;
|
|
42
46
|
let request = null;
|
|
43
47
|
request = (async () => {
|
|
48
|
+
const startedAt = getBootstrapTimestamp();
|
|
44
49
|
const abortController = new AbortController();
|
|
45
50
|
const timeoutId = typeof window !== 'undefined'
|
|
46
51
|
? window.setTimeout(() => abortController.abort(), 8000)
|
|
47
52
|
: null;
|
|
48
53
|
const isStaleRequest = () => authSessionEpochRef.current !== requestEpoch;
|
|
54
|
+
const logResult = (event, details) => {
|
|
55
|
+
logBootstrapDebug(event, {
|
|
56
|
+
durationMs: Math.max(0, Math.round(getBootstrapTimestamp() - startedAt)),
|
|
57
|
+
force,
|
|
58
|
+
sessionUrl,
|
|
59
|
+
...details
|
|
60
|
+
});
|
|
61
|
+
};
|
|
49
62
|
try {
|
|
50
63
|
const res = await fetch(sessionUrl, {
|
|
51
64
|
method: 'GET',
|
|
@@ -59,6 +72,7 @@ export function useTaskforceAuthBootstrap({ runtimeConfigReady, shouldProbeCloud
|
|
|
59
72
|
if (res.status === 429) {
|
|
60
73
|
setAuthSessionResolved(true);
|
|
61
74
|
setAuthStateReadyRefs(authSessionLastResultRef.current, true, false);
|
|
75
|
+
logResult('auth_session_rate_limited', { status: res.status });
|
|
62
76
|
return authSessionLastResultRef.current;
|
|
63
77
|
}
|
|
64
78
|
if (!res.ok) {
|
|
@@ -70,6 +84,7 @@ export function useTaskforceAuthBootstrap({ runtimeConfigReady, shouldProbeCloud
|
|
|
70
84
|
setAuthSessionResolved(true);
|
|
71
85
|
setAuthStateReadyRefs(false, true, false);
|
|
72
86
|
authSessionLastResultRef.current = false;
|
|
87
|
+
logResult('auth_session_request_failed', { status: res.status });
|
|
73
88
|
return false;
|
|
74
89
|
}
|
|
75
90
|
const data = await res.json();
|
|
@@ -117,9 +132,16 @@ export function useTaskforceAuthBootstrap({ runtimeConfigReady, shouldProbeCloud
|
|
|
117
132
|
authSessionLastResultRef.current = authenticated;
|
|
118
133
|
if (authenticated)
|
|
119
134
|
markBootstrapPhase('ready');
|
|
135
|
+
logResult('auth_session_resolved', {
|
|
136
|
+
status: res.status,
|
|
137
|
+
authenticated,
|
|
138
|
+
workspaceId: authenticated ? workspaceId : '',
|
|
139
|
+
userId: authenticated ? userId : 'anonymous',
|
|
140
|
+
authRequiredForApi: nextAuthRequiredForApi
|
|
141
|
+
});
|
|
120
142
|
return authenticated;
|
|
121
143
|
}
|
|
122
|
-
catch {
|
|
144
|
+
catch (error) {
|
|
123
145
|
if (isStaleRequest()) {
|
|
124
146
|
return authSessionLastResultRef.current;
|
|
125
147
|
}
|
|
@@ -129,6 +151,9 @@ export function useTaskforceAuthBootstrap({ runtimeConfigReady, shouldProbeCloud
|
|
|
129
151
|
setAuthStateReadyRefs(false, true, false);
|
|
130
152
|
authSessionLastResultRef.current = false;
|
|
131
153
|
markBootstrapStalled('Unable to verify your session.');
|
|
154
|
+
logResult('auth_session_exception', {
|
|
155
|
+
error: error instanceof Error ? error.message : String(error)
|
|
156
|
+
});
|
|
132
157
|
return authSessionLastResultRef.current;
|
|
133
158
|
}
|
|
134
159
|
finally {
|
|
@@ -45,6 +45,8 @@ export function resolveRepairPhase(args) {
|
|
|
45
45
|
return 'provision-local';
|
|
46
46
|
if (workspaceSyncPhase === 'attach-cloud')
|
|
47
47
|
return 'attach-cloud';
|
|
48
|
+
if (workspaceSyncPhase === 'active' || lastBootstrapPhase === 'active')
|
|
49
|
+
return 'active';
|
|
48
50
|
if (lastBootstrapPhase === 'provision-local' || lastBootstrapPhase === 'attach-cloud') {
|
|
49
51
|
return lastBootstrapPhase;
|
|
50
52
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { WorkspaceSyncPhase } from '../../sync/workspaceSyncState';
|
|
2
|
+
export type WorkspaceSyncBlockedReason = 'cloud-auth-unconfigured' | 'runtime-not-local' | 'sync-disabled' | 'invalid-workspace' | 'repair-active' | 'attach-cloud' | 'provision-local' | 'auth-required' | 'push-in-flight' | 'pull-in-flight' | 'retry-pending' | 'lease-held';
|
|
3
|
+
export type WorkspaceSyncOperation = 'pull' | 'push' | 'repair';
|
|
4
|
+
export type WorkspaceSyncControlPlaneDecision = {
|
|
5
|
+
allowed: true;
|
|
6
|
+
} | {
|
|
7
|
+
allowed: false;
|
|
8
|
+
reason: WorkspaceSyncBlockedReason;
|
|
9
|
+
retainPendingSignature: boolean;
|
|
10
|
+
};
|
|
11
|
+
interface WorkspaceSyncControlPlaneBaseArgs {
|
|
12
|
+
cloudAuthConfigured: boolean;
|
|
13
|
+
runtimeMode: 'local' | 'cloud';
|
|
14
|
+
workspaceCloudSyncEnabled: boolean;
|
|
15
|
+
currentWorkspaceId: string;
|
|
16
|
+
isAuthenticated: boolean;
|
|
17
|
+
}
|
|
18
|
+
interface PushSyncReadinessArgs extends WorkspaceSyncControlPlaneBaseArgs {
|
|
19
|
+
workspaceSyncPhase: WorkspaceSyncPhase;
|
|
20
|
+
repairModeActive: boolean;
|
|
21
|
+
repairModeBypass: boolean;
|
|
22
|
+
pushInFlight: boolean;
|
|
23
|
+
pullInFlight: boolean;
|
|
24
|
+
}
|
|
25
|
+
interface PullSyncReadinessArgs extends WorkspaceSyncControlPlaneBaseArgs {
|
|
26
|
+
workspaceSyncPhase: WorkspaceSyncPhase;
|
|
27
|
+
repairModeActive: boolean;
|
|
28
|
+
repairModeBypass: boolean;
|
|
29
|
+
retryPending: boolean;
|
|
30
|
+
pushInFlight: boolean;
|
|
31
|
+
pullInFlight: boolean;
|
|
32
|
+
}
|
|
33
|
+
export declare function resolvePushSyncReadiness(args: PushSyncReadinessArgs): WorkspaceSyncControlPlaneDecision;
|
|
34
|
+
export declare function resolvePullSyncReadiness(args: PullSyncReadinessArgs): WorkspaceSyncControlPlaneDecision;
|
|
35
|
+
export declare function resolveRetrySyncReadiness(args: WorkspaceSyncControlPlaneBaseArgs): WorkspaceSyncControlPlaneDecision;
|
|
36
|
+
export declare function createWorkspaceSyncLeaseHeldDecision(operation: WorkspaceSyncOperation): WorkspaceSyncControlPlaneDecision;
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { isValidWorkspaceKey } from './orchestratorShared';
|
|
2
|
+
const ALLOW_SYNC_OPERATION = { allowed: true };
|
|
3
|
+
function blockSyncOperation(reason, options) {
|
|
4
|
+
return {
|
|
5
|
+
allowed: false,
|
|
6
|
+
reason,
|
|
7
|
+
retainPendingSignature: options?.retainPendingSignature === true
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
function resolveBaseSyncReadiness(args, options) {
|
|
11
|
+
if (!args.cloudAuthConfigured)
|
|
12
|
+
return blockSyncOperation('cloud-auth-unconfigured');
|
|
13
|
+
if (args.runtimeMode !== 'local')
|
|
14
|
+
return blockSyncOperation('runtime-not-local');
|
|
15
|
+
if (!args.workspaceCloudSyncEnabled)
|
|
16
|
+
return blockSyncOperation('sync-disabled');
|
|
17
|
+
if ((options?.requireValidWorkspace ?? true) && !isValidWorkspaceKey(args.currentWorkspaceId)) {
|
|
18
|
+
return blockSyncOperation('invalid-workspace');
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
export function resolvePushSyncReadiness(args) {
|
|
23
|
+
const baseReadiness = resolveBaseSyncReadiness(args);
|
|
24
|
+
if (baseReadiness)
|
|
25
|
+
return baseReadiness;
|
|
26
|
+
if (args.repairModeActive && !args.repairModeBypass) {
|
|
27
|
+
return blockSyncOperation('repair-active', { retainPendingSignature: true });
|
|
28
|
+
}
|
|
29
|
+
if (args.workspaceSyncPhase === 'attach-cloud') {
|
|
30
|
+
return blockSyncOperation('attach-cloud', { retainPendingSignature: true });
|
|
31
|
+
}
|
|
32
|
+
if (!args.isAuthenticated) {
|
|
33
|
+
return blockSyncOperation('auth-required');
|
|
34
|
+
}
|
|
35
|
+
if (args.pushInFlight) {
|
|
36
|
+
return blockSyncOperation('push-in-flight', { retainPendingSignature: true });
|
|
37
|
+
}
|
|
38
|
+
if (args.pullInFlight) {
|
|
39
|
+
return blockSyncOperation('pull-in-flight', { retainPendingSignature: true });
|
|
40
|
+
}
|
|
41
|
+
return ALLOW_SYNC_OPERATION;
|
|
42
|
+
}
|
|
43
|
+
export function resolvePullSyncReadiness(args) {
|
|
44
|
+
const baseReadiness = resolveBaseSyncReadiness(args);
|
|
45
|
+
if (baseReadiness)
|
|
46
|
+
return baseReadiness;
|
|
47
|
+
if (args.repairModeActive && !args.repairModeBypass) {
|
|
48
|
+
return blockSyncOperation('repair-active');
|
|
49
|
+
}
|
|
50
|
+
if (args.workspaceSyncPhase === 'provision-local') {
|
|
51
|
+
return blockSyncOperation('provision-local');
|
|
52
|
+
}
|
|
53
|
+
if (args.retryPending) {
|
|
54
|
+
return blockSyncOperation('retry-pending');
|
|
55
|
+
}
|
|
56
|
+
if (!args.isAuthenticated) {
|
|
57
|
+
return blockSyncOperation('auth-required');
|
|
58
|
+
}
|
|
59
|
+
if (args.pullInFlight) {
|
|
60
|
+
return blockSyncOperation('pull-in-flight');
|
|
61
|
+
}
|
|
62
|
+
if (args.pushInFlight) {
|
|
63
|
+
return blockSyncOperation('push-in-flight');
|
|
64
|
+
}
|
|
65
|
+
return ALLOW_SYNC_OPERATION;
|
|
66
|
+
}
|
|
67
|
+
export function resolveRetrySyncReadiness(args) {
|
|
68
|
+
const baseReadiness = resolveBaseSyncReadiness(args, { requireValidWorkspace: false });
|
|
69
|
+
if (baseReadiness)
|
|
70
|
+
return baseReadiness;
|
|
71
|
+
if (!args.isAuthenticated) {
|
|
72
|
+
return blockSyncOperation('auth-required');
|
|
73
|
+
}
|
|
74
|
+
return ALLOW_SYNC_OPERATION;
|
|
75
|
+
}
|
|
76
|
+
export function createWorkspaceSyncLeaseHeldDecision(operation) {
|
|
77
|
+
return blockSyncOperation('lease-held', { retainPendingSignature: operation === 'push' });
|
|
78
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { createWorkspaceSyncLeaseHeldDecision, resolvePullSyncReadiness, resolvePushSyncReadiness, resolveRetrySyncReadiness, } from './controlPlane';
|
|
3
|
+
const makeBaseArgs = () => ({
|
|
4
|
+
cloudAuthConfigured: true,
|
|
5
|
+
runtimeMode: 'local',
|
|
6
|
+
workspaceCloudSyncEnabled: true,
|
|
7
|
+
currentWorkspaceId: 'workspace-123',
|
|
8
|
+
isAuthenticated: true
|
|
9
|
+
});
|
|
10
|
+
describe('sync control plane', () => {
|
|
11
|
+
it('blocks push during repair and retains the pending signature', () => {
|
|
12
|
+
expect(resolvePushSyncReadiness({
|
|
13
|
+
...makeBaseArgs(),
|
|
14
|
+
workspaceSyncPhase: 'active',
|
|
15
|
+
repairModeActive: true,
|
|
16
|
+
repairModeBypass: false,
|
|
17
|
+
pushInFlight: false,
|
|
18
|
+
pullInFlight: false
|
|
19
|
+
})).toEqual({
|
|
20
|
+
allowed: false,
|
|
21
|
+
reason: 'repair-active',
|
|
22
|
+
retainPendingSignature: true
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
it('allows repair push to bypass the repair-active gate', () => {
|
|
26
|
+
expect(resolvePushSyncReadiness({
|
|
27
|
+
...makeBaseArgs(),
|
|
28
|
+
workspaceSyncPhase: 'active',
|
|
29
|
+
repairModeActive: true,
|
|
30
|
+
repairModeBypass: true,
|
|
31
|
+
pushInFlight: false,
|
|
32
|
+
pullInFlight: false
|
|
33
|
+
})).toEqual({ allowed: true });
|
|
34
|
+
});
|
|
35
|
+
it('blocks push during attach-cloud and retains the pending signature', () => {
|
|
36
|
+
expect(resolvePushSyncReadiness({
|
|
37
|
+
...makeBaseArgs(),
|
|
38
|
+
workspaceSyncPhase: 'attach-cloud',
|
|
39
|
+
repairModeActive: false,
|
|
40
|
+
repairModeBypass: false,
|
|
41
|
+
pushInFlight: false,
|
|
42
|
+
pullInFlight: false
|
|
43
|
+
})).toEqual({
|
|
44
|
+
allowed: false,
|
|
45
|
+
reason: 'attach-cloud',
|
|
46
|
+
retainPendingSignature: true
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
it('blocks pull during provision-local', () => {
|
|
50
|
+
expect(resolvePullSyncReadiness({
|
|
51
|
+
...makeBaseArgs(),
|
|
52
|
+
workspaceSyncPhase: 'provision-local',
|
|
53
|
+
repairModeActive: false,
|
|
54
|
+
repairModeBypass: false,
|
|
55
|
+
retryPending: false,
|
|
56
|
+
pushInFlight: false,
|
|
57
|
+
pullInFlight: false
|
|
58
|
+
})).toEqual({
|
|
59
|
+
allowed: false,
|
|
60
|
+
reason: 'provision-local',
|
|
61
|
+
retainPendingSignature: false
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
it('blocks pull when a retry is already pending', () => {
|
|
65
|
+
expect(resolvePullSyncReadiness({
|
|
66
|
+
...makeBaseArgs(),
|
|
67
|
+
workspaceSyncPhase: 'active',
|
|
68
|
+
repairModeActive: false,
|
|
69
|
+
repairModeBypass: false,
|
|
70
|
+
retryPending: true,
|
|
71
|
+
pushInFlight: false,
|
|
72
|
+
pullInFlight: false
|
|
73
|
+
})).toEqual({
|
|
74
|
+
allowed: false,
|
|
75
|
+
reason: 'retry-pending',
|
|
76
|
+
retainPendingSignature: false
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
it('surfaces auth-required for retry readiness before the session is refreshed', () => {
|
|
80
|
+
expect(resolveRetrySyncReadiness({
|
|
81
|
+
...makeBaseArgs(),
|
|
82
|
+
isAuthenticated: false
|
|
83
|
+
})).toEqual({
|
|
84
|
+
allowed: false,
|
|
85
|
+
reason: 'auth-required',
|
|
86
|
+
retainPendingSignature: false
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
it('rejects invalid workspace ids for transfer operations', () => {
|
|
90
|
+
expect(resolvePushSyncReadiness({
|
|
91
|
+
...makeBaseArgs(),
|
|
92
|
+
currentWorkspaceId: 'default',
|
|
93
|
+
workspaceSyncPhase: 'active',
|
|
94
|
+
repairModeActive: false,
|
|
95
|
+
repairModeBypass: false,
|
|
96
|
+
pushInFlight: false,
|
|
97
|
+
pullInFlight: false
|
|
98
|
+
})).toEqual({
|
|
99
|
+
allowed: false,
|
|
100
|
+
reason: 'invalid-workspace',
|
|
101
|
+
retainPendingSignature: false
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
it('marks lease contention as retaining the pending signature only for push', () => {
|
|
105
|
+
expect(createWorkspaceSyncLeaseHeldDecision('push')).toEqual({
|
|
106
|
+
allowed: false,
|
|
107
|
+
reason: 'lease-held',
|
|
108
|
+
retainPendingSignature: true
|
|
109
|
+
});
|
|
110
|
+
expect(createWorkspaceSyncLeaseHeldDecision('pull')).toEqual({
|
|
111
|
+
allowed: false,
|
|
112
|
+
reason: 'lease-held',
|
|
113
|
+
retainPendingSignature: false
|
|
114
|
+
});
|
|
115
|
+
expect(createWorkspaceSyncLeaseHeldDecision('repair')).toEqual({
|
|
116
|
+
allowed: false,
|
|
117
|
+
reason: 'lease-held',
|
|
118
|
+
retainPendingSignature: false
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { WorkspaceSyncPhase } from '../../sync/workspaceSyncState';
|
|
2
|
+
export type WorkspaceSyncPresentationStatus = 'off' | 'syncing' | 'attention' | 'healthy';
|
|
3
|
+
interface WorkspaceSyncPresentationArgs {
|
|
4
|
+
enabled: boolean;
|
|
5
|
+
phase: WorkspaceSyncPhase;
|
|
6
|
+
busy: boolean;
|
|
7
|
+
retryAt: string | null;
|
|
8
|
+
lastErrorMessage: string | null;
|
|
9
|
+
}
|
|
10
|
+
interface WorkspaceSyncLifecycleBaseArgs {
|
|
11
|
+
cloudAuthConfigured: boolean;
|
|
12
|
+
runtimeMode: 'local' | 'cloud';
|
|
13
|
+
workspaceCloudSyncEnabled: boolean;
|
|
14
|
+
authSessionResolved: boolean;
|
|
15
|
+
}
|
|
16
|
+
interface WorkspaceSyncAutomaticPullArgs extends WorkspaceSyncLifecycleBaseArgs {
|
|
17
|
+
workspaceSyncPhase: WorkspaceSyncPhase;
|
|
18
|
+
lastPullAt: string | null;
|
|
19
|
+
retryPending: boolean;
|
|
20
|
+
}
|
|
21
|
+
interface WorkspaceSyncFullReconcileArgs extends WorkspaceSyncLifecycleBaseArgs {
|
|
22
|
+
workspaceSyncPhase: WorkspaceSyncPhase;
|
|
23
|
+
isAuthenticated: boolean;
|
|
24
|
+
}
|
|
25
|
+
interface WorkspaceSyncStartupRepairArgs extends WorkspaceSyncFullReconcileArgs {
|
|
26
|
+
alreadyRan: boolean;
|
|
27
|
+
}
|
|
28
|
+
interface WorkspaceSyncReactivePushArgs extends WorkspaceSyncLifecycleBaseArgs {
|
|
29
|
+
workspaceSyncPhase: WorkspaceSyncPhase;
|
|
30
|
+
workspaceSyncReferenceSnapshotsReady: boolean;
|
|
31
|
+
}
|
|
32
|
+
export declare function resolveWorkspaceSyncPresentation({ enabled, phase, busy, retryAt, lastErrorMessage }: WorkspaceSyncPresentationArgs): {
|
|
33
|
+
status: WorkspaceSyncPresentationStatus;
|
|
34
|
+
summary: string;
|
|
35
|
+
recommendedAction: string;
|
|
36
|
+
};
|
|
37
|
+
export declare function shouldScheduleWorkspaceReactivePush(args: WorkspaceSyncReactivePushArgs): boolean;
|
|
38
|
+
export declare function shouldStartWorkspacePushWatchdog(args: WorkspaceSyncReactivePushArgs): boolean;
|
|
39
|
+
export declare function resolveWorkspaceAutomaticPullPolicy(args: WorkspaceSyncAutomaticPullArgs): {
|
|
40
|
+
shouldStart: boolean;
|
|
41
|
+
shouldKickOffImmediately: boolean;
|
|
42
|
+
};
|
|
43
|
+
export declare function shouldStartWorkspacePeriodicFullReconcile(args: WorkspaceSyncFullReconcileArgs): boolean;
|
|
44
|
+
export declare function shouldRunWorkspaceStartupRepair(args: WorkspaceSyncStartupRepairArgs): boolean;
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
function canRunLocalWorkspaceSyncLifecycle(args) {
|
|
2
|
+
return args.cloudAuthConfigured
|
|
3
|
+
&& args.runtimeMode === 'local'
|
|
4
|
+
&& args.workspaceCloudSyncEnabled
|
|
5
|
+
&& args.authSessionResolved;
|
|
6
|
+
}
|
|
7
|
+
export function resolveWorkspaceSyncPresentation({ enabled, phase, busy, retryAt, lastErrorMessage }) {
|
|
8
|
+
if (!enabled) {
|
|
9
|
+
return {
|
|
10
|
+
status: 'off',
|
|
11
|
+
summary: 'Sync is turned off for this workspace.',
|
|
12
|
+
recommendedAction: 'Turn on sync when you are ready to back up this workspace.'
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
if (phase === 'provision-local') {
|
|
16
|
+
return {
|
|
17
|
+
status: 'syncing',
|
|
18
|
+
summary: busy
|
|
19
|
+
? 'Uploading this workspace to cloud for the first time.'
|
|
20
|
+
: 'Preparing the first upload to cloud.',
|
|
21
|
+
recommendedAction: 'Keep this window open while the first upload finishes. If it seems stalled, press Sync.'
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
if (phase === 'attach-cloud') {
|
|
25
|
+
return {
|
|
26
|
+
status: 'syncing',
|
|
27
|
+
summary: busy
|
|
28
|
+
? 'Pulling the cloud workspace into this device.'
|
|
29
|
+
: 'Waiting for the first cloud pull into this device.',
|
|
30
|
+
recommendedAction: 'Keep this window open for the first cloud pull. If it does not move, press Repair.'
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
if (retryAt) {
|
|
34
|
+
return {
|
|
35
|
+
status: 'attention',
|
|
36
|
+
summary: 'Sync hit a temporary problem and will retry automatically.',
|
|
37
|
+
recommendedAction: 'Wait for the automatic retry, or press Sync to retry now.'
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
if (lastErrorMessage || phase === 'error') {
|
|
41
|
+
return {
|
|
42
|
+
status: 'attention',
|
|
43
|
+
summary: 'Sync needs attention before it can continue.',
|
|
44
|
+
recommendedAction: 'Press Sync to retry. If the same error keeps returning, press Repair.'
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
if (busy) {
|
|
48
|
+
return {
|
|
49
|
+
status: 'syncing',
|
|
50
|
+
summary: 'Sync is currently running.',
|
|
51
|
+
recommendedAction: 'No action needed.'
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
status: 'healthy',
|
|
56
|
+
summary: 'Sync is healthy.',
|
|
57
|
+
recommendedAction: 'No action needed.'
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export function shouldScheduleWorkspaceReactivePush(args) {
|
|
61
|
+
return canRunLocalWorkspaceSyncLifecycle(args)
|
|
62
|
+
&& args.workspaceSyncPhase !== 'attach-cloud'
|
|
63
|
+
&& args.workspaceSyncReferenceSnapshotsReady;
|
|
64
|
+
}
|
|
65
|
+
export function shouldStartWorkspacePushWatchdog(args) {
|
|
66
|
+
return canRunLocalWorkspaceSyncLifecycle(args)
|
|
67
|
+
&& args.workspaceSyncPhase === 'active'
|
|
68
|
+
&& args.workspaceSyncReferenceSnapshotsReady;
|
|
69
|
+
}
|
|
70
|
+
export function resolveWorkspaceAutomaticPullPolicy(args) {
|
|
71
|
+
if (!canRunLocalWorkspaceSyncLifecycle(args)) {
|
|
72
|
+
return { shouldStart: false, shouldKickOffImmediately: false };
|
|
73
|
+
}
|
|
74
|
+
if (args.workspaceSyncPhase === 'provision-local' || args.workspaceSyncPhase === 'error' || args.retryPending) {
|
|
75
|
+
return { shouldStart: false, shouldKickOffImmediately: false };
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
shouldStart: true,
|
|
79
|
+
shouldKickOffImmediately: args.workspaceSyncPhase === 'attach-cloud' || !args.lastPullAt
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
export function shouldStartWorkspacePeriodicFullReconcile(args) {
|
|
83
|
+
return canRunLocalWorkspaceSyncLifecycle(args)
|
|
84
|
+
&& args.isAuthenticated
|
|
85
|
+
&& args.workspaceSyncPhase === 'active';
|
|
86
|
+
}
|
|
87
|
+
export function shouldRunWorkspaceStartupRepair(args) {
|
|
88
|
+
return canRunLocalWorkspaceSyncLifecycle(args)
|
|
89
|
+
&& args.isAuthenticated
|
|
90
|
+
&& args.workspaceSyncPhase !== 'provision-local'
|
|
91
|
+
&& args.workspaceSyncPhase !== 'attach-cloud'
|
|
92
|
+
&& !args.alreadyRan;
|
|
93
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { resolveWorkspaceAutomaticPullPolicy, resolveWorkspaceSyncPresentation, shouldRunWorkspaceStartupRepair, shouldScheduleWorkspaceReactivePush, shouldStartWorkspacePeriodicFullReconcile, shouldStartWorkspacePushWatchdog, } from './lifecyclePolicy';
|
|
3
|
+
const makeLifecycleBaseArgs = () => ({
|
|
4
|
+
cloudAuthConfigured: true,
|
|
5
|
+
runtimeMode: 'local',
|
|
6
|
+
workspaceCloudSyncEnabled: true,
|
|
7
|
+
authSessionResolved: true
|
|
8
|
+
});
|
|
9
|
+
describe('sync lifecycle policy', () => {
|
|
10
|
+
it('reports bootstrap phases as syncing with phase-specific guidance', () => {
|
|
11
|
+
expect(resolveWorkspaceSyncPresentation({
|
|
12
|
+
enabled: true,
|
|
13
|
+
phase: 'attach-cloud',
|
|
14
|
+
busy: false,
|
|
15
|
+
retryAt: null,
|
|
16
|
+
lastErrorMessage: null
|
|
17
|
+
})).toEqual({
|
|
18
|
+
status: 'syncing',
|
|
19
|
+
summary: 'Waiting for the first cloud pull into this device.',
|
|
20
|
+
recommendedAction: 'Keep this window open for the first cloud pull. If it does not move, press Repair.'
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
it('reports retry state as attention even when sync is otherwise idle', () => {
|
|
24
|
+
expect(resolveWorkspaceSyncPresentation({
|
|
25
|
+
enabled: true,
|
|
26
|
+
phase: 'active',
|
|
27
|
+
busy: false,
|
|
28
|
+
retryAt: '2026-04-18T16:00:00.000Z',
|
|
29
|
+
lastErrorMessage: null
|
|
30
|
+
})).toEqual({
|
|
31
|
+
status: 'attention',
|
|
32
|
+
summary: 'Sync hit a temporary problem and will retry automatically.',
|
|
33
|
+
recommendedAction: 'Wait for the automatic retry, or press Sync to retry now.'
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
it('allows reactive push only after bootstrap and reference snapshots are ready', () => {
|
|
37
|
+
expect(shouldScheduleWorkspaceReactivePush({
|
|
38
|
+
...makeLifecycleBaseArgs(),
|
|
39
|
+
workspaceSyncPhase: 'active',
|
|
40
|
+
workspaceSyncReferenceSnapshotsReady: true
|
|
41
|
+
})).toBe(true);
|
|
42
|
+
expect(shouldScheduleWorkspaceReactivePush({
|
|
43
|
+
...makeLifecycleBaseArgs(),
|
|
44
|
+
workspaceSyncPhase: 'attach-cloud',
|
|
45
|
+
workspaceSyncReferenceSnapshotsReady: true
|
|
46
|
+
})).toBe(false);
|
|
47
|
+
expect(shouldScheduleWorkspaceReactivePush({
|
|
48
|
+
...makeLifecycleBaseArgs(),
|
|
49
|
+
workspaceSyncPhase: 'active',
|
|
50
|
+
workspaceSyncReferenceSnapshotsReady: false
|
|
51
|
+
})).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
it('starts the push watchdog only for active sync', () => {
|
|
54
|
+
expect(shouldStartWorkspacePushWatchdog({
|
|
55
|
+
...makeLifecycleBaseArgs(),
|
|
56
|
+
workspaceSyncPhase: 'active',
|
|
57
|
+
workspaceSyncReferenceSnapshotsReady: true
|
|
58
|
+
})).toBe(true);
|
|
59
|
+
expect(shouldStartWorkspacePushWatchdog({
|
|
60
|
+
...makeLifecycleBaseArgs(),
|
|
61
|
+
workspaceSyncPhase: 'error',
|
|
62
|
+
workspaceSyncReferenceSnapshotsReady: true
|
|
63
|
+
})).toBe(false);
|
|
64
|
+
});
|
|
65
|
+
it('starts automatic pull for attach-cloud and kickstarts first active pull when no pull has succeeded yet', () => {
|
|
66
|
+
expect(resolveWorkspaceAutomaticPullPolicy({
|
|
67
|
+
...makeLifecycleBaseArgs(),
|
|
68
|
+
workspaceSyncPhase: 'attach-cloud',
|
|
69
|
+
lastPullAt: null,
|
|
70
|
+
retryPending: false
|
|
71
|
+
})).toEqual({
|
|
72
|
+
shouldStart: true,
|
|
73
|
+
shouldKickOffImmediately: true
|
|
74
|
+
});
|
|
75
|
+
expect(resolveWorkspaceAutomaticPullPolicy({
|
|
76
|
+
...makeLifecycleBaseArgs(),
|
|
77
|
+
workspaceSyncPhase: 'active',
|
|
78
|
+
lastPullAt: null,
|
|
79
|
+
retryPending: false
|
|
80
|
+
})).toEqual({
|
|
81
|
+
shouldStart: true,
|
|
82
|
+
shouldKickOffImmediately: true
|
|
83
|
+
});
|
|
84
|
+
expect(resolveWorkspaceAutomaticPullPolicy({
|
|
85
|
+
...makeLifecycleBaseArgs(),
|
|
86
|
+
workspaceSyncPhase: 'active',
|
|
87
|
+
lastPullAt: '2026-04-18T15:00:00.000Z',
|
|
88
|
+
retryPending: true
|
|
89
|
+
})).toEqual({
|
|
90
|
+
shouldStart: false,
|
|
91
|
+
shouldKickOffImmediately: false
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
it('runs periodic full reconcile and startup repair only in authenticated active sync', () => {
|
|
95
|
+
expect(shouldStartWorkspacePeriodicFullReconcile({
|
|
96
|
+
...makeLifecycleBaseArgs(),
|
|
97
|
+
workspaceSyncPhase: 'active',
|
|
98
|
+
isAuthenticated: true
|
|
99
|
+
})).toBe(true);
|
|
100
|
+
expect(shouldStartWorkspacePeriodicFullReconcile({
|
|
101
|
+
...makeLifecycleBaseArgs(),
|
|
102
|
+
workspaceSyncPhase: 'attach-cloud',
|
|
103
|
+
isAuthenticated: true
|
|
104
|
+
})).toBe(false);
|
|
105
|
+
expect(shouldRunWorkspaceStartupRepair({
|
|
106
|
+
...makeLifecycleBaseArgs(),
|
|
107
|
+
workspaceSyncPhase: 'active',
|
|
108
|
+
isAuthenticated: true,
|
|
109
|
+
alreadyRan: false
|
|
110
|
+
})).toBe(true);
|
|
111
|
+
expect(shouldRunWorkspaceStartupRepair({
|
|
112
|
+
...makeLifecycleBaseArgs(),
|
|
113
|
+
workspaceSyncPhase: 'error',
|
|
114
|
+
isAuthenticated: true,
|
|
115
|
+
alreadyRan: false
|
|
116
|
+
})).toBe(true);
|
|
117
|
+
expect(shouldRunWorkspaceStartupRepair({
|
|
118
|
+
...makeLifecycleBaseArgs(),
|
|
119
|
+
workspaceSyncPhase: 'active',
|
|
120
|
+
isAuthenticated: true,
|
|
121
|
+
alreadyRan: true
|
|
122
|
+
})).toBe(false);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { WorkspaceTaxonomyState } from '../../shared/taxonomyState.js';
|
|
2
2
|
import type { WorkspaceSyncAiProfileSnapshot, WorkspaceSyncDocumentReviewCommentSnapshot, WorkspaceSyncDocumentReviewSessionSnapshot, WorkspaceSyncTaskEventSnapshot } from '../../sync/workspaceSyncModel';
|
|
3
|
+
import type { WorkspaceSyncPushBaselineSnapshot } from '../../sync/workspaceSyncState.js';
|
|
3
4
|
import type { TaskItem } from '../../types';
|
|
4
5
|
export declare const USER_GLOBAL_SYNC_META_KEY = "taskforce.userGlobalSyncMeta.v1";
|
|
5
6
|
export declare const SYNC_DEBUG_LOG_KEY = "taskforce.syncDebug.v1";
|
|
@@ -20,9 +21,13 @@ export declare function buildTaxonomyStateFingerprint(taxonomyState?: WorkspaceT
|
|
|
20
21
|
export declare function flattenDocumentReviewComments(sessions: WorkspaceSyncDocumentReviewSessionSnapshot[]): WorkspaceSyncDocumentReviewCommentSnapshot[];
|
|
21
22
|
export declare function flattenTaskEvents(tasks: TaskItem[], archivedTasks: TaskItem[]): WorkspaceSyncTaskEventSnapshot[];
|
|
22
23
|
export interface PersistedSyncWatermarks {
|
|
24
|
+
taskIds: Set<string>;
|
|
23
25
|
taskWatermarks: Map<string, string>;
|
|
26
|
+
taskChangeKeys?: Map<string, string>;
|
|
24
27
|
initiativeWatermarks: Map<string, string>;
|
|
25
28
|
workstreamWatermarks: Map<string, string>;
|
|
29
|
+
aiProfileWatermarks: Map<string, string>;
|
|
30
|
+
aiProfileChangeKeys?: Map<string, string>;
|
|
26
31
|
documentWatermarks: Map<string, string>;
|
|
27
32
|
assetWatermarks: Map<string, string>;
|
|
28
33
|
documentReviewSessionWatermarks: Map<string, string>;
|
|
@@ -30,7 +35,11 @@ export interface PersistedSyncWatermarks {
|
|
|
30
35
|
annotatedAttachmentSessionWatermarks: Map<string, string>;
|
|
31
36
|
taskEventWatermarks: Map<string, string>;
|
|
32
37
|
}
|
|
38
|
+
export declare function buildPersistedSyncWatermarksSnapshot(data: PersistedSyncWatermarks & {
|
|
39
|
+
taxonomyStateFingerprint?: string;
|
|
40
|
+
}): WorkspaceSyncPushBaselineSnapshot;
|
|
41
|
+
export declare function parsePersistedSyncWatermarksSnapshot(snapshot: WorkspaceSyncPushBaselineSnapshot | null | undefined, taxonomyStateFingerprint?: string): PersistedSyncWatermarks | null;
|
|
33
42
|
export declare function readPersistedSyncWatermarks(workspaceId: string, taxonomyStateFingerprint?: string): PersistedSyncWatermarks | null;
|
|
34
43
|
export declare function savePersistedSyncWatermarks(workspaceId: string, data: PersistedSyncWatermarks & {
|
|
35
44
|
taxonomyStateFingerprint?: string;
|
|
36
|
-
}):
|
|
45
|
+
}): boolean;
|