@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
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
* and **executes** every tool call the browser relays back. The three mutations here cover the MVP:
|
|
12
12
|
*
|
|
13
13
|
* 1. {@link RealtimeClientSessionResolver.StartRealtimeClientSession} — authorize, create the
|
|
14
|
-
* session (storing the target agent id
|
|
15
|
-
* 2. {@link RealtimeClientSessionResolver.ExecuteRealtimeSessionTool} — execute a relayed tool call
|
|
16
|
-
*
|
|
14
|
+
* session (storing the target agent id on it), mint the ephemeral config.
|
|
15
|
+
* 2. {@link RealtimeClientSessionResolver.ExecuteRealtimeSessionTool} — execute a relayed tool call
|
|
16
|
+
* for the target agent stored on the session, re-authorized for the caller on every call.
|
|
17
17
|
* 3. {@link RealtimeClientSessionResolver.RelayRealtimeTranscript} — persist a transcript turn as a
|
|
18
18
|
* `Conversation Detail`.
|
|
19
19
|
*
|
|
@@ -21,12 +21,19 @@
|
|
|
21
21
|
* - **Start**: the caller must have `CanRun` on the *target* agent (the Realtime Co-Agent is an internal
|
|
22
22
|
* orchestration agent, so the meaningful gate is the agent doing the real work).
|
|
23
23
|
* - **Execute / Relay**: inbound ownership — the session's `UserID` must equal `contextUser.ID`.
|
|
24
|
+
* - **Session `Config`**: the owner can edit it through the generic entity API, so nothing in it is
|
|
25
|
+
* trusted. Execute re-checks `CanRun` on its target, direct actions never come from it, and its
|
|
26
|
+
* run ids are used only when {@link SessionRunIDVerifier} finds them to be records of the session.
|
|
27
|
+
* - **Scope-limited callers** (anonymous and resource-scoped magic-link sessions, including public
|
|
28
|
+
* web-widget guests): no direct actions. Their scope exists only as row-level filters, which an
|
|
29
|
+
* action can read around, and a scoped anonymous caller's relay runs as the system user. They
|
|
30
|
+
* reach the target agent only through `invoke-target-agent`.
|
|
24
31
|
*
|
|
25
32
|
* @module @memberjunction/server
|
|
26
33
|
*/
|
|
27
34
|
import { Resolver, Mutation, Arg, Ctx, Int, Float, ObjectType, Field, PubSub, PubSubEngine } from 'type-graphql';
|
|
28
35
|
import { AppContext, UserPayload } from '../types.js';
|
|
29
|
-
import { AuthorizationEvaluator, UserInfo, IMetadataProvider, LogError, LogStatus, RunView, BaseEntity } from '@memberjunction/core';
|
|
36
|
+
import { AuthorizationEvaluator, UserInfo, IMetadataProvider, LogError, LogErrorEx, LogStatus, RunView, BaseEntity } from '@memberjunction/core';
|
|
30
37
|
import { UUIDsEqual, IsValidUUID } from '@memberjunction/global';
|
|
31
38
|
import {
|
|
32
39
|
MJAIAgentEntity,
|
|
@@ -36,9 +43,10 @@ import {
|
|
|
36
43
|
MJArtifactVersionEntity,
|
|
37
44
|
MJConversationDetailArtifactEntity,
|
|
38
45
|
MJConversationDetailEntity,
|
|
46
|
+
ConversationEngine,
|
|
39
47
|
} from '@memberjunction/core-entities';
|
|
40
48
|
import { AIEngine } from '@memberjunction/aiengine';
|
|
41
|
-
import { AIAgentPermissionHelper, AIEngineBase } from '@memberjunction/ai-engine-base';
|
|
49
|
+
import { AIAgentPermissionHelper, AIEngineBase, HasRealtimeUsage, ParseRealtimeUsageRecord, type RealtimeUsageRecord } from '@memberjunction/ai-engine-base';
|
|
42
50
|
import {
|
|
43
51
|
RealtimeClientSessionService,
|
|
44
52
|
DelegatedRunArtifact,
|
|
@@ -50,6 +58,7 @@ import {
|
|
|
50
58
|
ResolveEffectiveRealtimeConfig,
|
|
51
59
|
ResolveRealtimeCoAgentID,
|
|
52
60
|
FilterAllowedAgentsByCanRun,
|
|
61
|
+
INVOKE_TARGET_AGENT_TOOL_NAME,
|
|
53
62
|
RealtimeAllowedAgent,
|
|
54
63
|
RealtimeDirectActionsConfig,
|
|
55
64
|
REALTIME_ADVANCED_SESSION_CONTROLS_AUTHORIZATION,
|
|
@@ -58,13 +67,27 @@ import {
|
|
|
58
67
|
writeRealtimeRecordingSegment,
|
|
59
68
|
deleteRealtimeRecordingSegments,
|
|
60
69
|
} from '@memberjunction/ai-agents';
|
|
61
|
-
import {
|
|
70
|
+
import {
|
|
71
|
+
AgentExecutionProgressCallback,
|
|
72
|
+
AppContextSnapshot,
|
|
73
|
+
IsValidRealtimeToolDefinition,
|
|
74
|
+
NormalizeChannelKey,
|
|
75
|
+
ParseRealtimeChannelCandidates,
|
|
76
|
+
type RealtimeChannelCandidate,
|
|
77
|
+
type RealtimeSessionClientPolicy
|
|
78
|
+
} from '@memberjunction/ai-core-plus';
|
|
62
79
|
import { ChatMessage, RealtimeToolDefinition } from '@memberjunction/ai';
|
|
63
80
|
import { ResolverBase } from '../generic/ResolverBase.js';
|
|
64
81
|
import { PUSH_STATUS_UPDATES_TOPIC } from '../generic/PushStatusResolver.js';
|
|
65
82
|
import { GetReadWriteProvider } from '../util.js';
|
|
66
83
|
import { SessionManager } from '../agentSessions/index.js';
|
|
67
|
-
import {
|
|
84
|
+
import { SessionRunIDVerifier } from '../agentSessions/SessionRunIDVerifier.js';
|
|
85
|
+
import { IsScopeLimitedPrincipal } from '../auth/scopeLimitedPrincipal.js';
|
|
86
|
+
import { ResolveWidgetGuestRunContext, ResolveScopedAnonymousRunUser, ResolveRecordingStoreUser } from '../realtimeWidget/widgetGuestElevation.js';
|
|
87
|
+
import { RunWithTrustedSessionConfigWrites } from '@memberjunction/core-entities-server';
|
|
88
|
+
import { BuildMintConfigStamps, LoadInheritedVerifiedIdentity } from '../realtimeSessions/sessionConfigStamps.js';
|
|
89
|
+
import { ParseConfigObject } from '../realtimeSessions/verificationCore.js';
|
|
90
|
+
import { LoadOwnedRealtimeSession } from '../realtimeSessions/ownedRealtimeSession.js';
|
|
68
91
|
|
|
69
92
|
/**
|
|
70
93
|
* Progress steps worth narrating to the realtime model — mirrors the normal agent-run path's filter
|
|
@@ -73,6 +96,12 @@ import { ResolveWidgetGuestRunContext, ResolveScopedAnonymousRunUser } from '../
|
|
|
73
96
|
*/
|
|
74
97
|
const SIGNIFICANT_PROGRESS_STEPS = ['prompt_execution', 'action_execution', 'subagent_execution', 'decision_processing'];
|
|
75
98
|
|
|
99
|
+
/**
|
|
100
|
+
* The longest `usageDetailsJson` a usage relay may carry. Every field of both detail blocks fits in a few hundred
|
|
101
|
+
* characters, so anything longer is not a relay the browser runtime sends.
|
|
102
|
+
*/
|
|
103
|
+
const MAX_USAGE_DETAILS_JSON_LENGTH = 4096;
|
|
104
|
+
|
|
76
105
|
/** Entity name — centralised so the `MJ:`-prefix convention is applied in exactly one place. */
|
|
77
106
|
const SESSION_ENTITY = 'MJ: AI Agent Sessions';
|
|
78
107
|
const CO_AGENT_ENTITY = 'MJ: AI Agent Co Agents';
|
|
@@ -100,7 +129,8 @@ const WHITEBOARD_ARTIFACT_TYPE_NAME = 'Whiteboard';
|
|
|
100
129
|
*/
|
|
101
130
|
const MAX_CLIENT_TOOLS = 64;
|
|
102
131
|
/** Maximum accepted size (chars) of the serialized client tool declarations. Raised in step with
|
|
103
|
-
* {@link MAX_CLIENT_TOOLS} — multi-channel tool sets with verbose descriptions + JSON schemas run large.
|
|
132
|
+
* {@link MAX_CLIENT_TOOLS} — multi-channel tool sets with verbose descriptions + JSON schemas run large.
|
|
133
|
+
* Also the largest serialized app-context snapshot stored on the session config. */
|
|
104
134
|
const MAX_CLIENT_TOOLS_JSON_CHARS = 256_000;
|
|
105
135
|
/** Maximum accepted size (chars) of a persisted channel state blob. */
|
|
106
136
|
const MAX_CHANNEL_STATE_CHARS = 2_000_000;
|
|
@@ -118,9 +148,9 @@ const MAX_CONVERSATION_HISTORY_TURNS = 30;
|
|
|
118
148
|
const MAX_CONVERSATION_HISTORY_CHARS = 8_000;
|
|
119
149
|
|
|
120
150
|
/**
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
151
|
+
* Shape persisted in `AIAgentSession.Config_` for a client-direct voice session. The server writes
|
|
152
|
+
* it, but the session owner can also edit it through the generic entity API, so relays re-check
|
|
153
|
+
* what they read from it (see the authorization model above).
|
|
124
154
|
*/
|
|
125
155
|
interface RealtimeSessionConfig {
|
|
126
156
|
/** The top-level target agent the co-agent voices on behalf of. */
|
|
@@ -157,6 +187,12 @@ interface RealtimeSessionConfig {
|
|
|
157
187
|
* for observability / continuity; absent when the session carries no app context.
|
|
158
188
|
*/
|
|
159
189
|
applicationID?: string;
|
|
190
|
+
/**
|
|
191
|
+
* The app-context snapshot the browser sent at session start. Passed into each delegated
|
|
192
|
+
* target-agent run so its prompt sees the surface context and tools. Absent when the start carried
|
|
193
|
+
* none, or when its JSON is longer than {@link MAX_CLIENT_TOOLS_JSON_CHARS}.
|
|
194
|
+
*/
|
|
195
|
+
appContext?: AppContextSnapshot;
|
|
160
196
|
/**
|
|
161
197
|
* The session's effective allowed delegation targets (union from the config cascade, incl. the app's
|
|
162
198
|
* `RelevantAgents`). Persisted at start so each relayed `invoke-target-agent` call can validate a
|
|
@@ -175,6 +211,24 @@ interface RealtimeSessionConfig {
|
|
|
175
211
|
* agent default kit is used (the common case).
|
|
176
212
|
*/
|
|
177
213
|
mediaCollectionID?: string;
|
|
214
|
+
/**
|
|
215
|
+
* The fork (`MJ: Conversation Branches.ID`) the session was started on, fixed at start; absent means
|
|
216
|
+
* Main. Every transcript, direct-action and anchor row the session writes carries it.
|
|
217
|
+
*/
|
|
218
|
+
conversationBranchID?: string;
|
|
219
|
+
/**
|
|
220
|
+
* Server-authoritative identity-verification state (policy snapshot taken at mint, pending/verified
|
|
221
|
+
* state written by `RealtimeSessionVerificationService`). Protected from owner writes by
|
|
222
|
+
* `MJAIAgentSessionEntityServer`; opaque to this resolver beyond preserving it across config rewrites.
|
|
223
|
+
*/
|
|
224
|
+
identityVerification?: Record<string, unknown>;
|
|
225
|
+
/**
|
|
226
|
+
* The keys of the interactive channels the session's resolved scope put IN the session (registry
|
|
227
|
+
* kill switch, agent and app configuration applied). Persisted at start so a later server-side
|
|
228
|
+
* call naming a channel — a state or artifact save — can check it is actually in scope. Absent
|
|
229
|
+
* (an unscoped session: a client that reported no channel candidates) ⇒ no channel check.
|
|
230
|
+
*/
|
|
231
|
+
channels?: string[];
|
|
178
232
|
}
|
|
179
233
|
|
|
180
234
|
/**
|
|
@@ -199,14 +253,32 @@ export class StartRealtimeClientSessionResult {
|
|
|
199
253
|
@Field(() => String)
|
|
200
254
|
Model: string;
|
|
201
255
|
|
|
202
|
-
/**
|
|
256
|
+
/**
|
|
257
|
+
* The short-lived client secret the browser presents to the provider to authenticate. Empty on a relay session
|
|
258
|
+
* ({@link StartRealtimeClientSessionResult.Transport} `relay`), whose {@link StartRealtimeClientSessionResult.RelayUrl}
|
|
259
|
+
* carries the relay's ticket instead.
|
|
260
|
+
*/
|
|
203
261
|
@Field(() => String)
|
|
204
262
|
EphemeralToken: string;
|
|
205
263
|
|
|
206
|
-
/** ISO-8601 timestamp at which {@link StartRealtimeClientSessionResult.EphemeralToken} expires. */
|
|
264
|
+
/** ISO-8601 timestamp at which {@link StartRealtimeClientSessionResult.EphemeralToken} (or a relay session's ticket) expires. */
|
|
207
265
|
@Field(() => String)
|
|
208
266
|
ExpiresAt: string;
|
|
209
267
|
|
|
268
|
+
/**
|
|
269
|
+
* How the browser reaches the provider: `direct` (with the ephemeral token) or `relay` (through MJAPI's realtime
|
|
270
|
+
* relay at {@link StartRealtimeClientSessionResult.RelayUrl}). Null means direct.
|
|
271
|
+
*/
|
|
272
|
+
@Field(() => String, { nullable: true })
|
|
273
|
+
Transport?: string;
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Where a relay session's browser connects (MJAPI's realtime relay). Null on a direct session. The URL carries the
|
|
277
|
+
* session's ticket, so it is a credential and is never logged.
|
|
278
|
+
*/
|
|
279
|
+
@Field(() => String, { nullable: true })
|
|
280
|
+
RelayUrl?: string;
|
|
281
|
+
|
|
210
282
|
/** JSON string of the provider-native session config the browser applies verbatim. */
|
|
211
283
|
@Field(() => String)
|
|
212
284
|
SessionConfigJson: string;
|
|
@@ -263,6 +335,25 @@ export class StartRealtimeClientSessionResult {
|
|
|
263
335
|
*/
|
|
264
336
|
@Field(() => String, { nullable: true })
|
|
265
337
|
EffectiveConfigJson?: string;
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* JSON of the server's resolved channel policy for this session (`RealtimeSessionClientPolicy`):
|
|
341
|
+
* which of the channel candidates the browser reported are in the session (registry kill switch,
|
|
342
|
+
* agent and app `channels` configuration applied), with each one's resolved display and config,
|
|
343
|
+
* plus the app/static client-tool tiers. Null when the caller reported no channel candidates (a
|
|
344
|
+
* client that predates channel scoping) — the browser then resolves the scope locally.
|
|
345
|
+
*/
|
|
346
|
+
@Field(() => String, { nullable: true })
|
|
347
|
+
ClientPolicyJson?: string;
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* JSON of the session's live-avatar status (`RealtimeAvatarStatus` from `@memberjunction/ai`): whether the voiced
|
|
351
|
+
* agent asked for an avatar, whether the model renders it, and why not. The browser reads it after connecting and
|
|
352
|
+
* tells the user once when the call is audio only. Null when the agent asked for no avatar (its video setting is
|
|
353
|
+
* off).
|
|
354
|
+
*/
|
|
355
|
+
@Field(() => String, { nullable: true })
|
|
356
|
+
AvatarStatusJson?: string;
|
|
266
357
|
}
|
|
267
358
|
|
|
268
359
|
/**
|
|
@@ -325,7 +416,7 @@ export class CancelRealtimeSessionToolResult {
|
|
|
325
416
|
* Result of {@link RealtimeClientSessionResolver.UploadRealtimeRecording} — a structured
|
|
326
417
|
* success/failure envelope. A recording-storage failure must never throw to the browser (the
|
|
327
418
|
* session it belongs to has already ended), so problems come back as `Success: false` with a
|
|
328
|
-
* human-readable reason; only ownership/authn violations (from `
|
|
419
|
+
* human-readable reason; only ownership/authn violations (from `LoadOwnedRealtimeSession`) still throw.
|
|
329
420
|
*/
|
|
330
421
|
@ObjectType()
|
|
331
422
|
export class UploadRealtimeRecordingResult {
|
|
@@ -356,6 +447,7 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
356
447
|
// state on — otherwise that cleanup silently no-ops on a freshly-constructed instance.
|
|
357
448
|
private readonly clientSessionService = new RealtimeClientSessionService();
|
|
358
449
|
private readonly sessionManager = new SessionManager(this.clientSessionService);
|
|
450
|
+
private readonly runIDVerifier = new SessionRunIDVerifier();
|
|
359
451
|
|
|
360
452
|
/**
|
|
361
453
|
* The reason a `Save()`/`Delete()` returned false, for a log line. `CompleteMessage` renders
|
|
@@ -392,7 +484,7 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
392
484
|
* agent's `DefaultCoAgentID` → type-level `AIAgentCoAgent` default row → global Realtime Co-Agent) —
|
|
393
485
|
* see {@link ResolveRealtimeCoAgentID} for the full contract.
|
|
394
486
|
* 3. Create the durable `AIAgentSession` (run by the co-agent), storing `targetAgentID` in its
|
|
395
|
-
* config
|
|
487
|
+
* config — the target later relays use, re-authorized for the caller on each relay.
|
|
396
488
|
* 4. Mint the {@link import('@memberjunction/ai').ClientRealtimeSessionConfig} via the service.
|
|
397
489
|
* On failure the just-created session is closed so no half-open session leaks.
|
|
398
490
|
*
|
|
@@ -418,6 +510,12 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
418
510
|
* `TypeConfiguration` ← this). **Authorization-gated**: requires the
|
|
419
511
|
* `Realtime: Advanced Session Controls` authorization — unauthorized callers receive a
|
|
420
512
|
* structured rejection (never a silent ignore). Must be a JSON object.
|
|
513
|
+
* @param showsAgentVideo `false` from an app that shows no agent video (no channel of it shows it: the embeddable
|
|
514
|
+
* widgets, the mobile app): the session asks the model for no avatar, and `AvatarStatusJson` says `host`. Absent or
|
|
515
|
+
* `true`: the app may show it. It only ever turns video off, so it needs no authorization.
|
|
516
|
+
* @param conversationBranchId Optional fork (`MJ: Conversation Branches.ID`) the session starts on;
|
|
517
|
+
* omitted is Main. It must be a fork of `conversationId`, else the start fails and no session is
|
|
518
|
+
* created. The session's history is read from that fork's path and its transcript is written there.
|
|
421
519
|
*
|
|
422
520
|
* @returns The ephemeral config + session linkage the browser needs to open its socket.
|
|
423
521
|
*/
|
|
@@ -436,6 +534,9 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
436
534
|
@Arg('mediaCollectionId', () => String, { nullable: true }) mediaCollectionId?: string,
|
|
437
535
|
@Arg('applicationId', () => String, { nullable: true }) applicationId?: string,
|
|
438
536
|
@Arg('appContextJson', () => String, { nullable: true }) appContextJson?: string,
|
|
537
|
+
@Arg('channelCandidatesJson', () => String, { nullable: true }) channelCandidatesJson?: string,
|
|
538
|
+
@Arg('showsAgentVideo', () => Boolean, { nullable: true }) showsAgentVideo?: boolean,
|
|
539
|
+
@Arg('conversationBranchId', () => String, { nullable: true }) conversationBranchId?: string,
|
|
439
540
|
): Promise<StartRealtimeClientSessionResult> {
|
|
440
541
|
const { contextUser, provider } = this.requireUserAndProvider(userPayload, providers);
|
|
441
542
|
|
|
@@ -473,22 +574,32 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
473
574
|
);
|
|
474
575
|
}
|
|
475
576
|
}
|
|
476
|
-
const
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
577
|
+
const sessionBranchId = await this.resolveSessionBranch(conversationId, conversationBranchId, contextUser, provider);
|
|
578
|
+
if (sessionBranchId) {
|
|
579
|
+
config.conversationBranchID = sessionBranchId;
|
|
580
|
+
}
|
|
581
|
+
// The initial Config carries `maxSessionDeadlineIso` for capped sessions — a server-authoritative key
|
|
582
|
+
// that `MJAIAgentSessionEntityServer` refuses to let the session OWNER write, so this write (and only
|
|
583
|
+
// this one, built entirely from server-decided values) runs in the trusted scope.
|
|
584
|
+
const session = await RunWithTrustedSessionConfigWrites(() =>
|
|
585
|
+
this.sessionManager.CreateSession(
|
|
586
|
+
{
|
|
587
|
+
agentID: coAgentID,
|
|
588
|
+
userID: contextUser.ID,
|
|
589
|
+
conversationID: conversationId,
|
|
590
|
+
lastSessionID: lastSessionId,
|
|
591
|
+
config: JSON.stringify(config),
|
|
592
|
+
},
|
|
593
|
+
contextUser,
|
|
594
|
+
provider,
|
|
595
|
+
),
|
|
486
596
|
);
|
|
487
597
|
|
|
488
598
|
// Best-effort: stamp recording-start metadata when the browser captured (with consent) at start.
|
|
489
599
|
await this.stampRecordingStart(session, recordingConsent, recordingStartedAt);
|
|
490
600
|
|
|
491
601
|
const clientTools = this.parseClientTools(clientToolsJson);
|
|
602
|
+
const channelCandidates = this.parseChannelCandidates(channelCandidatesJson);
|
|
492
603
|
// Best-effort model-context hydration: the PRIOR session chain's transcript (ownership-
|
|
493
604
|
// checked, capped) is framed into the system prompt so the model REMEMBERS the last leg.
|
|
494
605
|
// Strictly tolerant — any problem yields no hydration, never a failed start.
|
|
@@ -498,24 +609,103 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
498
609
|
// framed as the resumed transcript, and injecting the same turns twice puts two accounts of
|
|
499
610
|
// the same exchange in one prompt.
|
|
500
611
|
const conversationMessages = await this.loadConversationHistory(
|
|
501
|
-
conversationId, prior?.LegIDs ?? [], contextUser, provider,
|
|
612
|
+
conversationId, prior?.LegIDs ?? [], contextUser, provider, sessionBranchId,
|
|
502
613
|
);
|
|
503
614
|
const result = await this.prepareClientSessionOrClose(
|
|
504
615
|
session, coAgentID, effectiveTargetId, contextUser, provider, preferredModelId, clientTools, prior?.Text,
|
|
505
616
|
configOverridesJson, maxSessionSeconds, applicationId, this.parseAppContext(appContextJson), conversationMessages,
|
|
617
|
+
channelCandidates, showsAgentVideo,
|
|
506
618
|
);
|
|
619
|
+
await this.stampVerificationPolicy(session, result.EffectiveConfigJson, lastSessionId, contextUser, provider);
|
|
507
620
|
// Best-effort restore of the PRIOR session's persisted channel states (e.g. the whiteboard
|
|
508
621
|
// board). Strictly tolerant — any problem yields a null field, never a failed start.
|
|
509
622
|
result.PriorChannelStatesJson = (await this.loadPriorChannelStatesJson(lastSessionId, contextUser, provider)) ?? undefined;
|
|
510
623
|
return result;
|
|
511
624
|
}
|
|
512
625
|
|
|
626
|
+
/**
|
|
627
|
+
* The fork a session starts on: null (Main) when none is named; else the named fork, which must
|
|
628
|
+
* belong to the session's conversation. Returns the fork's stored id.
|
|
629
|
+
* @throws when a fork is named without a conversation, or is not a fork of it, or the fork rows cannot be read
|
|
630
|
+
*/
|
|
631
|
+
private async resolveSessionBranch(
|
|
632
|
+
conversationId: string | undefined,
|
|
633
|
+
branchId: string | undefined,
|
|
634
|
+
contextUser: UserInfo,
|
|
635
|
+
provider: IMetadataProvider,
|
|
636
|
+
): Promise<string | null> {
|
|
637
|
+
const named = branchId?.trim();
|
|
638
|
+
if (!named) {
|
|
639
|
+
return null;
|
|
640
|
+
}
|
|
641
|
+
if (!conversationId) {
|
|
642
|
+
throw new Error(`StartRealtimeClientSession: fork ${named} needs a conversation`);
|
|
643
|
+
}
|
|
644
|
+
try {
|
|
645
|
+
const scope = await ConversationEngine.LoadScope(conversationId, named, contextUser, provider);
|
|
646
|
+
return scope.BranchID;
|
|
647
|
+
} catch (error) {
|
|
648
|
+
throw new Error(`StartRealtimeClientSession: fork ${named} is not a readable fork of conversation ${conversationId}: ${(error as Error).message}`);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Stamps the identity-verification policy snapshot, the configured unverified duration cap and any
|
|
654
|
+
* verified identity inherited from the resumed session onto the new session's `Config`.
|
|
655
|
+
*
|
|
656
|
+
* The effective config (config cascade) is only known after prepare, so this is a second, narrowly
|
|
657
|
+
* scoped write: it runs only when there is something to stamp. It **fails closed** — a cap that could
|
|
658
|
+
* not be applied would leave the session uncapped, so on a save failure the session is closed and the
|
|
659
|
+
* start fails rather than proceeding without its limits.
|
|
660
|
+
*/
|
|
661
|
+
private async stampVerificationPolicy(
|
|
662
|
+
session: MJAIAgentSessionEntity,
|
|
663
|
+
effectiveConfigJson: string | undefined,
|
|
664
|
+
lastSessionId: string | undefined,
|
|
665
|
+
contextUser: UserInfo,
|
|
666
|
+
provider: IMetadataProvider,
|
|
667
|
+
): Promise<void> {
|
|
668
|
+
const plan = BuildMintConfigStamps({
|
|
669
|
+
EffectiveRealtime: this.readEffectiveRealtime(effectiveConfigJson),
|
|
670
|
+
ExistingConfigRaw: session.Config_,
|
|
671
|
+
InheritedVerified: await LoadInheritedVerifiedIdentity(lastSessionId, contextUser, provider),
|
|
672
|
+
NowMs: Date.now(),
|
|
673
|
+
});
|
|
674
|
+
if (!plan) {
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
session.Config_ = plan.NextConfigRaw;
|
|
678
|
+
const saved = await RunWithTrustedSessionConfigWrites(() => session.Save());
|
|
679
|
+
if (!saved) {
|
|
680
|
+
LogError(
|
|
681
|
+
`RealtimeClientSessionResolver.stampVerificationPolicy save failed for session ${session.ID}: ${this.describeSaveFailure(session)}`,
|
|
682
|
+
);
|
|
683
|
+
await this.sessionManager.CloseSession(session.ID, contextUser, provider, 'Error');
|
|
684
|
+
throw new Error('Failed to apply the session limits; the realtime session was not started.');
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/** The `realtime` section of the resolved effective config JSON, or undefined when absent/unparseable. */
|
|
689
|
+
private readEffectiveRealtime(effectiveConfigJson: string | undefined): unknown {
|
|
690
|
+
if (!effectiveConfigJson) {
|
|
691
|
+
return undefined;
|
|
692
|
+
}
|
|
693
|
+
try {
|
|
694
|
+
return (JSON.parse(effectiveConfigJson) as { realtime?: unknown }).realtime;
|
|
695
|
+
} catch (error) {
|
|
696
|
+
LogError(`RealtimeClientSessionResolver: effective config JSON was unparseable while stamping verification policy: ${(error as Error).message}`);
|
|
697
|
+
return undefined;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
513
701
|
/**
|
|
514
702
|
* Execute a single tool call the browser relayed from its provider socket and return the
|
|
515
703
|
* serialized result for the browser to relay back to the model.
|
|
516
704
|
*
|
|
517
|
-
* Ownership-gated
|
|
518
|
-
*
|
|
705
|
+
* Ownership-gated. The target agent id is read from the session config and `CanRun` on it is
|
|
706
|
+
* checked for the caller on every call, because the owner can edit the config. A direct-action
|
|
707
|
+
* call from a scope-limited caller is refused before any elevation. Heartbeats the session on
|
|
708
|
+
* success.
|
|
519
709
|
*
|
|
520
710
|
* @returns The serialized tool result JSON.
|
|
521
711
|
*/
|
|
@@ -531,13 +721,23 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
531
721
|
const { contextUser, provider } = this.requireUserAndProvider(userPayload, providers);
|
|
532
722
|
const session = await this.loadOwnedActiveSession(agentSessionId, contextUser, provider);
|
|
533
723
|
const config = this.readSessionConfig(session);
|
|
724
|
+
await this.assertCanRunTarget(config.targetAgentID, contextUser, provider);
|
|
725
|
+
if (toolName !== INVOKE_TARGET_AGENT_TOOL_NAME && IsScopeLimitedPrincipal(contextUser)) {
|
|
726
|
+
return this.refuseDirectAction(agentSessionId, toolName);
|
|
727
|
+
}
|
|
534
728
|
|
|
535
729
|
// SCOPED-ANONYMOUS ELEVATION (issue #3371): once ownership is proven above, the delegated
|
|
536
730
|
// run + its AI-run-entity writes execute as the system user for a scoped anonymous caller
|
|
537
731
|
// (the caller's role deliberately holds no grants on the run entities). The lead
|
|
538
|
-
// targetAgentID
|
|
539
|
-
//
|
|
732
|
+
// targetAgentID is CanRun-gated against the CALLER just above, and the colleague union
|
|
733
|
+
// just below, so elevation never widens agent authority.
|
|
540
734
|
const runUser = ResolveScopedAnonymousRunUser(contextUser);
|
|
735
|
+
const runIDs = await this.runIDVerifier.Verify(
|
|
736
|
+
session.ID,
|
|
737
|
+
{ CoAgentRunID: config.coAgentRunID, PendingFeedbackRunID: config.pendingFeedbackRunID },
|
|
738
|
+
runUser,
|
|
739
|
+
provider,
|
|
740
|
+
);
|
|
541
741
|
if (runUser !== contextUser) {
|
|
542
742
|
LogStatus(
|
|
543
743
|
`ExecuteRealtimeSessionTool: dispatching relayed tool '${toolName}' for session ${agentSessionId} ` +
|
|
@@ -555,16 +755,23 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
555
755
|
// Multi-target (Move 4): the session's persisted allowed-agent union — a model-named
|
|
556
756
|
// colleague in the call is validated against this; absent ⇒ single-target behavior.
|
|
557
757
|
AllowedAgents: await FilterAllowedAgentsByCanRun(config.allowedAgents, contextUser),
|
|
558
|
-
DirectActions:
|
|
758
|
+
// No DirectActions: the service takes them from the session's start-time projection
|
|
759
|
+
// or the target agent's metadata, never from the owner-editable config.
|
|
559
760
|
// Attribution follows the VISITOR even when `runUser` is elevated: the delegated run
|
|
560
761
|
// row and its context-memory scope must stay the person's, not the system user's.
|
|
561
762
|
AttributionUserID: contextUser.ID,
|
|
562
763
|
// Nest the delegated target-agent run under the co-agent observability run (when present).
|
|
563
|
-
ParentRunID:
|
|
764
|
+
ParentRunID: runIDs.CoAgentRunID,
|
|
765
|
+
// The client-tool channel is keyed on the browser session id, so the delegated run can call
|
|
766
|
+
// the surface's tools; the session-start app context gives its prompt the surface context.
|
|
767
|
+
BrowserSessionID: userPayload.sessionId,
|
|
768
|
+
// The app context is a snapshot the browser sent, so a run elevated to the system user
|
|
769
|
+
// does not get it in its prompt.
|
|
770
|
+
AppContext: runUser === contextUser ? config.appContext : undefined,
|
|
564
771
|
Call: { CallID: callId, ToolName: toolName, Arguments: argsJson },
|
|
565
772
|
OnProgress: this.buildDelegationProgressCallback(pubSub, userPayload, agentSessionId, callId),
|
|
566
773
|
// Resume a previously-paused delegated run (if any) with the user's answer.
|
|
567
|
-
ResumeRunID:
|
|
774
|
+
ResumeRunID: runIDs.PendingFeedbackRunID,
|
|
568
775
|
},
|
|
569
776
|
runUser,
|
|
570
777
|
provider,
|
|
@@ -574,7 +781,7 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
574
781
|
`ExecuteRealtimeSessionTool: completed relayed tool '${toolName}' (callId: ${callId}) in ${durationMs}ms`,
|
|
575
782
|
);
|
|
576
783
|
|
|
577
|
-
if (toolName !==
|
|
784
|
+
if (toolName !== INVOKE_TARGET_AGENT_TOOL_NAME) {
|
|
578
785
|
await this.persistDirectActionTurn(
|
|
579
786
|
session,
|
|
580
787
|
callId,
|
|
@@ -631,6 +838,19 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
631
838
|
};
|
|
632
839
|
}
|
|
633
840
|
|
|
841
|
+
/**
|
|
842
|
+
* The tool result for a direct-action call from a scope-limited caller (see the authorization
|
|
843
|
+
* model above). It has the broker's result shape, so the model hears that the tool is not
|
|
844
|
+
* available and can ask the target agent through `invoke-target-agent` instead.
|
|
845
|
+
*/
|
|
846
|
+
private refuseDirectAction(agentSessionId: string, toolName: string): string {
|
|
847
|
+
LogStatus(`ExecuteRealtimeSessionTool: refused direct action '${toolName}' for a scope-limited caller on session ${agentSessionId}.`);
|
|
848
|
+
return JSON.stringify({
|
|
849
|
+
success: false,
|
|
850
|
+
output: `The tool '${toolName}' is not available in this session. Ask the agent to do it with ${INVOKE_TARGET_AGENT_TOOL_NAME}.`,
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
|
|
634
854
|
/**
|
|
635
855
|
* Rolls the session's `pendingFeedbackRunID` forward after a relayed tool call. The id we just
|
|
636
856
|
* consumed (resumed or none) is dropped; a new paused run id is stored only when the run paused
|
|
@@ -646,17 +866,10 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
646
866
|
if (!previous && !pausedRunID) {
|
|
647
867
|
return;
|
|
648
868
|
}
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
coAgentRunStepID: config.coAgentRunStepID,
|
|
654
|
-
pendingFeedbackRunID: pausedRunID,
|
|
655
|
-
// Preserve the server-authoritative voice deadline across config rewrites.
|
|
656
|
-
maxSessionDeadlineIso: config.maxSessionDeadlineIso,
|
|
657
|
-
applicationID: config.applicationID,
|
|
658
|
-
allowedAgents: config.allowedAgents,
|
|
659
|
-
};
|
|
869
|
+
// Start from the RAW stored config so every key survives the rewrite — including the
|
|
870
|
+
// server-authoritative `maxSessionDeadlineIso` and `identityVerification` (dropping either would be
|
|
871
|
+
// refused by `MJAIAgentSessionEntityServer`, and silently losing a verification would be worse).
|
|
872
|
+
const next = { ...ParseConfigObject(session.Config_), targetAgentID: config.targetAgentID, pendingFeedbackRunID: pausedRunID };
|
|
660
873
|
session.Config_ = JSON.stringify(next);
|
|
661
874
|
if (!(await session.Save())) {
|
|
662
875
|
LogError(
|
|
@@ -701,14 +914,15 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
701
914
|
// conversation (run-viewer observability parity). Best-effort — never fails the transcript relay.
|
|
702
915
|
// The prompt-run write runs as the scoped-anonymous elevated user (issue #3371) — the visible
|
|
703
916
|
// Conversation Detail above deliberately stays on the caller.
|
|
704
|
-
const
|
|
917
|
+
const runUser = ResolveScopedAnonymousRunUser(contextUser);
|
|
918
|
+
const promptRunID = await this.readPromptRunID(session, runUser, provider);
|
|
705
919
|
if (promptRunID) {
|
|
706
920
|
await this.clientSessionService.AppendPromptRunMessage(
|
|
707
921
|
promptRunID,
|
|
708
922
|
this.mapTranscriptRoleToChatRole(role),
|
|
709
923
|
text,
|
|
710
924
|
replacesPrevious ?? false,
|
|
711
|
-
|
|
925
|
+
runUser,
|
|
712
926
|
provider,
|
|
713
927
|
);
|
|
714
928
|
}
|
|
@@ -731,7 +945,7 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
731
945
|
* the call and uploads the assembled blob (base64) once the session ends.
|
|
732
946
|
*
|
|
733
947
|
* Hard gates (in order):
|
|
734
|
-
* 1. Ownership — `
|
|
948
|
+
* 1. Ownership — `LoadOwnedRealtimeSession` enforces `UserID === contextUser.ID` (throws on violation).
|
|
735
949
|
* 2. Consent — `consent !== true` is a hard refusal: no audio is ever stored without it.
|
|
736
950
|
* 3. Storage configuration — the session's agent must resolve a recording storage account (the
|
|
737
951
|
* agent's `RecordingStorageProviderID`, else `AttachmentStorageProviderID`).
|
|
@@ -760,18 +974,18 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
760
974
|
@Arg('peaks', () => [Float], { nullable: true }) peaks?: number[],
|
|
761
975
|
): Promise<UploadRealtimeRecordingResult> {
|
|
762
976
|
const { contextUser, provider } = this.requireUserAndProvider(ctx.userPayload, ctx.providers);
|
|
763
|
-
const session = await
|
|
977
|
+
const session = await LoadOwnedRealtimeSession(agentSessionId, contextUser, provider);
|
|
764
978
|
|
|
765
979
|
// HARD consent gate — no audio is ever stored without an explicit grant.
|
|
766
980
|
if (consent !== true) {
|
|
767
981
|
return { Success: false, ErrorMessage: 'Recording consent was not granted.' };
|
|
768
982
|
}
|
|
769
983
|
|
|
770
|
-
//
|
|
771
|
-
//
|
|
772
|
-
//
|
|
773
|
-
// session link, so nothing here depends on the caller's identity.
|
|
774
|
-
const runUser =
|
|
984
|
+
// ELEVATION (issue #3371, #5195): past the ownership + consent gates, the store is server-side
|
|
985
|
+
// plumbing over entities (MJ: AI Agents read, MJ: Files, the file-session link, the session's
|
|
986
|
+
// recording stamp) that neither a scoped-anonymous relay role nor the stock UI role holds.
|
|
987
|
+
// Attribution flows through the session link, so nothing here depends on the caller's identity.
|
|
988
|
+
const runUser = ResolveRecordingStoreUser(contextUser);
|
|
775
989
|
try {
|
|
776
990
|
const agent = await provider.GetEntityObject<MJAIAgentEntity>('MJ: AI Agents', runUser);
|
|
777
991
|
if (!(await agent.Load(session.AgentID))) {
|
|
@@ -822,11 +1036,14 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
822
1036
|
|
|
823
1037
|
/**
|
|
824
1038
|
* Uploads ONE crash-recovery audio shard (~15s) for an IN-PROGRESS recording into the session's
|
|
825
|
-
* folder (`realtime-recordings/<sessionId>/seg-NNNN
|
|
826
|
-
*
|
|
827
|
-
*
|
|
828
|
-
*
|
|
829
|
-
*
|
|
1039
|
+
* folder (`realtime-recordings/<sessionId>/seg-NNNN.r<rate>.pcm` for PCM with a rate in its MIME type,
|
|
1040
|
+
* else the legacy `seg-NNNN.<ext>`) as a raw storage object. Durability insurance during a live call
|
|
1041
|
+
* so a browser/tab death loses at most the last window; the shards are deleted once the canonical
|
|
1042
|
+
* consolidated file lands via {@link UploadRealtimeRecording}, or by the session janitor once it has
|
|
1043
|
+
* recovered a recording from them when that upload never arrives. Ownership-
|
|
1044
|
+
* gated; consent was already established at session start. Refused (`false`) once the session has
|
|
1045
|
+
* its consolidated recording, since nothing would delete a shard written after that. Best-effort —
|
|
1046
|
+
* returns `false` (never throws) on any problem so a failed shard never disrupts the live call.
|
|
830
1047
|
*
|
|
831
1048
|
* @param agentSessionId The in-progress session (ownership-gated).
|
|
832
1049
|
* @param segmentIndex 0-based shard index within the session.
|
|
@@ -844,7 +1061,13 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
844
1061
|
): Promise<boolean> {
|
|
845
1062
|
try {
|
|
846
1063
|
const { contextUser, provider } = this.requireUserAndProvider(ctx.userPayload, ctx.providers);
|
|
847
|
-
const session = await
|
|
1064
|
+
const session = await LoadOwnedRealtimeSession(agentSessionId, contextUser, provider);
|
|
1065
|
+
// The consolidated recording already landed and its upload deleted the shards; a shard
|
|
1066
|
+
// arriving now (one still on the wire at hang-up) would be an orphan nothing deletes.
|
|
1067
|
+
if (session.RecordingFileID) {
|
|
1068
|
+
LogStatus(`RealtimeClientSessionResolver.UploadRealtimeRecordingSegment: shard ${segmentIndex} for session ${agentSessionId} refused — the session already has its consolidated recording`);
|
|
1069
|
+
return false;
|
|
1070
|
+
}
|
|
848
1071
|
// Scoped-anonymous elevation (issue #3371) — same rationale as UploadRealtimeRecording.
|
|
849
1072
|
const runUser = ResolveScopedAnonymousRunUser(contextUser);
|
|
850
1073
|
const agent = await provider.GetEntityObject<MJAIAgentEntity>('MJ: AI Agents', runUser);
|
|
@@ -906,7 +1129,8 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
906
1129
|
provider,
|
|
907
1130
|
);
|
|
908
1131
|
|
|
909
|
-
const
|
|
1132
|
+
const runUser = ResolveScopedAnonymousRunUser(contextUser);
|
|
1133
|
+
const promptRunID = await this.readPromptRunID(session, runUser, provider);
|
|
910
1134
|
if (!promptRunID) {
|
|
911
1135
|
return false;
|
|
912
1136
|
}
|
|
@@ -915,7 +1139,7 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
915
1139
|
'assistant',
|
|
916
1140
|
this.formatToolTurn(toolName, argsJson, resultJson),
|
|
917
1141
|
false,
|
|
918
|
-
|
|
1142
|
+
runUser,
|
|
919
1143
|
provider,
|
|
920
1144
|
);
|
|
921
1145
|
}
|
|
@@ -960,7 +1184,7 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
960
1184
|
@Arg('callId', () => String, { nullable: true }) callId?: string,
|
|
961
1185
|
): Promise<CancelRealtimeSessionToolResult> {
|
|
962
1186
|
const { contextUser, provider } = this.requireUserAndProvider(userPayload, providers);
|
|
963
|
-
await
|
|
1187
|
+
await LoadOwnedRealtimeSession(agentSessionId, contextUser, provider);
|
|
964
1188
|
try {
|
|
965
1189
|
const aborted = this.clientSessionService.CancelInFlightDelegations(agentSessionId, callId);
|
|
966
1190
|
return { AbortedCount: aborted, Success: true };
|
|
@@ -987,8 +1211,15 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
987
1211
|
* (observability creation was skipped), a failed load, or a failed save all log and return
|
|
988
1212
|
* `false` — usage relay must never break a live call.
|
|
989
1213
|
*
|
|
1214
|
+
* The optional details are the per-modality usage since the last relay: input and output
|
|
1215
|
+
* blocks shaped like Core's `RealtimeUsageModalityDetail`, avatar video seconds included. They
|
|
1216
|
+
* add into the usage record on the run's `ModelSpecificResponseDetails`, which prices the avatar
|
|
1217
|
+
* video at finalize. Only the fields a block defines, with values that are finite numbers of at
|
|
1218
|
+
* least 0, are kept; a relay that carries only details (avatar seconds, no tokens) is stored.
|
|
1219
|
+
*
|
|
990
1220
|
* @param inputTokens Input-token DELTA to add (negative/non-finite values are clamped to 0).
|
|
991
1221
|
* @param outputTokens Output-token DELTA to add (negative/non-finite values are clamped to 0).
|
|
1222
|
+
* @param usageDetailsJson The per-modality usage since the last relay, as JSON (`{ Input, Output }`).
|
|
992
1223
|
* @returns `true` when the accumulated usage was persisted; `false` on any tolerated failure.
|
|
993
1224
|
*/
|
|
994
1225
|
@Mutation(() => Boolean)
|
|
@@ -997,25 +1228,28 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
997
1228
|
@Arg('inputTokens', () => Int) inputTokens: number,
|
|
998
1229
|
@Arg('outputTokens', () => Int) outputTokens: number,
|
|
999
1230
|
@Ctx() { userPayload, providers }: AppContext,
|
|
1231
|
+
@Arg('usageDetailsJson', () => String, { nullable: true }) usageDetailsJson?: string,
|
|
1000
1232
|
): Promise<boolean> {
|
|
1001
1233
|
const { contextUser, provider } = this.requireUserAndProvider(userPayload, providers);
|
|
1002
|
-
const session = await
|
|
1234
|
+
const session = await LoadOwnedRealtimeSession(agentSessionId, contextUser, provider);
|
|
1003
1235
|
|
|
1004
1236
|
const inputDelta = this.clampTokenDelta(inputTokens);
|
|
1005
1237
|
const outputDelta = this.clampTokenDelta(outputTokens);
|
|
1006
|
-
|
|
1238
|
+
const details = this.readUsageDetails(usageDetailsJson);
|
|
1239
|
+
if (inputDelta === 0 && outputDelta === 0 && !details) {
|
|
1007
1240
|
return true; // nothing to add — a no-op flush is a success, not a failure
|
|
1008
1241
|
}
|
|
1009
1242
|
|
|
1010
|
-
|
|
1243
|
+
// Runs as the scoped-anonymous elevated user (issue #3371) — the caller's role holds no prompt-run grants.
|
|
1244
|
+
const runUser = ResolveScopedAnonymousRunUser(contextUser);
|
|
1245
|
+
const promptRunID = await this.readPromptRunID(session, runUser, provider);
|
|
1011
1246
|
if (!promptRunID) {
|
|
1012
1247
|
return false; // no observability prompt run for this session — logged in the helper
|
|
1013
1248
|
}
|
|
1014
1249
|
// Delegate to the service so usage writes share the per-run serialization with transcript-message
|
|
1015
1250
|
// appends — otherwise the frequent usage save clobbers freshly-appended Messages (and vice-versa).
|
|
1016
|
-
// Runs as the scoped-anonymous elevated user (issue #3371) — the caller's role holds no prompt-run grants.
|
|
1017
1251
|
return this.clientSessionService.AccumulatePromptRunUsage(
|
|
1018
|
-
promptRunID, inputDelta, outputDelta,
|
|
1252
|
+
promptRunID, inputDelta, outputDelta, runUser, provider, details ?? undefined,
|
|
1019
1253
|
);
|
|
1020
1254
|
}
|
|
1021
1255
|
|
|
@@ -1024,6 +1258,23 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
1024
1258
|
return Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
|
|
1025
1259
|
}
|
|
1026
1260
|
|
|
1261
|
+
/**
|
|
1262
|
+
* Reads the relayed per-modality usage, or `null` when there is none worth storing: absent,
|
|
1263
|
+
* malformed, longer than a relay ever needs (a client cannot make the server parse an
|
|
1264
|
+
* arbitrarily large string), or holding no amount above 0.
|
|
1265
|
+
*/
|
|
1266
|
+
private readUsageDetails(usageDetailsJson: string | undefined): RealtimeUsageRecord | null {
|
|
1267
|
+
if (typeof usageDetailsJson !== 'string') {
|
|
1268
|
+
return null;
|
|
1269
|
+
}
|
|
1270
|
+
if (usageDetailsJson.length > MAX_USAGE_DETAILS_JSON_LENGTH) {
|
|
1271
|
+
LogError(`RelayRealtimeUsage: usage details of ${usageDetailsJson.length} characters exceed ${MAX_USAGE_DETAILS_JSON_LENGTH} — details dropped.`);
|
|
1272
|
+
return null;
|
|
1273
|
+
}
|
|
1274
|
+
const details = ParseRealtimeUsageRecord(usageDetailsJson);
|
|
1275
|
+
return HasRealtimeUsage(details) ? details : null;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1027
1278
|
/**
|
|
1028
1279
|
* Sanitizes client-supplied waveform peaks before they're persisted: drops anything that isn't a
|
|
1029
1280
|
* finite number, clamps each to `[0, 1]`, and caps the array length so a hostile client can't bloat
|
|
@@ -1043,24 +1294,40 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
1043
1294
|
|
|
1044
1295
|
/**
|
|
1045
1296
|
* Reads the co-agent `AIPromptRun` id from the session config WITHOUT the relay path's
|
|
1046
|
-
* throw-on-missing-target semantics
|
|
1047
|
-
*
|
|
1297
|
+
* throw-on-missing-target semantics, and keeps it only when it is the prompt run of the
|
|
1298
|
+
* session's co-agent run (the owner can edit the config). Usage relay is best-effort, so a
|
|
1299
|
+
* missing/malformed config, an absent id, or an id of another session just logs and returns `null`.
|
|
1300
|
+
*
|
|
1301
|
+
* @param runUser The user the prompt-run write runs as; the check runs as the same user.
|
|
1048
1302
|
*/
|
|
1049
|
-
private readPromptRunID(
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1303
|
+
private async readPromptRunID(
|
|
1304
|
+
session: MJAIAgentSessionEntity,
|
|
1305
|
+
runUser: UserInfo,
|
|
1306
|
+
provider: IMetadataProvider,
|
|
1307
|
+
): Promise<string | null> {
|
|
1308
|
+
const config = this.tryReadSessionConfig(session);
|
|
1309
|
+
if (!config) {
|
|
1310
|
+
LogError(`RelayRealtimeUsage: session ${session.ID} has no parseable config — usage delta dropped.`);
|
|
1311
|
+
return null;
|
|
1312
|
+
}
|
|
1313
|
+
if (!config.promptRunID) {
|
|
1055
1314
|
LogStatus(
|
|
1056
1315
|
`RelayRealtimeUsage: session ${session.ID} has no co-agent promptRunID (observability run ` +
|
|
1057
1316
|
'creation was skipped or failed) — usage delta dropped.',
|
|
1058
1317
|
);
|
|
1059
1318
|
return null;
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1319
|
+
}
|
|
1320
|
+
const verified = await this.runIDVerifier.Verify(
|
|
1321
|
+
session.ID,
|
|
1322
|
+
{ CoAgentRunID: config.coAgentRunID, PromptRunID: config.promptRunID },
|
|
1323
|
+
runUser,
|
|
1324
|
+
provider,
|
|
1325
|
+
);
|
|
1326
|
+
if (!verified.PromptRunID) {
|
|
1327
|
+
LogError(`RelayRealtimeUsage: session ${session.ID} names a promptRunID that is not its co-agent prompt run — ignored.`);
|
|
1062
1328
|
return null;
|
|
1063
1329
|
}
|
|
1330
|
+
return verified.PromptRunID;
|
|
1064
1331
|
}
|
|
1065
1332
|
|
|
1066
1333
|
/**
|
|
@@ -1092,8 +1359,12 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
1092
1359
|
@Ctx() { userPayload, providers }: AppContext,
|
|
1093
1360
|
): Promise<boolean> {
|
|
1094
1361
|
const { contextUser, provider } = this.requireUserAndProvider(userPayload, providers);
|
|
1095
|
-
const session = await
|
|
1362
|
+
const session = await LoadOwnedRealtimeSession(agentSessionId, contextUser, provider);
|
|
1096
1363
|
|
|
1364
|
+
if (!this.isChannelInSessionScope(session, channelName)) {
|
|
1365
|
+
LogError(`SaveSessionChannelState: channel '${channelName}' is not in session ${agentSessionId}'s resolved scope — state not saved.`);
|
|
1366
|
+
return false;
|
|
1367
|
+
}
|
|
1097
1368
|
if (stateJson.length > MAX_CHANNEL_STATE_CHARS) {
|
|
1098
1369
|
LogError(
|
|
1099
1370
|
`SaveSessionChannelState: rejected oversized state for session ${agentSessionId} / channel '${channelName}' ` +
|
|
@@ -1171,8 +1442,13 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
1171
1442
|
@Ctx() { userPayload, providers }: AppContext,
|
|
1172
1443
|
): Promise<SaveSessionChannelArtifactResult> {
|
|
1173
1444
|
const { contextUser, provider } = this.requireUserAndProvider(userPayload, providers);
|
|
1174
|
-
const session = await
|
|
1445
|
+
const session = await LoadOwnedRealtimeSession(agentSessionId, contextUser, provider);
|
|
1175
1446
|
|
|
1447
|
+
if (!this.isChannelInSessionScope(session, channelName)) {
|
|
1448
|
+
const message = `SaveSessionChannelArtifact: channel '${channelName}' is not in session ${agentSessionId}'s resolved scope — nothing saved.`;
|
|
1449
|
+
LogError(message);
|
|
1450
|
+
return { Success: false, ErrorMessage: message, ConversationDetailLinked: false };
|
|
1451
|
+
}
|
|
1176
1452
|
if (contentJson.length > MAX_CHANNEL_STATE_CHARS) {
|
|
1177
1453
|
const message =
|
|
1178
1454
|
`SaveSessionChannelArtifact: rejected oversized content for session ${agentSessionId} / channel '${channelName}' ` +
|
|
@@ -1210,8 +1486,10 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
1210
1486
|
}
|
|
1211
1487
|
|
|
1212
1488
|
/**
|
|
1213
|
-
* Authorization gate for
|
|
1214
|
-
*
|
|
1489
|
+
* Authorization gate for the **target** agent: the caller must be able to run it. Denial throws.
|
|
1490
|
+
* {@link RealtimeClientSessionResolver.StartRealtimeClientSession} runs it before any session is
|
|
1491
|
+
* created, and {@link RealtimeClientSessionResolver.ExecuteRealtimeSessionTool} runs it on every
|
|
1492
|
+
* relayed call, because the owner can edit the target in the session config.
|
|
1215
1493
|
*/
|
|
1216
1494
|
private async assertCanRunTarget(
|
|
1217
1495
|
targetAgentId: string,
|
|
@@ -1479,6 +1757,7 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
1479
1757
|
* resumed session remembers the previous leg(s).
|
|
1480
1758
|
* @param conversationMessages Optional capped history of the TEXT conversation this session is
|
|
1481
1759
|
* starting from (from {@link loadConversationHistory}) — framed as "Conversation so far".
|
|
1760
|
+
* @param showsAgentVideo `false` when the app shows no agent video: the session asks for no avatar (`host`).
|
|
1482
1761
|
*/
|
|
1483
1762
|
private async prepareClientSessionOrClose(
|
|
1484
1763
|
session: MJAIAgentSessionEntity,
|
|
@@ -1494,6 +1773,8 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
1494
1773
|
applicationId?: string,
|
|
1495
1774
|
appContext?: AppContextSnapshot,
|
|
1496
1775
|
conversationMessages?: ChatMessage[],
|
|
1776
|
+
channelCandidates?: RealtimeChannelCandidate[],
|
|
1777
|
+
showsAgentVideo?: boolean,
|
|
1497
1778
|
): Promise<StartRealtimeClientSessionResult> {
|
|
1498
1779
|
const prep = await this.clientSessionService.PrepareClientSession(
|
|
1499
1780
|
{
|
|
@@ -1521,6 +1802,14 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
1521
1802
|
// (allowed-agent union); the snapshot is injected into the system prompt at mint.
|
|
1522
1803
|
ApplicationID: applicationId,
|
|
1523
1804
|
AppContext: appContext,
|
|
1805
|
+
// Scope-limited callers get no direct actions (see the authorization model above), so
|
|
1806
|
+
// the model is not given tools that the relay refuses.
|
|
1807
|
+
ExcludeDirectActions: IsScopeLimitedPrincipal(contextUser),
|
|
1808
|
+
// The channels the browser could mount: scoped here (registry + cascade) and handed back
|
|
1809
|
+
// as the resolved policy; the declared tools are narrowed to match.
|
|
1810
|
+
ChannelCandidates: channelCandidates,
|
|
1811
|
+
// An app with no channel that shows the agent's video: no avatar is asked for (reason `host`).
|
|
1812
|
+
ShowsAgentVideo: showsAgentVideo ?? undefined,
|
|
1524
1813
|
},
|
|
1525
1814
|
// SCOPED-ANONYMOUS ELEVATION (issue #3371): the prepare creates the co-agent
|
|
1526
1815
|
// observability AIAgentRun/AIPromptRun/run-step, which a scoped anonymous caller's role
|
|
@@ -1541,7 +1830,10 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
1541
1830
|
session, targetAgentId, prep.CoAgentRunID, prep.PromptRunID, prep.CoAgentRunStepID,
|
|
1542
1831
|
applicationId, prep.EffectiveConfig?.realtime?.allowedAgents,
|
|
1543
1832
|
prep.EffectiveConfig?.realtime?.directActions,
|
|
1833
|
+
prep.ClientPolicy?.Channels.map((c) => c.Key),
|
|
1834
|
+
appContext,
|
|
1544
1835
|
);
|
|
1836
|
+
this.pruneExcludedServerChannels(session.ID, prep.ClientPolicy);
|
|
1545
1837
|
|
|
1546
1838
|
const cfg = prep.ClientConfig;
|
|
1547
1839
|
return {
|
|
@@ -1551,20 +1843,43 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
1551
1843
|
Model: cfg.Model,
|
|
1552
1844
|
EphemeralToken: cfg.EphemeralToken,
|
|
1553
1845
|
ExpiresAt: cfg.ExpiresAt,
|
|
1846
|
+
Transport: cfg.Transport,
|
|
1847
|
+
RelayUrl: cfg.RelayUrl,
|
|
1554
1848
|
SessionConfigJson: JSON.stringify(cfg.SessionConfig),
|
|
1555
1849
|
ModelName: prep.ModelName,
|
|
1556
1850
|
DriverClass: prep.DriverClass,
|
|
1557
1851
|
NarrationInstructionsTemplate: prep.NarrationInstructionsTemplate,
|
|
1558
1852
|
NarrationPaceMs: prep.NarrationPaceMs,
|
|
1559
1853
|
EffectiveConfigJson: prep.EffectiveConfig ? JSON.stringify(prep.EffectiveConfig) : undefined,
|
|
1854
|
+
ClientPolicyJson: prep.ClientPolicy ? JSON.stringify(prep.ClientPolicy) : undefined,
|
|
1855
|
+
AvatarStatusJson: prep.AvatarStatus ? JSON.stringify(prep.AvatarStatus) : undefined,
|
|
1560
1856
|
};
|
|
1561
1857
|
}
|
|
1562
1858
|
|
|
1859
|
+
/**
|
|
1860
|
+
* Drops the server-side channel plugins of channels the resolved scope left out. Server plugins
|
|
1861
|
+
* start when the session row is created — before the scope is known — so an excluded channel's
|
|
1862
|
+
* server half would otherwise keep running. A no-op when the caller reported no candidates.
|
|
1863
|
+
*/
|
|
1864
|
+
private pruneExcludedServerChannels(agentSessionID: string, policy: RealtimeSessionClientPolicy | undefined): void {
|
|
1865
|
+
const excluded = (policy?.ExcludedChannels ?? []).map((e) => e.Key);
|
|
1866
|
+
if (excluded.length === 0) {
|
|
1867
|
+
return;
|
|
1868
|
+
}
|
|
1869
|
+
const dropped = RealtimeChannelServerHost.Instance.PruneSessionChannels(agentSessionID, excluded);
|
|
1870
|
+
if (dropped.length > 0) {
|
|
1871
|
+
LogStatus(`StartRealtimeClientSession: dropped the server plugins of out-of-scope channel(s) [${dropped.join(', ')}] for session ${agentSessionID}.`);
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1563
1875
|
/**
|
|
1564
1876
|
* Writes the co-agent observability run ids into the session's `Config_` alongside the
|
|
1565
|
-
*
|
|
1566
|
-
* finalize the runs (and on relay to nest delegated runs).
|
|
1567
|
-
*
|
|
1877
|
+
* `targetAgentID`, then saves the session. These ids are read back on close to
|
|
1878
|
+
* finalize the runs (and on relay to nest delegated runs). The app awareness (application id,
|
|
1879
|
+
* allowed agents, direct actions, app-context snapshot) is written with them; relays read the
|
|
1880
|
+
* allowed agents and the app context, never the direct actions.
|
|
1881
|
+
* Best-effort: a save failure is logged, not thrown — the voice session still proceeds, it just
|
|
1882
|
+
* won't carry the run ids.
|
|
1568
1883
|
*/
|
|
1569
1884
|
private async persistObservabilityRunIDs(
|
|
1570
1885
|
session: MJAIAgentSessionEntity,
|
|
@@ -1575,22 +1890,29 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
1575
1890
|
applicationID?: string,
|
|
1576
1891
|
allowedAgents?: RealtimeAllowedAgent[],
|
|
1577
1892
|
directActions?: RealtimeDirectActionsConfig,
|
|
1893
|
+
channels?: string[],
|
|
1894
|
+
appContext?: AppContextSnapshot,
|
|
1578
1895
|
): Promise<void> {
|
|
1579
|
-
//
|
|
1580
|
-
//
|
|
1581
|
-
|
|
1582
|
-
const config
|
|
1896
|
+
// Start from the RAW stored config so every key set at session start survives — the
|
|
1897
|
+
// server-authoritative voice deadline (`maxSessionDeadlineIso`) and the per-session media-kit
|
|
1898
|
+
// override among them — rather than rebuilding from a whitelist that silently drops the rest.
|
|
1899
|
+
const config = {
|
|
1900
|
+
...ParseConfigObject(session.Config_),
|
|
1583
1901
|
targetAgentID,
|
|
1584
1902
|
coAgentRunID,
|
|
1585
1903
|
promptRunID,
|
|
1586
1904
|
coAgentRunStepID,
|
|
1587
|
-
maxSessionDeadlineIso: existing?.maxSessionDeadlineIso,
|
|
1588
1905
|
applicationID,
|
|
1906
|
+
appContext: this.appContextToStore(appContext, session.ID),
|
|
1589
1907
|
allowedAgents: allowedAgents && allowedAgents.length > 0 ? allowedAgents : undefined,
|
|
1590
1908
|
directActions,
|
|
1909
|
+
channels,
|
|
1591
1910
|
};
|
|
1592
1911
|
session.Config_ = JSON.stringify(config);
|
|
1593
|
-
|
|
1912
|
+
// Every value written here is server-decided (run ids, the resolved target, the in-scope channel
|
|
1913
|
+
// set), and `channels` is a server-authoritative key the owner may not write — so this save runs in
|
|
1914
|
+
// the trusted scope. Protected keys already in the stored config are carried forward unchanged.
|
|
1915
|
+
const saved = await RunWithTrustedSessionConfigWrites(() => session.Save());
|
|
1594
1916
|
if (!saved) {
|
|
1595
1917
|
LogError(
|
|
1596
1918
|
`RealtimeClientSessionResolver.persistObservabilityRunIDs save failed for session ${session.ID}: ${this.describeSaveFailure(session)}`,
|
|
@@ -1599,41 +1921,43 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
1599
1921
|
}
|
|
1600
1922
|
|
|
1601
1923
|
/**
|
|
1602
|
-
*
|
|
1603
|
-
*
|
|
1924
|
+
* Returns the app-context snapshot to store on the session config. A snapshot whose JSON is longer
|
|
1925
|
+
* than {@link MAX_CLIENT_TOOLS_JSON_CHARS} is not stored: a warning is logged and `undefined` is
|
|
1926
|
+
* returned, so the session still starts and its delegated runs get no app context.
|
|
1927
|
+
*
|
|
1928
|
+
* @param appContext The snapshot the browser sent at session start, or undefined.
|
|
1929
|
+
* @param sessionID The session the snapshot belongs to (for the warning).
|
|
1930
|
+
* @returns The snapshot to store, or `undefined`.
|
|
1604
1931
|
*/
|
|
1605
|
-
private
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
provider: IMetadataProvider,
|
|
1609
|
-
): Promise<MJAIAgentSessionEntity> {
|
|
1610
|
-
const session = await this.loadOwnedSession(agentSessionId, contextUser, provider);
|
|
1611
|
-
if (session.Status === 'Closed') {
|
|
1612
|
-
throw new Error(`Realtime session ${agentSessionId} is closed`);
|
|
1932
|
+
private appContextToStore(appContext: AppContextSnapshot | undefined, sessionID: string): AppContextSnapshot | undefined {
|
|
1933
|
+
if (!appContext) {
|
|
1934
|
+
return undefined;
|
|
1613
1935
|
}
|
|
1614
|
-
|
|
1936
|
+
const chars = JSON.stringify(appContext).length;
|
|
1937
|
+
if (chars > MAX_CLIENT_TOOLS_JSON_CHARS) {
|
|
1938
|
+
LogErrorEx({
|
|
1939
|
+
message:
|
|
1940
|
+
`RealtimeClientSessionResolver: app context for session ${sessionID} not stored — ${chars} chars ` +
|
|
1941
|
+
`exceeds the ${MAX_CLIENT_TOOLS_JSON_CHARS} cap; delegated runs get no app context.`,
|
|
1942
|
+
severity: 'warning',
|
|
1943
|
+
});
|
|
1944
|
+
return undefined;
|
|
1945
|
+
}
|
|
1946
|
+
return appContext;
|
|
1615
1947
|
}
|
|
1616
1948
|
|
|
1617
1949
|
/**
|
|
1618
|
-
* Loads a session
|
|
1619
|
-
*
|
|
1620
|
-
* legitimately arrives after the session closed. Throws when not found / not owned.
|
|
1950
|
+
* Loads a session, enforcing inbound ownership (`UserID === contextUser.ID`) and that it is not
|
|
1951
|
+
* `Closed`. Throws on any violation. Returns the loaded session for the caller to read from.
|
|
1621
1952
|
*/
|
|
1622
|
-
private async
|
|
1953
|
+
private async loadOwnedActiveSession(
|
|
1623
1954
|
agentSessionId: string,
|
|
1624
1955
|
contextUser: UserInfo,
|
|
1625
1956
|
provider: IMetadataProvider,
|
|
1626
1957
|
): Promise<MJAIAgentSessionEntity> {
|
|
1627
|
-
const session = await
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
throw new Error(`Realtime session ${agentSessionId} not found`);
|
|
1631
|
-
}
|
|
1632
|
-
if (!UUIDsEqual(session.UserID, contextUser.ID)) {
|
|
1633
|
-
LogError(
|
|
1634
|
-
`RealtimeClientSessionResolver: ownership check failed — user ${contextUser.ID} attempted to operate on session ${agentSessionId} owned by ${session.UserID}`,
|
|
1635
|
-
);
|
|
1636
|
-
throw new Error('Not authorized: you do not own this realtime session');
|
|
1958
|
+
const session = await LoadOwnedRealtimeSession(agentSessionId, contextUser, provider);
|
|
1959
|
+
if (session.Status === 'Closed') {
|
|
1960
|
+
throw new Error(`Realtime session ${agentSessionId} is closed`);
|
|
1637
1961
|
}
|
|
1638
1962
|
return session;
|
|
1639
1963
|
}
|
|
@@ -1843,6 +2167,10 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
1843
2167
|
* as the resumed transcript are removed, so the prompt never carries two accounts of one
|
|
1844
2168
|
* exchange.
|
|
1845
2169
|
*
|
|
2170
|
+
* Only the rows on the session's path that no rerun replaced are read: the fork it started on,
|
|
2171
|
+
* or Main, in `Sequence` order. When that path cannot be built — its branch rows are missing or invalid — the session
|
|
2172
|
+
* starts with no history, never with every branch's rows.
|
|
2173
|
+
*
|
|
1846
2174
|
* Strictly tolerant — no conversation, a failed read, or any throw yields `[]`, never a failed
|
|
1847
2175
|
* session start. The call still works; it just starts cold.
|
|
1848
2176
|
*
|
|
@@ -1850,6 +2178,7 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
1850
2178
|
* @param coveredSessionIDs Session ids already framed via `PriorTranscript`.
|
|
1851
2179
|
* @param contextUser The calling user (RunView scope — the read is row-level-secured).
|
|
1852
2180
|
* @param provider The request-scoped metadata provider.
|
|
2181
|
+
* @param branchId The fork the session started on; null is Main.
|
|
1853
2182
|
* @returns The capped history, oldest first.
|
|
1854
2183
|
*/
|
|
1855
2184
|
private async loadConversationHistory(
|
|
@@ -1857,12 +2186,22 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
1857
2186
|
coveredSessionIDs: string[],
|
|
1858
2187
|
contextUser: UserInfo,
|
|
1859
2188
|
provider: IMetadataProvider,
|
|
2189
|
+
branchId: string | null,
|
|
1860
2190
|
): Promise<ChatMessage[]> {
|
|
1861
2191
|
if (!conversationId) {
|
|
1862
2192
|
return [];
|
|
1863
2193
|
}
|
|
2194
|
+
const rv = RunView.FromMetadataProvider(provider);
|
|
2195
|
+
let pathFilter: string;
|
|
2196
|
+
try {
|
|
2197
|
+
pathFilter = await ConversationEngine.BranchPathFilterFresh(conversationId, branchId, contextUser, provider);
|
|
2198
|
+
} catch (error) {
|
|
2199
|
+
LogError(
|
|
2200
|
+
`StartRealtimeClientSession: could not build the path of the session's view for conversation ${conversationId}: ${(error as Error).message}`,
|
|
2201
|
+
);
|
|
2202
|
+
return [];
|
|
2203
|
+
}
|
|
1864
2204
|
try {
|
|
1865
|
-
const rv = RunView.FromMetadataProvider(provider);
|
|
1866
2205
|
const result = await rv.RunView<{
|
|
1867
2206
|
Role: string;
|
|
1868
2207
|
Message: string | null;
|
|
@@ -1871,9 +2210,9 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
1871
2210
|
}>(
|
|
1872
2211
|
{
|
|
1873
2212
|
EntityName: CONVERSATION_DETAIL_ENTITY,
|
|
1874
|
-
ExtraFilter:
|
|
2213
|
+
ExtraFilter: ConversationEngine.LiveRowsFilter(pathFilter),
|
|
1875
2214
|
Fields: ['ID', 'Role', 'Message', 'HiddenToUser', 'AgentSessionID', '__mj_CreatedAt'],
|
|
1876
|
-
OrderBy: '
|
|
2215
|
+
OrderBy: 'Sequence ASC',
|
|
1877
2216
|
ResultType: 'simple',
|
|
1878
2217
|
},
|
|
1879
2218
|
contextUser,
|
|
@@ -2240,6 +2579,7 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
2240
2579
|
const detail = await provider.GetEntityObject<MJConversationDetailEntity>(CONVERSATION_DETAIL_ENTITY, callerUser);
|
|
2241
2580
|
detail.NewRecord();
|
|
2242
2581
|
detail.ConversationID = session.ConversationID;
|
|
2582
|
+
detail.BranchID = this.sessionBranchIdOf(session);
|
|
2243
2583
|
detail.Role = 'AI';
|
|
2244
2584
|
detail.HiddenToUser = true;
|
|
2245
2585
|
detail.Message = 'Artifacts produced during a realtime session (system anchor).';
|
|
@@ -2392,21 +2732,54 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
2392
2732
|
|
|
2393
2733
|
/** Shape check for one client tool declaration: Name/Description non-empty strings, ParametersSchema a plain object. */
|
|
2394
2734
|
private isValidClientTool(candidate: unknown): boolean {
|
|
2395
|
-
|
|
2735
|
+
return IsValidRealtimeToolDefinition(candidate);
|
|
2736
|
+
}
|
|
2737
|
+
|
|
2738
|
+
/**
|
|
2739
|
+
* Tolerantly parses the channel candidates the browser reported at mint (the same caps and
|
|
2740
|
+
* tolerance as {@link parseClientTools}: capped, never throws, anything wrong is dropped and
|
|
2741
|
+
* logged). A caller that sent none yields `undefined` — an unscoped session, exactly as before.
|
|
2742
|
+
*/
|
|
2743
|
+
private parseChannelCandidates(channelCandidatesJson?: string): RealtimeChannelCandidate[] | undefined {
|
|
2744
|
+
const parsed = ParseRealtimeChannelCandidates(channelCandidatesJson);
|
|
2745
|
+
for (const reason of parsed.Rejected) {
|
|
2746
|
+
LogError(`StartRealtimeClientSession: ${reason}.`);
|
|
2747
|
+
}
|
|
2748
|
+
return parsed.Candidates.length > 0 ? parsed.Candidates : undefined;
|
|
2749
|
+
}
|
|
2750
|
+
|
|
2751
|
+
/**
|
|
2752
|
+
* Whether a session's persisted scope allows a server-side call to name `channelName`. A session
|
|
2753
|
+
* with no persisted scope (it reported no candidates) is unscoped and allows everything; a scoped
|
|
2754
|
+
* one allows only the channels its policy put in the session.
|
|
2755
|
+
*/
|
|
2756
|
+
private isChannelInSessionScope(session: MJAIAgentSessionEntity, channelName: string): boolean {
|
|
2757
|
+
// Read the RAW config: an absent `channels` key means an unscoped session, but anything present
|
|
2758
|
+
// and malformed fails CLOSED — a scope that cannot be read must not widen to "everything".
|
|
2759
|
+
// (`channels` is a server-authoritative key, so the owner cannot delete it to unscope a session.)
|
|
2760
|
+
const channels = ParseConfigObject(session.Config_)['channels'];
|
|
2761
|
+
if (channels === undefined) {
|
|
2762
|
+
return true;
|
|
2763
|
+
}
|
|
2764
|
+
if (!Array.isArray(channels)) {
|
|
2765
|
+
LogError(`RealtimeClientSessionResolver: session ${session.ID} has a malformed channel scope; refusing channel '${channelName}'.`);
|
|
2396
2766
|
return false;
|
|
2397
2767
|
}
|
|
2398
|
-
const
|
|
2399
|
-
return (
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2768
|
+
const wanted = NormalizeChannelKey(channelName);
|
|
2769
|
+
return channels.some((key) => typeof key === 'string' && NormalizeChannelKey(key) === wanted);
|
|
2770
|
+
}
|
|
2771
|
+
|
|
2772
|
+
/** The fork the session was started on (its config's `conversationBranchID`); null is Main. */
|
|
2773
|
+
private sessionBranchIdOf(session: MJAIAgentSessionEntity): string | null {
|
|
2774
|
+
const branchId = ParseConfigObject(session.Config_)['conversationBranchID'];
|
|
2775
|
+
return typeof branchId === 'string' && branchId.length > 0 ? branchId : null;
|
|
2404
2776
|
}
|
|
2405
2777
|
|
|
2406
2778
|
/**
|
|
2407
|
-
* Parses the session's persisted config, returning
|
|
2408
|
-
*
|
|
2409
|
-
*
|
|
2779
|
+
* Parses the session's persisted config, returning its `targetAgentID` plus any observability run
|
|
2780
|
+
* ids (`coAgentRunID`/`promptRunID`). Throws when the config is missing/malformed or carries no
|
|
2781
|
+
* target — a relay cannot proceed without a target. The owner can edit the config, so callers
|
|
2782
|
+
* re-check what they act on.
|
|
2410
2783
|
*/
|
|
2411
2784
|
private readSessionConfig(session: MJAIAgentSessionEntity): RealtimeSessionConfig {
|
|
2412
2785
|
const raw = session.Config_;
|
|
@@ -2424,11 +2797,18 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
2424
2797
|
maxSessionDeadlineIso:
|
|
2425
2798
|
typeof parsed.maxSessionDeadlineIso === 'string' ? parsed.maxSessionDeadlineIso : undefined,
|
|
2426
2799
|
applicationID: typeof parsed.applicationID === 'string' ? parsed.applicationID : undefined,
|
|
2800
|
+
appContext:
|
|
2801
|
+
parsed.appContext && typeof parsed.appContext === 'object' && !Array.isArray(parsed.appContext)
|
|
2802
|
+
? parsed.appContext
|
|
2803
|
+
: undefined,
|
|
2427
2804
|
allowedAgents: Array.isArray(parsed.allowedAgents) ? parsed.allowedAgents : undefined,
|
|
2428
2805
|
directActions:
|
|
2429
2806
|
parsed.directActions && typeof parsed.directActions === 'object' && typeof parsed.directActions.enabled === 'boolean'
|
|
2430
2807
|
? (parsed.directActions as RealtimeDirectActionsConfig)
|
|
2431
2808
|
: undefined,
|
|
2809
|
+
conversationBranchID: typeof parsed.conversationBranchID === 'string' ? parsed.conversationBranchID : undefined,
|
|
2810
|
+
mediaCollectionID: typeof parsed.mediaCollectionID === 'string' ? parsed.mediaCollectionID : undefined,
|
|
2811
|
+
channels: Array.isArray(parsed.channels) ? parsed.channels.filter((k): k is string => typeof k === 'string') : undefined,
|
|
2432
2812
|
};
|
|
2433
2813
|
}
|
|
2434
2814
|
} catch {
|
|
@@ -2516,6 +2896,7 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
2516
2896
|
);
|
|
2517
2897
|
detail.NewRecord();
|
|
2518
2898
|
detail.ConversationID = session.ConversationID;
|
|
2899
|
+
detail.BranchID = this.sessionBranchIdOf(session);
|
|
2519
2900
|
detail.Role = this.mapTranscriptRole(role);
|
|
2520
2901
|
detail.Message = text;
|
|
2521
2902
|
detail.AgentSessionID = session.ID;
|
|
@@ -2689,6 +3070,7 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
2689
3070
|
);
|
|
2690
3071
|
detail.NewRecord();
|
|
2691
3072
|
detail.ConversationID = session.ConversationID;
|
|
3073
|
+
detail.BranchID = this.sessionBranchIdOf(session);
|
|
2692
3074
|
detail.Role = 'AI';
|
|
2693
3075
|
detail.HiddenToUser = true;
|
|
2694
3076
|
detail.AgentSessionID = session.ID;
|