@memberjunction/server 6.2.0-edge.3 → 6.2.0-edge.5
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 +20 -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 +236 -27
- package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
- package/dist/agentSessions/SessionJanitor.js +682 -54
- package/dist/agentSessions/SessionJanitor.js.map +1 -1
- package/dist/agentSessions/SessionManager.d.ts +48 -3
- package/dist/agentSessions/SessionManager.d.ts.map +1 -1
- package/dist/agentSessions/SessionManager.js +112 -9
- 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/apiRoot.d.ts +32 -0
- package/dist/apiRoot.d.ts.map +1 -0
- package/dist/apiRoot.js +67 -0
- package/dist/apiRoot.js.map +1 -0
- package/dist/auth/AuthProviderCatalogRouter.d.ts.map +1 -1
- package/dist/auth/AuthProviderCatalogRouter.js +25 -0
- package/dist/auth/AuthProviderCatalogRouter.js.map +1 -1
- package/dist/auth/LoginScreenEngine.d.ts +22 -0
- package/dist/auth/LoginScreenEngine.d.ts.map +1 -0
- package/dist/auth/LoginScreenEngine.js +57 -0
- package/dist/auth/LoginScreenEngine.js.map +1 -0
- 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 +1 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +4 -10
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.js +3 -2
- package/dist/auth/magicLink/MagicLinkRouter.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 +2514 -22
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +166 -4
- 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 +14 -11
- package/dist/context.js.map +1 -1
- package/dist/coreRoutes.d.ts +29 -0
- package/dist/coreRoutes.d.ts.map +1 -0
- package/dist/coreRoutes.js +142 -0
- package/dist/coreRoutes.js.map +1 -0
- package/dist/generated/graphql-schemas/__mj.d.ts +504 -3
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +2456 -7
- package/dist/generated/graphql-schemas/__mj.js.map +1 -1
- package/dist/generic/CacheInvalidationResolver.d.ts +43 -8
- package/dist/generic/CacheInvalidationResolver.d.ts.map +1 -1
- package/dist/generic/CacheInvalidationResolver.js +59 -19
- package/dist/generic/CacheInvalidationResolver.js.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.js +3 -3
- package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
- package/dist/generic/PushStatusResolver.js +1 -1
- package/dist/generic/PushStatusResolver.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +123 -48
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +293 -305
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +16 -0
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/globalCors.d.ts +33 -0
- package/dist/globalCors.d.ts.map +1 -0
- package/dist/globalCors.js +58 -0
- package/dist/globalCors.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +219 -278
- 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/multiTenancy/index.d.ts.map +1 -1
- package/dist/multiTenancy/index.js +22 -2
- package/dist/multiTenancy/index.js.map +1 -1
- 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 +139 -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 +99 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.js.map +1 -0
- package/dist/realtimeSessions/RecordingAssemblyScheduler.d.ts +100 -0
- package/dist/realtimeSessions/RecordingAssemblyScheduler.d.ts.map +1 -0
- package/dist/realtimeSessions/RecordingAssemblyScheduler.js +173 -0
- package/dist/realtimeSessions/RecordingAssemblyScheduler.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 +19 -0
- package/dist/realtimeSessions/index.d.ts.map +1 -0
- package/dist/realtimeSessions/index.js +19 -0
- package/dist/realtimeSessions/index.js.map +1 -0
- package/dist/realtimeSessions/ownedRealtimeSession.d.ts +30 -0
- package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -0
- package/dist/realtimeSessions/ownedRealtimeSession.js +47 -0
- package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -0
- package/dist/realtimeSessions/sessionAccess.d.ts +92 -0
- package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -0
- package/dist/realtimeSessions/sessionAccess.js +94 -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 +14 -1
- package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.js +21 -5
- package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
- package/dist/resolvers/ArtifactFileResolver.d.ts +5 -1
- package/dist/resolvers/ArtifactFileResolver.d.ts.map +1 -1
- package/dist/resolvers/ArtifactFileResolver.js +9 -7
- package/dist/resolvers/ArtifactFileResolver.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/ConversationRunEventsResolver.d.ts +8 -4
- package/dist/resolvers/ConversationRunEventsResolver.d.ts.map +1 -1
- package/dist/resolvers/ConversationRunEventsResolver.js +6 -6
- package/dist/resolvers/ConversationRunEventsResolver.js.map +1 -1
- package/dist/resolvers/DatasetResolver.d.ts +24 -3
- package/dist/resolvers/DatasetResolver.d.ts.map +1 -1
- package/dist/resolvers/DatasetResolver.js +57 -8
- package/dist/resolvers/DatasetResolver.js.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.d.ts +2 -1
- package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.js +3 -2
- package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts +27 -1
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.js +148 -2
- package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
- package/dist/resolvers/FileResolver.d.ts +82 -11
- package/dist/resolvers/FileResolver.d.ts.map +1 -1
- package/dist/resolvers/FileResolver.js +279 -101
- package/dist/resolvers/FileResolver.js.map +1 -1
- package/dist/resolvers/GetDataContextDataResolver.d.ts +33 -7
- package/dist/resolvers/GetDataContextDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataContextDataResolver.js +117 -38
- package/dist/resolvers/GetDataContextDataResolver.js.map +1 -1
- package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataResolver.js +6 -3
- package/dist/resolvers/GetDataResolver.js.map +1 -1
- package/dist/resolvers/HumanHandoffResolver.d.ts.map +1 -1
- package/dist/resolvers/HumanHandoffResolver.js +7 -1
- package/dist/resolvers/HumanHandoffResolver.js.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.js +4 -2
- package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
- 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/QueryResolver.d.ts +2 -0
- package/dist/resolvers/QueryResolver.d.ts.map +1 -1
- package/dist/resolvers/QueryResolver.js +13 -0
- package/dist/resolvers/QueryResolver.js.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.d.ts +54 -2
- package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.js +155 -10
- package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +157 -33
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +404 -116
- 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 +100 -14
- 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/SearchKnowledgeStreamResolver.d.ts +12 -1
- package/dist/resolvers/SearchKnowledgeStreamResolver.d.ts.map +1 -1
- package/dist/resolvers/SearchKnowledgeStreamResolver.js +15 -2
- package/dist/resolvers/SearchKnowledgeStreamResolver.js.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.js +2 -3
- package/dist/resolvers/SqlLoggingConfigResolver.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/TaskGraphFrameResolver.d.ts +2 -1
- package/dist/resolvers/TaskGraphFrameResolver.d.ts.map +1 -1
- package/dist/resolvers/TaskGraphFrameResolver.js +28 -1
- package/dist/resolvers/TaskGraphFrameResolver.js.map +1 -1
- package/dist/resolvers/TestQuerySQLResolver.d.ts +24 -0
- package/dist/resolvers/TestQuerySQLResolver.d.ts.map +1 -1
- package/dist/resolvers/TestQuerySQLResolver.js +65 -13
- package/dist/resolvers/TestQuerySQLResolver.js.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.d.ts +19 -2
- package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.js +102 -24
- package/dist/resolvers/TransactionGroupResolver.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/agent-run-data-guard.d.ts +31 -0
- package/dist/resolvers/agent-run-data-guard.d.ts.map +1 -0
- package/dist/resolvers/agent-run-data-guard.js +72 -0
- package/dist/resolvers/agent-run-data-guard.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/meetingMutations.d.ts +6 -0
- package/dist/resolvers/meetings/meetingMutations.d.ts.map +1 -1
- package/dist/resolvers/meetings/meetingMutations.js +21 -0
- package/dist/resolvers/meetings/meetingMutations.js.map +1 -1
- package/dist/rest/RealtimeRecordingHandler.d.ts +49 -0
- package/dist/rest/RealtimeRecordingHandler.d.ts.map +1 -0
- package/dist/rest/RealtimeRecordingHandler.js +383 -0
- package/dist/rest/RealtimeRecordingHandler.js.map +1 -0
- package/dist/rest/ViewOperationsHandler.d.ts +3 -1
- package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
- package/dist/rest/ViewOperationsHandler.js +21 -12
- package/dist/rest/ViewOperationsHandler.js.map +1 -1
- package/dist/rest/setupRESTEndpoints.d.ts +6 -0
- package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
- package/dist/rest/setupRESTEndpoints.js +6 -0
- package/dist/rest/setupRESTEndpoints.js.map +1 -1
- package/dist/serverExtensionReservedRoots.d.ts +12 -10
- package/dist/serverExtensionReservedRoots.d.ts.map +1 -1
- package/dist/serverExtensionReservedRoots.js +14 -17
- package/dist/serverExtensionReservedRoots.js.map +1 -1
- package/dist/services/MJStorageBlobStore.d.ts +8 -1
- package/dist/services/MJStorageBlobStore.d.ts.map +1 -1
- package/dist/services/MJStorageBlobStore.js +25 -19
- package/dist/services/MJStorageBlobStore.js.map +1 -1
- package/dist/services/StartTaskGraphDispatcher.js +2 -2
- package/dist/services/StartTaskGraphDispatcher.js.map +1 -1
- package/dist/services/TaskGraphActionRunner.d.ts +5 -3
- package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphActionRunner.js +27 -5
- package/dist/services/TaskGraphActionRunner.js.map +1 -1
- package/dist/services/TaskGraphAgentRunner.d.ts +14 -2
- package/dist/services/TaskGraphAgentRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphAgentRunner.js +157 -9
- package/dist/services/TaskGraphAgentRunner.js.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.d.ts +57 -4
- package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.js +607 -18
- package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
- package/dist/sharedCache.d.ts +105 -0
- package/dist/sharedCache.d.ts.map +1 -0
- package/dist/sharedCache.js +279 -0
- package/dist/sharedCache.js.map +1 -0
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +99 -96
- package/src/__tests__/ActionResolver.authorization.test.ts +353 -0
- 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__/DatasetResolver.itemFilters.test.ts +201 -0
- package/src/__tests__/EntityRecordNameResolver.rowSecurity.test.ts +152 -0
- package/src/__tests__/FetchEntityVectorsResolver.test.ts +433 -0
- package/src/__tests__/FileResolver.storageAccess.test.ts +562 -0
- package/src/__tests__/GetDataContextDataResolver.sqlItems.test.ts +416 -0
- package/src/__tests__/HostInstance.test.ts +84 -0
- package/src/__tests__/HumanHandoffResolver.test.ts +17 -0
- package/src/__tests__/ListOperationsResolver.adhocScreen.test.ts +137 -0
- package/src/__tests__/LoginScreenEngine.test.ts +86 -0
- package/src/__tests__/MJStorageBlobStore.storageGate.test.ts +63 -0
- package/src/__tests__/MeetingResolver.test.ts +75 -1
- package/src/__tests__/RealtimeBridgeResolver.test.ts +415 -6
- 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__/RecordingAssemblyScheduler.test.ts +178 -0
- package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +129 -0
- package/src/__tests__/ResolverBase.adhocReadScope.test.ts +133 -2
- package/src/__tests__/ResolverBase.aggregateScreen.test.ts +174 -0
- package/src/__tests__/ResolverBase.balancedClause.test.ts +155 -0
- package/src/__tests__/ResolverBase.clauseUnscopedRead.test.ts +127 -0
- package/src/__tests__/ResolverBase.clientOldValues.test.ts +212 -6
- package/src/__tests__/ResolverBase.filterEscaping.test.ts +5 -2
- package/src/__tests__/ReturningVisitorRecap.test.ts +115 -0
- package/src/__tests__/RunAIAgentResolver.branch.test.ts +170 -0
- package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +6 -4
- package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +258 -0
- 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__/RunViewResolver.cacheCheckScreen.test.ts +165 -0
- package/src/__tests__/SearchKnowledgeStreamResolver.test.ts +161 -0
- package/src/__tests__/SessionJanitor.test.ts +1203 -21
- package/src/__tests__/SessionManager.test.ts +240 -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 +25 -0
- package/src/__tests__/TaskGraphAgentRunner.test.ts +266 -0
- package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +403 -22
- package/src/__tests__/TaskGraphFrameResolver.test.ts +22 -0
- package/src/__tests__/TestQuerySQLResolver.test.ts +274 -0
- package/src/__tests__/TransactionGroupResolver.refusals.test.ts +98 -1
- package/src/__tests__/UpdateMJDataContextItem.oldValues.test.ts +220 -0
- package/src/__tests__/UserAvatarResolver.test.ts +255 -0
- package/src/__tests__/UserResolver.currentUser.test.ts +16 -1
- package/src/__tests__/ViewOperationsHandler.clauseScreen.test.ts +157 -0
- package/src/__tests__/actionAuthorization.test.ts +130 -0
- package/src/__tests__/agent-run-data-guard.test.ts +130 -0
- package/src/__tests__/agentVisionConsentAudit.test.ts +118 -0
- package/src/__tests__/apiRootBoot.test.ts +202 -0
- package/src/__tests__/avatarInputValidation.test.ts +160 -0
- package/src/__tests__/cacheInvalidationRecordData.test.ts +64 -0
- package/src/__tests__/conversationRunEvents.test.ts +56 -14
- package/src/__tests__/globalCors.test.ts +217 -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__/multiTenancy.postgresRewrite.test.ts +181 -0
- package/src/__tests__/multiTenancy.security.test.ts +51 -0
- package/src/__tests__/multiTenancy.test.ts +1 -1
- 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__/ownedRealtimeSession.test.ts +39 -0
- package/src/__tests__/pushStatusResolver.filter.test.ts +7 -0
- package/src/__tests__/realtimeRecordingHandler.test.ts +652 -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__/serverExtensionHostOverlay.test.ts +66 -0
- package/src/__tests__/serverExtensionReservedRoots.test.ts +11 -96
- package/src/__tests__/sharedCache.test.ts +312 -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 +821 -59
- package/src/agentSessions/SessionManager.ts +138 -26
- package/src/agentSessions/SessionRunIDVerifier.ts +171 -0
- package/src/agentSessions/index.ts +10 -3
- package/src/apiRoot.ts +78 -0
- package/src/auth/AuthProviderCatalogRouter.ts +26 -0
- package/src/auth/LoginScreenEngine.ts +50 -0
- package/src/auth/actionAuthorization.ts +116 -0
- package/src/auth/index.ts +4 -8
- package/src/auth/magicLink/MagicLinkRouter.ts +3 -2
- package/src/auth/magicLink/MagicLinkService.ts +6 -3
- package/src/auth/newUsers.ts +99 -11
- package/src/config.ts +171 -5
- package/src/context.ts +15 -10
- package/src/coreRoutes.ts +166 -0
- package/src/generated/graphql-schemas/__mj.ts +1815 -57
- package/src/generic/CacheInvalidationResolver.ts +73 -18
- package/src/generic/OrphanedConversationDetailReconciler.ts +3 -3
- package/src/generic/PushStatusResolver.ts +1 -1
- package/src/generic/ResolverBase.ts +335 -326
- package/src/generic/RunViewResolver.ts +21 -0
- package/src/globalCors.ts +71 -0
- package/src/index.ts +231 -300
- package/src/isolatedProvider.ts +22 -0
- package/src/multiTenancy/index.ts +24 -2
- 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 +168 -0
- package/src/realtimeSessions/RealtimeVerifyRouter.ts +109 -0
- package/src/realtimeSessions/RecordingAssemblyScheduler.ts +234 -0
- package/src/realtimeSessions/SessionVerificationPortsBase.ts +189 -0
- package/src/realtimeSessions/SlidingWindowRateLimiter.ts +92 -0
- package/src/realtimeSessions/index.ts +18 -0
- package/src/realtimeSessions/ownedRealtimeSession.ts +55 -0
- package/src/realtimeSessions/sessionAccess.ts +145 -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 +24 -6
- package/src/resolvers/ArtifactFileResolver.ts +9 -8
- package/src/resolvers/CacheStatsResolver.ts +89 -2
- package/src/resolvers/ClientToolRequestResolver.ts +25 -7
- package/src/resolvers/ConversationRunEventsResolver.ts +15 -11
- package/src/resolvers/DatasetResolver.ts +70 -10
- package/src/resolvers/EntityRecordNameResolver.ts +3 -2
- package/src/resolvers/FetchEntityVectorsResolver.ts +179 -2
- package/src/resolvers/FileResolver.ts +321 -106
- package/src/resolvers/GetDataContextDataResolver.ts +139 -41
- package/src/resolvers/GetDataResolver.ts +6 -3
- package/src/resolvers/HumanHandoffResolver.ts +8 -0
- package/src/resolvers/IntegrationDiscoveryResolver.ts +4 -2
- package/src/resolvers/MCPResolver.ts +8 -5
- package/src/resolvers/QueryResolver.ts +11 -0
- package/src/resolvers/RealtimeBridgeResolver.ts +163 -11
- package/src/resolvers/RealtimeClientSessionResolver.ts +511 -129
- 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 +116 -14
- package/src/resolvers/RunTemplateResolver.ts +13 -12
- package/src/resolvers/SearchKnowledgeStreamResolver.ts +19 -3
- package/src/resolvers/SqlLoggingConfigResolver.ts +2 -4
- package/src/resolvers/SyncDataResolver.ts +6 -2
- package/src/resolvers/SyncRolesUsersResolver.ts +24 -6
- package/src/resolvers/TaskGraphFrameResolver.ts +33 -1
- package/src/resolvers/TestQuerySQLResolver.ts +68 -13
- package/src/resolvers/TransactionGroupResolver.ts +131 -27
- package/src/resolvers/UserAvatarResolver.ts +111 -0
- package/src/resolvers/UserResolver.ts +2 -16
- package/src/resolvers/__tests__/GetDataResolver.validation.test.ts +95 -0
- package/src/resolvers/agent-run-data-guard.ts +89 -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/meetingMutations.ts +21 -0
- package/src/rest/RealtimeRecordingHandler.ts +464 -0
- package/src/rest/ViewOperationsHandler.ts +29 -13
- package/src/rest/setupRESTEndpoints.ts +6 -0
- package/src/serverExtensionReservedRoots.ts +14 -17
- package/src/services/MJStorageBlobStore.ts +31 -30
- package/src/services/StartTaskGraphDispatcher.ts +2 -2
- package/src/services/TaskGraphActionRunner.ts +25 -5
- package/src/services/TaskGraphAgentRunner.ts +188 -11
- package/src/services/TaskGraphContinuationDeliverer.ts +713 -17
- package/src/sharedCache.ts +305 -0
- package/src/types.ts +7 -0
|
@@ -29,7 +29,10 @@ interface FakeChannelRow {
|
|
|
29
29
|
const engineCoAgentsMock = vi.fn((): FakeCoAgentRow[] => []);
|
|
30
30
|
const engineChannelsMock = vi.fn((): FakeChannelRow[] => []);
|
|
31
31
|
const engineConfigMock = vi.fn(async (): Promise<void> => undefined);
|
|
32
|
-
|
|
32
|
+
// PARTIAL mock: the engine and the permission helper are stubbed; the pure usage-record functions (read, merge)
|
|
33
|
+
// stay real, so the usage relay's details tests assert what is actually stored.
|
|
34
|
+
vi.mock('@memberjunction/ai-engine-base', async (importOriginal) => ({
|
|
35
|
+
...(await importOriginal<Record<string, unknown>>()),
|
|
33
36
|
AIAgentPermissionHelper: {
|
|
34
37
|
HasPermission: (...args: unknown[]) => hasPermissionMock(...(args as [])),
|
|
35
38
|
},
|
|
@@ -87,13 +90,14 @@ const cancelInFlightMock = vi.fn((_agentSessionID: string, _callID?: string): nu
|
|
|
87
90
|
// class-property/getter references above).
|
|
88
91
|
const { resolveStorageMock, storeRecordingMock, writeSegmentMock, deleteSegmentsMock } = vi.hoisted(() => ({
|
|
89
92
|
resolveStorageMock: vi.fn(async (): Promise<string | null> => 'storage-acct-1'),
|
|
90
|
-
storeRecordingMock: vi.fn(async (): Promise<StoreRealtimeRecordingResult> => ({ FileID: 'file-1', ErrorMessage: null })),
|
|
93
|
+
storeRecordingMock: vi.fn(async (): Promise<StoreRealtimeRecordingResult> => ({ FileID: 'file-1', ErrorMessage: null, Superseded: false })),
|
|
91
94
|
writeSegmentMock: vi.fn(async (): Promise<boolean> => true),
|
|
92
95
|
deleteSegmentsMock: vi.fn(async (): Promise<number> => 0),
|
|
93
96
|
}));
|
|
94
97
|
const onChannelStateSaveMock = vi.fn(
|
|
95
98
|
async (_agentSessionID: string, _channelName: string, stateJson: string): Promise<string> => stateJson,
|
|
96
99
|
);
|
|
100
|
+
const pruneSessionChannelsMock = vi.fn((_agentSessionID: string, _channelNames: ReadonlyArray<string>): string[] => []);
|
|
97
101
|
// PARTIAL mock: the service + channel host are stubbed, but the PURE co-agent config module
|
|
98
102
|
// (DeepMergeConfigs / ResolveEffectiveRealtimeConfig / EvaluateRuntimeOverrideAuthorization /
|
|
99
103
|
// ParseRealtimeTypeConfiguration) stays REAL — the resolver's pairing/override gate is under test.
|
|
@@ -117,7 +121,7 @@ vi.mock('@memberjunction/ai-agents', async (importOriginal) => {
|
|
|
117
121
|
},
|
|
118
122
|
RealtimeChannelServerHost: {
|
|
119
123
|
get Instance() {
|
|
120
|
-
return { OnChannelStateSave: onChannelStateSaveMock };
|
|
124
|
+
return { OnChannelStateSave: onChannelStateSaveMock, PruneSessionChannels: pruneSessionChannelsMock };
|
|
121
125
|
},
|
|
122
126
|
},
|
|
123
127
|
resolveRecordingStorageAccountID: resolveStorageMock,
|
|
@@ -213,6 +217,8 @@ interface FakeSession {
|
|
|
213
217
|
LatestResult?: { CompleteMessage?: string; Success?: boolean };
|
|
214
218
|
/** Only set on the fakes exercising `describeSaveFailure`'s "no failure detail recorded" branches. */
|
|
215
219
|
ResultHistory?: unknown[];
|
|
220
|
+
/** The fork a conversation-detail fake is written on (null is Main). */
|
|
221
|
+
BranchID?: string | null;
|
|
216
222
|
}
|
|
217
223
|
|
|
218
224
|
/**
|
|
@@ -242,13 +248,44 @@ function makeSessionEntity(overrides: Partial<FakeSession> = {}): FakeSession {
|
|
|
242
248
|
};
|
|
243
249
|
}
|
|
244
250
|
|
|
245
|
-
/**
|
|
246
|
-
|
|
247
|
-
|
|
251
|
+
/** Co-agent observability and paused-run ids, as a voice session's Config holds them. */
|
|
252
|
+
const CO_RUN_ID = 'c0c0c0c0-0000-4000-8000-000000000001';
|
|
253
|
+
const PROMPT_RUN_ID = 'c0c0c0c0-0000-4000-8000-000000000002';
|
|
254
|
+
const PAUSED_RUN_ID = 'c0c0c0c0-0000-4000-8000-000000000003';
|
|
255
|
+
|
|
256
|
+
/** `RunView`/`RunViews` stubs for a provider. */
|
|
257
|
+
interface RunLookups {
|
|
258
|
+
RunView: ReturnType<typeof vi.fn>;
|
|
259
|
+
RunViews: ReturnType<typeof vi.fn>;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Answers the session run-id lookups: each id in `runIDs` is a run of the session, and
|
|
264
|
+
* `promptRunID` (when given) is the TargetLogID of the co-agent run's Prompt step.
|
|
265
|
+
*/
|
|
266
|
+
function sessionRunLookups(runIDs: string[], promptRunID?: string): RunLookups {
|
|
267
|
+
const runView = vi.fn(async (params: { EntityName?: string }) => {
|
|
268
|
+
if (params.EntityName === 'MJ: AI Agent Runs') {
|
|
269
|
+
return { Success: true, Results: runIDs.map((ID) => ({ ID })) };
|
|
270
|
+
}
|
|
271
|
+
if (params.EntityName === 'MJ: AI Agent Run Steps' && promptRunID) {
|
|
272
|
+
return { Success: true, Results: [{ ID: 'c0c0c0c0-0000-4000-8000-0000000000a1', StepType: 'Prompt', TargetLogID: promptRunID }] };
|
|
273
|
+
}
|
|
274
|
+
return { Success: true, Results: [] };
|
|
275
|
+
});
|
|
276
|
+
const runViews = vi.fn(async (paramsList: Array<{ EntityName?: string }>) => Promise.all(paramsList.map((params) => runView(params))));
|
|
277
|
+
return { RunView: runView, RunViews: runViews };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** Provider whose GetEntityObject returns a fixed entity per entity name, plus optional run-id lookups. */
|
|
281
|
+
function makeProvider(factory: (entityName: string) => FakeSession, lookups?: RunLookups): unknown {
|
|
282
|
+
return { GetEntityObject: vi.fn(async (name: string) => factory(name)), ...lookups };
|
|
248
283
|
}
|
|
249
284
|
|
|
250
285
|
beforeEach(() => {
|
|
251
286
|
hasPermissionMock.mockReset();
|
|
287
|
+
// Callers may run the agents in play unless a test says otherwise; relays re-check CanRun on the target.
|
|
288
|
+
hasPermissionMock.mockResolvedValue(true);
|
|
252
289
|
engineCoAgentsMock.mockReset();
|
|
253
290
|
engineCoAgentsMock.mockReturnValue([]);
|
|
254
291
|
engineChannelsMock.mockReset();
|
|
@@ -390,6 +427,38 @@ describe('RealtimeClientSessionResolver.StartRealtimeClientSession', () => {
|
|
|
390
427
|
expect(prepArg.PreferredModelID).toBeUndefined();
|
|
391
428
|
expect(result.ModelName).toBeUndefined();
|
|
392
429
|
expect(result.NarrationInstructionsTemplate).toBeUndefined();
|
|
430
|
+
// A direct session says nothing about the transport.
|
|
431
|
+
expect(result.Transport).toBeUndefined();
|
|
432
|
+
expect(result.RelayUrl).toBeUndefined();
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
it("returns a relay session's transport and relay URL as minted, with no token, and logs neither", async () => {
|
|
436
|
+
const ticket = '7d1e0c2a-5555-4666-8777-988899990000';
|
|
437
|
+
const relayUrl = `wss://mjapi.example.test/realtime/relay/${ticket}`;
|
|
438
|
+
hasPermissionMock.mockResolvedValue(true);
|
|
439
|
+
currentProvider = makeProvider(() => makeSessionEntity());
|
|
440
|
+
createSessionMock.mockResolvedValue(makeSessionEntity({ ID: 'session-relay' }));
|
|
441
|
+
prepareClientSessionMock.mockResolvedValue({
|
|
442
|
+
Success: true,
|
|
443
|
+
ClientConfig: {
|
|
444
|
+
Provider: 'gemini-enterprise',
|
|
445
|
+
Model: 'gemini-3.8-live',
|
|
446
|
+
EphemeralToken: '',
|
|
447
|
+
ExpiresAt: '2026-01-01T00:00:00Z',
|
|
448
|
+
Transport: 'relay',
|
|
449
|
+
RelayUrl: relayUrl,
|
|
450
|
+
SessionConfig: { model: 'gemini-3.8-live' },
|
|
451
|
+
},
|
|
452
|
+
});
|
|
453
|
+
const consoleSpies = (['log', 'info', 'warn', 'error', 'debug'] as const).map((method) => vi.spyOn(console, method));
|
|
454
|
+
|
|
455
|
+
const result = await makeResolver().StartRealtimeClientSession('target-1', makeCtx());
|
|
456
|
+
|
|
457
|
+
expect(result).toMatchObject({ Provider: 'gemini-enterprise', EphemeralToken: '', Transport: 'relay', RelayUrl: relayUrl });
|
|
458
|
+
expect(result.SessionConfigJson).not.toContain(ticket);
|
|
459
|
+
const logged = consoleSpies.flatMap((spy) => spy.mock.calls.map((call: unknown[]) => call.map(String).join(' ')));
|
|
460
|
+
expect(logged.filter((line) => line.includes(ticket))).toEqual([]);
|
|
461
|
+
consoleSpies.forEach((spy) => spy.mockRestore());
|
|
393
462
|
});
|
|
394
463
|
|
|
395
464
|
it('propagates the preferred-model failure (and closes the session) — no silent fallback', async () => {
|
|
@@ -685,10 +754,12 @@ describe('RealtimeClientSessionResolver.ExecuteRealtimeSessionTool', () => {
|
|
|
685
754
|
});
|
|
686
755
|
|
|
687
756
|
it('reads the target + co-agent run id from the session config and returns ResultJson', async () => {
|
|
688
|
-
currentProvider = makeProvider(
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
757
|
+
currentProvider = makeProvider(
|
|
758
|
+
() =>
|
|
759
|
+
makeSessionEntity({
|
|
760
|
+
Config_: JSON.stringify({ targetAgentID: 'target-from-session', coAgentRunID: CO_RUN_ID }),
|
|
761
|
+
}),
|
|
762
|
+
sessionRunLookups([CO_RUN_ID]),
|
|
692
763
|
);
|
|
693
764
|
executeRelayedToolMock.mockResolvedValue({ ResultJson: '{"ok":true}', Success: true });
|
|
694
765
|
const resolver = makeResolver();
|
|
@@ -711,7 +782,7 @@ describe('RealtimeClientSessionResolver.ExecuteRealtimeSessionTool', () => {
|
|
|
711
782
|
// Target comes from the session, NOT the client.
|
|
712
783
|
expect(relayArg.TargetAgentID).toBe('target-from-session');
|
|
713
784
|
// Delegated run nests under the co-agent observability run from the session config.
|
|
714
|
-
expect(relayArg.ParentRunID).toBe(
|
|
785
|
+
expect(relayArg.ParentRunID).toBe(CO_RUN_ID);
|
|
715
786
|
expect(relayArg.Call).toEqual({ CallID: 'call-1', ToolName: 'invoke-target-agent', Arguments: '{"request":"do it"}' });
|
|
716
787
|
expect(heartbeatMock).toHaveBeenCalledWith('session-1', USER, currentProvider);
|
|
717
788
|
});
|
|
@@ -775,9 +846,9 @@ describe('RealtimeClientSessionResolver.ExecuteRealtimeSessionTool', () => {
|
|
|
775
846
|
|
|
776
847
|
it('consumes pendingFeedbackRunID as ResumeRunID and clears it on the next call', async () => {
|
|
777
848
|
const session = makeSessionEntity({
|
|
778
|
-
Config_: JSON.stringify({ targetAgentID: 'target-1', pendingFeedbackRunID:
|
|
849
|
+
Config_: JSON.stringify({ targetAgentID: 'target-1', pendingFeedbackRunID: PAUSED_RUN_ID }),
|
|
779
850
|
});
|
|
780
|
-
currentProvider = makeProvider(() => session);
|
|
851
|
+
currentProvider = makeProvider(() => session, sessionRunLookups([PAUSED_RUN_ID]));
|
|
781
852
|
// Resumed run completes (no new pause).
|
|
782
853
|
executeRelayedToolMock.mockResolvedValue({ ResultJson: '{"done":1}', Success: true });
|
|
783
854
|
const resolver = makeResolver();
|
|
@@ -786,7 +857,7 @@ describe('RealtimeClientSessionResolver.ExecuteRealtimeSessionTool', () => {
|
|
|
786
857
|
|
|
787
858
|
// The paused id was passed in to resume the run...
|
|
788
859
|
const relayArg = executeRelayedToolMock.mock.calls[0][0] as { ResumeRunID?: string };
|
|
789
|
-
expect(relayArg.ResumeRunID).toBe(
|
|
860
|
+
expect(relayArg.ResumeRunID).toBe(PAUSED_RUN_ID);
|
|
790
861
|
// ...and cleared from the session config afterward.
|
|
791
862
|
expect(JSON.parse(session.Config_ as string).pendingFeedbackRunID).toBeUndefined();
|
|
792
863
|
expect(session.Save).toHaveBeenCalled();
|
|
@@ -794,16 +865,16 @@ describe('RealtimeClientSessionResolver.ExecuteRealtimeSessionTool', () => {
|
|
|
794
865
|
|
|
795
866
|
it('re-stores a new pendingFeedbackRunID when a resumed run pauses again', async () => {
|
|
796
867
|
const session = makeSessionEntity({
|
|
797
|
-
Config_: JSON.stringify({ targetAgentID: 'target-1', pendingFeedbackRunID:
|
|
868
|
+
Config_: JSON.stringify({ targetAgentID: 'target-1', pendingFeedbackRunID: PAUSED_RUN_ID }),
|
|
798
869
|
});
|
|
799
|
-
currentProvider = makeProvider(() => session);
|
|
870
|
+
currentProvider = makeProvider(() => session, sessionRunLookups([PAUSED_RUN_ID]));
|
|
800
871
|
executeRelayedToolMock.mockResolvedValue({ ResultJson: '{"q":2}', Success: true, PausedRunID: 'paused-2' });
|
|
801
872
|
const resolver = makeResolver();
|
|
802
873
|
|
|
803
874
|
await resolver.ExecuteRealtimeSessionTool('session-1', 'call-3', 'invoke-target-agent', '{"request":"yes"}', makeCtx(), makePubSub());
|
|
804
875
|
|
|
805
876
|
const relayArg = executeRelayedToolMock.mock.calls[0][0] as { ResumeRunID?: string };
|
|
806
|
-
expect(relayArg.ResumeRunID).toBe(
|
|
877
|
+
expect(relayArg.ResumeRunID).toBe(PAUSED_RUN_ID);
|
|
807
878
|
expect(JSON.parse(session.Config_ as string).pendingFeedbackRunID).toBe('paused-2');
|
|
808
879
|
});
|
|
809
880
|
|
|
@@ -908,6 +979,26 @@ describe('RealtimeClientSessionResolver.RelayRealtimeTranscript', () => {
|
|
|
908
979
|
expect(heartbeatMock).toHaveBeenCalledWith('session-1', USER, currentProvider);
|
|
909
980
|
});
|
|
910
981
|
|
|
982
|
+
it("writes the turn on the session's fork", async () => {
|
|
983
|
+
const detail = makeSessionEntity({ ID: 'detail-t' });
|
|
984
|
+
const session = makeSessionEntity({ ConversationID: 'conv-7', Config_: JSON.stringify({ targetAgentID: 'target-1', conversationBranchID: 'b1' }) });
|
|
985
|
+
currentProvider = makeProvider((name) => (name === 'MJ: Conversation Details' ? detail : session));
|
|
986
|
+
|
|
987
|
+
await makeResolver().RelayRealtimeTranscript('session-1', 'user', 'hello', makeCtx());
|
|
988
|
+
|
|
989
|
+
expect(detail.BranchID).toBe('b1');
|
|
990
|
+
});
|
|
991
|
+
|
|
992
|
+
it('writes the turn on Main when the session has no fork', async () => {
|
|
993
|
+
const detail = makeSessionEntity({ ID: 'detail-m' });
|
|
994
|
+
const session = makeSessionEntity({ ConversationID: 'conv-7' });
|
|
995
|
+
currentProvider = makeProvider((name) => (name === 'MJ: Conversation Details' ? detail : session));
|
|
996
|
+
|
|
997
|
+
await makeResolver().RelayRealtimeTranscript('session-1', 'user', 'hello', makeCtx());
|
|
998
|
+
|
|
999
|
+
expect(detail.BranchID).toBeNull();
|
|
1000
|
+
});
|
|
1001
|
+
|
|
911
1002
|
it("maps a non-user role to 'AI'", async () => {
|
|
912
1003
|
const detail = makeSessionEntity({ ID: 'detail-2' });
|
|
913
1004
|
const session = makeSessionEntity();
|
|
@@ -2292,10 +2383,11 @@ describe('RealtimeClientSessionResolver.RelayRealtimeUsage', () => {
|
|
|
2292
2383
|
}): { provider: unknown; promptRun: FakeSession } {
|
|
2293
2384
|
const session =
|
|
2294
2385
|
opts.session ??
|
|
2295
|
-
makeSessionEntity({ Config_: JSON.stringify({ targetAgentID: 'target-1', promptRunID:
|
|
2296
|
-
const promptRun = opts.promptRun ?? makeSessionEntity({ ID:
|
|
2386
|
+
makeSessionEntity({ Config_: JSON.stringify({ targetAgentID: 'target-1', coAgentRunID: CO_RUN_ID, promptRunID: PROMPT_RUN_ID }) });
|
|
2387
|
+
const promptRun = opts.promptRun ?? makeSessionEntity({ ID: PROMPT_RUN_ID, TokensPrompt: null, TokensCompletion: null, TokensUsed: null });
|
|
2297
2388
|
const provider = {
|
|
2298
2389
|
GetEntityObject: vi.fn(async (name: string) => (name === 'MJ: AI Prompt Runs' ? promptRun : session)),
|
|
2390
|
+
...sessionRunLookups([CO_RUN_ID], PROMPT_RUN_ID),
|
|
2299
2391
|
};
|
|
2300
2392
|
return { provider, promptRun };
|
|
2301
2393
|
}
|
|
@@ -2321,7 +2413,7 @@ describe('RealtimeClientSessionResolver.RelayRealtimeUsage', () => {
|
|
|
2321
2413
|
const ok = await resolver.RelayRealtimeUsage('session-1', 30, 5, makeCtx());
|
|
2322
2414
|
|
|
2323
2415
|
expect(ok).toBe(true);
|
|
2324
|
-
expect(promptRun.Load).toHaveBeenCalledWith(
|
|
2416
|
+
expect(promptRun.Load).toHaveBeenCalledWith(PROMPT_RUN_ID);
|
|
2325
2417
|
expect(promptRun.TokensPrompt).toBe(130);
|
|
2326
2418
|
expect(promptRun.TokensCompletion).toBe(45);
|
|
2327
2419
|
expect(promptRun.TokensUsed).toBe(175);
|
|
@@ -2418,7 +2510,7 @@ describe('RealtimeClientSessionResolver.RelayRealtimeUsage', () => {
|
|
|
2418
2510
|
const { provider, promptRun } = makeUsageProvider({
|
|
2419
2511
|
session: makeSessionEntity({
|
|
2420
2512
|
Status: 'Closed',
|
|
2421
|
-
Config_: JSON.stringify({ targetAgentID: 'target-1', promptRunID:
|
|
2513
|
+
Config_: JSON.stringify({ targetAgentID: 'target-1', coAgentRunID: CO_RUN_ID, promptRunID: PROMPT_RUN_ID }),
|
|
2422
2514
|
}),
|
|
2423
2515
|
});
|
|
2424
2516
|
currentProvider = provider;
|
|
@@ -2431,6 +2523,69 @@ describe('RealtimeClientSessionResolver.RelayRealtimeUsage', () => {
|
|
|
2431
2523
|
expect(promptRun.TokensCompletion).toBe(3);
|
|
2432
2524
|
expect(promptRun.TokensUsed).toBe(12);
|
|
2433
2525
|
});
|
|
2526
|
+
|
|
2527
|
+
describe('per-modality details (usageDetailsJson)', () => {
|
|
2528
|
+
/** The usage record the run's details hold after the relay. */
|
|
2529
|
+
function storedRecord(promptRun: FakeSession): unknown {
|
|
2530
|
+
const details = promptRun['ModelSpecificResponseDetails'];
|
|
2531
|
+
return typeof details === 'string' ? (JSON.parse(details) as Record<string, unknown>)['RealtimeUsage'] : undefined;
|
|
2532
|
+
}
|
|
2533
|
+
|
|
2534
|
+
it('adds the relayed blocks into the usage record on the prompt run', async () => {
|
|
2535
|
+
const { provider, promptRun } = makeUsageProvider({});
|
|
2536
|
+
currentProvider = provider;
|
|
2537
|
+
const details = JSON.stringify({ Input: { TextTokens: 60, AudioTokens: 40 }, Output: { AudioTokens: 100, VideoTokens: 6192, VideoSeconds: 1.5 } });
|
|
2538
|
+
|
|
2539
|
+
const ok = await makeResolver().RelayRealtimeUsage('session-1', 100, 6292, makeCtx(), details);
|
|
2540
|
+
|
|
2541
|
+
expect(ok).toBe(true);
|
|
2542
|
+
expect(promptRun.TokensCompletion).toBe(6292);
|
|
2543
|
+
expect(storedRecord(promptRun)).toEqual({ Input: { TextTokens: 60, AudioTokens: 40 }, Output: { AudioTokens: 100, VideoTokens: 6192, VideoSeconds: 1.5 } });
|
|
2544
|
+
});
|
|
2545
|
+
|
|
2546
|
+
it('stores a relay that carries only avatar video seconds, rather than treating it as a no-op', async () => {
|
|
2547
|
+
const { provider, promptRun } = makeUsageProvider({});
|
|
2548
|
+
currentProvider = provider;
|
|
2549
|
+
|
|
2550
|
+
const ok = await makeResolver().RelayRealtimeUsage('session-1', 0, 0, makeCtx(), JSON.stringify({ Output: { VideoSeconds: 2.25 } }));
|
|
2551
|
+
|
|
2552
|
+
expect(ok).toBe(true);
|
|
2553
|
+
expect(promptRun.Save).toHaveBeenCalled();
|
|
2554
|
+
expect(storedRecord(promptRun)).toEqual({ Output: { VideoSeconds: 2.25 } });
|
|
2555
|
+
});
|
|
2556
|
+
|
|
2557
|
+
it('is a no-op for details with nothing in them and no tokens', async () => {
|
|
2558
|
+
const { provider, promptRun } = makeUsageProvider({});
|
|
2559
|
+
currentProvider = provider;
|
|
2560
|
+
|
|
2561
|
+
for (const details of ['{"Output":{"VideoSeconds":0}}', '{broken', '{"Output":{"Bogus":5}}']) {
|
|
2562
|
+
await expect(makeResolver().RelayRealtimeUsage('session-1', 0, 0, makeCtx(), details)).resolves.toBe(true);
|
|
2563
|
+
}
|
|
2564
|
+
expect(promptRun.Load).not.toHaveBeenCalled();
|
|
2565
|
+
});
|
|
2566
|
+
|
|
2567
|
+
it('drops details longer than a relay ever needs, and still stores the tokens', async () => {
|
|
2568
|
+
const { provider, promptRun } = makeUsageProvider({});
|
|
2569
|
+
currentProvider = provider;
|
|
2570
|
+
const oversized = JSON.stringify({ Output: { VideoSeconds: 2 }, Padding: 'x'.repeat(5000) });
|
|
2571
|
+
|
|
2572
|
+
const ok = await makeResolver().RelayRealtimeUsage('session-1', 10, 5, makeCtx(), oversized);
|
|
2573
|
+
|
|
2574
|
+
expect(ok).toBe(true);
|
|
2575
|
+
expect(promptRun.TokensPrompt).toBe(10);
|
|
2576
|
+
expect(storedRecord(promptRun)).toBeUndefined();
|
|
2577
|
+
});
|
|
2578
|
+
|
|
2579
|
+
it('keeps only the fields a block defines, with usable values', async () => {
|
|
2580
|
+
const { provider, promptRun } = makeUsageProvider({});
|
|
2581
|
+
currentProvider = provider;
|
|
2582
|
+
const details = JSON.stringify({ Output: { VideoSeconds: -3, AudioTokens: 'lots', VideoTokens: 12, Injected: 1 }, Other: { TextTokens: 1 } });
|
|
2583
|
+
|
|
2584
|
+
await makeResolver().RelayRealtimeUsage('session-1', 1, 1, makeCtx(), details);
|
|
2585
|
+
|
|
2586
|
+
expect(storedRecord(promptRun)).toEqual({ Output: { VideoTokens: 12 } });
|
|
2587
|
+
});
|
|
2588
|
+
});
|
|
2434
2589
|
});
|
|
2435
2590
|
|
|
2436
2591
|
describe('RealtimeClientSessionResolver.StartRealtimeClientSession — pairing constraints (MJ: AI Agent Co Agents)', () => {
|
|
@@ -2815,7 +2970,7 @@ describe('RealtimeClientSessionResolver — app awareness (applicationId / appCo
|
|
|
2815
2970
|
expect(relayArg.AllowedAgents?.map(a => a.agentId)).toEqual(['skip-1']);
|
|
2816
2971
|
});
|
|
2817
2972
|
|
|
2818
|
-
it('
|
|
2973
|
+
it('does not pass the directActions stored in the session config into ExecuteRelayedTool', async () => {
|
|
2819
2974
|
currentProvider = makeProvider(() =>
|
|
2820
2975
|
makeSessionEntity({
|
|
2821
2976
|
Config_: JSON.stringify({
|
|
@@ -2832,7 +2987,167 @@ describe('RealtimeClientSessionResolver — app awareness (applicationId / appCo
|
|
|
2832
2987
|
);
|
|
2833
2988
|
|
|
2834
2989
|
const relayArg = executeRelayedToolMock.mock.calls[0][0] as { DirectActions?: { enabled: boolean; actionNames: string[] } };
|
|
2835
|
-
|
|
2990
|
+
// The owner can edit Config, so the service takes direct actions from server-side state instead.
|
|
2991
|
+
expect(relayArg.DirectActions).toBeUndefined();
|
|
2992
|
+
});
|
|
2993
|
+
});
|
|
2994
|
+
|
|
2995
|
+
describe('RealtimeClientSessionResolver — delegated runs get the browser session and app context', () => {
|
|
2996
|
+
const appContext = {
|
|
2997
|
+
App: { Name: 'Dashboards', Description: '' },
|
|
2998
|
+
ActiveNavItem: { Name: 'Sales Overview' },
|
|
2999
|
+
OtherNavItems: [],
|
|
3000
|
+
User: { Name: 'A', Roles: [] },
|
|
3001
|
+
Capabilities: { Tools: [{ Name: 'AddPanel', Description: 'Adds a panel', InputSchema: {} }] },
|
|
3002
|
+
};
|
|
3003
|
+
/** Mirrors the resolver's MAX_CLIENT_TOOLS_JSON_CHARS, the cap on a stored app-context snapshot. */
|
|
3004
|
+
const APP_CONTEXT_CAP_CHARS = 256_000;
|
|
3005
|
+
|
|
3006
|
+
beforeEach(() => {
|
|
3007
|
+
// The resolver logs each relayed tool dispatch to the console; keep test output clean.
|
|
3008
|
+
vi.spyOn(console, 'log').mockImplementation(() => undefined);
|
|
3009
|
+
});
|
|
3010
|
+
|
|
3011
|
+
/** Starts a session whose browser sent `appContextJson` and returns the created session entity. */
|
|
3012
|
+
async function startWithAppContext(appContextJson: string): Promise<FakeSession> {
|
|
3013
|
+
hasPermissionMock.mockResolvedValue(true);
|
|
3014
|
+
currentProvider = makeProvider(() => makeSessionEntity());
|
|
3015
|
+
const createdSession = makeSessionEntity({ ID: 'session-app' });
|
|
3016
|
+
createSessionMock.mockResolvedValue(createdSession);
|
|
3017
|
+
prepareClientSessionMock.mockResolvedValue({
|
|
3018
|
+
Success: true,
|
|
3019
|
+
ClientConfig: { Provider: 'openai', Model: 'm', EphemeralToken: 'ek', ExpiresAt: '2099-01-01T00:00:00Z', SessionConfig: {} },
|
|
3020
|
+
});
|
|
3021
|
+
await makeResolver().StartRealtimeClientSession(
|
|
3022
|
+
'target-1', makeCtx(),
|
|
3023
|
+
undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined,
|
|
3024
|
+
'app-9', appContextJson,
|
|
3025
|
+
);
|
|
3026
|
+
return createdSession;
|
|
3027
|
+
}
|
|
3028
|
+
|
|
3029
|
+
/** An app-context snapshot whose JSON is exactly `chars` long, padded through AdditionalContext. */
|
|
3030
|
+
function snapshotOfLength(chars: number): typeof appContext & { AdditionalContext: { Padding: string } } {
|
|
3031
|
+
const unpadded = { ...appContext, AdditionalContext: { Padding: '' } };
|
|
3032
|
+
return { ...appContext, AdditionalContext: { Padding: 'x'.repeat(chars - JSON.stringify(unpadded).length) } };
|
|
3033
|
+
}
|
|
3034
|
+
|
|
3035
|
+
it('stores the parsed appContext on the session config in the post-prepare write', async () => {
|
|
3036
|
+
const session = await startWithAppContext(JSON.stringify(appContext));
|
|
3037
|
+
|
|
3038
|
+
expect(JSON.parse(session.Config_ as string).appContext).toEqual(appContext);
|
|
3039
|
+
});
|
|
3040
|
+
|
|
3041
|
+
it('stores an appContext whose JSON is exactly at the size cap', async () => {
|
|
3042
|
+
const atCap = snapshotOfLength(APP_CONTEXT_CAP_CHARS);
|
|
3043
|
+
expect(JSON.stringify(atCap)).toHaveLength(APP_CONTEXT_CAP_CHARS);
|
|
3044
|
+
|
|
3045
|
+
const session = await startWithAppContext(JSON.stringify(atCap));
|
|
3046
|
+
|
|
3047
|
+
expect(JSON.parse(session.Config_ as string).appContext).toEqual(atCap);
|
|
3048
|
+
});
|
|
3049
|
+
|
|
3050
|
+
it('stores no appContext over the size cap and logs a warning', async () => {
|
|
3051
|
+
errSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
|
|
3052
|
+
|
|
3053
|
+
const session = await startWithAppContext(JSON.stringify(snapshotOfLength(APP_CONTEXT_CAP_CHARS + 1)));
|
|
3054
|
+
|
|
3055
|
+
const persisted = JSON.parse(session.Config_ as string) as { targetAgentID?: string; appContext?: unknown };
|
|
3056
|
+
expect(persisted.targetAgentID).toBe('target-1');
|
|
3057
|
+
expect(persisted.appContext).toBeUndefined();
|
|
3058
|
+
const warning = errSpy.mock.calls.map((call) => String(call[0])).find((line) => line.includes('app context'));
|
|
3059
|
+
expect(warning).toContain('[WARNING]');
|
|
3060
|
+
expect(warning).toContain('session-app');
|
|
3061
|
+
expect(warning).toContain(String(APP_CONTEXT_CAP_CHARS));
|
|
3062
|
+
});
|
|
3063
|
+
|
|
3064
|
+
it('passes the browser session id and the stored appContext into ExecuteRelayedTool', async () => {
|
|
3065
|
+
currentProvider = makeProvider(() =>
|
|
3066
|
+
makeSessionEntity({ Config_: JSON.stringify({ targetAgentID: 'target-1', appContext }) }),
|
|
3067
|
+
);
|
|
3068
|
+
executeRelayedToolMock.mockResolvedValue({ ResultJson: '{"ok":true}', Success: true });
|
|
3069
|
+
|
|
3070
|
+
await makeResolver().ExecuteRealtimeSessionTool(
|
|
3071
|
+
'session-1', 'call-1', 'invoke-target-agent', '{"request":"x"}', makeCtx(), makePubSub(),
|
|
3072
|
+
);
|
|
3073
|
+
|
|
3074
|
+
const relayArg = executeRelayedToolMock.mock.calls[0][0] as { BrowserSessionID?: string; AppContext?: unknown };
|
|
3075
|
+
// The client-tool channel is keyed on the caller's GraphQL session id (x-session-id).
|
|
3076
|
+
expect(relayArg.BrowserSessionID).toBe('pubsub-session-1');
|
|
3077
|
+
expect(relayArg.AppContext).toEqual(appContext);
|
|
3078
|
+
});
|
|
3079
|
+
|
|
3080
|
+
it('passes no AppContext when the session config has none', async () => {
|
|
3081
|
+
currentProvider = makeProvider(() => makeSessionEntity({ Config_: JSON.stringify({ targetAgentID: 'target-1' }) }));
|
|
3082
|
+
executeRelayedToolMock.mockResolvedValue({ ResultJson: '{"ok":true}', Success: true });
|
|
3083
|
+
|
|
3084
|
+
await makeResolver().ExecuteRealtimeSessionTool('session-1', 'call-1', 'invoke-target-agent', '{}', makeCtx(), makePubSub());
|
|
3085
|
+
|
|
3086
|
+
const relayArg = executeRelayedToolMock.mock.calls[0][0] as { BrowserSessionID?: string; AppContext?: unknown };
|
|
3087
|
+
expect(relayArg.AppContext).toBeUndefined();
|
|
3088
|
+
expect(relayArg.BrowserSessionID).toBe('pubsub-session-1');
|
|
3089
|
+
});
|
|
3090
|
+
|
|
3091
|
+
it('ignores a stored appContext that is not an object', async () => {
|
|
3092
|
+
currentProvider = makeProvider(() =>
|
|
3093
|
+
makeSessionEntity({ Config_: JSON.stringify({ targetAgentID: 'target-1', appContext: 'not-an-object' }) }),
|
|
3094
|
+
);
|
|
3095
|
+
executeRelayedToolMock.mockResolvedValue({ ResultJson: '{"ok":true}', Success: true });
|
|
3096
|
+
|
|
3097
|
+
await makeResolver().ExecuteRealtimeSessionTool('session-1', 'call-1', 'invoke-target-agent', '{}', makeCtx(), makePubSub());
|
|
3098
|
+
|
|
3099
|
+
const relayArg = executeRelayedToolMock.mock.calls[0][0] as { AppContext?: unknown };
|
|
3100
|
+
expect(relayArg.AppContext).toBeUndefined();
|
|
3101
|
+
});
|
|
3102
|
+
|
|
3103
|
+
it('passes no AppContext when the run is elevated to the system user for a scoped-anonymous caller', async () => {
|
|
3104
|
+
const systemUser = { ID: 'system-1', Email: 'system@system.org' } as UserInfo;
|
|
3105
|
+
const anonymousCaller = {
|
|
3106
|
+
ID: 'anon-1',
|
|
3107
|
+
Email: 'anonymous@magic-link.local',
|
|
3108
|
+
IsMagicLinkAnonymous: true,
|
|
3109
|
+
MagicLinkScope: { ResourceID: 'scope-res-1' },
|
|
3110
|
+
} as UserInfo;
|
|
3111
|
+
/** A resolver whose caller is a scoped anonymous magic-link visitor. */
|
|
3112
|
+
class AnonymousCallerResolver extends RealtimeClientSessionResolver {
|
|
3113
|
+
protected override GetUserFromPayload(): UserInfo | undefined {
|
|
3114
|
+
return anonymousCaller;
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
3117
|
+
getSystemUserMock.mockReturnValue(systemUser);
|
|
3118
|
+
currentProvider = makeProvider(() =>
|
|
3119
|
+
makeSessionEntity({ UserID: 'anon-1', Config_: JSON.stringify({ targetAgentID: 'target-1', appContext }) }),
|
|
3120
|
+
);
|
|
3121
|
+
executeRelayedToolMock.mockResolvedValue({ ResultJson: '{"ok":true}', Success: true });
|
|
3122
|
+
|
|
3123
|
+
await new AnonymousCallerResolver().ExecuteRealtimeSessionTool(
|
|
3124
|
+
'session-1', 'call-1', 'invoke-target-agent', '{}', makeCtx(), makePubSub(),
|
|
3125
|
+
);
|
|
3126
|
+
|
|
3127
|
+
expect(executeRelayedToolMock.mock.calls[0][1]).toBe(systemUser);
|
|
3128
|
+
const relayArg = executeRelayedToolMock.mock.calls[0][0] as { BrowserSessionID?: string; AppContext?: unknown };
|
|
3129
|
+
expect(relayArg.AppContext).toBeUndefined();
|
|
3130
|
+
expect(relayArg.BrowserSessionID).toBe('pubsub-session-1');
|
|
3131
|
+
});
|
|
3132
|
+
|
|
3133
|
+
it('keeps appContext and directActions when a paused run rewrites the session config', async () => {
|
|
3134
|
+
const directActions = { enabled: true, actionNames: ['SendEmail'] };
|
|
3135
|
+
const session = makeSessionEntity({
|
|
3136
|
+
Config_: JSON.stringify({ targetAgentID: 'target-1', appContext, directActions }),
|
|
3137
|
+
});
|
|
3138
|
+
currentProvider = makeProvider(() => session);
|
|
3139
|
+
executeRelayedToolMock.mockResolvedValue({ ResultJson: '{"q":1}', Success: true, PausedRunID: 'paused-1' });
|
|
3140
|
+
|
|
3141
|
+
await makeResolver().ExecuteRealtimeSessionTool('session-1', 'call-1', 'invoke-target-agent', '{}', makeCtx(), makePubSub());
|
|
3142
|
+
|
|
3143
|
+
const persisted = JSON.parse(session.Config_ as string) as {
|
|
3144
|
+
pendingFeedbackRunID?: string;
|
|
3145
|
+
appContext?: unknown;
|
|
3146
|
+
directActions?: unknown;
|
|
3147
|
+
};
|
|
3148
|
+
expect(persisted.pendingFeedbackRunID).toBe('paused-1');
|
|
3149
|
+
expect(persisted.appContext).toEqual(appContext);
|
|
3150
|
+
expect(persisted.directActions).toEqual(directActions);
|
|
2836
3151
|
});
|
|
2837
3152
|
});
|
|
2838
3153
|
|
|
@@ -2925,7 +3240,7 @@ describe('RealtimeClientSessionResolver — scoped-anonymous elevation (issue #3
|
|
|
2925
3240
|
expect(executeRelayedToolMock.mock.calls[0][1]).toBe(ANON_USER);
|
|
2926
3241
|
});
|
|
2927
3242
|
|
|
2928
|
-
it('
|
|
3243
|
+
it('refuses a direct action from a scoped anonymous caller without dispatching it or writing a turn as the system user (#4791)', async () => {
|
|
2929
3244
|
const detail = makeSessionEntity({ ID: 'detail-1' });
|
|
2930
3245
|
currentProvider = {
|
|
2931
3246
|
GetEntityObject: vi.fn(async (name: string) =>
|
|
@@ -2935,17 +3250,16 @@ describe('RealtimeClientSessionResolver — scoped-anonymous elevation (issue #3
|
|
|
2935
3250
|
executeRelayedToolMock.mockResolvedValue({ ResultJson: '{"success":true}', Success: true });
|
|
2936
3251
|
const resolver = makeAnonResolver();
|
|
2937
3252
|
|
|
2938
|
-
await resolver.ExecuteRealtimeSessionTool(
|
|
3253
|
+
const out = await resolver.ExecuteRealtimeSessionTool(
|
|
2939
3254
|
'session-1', 'call-1', 'Some Direct Action', '{}', makeCtx(), makePubSub(),
|
|
2940
3255
|
);
|
|
2941
3256
|
|
|
2942
|
-
//
|
|
2943
|
-
expect(
|
|
2944
|
-
|
|
2945
|
-
|
|
3257
|
+
// Scope-limited callers get no direct actions, so nothing is dispatched, elevated or not…
|
|
3258
|
+
expect(JSON.parse(out)).toMatchObject({ success: false });
|
|
3259
|
+
expect(executeRelayedToolMock).not.toHaveBeenCalled();
|
|
3260
|
+
// …and no hidden Conversation Detail turn is written, least of all as the system user.
|
|
2946
3261
|
expect(getEntityObjectSpy()).not.toHaveBeenCalledWith('MJ: Conversation Details', SYSTEM_USER);
|
|
2947
|
-
expect(detail.Save).toHaveBeenCalled();
|
|
2948
|
-
expect(detail.UserID).toBe('anon-1');
|
|
3262
|
+
expect(detail.Save).not.toHaveBeenCalled();
|
|
2949
3263
|
});
|
|
2950
3264
|
|
|
2951
3265
|
it('writes the direct-action tool turn as the caller unchanged for a normal authenticated user', async () => {
|
|
@@ -2966,11 +3280,12 @@ describe('RealtimeClientSessionResolver — scoped-anonymous elevation (issue #3
|
|
|
2966
3280
|
it('accumulates relayed usage onto the prompt run under the SYSTEM user', async () => {
|
|
2967
3281
|
const session = makeSessionEntity({
|
|
2968
3282
|
UserID: 'anon-1',
|
|
2969
|
-
Config_: JSON.stringify({ targetAgentID: 'target-1', promptRunID:
|
|
3283
|
+
Config_: JSON.stringify({ targetAgentID: 'target-1', coAgentRunID: CO_RUN_ID, promptRunID: PROMPT_RUN_ID }),
|
|
2970
3284
|
});
|
|
2971
|
-
const promptRun = makeSessionEntity({ ID:
|
|
3285
|
+
const promptRun = makeSessionEntity({ ID: PROMPT_RUN_ID, TokensPrompt: null, TokensCompletion: null, TokensUsed: null });
|
|
2972
3286
|
currentProvider = {
|
|
2973
3287
|
GetEntityObject: vi.fn(async (name: string) => (name === 'MJ: AI Prompt Runs' ? promptRun : session)),
|
|
3288
|
+
...sessionRunLookups([CO_RUN_ID], PROMPT_RUN_ID),
|
|
2974
3289
|
};
|
|
2975
3290
|
const resolver = makeAnonResolver();
|
|
2976
3291
|
|
|
@@ -2985,9 +3300,9 @@ describe('RealtimeClientSessionResolver — scoped-anonymous elevation (issue #3
|
|
|
2985
3300
|
it('mirrors the transcript turn onto the prompt run as SYSTEM while the Conversation Detail stays the caller', async () => {
|
|
2986
3301
|
const session = makeSessionEntity({
|
|
2987
3302
|
UserID: 'anon-1',
|
|
2988
|
-
Config_: JSON.stringify({ targetAgentID: 'target-1', promptRunID:
|
|
3303
|
+
Config_: JSON.stringify({ targetAgentID: 'target-1', coAgentRunID: CO_RUN_ID, promptRunID: PROMPT_RUN_ID }),
|
|
2989
3304
|
});
|
|
2990
|
-
const promptRun = makeSessionEntity({ ID:
|
|
3305
|
+
const promptRun = makeSessionEntity({ ID: PROMPT_RUN_ID, Messages: null });
|
|
2991
3306
|
const detail = makeSessionEntity({ ID: 'detail-1' });
|
|
2992
3307
|
currentProvider = {
|
|
2993
3308
|
GetEntityObject: vi.fn(async (name: string) => {
|
|
@@ -2995,6 +3310,7 @@ describe('RealtimeClientSessionResolver — scoped-anonymous elevation (issue #3
|
|
|
2995
3310
|
if (name === 'MJ: Conversation Details') return detail;
|
|
2996
3311
|
return session;
|
|
2997
3312
|
}),
|
|
3313
|
+
...sessionRunLookups([CO_RUN_ID], PROMPT_RUN_ID),
|
|
2998
3314
|
};
|
|
2999
3315
|
const resolver = makeAnonResolver();
|
|
3000
3316
|
|
|
@@ -3011,11 +3327,12 @@ describe('RealtimeClientSessionResolver — scoped-anonymous elevation (issue #3
|
|
|
3011
3327
|
it('records the co-agent tool turn (RelayRealtimeToolTurn) under the SYSTEM user', async () => {
|
|
3012
3328
|
const session = makeSessionEntity({
|
|
3013
3329
|
UserID: 'anon-1',
|
|
3014
|
-
Config_: JSON.stringify({ targetAgentID: 'target-1', promptRunID:
|
|
3330
|
+
Config_: JSON.stringify({ targetAgentID: 'target-1', coAgentRunID: CO_RUN_ID, promptRunID: PROMPT_RUN_ID }),
|
|
3015
3331
|
});
|
|
3016
|
-
const promptRun = makeSessionEntity({ ID:
|
|
3332
|
+
const promptRun = makeSessionEntity({ ID: PROMPT_RUN_ID, Messages: null });
|
|
3017
3333
|
currentProvider = {
|
|
3018
3334
|
GetEntityObject: vi.fn(async (name: string) => (name === 'MJ: AI Prompt Runs' ? promptRun : session)),
|
|
3335
|
+
...sessionRunLookups([CO_RUN_ID], PROMPT_RUN_ID),
|
|
3019
3336
|
};
|
|
3020
3337
|
const resolver = makeAnonResolver();
|
|
3021
3338
|
|
|
@@ -3028,9 +3345,9 @@ describe('RealtimeClientSessionResolver — scoped-anonymous elevation (issue #3
|
|
|
3028
3345
|
it('writes the hidden co-agent tool turn (RelayRealtimeToolTurn) as the CALLER (#4791)', async () => {
|
|
3029
3346
|
const session = makeSessionEntity({
|
|
3030
3347
|
UserID: 'anon-1',
|
|
3031
|
-
Config_: JSON.stringify({ targetAgentID: 'target-1', promptRunID:
|
|
3348
|
+
Config_: JSON.stringify({ targetAgentID: 'target-1', coAgentRunID: CO_RUN_ID, promptRunID: PROMPT_RUN_ID }),
|
|
3032
3349
|
});
|
|
3033
|
-
const promptRun = makeSessionEntity({ ID:
|
|
3350
|
+
const promptRun = makeSessionEntity({ ID: PROMPT_RUN_ID, Messages: null });
|
|
3034
3351
|
const detail = makeSessionEntity({ ID: 'detail-3' });
|
|
3035
3352
|
currentProvider = {
|
|
3036
3353
|
GetEntityObject: vi.fn(async (name: string) => {
|
|
@@ -3038,6 +3355,7 @@ describe('RealtimeClientSessionResolver — scoped-anonymous elevation (issue #3
|
|
|
3038
3355
|
if (name === 'MJ: Conversation Details') return detail;
|
|
3039
3356
|
return session;
|
|
3040
3357
|
}),
|
|
3358
|
+
...sessionRunLookups([CO_RUN_ID], PROMPT_RUN_ID),
|
|
3041
3359
|
};
|
|
3042
3360
|
const resolver = makeAnonResolver();
|
|
3043
3361
|
|
|
@@ -3080,6 +3398,30 @@ describe('RealtimeClientSessionResolver — scoped-anonymous elevation (issue #3
|
|
|
3080
3398
|
expect(prepInput.UserID).toBe('anon-1');
|
|
3081
3399
|
});
|
|
3082
3400
|
|
|
3401
|
+
/** Starts a session as `resolver`'s caller and returns the input the prepare service received. */
|
|
3402
|
+
async function startAndCapturePrepInput(resolver: RealtimeClientSessionResolver, userID: string): Promise<{ ExcludeDirectActions?: boolean }> {
|
|
3403
|
+
currentProvider = makeProvider(() => makeSessionEntity({ UserID: userID }));
|
|
3404
|
+
createSessionMock.mockResolvedValue(makeSessionEntity({ ID: 'session-start', UserID: userID }));
|
|
3405
|
+
prepareClientSessionMock.mockResolvedValue({
|
|
3406
|
+
Success: true,
|
|
3407
|
+
ClientConfig: { Provider: 'openai', Model: 'gpt-realtime', EphemeralToken: 'ek_abc', ExpiresAt: '2026-01-01T00:00:00Z', SessionConfig: {} },
|
|
3408
|
+
});
|
|
3409
|
+
await resolver.StartRealtimeClientSession('target-1', makeCtx());
|
|
3410
|
+
return prepareClientSessionMock.mock.calls[0][0] as { ExcludeDirectActions?: boolean };
|
|
3411
|
+
}
|
|
3412
|
+
|
|
3413
|
+
it('prepares a scope-limited caller\'s session with no direct actions', async () => {
|
|
3414
|
+
const prepInput = await startAndCapturePrepInput(makeAnonResolver(), 'anon-1');
|
|
3415
|
+
|
|
3416
|
+
expect(prepInput.ExcludeDirectActions).toBe(true);
|
|
3417
|
+
});
|
|
3418
|
+
|
|
3419
|
+
it('prepares a caller that is not scope-limited with direct actions', async () => {
|
|
3420
|
+
const prepInput = await startAndCapturePrepInput(makeResolver(), 'user-1');
|
|
3421
|
+
|
|
3422
|
+
expect(prepInput.ExcludeDirectActions).toBe(false);
|
|
3423
|
+
});
|
|
3424
|
+
|
|
3083
3425
|
it('junction-links delegated artifacts under the SYSTEM user and stamps the hidden anchor with the SESSION owner', async () => {
|
|
3084
3426
|
const session = makeSessionEntity({ UserID: 'anon-1' });
|
|
3085
3427
|
const junctions: FakeSession[] = [];
|
|
@@ -3198,7 +3540,21 @@ describe('RealtimeClientSessionResolver — scoped-anonymous elevation (issue #3
|
|
|
3198
3540
|
expect((writeSegmentMock.mock.calls[0][0] as { ContextUser: unknown }).ContextUser).toBe(SYSTEM_USER);
|
|
3199
3541
|
});
|
|
3200
3542
|
|
|
3201
|
-
it('
|
|
3543
|
+
it('refuses a crash-recovery segment once the session has its consolidated recording (no orphan shard) (#5197)', async () => {
|
|
3544
|
+
// A shard upload still on the wire at hang-up can land after UploadRealtimeRecording stored the
|
|
3545
|
+
// file and deleted the shards; nothing deletes a shard written after that.
|
|
3546
|
+
const session = makeSessionEntity({ UserID: 'anon-1', RecordingFileID: 'file-1' });
|
|
3547
|
+
const agent = makeSessionEntity({ ID: 'co-agent-1' });
|
|
3548
|
+
currentProvider = { GetEntityObject: vi.fn(async (name: string) => (name === 'MJ: AI Agents' ? agent : session)) };
|
|
3549
|
+
const resolver = makeAnonResolver();
|
|
3550
|
+
|
|
3551
|
+
const ok = await resolver.UploadRealtimeRecordingSegment('session-1', 3, AUDIO_B64, 'audio/L16;rate=24000', makeCtx());
|
|
3552
|
+
|
|
3553
|
+
expect(ok).toBe(false);
|
|
3554
|
+
expect(writeSegmentMock).not.toHaveBeenCalled();
|
|
3555
|
+
});
|
|
3556
|
+
|
|
3557
|
+
it('a normal authenticated owner’s recording store runs as the SYSTEM user; ownership stays on the caller (#5195)', async () => {
|
|
3202
3558
|
currentProvider = makeRecordingProvider();
|
|
3203
3559
|
// A named caller owning the session (the recording provider stamps UserID 'anon-1', so re-stamp).
|
|
3204
3560
|
const session = makeSessionEntity({ UserID: 'user-1' });
|
|
@@ -3211,24 +3567,29 @@ describe('RealtimeClientSessionResolver — scoped-anonymous elevation (issue #3
|
|
|
3211
3567
|
const result = await resolver.UploadRealtimeRecording('session-1', AUDIO_B64, 'audio/wav', makeCtx(), 1000, true);
|
|
3212
3568
|
|
|
3213
3569
|
expect(result.Success).toBe(true);
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
expect(
|
|
3570
|
+
// Ownership is proven as the CALLER; the store (MJ: Files, the file link and the session stamp —
|
|
3571
|
+
// entities the stock UI role cannot create/update) runs as SYSTEM.
|
|
3572
|
+
expect(getEntityObjectSpy()).toHaveBeenCalledWith('MJ: AI Agent Sessions', USER);
|
|
3573
|
+
expect(getEntityObjectSpy()).toHaveBeenCalledWith('MJ: AI Agents', SYSTEM_USER);
|
|
3574
|
+
expect(resolveStorageMock.mock.calls[0][1]).toBe(SYSTEM_USER);
|
|
3575
|
+
expect((storeRecordingMock.mock.calls[0][0] as { ContextUser: unknown }).ContextUser).toBe(SYSTEM_USER);
|
|
3576
|
+
expect(deleteSegmentsMock).toHaveBeenCalledWith('session-1', 'storage-acct-1', SYSTEM_USER);
|
|
3217
3577
|
});
|
|
3218
3578
|
|
|
3219
3579
|
/** Provider routing a hidden-tool-turn RelayRealtimeToolTurn call: session, prompt run, detail. */
|
|
3220
3580
|
function makeToolTurnProvider(detail: FakeSession): unknown {
|
|
3221
3581
|
const session = makeSessionEntity({
|
|
3222
3582
|
UserID: 'anon-1',
|
|
3223
|
-
Config_: JSON.stringify({ targetAgentID: 'target-1', promptRunID:
|
|
3583
|
+
Config_: JSON.stringify({ targetAgentID: 'target-1', coAgentRunID: CO_RUN_ID, promptRunID: PROMPT_RUN_ID }),
|
|
3224
3584
|
});
|
|
3225
|
-
const promptRun = makeSessionEntity({ ID:
|
|
3585
|
+
const promptRun = makeSessionEntity({ ID: PROMPT_RUN_ID, Messages: null });
|
|
3226
3586
|
return {
|
|
3227
3587
|
GetEntityObject: vi.fn(async (name: string) => {
|
|
3228
3588
|
if (name === 'MJ: AI Prompt Runs') return promptRun;
|
|
3229
3589
|
if (name === 'MJ: Conversation Details') return detail;
|
|
3230
3590
|
return session;
|
|
3231
3591
|
}),
|
|
3592
|
+
...sessionRunLookups([CO_RUN_ID], PROMPT_RUN_ID),
|
|
3232
3593
|
};
|
|
3233
3594
|
}
|
|
3234
3595
|
|
|
@@ -3388,7 +3749,8 @@ describe('RealtimeClientSessionResolver — colleague authorization (allowedAgen
|
|
|
3388
3749
|
|
|
3389
3750
|
it('excludes every colleague when the caller can run none of them', async () => {
|
|
3390
3751
|
currentProvider = makeProvider(() => makeMultiTargetSession('user-1'));
|
|
3391
|
-
|
|
3752
|
+
// The caller can run the lead target, and none of the colleagues.
|
|
3753
|
+
hasPermissionMock.mockImplementation((...args: unknown[]) => Promise.resolve(args[0] === 'target-1'));
|
|
3392
3754
|
|
|
3393
3755
|
await makeResolver().ExecuteRealtimeSessionTool(
|
|
3394
3756
|
'session-1', 'call-1', 'invoke-target-agent', '{}', makeCtx(), makePubSub(),
|
|
@@ -3397,7 +3759,7 @@ describe('RealtimeClientSessionResolver — colleague authorization (allowedAgen
|
|
|
3397
3759
|
expect(dispatchedAgentIDs()).toEqual([]);
|
|
3398
3760
|
});
|
|
3399
3761
|
|
|
3400
|
-
it('passes a single-target session through with
|
|
3762
|
+
it('passes a single-target session through with only the target permission check', async () => {
|
|
3401
3763
|
currentProvider = makeProvider(() => makeSessionEntity());
|
|
3402
3764
|
hasPermissionMock.mockResolvedValue(true);
|
|
3403
3765
|
|
|
@@ -3407,7 +3769,8 @@ describe('RealtimeClientSessionResolver — colleague authorization (allowedAgen
|
|
|
3407
3769
|
|
|
3408
3770
|
const input = executeRelayedToolMock.mock.calls[0][0] as { AllowedAgents?: unknown };
|
|
3409
3771
|
expect(input.AllowedAgents).toBeUndefined();
|
|
3410
|
-
expect(hasPermissionMock).
|
|
3772
|
+
expect(hasPermissionMock).toHaveBeenCalledTimes(1);
|
|
3773
|
+
expect((hasPermissionMock.mock.calls[0] as unknown[])[0]).toBe('target-1');
|
|
3411
3774
|
});
|
|
3412
3775
|
});
|
|
3413
3776
|
|
|
@@ -3438,18 +3801,36 @@ describe('RealtimeClientSessionResolver — conversation-history hydration', ()
|
|
|
3438
3801
|
return { ID: `h-${message ?? 'x'}`, Role: role, Message: message, HiddenToUser: false, AgentSessionID: null, ...over };
|
|
3439
3802
|
}
|
|
3440
3803
|
|
|
3804
|
+
/** A `MJ: Conversation Branches` row as the branch-path read selects it. */
|
|
3805
|
+
interface BranchRow {
|
|
3806
|
+
ID: string;
|
|
3807
|
+
ConversationID: string;
|
|
3808
|
+
ParentBranchID: string | null;
|
|
3809
|
+
ForkFromSequence: number | null;
|
|
3810
|
+
Name: string | null;
|
|
3811
|
+
}
|
|
3812
|
+
|
|
3441
3813
|
/**
|
|
3442
3814
|
* Provider that answers BOTH Conversation Details queries separately: the prior-leg transcript
|
|
3443
|
-
* (filtered by `AgentSessionID IN (…)`) and the conversation history (
|
|
3444
|
-
* Routing on the filter is what keeps the two from being confused for one another.
|
|
3815
|
+
* (filtered by `AgentSessionID IN (…)`) and the conversation history (the session's path filter).
|
|
3816
|
+
* Routing on the filter is what keeps the two from being confused for one another. It also
|
|
3817
|
+
* serves the conversation's branch rows, which the start reads to check the named fork and
|
|
3818
|
+
* to build its path.
|
|
3445
3819
|
*/
|
|
3446
3820
|
function makeProvider(opts: {
|
|
3447
3821
|
chain?: Record<string, { UserID: string; LastSessionID?: string | null }>;
|
|
3448
3822
|
legRows?: Array<{ ID: string; Role: string; Message: string | null; HiddenToUser: boolean }>;
|
|
3449
3823
|
historyRows?: HistoryRow[];
|
|
3450
3824
|
historyFails?: boolean;
|
|
3825
|
+
branchRows?: BranchRow[];
|
|
3826
|
+
branchesFail?: boolean;
|
|
3451
3827
|
}): { provider: unknown; runView: ReturnType<typeof vi.fn> } {
|
|
3452
3828
|
const runView = vi.fn(async (params: { EntityName: string; ExtraFilter?: string }) => {
|
|
3829
|
+
if (params.EntityName === 'MJ: Conversation Branches') {
|
|
3830
|
+
return opts.branchesFail
|
|
3831
|
+
? { Success: false, ErrorMessage: 'db down', Results: [] }
|
|
3832
|
+
: { Success: true, Results: opts.branchRows ?? [] };
|
|
3833
|
+
}
|
|
3453
3834
|
if (params.EntityName === 'MJ: Conversation Details') {
|
|
3454
3835
|
if (params.ExtraFilter?.startsWith('AgentSessionID IN')) {
|
|
3455
3836
|
return { Success: true, Results: opts.legRows ?? [] };
|
|
@@ -3478,6 +3859,15 @@ describe('RealtimeClientSessionResolver — conversation-history hydration', ()
|
|
|
3478
3859
|
return { provider, runView };
|
|
3479
3860
|
}
|
|
3480
3861
|
|
|
3862
|
+
/** The history read: the Conversation Details query that is not the prior-leg lookup. */
|
|
3863
|
+
function historyCall(runView: ReturnType<typeof vi.fn>): { ExtraFilter: string; OrderBy: string } | undefined {
|
|
3864
|
+
const call = runView.mock.calls.find((c) => {
|
|
3865
|
+
const params = c[0] as { EntityName: string; ExtraFilter?: string };
|
|
3866
|
+
return params.EntityName === 'MJ: Conversation Details' && !params.ExtraFilter?.startsWith('AgentSessionID IN');
|
|
3867
|
+
});
|
|
3868
|
+
return call?.[0] as { ExtraFilter: string; OrderBy: string } | undefined;
|
|
3869
|
+
}
|
|
3870
|
+
|
|
3481
3871
|
function historyArg(): Array<{ role: string; content: unknown }> {
|
|
3482
3872
|
return (prepareClientSessionMock.mock.calls[0][0] as { ConversationMessages: Array<{ role: string; content: unknown }> })
|
|
3483
3873
|
.ConversationMessages;
|
|
@@ -3515,17 +3905,62 @@ describe('RealtimeClientSessionResolver — conversation-history hydration', ()
|
|
|
3515
3905
|
]);
|
|
3516
3906
|
});
|
|
3517
3907
|
|
|
3518
|
-
|
|
3908
|
+
/** Starts a session on `conversationId`, optionally on a fork (the mutation's last argument). */
|
|
3909
|
+
function start(conversationId: string, forkId?: string): Promise<{ AgentSessionId: string }> {
|
|
3910
|
+
return makeResolver().StartRealtimeClientSession(
|
|
3911
|
+
'target-1', makeCtx(), conversationId,
|
|
3912
|
+
undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined,
|
|
3913
|
+
undefined, undefined, forkId,
|
|
3914
|
+
);
|
|
3915
|
+
}
|
|
3916
|
+
|
|
3917
|
+
it('reads Main when the start names no fork, with the apostrophe doubled once', async () => {
|
|
3519
3918
|
const { provider, runView } = makeProvider({ historyRows: [row('User', 'hi')] });
|
|
3520
3919
|
currentProvider = provider;
|
|
3521
3920
|
|
|
3522
|
-
await
|
|
3921
|
+
await start("conv-o'brien");
|
|
3523
3922
|
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
);
|
|
3527
|
-
|
|
3528
|
-
|
|
3923
|
+
expect(historyCall(runView)).toMatchObject({ ExtraFilter: "[ConversationID]='conv-o''brien' AND [BranchID] IS NULL AND [ReplacedAt] IS NULL", OrderBy: 'Sequence ASC' });
|
|
3924
|
+
expect(runView.mock.calls.some((c) => (c[0] as { EntityName: string }).EntityName === 'MJ: Conversation Branches')).toBe(false);
|
|
3925
|
+
expect(historyArg()).toEqual([{ role: 'user', content: 'hi' }]);
|
|
3926
|
+
});
|
|
3927
|
+
|
|
3928
|
+
it('reads the path of the fork the start names, and stores the fork on the session', async () => {
|
|
3929
|
+
const { provider, runView } = makeProvider({
|
|
3930
|
+
historyRows: [row('User', 'in the fork')],
|
|
3931
|
+
branchRows: [{ ID: 'b1', ConversationID: 'conv-1', ParentBranchID: null, ForkFromSequence: 4, Name: null }],
|
|
3932
|
+
});
|
|
3933
|
+
currentProvider = provider;
|
|
3934
|
+
|
|
3935
|
+
await start('conv-1', 'B1');
|
|
3936
|
+
|
|
3937
|
+
expect(historyCall(runView)).toMatchObject({
|
|
3938
|
+
ExtraFilter: "[ConversationID]='conv-1' AND ([BranchID]='b1' OR ([BranchID] IS NULL AND [Sequence] <= 4)) AND [ReplacedAt] IS NULL",
|
|
3939
|
+
OrderBy: 'Sequence ASC',
|
|
3940
|
+
});
|
|
3941
|
+
const config = JSON.parse((createSessionMock.mock.calls.at(-1)![0] as { config: string }).config) as { conversationBranchID?: string };
|
|
3942
|
+
expect(config.conversationBranchID).toBe('b1');
|
|
3943
|
+
});
|
|
3944
|
+
|
|
3945
|
+
it.each([
|
|
3946
|
+
['the fork is not a fork of the conversation', { branchRows: [] }],
|
|
3947
|
+
['the fork rows cannot be read', { branchesFail: true }],
|
|
3948
|
+
])('refuses the start when %s', async (_label, opts) => {
|
|
3949
|
+
const { provider } = makeProvider({ historyRows: [row('User', 'never read')], ...opts });
|
|
3950
|
+
currentProvider = provider;
|
|
3951
|
+
const sessionsBefore = createSessionMock.mock.calls.length;
|
|
3952
|
+
|
|
3953
|
+
await expect(start('conv-1', 'b1')).rejects.toThrow(/fork b1/);
|
|
3954
|
+
expect(createSessionMock.mock.calls.length).toBe(sessionsBefore);
|
|
3955
|
+
});
|
|
3956
|
+
|
|
3957
|
+
it('refuses a fork without a conversation', async () => {
|
|
3958
|
+
const { provider } = makeProvider({});
|
|
3959
|
+
currentProvider = provider;
|
|
3960
|
+
|
|
3961
|
+
await expect(makeResolver().StartRealtimeClientSession(
|
|
3962
|
+
'target-1', makeCtx(), undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, 'b1',
|
|
3963
|
+
)).rejects.toThrow(/needs a conversation/);
|
|
3529
3964
|
});
|
|
3530
3965
|
|
|
3531
3966
|
it('skips hidden rows, error rows, and empty messages', async () => {
|
|
@@ -3636,6 +4071,195 @@ describe('RealtimeClientSessionResolver — conversation-history hydration', ()
|
|
|
3636
4071
|
await makeResolver().StartRealtimeClientSession('target-1', makeCtx());
|
|
3637
4072
|
|
|
3638
4073
|
expect(historyArg()).toEqual([]);
|
|
3639
|
-
expect(runView
|
|
4074
|
+
expect(historyCall(runView)).toBeUndefined();
|
|
4075
|
+
expect(runView.mock.calls.some((c) => (c[0] as { EntityName: string }).EntityName === 'MJ: Conversations')).toBe(false);
|
|
4076
|
+
});
|
|
4077
|
+
});
|
|
4078
|
+
|
|
4079
|
+
|
|
4080
|
+
describe('RealtimeClientSessionResolver — channel scoping at mint (Realtime Channels v2)', () => {
|
|
4081
|
+
const okConfig = { Provider: 'openai', Model: 'm', EphemeralToken: 'ek', ExpiresAt: '2099-01-01T00:00:00Z', SessionConfig: {} };
|
|
4082
|
+
const CANDIDATE = {
|
|
4083
|
+
Key: 'Whiteboard',
|
|
4084
|
+
DefaultAvailability: 'all-sessions',
|
|
4085
|
+
DisplayPolicy: 'open-on-start',
|
|
4086
|
+
MaxExposure: 'pixels',
|
|
4087
|
+
ToolNamePrefix: 'Whiteboard_',
|
|
4088
|
+
Tools: [{ Name: 'Whiteboard_AddNote', Description: 'Add a note', ParametersSchema: { type: 'object' } }],
|
|
4089
|
+
};
|
|
4090
|
+
const POLICY = {
|
|
4091
|
+
Version: 1,
|
|
4092
|
+
Channels: [{ Key: 'Whiteboard', DisplayPolicy: 'open-on-start', MaxExposure: 'pixels', Source: 'default' }],
|
|
4093
|
+
ExcludedChannels: [{ Key: 'RemoteBrowser', Reason: 'excluded-by-config' }],
|
|
4094
|
+
};
|
|
4095
|
+
|
|
4096
|
+
function setupStart(prepOverrides: Record<string, unknown> = {}): { created: FakeSession } {
|
|
4097
|
+
hasPermissionMock.mockResolvedValue(true);
|
|
4098
|
+
currentProvider = makeProvider(() => makeSessionEntity());
|
|
4099
|
+
const created = makeSessionEntity({ ID: 'session-ch' });
|
|
4100
|
+
createSessionMock.mockResolvedValue(created);
|
|
4101
|
+
prepareClientSessionMock.mockResolvedValue({ Success: true, ClientConfig: okConfig, ...prepOverrides });
|
|
4102
|
+
pruneSessionChannelsMock.mockClear();
|
|
4103
|
+
return { created };
|
|
4104
|
+
}
|
|
4105
|
+
|
|
4106
|
+
async function start(channelCandidatesJson?: string) {
|
|
4107
|
+
const resolver = makeResolver();
|
|
4108
|
+
return resolver.StartRealtimeClientSession(
|
|
4109
|
+
'target-1', makeCtx(),
|
|
4110
|
+
undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined,
|
|
4111
|
+
undefined, undefined, channelCandidatesJson,
|
|
4112
|
+
);
|
|
4113
|
+
}
|
|
4114
|
+
|
|
4115
|
+
it('threads valid candidates to PrepareClientSession and returns the resolved policy as ClientPolicyJson', async () => {
|
|
4116
|
+
setupStart({ ClientPolicy: POLICY });
|
|
4117
|
+
const result = await start(JSON.stringify([CANDIDATE]));
|
|
4118
|
+
const prepArg = prepareClientSessionMock.mock.calls[0][0] as { ChannelCandidates?: Array<{ Key: string }> };
|
|
4119
|
+
expect(prepArg.ChannelCandidates?.map((c) => c.Key)).toEqual(['Whiteboard']);
|
|
4120
|
+
expect(JSON.parse(result.ClientPolicyJson ?? 'null')).toEqual(POLICY);
|
|
4121
|
+
});
|
|
4122
|
+
|
|
4123
|
+
it('returns the avatar status as AvatarStatusJson, and none when the prepare reported none', async () => {
|
|
4124
|
+
setupStart({ AvatarStatus: { Requested: true, Granted: false, Reason: 'endpoint' } });
|
|
4125
|
+
const result = await start(undefined);
|
|
4126
|
+
expect(JSON.parse(result.AvatarStatusJson ?? 'null')).toEqual({ Requested: true, Granted: false, Reason: 'endpoint' });
|
|
4127
|
+
|
|
4128
|
+
setupStart();
|
|
4129
|
+
const none = await start(undefined);
|
|
4130
|
+
expect(none.AvatarStatusJson).toBeUndefined();
|
|
4131
|
+
});
|
|
4132
|
+
|
|
4133
|
+
it("threads an app's showsAgentVideo: false to PrepareClientSession, and nothing when the app did not say", async () => {
|
|
4134
|
+
const startWith = (showsAgentVideo?: boolean | null) =>
|
|
4135
|
+
makeResolver().StartRealtimeClientSession(
|
|
4136
|
+
'target-1', makeCtx(),
|
|
4137
|
+
undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined,
|
|
4138
|
+
undefined, undefined, undefined, showsAgentVideo as boolean | undefined,
|
|
4139
|
+
);
|
|
4140
|
+
setupStart({ AvatarStatus: { Requested: true, Granted: false, Reason: 'host' } });
|
|
4141
|
+
const result = await startWith(false);
|
|
4142
|
+
expect((prepareClientSessionMock.mock.calls[0][0] as { ShowsAgentVideo?: boolean }).ShowsAgentVideo).toBe(false);
|
|
4143
|
+
expect(JSON.parse(result.AvatarStatusJson ?? 'null')).toEqual({ Requested: true, Granted: false, Reason: 'host' });
|
|
4144
|
+
|
|
4145
|
+
for (const unstated of [undefined, null]) {
|
|
4146
|
+
prepareClientSessionMock.mockClear();
|
|
4147
|
+
setupStart();
|
|
4148
|
+
await startWith(unstated);
|
|
4149
|
+
expect((prepareClientSessionMock.mock.calls[0][0] as { ShowsAgentVideo?: boolean }).ShowsAgentVideo).toBeUndefined();
|
|
4150
|
+
}
|
|
4151
|
+
});
|
|
4152
|
+
|
|
4153
|
+
it('sends no candidates and returns no policy when the caller reported none (a client that predates scoping)', async () => {
|
|
4154
|
+
setupStart();
|
|
4155
|
+
const result = await start(undefined);
|
|
4156
|
+
const prepArg = prepareClientSessionMock.mock.calls[0][0] as { ChannelCandidates?: unknown };
|
|
4157
|
+
expect(prepArg.ChannelCandidates).toBeUndefined();
|
|
4158
|
+
expect(result.ClientPolicyJson).toBeUndefined();
|
|
4159
|
+
});
|
|
4160
|
+
|
|
4161
|
+
it('persists the in-scope channel keys on the session config, preserving what was stamped at start', async () => {
|
|
4162
|
+
const { created } = setupStart({ ClientPolicy: POLICY });
|
|
4163
|
+
await start(JSON.stringify([CANDIDATE]));
|
|
4164
|
+
const persisted = JSON.parse(created.Config_ as string) as { channels?: string[]; targetAgentID: string };
|
|
4165
|
+
expect(persisted.channels).toEqual(['Whiteboard']);
|
|
4166
|
+
expect(persisted.targetAgentID).toBe('target-1');
|
|
4167
|
+
});
|
|
4168
|
+
|
|
4169
|
+
it('carries the config stamped at session start (the media-kit override) through the run-id rewrite', async () => {
|
|
4170
|
+
const { created } = setupStart({ CoAgentRunID: 'run-1' });
|
|
4171
|
+
const resolver = makeResolver();
|
|
4172
|
+
// The create call receives the initial config; emulate the stored value the rewrite reads back.
|
|
4173
|
+
createSessionMock.mockImplementation(async (arg: { config: string }) => {
|
|
4174
|
+
created.Config_ = arg.config;
|
|
4175
|
+
return created;
|
|
4176
|
+
});
|
|
4177
|
+
await resolver.StartRealtimeClientSession(
|
|
4178
|
+
'target-1', makeCtx(),
|
|
4179
|
+
undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined,
|
|
4180
|
+
'11111111-1111-4111-8111-111111111111',
|
|
4181
|
+
);
|
|
4182
|
+
const persisted = JSON.parse(created.Config_ as string) as { mediaCollectionID?: string; coAgentRunID?: string };
|
|
4183
|
+
expect(persisted.mediaCollectionID).toBe('11111111-1111-4111-8111-111111111111');
|
|
4184
|
+
expect(persisted.coAgentRunID).toBe('run-1');
|
|
4185
|
+
});
|
|
4186
|
+
|
|
4187
|
+
it('prunes the server plugins of channels the scope excluded', async () => {
|
|
4188
|
+
setupStart({ ClientPolicy: POLICY });
|
|
4189
|
+
await start(JSON.stringify([CANDIDATE]));
|
|
4190
|
+
expect(pruneSessionChannelsMock).toHaveBeenCalledWith('session-ch', ['RemoteBrowser']);
|
|
4191
|
+
});
|
|
4192
|
+
|
|
4193
|
+
it('does not touch server plugins when nothing was excluded', async () => {
|
|
4194
|
+
setupStart({ ClientPolicy: { Version: 1, Channels: POLICY.Channels } });
|
|
4195
|
+
await start(JSON.stringify([CANDIDATE]));
|
|
4196
|
+
expect(pruneSessionChannelsMock).not.toHaveBeenCalled();
|
|
4197
|
+
});
|
|
4198
|
+
|
|
4199
|
+
it('tolerantly drops malformed, oversized and flooded candidate payloads (the session still starts)', async () => {
|
|
4200
|
+
for (const payload of ['{not json', JSON.stringify({ not: 'an array' }), JSON.stringify(Array.from({ length: 33 }, (_, i) => ({ ...CANDIDATE, Key: `C${i}` }))), 'x'.repeat(300_000)]) {
|
|
4201
|
+
setupStart();
|
|
4202
|
+
await start(payload);
|
|
4203
|
+
const prepArg = prepareClientSessionMock.mock.calls.at(-1)?.[0] as { ChannelCandidates?: unknown };
|
|
4204
|
+
expect(prepArg.ChannelCandidates).toBeUndefined();
|
|
4205
|
+
prepareClientSessionMock.mockClear();
|
|
4206
|
+
}
|
|
4207
|
+
});
|
|
4208
|
+
|
|
4209
|
+
it('skips an invalid candidate but keeps the valid ones', async () => {
|
|
4210
|
+
setupStart();
|
|
4211
|
+
await start(JSON.stringify([CANDIDATE, { Key: 'Broken', DefaultAvailability: 'sometimes' }]));
|
|
4212
|
+
const prepArg = prepareClientSessionMock.mock.calls[0][0] as { ChannelCandidates?: Array<{ Key: string }> };
|
|
4213
|
+
expect(prepArg.ChannelCandidates?.map((c) => c.Key)).toEqual(['Whiteboard']);
|
|
4214
|
+
});
|
|
4215
|
+
});
|
|
4216
|
+
|
|
4217
|
+
describe('RealtimeClientSessionResolver — save endpoints honor the session\'s resolved channel scope', () => {
|
|
4218
|
+
const CHANNEL_ROW = { ID: 'channel-wb', IsActive: true };
|
|
4219
|
+
|
|
4220
|
+
function provider(session: FakeSession) {
|
|
4221
|
+
engineChannelsMock.mockReturnValue([{ Name: 'Whiteboard', ...CHANNEL_ROW }]);
|
|
4222
|
+
const newRow = makeSessionEntity({ ID: 'sc-new' });
|
|
4223
|
+
return {
|
|
4224
|
+
newRow,
|
|
4225
|
+
provider: {
|
|
4226
|
+
GetEntityObject: vi.fn(async (name: string) => (name === 'MJ: AI Agent Session Channels' ? newRow : session)),
|
|
4227
|
+
RunView: vi.fn(async () => ({ Success: true, Results: [] })),
|
|
4228
|
+
},
|
|
4229
|
+
};
|
|
4230
|
+
}
|
|
4231
|
+
|
|
4232
|
+
it('refuses to save state for a channel the session\'s scope excluded', async () => {
|
|
4233
|
+
const session = makeSessionEntity({ Config_: JSON.stringify({ targetAgentID: 't', channels: ['Media'] }) });
|
|
4234
|
+
const p = provider(session);
|
|
4235
|
+
currentProvider = p.provider;
|
|
4236
|
+
const ok = await makeResolver().SaveSessionChannelState('session-1', 'Whiteboard', '{}', makeCtx());
|
|
4237
|
+
expect(ok).toBe(false);
|
|
4238
|
+
expect(p.newRow.Save).not.toHaveBeenCalled();
|
|
4239
|
+
});
|
|
4240
|
+
|
|
4241
|
+
it('saves state for an in-scope channel (case-insensitively)', async () => {
|
|
4242
|
+
const session = makeSessionEntity({ Config_: JSON.stringify({ targetAgentID: 't', channels: ['whiteboard'] }) });
|
|
4243
|
+
const p = provider(session);
|
|
4244
|
+
currentProvider = p.provider;
|
|
4245
|
+
const ok = await makeResolver().SaveSessionChannelState('session-1', 'Whiteboard', '{}', makeCtx());
|
|
4246
|
+
expect(ok).toBe(true);
|
|
4247
|
+
});
|
|
4248
|
+
|
|
4249
|
+
it('an unscoped session (no persisted channels) allows any channel, as before', async () => {
|
|
4250
|
+
const session = makeSessionEntity({ Config_: JSON.stringify({ targetAgentID: 't' }) });
|
|
4251
|
+
const p = provider(session);
|
|
4252
|
+
currentProvider = p.provider;
|
|
4253
|
+
const ok = await makeResolver().SaveSessionChannelState('session-1', 'Whiteboard', '{}', makeCtx());
|
|
4254
|
+
expect(ok).toBe(true);
|
|
4255
|
+
});
|
|
4256
|
+
|
|
4257
|
+
it('refuses to save an artifact for an out-of-scope channel with a structured failure', async () => {
|
|
4258
|
+
const session = makeSessionEntity({ Config_: JSON.stringify({ targetAgentID: 't', channels: ['Media'] }) });
|
|
4259
|
+
const p = provider(session);
|
|
4260
|
+
currentProvider = p.provider;
|
|
4261
|
+
const result = await makeResolver().SaveSessionChannelArtifact('session-1', 'Whiteboard', 'Board', '{}', makeCtx());
|
|
4262
|
+
expect(result.Success).toBe(false);
|
|
4263
|
+
expect(result.ErrorMessage).toContain('not in session');
|
|
3640
4264
|
});
|
|
3641
4265
|
});
|