@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
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { IncomingMessage } from 'http';
|
|
2
|
+
import type { TaskforceCore } from '../../core/Taskforce.js';
|
|
3
|
+
import type { RouteContext } from './primitives.js';
|
|
4
|
+
export declare function resolveBooleanSetting(value: unknown, fallback: boolean): boolean;
|
|
5
|
+
export declare function getRouteEmailConfig(core: TaskforceCore, context: RouteContext): import("../email.js").TaskforceEmailConfig;
|
|
6
|
+
export declare function buildAuthFlowBehavior(core: TaskforceCore): {
|
|
7
|
+
allowSelfRegistration: boolean;
|
|
8
|
+
requireVerifiedEmail: boolean;
|
|
9
|
+
defaultNewUserBetaAccess: boolean;
|
|
10
|
+
authRateLimitEnabled: boolean;
|
|
11
|
+
authRateLimitMaxRequests: number;
|
|
12
|
+
authRateLimitWindowMs: number;
|
|
13
|
+
};
|
|
14
|
+
export declare function buildAuthBehavior(core: TaskforceCore): {
|
|
15
|
+
onboardingPolicyMode: import("../../core/types.js").OnboardingPolicyMode;
|
|
16
|
+
onboardingPlanId: string | null;
|
|
17
|
+
allowSelfRegistration: boolean;
|
|
18
|
+
requireVerifiedEmail: boolean;
|
|
19
|
+
defaultNewUserBetaAccess: boolean;
|
|
20
|
+
authRateLimitEnabled: boolean;
|
|
21
|
+
authRateLimitMaxRequests: number;
|
|
22
|
+
authRateLimitWindowMs: number;
|
|
23
|
+
};
|
|
24
|
+
export declare function buildSiteBehavior(core: TaskforceCore): {
|
|
25
|
+
showRunTaskforceLocally: boolean;
|
|
26
|
+
pricingPageEnabled: boolean;
|
|
27
|
+
};
|
|
28
|
+
export declare function shouldExposeDevAuthTokens(): boolean;
|
|
29
|
+
export declare function resolveRuntimeConfigBaseUrl(req: IncomingMessage): string;
|
|
30
|
+
export declare function resolvePublicAuthBaseUrl(req: IncomingMessage): string;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { resolveEffectiveEmailConfig } from '../email.js';
|
|
2
|
+
import { resolveDeploymentConfig } from '../../config/deployment.js';
|
|
3
|
+
export function resolveBooleanSetting(value, fallback) {
|
|
4
|
+
if (typeof value === 'boolean')
|
|
5
|
+
return value;
|
|
6
|
+
if (typeof value === 'number')
|
|
7
|
+
return value !== 0;
|
|
8
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
9
|
+
if (!normalized)
|
|
10
|
+
return fallback;
|
|
11
|
+
if (['1', 'true', 'yes', 'on'].includes(normalized))
|
|
12
|
+
return true;
|
|
13
|
+
if (['0', 'false', 'no', 'off'].includes(normalized))
|
|
14
|
+
return false;
|
|
15
|
+
return fallback;
|
|
16
|
+
}
|
|
17
|
+
export function getRouteEmailConfig(core, context) {
|
|
18
|
+
if (context.authConfig?.runtimeMode !== 'cloud') {
|
|
19
|
+
return resolveEffectiveEmailConfig(process.env, { provider: 'console' });
|
|
20
|
+
}
|
|
21
|
+
const globalSettings = core.getGlobalSettings();
|
|
22
|
+
return resolveEffectiveEmailConfig(process.env, globalSettings.email || null);
|
|
23
|
+
}
|
|
24
|
+
export function buildAuthFlowBehavior(core) {
|
|
25
|
+
const globalSettings = core.getGlobalSettings();
|
|
26
|
+
const authSettings = globalSettings.auth || {};
|
|
27
|
+
const authRateLimitMaxRequestsRaw = Number(authSettings.authRateLimitMaxRequests);
|
|
28
|
+
const authRateLimitWindowMsRaw = Number(authSettings.authRateLimitWindowMs);
|
|
29
|
+
return {
|
|
30
|
+
allowSelfRegistration: resolveBooleanSetting(authSettings.allowSelfRegistration, true),
|
|
31
|
+
requireVerifiedEmail: resolveBooleanSetting(authSettings.requireVerifiedEmail, true),
|
|
32
|
+
defaultNewUserBetaAccess: resolveBooleanSetting(authSettings.defaultNewUserBetaAccess, false),
|
|
33
|
+
authRateLimitEnabled: resolveBooleanSetting(authSettings.authRateLimitEnabled, true),
|
|
34
|
+
authRateLimitMaxRequests: Number.isFinite(authRateLimitMaxRequestsRaw) && authRateLimitMaxRequestsRaw >= 1
|
|
35
|
+
? Math.floor(authRateLimitMaxRequestsRaw)
|
|
36
|
+
: 10,
|
|
37
|
+
authRateLimitWindowMs: Number.isFinite(authRateLimitWindowMsRaw) && authRateLimitWindowMsRaw >= 1000
|
|
38
|
+
? Math.floor(authRateLimitWindowMsRaw)
|
|
39
|
+
: 15 * 60_000,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export function buildAuthBehavior(core) {
|
|
43
|
+
const authBehavior = buildAuthFlowBehavior(core);
|
|
44
|
+
const onboardingPolicy = core.getOnboardingPolicy();
|
|
45
|
+
return {
|
|
46
|
+
...authBehavior,
|
|
47
|
+
onboardingPolicyMode: onboardingPolicy.mode,
|
|
48
|
+
onboardingPlanId: onboardingPolicy.planId
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export function buildSiteBehavior(core) {
|
|
52
|
+
const globalSettings = core.getGlobalSettings();
|
|
53
|
+
const siteSettings = globalSettings.site || {};
|
|
54
|
+
return {
|
|
55
|
+
showRunTaskforceLocally: resolveBooleanSetting(siteSettings.showRunTaskforceLocally, true),
|
|
56
|
+
pricingPageEnabled: resolveBooleanSetting(siteSettings.pricingPageEnabled, true)
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export function shouldExposeDevAuthTokens() {
|
|
60
|
+
const explicit = String(process.env.TASKFORCE_AUTH_EXPOSE_DEV_TOKENS || '').trim().toLowerCase();
|
|
61
|
+
return explicit === '1' || explicit === 'true' || explicit === 'yes';
|
|
62
|
+
}
|
|
63
|
+
export function resolveRuntimeConfigBaseUrl(req) {
|
|
64
|
+
const host = String(req.headers['x-forwarded-host'] || req.headers.host || '').trim();
|
|
65
|
+
if (!host)
|
|
66
|
+
return '';
|
|
67
|
+
const proto = String(req.headers['x-forwarded-proto'] || '').trim().toLowerCase() === 'https'
|
|
68
|
+
? 'https'
|
|
69
|
+
: (req.socket?.encrypted ? 'https' : 'http');
|
|
70
|
+
return `${proto}://${host}`.replace(/\/+$/, '');
|
|
71
|
+
}
|
|
72
|
+
export function resolvePublicAuthBaseUrl(req) {
|
|
73
|
+
const configured = String(process.env.TASKFORCE_AUTH_PUBLIC_BASE_URL || '').trim().replace(/\/+$/, '');
|
|
74
|
+
if (configured)
|
|
75
|
+
return configured;
|
|
76
|
+
const host = String(req.headers['x-forwarded-host'] || req.headers.host || '').trim();
|
|
77
|
+
const proto = String(req.headers['x-forwarded-proto'] || '').trim().toLowerCase() === 'http' ? 'http' : 'https';
|
|
78
|
+
if (!host)
|
|
79
|
+
return resolveDeploymentConfig(process.env).cloudBaseUrl || 'https://app.taskforcehq.ai';
|
|
80
|
+
return `${proto}://${host}`;
|
|
81
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { IncomingMessage } from 'http';
|
|
2
2
|
import type { TaskforceCore } from '../../core/Taskforce.js';
|
|
3
|
-
import type
|
|
3
|
+
import { type RouteHandler, type RouteContext } from './primitives.js';
|
|
4
4
|
export type BillingInterval = 'month' | 'year';
|
|
5
5
|
export type BillingPricingType = 'stripe' | 'free';
|
|
6
6
|
export interface BillingRouteDependencies {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Stripe from 'stripe';
|
|
2
|
-
import { parseJsonBody } from '
|
|
2
|
+
import { parseJsonBody } from './primitives.js';
|
|
3
3
|
import { resolveTaskforceCloudEnvironmentFromHostname } from '../../config/cloudEnvironment.js';
|
|
4
4
|
import { buildMockStripeHostedCheckoutUrl, completeMockStripeCheckoutSession, createMockStripeClient, isMockStripeClient, isMockStripeEnabled, readMockStripeCheckoutSession, renderMockStripeCheckoutHtml } from '../mockStripe.js';
|
|
5
5
|
const BILLING_CONFIG_KEY_PLAN = 'billing.config.v1';
|
|
@@ -8,16 +8,63 @@ const BILLING_DIAGNOSTIC_RETENTION_DAYS = 30;
|
|
|
8
8
|
const BILLING_DIAGNOSTIC_MAX_PAYLOAD_BYTES = 8 * 1024;
|
|
9
9
|
const BILLING_CHECKOUT_ATTEMPT_RETENTION_DAYS = 14;
|
|
10
10
|
const BILLING_STATUS_RECONCILIATION_TTL_MS = 30_000;
|
|
11
|
+
const PUBLIC_PRICING_CACHE_TTL_MS = 60_000;
|
|
11
12
|
const TASKFORCE_PROXY_ORIGIN_HOST_HEADER = 'x-taskforce-origin-host';
|
|
12
13
|
const TASKFORCE_PROXY_ORIGIN_PROTO_HEADER = 'x-taskforce-origin-proto';
|
|
13
14
|
const TASKFORCE_PROXY_ORIGIN_BASE_URL_HEADER = 'x-taskforce-origin-base-url';
|
|
14
15
|
const TASKFORCE_PROXY_ORIGIN_RUNTIME_HEADER = 'x-taskforce-origin-runtime';
|
|
16
|
+
const INTERNAL_PUBLIC_PRICING_FEATURE_KEYS = new Set([
|
|
17
|
+
'collaboration.invites',
|
|
18
|
+
'collaboration.seat_limit'
|
|
19
|
+
]);
|
|
20
|
+
const publicPricingCache = new Map();
|
|
15
21
|
function parsePricingType(raw) {
|
|
16
22
|
const value = String(raw || '').trim().toLowerCase();
|
|
17
23
|
if (value === 'stripe' || value === 'free')
|
|
18
24
|
return value;
|
|
19
25
|
return null;
|
|
20
26
|
}
|
|
27
|
+
function readTableVersionStats(core, tableName) {
|
|
28
|
+
const db = core.getDatabaseAdapter();
|
|
29
|
+
const tenantId = core.getTenantId();
|
|
30
|
+
try {
|
|
31
|
+
const row = db.prepare(`
|
|
32
|
+
SELECT COUNT(*) AS count, MAX(updated_at) AS max_updated_at
|
|
33
|
+
FROM ${tableName}
|
|
34
|
+
WHERE tenant_id = ?
|
|
35
|
+
`).get(tenantId);
|
|
36
|
+
return {
|
|
37
|
+
count: Number(row?.count || 0),
|
|
38
|
+
maxUpdatedAt: String(row?.max_updated_at || '')
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
const message = String(error?.message || '');
|
|
43
|
+
if (message.toLowerCase().includes(`no such table: ${tableName}`)) {
|
|
44
|
+
return { count: 0, maxUpdatedAt: '' };
|
|
45
|
+
}
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function buildPublicPricingCacheVersion(core) {
|
|
50
|
+
const catalog = readTableVersionStats(core, 'plan_catalog');
|
|
51
|
+
const versions = readTableVersionStats(core, 'plan_versions');
|
|
52
|
+
const prices = readTableVersionStats(core, 'billing_price_mappings');
|
|
53
|
+
const features = readTableVersionStats(core, 'plan_feature_matrix');
|
|
54
|
+
const featureCatalogOverrides = readTableVersionStats(core, 'plan_feature_catalog_overrides');
|
|
55
|
+
return [
|
|
56
|
+
catalog.count,
|
|
57
|
+
catalog.maxUpdatedAt,
|
|
58
|
+
versions.count,
|
|
59
|
+
versions.maxUpdatedAt,
|
|
60
|
+
prices.count,
|
|
61
|
+
prices.maxUpdatedAt,
|
|
62
|
+
features.count,
|
|
63
|
+
features.maxUpdatedAt,
|
|
64
|
+
featureCatalogOverrides.count,
|
|
65
|
+
featureCatalogOverrides.maxUpdatedAt
|
|
66
|
+
].join('|');
|
|
67
|
+
}
|
|
21
68
|
function buildFreePricingSyntheticId(planVersionId, interval) {
|
|
22
69
|
return `free:${planVersionId}:${interval}`;
|
|
23
70
|
}
|
|
@@ -664,7 +711,11 @@ function resolveCheckoutLifecycleGuard(core, input) {
|
|
|
664
711
|
: currentOpenAttempt;
|
|
665
712
|
const attemptStatus = String(checkoutAttempt?.status || '').trim().toLowerCase();
|
|
666
713
|
const attemptPlanVersionId = String(checkoutAttempt?.plan_version_id || '').trim();
|
|
714
|
+
const sourceLabel = String(input.source || 'checkout-confirm').trim() || 'checkout-confirm';
|
|
667
715
|
if (attemptStatus === 'expired') {
|
|
716
|
+
console.warn(`[Taskforce Billing] Lifecycle guard blocked expired checkout for user=${input.userId} source=${sourceLabel} `
|
|
717
|
+
+ `session=${String(input.sessionId || '').trim() || '(none)'} subscription=${String(input.subscriptionId || '').trim() || '(none)'} `
|
|
718
|
+
+ `requestedPlanVersionId=${String(input.planVersionId || '').trim() || '(none)'} attemptPlanVersionId=${attemptPlanVersionId || '(none)'}`);
|
|
668
719
|
return {
|
|
669
720
|
blocked: true,
|
|
670
721
|
code: 'CHECKOUT_SESSION_EXPIRED',
|
|
@@ -672,6 +723,9 @@ function resolveCheckoutLifecycleGuard(core, input) {
|
|
|
672
723
|
};
|
|
673
724
|
}
|
|
674
725
|
if (attemptStatus === 'superseded') {
|
|
726
|
+
console.warn(`[Taskforce Billing] Lifecycle guard blocked superseded checkout for user=${input.userId} source=${sourceLabel} `
|
|
727
|
+
+ `session=${String(input.sessionId || '').trim() || '(none)'} subscription=${String(input.subscriptionId || '').trim() || '(none)'} `
|
|
728
|
+
+ `requestedPlanVersionId=${String(input.planVersionId || '').trim() || '(none)'} attemptPlanVersionId=${attemptPlanVersionId || '(none)'}`);
|
|
675
729
|
return {
|
|
676
730
|
blocked: true,
|
|
677
731
|
code: 'CHECKOUT_SESSION_SUPERSEDED',
|
|
@@ -679,6 +733,9 @@ function resolveCheckoutLifecycleGuard(core, input) {
|
|
|
679
733
|
};
|
|
680
734
|
}
|
|
681
735
|
if (attemptStatus === 'open' && attemptPlanVersionId && input.planVersionId && attemptPlanVersionId !== input.planVersionId) {
|
|
736
|
+
console.warn(`[Taskforce Billing] Lifecycle guard blocked mismatched open checkout for user=${input.userId} source=${sourceLabel} `
|
|
737
|
+
+ `session=${String(input.sessionId || '').trim() || '(none)'} subscription=${String(input.subscriptionId || '').trim() || '(none)'} `
|
|
738
|
+
+ `requestedPlanVersionId=${String(input.planVersionId || '').trim() || '(none)'} attemptPlanVersionId=${attemptPlanVersionId}`);
|
|
682
739
|
return {
|
|
683
740
|
blocked: true,
|
|
684
741
|
code: 'CHECKOUT_SESSION_SUPERSEDED',
|
|
@@ -688,6 +745,9 @@ function resolveCheckoutLifecycleGuard(core, input) {
|
|
|
688
745
|
const currentBilling = getBillingMirrorRow(core, input.userId);
|
|
689
746
|
const currentSubscriptionId = String(currentBilling?.stripe_subscription_id || '').trim();
|
|
690
747
|
if (!currentOpenAttempt && input.subscriptionId && currentSubscriptionId && currentSubscriptionId !== input.subscriptionId) {
|
|
748
|
+
console.warn(`[Taskforce Billing] Lifecycle guard blocked stale subscription linkage for user=${input.userId} source=${sourceLabel} `
|
|
749
|
+
+ `currentSubscriptionId=${currentSubscriptionId} requestedSubscriptionId=${String(input.subscriptionId || '').trim() || '(none)'} `
|
|
750
|
+
+ `requestedPlanVersionId=${String(input.planVersionId || '').trim() || '(none)'}`);
|
|
691
751
|
return {
|
|
692
752
|
blocked: true,
|
|
693
753
|
code: 'CHECKOUT_SESSION_SUPERSEDED',
|
|
@@ -701,17 +761,28 @@ function resolveCheckoutLifecycleGuard(core, input) {
|
|
|
701
761
|
const currentPlanVersionId = String(entitlement.plan_version_id || '').trim();
|
|
702
762
|
const updatedBy = String(entitlement.updated_by || '').trim().toLowerCase();
|
|
703
763
|
const expiredPendingCheckout = currentState === 'checkout_pending' && isIsoTimestampExpired(entitlement.effective_until);
|
|
704
|
-
|
|
764
|
+
const fromStripeWebhook = input.source === 'stripe-webhook';
|
|
765
|
+
if (!fromStripeWebhook && (expiredPendingCheckout || (currentState === 'pending_plan_selection' && updatedBy === 'checkout-expired'))) {
|
|
766
|
+
console.warn(`[Taskforce Billing] Lifecycle guard blocked expired entitlement recovery for user=${input.userId} source=${sourceLabel} `
|
|
767
|
+
+ `entitlementState=${currentState || '(none)'} updatedBy=${updatedBy || '(none)'} `
|
|
768
|
+
+ `requestedPlanVersionId=${String(input.planVersionId || '').trim() || '(none)'} currentPlanVersionId=${currentPlanVersionId || '(none)'}`);
|
|
705
769
|
return {
|
|
706
770
|
blocked: true,
|
|
707
771
|
code: 'CHECKOUT_SESSION_EXPIRED',
|
|
708
772
|
message: 'This checkout session expired. Choose a plan and start checkout again.'
|
|
709
773
|
};
|
|
710
774
|
}
|
|
775
|
+
if (fromStripeWebhook && (expiredPendingCheckout || (currentState === 'pending_plan_selection' && updatedBy === 'checkout-expired'))) {
|
|
776
|
+
console.info(`[Taskforce Billing] Lifecycle guard allowed authoritative webhook recovery for user=${input.userId} `
|
|
777
|
+
+ `subscription=${String(input.subscriptionId || '').trim() || '(none)'} requestedPlanVersionId=${String(input.planVersionId || '').trim() || '(none)'} `
|
|
778
|
+
+ `entitlementState=${currentState || '(none)'} updatedBy=${updatedBy || '(none)'}`);
|
|
779
|
+
}
|
|
711
780
|
if (currentState === 'checkout_pending'
|
|
712
781
|
&& currentPlanVersionId
|
|
713
782
|
&& input.planVersionId
|
|
714
783
|
&& currentPlanVersionId !== input.planVersionId) {
|
|
784
|
+
console.warn(`[Taskforce Billing] Lifecycle guard blocked superseded checkout_pending entitlement for user=${input.userId} source=${sourceLabel} `
|
|
785
|
+
+ `currentPlanVersionId=${currentPlanVersionId} requestedPlanVersionId=${String(input.planVersionId || '').trim() || '(none)'}`);
|
|
715
786
|
return {
|
|
716
787
|
blocked: true,
|
|
717
788
|
code: 'CHECKOUT_SESSION_SUPERSEDED',
|
|
@@ -795,11 +866,15 @@ function applySubscriptionSnapshot(core, input) {
|
|
|
795
866
|
? (currentPeriodEnd && currentPeriodEnd > nowIso ? 'active' : 'canceled')
|
|
796
867
|
: null;
|
|
797
868
|
const mappedCanonicalState = canceledState || mapStripeStatusToCanonicalState(stripeStatus);
|
|
869
|
+
const existingPlanVersionId = String(existingEntitlement?.plan_version_id || '').trim();
|
|
798
870
|
const preserveExistingAccess = mappedCanonicalState === 'checkout_pending'
|
|
871
|
+
&& Boolean(planVersionId)
|
|
872
|
+
&& existingPlanVersionId === planVersionId
|
|
799
873
|
&& isCommercialStateAppAccessible(existingEntitlement?.entitlement_state);
|
|
800
874
|
const canonicalState = preserveExistingAccess
|
|
801
875
|
? String(existingEntitlement?.entitlement_state || 'active').trim().toLowerCase()
|
|
802
876
|
: mappedCanonicalState;
|
|
877
|
+
const billingConfirmedSource = source === 'stripe-webhook' || source === 'checkout-confirm';
|
|
803
878
|
const effectiveUntil = stripeStatus === 'canceled'
|
|
804
879
|
? currentPeriodEnd
|
|
805
880
|
: (currentPeriodEnd || null);
|
|
@@ -826,7 +901,7 @@ function applySubscriptionSnapshot(core, input) {
|
|
|
826
901
|
});
|
|
827
902
|
}
|
|
828
903
|
}
|
|
829
|
-
else if (planVersionId) {
|
|
904
|
+
else if (planVersionId && billingConfirmedSource) {
|
|
830
905
|
core.transitionCanonicalCommercialState({
|
|
831
906
|
accountId: userId,
|
|
832
907
|
planVersionId: planVersionId || existingEntitlement?.plan_version_id || core.getDefaultPlanVersionIdByMode('personal', { allowFallback: true }),
|
|
@@ -1402,6 +1477,23 @@ export function registerBillingRoutes(deps) {
|
|
|
1402
1477
|
res.end(JSON.stringify({ error: `Stripe price interval (${stripeInterval}) does not match requested interval (${interval}).`, code: 'STRIPE_PRICE_INTERVAL_MISMATCH' }));
|
|
1403
1478
|
return;
|
|
1404
1479
|
}
|
|
1480
|
+
const conflictingMapping = db.prepare(`
|
|
1481
|
+
SELECT plan_version_id
|
|
1482
|
+
FROM billing_price_mappings
|
|
1483
|
+
WHERE tenant_id = ?
|
|
1484
|
+
AND stripe_price_id = ?
|
|
1485
|
+
AND active = 1
|
|
1486
|
+
AND plan_version_id <> ?
|
|
1487
|
+
LIMIT 1
|
|
1488
|
+
`).get(tenantId, stripePriceId, planVersionId);
|
|
1489
|
+
if (conflictingMapping?.plan_version_id) {
|
|
1490
|
+
res.writeHead(409, { 'Content-Type': 'application/json' });
|
|
1491
|
+
res.end(JSON.stringify({
|
|
1492
|
+
error: `Stripe price ${stripePriceId} is already mapped to ${String(conflictingMapping.plan_version_id)}.`,
|
|
1493
|
+
code: 'BILLING_PRICE_ALREADY_ASSIGNED'
|
|
1494
|
+
}));
|
|
1495
|
+
return;
|
|
1496
|
+
}
|
|
1405
1497
|
unitAmount = typeof stripePrice.unit_amount === 'number' ? stripePrice.unit_amount : null;
|
|
1406
1498
|
currency = stripePrice.currency || null;
|
|
1407
1499
|
}
|
|
@@ -1769,8 +1861,8 @@ export function registerBillingRoutes(deps) {
|
|
|
1769
1861
|
const stripe = getStripeClient();
|
|
1770
1862
|
let currentSubscriptionId = String(currentBilling?.stripe_subscription_id || '').trim();
|
|
1771
1863
|
const currentCustomerId = String(currentBilling?.stripe_customer_id || '').trim();
|
|
1772
|
-
|
|
1773
|
-
|
|
1864
|
+
let currentPlanVersionId = String(currentEntitlement?.plan_version_id || '').trim();
|
|
1865
|
+
let currentInterval = parseInterval(String(currentBilling?.billing_interval || '').trim());
|
|
1774
1866
|
const stripeAccountDiscovery = await discoverStripeAccountStateForUser(core, {
|
|
1775
1867
|
userId,
|
|
1776
1868
|
normalizedEmail: normalizedUserEmail,
|
|
@@ -1786,8 +1878,13 @@ export function registerBillingRoutes(deps) {
|
|
|
1786
1878
|
return;
|
|
1787
1879
|
}
|
|
1788
1880
|
let resolvedCustomerId = currentCustomerId || String(stripeAccountDiscovery.preferredCustomerId || '').trim();
|
|
1789
|
-
|
|
1790
|
-
|
|
1881
|
+
const activeSubscription = stripeAccountDiscovery.activeSubscriptions.length === 1
|
|
1882
|
+
? stripeAccountDiscovery.activeSubscriptions[0]
|
|
1883
|
+
: null;
|
|
1884
|
+
let discoveredPlanVersionId = null;
|
|
1885
|
+
let discoveredInterval = null;
|
|
1886
|
+
let discoveredStripePriceId = null;
|
|
1887
|
+
if (!currentSubscriptionId && activeSubscription) {
|
|
1791
1888
|
currentSubscriptionId = String(activeSubscription.subscription?.id || '').trim();
|
|
1792
1889
|
resolvedCustomerId = String(activeSubscription.customerId || '').trim() || resolvedCustomerId;
|
|
1793
1890
|
if (currentSubscriptionId || resolvedCustomerId) {
|
|
@@ -1804,7 +1901,28 @@ export function registerBillingRoutes(deps) {
|
|
|
1804
1901
|
stripeCustomerId: resolvedCustomerId
|
|
1805
1902
|
});
|
|
1806
1903
|
}
|
|
1807
|
-
if (
|
|
1904
|
+
if (activeSubscription) {
|
|
1905
|
+
const activeSubscriptionPriceId = String(activeSubscription.subscription?.items?.data?.[0]?.price?.id || '').trim();
|
|
1906
|
+
discoveredStripePriceId = activeSubscriptionPriceId || null;
|
|
1907
|
+
const activePlanVersionId = resolvePlanVersionIdForBillingEvent(core, [
|
|
1908
|
+
activeSubscription.subscription?.metadata?.planVersionId,
|
|
1909
|
+
activeSubscription.subscription?.metadata?.plan_version_id
|
|
1910
|
+
], activeSubscriptionPriceId).planVersionId;
|
|
1911
|
+
discoveredPlanVersionId = activePlanVersionId;
|
|
1912
|
+
discoveredInterval = parseInterval(activeSubscription.subscription?.items?.data?.[0]?.price?.recurring?.interval || '');
|
|
1913
|
+
if (!currentPlanVersionId && activePlanVersionId) {
|
|
1914
|
+
currentPlanVersionId = activePlanVersionId;
|
|
1915
|
+
}
|
|
1916
|
+
if (!currentInterval) {
|
|
1917
|
+
currentInterval = discoveredInterval;
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
const isCurrentRequestedPlan = (currentPlanVersionId === planVersionId && currentInterval === interval) || (Boolean(currentSubscriptionId)
|
|
1921
|
+
&& discoveredPlanVersionId === planVersionId
|
|
1922
|
+
&& discoveredInterval === interval) || (Boolean(currentSubscriptionId)
|
|
1923
|
+
&& discoveredStripePriceId === priceId
|
|
1924
|
+
&& discoveredInterval === interval);
|
|
1925
|
+
if (currentSubscriptionId && isCurrentRequestedPlan) {
|
|
1808
1926
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1809
1927
|
res.end(JSON.stringify({
|
|
1810
1928
|
checkoutState: 'already_current_plan',
|
|
@@ -1816,6 +1934,17 @@ export function registerBillingRoutes(deps) {
|
|
|
1816
1934
|
if (currentSubscriptionId) {
|
|
1817
1935
|
const current = await stripe.subscriptions.retrieve(currentSubscriptionId);
|
|
1818
1936
|
const itemId = current.items.data[0]?.id;
|
|
1937
|
+
const currentStripePriceId = String(current.items.data[0]?.price?.id || '').trim();
|
|
1938
|
+
const currentSubscriptionInterval = parseInterval(current.items.data[0]?.price?.recurring?.interval || '');
|
|
1939
|
+
if (currentStripePriceId === priceId && currentSubscriptionInterval === interval) {
|
|
1940
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1941
|
+
res.end(JSON.stringify({
|
|
1942
|
+
checkoutState: 'already_current_plan',
|
|
1943
|
+
planVersionId,
|
|
1944
|
+
message: 'You are already on this plan.'
|
|
1945
|
+
}));
|
|
1946
|
+
return;
|
|
1947
|
+
}
|
|
1819
1948
|
if (!itemId) {
|
|
1820
1949
|
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1821
1950
|
res.end(JSON.stringify({ error: 'Subscription has no updatable line item.' }));
|
|
@@ -1840,7 +1969,7 @@ export function registerBillingRoutes(deps) {
|
|
|
1840
1969
|
res.end(JSON.stringify({
|
|
1841
1970
|
checkoutState: 'updated',
|
|
1842
1971
|
planVersionId,
|
|
1843
|
-
message: 'Your subscription was
|
|
1972
|
+
message: 'Your subscription update was submitted. Changes will appear after Stripe confirms the update.'
|
|
1844
1973
|
}));
|
|
1845
1974
|
return;
|
|
1846
1975
|
}
|
|
@@ -1857,10 +1986,17 @@ export function registerBillingRoutes(deps) {
|
|
|
1857
1986
|
planVersionId,
|
|
1858
1987
|
interval
|
|
1859
1988
|
});
|
|
1989
|
+
const successParams = new URLSearchParams({
|
|
1990
|
+
screen: 'plans',
|
|
1991
|
+
checkout: 'success',
|
|
1992
|
+
planId: String(planId || ''),
|
|
1993
|
+
planVersionId,
|
|
1994
|
+
interval
|
|
1995
|
+
});
|
|
1860
1996
|
const session = await stripe.checkout.sessions.create({
|
|
1861
1997
|
mode: 'subscription',
|
|
1862
1998
|
line_items: [{ price: priceId, quantity: 1 }],
|
|
1863
|
-
success_url: `${baseUrl}
|
|
1999
|
+
success_url: `${baseUrl}/?${successParams.toString()}&session_id={CHECKOUT_SESSION_ID}`,
|
|
1864
2000
|
cancel_url: `${baseUrl}/?${cancelParams.toString()}`,
|
|
1865
2001
|
...(resolvedCustomerId
|
|
1866
2002
|
? { customer: resolvedCustomerId }
|
|
@@ -1979,7 +2115,10 @@ export function registerBillingRoutes(deps) {
|
|
|
1979
2115
|
}
|
|
1980
2116
|
if (resolvedUserId !== expectedUserId) {
|
|
1981
2117
|
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
1982
|
-
res.end(JSON.stringify({
|
|
2118
|
+
res.end(JSON.stringify({
|
|
2119
|
+
error: 'Checkout session does not belong to the current account.',
|
|
2120
|
+
code: 'CHECKOUT_SESSION_ACCOUNT_MISMATCH'
|
|
2121
|
+
}));
|
|
1983
2122
|
return;
|
|
1984
2123
|
}
|
|
1985
2124
|
let resolvedPlanVersionId = null;
|
|
@@ -1999,7 +2138,8 @@ export function registerBillingRoutes(deps) {
|
|
|
1999
2138
|
userId: resolvedUserId,
|
|
2000
2139
|
planVersionId: resolvedPlanVersionId,
|
|
2001
2140
|
sessionId,
|
|
2002
|
-
subscriptionId
|
|
2141
|
+
subscriptionId,
|
|
2142
|
+
source: 'checkout-confirm'
|
|
2003
2143
|
});
|
|
2004
2144
|
if (lifecycleGuard.blocked) {
|
|
2005
2145
|
res.writeHead(409, { 'Content-Type': 'application/json' });
|
|
@@ -2010,20 +2150,54 @@ export function registerBillingRoutes(deps) {
|
|
|
2010
2150
|
return;
|
|
2011
2151
|
}
|
|
2012
2152
|
}
|
|
2013
|
-
|
|
2014
|
-
|
|
2153
|
+
if (!subscriptionId || !subscription) {
|
|
2154
|
+
res.writeHead(409, { 'Content-Type': 'application/json' });
|
|
2155
|
+
res.end(JSON.stringify({
|
|
2156
|
+
error: 'Checkout session is still finalizing your subscription.',
|
|
2157
|
+
code: 'CHECKOUT_SESSION_INCOMPLETE'
|
|
2158
|
+
}));
|
|
2159
|
+
return;
|
|
2160
|
+
}
|
|
2161
|
+
if (!resolvedPlanVersionId) {
|
|
2162
|
+
res.writeHead(409, { 'Content-Type': 'application/json' });
|
|
2163
|
+
res.end(JSON.stringify({
|
|
2164
|
+
error: 'Checkout completed, but the purchased plan could not be resolved yet.',
|
|
2165
|
+
code: 'CHECKOUT_PLAN_VERSION_UNRESOLVED'
|
|
2166
|
+
}));
|
|
2167
|
+
return;
|
|
2168
|
+
}
|
|
2169
|
+
const snapshot = applySubscriptionSnapshot(core, {
|
|
2015
2170
|
userId: resolvedUserId,
|
|
2016
|
-
|
|
2017
|
-
|
|
2171
|
+
subscription,
|
|
2172
|
+
source: 'checkout-confirm'
|
|
2018
2173
|
});
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2174
|
+
resolvedPlanVersionId = snapshot.planVersionId;
|
|
2175
|
+
if (!resolvedPlanVersionId) {
|
|
2176
|
+
res.writeHead(409, { 'Content-Type': 'application/json' });
|
|
2177
|
+
res.end(JSON.stringify({
|
|
2178
|
+
error: 'Checkout completed, but the purchased plan could not be resolved yet.',
|
|
2179
|
+
code: 'CHECKOUT_PLAN_VERSION_UNRESOLVED'
|
|
2180
|
+
}));
|
|
2181
|
+
return;
|
|
2182
|
+
}
|
|
2183
|
+
const updatedEntitlement = getEntitlementRow(core, resolvedUserId);
|
|
2184
|
+
const updatedPlanVersionId = String(updatedEntitlement?.plan_version_id || '').trim();
|
|
2185
|
+
const updatedState = String(updatedEntitlement?.entitlement_state || '').trim().toLowerCase();
|
|
2186
|
+
const canonicalSettled = snapshot.canonicalState !== 'checkout_pending'
|
|
2187
|
+
&& updatedPlanVersionId === resolvedPlanVersionId
|
|
2188
|
+
&& updatedState === snapshot.canonicalState;
|
|
2189
|
+
if (!canonicalSettled) {
|
|
2190
|
+
console.warn(`[Taskforce Billing] Checkout confirmation still settling for user=${resolvedUserId} session=${sessionId} subscription=${subscriptionId || '(missing)'} `
|
|
2191
|
+
+ `planVersionId=${resolvedPlanVersionId} canonicalState=${snapshot.canonicalState} persistedPlanVersionId=${updatedPlanVersionId || '(missing)'} `
|
|
2192
|
+
+ `persistedState=${updatedState || '(missing)'}`);
|
|
2193
|
+
res.writeHead(409, { 'Content-Type': 'application/json' });
|
|
2194
|
+
res.end(JSON.stringify({
|
|
2195
|
+
error: 'Checkout session is still finalizing your plan.',
|
|
2196
|
+
code: 'CHECKOUT_SESSION_INCOMPLETE'
|
|
2197
|
+
}));
|
|
2198
|
+
return;
|
|
2026
2199
|
}
|
|
2200
|
+
updateCheckoutAttemptStatus(core, sessionId, 'confirmed');
|
|
2027
2201
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2028
2202
|
res.end(JSON.stringify({
|
|
2029
2203
|
success: true,
|
|
@@ -2088,7 +2262,7 @@ export function registerBillingRoutes(deps) {
|
|
|
2088
2262
|
const baseUrl = resolveBaseUrl(req);
|
|
2089
2263
|
const session = await stripe.billingPortal.sessions.create({
|
|
2090
2264
|
customer: customerId,
|
|
2091
|
-
return_url: `${baseUrl}
|
|
2265
|
+
return_url: `${baseUrl}/?screen=plans`
|
|
2092
2266
|
});
|
|
2093
2267
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2094
2268
|
res.end(JSON.stringify({ url: session.url, returnBaseUrl: baseUrl }));
|
|
@@ -2249,7 +2423,8 @@ export function registerBillingRoutes(deps) {
|
|
|
2249
2423
|
const lifecycleGuard = resolveCheckoutLifecycleGuard(core, {
|
|
2250
2424
|
userId,
|
|
2251
2425
|
planVersionId,
|
|
2252
|
-
subscriptionId: String(subscription.id || '').trim()
|
|
2426
|
+
subscriptionId: String(subscription.id || '').trim(),
|
|
2427
|
+
source: 'stripe-webhook'
|
|
2253
2428
|
});
|
|
2254
2429
|
if (lifecycleGuard.blocked) {
|
|
2255
2430
|
recordWebhookFailure(core, {
|
|
@@ -2294,7 +2469,8 @@ export function registerBillingRoutes(deps) {
|
|
|
2294
2469
|
const lifecycleGuard = resolveCheckoutLifecycleGuard(core, {
|
|
2295
2470
|
userId,
|
|
2296
2471
|
planVersionId,
|
|
2297
|
-
subscriptionId
|
|
2472
|
+
subscriptionId,
|
|
2473
|
+
source: 'stripe-webhook'
|
|
2298
2474
|
});
|
|
2299
2475
|
if (lifecycleGuard.blocked) {
|
|
2300
2476
|
recordWebhookFailure(core, {
|
|
@@ -2376,6 +2552,15 @@ export function registerBillingRoutes(deps) {
|
|
|
2376
2552
|
try {
|
|
2377
2553
|
const db = core.getDatabaseAdapter();
|
|
2378
2554
|
const tenantId = core.getTenantId();
|
|
2555
|
+
const environment = requestPricingEnvironment(req);
|
|
2556
|
+
const cacheKey = `${tenantId}:${environment}`;
|
|
2557
|
+
const cacheVersion = buildPublicPricingCacheVersion(core);
|
|
2558
|
+
const cached = publicPricingCache.get(cacheKey);
|
|
2559
|
+
if (cached && cached.version === cacheVersion && cached.expiresAt > Date.now()) {
|
|
2560
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2561
|
+
res.end(JSON.stringify(cached.payload));
|
|
2562
|
+
return;
|
|
2563
|
+
}
|
|
2379
2564
|
// Get all active plans
|
|
2380
2565
|
const activePlans = core.listPlanCatalog().filter(p => p.status === 'active');
|
|
2381
2566
|
// Get all active price mappings with their amounts
|
|
@@ -2384,6 +2569,17 @@ export function registerBillingRoutes(deps) {
|
|
|
2384
2569
|
FROM billing_price_mappings
|
|
2385
2570
|
WHERE tenant_id = ? AND active = 1
|
|
2386
2571
|
`).all(tenantId);
|
|
2572
|
+
const pricingByVersionAndInterval = new Map();
|
|
2573
|
+
for (const price of activePrices) {
|
|
2574
|
+
const planVersionId = String(price.plan_version_id || '').trim();
|
|
2575
|
+
const interval = parseInterval(price.billing_interval);
|
|
2576
|
+
if (!planVersionId || !interval)
|
|
2577
|
+
continue;
|
|
2578
|
+
pricingByVersionAndInterval.set(`${planVersionId}:${interval}`, price);
|
|
2579
|
+
}
|
|
2580
|
+
const featureCatalog = core.listPlanFeatureCatalog();
|
|
2581
|
+
const featureCatalogByKey = new Map(featureCatalog.map((feature) => [feature.featureKey, feature]));
|
|
2582
|
+
const featureCatalogIndexByKey = new Map(featureCatalog.map((feature, index) => [feature.featureKey, index]));
|
|
2387
2583
|
const plansPayload = activePlans.map((plan) => {
|
|
2388
2584
|
const versions = core.listPlanVersions(plan.planId);
|
|
2389
2585
|
// Prefer default, else fallback to highest version number
|
|
@@ -2391,11 +2587,12 @@ export function registerBillingRoutes(deps) {
|
|
|
2391
2587
|
if (!defaultVersion) {
|
|
2392
2588
|
return null; // Skip if no valid version exists (edge case)
|
|
2393
2589
|
}
|
|
2394
|
-
const monthPrice =
|
|
2395
|
-
const yearPrice =
|
|
2590
|
+
const monthPrice = pricingByVersionAndInterval.get(`${defaultVersion.planVersionId}:month`);
|
|
2591
|
+
const yearPrice = pricingByVersionAndInterval.get(`${defaultVersion.planVersionId}:year`);
|
|
2396
2592
|
return {
|
|
2397
2593
|
planId: plan.planId,
|
|
2398
2594
|
displayName: plan.displayName,
|
|
2595
|
+
description: plan.description ?? null,
|
|
2399
2596
|
status: plan.status,
|
|
2400
2597
|
defaultVersion: {
|
|
2401
2598
|
planVersionId: defaultVersion.planVersionId,
|
|
@@ -2404,9 +2601,48 @@ export function registerBillingRoutes(deps) {
|
|
|
2404
2601
|
trialDays: defaultVersion.trialDays,
|
|
2405
2602
|
metadata: defaultVersion.metadata
|
|
2406
2603
|
},
|
|
2407
|
-
features: Object.values(defaultVersion.features || {})
|
|
2604
|
+
features: Object.values(defaultVersion.features || {})
|
|
2605
|
+
.reduce((acc, feature) => {
|
|
2408
2606
|
const access = String(feature?.access || '').trim().toLowerCase();
|
|
2409
|
-
|
|
2607
|
+
if (access === 'disabled') {
|
|
2608
|
+
return acc;
|
|
2609
|
+
}
|
|
2610
|
+
const featureKey = String(feature?.featureKey || '').trim();
|
|
2611
|
+
if (!featureKey || INTERNAL_PUBLIC_PRICING_FEATURE_KEYS.has(featureKey)) {
|
|
2612
|
+
return acc;
|
|
2613
|
+
}
|
|
2614
|
+
const catalogEntry = featureCatalogByKey.get(featureKey);
|
|
2615
|
+
acc.push({
|
|
2616
|
+
featureKey,
|
|
2617
|
+
access,
|
|
2618
|
+
config: feature?.config && typeof feature.config === 'object'
|
|
2619
|
+
? feature.config
|
|
2620
|
+
: {},
|
|
2621
|
+
label: catalogEntry?.label ?? null,
|
|
2622
|
+
description: catalogEntry?.description ?? null,
|
|
2623
|
+
publicLabel: catalogEntry?.publicLabel ?? null,
|
|
2624
|
+
publicDescription: catalogEntry?.publicDescription ?? null
|
|
2625
|
+
});
|
|
2626
|
+
return acc;
|
|
2627
|
+
}, [])
|
|
2628
|
+
.sort((a, b) => {
|
|
2629
|
+
const orderA = featureCatalogByKey.get(a.featureKey)?.publicDisplayOrder;
|
|
2630
|
+
const orderB = featureCatalogByKey.get(b.featureKey)?.publicDisplayOrder;
|
|
2631
|
+
const normalizedOrderA = typeof orderA === 'number' && Number.isFinite(orderA)
|
|
2632
|
+
? orderA
|
|
2633
|
+
: Number.MAX_SAFE_INTEGER;
|
|
2634
|
+
const normalizedOrderB = typeof orderB === 'number' && Number.isFinite(orderB)
|
|
2635
|
+
? orderB
|
|
2636
|
+
: Number.MAX_SAFE_INTEGER;
|
|
2637
|
+
if (normalizedOrderA !== normalizedOrderB) {
|
|
2638
|
+
return normalizedOrderA - normalizedOrderB;
|
|
2639
|
+
}
|
|
2640
|
+
const catalogIndexA = featureCatalogIndexByKey.get(a.featureKey) ?? Number.MAX_SAFE_INTEGER;
|
|
2641
|
+
const catalogIndexB = featureCatalogIndexByKey.get(b.featureKey) ?? Number.MAX_SAFE_INTEGER;
|
|
2642
|
+
if (catalogIndexA !== catalogIndexB) {
|
|
2643
|
+
return catalogIndexA - catalogIndexB;
|
|
2644
|
+
}
|
|
2645
|
+
return a.featureKey.localeCompare(b.featureKey);
|
|
2410
2646
|
}),
|
|
2411
2647
|
pricing: {
|
|
2412
2648
|
month: monthPrice ? {
|
|
@@ -2426,15 +2662,21 @@ export function registerBillingRoutes(deps) {
|
|
|
2426
2662
|
}
|
|
2427
2663
|
};
|
|
2428
2664
|
}).filter(Boolean);
|
|
2429
|
-
|
|
2430
|
-
res.end(JSON.stringify({
|
|
2665
|
+
const payload = {
|
|
2431
2666
|
asOf: new Date().toISOString(),
|
|
2432
2667
|
pricingSource: {
|
|
2433
|
-
environment
|
|
2668
|
+
environment,
|
|
2434
2669
|
runtimeMode: 'cloud'
|
|
2435
2670
|
},
|
|
2436
2671
|
plans: plansPayload
|
|
2437
|
-
}
|
|
2672
|
+
};
|
|
2673
|
+
publicPricingCache.set(cacheKey, {
|
|
2674
|
+
payload,
|
|
2675
|
+
version: cacheVersion,
|
|
2676
|
+
expiresAt: Date.now() + PUBLIC_PRICING_CACHE_TTL_MS
|
|
2677
|
+
});
|
|
2678
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2679
|
+
res.end(JSON.stringify(payload));
|
|
2438
2680
|
}
|
|
2439
2681
|
catch (error) {
|
|
2440
2682
|
const statusCode = resolveErrorStatusCode(error, 500);
|