@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,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Apple OAuth client-secret JWT minter.
|
|
3
|
+
*
|
|
4
|
+
* Apple Sign In requires a short-lived JWT as the OAuth `client_secret` rather
|
|
5
|
+
* than a static secret. The JWT must be:
|
|
6
|
+
* - Signed with ES256 (ECDSA P-256) using the private key registered in App Store Connect
|
|
7
|
+
* - Valid for at most 6 months (Apple enforces this; we use 10 min for freshness)
|
|
8
|
+
* - Cached so callers don't re-sign on every request
|
|
9
|
+
*
|
|
10
|
+
* Reference: https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens
|
|
11
|
+
*/
|
|
12
|
+
import { sign } from 'crypto';
|
|
13
|
+
// ~9 min — well under Apple's 10-min max, allows safe margin for clock skew
|
|
14
|
+
const CACHE_TTL_MS = 9 * 60 * 1000;
|
|
15
|
+
// Apple max is 6 months; we use 10 min so tokens stay fresh
|
|
16
|
+
const TOKEN_LIFETIME_SECONDS = 600;
|
|
17
|
+
const secretCache = new Map();
|
|
18
|
+
function configCacheKey(cfg) {
|
|
19
|
+
return `${cfg.teamId}:${cfg.servicesId}:${cfg.keyId}`;
|
|
20
|
+
}
|
|
21
|
+
function buildJwt(cfg) {
|
|
22
|
+
const now = Math.floor(Date.now() / 1000);
|
|
23
|
+
const header = Buffer.from(JSON.stringify({ alg: 'ES256', kid: cfg.keyId })).toString('base64url');
|
|
24
|
+
const payload = Buffer.from(JSON.stringify({
|
|
25
|
+
iss: cfg.teamId,
|
|
26
|
+
iat: now,
|
|
27
|
+
exp: now + TOKEN_LIFETIME_SECONDS,
|
|
28
|
+
aud: 'https://appleid.apple.com',
|
|
29
|
+
sub: cfg.servicesId
|
|
30
|
+
})).toString('base64url');
|
|
31
|
+
const signingInput = `${header}.${payload}`;
|
|
32
|
+
// sign() with dsaEncoding: 'ieee-p1363' produces raw r||s (64 bytes) required by JWT ES256
|
|
33
|
+
const signature = sign('SHA256', Buffer.from(signingInput), {
|
|
34
|
+
key: cfg.privateKeyPem,
|
|
35
|
+
dsaEncoding: 'ieee-p1363'
|
|
36
|
+
}).toString('base64url');
|
|
37
|
+
return `${signingInput}.${signature}`;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Returns a cached client-secret JWT for the given Apple config, minting a
|
|
41
|
+
* fresh one if the cache entry is absent or within 60 s of expiry.
|
|
42
|
+
*/
|
|
43
|
+
export function getAppleClientSecret(cfg) {
|
|
44
|
+
const key = configCacheKey(cfg);
|
|
45
|
+
const cached = secretCache.get(key);
|
|
46
|
+
if (cached && cached.expiresAt > Date.now() + 60_000) {
|
|
47
|
+
return cached.token;
|
|
48
|
+
}
|
|
49
|
+
const token = buildJwt(cfg);
|
|
50
|
+
secretCache.set(key, { token, expiresAt: Date.now() + CACHE_TTL_MS });
|
|
51
|
+
return token;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Force-mints a fresh JWT (bypasses cache) for health-check purposes.
|
|
55
|
+
* Returns success/error without updating the cache.
|
|
56
|
+
*/
|
|
57
|
+
export function testMintAppleClientSecret(cfg) {
|
|
58
|
+
try {
|
|
59
|
+
const token = buildJwt(cfg);
|
|
60
|
+
return { ok: true, token };
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub OAuth adapter.
|
|
3
|
+
*
|
|
4
|
+
* Flow:
|
|
5
|
+
* 1. Redirect user to buildGithubAuthUrl() (includes PKCE S256 for GitHub Apps).
|
|
6
|
+
* 2. Receive code on callback; call exchangeGithubCode() to get profile.
|
|
7
|
+
*
|
|
8
|
+
* Email selection rules:
|
|
9
|
+
* - Prefer the primary + verified email from /user/emails.
|
|
10
|
+
* - Fall back to any verified email, then to user.email (public profile field).
|
|
11
|
+
* - If none is available, email is null.
|
|
12
|
+
*/
|
|
13
|
+
import type { NormalizedOAuthProfile } from './types.js';
|
|
14
|
+
export declare function buildGithubAuthUrl(input: {
|
|
15
|
+
clientId: string;
|
|
16
|
+
redirectUri: string;
|
|
17
|
+
state: string;
|
|
18
|
+
codeChallenge: string;
|
|
19
|
+
}): string;
|
|
20
|
+
export declare function exchangeGithubCode(input: {
|
|
21
|
+
code: string;
|
|
22
|
+
codeVerifier: string;
|
|
23
|
+
redirectUri: string;
|
|
24
|
+
clientId: string;
|
|
25
|
+
clientSecret: string;
|
|
26
|
+
}): Promise<NormalizedOAuthProfile>;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export function buildGithubAuthUrl(input) {
|
|
2
|
+
const params = new URLSearchParams({
|
|
3
|
+
client_id: input.clientId,
|
|
4
|
+
redirect_uri: input.redirectUri,
|
|
5
|
+
state: input.state,
|
|
6
|
+
scope: 'read:user user:email',
|
|
7
|
+
code_challenge: input.codeChallenge,
|
|
8
|
+
code_challenge_method: 'S256',
|
|
9
|
+
});
|
|
10
|
+
return `https://github.com/login/oauth/authorize?${params.toString()}`;
|
|
11
|
+
}
|
|
12
|
+
export async function exchangeGithubCode(input) {
|
|
13
|
+
// Step 1: exchange authorisation code for access token
|
|
14
|
+
const tokenRes = await fetch('https://github.com/login/oauth/access_token', {
|
|
15
|
+
method: 'POST',
|
|
16
|
+
headers: {
|
|
17
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
18
|
+
Accept: 'application/json',
|
|
19
|
+
},
|
|
20
|
+
body: new URLSearchParams({
|
|
21
|
+
client_id: input.clientId,
|
|
22
|
+
client_secret: input.clientSecret,
|
|
23
|
+
code: input.code,
|
|
24
|
+
redirect_uri: input.redirectUri,
|
|
25
|
+
code_verifier: input.codeVerifier,
|
|
26
|
+
}).toString(),
|
|
27
|
+
});
|
|
28
|
+
if (!tokenRes.ok) {
|
|
29
|
+
const body = await tokenRes.text().catch(() => '');
|
|
30
|
+
throw new Error(`GitHub token exchange failed (${tokenRes.status}): ${body}`);
|
|
31
|
+
}
|
|
32
|
+
const tokens = await tokenRes.json();
|
|
33
|
+
if (tokens.error) {
|
|
34
|
+
throw new Error(`GitHub token exchange error: ${tokens.error}${tokens.error_description ? ` — ${tokens.error_description}` : ''}`);
|
|
35
|
+
}
|
|
36
|
+
const accessToken = String(tokens.access_token || '').trim();
|
|
37
|
+
if (!accessToken)
|
|
38
|
+
throw new Error('GitHub token exchange: missing access_token');
|
|
39
|
+
// Step 2: fetch user + emails in parallel
|
|
40
|
+
const ghHeaders = {
|
|
41
|
+
Authorization: `Bearer ${accessToken}`,
|
|
42
|
+
'User-Agent': 'Taskforce/1.0',
|
|
43
|
+
Accept: 'application/vnd.github+json',
|
|
44
|
+
};
|
|
45
|
+
const [userRes, emailsRes] = await Promise.all([
|
|
46
|
+
fetch('https://api.github.com/user', { headers: ghHeaders }),
|
|
47
|
+
fetch('https://api.github.com/user/emails', { headers: ghHeaders }),
|
|
48
|
+
]);
|
|
49
|
+
if (!userRes.ok) {
|
|
50
|
+
throw new Error(`GitHub /user API failed (${userRes.status})`);
|
|
51
|
+
}
|
|
52
|
+
const user = await userRes.json();
|
|
53
|
+
const numericId = String(user.id || '').trim();
|
|
54
|
+
if (!numericId)
|
|
55
|
+
throw new Error('GitHub /user API: missing id');
|
|
56
|
+
// Step 3: resolve best email
|
|
57
|
+
let email = null;
|
|
58
|
+
let emailVerified = false;
|
|
59
|
+
if (emailsRes.ok) {
|
|
60
|
+
const emails = await emailsRes.json().catch(() => []);
|
|
61
|
+
const primaryVerified = emails.find(e => e.primary === true && e.verified === true);
|
|
62
|
+
if (primaryVerified) {
|
|
63
|
+
email = String(primaryVerified.email || '').trim().toLowerCase() || null;
|
|
64
|
+
emailVerified = true;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
const anyVerified = emails.find(e => e.verified === true);
|
|
68
|
+
if (anyVerified) {
|
|
69
|
+
email = String(anyVerified.email || '').trim().toLowerCase() || null;
|
|
70
|
+
emailVerified = true;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (!email) {
|
|
75
|
+
// Last resort: public profile email (may be null / not set)
|
|
76
|
+
email = String(user.email || '').trim().toLowerCase() || null;
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
provider: 'github',
|
|
80
|
+
providerSubject: numericId,
|
|
81
|
+
email,
|
|
82
|
+
emailVerified,
|
|
83
|
+
displayName: String(user.name || user.login || '').trim() || null,
|
|
84
|
+
avatarUrl: String(user.avatar_url || '').trim() || null,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google OAuth 2.0 / OpenID Connect adapter.
|
|
3
|
+
*
|
|
4
|
+
* Flow:
|
|
5
|
+
* 1. Redirect user to buildGoogleAuthUrl() (PKCE + S256).
|
|
6
|
+
* 2. Receive code on callback; call exchangeGoogleCode() to get profile.
|
|
7
|
+
*/
|
|
8
|
+
import type { NormalizedOAuthProfile } from './types.js';
|
|
9
|
+
export declare function buildGoogleAuthUrl(input: {
|
|
10
|
+
clientId: string;
|
|
11
|
+
redirectUri: string;
|
|
12
|
+
state: string;
|
|
13
|
+
codeChallenge: string;
|
|
14
|
+
}): string;
|
|
15
|
+
export declare function exchangeGoogleCode(input: {
|
|
16
|
+
code: string;
|
|
17
|
+
codeVerifier: string;
|
|
18
|
+
redirectUri: string;
|
|
19
|
+
clientId: string;
|
|
20
|
+
clientSecret: string;
|
|
21
|
+
}): Promise<NormalizedOAuthProfile>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export function buildGoogleAuthUrl(input) {
|
|
2
|
+
const params = new URLSearchParams({
|
|
3
|
+
client_id: input.clientId,
|
|
4
|
+
redirect_uri: input.redirectUri,
|
|
5
|
+
response_type: 'code',
|
|
6
|
+
scope: 'openid email profile',
|
|
7
|
+
state: input.state,
|
|
8
|
+
code_challenge: input.codeChallenge,
|
|
9
|
+
code_challenge_method: 'S256',
|
|
10
|
+
access_type: 'offline',
|
|
11
|
+
prompt: 'select_account',
|
|
12
|
+
});
|
|
13
|
+
return `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
|
|
14
|
+
}
|
|
15
|
+
export async function exchangeGoogleCode(input) {
|
|
16
|
+
// Step 1: exchange authorisation code for tokens
|
|
17
|
+
const tokenRes = await fetch('https://oauth2.googleapis.com/token', {
|
|
18
|
+
method: 'POST',
|
|
19
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
20
|
+
body: new URLSearchParams({
|
|
21
|
+
grant_type: 'authorization_code',
|
|
22
|
+
code: input.code,
|
|
23
|
+
redirect_uri: input.redirectUri,
|
|
24
|
+
client_id: input.clientId,
|
|
25
|
+
client_secret: input.clientSecret,
|
|
26
|
+
code_verifier: input.codeVerifier,
|
|
27
|
+
}).toString(),
|
|
28
|
+
});
|
|
29
|
+
if (!tokenRes.ok) {
|
|
30
|
+
const body = await tokenRes.text().catch(() => '');
|
|
31
|
+
throw new Error(`Google token exchange failed (${tokenRes.status}): ${body}`);
|
|
32
|
+
}
|
|
33
|
+
const tokens = await tokenRes.json();
|
|
34
|
+
const accessToken = String(tokens.access_token || '').trim();
|
|
35
|
+
if (!accessToken)
|
|
36
|
+
throw new Error('Google token exchange: missing access_token');
|
|
37
|
+
// Step 2: fetch userinfo
|
|
38
|
+
const userinfoRes = await fetch('https://www.googleapis.com/oauth2/v3/userinfo', {
|
|
39
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
40
|
+
});
|
|
41
|
+
if (!userinfoRes.ok) {
|
|
42
|
+
throw new Error(`Google userinfo failed (${userinfoRes.status})`);
|
|
43
|
+
}
|
|
44
|
+
const info = await userinfoRes.json();
|
|
45
|
+
const sub = String(info.sub || '').trim();
|
|
46
|
+
if (!sub)
|
|
47
|
+
throw new Error('Google userinfo: missing sub');
|
|
48
|
+
return {
|
|
49
|
+
provider: 'google',
|
|
50
|
+
providerSubject: sub,
|
|
51
|
+
email: String(info.email || '').trim().toLowerCase() || null,
|
|
52
|
+
emailVerified: info.email_verified === true,
|
|
53
|
+
displayName: String(info.name || '').trim() || null,
|
|
54
|
+
avatarUrl: String(info.picture || '').trim() || null,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalised OAuth profile returned by every provider adapter.
|
|
3
|
+
* The callback route uses this to resolve/create the Taskforce identity.
|
|
4
|
+
*/
|
|
5
|
+
export type OAuthProviderName = 'google' | 'github' | 'apple';
|
|
6
|
+
export interface NormalizedOAuthProfile {
|
|
7
|
+
/** The provider that authenticated the user. */
|
|
8
|
+
provider: OAuthProviderName;
|
|
9
|
+
/** Stable, provider-assigned user ID (Google `sub`, GitHub numeric `id`, Apple `sub`). */
|
|
10
|
+
providerSubject: string;
|
|
11
|
+
/** Primary email address, or null if the provider withheld it (e.g. Apple private relay). */
|
|
12
|
+
email: string | null;
|
|
13
|
+
/** Whether the provider has verified ownership of the email address. */
|
|
14
|
+
emailVerified: boolean;
|
|
15
|
+
/** Human-readable display name, or null if not provided. */
|
|
16
|
+
displayName: string | null;
|
|
17
|
+
/** Avatar / profile picture URL, or null if not provided. */
|
|
18
|
+
avatarUrl: string | null;
|
|
19
|
+
/** Apple-specific: true when `email` is a private relay address. */
|
|
20
|
+
isPrivateEmail?: boolean;
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/server/auth.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ export declare function createSessionTokenCookie(sessionId: string, config: Task
|
|
|
30
30
|
role?: TaskforceRole;
|
|
31
31
|
workspaceId?: string;
|
|
32
32
|
userId?: string;
|
|
33
|
+
identityId?: string;
|
|
33
34
|
secure?: boolean;
|
|
34
35
|
sameSite?: 'Lax' | 'Strict' | 'None';
|
|
35
36
|
}): string;
|
|
@@ -42,6 +43,7 @@ export interface RequestAccessContext {
|
|
|
42
43
|
systemRole: TaskforceSystemRole;
|
|
43
44
|
workspaceId: string;
|
|
44
45
|
userId: string;
|
|
46
|
+
identityId: string | null;
|
|
45
47
|
}
|
|
46
48
|
export declare function resolveRequestAccess(req: IncomingMessage, config: TaskforceAuthConfig): RequestAccessContext;
|
|
47
49
|
export declare function isRequestAuthorized(req: IncomingMessage, config: TaskforceAuthConfig): {
|
package/dist/server/auth.js
CHANGED
|
@@ -176,7 +176,8 @@ export function createSessionTokenCookie(sessionId, config, options) {
|
|
|
176
176
|
exp,
|
|
177
177
|
role: options?.role,
|
|
178
178
|
workspaceId: options?.workspaceId,
|
|
179
|
-
userId: options?.userId
|
|
179
|
+
userId: options?.userId,
|
|
180
|
+
identityId: options?.identityId
|
|
180
181
|
}));
|
|
181
182
|
const sig = signPayload(payload, config.sessionSecret);
|
|
182
183
|
const value = `${payload}.${sig}`;
|
|
@@ -240,7 +241,8 @@ export function resolveRequestAccess(req, config) {
|
|
|
240
241
|
role: config.defaultRole,
|
|
241
242
|
systemRole: 'user',
|
|
242
243
|
workspaceId: fallbackWorkspaceId,
|
|
243
|
-
userId: fallbackUserId
|
|
244
|
+
userId: fallbackUserId,
|
|
245
|
+
identityId: null
|
|
244
246
|
};
|
|
245
247
|
const sessionClaims = readSessionClaimsFromRequest(req, config);
|
|
246
248
|
if (sessionClaims?.sessionId) {
|
|
@@ -251,7 +253,8 @@ export function resolveRequestAccess(req, config) {
|
|
|
251
253
|
role,
|
|
252
254
|
systemRole: 'user',
|
|
253
255
|
workspaceId: String(sessionClaims.workspaceId || config.defaultWorkspaceId).trim() || config.defaultWorkspaceId,
|
|
254
|
-
userId: String(sessionClaims.userId || fallbackUserId).trim() || fallbackUserId
|
|
256
|
+
userId: String(sessionClaims.userId || fallbackUserId).trim() || fallbackUserId,
|
|
257
|
+
identityId: String(sessionClaims.identityId || '').trim() || null
|
|
255
258
|
};
|
|
256
259
|
}
|
|
257
260
|
return base;
|
|
@@ -70,9 +70,9 @@ describe('document review routes', () => {
|
|
|
70
70
|
core.createWorkspace({ workspaceId: 'workspace-1', name: 'Workspace 1' });
|
|
71
71
|
const now = new Date().toISOString();
|
|
72
72
|
core.db.prepare(`
|
|
73
|
-
INSERT INTO users (id, tenant_id, email, display_name,
|
|
74
|
-
VALUES (?, ?, ?, ?, ?, ?, ?,
|
|
75
|
-
`).run('user-local-1', core.getTenantId(), 'local@example.com', 'Local User',
|
|
73
|
+
INSERT INTO users (id, tenant_id, email, display_name, beta_access, is_disabled, created_at, updated_at)
|
|
74
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
75
|
+
`).run('user-local-1', core.getTenantId(), 'local@example.com', 'Local User', 1, 0, now, now);
|
|
76
76
|
core.db.prepare(`
|
|
77
77
|
INSERT INTO workspace_memberships (id, tenant_id, workspace_id, user_id, role, status, created_at, updated_at)
|
|
78
78
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
@@ -154,4 +154,37 @@ describe('document review routes', () => {
|
|
|
154
154
|
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
155
155
|
}
|
|
156
156
|
});
|
|
157
|
+
it('blocks anonymous cloud access before reading document review sessions', async () => {
|
|
158
|
+
const projectRoot = createProjectRoot();
|
|
159
|
+
const core = new TaskforceCore({
|
|
160
|
+
projectRoot,
|
|
161
|
+
storagePath: path.join(projectRoot, '.taskforce'),
|
|
162
|
+
});
|
|
163
|
+
try {
|
|
164
|
+
const routes = createRoutes(core, {
|
|
165
|
+
authConfig: {
|
|
166
|
+
runtimeMode: 'cloud',
|
|
167
|
+
enabled: true,
|
|
168
|
+
requiredForApi: true,
|
|
169
|
+
excludedPaths: ['/api/taskforce/health', '/api/taskforce/auth'],
|
|
170
|
+
defaultRole: 'member',
|
|
171
|
+
defaultWorkspaceId: '',
|
|
172
|
+
sessionCookieName: 'taskforce_session',
|
|
173
|
+
sessionSecret: 'test-secret',
|
|
174
|
+
sessionTtlSeconds: 3600,
|
|
175
|
+
sessionCookieSameSite: 'None',
|
|
176
|
+
sessionCookieSecure: true,
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
const req = createMockReq('GET', '/api/taskforce/document-reviews/sessions?assetId=asset-doc-1', undefined, { 'x-taskforce-runtime-mode': 'cloud' });
|
|
180
|
+
const res = createMockRes();
|
|
181
|
+
await matchRoute('GET', '/api/taskforce/document-reviews/sessions', routes)?.handler(req, res, {});
|
|
182
|
+
expect(res.statusCode).toBe(401);
|
|
183
|
+
expect(res.__getBody()).toContain('Authentication required.');
|
|
184
|
+
}
|
|
185
|
+
finally {
|
|
186
|
+
core.close();
|
|
187
|
+
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
188
|
+
}
|
|
189
|
+
});
|
|
157
190
|
});
|
|
@@ -29,6 +29,7 @@ describe('proxy cookie rewrite helpers', () => {
|
|
|
29
29
|
expect(shouldProxyCloudPath('/api/taskforce/billing/checkout-session')).toBe(true);
|
|
30
30
|
expect(shouldProxyCloudPath('/api/taskforce/auth/session')).toBe(true);
|
|
31
31
|
expect(shouldProxyCloudPath('/api/taskforce/workspace/assignee-options')).toBe(false);
|
|
32
|
+
expect(shouldProxyCloudPath('/api/taskforce/workspace/ai-profiles/avatar')).toBe(false);
|
|
32
33
|
expect(shouldProxyCloudPath('/api/taskforce/settings/mcp/tokens')).toBe(true);
|
|
33
34
|
expect(shouldProxyCloudPath('/api/taskforce/settings/mcp/test-connection')).toBe(true);
|
|
34
35
|
expect(shouldProxyCloudPath('/api/platform/plans')).toBe(true);
|
package/dist/server/index.d.ts
CHANGED
|
@@ -11,6 +11,13 @@ export interface StandaloneServerOptions {
|
|
|
11
11
|
projectRoot: string;
|
|
12
12
|
port: number;
|
|
13
13
|
}
|
|
14
|
+
export type HttpServerTimeoutSettings = {
|
|
15
|
+
keepAliveTimeoutMs: number;
|
|
16
|
+
headersTimeoutMs: number;
|
|
17
|
+
requestTimeoutMs: number;
|
|
18
|
+
};
|
|
19
|
+
export declare function resolveHttpServerTimeoutSettings(env?: NodeJS.ProcessEnv): HttpServerTimeoutSettings;
|
|
20
|
+
export declare function configureHttpServerTimeouts(server: Pick<http.Server, 'keepAliveTimeout' | 'headersTimeout' | 'requestTimeout'>, env?: NodeJS.ProcessEnv): HttpServerTimeoutSettings;
|
|
14
21
|
export declare const TASKFORCE_PROXY_ORIGIN_HOST_HEADER = "x-taskforce-origin-host";
|
|
15
22
|
export declare const TASKFORCE_PROXY_ORIGIN_PROTO_HEADER = "x-taskforce-origin-proto";
|
|
16
23
|
export declare const TASKFORCE_PROXY_ORIGIN_BASE_URL_HEADER = "x-taskforce-origin-base-url";
|
|
@@ -18,10 +25,15 @@ export declare const TASKFORCE_PROXY_ORIGIN_RUNTIME_HEADER = "x-taskforce-origin
|
|
|
18
25
|
export declare const TASKFORCE_PROXY_CLOUD_ENVIRONMENT_HEADER = "x-taskforce-cloud-environment";
|
|
19
26
|
interface AuthorizedRequestAccess extends RequestAccessContext {
|
|
20
27
|
disabled?: boolean;
|
|
28
|
+
accountDisabled?: boolean;
|
|
29
|
+
workspaceDisabled?: boolean;
|
|
21
30
|
}
|
|
31
|
+
export declare function isWorkspaceAccessRecoveryRoute(pathname: string): boolean;
|
|
32
|
+
export declare function resolveDisabledAccessError(access: Pick<AuthorizedRequestAccess, 'disabled' | 'accountDisabled' | 'workspaceDisabled'>, pathname: string): string | null;
|
|
22
33
|
export declare function shouldProxyCloudPath(pathname: string): boolean;
|
|
23
34
|
export declare function resolveCloudProxyUpstreamBaseUrl(req: http.IncomingMessage, fallbackBaseUrl: string): string;
|
|
24
35
|
export declare function resolveAuthorizedWorkspaceAccess(core: Pick<TaskforceCore, 'resolveUserAccess' | 'getSystemRole'>, access: AuthorizedRequestAccess, requestedWorkspaceId: string): AuthorizedRequestAccess;
|
|
36
|
+
export declare function shouldApplyReadOnlyMutationGuard(methodRaw: string | undefined, pathname: string): boolean;
|
|
25
37
|
export declare function resolveEffectiveRequestUserId(accessUserId: string, explicitUserId: string): string;
|
|
26
38
|
export declare function resolveMaxPayloadBytesForPath(pathname: string, defaultMaxBytes: number): number;
|
|
27
39
|
export declare function shouldRewriteSetCookieForLocalDevHost(hostHeader: string | undefined): boolean;
|
package/dist/server/index.js
CHANGED
|
@@ -84,11 +84,85 @@ const CORS_RESPONSE_HEADERS = new Set([
|
|
|
84
84
|
'access-control-max-age',
|
|
85
85
|
'vary'
|
|
86
86
|
]);
|
|
87
|
+
const REFRESH_DIAGNOSTIC_PATH_PREFIXES = [
|
|
88
|
+
'/api/taskforce/auth/session',
|
|
89
|
+
'/api/taskforce/config',
|
|
90
|
+
'/api/taskforce/tasks',
|
|
91
|
+
'/api/taskforce/archive',
|
|
92
|
+
'/api/taskforce/workspaces',
|
|
93
|
+
'/api/taskforce/workspace/assignee-options',
|
|
94
|
+
'/api/taskforce/auth/workspace-members',
|
|
95
|
+
'/api/taskforce/taxonomy-state',
|
|
96
|
+
'/api/taskforce/categories',
|
|
97
|
+
'/api/taskforce/types',
|
|
98
|
+
'/api/taskforce/priorities',
|
|
99
|
+
'/api/taskforce/taxonomies',
|
|
100
|
+
'/api/taskforce/initiative-templates',
|
|
101
|
+
'/api/taskforce/workflow-templates',
|
|
102
|
+
'/api/taskforce/workflow-editor/overrides',
|
|
103
|
+
'/api/taskforce/environments',
|
|
104
|
+
'/api/taskforce/sync/'
|
|
105
|
+
];
|
|
106
|
+
const REFRESH_DIAGNOSTIC_DURATION_MS = 100;
|
|
107
|
+
const DEFAULT_HTTP_KEEP_ALIVE_TIMEOUT_MS = 65_000;
|
|
108
|
+
const DEFAULT_HTTP_HEADERS_TIMEOUT_MS = 70_000;
|
|
109
|
+
const DEFAULT_HTTP_REQUEST_TIMEOUT_MS = 120_000;
|
|
110
|
+
export function resolveHttpServerTimeoutSettings(env = process.env) {
|
|
111
|
+
const keepAliveTimeoutMs = readPositiveInteger(env.TASKFORCE_HTTP_KEEP_ALIVE_TIMEOUT_MS, DEFAULT_HTTP_KEEP_ALIVE_TIMEOUT_MS);
|
|
112
|
+
const configuredHeadersTimeoutMs = readPositiveInteger(env.TASKFORCE_HTTP_HEADERS_TIMEOUT_MS, DEFAULT_HTTP_HEADERS_TIMEOUT_MS);
|
|
113
|
+
const headersTimeoutMs = Math.max(configuredHeadersTimeoutMs, keepAliveTimeoutMs + 5_000);
|
|
114
|
+
const requestTimeoutMs = readPositiveInteger(env.TASKFORCE_HTTP_REQUEST_TIMEOUT_MS, DEFAULT_HTTP_REQUEST_TIMEOUT_MS);
|
|
115
|
+
return {
|
|
116
|
+
keepAliveTimeoutMs,
|
|
117
|
+
headersTimeoutMs,
|
|
118
|
+
requestTimeoutMs
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
export function configureHttpServerTimeouts(server, env = process.env) {
|
|
122
|
+
const settings = resolveHttpServerTimeoutSettings(env);
|
|
123
|
+
server.keepAliveTimeout = settings.keepAliveTimeoutMs;
|
|
124
|
+
server.headersTimeout = settings.headersTimeoutMs;
|
|
125
|
+
server.requestTimeout = settings.requestTimeoutMs;
|
|
126
|
+
return settings;
|
|
127
|
+
}
|
|
87
128
|
export const TASKFORCE_PROXY_ORIGIN_HOST_HEADER = 'x-taskforce-origin-host';
|
|
88
129
|
export const TASKFORCE_PROXY_ORIGIN_PROTO_HEADER = 'x-taskforce-origin-proto';
|
|
89
130
|
export const TASKFORCE_PROXY_ORIGIN_BASE_URL_HEADER = 'x-taskforce-origin-base-url';
|
|
90
131
|
export const TASKFORCE_PROXY_ORIGIN_RUNTIME_HEADER = 'x-taskforce-origin-runtime';
|
|
91
132
|
export const TASKFORCE_PROXY_CLOUD_ENVIRONMENT_HEADER = 'x-taskforce-cloud-environment';
|
|
133
|
+
export function isWorkspaceAccessRecoveryRoute(pathname) {
|
|
134
|
+
return pathname === '/api/taskforce/workspaces'
|
|
135
|
+
|| pathname === '/api/taskforce/session/workspace';
|
|
136
|
+
}
|
|
137
|
+
export function resolveDisabledAccessError(access, pathname) {
|
|
138
|
+
if (pathname.startsWith('/api/taskforce/auth/'))
|
|
139
|
+
return null;
|
|
140
|
+
if (access.accountDisabled ?? access.disabled) {
|
|
141
|
+
return 'Forbidden: user account is disabled.';
|
|
142
|
+
}
|
|
143
|
+
if (access.workspaceDisabled && !isWorkspaceAccessRecoveryRoute(pathname)) {
|
|
144
|
+
return 'Forbidden: workspace access is not active for this user.';
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
function shouldLogRefreshDiagnostic(pathname) {
|
|
149
|
+
return REFRESH_DIAGNOSTIC_PATH_PREFIXES.some((prefix) => pathname === prefix || pathname.startsWith(prefix));
|
|
150
|
+
}
|
|
151
|
+
function getRequestTimingNow() {
|
|
152
|
+
return Date.now();
|
|
153
|
+
}
|
|
154
|
+
function isCloudOwnedUserAvatarContextPath(pathname) {
|
|
155
|
+
const prefix = '/api/taskforce/context/';
|
|
156
|
+
if (!pathname.startsWith(prefix))
|
|
157
|
+
return false;
|
|
158
|
+
try {
|
|
159
|
+
const decodedPath = decodeURIComponent(pathname.slice(prefix.length)).replace(/\\/g, '/');
|
|
160
|
+
return /^users\/[^/]+\/profile\/avatar\/[^/]+$/i.test(decodedPath);
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
92
166
|
export function shouldProxyCloudPath(pathname) {
|
|
93
167
|
if (!pathname)
|
|
94
168
|
return false;
|
|
@@ -114,6 +188,7 @@ export function shouldProxyCloudPath(pathname) {
|
|
|
114
188
|
|| pathname.startsWith('/api/taskforce/admin/')
|
|
115
189
|
|| pathname.startsWith('/api/taskforce/billing/')
|
|
116
190
|
|| pathname.startsWith('/api/taskforce/auth/')
|
|
191
|
+
|| isCloudOwnedUserAvatarContextPath(pathname)
|
|
117
192
|
|| pathname.startsWith('/api/taskforce/settings/mcp/')
|
|
118
193
|
|| pathname.startsWith('/api/platform/')
|
|
119
194
|
|| pathname.startsWith('/api/taskforce/sync/');
|
|
@@ -142,11 +217,27 @@ export function resolveAuthorizedWorkspaceAccess(core, access, requestedWorkspac
|
|
|
142
217
|
workspaceId: resolved.workspaceId,
|
|
143
218
|
userId: resolved.userId,
|
|
144
219
|
systemRole: core.getSystemRole(resolved.userId),
|
|
145
|
-
disabled: resolved.disabled
|
|
220
|
+
disabled: resolved.disabled,
|
|
221
|
+
accountDisabled: resolved.accountDisabled ?? resolved.disabled,
|
|
222
|
+
workspaceDisabled: resolved.workspaceDisabled ?? false
|
|
146
223
|
};
|
|
147
224
|
}
|
|
148
225
|
return access;
|
|
149
226
|
}
|
|
227
|
+
export function shouldApplyReadOnlyMutationGuard(methodRaw, pathname) {
|
|
228
|
+
const method = String(methodRaw || 'GET').toUpperCase();
|
|
229
|
+
if (method !== 'POST' && method !== 'PATCH' && method !== 'DELETE')
|
|
230
|
+
return false;
|
|
231
|
+
if (!pathname.startsWith('/api/taskforce/'))
|
|
232
|
+
return false;
|
|
233
|
+
if (pathname.startsWith('/api/taskforce/auth/'))
|
|
234
|
+
return false;
|
|
235
|
+
if (pathname === '/api/taskforce/health')
|
|
236
|
+
return false;
|
|
237
|
+
if (method === 'POST' && pathname === '/api/taskforce/session/workspace')
|
|
238
|
+
return false;
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
150
241
|
export function resolveEffectiveRequestUserId(accessUserId, explicitUserId) {
|
|
151
242
|
const normalizedAccessUserId = String(accessUserId || '').trim();
|
|
152
243
|
if (normalizedAccessUserId && normalizedAccessUserId !== 'anonymous') {
|
|
@@ -259,6 +350,7 @@ export function shouldApplyAuthRateLimitForPath(pathname) {
|
|
|
259
350
|
const normalizedPath = String(pathname || '').trim();
|
|
260
351
|
return normalizedPath.startsWith('/api/taskforce/auth/')
|
|
261
352
|
&& normalizedPath !== '/api/taskforce/auth/session'
|
|
353
|
+
&& normalizedPath !== '/api/taskforce/auth/providers'
|
|
262
354
|
&& normalizedPath !== '/api/taskforce/auth/runtime-config'
|
|
263
355
|
&& normalizedPath !== '/api/taskforce/auth/admin-session';
|
|
264
356
|
}
|
|
@@ -458,6 +550,17 @@ export function createStandaloneServer(options) {
|
|
|
458
550
|
statusCode
|
|
459
551
|
}, counterKeys);
|
|
460
552
|
};
|
|
553
|
+
const logRefreshDiagnostic = (input) => {
|
|
554
|
+
const { pathname, method, statusCode, durationMs, req } = input;
|
|
555
|
+
if (!shouldLogRefreshDiagnostic(pathname))
|
|
556
|
+
return;
|
|
557
|
+
if (durationMs < REFRESH_DIAGNOSTIC_DURATION_MS && statusCode < 400)
|
|
558
|
+
return;
|
|
559
|
+
const requestId = String(req.headers['x-request-id'] || req.headers['x-railway-request-id'] || '').trim() || '-';
|
|
560
|
+
const workspaceId = String(req.headers['x-taskforce-workspace-id'] || '').trim() || '-';
|
|
561
|
+
const userId = String(req.headers['x-taskforce-user-id'] || '').trim() || '-';
|
|
562
|
+
console.info(`[Taskforce RefreshDiag] method=${method} path=${pathname} status=${statusCode} durationMs=${durationMs} requestId=${requestId} workspaceId=${workspaceId} userId=${userId}`);
|
|
563
|
+
};
|
|
461
564
|
// Resolve static files directory at runtime
|
|
462
565
|
// Standalone UI source now lives in apps/app and builds to dist/ui.
|
|
463
566
|
// When built: dist/server/index.js -> dist/ui/
|
|
@@ -514,6 +617,7 @@ export function createStandaloneServer(options) {
|
|
|
514
617
|
const integritySchedulePollMinutes = readPositiveInteger(process.env.TASKFORCE_STORAGE_INTEGRITY_SCHEDULE_POLL_MINUTES, 1);
|
|
515
618
|
console.log(`[Taskforce] Storage integrity scheduler poll: every ${integritySchedulePollMinutes} minute(s)`);
|
|
516
619
|
const server = http.createServer(async (req, res) => {
|
|
620
|
+
const requestStartedAt = getRequestTimingNow();
|
|
517
621
|
if (!distUiPath) {
|
|
518
622
|
uiResolution = resolveStaticUiPath();
|
|
519
623
|
distUiPath = uiResolution.uiPath;
|
|
@@ -568,11 +672,12 @@ export function createStandaloneServer(options) {
|
|
|
568
672
|
const hasBody = method !== 'GET' && method !== 'HEAD';
|
|
569
673
|
const requestMaxPayload = resolveMaxPayloadBytesForPath(url.pathname, maxPayloadBytes);
|
|
570
674
|
const body = hasBody ? await readRequestBody(req, requestMaxPayload) : undefined;
|
|
675
|
+
const shouldStreamCloudAvatar = method === 'GET' && isCloudOwnedUserAvatarContextPath(url.pathname);
|
|
571
676
|
const upstream = await fetch(upstreamUrl, {
|
|
572
677
|
method,
|
|
573
678
|
headers,
|
|
574
679
|
body: body && body.length > 0 ? new Uint8Array(body) : undefined,
|
|
575
|
-
redirect: 'manual'
|
|
680
|
+
redirect: shouldStreamCloudAvatar ? 'follow' : 'manual'
|
|
576
681
|
});
|
|
577
682
|
const outgoingHeaders = {};
|
|
578
683
|
for (const [key, value] of upstream.headers.entries()) {
|
|
@@ -714,19 +819,16 @@ export function createStandaloneServer(options) {
|
|
|
714
819
|
return;
|
|
715
820
|
}
|
|
716
821
|
const pathname = url.pathname;
|
|
717
|
-
const isMutation = (req.method
|
|
718
|
-
&& pathname.startsWith('/api/taskforce/')
|
|
719
|
-
&& !pathname.startsWith('/api/taskforce/auth/')
|
|
720
|
-
&& pathname !== '/api/taskforce/health';
|
|
822
|
+
const isMutation = shouldApplyReadOnlyMutationGuard(req.method, pathname);
|
|
721
823
|
let access = accessForRateLimit;
|
|
722
|
-
const isAuthRoute = pathname.startsWith('/api/taskforce/auth/');
|
|
723
824
|
if (access.authenticated) {
|
|
724
825
|
access = resolveAuthorizedWorkspaceAccess(core, access, Array.isArray(req.headers['x-taskforce-workspace-id'])
|
|
725
826
|
? String(req.headers['x-taskforce-workspace-id'][0] || '')
|
|
726
827
|
: String(req.headers['x-taskforce-workspace-id'] || ''));
|
|
727
|
-
|
|
828
|
+
const disabledAccessError = resolveDisabledAccessError(access, pathname);
|
|
829
|
+
if (disabledAccessError) {
|
|
728
830
|
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
729
|
-
res.end(JSON.stringify({ error:
|
|
831
|
+
res.end(JSON.stringify({ error: disabledAccessError }));
|
|
730
832
|
return;
|
|
731
833
|
}
|
|
732
834
|
}
|
|
@@ -756,6 +858,13 @@ export function createStandaloneServer(options) {
|
|
|
756
858
|
res.writeHead = originalWriteHead;
|
|
757
859
|
const finalStatus = Number.isFinite(Number(observedStatusCode)) ? Number(observedStatusCode) : 200;
|
|
758
860
|
recordCollaborationRouteTelemetry(url.pathname, req.method || 'GET', finalStatus);
|
|
861
|
+
logRefreshDiagnostic({
|
|
862
|
+
pathname: url.pathname,
|
|
863
|
+
method: String(req.method || 'GET').toUpperCase(),
|
|
864
|
+
statusCode: finalStatus,
|
|
865
|
+
durationMs: Math.max(0, Math.round(getRequestTimingNow() - requestStartedAt)),
|
|
866
|
+
req
|
|
867
|
+
});
|
|
759
868
|
}
|
|
760
869
|
return;
|
|
761
870
|
}
|
|
@@ -789,6 +898,8 @@ export function createStandaloneServer(options) {
|
|
|
789
898
|
res.writeHead(404);
|
|
790
899
|
res.end('Not Found');
|
|
791
900
|
});
|
|
901
|
+
const timeoutSettings = configureHttpServerTimeouts(server);
|
|
902
|
+
console.log(`[Taskforce] HTTP timeouts: keepAlive=${timeoutSettings.keepAliveTimeoutMs}ms headers=${timeoutSettings.headersTimeoutMs}ms request=${timeoutSettings.requestTimeoutMs}ms`);
|
|
792
903
|
// WebSocket server at /Taskforce-ws path
|
|
793
904
|
const wss = new WebSocketServer({ server, path: '/taskforce-ws' });
|
|
794
905
|
const realtimeWsManager = createRealtimeSyncWsManager(core, authConfig);
|
|
@@ -7,6 +7,9 @@ describe('shouldApplyAuthRateLimitForPath', () => {
|
|
|
7
7
|
it('excludes auth session endpoint', () => {
|
|
8
8
|
expect(shouldApplyAuthRateLimitForPath('/api/taskforce/auth/session')).toBe(false);
|
|
9
9
|
});
|
|
10
|
+
it('excludes auth providers metadata endpoint', () => {
|
|
11
|
+
expect(shouldApplyAuthRateLimitForPath('/api/taskforce/auth/providers')).toBe(false);
|
|
12
|
+
});
|
|
10
13
|
it('excludes runtime config bootstrap endpoint', () => {
|
|
11
14
|
expect(shouldApplyAuthRateLimitForPath('/api/taskforce/auth/runtime-config')).toBe(false);
|
|
12
15
|
});
|