@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,6 +1,6 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from 'http';
|
|
2
2
|
import type { TaskforceCore } from '../../core/Taskforce.js';
|
|
3
|
-
import type
|
|
3
|
+
import { type RouteHandler } from './primitives.js';
|
|
4
4
|
export interface DocumentReviewRouteDependencies {
|
|
5
5
|
addRoute: (method: string, path: string, handler: RouteHandler) => void;
|
|
6
6
|
core: TaskforceCore;
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* GET /api/taskforce/context-metadata
|
|
16
16
|
* POST /api/taskforce/context/purge-expired
|
|
17
17
|
* GET /api/taskforce/documents
|
|
18
|
+
* POST /api/taskforce/documents/:assetId/attach
|
|
18
19
|
* DELETE /api/taskforce/documents/:assetId
|
|
19
20
|
* PATCH /api/taskforce/documents/:assetId
|
|
20
21
|
* POST /api/taskforce/documents/save
|
|
@@ -28,7 +29,7 @@ import { ObjectStorageClient } from '../../storage/objectStorageClient.js';
|
|
|
28
29
|
import type { DocumentRegistryEntry } from '../../storage/documentRegistry.js';
|
|
29
30
|
import { TaskAssetStore } from '../../storage/taskAssetStore.js';
|
|
30
31
|
import { WorkspaceAssetStore, type WorkspaceAssetEntry, type WorkspaceAssetKind } from '../../storage/workspaceAssetStore.js';
|
|
31
|
-
import type
|
|
32
|
+
import { type RouteContext, type RouteHandler } from './primitives.js';
|
|
32
33
|
type AssetMetadata = DocumentRegistryEntry & {
|
|
33
34
|
quarantineStatus?: 'none' | 'quarantined' | 'released';
|
|
34
35
|
workspaceId?: string;
|
|
@@ -51,7 +52,9 @@ export interface DocumentRouteDependencies {
|
|
|
51
52
|
resolveEffectiveObjectStorageConfig: () => ResolvedObjectStorageConfig;
|
|
52
53
|
getObjectStorageClient: () => ObjectStorageClient | null;
|
|
53
54
|
resolveStorageWorkspaceId: (req: IncomingMessage, body?: any) => string | null;
|
|
54
|
-
ensureContextUploadAllowed: (req: IncomingMessage, res: ServerResponse, workspaceId: string
|
|
55
|
+
ensureContextUploadAllowed: (req: IncomingMessage, res: ServerResponse, workspaceId: string, options?: {
|
|
56
|
+
requestedBytes?: number | null;
|
|
57
|
+
}) => boolean;
|
|
55
58
|
parseStorageFolderSegments: (folderPathRaw: unknown) => string[] | null;
|
|
56
59
|
buildCanonicalWorkspaceAssetRelativePath: (params: {
|
|
57
60
|
workspaceIdRaw: unknown;
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* GET /api/taskforce/context-metadata
|
|
16
16
|
* POST /api/taskforce/context/purge-expired
|
|
17
17
|
* GET /api/taskforce/documents
|
|
18
|
+
* POST /api/taskforce/documents/:assetId/attach
|
|
18
19
|
* DELETE /api/taskforce/documents/:assetId
|
|
19
20
|
* PATCH /api/taskforce/documents/:assetId
|
|
20
21
|
* POST /api/taskforce/documents/save
|
|
@@ -29,21 +30,122 @@ import { purgeExpiredDocuments } from '../../storage/documentPurge.js';
|
|
|
29
30
|
import { parsePlan } from '../../utils/planParser.js';
|
|
30
31
|
import { getCanonicalDocumentName, getReadableDocumentName, isGeneratedDocumentName } from '../../utils/documentNames.js';
|
|
31
32
|
import { requestRuntimeMode } from './shared.js';
|
|
32
|
-
import { parseJsonBody } from '
|
|
33
|
+
import { parseJsonBody } from './primitives.js';
|
|
34
|
+
import { resolveDeploymentConfig } from '../../config/deployment.js';
|
|
35
|
+
function isUserProfileAvatarPath(relativePath) {
|
|
36
|
+
return /^users\/[^/]+\/profile\/avatar\/[^/]+$/i.test(String(relativePath || '').replace(/\\/g, '/'));
|
|
37
|
+
}
|
|
38
|
+
function isAiProfileAvatarPath(relativePath) {
|
|
39
|
+
return /^workspaces\/[^/]+\/assets\/images\/ai-profiles\/[^/]+\/avatar\/[^/]+$/i.test(String(relativePath || '').replace(/\\/g, '/'));
|
|
40
|
+
}
|
|
41
|
+
function resolveRequestOrigin(req) {
|
|
42
|
+
const host = String(req.headers.host || '').trim();
|
|
43
|
+
if (!host)
|
|
44
|
+
return '';
|
|
45
|
+
const proto = String(req.headers['x-forwarded-proto'] || '').trim().toLowerCase() === 'https'
|
|
46
|
+
? 'https'
|
|
47
|
+
: (req.socket?.encrypted ? 'https' : 'http');
|
|
48
|
+
return `${proto}://${host.replace(/\/+$/, '')}`;
|
|
49
|
+
}
|
|
50
|
+
async function proxyCloudUserProfileAvatar(req, res, relativePath, contentTypeForExt) {
|
|
51
|
+
if (!isUserProfileAvatarPath(relativePath))
|
|
52
|
+
return false;
|
|
53
|
+
const deployment = resolveDeploymentConfig(process.env);
|
|
54
|
+
const cloudBaseUrl = String(deployment.cloudBaseUrl || deployment.cloudAuthBaseUrl || '').trim().replace(/\/+$/, '');
|
|
55
|
+
if (!cloudBaseUrl)
|
|
56
|
+
return false;
|
|
57
|
+
if (cloudBaseUrl === resolveRequestOrigin(req))
|
|
58
|
+
return false;
|
|
59
|
+
const requestUrl = new URL(req.url || '', 'http://localhost');
|
|
60
|
+
const upstreamUrl = `${cloudBaseUrl}/api/taskforce/context/${encodeURIComponent(relativePath)}${requestUrl.search || ''}`;
|
|
61
|
+
const headers = new Headers();
|
|
62
|
+
const cookie = String(req.headers.cookie || '').trim();
|
|
63
|
+
const accept = String(req.headers.accept || '').trim();
|
|
64
|
+
const userAgent = String(req.headers['user-agent'] || '').trim();
|
|
65
|
+
if (cookie)
|
|
66
|
+
headers.set('cookie', cookie);
|
|
67
|
+
if (accept)
|
|
68
|
+
headers.set('accept', accept);
|
|
69
|
+
if (userAgent)
|
|
70
|
+
headers.set('user-agent', userAgent);
|
|
71
|
+
headers.set('x-taskforce-origin-runtime', 'local-proxy');
|
|
72
|
+
const cfClientId = String(process.env.TASKFORCE_CF_ACCESS_CLIENT_ID || process.env.CF_ACCESS_CLIENT_ID || '').trim();
|
|
73
|
+
const cfClientSecret = String(process.env.TASKFORCE_CF_ACCESS_CLIENT_SECRET || process.env.CF_ACCESS_CLIENT_SECRET || '').trim();
|
|
74
|
+
if (cfClientId && cfClientSecret) {
|
|
75
|
+
headers.set('CF-Access-Client-Id', cfClientId);
|
|
76
|
+
headers.set('CF-Access-Client-Secret', cfClientSecret);
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const upstream = await fetch(upstreamUrl, {
|
|
80
|
+
method: 'GET',
|
|
81
|
+
headers
|
|
82
|
+
});
|
|
83
|
+
if (upstream.status === 404)
|
|
84
|
+
return false;
|
|
85
|
+
const outgoingHeaders = {};
|
|
86
|
+
for (const [key, value] of upstream.headers.entries()) {
|
|
87
|
+
const name = key.toLowerCase();
|
|
88
|
+
if (name === 'set-cookie' || name === 'content-length' || name === 'transfer-encoding')
|
|
89
|
+
continue;
|
|
90
|
+
outgoingHeaders[key] = value;
|
|
91
|
+
}
|
|
92
|
+
if (!outgoingHeaders['content-type']) {
|
|
93
|
+
outgoingHeaders['Content-Type'] = contentTypeForExt(path.extname(relativePath).toLowerCase());
|
|
94
|
+
}
|
|
95
|
+
res.writeHead(upstream.status, outgoingHeaders);
|
|
96
|
+
res.end(Buffer.from(await upstream.arrayBuffer()));
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
33
103
|
export function registerDocumentRoutes(deps) {
|
|
34
104
|
const { addRoute, core, context, requestWorkspaceId, ensureAdminRole, resolveEffectiveObjectStorageConfig, getObjectStorageClient, resolveStorageWorkspaceId, ensureContextUploadAllowed, parseStorageFolderSegments, buildCanonicalWorkspaceAssetRelativePath, inferAssetKindFromPath, appendStorageTelemetry, scanAndRecordDocument, buildTaskAttachmentPayload, workspaceAssetStore, taskAssetStore, normalizeDocumentPathInput, getAssetMetadata, getCanonicalAssetMetadata, documentRetentionDays, appendDocumentPurgeHistory, markAssetDeleted, isAssetDeleted, clearAssetDeleted, ensureWithinDir, sanitizeDownloadFilename, contentTypeForExt, inferInlineDocumentFromPath, resolveCanonicalDocumentRead, inferWorkspaceIdFromStoragePath, inferTaskIdFromStoragePath, } = deps;
|
|
105
|
+
const removeDocumentAttachmentFromTaskAttachments = (attachmentsRaw, asset) => {
|
|
106
|
+
const canonicalDocumentPath = `/api/taskforce/documents/${encodeURIComponent(asset.assetId)}/content`;
|
|
107
|
+
const legacyContextPath = `/api/taskforce/context/${encodeURIComponent(asset.storageKey)}`;
|
|
108
|
+
if (!Array.isArray(attachmentsRaw))
|
|
109
|
+
return [];
|
|
110
|
+
return attachmentsRaw.filter((attachment) => {
|
|
111
|
+
if (typeof attachment === 'string') {
|
|
112
|
+
const normalized = attachment.trim();
|
|
113
|
+
if (!normalized)
|
|
114
|
+
return false;
|
|
115
|
+
return normalized !== canonicalDocumentPath
|
|
116
|
+
&& normalized !== legacyContextPath
|
|
117
|
+
&& normalized !== asset.storageKey;
|
|
118
|
+
}
|
|
119
|
+
if (!attachment || typeof attachment !== 'object')
|
|
120
|
+
return false;
|
|
121
|
+
const candidate = attachment;
|
|
122
|
+
const candidatePath = String(candidate.path || '').trim();
|
|
123
|
+
const candidateFsPath = String(candidate.fsPath || '').trim();
|
|
124
|
+
const candidateAssetId = String(candidate.assetId || '').trim();
|
|
125
|
+
if (candidateAssetId && candidateAssetId === asset.assetId)
|
|
126
|
+
return false;
|
|
127
|
+
if (candidatePath === canonicalDocumentPath || candidatePath === legacyContextPath)
|
|
128
|
+
return false;
|
|
129
|
+
if (candidateFsPath && candidateFsPath === asset.storageKey)
|
|
130
|
+
return false;
|
|
131
|
+
return !!candidatePath;
|
|
132
|
+
});
|
|
133
|
+
};
|
|
35
134
|
addRoute('POST', '/api/taskforce/context-upload/init', async (req, res) => {
|
|
36
135
|
const resolvedObjectStorage = resolveEffectiveObjectStorageConfig();
|
|
37
136
|
const objectStorageClient = getObjectStorageClient();
|
|
38
137
|
const body = await parseJsonBody(req);
|
|
39
138
|
const { originalName, mimeType, taskId, folderPath } = body || {};
|
|
40
139
|
const workspaceId = resolveStorageWorkspaceId(req, body);
|
|
140
|
+
const requestedBytes = Number(body?.size);
|
|
41
141
|
if (!workspaceId) {
|
|
42
142
|
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
43
143
|
res.end(JSON.stringify({ error: 'workspaceId is required and cannot be default for uploads.' }));
|
|
44
144
|
return;
|
|
45
145
|
}
|
|
46
|
-
if (!ensureContextUploadAllowed(req, res, workspaceId
|
|
146
|
+
if (!ensureContextUploadAllowed(req, res, workspaceId, {
|
|
147
|
+
requestedBytes: Number.isFinite(requestedBytes) ? requestedBytes : null
|
|
148
|
+
}))
|
|
47
149
|
return;
|
|
48
150
|
const folderSegments = parseStorageFolderSegments(folderPath);
|
|
49
151
|
if (folderSegments === null) {
|
|
@@ -144,6 +246,10 @@ export function registerDocumentRoutes(deps) {
|
|
|
144
246
|
res.end(JSON.stringify({ error: 'Uploaded file not found.' }));
|
|
145
247
|
return;
|
|
146
248
|
}
|
|
249
|
+
if (!ensureContextUploadAllowed(req, res, workspaceId, {
|
|
250
|
+
requestedBytes: object.body.length
|
|
251
|
+
}))
|
|
252
|
+
return;
|
|
147
253
|
try {
|
|
148
254
|
const taskId = typeof body?.taskId === 'string' && body.taskId.trim().length > 0
|
|
149
255
|
? body.taskId.trim()
|
|
@@ -191,8 +297,6 @@ export function registerDocumentRoutes(deps) {
|
|
|
191
297
|
res.end(JSON.stringify({ error: 'workspaceId is required and cannot be default for uploads.' }));
|
|
192
298
|
return;
|
|
193
299
|
}
|
|
194
|
-
if (!ensureContextUploadAllowed(req, res, workspaceId))
|
|
195
|
-
return;
|
|
196
300
|
const folderSegments = parseStorageFolderSegments(folderPath);
|
|
197
301
|
if (folderSegments === null) {
|
|
198
302
|
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
@@ -233,6 +337,10 @@ export function registerDocumentRoutes(deps) {
|
|
|
233
337
|
const actualFilename = `${safeBase}-${Date.now()}${extension}`;
|
|
234
338
|
const { basePath, projectRoot } = core.getPaths();
|
|
235
339
|
const buffer = Buffer.from(base64Data, 'base64');
|
|
340
|
+
if (!ensureContextUploadAllowed(req, res, workspaceId, {
|
|
341
|
+
requestedBytes: buffer.length
|
|
342
|
+
}))
|
|
343
|
+
return;
|
|
236
344
|
const relativeToStorage = buildCanonicalWorkspaceAssetRelativePath({
|
|
237
345
|
workspaceIdRaw: workspaceId,
|
|
238
346
|
kind: mimeType.startsWith('image/') ? 'image' : inferAssetKindFromPath(actualFilename),
|
|
@@ -358,9 +466,14 @@ export function registerDocumentRoutes(deps) {
|
|
|
358
466
|
return;
|
|
359
467
|
}
|
|
360
468
|
if (objectStorageClient && normalizedRelative && resolvedObjectStorage.provider === 'r2' && resolvedObjectStorage.r2) {
|
|
361
|
-
|
|
469
|
+
const shouldProxyInlineUserAvatar = isUserProfileAvatarPath(normalizedRelative);
|
|
470
|
+
const shouldProxyInlineAiAvatar = isAiProfileAvatarPath(normalizedRelative);
|
|
471
|
+
if (shouldProxyInlineDocument || shouldProxyInlineUserAvatar || shouldProxyInlineAiAvatar) {
|
|
362
472
|
const object = await objectStorageClient.getObject(normalizedRelative);
|
|
363
473
|
if (!object) {
|
|
474
|
+
if (shouldProxyInlineUserAvatar && await proxyCloudUserProfileAvatar(req, res, normalizedRelative, contentTypeForExt)) {
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
364
477
|
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
365
478
|
res.end('Context file not found');
|
|
366
479
|
return;
|
|
@@ -385,6 +498,9 @@ export function registerDocumentRoutes(deps) {
|
|
|
385
498
|
res.end(fs.readFileSync(filePath));
|
|
386
499
|
return;
|
|
387
500
|
}
|
|
501
|
+
if (normalizedRelative && await proxyCloudUserProfileAvatar(req, res, normalizedRelative, contentTypeForExt)) {
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
388
504
|
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
389
505
|
res.end('Context file not found');
|
|
390
506
|
});
|
|
@@ -552,9 +668,6 @@ export function registerDocumentRoutes(deps) {
|
|
|
552
668
|
});
|
|
553
669
|
addRoute('GET', '/api/taskforce/documents', async (req, res) => {
|
|
554
670
|
const workspaceId = requestWorkspaceId(req);
|
|
555
|
-
const { basePath, projectRoot } = core.getPaths();
|
|
556
|
-
const planningDocsDir = path.join(basePath, 'docs');
|
|
557
|
-
const runtimeMode = context.authConfig?.runtimeMode === 'cloud' ? 'cloud' : 'local';
|
|
558
671
|
function inferDocType(filename) {
|
|
559
672
|
const base = path.basename(filename, path.extname(filename)).toLowerCase();
|
|
560
673
|
if (base.includes('implementation-plan') || base.includes('implementation_plan'))
|
|
@@ -592,12 +705,9 @@ export function registerDocumentRoutes(deps) {
|
|
|
592
705
|
: (taskId ? 1 : 0);
|
|
593
706
|
const attachmentState = input.attachmentState === 'attached'
|
|
594
707
|
|| input.attachmentState === 'unattached'
|
|
595
|
-
|| input.attachmentState === 'standalone'
|
|
596
708
|
? (attachmentCount > 0 || taskId
|
|
597
709
|
? 'attached'
|
|
598
|
-
:
|
|
599
|
-
? 'standalone'
|
|
600
|
-
: 'unattached')
|
|
710
|
+
: 'unattached')
|
|
601
711
|
: (attachmentCount > 0 || taskId ? 'attached' : 'unattached');
|
|
602
712
|
return {
|
|
603
713
|
id: input.id,
|
|
@@ -620,12 +730,6 @@ export function registerDocumentRoutes(deps) {
|
|
|
620
730
|
attachmentState
|
|
621
731
|
};
|
|
622
732
|
}
|
|
623
|
-
function safeRelative(fullPath) {
|
|
624
|
-
const rel = path.relative(projectRoot, fullPath);
|
|
625
|
-
if (rel.startsWith('..'))
|
|
626
|
-
return null;
|
|
627
|
-
return rel;
|
|
628
|
-
}
|
|
629
733
|
const documents = [];
|
|
630
734
|
const seenDocumentPaths = new Set();
|
|
631
735
|
const pushDocument = (doc) => {
|
|
@@ -636,6 +740,9 @@ export function registerDocumentRoutes(deps) {
|
|
|
636
740
|
seenDocumentPaths.add(key);
|
|
637
741
|
documents.push(normalizedDoc);
|
|
638
742
|
};
|
|
743
|
+
// Shared document listing is canonical-document-only. Raw files under
|
|
744
|
+
// .taskforce/docs are intentionally excluded because they are not
|
|
745
|
+
// workspace-scoped managed documents.
|
|
639
746
|
const canonicalDocuments = workspaceAssetStore?.listAllByWorkspace(workspaceId, {
|
|
640
747
|
includeDeleted: false,
|
|
641
748
|
kind: 'document'
|
|
@@ -651,8 +758,11 @@ export function registerDocumentRoutes(deps) {
|
|
|
651
758
|
});
|
|
652
759
|
const activeLinks = (workspaceAssetStore?.listLinksForAsset(asset.assetId, workspaceId) || [])
|
|
653
760
|
.filter((link) => !link.deletedAt)
|
|
761
|
+
.filter((link) => link.targetType !== 'task' || (!!link.taskId && !!core.getTask(link.taskId, workspaceId)));
|
|
762
|
+
const activeTaskLinks = activeLinks
|
|
763
|
+
.filter((link) => link.targetType === 'task' && !!link.taskId)
|
|
654
764
|
.filter((link) => !!core.getTask(link.taskId, workspaceId));
|
|
655
|
-
const firstLink =
|
|
765
|
+
const firstLink = activeTaskLinks[0] || null;
|
|
656
766
|
const repairedLogicalName = isGeneratedDocumentName(asset.logicalName || null, asset.assetId || null)
|
|
657
767
|
? (String(firstLink?.displayName || '').trim()
|
|
658
768
|
|| repairedReadableName)
|
|
@@ -704,58 +814,6 @@ export function registerDocumentRoutes(deps) {
|
|
|
704
814
|
attachmentState: activeLinks.length > 0 ? 'attached' : 'unattached',
|
|
705
815
|
});
|
|
706
816
|
}
|
|
707
|
-
if (runtimeMode !== 'cloud') {
|
|
708
|
-
const walkMarkdownFiles = (dir, visitor) => {
|
|
709
|
-
let entries = [];
|
|
710
|
-
try {
|
|
711
|
-
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
712
|
-
}
|
|
713
|
-
catch {
|
|
714
|
-
return;
|
|
715
|
-
}
|
|
716
|
-
for (const entry of entries) {
|
|
717
|
-
const fullPath = path.join(dir, entry.name);
|
|
718
|
-
if (entry.isDirectory()) {
|
|
719
|
-
walkMarkdownFiles(fullPath, visitor);
|
|
720
|
-
continue;
|
|
721
|
-
}
|
|
722
|
-
if (!entry.isFile() || path.extname(entry.name).toLowerCase() !== '.md') {
|
|
723
|
-
continue;
|
|
724
|
-
}
|
|
725
|
-
let stat;
|
|
726
|
-
try {
|
|
727
|
-
stat = fs.statSync(fullPath);
|
|
728
|
-
}
|
|
729
|
-
catch {
|
|
730
|
-
continue;
|
|
731
|
-
}
|
|
732
|
-
const relativePath = safeRelative(fullPath);
|
|
733
|
-
if (!relativePath)
|
|
734
|
-
continue;
|
|
735
|
-
visitor({ relativePath, entryName: entry.name, stat });
|
|
736
|
-
}
|
|
737
|
-
};
|
|
738
|
-
walkMarkdownFiles(planningDocsDir, ({ relativePath, entryName, stat }) => {
|
|
739
|
-
pushDocument({
|
|
740
|
-
id: relativePath,
|
|
741
|
-
assetId: null,
|
|
742
|
-
referenceNumber: null,
|
|
743
|
-
referenceLabel: null,
|
|
744
|
-
fsPath: relativePath,
|
|
745
|
-
apiUrl: `/api/taskforce/context-link?path=${encodeURIComponent(relativePath)}`,
|
|
746
|
-
taskId: null,
|
|
747
|
-
displayName: inferTitle(entryName),
|
|
748
|
-
originalFilename: entryName,
|
|
749
|
-
title: inferTitle(entryName),
|
|
750
|
-
docType: 'planning',
|
|
751
|
-
sizeBytes: stat.size,
|
|
752
|
-
updatedAt: stat.mtime.toISOString(),
|
|
753
|
-
editable: true,
|
|
754
|
-
attachmentCount: 0,
|
|
755
|
-
attachmentState: 'standalone',
|
|
756
|
-
});
|
|
757
|
-
});
|
|
758
|
-
}
|
|
759
817
|
documents.sort((a, b) => {
|
|
760
818
|
if (!a.updatedAt && !b.updatedAt)
|
|
761
819
|
return 0;
|
|
@@ -768,9 +826,90 @@ export function registerDocumentRoutes(deps) {
|
|
|
768
826
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
769
827
|
res.end(JSON.stringify({ documents }));
|
|
770
828
|
});
|
|
829
|
+
addRoute('POST', '/api/taskforce/documents/:assetId/attach', async (req, res, params) => {
|
|
830
|
+
const workspaceId = requestWorkspaceId(req);
|
|
831
|
+
const assetId = String(params.assetId || '').trim();
|
|
832
|
+
const body = await parseJsonBody(req);
|
|
833
|
+
const targetType = String(body?.targetType || 'task').trim();
|
|
834
|
+
const targetId = String(body?.targetId || '').trim();
|
|
835
|
+
if (!workspaceAssetStore || !assetId) {
|
|
836
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
837
|
+
res.end(JSON.stringify({ error: 'Document not found.' }));
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
if (!targetId) {
|
|
841
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
842
|
+
res.end(JSON.stringify({ error: 'Missing required targetId.' }));
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
if (targetType !== 'task') {
|
|
846
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
847
|
+
res.end(JSON.stringify({ error: 'Only task attachments are supported from the document manager right now.' }));
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
const asset = workspaceAssetStore.get(assetId, workspaceId);
|
|
851
|
+
if (!asset || asset.kind !== 'document' || asset.deletedAt) {
|
|
852
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
853
|
+
res.end(JSON.stringify({ error: 'Document not found.' }));
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
const task = core.getTask(targetId, workspaceId);
|
|
857
|
+
if (!task) {
|
|
858
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
859
|
+
res.end(JSON.stringify({ error: 'Task not found.' }));
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
const existingLinks = workspaceAssetStore.listLinksForTarget('task', targetId, workspaceId)
|
|
863
|
+
.filter((link) => !link.deletedAt);
|
|
864
|
+
const alreadyAttached = existingLinks.some((link) => link.assetId === asset.assetId && link.role === 'attachment');
|
|
865
|
+
if (alreadyAttached) {
|
|
866
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
867
|
+
res.end(JSON.stringify({
|
|
868
|
+
success: true,
|
|
869
|
+
alreadyAttached: true,
|
|
870
|
+
targetType: 'task',
|
|
871
|
+
targetId,
|
|
872
|
+
attachmentCount: existingLinks.length,
|
|
873
|
+
}));
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
const payload = buildTaskAttachmentPayload(asset.storageKey, {
|
|
877
|
+
caption: getCanonicalDocumentName({
|
|
878
|
+
assetId: asset.assetId,
|
|
879
|
+
fsPath: asset.storageKey,
|
|
880
|
+
logicalName: asset.logicalName || null,
|
|
881
|
+
title: asset.logicalName || asset.originalFilename,
|
|
882
|
+
originalFilename: asset.originalFilename,
|
|
883
|
+
}),
|
|
884
|
+
assetId: asset.assetId,
|
|
885
|
+
taskId: targetId,
|
|
886
|
+
});
|
|
887
|
+
if (!payload) {
|
|
888
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
889
|
+
res.end(JSON.stringify({ error: 'Failed to build attachment payload for this document.' }));
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
core.updateTask(targetId, {
|
|
893
|
+
attachments: [...(Array.isArray(task.attachments) ? task.attachments : []), payload],
|
|
894
|
+
}, workspaceId, {
|
|
895
|
+
actorRef: 'user',
|
|
896
|
+
saveSource: 'manual',
|
|
897
|
+
});
|
|
898
|
+
core.notifyDocumentMetadataMutation(workspaceId, [asset.storageKey], 'upsert');
|
|
899
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
900
|
+
res.end(JSON.stringify({
|
|
901
|
+
success: true,
|
|
902
|
+
attached: true,
|
|
903
|
+
alreadyAttached: false,
|
|
904
|
+
targetType: 'task',
|
|
905
|
+
targetId,
|
|
906
|
+
}));
|
|
907
|
+
});
|
|
771
908
|
addRoute('DELETE', '/api/taskforce/documents/:assetId', async (req, res, params) => {
|
|
772
909
|
const workspaceId = requestWorkspaceId(req);
|
|
773
910
|
const assetId = String(params.assetId || '').trim();
|
|
911
|
+
const body = await parseJsonBody(req);
|
|
912
|
+
const forceDetach = body?.forceDetach === true;
|
|
774
913
|
if (!workspaceAssetStore || !assetId) {
|
|
775
914
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
776
915
|
res.end(JSON.stringify({ error: 'Document not found' }));
|
|
@@ -784,16 +923,44 @@ export function registerDocumentRoutes(deps) {
|
|
|
784
923
|
}
|
|
785
924
|
const activeLinks = workspaceAssetStore.listLinksForAsset(asset.assetId, workspaceId)
|
|
786
925
|
.filter((link) => !link.deletedAt)
|
|
926
|
+
.filter((link) => link.targetType === 'task' && !!link.taskId)
|
|
787
927
|
.filter((link) => !!core.getTask(link.taskId, workspaceId));
|
|
788
928
|
if (activeLinks.length > 0) {
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
929
|
+
if (!forceDetach) {
|
|
930
|
+
res.writeHead(409, { 'Content-Type': 'application/json' });
|
|
931
|
+
res.end(JSON.stringify({
|
|
932
|
+
error: 'Document is still attached to one or more tasks.',
|
|
933
|
+
activeAttachmentCount: activeLinks.length,
|
|
934
|
+
requiresForceDetach: true
|
|
935
|
+
}));
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
for (const link of activeLinks) {
|
|
939
|
+
const taskId = link.taskId;
|
|
940
|
+
const task = core.getTask(taskId, workspaceId);
|
|
941
|
+
if (!task)
|
|
942
|
+
continue;
|
|
943
|
+
const updatedAttachments = removeDocumentAttachmentFromTaskAttachments(task.attachments, asset);
|
|
944
|
+
core.updateTask(taskId, { attachments: updatedAttachments }, workspaceId, {
|
|
945
|
+
actorRef: 'user',
|
|
946
|
+
saveSource: 'manual'
|
|
947
|
+
});
|
|
948
|
+
}
|
|
792
949
|
}
|
|
793
950
|
const nowIso = new Date().toISOString();
|
|
794
951
|
const purgeAfter = new Date(Date.now() + documentRetentionDays * 24 * 60 * 60 * 1000).toISOString();
|
|
795
952
|
const metadata = markAssetDeleted(asset.storageKey);
|
|
796
953
|
workspaceAssetStore.markDeleted(asset.assetId, workspaceId, nowIso, purgeAfter);
|
|
954
|
+
const links = workspaceAssetStore.listLinksForAsset(asset.assetId, workspaceId, { includeDeleted: true });
|
|
955
|
+
for (const link of links) {
|
|
956
|
+
workspaceAssetStore.deleteLink({
|
|
957
|
+
workspaceId,
|
|
958
|
+
assetId: asset.assetId,
|
|
959
|
+
targetType: link.targetType,
|
|
960
|
+
targetId: link.targetId,
|
|
961
|
+
role: link.role
|
|
962
|
+
});
|
|
963
|
+
}
|
|
797
964
|
appendStorageTelemetry('delete.soft', {
|
|
798
965
|
path: asset.storageKey,
|
|
799
966
|
status: 'soft-deleted',
|
|
@@ -803,6 +970,7 @@ export function registerDocumentRoutes(deps) {
|
|
|
803
970
|
res.end(JSON.stringify({
|
|
804
971
|
success: true,
|
|
805
972
|
softDeleted: true,
|
|
973
|
+
detachedTaskCount: activeLinks.length,
|
|
806
974
|
retentionDays: documentRetentionDays,
|
|
807
975
|
purgeAfter: metadata?.purgeAfter || purgeAfter
|
|
808
976
|
}));
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { IncomingMessage, ServerResponse } from 'http';
|
|
2
|
+
import type { TaskforceAuthConfig } from '../auth.js';
|
|
3
|
+
import type { ResolvedObjectStorageConfig } from '../../storage/objectStorage.js';
|
|
4
|
+
export type RouteHandler = (req: IncomingMessage, res: ServerResponse, params: Record<string, string>) => Promise<void>;
|
|
5
|
+
export interface RouteContext {
|
|
6
|
+
authConfig?: TaskforceAuthConfig;
|
|
7
|
+
objectStorage?: ResolvedObjectStorageConfig;
|
|
8
|
+
}
|
|
9
|
+
export declare function parseJsonBody(req: IncomingMessage, maxBytes?: number): Promise<any>;
|
|
10
|
+
export declare function parseFormBody(req: IncomingMessage, maxBytes?: number): Promise<Record<string, string>>;
|
|
11
|
+
export declare function escapeHtml(value: unknown): string;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const DEFAULT_MAX_JSON_BODY_BYTES = 1024 * 1024; // 1MB
|
|
2
|
+
function createHttpError(message, statusCode) {
|
|
3
|
+
const error = new Error(message);
|
|
4
|
+
error.statusCode = statusCode;
|
|
5
|
+
return error;
|
|
6
|
+
}
|
|
7
|
+
export async function parseJsonBody(req, maxBytes = DEFAULT_MAX_JSON_BODY_BYTES) {
|
|
8
|
+
const safeMaxBytes = Number.isFinite(maxBytes) && maxBytes > 0
|
|
9
|
+
? Math.floor(maxBytes)
|
|
10
|
+
: DEFAULT_MAX_JSON_BODY_BYTES;
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
let body = '';
|
|
13
|
+
let receivedBytes = 0;
|
|
14
|
+
req.on('data', chunk => {
|
|
15
|
+
receivedBytes += chunk.length;
|
|
16
|
+
if (receivedBytes > safeMaxBytes) {
|
|
17
|
+
req.pause();
|
|
18
|
+
reject(createHttpError(`Payload too large (max ${safeMaxBytes} bytes)`, 413));
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
body += chunk;
|
|
22
|
+
});
|
|
23
|
+
req.on('end', () => {
|
|
24
|
+
try {
|
|
25
|
+
resolve(body ? JSON.parse(body) : {});
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
reject(createHttpError('Invalid JSON', 400));
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
req.on('error', reject);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
export async function parseFormBody(req, maxBytes = DEFAULT_MAX_JSON_BODY_BYTES) {
|
|
35
|
+
const safeMaxBytes = Number.isFinite(maxBytes) && maxBytes > 0
|
|
36
|
+
? Math.floor(maxBytes)
|
|
37
|
+
: DEFAULT_MAX_JSON_BODY_BYTES;
|
|
38
|
+
return new Promise((resolve, reject) => {
|
|
39
|
+
let body = '';
|
|
40
|
+
let receivedBytes = 0;
|
|
41
|
+
req.on('data', chunk => {
|
|
42
|
+
receivedBytes += chunk.length;
|
|
43
|
+
if (receivedBytes > safeMaxBytes) {
|
|
44
|
+
req.pause();
|
|
45
|
+
reject(createHttpError(`Payload too large (max ${safeMaxBytes} bytes)`, 413));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
body += chunk;
|
|
49
|
+
});
|
|
50
|
+
req.on('end', () => {
|
|
51
|
+
try {
|
|
52
|
+
const params = new URLSearchParams(body);
|
|
53
|
+
const parsed = {};
|
|
54
|
+
params.forEach((value, key) => {
|
|
55
|
+
parsed[String(key)] = String(value);
|
|
56
|
+
});
|
|
57
|
+
resolve(parsed);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
reject(createHttpError('Invalid form body', 400));
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
req.on('error', reject);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
export function escapeHtml(value) {
|
|
67
|
+
return String(value || '')
|
|
68
|
+
.replace(/&/g, '&')
|
|
69
|
+
.replace(/</g, '<')
|
|
70
|
+
.replace(/>/g, '>')
|
|
71
|
+
.replace(/"/g, '"')
|
|
72
|
+
.replace(/'/g, ''');
|
|
73
|
+
}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* GET /api/taskforce/environments
|
|
18
18
|
*/
|
|
19
19
|
import type { TaskforceCore } from '../../core/Taskforce.js';
|
|
20
|
-
import { type RouteHandler } from '
|
|
20
|
+
import { type RouteHandler } from './primitives.js';
|
|
21
21
|
export interface ResourceRouteDependencies {
|
|
22
22
|
addRoute: (method: string, path: string, handler: RouteHandler) => void;
|
|
23
23
|
core: TaskforceCore;
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
import * as fs from 'fs';
|
|
20
20
|
import * as path from 'path';
|
|
21
21
|
import { execFileSync } from 'child_process';
|
|
22
|
-
import { parseJsonBody } from '
|
|
22
|
+
import { parseJsonBody } from './primitives.js';
|
|
23
23
|
const WORKFLOW_EDITOR_LOCKED_FIELDS = {
|
|
24
24
|
do: ['aliasesText', 'dispatchDefault', 'criticalToolsText', 'contentDefault'],
|
|
25
25
|
execute: ['aliasesText', 'dispatchDefault', 'criticalToolsText', 'contentDefault']
|
|
@@ -8,9 +8,34 @@
|
|
|
8
8
|
import { IncomingMessage, ServerResponse } from 'http';
|
|
9
9
|
import { TaskforceCore } from '../../core/Taskforce.js';
|
|
10
10
|
import type { McpAccessTokenAuthResult, McpOAuthAccessTokenAuthResult, McpAccessTokenScope } from '../../core/Taskforce.js';
|
|
11
|
-
import type { RouteContext } from '
|
|
11
|
+
import type { RouteContext } from './primitives.js';
|
|
12
12
|
import type { AiProfileBootstrapConfig } from '../../mcp/aiProfileBootstrap.js';
|
|
13
|
+
import type { AiProfileSeatScope } from '../../shared/aiProfileSeatScope.js';
|
|
13
14
|
export declare function resolveErrorStatusCode(error: unknown, fallback?: number): number;
|
|
15
|
+
export declare const BOOTSTRAP_ROUTE_SLOW_THRESHOLD_MS = 1500;
|
|
16
|
+
export declare const BOOTSTRAP_ROUTE_SEVERE_THRESHOLD_MS = 5000;
|
|
17
|
+
export type ServerTimingMetric = {
|
|
18
|
+
name: string;
|
|
19
|
+
durationMs: number;
|
|
20
|
+
};
|
|
21
|
+
export declare function getRouteTimingNow(): number;
|
|
22
|
+
export declare function buildServerTimingMetric(name: string, durationMs: number): string;
|
|
23
|
+
export declare function setServerTimingHeader(res: ServerResponse, metrics: ServerTimingMetric[]): void;
|
|
24
|
+
export declare function serializeJsonWithTiming(payload: unknown, startedAt?: number): {
|
|
25
|
+
body: string;
|
|
26
|
+
byteLength: number;
|
|
27
|
+
durationMs: number;
|
|
28
|
+
};
|
|
29
|
+
export declare function requestBootstrapTraceId(req: IncomingMessage): string;
|
|
30
|
+
export declare function logSlowBootstrapRoute(input: {
|
|
31
|
+
route: string;
|
|
32
|
+
workspaceId: string;
|
|
33
|
+
durationMs: number;
|
|
34
|
+
traceId?: string | null;
|
|
35
|
+
thresholdMs?: number;
|
|
36
|
+
severeThresholdMs?: number;
|
|
37
|
+
details?: Record<string, unknown>;
|
|
38
|
+
}): void;
|
|
14
39
|
export declare function buildCloudMcpRuntimeAuth(input: {
|
|
15
40
|
req: IncomingMessage;
|
|
16
41
|
auth: {
|
|
@@ -34,6 +59,7 @@ export declare function buildCloudMcpRuntimeAuth(input: {
|
|
|
34
59
|
aiProfileProvider?: AiProfileBootstrapConfig['aiProfileProvider'];
|
|
35
60
|
aiProfileModel?: AiProfileBootstrapConfig['aiProfileModel'];
|
|
36
61
|
aiProfileSurfaceType?: AiProfileBootstrapConfig['aiProfileSurfaceType'];
|
|
62
|
+
aiProfileSeatScope?: AiProfileSeatScope | null;
|
|
37
63
|
actorType?: 'local' | 'user' | 'token' | 'oauth';
|
|
38
64
|
requestId?: string | null;
|
|
39
65
|
ip?: string | null;
|
|
@@ -51,7 +77,7 @@ export declare function makeResolveAuthenticatedUserId(core: TaskforceCore, cont
|
|
|
51
77
|
export declare function makeResolveAuthenticatedMutationUserId(core: TaskforceCore, context: RouteContext, requestWorkspaceId: (req: IncomingMessage) => string): (req: IncomingMessage) => string;
|
|
52
78
|
export declare function makeEnsureAppAccess(core: TaskforceCore, context: RouteContext): (req: IncomingMessage, res: ServerResponse) => boolean;
|
|
53
79
|
export declare function makeEnsureAdminRole(core: TaskforceCore, context: RouteContext): (req: IncomingMessage) => boolean;
|
|
54
|
-
export declare function makeEnsureAuthenticatedUser(): (req: IncomingMessage) => boolean;
|
|
80
|
+
export declare function makeEnsureAuthenticatedUser(authConfig?: RouteContext['authConfig']): (req: IncomingMessage) => boolean;
|
|
55
81
|
export declare function ensureOwnerRole(req: IncomingMessage): boolean;
|
|
56
82
|
export declare function ensureWorkspaceAdminRole(req: IncomingMessage): boolean;
|
|
57
83
|
export declare function makeResolveAdminRequestActor(core: TaskforceCore, context: RouteContext): (req: IncomingMessage) => {
|
|
@@ -60,6 +86,6 @@ export declare function makeResolveAdminRequestActor(core: TaskforceCore, contex
|
|
|
60
86
|
systemRole: "system_admin" | "user";
|
|
61
87
|
};
|
|
62
88
|
export declare function makeAuditAdminAction(core: TaskforceCore, context: RouteContext): (req: IncomingMessage, action: string, details: Record<string, unknown>) => void;
|
|
63
|
-
export declare function makeAuditAuthAction(core: TaskforceCore, requestWorkspaceId: (req: IncomingMessage) => string, context: RouteContext): (req: IncomingMessage, action: string, details: Record<string, unknown
|
|
89
|
+
export declare function makeAuditAuthAction(core: TaskforceCore, requestWorkspaceId: (req: IncomingMessage) => string, context: RouteContext): (req: IncomingMessage, action: string, details: Record<string, unknown>, workspaceIdOverride?: string | null) => void;
|
|
64
90
|
export declare function makeRequireCloudRuntime(context: RouteContext): (res: ServerResponse, feature: string) => boolean;
|
|
65
91
|
export declare function makeEnsureCloudPlanData(context: RouteContext): (req: IncomingMessage, res: ServerResponse) => boolean;
|