@taskforcehq/taskforce 0.3.304 → 0.3.306
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +140 -247
- package/dist/Taskforce.module.css +295 -89
- package/dist/TaskforceCore.js +378 -111
- package/dist/TaskforceCore.test.js +1221 -107
- package/dist/assets/fonts/CourierPrime-Bold.woff2 +0 -0
- package/dist/assets/fonts/CourierPrime-BoldItalic.woff2 +0 -0
- package/dist/assets/fonts/CourierPrime-Italic.woff2 +0 -0
- package/dist/assets/fonts/CourierPrime-Regular.woff2 +0 -0
- package/dist/assets/fonts/Noir_medium.woff2 +0 -0
- package/dist/assets/fonts/Noir_regular.woff2 +0 -0
- package/dist/assets/fonts/RussoOne-Regular.woff2 +0 -0
- package/dist/assets/fonts/SpecialElite-Regular.woff2 +0 -0
- package/dist/compat/workspaceSyncCompat.js +52 -2
- package/dist/compat/workspaceSyncCompat.test.js +38 -1
- package/dist/components/context/ContextAttachmentManager.d.ts +14 -0
- package/dist/components/context/ContextAttachmentManager.js +549 -0
- package/dist/components/features/AgentsModule.d.ts +10 -1
- package/dist/components/features/AgentsModule.js +260 -15
- package/dist/components/features/AgentsModule.test.js +255 -5
- package/dist/components/features/DocumentIndex.d.ts +1 -1
- package/dist/components/features/DocumentIndex.js +1 -1
- package/dist/components/features/DocumentViewer.d.ts +6 -2
- package/dist/components/features/DocumentViewer.js +90 -10
- package/dist/components/features/DocumentWorkspace.d.ts +5 -22
- package/dist/components/features/DocumentWorkspace.js +12 -155
- package/dist/components/features/DocumentWorkspace.test.js +226 -6
- package/dist/components/features/TaskSettings.js +29 -3
- package/dist/components/features/TaskSettings.test.js +42 -1
- package/dist/components/features/documentWorkspaceModel.d.ts +24 -0
- package/dist/components/features/documentWorkspaceModel.js +57 -0
- package/dist/components/features/useDocumentWorkspaceController.d.ts +34 -0
- package/dist/components/features/useDocumentWorkspaceController.js +113 -0
- package/dist/components/task/TaskCard.js +19 -28
- package/dist/components/task/TaskCard.test.js +38 -3
- package/dist/components/task/TaskContextUpload.js +4 -526
- package/dist/components/task/TaskForm.d.ts +8 -8
- package/dist/components/task/TaskForm.js +362 -230
- package/dist/components/task/TaskForm.test.js +558 -381
- package/dist/components/task/TaskKanban.d.ts +2 -1
- package/dist/components/task/TaskKanban.js +96 -14
- package/dist/components/task/TaskKanban.test.js +36 -0
- package/dist/components/ui/EditableAvatarButton.d.ts +18 -0
- package/dist/components/ui/EditableAvatarButton.js +18 -0
- package/dist/components/views/PlanComparisonPage.d.ts +9 -1
- package/dist/components/views/PlanComparisonPage.js +70 -17
- package/dist/components/views/PlanComparisonPage.test.js +289 -41
- package/dist/components/views/PlansPage.js +239 -106
- package/dist/components/views/PlansPage.test.js +593 -42
- package/dist/components/views/StandaloneLayout.js +310 -554
- package/dist/components/views/panels/FilterToolbar.test.js +6 -4
- package/dist/components/views/panels/PlanningDrawer.d.ts +8 -1
- package/dist/components/views/panels/PlanningDrawer.js +18 -11
- package/dist/components/views/panels/PlanningDrawer.test.d.ts +1 -0
- package/dist/components/views/panels/PlanningDrawer.test.js +141 -0
- package/dist/components/views/planningScope.d.ts +20 -0
- package/dist/components/views/planningScope.js +25 -0
- package/dist/components/views/planningScope.test.d.ts +1 -0
- package/dist/components/views/planningScope.test.js +60 -0
- package/dist/components/views/standalone/modals/AccountHubModal.d.ts +27 -14
- package/dist/components/views/standalone/modals/AccountHubModal.js +87 -15
- package/dist/components/views/standalone/modals/AccountHubModal.test.js +42 -2
- package/dist/components/views/standalone/modals/AvatarImageManagerModal.d.ts +20 -0
- package/dist/components/views/standalone/modals/AvatarImageManagerModal.js +127 -0
- package/dist/components/views/standalone/modals/EditProfileModal.d.ts +2 -9
- package/dist/components/views/standalone/modals/EditProfileModal.js +3 -2
- package/dist/components/views/standalone/modals/SyncEnableWarningModal.d.ts +9 -0
- package/dist/components/views/standalone/modals/SyncEnableWarningModal.js +6 -0
- package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.d.ts +1 -0
- package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.js +24 -0
- package/dist/components/views/standalone/modals/SyncStatusModal.d.ts +2 -1
- package/dist/components/views/standalone/modals/SyncStatusModal.js +2 -2
- package/dist/config/envSchema.js +1 -1
- package/dist/core/AiProfileService.d.ts +45 -3
- package/dist/core/AiProfileService.js +774 -63
- package/dist/core/AiProfileService.test.js +22 -2
- package/dist/core/AiProfiles.test.js +761 -2
- package/dist/core/AttachmentLinkService.js +57 -15
- package/dist/core/AuthIdentityService.d.ts +107 -0
- package/dist/core/AuthIdentityService.js +284 -0
- package/dist/core/AuthTokenService.d.ts +4 -0
- package/dist/core/AuthTokenService.js +34 -6
- package/dist/core/AuthTokenService.test.d.ts +1 -0
- package/dist/core/AuthTokenService.test.js +78 -0
- package/dist/core/EntitlementsPolicy.test.js +77 -15
- package/dist/core/GlobalSettingsService.js +115 -6
- package/dist/core/PlanEntitlementService.d.ts +30 -3
- package/dist/core/PlanEntitlementService.js +454 -93
- package/dist/core/PlanFeatureCatalog.test.d.ts +1 -0
- package/dist/core/PlanFeatureCatalog.test.js +84 -0
- package/dist/core/PlanVersionPolicy.test.js +54 -28
- package/dist/core/PlanningEntities.test.js +52 -0
- package/dist/core/SignupMonetizationSettings.test.js +241 -44
- package/dist/core/SyncReconciliationService.js +2 -65
- package/dist/core/SystemAdmin.test.js +244 -88
- package/dist/core/TaskAttachmentsCanonical.test.js +51 -1
- package/dist/core/TaskTaxonomyValidation.test.js +53 -0
- package/dist/core/Taskforce.d.ts +228 -14
- package/dist/core/Taskforce.js +814 -201
- package/dist/core/Taskforce.listTasksSlim.test.d.ts +1 -0
- package/dist/core/Taskforce.listTasksSlim.test.js +91 -0
- package/dist/core/UserProfileAvatarDrafts.test.js +30 -3
- package/dist/core/WorkspaceLifecycleService.js +0 -5
- package/dist/core/WorkspacePermissions.test.js +25 -1
- package/dist/core/WorkspacePlanMode.test.js +128 -27
- package/dist/core/shared.d.ts +3 -1
- package/dist/core/shared.js +16 -1
- package/dist/core/types.d.ts +68 -2
- package/dist/hooks/auth/useTaskforceAuthBootstrap.js +26 -1
- package/dist/hooks/sync/auth.js +3 -1
- package/dist/hooks/sync/bootstrap.js +2 -0
- package/dist/hooks/sync/controlPlane.d.ts +37 -0
- package/dist/hooks/sync/controlPlane.js +78 -0
- package/dist/hooks/sync/controlPlane.test.d.ts +1 -0
- package/dist/hooks/sync/controlPlane.test.js +121 -0
- package/dist/hooks/sync/lifecyclePolicy.d.ts +45 -0
- package/dist/hooks/sync/lifecyclePolicy.js +93 -0
- package/dist/hooks/sync/lifecyclePolicy.test.d.ts +1 -0
- package/dist/hooks/sync/lifecyclePolicy.test.js +124 -0
- package/dist/hooks/sync/orchestratorShared.d.ts +10 -1
- package/dist/hooks/sync/orchestratorShared.js +68 -47
- package/dist/hooks/sync/orchestratorShared.test.js +50 -1
- package/dist/hooks/sync/pullLifecycle.d.ts +31 -0
- package/dist/hooks/sync/pullLifecycle.js +24 -0
- package/dist/hooks/sync/pullLifecycle.test.d.ts +1 -0
- package/dist/hooks/sync/pullLifecycle.test.js +80 -0
- package/dist/hooks/sync/recovery.d.ts +16 -2
- package/dist/hooks/sync/recovery.js +171 -53
- package/dist/hooks/sync/recovery.test.d.ts +1 -0
- package/dist/hooks/sync/recovery.test.js +292 -0
- package/dist/hooks/sync/transfers.d.ts +16 -3
- package/dist/hooks/sync/transfers.js +186 -65
- package/dist/hooks/sync/transfers.test.d.ts +1 -0
- package/dist/hooks/sync/transfers.test.js +396 -0
- package/dist/hooks/sync/useRecentSyncEvents.d.ts +21 -0
- package/dist/hooks/sync/useRecentSyncEvents.js +92 -0
- package/dist/hooks/sync/useRecentSyncEvents.test.d.ts +1 -0
- package/dist/hooks/sync/useRecentSyncEvents.test.js +124 -0
- package/dist/hooks/sync/useSyncStatusActions.d.ts +30 -0
- package/dist/hooks/sync/useSyncStatusActions.js +88 -0
- package/dist/hooks/sync/useSyncStatusActions.test.d.ts +1 -0
- package/dist/hooks/sync/useSyncStatusActions.test.js +133 -0
- package/dist/hooks/sync/useSyncStatusControls.d.ts +25 -0
- package/dist/hooks/sync/useSyncStatusControls.js +60 -0
- package/dist/hooks/sync/useSyncStatusControls.test.d.ts +1 -0
- package/dist/hooks/sync/useSyncStatusControls.test.js +88 -0
- package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +12 -0
- package/dist/hooks/useSyncOrchestrator.d.ts +16 -6
- package/dist/hooks/useSyncOrchestrator.js +424 -175
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +487 -10
- package/dist/hooks/useTaskData.js +8 -3
- package/dist/hooks/useTaskData.test.js +45 -0
- package/dist/hooks/useTaskMutations.d.ts +2 -1
- package/dist/hooks/useTaskMutations.js +12 -1
- package/dist/hooks/useTaskMutations.test.js +30 -1
- package/dist/hooks/useTaskforce.d.ts +65 -4
- package/dist/hooks/useTaskforce.js +564 -243
- package/dist/hooks/useTaskforce.runtime-routing.test.d.ts +1 -0
- package/dist/hooks/useTaskforce.runtime-routing.test.js +152 -0
- package/dist/hooks/useTaskforce.sync-behavior.test.js +2454 -208
- package/dist/hooks/useWorkspaceSyncController.d.ts +4 -1
- package/dist/hooks/useWorkspaceSyncController.js +17 -2
- package/dist/hooks/useWorkspaceSyncController.test.js +1 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +16 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +316 -35
- package/dist/localization/locales/en-US.d.ts +0 -1
- package/dist/localization/locales/en-US.js +0 -1
- package/dist/localization/locales/es-419.js +0 -1
- package/dist/localization/locales/pt-BR.js +0 -1
- package/dist/mcp/adminWorkspaceRegistrar.d.ts +2 -0
- package/dist/mcp/adminWorkspaceRegistrar.js +154 -0
- package/dist/mcp/collaborationRegistrar.d.ts +2 -0
- package/dist/mcp/collaborationRegistrar.js +71 -0
- package/dist/mcp/documentAssetRegistrar.d.ts +2 -0
- package/dist/mcp/documentAssetRegistrar.js +346 -0
- package/dist/mcp/runtime.d.ts +2 -0
- package/dist/mcp/runtime.js +2433 -3534
- package/dist/mcp/runtime.test.js +440 -2
- package/dist/mcp/taskPlanningRegistrar.d.ts +2 -0
- package/dist/mcp/taskPlanningRegistrar.js +418 -0
- package/dist/mcp/toolCatalog.d.ts +35 -0
- package/dist/mcp/toolCatalog.js +3 -0
- package/dist/migrations/taskSchemaMigrations.d.ts +1 -1
- package/dist/migrations/taskSchemaMigrations.js +171 -61
- package/dist/migrations/taskSchemaMigrations.test.js +15 -0
- package/dist/resources/templates/workflow-sources/workflowDocs.mjs +6 -6
- package/dist/resources/templates/workflows/collaborate.yaml +1 -1
- package/dist/resources/templates/workflows/evaluate.yaml +2 -2
- package/dist/resources/templates/workflows/plan.yaml +1 -1
- package/dist/resources/templates/workflows/review.yaml +2 -2
- package/dist/server/annotatedAttachmentsRoutes.test.js +3 -3
- package/dist/server/auth/providers/apple.d.ts +33 -0
- package/dist/server/auth/providers/apple.js +82 -0
- package/dist/server/auth/providers/appleClientSecret.d.ts +37 -0
- package/dist/server/auth/providers/appleClientSecret.js +65 -0
- package/dist/server/auth/providers/github.d.ts +26 -0
- package/dist/server/auth/providers/github.js +86 -0
- package/dist/server/auth/providers/google.d.ts +21 -0
- package/dist/server/auth/providers/google.js +56 -0
- package/dist/server/auth/providers/types.d.ts +21 -0
- package/dist/server/auth/providers/types.js +1 -0
- package/dist/server/auth.d.ts +2 -0
- package/dist/server/auth.js +6 -3
- package/dist/server/documentReviewRoutes.test.js +36 -3
- package/dist/server/index.cookieProxy.test.js +1 -0
- package/dist/server/index.d.ts +12 -0
- package/dist/server/index.js +120 -9
- package/dist/server/index.rateLimit.test.js +3 -0
- package/dist/server/index.test.js +106 -3
- package/dist/server/routes/admin.d.ts +4 -5
- package/dist/server/routes/admin.js +410 -80
- package/dist/server/routes/annotatedAttachments.d.ts +1 -1
- package/dist/server/routes/annotatedAttachments.js +1 -1
- package/dist/server/routes/auth.d.ts +16 -6
- package/dist/server/routes/auth.js +1015 -118
- package/dist/server/routes/authSupport.d.ts +30 -0
- package/dist/server/routes/authSupport.js +81 -0
- package/dist/server/routes/billing.d.ts +1 -1
- package/dist/server/routes/billing.js +276 -34
- package/dist/server/routes/billing.test.js +760 -52
- package/dist/server/routes/documentReviews.d.ts +1 -1
- package/dist/server/routes/documentReviews.js +1 -1
- package/dist/server/routes/documents.d.ts +5 -2
- package/dist/server/routes/documents.js +242 -74
- package/dist/server/routes/primitives.d.ts +11 -0
- package/dist/server/routes/primitives.js +73 -0
- package/dist/server/routes/resources.d.ts +1 -1
- package/dist/server/routes/resources.js +1 -1
- package/dist/server/routes/shared.d.ts +29 -3
- package/dist/server/routes/shared.js +69 -4
- package/dist/server/routes/sync.d.ts +1 -1
- package/dist/server/routes/sync.integration.test.js +437 -39
- package/dist/server/routes/sync.js +62 -606
- package/dist/server/routes/syncAuxRoutes.d.ts +15 -0
- package/dist/server/routes/syncAuxRoutes.js +91 -0
- package/dist/server/routes/syncAuxRoutes.test.d.ts +1 -0
- package/dist/server/routes/syncAuxRoutes.test.js +158 -0
- package/dist/server/routes/syncPullApplyRoutes.d.ts +36 -0
- package/dist/server/routes/syncPullApplyRoutes.js +204 -0
- package/dist/server/routes/syncPushRoutes.d.ts +24 -0
- package/dist/server/routes/syncPushRoutes.js +212 -0
- package/dist/server/routes/syncRouteGuards.d.ts +36 -0
- package/dist/server/routes/syncRouteGuards.js +67 -0
- package/dist/server/routes/syncRouteGuards.test.d.ts +1 -0
- package/dist/server/routes/syncRouteGuards.test.js +94 -0
- package/dist/server/routes/syncRouteTypes.d.ts +13 -0
- package/dist/server/routes/syncRouteTypes.js +1 -0
- package/dist/server/routes/syncSnapshotRoutes.d.ts +66 -0
- package/dist/server/routes/syncSnapshotRoutes.js +180 -0
- package/dist/server/routes/tasks.d.ts +1 -1
- package/dist/server/routes/tasks.js +44 -2
- package/dist/server/routes/workspaces.d.ts +1 -1
- package/dist/server/routes/workspaces.js +37 -6
- package/dist/server/routes.d.ts +2 -9
- package/dist/server/routes.js +136 -165
- package/dist/server/routes.test.js +2457 -162
- package/dist/services/workflowExportSnapshots.test.js +2 -0
- package/dist/shared/aiProfileSeatScope.d.ts +5 -0
- package/dist/shared/aiProfileSeatScope.js +21 -0
- package/dist/shared/passwordPolicy.d.ts +13 -0
- package/dist/shared/passwordPolicy.js +84 -0
- package/dist/shared/passwordPolicy.test.d.ts +1 -0
- package/dist/shared/passwordPolicy.test.js +34 -0
- package/dist/storage/workspaceAssetStore.d.ts +15 -3
- package/dist/storage/workspaceAssetStore.js +132 -44
- package/dist/storage/workspaceAssetStore.test.js +93 -6
- package/dist/styles/fonts.css +70 -0
- package/dist/sync/collaborationSyncPayload.d.ts +6 -1
- package/dist/sync/collaborationSyncPayload.js +17 -11
- package/dist/sync/collaborationSyncPayload.test.js +13 -1
- package/dist/sync/contentSyncState.d.ts +4 -8
- package/dist/sync/contentSyncState.js +33 -77
- package/dist/sync/syncApplyHandlers.d.ts +7 -0
- package/dist/sync/syncApplyHandlers.js +28 -13
- package/dist/sync/syncApplyHandlers.test.js +72 -3
- package/dist/sync/syncFieldSemantics.d.ts +12 -0
- package/dist/sync/syncFieldSemantics.js +55 -0
- package/dist/sync/syncResourceAdapters.d.ts +44 -0
- package/dist/sync/syncResourceAdapters.js +77 -0
- package/dist/sync/syncService.d.ts +6 -0
- package/dist/sync/syncService.js +60 -0
- package/dist/sync/syncService.test.js +71 -3
- package/dist/sync/taskSyncChangeKey.d.ts +2 -0
- package/dist/sync/taskSyncChangeKey.js +143 -0
- package/dist/sync/taskSyncPayload.js +3 -8
- package/dist/sync/workspacePullFeed.js +4 -3
- package/dist/sync/workspacePullFeed.test.js +43 -0
- package/dist/sync/workspaceRepair.js +5 -2
- package/dist/sync/workspaceSyncModel.d.ts +48 -0
- package/dist/sync/workspaceSyncModel.js +195 -31
- package/dist/sync/workspaceSyncModel.test.js +325 -12
- package/dist/sync/workspaceSyncState.d.ts +19 -0
- package/dist/sync/workspaceSyncState.js +3 -1
- package/dist/sync/workspaceSyncSurface.js +3 -8
- package/dist/types.d.ts +17 -0
- package/dist/ui/assets/AgentsModule-DrLawwNl.js +1 -0
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-BlS-cqnl.js → AnnotatedAttachmentWorkspace-Z9_whf7F.js} +1 -1
- package/dist/ui/assets/CourierPrime-Bold-BuLj_dpw.woff2 +0 -0
- package/dist/ui/assets/CourierPrime-BoldItalic-BrK2pUkZ.woff2 +0 -0
- package/dist/ui/assets/CourierPrime-Italic-DFUZK5Ey.woff2 +0 -0
- package/dist/ui/assets/CourierPrime-Regular-BsKphzVf.woff2 +0 -0
- package/dist/ui/assets/DocumentWorkspace-B03MaSkw.js +252 -0
- package/dist/ui/assets/DocumentWorkspace-Dhdso07p.css +1 -0
- package/dist/ui/assets/{InitiativesModule-BjR6iBf9.js → InitiativesModule-9E6ParrY.js} +1 -1
- package/dist/ui/assets/Noir_medium-bxQwKGzB.woff2 +0 -0
- package/dist/ui/assets/Noir_regular-ojf2kxlG.woff2 +0 -0
- package/dist/ui/assets/PlansPage-CvfnMiWq.css +1 -0
- package/dist/ui/assets/PlansPage-DRXscYxH.js +1 -0
- package/dist/ui/assets/RussoOne-Regular-Cu_qq_qC.woff2 +0 -0
- package/dist/ui/assets/SpecialElite-Regular-Di6gSvAS.woff2 +0 -0
- package/dist/ui/assets/TaskSettings-DZX4jk7e.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-DnFqdUME.js → WorkflowsModule-BA7jcEpH.js} +1 -1
- package/dist/ui/assets/index-9eT8e0Lu.css +1 -0
- package/dist/ui/assets/index-C6k75jr8.js +6 -0
- package/dist/ui/assets/{vendor-icons-I7ZwG1z_.js → vendor-icons-DuEd_65c.js} +1 -1
- package/dist/ui/fonts/CourierPrime-Bold.ttf +0 -0
- package/dist/ui/fonts/CourierPrime-Bold.woff2 +0 -0
- package/dist/ui/fonts/CourierPrime-BoldItalic.ttf +0 -0
- package/dist/ui/fonts/CourierPrime-BoldItalic.woff2 +0 -0
- package/dist/ui/fonts/CourierPrime-Italic.ttf +0 -0
- package/dist/ui/fonts/CourierPrime-Italic.woff2 +0 -0
- package/dist/ui/fonts/CourierPrime-Regular.ttf +0 -0
- package/dist/ui/fonts/CourierPrime-Regular.woff2 +0 -0
- package/dist/ui/fonts/Noir_medium.otf +0 -0
- package/dist/ui/fonts/Noir_medium.woff2 +0 -0
- package/dist/ui/fonts/Noir_regular.otf +0 -0
- package/dist/ui/fonts/Noir_regular.woff2 +0 -0
- package/dist/ui/fonts/RussoOne-Regular.woff2 +0 -0
- package/dist/ui/fonts/SpecialElite-Regular.ttf +0 -0
- package/dist/ui/fonts/SpecialElite-Regular.woff2 +0 -0
- package/dist/ui/index.html +3 -3
- package/dist/utils/accountProfileSummaryCache.d.ts +5 -0
- package/dist/utils/assignees.d.ts +2 -0
- package/dist/utils/assignees.js +2 -0
- package/dist/utils/avatarUpload.d.ts +27 -0
- package/dist/utils/avatarUpload.js +132 -0
- package/dist/utils/avatarUpload.test.d.ts +1 -0
- package/dist/utils/avatarUpload.test.js +40 -0
- package/dist/utils/bootstrapTrace.d.ts +7 -0
- package/dist/utils/bootstrapTrace.js +29 -0
- package/dist/utils/commercialLifecyclePresentation.d.ts +19 -0
- package/dist/utils/commercialLifecyclePresentation.js +199 -0
- package/dist/utils/contextAssetEvents.d.ts +2 -0
- package/dist/utils/syncEventDetails.d.ts +3 -0
- package/dist/utils/syncEventDetails.js +64 -0
- package/dist/utils/syncEventDetails.test.d.ts +1 -0
- package/dist/utils/syncEventDetails.test.js +23 -0
- package/dist/utils/syncEventPresentation.d.ts +15 -0
- package/dist/utils/syncEventPresentation.js +129 -0
- package/dist/utils/syncEventPresentation.test.d.ts +1 -0
- package/dist/utils/syncEventPresentation.test.js +64 -0
- package/dist/utils/syncStatusPresentation.d.ts +72 -0
- package/dist/utils/syncStatusPresentation.js +217 -0
- package/dist/utils/syncStatusPresentation.test.d.ts +1 -0
- package/dist/utils/syncStatusPresentation.test.js +164 -0
- package/dist/utils/taskActivity.d.ts +2 -0
- package/dist/utils/taskActivity.js +228 -22
- package/dist/utils/taskActivity.test.js +119 -8
- package/dist/utils/workspaceSyncPresentation.d.ts +4 -0
- package/dist/utils/workspaceSyncPresentation.js +68 -2
- package/dist/utils/workspaceSyncPresentation.test.js +36 -1
- package/package.json +10 -2
- package/scripts/check-cloud-mcp.mjs +83 -0
- package/scripts/export-sqlite-to-postgres.mjs +7 -9
- package/scripts/playwright-auth.sh +5 -1
- package/scripts/run-billing-performance-smoke.sh +24 -0
- package/scripts/run-e2e-realtime.sh +17 -3
- package/scripts/run-smoke.sh +17 -5
- package/scripts/run-soak.sh +17 -5
- package/src/resources/templates/workflow-sources/workflowDocs.mjs +6 -6
- package/src/resources/templates/workflows/collaborate.yaml +1 -1
- package/src/resources/templates/workflows/evaluate.yaml +2 -2
- package/src/resources/templates/workflows/plan.yaml +1 -1
- package/src/resources/templates/workflows/review.yaml +2 -2
- package/dist/ui/assets/AgentsModule-BLWVbuKP.js +0 -1
- package/dist/ui/assets/DocumentWorkspace-BH_6DF6x.js +0 -250
- package/dist/ui/assets/DocumentWorkspace-C_8T8oz-.css +0 -1
- package/dist/ui/assets/PlansPage-C2dd2n1X.css +0 -1
- package/dist/ui/assets/PlansPage-CSDQ4sLa.js +0 -1
- package/dist/ui/assets/RussoOne-Regular-C3BxZIj7.ttf +0 -0
- package/dist/ui/assets/TaskSettings-CEDy34Jo.js +0 -9
- package/dist/ui/assets/index-DXUdtH1B.js +0 -6
- package/dist/ui/assets/index-Ii0B0rTO.css +0 -1
- package/scripts/migrate-planning-model.ts +0 -233
|
@@ -1,168 +1,25 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useEffect, useCallback } from 'react';
|
|
3
2
|
import styles from '../../Taskforce.module.css';
|
|
4
3
|
import { DocumentIndex } from './DocumentIndex';
|
|
5
4
|
import { DocumentViewer } from './DocumentViewer';
|
|
6
|
-
import {
|
|
7
|
-
import { logDocumentManagerDebug, resolveDocumentManagerApiBase } from '../../utils/documentManagerApi';
|
|
8
|
-
import { getReadableDocumentName } from '../../utils/documentNames';
|
|
9
|
-
import { DOCUMENT_ATTACHMENT_FILTER_OPTIONS, DOCUMENT_TYPE_FILTER_OPTIONS, } from './documentWorkspaceFilters';
|
|
5
|
+
import { useDocumentWorkspaceController } from './useDocumentWorkspaceController';
|
|
10
6
|
export { DOCUMENT_TYPE_FILTER_OPTIONS, DOCUMENT_ATTACHMENT_FILTER_OPTIONS } from './documentWorkspaceFilters';
|
|
11
|
-
function
|
|
12
|
-
let decoded = value;
|
|
13
|
-
try {
|
|
14
|
-
decoded = decodeURIComponent(value);
|
|
15
|
-
}
|
|
16
|
-
catch {
|
|
17
|
-
decoded = value;
|
|
18
|
-
}
|
|
19
|
-
const normalized = decoded
|
|
20
|
-
.trim()
|
|
21
|
-
.replace(/\\/g, '/')
|
|
22
|
-
.replace(/^\/+/, '')
|
|
23
|
-
.replace(/^\.\//, '')
|
|
24
|
-
.replace(/^\.taskforce\//, '');
|
|
25
|
-
return normalized;
|
|
26
|
-
}
|
|
27
|
-
function docPathMatches(candidate, requested) {
|
|
28
|
-
if (candidate === requested)
|
|
29
|
-
return true;
|
|
30
|
-
if (candidate.endsWith(`/${requested}`))
|
|
31
|
-
return true;
|
|
32
|
-
if (requested.endsWith(`/${candidate}`))
|
|
33
|
-
return true;
|
|
34
|
-
return false;
|
|
35
|
-
}
|
|
36
|
-
function extractAssetIdFromPath(value) {
|
|
37
|
-
const normalized = normalizeDocPath(String(value || ''));
|
|
38
|
-
if (!normalized)
|
|
39
|
-
return null;
|
|
40
|
-
const match = normalized.match(/(^|\/)(asset-[a-f0-9]{32})(?=[^/]*$)/i);
|
|
41
|
-
return match?.[2] ? match[2].toLowerCase() : null;
|
|
42
|
-
}
|
|
43
|
-
export function getEffectiveAttachmentState(doc) {
|
|
44
|
-
if (doc.attachmentState === 'attached' || doc.attachmentState === 'unattached') {
|
|
45
|
-
return doc.attachmentState;
|
|
46
|
-
}
|
|
47
|
-
const attachmentCount = typeof doc.attachmentCount === 'number' && Number.isFinite(doc.attachmentCount)
|
|
48
|
-
? doc.attachmentCount
|
|
49
|
-
: 0;
|
|
50
|
-
if (attachmentCount > 0 || !!doc.taskId) {
|
|
51
|
-
return 'attached';
|
|
52
|
-
}
|
|
53
|
-
return 'unattached';
|
|
54
|
-
}
|
|
55
|
-
export function DocumentWorkspace({ tasks, apiBaseUrl = '', documents, loading, error, selectedDoc, searchQuery, typeFilters, attachmentFilters, onSearchChange, onSelectDoc, onRefresh, enableTaskGeneration = false }) {
|
|
56
|
-
const filteredDocs = documents.filter(doc => {
|
|
57
|
-
const readableName = getReadableDocumentName(doc);
|
|
58
|
-
const haystack = `${readableName} ${doc.originalFilename || ''}`.toLowerCase();
|
|
59
|
-
const matchesSearch = !searchQuery || haystack.includes(searchQuery.toLowerCase());
|
|
60
|
-
const matchesType = typeFilters.length === 0 || typeFilters.includes(doc.docType);
|
|
61
|
-
const attachmentState = getEffectiveAttachmentState(doc);
|
|
62
|
-
const matchesAttachment = attachmentFilters.length === 0 || attachmentFilters.includes(attachmentState);
|
|
63
|
-
return matchesSearch && matchesType && matchesAttachment;
|
|
64
|
-
});
|
|
65
|
-
const hasActiveFilters = searchQuery.trim().length > 0 || typeFilters.length > 0 || attachmentFilters.length > 0;
|
|
7
|
+
export function DocumentWorkspace({ tasks, apiBaseUrl = '', documents, loading, error, selectedDoc, searchQuery, hasActiveFilters, onSearchChange, onSelectDoc, onRefresh, onDeleteSelected, enableTaskGeneration = false }) {
|
|
66
8
|
const taskById = new Map(tasks.map(t => [t.id, t]));
|
|
67
|
-
return (_jsxs("div", { className: styles.docWorkspace, children: [_jsx("div", { className: styles.docIndexPanel, children: _jsx(DocumentIndex, { documents:
|
|
68
|
-
onSelectDoc(null);
|
|
69
|
-
void onRefresh();
|
|
70
|
-
}, enableTaskGeneration: enableTaskGeneration })) : !loading && (_jsx("div", { className: styles.docViewerEmpty, children: _jsx("p", { className: "tf-empty-copy", children: filteredDocs.length === 0 ? 'No documents match your current filters.' : 'Select a document to view it.' }) })) })] }));
|
|
9
|
+
return (_jsxs("div", { className: styles.docWorkspace, children: [_jsx("div", { className: styles.docIndexPanel, children: _jsx(DocumentIndex, { documents: documents, selectedDocId: selectedDoc?.id ?? null, loading: loading, error: error, searchQuery: searchQuery, onSearchChange: onSearchChange, onSelect: (doc) => onSelectDoc(doc), onRefresh: onRefresh, taskById: taskById, hasActiveFilters: hasActiveFilters }) }), _jsx("div", { className: styles.docViewerPanel, children: selectedDoc ? (_jsx(DocumentViewer, { doc: selectedDoc, taskTitle: selectedDoc.taskId ? (taskById.get(selectedDoc.taskId)?.title ?? null) : null, tasks: tasks, apiBaseUrl: apiBaseUrl, onSaved: (_updatedAt) => onRefresh(), onDeleted: () => void onDeleteSelected(), enableTaskGeneration: enableTaskGeneration })) : !loading && (_jsx("div", { className: styles.docViewerEmpty, children: _jsx("p", { className: "tf-empty-copy", children: documents.length === 0 ? 'No documents match your current filters.' : 'Select a document to view it.' }) })) })] }));
|
|
71
10
|
}
|
|
72
11
|
export function DocumentWorkspaceShell({ tasks, runtimeMode = 'local', apiBaseUrl = '', cloudAuthBaseUrl = '', typeFilters: controlledTypeFilters, attachmentFilters: controlledAttachmentFilters, onTypeFiltersChange, onAttachmentFiltersChange, requestedDocPath = null, requestedDocAssetId = null, onRequestedDocHandled, enableTaskGeneration = false }) {
|
|
73
|
-
const
|
|
74
|
-
const [loading, setLoading] = useState(true);
|
|
75
|
-
const [error, setError] = useState(null);
|
|
76
|
-
const [selectedDoc, setSelectedDoc] = useState(null);
|
|
77
|
-
const [searchQuery, setSearchQuery] = useState('');
|
|
78
|
-
const [internalTypeFilters, setInternalTypeFilters] = useState(() => DOCUMENT_TYPE_FILTER_OPTIONS.map((option) => option.value));
|
|
79
|
-
const [internalAttachmentFilters, setInternalAttachmentFilters] = useState(() => DOCUMENT_ATTACHMENT_FILTER_OPTIONS.map((option) => option.value));
|
|
80
|
-
const documentApiBaseUrl = resolveDocumentManagerApiBase({
|
|
12
|
+
const { documentApiBaseUrl, filteredDocs, loading, error, selectedDoc, searchQuery, hasActiveFilters, setSearchQuery, fetchDocuments, selectDoc, handleDeleteSelected, } = useDocumentWorkspaceController({
|
|
81
13
|
runtimeMode,
|
|
82
14
|
apiBaseUrl,
|
|
83
15
|
cloudAuthBaseUrl,
|
|
16
|
+
typeFilters: controlledTypeFilters,
|
|
17
|
+
attachmentFilters: controlledAttachmentFilters,
|
|
18
|
+
onTypeFiltersChange,
|
|
19
|
+
onAttachmentFiltersChange,
|
|
20
|
+
requestedDocPath,
|
|
21
|
+
requestedDocAssetId,
|
|
22
|
+
onRequestedDocHandled,
|
|
84
23
|
});
|
|
85
|
-
|
|
86
|
-
const attachmentFilters = controlledAttachmentFilters ?? internalAttachmentFilters;
|
|
87
|
-
const handleTypeFiltersChange = onTypeFiltersChange ?? setInternalTypeFilters;
|
|
88
|
-
const handleAttachmentFiltersChange = onAttachmentFiltersChange ?? setInternalAttachmentFilters;
|
|
89
|
-
const fetchDocuments = useCallback(async () => {
|
|
90
|
-
setLoading(true);
|
|
91
|
-
setError(null);
|
|
92
|
-
try {
|
|
93
|
-
const res = await fetchTaskforceApi('/api/taskforce/documents', {
|
|
94
|
-
credentials: 'include',
|
|
95
|
-
}, documentApiBaseUrl);
|
|
96
|
-
if (!res.ok)
|
|
97
|
-
throw new Error(`Failed to load documents (${res.status})`);
|
|
98
|
-
const data = await res.json();
|
|
99
|
-
const nextDocuments = data.documents || [];
|
|
100
|
-
logDocumentManagerDebug('list.loaded', {
|
|
101
|
-
runtimeMode,
|
|
102
|
-
apiBaseUrl: documentApiBaseUrl || '(same-origin)',
|
|
103
|
-
count: Array.isArray(nextDocuments) ? nextDocuments.length : 0,
|
|
104
|
-
});
|
|
105
|
-
setDocuments(nextDocuments);
|
|
106
|
-
}
|
|
107
|
-
catch (e) {
|
|
108
|
-
setError(e?.message || 'Failed to load documents');
|
|
109
|
-
logDocumentManagerDebug('list.error', {
|
|
110
|
-
runtimeMode,
|
|
111
|
-
apiBaseUrl: documentApiBaseUrl || '(same-origin)',
|
|
112
|
-
message: e?.message || 'Failed to load documents',
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
finally {
|
|
116
|
-
setLoading(false);
|
|
117
|
-
}
|
|
118
|
-
}, [documentApiBaseUrl, runtimeMode]);
|
|
119
|
-
useEffect(() => {
|
|
120
|
-
fetchDocuments();
|
|
121
|
-
}, [fetchDocuments]);
|
|
122
|
-
// Auto-select first doc when list loads
|
|
123
|
-
useEffect(() => {
|
|
124
|
-
if (requestedDocPath || requestedDocAssetId)
|
|
125
|
-
return;
|
|
126
|
-
if (!selectedDoc && documents.length > 0) {
|
|
127
|
-
setSelectedDoc(documents[0]);
|
|
128
|
-
}
|
|
129
|
-
}, [documents, selectedDoc, requestedDocAssetId, requestedDocPath]);
|
|
130
|
-
useEffect(() => {
|
|
131
|
-
if ((!requestedDocPath && !requestedDocAssetId) || documents.length === 0)
|
|
132
|
-
return;
|
|
133
|
-
const requested = requestedDocPath ? normalizeDocPath(requestedDocPath) : '';
|
|
134
|
-
const requestedAssetId = String(requestedDocAssetId || '').trim().toLowerCase()
|
|
135
|
-
|| extractAssetIdFromPath(requestedDocPath);
|
|
136
|
-
const matched = documents.find((doc) => {
|
|
137
|
-
const candidateAssetId = extractAssetIdFromPath(doc.fsPath) || String(doc.assetId || '').trim().toLowerCase() || null;
|
|
138
|
-
if (requestedAssetId && candidateAssetId && candidateAssetId === requestedAssetId)
|
|
139
|
-
return true;
|
|
140
|
-
if (!requested)
|
|
141
|
-
return false;
|
|
142
|
-
const candidate = normalizeDocPath(doc.fsPath);
|
|
143
|
-
return docPathMatches(candidate, requested);
|
|
144
|
-
});
|
|
145
|
-
if (matched) {
|
|
146
|
-
setSelectedDoc(matched);
|
|
147
|
-
onRequestedDocHandled?.();
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
}, [requestedDocAssetId, requestedDocPath, documents, onRequestedDocHandled]);
|
|
151
|
-
useEffect(() => {
|
|
152
|
-
if ((requestedDocPath || requestedDocAssetId) && !selectedDoc)
|
|
153
|
-
return;
|
|
154
|
-
const filtered = documents.filter((doc) => {
|
|
155
|
-
const readableName = getReadableDocumentName(doc);
|
|
156
|
-
const haystack = `${readableName} ${doc.originalFilename || ''}`.toLowerCase();
|
|
157
|
-
const matchesSearch = !searchQuery || haystack.includes(searchQuery.toLowerCase());
|
|
158
|
-
const matchesType = typeFilters.length === 0 || typeFilters.includes(doc.docType);
|
|
159
|
-
const attachmentState = getEffectiveAttachmentState(doc);
|
|
160
|
-
const matchesAttachment = attachmentFilters.length === 0 || attachmentFilters.includes(attachmentState);
|
|
161
|
-
return matchesSearch && matchesType && matchesAttachment;
|
|
162
|
-
});
|
|
163
|
-
if (selectedDoc && filtered.some((doc) => doc.id === selectedDoc.id))
|
|
164
|
-
return;
|
|
165
|
-
setSelectedDoc(filtered[0] ?? null);
|
|
166
|
-
}, [attachmentFilters, documents, requestedDocAssetId, requestedDocPath, searchQuery, selectedDoc, typeFilters]);
|
|
167
|
-
return (_jsx(DocumentWorkspace, { tasks: tasks, apiBaseUrl: documentApiBaseUrl, documents: documents, loading: loading, error: error, selectedDoc: selectedDoc, searchQuery: searchQuery, typeFilters: typeFilters, attachmentFilters: attachmentFilters, onSearchChange: setSearchQuery, onSelectDoc: setSelectedDoc, onRefresh: fetchDocuments, enableTaskGeneration: enableTaskGeneration }));
|
|
24
|
+
return (_jsx(DocumentWorkspace, { tasks: tasks, apiBaseUrl: documentApiBaseUrl, documents: filteredDocs, loading: loading, error: error, selectedDoc: selectedDoc, searchQuery: searchQuery, hasActiveFilters: hasActiveFilters, onSearchChange: setSearchQuery, onSelectDoc: selectDoc, onRefresh: fetchDocuments, onDeleteSelected: handleDeleteSelected, enableTaskGeneration: enableTaskGeneration }));
|
|
168
25
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from 'react';
|
|
3
3
|
import { fireEvent, render, screen, waitFor, within } from '@testing-library/react';
|
|
4
4
|
import userEvent from '@testing-library/user-event';
|
|
@@ -44,6 +44,9 @@ function createEmptyReviewSessionsResponse() {
|
|
|
44
44
|
json: async () => ({ sessions: [] }),
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
|
+
function renderDocumentWorkspace(props) {
|
|
48
|
+
return render(_jsx(DocumentWorkspace, { tasks: props.tasks ?? [], documents: props.documents, loading: false, error: null, selectedDoc: props.selectedDoc ?? null, searchQuery: props.searchQuery ?? '', hasActiveFilters: props.hasActiveFilters ?? false, onSearchChange: props.onSearchChange ?? (() => { }), onSelectDoc: props.onSelectDoc ?? (() => { }), onRefresh: props.onRefresh ?? (() => { }), onDeleteSelected: props.onDeleteSelected ?? (() => { }) }));
|
|
49
|
+
}
|
|
47
50
|
describe('Document workspace management', () => {
|
|
48
51
|
beforeEach(() => {
|
|
49
52
|
fetchTaskforceApi.mockReset();
|
|
@@ -87,7 +90,7 @@ describe('Document workspace management', () => {
|
|
|
87
90
|
];
|
|
88
91
|
function Harness() {
|
|
89
92
|
const [selectedDoc, setSelectedDoc] = useState(null);
|
|
90
|
-
return (_jsx(DocumentWorkspace, { tasks: [{ id: 'task-1', title: 'Parent task' }], documents: docs, loading: false, error: null, selectedDoc: selectedDoc, searchQuery: "",
|
|
93
|
+
return (_jsx(DocumentWorkspace, { tasks: [{ id: 'task-1', title: 'Parent task' }], documents: docs.filter((doc) => doc.attachmentState === 'unattached'), loading: false, error: null, selectedDoc: selectedDoc, searchQuery: "", hasActiveFilters: true, onSearchChange: () => { }, onSelectDoc: setSelectedDoc, onRefresh: () => { }, onDeleteSelected: () => { } }));
|
|
91
94
|
}
|
|
92
95
|
render(_jsx(Harness, {}));
|
|
93
96
|
expect(screen.getByRole('button', { name: /Orphan notes/i })).toBeInTheDocument();
|
|
@@ -95,13 +98,13 @@ describe('Document workspace management', () => {
|
|
|
95
98
|
expect(screen.getByText('Unattached')).toBeInTheDocument();
|
|
96
99
|
});
|
|
97
100
|
it('keeps document search in the index without duplicate filter controls', () => {
|
|
98
|
-
|
|
101
|
+
renderDocumentWorkspace({ documents: [createDoc()] });
|
|
99
102
|
expect(screen.getByPlaceholderText('Search documents…')).toBeInTheDocument();
|
|
100
103
|
expect(screen.queryByRole('button', { name: 'Attached' })).not.toBeInTheDocument();
|
|
101
104
|
expect(screen.queryByRole('button', { name: 'Plans' })).not.toBeInTheDocument();
|
|
102
105
|
});
|
|
103
106
|
it('shows the document reference badge in the bottom metadata row of index cards', () => {
|
|
104
|
-
|
|
107
|
+
renderDocumentWorkspace({ documents: [createDoc({ referenceLabel: 'D-42' })] });
|
|
105
108
|
expect(screen.getByText('D-42')).toBeInTheDocument();
|
|
106
109
|
expect(screen.getByText(/64B/)).toBeInTheDocument();
|
|
107
110
|
});
|
|
@@ -142,6 +145,82 @@ describe('Document workspace management', () => {
|
|
|
142
145
|
confirmSpy.mockRestore();
|
|
143
146
|
alertSpy.mockRestore();
|
|
144
147
|
});
|
|
148
|
+
it('opens an attach modal from the document viewer and attaches the document to a task', async () => {
|
|
149
|
+
const user = userEvent.setup();
|
|
150
|
+
fetchTaskforceApi
|
|
151
|
+
.mockResolvedValueOnce({
|
|
152
|
+
ok: true,
|
|
153
|
+
text: async () => '# Example\n',
|
|
154
|
+
})
|
|
155
|
+
.mockResolvedValueOnce(createEmptyReviewSessionsResponse())
|
|
156
|
+
.mockResolvedValueOnce({
|
|
157
|
+
ok: true,
|
|
158
|
+
json: async () => ({ success: true, attached: true, alreadyAttached: false }),
|
|
159
|
+
});
|
|
160
|
+
const onSaved = vi.fn();
|
|
161
|
+
render(_jsx(DocumentViewer, { doc: createDoc({
|
|
162
|
+
id: 'doc-attach',
|
|
163
|
+
assetId: 'asset-doc-attach',
|
|
164
|
+
attachmentCount: 0,
|
|
165
|
+
attachmentState: 'unattached',
|
|
166
|
+
}), taskTitle: null, tasks: [
|
|
167
|
+
{ id: 'task-1', title: 'First attach target' },
|
|
168
|
+
{ id: 'task-2', title: 'Second attach target' },
|
|
169
|
+
], onSaved: onSaved }));
|
|
170
|
+
await waitFor(() => expect(fetchTaskforceApi).toHaveBeenCalledWith('/api/taskforce/documents/asset-doc-1/content', { credentials: 'same-origin' }, ''));
|
|
171
|
+
await user.click(screen.getByRole('button', { name: /^Attach$/i }));
|
|
172
|
+
expect(screen.getByText(/Attach this document to a task from the document manager/i)).toBeInTheDocument();
|
|
173
|
+
await user.type(screen.getByPlaceholderText('Search tasks…'), 'Second');
|
|
174
|
+
await user.click(screen.getByRole('button', { name: /Second attach target/i }));
|
|
175
|
+
await waitFor(() => expect(fetchTaskforceApi).toHaveBeenLastCalledWith('/api/taskforce/documents/asset-doc-attach/attach', expect.objectContaining({
|
|
176
|
+
method: 'POST',
|
|
177
|
+
credentials: 'include',
|
|
178
|
+
}), ''));
|
|
179
|
+
expect(onSaved).toHaveBeenCalled();
|
|
180
|
+
});
|
|
181
|
+
it('shows delete and unlink for attached docs and sends forceDetach on delete', async () => {
|
|
182
|
+
const user = userEvent.setup();
|
|
183
|
+
fetchTaskforceApi
|
|
184
|
+
.mockResolvedValueOnce({
|
|
185
|
+
ok: true,
|
|
186
|
+
text: async () => '# Example\n',
|
|
187
|
+
})
|
|
188
|
+
.mockResolvedValueOnce(createEmptyReviewSessionsResponse())
|
|
189
|
+
.mockResolvedValueOnce({
|
|
190
|
+
ok: true,
|
|
191
|
+
json: async () => ({ success: true, softDeleted: true, detachedTaskCount: 2 }),
|
|
192
|
+
});
|
|
193
|
+
const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(true);
|
|
194
|
+
const alertSpy = vi.spyOn(window, 'alert').mockImplementation(() => { });
|
|
195
|
+
const onDeleted = vi.fn();
|
|
196
|
+
render(_jsx(DocumentViewer, { doc: {
|
|
197
|
+
id: 'doc-attached',
|
|
198
|
+
assetId: 'asset-doc-attached',
|
|
199
|
+
fsPath: 'workspaces/default/assets/documents/doc-attached.md',
|
|
200
|
+
apiUrl: '/api/taskforce/documents/asset-doc-attached/content',
|
|
201
|
+
taskId: 'task-1',
|
|
202
|
+
displayName: 'Attached spec',
|
|
203
|
+
title: 'Attached spec',
|
|
204
|
+
docType: 'other',
|
|
205
|
+
sizeBytes: 64,
|
|
206
|
+
updatedAt: '2026-03-19T11:00:00.000Z',
|
|
207
|
+
editable: true,
|
|
208
|
+
attachmentCount: 2,
|
|
209
|
+
attachmentState: 'attached',
|
|
210
|
+
}, taskTitle: null, onDeleted: onDeleted }));
|
|
211
|
+
await waitFor(() => expect(fetchTaskforceApi).toHaveBeenCalledWith('/api/taskforce/documents/asset-doc-attached/content', { credentials: 'same-origin' }, ''));
|
|
212
|
+
await user.click(screen.getByRole('button', { name: /Delete & Unlink/i }));
|
|
213
|
+
await waitFor(() => expect(fetchTaskforceApi).toHaveBeenLastCalledWith('/api/taskforce/documents/asset-doc-attached', expect.objectContaining({
|
|
214
|
+
method: 'DELETE',
|
|
215
|
+
credentials: 'include',
|
|
216
|
+
headers: { 'Content-Type': 'application/json' },
|
|
217
|
+
body: JSON.stringify({ forceDetach: true })
|
|
218
|
+
}), ''));
|
|
219
|
+
expect(confirmSpy).toHaveBeenCalledWith(expect.stringContaining('remove it from 2 linked tasks'));
|
|
220
|
+
expect(onDeleted).toHaveBeenCalled();
|
|
221
|
+
confirmSpy.mockRestore();
|
|
222
|
+
alertSpy.mockRestore();
|
|
223
|
+
});
|
|
145
224
|
it('uses same-origin document routes in local runtime even when an api base is configured', async () => {
|
|
146
225
|
const user = userEvent.setup();
|
|
147
226
|
fetchTaskforceApi
|
|
@@ -226,6 +305,46 @@ describe('Document workspace management', () => {
|
|
|
226
305
|
expect(await screen.findByText('Please tighten the rollout section.')).toBeInTheDocument();
|
|
227
306
|
expect(screen.getByText('Lines 18-24')).toBeInTheDocument();
|
|
228
307
|
});
|
|
308
|
+
it('toggles the review sidebar from the document header', async () => {
|
|
309
|
+
const user = userEvent.setup();
|
|
310
|
+
fetchTaskforceApi
|
|
311
|
+
.mockResolvedValueOnce({
|
|
312
|
+
ok: true,
|
|
313
|
+
text: async () => '# Example\n',
|
|
314
|
+
})
|
|
315
|
+
.mockResolvedValueOnce({
|
|
316
|
+
ok: true,
|
|
317
|
+
json: async () => ({
|
|
318
|
+
sessions: [{
|
|
319
|
+
id: 'review-1',
|
|
320
|
+
tenantId: 'default',
|
|
321
|
+
workspaceId: 'default',
|
|
322
|
+
assetId: 'asset-doc-1',
|
|
323
|
+
documentId: 'doc-1',
|
|
324
|
+
documentVersion: 1,
|
|
325
|
+
title: 'Review 1',
|
|
326
|
+
status: 'open',
|
|
327
|
+
createdByActorId: 'user',
|
|
328
|
+
updatedByActorId: 'user',
|
|
329
|
+
createdByActor: null,
|
|
330
|
+
updatedByActor: null,
|
|
331
|
+
createdAt: '2026-03-19T12:00:00.000Z',
|
|
332
|
+
updatedAt: '2026-03-19T12:05:00.000Z',
|
|
333
|
+
deletedAt: null,
|
|
334
|
+
comments: [],
|
|
335
|
+
}],
|
|
336
|
+
}),
|
|
337
|
+
});
|
|
338
|
+
render(_jsx(DocumentViewer, { doc: createDoc(), taskTitle: null }));
|
|
339
|
+
expect(await screen.findByText('Review & Comments')).toBeInTheDocument();
|
|
340
|
+
expect(screen.getByPlaceholderText('Add a comment for this document review…')).toBeInTheDocument();
|
|
341
|
+
await user.click(screen.getByRole('button', { name: 'Hide Review' }));
|
|
342
|
+
expect(screen.queryByText('Review & Comments')).not.toBeInTheDocument();
|
|
343
|
+
expect(screen.queryByPlaceholderText('Add a comment for this document review…')).not.toBeInTheDocument();
|
|
344
|
+
await user.click(screen.getByRole('button', { name: 'Show Review' }));
|
|
345
|
+
expect(await screen.findByText('Review & Comments')).toBeInTheDocument();
|
|
346
|
+
expect(screen.getByPlaceholderText('Add a comment for this document review…')).toBeInTheDocument();
|
|
347
|
+
});
|
|
229
348
|
it('creates a review session and posts comments from the document viewer', async () => {
|
|
230
349
|
const user = userEvent.setup();
|
|
231
350
|
fetchTaskforceApi
|
|
@@ -1087,13 +1206,114 @@ Body copy.
|
|
|
1087
1206
|
attachmentCount: 0,
|
|
1088
1207
|
},
|
|
1089
1208
|
];
|
|
1090
|
-
const { rerender } =
|
|
1209
|
+
const { rerender } = renderDocumentWorkspace({
|
|
1210
|
+
tasks: [{ id: 'task-1', title: 'Parent task' }],
|
|
1211
|
+
documents: docs.filter((doc) => doc.taskId),
|
|
1212
|
+
hasActiveFilters: true,
|
|
1213
|
+
});
|
|
1091
1214
|
expect(screen.getByText('Attached fallback')).toBeInTheDocument();
|
|
1092
1215
|
expect(screen.queryByText('Unattached fallback')).not.toBeInTheDocument();
|
|
1093
|
-
rerender(_jsx(DocumentWorkspace, { tasks: [{ id: 'task-1', title: 'Parent task' }], documents: docs, loading: false, error: null, selectedDoc: null, searchQuery: "",
|
|
1216
|
+
rerender(_jsx(DocumentWorkspace, { tasks: [{ id: 'task-1', title: 'Parent task' }], documents: docs.filter((doc) => !doc.taskId), loading: false, error: null, selectedDoc: null, searchQuery: "", hasActiveFilters: true, onSearchChange: () => { }, onSelectDoc: () => { }, onRefresh: () => { }, onDeleteSelected: () => { } }));
|
|
1094
1217
|
expect(screen.getByText('Unattached fallback')).toBeInTheDocument();
|
|
1095
1218
|
expect(screen.queryByText('Attached fallback')).not.toBeInTheDocument();
|
|
1096
1219
|
});
|
|
1220
|
+
it('switches the active document when a different index item is selected', async () => {
|
|
1221
|
+
const user = userEvent.setup();
|
|
1222
|
+
const docs = [
|
|
1223
|
+
createDoc({
|
|
1224
|
+
id: 'doc-first',
|
|
1225
|
+
assetId: 'asset-doc-first',
|
|
1226
|
+
apiUrl: '/api/taskforce/documents/asset-doc-first/content',
|
|
1227
|
+
displayName: 'First notes',
|
|
1228
|
+
title: 'First notes',
|
|
1229
|
+
}),
|
|
1230
|
+
createDoc({
|
|
1231
|
+
id: 'doc-second',
|
|
1232
|
+
assetId: 'asset-doc-second',
|
|
1233
|
+
apiUrl: '/api/taskforce/documents/asset-doc-second/content',
|
|
1234
|
+
displayName: 'Second notes',
|
|
1235
|
+
title: 'Second notes',
|
|
1236
|
+
fsPath: 'workspaces/default/assets/documents/second-notes.md',
|
|
1237
|
+
}),
|
|
1238
|
+
];
|
|
1239
|
+
fetchTaskforceApi
|
|
1240
|
+
.mockResolvedValueOnce({
|
|
1241
|
+
ok: true,
|
|
1242
|
+
json: async () => ({ documents: docs }),
|
|
1243
|
+
})
|
|
1244
|
+
.mockResolvedValueOnce({
|
|
1245
|
+
ok: true,
|
|
1246
|
+
text: async () => '# First content\n',
|
|
1247
|
+
})
|
|
1248
|
+
.mockResolvedValueOnce(createEmptyReviewSessionsResponse())
|
|
1249
|
+
.mockResolvedValueOnce({
|
|
1250
|
+
ok: true,
|
|
1251
|
+
text: async () => '# Second content\n',
|
|
1252
|
+
})
|
|
1253
|
+
.mockResolvedValueOnce(createEmptyReviewSessionsResponse());
|
|
1254
|
+
render(_jsx(DocumentWorkspaceShell, { tasks: [], runtimeMode: "local" }));
|
|
1255
|
+
expect(await screen.findByText('First content')).toBeInTheDocument();
|
|
1256
|
+
await user.click(screen.getByRole('button', { name: /Second notes/i }));
|
|
1257
|
+
expect(await screen.findByText('Second content')).toBeInTheDocument();
|
|
1258
|
+
await waitFor(() => expect(fetchTaskforceApi).toHaveBeenNthCalledWith(4, '/api/taskforce/documents/asset-doc-second/content', expect.objectContaining({ credentials: 'same-origin' }), ''));
|
|
1259
|
+
});
|
|
1260
|
+
it('returns to the remaining filtered document when the active selection is filtered out', async () => {
|
|
1261
|
+
const user = userEvent.setup();
|
|
1262
|
+
const docs = [
|
|
1263
|
+
createDoc({
|
|
1264
|
+
id: 'doc-attached-return',
|
|
1265
|
+
assetId: 'asset-doc-attached-return',
|
|
1266
|
+
apiUrl: '/api/taskforce/documents/asset-doc-attached-return/content',
|
|
1267
|
+
displayName: 'Attached return doc',
|
|
1268
|
+
title: 'Attached return doc',
|
|
1269
|
+
attachmentCount: 1,
|
|
1270
|
+
attachmentState: 'attached',
|
|
1271
|
+
taskId: 'task-1',
|
|
1272
|
+
}),
|
|
1273
|
+
createDoc({
|
|
1274
|
+
id: 'doc-unattached-return',
|
|
1275
|
+
assetId: 'asset-doc-unattached-return',
|
|
1276
|
+
apiUrl: '/api/taskforce/documents/asset-doc-unattached-return/content',
|
|
1277
|
+
displayName: 'Unattached return doc',
|
|
1278
|
+
title: 'Unattached return doc',
|
|
1279
|
+
fsPath: 'workspaces/default/assets/documents/unattached-return-doc.md',
|
|
1280
|
+
attachmentCount: 0,
|
|
1281
|
+
attachmentState: 'unattached',
|
|
1282
|
+
taskId: null,
|
|
1283
|
+
}),
|
|
1284
|
+
];
|
|
1285
|
+
function Harness() {
|
|
1286
|
+
const [attachmentFilters, setAttachmentFilters] = useState([]);
|
|
1287
|
+
return (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", onClick: () => setAttachmentFilters(['attached']), children: "Show attached only" }), _jsx(DocumentWorkspaceShell, { tasks: [{ id: 'task-1', title: 'Parent task' }], runtimeMode: "local", attachmentFilters: attachmentFilters })] }));
|
|
1288
|
+
}
|
|
1289
|
+
fetchTaskforceApi
|
|
1290
|
+
.mockResolvedValueOnce({
|
|
1291
|
+
ok: true,
|
|
1292
|
+
json: async () => ({ documents: docs }),
|
|
1293
|
+
})
|
|
1294
|
+
.mockResolvedValueOnce({
|
|
1295
|
+
ok: true,
|
|
1296
|
+
text: async () => '# Attached return content\n',
|
|
1297
|
+
})
|
|
1298
|
+
.mockResolvedValueOnce(createEmptyReviewSessionsResponse())
|
|
1299
|
+
.mockResolvedValueOnce({
|
|
1300
|
+
ok: true,
|
|
1301
|
+
text: async () => '# Unattached return content\n',
|
|
1302
|
+
})
|
|
1303
|
+
.mockResolvedValueOnce(createEmptyReviewSessionsResponse())
|
|
1304
|
+
.mockResolvedValueOnce({
|
|
1305
|
+
ok: true,
|
|
1306
|
+
text: async () => '# Attached return content\n',
|
|
1307
|
+
})
|
|
1308
|
+
.mockResolvedValueOnce(createEmptyReviewSessionsResponse());
|
|
1309
|
+
render(_jsx(Harness, {}));
|
|
1310
|
+
expect(await screen.findByText('Attached return content')).toBeInTheDocument();
|
|
1311
|
+
await user.click(screen.getByRole('button', { name: /Unattached return doc/i }));
|
|
1312
|
+
expect(await screen.findByText('Unattached return content')).toBeInTheDocument();
|
|
1313
|
+
await user.click(screen.getByRole('button', { name: /Show attached only/i }));
|
|
1314
|
+
expect(await screen.findByText('Attached return content')).toBeInTheDocument();
|
|
1315
|
+
await waitFor(() => expect(fetchTaskforceApi).toHaveBeenNthCalledWith(6, '/api/taskforce/documents/asset-doc-attached-return/content', expect.objectContaining({ credentials: 'same-origin' }), ''));
|
|
1316
|
+
});
|
|
1097
1317
|
it('selects a requested document by asset id when normalized paths differ', async () => {
|
|
1098
1318
|
const docs = [
|
|
1099
1319
|
{
|
|
@@ -534,13 +534,36 @@ export function TaskSettings(props) {
|
|
|
534
534
|
const workspaceName = typeof details?.tokenWorkspaceId === 'string'
|
|
535
535
|
? String(details.tokenWorkspaceId)
|
|
536
536
|
: undefined;
|
|
537
|
-
const
|
|
537
|
+
const endpoint = typeof details?.endpoint === 'string'
|
|
538
|
+
? String(details.endpoint)
|
|
539
|
+
: cloudMcpEndpoint;
|
|
540
|
+
let message = payload?.code === 'MCP_TOKEN_MISSING_SCOPES' && missingScopes.length > 0
|
|
538
541
|
? `Token is missing required scopes: ${missingScopes.join(', ')}.`
|
|
539
542
|
: String(payload?.error || 'Failed to test MCP connection.');
|
|
543
|
+
let followup;
|
|
544
|
+
if (payload?.code === 'MCP_ENDPOINT_GATEWAY_ERROR') {
|
|
545
|
+
message = 'The dedicated MCP host is responding with a gateway error.';
|
|
546
|
+
followup = 'This usually means the cloud MCP service is degraded. Retry shortly, and if it keeps failing treat it as an environment incident rather than a bad token.';
|
|
547
|
+
}
|
|
548
|
+
else if (payload?.code === 'MCP_ENDPOINT_TIMEOUT') {
|
|
549
|
+
message = 'The dedicated MCP host timed out during MCP initialization.';
|
|
550
|
+
followup = 'This usually points to a degraded or overloaded cloud MCP service.';
|
|
551
|
+
}
|
|
552
|
+
else if (payload?.code === 'MCP_ENDPOINT_UNREACHABLE') {
|
|
553
|
+
message = 'The dedicated MCP host could not be reached.';
|
|
554
|
+
followup = 'Check the cloud MCP hostname and environment health before rotating credentials.';
|
|
555
|
+
}
|
|
556
|
+
else if (payload?.code === 'MCP_ENDPOINT_REJECTED_VALIDATED_TOKEN') {
|
|
557
|
+
message = 'The dedicated MCP host rejected a token that validated locally.';
|
|
558
|
+
followup = 'This suggests the MCP host is stale, routed incorrectly, or out of sync with auth data.';
|
|
559
|
+
}
|
|
540
560
|
setMcpConnectionResult({
|
|
541
561
|
type: 'error',
|
|
542
562
|
message,
|
|
543
563
|
workspaceName,
|
|
564
|
+
endpoint,
|
|
565
|
+
errorCode: typeof payload?.code === 'string' ? String(payload.code) : undefined,
|
|
566
|
+
followup,
|
|
544
567
|
});
|
|
545
568
|
return;
|
|
546
569
|
}
|
|
@@ -551,12 +574,15 @@ export function TaskSettings(props) {
|
|
|
551
574
|
scopes: Array.isArray(result?.scopes) ? result.scopes.map((scope) => String(scope)) : [],
|
|
552
575
|
workspaceName: typeof result?.workspaceName === 'string' ? String(result.workspaceName) : '',
|
|
553
576
|
workspaceId: typeof result?.workspaceId === 'string' ? String(result.workspaceId) : '',
|
|
577
|
+
endpoint: typeof result?.endpoint === 'string' ? String(result.endpoint) : cloudMcpEndpoint,
|
|
554
578
|
});
|
|
555
579
|
}
|
|
556
580
|
catch {
|
|
557
581
|
setMcpConnectionResult({
|
|
558
582
|
type: 'error',
|
|
559
|
-
message: 'Failed to test MCP connection.'
|
|
583
|
+
message: 'Failed to test MCP connection.',
|
|
584
|
+
endpoint: cloudMcpEndpoint,
|
|
585
|
+
followup: 'The settings page could not complete the connection probe. Check your network connection and the selected cloud environment.'
|
|
560
586
|
});
|
|
561
587
|
}
|
|
562
588
|
finally {
|
|
@@ -893,7 +919,7 @@ export function TaskSettings(props) {
|
|
|
893
919
|
} })] }), _jsxs("div", { className: styles.marginTop12, style: { display: 'flex', gap: '8px', flexWrap: 'wrap', justifyContent: 'flex-end' }, children: [generatedTokenValue && (_jsx("button", { className: styles.copyBtn, onClick: () => {
|
|
894
920
|
setMcpConnectionToken(generatedTokenValue);
|
|
895
921
|
setMcpConnectionResult(null);
|
|
896
|
-
}, children: "Use Generated Token" })), _jsxs("button", { className: styles.copyBtn, onClick: () => void handleTestMcpConnection(), disabled: mcpConnectionBusy, children: [mcpConnectionBusy ? _jsx(Loader2, { size: 14, className: styles.spinIcon }) : _jsx(Zap, { size: 14 }), "Test Connection"] })] }), mcpConnectionResult && (_jsxs("div", { className: `${mcpConnectionResult.type === 'success' ? styles.successMessage : styles.errorMessage} ${styles.marginTop12}`, children: [mcpConnectionResult.type === 'success' ? _jsx(Check, { size: 14 }) : _jsx(AlertTriangle, { size: 14 }), _jsxs("div", { children: [_jsx("div", { children: mcpConnectionResult.message }), mcpConnectionResult.type === 'success' && (mcpConnectionResult.workspaceName || mcpConnectionResult.workspaceId) && (_jsxs("div", { className: styles.settingHelper, children: ["Workspace: ", mcpConnectionResult.workspaceName || mcpConnectionResult.workspaceId, mcpConnectionResult.workspaceId ? ` (${mcpConnectionResult.workspaceId})` : ''] })), mcpConnectionResult.type === 'success' && Array.isArray(mcpConnectionResult.scopes) && mcpConnectionResult.scopes.length > 0 && (_jsxs("div", { className: styles.settingHelper, children: ["Granted scopes: ", mcpConnectionResult.scopes.join(', ')] }))] })] }))] }))] }), _jsxs("div", { className: `${styles.settingGroup} ${styles.marginTop16}`, children: [_jsxs("div", { className: styles.toggleHeading, onClick: () => setShowMcpTokensList((prev) => !prev), onKeyDown: (e) => {
|
|
922
|
+
}, children: "Use Generated Token" })), _jsxs("button", { className: styles.copyBtn, onClick: () => void handleTestMcpConnection(), disabled: mcpConnectionBusy, children: [mcpConnectionBusy ? _jsx(Loader2, { size: 14, className: styles.spinIcon }) : _jsx(Zap, { size: 14 }), "Test Connection"] })] }), mcpConnectionResult && (_jsxs("div", { className: `${mcpConnectionResult.type === 'success' ? styles.successMessage : styles.errorMessage} ${styles.marginTop12}`, children: [mcpConnectionResult.type === 'success' ? _jsx(Check, { size: 14 }) : _jsx(AlertTriangle, { size: 14 }), _jsxs("div", { children: [_jsx("div", { children: mcpConnectionResult.message }), mcpConnectionResult.type === 'success' && (mcpConnectionResult.workspaceName || mcpConnectionResult.workspaceId) && (_jsxs("div", { className: styles.settingHelper, children: ["Workspace: ", mcpConnectionResult.workspaceName || mcpConnectionResult.workspaceId, mcpConnectionResult.workspaceId ? ` (${mcpConnectionResult.workspaceId})` : ''] })), mcpConnectionResult.type === 'success' && Array.isArray(mcpConnectionResult.scopes) && mcpConnectionResult.scopes.length > 0 && (_jsxs("div", { className: styles.settingHelper, children: ["Granted scopes: ", mcpConnectionResult.scopes.join(', ')] })), mcpConnectionResult.endpoint && (_jsxs("div", { className: styles.settingHelper, children: ["Endpoint: ", mcpConnectionResult.endpoint] })), mcpConnectionResult.type === 'error' && mcpConnectionResult.errorCode && (_jsxs("div", { className: styles.settingHelper, children: ["Error code: ", mcpConnectionResult.errorCode] })), mcpConnectionResult.type === 'error' && mcpConnectionResult.followup && (_jsx("div", { className: styles.settingHelper, children: mcpConnectionResult.followup }))] })] }))] }))] }), _jsxs("div", { className: `${styles.settingGroup} ${styles.marginTop16}`, children: [_jsxs("div", { className: styles.toggleHeading, onClick: () => setShowMcpTokensList((prev) => !prev), onKeyDown: (e) => {
|
|
897
923
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
898
924
|
e.preventDefault();
|
|
899
925
|
setShowMcpTokensList((prev) => !prev);
|
|
@@ -970,7 +970,8 @@ describe('TaskSettings Component', () => {
|
|
|
970
970
|
result: {
|
|
971
971
|
workspaceName: 'Workspace Alpha',
|
|
972
972
|
workspaceId: 'workspace-alpha',
|
|
973
|
-
scopes: ['tasks:read', 'tasks:write', 'workspace:read']
|
|
973
|
+
scopes: ['tasks:read', 'tasks:write', 'workspace:read'],
|
|
974
|
+
endpoint: 'https://performance-mcp.taskforcehq.ai/mcp'
|
|
974
975
|
}
|
|
975
976
|
})
|
|
976
977
|
};
|
|
@@ -999,6 +1000,46 @@ describe('TaskSettings Component', () => {
|
|
|
999
1000
|
}));
|
|
1000
1001
|
expect(await screen.findByText('Connection looks good for this workspace.')).toBeInTheDocument();
|
|
1001
1002
|
expect(screen.getByText(/Granted scopes: tasks:read, tasks:write, workspace:read/i)).toBeInTheDocument();
|
|
1003
|
+
expect(screen.getByText(/Endpoint: https:\/\/performance-mcp\.taskforcehq\.ai\/mcp/i)).toBeInTheDocument();
|
|
1004
|
+
}, 10000);
|
|
1005
|
+
it('4.23d2 MCP test connection shows a dedicated-host incident message when the endpoint returns a gateway failure', async () => {
|
|
1006
|
+
const fetchCloudAuthApi = vi.fn(async (path) => {
|
|
1007
|
+
if (path === '/api/taskforce/settings/mcp/test-connection') {
|
|
1008
|
+
return {
|
|
1009
|
+
ok: false,
|
|
1010
|
+
json: async () => ({
|
|
1011
|
+
code: 'MCP_ENDPOINT_GATEWAY_ERROR',
|
|
1012
|
+
error: 'Cloud MCP endpoint https://performance-mcp.taskforcehq.ai/mcp returned 502.',
|
|
1013
|
+
details: {
|
|
1014
|
+
endpoint: 'https://performance-mcp.taskforcehq.ai/mcp',
|
|
1015
|
+
upstreamStatus: 502
|
|
1016
|
+
}
|
|
1017
|
+
})
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
return {
|
|
1021
|
+
ok: true,
|
|
1022
|
+
json: async () => ({ items: [] })
|
|
1023
|
+
};
|
|
1024
|
+
});
|
|
1025
|
+
const user = userEvent.setup();
|
|
1026
|
+
renderComponent({
|
|
1027
|
+
settingsModel: {
|
|
1028
|
+
runtimeMode: 'local',
|
|
1029
|
+
isAuthenticated: true,
|
|
1030
|
+
currentWorkspaceName: 'Workspace Alpha',
|
|
1031
|
+
cloudMcpBaseUrl: 'https://performance-mcp.taskforcehq.ai',
|
|
1032
|
+
fetchCloudAuthApi
|
|
1033
|
+
}
|
|
1034
|
+
});
|
|
1035
|
+
await openCloudMcpSettings(user);
|
|
1036
|
+
await user.click(screen.getByRole('button', { name: 'Test Connection' }));
|
|
1037
|
+
await user.type(await screen.findByPlaceholderText('tfmcp_xxxxx_replace-me'), 'tfmcp_example_secret');
|
|
1038
|
+
await user.click(screen.getAllByRole('button', { name: 'Test Connection' })[1]);
|
|
1039
|
+
expect(await screen.findByText('The dedicated MCP host is responding with a gateway error.')).toBeInTheDocument();
|
|
1040
|
+
expect(screen.getByText(/Endpoint: https:\/\/performance-mcp\.taskforcehq\.ai\/mcp/i)).toBeInTheDocument();
|
|
1041
|
+
expect(screen.getByText(/Error code: MCP_ENDPOINT_GATEWAY_ERROR/i)).toBeInTheDocument();
|
|
1042
|
+
expect(screen.getByText(/This usually means the cloud MCP service is degraded/i)).toBeInTheDocument();
|
|
1002
1043
|
}, 10000);
|
|
1003
1044
|
it('4.23e Antigravity uses serverUrl instead of transport in the cloud snippet', async () => {
|
|
1004
1045
|
const fetchCloudAuthApi = vi.fn(async () => ({
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { DocumentAttachmentFilter, DocumentTypeFilter } from './documentWorkspaceFilters';
|
|
2
|
+
export interface DocMeta {
|
|
3
|
+
id: string;
|
|
4
|
+
assetId?: string;
|
|
5
|
+
referenceNumber?: number | null;
|
|
6
|
+
referenceLabel?: string | null;
|
|
7
|
+
fsPath: string;
|
|
8
|
+
apiUrl: string;
|
|
9
|
+
taskId: string | null;
|
|
10
|
+
displayName: string;
|
|
11
|
+
originalFilename?: string | null;
|
|
12
|
+
title: string;
|
|
13
|
+
docType: 'implementation-plan' | 'review' | 'walkthrough' | 'planning' | 'other';
|
|
14
|
+
sizeBytes: number;
|
|
15
|
+
updatedAt: string | null;
|
|
16
|
+
editable?: boolean;
|
|
17
|
+
attachmentCount?: number;
|
|
18
|
+
attachmentState?: 'attached' | 'unattached';
|
|
19
|
+
}
|
|
20
|
+
export declare function normalizeDocPath(value: string): string;
|
|
21
|
+
export declare function docPathMatches(candidate: string, requested: string): boolean;
|
|
22
|
+
export declare function extractAssetIdFromPath(value: string | null | undefined): string | null;
|
|
23
|
+
export declare function getEffectiveAttachmentState(doc: Pick<DocMeta, 'attachmentState' | 'attachmentCount' | 'taskId'>): 'attached' | 'unattached';
|
|
24
|
+
export declare function filterDocuments(documents: DocMeta[], searchQuery: string, typeFilters: DocumentTypeFilter[], attachmentFilters: DocumentAttachmentFilter[]): DocMeta[];
|