@taskforcehq/taskforce 0.3.305 → 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/dist/Taskforce.module.css +295 -89
- package/dist/TaskforceCore.js +215 -43
- package/dist/TaskforceCore.test.js +433 -28
- 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 +46 -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/DocumentViewer.d.ts +5 -1
- package/dist/components/features/DocumentViewer.js +60 -3
- package/dist/components/features/DocumentWorkspace.js +1 -1
- package/dist/components/features/DocumentWorkspace.test.js +33 -0
- package/dist/components/features/documentWorkspaceModel.d.ts +1 -1
- 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.js +85 -90
- package/dist/components/task/TaskForm.test.js +110 -8
- package/dist/components/task/TaskKanban.d.ts +2 -1
- package/dist/components/task/TaskKanban.js +94 -13
- 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 -14
- package/dist/components/views/PlanComparisonPage.test.js +238 -0
- package/dist/components/views/PlansPage.js +127 -36
- package/dist/components/views/PlansPage.test.js +294 -12
- package/dist/components/views/StandaloneLayout.js +283 -533
- 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 +26 -1
- package/dist/components/views/standalone/modals/AccountHubModal.js +76 -2
- package/dist/components/views/standalone/modals/AccountHubModal.test.js +20 -3
- 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/core/AiProfileService.d.ts +39 -2
- package/dist/core/AiProfileService.js +639 -73
- package/dist/core/AiProfileService.test.js +19 -1
- package/dist/core/AiProfiles.test.js +566 -7
- 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.js +3 -3
- package/dist/core/EntitlementsPolicy.test.js +2 -2
- package/dist/core/GlobalSettingsService.js +115 -6
- package/dist/core/PlanEntitlementService.d.ts +23 -2
- package/dist/core/PlanEntitlementService.js +343 -57
- package/dist/core/PlanFeatureCatalog.test.d.ts +1 -0
- package/dist/core/PlanFeatureCatalog.test.js +84 -0
- package/dist/core/PlanVersionPolicy.test.js +35 -2
- package/dist/core/PlanningEntities.test.js +52 -0
- package/dist/core/SignupMonetizationSettings.test.js +199 -27
- package/dist/core/SyncReconciliationService.js +2 -65
- package/dist/core/SystemAdmin.test.js +42 -42
- package/dist/core/TaskAttachmentsCanonical.test.js +51 -1
- package/dist/core/Taskforce.d.ts +225 -14
- package/dist/core/Taskforce.js +765 -192
- 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 +117 -16
- package/dist/core/shared.d.ts +1 -1
- package/dist/core/shared.js +5 -1
- package/dist/core/types.d.ts +68 -2
- package/dist/hooks/auth/useTaskforceAuthBootstrap.js +26 -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 +15 -3
- package/dist/hooks/sync/transfers.js +179 -62
- 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 +9 -2
- package/dist/hooks/useSyncOrchestrator.js +359 -178
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +407 -0
- package/dist/hooks/useTaskData.js +8 -3
- package/dist/hooks/useTaskData.test.js +45 -0
- package/dist/hooks/useTaskforce.d.ts +60 -2
- package/dist/hooks/useTaskforce.js +549 -240
- 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 +2380 -212
- package/dist/hooks/useWorkspaceSyncController.d.ts +1 -0
- package/dist/hooks/useWorkspaceSyncController.js +6 -1
- 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/documentAssetRegistrar.js +3 -3
- package/dist/mcp/runtime.d.ts +2 -0
- package/dist/mcp/runtime.js +131 -64
- package/dist/mcp/runtime.test.js +291 -12
- 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 +3 -0
- package/dist/server/routes/admin.js +272 -19
- package/dist/server/routes/auth.d.ts +15 -2
- package/dist/server/routes/auth.js +994 -57
- package/dist/server/routes/authSupport.d.ts +1 -1
- package/dist/server/routes/authSupport.js +1 -1
- package/dist/server/routes/billing.js +361 -35
- package/dist/server/routes/billing.test.js +592 -12
- package/dist/server/routes/documents.d.ts +1 -0
- package/dist/server/routes/documents.js +172 -70
- package/dist/server/routes/shared.d.ts +28 -2
- package/dist/server/routes/shared.js +69 -4
- package/dist/server/routes/sync.integration.test.js +437 -39
- package/dist/server/routes/sync.js +61 -605
- 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.js +43 -1
- package/dist/server/routes/workspaces.js +36 -5
- package/dist/server/routes.js +60 -3
- package/dist/server/routes.test.js +1824 -171
- 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 +17 -70
- package/dist/sync/syncApplyHandlers.d.ts +7 -0
- package/dist/sync/syncApplyHandlers.js +28 -13
- package/dist/sync/syncApplyHandlers.test.js +70 -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 +5 -0
- package/dist/sync/syncService.js +52 -0
- package/dist/sync/syncService.test.js +57 -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 +45 -0
- package/dist/sync/workspaceSyncModel.js +183 -28
- package/dist/sync/workspaceSyncModel.test.js +291 -12
- package/dist/sync/workspaceSyncState.d.ts +17 -0
- package/dist/sync/workspaceSyncState.js +2 -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-BG6P_GVW.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-Dhm7M8e7.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-ln0aF7xc.js → TaskSettings-DZX4jk7e.js} +1 -1
- package/dist/ui/assets/{WorkflowsModule-BcS4afRn.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-BSUaRwF8.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.js +3 -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 +3 -2
- package/scripts/export-sqlite-to-postgres.mjs +7 -9
- 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-N2MnQBZk.js +0 -1
- package/dist/ui/assets/DocumentWorkspace-B3nV_Gc5.css +0 -1
- package/dist/ui/assets/DocumentWorkspace-DRbDMwh8.js +0 -252
- package/dist/ui/assets/PlansPage-C2dd2n1X.css +0 -1
- package/dist/ui/assets/PlansPage-Cq0zXj3I.js +0 -1
- package/dist/ui/assets/RussoOne-Regular-C3BxZIj7.ttf +0 -0
- package/dist/ui/assets/index-C2-OXZV7.css +0 -1
- package/dist/ui/assets/index-DcSI5F86.js +0 -6
- package/scripts/migrate-planning-model.ts +0 -233
|
@@ -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
|
|
@@ -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
|
|
@@ -30,6 +31,75 @@ import { parsePlan } from '../../utils/planParser.js';
|
|
|
30
31
|
import { getCanonicalDocumentName, getReadableDocumentName, isGeneratedDocumentName } from '../../utils/documentNames.js';
|
|
31
32
|
import { requestRuntimeMode } from './shared.js';
|
|
32
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;
|
|
35
105
|
const removeDocumentAttachmentFromTaskAttachments = (attachmentsRaw, asset) => {
|
|
@@ -396,9 +466,14 @@ export function registerDocumentRoutes(deps) {
|
|
|
396
466
|
return;
|
|
397
467
|
}
|
|
398
468
|
if (objectStorageClient && normalizedRelative && resolvedObjectStorage.provider === 'r2' && resolvedObjectStorage.r2) {
|
|
399
|
-
|
|
469
|
+
const shouldProxyInlineUserAvatar = isUserProfileAvatarPath(normalizedRelative);
|
|
470
|
+
const shouldProxyInlineAiAvatar = isAiProfileAvatarPath(normalizedRelative);
|
|
471
|
+
if (shouldProxyInlineDocument || shouldProxyInlineUserAvatar || shouldProxyInlineAiAvatar) {
|
|
400
472
|
const object = await objectStorageClient.getObject(normalizedRelative);
|
|
401
473
|
if (!object) {
|
|
474
|
+
if (shouldProxyInlineUserAvatar && await proxyCloudUserProfileAvatar(req, res, normalizedRelative, contentTypeForExt)) {
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
402
477
|
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
403
478
|
res.end('Context file not found');
|
|
404
479
|
return;
|
|
@@ -423,6 +498,9 @@ export function registerDocumentRoutes(deps) {
|
|
|
423
498
|
res.end(fs.readFileSync(filePath));
|
|
424
499
|
return;
|
|
425
500
|
}
|
|
501
|
+
if (normalizedRelative && await proxyCloudUserProfileAvatar(req, res, normalizedRelative, contentTypeForExt)) {
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
426
504
|
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
427
505
|
res.end('Context file not found');
|
|
428
506
|
});
|
|
@@ -590,9 +668,6 @@ export function registerDocumentRoutes(deps) {
|
|
|
590
668
|
});
|
|
591
669
|
addRoute('GET', '/api/taskforce/documents', async (req, res) => {
|
|
592
670
|
const workspaceId = requestWorkspaceId(req);
|
|
593
|
-
const { basePath, projectRoot } = core.getPaths();
|
|
594
|
-
const planningDocsDir = path.join(basePath, 'docs');
|
|
595
|
-
const runtimeMode = context.authConfig?.runtimeMode === 'cloud' ? 'cloud' : 'local';
|
|
596
671
|
function inferDocType(filename) {
|
|
597
672
|
const base = path.basename(filename, path.extname(filename)).toLowerCase();
|
|
598
673
|
if (base.includes('implementation-plan') || base.includes('implementation_plan'))
|
|
@@ -630,12 +705,9 @@ export function registerDocumentRoutes(deps) {
|
|
|
630
705
|
: (taskId ? 1 : 0);
|
|
631
706
|
const attachmentState = input.attachmentState === 'attached'
|
|
632
707
|
|| input.attachmentState === 'unattached'
|
|
633
|
-
|| input.attachmentState === 'standalone'
|
|
634
708
|
? (attachmentCount > 0 || taskId
|
|
635
709
|
? 'attached'
|
|
636
|
-
:
|
|
637
|
-
? 'standalone'
|
|
638
|
-
: 'unattached')
|
|
710
|
+
: 'unattached')
|
|
639
711
|
: (attachmentCount > 0 || taskId ? 'attached' : 'unattached');
|
|
640
712
|
return {
|
|
641
713
|
id: input.id,
|
|
@@ -658,12 +730,6 @@ export function registerDocumentRoutes(deps) {
|
|
|
658
730
|
attachmentState
|
|
659
731
|
};
|
|
660
732
|
}
|
|
661
|
-
function safeRelative(fullPath) {
|
|
662
|
-
const rel = path.relative(projectRoot, fullPath);
|
|
663
|
-
if (rel.startsWith('..'))
|
|
664
|
-
return null;
|
|
665
|
-
return rel;
|
|
666
|
-
}
|
|
667
733
|
const documents = [];
|
|
668
734
|
const seenDocumentPaths = new Set();
|
|
669
735
|
const pushDocument = (doc) => {
|
|
@@ -674,6 +740,9 @@ export function registerDocumentRoutes(deps) {
|
|
|
674
740
|
seenDocumentPaths.add(key);
|
|
675
741
|
documents.push(normalizedDoc);
|
|
676
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.
|
|
677
746
|
const canonicalDocuments = workspaceAssetStore?.listAllByWorkspace(workspaceId, {
|
|
678
747
|
includeDeleted: false,
|
|
679
748
|
kind: 'document'
|
|
@@ -689,8 +758,11 @@ export function registerDocumentRoutes(deps) {
|
|
|
689
758
|
});
|
|
690
759
|
const activeLinks = (workspaceAssetStore?.listLinksForAsset(asset.assetId, workspaceId) || [])
|
|
691
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)
|
|
692
764
|
.filter((link) => !!core.getTask(link.taskId, workspaceId));
|
|
693
|
-
const firstLink =
|
|
765
|
+
const firstLink = activeTaskLinks[0] || null;
|
|
694
766
|
const repairedLogicalName = isGeneratedDocumentName(asset.logicalName || null, asset.assetId || null)
|
|
695
767
|
? (String(firstLink?.displayName || '').trim()
|
|
696
768
|
|| repairedReadableName)
|
|
@@ -742,58 +814,6 @@ export function registerDocumentRoutes(deps) {
|
|
|
742
814
|
attachmentState: activeLinks.length > 0 ? 'attached' : 'unattached',
|
|
743
815
|
});
|
|
744
816
|
}
|
|
745
|
-
if (runtimeMode !== 'cloud') {
|
|
746
|
-
const walkMarkdownFiles = (dir, visitor) => {
|
|
747
|
-
let entries = [];
|
|
748
|
-
try {
|
|
749
|
-
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
750
|
-
}
|
|
751
|
-
catch {
|
|
752
|
-
return;
|
|
753
|
-
}
|
|
754
|
-
for (const entry of entries) {
|
|
755
|
-
const fullPath = path.join(dir, entry.name);
|
|
756
|
-
if (entry.isDirectory()) {
|
|
757
|
-
walkMarkdownFiles(fullPath, visitor);
|
|
758
|
-
continue;
|
|
759
|
-
}
|
|
760
|
-
if (!entry.isFile() || path.extname(entry.name).toLowerCase() !== '.md') {
|
|
761
|
-
continue;
|
|
762
|
-
}
|
|
763
|
-
let stat;
|
|
764
|
-
try {
|
|
765
|
-
stat = fs.statSync(fullPath);
|
|
766
|
-
}
|
|
767
|
-
catch {
|
|
768
|
-
continue;
|
|
769
|
-
}
|
|
770
|
-
const relativePath = safeRelative(fullPath);
|
|
771
|
-
if (!relativePath)
|
|
772
|
-
continue;
|
|
773
|
-
visitor({ relativePath, entryName: entry.name, stat });
|
|
774
|
-
}
|
|
775
|
-
};
|
|
776
|
-
walkMarkdownFiles(planningDocsDir, ({ relativePath, entryName, stat }) => {
|
|
777
|
-
pushDocument({
|
|
778
|
-
id: relativePath,
|
|
779
|
-
assetId: null,
|
|
780
|
-
referenceNumber: null,
|
|
781
|
-
referenceLabel: null,
|
|
782
|
-
fsPath: relativePath,
|
|
783
|
-
apiUrl: `/api/taskforce/context-link?path=${encodeURIComponent(relativePath)}`,
|
|
784
|
-
taskId: null,
|
|
785
|
-
displayName: inferTitle(entryName),
|
|
786
|
-
originalFilename: entryName,
|
|
787
|
-
title: inferTitle(entryName),
|
|
788
|
-
docType: 'planning',
|
|
789
|
-
sizeBytes: stat.size,
|
|
790
|
-
updatedAt: stat.mtime.toISOString(),
|
|
791
|
-
editable: true,
|
|
792
|
-
attachmentCount: 0,
|
|
793
|
-
attachmentState: 'standalone',
|
|
794
|
-
});
|
|
795
|
-
});
|
|
796
|
-
}
|
|
797
817
|
documents.sort((a, b) => {
|
|
798
818
|
if (!a.updatedAt && !b.updatedAt)
|
|
799
819
|
return 0;
|
|
@@ -806,6 +826,85 @@ export function registerDocumentRoutes(deps) {
|
|
|
806
826
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
807
827
|
res.end(JSON.stringify({ documents }));
|
|
808
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
|
+
});
|
|
809
908
|
addRoute('DELETE', '/api/taskforce/documents/:assetId', async (req, res, params) => {
|
|
810
909
|
const workspaceId = requestWorkspaceId(req);
|
|
811
910
|
const assetId = String(params.assetId || '').trim();
|
|
@@ -824,6 +923,7 @@ export function registerDocumentRoutes(deps) {
|
|
|
824
923
|
}
|
|
825
924
|
const activeLinks = workspaceAssetStore.listLinksForAsset(asset.assetId, workspaceId)
|
|
826
925
|
.filter((link) => !link.deletedAt)
|
|
926
|
+
.filter((link) => link.targetType === 'task' && !!link.taskId)
|
|
827
927
|
.filter((link) => !!core.getTask(link.taskId, workspaceId));
|
|
828
928
|
if (activeLinks.length > 0) {
|
|
829
929
|
if (!forceDetach) {
|
|
@@ -836,11 +936,12 @@ export function registerDocumentRoutes(deps) {
|
|
|
836
936
|
return;
|
|
837
937
|
}
|
|
838
938
|
for (const link of activeLinks) {
|
|
839
|
-
const
|
|
939
|
+
const taskId = link.taskId;
|
|
940
|
+
const task = core.getTask(taskId, workspaceId);
|
|
840
941
|
if (!task)
|
|
841
942
|
continue;
|
|
842
943
|
const updatedAttachments = removeDocumentAttachmentFromTaskAttachments(task.attachments, asset);
|
|
843
|
-
core.updateTask(
|
|
944
|
+
core.updateTask(taskId, { attachments: updatedAttachments }, workspaceId, {
|
|
844
945
|
actorRef: 'user',
|
|
845
946
|
saveSource: 'manual'
|
|
846
947
|
});
|
|
@@ -855,7 +956,8 @@ export function registerDocumentRoutes(deps) {
|
|
|
855
956
|
workspaceAssetStore.deleteLink({
|
|
856
957
|
workspaceId,
|
|
857
958
|
assetId: asset.assetId,
|
|
858
|
-
|
|
959
|
+
targetType: link.targetType,
|
|
960
|
+
targetId: link.targetId,
|
|
859
961
|
role: link.role
|
|
860
962
|
});
|
|
861
963
|
}
|
|
@@ -10,7 +10,32 @@ import { TaskforceCore } from '../../core/Taskforce.js';
|
|
|
10
10
|
import type { McpAccessTokenAuthResult, McpOAuthAccessTokenAuthResult, McpAccessTokenScope } from '../../core/Taskforce.js';
|
|
11
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;
|
|
@@ -17,6 +17,57 @@ export function resolveErrorStatusCode(error, fallback = 400) {
|
|
|
17
17
|
}
|
|
18
18
|
return fallback;
|
|
19
19
|
}
|
|
20
|
+
// === SECTION: Route timing helpers ===
|
|
21
|
+
export const BOOTSTRAP_ROUTE_SLOW_THRESHOLD_MS = 1500;
|
|
22
|
+
export const BOOTSTRAP_ROUTE_SEVERE_THRESHOLD_MS = 5000;
|
|
23
|
+
function sanitizeServerTimingMetricName(name) {
|
|
24
|
+
return String(name || 'taskforce-route')
|
|
25
|
+
.trim()
|
|
26
|
+
.replace(/[^A-Za-z0-9_-]+/g, '-')
|
|
27
|
+
.replace(/^-+|-+$/g, '')
|
|
28
|
+
|| 'taskforce-route';
|
|
29
|
+
}
|
|
30
|
+
export function getRouteTimingNow() {
|
|
31
|
+
return Date.now();
|
|
32
|
+
}
|
|
33
|
+
export function buildServerTimingMetric(name, durationMs) {
|
|
34
|
+
return `${sanitizeServerTimingMetricName(name)};dur=${Math.max(0, Math.round(durationMs))}`;
|
|
35
|
+
}
|
|
36
|
+
export function setServerTimingHeader(res, metrics) {
|
|
37
|
+
const value = metrics
|
|
38
|
+
.filter((metric) => String(metric.name || '').trim().length > 0)
|
|
39
|
+
.map((metric) => buildServerTimingMetric(metric.name, metric.durationMs))
|
|
40
|
+
.join(', ');
|
|
41
|
+
if (value)
|
|
42
|
+
res.setHeader('Server-Timing', value);
|
|
43
|
+
}
|
|
44
|
+
export function serializeJsonWithTiming(payload, startedAt = getRouteTimingNow()) {
|
|
45
|
+
const body = JSON.stringify(payload);
|
|
46
|
+
return {
|
|
47
|
+
body,
|
|
48
|
+
byteLength: Buffer.byteLength(body, 'utf8'),
|
|
49
|
+
durationMs: Math.max(0, getRouteTimingNow() - startedAt),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export function requestBootstrapTraceId(req) {
|
|
53
|
+
return requestHeader(req, 'x-taskforce-bootstrap-trace-id', '').trim();
|
|
54
|
+
}
|
|
55
|
+
export function logSlowBootstrapRoute(input) {
|
|
56
|
+
const thresholdMs = Number(input.thresholdMs) > 0 ? Number(input.thresholdMs) : BOOTSTRAP_ROUTE_SLOW_THRESHOLD_MS;
|
|
57
|
+
const severeThresholdMs = Number(input.severeThresholdMs) > 0 ? Number(input.severeThresholdMs) : BOOTSTRAP_ROUTE_SEVERE_THRESHOLD_MS;
|
|
58
|
+
const durationMs = Math.max(0, Math.round(input.durationMs));
|
|
59
|
+
if (durationMs < thresholdMs)
|
|
60
|
+
return;
|
|
61
|
+
const payload = {
|
|
62
|
+
route: input.route,
|
|
63
|
+
workspaceId: String(input.workspaceId || '').trim() || 'default',
|
|
64
|
+
traceId: String(input.traceId || '').trim() || null,
|
|
65
|
+
durationMs,
|
|
66
|
+
severity: durationMs >= severeThresholdMs ? 'high' : 'warning',
|
|
67
|
+
...(input.details || {}),
|
|
68
|
+
};
|
|
69
|
+
console.warn('[Taskforce Bootstrap Timing]', payload);
|
|
70
|
+
}
|
|
20
71
|
export function buildCloudMcpRuntimeAuth(input) {
|
|
21
72
|
const { req, auth, requestHeader, requestId, authScopes } = input;
|
|
22
73
|
const actorType = auth.authenticatedViaToken
|
|
@@ -37,6 +88,7 @@ export function buildCloudMcpRuntimeAuth(input) {
|
|
|
37
88
|
aiProfileProvider: auth.aiProfileProvider,
|
|
38
89
|
aiProfileModel: auth.aiProfileModel,
|
|
39
90
|
aiProfileSurfaceType: auth.aiProfileSurfaceType,
|
|
91
|
+
aiProfileSeatScope: 'cloud_metered',
|
|
40
92
|
actorType,
|
|
41
93
|
requestId: String(requestId || req.headers['x-request-id'] || req.headers['x-railway-request-id'] || '').trim() || undefined,
|
|
42
94
|
ip: req.socket?.remoteAddress || null,
|
|
@@ -216,8 +268,14 @@ export function makeEnsureAdminRole(core, context) {
|
|
|
216
268
|
return actor.authenticated && actor.systemRole === 'system_admin';
|
|
217
269
|
};
|
|
218
270
|
}
|
|
219
|
-
export function makeEnsureAuthenticatedUser() {
|
|
271
|
+
export function makeEnsureAuthenticatedUser(authConfig) {
|
|
220
272
|
return function ensureAuthenticatedUser(req) {
|
|
273
|
+
if (authConfig?.enabled) {
|
|
274
|
+
const access = resolveRequestAccess(req, authConfig);
|
|
275
|
+
if (access.authenticated && access.userId && access.userId !== 'anonymous') {
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
221
279
|
const userId = String(requestUserId(req) || '').trim();
|
|
222
280
|
return userId.length > 0 && userId !== 'anonymous';
|
|
223
281
|
};
|
|
@@ -286,21 +344,28 @@ export function makeAuditAdminAction(core, context) {
|
|
|
286
344
|
};
|
|
287
345
|
}
|
|
288
346
|
export function makeAuditAuthAction(core, requestWorkspaceId, context) {
|
|
289
|
-
return function auditAuthAction(req, action, details) {
|
|
347
|
+
return function auditAuthAction(req, action, details, workspaceIdOverride) {
|
|
290
348
|
const authConfig = context.authConfig;
|
|
291
349
|
const resolvedAccess = authConfig?.enabled ? resolveRequestAccess(req, authConfig) : null;
|
|
292
350
|
const actorUserId = resolvedAccess?.authenticated && resolvedAccess.userId && resolvedAccess.userId !== 'anonymous'
|
|
293
351
|
? resolvedAccess.userId
|
|
294
352
|
: requestUserId(req);
|
|
295
|
-
|
|
353
|
+
let actorRole = resolvedAccess?.authenticated
|
|
296
354
|
? String(resolvedAccess.role || '').trim().toLowerCase() || requestRole(req)
|
|
297
355
|
: requestRole(req);
|
|
356
|
+
const workspaceId = String(workspaceIdOverride || requestWorkspaceId(req) || '').trim() || 'default';
|
|
357
|
+
if (workspaceIdOverride && actorUserId && actorUserId !== 'anonymous') {
|
|
358
|
+
const membership = core.listWorkspaceUsers(workspaceId).find((user) => String(user.userId || '').trim() === actorUserId);
|
|
359
|
+
const role = String(membership?.role || '').trim().toLowerCase();
|
|
360
|
+
if (role)
|
|
361
|
+
actorRole = role;
|
|
362
|
+
}
|
|
298
363
|
const payload = {
|
|
299
364
|
ts: new Date().toISOString(),
|
|
300
365
|
action,
|
|
301
366
|
actorUserId,
|
|
302
367
|
actorRole,
|
|
303
|
-
workspaceId
|
|
368
|
+
workspaceId,
|
|
304
369
|
details
|
|
305
370
|
};
|
|
306
371
|
try {
|