@taskforcehq/taskforce 0.3.304 → 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/README.md +140 -247
- package/dist/Taskforce.module.css +295 -89
- package/dist/TaskforceCore.js +378 -111
- package/dist/TaskforceCore.test.js +1221 -107
- 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 +52 -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/DocumentIndex.d.ts +1 -1
- package/dist/components/features/DocumentIndex.js +1 -1
- package/dist/components/features/DocumentViewer.d.ts +6 -2
- package/dist/components/features/DocumentViewer.js +90 -10
- package/dist/components/features/DocumentWorkspace.d.ts +5 -22
- package/dist/components/features/DocumentWorkspace.js +12 -155
- package/dist/components/features/DocumentWorkspace.test.js +226 -6
- package/dist/components/features/TaskSettings.js +29 -3
- package/dist/components/features/TaskSettings.test.js +42 -1
- 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/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.d.ts +8 -8
- package/dist/components/task/TaskForm.js +362 -230
- package/dist/components/task/TaskForm.test.js +558 -381
- package/dist/components/task/TaskKanban.d.ts +2 -1
- package/dist/components/task/TaskKanban.js +96 -14
- 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 -17
- package/dist/components/views/PlanComparisonPage.test.js +289 -41
- package/dist/components/views/PlansPage.js +239 -106
- package/dist/components/views/PlansPage.test.js +593 -42
- package/dist/components/views/StandaloneLayout.js +310 -554
- package/dist/components/views/panels/FilterToolbar.test.js +6 -4
- 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 +27 -14
- package/dist/components/views/standalone/modals/AccountHubModal.js +87 -15
- package/dist/components/views/standalone/modals/AccountHubModal.test.js +42 -2
- 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/config/envSchema.js +1 -1
- package/dist/core/AiProfileService.d.ts +45 -3
- package/dist/core/AiProfileService.js +774 -63
- package/dist/core/AiProfileService.test.js +22 -2
- package/dist/core/AiProfiles.test.js +761 -2
- 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.d.ts +1 -0
- package/dist/core/AuthTokenService.test.js +78 -0
- package/dist/core/EntitlementsPolicy.test.js +77 -15
- package/dist/core/GlobalSettingsService.js +115 -6
- package/dist/core/PlanEntitlementService.d.ts +30 -3
- package/dist/core/PlanEntitlementService.js +454 -93
- package/dist/core/PlanFeatureCatalog.test.d.ts +1 -0
- package/dist/core/PlanFeatureCatalog.test.js +84 -0
- package/dist/core/PlanVersionPolicy.test.js +54 -28
- package/dist/core/PlanningEntities.test.js +52 -0
- package/dist/core/SignupMonetizationSettings.test.js +241 -44
- package/dist/core/SyncReconciliationService.js +2 -65
- package/dist/core/SystemAdmin.test.js +244 -88
- package/dist/core/TaskAttachmentsCanonical.test.js +51 -1
- package/dist/core/TaskTaxonomyValidation.test.js +53 -0
- package/dist/core/Taskforce.d.ts +228 -14
- package/dist/core/Taskforce.js +814 -201
- 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 +128 -27
- package/dist/core/shared.d.ts +3 -1
- package/dist/core/shared.js +16 -1
- package/dist/core/types.d.ts +68 -2
- package/dist/hooks/auth/useTaskforceAuthBootstrap.js +26 -1
- package/dist/hooks/sync/auth.js +3 -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 +16 -3
- package/dist/hooks/sync/transfers.js +186 -65
- 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 +16 -6
- package/dist/hooks/useSyncOrchestrator.js +424 -175
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +487 -10
- package/dist/hooks/useTaskData.js +8 -3
- package/dist/hooks/useTaskData.test.js +45 -0
- package/dist/hooks/useTaskMutations.d.ts +2 -1
- package/dist/hooks/useTaskMutations.js +12 -1
- package/dist/hooks/useTaskMutations.test.js +30 -1
- package/dist/hooks/useTaskforce.d.ts +65 -4
- package/dist/hooks/useTaskforce.js +564 -243
- 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 +2454 -208
- package/dist/hooks/useWorkspaceSyncController.d.ts +4 -1
- package/dist/hooks/useWorkspaceSyncController.js +17 -2
- package/dist/hooks/useWorkspaceSyncController.test.js +1 -0
- 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/adminWorkspaceRegistrar.d.ts +2 -0
- package/dist/mcp/adminWorkspaceRegistrar.js +154 -0
- 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/runtime.d.ts +2 -0
- package/dist/mcp/runtime.js +2433 -3534
- package/dist/mcp/runtime.test.js +440 -2
- 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/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 +4 -5
- package/dist/server/routes/admin.js +410 -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 +16 -6
- package/dist/server/routes/auth.js +1015 -118
- 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 +276 -34
- package/dist/server/routes/billing.test.js +760 -52
- package/dist/server/routes/documentReviews.d.ts +1 -1
- package/dist/server/routes/documentReviews.js +1 -1
- package/dist/server/routes/documents.d.ts +5 -2
- package/dist/server/routes/documents.js +242 -74
- 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 +29 -3
- package/dist/server/routes/shared.js +69 -4
- package/dist/server/routes/sync.d.ts +1 -1
- package/dist/server/routes/sync.integration.test.js +437 -39
- package/dist/server/routes/sync.js +62 -606
- 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.d.ts +1 -1
- package/dist/server/routes/tasks.js +44 -2
- package/dist/server/routes/workspaces.d.ts +1 -1
- package/dist/server/routes/workspaces.js +37 -6
- package/dist/server/routes.d.ts +2 -9
- package/dist/server/routes.js +136 -165
- package/dist/server/routes.test.js +2457 -162
- 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 +33 -77
- package/dist/sync/syncApplyHandlers.d.ts +7 -0
- package/dist/sync/syncApplyHandlers.js +28 -13
- package/dist/sync/syncApplyHandlers.test.js +72 -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 +6 -0
- package/dist/sync/syncService.js +60 -0
- package/dist/sync/syncService.test.js +71 -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 +48 -0
- package/dist/sync/workspaceSyncModel.js +195 -31
- package/dist/sync/workspaceSyncModel.test.js +325 -12
- package/dist/sync/workspaceSyncState.d.ts +19 -0
- package/dist/sync/workspaceSyncState.js +3 -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-BlS-cqnl.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-BjR6iBf9.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-DZX4jk7e.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-DnFqdUME.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-I7ZwG1z_.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.d.ts +19 -0
- package/dist/utils/commercialLifecyclePresentation.js +199 -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 +10 -2
- package/scripts/check-cloud-mcp.mjs +83 -0
- package/scripts/export-sqlite-to-postgres.mjs +7 -9
- package/scripts/playwright-auth.sh +5 -1
- package/scripts/run-billing-performance-smoke.sh +24 -0
- 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-BLWVbuKP.js +0 -1
- package/dist/ui/assets/DocumentWorkspace-BH_6DF6x.js +0 -250
- package/dist/ui/assets/DocumentWorkspace-C_8T8oz-.css +0 -1
- package/dist/ui/assets/PlansPage-C2dd2n1X.css +0 -1
- package/dist/ui/assets/PlansPage-CSDQ4sLa.js +0 -1
- package/dist/ui/assets/RussoOne-Regular-C3BxZIj7.ttf +0 -0
- package/dist/ui/assets/TaskSettings-CEDy34Jo.js +0 -9
- package/dist/ui/assets/index-DXUdtH1B.js +0 -6
- package/dist/ui/assets/index-Ii0B0rTO.css +0 -1
- package/scripts/migrate-planning-model.ts +0 -233
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { describe, expect, it, vi, afterEach } from 'vitest';
|
|
2
4
|
import { applyCollaborationSyncChanges, applyContentSyncChanges, applyPreparedTaskSyncSnapshot, applyTaskSyncDeletes, classifyWorkspaceSyncChanges, prepareTaskSyncSnapshotApply, } from './syncApplyHandlers.js';
|
|
3
5
|
function createCoreFixture(overrides = {}) {
|
|
4
6
|
return {
|
|
@@ -29,6 +31,9 @@ function createCoreFixture(overrides = {}) {
|
|
|
29
31
|
};
|
|
30
32
|
}
|
|
31
33
|
describe('syncApplyHandlers', () => {
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
vi.restoreAllMocks();
|
|
36
|
+
});
|
|
32
37
|
it('classifies task and workstream changes, then prepares and applies the task snapshot pipeline', () => {
|
|
33
38
|
const diagnostics = [];
|
|
34
39
|
const workspaceId = 'workspace-1';
|
|
@@ -76,15 +81,18 @@ describe('syncApplyHandlers', () => {
|
|
|
76
81
|
{
|
|
77
82
|
op: 'delete',
|
|
78
83
|
taskId: 'task-1',
|
|
84
|
+
deletedAt: '2026-04-10T11:00:00.000Z',
|
|
79
85
|
},
|
|
80
86
|
{
|
|
81
87
|
op: 'delete',
|
|
82
88
|
taskId: 'task-stale',
|
|
89
|
+
deletedAt: '2026-04-10T08:00:00.000Z',
|
|
83
90
|
},
|
|
84
91
|
],
|
|
85
92
|
normalizeWorkspaceSyncDocumentPath: (rawPath) => typeof rawPath === 'string' ? rawPath : null,
|
|
86
93
|
normalizeTaskAssetPath: (rawPath) => typeof rawPath === 'string' ? rawPath : null,
|
|
87
94
|
normalizeIncomingAiProfileSurfaceType: () => null,
|
|
95
|
+
normalizeIncomingAiProfileSeatScope: () => null,
|
|
88
96
|
normalizeTaskEventSyncPayload: () => null,
|
|
89
97
|
recordSyncDiagnostic: (targetWorkspaceId, eventType, message, details) => {
|
|
90
98
|
diagnostics.push({ workspaceId: targetWorkspaceId, eventType, message, details });
|
|
@@ -229,7 +237,7 @@ describe('syncApplyHandlers', () => {
|
|
|
229
237
|
expect(core.notifyDocumentMetadataMutation).toHaveBeenNthCalledWith(1, 'workspace-1', ['docs/spec.md', 'assets/proof.png'], 'upsert');
|
|
230
238
|
expect(core.notifyDocumentMetadataMutation).toHaveBeenNthCalledWith(2, 'workspace-1', ['docs/stale.md', 'assets/stale.png'], 'delete');
|
|
231
239
|
});
|
|
232
|
-
it('
|
|
240
|
+
it('allows reference reassignment when metadata drift requires normal sync convergence', async () => {
|
|
233
241
|
const core = createCoreFixture({
|
|
234
242
|
getDocumentWatermark: vi.fn().mockReturnValue(null),
|
|
235
243
|
});
|
|
@@ -278,9 +286,70 @@ describe('syncApplyHandlers', () => {
|
|
|
278
286
|
}),
|
|
279
287
|
recordSyncDiagnostic: vi.fn(),
|
|
280
288
|
});
|
|
281
|
-
expect(writeWorkspaceSyncDocument).toHaveBeenCalledWith('/tmp/taskforce-sync-content', expect.objectContaining({ path: 'docs/spec.md' }), 'workspace-1', { allowReferenceNumberReassignment:
|
|
289
|
+
expect(writeWorkspaceSyncDocument).toHaveBeenCalledWith('/tmp/taskforce-sync-content', expect.objectContaining({ path: 'docs/spec.md' }), 'workspace-1', { allowReferenceNumberReassignment: true });
|
|
282
290
|
expect(writeWorkspaceSyncBinaryAsset).toHaveBeenCalledWith('/tmp/taskforce-sync-content', expect.objectContaining({ path: 'assets/proof.png' }), 'workspace-1', { allowReferenceNumberReassignment: true });
|
|
283
291
|
});
|
|
292
|
+
it('reuses persisted content hashes instead of re-reading unchanged local files', async () => {
|
|
293
|
+
const existingDocContent = '# Spec';
|
|
294
|
+
const existingAssetBytes = Buffer.from('png-bytes');
|
|
295
|
+
const core = createCoreFixture({
|
|
296
|
+
getDocumentWatermark: vi.fn().mockReturnValue({
|
|
297
|
+
updatedAt: '2026-04-10T10:00:00.000Z',
|
|
298
|
+
contentHash: createHash('sha256').update(existingDocContent).digest('hex'),
|
|
299
|
+
}),
|
|
300
|
+
});
|
|
301
|
+
const writeWorkspaceSyncDocument = vi.fn().mockResolvedValue(undefined);
|
|
302
|
+
const writeWorkspaceSyncBinaryAsset = vi.fn().mockResolvedValue(undefined);
|
|
303
|
+
const readSpy = vi.spyOn(fs, 'readFileSync').mockImplementation(((targetPath) => {
|
|
304
|
+
const value = String(targetPath);
|
|
305
|
+
if (value.endsWith('spec.md'))
|
|
306
|
+
return existingDocContent;
|
|
307
|
+
return existingAssetBytes;
|
|
308
|
+
}));
|
|
309
|
+
await applyContentSyncChanges({
|
|
310
|
+
core,
|
|
311
|
+
workspaceId: 'workspace-1',
|
|
312
|
+
incomingDocuments: [
|
|
313
|
+
{
|
|
314
|
+
path: 'docs/spec.md',
|
|
315
|
+
updatedAt: '2026-04-10T10:00:00.000Z',
|
|
316
|
+
content: existingDocContent,
|
|
317
|
+
assetId: 'asset-doc-1',
|
|
318
|
+
},
|
|
319
|
+
],
|
|
320
|
+
incomingDocumentDeletes: new Map(),
|
|
321
|
+
incomingAssets: [
|
|
322
|
+
{
|
|
323
|
+
path: 'assets/proof.png',
|
|
324
|
+
updatedAt: '2026-04-10T10:05:00.000Z',
|
|
325
|
+
contentBase64: existingAssetBytes.toString('base64'),
|
|
326
|
+
assetId: 'asset-image-1',
|
|
327
|
+
kind: 'image',
|
|
328
|
+
mimeType: 'image/png',
|
|
329
|
+
},
|
|
330
|
+
],
|
|
331
|
+
incomingAssetDeletes: new Map(),
|
|
332
|
+
basePath: '/tmp/taskforce-sync-content',
|
|
333
|
+
hasObjectStorageClient: () => true,
|
|
334
|
+
hasDocumentSyncMetadataDrift: () => false,
|
|
335
|
+
hasBinaryAssetSyncMetadataDrift: () => false,
|
|
336
|
+
getExistingDocument: () => {
|
|
337
|
+
throw new Error('document fallback should not be used when persisted metadata exists');
|
|
338
|
+
},
|
|
339
|
+
writeWorkspaceSyncDocument,
|
|
340
|
+
deleteWorkspaceSyncDocument: vi.fn(),
|
|
341
|
+
writeWorkspaceSyncBinaryAsset,
|
|
342
|
+
deleteWorkspaceSyncBinaryAsset: vi.fn(),
|
|
343
|
+
getExistingBinaryAsset: () => ({
|
|
344
|
+
updatedAt: '2026-04-10T10:05:00.000Z',
|
|
345
|
+
contentSha256: createHash('sha256').update(existingAssetBytes).digest('hex'),
|
|
346
|
+
}),
|
|
347
|
+
recordSyncDiagnostic: vi.fn(),
|
|
348
|
+
});
|
|
349
|
+
expect(readSpy).not.toHaveBeenCalled();
|
|
350
|
+
expect(writeWorkspaceSyncDocument).not.toHaveBeenCalled();
|
|
351
|
+
expect(writeWorkspaceSyncBinaryAsset).not.toHaveBeenCalled();
|
|
352
|
+
});
|
|
284
353
|
it('applies collaboration upserts and deletes through the collaboration handlers', () => {
|
|
285
354
|
const core = createCoreFixture();
|
|
286
355
|
applyCollaborationSyncChanges({
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare function hasOwnSyncField(source: unknown, field: string): boolean;
|
|
2
|
+
export declare function normalizeSyncOptionalString(value: unknown): string | undefined;
|
|
3
|
+
export declare function normalizeSyncNullableString(value: unknown): string | null;
|
|
4
|
+
export declare function normalizeSyncOptionalId(value: unknown): string | undefined;
|
|
5
|
+
export declare function normalizeSyncNullableId(value: unknown): string | null;
|
|
6
|
+
export declare function normalizeSyncNullableIsoString(value: unknown): string | null;
|
|
7
|
+
export declare function normalizeSyncSameOriginAssetUrl(value: unknown): string | null;
|
|
8
|
+
export declare function readSyncOptionalStringField(source: Record<string, unknown>, field: string): string | undefined;
|
|
9
|
+
export declare function readSyncNullableStringField(source: Record<string, unknown>, field: string): string | null | undefined;
|
|
10
|
+
export declare function readSyncNullableIdField(source: Record<string, unknown>, field: string): string | null | undefined;
|
|
11
|
+
export declare function readSyncNullableIsoStringField(source: Record<string, unknown>, field: string): string | null | undefined;
|
|
12
|
+
export declare function readSyncSameOriginAssetUrlField(source: Record<string, unknown>, field: string): string | null | undefined;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export function hasOwnSyncField(source, field) {
|
|
2
|
+
return Boolean(source) && typeof source === 'object' && Object.prototype.hasOwnProperty.call(source, field);
|
|
3
|
+
}
|
|
4
|
+
export function normalizeSyncOptionalString(value) {
|
|
5
|
+
if (value === undefined || value === null)
|
|
6
|
+
return undefined;
|
|
7
|
+
const normalized = String(value).trim();
|
|
8
|
+
return normalized.length > 0 ? normalized : undefined;
|
|
9
|
+
}
|
|
10
|
+
export function normalizeSyncNullableString(value) {
|
|
11
|
+
if (value === undefined || value === null)
|
|
12
|
+
return null;
|
|
13
|
+
const normalized = String(value).trim();
|
|
14
|
+
return normalized.length > 0 ? normalized : null;
|
|
15
|
+
}
|
|
16
|
+
export function normalizeSyncOptionalId(value) {
|
|
17
|
+
return normalizeSyncOptionalString(value);
|
|
18
|
+
}
|
|
19
|
+
export function normalizeSyncNullableId(value) {
|
|
20
|
+
return normalizeSyncNullableString(value);
|
|
21
|
+
}
|
|
22
|
+
export function normalizeSyncNullableIsoString(value) {
|
|
23
|
+
return normalizeSyncNullableString(value);
|
|
24
|
+
}
|
|
25
|
+
export function normalizeSyncSameOriginAssetUrl(value) {
|
|
26
|
+
const normalized = normalizeSyncNullableString(value);
|
|
27
|
+
if (!normalized)
|
|
28
|
+
return null;
|
|
29
|
+
return normalized.startsWith('/api/taskforce/context/') ? normalized : null;
|
|
30
|
+
}
|
|
31
|
+
export function readSyncOptionalStringField(source, field) {
|
|
32
|
+
if (!hasOwnSyncField(source, field))
|
|
33
|
+
return undefined;
|
|
34
|
+
return normalizeSyncOptionalString(source[field]);
|
|
35
|
+
}
|
|
36
|
+
export function readSyncNullableStringField(source, field) {
|
|
37
|
+
if (!hasOwnSyncField(source, field))
|
|
38
|
+
return undefined;
|
|
39
|
+
return normalizeSyncNullableString(source[field]);
|
|
40
|
+
}
|
|
41
|
+
export function readSyncNullableIdField(source, field) {
|
|
42
|
+
if (!hasOwnSyncField(source, field))
|
|
43
|
+
return undefined;
|
|
44
|
+
return normalizeSyncNullableId(source[field]);
|
|
45
|
+
}
|
|
46
|
+
export function readSyncNullableIsoStringField(source, field) {
|
|
47
|
+
if (!hasOwnSyncField(source, field))
|
|
48
|
+
return undefined;
|
|
49
|
+
return normalizeSyncNullableIsoString(source[field]);
|
|
50
|
+
}
|
|
51
|
+
export function readSyncSameOriginAssetUrlField(source, field) {
|
|
52
|
+
if (!hasOwnSyncField(source, field))
|
|
53
|
+
return undefined;
|
|
54
|
+
return normalizeSyncSameOriginAssetUrl(source[field]);
|
|
55
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { TaskItem } from '../types.js';
|
|
2
|
+
export interface SyncAiProfileLike {
|
|
3
|
+
id?: string | null;
|
|
4
|
+
workspaceId?: string | null;
|
|
5
|
+
profileToken?: string | null;
|
|
6
|
+
name?: string | null;
|
|
7
|
+
username?: string | null;
|
|
8
|
+
icon?: string | null;
|
|
9
|
+
color?: string | null;
|
|
10
|
+
avatarUrl?: string | null;
|
|
11
|
+
avatarSourceUrl?: string | null;
|
|
12
|
+
description?: string | null;
|
|
13
|
+
role?: string | null;
|
|
14
|
+
provider?: string | null;
|
|
15
|
+
model?: string | null;
|
|
16
|
+
surfaceType?: string | null;
|
|
17
|
+
seatScope?: string | null;
|
|
18
|
+
providerMetadata?: Record<string, unknown> | null;
|
|
19
|
+
archivedAt?: string | null;
|
|
20
|
+
archivedBy?: string | null;
|
|
21
|
+
archivedReason?: string | null;
|
|
22
|
+
mergedIntoProfileId?: string | null;
|
|
23
|
+
rosterStateUpdatedAt?: string | null;
|
|
24
|
+
createdAt?: string | null;
|
|
25
|
+
updatedAt?: string | null;
|
|
26
|
+
lastActiveAt?: string | null;
|
|
27
|
+
}
|
|
28
|
+
export interface SyncResourceAdapter<T> {
|
|
29
|
+
kind: string;
|
|
30
|
+
getId: (resource: T) => string;
|
|
31
|
+
getWatermark: (resource: T) => string;
|
|
32
|
+
getChangeKey: (resource: T) => string;
|
|
33
|
+
}
|
|
34
|
+
export interface SyncResourceBaselineState {
|
|
35
|
+
watermarks?: Map<string, string> | null;
|
|
36
|
+
changeKeys?: Map<string, string> | null;
|
|
37
|
+
}
|
|
38
|
+
export declare function getAiProfileSyncWatermark(profile: Partial<SyncAiProfileLike> | null | undefined): string;
|
|
39
|
+
export declare function getAiProfileSyncChangeKey(profile: Partial<SyncAiProfileLike> | null | undefined): string;
|
|
40
|
+
export declare const taskSyncResourceAdapter: SyncResourceAdapter<Partial<TaskItem> & {
|
|
41
|
+
isArchived?: boolean | null;
|
|
42
|
+
}>;
|
|
43
|
+
export declare const aiProfileSyncResourceAdapter: SyncResourceAdapter<Partial<SyncAiProfileLike>>;
|
|
44
|
+
export declare function shouldSyncResource<T>(adapter: SyncResourceAdapter<T>, resource: T, baseline?: SyncResourceBaselineState | null): boolean;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { buildTaskSyncChangeKey } from './taskSyncChangeKey.js';
|
|
2
|
+
import { normalizeSyncNullableIsoString, normalizeSyncNullableString, normalizeSyncOptionalId, normalizeSyncSameOriginAssetUrl, } from './syncFieldSemantics.js';
|
|
3
|
+
function normalizeSyncTimestampMax(...values) {
|
|
4
|
+
return values.reduce((latest, value) => {
|
|
5
|
+
const normalized = normalizeSyncNullableIsoString(value) || '';
|
|
6
|
+
if (!latest)
|
|
7
|
+
return normalized;
|
|
8
|
+
if (!normalized)
|
|
9
|
+
return latest;
|
|
10
|
+
return normalized > latest ? normalized : latest;
|
|
11
|
+
}, '');
|
|
12
|
+
}
|
|
13
|
+
export function getAiProfileSyncWatermark(profile) {
|
|
14
|
+
const source = profile || {};
|
|
15
|
+
return normalizeSyncTimestampMax(source.createdAt, source.updatedAt, source.lastActiveAt, source.archivedAt, source.rosterStateUpdatedAt);
|
|
16
|
+
}
|
|
17
|
+
export function getAiProfileSyncChangeKey(profile) {
|
|
18
|
+
const source = profile || {};
|
|
19
|
+
return JSON.stringify({
|
|
20
|
+
id: String(source.id || '').trim(),
|
|
21
|
+
workspaceId: String(source.workspaceId || '').trim(),
|
|
22
|
+
profileToken: String(source.profileToken || '').trim(),
|
|
23
|
+
name: String(source.name || '').trim(),
|
|
24
|
+
username: String(source.username || '').trim(),
|
|
25
|
+
icon: String(source.icon || 'Bot').trim() || 'Bot',
|
|
26
|
+
color: String(source.color || '#8b5cf6').trim() || '#8b5cf6',
|
|
27
|
+
avatarUrl: normalizeSyncSameOriginAssetUrl(source.avatarUrl) || '',
|
|
28
|
+
avatarSourceUrl: normalizeSyncSameOriginAssetUrl(source.avatarSourceUrl) || '',
|
|
29
|
+
description: normalizeSyncNullableString(source.description) || '',
|
|
30
|
+
role: normalizeSyncNullableString(source.role) || '',
|
|
31
|
+
provider: normalizeSyncNullableString(source.provider) || '',
|
|
32
|
+
model: normalizeSyncNullableString(source.model) || '',
|
|
33
|
+
surfaceType: normalizeSyncNullableString(source.surfaceType) || '',
|
|
34
|
+
seatScope: normalizeSyncNullableString(source.seatScope) || '',
|
|
35
|
+
providerMetadata: source.providerMetadata && typeof source.providerMetadata === 'object' && !Array.isArray(source.providerMetadata)
|
|
36
|
+
? source.providerMetadata
|
|
37
|
+
: null,
|
|
38
|
+
archivedAt: normalizeSyncNullableIsoString(source.archivedAt) || '',
|
|
39
|
+
archivedBy: normalizeSyncOptionalId(source.archivedBy) || '',
|
|
40
|
+
archivedReason: normalizeSyncNullableString(source.archivedReason) || '',
|
|
41
|
+
mergedIntoProfileId: normalizeSyncOptionalId(source.mergedIntoProfileId) || '',
|
|
42
|
+
rosterStateUpdatedAt: normalizeSyncNullableIsoString(source.rosterStateUpdatedAt) || '',
|
|
43
|
+
createdAt: normalizeSyncNullableIsoString(source.createdAt) || '',
|
|
44
|
+
updatedAt: normalizeSyncNullableIsoString(source.updatedAt) || '',
|
|
45
|
+
lastActiveAt: normalizeSyncNullableIsoString(source.lastActiveAt) || '',
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
export const taskSyncResourceAdapter = {
|
|
49
|
+
kind: 'task',
|
|
50
|
+
getId: (task) => String(task?.id || '').trim(),
|
|
51
|
+
getWatermark: (task) => String(task?.updatedAt || task?.createdAt || '').trim(),
|
|
52
|
+
getChangeKey: (task) => buildTaskSyncChangeKey(task, Boolean(task?.isArchived))
|
|
53
|
+
};
|
|
54
|
+
export const aiProfileSyncResourceAdapter = {
|
|
55
|
+
kind: 'ai-profile',
|
|
56
|
+
getId: (profile) => String(profile?.id || '').trim(),
|
|
57
|
+
getWatermark: (profile) => getAiProfileSyncWatermark(profile),
|
|
58
|
+
getChangeKey: (profile) => getAiProfileSyncChangeKey(profile)
|
|
59
|
+
};
|
|
60
|
+
export function shouldSyncResource(adapter, resource, baseline) {
|
|
61
|
+
const id = adapter.getId(resource);
|
|
62
|
+
if (!id)
|
|
63
|
+
return false;
|
|
64
|
+
const currentChangeKey = adapter.getChangeKey(resource);
|
|
65
|
+
const previousWatermarks = baseline?.watermarks;
|
|
66
|
+
if (!previousWatermarks || previousWatermarks.size === 0)
|
|
67
|
+
return true;
|
|
68
|
+
if (!previousWatermarks.has(id))
|
|
69
|
+
return true;
|
|
70
|
+
const previousChangeKeys = baseline?.changeKeys;
|
|
71
|
+
const previousChangeKey = previousChangeKeys?.get(id);
|
|
72
|
+
if (typeof previousChangeKey === 'string') {
|
|
73
|
+
return previousChangeKey !== currentChangeKey;
|
|
74
|
+
}
|
|
75
|
+
const previousWatermark = previousWatermarks.get(id) || '';
|
|
76
|
+
return previousWatermark !== currentChangeKey;
|
|
77
|
+
}
|
|
@@ -7,6 +7,7 @@ export interface EnsureWorkspaceReadyInput {
|
|
|
7
7
|
resolveCloudAuthUrl: (path: string) => string;
|
|
8
8
|
workspaceId: string;
|
|
9
9
|
workspaceName: string;
|
|
10
|
+
allowProvisionOnMismatch?: boolean;
|
|
10
11
|
}
|
|
11
12
|
export interface EnsureWorkspaceReadyResult {
|
|
12
13
|
success: boolean;
|
|
@@ -55,6 +56,8 @@ export interface WorkspacePushSyncResult {
|
|
|
55
56
|
changeCount: number;
|
|
56
57
|
deleteCount: number;
|
|
57
58
|
currentTaskIds: Set<string>;
|
|
59
|
+
currentTaskWatermarks?: Map<string, string>;
|
|
60
|
+
currentTaskChangeKeys?: Map<string, string>;
|
|
58
61
|
deleteTaskIds: Set<string>;
|
|
59
62
|
currentInitiativeIds: Set<string>;
|
|
60
63
|
currentInitiativeWatermarks: Map<string, string>;
|
|
@@ -62,6 +65,7 @@ export interface WorkspacePushSyncResult {
|
|
|
62
65
|
currentWorkstreamWatermarks: Map<string, string>;
|
|
63
66
|
currentAiProfileIds: Set<string>;
|
|
64
67
|
currentAiProfileWatermarks: Map<string, string>;
|
|
68
|
+
currentAiProfileChangeKeys?: Map<string, string>;
|
|
65
69
|
currentDocumentPaths: Set<string>;
|
|
66
70
|
deleteDocumentPaths: Set<string>;
|
|
67
71
|
currentDocumentWatermarks: Map<string, string>;
|
|
@@ -80,6 +84,7 @@ export interface WorkspacePushSyncResult {
|
|
|
80
84
|
currentTaskEventWatermarks: Map<string, string>;
|
|
81
85
|
/** Watermarks for every task upsert that was successfully pushed. */
|
|
82
86
|
pushedWatermarks: Map<string, string>;
|
|
87
|
+
pushedTaskChangeKeys?: Map<string, string>;
|
|
83
88
|
/** Server-emitted realtime mutation event ids for this push apply. */
|
|
84
89
|
emittedEventIds: string[];
|
|
85
90
|
/** Authoritative task snapshots returned immediately by the push route for incoming task upserts. */
|
|
@@ -105,6 +110,7 @@ export interface WorkspacePullSyncResult {
|
|
|
105
110
|
success: boolean;
|
|
106
111
|
kind?: WorkspacePullFailureKind;
|
|
107
112
|
status?: number;
|
|
113
|
+
errorCode?: string;
|
|
108
114
|
retryAfterMs?: number;
|
|
109
115
|
transient?: boolean;
|
|
110
116
|
failures?: string[];
|
package/dist/sync/syncService.js
CHANGED
|
@@ -15,6 +15,14 @@ function resolveWorkspaceAccessConflictMessage(data, fallbackStatus) {
|
|
|
15
15
|
}
|
|
16
16
|
return rawError || `Workspace provisioning failed (${fallbackStatus})`;
|
|
17
17
|
}
|
|
18
|
+
function isMissingCloudWorkspaceSelection(data, status) {
|
|
19
|
+
if (status !== 400)
|
|
20
|
+
return false;
|
|
21
|
+
const code = String(data.code || '').trim().toUpperCase();
|
|
22
|
+
const rawError = String(data.error || '').trim().toLowerCase();
|
|
23
|
+
return code === 'WORKSPACE_ID_REQUIRED'
|
|
24
|
+
&& rawError.includes('no cloud workspace is selected');
|
|
25
|
+
}
|
|
18
26
|
export async function ensureCloudWorkspaceReadyForSyncService(input) {
|
|
19
27
|
if (!input.cloudAuthConfigured)
|
|
20
28
|
return { success: false, error: 'Cloud auth endpoint is not configured.' };
|
|
@@ -44,7 +52,48 @@ export async function ensureCloudWorkspaceReadyForSyncService(input) {
|
|
|
44
52
|
error: String(data.error || 'Workspace sync rate limited. Please retry later.')
|
|
45
53
|
};
|
|
46
54
|
}
|
|
55
|
+
if (isMissingCloudWorkspaceSelection(data, handshakeResult.status)) {
|
|
56
|
+
const provisionResult = await provisionWorkspace(input.resolveCloudAuthUrl, {
|
|
57
|
+
workspaceId: input.workspaceId,
|
|
58
|
+
name: input.workspaceName || input.workspaceId
|
|
59
|
+
});
|
|
60
|
+
if (!provisionResult.ok) {
|
|
61
|
+
const provisionError = asObjectRecord(provisionResult.data);
|
|
62
|
+
return {
|
|
63
|
+
success: false,
|
|
64
|
+
statusCode: provisionResult.status,
|
|
65
|
+
transient: isTransientWorkspaceSyncStatus(provisionResult.status),
|
|
66
|
+
retryAfterMs: provisionResult.retryAfterMs,
|
|
67
|
+
error: resolveWorkspaceAccessConflictMessage(provisionError, provisionResult.status)
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
handshakeResult = await handshakeWorkspace(input.resolveCloudAuthUrl, input.workspaceId);
|
|
71
|
+
if (!handshakeResult.ok) {
|
|
72
|
+
const retryData = asObjectRecord(handshakeResult.data);
|
|
73
|
+
if (handshakeResult.status === 401) {
|
|
74
|
+
return { success: false, statusCode: 401, transient: false, error: String(retryData.error || 'Authentication required for workspace sync.') };
|
|
75
|
+
}
|
|
76
|
+
if (handshakeResult.status === 429) {
|
|
77
|
+
return { success: false, statusCode: 429, retryAfterMs: handshakeResult.retryAfterMs, transient: true, error: String(retryData.error || 'Workspace sync rate limited. Please retry later.') };
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
success: false,
|
|
81
|
+
statusCode: handshakeResult.status,
|
|
82
|
+
transient: isTransientWorkspaceSyncStatus(handshakeResult.status),
|
|
83
|
+
error: String(retryData.error || `Workspace sync handshake failed (${handshakeResult.status})`)
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return { success: true, provisioned: true };
|
|
87
|
+
}
|
|
47
88
|
if (handshakeResult.status === 409 && data.code === 'WORKSPACE_ID_MISMATCH') {
|
|
89
|
+
if (input.allowProvisionOnMismatch === false) {
|
|
90
|
+
return {
|
|
91
|
+
success: false,
|
|
92
|
+
statusCode: 409,
|
|
93
|
+
transient: false,
|
|
94
|
+
error: String(data.error || 'Selected cloud workspace could not be attached to this local workspace.')
|
|
95
|
+
};
|
|
96
|
+
}
|
|
48
97
|
const provisionResult = await provisionWorkspace(input.resolveCloudAuthUrl, {
|
|
49
98
|
workspaceId: input.workspaceId,
|
|
50
99
|
name: input.workspaceName || input.workspaceId
|
|
@@ -165,6 +214,8 @@ export async function runWorkspacePushSyncService(input) {
|
|
|
165
214
|
changeCount,
|
|
166
215
|
deleteCount,
|
|
167
216
|
currentTaskIds: input.payload.currentTaskIds,
|
|
217
|
+
currentTaskWatermarks: input.payload.currentTaskWatermarks ?? new Map(),
|
|
218
|
+
currentTaskChangeKeys: input.payload.currentTaskChangeKeys ?? new Map(),
|
|
168
219
|
deleteTaskIds: input.payload.deleteTaskIds,
|
|
169
220
|
currentInitiativeIds: input.payload.currentInitiativeIds,
|
|
170
221
|
currentInitiativeWatermarks: input.payload.currentInitiativeWatermarks,
|
|
@@ -172,6 +223,7 @@ export async function runWorkspacePushSyncService(input) {
|
|
|
172
223
|
currentWorkstreamWatermarks: input.payload.currentWorkstreamWatermarks,
|
|
173
224
|
currentAiProfileIds: input.payload.currentAiProfileIds,
|
|
174
225
|
currentAiProfileWatermarks: input.payload.currentAiProfileWatermarks,
|
|
226
|
+
currentAiProfileChangeKeys: input.payload.currentAiProfileChangeKeys ?? new Map(),
|
|
175
227
|
currentDocumentPaths: input.payload.currentDocumentPaths,
|
|
176
228
|
deleteDocumentPaths: input.payload.deleteDocumentPaths,
|
|
177
229
|
currentDocumentWatermarks: input.payload.currentDocumentWatermarks,
|
|
@@ -189,6 +241,7 @@ export async function runWorkspacePushSyncService(input) {
|
|
|
189
241
|
currentAnnotatedAttachmentSessionWatermarks: input.payload.currentAnnotatedAttachmentSessionWatermarks,
|
|
190
242
|
currentTaskEventWatermarks: input.payload.currentTaskEventWatermarks,
|
|
191
243
|
pushedWatermarks: new Map(),
|
|
244
|
+
pushedTaskChangeKeys: new Map(),
|
|
192
245
|
emittedEventIds: [],
|
|
193
246
|
appliedTaskUpserts: []
|
|
194
247
|
};
|
|
@@ -223,6 +276,8 @@ export async function runWorkspacePushSyncService(input) {
|
|
|
223
276
|
changeCount,
|
|
224
277
|
deleteCount,
|
|
225
278
|
currentTaskIds: input.payload.currentTaskIds,
|
|
279
|
+
currentTaskWatermarks: input.payload.currentTaskWatermarks ?? new Map(),
|
|
280
|
+
currentTaskChangeKeys: input.payload.currentTaskChangeKeys ?? new Map(),
|
|
226
281
|
deleteTaskIds: input.payload.deleteTaskIds,
|
|
227
282
|
currentInitiativeIds: input.payload.currentInitiativeIds,
|
|
228
283
|
currentInitiativeWatermarks: input.payload.currentInitiativeWatermarks,
|
|
@@ -230,6 +285,7 @@ export async function runWorkspacePushSyncService(input) {
|
|
|
230
285
|
currentWorkstreamWatermarks: input.payload.currentWorkstreamWatermarks,
|
|
231
286
|
currentAiProfileIds: input.payload.currentAiProfileIds,
|
|
232
287
|
currentAiProfileWatermarks: input.payload.currentAiProfileWatermarks,
|
|
288
|
+
currentAiProfileChangeKeys: input.payload.currentAiProfileChangeKeys ?? new Map(),
|
|
233
289
|
currentDocumentPaths: input.payload.currentDocumentPaths,
|
|
234
290
|
deleteDocumentPaths: input.payload.deleteDocumentPaths,
|
|
235
291
|
currentDocumentWatermarks: input.payload.currentDocumentWatermarks,
|
|
@@ -247,6 +303,7 @@ export async function runWorkspacePushSyncService(input) {
|
|
|
247
303
|
currentAnnotatedAttachmentSessionWatermarks: input.payload.currentAnnotatedAttachmentSessionWatermarks,
|
|
248
304
|
currentTaskEventWatermarks: input.payload.currentTaskEventWatermarks,
|
|
249
305
|
pushedWatermarks: input.payload.pushedWatermarks,
|
|
306
|
+
pushedTaskChangeKeys: input.payload.pushedTaskChangeKeys ?? new Map(),
|
|
250
307
|
emittedEventIds,
|
|
251
308
|
appliedTaskUpserts
|
|
252
309
|
};
|
|
@@ -288,10 +345,13 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
288
345
|
}
|
|
289
346
|
}
|
|
290
347
|
if (!pullRes.ok) {
|
|
348
|
+
const pullErrorData = asObjectRecord(pullRes.data);
|
|
291
349
|
return {
|
|
292
350
|
success: false,
|
|
293
351
|
kind: 'pull_http',
|
|
294
352
|
status: pullRes.status,
|
|
353
|
+
errorCode: typeof pullErrorData.code === 'string' ? pullErrorData.code : undefined,
|
|
354
|
+
error: typeof pullErrorData.error === 'string' ? pullErrorData.error : undefined,
|
|
295
355
|
retryAfterMs: pullRes.retryAfterMs,
|
|
296
356
|
transient: isTransientWorkspaceSyncStatus(pullRes.status),
|
|
297
357
|
cursor,
|
|
@@ -443,7 +443,7 @@ describe('syncService', () => {
|
|
|
443
443
|
status: 200,
|
|
444
444
|
data: {
|
|
445
445
|
success: true,
|
|
446
|
-
changes: [{ op: 'upsert', task: { id: '
|
|
446
|
+
changes: [{ op: 'upsert', task: { id: 'task-apply-error', title: 'Apply error', status: 'done', updatedAt: '2026-03-15T20:27:08.000Z' } }],
|
|
447
447
|
nextCursor: null,
|
|
448
448
|
hasMore: false
|
|
449
449
|
}
|
|
@@ -452,7 +452,7 @@ describe('syncService', () => {
|
|
|
452
452
|
ok: false,
|
|
453
453
|
status: 400,
|
|
454
454
|
failures: ['/api/taskforce/sync/workspace/apply-local:400'],
|
|
455
|
-
data: { error: '
|
|
455
|
+
data: { error: 'Task apply rejected by local validation' }
|
|
456
456
|
});
|
|
457
457
|
const res = await runWorkspacePullSyncService({
|
|
458
458
|
resolveCloudAuthUrl: mockResolveCloudAuthUrl,
|
|
@@ -463,7 +463,7 @@ describe('syncService', () => {
|
|
|
463
463
|
expect(res.success).toBe(false);
|
|
464
464
|
expect(res.kind).toBe('apply_all_candidates');
|
|
465
465
|
expect(res.status).toBe(400);
|
|
466
|
-
expect(res.error).toBe('
|
|
466
|
+
expect(res.error).toBe('Task apply rejected by local validation');
|
|
467
467
|
});
|
|
468
468
|
it('surfaces document decryption failures without failing pull', async () => {
|
|
469
469
|
const previousKey = process.env.TASKFORCE_SYNC_KEY;
|
|
@@ -702,6 +702,60 @@ describe('syncService', () => {
|
|
|
702
702
|
expect(res.statusCode).toBe(0);
|
|
703
703
|
expect(res.transient).toBe(true);
|
|
704
704
|
});
|
|
705
|
+
it('recovers from missing cloud workspace selection by provisioning and retrying handshake', async () => {
|
|
706
|
+
vi.mocked(handshakeWorkspace)
|
|
707
|
+
.mockResolvedValueOnce({
|
|
708
|
+
ok: false,
|
|
709
|
+
status: 400,
|
|
710
|
+
data: {
|
|
711
|
+
code: 'WORKSPACE_ID_REQUIRED',
|
|
712
|
+
error: 'No cloud workspace is selected. Complete workspace setup first.'
|
|
713
|
+
}
|
|
714
|
+
})
|
|
715
|
+
.mockResolvedValueOnce({
|
|
716
|
+
ok: true,
|
|
717
|
+
status: 200,
|
|
718
|
+
data: {}
|
|
719
|
+
});
|
|
720
|
+
vi.mocked(provisionWorkspace).mockResolvedValueOnce({
|
|
721
|
+
ok: true,
|
|
722
|
+
status: 200,
|
|
723
|
+
data: {
|
|
724
|
+
success: true,
|
|
725
|
+
existing: true
|
|
726
|
+
}
|
|
727
|
+
});
|
|
728
|
+
const res = await ensureCloudWorkspaceReadyForSyncService(baseHandshakeInput);
|
|
729
|
+
expect(res.success).toBe(true);
|
|
730
|
+
expect(res.provisioned).toBe(true);
|
|
731
|
+
expect(provisionWorkspace).toHaveBeenCalledWith(mockResolveCloudAuthUrl, {
|
|
732
|
+
workspaceId: 'ws-test',
|
|
733
|
+
name: 'Test Workspace'
|
|
734
|
+
});
|
|
735
|
+
expect(handshakeWorkspace).toHaveBeenCalledTimes(2);
|
|
736
|
+
});
|
|
737
|
+
it('surfaces provisioning failure when recovering missing cloud workspace selection', async () => {
|
|
738
|
+
vi.mocked(handshakeWorkspace).mockResolvedValueOnce({
|
|
739
|
+
ok: false,
|
|
740
|
+
status: 400,
|
|
741
|
+
data: {
|
|
742
|
+
code: 'WORKSPACE_ID_REQUIRED',
|
|
743
|
+
error: 'No cloud workspace is selected. Complete workspace setup first.'
|
|
744
|
+
}
|
|
745
|
+
});
|
|
746
|
+
vi.mocked(provisionWorkspace).mockResolvedValueOnce({
|
|
747
|
+
ok: false,
|
|
748
|
+
status: 409,
|
|
749
|
+
data: {
|
|
750
|
+
code: 'WORKSPACE_ID_ALREADY_EXISTS',
|
|
751
|
+
error: 'Workspace ID already exists'
|
|
752
|
+
}
|
|
753
|
+
});
|
|
754
|
+
const res = await ensureCloudWorkspaceReadyForSyncService(baseHandshakeInput);
|
|
755
|
+
expect(res.success).toBe(false);
|
|
756
|
+
expect(res.statusCode).toBe(409);
|
|
757
|
+
expect(res.error).toContain('already linked to another cloud account');
|
|
758
|
+
});
|
|
705
759
|
it('returns failure when cloudAuthConfigured is false', async () => {
|
|
706
760
|
const res = await ensureCloudWorkspaceReadyForSyncService({
|
|
707
761
|
...baseHandshakeInput,
|
|
@@ -743,6 +797,20 @@ describe('syncService', () => {
|
|
|
743
797
|
expect(res.error).toContain('already linked to another cloud account');
|
|
744
798
|
expect(res.error).toContain('grant you access');
|
|
745
799
|
});
|
|
800
|
+
it('does not provision a new cloud workspace during attach-cloud mismatch handling', async () => {
|
|
801
|
+
vi.mocked(handshakeWorkspace).mockResolvedValueOnce({
|
|
802
|
+
ok: false,
|
|
803
|
+
status: 409,
|
|
804
|
+
data: { code: 'WORKSPACE_ID_MISMATCH', error: 'Workspace mismatch' }
|
|
805
|
+
});
|
|
806
|
+
const res = await ensureCloudWorkspaceReadyForSyncService({
|
|
807
|
+
...baseHandshakeInput,
|
|
808
|
+
allowProvisionOnMismatch: false
|
|
809
|
+
});
|
|
810
|
+
expect(res.success).toBe(false);
|
|
811
|
+
expect(res.statusCode).toBe(409);
|
|
812
|
+
expect(provisionWorkspace).not.toHaveBeenCalled();
|
|
813
|
+
});
|
|
746
814
|
});
|
|
747
815
|
describe('syncUserGlobalSettingsService transient metadata', () => {
|
|
748
816
|
beforeEach(() => {
|