@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
|
@@ -17,6 +17,8 @@ describe('Workflow export snapshots', () => {
|
|
|
17
17
|
for (const workflowName of workflowNames) {
|
|
18
18
|
it(`${envId}/${workflowName} export snapshot`, () => {
|
|
19
19
|
const result = engine.processTemplate(workflowName, envId);
|
|
20
|
+
expect(result.content).not.toContain('shared docs under `docs/`');
|
|
21
|
+
expect(result.content).not.toContain('store it under `docs/`');
|
|
20
22
|
expect({
|
|
21
23
|
filename: result.filename,
|
|
22
24
|
strategy: result.strategy,
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const AI_PROFILE_SEAT_SCOPES: readonly ["cloud_metered", "local_unmetered"];
|
|
2
|
+
export type AiProfileSeatScope = typeof AI_PROFILE_SEAT_SCOPES[number];
|
|
3
|
+
export declare function isAiProfileSeatScope(value: unknown): value is AiProfileSeatScope;
|
|
4
|
+
export declare function parseAiProfileSeatScope(value: unknown): AiProfileSeatScope | null;
|
|
5
|
+
export declare function formatAiProfileSeatScopeLabel(value: AiProfileSeatScope): string;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const AI_PROFILE_SEAT_SCOPES = [
|
|
2
|
+
'cloud_metered',
|
|
3
|
+
'local_unmetered',
|
|
4
|
+
];
|
|
5
|
+
const AI_PROFILE_SEAT_SCOPE_LABELS = {
|
|
6
|
+
cloud_metered: 'Cloud MCP',
|
|
7
|
+
local_unmetered: 'Local MCP',
|
|
8
|
+
};
|
|
9
|
+
export function isAiProfileSeatScope(value) {
|
|
10
|
+
return typeof value === 'string'
|
|
11
|
+
&& AI_PROFILE_SEAT_SCOPES.includes(value);
|
|
12
|
+
}
|
|
13
|
+
export function parseAiProfileSeatScope(value) {
|
|
14
|
+
if (typeof value !== 'string')
|
|
15
|
+
return null;
|
|
16
|
+
const normalized = value.trim();
|
|
17
|
+
return isAiProfileSeatScope(normalized) ? normalized : null;
|
|
18
|
+
}
|
|
19
|
+
export function formatAiProfileSeatScopeLabel(value) {
|
|
20
|
+
return AI_PROFILE_SEAT_SCOPE_LABELS[value];
|
|
21
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const MIN_PASSWORD_LENGTH = 12;
|
|
2
|
+
export type PasswordPolicyCode = 'PASSWORD_REQUIRED' | 'PASSWORD_TOO_SHORT' | 'PASSWORD_TOO_COMMON' | 'PASSWORD_CONTAINS_PERSONAL_INFO';
|
|
3
|
+
export interface PasswordPolicyResult {
|
|
4
|
+
ok: boolean;
|
|
5
|
+
code?: PasswordPolicyCode;
|
|
6
|
+
message?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface PasswordPolicyContext {
|
|
9
|
+
email?: string | null;
|
|
10
|
+
displayName?: string | null;
|
|
11
|
+
}
|
|
12
|
+
export declare function getPasswordPolicyGuidance(): string[];
|
|
13
|
+
export declare function validatePasswordPolicy(password: string, context?: PasswordPolicyContext): PasswordPolicyResult;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export const MIN_PASSWORD_LENGTH = 12;
|
|
2
|
+
const COMMON_PASSWORDS = new Set([
|
|
3
|
+
'12345678',
|
|
4
|
+
'123456789',
|
|
5
|
+
'1234567890',
|
|
6
|
+
'admin123',
|
|
7
|
+
'changeme',
|
|
8
|
+
'letmein',
|
|
9
|
+
'letmein123',
|
|
10
|
+
'password',
|
|
11
|
+
'password1',
|
|
12
|
+
'password12',
|
|
13
|
+
'password123',
|
|
14
|
+
'password1234',
|
|
15
|
+
'qwerty',
|
|
16
|
+
'qwerty123',
|
|
17
|
+
'welcome',
|
|
18
|
+
'welcome123'
|
|
19
|
+
]);
|
|
20
|
+
const normalizeForComparison = (value) => (String(value || '')
|
|
21
|
+
.trim()
|
|
22
|
+
.toLowerCase()
|
|
23
|
+
.replace(/[^a-z0-9]/g, ''));
|
|
24
|
+
function extractPersonalInfoTokens(context) {
|
|
25
|
+
const tokens = new Set();
|
|
26
|
+
const email = String(context?.email || '').trim().toLowerCase();
|
|
27
|
+
const displayName = String(context?.displayName || '').trim().toLowerCase();
|
|
28
|
+
if (email.includes('@')) {
|
|
29
|
+
const localPart = email.split('@')[0] || '';
|
|
30
|
+
const compactLocalPart = localPart.replace(/[^a-z0-9]/g, '');
|
|
31
|
+
if (compactLocalPart.length >= 4)
|
|
32
|
+
tokens.add(compactLocalPart);
|
|
33
|
+
for (const part of localPart.split(/[^a-z0-9]+/)) {
|
|
34
|
+
if (part.length >= 4)
|
|
35
|
+
tokens.add(part);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
for (const part of displayName.split(/[^a-z0-9]+/)) {
|
|
39
|
+
if (part.length >= 4)
|
|
40
|
+
tokens.add(part);
|
|
41
|
+
}
|
|
42
|
+
return [...tokens];
|
|
43
|
+
}
|
|
44
|
+
export function getPasswordPolicyGuidance() {
|
|
45
|
+
return [
|
|
46
|
+
`Use at least ${MIN_PASSWORD_LENGTH} characters.`,
|
|
47
|
+
'Avoid common passwords or obvious patterns.',
|
|
48
|
+
'Do not include your email or display name.'
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
export function validatePasswordPolicy(password, context) {
|
|
52
|
+
const rawPassword = String(password || '');
|
|
53
|
+
if (!rawPassword) {
|
|
54
|
+
return {
|
|
55
|
+
ok: false,
|
|
56
|
+
code: 'PASSWORD_REQUIRED',
|
|
57
|
+
message: 'Password is required.'
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (rawPassword.length < MIN_PASSWORD_LENGTH) {
|
|
61
|
+
return {
|
|
62
|
+
ok: false,
|
|
63
|
+
code: 'PASSWORD_TOO_SHORT',
|
|
64
|
+
message: `Password must be at least ${MIN_PASSWORD_LENGTH} characters.`
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const normalizedPassword = normalizeForComparison(rawPassword);
|
|
68
|
+
if (COMMON_PASSWORDS.has(normalizedPassword)) {
|
|
69
|
+
return {
|
|
70
|
+
ok: false,
|
|
71
|
+
code: 'PASSWORD_TOO_COMMON',
|
|
72
|
+
message: 'Choose a less common password.'
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const personalTokens = extractPersonalInfoTokens(context);
|
|
76
|
+
if (personalTokens.some((token) => normalizedPassword.includes(token))) {
|
|
77
|
+
return {
|
|
78
|
+
ok: false,
|
|
79
|
+
code: 'PASSWORD_CONTAINS_PERSONAL_INFO',
|
|
80
|
+
message: 'Password cannot contain your email or display name.'
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return { ok: true };
|
|
84
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { getPasswordPolicyGuidance, MIN_PASSWORD_LENGTH, validatePasswordPolicy } from './passwordPolicy';
|
|
3
|
+
describe('passwordPolicy', () => {
|
|
4
|
+
it('describes the active password policy guidance', () => {
|
|
5
|
+
expect(getPasswordPolicyGuidance()).toEqual([
|
|
6
|
+
`Use at least ${MIN_PASSWORD_LENGTH} characters.`,
|
|
7
|
+
'Avoid common passwords or obvious patterns.',
|
|
8
|
+
'Do not include your email or display name.'
|
|
9
|
+
]);
|
|
10
|
+
});
|
|
11
|
+
it('rejects common passwords even when padded with punctuation', () => {
|
|
12
|
+
expect(validatePasswordPolicy('Password123!')).toEqual({
|
|
13
|
+
ok: false,
|
|
14
|
+
code: 'PASSWORD_TOO_COMMON',
|
|
15
|
+
message: 'Choose a less common password.'
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
it('rejects passwords containing personal information', () => {
|
|
19
|
+
expect(validatePasswordPolicy('RossBurkeSecure!2026', {
|
|
20
|
+
email: 'ross@example.com',
|
|
21
|
+
displayName: 'Ross Burke'
|
|
22
|
+
})).toEqual({
|
|
23
|
+
ok: false,
|
|
24
|
+
code: 'PASSWORD_CONTAINS_PERSONAL_INFO',
|
|
25
|
+
message: 'Password cannot contain your email or display name.'
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
it('accepts long passwords without common or personal tokens', () => {
|
|
29
|
+
expect(validatePasswordPolicy('MapleHarborOrbit!2026', {
|
|
30
|
+
email: 'ross@example.com',
|
|
31
|
+
displayName: 'Ross Burke'
|
|
32
|
+
})).toEqual({ ok: true });
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -3,6 +3,7 @@ import type { ObjectStorageProvider } from './objectStorage.js';
|
|
|
3
3
|
export type WorkspaceAssetKind = 'document' | 'image' | 'file';
|
|
4
4
|
export type WorkspaceAssetScanStatus = 'pending' | 'clean' | 'infected' | 'failed';
|
|
5
5
|
export type WorkspaceAssetLinkRole = 'attachment' | 'image' | 'reference';
|
|
6
|
+
export type WorkspaceAssetLinkTargetType = 'task' | 'initiative' | 'workstream';
|
|
6
7
|
export interface WorkspaceAssetEntry {
|
|
7
8
|
assetId: string;
|
|
8
9
|
tenantId: string;
|
|
@@ -32,7 +33,9 @@ export interface WorkspaceAssetLinkEntry {
|
|
|
32
33
|
tenantId: string;
|
|
33
34
|
workspaceId: string;
|
|
34
35
|
assetId: string;
|
|
35
|
-
|
|
36
|
+
targetType: WorkspaceAssetLinkTargetType;
|
|
37
|
+
targetId: string;
|
|
38
|
+
taskId: string | null;
|
|
36
39
|
role: WorkspaceAssetLinkRole;
|
|
37
40
|
displayName: string | null;
|
|
38
41
|
sortOrder: number | null;
|
|
@@ -43,7 +46,9 @@ export interface WorkspaceAssetLinkEntry {
|
|
|
43
46
|
export declare class WorkspaceAssetStore {
|
|
44
47
|
private readonly db;
|
|
45
48
|
private readonly tenantId;
|
|
49
|
+
private supportsTargetTypeCache;
|
|
46
50
|
constructor(db: DatabaseAdapter, tenantId: string);
|
|
51
|
+
private supportsTargetType;
|
|
47
52
|
private normalizePersistedAsset;
|
|
48
53
|
get(assetIdRaw: string, workspaceIdRaw?: string): WorkspaceAssetEntry | null;
|
|
49
54
|
getByStorageKey(storageKeyRaw: string, workspaceIdRaw: string): WorkspaceAssetEntry | null;
|
|
@@ -98,7 +103,9 @@ export declare class WorkspaceAssetStore {
|
|
|
98
103
|
upsertLink(input: {
|
|
99
104
|
workspaceId: string;
|
|
100
105
|
assetId: string;
|
|
101
|
-
taskId
|
|
106
|
+
taskId?: string;
|
|
107
|
+
targetType?: WorkspaceAssetLinkTargetType;
|
|
108
|
+
targetId?: string;
|
|
102
109
|
role: WorkspaceAssetLinkRole;
|
|
103
110
|
displayName?: string | null;
|
|
104
111
|
sortOrder?: number | null;
|
|
@@ -109,13 +116,18 @@ export declare class WorkspaceAssetStore {
|
|
|
109
116
|
listLinksForTask(taskIdRaw: string, workspaceIdRaw: string, options?: {
|
|
110
117
|
includeDeleted?: boolean;
|
|
111
118
|
}): WorkspaceAssetLinkEntry[];
|
|
119
|
+
listLinksForTarget(targetTypeRaw: WorkspaceAssetLinkTargetType, targetIdRaw: string, workspaceIdRaw: string, options?: {
|
|
120
|
+
includeDeleted?: boolean;
|
|
121
|
+
}): WorkspaceAssetLinkEntry[];
|
|
112
122
|
listLinksForAsset(assetIdRaw: string, workspaceIdRaw: string, options?: {
|
|
113
123
|
includeDeleted?: boolean;
|
|
114
124
|
}): WorkspaceAssetLinkEntry[];
|
|
115
125
|
deleteLink(input: {
|
|
116
126
|
workspaceId: string;
|
|
117
127
|
assetId: string;
|
|
118
|
-
taskId
|
|
128
|
+
taskId?: string;
|
|
129
|
+
targetType?: WorkspaceAssetLinkTargetType;
|
|
130
|
+
targetId?: string;
|
|
119
131
|
role: WorkspaceAssetLinkRole;
|
|
120
132
|
hardDelete?: boolean;
|
|
121
133
|
}): boolean;
|
|
@@ -47,11 +47,15 @@ function mapAssetRow(row) {
|
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
49
|
function mapLinkRow(row) {
|
|
50
|
+
const targetType = String(row.target_type || 'task').trim();
|
|
51
|
+
const targetId = String(row.task_id || '').trim();
|
|
50
52
|
return {
|
|
51
53
|
tenantId: String(row.tenant_id || 'default'),
|
|
52
54
|
workspaceId: String(row.workspace_id || 'default'),
|
|
53
55
|
assetId: String(row.asset_id || ''),
|
|
54
|
-
|
|
56
|
+
targetType,
|
|
57
|
+
targetId,
|
|
58
|
+
taskId: targetType === 'task' ? targetId : null,
|
|
55
59
|
role: String(row.role || 'attachment'),
|
|
56
60
|
displayName: row.display_name == null ? null : String(row.display_name),
|
|
57
61
|
sortOrder: row.sort_order == null ? null : Number(row.sort_order),
|
|
@@ -73,10 +77,36 @@ function usesReadableAssetReference(kind) {
|
|
|
73
77
|
export class WorkspaceAssetStore {
|
|
74
78
|
db;
|
|
75
79
|
tenantId;
|
|
80
|
+
supportsTargetTypeCache = null;
|
|
76
81
|
constructor(db, tenantId) {
|
|
77
82
|
this.db = db;
|
|
78
83
|
this.tenantId = tenantId;
|
|
79
84
|
}
|
|
85
|
+
supportsTargetType() {
|
|
86
|
+
if (this.supportsTargetTypeCache !== null)
|
|
87
|
+
return this.supportsTargetTypeCache;
|
|
88
|
+
try {
|
|
89
|
+
if (this.db.provider === 'postgres') {
|
|
90
|
+
const row = this.db.prepare(`
|
|
91
|
+
SELECT 1
|
|
92
|
+
FROM information_schema.columns
|
|
93
|
+
WHERE table_schema = current_schema()
|
|
94
|
+
AND table_name = 'workspace_asset_links'
|
|
95
|
+
AND column_name = 'target_type'
|
|
96
|
+
LIMIT 1
|
|
97
|
+
`).get();
|
|
98
|
+
this.supportsTargetTypeCache = Boolean(row);
|
|
99
|
+
return this.supportsTargetTypeCache;
|
|
100
|
+
}
|
|
101
|
+
const columns = this.db.prepare(`PRAGMA table_info(workspace_asset_links)`).all();
|
|
102
|
+
this.supportsTargetTypeCache = columns.some((column) => String(column.name || '').trim() === 'target_type');
|
|
103
|
+
return this.supportsTargetTypeCache;
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
this.supportsTargetTypeCache = false;
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
80
110
|
normalizePersistedAsset(entry) {
|
|
81
111
|
const normalizedKind = normalizeCanonicalAssetKind({
|
|
82
112
|
kind: entry.kind,
|
|
@@ -411,47 +441,93 @@ export class WorkspaceAssetStore {
|
|
|
411
441
|
upsertLink(input) {
|
|
412
442
|
const workspaceId = normalizeWorkspaceId(input.workspaceId);
|
|
413
443
|
const assetId = normalizeId(input.assetId);
|
|
414
|
-
const
|
|
444
|
+
const targetType = normalizeId(input.targetType, input.taskId ? 'task' : 'task');
|
|
445
|
+
const targetId = normalizeId(input.targetId, input.taskId || '');
|
|
415
446
|
const role = normalizeId(input.role, 'attachment');
|
|
416
447
|
const createdAt = normalizeId(input.createdAt, new Date().toISOString());
|
|
417
448
|
const updatedAt = normalizeId(input.updatedAt, createdAt);
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
449
|
+
if (!targetId) {
|
|
450
|
+
throw new Error('Workspace asset links require a targetId.');
|
|
451
|
+
}
|
|
452
|
+
if (this.supportsTargetType()) {
|
|
453
|
+
this.db.prepare(`
|
|
454
|
+
INSERT INTO workspace_asset_links (
|
|
455
|
+
tenant_id,
|
|
456
|
+
workspace_id,
|
|
457
|
+
asset_id,
|
|
458
|
+
target_type,
|
|
459
|
+
task_id,
|
|
460
|
+
role,
|
|
461
|
+
display_name,
|
|
462
|
+
sort_order,
|
|
463
|
+
created_at,
|
|
464
|
+
updated_at,
|
|
465
|
+
deleted_at
|
|
466
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
467
|
+
ON CONFLICT(tenant_id, workspace_id, asset_id, task_id, role) DO UPDATE SET
|
|
468
|
+
target_type = excluded.target_type,
|
|
469
|
+
display_name = excluded.display_name,
|
|
470
|
+
sort_order = excluded.sort_order,
|
|
471
|
+
updated_at = excluded.updated_at,
|
|
472
|
+
deleted_at = excluded.deleted_at
|
|
473
|
+
`).run(this.tenantId, workspaceId, assetId, targetType, targetId, role, normalizeNullable(input.displayName), input.sortOrder == null ? null : Math.floor(Number(input.sortOrder)), createdAt, updatedAt, normalizeNullable(input.deletedAt));
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
this.db.prepare(`
|
|
477
|
+
INSERT INTO workspace_asset_links (
|
|
478
|
+
tenant_id,
|
|
479
|
+
workspace_id,
|
|
480
|
+
asset_id,
|
|
481
|
+
task_id,
|
|
482
|
+
role,
|
|
483
|
+
display_name,
|
|
484
|
+
sort_order,
|
|
485
|
+
created_at,
|
|
486
|
+
updated_at,
|
|
487
|
+
deleted_at
|
|
488
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
489
|
+
ON CONFLICT(tenant_id, workspace_id, asset_id, task_id, role) DO UPDATE SET
|
|
490
|
+
display_name = excluded.display_name,
|
|
491
|
+
sort_order = excluded.sort_order,
|
|
492
|
+
updated_at = excluded.updated_at,
|
|
493
|
+
deleted_at = excluded.deleted_at
|
|
494
|
+
`).run(this.tenantId, workspaceId, assetId, targetId, role, normalizeNullable(input.displayName), input.sortOrder == null ? null : Math.floor(Number(input.sortOrder)), createdAt, updatedAt, normalizeNullable(input.deletedAt));
|
|
495
|
+
}
|
|
437
496
|
return this.listLinksForAsset(assetId, workspaceId, { includeDeleted: true })
|
|
438
|
-
.find((entry) => entry.
|
|
497
|
+
.find((entry) => entry.targetType === targetType && entry.targetId === targetId && entry.role === role);
|
|
439
498
|
}
|
|
440
499
|
listLinksForTask(taskIdRaw, workspaceIdRaw, options) {
|
|
500
|
+
return this.listLinksForTarget('task', taskIdRaw, workspaceIdRaw, options);
|
|
501
|
+
}
|
|
502
|
+
listLinksForTarget(targetTypeRaw, targetIdRaw, workspaceIdRaw, options) {
|
|
441
503
|
const workspaceId = normalizeWorkspaceId(workspaceIdRaw);
|
|
442
|
-
const
|
|
443
|
-
|
|
504
|
+
const targetType = normalizeId(targetTypeRaw, 'task');
|
|
505
|
+
const targetId = normalizeId(targetIdRaw);
|
|
506
|
+
if (!targetId)
|
|
444
507
|
return [];
|
|
445
508
|
const includeDeleted = options?.includeDeleted === true;
|
|
446
|
-
const rows = this.
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
509
|
+
const rows = this.supportsTargetType()
|
|
510
|
+
? this.db.prepare(`
|
|
511
|
+
SELECT *
|
|
512
|
+
FROM workspace_asset_links
|
|
513
|
+
WHERE tenant_id = ?
|
|
514
|
+
AND workspace_id = ?
|
|
515
|
+
AND target_type = ?
|
|
516
|
+
AND task_id = ?
|
|
517
|
+
${includeDeleted ? '' : 'AND deleted_at IS NULL'}
|
|
518
|
+
ORDER BY COALESCE(sort_order, 2147483647) ASC, created_at ASC, asset_id ASC
|
|
519
|
+
`).all(this.tenantId, workspaceId, targetType, targetId)
|
|
520
|
+
: (targetType === 'task'
|
|
521
|
+
? this.db.prepare(`
|
|
522
|
+
SELECT *
|
|
523
|
+
FROM workspace_asset_links
|
|
524
|
+
WHERE tenant_id = ?
|
|
525
|
+
AND workspace_id = ?
|
|
526
|
+
AND task_id = ?
|
|
527
|
+
${includeDeleted ? '' : 'AND deleted_at IS NULL'}
|
|
528
|
+
ORDER BY COALESCE(sort_order, 2147483647) ASC, created_at ASC, asset_id ASC
|
|
529
|
+
`).all(this.tenantId, workspaceId, targetId)
|
|
530
|
+
: []);
|
|
455
531
|
return rows.map(mapLinkRow);
|
|
456
532
|
}
|
|
457
533
|
listLinksForAsset(assetIdRaw, workspaceIdRaw, options) {
|
|
@@ -474,23 +550,35 @@ export class WorkspaceAssetStore {
|
|
|
474
550
|
deleteLink(input) {
|
|
475
551
|
const workspaceId = normalizeWorkspaceId(input.workspaceId);
|
|
476
552
|
const assetId = normalizeId(input.assetId);
|
|
477
|
-
const
|
|
553
|
+
const targetType = normalizeId(input.targetType, input.taskId ? 'task' : 'task');
|
|
554
|
+
const targetId = normalizeId(input.targetId, input.taskId || '');
|
|
478
555
|
const role = normalizeId(input.role);
|
|
479
|
-
if (!assetId || !
|
|
556
|
+
if (!assetId || !targetId || !role)
|
|
480
557
|
return false;
|
|
481
558
|
if (input.hardDelete) {
|
|
482
|
-
const result = this.
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
559
|
+
const result = this.supportsTargetType()
|
|
560
|
+
? this.db.prepare(`
|
|
561
|
+
DELETE FROM workspace_asset_links
|
|
562
|
+
WHERE tenant_id = ? AND workspace_id = ? AND asset_id = ? AND target_type = ? AND task_id = ? AND role = ?
|
|
563
|
+
`).run(this.tenantId, workspaceId, assetId, targetType, targetId, role)
|
|
564
|
+
: this.db.prepare(`
|
|
565
|
+
DELETE FROM workspace_asset_links
|
|
566
|
+
WHERE tenant_id = ? AND workspace_id = ? AND asset_id = ? AND task_id = ? AND role = ?
|
|
567
|
+
`).run(this.tenantId, workspaceId, assetId, targetId, role);
|
|
486
568
|
return Number(result?.changes || 0) > 0;
|
|
487
569
|
}
|
|
488
570
|
const nowIso = new Date().toISOString();
|
|
489
|
-
const result = this.
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
571
|
+
const result = this.supportsTargetType()
|
|
572
|
+
? this.db.prepare(`
|
|
573
|
+
UPDATE workspace_asset_links
|
|
574
|
+
SET deleted_at = ?, updated_at = ?
|
|
575
|
+
WHERE tenant_id = ? AND workspace_id = ? AND asset_id = ? AND target_type = ? AND task_id = ? AND role = ?
|
|
576
|
+
`).run(nowIso, nowIso, this.tenantId, workspaceId, assetId, targetType, targetId, role)
|
|
577
|
+
: this.db.prepare(`
|
|
578
|
+
UPDATE workspace_asset_links
|
|
579
|
+
SET deleted_at = ?, updated_at = ?
|
|
580
|
+
WHERE tenant_id = ? AND workspace_id = ? AND asset_id = ? AND task_id = ? AND role = ?
|
|
581
|
+
`).run(nowIso, nowIso, this.tenantId, workspaceId, assetId, targetId, role);
|
|
494
582
|
return Number(result?.changes || 0) > 0;
|
|
495
583
|
}
|
|
496
584
|
}
|
|
@@ -23,6 +23,40 @@ function makeStore() {
|
|
|
23
23
|
}
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
|
+
function makeLegacyLinkStore() {
|
|
27
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'taskforce-workspace-assets-legacy-'));
|
|
28
|
+
const dbPath = path.join(root, 'taskforce.db');
|
|
29
|
+
const db = createDatabaseAdapter({ sqliteDbPath: dbPath });
|
|
30
|
+
ensureTaskAssetsSchema(db);
|
|
31
|
+
ensureWorkspaceAssetsSchema(db);
|
|
32
|
+
ensureDocumentReferenceSchema(db);
|
|
33
|
+
db.exec(`DROP TABLE IF EXISTS workspace_asset_links`);
|
|
34
|
+
db.exec(`
|
|
35
|
+
CREATE TABLE IF NOT EXISTS workspace_asset_links (
|
|
36
|
+
tenant_id TEXT NOT NULL DEFAULT 'default',
|
|
37
|
+
workspace_id TEXT NOT NULL,
|
|
38
|
+
asset_id TEXT NOT NULL,
|
|
39
|
+
task_id TEXT NOT NULL,
|
|
40
|
+
role TEXT NOT NULL,
|
|
41
|
+
display_name TEXT,
|
|
42
|
+
sort_order INTEGER,
|
|
43
|
+
created_at TEXT NOT NULL,
|
|
44
|
+
updated_at TEXT NOT NULL,
|
|
45
|
+
deleted_at TEXT,
|
|
46
|
+
PRIMARY KEY (tenant_id, workspace_id, asset_id, task_id, role)
|
|
47
|
+
);
|
|
48
|
+
`);
|
|
49
|
+
const store = new WorkspaceAssetStore(db, 'default');
|
|
50
|
+
return {
|
|
51
|
+
root,
|
|
52
|
+
db,
|
|
53
|
+
store,
|
|
54
|
+
cleanup() {
|
|
55
|
+
db.close();
|
|
56
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
26
60
|
describe('WorkspaceAssetStore', () => {
|
|
27
61
|
const cleanups = [];
|
|
28
62
|
afterEach(() => {
|
|
@@ -64,6 +98,50 @@ describe('WorkspaceAssetStore', () => {
|
|
|
64
98
|
referenceNumber: 1
|
|
65
99
|
});
|
|
66
100
|
});
|
|
101
|
+
it('supports legacy workspace asset links without target_type', () => {
|
|
102
|
+
const ctx = makeLegacyLinkStore();
|
|
103
|
+
cleanups.push(() => ctx.cleanup());
|
|
104
|
+
const now = new Date().toISOString();
|
|
105
|
+
ctx.store.upsertAsset({
|
|
106
|
+
assetId: 'asset-legacy-1',
|
|
107
|
+
workspaceId: 'workspace-1',
|
|
108
|
+
kind: 'file',
|
|
109
|
+
originalFilename: 'legacy.pdf',
|
|
110
|
+
mimeType: 'application/pdf',
|
|
111
|
+
storageProvider: 'local',
|
|
112
|
+
storageKey: 'workspaces/workspace-1/assets/files/asset-legacy-1-legacy.pdf',
|
|
113
|
+
contentSha256: 'sha-legacy',
|
|
114
|
+
sizeBytes: 42,
|
|
115
|
+
createdAt: now,
|
|
116
|
+
updatedAt: now
|
|
117
|
+
});
|
|
118
|
+
const created = ctx.store.upsertLink({
|
|
119
|
+
workspaceId: 'workspace-1',
|
|
120
|
+
assetId: 'asset-legacy-1',
|
|
121
|
+
taskId: 'task-1',
|
|
122
|
+
role: 'attachment',
|
|
123
|
+
displayName: 'Legacy attachment',
|
|
124
|
+
createdAt: now,
|
|
125
|
+
updatedAt: now
|
|
126
|
+
});
|
|
127
|
+
expect(created.targetType).toBe('task');
|
|
128
|
+
expect(created.targetId).toBe('task-1');
|
|
129
|
+
expect(ctx.store.listLinksForTask('task-1', 'workspace-1')).toEqual([
|
|
130
|
+
expect.objectContaining({
|
|
131
|
+
assetId: 'asset-legacy-1',
|
|
132
|
+
targetType: 'task',
|
|
133
|
+
targetId: 'task-1',
|
|
134
|
+
role: 'attachment'
|
|
135
|
+
})
|
|
136
|
+
]);
|
|
137
|
+
expect(ctx.store.deleteLink({
|
|
138
|
+
workspaceId: 'workspace-1',
|
|
139
|
+
assetId: 'asset-legacy-1',
|
|
140
|
+
taskId: 'task-1',
|
|
141
|
+
role: 'attachment'
|
|
142
|
+
})).toBe(true);
|
|
143
|
+
expect(ctx.store.listLinksForTask('task-1', 'workspace-1')).toEqual([]);
|
|
144
|
+
});
|
|
67
145
|
it('assigns sequential document reference numbers per workspace', () => {
|
|
68
146
|
const ctx = makeStore();
|
|
69
147
|
cleanups.push(() => ctx.cleanup());
|
|
@@ -382,7 +460,7 @@ describe('WorkspaceAssetStore', () => {
|
|
|
382
460
|
const all = ctx.store.listByWorkspace('workspace-1', { includeDeleted: true });
|
|
383
461
|
expect(all.map((entry) => entry.assetId).sort()).toEqual(['asset-a', 'asset-b']);
|
|
384
462
|
});
|
|
385
|
-
it('creates, lists, and soft-deletes
|
|
463
|
+
it('creates, lists, and soft-deletes generic target links while preserving task compatibility helpers', () => {
|
|
386
464
|
const ctx = makeStore();
|
|
387
465
|
cleanups.push(() => ctx.cleanup());
|
|
388
466
|
ctx.store.upsertAsset({
|
|
@@ -399,27 +477,36 @@ describe('WorkspaceAssetStore', () => {
|
|
|
399
477
|
ctx.store.upsertLink({
|
|
400
478
|
workspaceId: 'workspace-1',
|
|
401
479
|
assetId: 'asset-link-1',
|
|
402
|
-
|
|
480
|
+
targetType: 'task',
|
|
481
|
+
targetId: 'task-1',
|
|
403
482
|
role: 'attachment',
|
|
404
483
|
sortOrder: 2
|
|
405
484
|
});
|
|
406
485
|
ctx.store.upsertLink({
|
|
407
486
|
workspaceId: 'workspace-1',
|
|
408
487
|
assetId: 'asset-link-1',
|
|
409
|
-
|
|
488
|
+
targetType: 'initiative',
|
|
489
|
+
targetId: 'initiative-2',
|
|
410
490
|
role: 'reference',
|
|
411
491
|
sortOrder: 1
|
|
412
492
|
});
|
|
413
493
|
const taskLinks = ctx.store.listLinksForTask('task-1', 'workspace-1');
|
|
414
494
|
expect(taskLinks).toHaveLength(1);
|
|
415
|
-
expect(taskLinks[0]).toMatchObject({ assetId: 'asset-link-1', role: 'attachment' });
|
|
495
|
+
expect(taskLinks[0]).toMatchObject({ assetId: 'asset-link-1', role: 'attachment', targetType: 'task', targetId: 'task-1', taskId: 'task-1' });
|
|
496
|
+
const initiativeLinks = ctx.store.listLinksForTarget('initiative', 'initiative-2', 'workspace-1');
|
|
497
|
+
expect(initiativeLinks).toHaveLength(1);
|
|
498
|
+
expect(initiativeLinks[0]).toMatchObject({ assetId: 'asset-link-1', role: 'reference', targetType: 'initiative', targetId: 'initiative-2', taskId: null });
|
|
416
499
|
const assetLinks = ctx.store.listLinksForAsset('asset-link-1', 'workspace-1');
|
|
417
500
|
expect(assetLinks).toHaveLength(2);
|
|
418
|
-
expect(assetLinks.map((entry) => entry.
|
|
501
|
+
expect(assetLinks.map((entry) => [entry.targetType, entry.targetId])).toEqual([
|
|
502
|
+
['initiative', 'initiative-2'],
|
|
503
|
+
['task', 'task-1'],
|
|
504
|
+
]);
|
|
419
505
|
expect(ctx.store.deleteLink({
|
|
420
506
|
workspaceId: 'workspace-1',
|
|
421
507
|
assetId: 'asset-link-1',
|
|
422
|
-
|
|
508
|
+
targetType: 'task',
|
|
509
|
+
targetId: 'task-1',
|
|
423
510
|
role: 'attachment'
|
|
424
511
|
})).toBe(true);
|
|
425
512
|
expect(ctx.store.listLinksForTask('task-1', 'workspace-1')).toHaveLength(0);
|