@memberjunction/server 6.2.0-edge.2 → 6.2.0-edge.4
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 +30 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/agentSessions/BridgeHostInstance.d.ts +25 -0
- package/dist/agentSessions/BridgeHostInstance.d.ts.map +1 -0
- package/dist/agentSessions/BridgeHostInstance.js +30 -0
- package/dist/agentSessions/BridgeHostInstance.js.map +1 -0
- package/dist/agentSessions/HostInstance.d.ts +75 -11
- package/dist/agentSessions/HostInstance.d.ts.map +1 -1
- package/dist/agentSessions/HostInstance.js +99 -28
- package/dist/agentSessions/HostInstance.js.map +1 -1
- package/dist/agentSessions/ReturningVisitorRecap.d.ts.map +1 -1
- package/dist/agentSessions/ReturningVisitorRecap.js +7 -3
- package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -1
- package/dist/agentSessions/SessionJanitor.d.ts +173 -25
- package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
- package/dist/agentSessions/SessionJanitor.js +452 -54
- package/dist/agentSessions/SessionJanitor.js.map +1 -1
- package/dist/agentSessions/SessionManager.d.ts +43 -2
- package/dist/agentSessions/SessionManager.d.ts.map +1 -1
- package/dist/agentSessions/SessionManager.js +99 -8
- package/dist/agentSessions/SessionManager.js.map +1 -1
- package/dist/agentSessions/SessionRunIDVerifier.d.ts +63 -0
- package/dist/agentSessions/SessionRunIDVerifier.d.ts.map +1 -0
- package/dist/agentSessions/SessionRunIDVerifier.js +129 -0
- package/dist/agentSessions/SessionRunIDVerifier.js.map +1 -0
- package/dist/agentSessions/index.d.ts +2 -0
- package/dist/agentSessions/index.d.ts.map +1 -1
- package/dist/agentSessions/index.js +10 -3
- package/dist/agentSessions/index.js.map +1 -1
- package/dist/auth/actionAuthorization.d.ts +40 -0
- package/dist/auth/actionAuthorization.d.ts.map +1 -0
- package/dist/auth/actionAuthorization.js +94 -0
- package/dist/auth/actionAuthorization.js.map +1 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +3 -10
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.js +6 -3
- package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
- package/dist/auth/newUsers.d.ts +10 -0
- package/dist/auth/newUsers.d.ts.map +1 -1
- package/dist/auth/newUsers.js +80 -11
- package/dist/auth/newUsers.js.map +1 -1
- package/dist/config.d.ts +4567 -71
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +246 -3
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +8 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +25 -34
- package/dist/context.js.map +1 -1
- package/dist/generated/graphql-schemas/__mj.d.ts +1073 -10
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +9188 -3649
- package/dist/generated/graphql-schemas/__mj.js.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.js +3 -3
- package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +62 -6
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +179 -35
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts +4 -0
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +37 -1
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +214 -159
- package/dist/index.js.map +1 -1
- package/dist/isolatedProvider.d.ts +20 -0
- package/dist/isolatedProvider.d.ts.map +1 -0
- package/dist/isolatedProvider.js +22 -0
- package/dist/isolatedProvider.js.map +1 -0
- package/dist/postgresqlCompat.d.ts +8 -0
- package/dist/postgresqlCompat.d.ts.map +1 -0
- package/dist/postgresqlCompat.js +19 -0
- package/dist/postgresqlCompat.js.map +1 -0
- package/dist/postgresqlPoolSettings.d.ts +108 -0
- package/dist/postgresqlPoolSettings.d.ts.map +1 -0
- package/dist/postgresqlPoolSettings.js +134 -0
- package/dist/postgresqlPoolSettings.js.map +1 -0
- package/dist/realtimeProxy/RealtimeProxyServer.d.ts +44 -4
- package/dist/realtimeProxy/RealtimeProxyServer.d.ts.map +1 -1
- package/dist/realtimeProxy/RealtimeProxyServer.js +121 -16
- package/dist/realtimeProxy/RealtimeProxyServer.js.map +1 -1
- package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts +171 -0
- package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts.map +1 -0
- package/dist/realtimeProxy/RealtimeRelayTunnel.js +532 -0
- package/dist/realtimeProxy/RealtimeRelayTunnel.js.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.d.ts +148 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.d.ts.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.js +191 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.js.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts +66 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.js +138 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.js.map +1 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts +38 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts.map +1 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.js +98 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.js.map +1 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts +61 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts.map +1 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.js +149 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.js.map +1 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts +57 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts.map +1 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.js +69 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.js.map +1 -0
- package/dist/realtimeSessions/index.d.ts +17 -0
- package/dist/realtimeSessions/index.d.ts.map +1 -0
- package/dist/realtimeSessions/index.js +17 -0
- package/dist/realtimeSessions/index.js.map +1 -0
- package/dist/realtimeSessions/sessionAccess.d.ts +86 -0
- package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -0
- package/dist/realtimeSessions/sessionAccess.js +90 -0
- package/dist/realtimeSessions/sessionAccess.js.map +1 -0
- package/dist/realtimeSessions/sessionConfigStamps.d.ts +70 -0
- package/dist/realtimeSessions/sessionConfigStamps.d.ts.map +1 -0
- package/dist/realtimeSessions/sessionConfigStamps.js +93 -0
- package/dist/realtimeSessions/sessionConfigStamps.js.map +1 -0
- package/dist/realtimeSessions/subscriptionAuthorization.d.ts +65 -0
- package/dist/realtimeSessions/subscriptionAuthorization.d.ts.map +1 -0
- package/dist/realtimeSessions/subscriptionAuthorization.js +71 -0
- package/dist/realtimeSessions/subscriptionAuthorization.js.map +1 -0
- package/dist/realtimeSessions/verificationCore.d.ts +243 -0
- package/dist/realtimeSessions/verificationCore.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationCore.js +356 -0
- package/dist/realtimeSessions/verificationCore.js.map +1 -0
- package/dist/realtimeSessions/verificationPages.d.ts +66 -0
- package/dist/realtimeSessions/verificationPages.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationPages.js +110 -0
- package/dist/realtimeSessions/verificationPages.js.map +1 -0
- package/dist/realtimeSessions/verificationPolicy.d.ts +147 -0
- package/dist/realtimeSessions/verificationPolicy.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationPolicy.js +249 -0
- package/dist/realtimeSessions/verificationPolicy.js.map +1 -0
- package/dist/realtimeSessions/verificationWorkflow.d.ts +263 -0
- package/dist/realtimeSessions/verificationWorkflow.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationWorkflow.js +435 -0
- package/dist/realtimeSessions/verificationWorkflow.js.map +1 -0
- package/dist/realtimeWidget/widgetGuestElevation.d.ts +20 -2
- package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.js +31 -2
- package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
- package/dist/resolvers/ActionResolver.d.ts +31 -5
- package/dist/resolvers/ActionResolver.d.ts.map +1 -1
- package/dist/resolvers/ActionResolver.js +75 -14
- package/dist/resolvers/ActionResolver.js.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.d.ts +35 -17
- package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.js +92 -166
- package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
- package/dist/resolvers/CacheStatsResolver.d.ts +27 -0
- package/dist/resolvers/CacheStatsResolver.d.ts.map +1 -1
- package/dist/resolvers/CacheStatsResolver.js +113 -2
- package/dist/resolvers/CacheStatsResolver.js.map +1 -1
- package/dist/resolvers/ClientToolRequestResolver.d.ts +4 -2
- package/dist/resolvers/ClientToolRequestResolver.d.ts.map +1 -1
- package/dist/resolvers/ClientToolRequestResolver.js +28 -10
- package/dist/resolvers/ClientToolRequestResolver.js.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.d.ts +7 -23
- package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.js +9 -47
- package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts +12 -1
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.js +83 -2
- package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
- package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataResolver.js +9 -0
- package/dist/resolvers/GetDataResolver.js.map +1 -1
- package/dist/resolvers/HumanHandoffResolver.d.ts +107 -0
- package/dist/resolvers/HumanHandoffResolver.d.ts.map +1 -0
- package/dist/resolvers/HumanHandoffResolver.js +330 -0
- package/dist/resolvers/HumanHandoffResolver.js.map +1 -0
- package/dist/resolvers/MCPResolver.d.ts.map +1 -1
- package/dist/resolvers/MCPResolver.js +9 -4
- package/dist/resolvers/MCPResolver.js.map +1 -1
- package/dist/resolvers/MeetingResolver.d.ts +69 -0
- package/dist/resolvers/MeetingResolver.d.ts.map +1 -0
- package/dist/resolvers/MeetingResolver.js +381 -0
- package/dist/resolvers/MeetingResolver.js.map +1 -0
- package/dist/resolvers/RealtimeBridgeResolver.d.ts +88 -3
- package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.js +352 -12
- package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +156 -108
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +395 -293
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RealtimeSessionEventsResolver.d.ts +61 -0
- package/dist/resolvers/RealtimeSessionEventsResolver.d.ts.map +1 -0
- package/dist/resolvers/RealtimeSessionEventsResolver.js +130 -0
- package/dist/resolvers/RealtimeSessionEventsResolver.js.map +1 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts +85 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts.map +1 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.js +188 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.js.map +1 -0
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts +2 -0
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js +5 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts +10 -0
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +68 -11
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTemplateResolver.js +12 -11
- package/dist/resolvers/RunTemplateResolver.js.map +1 -1
- package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncDataResolver.js +6 -2
- package/dist/resolvers/SyncDataResolver.js.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.d.ts +2 -0
- package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.js +23 -6
- package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
- package/dist/resolvers/UserAvatarResolver.d.ts +16 -0
- package/dist/resolvers/UserAvatarResolver.d.ts.map +1 -0
- package/dist/resolvers/UserAvatarResolver.js +124 -0
- package/dist/resolvers/UserAvatarResolver.js.map +1 -0
- package/dist/resolvers/UserResolver.d.ts.map +1 -1
- package/dist/resolvers/UserResolver.js +2 -17
- package/dist/resolvers/UserResolver.js.map +1 -1
- package/dist/resolvers/adhocTimeout.d.ts +7 -0
- package/dist/resolvers/adhocTimeout.d.ts.map +1 -0
- package/dist/resolvers/adhocTimeout.js +12 -0
- package/dist/resolvers/adhocTimeout.js.map +1 -0
- package/dist/resolvers/agentVisionConsentAudit.d.ts +37 -0
- package/dist/resolvers/agentVisionConsentAudit.d.ts.map +1 -0
- package/dist/resolvers/agentVisionConsentAudit.js +63 -0
- package/dist/resolvers/agentVisionConsentAudit.js.map +1 -0
- package/dist/resolvers/avatarInputValidation.d.ts +40 -0
- package/dist/resolvers/avatarInputValidation.d.ts.map +1 -0
- package/dist/resolvers/avatarInputValidation.js +97 -0
- package/dist/resolvers/avatarInputValidation.js.map +1 -0
- package/dist/resolvers/currentUserRoles.d.ts +29 -0
- package/dist/resolvers/currentUserRoles.d.ts.map +1 -1
- package/dist/resolvers/currentUserRoles.js +30 -0
- package/dist/resolvers/currentUserRoles.js.map +1 -1
- package/dist/resolvers/meetings/index.d.ts +5 -0
- package/dist/resolvers/meetings/index.d.ts.map +1 -0
- package/dist/resolvers/meetings/index.js +5 -0
- package/dist/resolvers/meetings/index.js.map +1 -0
- package/dist/resolvers/meetings/meetingHelpers.d.ts +17 -0
- package/dist/resolvers/meetings/meetingHelpers.d.ts.map +1 -0
- package/dist/resolvers/meetings/meetingHelpers.js +212 -0
- package/dist/resolvers/meetings/meetingHelpers.js.map +1 -0
- package/dist/resolvers/meetings/meetingMutations.d.ts +18 -0
- package/dist/resolvers/meetings/meetingMutations.d.ts.map +1 -0
- package/dist/resolvers/meetings/meetingMutations.js +439 -0
- package/dist/resolvers/meetings/meetingMutations.js.map +1 -0
- package/dist/resolvers/meetings/meetingQueries.d.ts +7 -0
- package/dist/resolvers/meetings/meetingQueries.d.ts.map +1 -0
- package/dist/resolvers/meetings/meetingQueries.js +113 -0
- package/dist/resolvers/meetings/meetingQueries.js.map +1 -0
- package/dist/resolvers/meetings/meetingTypes.d.ts +89 -0
- package/dist/resolvers/meetings/meetingTypes.d.ts.map +1 -0
- package/dist/resolvers/meetings/meetingTypes.js +345 -0
- package/dist/resolvers/meetings/meetingTypes.js.map +1 -0
- package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
- package/dist/rest/ViewOperationsHandler.js +8 -5
- package/dist/rest/ViewOperationsHandler.js.map +1 -1
- package/dist/services/TaskGraphActionRunner.d.ts +8 -0
- package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphActionRunner.js +27 -1
- package/dist/services/TaskGraphActionRunner.js.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.d.ts +4 -3
- package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.js +13 -7
- package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
- package/dist/services/WorkQueueHostService.d.ts +36 -0
- package/dist/services/WorkQueueHostService.d.ts.map +1 -0
- package/dist/services/WorkQueueHostService.js +97 -0
- package/dist/services/WorkQueueHostService.js.map +1 -0
- package/dist/services/workQueueConfig.d.ts +103 -0
- package/dist/services/workQueueConfig.d.ts.map +1 -0
- package/dist/services/workQueueConfig.js +48 -0
- package/dist/services/workQueueConfig.js.map +1 -0
- package/dist/sharedCache.d.ts +105 -0
- package/dist/sharedCache.d.ts.map +1 -0
- package/dist/sharedCache.js +276 -0
- package/dist/sharedCache.js.map +1 -0
- package/dist/types.d.ts +11 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +97 -94
- package/src/__tests__/ActionResolver.authorization.test.ts +353 -0
- package/src/__tests__/AdhocQueryResolver.bugs.test.ts +14 -35
- package/src/__tests__/AdhocQueryResolver.pagination.test.ts +6 -2
- package/src/__tests__/AdhocQueryResolver.scopeGuard.test.ts +3 -3
- package/src/__tests__/AdhocQueryResolver.sqlAuthorizer.test.ts +104 -0
- package/src/__tests__/BridgeHostInstance.test.ts +232 -0
- package/src/__tests__/ClientToolRequestResolver.test.ts +116 -0
- package/src/__tests__/EntityRecordNameResolver.fieldSecurity.test.ts +27 -72
- package/src/__tests__/FetchEntityVectorsResolver.test.ts +285 -0
- package/src/__tests__/HostInstance.test.ts +84 -0
- package/src/__tests__/HumanHandoffResolver.test.ts +250 -0
- package/src/__tests__/MeetingResolver.test.ts +448 -0
- package/src/__tests__/RealtimeBridgeResolver.test.ts +651 -4
- package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +383 -0
- package/src/__tests__/RealtimeClientSessionResolver.test.ts +682 -58
- package/src/__tests__/RealtimeProxyServer.test.ts +16 -1
- package/src/__tests__/RealtimeRelay.test.ts +876 -0
- package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +129 -0
- package/src/__tests__/ResolverBase.adhocReadScope.test.ts +96 -0
- package/src/__tests__/ResolverBase.aggregateScreen.test.ts +174 -0
- package/src/__tests__/ResolverBase.clientOldValues.test.ts +6 -3
- package/src/__tests__/ResolverBase.filterEscaping.test.ts +21 -2
- package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +291 -0
- package/src/__tests__/ReturningVisitorRecap.test.ts +115 -0
- package/src/__tests__/RunAIAgentResolver.branch.test.ts +169 -0
- package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +4 -4
- package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +166 -0
- package/src/__tests__/RunTemplateResolver.test.ts +132 -0
- package/src/__tests__/RunViewResolver.cacheCheckForwarding.test.ts +63 -0
- package/src/__tests__/SessionJanitor.test.ts +845 -18
- package/src/__tests__/SessionManager.test.ts +213 -17
- package/src/__tests__/SessionRunIDVerifier.test.ts +251 -0
- package/src/__tests__/SyncRolesUsersResolver.userRoles.test.ts +97 -0
- package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +41 -0
- package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +50 -3
- package/src/__tests__/UserAvatarResolver.test.ts +255 -0
- package/src/__tests__/UserResolver.currentUser.test.ts +16 -1
- package/src/__tests__/WorkQueueHostService.test.ts +133 -0
- package/src/__tests__/actionAuthorization.test.ts +130 -0
- package/src/__tests__/adhocTimeoutClamp.test.ts +26 -0
- package/src/__tests__/agentVisionConsentAudit.test.ts +118 -0
- package/src/__tests__/avatarInputValidation.test.ts +160 -0
- package/src/__tests__/isolatedProviderTransactions.test.ts +169 -0
- package/src/__tests__/magicLinkService.consume.test.ts +6 -0
- package/src/__tests__/magicLinkService.provisionLog.test.ts +5 -0
- package/src/__tests__/newUsers.template.test.ts +151 -0
- package/src/__tests__/newUsers.test.ts +74 -5
- package/src/__tests__/orphanedConversationDetailReconciler.test.ts +17 -2
- package/src/__tests__/postgresqlPoolSettings.test.ts +131 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionEventService.test.ts +201 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionEventsResolver.test.ts +215 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionVerificationResolver.test.ts +102 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionVerificationService.test.ts +349 -0
- package/src/__tests__/realtimeSessions/RealtimeVerifyRouter.test.ts +131 -0
- package/src/__tests__/realtimeSessions/SlidingWindowRateLimiter.test.ts +56 -0
- package/src/__tests__/realtimeSessions/helpers.ts +4 -0
- package/src/__tests__/realtimeSessions/sessionAccess.test.ts +129 -0
- package/src/__tests__/realtimeSessions/sessionConfigStamps.test.ts +124 -0
- package/src/__tests__/realtimeSessions/verificationCore.test.ts +355 -0
- package/src/__tests__/realtimeSessions/verificationPages.test.ts +84 -0
- package/src/__tests__/realtimeSessions/verificationPolicy.test.ts +226 -0
- package/src/__tests__/realtimeSessions/verificationWorkflow.test.ts +633 -0
- package/src/__tests__/resolverBase.fls.test.ts +87 -4
- package/src/__tests__/serverExtensionReservedRoots.test.ts +2 -0
- package/src/__tests__/sharedCache.test.ts +310 -0
- package/src/__tests__/translateBracketsToPG.test.ts +20 -0
- package/src/__tests__/widgetGuestElevation.test.ts +44 -0
- package/src/agentSessions/BridgeHostInstance.ts +32 -0
- package/src/agentSessions/HostInstance.ts +124 -28
- package/src/agentSessions/ReturningVisitorRecap.ts +7 -3
- package/src/agentSessions/SessionJanitor.ts +543 -57
- package/src/agentSessions/SessionManager.ts +125 -25
- package/src/agentSessions/SessionRunIDVerifier.ts +171 -0
- package/src/agentSessions/index.ts +10 -3
- package/src/auth/actionAuthorization.ts +116 -0
- package/src/auth/index.ts +3 -8
- package/src/auth/magicLink/MagicLinkService.ts +6 -3
- package/src/auth/newUsers.ts +99 -11
- package/src/config.ts +255 -3
- package/src/context.ts +26 -34
- package/src/generated/graphql-schemas/__mj.ts +22701 -18838
- package/src/generic/OrphanedConversationDetailReconciler.ts +3 -3
- package/src/generic/ResolverBase.ts +210 -36
- package/src/generic/RunViewResolver.ts +37 -2
- package/src/index.ts +236 -170
- package/src/isolatedProvider.ts +22 -0
- package/src/postgresqlCompat.ts +21 -0
- package/src/postgresqlPoolSettings.ts +213 -0
- package/src/realtimeProxy/RealtimeProxyServer.ts +148 -16
- package/src/realtimeProxy/RealtimeRelayTunnel.ts +645 -0
- package/src/realtimeSessions/README.md +179 -0
- package/src/realtimeSessions/RealtimeSessionEventService.ts +274 -0
- package/src/realtimeSessions/RealtimeSessionVerificationService.ts +167 -0
- package/src/realtimeSessions/RealtimeVerifyRouter.ts +108 -0
- package/src/realtimeSessions/SessionVerificationPortsBase.ts +189 -0
- package/src/realtimeSessions/SlidingWindowRateLimiter.ts +92 -0
- package/src/realtimeSessions/index.ts +16 -0
- package/src/realtimeSessions/sessionAccess.ts +141 -0
- package/src/realtimeSessions/sessionConfigStamps.ts +123 -0
- package/src/realtimeSessions/subscriptionAuthorization.ts +116 -0
- package/src/realtimeSessions/verificationCore.ts +502 -0
- package/src/realtimeSessions/verificationPages.ts +149 -0
- package/src/realtimeSessions/verificationPolicy.ts +321 -0
- package/src/realtimeSessions/verificationWorkflow.ts +702 -0
- package/src/realtimeWidget/widgetGuestElevation.ts +34 -2
- package/src/resolvers/ActionResolver.ts +84 -21
- package/src/resolvers/AdhocQueryResolver.ts +105 -187
- package/src/resolvers/CacheStatsResolver.ts +89 -2
- package/src/resolvers/ClientToolRequestResolver.ts +25 -7
- package/src/resolvers/EntityRecordNameResolver.ts +9 -51
- package/src/resolvers/FetchEntityVectorsResolver.ts +97 -2
- package/src/resolvers/GetDataResolver.ts +10 -0
- package/src/resolvers/HumanHandoffResolver.ts +312 -0
- package/src/resolvers/MCPResolver.ts +8 -5
- package/src/resolvers/MeetingResolver.ts +438 -0
- package/src/resolvers/RealtimeBridgeResolver.ts +385 -19
- package/src/resolvers/RealtimeClientSessionResolver.ts +503 -344
- package/src/resolvers/RealtimeSessionEventsResolver.ts +133 -0
- package/src/resolvers/RealtimeSessionVerificationResolver.ts +174 -0
- package/src/resolvers/RemoteBrowserActionResolver.ts +11 -1
- package/src/resolvers/RunAIAgentResolver.ts +78 -11
- package/src/resolvers/RunTemplateResolver.ts +13 -12
- package/src/resolvers/SyncDataResolver.ts +6 -2
- package/src/resolvers/SyncRolesUsersResolver.ts +24 -6
- package/src/resolvers/UserAvatarResolver.ts +111 -0
- package/src/resolvers/UserResolver.ts +2 -16
- package/src/resolvers/adhocTimeout.ts +12 -0
- package/src/resolvers/agentVisionConsentAudit.ts +82 -0
- package/src/resolvers/avatarInputValidation.ts +123 -0
- package/src/resolvers/currentUserRoles.ts +32 -1
- package/src/resolvers/meetings/index.ts +4 -0
- package/src/resolvers/meetings/meetingHelpers.ts +307 -0
- package/src/resolvers/meetings/meetingMutations.ts +556 -0
- package/src/resolvers/meetings/meetingQueries.ts +172 -0
- package/src/resolvers/meetings/meetingTypes.ts +238 -0
- package/src/rest/ViewOperationsHandler.ts +8 -5
- package/src/services/TaskGraphActionRunner.ts +29 -1
- package/src/services/TaskGraphContinuationDeliverer.ts +13 -7
- package/src/services/WorkQueueHostService.ts +129 -0
- package/src/services/workQueueConfig.ts +52 -0
- package/src/sharedCache.ts +302 -0
- package/src/types.ts +12 -0
- package/dist/resolvers/adhoc-query-helpers.d.ts +0 -33
- package/dist/resolvers/adhoc-query-helpers.d.ts.map +0 -1
- package/dist/resolvers/adhoc-query-helpers.js +0 -50
- package/dist/resolvers/adhoc-query-helpers.js.map +0 -1
- package/src/__tests__/adhoc-query-helpers.test.ts +0 -50
- package/src/resolvers/adhoc-query-helpers.ts +0 -67
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
// is loaded first. Vitest does not bring it in automatically — this MUST precede the resolver import.
|
|
3
3
|
import 'reflect-metadata';
|
|
4
4
|
|
|
5
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
6
|
-
import type { UserInfo } from '@memberjunction/core';
|
|
5
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
6
|
+
import type { IMetadataProvider, UserInfo } from '@memberjunction/core';
|
|
7
|
+
import type { MJAIAgentSessionEntity } from '@memberjunction/core-entities';
|
|
8
|
+
import type { BridgeDisconnectReason } from '@memberjunction/ai-bridge-base';
|
|
9
|
+
import type { AgentRoomSession, StartAgentRoomSessionParams } from '@memberjunction/livekit-room-server';
|
|
7
10
|
|
|
8
11
|
// Mock the server-side LiveKit services so the thin resolver is tested in isolation (no LiveKit creds).
|
|
9
12
|
const h = vi.hoisted(() => ({
|
|
@@ -13,8 +16,24 @@ const h = vi.hoisted(() => ({
|
|
|
13
16
|
Identity: identity,
|
|
14
17
|
RoomName: room,
|
|
15
18
|
})),
|
|
19
|
+
startAgentRoomSession: vi.fn(
|
|
20
|
+
async (_params: StartAgentRoomSessionParams): Promise<AgentRoomSession> => ({ SessionBridgeID: 'bridge-std', RoomName: 'room-std', ServerUrl: 'wss://x.livekit.cloud' }),
|
|
21
|
+
),
|
|
22
|
+
handoffDeps: { AgentStarter: undefined as undefined | ((req: Record<string, unknown>) => Promise<{ SessionBridgeID: string }>) },
|
|
16
23
|
startRecording: vi.fn(async () => ({ EgressID: 'eg-1', RoomName: 'room-1', Status: 'EGRESS_ACTIVE' })),
|
|
24
|
+
getRoomTurnState: vi.fn((room: string): unknown => (room === 'busy-room' ? { RoomId: room, Agents: [] } : null)),
|
|
25
|
+
setTurnToolBinder: vi.fn(),
|
|
26
|
+
setAgentVisionResolver: vi.fn(),
|
|
27
|
+
setFailedStartRecorder: vi.fn(),
|
|
28
|
+
getBridgeRuntime: vi.fn(),
|
|
17
29
|
stopRecording: vi.fn(async () => ({ EgressID: 'eg-1', RoomName: 'room-1', Status: 'EGRESS_COMPLETE' })),
|
|
30
|
+
setAgentVision: vi.fn(async (): Promise<{ Success: boolean; NotInRoom?: boolean; ErrorMessage?: string }> => ({ Success: true })),
|
|
31
|
+
writeConsentAudit: vi.fn(async () => undefined),
|
|
32
|
+
getRoomForEgress: vi.fn(async (_id: string): Promise<string | undefined> => 'room-1'),
|
|
33
|
+
stopAgentRoomSession: vi.fn(async (_sessionBridgeID: string, _reason?: BridgeDisconnectReason): Promise<boolean> => true),
|
|
34
|
+
stopAllAgentsInRoom: vi.fn(async (_roomName: string, _reason?: BridgeDisconnectReason): Promise<number> => 1),
|
|
35
|
+
getRoomForBridge: vi.fn((_id: string): string | undefined => 'room-1'),
|
|
36
|
+
sessionClosedHook: vi.fn(async () => undefined),
|
|
18
37
|
}));
|
|
19
38
|
|
|
20
39
|
// These two are instantiated with `new` by the resolver, so they must be constructible. They were
|
|
@@ -25,13 +44,53 @@ vi.mock('@memberjunction/livekit-room-server', () => ({
|
|
|
25
44
|
MintClientToken = h.mintClientToken;
|
|
26
45
|
},
|
|
27
46
|
// SetSessionFactory is exercised by the resolver's module-load binding of the realtime-session factory.
|
|
28
|
-
LiveKitAgentRoomCoordinator: {
|
|
47
|
+
LiveKitAgentRoomCoordinator: {
|
|
48
|
+
Instance: {
|
|
49
|
+
StartAgentRoomSession: h.startAgentRoomSession,
|
|
50
|
+
SetSessionFactory: vi.fn(),
|
|
51
|
+
SetTurnToolBinder: h.setTurnToolBinder,
|
|
52
|
+
SetAgentVisionResolver: h.setAgentVisionResolver,
|
|
53
|
+
SetFailedStartRecorder: h.setFailedStartRecorder,
|
|
54
|
+
GetRoomTurnState: h.getRoomTurnState,
|
|
55
|
+
StopAgentRoomSession: h.stopAgentRoomSession,
|
|
56
|
+
StopAllAgentsInRoom: h.stopAllAgentsInRoom,
|
|
57
|
+
GetRoomForBridge: h.getRoomForBridge,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
LiveKitUserIdentity: (id: string) => `user-${id}`.toLowerCase(),
|
|
61
|
+
RoomHandoffEngine: { Instance: { Deps: h.handoffDeps } },
|
|
29
62
|
LiveKitEgressService: class {
|
|
30
63
|
StartRoomRecording = h.startRecording;
|
|
31
64
|
StopRecording = h.stopRecording;
|
|
65
|
+
GetRoomForEgress = h.getRoomForEgress;
|
|
66
|
+
},
|
|
67
|
+
RoomAuthorizationService: (() => {
|
|
68
|
+
let testAuthorizer: ((roomName: string, user: unknown, provider?: unknown) => Promise<{ Authorized: boolean; Reason?: string }>) | undefined = undefined;
|
|
69
|
+
return {
|
|
70
|
+
Instance: {
|
|
71
|
+
AuthorizeRoomAccess: vi.fn(async (roomName: string, user: unknown, provider?: unknown) => {
|
|
72
|
+
if (testAuthorizer) {
|
|
73
|
+
return await testAuthorizer(roomName, user, provider);
|
|
74
|
+
}
|
|
75
|
+
return { Authorized: true };
|
|
76
|
+
}),
|
|
77
|
+
SetAuthorizerForTesting: vi.fn((fn?: (roomName: string, user: unknown, provider?: unknown) => Promise<{ Authorized: boolean; Reason?: string }>) => {
|
|
78
|
+
testAuthorizer = fn;
|
|
79
|
+
}),
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
})(),
|
|
83
|
+
LiveKitParticipantService: class {
|
|
84
|
+
SetAgentVision = h.setAgentVision;
|
|
32
85
|
},
|
|
33
86
|
}));
|
|
34
87
|
|
|
88
|
+
// Mock the consent audit writer so the thin resolver is tested in isolation. Its own behavior is covered by
|
|
89
|
+
// agentVisionConsentAudit.test.ts.
|
|
90
|
+
vi.mock('../resolvers/agentVisionConsentAudit', () => ({
|
|
91
|
+
WriteAgentVisionConsentAudit: h.writeConsentAudit,
|
|
92
|
+
}));
|
|
93
|
+
|
|
35
94
|
// Mock the agent factory so importing the resolver doesn't pull the heavy @memberjunction/ai-agents graph
|
|
36
95
|
// into this thin resolver test (the binding only needs the symbol to exist).
|
|
37
96
|
vi.mock('@memberjunction/ai-agents', () => ({
|
|
@@ -39,12 +98,15 @@ vi.mock('@memberjunction/ai-agents', () => ({
|
|
|
39
98
|
FinalizeBridgeCoAgentRuns: vi.fn(),
|
|
40
99
|
GetRealtimeModelVoices: vi.fn(),
|
|
41
100
|
CreateBridgeRoomTranscriptSink: vi.fn(),
|
|
101
|
+
GetBridgeRealtimeRuntime: h.getBridgeRuntime,
|
|
42
102
|
// SessionManager's constructor defaults to `new RealtimeClientSessionService()` when this resolver
|
|
43
103
|
// doesn't inject one (it never finalizes client-direct co-agent runs itself) — the mock must still
|
|
44
104
|
// export the class so that default construction doesn't throw.
|
|
45
105
|
RealtimeClientSessionService: class {
|
|
46
106
|
FinalizeCoAgentRun = vi.fn(async () => undefined);
|
|
47
107
|
},
|
|
108
|
+
// SessionManager.CloseSession tells the channel-plugin host a session closed.
|
|
109
|
+
RealtimeChannelServerHost: { Instance: { OnSessionClosed: h.sessionClosedHook } },
|
|
48
110
|
}));
|
|
49
111
|
|
|
50
112
|
// Mock the meeting-recording registration so the thin resolver is tested in isolation (no MJStorage /
|
|
@@ -56,8 +118,13 @@ vi.mock('../resolvers/meetingRecordingRegistration', () => ({
|
|
|
56
118
|
get correlateRecordingStart() { return this.CorrelateRecordingStart; },
|
|
57
119
|
}));
|
|
58
120
|
|
|
59
|
-
import {
|
|
121
|
+
import { buildSchema, Query, Resolver } from 'type-graphql';
|
|
122
|
+
import type { GraphQLObjectType } from 'graphql';
|
|
123
|
+
import { GetRealtimeModelVoices } from '@memberjunction/ai-agents';
|
|
124
|
+
import { RealtimeBridgeResolver, MintLiveKitClientTokenInput, LiveKitRecordingInput, SetLiveKitAgentVisionInput, StartLiveKitAgentRoomSessionInput, RealtimeModelVoicesResult } from '../resolvers/RealtimeBridgeResolver';
|
|
125
|
+
import { RoomAuthorizationService } from '@memberjunction/livekit-room-server';
|
|
60
126
|
import type { AppContext } from '../types.js';
|
|
127
|
+
import type { SessionManager } from '../agentSessions/SessionManager.js';
|
|
61
128
|
|
|
62
129
|
/** A resolver subclass that supplies a fake authenticated user (GetUserFromPayload is protected). */
|
|
63
130
|
class TestableResolver extends RealtimeBridgeResolver {
|
|
@@ -67,13 +134,29 @@ class TestableResolver extends RealtimeBridgeResolver {
|
|
|
67
134
|
}
|
|
68
135
|
}
|
|
69
136
|
|
|
137
|
+
/** The resolver's own session manager (a private field), so a test can stand in for or watch the writes it makes. */
|
|
138
|
+
const sessionManagerOf = (resolver: RealtimeBridgeResolver): SessionManager =>
|
|
139
|
+
(resolver as unknown as { sessionManager: SessionManager }).sessionManager;
|
|
140
|
+
|
|
70
141
|
const ctx = {} as AppContext;
|
|
71
142
|
|
|
143
|
+
// The resolver module installs the binder once, as it loads. `restoreMocks` clears call history before each test,
|
|
144
|
+
// so take it now rather than reading `mock.calls` inside a test.
|
|
145
|
+
type TurnToolBinderFn = (session: object, handler: object) => void;
|
|
146
|
+
const installedTurnToolBinder = h.setTurnToolBinder.mock.calls[0]?.[0] as TurnToolBinderFn | undefined;
|
|
147
|
+
type AgentVisionResolverFn = (session: object) => boolean;
|
|
148
|
+
const installedAgentVisionResolver = h.setAgentVisionResolver.mock.calls[0]?.[0] as AgentVisionResolverFn | undefined;
|
|
149
|
+
type FailedStartRecorderFn = (session: object, error: string) => Promise<void>;
|
|
150
|
+
const installedFailedStartRecorder = h.setFailedStartRecorder.mock.calls[0]?.[0] as FailedStartRecorderFn | undefined;
|
|
151
|
+
|
|
72
152
|
describe('RealtimeBridgeResolver', () => {
|
|
73
153
|
let resolver: TestableResolver;
|
|
74
154
|
|
|
75
155
|
beforeEach(() => {
|
|
76
156
|
resolver = new TestableResolver();
|
|
157
|
+
h.setAgentVision.mockClear();
|
|
158
|
+
h.writeConsentAudit.mockClear();
|
|
159
|
+
RoomAuthorizationService.Instance.SetAuthorizerForTesting(async () => ({ Authorized: true }));
|
|
77
160
|
});
|
|
78
161
|
|
|
79
162
|
describe('MintLiveKitClientToken', () => {
|
|
@@ -97,6 +180,88 @@ describe('RealtimeBridgeResolver', () => {
|
|
|
97
180
|
});
|
|
98
181
|
});
|
|
99
182
|
|
|
183
|
+
describe('GetLiveKitRoomTurnState', () => {
|
|
184
|
+
it('serves a room\'s snapshot as JSON for the typed client to parse', async () => {
|
|
185
|
+
const result = await resolver.GetLiveKitRoomTurnState('busy-room', ctx);
|
|
186
|
+
expect(result.Success).toBe(true);
|
|
187
|
+
expect(JSON.parse(result.StateJSON as string)).toEqual({ RoomId: 'busy-room', Agents: [] });
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('reports success with no state when the room holds no agents', async () => {
|
|
191
|
+
const result = await resolver.GetLiveKitRoomTurnState('empty-room', ctx);
|
|
192
|
+
expect(result).toEqual({ Success: true, StateJSON: undefined });
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('requires an authenticated user', async () => {
|
|
196
|
+
resolver.user = undefined;
|
|
197
|
+
const result = await resolver.GetLiveKitRoomTurnState('busy-room', ctx);
|
|
198
|
+
expect(result.Success).toBe(false);
|
|
199
|
+
expect(result.ErrorMessage).toMatch(/current user/i);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('turns a failure into a structured error instead of throwing', async () => {
|
|
203
|
+
h.getRoomTurnState.mockImplementationOnce(() => {
|
|
204
|
+
throw new Error('boom');
|
|
205
|
+
});
|
|
206
|
+
const result = await resolver.GetLiveKitRoomTurnState('busy-room', ctx);
|
|
207
|
+
expect(result).toMatchObject({ Success: false, ErrorMessage: 'boom' });
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
describe('turn-taking tool binding', () => {
|
|
212
|
+
it('installs the engine\'s tool handler as the model session runtime\'s local tool handler', () => {
|
|
213
|
+
const binder = installedTurnToolBinder!;
|
|
214
|
+
const setLocalToolHandler = vi.fn();
|
|
215
|
+
h.getBridgeRuntime.mockReturnValueOnce({ SetLocalToolHandler: setLocalToolHandler });
|
|
216
|
+
const session = {};
|
|
217
|
+
const handler = {};
|
|
218
|
+
binder(session, handler);
|
|
219
|
+
expect(h.getBridgeRuntime).toHaveBeenCalledWith(session);
|
|
220
|
+
expect(setLocalToolHandler).toHaveBeenCalledWith(handler);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('does not throw when the session has no bridge runtime', () => {
|
|
224
|
+
const binder = installedTurnToolBinder!;
|
|
225
|
+
h.getBridgeRuntime.mockReturnValueOnce(undefined);
|
|
226
|
+
expect(() => binder({}, {})).not.toThrow();
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
describe('agent vision binding', () => {
|
|
231
|
+
it("says an agent watches meetings exactly when its session runtime says so", () => {
|
|
232
|
+
const resolve = installedAgentVisionResolver!;
|
|
233
|
+
const session = {};
|
|
234
|
+
h.getBridgeRuntime.mockReturnValueOnce({ WatchesMeetingVideo: true });
|
|
235
|
+
expect(resolve(session)).toBe(true);
|
|
236
|
+
expect(h.getBridgeRuntime).toHaveBeenCalledWith(session);
|
|
237
|
+
h.getBridgeRuntime.mockReturnValueOnce({ WatchesMeetingVideo: false });
|
|
238
|
+
expect(resolve(session)).toBe(false);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('says no for a session with no bridge runtime', () => {
|
|
242
|
+
h.getBridgeRuntime.mockReturnValueOnce(undefined);
|
|
243
|
+
expect(installedAgentVisionResolver!({})).toBe(false);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
describe('failed start binding', () => {
|
|
248
|
+
it("finalizes the model session's co-agent run as failed, with the start's error", async () => {
|
|
249
|
+
const finalize = vi.fn(async () => undefined);
|
|
250
|
+
h.getBridgeRuntime.mockReturnValueOnce({ Finalize: finalize });
|
|
251
|
+
const session = {};
|
|
252
|
+
|
|
253
|
+
await installedFailedStartRecorder!(session, 'Failed to create AIAgentSessionBridge: denied');
|
|
254
|
+
|
|
255
|
+
expect(h.getBridgeRuntime).toHaveBeenCalledWith(session);
|
|
256
|
+
expect(finalize).toHaveBeenCalledWith(false, 'Failed to create AIAgentSessionBridge: denied');
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('does nothing for a session with no bridge runtime', async () => {
|
|
260
|
+
h.getBridgeRuntime.mockReturnValueOnce(undefined);
|
|
261
|
+
await expect(installedFailedStartRecorder!({}, 'mint failed')).resolves.toBeUndefined();
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
100
265
|
describe('recording', () => {
|
|
101
266
|
it('starts a recording and maps the result', async () => {
|
|
102
267
|
const input = Object.assign(new LiveKitRecordingInput(), { RoomName: 'room-1', Layout: 'grid' });
|
|
@@ -114,6 +279,23 @@ describe('RealtimeBridgeResolver', () => {
|
|
|
114
279
|
expect(result.RecordingFileID).toBe('file-1');
|
|
115
280
|
});
|
|
116
281
|
|
|
282
|
+
it('refuses to stop a recording when user is unauthorized for the room', async () => {
|
|
283
|
+
RoomAuthorizationService.Instance.SetAuthorizerForTesting(async () => ({
|
|
284
|
+
Authorized: false,
|
|
285
|
+
Reason: 'Not allowed to access this room',
|
|
286
|
+
}));
|
|
287
|
+
const result = await resolver.StopLiveKitRecording('eg-1', ctx);
|
|
288
|
+
expect(result.Success).toBe(false);
|
|
289
|
+
expect(result.ErrorMessage).toMatch(/Not allowed to access this room/);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('refuses to stop a recording when room cannot be determined for egress ID', async () => {
|
|
293
|
+
h.getRoomForEgress.mockResolvedValueOnce(undefined);
|
|
294
|
+
const result = await resolver.StopLiveKitRecording('unknown-egress', ctx);
|
|
295
|
+
expect(result.Success).toBe(false);
|
|
296
|
+
expect(result.ErrorMessage).toMatch(/Unable to determine room/);
|
|
297
|
+
});
|
|
298
|
+
|
|
117
299
|
it('requires an authenticated user to record', async () => {
|
|
118
300
|
resolver.user = undefined;
|
|
119
301
|
const input = Object.assign(new LiveKitRecordingInput(), { RoomName: 'room-1' });
|
|
@@ -122,4 +304,469 @@ describe('RealtimeBridgeResolver', () => {
|
|
|
122
304
|
expect(result.ErrorMessage).toMatch(/current user/i);
|
|
123
305
|
});
|
|
124
306
|
});
|
|
307
|
+
|
|
308
|
+
describe('SetLiveKitAgentVision', () => {
|
|
309
|
+
const choose = (allow: boolean) => Object.assign(new SetLiveKitAgentVisionInput(), { RoomName: 'room-1', Allow: allow });
|
|
310
|
+
|
|
311
|
+
it("records the signed-in user's own choice, as their participant identity, and audits it", async () => {
|
|
312
|
+
const result = await resolver.SetLiveKitAgentVision(choose(true), ctx);
|
|
313
|
+
expect(result).toEqual({ Success: true });
|
|
314
|
+
expect(h.setAgentVision).toHaveBeenCalledWith('room-1', 'user-u1', true);
|
|
315
|
+
expect(h.writeConsentAudit).toHaveBeenCalledWith(
|
|
316
|
+
{ User: resolver.user, RoomName: 'room-1', Allow: true, Applied: true, ErrorMessage: undefined },
|
|
317
|
+
null,
|
|
318
|
+
);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('passes a withdrawal through as it is', async () => {
|
|
322
|
+
await resolver.SetLiveKitAgentVision(choose(false), ctx);
|
|
323
|
+
expect(h.setAgentVision).toHaveBeenCalledWith('room-1', 'user-u1', false);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it('says when the user is not in the room, and audits the refusal', async () => {
|
|
327
|
+
h.setAgentVision.mockResolvedValueOnce({ Success: false, NotInRoom: true, ErrorMessage: 'user-u1 is not in room room-1.' });
|
|
328
|
+
const result = await resolver.SetLiveKitAgentVision(choose(true), ctx);
|
|
329
|
+
expect(result).toEqual({ Success: false, ErrorMessage: 'You are not in this room.' });
|
|
330
|
+
expect(h.writeConsentAudit).toHaveBeenCalledWith(expect.objectContaining({ Applied: false, ErrorMessage: 'user-u1 is not in room room-1.' }), null);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it("passes LiveKit's other refusals on", async () => {
|
|
334
|
+
h.setAgentVision.mockResolvedValueOnce({ Success: false, ErrorMessage: 'LiveKit is not configured on this server.' });
|
|
335
|
+
const result = await resolver.SetLiveKitAgentVision(choose(true), ctx);
|
|
336
|
+
expect(result).toEqual({ Success: false, ErrorMessage: 'LiveKit is not configured on this server.' });
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it('does nothing without an authenticated user', async () => {
|
|
340
|
+
resolver.user = undefined;
|
|
341
|
+
const result = await resolver.SetLiveKitAgentVision(choose(true), ctx);
|
|
342
|
+
expect(result.Success).toBe(false);
|
|
343
|
+
expect(result.ErrorMessage).toMatch(/current user/i);
|
|
344
|
+
expect(h.setAgentVision).not.toHaveBeenCalled();
|
|
345
|
+
expect(h.writeConsentAudit).not.toHaveBeenCalled();
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it('refuses, and audits the refusal, when the user may not enter the room', async () => {
|
|
349
|
+
RoomAuthorizationService.Instance.SetAuthorizerForTesting(async () => ({ Authorized: false, Reason: 'User is not authorized for this room.' }));
|
|
350
|
+
const result = await resolver.SetLiveKitAgentVision(choose(true), ctx);
|
|
351
|
+
expect(result).toEqual({ Success: false, ErrorMessage: 'User is not authorized for this room.' });
|
|
352
|
+
expect(h.setAgentVision).not.toHaveBeenCalled();
|
|
353
|
+
expect(h.writeConsentAudit).toHaveBeenCalledWith(
|
|
354
|
+
expect.objectContaining({ Allow: true, Applied: false, ErrorMessage: 'User is not authorized for this room.' }),
|
|
355
|
+
null,
|
|
356
|
+
);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it('applies a withdrawal without the room check, so a cancelled meeting cannot keep an agent watching', async () => {
|
|
360
|
+
const authorizer = vi.fn(async () => ({ Authorized: false, Reason: 'Meeting is cancelled.' }));
|
|
361
|
+
RoomAuthorizationService.Instance.SetAuthorizerForTesting(authorizer);
|
|
362
|
+
const result = await resolver.SetLiveKitAgentVision(choose(false), ctx);
|
|
363
|
+
expect(result).toEqual({ Success: true });
|
|
364
|
+
expect(authorizer).not.toHaveBeenCalled();
|
|
365
|
+
expect(h.setAgentVision).toHaveBeenCalledWith('room-1', 'user-u1', false);
|
|
366
|
+
expect(h.writeConsentAudit).toHaveBeenCalledWith(expect.objectContaining({ Allow: false, Applied: true }), null);
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
describe('StartLiveKitAgentRoomSession with handoff', () => {
|
|
371
|
+
const input = (over: Partial<StartLiveKitAgentRoomSessionInput> = {}) =>
|
|
372
|
+
Object.assign(new StartLiveKitAgentRoomSessionInput(), { RoomName: 'room-h', AgentID: 'co-agent', TargetAgentID: 'target-agent', AgentName: 'Sage', AgentSessionID: 'AS1', ...over });
|
|
373
|
+
|
|
374
|
+
beforeEach(() => {
|
|
375
|
+
h.startAgentRoomSession.mockClear();
|
|
376
|
+
h.mintClientToken.mockClear();
|
|
377
|
+
h.handoffDeps.AgentStarter = undefined;
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it('starts the agent through the handoff-capable starter when asked, naming the signed-in user as the caller, and returns a token for the same room', async () => {
|
|
381
|
+
const starter = vi.fn(async () => ({ SessionBridgeID: 'bridge-h' }));
|
|
382
|
+
h.handoffDeps.AgentStarter = starter;
|
|
383
|
+
const result = await resolver.StartLiveKitAgentRoomSession(input({ EnableHandoff: true, RealtimeVoice: 'echo' }), ctx);
|
|
384
|
+
expect(result).toMatchObject({ Success: true, SessionBridgeID: 'bridge-h', RoomName: 'room-h', ClientToken: 'jwt-user-u1', Identity: 'user-u1' });
|
|
385
|
+
expect(starter).toHaveBeenCalledWith(
|
|
386
|
+
expect.objectContaining({ RoomName: 'room-h', AgentID: 'target-agent', AgentName: 'Sage', CallerLabel: 'Amith', RealtimeVoice: 'echo' }),
|
|
387
|
+
);
|
|
388
|
+
expect(h.startAgentRoomSession).not.toHaveBeenCalled();
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it('falls back to the standard room start when handoff is requested but the server cannot do it', async () => {
|
|
392
|
+
const result = await resolver.StartLiveKitAgentRoomSession(input({ EnableHandoff: true }), ctx);
|
|
393
|
+
expect(result).toMatchObject({ Success: true, SessionBridgeID: 'bridge-std' });
|
|
394
|
+
expect(h.startAgentRoomSession).toHaveBeenCalledTimes(1);
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
it('does not touch the handoff starter unless asked', async () => {
|
|
398
|
+
const starter = vi.fn(async () => ({ SessionBridgeID: 'bridge-h' }));
|
|
399
|
+
h.handoffDeps.AgentStarter = starter;
|
|
400
|
+
await resolver.StartLiveKitAgentRoomSession(input(), ctx);
|
|
401
|
+
expect(starter).not.toHaveBeenCalled();
|
|
402
|
+
expect(h.startAgentRoomSession).toHaveBeenCalledTimes(1);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it('reports a failure to start the handoff-capable agent instead of silently starting a plain one', async () => {
|
|
406
|
+
h.handoffDeps.AgentStarter = vi.fn(async () => {
|
|
407
|
+
throw new Error('All agent lines are busy right now.');
|
|
408
|
+
});
|
|
409
|
+
const result = await resolver.StartLiveKitAgentRoomSession(input({ EnableHandoff: true }), ctx);
|
|
410
|
+
expect(result.Success).toBe(false);
|
|
411
|
+
expect(result.ErrorMessage).toContain('busy');
|
|
412
|
+
expect(h.startAgentRoomSession).not.toHaveBeenCalled();
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
it("fails before the handoff-capable agent joins when the user's token can't be minted", async () => {
|
|
416
|
+
const starter = vi.fn(async () => ({ SessionBridgeID: 'bridge-h' }));
|
|
417
|
+
h.handoffDeps.AgentStarter = starter;
|
|
418
|
+
h.mintClientToken.mockRejectedValueOnce(new Error('LiveKitTokenService is not configured.'));
|
|
419
|
+
|
|
420
|
+
const result = await resolver.StartLiveKitAgentRoomSession(input({ EnableHandoff: true }), ctx);
|
|
421
|
+
|
|
422
|
+
expect(result).toMatchObject({ Success: false, ErrorMessage: 'LiveKitTokenService is not configured.', SessionBridgeID: '' });
|
|
423
|
+
expect(starter).not.toHaveBeenCalled();
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
it('requires an authenticated user', async () => {
|
|
427
|
+
resolver.user = undefined;
|
|
428
|
+
const result = await resolver.StartLiveKitAgentRoomSession(input({ EnableHandoff: true }), ctx);
|
|
429
|
+
expect(result.Success).toBe(false);
|
|
430
|
+
expect(result.ErrorMessage).toMatch(/current user/i);
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
describe('the agent session it created closes when the agent leaves the room (#5356) or fails to start (#5308)', () => {
|
|
435
|
+
/** One `MJ: AI Agent Sessions` row in the fake database. */
|
|
436
|
+
interface SessionRow {
|
|
437
|
+
ID: string;
|
|
438
|
+
Status: string;
|
|
439
|
+
ClosedAt: Date | null;
|
|
440
|
+
CloseReason: string | null;
|
|
441
|
+
ConversationID: string | null;
|
|
442
|
+
Config_: string | null;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/** An `MJ: AI Agent Sessions` entity over the fake database: `Load` reads a row, `Save` writes it back. */
|
|
446
|
+
class FakeSessionEntity implements SessionRow {
|
|
447
|
+
public ID = '';
|
|
448
|
+
public Status = '';
|
|
449
|
+
public ClosedAt: Date | null = null;
|
|
450
|
+
public CloseReason: string | null = null;
|
|
451
|
+
public ConversationID: string | null = null;
|
|
452
|
+
public Config_: string | null = null;
|
|
453
|
+
|
|
454
|
+
constructor(private readonly rows: Map<string, SessionRow>) {}
|
|
455
|
+
|
|
456
|
+
public async Load(id: string): Promise<boolean> {
|
|
457
|
+
const row = this.rows.get(id);
|
|
458
|
+
if (!row) {
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
Object.assign(this, row);
|
|
462
|
+
return true;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
public async Save(): Promise<boolean> {
|
|
466
|
+
const { ID, Status, ClosedAt, CloseReason, ConversationID, Config_ } = this;
|
|
467
|
+
this.rows.set(ID, { ID, Status, ClosedAt, CloseReason, ConversationID, Config_ });
|
|
468
|
+
return true;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Stands in for the room coordinator and the bridge engine: a stop runs the stopped agent's end-of-session hook before
|
|
474
|
+
* it resolves, as the engine does once the bridge row is terminal.
|
|
475
|
+
*/
|
|
476
|
+
class FakeRooms {
|
|
477
|
+
private readonly agents = new Map<string, StartAgentRoomSessionParams>();
|
|
478
|
+
private seq = 0;
|
|
479
|
+
|
|
480
|
+
public readonly Start = async (params: StartAgentRoomSessionParams): Promise<AgentRoomSession> => {
|
|
481
|
+
const id = `bridge-${++this.seq}`;
|
|
482
|
+
this.agents.set(id, params);
|
|
483
|
+
return { SessionBridgeID: id, RoomName: params.RoomName, ServerUrl: 'wss://x.livekit.cloud' };
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
public readonly Stop = async (sessionBridgeID: string, reason: BridgeDisconnectReason = 'Explicit'): Promise<boolean> =>
|
|
487
|
+
this.End(sessionBridgeID, reason);
|
|
488
|
+
|
|
489
|
+
public readonly StopAll = async (roomName: string, reason: BridgeDisconnectReason = 'Explicit'): Promise<number> => {
|
|
490
|
+
const ids = [...this.agents].filter(([, params]) => params.RoomName === roomName).map(([id]) => id);
|
|
491
|
+
for (const id of ids) {
|
|
492
|
+
await this.End(id, reason);
|
|
493
|
+
}
|
|
494
|
+
return ids.length;
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
/** The agent's bridge ends: stopped, or on its own (everyone left, the model session was lost). */
|
|
498
|
+
public async End(sessionBridgeID: string, reason: BridgeDisconnectReason): Promise<boolean> {
|
|
499
|
+
const params = this.agents.get(sessionBridgeID);
|
|
500
|
+
if (!params) {
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
this.agents.delete(sessionBridgeID);
|
|
504
|
+
await params.Host?.OnSessionEnded?.(reason);
|
|
505
|
+
return true;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/** How many agents are in the room now. */
|
|
509
|
+
public AgentsIn(roomName: string): number {
|
|
510
|
+
return [...this.agents.values()].filter((params) => params.RoomName === roomName).length;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const sessions = new Map<string, SessionRow>();
|
|
515
|
+
const db = {
|
|
516
|
+
GetEntityObject: async (entityName: string) => {
|
|
517
|
+
if (entityName !== 'MJ: AI Agent Sessions') {
|
|
518
|
+
throw new Error(`Unexpected entity ${entityName}`);
|
|
519
|
+
}
|
|
520
|
+
return new FakeSessionEntity(sessions);
|
|
521
|
+
},
|
|
522
|
+
RunView: async () => ({ Success: true, Results: [] }), // the sessions have no channel rows to disconnect
|
|
523
|
+
} as unknown as IMetadataProvider;
|
|
524
|
+
const dbCtx = { providers: [{ type: 'Read-Write', provider: db }] } as unknown as AppContext;
|
|
525
|
+
let rooms: FakeRooms;
|
|
526
|
+
|
|
527
|
+
const addSession = (id: string): void => {
|
|
528
|
+
sessions.set(id, { ID: id, Status: 'Active', ClosedAt: null, CloseReason: null, ConversationID: null, Config_: null });
|
|
529
|
+
};
|
|
530
|
+
const start = (over: Partial<StartLiveKitAgentRoomSessionInput> = {}) =>
|
|
531
|
+
resolver.StartLiveKitAgentRoomSession(
|
|
532
|
+
Object.assign(new StartLiveKitAgentRoomSessionInput(), { RoomName: 'meet-room', AgentID: 'co-agent', TargetAgentID: 'target-agent', AgentName: 'Sage', ...over }),
|
|
533
|
+
dbCtx,
|
|
534
|
+
);
|
|
535
|
+
|
|
536
|
+
beforeEach(() => {
|
|
537
|
+
sessions.clear();
|
|
538
|
+
rooms = new FakeRooms();
|
|
539
|
+
h.startAgentRoomSession.mockImplementation(rooms.Start);
|
|
540
|
+
h.stopAgentRoomSession.mockImplementation(rooms.Stop);
|
|
541
|
+
h.stopAllAgentsInRoom.mockImplementation(rooms.StopAll);
|
|
542
|
+
let created = 0;
|
|
543
|
+
vi.spyOn(sessionManagerOf(resolver), 'CreateSession').mockImplementation(async () => {
|
|
544
|
+
const id = `AS-${++created}`;
|
|
545
|
+
addSession(id);
|
|
546
|
+
return { ID: id } as unknown as MJAIAgentSessionEntity;
|
|
547
|
+
});
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
afterEach(() => {
|
|
551
|
+
// Back to the file's defaults for the blocks that follow.
|
|
552
|
+
h.startAgentRoomSession.mockReset();
|
|
553
|
+
h.stopAgentRoomSession.mockReset();
|
|
554
|
+
h.stopAllAgentsInRoom.mockReset();
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
it('closes it at once when the agent is stopped, as Explicit', async () => {
|
|
558
|
+
const started = await start();
|
|
559
|
+
expect(started.Success).toBe(true);
|
|
560
|
+
expect(sessions.get('AS-1')?.Status).toBe('Active');
|
|
561
|
+
|
|
562
|
+
expect(await resolver.StopLiveKitAgentRoomSession(started.SessionBridgeID, dbCtx)).toBe(true);
|
|
563
|
+
|
|
564
|
+
expect(sessions.get('AS-1')).toMatchObject({ Status: 'Closed', CloseReason: 'Explicit', ClosedAt: expect.any(Date) });
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
it("closes every agent's session when the meeting is ended", async () => {
|
|
568
|
+
await start({ AgentName: 'Sage' });
|
|
569
|
+
await start({ AgentName: 'Analyst', TargetAgentID: 'other-target' });
|
|
570
|
+
|
|
571
|
+
expect(await resolver.EndLiveKitRoom('meet-room', dbCtx)).toBe(true);
|
|
572
|
+
|
|
573
|
+
expect(sessions.get('AS-1')).toMatchObject({ Status: 'Closed', CloseReason: 'Explicit' });
|
|
574
|
+
expect(sessions.get('AS-2')).toMatchObject({ Status: 'Closed', CloseReason: 'Explicit' });
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
it('closes it when the agent leaves on its own: everyone gone as Explicit, the model session lost as Error', async () => {
|
|
578
|
+
const first = await start();
|
|
579
|
+
const second = await start();
|
|
580
|
+
|
|
581
|
+
await rooms.End(first.SessionBridgeID, 'HostEnded');
|
|
582
|
+
await rooms.End(second.SessionBridgeID, 'Error');
|
|
583
|
+
|
|
584
|
+
expect(sessions.get('AS-1')).toMatchObject({ Status: 'Closed', CloseReason: 'Explicit' });
|
|
585
|
+
expect(sessions.get('AS-2')).toMatchObject({ Status: 'Closed', CloseReason: 'Error' });
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
it('closes it as the user who started the agent, whoever stops it', async () => {
|
|
589
|
+
const starter = resolver.user;
|
|
590
|
+
const started = await start();
|
|
591
|
+
const close = vi.spyOn(sessionManagerOf(resolver), 'CloseSession');
|
|
592
|
+
resolver.user = { ID: 'U2', Name: 'Second Participant', Email: 'second@example.com' } as unknown as UserInfo;
|
|
593
|
+
|
|
594
|
+
await resolver.StopLiveKitAgentRoomSession(started.SessionBridgeID, dbCtx);
|
|
595
|
+
|
|
596
|
+
expect(close).toHaveBeenCalledWith('AS-1', starter, db, 'Explicit');
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
it("leaves open a session the caller supplied: it is the caller's to close", async () => {
|
|
600
|
+
addSession('AS-supplied');
|
|
601
|
+
const started = await start({ AgentSessionID: 'AS-supplied' });
|
|
602
|
+
|
|
603
|
+
await resolver.StopLiveKitAgentRoomSession(started.SessionBridgeID, dbCtx);
|
|
604
|
+
|
|
605
|
+
expect(sessions.get('AS-supplied')?.Status).toBe('Active');
|
|
606
|
+
expect(sessionManagerOf(resolver).CreateSession).not.toHaveBeenCalled();
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
// The coordinator's start throws once it has closed any model session it opened; the agent never joined, so no
|
|
610
|
+
// end-of-session hook runs for it.
|
|
611
|
+
it.each([
|
|
612
|
+
["the bot's token can't be minted", 'mint failed'],
|
|
613
|
+
["the bridge doesn't start", 'Failed to create AIAgentSessionBridge: the user may not create MJ: AI Agent Session Bridges'],
|
|
614
|
+
])('closes it at once, as Error, when the agent fails to start because %s', async (_why, startError) => {
|
|
615
|
+
h.startAgentRoomSession.mockRejectedValueOnce(new Error(startError));
|
|
616
|
+
const close = vi.spyOn(sessionManagerOf(resolver), 'CloseSession');
|
|
617
|
+
|
|
618
|
+
const started = await start();
|
|
619
|
+
|
|
620
|
+
expect(started).toMatchObject({ Success: false, ErrorMessage: startError });
|
|
621
|
+
expect(sessions.get('AS-1')).toMatchObject({ Status: 'Closed', CloseReason: 'Error', ClosedAt: expect.any(Date) });
|
|
622
|
+
expect(close).toHaveBeenCalledWith('AS-1', resolver.user, db, 'Error');
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
it('leaves a session the caller supplied as it was when the agent fails to start', async () => {
|
|
626
|
+
addSession('AS-supplied');
|
|
627
|
+
h.startAgentRoomSession.mockRejectedValueOnce(new Error('mint failed'));
|
|
628
|
+
|
|
629
|
+
const started = await start({ AgentSessionID: 'AS-supplied' });
|
|
630
|
+
|
|
631
|
+
expect(started.Success).toBe(false);
|
|
632
|
+
expect(sessions.get('AS-supplied')?.Status).toBe('Active');
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
it("returns the start's own error when closing the session fails too, leaving the session to the janitor", async () => {
|
|
636
|
+
h.startAgentRoomSession.mockRejectedValueOnce(new Error('mint failed'));
|
|
637
|
+
vi.spyOn(sessionManagerOf(resolver), 'CloseSession').mockRejectedValue(new Error('database unreachable'));
|
|
638
|
+
|
|
639
|
+
const started = await start();
|
|
640
|
+
|
|
641
|
+
expect(started).toMatchObject({ Success: false, ErrorMessage: 'mint failed' });
|
|
642
|
+
expect(sessions.get('AS-1')?.Status).toBe('Active');
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
// Minted after the agent joined, a token that couldn't be minted failed the start with the agent left in the room and
|
|
646
|
+
// its session Active, and the UI got no bridge id to stop it by.
|
|
647
|
+
it("starts nothing when the user's token can't be minted: no agent joins, no session is left Active", async () => {
|
|
648
|
+
h.mintClientToken.mockRejectedValueOnce(new Error('LiveKitTokenService is not configured.'));
|
|
649
|
+
|
|
650
|
+
const started = await start();
|
|
651
|
+
|
|
652
|
+
expect(started).toMatchObject({ Success: false, ErrorMessage: 'LiveKitTokenService is not configured.' });
|
|
653
|
+
expect(rooms.AgentsIn('meet-room')).toBe(0);
|
|
654
|
+
expect([...sessions.values()].filter((row) => row.Status === 'Active')).toEqual([]);
|
|
655
|
+
});
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
describe('Per-room authorization enforcement', () => {
|
|
659
|
+
beforeEach(() => {
|
|
660
|
+
vi.clearAllMocks();
|
|
661
|
+
RoomAuthorizationService.Instance.SetAuthorizerForTesting(async () => ({
|
|
662
|
+
Authorized: false,
|
|
663
|
+
Reason: 'User is not authorized for this room.',
|
|
664
|
+
}));
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
it('MintLiveKitClientToken rejects unauthorized room access', async () => {
|
|
668
|
+
const input = Object.assign(new MintLiveKitClientTokenInput(), { RoomName: 'private-room' });
|
|
669
|
+
const result = await resolver.MintLiveKitClientToken(input, ctx);
|
|
670
|
+
expect(result.Success).toBe(false);
|
|
671
|
+
expect(result.ErrorMessage).toBe('User is not authorized for this room.');
|
|
672
|
+
expect(h.mintClientToken).not.toHaveBeenCalled();
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
it('StartLiveKitAgentRoomSession rejects unauthorized room access', async () => {
|
|
676
|
+
const input = Object.assign(new StartLiveKitAgentRoomSessionInput(), {
|
|
677
|
+
RoomName: 'private-room',
|
|
678
|
+
AgentID: 'agent-1',
|
|
679
|
+
});
|
|
680
|
+
const result = await resolver.StartLiveKitAgentRoomSession(input, ctx);
|
|
681
|
+
expect(result.Success).toBe(false);
|
|
682
|
+
expect(result.ErrorMessage).toBe('User is not authorized for this room.');
|
|
683
|
+
expect(h.startAgentRoomSession).not.toHaveBeenCalled();
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
it('StopLiveKitAgentRoomSession rejects unauthorized room access', async () => {
|
|
687
|
+
h.getRoomForBridge.mockReturnValueOnce('private-room');
|
|
688
|
+
const result = await resolver.StopLiveKitAgentRoomSession('bridge-1', ctx);
|
|
689
|
+
expect(result).toBe(false);
|
|
690
|
+
expect(h.stopAgentRoomSession).not.toHaveBeenCalled();
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
it('StopLiveKitAgentRoomSession proceeds when room access is authorized', async () => {
|
|
694
|
+
RoomAuthorizationService.Instance.SetAuthorizerForTesting(async () => ({ Authorized: true }));
|
|
695
|
+
h.getRoomForBridge.mockReturnValueOnce('private-room');
|
|
696
|
+
const result = await resolver.StopLiveKitAgentRoomSession('bridge-1', ctx);
|
|
697
|
+
expect(result).toBe(true);
|
|
698
|
+
expect(h.stopAgentRoomSession).toHaveBeenCalledWith('bridge-1', 'Explicit', resolver.user, null);
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
it('EndLiveKitRoom rejects unauthorized room access', async () => {
|
|
702
|
+
const result = await resolver.EndLiveKitRoom('private-room', ctx);
|
|
703
|
+
expect(result).toBe(false);
|
|
704
|
+
expect(h.stopAllAgentsInRoom).not.toHaveBeenCalled();
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
it('EndLiveKitRoom proceeds when room access is authorized', async () => {
|
|
708
|
+
RoomAuthorizationService.Instance.SetAuthorizerForTesting(async () => ({ Authorized: true }));
|
|
709
|
+
const result = await resolver.EndLiveKitRoom('private-room', ctx);
|
|
710
|
+
expect(result).toBe(true);
|
|
711
|
+
expect(h.stopAllAgentsInRoom).toHaveBeenCalledWith('private-room', 'Explicit', resolver.user, null);
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
it('GetLiveKitRoomTurnState rejects unauthorized room access', async () => {
|
|
715
|
+
const result = await resolver.GetLiveKitRoomTurnState('private-room', ctx);
|
|
716
|
+
expect(result.Success).toBe(false);
|
|
717
|
+
expect(result.ErrorMessage).toBe('User is not authorized for this room.');
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
it('InviteUsersToLiveKitRoom rejects unauthorized room access', async () => {
|
|
721
|
+
const result = await resolver.InviteUsersToLiveKitRoom('private-room', ['user-2'], ctx);
|
|
722
|
+
expect(result).toBe(false);
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
it('StartLiveKitRecording rejects unauthorized room access', async () => {
|
|
726
|
+
const input = Object.assign(new LiveKitRecordingInput(), { RoomName: 'private-room' });
|
|
727
|
+
const result = await resolver.StartLiveKitRecording(input, ctx);
|
|
728
|
+
expect(result.Success).toBe(false);
|
|
729
|
+
expect(result.ErrorMessage).toBe('User is not authorized for this room.');
|
|
730
|
+
expect(h.startRecording).not.toHaveBeenCalled();
|
|
731
|
+
});
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
describe('GetRealtimeModelVoices', () => {
|
|
735
|
+
it("types each voice's persona, avatar and preview image as nullable strings", async () => {
|
|
736
|
+
const schema = await buildSchema({ resolvers: [VoiceProbeResolver], validate: false });
|
|
737
|
+
// Read the built type rather than printing the schema (printSchema's instanceOf checks fail when type-graphql
|
|
738
|
+
// and the test resolve separate copies of `graphql`).
|
|
739
|
+
const fields = (schema.getType('RealtimeVoiceOptionResult') as GraphQLObjectType).getFields();
|
|
740
|
+
expect(String(fields['ID'].type)).toBe('String!');
|
|
741
|
+
expect(String(fields['Name'].type)).toBe('String!');
|
|
742
|
+
expect(String(fields['PersonaID'].type)).toBe('String');
|
|
743
|
+
expect(String(fields['AvatarID'].type)).toBe('String');
|
|
744
|
+
expect(String(fields['PreviewImageURL'].type)).toBe('String');
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
it('returns the marks the agents package resolved, unchanged', async () => {
|
|
748
|
+
const models = [
|
|
749
|
+
{
|
|
750
|
+
ModelID: 'm1',
|
|
751
|
+
ModelName: 'Live Voice Model',
|
|
752
|
+
Voices: [
|
|
753
|
+
{ ID: 'Puck', Name: 'Puck', PersonaID: 'p-puck' },
|
|
754
|
+
{ ID: 'Puck', Name: 'Avery', PersonaID: 'p-avery', AvatarID: 'Avery', PreviewImageURL: 'https://img.example.test/avery.png' },
|
|
755
|
+
{ ID: 'Kore', Name: 'Kore' },
|
|
756
|
+
],
|
|
757
|
+
},
|
|
758
|
+
];
|
|
759
|
+
vi.mocked(GetRealtimeModelVoices).mockResolvedValueOnce(models);
|
|
760
|
+
expect(await resolver.GetRealtimeModelVoices(ctx)).toEqual(models);
|
|
761
|
+
});
|
|
762
|
+
});
|
|
125
763
|
});
|
|
764
|
+
|
|
765
|
+
/** Exposes the voice-picker result type in a schema of its own, so the test reads the type the client queries. */
|
|
766
|
+
@Resolver()
|
|
767
|
+
class VoiceProbeResolver {
|
|
768
|
+
@Query(() => [RealtimeModelVoicesResult])
|
|
769
|
+
RealtimeVoices(): RealtimeModelVoicesResult[] {
|
|
770
|
+
return [];
|
|
771
|
+
}
|
|
772
|
+
}
|