@taskforcehq/taskforce 0.3.304 → 0.3.306
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +140 -247
- package/dist/Taskforce.module.css +295 -89
- package/dist/TaskforceCore.js +378 -111
- package/dist/TaskforceCore.test.js +1221 -107
- package/dist/assets/fonts/CourierPrime-Bold.woff2 +0 -0
- package/dist/assets/fonts/CourierPrime-BoldItalic.woff2 +0 -0
- package/dist/assets/fonts/CourierPrime-Italic.woff2 +0 -0
- package/dist/assets/fonts/CourierPrime-Regular.woff2 +0 -0
- package/dist/assets/fonts/Noir_medium.woff2 +0 -0
- package/dist/assets/fonts/Noir_regular.woff2 +0 -0
- package/dist/assets/fonts/RussoOne-Regular.woff2 +0 -0
- package/dist/assets/fonts/SpecialElite-Regular.woff2 +0 -0
- package/dist/compat/workspaceSyncCompat.js +52 -2
- package/dist/compat/workspaceSyncCompat.test.js +38 -1
- package/dist/components/context/ContextAttachmentManager.d.ts +14 -0
- package/dist/components/context/ContextAttachmentManager.js +549 -0
- package/dist/components/features/AgentsModule.d.ts +10 -1
- package/dist/components/features/AgentsModule.js +260 -15
- package/dist/components/features/AgentsModule.test.js +255 -5
- package/dist/components/features/DocumentIndex.d.ts +1 -1
- package/dist/components/features/DocumentIndex.js +1 -1
- package/dist/components/features/DocumentViewer.d.ts +6 -2
- package/dist/components/features/DocumentViewer.js +90 -10
- package/dist/components/features/DocumentWorkspace.d.ts +5 -22
- package/dist/components/features/DocumentWorkspace.js +12 -155
- package/dist/components/features/DocumentWorkspace.test.js +226 -6
- package/dist/components/features/TaskSettings.js +29 -3
- package/dist/components/features/TaskSettings.test.js +42 -1
- package/dist/components/features/documentWorkspaceModel.d.ts +24 -0
- package/dist/components/features/documentWorkspaceModel.js +57 -0
- package/dist/components/features/useDocumentWorkspaceController.d.ts +34 -0
- package/dist/components/features/useDocumentWorkspaceController.js +113 -0
- package/dist/components/task/TaskCard.js +19 -28
- package/dist/components/task/TaskCard.test.js +38 -3
- package/dist/components/task/TaskContextUpload.js +4 -526
- package/dist/components/task/TaskForm.d.ts +8 -8
- package/dist/components/task/TaskForm.js +362 -230
- package/dist/components/task/TaskForm.test.js +558 -381
- package/dist/components/task/TaskKanban.d.ts +2 -1
- package/dist/components/task/TaskKanban.js +96 -14
- package/dist/components/task/TaskKanban.test.js +36 -0
- package/dist/components/ui/EditableAvatarButton.d.ts +18 -0
- package/dist/components/ui/EditableAvatarButton.js +18 -0
- package/dist/components/views/PlanComparisonPage.d.ts +9 -1
- package/dist/components/views/PlanComparisonPage.js +70 -17
- package/dist/components/views/PlanComparisonPage.test.js +289 -41
- package/dist/components/views/PlansPage.js +239 -106
- package/dist/components/views/PlansPage.test.js +593 -42
- package/dist/components/views/StandaloneLayout.js +310 -554
- package/dist/components/views/panels/FilterToolbar.test.js +6 -4
- package/dist/components/views/panels/PlanningDrawer.d.ts +8 -1
- package/dist/components/views/panels/PlanningDrawer.js +18 -11
- package/dist/components/views/panels/PlanningDrawer.test.d.ts +1 -0
- package/dist/components/views/panels/PlanningDrawer.test.js +141 -0
- package/dist/components/views/planningScope.d.ts +20 -0
- package/dist/components/views/planningScope.js +25 -0
- package/dist/components/views/planningScope.test.d.ts +1 -0
- package/dist/components/views/planningScope.test.js +60 -0
- package/dist/components/views/standalone/modals/AccountHubModal.d.ts +27 -14
- package/dist/components/views/standalone/modals/AccountHubModal.js +87 -15
- package/dist/components/views/standalone/modals/AccountHubModal.test.js +42 -2
- package/dist/components/views/standalone/modals/AvatarImageManagerModal.d.ts +20 -0
- package/dist/components/views/standalone/modals/AvatarImageManagerModal.js +127 -0
- package/dist/components/views/standalone/modals/EditProfileModal.d.ts +2 -9
- package/dist/components/views/standalone/modals/EditProfileModal.js +3 -2
- package/dist/components/views/standalone/modals/SyncEnableWarningModal.d.ts +9 -0
- package/dist/components/views/standalone/modals/SyncEnableWarningModal.js +6 -0
- package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.d.ts +1 -0
- package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.js +24 -0
- package/dist/components/views/standalone/modals/SyncStatusModal.d.ts +2 -1
- package/dist/components/views/standalone/modals/SyncStatusModal.js +2 -2
- package/dist/config/envSchema.js +1 -1
- package/dist/core/AiProfileService.d.ts +45 -3
- package/dist/core/AiProfileService.js +774 -63
- package/dist/core/AiProfileService.test.js +22 -2
- package/dist/core/AiProfiles.test.js +761 -2
- package/dist/core/AttachmentLinkService.js +57 -15
- package/dist/core/AuthIdentityService.d.ts +107 -0
- package/dist/core/AuthIdentityService.js +284 -0
- package/dist/core/AuthTokenService.d.ts +4 -0
- package/dist/core/AuthTokenService.js +34 -6
- package/dist/core/AuthTokenService.test.d.ts +1 -0
- package/dist/core/AuthTokenService.test.js +78 -0
- package/dist/core/EntitlementsPolicy.test.js +77 -15
- package/dist/core/GlobalSettingsService.js +115 -6
- package/dist/core/PlanEntitlementService.d.ts +30 -3
- package/dist/core/PlanEntitlementService.js +454 -93
- package/dist/core/PlanFeatureCatalog.test.d.ts +1 -0
- package/dist/core/PlanFeatureCatalog.test.js +84 -0
- package/dist/core/PlanVersionPolicy.test.js +54 -28
- package/dist/core/PlanningEntities.test.js +52 -0
- package/dist/core/SignupMonetizationSettings.test.js +241 -44
- package/dist/core/SyncReconciliationService.js +2 -65
- package/dist/core/SystemAdmin.test.js +244 -88
- package/dist/core/TaskAttachmentsCanonical.test.js +51 -1
- package/dist/core/TaskTaxonomyValidation.test.js +53 -0
- package/dist/core/Taskforce.d.ts +228 -14
- package/dist/core/Taskforce.js +814 -201
- package/dist/core/Taskforce.listTasksSlim.test.d.ts +1 -0
- package/dist/core/Taskforce.listTasksSlim.test.js +91 -0
- package/dist/core/UserProfileAvatarDrafts.test.js +30 -3
- package/dist/core/WorkspaceLifecycleService.js +0 -5
- package/dist/core/WorkspacePermissions.test.js +25 -1
- package/dist/core/WorkspacePlanMode.test.js +128 -27
- package/dist/core/shared.d.ts +3 -1
- package/dist/core/shared.js +16 -1
- package/dist/core/types.d.ts +68 -2
- package/dist/hooks/auth/useTaskforceAuthBootstrap.js +26 -1
- package/dist/hooks/sync/auth.js +3 -1
- package/dist/hooks/sync/bootstrap.js +2 -0
- package/dist/hooks/sync/controlPlane.d.ts +37 -0
- package/dist/hooks/sync/controlPlane.js +78 -0
- package/dist/hooks/sync/controlPlane.test.d.ts +1 -0
- package/dist/hooks/sync/controlPlane.test.js +121 -0
- package/dist/hooks/sync/lifecyclePolicy.d.ts +45 -0
- package/dist/hooks/sync/lifecyclePolicy.js +93 -0
- package/dist/hooks/sync/lifecyclePolicy.test.d.ts +1 -0
- package/dist/hooks/sync/lifecyclePolicy.test.js +124 -0
- package/dist/hooks/sync/orchestratorShared.d.ts +10 -1
- package/dist/hooks/sync/orchestratorShared.js +68 -47
- package/dist/hooks/sync/orchestratorShared.test.js +50 -1
- package/dist/hooks/sync/pullLifecycle.d.ts +31 -0
- package/dist/hooks/sync/pullLifecycle.js +24 -0
- package/dist/hooks/sync/pullLifecycle.test.d.ts +1 -0
- package/dist/hooks/sync/pullLifecycle.test.js +80 -0
- package/dist/hooks/sync/recovery.d.ts +16 -2
- package/dist/hooks/sync/recovery.js +171 -53
- package/dist/hooks/sync/recovery.test.d.ts +1 -0
- package/dist/hooks/sync/recovery.test.js +292 -0
- package/dist/hooks/sync/transfers.d.ts +16 -3
- package/dist/hooks/sync/transfers.js +186 -65
- package/dist/hooks/sync/transfers.test.d.ts +1 -0
- package/dist/hooks/sync/transfers.test.js +396 -0
- package/dist/hooks/sync/useRecentSyncEvents.d.ts +21 -0
- package/dist/hooks/sync/useRecentSyncEvents.js +92 -0
- package/dist/hooks/sync/useRecentSyncEvents.test.d.ts +1 -0
- package/dist/hooks/sync/useRecentSyncEvents.test.js +124 -0
- package/dist/hooks/sync/useSyncStatusActions.d.ts +30 -0
- package/dist/hooks/sync/useSyncStatusActions.js +88 -0
- package/dist/hooks/sync/useSyncStatusActions.test.d.ts +1 -0
- package/dist/hooks/sync/useSyncStatusActions.test.js +133 -0
- package/dist/hooks/sync/useSyncStatusControls.d.ts +25 -0
- package/dist/hooks/sync/useSyncStatusControls.js +60 -0
- package/dist/hooks/sync/useSyncStatusControls.test.d.ts +1 -0
- package/dist/hooks/sync/useSyncStatusControls.test.js +88 -0
- package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +12 -0
- package/dist/hooks/useSyncOrchestrator.d.ts +16 -6
- package/dist/hooks/useSyncOrchestrator.js +424 -175
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +487 -10
- package/dist/hooks/useTaskData.js +8 -3
- package/dist/hooks/useTaskData.test.js +45 -0
- package/dist/hooks/useTaskMutations.d.ts +2 -1
- package/dist/hooks/useTaskMutations.js +12 -1
- package/dist/hooks/useTaskMutations.test.js +30 -1
- package/dist/hooks/useTaskforce.d.ts +65 -4
- package/dist/hooks/useTaskforce.js +564 -243
- package/dist/hooks/useTaskforce.runtime-routing.test.d.ts +1 -0
- package/dist/hooks/useTaskforce.runtime-routing.test.js +152 -0
- package/dist/hooks/useTaskforce.sync-behavior.test.js +2454 -208
- package/dist/hooks/useWorkspaceSyncController.d.ts +4 -1
- package/dist/hooks/useWorkspaceSyncController.js +17 -2
- package/dist/hooks/useWorkspaceSyncController.test.js +1 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +16 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +316 -35
- package/dist/localization/locales/en-US.d.ts +0 -1
- package/dist/localization/locales/en-US.js +0 -1
- package/dist/localization/locales/es-419.js +0 -1
- package/dist/localization/locales/pt-BR.js +0 -1
- package/dist/mcp/adminWorkspaceRegistrar.d.ts +2 -0
- package/dist/mcp/adminWorkspaceRegistrar.js +154 -0
- package/dist/mcp/collaborationRegistrar.d.ts +2 -0
- package/dist/mcp/collaborationRegistrar.js +71 -0
- package/dist/mcp/documentAssetRegistrar.d.ts +2 -0
- package/dist/mcp/documentAssetRegistrar.js +346 -0
- package/dist/mcp/runtime.d.ts +2 -0
- package/dist/mcp/runtime.js +2433 -3534
- package/dist/mcp/runtime.test.js +440 -2
- package/dist/mcp/taskPlanningRegistrar.d.ts +2 -0
- package/dist/mcp/taskPlanningRegistrar.js +418 -0
- package/dist/mcp/toolCatalog.d.ts +35 -0
- package/dist/mcp/toolCatalog.js +3 -0
- package/dist/migrations/taskSchemaMigrations.d.ts +1 -1
- package/dist/migrations/taskSchemaMigrations.js +171 -61
- package/dist/migrations/taskSchemaMigrations.test.js +15 -0
- package/dist/resources/templates/workflow-sources/workflowDocs.mjs +6 -6
- package/dist/resources/templates/workflows/collaborate.yaml +1 -1
- package/dist/resources/templates/workflows/evaluate.yaml +2 -2
- package/dist/resources/templates/workflows/plan.yaml +1 -1
- package/dist/resources/templates/workflows/review.yaml +2 -2
- package/dist/server/annotatedAttachmentsRoutes.test.js +3 -3
- package/dist/server/auth/providers/apple.d.ts +33 -0
- package/dist/server/auth/providers/apple.js +82 -0
- package/dist/server/auth/providers/appleClientSecret.d.ts +37 -0
- package/dist/server/auth/providers/appleClientSecret.js +65 -0
- package/dist/server/auth/providers/github.d.ts +26 -0
- package/dist/server/auth/providers/github.js +86 -0
- package/dist/server/auth/providers/google.d.ts +21 -0
- package/dist/server/auth/providers/google.js +56 -0
- package/dist/server/auth/providers/types.d.ts +21 -0
- package/dist/server/auth/providers/types.js +1 -0
- package/dist/server/auth.d.ts +2 -0
- package/dist/server/auth.js +6 -3
- package/dist/server/documentReviewRoutes.test.js +36 -3
- package/dist/server/index.cookieProxy.test.js +1 -0
- package/dist/server/index.d.ts +12 -0
- package/dist/server/index.js +120 -9
- package/dist/server/index.rateLimit.test.js +3 -0
- package/dist/server/index.test.js +106 -3
- package/dist/server/routes/admin.d.ts +4 -5
- package/dist/server/routes/admin.js +410 -80
- package/dist/server/routes/annotatedAttachments.d.ts +1 -1
- package/dist/server/routes/annotatedAttachments.js +1 -1
- package/dist/server/routes/auth.d.ts +16 -6
- package/dist/server/routes/auth.js +1015 -118
- package/dist/server/routes/authSupport.d.ts +30 -0
- package/dist/server/routes/authSupport.js +81 -0
- package/dist/server/routes/billing.d.ts +1 -1
- package/dist/server/routes/billing.js +276 -34
- package/dist/server/routes/billing.test.js +760 -52
- package/dist/server/routes/documentReviews.d.ts +1 -1
- package/dist/server/routes/documentReviews.js +1 -1
- package/dist/server/routes/documents.d.ts +5 -2
- package/dist/server/routes/documents.js +242 -74
- package/dist/server/routes/primitives.d.ts +11 -0
- package/dist/server/routes/primitives.js +73 -0
- package/dist/server/routes/resources.d.ts +1 -1
- package/dist/server/routes/resources.js +1 -1
- package/dist/server/routes/shared.d.ts +29 -3
- package/dist/server/routes/shared.js +69 -4
- package/dist/server/routes/sync.d.ts +1 -1
- package/dist/server/routes/sync.integration.test.js +437 -39
- package/dist/server/routes/sync.js +62 -606
- package/dist/server/routes/syncAuxRoutes.d.ts +15 -0
- package/dist/server/routes/syncAuxRoutes.js +91 -0
- package/dist/server/routes/syncAuxRoutes.test.d.ts +1 -0
- package/dist/server/routes/syncAuxRoutes.test.js +158 -0
- package/dist/server/routes/syncPullApplyRoutes.d.ts +36 -0
- package/dist/server/routes/syncPullApplyRoutes.js +204 -0
- package/dist/server/routes/syncPushRoutes.d.ts +24 -0
- package/dist/server/routes/syncPushRoutes.js +212 -0
- package/dist/server/routes/syncRouteGuards.d.ts +36 -0
- package/dist/server/routes/syncRouteGuards.js +67 -0
- package/dist/server/routes/syncRouteGuards.test.d.ts +1 -0
- package/dist/server/routes/syncRouteGuards.test.js +94 -0
- package/dist/server/routes/syncRouteTypes.d.ts +13 -0
- package/dist/server/routes/syncRouteTypes.js +1 -0
- package/dist/server/routes/syncSnapshotRoutes.d.ts +66 -0
- package/dist/server/routes/syncSnapshotRoutes.js +180 -0
- package/dist/server/routes/tasks.d.ts +1 -1
- package/dist/server/routes/tasks.js +44 -2
- package/dist/server/routes/workspaces.d.ts +1 -1
- package/dist/server/routes/workspaces.js +37 -6
- package/dist/server/routes.d.ts +2 -9
- package/dist/server/routes.js +136 -165
- package/dist/server/routes.test.js +2457 -162
- package/dist/services/workflowExportSnapshots.test.js +2 -0
- package/dist/shared/aiProfileSeatScope.d.ts +5 -0
- package/dist/shared/aiProfileSeatScope.js +21 -0
- package/dist/shared/passwordPolicy.d.ts +13 -0
- package/dist/shared/passwordPolicy.js +84 -0
- package/dist/shared/passwordPolicy.test.d.ts +1 -0
- package/dist/shared/passwordPolicy.test.js +34 -0
- package/dist/storage/workspaceAssetStore.d.ts +15 -3
- package/dist/storage/workspaceAssetStore.js +132 -44
- package/dist/storage/workspaceAssetStore.test.js +93 -6
- package/dist/styles/fonts.css +70 -0
- package/dist/sync/collaborationSyncPayload.d.ts +6 -1
- package/dist/sync/collaborationSyncPayload.js +17 -11
- package/dist/sync/collaborationSyncPayload.test.js +13 -1
- package/dist/sync/contentSyncState.d.ts +4 -8
- package/dist/sync/contentSyncState.js +33 -77
- package/dist/sync/syncApplyHandlers.d.ts +7 -0
- package/dist/sync/syncApplyHandlers.js +28 -13
- package/dist/sync/syncApplyHandlers.test.js +72 -3
- package/dist/sync/syncFieldSemantics.d.ts +12 -0
- package/dist/sync/syncFieldSemantics.js +55 -0
- package/dist/sync/syncResourceAdapters.d.ts +44 -0
- package/dist/sync/syncResourceAdapters.js +77 -0
- package/dist/sync/syncService.d.ts +6 -0
- package/dist/sync/syncService.js +60 -0
- package/dist/sync/syncService.test.js +71 -3
- package/dist/sync/taskSyncChangeKey.d.ts +2 -0
- package/dist/sync/taskSyncChangeKey.js +143 -0
- package/dist/sync/taskSyncPayload.js +3 -8
- package/dist/sync/workspacePullFeed.js +4 -3
- package/dist/sync/workspacePullFeed.test.js +43 -0
- package/dist/sync/workspaceRepair.js +5 -2
- package/dist/sync/workspaceSyncModel.d.ts +48 -0
- package/dist/sync/workspaceSyncModel.js +195 -31
- package/dist/sync/workspaceSyncModel.test.js +325 -12
- package/dist/sync/workspaceSyncState.d.ts +19 -0
- package/dist/sync/workspaceSyncState.js +3 -1
- package/dist/sync/workspaceSyncSurface.js +3 -8
- package/dist/types.d.ts +17 -0
- package/dist/ui/assets/AgentsModule-DrLawwNl.js +1 -0
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-BlS-cqnl.js → AnnotatedAttachmentWorkspace-Z9_whf7F.js} +1 -1
- package/dist/ui/assets/CourierPrime-Bold-BuLj_dpw.woff2 +0 -0
- package/dist/ui/assets/CourierPrime-BoldItalic-BrK2pUkZ.woff2 +0 -0
- package/dist/ui/assets/CourierPrime-Italic-DFUZK5Ey.woff2 +0 -0
- package/dist/ui/assets/CourierPrime-Regular-BsKphzVf.woff2 +0 -0
- package/dist/ui/assets/DocumentWorkspace-B03MaSkw.js +252 -0
- package/dist/ui/assets/DocumentWorkspace-Dhdso07p.css +1 -0
- package/dist/ui/assets/{InitiativesModule-BjR6iBf9.js → InitiativesModule-9E6ParrY.js} +1 -1
- package/dist/ui/assets/Noir_medium-bxQwKGzB.woff2 +0 -0
- package/dist/ui/assets/Noir_regular-ojf2kxlG.woff2 +0 -0
- package/dist/ui/assets/PlansPage-CvfnMiWq.css +1 -0
- package/dist/ui/assets/PlansPage-DRXscYxH.js +1 -0
- package/dist/ui/assets/RussoOne-Regular-Cu_qq_qC.woff2 +0 -0
- package/dist/ui/assets/SpecialElite-Regular-Di6gSvAS.woff2 +0 -0
- package/dist/ui/assets/TaskSettings-DZX4jk7e.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-DnFqdUME.js → WorkflowsModule-BA7jcEpH.js} +1 -1
- package/dist/ui/assets/index-9eT8e0Lu.css +1 -0
- package/dist/ui/assets/index-C6k75jr8.js +6 -0
- package/dist/ui/assets/{vendor-icons-I7ZwG1z_.js → vendor-icons-DuEd_65c.js} +1 -1
- package/dist/ui/fonts/CourierPrime-Bold.ttf +0 -0
- package/dist/ui/fonts/CourierPrime-Bold.woff2 +0 -0
- package/dist/ui/fonts/CourierPrime-BoldItalic.ttf +0 -0
- package/dist/ui/fonts/CourierPrime-BoldItalic.woff2 +0 -0
- package/dist/ui/fonts/CourierPrime-Italic.ttf +0 -0
- package/dist/ui/fonts/CourierPrime-Italic.woff2 +0 -0
- package/dist/ui/fonts/CourierPrime-Regular.ttf +0 -0
- package/dist/ui/fonts/CourierPrime-Regular.woff2 +0 -0
- package/dist/ui/fonts/Noir_medium.otf +0 -0
- package/dist/ui/fonts/Noir_medium.woff2 +0 -0
- package/dist/ui/fonts/Noir_regular.otf +0 -0
- package/dist/ui/fonts/Noir_regular.woff2 +0 -0
- package/dist/ui/fonts/RussoOne-Regular.woff2 +0 -0
- package/dist/ui/fonts/SpecialElite-Regular.ttf +0 -0
- package/dist/ui/fonts/SpecialElite-Regular.woff2 +0 -0
- package/dist/ui/index.html +3 -3
- package/dist/utils/accountProfileSummaryCache.d.ts +5 -0
- package/dist/utils/assignees.d.ts +2 -0
- package/dist/utils/assignees.js +2 -0
- package/dist/utils/avatarUpload.d.ts +27 -0
- package/dist/utils/avatarUpload.js +132 -0
- package/dist/utils/avatarUpload.test.d.ts +1 -0
- package/dist/utils/avatarUpload.test.js +40 -0
- package/dist/utils/bootstrapTrace.d.ts +7 -0
- package/dist/utils/bootstrapTrace.js +29 -0
- package/dist/utils/commercialLifecyclePresentation.d.ts +19 -0
- package/dist/utils/commercialLifecyclePresentation.js +199 -0
- package/dist/utils/contextAssetEvents.d.ts +2 -0
- package/dist/utils/syncEventDetails.d.ts +3 -0
- package/dist/utils/syncEventDetails.js +64 -0
- package/dist/utils/syncEventDetails.test.d.ts +1 -0
- package/dist/utils/syncEventDetails.test.js +23 -0
- package/dist/utils/syncEventPresentation.d.ts +15 -0
- package/dist/utils/syncEventPresentation.js +129 -0
- package/dist/utils/syncEventPresentation.test.d.ts +1 -0
- package/dist/utils/syncEventPresentation.test.js +64 -0
- package/dist/utils/syncStatusPresentation.d.ts +72 -0
- package/dist/utils/syncStatusPresentation.js +217 -0
- package/dist/utils/syncStatusPresentation.test.d.ts +1 -0
- package/dist/utils/syncStatusPresentation.test.js +164 -0
- package/dist/utils/taskActivity.d.ts +2 -0
- package/dist/utils/taskActivity.js +228 -22
- package/dist/utils/taskActivity.test.js +119 -8
- package/dist/utils/workspaceSyncPresentation.d.ts +4 -0
- package/dist/utils/workspaceSyncPresentation.js +68 -2
- package/dist/utils/workspaceSyncPresentation.test.js +36 -1
- package/package.json +10 -2
- package/scripts/check-cloud-mcp.mjs +83 -0
- package/scripts/export-sqlite-to-postgres.mjs +7 -9
- package/scripts/playwright-auth.sh +5 -1
- package/scripts/run-billing-performance-smoke.sh +24 -0
- package/scripts/run-e2e-realtime.sh +17 -3
- package/scripts/run-smoke.sh +17 -5
- package/scripts/run-soak.sh +17 -5
- package/src/resources/templates/workflow-sources/workflowDocs.mjs +6 -6
- package/src/resources/templates/workflows/collaborate.yaml +1 -1
- package/src/resources/templates/workflows/evaluate.yaml +2 -2
- package/src/resources/templates/workflows/plan.yaml +1 -1
- package/src/resources/templates/workflows/review.yaml +2 -2
- package/dist/ui/assets/AgentsModule-BLWVbuKP.js +0 -1
- package/dist/ui/assets/DocumentWorkspace-BH_6DF6x.js +0 -250
- package/dist/ui/assets/DocumentWorkspace-C_8T8oz-.css +0 -1
- package/dist/ui/assets/PlansPage-C2dd2n1X.css +0 -1
- package/dist/ui/assets/PlansPage-CSDQ4sLa.js +0 -1
- package/dist/ui/assets/RussoOne-Regular-C3BxZIj7.ttf +0 -0
- package/dist/ui/assets/TaskSettings-CEDy34Jo.js +0 -9
- package/dist/ui/assets/index-DXUdtH1B.js +0 -6
- package/dist/ui/assets/index-Ii0B0rTO.css +0 -1
- package/scripts/migrate-planning-model.ts +0 -233
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
import { isAiProfileSurfaceType } from '../shared/aiProfileSurfaceType.js';
|
|
2
|
+
import { isAiProfileSeatScope } from '../shared/aiProfileSeatScope.js';
|
|
3
|
+
import { normalizeSyncSameOriginAssetUrl } from '../sync/syncFieldSemantics.js';
|
|
4
|
+
function normalizeNullableIsoString(value) {
|
|
5
|
+
if (typeof value !== 'string')
|
|
6
|
+
return null;
|
|
7
|
+
const normalized = value.trim();
|
|
8
|
+
return normalized.length > 0 ? normalized : null;
|
|
9
|
+
}
|
|
10
|
+
function normalizeNullableProfileReference(value) {
|
|
11
|
+
const normalized = String(value || '').trim();
|
|
12
|
+
return normalized.length > 0 ? normalized : null;
|
|
13
|
+
}
|
|
2
14
|
function normalizeOptionalProfileField(value) {
|
|
3
15
|
if (value === undefined)
|
|
4
16
|
return undefined;
|
|
@@ -7,6 +19,34 @@ function normalizeOptionalProfileField(value) {
|
|
|
7
19
|
const normalized = String(value).trim();
|
|
8
20
|
return normalized.length > 0 ? normalized : undefined;
|
|
9
21
|
}
|
|
22
|
+
function normalizeAiProfileAvatarUrl(value) {
|
|
23
|
+
return normalizeSyncSameOriginAssetUrl(value);
|
|
24
|
+
}
|
|
25
|
+
function compareIsoStrings(left, right) {
|
|
26
|
+
const normalizedLeft = normalizeNullableIsoString(left);
|
|
27
|
+
const normalizedRight = normalizeNullableIsoString(right);
|
|
28
|
+
if (!normalizedLeft && !normalizedRight)
|
|
29
|
+
return 0;
|
|
30
|
+
if (!normalizedLeft)
|
|
31
|
+
return -1;
|
|
32
|
+
if (!normalizedRight)
|
|
33
|
+
return 1;
|
|
34
|
+
return normalizedLeft.localeCompare(normalizedRight);
|
|
35
|
+
}
|
|
36
|
+
function deriveRosterStateUpdatedAt(value, fallbacks) {
|
|
37
|
+
const explicitValue = normalizeNullableIsoString(value);
|
|
38
|
+
const archivedAt = normalizeNullableIsoString(fallbacks.archivedAt);
|
|
39
|
+
const createdAt = normalizeNullableIsoString(fallbacks.createdAt);
|
|
40
|
+
if (archivedAt) {
|
|
41
|
+
return explicitValue && compareIsoStrings(explicitValue, archivedAt) >= 0
|
|
42
|
+
? explicitValue
|
|
43
|
+
: archivedAt;
|
|
44
|
+
}
|
|
45
|
+
// Legacy active snapshots may carry fresh metadata updatedAt values without
|
|
46
|
+
// carrying an intentional roster lifecycle event. Treat creation as the
|
|
47
|
+
// active lifecycle baseline so metadata-only sync cannot resurrect archives.
|
|
48
|
+
return explicitValue ?? createdAt;
|
|
49
|
+
}
|
|
10
50
|
function normalizeAiProfileSurfaceTypeInput(deps, value) {
|
|
11
51
|
if (value === undefined)
|
|
12
52
|
return undefined;
|
|
@@ -23,8 +63,218 @@ function normalizeAiProfileSurfaceTypeInput(deps, value) {
|
|
|
23
63
|
}
|
|
24
64
|
return normalized;
|
|
25
65
|
}
|
|
26
|
-
|
|
66
|
+
function normalizeAiProfileSeatScopeInput(deps, value) {
|
|
67
|
+
if (value === undefined)
|
|
68
|
+
return undefined;
|
|
69
|
+
if (value === null)
|
|
70
|
+
return null;
|
|
71
|
+
if (typeof value !== 'string') {
|
|
72
|
+
throw deps.createRuleError('AI profile seatScope must be cloud_metered or local_unmetered', 400, 'AI_PROFILE_SEAT_SCOPE_INVALID');
|
|
73
|
+
}
|
|
74
|
+
const normalized = value.trim();
|
|
75
|
+
if (!normalized)
|
|
76
|
+
return null;
|
|
77
|
+
if (!isAiProfileSeatScope(normalized)) {
|
|
78
|
+
throw deps.createRuleError('AI profile seatScope must be cloud_metered or local_unmetered', 400, 'AI_PROFILE_SEAT_SCOPE_INVALID');
|
|
79
|
+
}
|
|
80
|
+
return normalized;
|
|
81
|
+
}
|
|
82
|
+
function resolveAiProfileSeatScope(deps, inputSeatScope, existing) {
|
|
83
|
+
const incomingSeatScope = normalizeAiProfileSeatScopeInput(deps, inputSeatScope);
|
|
84
|
+
const existingSeatScope = existing?.seatScope ?? null;
|
|
85
|
+
if (incomingSeatScope === undefined) {
|
|
86
|
+
return {
|
|
87
|
+
seatScope: existingSeatScope,
|
|
88
|
+
seatScopeProvided: false,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
if (incomingSeatScope === 'cloud_metered' || existingSeatScope === 'cloud_metered') {
|
|
92
|
+
return {
|
|
93
|
+
seatScope: 'cloud_metered',
|
|
94
|
+
seatScopeProvided: true,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
seatScope: incomingSeatScope ?? existingSeatScope,
|
|
99
|
+
seatScopeProvided: true,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function resolveSyncedAiProfileSeatScope(deps, inputSeatScope, existing) {
|
|
103
|
+
const incomingSeatScope = normalizeAiProfileSeatScopeInput(deps, inputSeatScope);
|
|
104
|
+
const existingSeatScope = existing?.seatScope ?? null;
|
|
105
|
+
if (incomingSeatScope === undefined) {
|
|
106
|
+
return {
|
|
107
|
+
seatScope: existingSeatScope,
|
|
108
|
+
seatScopeProvided: false,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
seatScope: incomingSeatScope ?? existingSeatScope,
|
|
113
|
+
seatScopeProvided: true,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function resolveTrustedAiProfileSeatScope(deps, trustedSeatScope, existing) {
|
|
117
|
+
const normalizedTrustedSeatScope = normalizeAiProfileSeatScopeInput(deps, trustedSeatScope);
|
|
118
|
+
if (normalizedTrustedSeatScope === undefined) {
|
|
119
|
+
return {
|
|
120
|
+
seatScope: existing?.seatScope ?? null,
|
|
121
|
+
seatScopeProvided: false,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
seatScope: normalizedTrustedSeatScope,
|
|
126
|
+
seatScopeProvided: true,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function aiProfileMatchesTrustedSeatScope(deps, row, trustedSeatScope) {
|
|
130
|
+
if (trustedSeatScope === undefined)
|
|
131
|
+
return true;
|
|
132
|
+
const profile = deps.aiProfileRowToRecord(row);
|
|
133
|
+
const profileSeatScope = profile.seatScope ?? null;
|
|
134
|
+
if (profileSeatScope === null)
|
|
135
|
+
return true;
|
|
136
|
+
return profileSeatScope === trustedSeatScope;
|
|
137
|
+
}
|
|
138
|
+
function findFirstAiProfileRowForTrustedSeatScope(deps, rows, trustedSeatScope) {
|
|
139
|
+
if (trustedSeatScope === undefined)
|
|
140
|
+
return rows[0];
|
|
141
|
+
return rows.find((row) => aiProfileMatchesTrustedSeatScope(deps, row, trustedSeatScope));
|
|
142
|
+
}
|
|
143
|
+
function filterAiProfileRowsForTrustedSeatScope(deps, rows, trustedSeatScope) {
|
|
144
|
+
if (trustedSeatScope === undefined)
|
|
145
|
+
return rows;
|
|
146
|
+
return rows.filter((row) => aiProfileMatchesTrustedSeatScope(deps, row, trustedSeatScope));
|
|
147
|
+
}
|
|
148
|
+
function requiresCloudSeatReservation(existing, nextSeatScope) {
|
|
149
|
+
if (nextSeatScope !== 'cloud_metered')
|
|
150
|
+
return false;
|
|
151
|
+
if (existing?.archivedAt)
|
|
152
|
+
return true;
|
|
153
|
+
return existing?.seatScope !== 'cloud_metered';
|
|
154
|
+
}
|
|
155
|
+
function assertAiProfileSeatAvailable(deps, workspaceId) {
|
|
156
|
+
const aiProfileUsage = deps.countAiProfileUsage(workspaceId);
|
|
157
|
+
if (aiProfileUsage.limit !== null && aiProfileUsage.used >= aiProfileUsage.limit) {
|
|
158
|
+
throw deps.createRuleError('No AI seats are available for this workspace. Remove an existing cloud AI profile or upgrade your plan.', 403, 'AI_PROFILE_LIMIT_REACHED', {
|
|
159
|
+
workspaceId,
|
|
160
|
+
limit: aiProfileUsage.limit,
|
|
161
|
+
used: aiProfileUsage.used,
|
|
162
|
+
remaining: aiProfileUsage.remaining
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function inferAiProfileProviderModelDefaults(name) {
|
|
167
|
+
const normalized = String(name || '').trim().toLowerCase();
|
|
168
|
+
if (!normalized)
|
|
169
|
+
return {};
|
|
170
|
+
if (normalized.includes('claude code'))
|
|
171
|
+
return { provider: 'Anthropic', model: 'Claude Code' };
|
|
172
|
+
if (normalized.includes('claude'))
|
|
173
|
+
return { provider: 'Anthropic', model: 'Claude' };
|
|
174
|
+
if (normalized.includes('chatgpt'))
|
|
175
|
+
return { provider: 'OpenAI', model: 'ChatGPT' };
|
|
176
|
+
if (normalized.includes('codex'))
|
|
177
|
+
return { provider: 'OpenAI', model: 'Codex' };
|
|
178
|
+
if (normalized.includes('gemini'))
|
|
179
|
+
return { provider: 'Google', model: 'Gemini' };
|
|
180
|
+
return {};
|
|
181
|
+
}
|
|
182
|
+
function inferAiProfileMetadataDefaults(name, surfaceType) {
|
|
183
|
+
const inferredProviderModel = inferAiProfileProviderModelDefaults(name);
|
|
184
|
+
switch (surfaceType) {
|
|
185
|
+
case 'agent':
|
|
186
|
+
return {
|
|
187
|
+
description: `${name} AI collaborator registered in Taskforce.`,
|
|
188
|
+
role: 'AI Collaborator',
|
|
189
|
+
...inferredProviderModel
|
|
190
|
+
};
|
|
191
|
+
case 'coding_tool':
|
|
192
|
+
return {
|
|
193
|
+
description: `${name} coding agent connected through MCP.`,
|
|
194
|
+
role: 'Coding Agent',
|
|
195
|
+
...inferredProviderModel
|
|
196
|
+
};
|
|
197
|
+
case 'chat_app':
|
|
198
|
+
return {
|
|
199
|
+
description: `${name} conversational AI connected through Taskforce.`,
|
|
200
|
+
role: 'AI Assistant',
|
|
201
|
+
...inferredProviderModel
|
|
202
|
+
};
|
|
203
|
+
case 'ide':
|
|
204
|
+
return {
|
|
205
|
+
description: `${name} IDE-connected AI assistant.`,
|
|
206
|
+
role: 'Coding Assistant',
|
|
207
|
+
...inferredProviderModel
|
|
208
|
+
};
|
|
209
|
+
case 'other':
|
|
210
|
+
return {
|
|
211
|
+
description: `${name} AI profile connected through Taskforce.`,
|
|
212
|
+
role: 'AI Assistant',
|
|
213
|
+
...inferredProviderModel
|
|
214
|
+
};
|
|
215
|
+
default:
|
|
216
|
+
return {};
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function resolveOptionalProfileField(inputValue, existingValue, fallbackValue) {
|
|
220
|
+
const explicitValue = normalizeOptionalProfileField(inputValue);
|
|
221
|
+
if (explicitValue !== undefined)
|
|
222
|
+
return explicitValue;
|
|
223
|
+
const preservedValue = normalizeOptionalProfileField(existingValue);
|
|
224
|
+
if (preservedValue !== undefined)
|
|
225
|
+
return preservedValue;
|
|
226
|
+
return normalizeOptionalProfileField(fallbackValue);
|
|
227
|
+
}
|
|
228
|
+
function resolveAiProfileMetadataFields(deps, input, existing) {
|
|
229
|
+
const surfaceTypeInput = normalizeAiProfileSurfaceTypeInput(deps, input.surfaceType);
|
|
230
|
+
const effectiveSurfaceType = surfaceTypeInput === undefined
|
|
231
|
+
? existing?.surfaceType ?? null
|
|
232
|
+
: surfaceTypeInput;
|
|
233
|
+
const inferredDefaults = inferAiProfileMetadataDefaults(input.name, effectiveSurfaceType);
|
|
234
|
+
return {
|
|
235
|
+
description: resolveOptionalProfileField(input.description, existing?.description, inferredDefaults.description) ?? null,
|
|
236
|
+
role: resolveOptionalProfileField(input.role, existing?.role, inferredDefaults.role) ?? null,
|
|
237
|
+
provider: resolveOptionalProfileField(input.provider, existing?.provider, inferredDefaults.provider) ?? null,
|
|
238
|
+
model: resolveOptionalProfileField(input.model, existing?.model, inferredDefaults.model) ?? null,
|
|
239
|
+
surfaceType: effectiveSurfaceType,
|
|
240
|
+
surfaceTypeProvided: surfaceTypeInput !== undefined,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
function applyAiProfileMetadataDefaults(record) {
|
|
244
|
+
const inferredDefaults = inferAiProfileMetadataDefaults(record.name, record.surfaceType);
|
|
245
|
+
return {
|
|
246
|
+
...record,
|
|
247
|
+
description: resolveOptionalProfileField(undefined, record.description, inferredDefaults.description) ?? null,
|
|
248
|
+
role: resolveOptionalProfileField(undefined, record.role, inferredDefaults.role) ?? null,
|
|
249
|
+
provider: resolveOptionalProfileField(undefined, record.provider, inferredDefaults.provider) ?? null,
|
|
250
|
+
model: resolveOptionalProfileField(undefined, record.model, inferredDefaults.model) ?? null,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function hydrateAiProfileRecord(deps, row, diagnostics) {
|
|
254
|
+
const record = deps.aiProfileRowToRecord(row);
|
|
255
|
+
const hydrated = applyAiProfileMetadataDefaults(record);
|
|
256
|
+
const metadataChanged = record.description !== hydrated.description
|
|
257
|
+
|| record.role !== hydrated.role
|
|
258
|
+
|| record.provider !== hydrated.provider
|
|
259
|
+
|| record.model !== hydrated.model;
|
|
260
|
+
if (metadataChanged) {
|
|
261
|
+
deps.db.prepare(`
|
|
262
|
+
UPDATE ai_profiles
|
|
263
|
+
SET description = ?,
|
|
264
|
+
role = ?,
|
|
265
|
+
provider = ?,
|
|
266
|
+
model = ?
|
|
267
|
+
WHERE tenant_id = ? AND id = ?
|
|
268
|
+
`).run(hydrated.description ?? null, hydrated.role ?? null, hydrated.provider ?? null, hydrated.model ?? null, deps.tenantId, record.id);
|
|
269
|
+
if (diagnostics) {
|
|
270
|
+
diagnostics.metadataBackfillCount = (diagnostics.metadataBackfillCount || 0) + 1;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return hydrated;
|
|
274
|
+
}
|
|
275
|
+
export function listAiProfilesService(deps, workspaceId, options) {
|
|
27
276
|
const normalizedWorkspaceId = deps.normalizeWorkspaceId(workspaceId);
|
|
277
|
+
const includeArchived = options?.includeArchived === true;
|
|
28
278
|
const rows = deps.db.prepare(`
|
|
29
279
|
SELECT
|
|
30
280
|
id,
|
|
@@ -34,36 +284,49 @@ export function listAiProfilesService(deps, workspaceId) {
|
|
|
34
284
|
username,
|
|
35
285
|
icon,
|
|
36
286
|
color,
|
|
287
|
+
avatar_url,
|
|
288
|
+
avatar_source_url,
|
|
37
289
|
description,
|
|
38
290
|
role,
|
|
39
291
|
provider,
|
|
40
292
|
model,
|
|
41
293
|
surface_type,
|
|
294
|
+
seat_scope,
|
|
42
295
|
provider_metadata_json,
|
|
296
|
+
archived_at,
|
|
297
|
+
archived_by,
|
|
298
|
+
archived_reason,
|
|
299
|
+
merged_into_profile_id,
|
|
300
|
+
roster_state_updated_at,
|
|
43
301
|
created_at,
|
|
44
302
|
updated_at,
|
|
45
303
|
last_active_at
|
|
46
304
|
FROM ai_profiles
|
|
47
305
|
WHERE tenant_id = ?
|
|
48
306
|
AND workspace_id = ?
|
|
307
|
+
AND (? = 1 OR archived_at IS NULL)
|
|
49
308
|
ORDER BY COALESCE(updated_at, created_at) DESC, LOWER(name) ASC
|
|
50
|
-
`).all(deps.tenantId, normalizedWorkspaceId);
|
|
51
|
-
return rows.map((row) => deps
|
|
309
|
+
`).all(deps.tenantId, normalizedWorkspaceId, includeArchived ? 1 : 0);
|
|
310
|
+
return rows.map((row) => hydrateAiProfileRecord(deps, row, options?.diagnostics));
|
|
52
311
|
}
|
|
53
312
|
export function getAiProfileByTokenService(deps, input) {
|
|
54
313
|
const normalizedWorkspaceId = deps.normalizeWorkspaceId(input.workspaceId);
|
|
55
314
|
const profileToken = String(input.profileToken || '').trim();
|
|
315
|
+
const includeArchived = input.includeArchived === true;
|
|
316
|
+
const trustedSeatScope = normalizeAiProfileSeatScopeInput(deps, input.seatScope);
|
|
56
317
|
if (!profileToken)
|
|
57
318
|
return null;
|
|
58
|
-
const
|
|
319
|
+
const rows = deps.db.prepare(`
|
|
59
320
|
SELECT *
|
|
60
321
|
FROM ai_profiles
|
|
61
322
|
WHERE tenant_id = ?
|
|
62
323
|
AND workspace_id = ?
|
|
63
324
|
AND profile_token = ?
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
325
|
+
AND (? = 1 OR archived_at IS NULL)
|
|
326
|
+
ORDER BY COALESCE(last_active_at, updated_at, created_at) DESC, id ASC
|
|
327
|
+
`).all(deps.tenantId, normalizedWorkspaceId, profileToken, includeArchived ? 1 : 0);
|
|
328
|
+
const row = findFirstAiProfileRowForTrustedSeatScope(deps, rows, trustedSeatScope);
|
|
329
|
+
return row ? hydrateAiProfileRecord(deps, row) : null;
|
|
67
330
|
}
|
|
68
331
|
export function upsertAiProfileForSyncService(deps, profile) {
|
|
69
332
|
const normalizedWorkspaceId = deps.normalizeWorkspaceId(profile.workspaceId);
|
|
@@ -77,24 +340,144 @@ export function upsertAiProfileForSyncService(deps, profile) {
|
|
|
77
340
|
const normalizedLastActiveAt = typeof profile.lastActiveAt === 'string' && profile.lastActiveAt.trim().length > 0
|
|
78
341
|
? profile.lastActiveAt.trim()
|
|
79
342
|
: null;
|
|
80
|
-
const
|
|
343
|
+
const normalizedArchivedAt = normalizeNullableIsoString(profile.archivedAt);
|
|
344
|
+
const normalizedArchivedBy = normalizeNullableProfileReference(profile.archivedBy);
|
|
345
|
+
const normalizedArchivedReason = normalizeOptionalProfileField(profile.archivedReason) ?? null;
|
|
346
|
+
const normalizedMergedIntoProfileId = normalizeNullableProfileReference(profile.mergedIntoProfileId);
|
|
347
|
+
const normalizedRosterStateUpdatedAt = deriveRosterStateUpdatedAt(profile.rosterStateUpdatedAt, {
|
|
348
|
+
archivedAt: normalizedArchivedAt,
|
|
349
|
+
createdAt: normalizedCreatedAt,
|
|
350
|
+
updatedAt: normalizedUpdatedAt,
|
|
351
|
+
});
|
|
81
352
|
const existingRow = deps.db.prepare(`
|
|
82
353
|
SELECT *
|
|
83
354
|
FROM ai_profiles
|
|
84
355
|
WHERE tenant_id = ? AND workspace_id = ? AND id = ?
|
|
85
356
|
LIMIT 1
|
|
86
357
|
`).get(deps.tenantId, normalizedWorkspaceId, profileId);
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
358
|
+
const existingProfile = existingRow ? deps.aiProfileRowToRecord(existingRow) : null;
|
|
359
|
+
const incomingAvatarUrlProvided = Object.prototype.hasOwnProperty.call(profile, 'avatarUrl');
|
|
360
|
+
const incomingAvatarSourceUrlProvided = Object.prototype.hasOwnProperty.call(profile, 'avatarSourceUrl');
|
|
361
|
+
const existingCreatedAt = existingRow
|
|
362
|
+
? String(existingRow.created_at || existingRow.updated_at || '').trim()
|
|
363
|
+
: '';
|
|
364
|
+
const existingUpdatedAt = existingRow
|
|
365
|
+
? String(existingRow.updated_at || existingRow.created_at || '').trim()
|
|
366
|
+
: '';
|
|
367
|
+
const existingLastActiveAt = existingRow
|
|
368
|
+
? normalizeNullableIsoString(existingRow.last_active_at)
|
|
369
|
+
: null;
|
|
370
|
+
const existingArchivedAt = existingRow
|
|
371
|
+
? normalizeNullableIsoString(existingRow.archived_at)
|
|
372
|
+
: null;
|
|
373
|
+
const existingArchivedBy = existingRow
|
|
374
|
+
? normalizeNullableProfileReference(existingRow.archived_by)
|
|
375
|
+
: null;
|
|
376
|
+
const existingArchivedReason = existingRow
|
|
377
|
+
? normalizeOptionalProfileField(existingRow.archived_reason) ?? null
|
|
378
|
+
: null;
|
|
379
|
+
const existingMergedIntoProfileId = existingRow
|
|
380
|
+
? normalizeNullableProfileReference(existingRow.merged_into_profile_id)
|
|
381
|
+
: null;
|
|
382
|
+
const existingRosterStateUpdatedAt = existingRow
|
|
383
|
+
? deriveRosterStateUpdatedAt(existingRow.roster_state_updated_at, {
|
|
384
|
+
archivedAt: existingArchivedAt,
|
|
385
|
+
createdAt: existingCreatedAt,
|
|
386
|
+
updatedAt: existingUpdatedAt,
|
|
387
|
+
})
|
|
388
|
+
: null;
|
|
389
|
+
const metadataComparison = compareIsoStrings(normalizedUpdatedAt, existingUpdatedAt);
|
|
390
|
+
const incomingMetadataWins = !existingRow || metadataComparison > 0;
|
|
391
|
+
const lifecycleComparison = compareIsoStrings(normalizedRosterStateUpdatedAt, existingRosterStateUpdatedAt);
|
|
392
|
+
const incomingArchived = normalizedArchivedAt !== null;
|
|
393
|
+
const existingArchived = existingArchivedAt !== null;
|
|
394
|
+
const incomingLifecycleWins = !existingRow
|
|
395
|
+
|| lifecycleComparison > 0
|
|
396
|
+
|| (lifecycleComparison === 0 && incomingArchived && !existingArchived);
|
|
397
|
+
const metadataSourceProfile = incomingMetadataWins
|
|
398
|
+
? {
|
|
399
|
+
id: profileId,
|
|
400
|
+
workspaceId: normalizedWorkspaceId,
|
|
401
|
+
profileToken: String(profile.profileToken || '').trim(),
|
|
402
|
+
name: deps.normalizeAiProfileName(profile.name),
|
|
403
|
+
username: deps.normalizeAiProfileUsername(profile.username, profile.name),
|
|
404
|
+
icon: deps.normalizeAiProfileIcon(profile.icon),
|
|
405
|
+
color: deps.normalizeAiProfileColor(profile.color),
|
|
406
|
+
avatarUrl: incomingAvatarUrlProvided
|
|
407
|
+
? normalizeAiProfileAvatarUrl(profile.avatarUrl)
|
|
408
|
+
: normalizeAiProfileAvatarUrl(existingProfile?.avatarUrl),
|
|
409
|
+
avatarSourceUrl: incomingAvatarSourceUrlProvided
|
|
410
|
+
? normalizeAiProfileAvatarUrl(profile.avatarSourceUrl)
|
|
411
|
+
: normalizeAiProfileAvatarUrl(existingProfile?.avatarSourceUrl),
|
|
412
|
+
description: profile.description ?? null,
|
|
413
|
+
role: profile.role ?? null,
|
|
414
|
+
provider: profile.provider ?? null,
|
|
415
|
+
model: profile.model ?? null,
|
|
416
|
+
surfaceType: profile.surfaceType ?? null,
|
|
417
|
+
seatScope: profile.seatScope ?? null,
|
|
418
|
+
providerMetadata: profile.providerMetadata ?? null,
|
|
419
|
+
archivedAt: normalizedArchivedAt,
|
|
420
|
+
archivedBy: normalizedArchivedBy,
|
|
421
|
+
archivedReason: normalizedArchivedReason,
|
|
422
|
+
mergedIntoProfileId: normalizedMergedIntoProfileId,
|
|
423
|
+
rosterStateUpdatedAt: normalizedRosterStateUpdatedAt,
|
|
424
|
+
createdAt: normalizedCreatedAt,
|
|
425
|
+
updatedAt: normalizedUpdatedAt,
|
|
426
|
+
lastActiveAt: normalizedLastActiveAt
|
|
91
427
|
}
|
|
92
|
-
|
|
428
|
+
: existingProfile;
|
|
429
|
+
const resolvedMetadata = resolveAiProfileMetadataFields(deps, {
|
|
430
|
+
name: metadataSourceProfile?.name ?? deps.normalizeAiProfileName(profile.name),
|
|
431
|
+
description: metadataSourceProfile?.description,
|
|
432
|
+
role: metadataSourceProfile?.role,
|
|
433
|
+
provider: metadataSourceProfile?.provider,
|
|
434
|
+
model: metadataSourceProfile?.model,
|
|
435
|
+
surfaceType: metadataSourceProfile?.surfaceType,
|
|
436
|
+
}, existingProfile);
|
|
437
|
+
const resolvedSeatScope = incomingMetadataWins
|
|
438
|
+
? resolveSyncedAiProfileSeatScope(deps, profile.seatScope, existingProfile)
|
|
439
|
+
: {
|
|
440
|
+
seatScope: existingProfile?.seatScope ?? null,
|
|
441
|
+
seatScopeProvided: existingProfile?.seatScope !== undefined,
|
|
442
|
+
};
|
|
443
|
+
const mergedProfileToken = incomingMetadataWins
|
|
444
|
+
? String(profile.profileToken || '').trim()
|
|
445
|
+
: String(existingRow?.profile_token || '').trim();
|
|
446
|
+
const mergedName = metadataSourceProfile?.name ?? deps.normalizeAiProfileName(profile.name);
|
|
447
|
+
const mergedUsername = metadataSourceProfile?.username ?? deps.normalizeAiProfileUsername(profile.username, profile.name);
|
|
448
|
+
const mergedIcon = metadataSourceProfile?.icon ?? deps.normalizeAiProfileIcon(profile.icon);
|
|
449
|
+
const mergedColor = metadataSourceProfile?.color ?? deps.normalizeAiProfileColor(profile.color);
|
|
450
|
+
const incomingAvatarFieldsWin = incomingMetadataWins || metadataComparison === 0;
|
|
451
|
+
const mergedAvatarUrl = incomingAvatarUrlProvided && incomingAvatarFieldsWin
|
|
452
|
+
? normalizeAiProfileAvatarUrl(profile.avatarUrl)
|
|
453
|
+
: normalizeAiProfileAvatarUrl(existingProfile?.avatarUrl);
|
|
454
|
+
const mergedAvatarSourceUrl = incomingAvatarSourceUrlProvided && incomingAvatarFieldsWin
|
|
455
|
+
? normalizeAiProfileAvatarUrl(profile.avatarSourceUrl)
|
|
456
|
+
: normalizeAiProfileAvatarUrl(existingProfile?.avatarSourceUrl);
|
|
457
|
+
const mergedProviderMetadata = incomingMetadataWins
|
|
458
|
+
? (profile.providerMetadata && typeof profile.providerMetadata === 'object' && !Array.isArray(profile.providerMetadata)
|
|
459
|
+
? JSON.stringify(profile.providerMetadata)
|
|
460
|
+
: null)
|
|
461
|
+
: (typeof existingRow?.provider_metadata_json === 'string' ? existingRow.provider_metadata_json : null);
|
|
462
|
+
const mergedArchivedAt = incomingLifecycleWins ? normalizedArchivedAt : existingArchivedAt;
|
|
463
|
+
const mergedArchivedBy = incomingLifecycleWins ? normalizedArchivedBy : existingArchivedBy;
|
|
464
|
+
const mergedArchivedReason = incomingLifecycleWins ? normalizedArchivedReason : existingArchivedReason;
|
|
465
|
+
const mergedMergedIntoProfileId = incomingLifecycleWins ? normalizedMergedIntoProfileId : existingMergedIntoProfileId;
|
|
466
|
+
const mergedRosterStateUpdatedAt = incomingLifecycleWins
|
|
467
|
+
? normalizedRosterStateUpdatedAt
|
|
468
|
+
: existingRosterStateUpdatedAt;
|
|
469
|
+
const mergedCreatedAt = existingCreatedAt || normalizedCreatedAt;
|
|
470
|
+
const mergedUpdatedAt = incomingMetadataWins ? normalizedUpdatedAt : (existingUpdatedAt || normalizedUpdatedAt);
|
|
471
|
+
const mergedLastActiveAt = compareIsoStrings(normalizedLastActiveAt, existingLastActiveAt) > 0
|
|
472
|
+
? normalizedLastActiveAt
|
|
473
|
+
: existingLastActiveAt;
|
|
93
474
|
deps.db.prepare(`
|
|
94
475
|
INSERT INTO ai_profiles (
|
|
95
476
|
id, tenant_id, workspace_id, profile_token, name, username, icon, color,
|
|
96
|
-
description, role, provider, model, surface_type,
|
|
97
|
-
|
|
477
|
+
avatar_url, avatar_source_url, description, role, provider, model, surface_type, seat_scope, provider_metadata_json,
|
|
478
|
+
archived_at, archived_by, archived_reason, merged_into_profile_id,
|
|
479
|
+
roster_state_updated_at, created_at, updated_at, last_active_at
|
|
480
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
98
481
|
ON CONFLICT(id) DO UPDATE SET
|
|
99
482
|
workspace_id = excluded.workspace_id,
|
|
100
483
|
profile_token = excluded.profile_token,
|
|
@@ -102,18 +485,24 @@ export function upsertAiProfileForSyncService(deps, profile) {
|
|
|
102
485
|
username = excluded.username,
|
|
103
486
|
icon = excluded.icon,
|
|
104
487
|
color = excluded.color,
|
|
488
|
+
avatar_url = excluded.avatar_url,
|
|
489
|
+
avatar_source_url = excluded.avatar_source_url,
|
|
105
490
|
description = excluded.description,
|
|
106
491
|
role = excluded.role,
|
|
107
492
|
provider = excluded.provider,
|
|
108
493
|
model = excluded.model,
|
|
109
494
|
surface_type = excluded.surface_type,
|
|
495
|
+
seat_scope = excluded.seat_scope,
|
|
110
496
|
provider_metadata_json = excluded.provider_metadata_json,
|
|
497
|
+
archived_at = excluded.archived_at,
|
|
498
|
+
archived_by = excluded.archived_by,
|
|
499
|
+
archived_reason = excluded.archived_reason,
|
|
500
|
+
merged_into_profile_id = excluded.merged_into_profile_id,
|
|
501
|
+
roster_state_updated_at = excluded.roster_state_updated_at,
|
|
111
502
|
created_at = excluded.created_at,
|
|
112
503
|
updated_at = excluded.updated_at,
|
|
113
504
|
last_active_at = excluded.last_active_at
|
|
114
|
-
`).run(profileId, deps.tenantId, normalizedWorkspaceId,
|
|
115
|
-
? JSON.stringify(profile.providerMetadata)
|
|
116
|
-
: null, normalizedCreatedAt, normalizedUpdatedAt, normalizedLastActiveAt);
|
|
505
|
+
`).run(profileId, deps.tenantId, normalizedWorkspaceId, mergedProfileToken, mergedName, mergedUsername, mergedIcon, mergedColor, mergedAvatarUrl, mergedAvatarSourceUrl, resolvedMetadata.description, resolvedMetadata.role, resolvedMetadata.provider, resolvedMetadata.model, resolvedMetadata.surfaceType, resolvedSeatScope.seatScope, mergedProviderMetadata, mergedArchivedAt, mergedArchivedBy, mergedArchivedReason, mergedMergedIntoProfileId, mergedRosterStateUpdatedAt, mergedCreatedAt, mergedUpdatedAt, mergedLastActiveAt);
|
|
117
506
|
const refreshed = deps.db.prepare(`
|
|
118
507
|
SELECT *
|
|
119
508
|
FROM ai_profiles
|
|
@@ -123,7 +512,7 @@ export function upsertAiProfileForSyncService(deps, profile) {
|
|
|
123
512
|
if (!refreshed) {
|
|
124
513
|
throw deps.createRuleError('Failed to upsert AI profile during sync', 500, 'AI_PROFILE_SYNC_UPSERT_FAILED');
|
|
125
514
|
}
|
|
126
|
-
return deps
|
|
515
|
+
return hydrateAiProfileRecord(deps, refreshed);
|
|
127
516
|
}
|
|
128
517
|
export function mergeAiProfilesService(deps, input) {
|
|
129
518
|
const normalizedWorkspaceId = deps.normalizeWorkspaceId(input.workspaceId);
|
|
@@ -134,15 +523,18 @@ export function mergeAiProfilesService(deps, input) {
|
|
|
134
523
|
if (keepId === mergeId)
|
|
135
524
|
throw deps.createRuleError('keepId and mergeId must be different profiles', 400, 'MERGE_PROFILES_SAME_ID');
|
|
136
525
|
const keepProfile = deps.db.prepare(`
|
|
137
|
-
SELECT * FROM ai_profiles WHERE tenant_id = ? AND workspace_id = ? AND id = ? LIMIT 1
|
|
526
|
+
SELECT * FROM ai_profiles WHERE tenant_id = ? AND workspace_id = ? AND id = ? AND archived_at IS NULL LIMIT 1
|
|
138
527
|
`).get(deps.tenantId, normalizedWorkspaceId, keepId);
|
|
139
528
|
if (!keepProfile)
|
|
140
529
|
throw deps.createRuleError(`Profile ${keepId} not found`, 404, 'MERGE_PROFILES_KEEP_NOT_FOUND');
|
|
141
530
|
const mergeProfile = deps.db.prepare(`
|
|
142
|
-
SELECT * FROM ai_profiles WHERE tenant_id = ? AND workspace_id = ? AND id = ? LIMIT 1
|
|
531
|
+
SELECT * FROM ai_profiles WHERE tenant_id = ? AND workspace_id = ? AND id = ? AND archived_at IS NULL LIMIT 1
|
|
143
532
|
`).get(deps.tenantId, normalizedWorkspaceId, mergeId);
|
|
144
533
|
if (!mergeProfile)
|
|
145
534
|
throw deps.createRuleError(`Profile ${mergeId} not found`, 404, 'MERGE_PROFILES_MERGE_NOT_FOUND');
|
|
535
|
+
const keepRecord = deps.aiProfileRowToRecord(keepProfile);
|
|
536
|
+
const mergeRecord = deps.aiProfileRowToRecord(mergeProfile);
|
|
537
|
+
const mergedSeatScope = resolveAiProfileSeatScope(deps, mergeRecord.seatScope, keepRecord);
|
|
146
538
|
const mergedAt = new Date().toISOString();
|
|
147
539
|
const doMerge = deps.db.transaction(() => {
|
|
148
540
|
deps.db.prepare(`
|
|
@@ -153,51 +545,330 @@ export function mergeAiProfilesService(deps, input) {
|
|
|
153
545
|
UPDATE tasks SET created_by = ?, updated_at = ?
|
|
154
546
|
WHERE tenant_id = ? AND workspace_id = ? AND created_by = ?
|
|
155
547
|
`).run(keepId, mergedAt, deps.tenantId, normalizedWorkspaceId, mergeId);
|
|
548
|
+
deps.db.prepare(`
|
|
549
|
+
UPDATE ai_profiles
|
|
550
|
+
SET seat_scope = ?,
|
|
551
|
+
avatar_url = COALESCE(avatar_url, ?),
|
|
552
|
+
avatar_source_url = COALESCE(avatar_source_url, ?),
|
|
553
|
+
updated_at = ?
|
|
554
|
+
WHERE tenant_id = ? AND workspace_id = ? AND id = ?
|
|
555
|
+
`).run(mergedSeatScope.seatScope, normalizeAiProfileAvatarUrl(mergeRecord.avatarUrl), normalizeAiProfileAvatarUrl(mergeRecord.avatarSourceUrl), mergedAt, deps.tenantId, normalizedWorkspaceId, keepId);
|
|
156
556
|
deps.db.prepare(`
|
|
157
557
|
UPDATE comments SET author = ?
|
|
158
558
|
WHERE tenant_id = ? AND workspace_id = ? AND author = ?
|
|
159
559
|
`).run(keepId, deps.tenantId, normalizedWorkspaceId, mergeId);
|
|
160
560
|
deps.db.prepare(`
|
|
161
|
-
|
|
162
|
-
|
|
561
|
+
UPDATE ai_profiles
|
|
562
|
+
SET archived_at = ?,
|
|
563
|
+
archived_by = ?,
|
|
564
|
+
archived_reason = ?,
|
|
565
|
+
merged_into_profile_id = ?,
|
|
566
|
+
roster_state_updated_at = ?,
|
|
567
|
+
updated_at = ?
|
|
568
|
+
WHERE tenant_id = ? AND workspace_id = ? AND id = ?
|
|
569
|
+
`).run(mergedAt, keepId, 'merged_duplicate', keepId, mergedAt, mergedAt, deps.tenantId, normalizedWorkspaceId, mergeId);
|
|
163
570
|
});
|
|
164
571
|
doMerge();
|
|
165
572
|
const refreshed = deps.db.prepare(`
|
|
166
573
|
SELECT * FROM ai_profiles WHERE tenant_id = ? AND id = ? LIMIT 1
|
|
167
574
|
`).get(deps.tenantId, keepId);
|
|
168
|
-
return deps
|
|
575
|
+
return hydrateAiProfileRecord(deps, refreshed);
|
|
576
|
+
}
|
|
577
|
+
export function updateAiProfileAvatarService(deps, input) {
|
|
578
|
+
const normalizedWorkspaceId = deps.normalizeWorkspaceId(input.workspaceId);
|
|
579
|
+
const profileId = String(input.profileId || '').trim();
|
|
580
|
+
if (!profileId)
|
|
581
|
+
throw deps.createRuleError('profileId is required', 400, 'AI_PROFILE_ID_REQUIRED');
|
|
582
|
+
const existing = deps.db.prepare(`
|
|
583
|
+
SELECT *
|
|
584
|
+
FROM ai_profiles
|
|
585
|
+
WHERE tenant_id = ? AND workspace_id = ? AND id = ? AND archived_at IS NULL
|
|
586
|
+
LIMIT 1
|
|
587
|
+
`).get(deps.tenantId, normalizedWorkspaceId, profileId);
|
|
588
|
+
if (!existing) {
|
|
589
|
+
throw deps.createRuleError(`Profile ${profileId} not found`, 404, 'AI_PROFILE_NOT_FOUND');
|
|
590
|
+
}
|
|
591
|
+
const avatarUrl = normalizeAiProfileAvatarUrl(input.avatarUrl);
|
|
592
|
+
if (typeof input.avatarUrl === 'string' && input.avatarUrl.trim().length > 0 && !avatarUrl) {
|
|
593
|
+
throw deps.createRuleError('avatarUrl must be a Taskforce context URL', 400, 'AI_PROFILE_AVATAR_URL_INVALID');
|
|
594
|
+
}
|
|
595
|
+
const avatarSourceUrl = input.avatarSourceUrl === undefined
|
|
596
|
+
? normalizeAiProfileAvatarUrl(existing.avatar_source_url)
|
|
597
|
+
: normalizeAiProfileAvatarUrl(input.avatarSourceUrl);
|
|
598
|
+
if (typeof input.avatarSourceUrl === 'string' && input.avatarSourceUrl.trim().length > 0 && !avatarSourceUrl) {
|
|
599
|
+
throw deps.createRuleError('avatarSourceUrl must be a Taskforce context URL', 400, 'AI_PROFILE_AVATAR_SOURCE_URL_INVALID');
|
|
600
|
+
}
|
|
601
|
+
const updatedAt = new Date().toISOString();
|
|
602
|
+
deps.db.prepare(`
|
|
603
|
+
UPDATE ai_profiles
|
|
604
|
+
SET avatar_url = ?,
|
|
605
|
+
avatar_source_url = ?,
|
|
606
|
+
updated_at = ?
|
|
607
|
+
WHERE tenant_id = ? AND workspace_id = ? AND id = ?
|
|
608
|
+
`).run(avatarUrl, avatarSourceUrl, updatedAt, deps.tenantId, normalizedWorkspaceId, profileId);
|
|
609
|
+
const refreshed = deps.db.prepare(`
|
|
610
|
+
SELECT *
|
|
611
|
+
FROM ai_profiles
|
|
612
|
+
WHERE tenant_id = ? AND workspace_id = ? AND id = ?
|
|
613
|
+
LIMIT 1
|
|
614
|
+
`).get(deps.tenantId, normalizedWorkspaceId, profileId);
|
|
615
|
+
if (!refreshed) {
|
|
616
|
+
throw deps.createRuleError('Failed to update AI profile avatar', 500, 'AI_PROFILE_AVATAR_UPDATE_FAILED');
|
|
617
|
+
}
|
|
618
|
+
return hydrateAiProfileRecord(deps, refreshed);
|
|
619
|
+
}
|
|
620
|
+
export function archiveAiProfileService(deps, input) {
|
|
621
|
+
const normalizedWorkspaceId = deps.normalizeWorkspaceId(input.workspaceId);
|
|
622
|
+
const profileId = String(input.profileId || '').trim();
|
|
623
|
+
if (!profileId)
|
|
624
|
+
throw deps.createRuleError('profileId is required', 400, 'ARCHIVE_AI_PROFILE_INVALID_INPUT');
|
|
625
|
+
const existingRow = deps.db.prepare(`
|
|
626
|
+
SELECT *
|
|
627
|
+
FROM ai_profiles
|
|
628
|
+
WHERE tenant_id = ? AND workspace_id = ? AND id = ? AND archived_at IS NULL
|
|
629
|
+
LIMIT 1
|
|
630
|
+
`).get(deps.tenantId, normalizedWorkspaceId, profileId);
|
|
631
|
+
if (!existingRow) {
|
|
632
|
+
throw deps.createRuleError(`Profile ${profileId} not found`, 404, 'ARCHIVE_AI_PROFILE_NOT_FOUND');
|
|
633
|
+
}
|
|
634
|
+
const openTaskRow = deps.db.prepare(`
|
|
635
|
+
SELECT COUNT(*) AS c
|
|
636
|
+
FROM tasks
|
|
637
|
+
WHERE tenant_id = ?
|
|
638
|
+
AND workspace_id = ?
|
|
639
|
+
AND assignee = ?
|
|
640
|
+
AND COALESCE(is_archived, 0) = 0
|
|
641
|
+
AND LOWER(COALESCE(status, 'task')) NOT IN ('done', 'cancelled')
|
|
642
|
+
`).get(deps.tenantId, normalizedWorkspaceId, profileId);
|
|
643
|
+
const openTaskCount = Math.max(0, Number(openTaskRow?.c || 0));
|
|
644
|
+
if (openTaskCount > 0) {
|
|
645
|
+
throw deps.createRuleError('This AI profile still owns open tasks. Reassign or close them before removing it from the roster.', 409, 'AI_PROFILE_HAS_OPEN_TASKS', { workspaceId: normalizedWorkspaceId, profileId, openTaskCount });
|
|
646
|
+
}
|
|
647
|
+
const archivedAt = new Date().toISOString();
|
|
648
|
+
deps.db.prepare(`
|
|
649
|
+
UPDATE ai_profiles
|
|
650
|
+
SET archived_at = ?,
|
|
651
|
+
archived_by = ?,
|
|
652
|
+
archived_reason = ?,
|
|
653
|
+
merged_into_profile_id = NULL,
|
|
654
|
+
roster_state_updated_at = ?,
|
|
655
|
+
updated_at = ?
|
|
656
|
+
WHERE tenant_id = ? AND workspace_id = ? AND id = ?
|
|
657
|
+
`).run(archivedAt, normalizeNullableProfileReference(input.archivedBy), normalizeOptionalProfileField(input.reason) ?? 'manual_archive', archivedAt, archivedAt, deps.tenantId, normalizedWorkspaceId, profileId);
|
|
658
|
+
const archivedRow = deps.db.prepare(`
|
|
659
|
+
SELECT *
|
|
660
|
+
FROM ai_profiles
|
|
661
|
+
WHERE tenant_id = ? AND workspace_id = ? AND id = ?
|
|
662
|
+
LIMIT 1
|
|
663
|
+
`).get(deps.tenantId, normalizedWorkspaceId, profileId);
|
|
664
|
+
if (!archivedRow) {
|
|
665
|
+
throw deps.createRuleError('Failed to archive AI profile', 500, 'ARCHIVE_AI_PROFILE_FAILED');
|
|
666
|
+
}
|
|
667
|
+
return {
|
|
668
|
+
profile: hydrateAiProfileRecord(deps, archivedRow),
|
|
669
|
+
openTaskCount,
|
|
670
|
+
};
|
|
169
671
|
}
|
|
170
|
-
export function resolveAiProfileService(deps, input) {
|
|
672
|
+
export function resolveAiProfileService(deps, input, options) {
|
|
171
673
|
const normalizedWorkspaceId = deps.normalizeWorkspaceId(input.workspaceId);
|
|
172
674
|
const name = deps.normalizeAiProfileName(input.name);
|
|
173
675
|
const profileToken = String(input.profileToken || '').trim();
|
|
174
676
|
const now = new Date().toISOString();
|
|
677
|
+
const trustedSeatScope = normalizeAiProfileSeatScopeInput(deps, options?.trustedSeatScope);
|
|
175
678
|
if (profileToken) {
|
|
176
|
-
const
|
|
679
|
+
const existingRows = deps.db.prepare(`
|
|
177
680
|
SELECT *
|
|
178
681
|
FROM ai_profiles
|
|
179
682
|
WHERE tenant_id = ?
|
|
180
683
|
AND workspace_id = ?
|
|
181
684
|
AND profile_token = ?
|
|
182
|
-
|
|
183
|
-
|
|
685
|
+
AND archived_at IS NULL
|
|
686
|
+
ORDER BY COALESCE(last_active_at, updated_at, created_at) DESC, id ASC
|
|
687
|
+
`).all(deps.tenantId, normalizedWorkspaceId, profileToken);
|
|
688
|
+
const existingRow = findFirstAiProfileRowForTrustedSeatScope(deps, existingRows, trustedSeatScope);
|
|
184
689
|
if (!existingRow) {
|
|
185
|
-
|
|
690
|
+
const archivedRows = deps.db.prepare(`
|
|
691
|
+
SELECT *
|
|
692
|
+
FROM ai_profiles
|
|
693
|
+
WHERE tenant_id = ?
|
|
694
|
+
AND workspace_id = ?
|
|
695
|
+
AND profile_token = ?
|
|
696
|
+
AND archived_at IS NOT NULL
|
|
697
|
+
ORDER BY COALESCE(last_active_at, updated_at, created_at) DESC, id ASC
|
|
698
|
+
`).all(deps.tenantId, normalizedWorkspaceId, profileToken);
|
|
699
|
+
const archivedRow = findFirstAiProfileRowForTrustedSeatScope(deps, archivedRows, trustedSeatScope);
|
|
700
|
+
if (!archivedRow) {
|
|
701
|
+
if (existingRows.length === 0 && archivedRows.length === 0) {
|
|
702
|
+
throw deps.createRuleError('AI profile token is invalid for this workspace', 404, 'AI_PROFILE_NOT_FOUND');
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
else {
|
|
706
|
+
const existingProfile = deps.aiProfileRowToRecord(archivedRow);
|
|
707
|
+
const nextName = name;
|
|
708
|
+
const nextUsername = deps.normalizeAiProfileUsername(undefined, nextName);
|
|
709
|
+
const nextIcon = input.icon === undefined
|
|
710
|
+
? existingProfile.icon
|
|
711
|
+
: deps.normalizeAiProfileIcon(input.icon);
|
|
712
|
+
const nextColor = input.color === undefined
|
|
713
|
+
? existingProfile.color
|
|
714
|
+
: deps.normalizeAiProfileColor(input.color);
|
|
715
|
+
const nextDescription = normalizeOptionalProfileField(input.description);
|
|
716
|
+
const nextRole = normalizeOptionalProfileField(input.role);
|
|
717
|
+
const nextProvider = normalizeOptionalProfileField(input.provider);
|
|
718
|
+
const nextModel = normalizeOptionalProfileField(input.model);
|
|
719
|
+
const resolvedMetadata = resolveAiProfileMetadataFields(deps, {
|
|
720
|
+
name: nextName,
|
|
721
|
+
description: nextDescription,
|
|
722
|
+
role: nextRole,
|
|
723
|
+
provider: nextProvider,
|
|
724
|
+
model: nextModel,
|
|
725
|
+
surfaceType: input.surfaceType,
|
|
726
|
+
}, existingProfile);
|
|
727
|
+
const resolvedSeatScope = resolveTrustedAiProfileSeatScope(deps, trustedSeatScope, existingProfile);
|
|
728
|
+
if (requiresCloudSeatReservation(existingProfile, resolvedSeatScope.seatScope)) {
|
|
729
|
+
assertAiProfileSeatAvailable(deps, normalizedWorkspaceId);
|
|
730
|
+
}
|
|
731
|
+
deps.db.prepare(`
|
|
732
|
+
UPDATE ai_profiles
|
|
733
|
+
SET name = ?,
|
|
734
|
+
username = ?,
|
|
735
|
+
icon = ?,
|
|
736
|
+
color = ?,
|
|
737
|
+
description = COALESCE(?, description),
|
|
738
|
+
role = COALESCE(?, role),
|
|
739
|
+
provider = COALESCE(?, provider),
|
|
740
|
+
model = COALESCE(?, model),
|
|
741
|
+
surface_type = CASE
|
|
742
|
+
WHEN ? = 1 THEN ?
|
|
743
|
+
ELSE surface_type
|
|
744
|
+
END,
|
|
745
|
+
seat_scope = CASE
|
|
746
|
+
WHEN ? = 1 THEN ?
|
|
747
|
+
ELSE seat_scope
|
|
748
|
+
END,
|
|
749
|
+
archived_at = NULL,
|
|
750
|
+
archived_by = NULL,
|
|
751
|
+
archived_reason = NULL,
|
|
752
|
+
merged_into_profile_id = NULL,
|
|
753
|
+
roster_state_updated_at = ?,
|
|
754
|
+
last_active_at = ?,
|
|
755
|
+
updated_at = ?
|
|
756
|
+
WHERE tenant_id = ? AND id = ?
|
|
757
|
+
`).run(nextName, nextUsername, nextIcon, nextColor, resolvedMetadata.description, resolvedMetadata.role, resolvedMetadata.provider, resolvedMetadata.model, resolvedMetadata.surfaceTypeProvided ? 1 : 0, resolvedMetadata.surfaceType, resolvedSeatScope.seatScopeProvided ? 1 : 0, resolvedSeatScope.seatScope, now, now, now, deps.tenantId, String(archivedRow.id || '').trim());
|
|
758
|
+
const refreshed = deps.db.prepare(`
|
|
759
|
+
SELECT *
|
|
760
|
+
FROM ai_profiles
|
|
761
|
+
WHERE tenant_id = ? AND id = ?
|
|
762
|
+
LIMIT 1
|
|
763
|
+
`).get(deps.tenantId, String(archivedRow.id || '').trim());
|
|
764
|
+
if (!refreshed) {
|
|
765
|
+
throw deps.createRuleError('Failed to refresh AI profile', 500, 'AI_PROFILE_REFRESH_FAILED');
|
|
766
|
+
}
|
|
767
|
+
return {
|
|
768
|
+
created: false,
|
|
769
|
+
profile: hydrateAiProfileRecord(deps, refreshed)
|
|
770
|
+
};
|
|
771
|
+
}
|
|
186
772
|
}
|
|
187
|
-
|
|
773
|
+
else {
|
|
774
|
+
const existingProfile = deps.aiProfileRowToRecord(existingRow);
|
|
775
|
+
const nextName = name;
|
|
776
|
+
const nextUsername = deps.normalizeAiProfileUsername(undefined, nextName);
|
|
777
|
+
const nextIcon = input.icon === undefined
|
|
778
|
+
? existingProfile.icon
|
|
779
|
+
: deps.normalizeAiProfileIcon(input.icon);
|
|
780
|
+
const nextColor = input.color === undefined
|
|
781
|
+
? existingProfile.color
|
|
782
|
+
: deps.normalizeAiProfileColor(input.color);
|
|
783
|
+
const nextDescription = normalizeOptionalProfileField(input.description);
|
|
784
|
+
const nextRole = normalizeOptionalProfileField(input.role);
|
|
785
|
+
const nextProvider = normalizeOptionalProfileField(input.provider);
|
|
786
|
+
const nextModel = normalizeOptionalProfileField(input.model);
|
|
787
|
+
const resolvedMetadata = resolveAiProfileMetadataFields(deps, {
|
|
788
|
+
name: nextName,
|
|
789
|
+
description: nextDescription,
|
|
790
|
+
role: nextRole,
|
|
791
|
+
provider: nextProvider,
|
|
792
|
+
model: nextModel,
|
|
793
|
+
surfaceType: input.surfaceType,
|
|
794
|
+
}, existingProfile);
|
|
795
|
+
const resolvedSeatScope = resolveTrustedAiProfileSeatScope(deps, trustedSeatScope, existingProfile);
|
|
796
|
+
if (requiresCloudSeatReservation(existingProfile, resolvedSeatScope.seatScope)) {
|
|
797
|
+
assertAiProfileSeatAvailable(deps, normalizedWorkspaceId);
|
|
798
|
+
}
|
|
799
|
+
deps.db.prepare(`
|
|
800
|
+
UPDATE ai_profiles
|
|
801
|
+
SET name = ?,
|
|
802
|
+
username = ?,
|
|
803
|
+
icon = ?,
|
|
804
|
+
color = ?,
|
|
805
|
+
description = COALESCE(?, description),
|
|
806
|
+
role = COALESCE(?, role),
|
|
807
|
+
provider = COALESCE(?, provider),
|
|
808
|
+
model = COALESCE(?, model),
|
|
809
|
+
surface_type = CASE
|
|
810
|
+
WHEN ? = 1 THEN ?
|
|
811
|
+
ELSE surface_type
|
|
812
|
+
END,
|
|
813
|
+
seat_scope = CASE
|
|
814
|
+
WHEN ? = 1 THEN ?
|
|
815
|
+
ELSE seat_scope
|
|
816
|
+
END,
|
|
817
|
+
last_active_at = ?,
|
|
818
|
+
updated_at = ?
|
|
819
|
+
WHERE tenant_id = ? AND id = ?
|
|
820
|
+
`).run(nextName, nextUsername, nextIcon, nextColor, resolvedMetadata.description, resolvedMetadata.role, resolvedMetadata.provider, resolvedMetadata.model, resolvedMetadata.surfaceTypeProvided ? 1 : 0, resolvedMetadata.surfaceType, resolvedSeatScope.seatScopeProvided ? 1 : 0, resolvedSeatScope.seatScope, now, now, deps.tenantId, String(existingRow.id || '').trim());
|
|
821
|
+
const refreshed = deps.db.prepare(`
|
|
822
|
+
SELECT *
|
|
823
|
+
FROM ai_profiles
|
|
824
|
+
WHERE tenant_id = ? AND id = ?
|
|
825
|
+
LIMIT 1
|
|
826
|
+
`).get(deps.tenantId, String(existingRow.id || '').trim());
|
|
827
|
+
if (!refreshed) {
|
|
828
|
+
throw deps.createRuleError('Failed to refresh AI profile', 500, 'AI_PROFILE_REFRESH_FAILED');
|
|
829
|
+
}
|
|
830
|
+
return {
|
|
831
|
+
created: false,
|
|
832
|
+
profile: hydrateAiProfileRecord(deps, refreshed)
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
const exactMatches = deps.db.prepare(`
|
|
837
|
+
SELECT *
|
|
838
|
+
FROM ai_profiles
|
|
839
|
+
WHERE tenant_id = ?
|
|
840
|
+
AND workspace_id = ?
|
|
841
|
+
AND archived_at IS NULL
|
|
842
|
+
AND (
|
|
843
|
+
LOWER(name) = LOWER(?)
|
|
844
|
+
OR LOWER(username) = LOWER(?)
|
|
845
|
+
)
|
|
846
|
+
ORDER BY COALESCE(last_active_at, updated_at, created_at) DESC, id ASC
|
|
847
|
+
`).all(deps.tenantId, normalizedWorkspaceId, name, deps.normalizeAiProfileUsername(undefined, name));
|
|
848
|
+
const scopedExactMatches = filterAiProfileRowsForTrustedSeatScope(deps, exactMatches, trustedSeatScope);
|
|
849
|
+
if (scopedExactMatches.length > 0) {
|
|
850
|
+
const canonicalId = String(scopedExactMatches[0].id || '').trim();
|
|
188
851
|
const nextName = name;
|
|
189
852
|
const nextUsername = deps.normalizeAiProfileUsername(undefined, nextName);
|
|
190
|
-
const nextIcon = input.icon
|
|
191
|
-
|
|
192
|
-
: deps.normalizeAiProfileIcon(input.icon);
|
|
193
|
-
const nextColor = input.color === undefined
|
|
194
|
-
? existingProfile.color
|
|
195
|
-
: deps.normalizeAiProfileColor(input.color);
|
|
853
|
+
const nextIcon = deps.normalizeAiProfileIcon(input.icon);
|
|
854
|
+
const nextColor = deps.normalizeAiProfileColor(input.color);
|
|
196
855
|
const nextDescription = normalizeOptionalProfileField(input.description);
|
|
197
856
|
const nextRole = normalizeOptionalProfileField(input.role);
|
|
198
857
|
const nextProvider = normalizeOptionalProfileField(input.provider);
|
|
199
858
|
const nextModel = normalizeOptionalProfileField(input.model);
|
|
200
|
-
const
|
|
859
|
+
const existingProfile = deps.aiProfileRowToRecord(scopedExactMatches[0]);
|
|
860
|
+
const resolvedMetadata = resolveAiProfileMetadataFields(deps, {
|
|
861
|
+
name: nextName,
|
|
862
|
+
description: nextDescription,
|
|
863
|
+
role: nextRole,
|
|
864
|
+
provider: nextProvider,
|
|
865
|
+
model: nextModel,
|
|
866
|
+
surfaceType: input.surfaceType,
|
|
867
|
+
}, existingProfile);
|
|
868
|
+
const resolvedSeatScope = resolveTrustedAiProfileSeatScope(deps, trustedSeatScope, existingProfile);
|
|
869
|
+
if (requiresCloudSeatReservation(existingProfile, resolvedSeatScope.seatScope)) {
|
|
870
|
+
assertAiProfileSeatAvailable(deps, normalizedWorkspaceId);
|
|
871
|
+
}
|
|
201
872
|
deps.db.prepare(`
|
|
202
873
|
UPDATE ai_profiles
|
|
203
874
|
SET name = ?,
|
|
@@ -208,41 +879,53 @@ export function resolveAiProfileService(deps, input) {
|
|
|
208
879
|
role = COALESCE(?, role),
|
|
209
880
|
provider = COALESCE(?, provider),
|
|
210
881
|
model = COALESCE(?, model),
|
|
882
|
+
last_active_at = ?,
|
|
883
|
+
updated_at = ?,
|
|
211
884
|
surface_type = CASE
|
|
212
885
|
WHEN ? = 1 THEN ?
|
|
213
886
|
ELSE surface_type
|
|
214
887
|
END,
|
|
215
|
-
|
|
216
|
-
|
|
888
|
+
seat_scope = CASE
|
|
889
|
+
WHEN ? = 1 THEN ?
|
|
890
|
+
ELSE seat_scope
|
|
891
|
+
END
|
|
217
892
|
WHERE tenant_id = ? AND id = ?
|
|
218
|
-
`).run(nextName, nextUsername, nextIcon, nextColor,
|
|
219
|
-
const
|
|
893
|
+
`).run(nextName, nextUsername, nextIcon, nextColor, resolvedMetadata.description, resolvedMetadata.role, resolvedMetadata.provider, resolvedMetadata.model, now, now, resolvedMetadata.surfaceTypeProvided ? 1 : 0, resolvedMetadata.surfaceType, resolvedSeatScope.seatScopeProvided ? 1 : 0, resolvedSeatScope.seatScope, deps.tenantId, canonicalId);
|
|
894
|
+
const reused = deps.db.prepare(`
|
|
220
895
|
SELECT *
|
|
221
896
|
FROM ai_profiles
|
|
222
897
|
WHERE tenant_id = ? AND id = ?
|
|
223
898
|
LIMIT 1
|
|
224
|
-
`).get(deps.tenantId,
|
|
225
|
-
if (!
|
|
899
|
+
`).get(deps.tenantId, canonicalId);
|
|
900
|
+
if (!reused) {
|
|
226
901
|
throw deps.createRuleError('Failed to refresh AI profile', 500, 'AI_PROFILE_REFRESH_FAILED');
|
|
227
902
|
}
|
|
228
903
|
return {
|
|
229
904
|
created: false,
|
|
230
|
-
profile: deps
|
|
905
|
+
profile: hydrateAiProfileRecord(deps, reused),
|
|
906
|
+
...(scopedExactMatches.length > 1 ? {
|
|
907
|
+
duplicateWarning: {
|
|
908
|
+
existingProfileId: canonicalId,
|
|
909
|
+
message: `Multiple profiles matched "${name}". Reused ${canonicalId}; merge the remaining duplicates.`
|
|
910
|
+
}
|
|
911
|
+
} : {})
|
|
231
912
|
};
|
|
232
913
|
}
|
|
233
|
-
const
|
|
914
|
+
const archivedMatches = deps.db.prepare(`
|
|
234
915
|
SELECT *
|
|
235
916
|
FROM ai_profiles
|
|
236
917
|
WHERE tenant_id = ?
|
|
237
918
|
AND workspace_id = ?
|
|
919
|
+
AND archived_at IS NOT NULL
|
|
238
920
|
AND (
|
|
239
921
|
LOWER(name) = LOWER(?)
|
|
240
922
|
OR LOWER(username) = LOWER(?)
|
|
241
923
|
)
|
|
242
924
|
ORDER BY COALESCE(last_active_at, updated_at, created_at) DESC, id ASC
|
|
243
925
|
`).all(deps.tenantId, normalizedWorkspaceId, name, deps.normalizeAiProfileUsername(undefined, name));
|
|
244
|
-
|
|
245
|
-
|
|
926
|
+
const scopedArchivedMatches = filterAiProfileRowsForTrustedSeatScope(deps, archivedMatches, trustedSeatScope);
|
|
927
|
+
if (scopedArchivedMatches.length > 0) {
|
|
928
|
+
const canonicalId = String(scopedArchivedMatches[0].id || '').trim();
|
|
246
929
|
const nextName = name;
|
|
247
930
|
const nextUsername = deps.normalizeAiProfileUsername(undefined, nextName);
|
|
248
931
|
const nextIcon = deps.normalizeAiProfileIcon(input.icon);
|
|
@@ -251,7 +934,19 @@ export function resolveAiProfileService(deps, input) {
|
|
|
251
934
|
const nextRole = normalizeOptionalProfileField(input.role);
|
|
252
935
|
const nextProvider = normalizeOptionalProfileField(input.provider);
|
|
253
936
|
const nextModel = normalizeOptionalProfileField(input.model);
|
|
254
|
-
const
|
|
937
|
+
const existingProfile = deps.aiProfileRowToRecord(scopedArchivedMatches[0]);
|
|
938
|
+
const resolvedMetadata = resolveAiProfileMetadataFields(deps, {
|
|
939
|
+
name: nextName,
|
|
940
|
+
description: nextDescription,
|
|
941
|
+
role: nextRole,
|
|
942
|
+
provider: nextProvider,
|
|
943
|
+
model: nextModel,
|
|
944
|
+
surfaceType: input.surfaceType,
|
|
945
|
+
}, existingProfile);
|
|
946
|
+
const resolvedSeatScope = resolveTrustedAiProfileSeatScope(deps, trustedSeatScope, existingProfile);
|
|
947
|
+
if (requiresCloudSeatReservation(existingProfile, resolvedSeatScope.seatScope)) {
|
|
948
|
+
assertAiProfileSeatAvailable(deps, normalizedWorkspaceId);
|
|
949
|
+
}
|
|
255
950
|
deps.db.prepare(`
|
|
256
951
|
UPDATE ai_profiles
|
|
257
952
|
SET name = ?,
|
|
@@ -264,30 +959,33 @@ export function resolveAiProfileService(deps, input) {
|
|
|
264
959
|
model = COALESCE(?, model),
|
|
265
960
|
last_active_at = ?,
|
|
266
961
|
updated_at = ?,
|
|
962
|
+
archived_at = NULL,
|
|
963
|
+
archived_by = NULL,
|
|
964
|
+
archived_reason = NULL,
|
|
965
|
+
merged_into_profile_id = NULL,
|
|
966
|
+
roster_state_updated_at = ?,
|
|
267
967
|
surface_type = CASE
|
|
268
968
|
WHEN ? = 1 THEN ?
|
|
269
969
|
ELSE surface_type
|
|
970
|
+
END,
|
|
971
|
+
seat_scope = CASE
|
|
972
|
+
WHEN ? = 1 THEN ?
|
|
973
|
+
ELSE seat_scope
|
|
270
974
|
END
|
|
271
975
|
WHERE tenant_id = ? AND id = ?
|
|
272
|
-
`).run(nextName, nextUsername, nextIcon, nextColor,
|
|
273
|
-
const
|
|
976
|
+
`).run(nextName, nextUsername, nextIcon, nextColor, resolvedMetadata.description, resolvedMetadata.role, resolvedMetadata.provider, resolvedMetadata.model, now, now, now, resolvedMetadata.surfaceTypeProvided ? 1 : 0, resolvedMetadata.surfaceType, resolvedSeatScope.seatScopeProvided ? 1 : 0, resolvedSeatScope.seatScope, deps.tenantId, canonicalId);
|
|
977
|
+
const reactivated = deps.db.prepare(`
|
|
274
978
|
SELECT *
|
|
275
979
|
FROM ai_profiles
|
|
276
980
|
WHERE tenant_id = ? AND id = ?
|
|
277
981
|
LIMIT 1
|
|
278
982
|
`).get(deps.tenantId, canonicalId);
|
|
279
|
-
if (!
|
|
983
|
+
if (!reactivated) {
|
|
280
984
|
throw deps.createRuleError('Failed to refresh AI profile', 500, 'AI_PROFILE_REFRESH_FAILED');
|
|
281
985
|
}
|
|
282
986
|
return {
|
|
283
987
|
created: false,
|
|
284
|
-
profile: deps
|
|
285
|
-
...(exactMatches.length > 1 ? {
|
|
286
|
-
duplicateWarning: {
|
|
287
|
-
existingProfileId: canonicalId,
|
|
288
|
-
message: `Multiple profiles matched "${name}". Reused ${canonicalId}; merge the remaining duplicates.`
|
|
289
|
-
}
|
|
290
|
-
} : {})
|
|
988
|
+
profile: hydrateAiProfileRecord(deps, reactivated)
|
|
291
989
|
};
|
|
292
990
|
}
|
|
293
991
|
const id = deps.generateEntityId('ai-profile');
|
|
@@ -299,16 +997,29 @@ export function resolveAiProfileService(deps, input) {
|
|
|
299
997
|
const role = normalizeOptionalProfileField(input.role);
|
|
300
998
|
const provider = normalizeOptionalProfileField(input.provider);
|
|
301
999
|
const model = normalizeOptionalProfileField(input.model);
|
|
302
|
-
const
|
|
1000
|
+
const resolvedMetadata = resolveAiProfileMetadataFields(deps, {
|
|
1001
|
+
name,
|
|
1002
|
+
description,
|
|
1003
|
+
role,
|
|
1004
|
+
provider,
|
|
1005
|
+
model,
|
|
1006
|
+
surfaceType: input.surfaceType,
|
|
1007
|
+
}, null);
|
|
1008
|
+
const resolvedSeatScope = resolveTrustedAiProfileSeatScope(deps, trustedSeatScope, null);
|
|
1009
|
+
if (resolvedSeatScope.seatScope === 'cloud_metered') {
|
|
1010
|
+
assertAiProfileSeatAvailable(deps, normalizedWorkspaceId);
|
|
1011
|
+
}
|
|
303
1012
|
const providerMetadata = input.providerMetadata && typeof input.providerMetadata === 'object'
|
|
304
1013
|
? input.providerMetadata
|
|
305
1014
|
: null;
|
|
306
1015
|
deps.db.prepare(`
|
|
307
1016
|
INSERT INTO ai_profiles (
|
|
308
1017
|
id, tenant_id, workspace_id, profile_token, name, username, icon, color,
|
|
309
|
-
description, role, provider, model, surface_type,
|
|
310
|
-
|
|
311
|
-
|
|
1018
|
+
avatar_url, avatar_source_url, description, role, provider, model, surface_type, seat_scope, provider_metadata_json,
|
|
1019
|
+
archived_at, archived_by, archived_reason, merged_into_profile_id,
|
|
1020
|
+
roster_state_updated_at, created_at, updated_at, last_active_at
|
|
1021
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1022
|
+
`).run(id, deps.tenantId, normalizedWorkspaceId, issuedProfileToken, name, username, icon, color, null, null, resolvedMetadata.description, resolvedMetadata.role, resolvedMetadata.provider, resolvedMetadata.model, resolvedMetadata.surfaceType, resolvedSeatScope.seatScope, providerMetadata ? JSON.stringify(providerMetadata) : null, null, null, null, null, now, now, now, now);
|
|
312
1023
|
const created = deps.db.prepare(`
|
|
313
1024
|
SELECT *
|
|
314
1025
|
FROM ai_profiles
|
|
@@ -320,6 +1031,6 @@ export function resolveAiProfileService(deps, input) {
|
|
|
320
1031
|
}
|
|
321
1032
|
return {
|
|
322
1033
|
created: true,
|
|
323
|
-
profile: deps
|
|
1034
|
+
profile: hydrateAiProfileRecord(deps, created),
|
|
324
1035
|
};
|
|
325
1036
|
}
|