@memberjunction/server 6.2.0-edge.3 → 6.2.0-edge.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/agentSessions/BridgeHostInstance.d.ts +25 -0
- package/dist/agentSessions/BridgeHostInstance.d.ts.map +1 -0
- package/dist/agentSessions/BridgeHostInstance.js +30 -0
- package/dist/agentSessions/BridgeHostInstance.js.map +1 -0
- package/dist/agentSessions/HostInstance.d.ts +75 -11
- package/dist/agentSessions/HostInstance.d.ts.map +1 -1
- package/dist/agentSessions/HostInstance.js +99 -28
- package/dist/agentSessions/HostInstance.js.map +1 -1
- package/dist/agentSessions/ReturningVisitorRecap.d.ts.map +1 -1
- package/dist/agentSessions/ReturningVisitorRecap.js +7 -3
- package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -1
- package/dist/agentSessions/SessionJanitor.d.ts +173 -25
- package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
- package/dist/agentSessions/SessionJanitor.js +452 -54
- package/dist/agentSessions/SessionJanitor.js.map +1 -1
- package/dist/agentSessions/SessionManager.d.ts +43 -2
- package/dist/agentSessions/SessionManager.d.ts.map +1 -1
- package/dist/agentSessions/SessionManager.js +99 -8
- package/dist/agentSessions/SessionManager.js.map +1 -1
- package/dist/agentSessions/SessionRunIDVerifier.d.ts +63 -0
- package/dist/agentSessions/SessionRunIDVerifier.d.ts.map +1 -0
- package/dist/agentSessions/SessionRunIDVerifier.js +129 -0
- package/dist/agentSessions/SessionRunIDVerifier.js.map +1 -0
- package/dist/agentSessions/index.d.ts +2 -0
- package/dist/agentSessions/index.d.ts.map +1 -1
- package/dist/agentSessions/index.js +10 -3
- package/dist/agentSessions/index.js.map +1 -1
- package/dist/auth/actionAuthorization.d.ts +40 -0
- package/dist/auth/actionAuthorization.d.ts.map +1 -0
- package/dist/auth/actionAuthorization.js +94 -0
- package/dist/auth/actionAuthorization.js.map +1 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +3 -10
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.js +6 -3
- package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
- package/dist/auth/newUsers.d.ts +10 -0
- package/dist/auth/newUsers.d.ts.map +1 -1
- package/dist/auth/newUsers.js +80 -11
- package/dist/auth/newUsers.js.map +1 -1
- package/dist/config.d.ts +2378 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +123 -0
- 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/generated/graphql-schemas/__mj.d.ts +135 -0
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +706 -0
- package/dist/generated/graphql-schemas/__mj.js.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.js +3 -3
- package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +20 -4
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +39 -10
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +4 -0
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +120 -131
- 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/realtimeProxy/RealtimeProxyServer.d.ts +44 -4
- package/dist/realtimeProxy/RealtimeProxyServer.d.ts.map +1 -1
- package/dist/realtimeProxy/RealtimeProxyServer.js +121 -16
- package/dist/realtimeProxy/RealtimeProxyServer.js.map +1 -1
- package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts +171 -0
- package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts.map +1 -0
- package/dist/realtimeProxy/RealtimeRelayTunnel.js +532 -0
- package/dist/realtimeProxy/RealtimeRelayTunnel.js.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.d.ts +148 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.d.ts.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.js +191 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.js.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts +66 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.js +138 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.js.map +1 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts +38 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts.map +1 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.js +98 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.js.map +1 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts +61 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts.map +1 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.js +149 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.js.map +1 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts +57 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts.map +1 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.js +69 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.js.map +1 -0
- package/dist/realtimeSessions/index.d.ts +17 -0
- package/dist/realtimeSessions/index.d.ts.map +1 -0
- package/dist/realtimeSessions/index.js +17 -0
- package/dist/realtimeSessions/index.js.map +1 -0
- package/dist/realtimeSessions/sessionAccess.d.ts +86 -0
- package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -0
- package/dist/realtimeSessions/sessionAccess.js +90 -0
- package/dist/realtimeSessions/sessionAccess.js.map +1 -0
- package/dist/realtimeSessions/sessionConfigStamps.d.ts +70 -0
- package/dist/realtimeSessions/sessionConfigStamps.d.ts.map +1 -0
- package/dist/realtimeSessions/sessionConfigStamps.js +93 -0
- package/dist/realtimeSessions/sessionConfigStamps.js.map +1 -0
- package/dist/realtimeSessions/subscriptionAuthorization.d.ts +65 -0
- package/dist/realtimeSessions/subscriptionAuthorization.d.ts.map +1 -0
- package/dist/realtimeSessions/subscriptionAuthorization.js +71 -0
- package/dist/realtimeSessions/subscriptionAuthorization.js.map +1 -0
- package/dist/realtimeSessions/verificationCore.d.ts +243 -0
- package/dist/realtimeSessions/verificationCore.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationCore.js +356 -0
- package/dist/realtimeSessions/verificationCore.js.map +1 -0
- package/dist/realtimeSessions/verificationPages.d.ts +66 -0
- package/dist/realtimeSessions/verificationPages.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationPages.js +110 -0
- package/dist/realtimeSessions/verificationPages.js.map +1 -0
- package/dist/realtimeSessions/verificationPolicy.d.ts +147 -0
- package/dist/realtimeSessions/verificationPolicy.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationPolicy.js +249 -0
- package/dist/realtimeSessions/verificationPolicy.js.map +1 -0
- package/dist/realtimeSessions/verificationWorkflow.d.ts +263 -0
- package/dist/realtimeSessions/verificationWorkflow.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationWorkflow.js +435 -0
- package/dist/realtimeSessions/verificationWorkflow.js.map +1 -0
- package/dist/realtimeWidget/widgetGuestElevation.d.ts +20 -2
- package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.js +31 -2
- package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
- package/dist/resolvers/ActionResolver.d.ts +31 -5
- package/dist/resolvers/ActionResolver.d.ts.map +1 -1
- package/dist/resolvers/ActionResolver.js +75 -14
- package/dist/resolvers/ActionResolver.js.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.d.ts +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/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/FetchEntityVectorsResolver.d.ts +12 -1
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.js +83 -2
- package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
- package/dist/resolvers/HumanHandoffResolver.d.ts.map +1 -1
- package/dist/resolvers/HumanHandoffResolver.js +7 -1
- package/dist/resolvers/HumanHandoffResolver.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/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 +155 -25
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +394 -90
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RealtimeSessionEventsResolver.d.ts +61 -0
- package/dist/resolvers/RealtimeSessionEventsResolver.d.ts.map +1 -0
- package/dist/resolvers/RealtimeSessionEventsResolver.js +130 -0
- package/dist/resolvers/RealtimeSessionEventsResolver.js.map +1 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts +85 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts.map +1 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.js +188 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.js.map +1 -0
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts +2 -0
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js +5 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts +10 -0
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +68 -11
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTemplateResolver.js +12 -11
- package/dist/resolvers/RunTemplateResolver.js.map +1 -1
- package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncDataResolver.js +6 -2
- package/dist/resolvers/SyncDataResolver.js.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.d.ts +2 -0
- package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.js +23 -6
- package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
- package/dist/resolvers/UserAvatarResolver.d.ts +16 -0
- package/dist/resolvers/UserAvatarResolver.d.ts.map +1 -0
- package/dist/resolvers/UserAvatarResolver.js +124 -0
- package/dist/resolvers/UserAvatarResolver.js.map +1 -0
- package/dist/resolvers/UserResolver.d.ts.map +1 -1
- package/dist/resolvers/UserResolver.js +2 -17
- package/dist/resolvers/UserResolver.js.map +1 -1
- package/dist/resolvers/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/services/TaskGraphContinuationDeliverer.d.ts +4 -3
- package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.js +13 -7
- package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
- package/dist/sharedCache.d.ts +105 -0
- package/dist/sharedCache.d.ts.map +1 -0
- package/dist/sharedCache.js +276 -0
- package/dist/sharedCache.js.map +1 -0
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +97 -95
- 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__/FetchEntityVectorsResolver.test.ts +285 -0
- package/src/__tests__/HostInstance.test.ts +84 -0
- package/src/__tests__/HumanHandoffResolver.test.ts +17 -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__/RemoteBrowserActionResolver.goalRunLink.test.ts +129 -0
- package/src/__tests__/ResolverBase.aggregateScreen.test.ts +174 -0
- package/src/__tests__/ResolverBase.clientOldValues.test.ts +6 -3
- package/src/__tests__/ReturningVisitorRecap.test.ts +115 -0
- package/src/__tests__/RunAIAgentResolver.branch.test.ts +169 -0
- package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +4 -4
- package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +166 -0
- package/src/__tests__/RunTemplateResolver.test.ts +132 -0
- package/src/__tests__/RunViewResolver.cacheCheckForwarding.test.ts +63 -0
- package/src/__tests__/SessionJanitor.test.ts +845 -18
- package/src/__tests__/SessionManager.test.ts +213 -17
- package/src/__tests__/SessionRunIDVerifier.test.ts +251 -0
- package/src/__tests__/SyncRolesUsersResolver.userRoles.test.ts +97 -0
- package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +50 -3
- package/src/__tests__/UserAvatarResolver.test.ts +255 -0
- package/src/__tests__/UserResolver.currentUser.test.ts +16 -1
- package/src/__tests__/actionAuthorization.test.ts +130 -0
- package/src/__tests__/agentVisionConsentAudit.test.ts +118 -0
- package/src/__tests__/avatarInputValidation.test.ts +160 -0
- package/src/__tests__/isolatedProviderTransactions.test.ts +169 -0
- package/src/__tests__/magicLinkService.consume.test.ts +6 -0
- package/src/__tests__/magicLinkService.provisionLog.test.ts +5 -0
- package/src/__tests__/newUsers.template.test.ts +151 -0
- package/src/__tests__/newUsers.test.ts +74 -5
- package/src/__tests__/orphanedConversationDetailReconciler.test.ts +17 -2
- package/src/__tests__/realtimeSessions/RealtimeSessionEventService.test.ts +201 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionEventsResolver.test.ts +215 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionVerificationResolver.test.ts +102 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionVerificationService.test.ts +349 -0
- package/src/__tests__/realtimeSessions/RealtimeVerifyRouter.test.ts +131 -0
- package/src/__tests__/realtimeSessions/SlidingWindowRateLimiter.test.ts +56 -0
- package/src/__tests__/realtimeSessions/helpers.ts +4 -0
- package/src/__tests__/realtimeSessions/sessionAccess.test.ts +129 -0
- package/src/__tests__/realtimeSessions/sessionConfigStamps.test.ts +124 -0
- package/src/__tests__/realtimeSessions/verificationCore.test.ts +355 -0
- package/src/__tests__/realtimeSessions/verificationPages.test.ts +84 -0
- package/src/__tests__/realtimeSessions/verificationPolicy.test.ts +226 -0
- package/src/__tests__/realtimeSessions/verificationWorkflow.test.ts +633 -0
- package/src/__tests__/resolverBase.fls.test.ts +87 -4
- package/src/__tests__/serverExtensionReservedRoots.test.ts +2 -0
- package/src/__tests__/sharedCache.test.ts +310 -0
- package/src/__tests__/widgetGuestElevation.test.ts +44 -0
- package/src/agentSessions/BridgeHostInstance.ts +32 -0
- package/src/agentSessions/HostInstance.ts +124 -28
- package/src/agentSessions/ReturningVisitorRecap.ts +7 -3
- package/src/agentSessions/SessionJanitor.ts +543 -57
- package/src/agentSessions/SessionManager.ts +125 -25
- package/src/agentSessions/SessionRunIDVerifier.ts +171 -0
- package/src/agentSessions/index.ts +10 -3
- package/src/auth/actionAuthorization.ts +116 -0
- package/src/auth/index.ts +3 -8
- package/src/auth/magicLink/MagicLinkService.ts +6 -3
- package/src/auth/newUsers.ts +99 -11
- package/src/config.ts +125 -0
- package/src/context.ts +15 -10
- package/src/generated/graphql-schemas/__mj.ts +496 -1
- package/src/generic/OrphanedConversationDetailReconciler.ts +3 -3
- package/src/generic/ResolverBase.ts +39 -9
- package/src/generic/RunViewResolver.ts +4 -0
- package/src/index.ts +132 -143
- package/src/isolatedProvider.ts +22 -0
- package/src/realtimeProxy/RealtimeProxyServer.ts +148 -16
- package/src/realtimeProxy/RealtimeRelayTunnel.ts +645 -0
- package/src/realtimeSessions/README.md +179 -0
- package/src/realtimeSessions/RealtimeSessionEventService.ts +274 -0
- package/src/realtimeSessions/RealtimeSessionVerificationService.ts +167 -0
- package/src/realtimeSessions/RealtimeVerifyRouter.ts +108 -0
- package/src/realtimeSessions/SessionVerificationPortsBase.ts +189 -0
- package/src/realtimeSessions/SlidingWindowRateLimiter.ts +92 -0
- package/src/realtimeSessions/index.ts +16 -0
- package/src/realtimeSessions/sessionAccess.ts +141 -0
- package/src/realtimeSessions/sessionConfigStamps.ts +123 -0
- package/src/realtimeSessions/subscriptionAuthorization.ts +116 -0
- package/src/realtimeSessions/verificationCore.ts +502 -0
- package/src/realtimeSessions/verificationPages.ts +149 -0
- package/src/realtimeSessions/verificationPolicy.ts +321 -0
- package/src/realtimeSessions/verificationWorkflow.ts +702 -0
- package/src/realtimeWidget/widgetGuestElevation.ts +34 -2
- package/src/resolvers/ActionResolver.ts +84 -21
- package/src/resolvers/AdhocQueryResolver.ts +24 -6
- package/src/resolvers/CacheStatsResolver.ts +89 -2
- package/src/resolvers/ClientToolRequestResolver.ts +25 -7
- package/src/resolvers/FetchEntityVectorsResolver.ts +97 -2
- package/src/resolvers/HumanHandoffResolver.ts +8 -0
- package/src/resolvers/MCPResolver.ts +8 -5
- package/src/resolvers/RealtimeBridgeResolver.ts +163 -11
- package/src/resolvers/RealtimeClientSessionResolver.ts +501 -96
- package/src/resolvers/RealtimeSessionEventsResolver.ts +133 -0
- package/src/resolvers/RealtimeSessionVerificationResolver.ts +174 -0
- package/src/resolvers/RemoteBrowserActionResolver.ts +11 -1
- package/src/resolvers/RunAIAgentResolver.ts +78 -11
- package/src/resolvers/RunTemplateResolver.ts +13 -12
- package/src/resolvers/SyncDataResolver.ts +6 -2
- package/src/resolvers/SyncRolesUsersResolver.ts +24 -6
- package/src/resolvers/UserAvatarResolver.ts +111 -0
- package/src/resolvers/UserResolver.ts +2 -16
- package/src/resolvers/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/services/TaskGraphContinuationDeliverer.ts +13 -7
- package/src/sharedCache.ts +302 -0
- package/src/types.ts +7 -0
|
@@ -23,9 +23,9 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
|
23
23
|
* and **executes** every tool call the browser relays back. The three mutations here cover the MVP:
|
|
24
24
|
*
|
|
25
25
|
* 1. {@link RealtimeClientSessionResolver.StartRealtimeClientSession} — authorize, create the
|
|
26
|
-
* session (storing the target agent id
|
|
27
|
-
* 2. {@link RealtimeClientSessionResolver.ExecuteRealtimeSessionTool} — execute a relayed tool call
|
|
28
|
-
*
|
|
26
|
+
* session (storing the target agent id on it), mint the ephemeral config.
|
|
27
|
+
* 2. {@link RealtimeClientSessionResolver.ExecuteRealtimeSessionTool} — execute a relayed tool call
|
|
28
|
+
* for the target agent stored on the session, re-authorized for the caller on every call.
|
|
29
29
|
* 3. {@link RealtimeClientSessionResolver.RelayRealtimeTranscript} — persist a transcript turn as a
|
|
30
30
|
* `Conversation Detail`.
|
|
31
31
|
*
|
|
@@ -33,25 +33,44 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
|
33
33
|
* - **Start**: the caller must have `CanRun` on the *target* agent (the Realtime Co-Agent is an internal
|
|
34
34
|
* orchestration agent, so the meaningful gate is the agent doing the real work).
|
|
35
35
|
* - **Execute / Relay**: inbound ownership — the session's `UserID` must equal `contextUser.ID`.
|
|
36
|
+
* - **Session `Config`**: the owner can edit it through the generic entity API, so nothing in it is
|
|
37
|
+
* trusted. Execute re-checks `CanRun` on its target, direct actions never come from it, and its
|
|
38
|
+
* run ids are used only when {@link SessionRunIDVerifier} finds them to be records of the session.
|
|
39
|
+
* - **Scope-limited callers** (anonymous and resource-scoped magic-link sessions, including public
|
|
40
|
+
* web-widget guests): no direct actions. Their scope exists only as row-level filters, which an
|
|
41
|
+
* action can read around, and a scoped anonymous caller's relay runs as the system user. They
|
|
42
|
+
* reach the target agent only through `invoke-target-agent`.
|
|
36
43
|
*
|
|
37
44
|
* @module @memberjunction/server
|
|
38
45
|
*/
|
|
39
46
|
import { Resolver, Mutation, Arg, Ctx, Int, Float, ObjectType, Field, PubSub, PubSubEngine } from 'type-graphql';
|
|
40
|
-
import { AuthorizationEvaluator, LogError, LogStatus, RunView } from '@memberjunction/core';
|
|
47
|
+
import { AuthorizationEvaluator, LogError, LogErrorEx, LogStatus, RunView } from '@memberjunction/core';
|
|
41
48
|
import { UUIDsEqual, IsValidUUID } from '@memberjunction/global';
|
|
49
|
+
import { ConversationEngine, } from '@memberjunction/core-entities';
|
|
42
50
|
import { AIEngine } from '@memberjunction/aiengine';
|
|
43
|
-
import { AIAgentPermissionHelper, AIEngineBase } from '@memberjunction/ai-engine-base';
|
|
44
|
-
import { RealtimeClientSessionService, RealtimeChannelServerHost, EvaluateRuntimeOverrideAuthorization, FindIgnoredRealtimeConfigKeys, ParseRealtimeTypeConfiguration, ResolveEffectiveRealtimeConfig, ResolveRealtimeCoAgentID, FilterAllowedAgentsByCanRun, REALTIME_ADVANCED_SESSION_CONTROLS_AUTHORIZATION, resolveRecordingStorageAccountID, storeRealtimeRecording, writeRealtimeRecordingSegment, deleteRealtimeRecordingSegments, } from '@memberjunction/ai-agents';
|
|
51
|
+
import { AIAgentPermissionHelper, AIEngineBase, HasRealtimeUsage, ParseRealtimeUsageRecord } from '@memberjunction/ai-engine-base';
|
|
52
|
+
import { RealtimeClientSessionService, RealtimeChannelServerHost, EvaluateRuntimeOverrideAuthorization, FindIgnoredRealtimeConfigKeys, ParseRealtimeTypeConfiguration, ResolveEffectiveRealtimeConfig, ResolveRealtimeCoAgentID, FilterAllowedAgentsByCanRun, INVOKE_TARGET_AGENT_TOOL_NAME, REALTIME_ADVANCED_SESSION_CONTROLS_AUTHORIZATION, resolveRecordingStorageAccountID, storeRealtimeRecording, writeRealtimeRecordingSegment, deleteRealtimeRecordingSegments, } from '@memberjunction/ai-agents';
|
|
53
|
+
import { IsValidRealtimeToolDefinition, NormalizeChannelKey, ParseRealtimeChannelCandidates } from '@memberjunction/ai-core-plus';
|
|
45
54
|
import { ResolverBase } from '../generic/ResolverBase.js';
|
|
46
55
|
import { GetReadWriteProvider } from '../util.js';
|
|
47
56
|
import { SessionManager } from '../agentSessions/index.js';
|
|
48
|
-
import {
|
|
57
|
+
import { SessionRunIDVerifier } from '../agentSessions/SessionRunIDVerifier.js';
|
|
58
|
+
import { IsScopeLimitedPrincipal } from '../auth/scopeLimitedPrincipal.js';
|
|
59
|
+
import { ResolveWidgetGuestRunContext, ResolveScopedAnonymousRunUser, ResolveRecordingStoreUser } from '../realtimeWidget/widgetGuestElevation.js';
|
|
60
|
+
import { RunWithTrustedSessionConfigWrites } from '@memberjunction/core-entities-server';
|
|
61
|
+
import { BuildMintConfigStamps, LoadInheritedVerifiedIdentity } from '../realtimeSessions/sessionConfigStamps.js';
|
|
62
|
+
import { ParseConfigObject } from '../realtimeSessions/verificationCore.js';
|
|
49
63
|
/**
|
|
50
64
|
* Progress steps worth narrating to the realtime model — mirrors the normal agent-run path's filter
|
|
51
65
|
* in {@link import('./RunAIAgentResolver.js').RunAIAgentResolver}. Initialization/validation/finalization
|
|
52
66
|
* noise is dropped so the model only narrates meaningful work.
|
|
53
67
|
*/
|
|
54
68
|
const SIGNIFICANT_PROGRESS_STEPS = ['prompt_execution', 'action_execution', 'subagent_execution', 'decision_processing'];
|
|
69
|
+
/**
|
|
70
|
+
* The longest `usageDetailsJson` a usage relay may carry. Every field of both detail blocks fits in a few hundred
|
|
71
|
+
* characters, so anything longer is not a relay the browser runtime sends.
|
|
72
|
+
*/
|
|
73
|
+
const MAX_USAGE_DETAILS_JSON_LENGTH = 4096;
|
|
55
74
|
/** Entity name — centralised so the `MJ:`-prefix convention is applied in exactly one place. */
|
|
56
75
|
const SESSION_ENTITY = 'MJ: AI Agent Sessions';
|
|
57
76
|
const CO_AGENT_ENTITY = 'MJ: AI Agent Co Agents';
|
|
@@ -77,7 +96,8 @@ const WHITEBOARD_ARTIFACT_TYPE_NAME = 'Whiteboard';
|
|
|
77
96
|
*/
|
|
78
97
|
const MAX_CLIENT_TOOLS = 64;
|
|
79
98
|
/** Maximum accepted size (chars) of the serialized client tool declarations. Raised in step with
|
|
80
|
-
* {@link MAX_CLIENT_TOOLS} — multi-channel tool sets with verbose descriptions + JSON schemas run large.
|
|
99
|
+
* {@link MAX_CLIENT_TOOLS} — multi-channel tool sets with verbose descriptions + JSON schemas run large.
|
|
100
|
+
* Also the largest serialized app-context snapshot stored on the session config. */
|
|
81
101
|
const MAX_CLIENT_TOOLS_JSON_CHARS = 256_000;
|
|
82
102
|
/** Maximum accepted size (chars) of a persisted channel state blob. */
|
|
83
103
|
const MAX_CHANNEL_STATE_CHARS = 2_000_000;
|
|
@@ -121,6 +141,14 @@ __decorate([
|
|
|
121
141
|
Field(() => String),
|
|
122
142
|
__metadata("design:type", String)
|
|
123
143
|
], StartRealtimeClientSessionResult.prototype, "ExpiresAt", void 0);
|
|
144
|
+
__decorate([
|
|
145
|
+
Field(() => String, { nullable: true }),
|
|
146
|
+
__metadata("design:type", String)
|
|
147
|
+
], StartRealtimeClientSessionResult.prototype, "Transport", void 0);
|
|
148
|
+
__decorate([
|
|
149
|
+
Field(() => String, { nullable: true }),
|
|
150
|
+
__metadata("design:type", String)
|
|
151
|
+
], StartRealtimeClientSessionResult.prototype, "RelayUrl", void 0);
|
|
124
152
|
__decorate([
|
|
125
153
|
Field(() => String),
|
|
126
154
|
__metadata("design:type", String)
|
|
@@ -149,6 +177,14 @@ __decorate([
|
|
|
149
177
|
Field(() => String, { nullable: true }),
|
|
150
178
|
__metadata("design:type", String)
|
|
151
179
|
], StartRealtimeClientSessionResult.prototype, "EffectiveConfigJson", void 0);
|
|
180
|
+
__decorate([
|
|
181
|
+
Field(() => String, { nullable: true }),
|
|
182
|
+
__metadata("design:type", String)
|
|
183
|
+
], StartRealtimeClientSessionResult.prototype, "ClientPolicyJson", void 0);
|
|
184
|
+
__decorate([
|
|
185
|
+
Field(() => String, { nullable: true }),
|
|
186
|
+
__metadata("design:type", String)
|
|
187
|
+
], StartRealtimeClientSessionResult.prototype, "AvatarStatusJson", void 0);
|
|
152
188
|
StartRealtimeClientSessionResult = __decorate([
|
|
153
189
|
ObjectType()
|
|
154
190
|
], StartRealtimeClientSessionResult);
|
|
@@ -246,6 +282,7 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
246
282
|
// state on — otherwise that cleanup silently no-ops on a freshly-constructed instance.
|
|
247
283
|
this.clientSessionService = new RealtimeClientSessionService();
|
|
248
284
|
this.sessionManager = new SessionManager(this.clientSessionService);
|
|
285
|
+
this.runIDVerifier = new SessionRunIDVerifier();
|
|
249
286
|
}
|
|
250
287
|
/**
|
|
251
288
|
* The reason a `Save()`/`Delete()` returned false, for a log line. `CompleteMessage` renders
|
|
@@ -281,7 +318,7 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
281
318
|
* agent's `DefaultCoAgentID` → type-level `AIAgentCoAgent` default row → global Realtime Co-Agent) —
|
|
282
319
|
* see {@link ResolveRealtimeCoAgentID} for the full contract.
|
|
283
320
|
* 3. Create the durable `AIAgentSession` (run by the co-agent), storing `targetAgentID` in its
|
|
284
|
-
* config
|
|
321
|
+
* config — the target later relays use, re-authorized for the caller on each relay.
|
|
285
322
|
* 4. Mint the {@link import('@memberjunction/ai').ClientRealtimeSessionConfig} via the service.
|
|
286
323
|
* On failure the just-created session is closed so no half-open session leaks.
|
|
287
324
|
*
|
|
@@ -307,10 +344,16 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
307
344
|
* `TypeConfiguration` ← this). **Authorization-gated**: requires the
|
|
308
345
|
* `Realtime: Advanced Session Controls` authorization — unauthorized callers receive a
|
|
309
346
|
* structured rejection (never a silent ignore). Must be a JSON object.
|
|
347
|
+
* @param showsAgentVideo `false` from an app that shows no agent video (no channel of it shows it: the embeddable
|
|
348
|
+
* widgets, the mobile app): the session asks the model for no avatar, and `AvatarStatusJson` says `host`. Absent or
|
|
349
|
+
* `true`: the app may show it. It only ever turns video off, so it needs no authorization.
|
|
350
|
+
* @param conversationBranchId Optional fork (`MJ: Conversation Branches.ID`) the session starts on;
|
|
351
|
+
* omitted is Main. It must be a fork of `conversationId`, else the start fails and no session is
|
|
352
|
+
* created. The session's history is read from that fork's path and its transcript is written there.
|
|
310
353
|
*
|
|
311
354
|
* @returns The ephemeral config + session linkage the browser needs to open its socket.
|
|
312
355
|
*/
|
|
313
|
-
async StartRealtimeClientSession(targetAgentId, { userPayload, providers }, conversationId, lastSessionId, preferredModelId, clientToolsJson, coAgentId, configOverridesJson, recordingStartedAt, recordingConsent, mediaCollectionId, applicationId, appContextJson) {
|
|
356
|
+
async StartRealtimeClientSession(targetAgentId, { userPayload, providers }, conversationId, lastSessionId, preferredModelId, clientToolsJson, coAgentId, configOverridesJson, recordingStartedAt, recordingConsent, mediaCollectionId, applicationId, appContextJson, channelCandidatesJson, showsAgentVideo, conversationBranchId) {
|
|
314
357
|
const { contextUser, provider } = this.requireUserAndProvider(userPayload, providers);
|
|
315
358
|
const coAgentID = await ResolveRealtimeCoAgentID(targetAgentId, coAgentId, contextUser, provider);
|
|
316
359
|
// PAIRING CONSTRAINTS: a co-agent with pairing rows is restricted to that target list
|
|
@@ -344,16 +387,24 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
344
387
|
'using the agent default media kit.');
|
|
345
388
|
}
|
|
346
389
|
}
|
|
347
|
-
const
|
|
390
|
+
const sessionBranchId = await this.resolveSessionBranch(conversationId, conversationBranchId, contextUser, provider);
|
|
391
|
+
if (sessionBranchId) {
|
|
392
|
+
config.conversationBranchID = sessionBranchId;
|
|
393
|
+
}
|
|
394
|
+
// The initial Config carries `maxSessionDeadlineIso` for capped sessions — a server-authoritative key
|
|
395
|
+
// that `MJAIAgentSessionEntityServer` refuses to let the session OWNER write, so this write (and only
|
|
396
|
+
// this one, built entirely from server-decided values) runs in the trusted scope.
|
|
397
|
+
const session = await RunWithTrustedSessionConfigWrites(() => this.sessionManager.CreateSession({
|
|
348
398
|
agentID: coAgentID,
|
|
349
399
|
userID: contextUser.ID,
|
|
350
400
|
conversationID: conversationId,
|
|
351
401
|
lastSessionID: lastSessionId,
|
|
352
402
|
config: JSON.stringify(config),
|
|
353
|
-
}, contextUser, provider);
|
|
403
|
+
}, contextUser, provider));
|
|
354
404
|
// Best-effort: stamp recording-start metadata when the browser captured (with consent) at start.
|
|
355
405
|
await this.stampRecordingStart(session, recordingConsent, recordingStartedAt);
|
|
356
406
|
const clientTools = this.parseClientTools(clientToolsJson);
|
|
407
|
+
const channelCandidates = this.parseChannelCandidates(channelCandidatesJson);
|
|
357
408
|
// Best-effort model-context hydration: the PRIOR session chain's transcript (ownership-
|
|
358
409
|
// checked, capped) is framed into the system prompt so the model REMEMBERS the last leg.
|
|
359
410
|
// Strictly tolerant — any problem yields no hydration, never a failed start.
|
|
@@ -362,19 +413,83 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
362
413
|
// mid-thread knows what was already typed. The legs above are excluded: they are already
|
|
363
414
|
// framed as the resumed transcript, and injecting the same turns twice puts two accounts of
|
|
364
415
|
// the same exchange in one prompt.
|
|
365
|
-
const conversationMessages = await this.loadConversationHistory(conversationId, prior?.LegIDs ?? [], contextUser, provider);
|
|
366
|
-
const result = await this.prepareClientSessionOrClose(session, coAgentID, effectiveTargetId, contextUser, provider, preferredModelId, clientTools, prior?.Text, configOverridesJson, maxSessionSeconds, applicationId, this.parseAppContext(appContextJson), conversationMessages);
|
|
416
|
+
const conversationMessages = await this.loadConversationHistory(conversationId, prior?.LegIDs ?? [], contextUser, provider, sessionBranchId);
|
|
417
|
+
const result = await this.prepareClientSessionOrClose(session, coAgentID, effectiveTargetId, contextUser, provider, preferredModelId, clientTools, prior?.Text, configOverridesJson, maxSessionSeconds, applicationId, this.parseAppContext(appContextJson), conversationMessages, channelCandidates, showsAgentVideo);
|
|
418
|
+
await this.stampVerificationPolicy(session, result.EffectiveConfigJson, lastSessionId, contextUser, provider);
|
|
367
419
|
// Best-effort restore of the PRIOR session's persisted channel states (e.g. the whiteboard
|
|
368
420
|
// board). Strictly tolerant — any problem yields a null field, never a failed start.
|
|
369
421
|
result.PriorChannelStatesJson = (await this.loadPriorChannelStatesJson(lastSessionId, contextUser, provider)) ?? undefined;
|
|
370
422
|
return result;
|
|
371
423
|
}
|
|
424
|
+
/**
|
|
425
|
+
* The fork a session starts on: null (Main) when none is named; else the named fork, which must
|
|
426
|
+
* belong to the session's conversation. Returns the fork's stored id.
|
|
427
|
+
* @throws when a fork is named without a conversation, or is not a fork of it, or the fork rows cannot be read
|
|
428
|
+
*/
|
|
429
|
+
async resolveSessionBranch(conversationId, branchId, contextUser, provider) {
|
|
430
|
+
const named = branchId?.trim();
|
|
431
|
+
if (!named) {
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
if (!conversationId) {
|
|
435
|
+
throw new Error(`StartRealtimeClientSession: fork ${named} needs a conversation`);
|
|
436
|
+
}
|
|
437
|
+
try {
|
|
438
|
+
const scope = await ConversationEngine.LoadScope(conversationId, named, contextUser, provider);
|
|
439
|
+
return scope.BranchID;
|
|
440
|
+
}
|
|
441
|
+
catch (error) {
|
|
442
|
+
throw new Error(`StartRealtimeClientSession: fork ${named} is not a readable fork of conversation ${conversationId}: ${error.message}`);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Stamps the identity-verification policy snapshot, the configured unverified duration cap and any
|
|
447
|
+
* verified identity inherited from the resumed session onto the new session's `Config`.
|
|
448
|
+
*
|
|
449
|
+
* The effective config (config cascade) is only known after prepare, so this is a second, narrowly
|
|
450
|
+
* scoped write: it runs only when there is something to stamp. It **fails closed** — a cap that could
|
|
451
|
+
* not be applied would leave the session uncapped, so on a save failure the session is closed and the
|
|
452
|
+
* start fails rather than proceeding without its limits.
|
|
453
|
+
*/
|
|
454
|
+
async stampVerificationPolicy(session, effectiveConfigJson, lastSessionId, contextUser, provider) {
|
|
455
|
+
const plan = BuildMintConfigStamps({
|
|
456
|
+
EffectiveRealtime: this.readEffectiveRealtime(effectiveConfigJson),
|
|
457
|
+
ExistingConfigRaw: session.Config_,
|
|
458
|
+
InheritedVerified: await LoadInheritedVerifiedIdentity(lastSessionId, contextUser, provider),
|
|
459
|
+
NowMs: Date.now(),
|
|
460
|
+
});
|
|
461
|
+
if (!plan) {
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
session.Config_ = plan.NextConfigRaw;
|
|
465
|
+
const saved = await RunWithTrustedSessionConfigWrites(() => session.Save());
|
|
466
|
+
if (!saved) {
|
|
467
|
+
LogError(`RealtimeClientSessionResolver.stampVerificationPolicy save failed for session ${session.ID}: ${this.describeSaveFailure(session)}`);
|
|
468
|
+
await this.sessionManager.CloseSession(session.ID, contextUser, provider, 'Error');
|
|
469
|
+
throw new Error('Failed to apply the session limits; the realtime session was not started.');
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
/** The `realtime` section of the resolved effective config JSON, or undefined when absent/unparseable. */
|
|
473
|
+
readEffectiveRealtime(effectiveConfigJson) {
|
|
474
|
+
if (!effectiveConfigJson) {
|
|
475
|
+
return undefined;
|
|
476
|
+
}
|
|
477
|
+
try {
|
|
478
|
+
return JSON.parse(effectiveConfigJson).realtime;
|
|
479
|
+
}
|
|
480
|
+
catch (error) {
|
|
481
|
+
LogError(`RealtimeClientSessionResolver: effective config JSON was unparseable while stamping verification policy: ${error.message}`);
|
|
482
|
+
return undefined;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
372
485
|
/**
|
|
373
486
|
* Execute a single tool call the browser relayed from its provider socket and return the
|
|
374
487
|
* serialized result for the browser to relay back to the model.
|
|
375
488
|
*
|
|
376
|
-
* Ownership-gated
|
|
377
|
-
*
|
|
489
|
+
* Ownership-gated. The target agent id is read from the session config and `CanRun` on it is
|
|
490
|
+
* checked for the caller on every call, because the owner can edit the config. A direct-action
|
|
491
|
+
* call from a scope-limited caller is refused before any elevation. Heartbeats the session on
|
|
492
|
+
* success.
|
|
378
493
|
*
|
|
379
494
|
* @returns The serialized tool result JSON.
|
|
380
495
|
*/
|
|
@@ -382,12 +497,17 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
382
497
|
const { contextUser, provider } = this.requireUserAndProvider(userPayload, providers);
|
|
383
498
|
const session = await this.loadOwnedActiveSession(agentSessionId, contextUser, provider);
|
|
384
499
|
const config = this.readSessionConfig(session);
|
|
500
|
+
await this.assertCanRunTarget(config.targetAgentID, contextUser, provider);
|
|
501
|
+
if (toolName !== INVOKE_TARGET_AGENT_TOOL_NAME && IsScopeLimitedPrincipal(contextUser)) {
|
|
502
|
+
return this.refuseDirectAction(agentSessionId, toolName);
|
|
503
|
+
}
|
|
385
504
|
// SCOPED-ANONYMOUS ELEVATION (issue #3371): once ownership is proven above, the delegated
|
|
386
505
|
// run + its AI-run-entity writes execute as the system user for a scoped anonymous caller
|
|
387
506
|
// (the caller's role deliberately holds no grants on the run entities). The lead
|
|
388
|
-
// targetAgentID
|
|
389
|
-
//
|
|
507
|
+
// targetAgentID is CanRun-gated against the CALLER just above, and the colleague union
|
|
508
|
+
// just below, so elevation never widens agent authority.
|
|
390
509
|
const runUser = ResolveScopedAnonymousRunUser(contextUser);
|
|
510
|
+
const runIDs = await this.runIDVerifier.Verify(session.ID, { CoAgentRunID: config.coAgentRunID, PendingFeedbackRunID: config.pendingFeedbackRunID }, runUser, provider);
|
|
391
511
|
if (runUser !== contextUser) {
|
|
392
512
|
LogStatus(`ExecuteRealtimeSessionTool: dispatching relayed tool '${toolName}' for session ${agentSessionId} ` +
|
|
393
513
|
'under the system user (scoped-anonymous caller).');
|
|
@@ -400,20 +520,27 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
400
520
|
// Multi-target (Move 4): the session's persisted allowed-agent union — a model-named
|
|
401
521
|
// colleague in the call is validated against this; absent ⇒ single-target behavior.
|
|
402
522
|
AllowedAgents: await FilterAllowedAgentsByCanRun(config.allowedAgents, contextUser),
|
|
403
|
-
DirectActions:
|
|
523
|
+
// No DirectActions: the service takes them from the session's start-time projection
|
|
524
|
+
// or the target agent's metadata, never from the owner-editable config.
|
|
404
525
|
// Attribution follows the VISITOR even when `runUser` is elevated: the delegated run
|
|
405
526
|
// row and its context-memory scope must stay the person's, not the system user's.
|
|
406
527
|
AttributionUserID: contextUser.ID,
|
|
407
528
|
// Nest the delegated target-agent run under the co-agent observability run (when present).
|
|
408
|
-
ParentRunID:
|
|
529
|
+
ParentRunID: runIDs.CoAgentRunID,
|
|
530
|
+
// The client-tool channel is keyed on the browser session id, so the delegated run can call
|
|
531
|
+
// the surface's tools; the session-start app context gives its prompt the surface context.
|
|
532
|
+
BrowserSessionID: userPayload.sessionId,
|
|
533
|
+
// The app context is a snapshot the browser sent, so a run elevated to the system user
|
|
534
|
+
// does not get it in its prompt.
|
|
535
|
+
AppContext: runUser === contextUser ? config.appContext : undefined,
|
|
409
536
|
Call: { CallID: callId, ToolName: toolName, Arguments: argsJson },
|
|
410
537
|
OnProgress: this.buildDelegationProgressCallback(pubSub, userPayload, agentSessionId, callId),
|
|
411
538
|
// Resume a previously-paused delegated run (if any) with the user's answer.
|
|
412
|
-
ResumeRunID:
|
|
539
|
+
ResumeRunID: runIDs.PendingFeedbackRunID,
|
|
413
540
|
}, runUser, provider);
|
|
414
541
|
const durationMs = Date.now() - startTime;
|
|
415
542
|
LogStatus(`ExecuteRealtimeSessionTool: completed relayed tool '${toolName}' (callId: ${callId}) in ${durationMs}ms`);
|
|
416
|
-
if (toolName !==
|
|
543
|
+
if (toolName !== INVOKE_TARGET_AGENT_TOOL_NAME) {
|
|
417
544
|
await this.persistDirectActionTurn(session, callId, toolName, argsJson, ResultJson, durationMs, contextUser, provider);
|
|
418
545
|
}
|
|
419
546
|
// Roll the paused-run id forward in the session config: clear the one we just consumed, and
|
|
@@ -451,6 +578,18 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
451
578
|
}), userPayload);
|
|
452
579
|
};
|
|
453
580
|
}
|
|
581
|
+
/**
|
|
582
|
+
* The tool result for a direct-action call from a scope-limited caller (see the authorization
|
|
583
|
+
* model above). It has the broker's result shape, so the model hears that the tool is not
|
|
584
|
+
* available and can ask the target agent through `invoke-target-agent` instead.
|
|
585
|
+
*/
|
|
586
|
+
refuseDirectAction(agentSessionId, toolName) {
|
|
587
|
+
LogStatus(`ExecuteRealtimeSessionTool: refused direct action '${toolName}' for a scope-limited caller on session ${agentSessionId}.`);
|
|
588
|
+
return JSON.stringify({
|
|
589
|
+
success: false,
|
|
590
|
+
output: `The tool '${toolName}' is not available in this session. Ask the agent to do it with ${INVOKE_TARGET_AGENT_TOOL_NAME}.`,
|
|
591
|
+
});
|
|
592
|
+
}
|
|
454
593
|
/**
|
|
455
594
|
* Rolls the session's `pendingFeedbackRunID` forward after a relayed tool call. The id we just
|
|
456
595
|
* consumed (resumed or none) is dropped; a new paused run id is stored only when the run paused
|
|
@@ -462,17 +601,10 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
462
601
|
if (!previous && !pausedRunID) {
|
|
463
602
|
return;
|
|
464
603
|
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
coAgentRunStepID: config.coAgentRunStepID,
|
|
470
|
-
pendingFeedbackRunID: pausedRunID,
|
|
471
|
-
// Preserve the server-authoritative voice deadline across config rewrites.
|
|
472
|
-
maxSessionDeadlineIso: config.maxSessionDeadlineIso,
|
|
473
|
-
applicationID: config.applicationID,
|
|
474
|
-
allowedAgents: config.allowedAgents,
|
|
475
|
-
};
|
|
604
|
+
// Start from the RAW stored config so every key survives the rewrite — including the
|
|
605
|
+
// server-authoritative `maxSessionDeadlineIso` and `identityVerification` (dropping either would be
|
|
606
|
+
// refused by `MJAIAgentSessionEntityServer`, and silently losing a verification would be worse).
|
|
607
|
+
const next = { ...ParseConfigObject(session.Config_), targetAgentID: config.targetAgentID, pendingFeedbackRunID: pausedRunID };
|
|
476
608
|
session.Config_ = JSON.stringify(next);
|
|
477
609
|
if (!(await session.Save())) {
|
|
478
610
|
LogError(`RealtimeClientSessionResolver.updatePendingFeedbackRunID save failed for session ${session.ID}: ${this.describeSaveFailure(session)}`);
|
|
@@ -504,9 +636,10 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
504
636
|
// conversation (run-viewer observability parity). Best-effort — never fails the transcript relay.
|
|
505
637
|
// The prompt-run write runs as the scoped-anonymous elevated user (issue #3371) — the visible
|
|
506
638
|
// Conversation Detail above deliberately stays on the caller.
|
|
507
|
-
const
|
|
639
|
+
const runUser = ResolveScopedAnonymousRunUser(contextUser);
|
|
640
|
+
const promptRunID = await this.readPromptRunID(session, runUser, provider);
|
|
508
641
|
if (promptRunID) {
|
|
509
|
-
await this.clientSessionService.AppendPromptRunMessage(promptRunID, this.mapTranscriptRoleToChatRole(role), text, replacesPrevious ?? false,
|
|
642
|
+
await this.clientSessionService.AppendPromptRunMessage(promptRunID, this.mapTranscriptRoleToChatRole(role), text, replacesPrevious ?? false, runUser, provider);
|
|
510
643
|
}
|
|
511
644
|
await this.sessionManager.Heartbeat(agentSessionId, contextUser, provider);
|
|
512
645
|
return true;
|
|
@@ -550,11 +683,11 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
550
683
|
if (consent !== true) {
|
|
551
684
|
return { Success: false, ErrorMessage: 'Recording consent was not granted.' };
|
|
552
685
|
}
|
|
553
|
-
//
|
|
554
|
-
//
|
|
555
|
-
//
|
|
556
|
-
// session link, so nothing here depends on the caller's identity.
|
|
557
|
-
const runUser =
|
|
686
|
+
// ELEVATION (issue #3371, #5195): past the ownership + consent gates, the store is server-side
|
|
687
|
+
// plumbing over entities (MJ: AI Agents read, MJ: Files, the file-session link, the session's
|
|
688
|
+
// recording stamp) that neither a scoped-anonymous relay role nor the stock UI role holds.
|
|
689
|
+
// Attribution flows through the session link, so nothing here depends on the caller's identity.
|
|
690
|
+
const runUser = ResolveRecordingStoreUser(contextUser);
|
|
558
691
|
try {
|
|
559
692
|
const agent = await provider.GetEntityObject('MJ: AI Agents', runUser);
|
|
560
693
|
if (!(await agent.Load(session.AgentID))) {
|
|
@@ -600,11 +733,14 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
600
733
|
}
|
|
601
734
|
/**
|
|
602
735
|
* Uploads ONE crash-recovery audio shard (~15s) for an IN-PROGRESS recording into the session's
|
|
603
|
-
* folder (`realtime-recordings/<sessionId>/seg-NNNN
|
|
604
|
-
*
|
|
605
|
-
*
|
|
606
|
-
*
|
|
607
|
-
*
|
|
736
|
+
* folder (`realtime-recordings/<sessionId>/seg-NNNN.r<rate>.pcm` for PCM with a rate in its MIME type,
|
|
737
|
+
* else the legacy `seg-NNNN.<ext>`) as a raw storage object. Durability insurance during a live call
|
|
738
|
+
* so a browser/tab death loses at most the last window; the shards are deleted once the canonical
|
|
739
|
+
* consolidated file lands via {@link UploadRealtimeRecording}, or by the session janitor once it has
|
|
740
|
+
* recovered a recording from them when that upload never arrives. Ownership-
|
|
741
|
+
* gated; consent was already established at session start. Refused (`false`) once the session has
|
|
742
|
+
* its consolidated recording, since nothing would delete a shard written after that. Best-effort —
|
|
743
|
+
* returns `false` (never throws) on any problem so a failed shard never disrupts the live call.
|
|
608
744
|
*
|
|
609
745
|
* @param agentSessionId The in-progress session (ownership-gated).
|
|
610
746
|
* @param segmentIndex 0-based shard index within the session.
|
|
@@ -616,6 +752,12 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
616
752
|
try {
|
|
617
753
|
const { contextUser, provider } = this.requireUserAndProvider(ctx.userPayload, ctx.providers);
|
|
618
754
|
const session = await this.loadOwnedSession(agentSessionId, contextUser, provider);
|
|
755
|
+
// The consolidated recording already landed and its upload deleted the shards; a shard
|
|
756
|
+
// arriving now (one still on the wire at hang-up) would be an orphan nothing deletes.
|
|
757
|
+
if (session.RecordingFileID) {
|
|
758
|
+
LogStatus(`RealtimeClientSessionResolver.UploadRealtimeRecordingSegment: shard ${segmentIndex} for session ${agentSessionId} refused — the session already has its consolidated recording`);
|
|
759
|
+
return false;
|
|
760
|
+
}
|
|
619
761
|
// Scoped-anonymous elevation (issue #3371) — same rationale as UploadRealtimeRecording.
|
|
620
762
|
const runUser = ResolveScopedAnonymousRunUser(contextUser);
|
|
621
763
|
const agent = await provider.GetEntityObject('MJ: AI Agents', runUser);
|
|
@@ -659,11 +801,12 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
659
801
|
// Persist the tool turn facts into a hidden ConversationDetail turn so session review
|
|
660
802
|
// can reconstruct the tool card dynamically on load (Option 1). Best-effort.
|
|
661
803
|
await this.persistDirectActionTurn(session, undefined, toolName, argsJson, resultJson, undefined, contextUser, provider);
|
|
662
|
-
const
|
|
804
|
+
const runUser = ResolveScopedAnonymousRunUser(contextUser);
|
|
805
|
+
const promptRunID = await this.readPromptRunID(session, runUser, provider);
|
|
663
806
|
if (!promptRunID) {
|
|
664
807
|
return false;
|
|
665
808
|
}
|
|
666
|
-
return this.clientSessionService.AppendPromptRunMessage(promptRunID, 'assistant', this.formatToolTurn(toolName, argsJson, resultJson), false,
|
|
809
|
+
return this.clientSessionService.AppendPromptRunMessage(promptRunID, 'assistant', this.formatToolTurn(toolName, argsJson, resultJson), false, runUser, provider);
|
|
667
810
|
}
|
|
668
811
|
/**
|
|
669
812
|
* Formats a co-agent tool call into a compact, human-readable run line: `🔧 <tool> <args> → <result>`,
|
|
@@ -726,31 +869,56 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
726
869
|
* (observability creation was skipped), a failed load, or a failed save all log and return
|
|
727
870
|
* `false` — usage relay must never break a live call.
|
|
728
871
|
*
|
|
872
|
+
* The optional details are the per-modality usage since the last relay: input and output
|
|
873
|
+
* blocks shaped like Core's `RealtimeUsageModalityDetail`, avatar video seconds included. They
|
|
874
|
+
* add into the usage record on the run's `ModelSpecificResponseDetails`, which prices the avatar
|
|
875
|
+
* video at finalize. Only the fields a block defines, with values that are finite numbers of at
|
|
876
|
+
* least 0, are kept; a relay that carries only details (avatar seconds, no tokens) is stored.
|
|
877
|
+
*
|
|
729
878
|
* @param inputTokens Input-token DELTA to add (negative/non-finite values are clamped to 0).
|
|
730
879
|
* @param outputTokens Output-token DELTA to add (negative/non-finite values are clamped to 0).
|
|
880
|
+
* @param usageDetailsJson The per-modality usage since the last relay, as JSON (`{ Input, Output }`).
|
|
731
881
|
* @returns `true` when the accumulated usage was persisted; `false` on any tolerated failure.
|
|
732
882
|
*/
|
|
733
|
-
async RelayRealtimeUsage(agentSessionId, inputTokens, outputTokens, { userPayload, providers }) {
|
|
883
|
+
async RelayRealtimeUsage(agentSessionId, inputTokens, outputTokens, { userPayload, providers }, usageDetailsJson) {
|
|
734
884
|
const { contextUser, provider } = this.requireUserAndProvider(userPayload, providers);
|
|
735
885
|
const session = await this.loadOwnedSession(agentSessionId, contextUser, provider);
|
|
736
886
|
const inputDelta = this.clampTokenDelta(inputTokens);
|
|
737
887
|
const outputDelta = this.clampTokenDelta(outputTokens);
|
|
738
|
-
|
|
888
|
+
const details = this.readUsageDetails(usageDetailsJson);
|
|
889
|
+
if (inputDelta === 0 && outputDelta === 0 && !details) {
|
|
739
890
|
return true; // nothing to add — a no-op flush is a success, not a failure
|
|
740
891
|
}
|
|
741
|
-
|
|
892
|
+
// Runs as the scoped-anonymous elevated user (issue #3371) — the caller's role holds no prompt-run grants.
|
|
893
|
+
const runUser = ResolveScopedAnonymousRunUser(contextUser);
|
|
894
|
+
const promptRunID = await this.readPromptRunID(session, runUser, provider);
|
|
742
895
|
if (!promptRunID) {
|
|
743
896
|
return false; // no observability prompt run for this session — logged in the helper
|
|
744
897
|
}
|
|
745
898
|
// Delegate to the service so usage writes share the per-run serialization with transcript-message
|
|
746
899
|
// appends — otherwise the frequent usage save clobbers freshly-appended Messages (and vice-versa).
|
|
747
|
-
|
|
748
|
-
return this.clientSessionService.AccumulatePromptRunUsage(promptRunID, inputDelta, outputDelta, ResolveScopedAnonymousRunUser(contextUser), provider);
|
|
900
|
+
return this.clientSessionService.AccumulatePromptRunUsage(promptRunID, inputDelta, outputDelta, runUser, provider, details ?? undefined);
|
|
749
901
|
}
|
|
750
902
|
/** Clamps a relayed token delta: negative / non-finite values become 0. */
|
|
751
903
|
clampTokenDelta(value) {
|
|
752
904
|
return Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
|
|
753
905
|
}
|
|
906
|
+
/**
|
|
907
|
+
* Reads the relayed per-modality usage, or `null` when there is none worth storing: absent,
|
|
908
|
+
* malformed, longer than a relay ever needs (a client cannot make the server parse an
|
|
909
|
+
* arbitrarily large string), or holding no amount above 0.
|
|
910
|
+
*/
|
|
911
|
+
readUsageDetails(usageDetailsJson) {
|
|
912
|
+
if (typeof usageDetailsJson !== 'string') {
|
|
913
|
+
return null;
|
|
914
|
+
}
|
|
915
|
+
if (usageDetailsJson.length > MAX_USAGE_DETAILS_JSON_LENGTH) {
|
|
916
|
+
LogError(`RelayRealtimeUsage: usage details of ${usageDetailsJson.length} characters exceed ${MAX_USAGE_DETAILS_JSON_LENGTH} — details dropped.`);
|
|
917
|
+
return null;
|
|
918
|
+
}
|
|
919
|
+
const details = ParseRealtimeUsageRecord(usageDetailsJson);
|
|
920
|
+
return HasRealtimeUsage(details) ? details : null;
|
|
921
|
+
}
|
|
754
922
|
/**
|
|
755
923
|
* Sanitizes client-supplied waveform peaks before they're persisted: drops anything that isn't a
|
|
756
924
|
* finite number, clamps each to `[0, 1]`, and caps the array length so a hostile client can't bloat
|
|
@@ -769,23 +937,29 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
769
937
|
}
|
|
770
938
|
/**
|
|
771
939
|
* Reads the co-agent `AIPromptRun` id from the session config WITHOUT the relay path's
|
|
772
|
-
* throw-on-missing-target semantics
|
|
773
|
-
*
|
|
940
|
+
* throw-on-missing-target semantics, and keeps it only when it is the prompt run of the
|
|
941
|
+
* session's co-agent run (the owner can edit the config). Usage relay is best-effort, so a
|
|
942
|
+
* missing/malformed config, an absent id, or an id of another session just logs and returns `null`.
|
|
943
|
+
*
|
|
944
|
+
* @param runUser The user the prompt-run write runs as; the check runs as the same user.
|
|
774
945
|
*/
|
|
775
|
-
readPromptRunID(session) {
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
946
|
+
async readPromptRunID(session, runUser, provider) {
|
|
947
|
+
const config = this.tryReadSessionConfig(session);
|
|
948
|
+
if (!config) {
|
|
949
|
+
LogError(`RelayRealtimeUsage: session ${session.ID} has no parseable config — usage delta dropped.`);
|
|
950
|
+
return null;
|
|
951
|
+
}
|
|
952
|
+
if (!config.promptRunID) {
|
|
781
953
|
LogStatus(`RelayRealtimeUsage: session ${session.ID} has no co-agent promptRunID (observability run ` +
|
|
782
954
|
'creation was skipped or failed) — usage delta dropped.');
|
|
783
955
|
return null;
|
|
784
956
|
}
|
|
785
|
-
|
|
786
|
-
|
|
957
|
+
const verified = await this.runIDVerifier.Verify(session.ID, { CoAgentRunID: config.coAgentRunID, PromptRunID: config.promptRunID }, runUser, provider);
|
|
958
|
+
if (!verified.PromptRunID) {
|
|
959
|
+
LogError(`RelayRealtimeUsage: session ${session.ID} names a promptRunID that is not its co-agent prompt run — ignored.`);
|
|
787
960
|
return null;
|
|
788
961
|
}
|
|
962
|
+
return verified.PromptRunID;
|
|
789
963
|
}
|
|
790
964
|
/**
|
|
791
965
|
* Persist an interactive channel's state of record (e.g. the live whiteboard's serialized
|
|
@@ -811,6 +985,10 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
811
985
|
async SaveSessionChannelState(agentSessionId, channelName, stateJson, { userPayload, providers }) {
|
|
812
986
|
const { contextUser, provider } = this.requireUserAndProvider(userPayload, providers);
|
|
813
987
|
const session = await this.loadOwnedSession(agentSessionId, contextUser, provider);
|
|
988
|
+
if (!this.isChannelInSessionScope(session, channelName)) {
|
|
989
|
+
LogError(`SaveSessionChannelState: channel '${channelName}' is not in session ${agentSessionId}'s resolved scope — state not saved.`);
|
|
990
|
+
return false;
|
|
991
|
+
}
|
|
814
992
|
if (stateJson.length > MAX_CHANNEL_STATE_CHARS) {
|
|
815
993
|
LogError(`SaveSessionChannelState: rejected oversized state for session ${agentSessionId} / channel '${channelName}' ` +
|
|
816
994
|
`(${stateJson.length} chars > ${MAX_CHANNEL_STATE_CHARS}).`);
|
|
@@ -873,6 +1051,11 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
873
1051
|
async SaveSessionChannelArtifact(agentSessionId, channelName, name, contentJson, { userPayload, providers }) {
|
|
874
1052
|
const { contextUser, provider } = this.requireUserAndProvider(userPayload, providers);
|
|
875
1053
|
const session = await this.loadOwnedSession(agentSessionId, contextUser, provider);
|
|
1054
|
+
if (!this.isChannelInSessionScope(session, channelName)) {
|
|
1055
|
+
const message = `SaveSessionChannelArtifact: channel '${channelName}' is not in session ${agentSessionId}'s resolved scope — nothing saved.`;
|
|
1056
|
+
LogError(message);
|
|
1057
|
+
return { Success: false, ErrorMessage: message, ConversationDetailLinked: false };
|
|
1058
|
+
}
|
|
876
1059
|
if (contentJson.length > MAX_CHANNEL_STATE_CHARS) {
|
|
877
1060
|
const message = `SaveSessionChannelArtifact: rejected oversized content for session ${agentSessionId} / channel '${channelName}' ` +
|
|
878
1061
|
`(${contentJson.length} chars > ${MAX_CHANNEL_STATE_CHARS}).`;
|
|
@@ -900,8 +1083,10 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
900
1083
|
return { contextUser, provider: GetReadWriteProvider(providers) };
|
|
901
1084
|
}
|
|
902
1085
|
/**
|
|
903
|
-
* Authorization gate for
|
|
904
|
-
*
|
|
1086
|
+
* Authorization gate for the **target** agent: the caller must be able to run it. Denial throws.
|
|
1087
|
+
* {@link RealtimeClientSessionResolver.StartRealtimeClientSession} runs it before any session is
|
|
1088
|
+
* created, and {@link RealtimeClientSessionResolver.ExecuteRealtimeSessionTool} runs it on every
|
|
1089
|
+
* relayed call, because the owner can edit the target in the session config.
|
|
905
1090
|
*/
|
|
906
1091
|
async assertCanRunTarget(targetAgentId, contextUser, _provider) {
|
|
907
1092
|
const canRun = await AIAgentPermissionHelper.HasPermission(targetAgentId, contextUser, 'run');
|
|
@@ -1123,8 +1308,9 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
1123
1308
|
* resumed session remembers the previous leg(s).
|
|
1124
1309
|
* @param conversationMessages Optional capped history of the TEXT conversation this session is
|
|
1125
1310
|
* starting from (from {@link loadConversationHistory}) — framed as "Conversation so far".
|
|
1311
|
+
* @param showsAgentVideo `false` when the app shows no agent video: the session asks for no avatar (`host`).
|
|
1126
1312
|
*/
|
|
1127
|
-
async prepareClientSessionOrClose(session, coAgentID, targetAgentId, contextUser, provider, preferredModelId, clientTools, priorTranscript, configOverridesJson, maxSessionSeconds, applicationId, appContext, conversationMessages) {
|
|
1313
|
+
async prepareClientSessionOrClose(session, coAgentID, targetAgentId, contextUser, provider, preferredModelId, clientTools, priorTranscript, configOverridesJson, maxSessionSeconds, applicationId, appContext, conversationMessages, channelCandidates, showsAgentVideo) {
|
|
1128
1314
|
const prep = await this.clientSessionService.PrepareClientSession({
|
|
1129
1315
|
CoAgentID: coAgentID,
|
|
1130
1316
|
TargetAgentID: targetAgentId,
|
|
@@ -1150,6 +1336,14 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
1150
1336
|
// (allowed-agent union); the snapshot is injected into the system prompt at mint.
|
|
1151
1337
|
ApplicationID: applicationId,
|
|
1152
1338
|
AppContext: appContext,
|
|
1339
|
+
// Scope-limited callers get no direct actions (see the authorization model above), so
|
|
1340
|
+
// the model is not given tools that the relay refuses.
|
|
1341
|
+
ExcludeDirectActions: IsScopeLimitedPrincipal(contextUser),
|
|
1342
|
+
// The channels the browser could mount: scoped here (registry + cascade) and handed back
|
|
1343
|
+
// as the resolved policy; the declared tools are narrowed to match.
|
|
1344
|
+
ChannelCandidates: channelCandidates,
|
|
1345
|
+
// An app with no channel that shows the agent's video: no avatar is asked for (reason `host`).
|
|
1346
|
+
ShowsAgentVideo: showsAgentVideo ?? undefined,
|
|
1153
1347
|
},
|
|
1154
1348
|
// SCOPED-ANONYMOUS ELEVATION (issue #3371): the prepare creates the co-agent
|
|
1155
1349
|
// observability AIAgentRun/AIPromptRun/run-step, which a scoped anonymous caller's role
|
|
@@ -1162,7 +1356,8 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
1162
1356
|
await this.sessionManager.CloseSession(session.ID, contextUser, provider, 'Error');
|
|
1163
1357
|
throw new Error(prep.ErrorMessage ?? 'Failed to prepare the client realtime session.');
|
|
1164
1358
|
}
|
|
1165
|
-
await this.persistObservabilityRunIDs(session, targetAgentId, prep.CoAgentRunID, prep.PromptRunID, prep.CoAgentRunStepID, applicationId, prep.EffectiveConfig?.realtime?.allowedAgents, prep.EffectiveConfig?.realtime?.directActions);
|
|
1359
|
+
await this.persistObservabilityRunIDs(session, targetAgentId, prep.CoAgentRunID, prep.PromptRunID, prep.CoAgentRunStepID, applicationId, prep.EffectiveConfig?.realtime?.allowedAgents, prep.EffectiveConfig?.realtime?.directActions, prep.ClientPolicy?.Channels.map((c) => c.Key), appContext);
|
|
1360
|
+
this.pruneExcludedServerChannels(session.ID, prep.ClientPolicy);
|
|
1166
1361
|
const cfg = prep.ClientConfig;
|
|
1167
1362
|
return {
|
|
1168
1363
|
AgentSessionId: session.ID,
|
|
@@ -1171,40 +1366,91 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
1171
1366
|
Model: cfg.Model,
|
|
1172
1367
|
EphemeralToken: cfg.EphemeralToken,
|
|
1173
1368
|
ExpiresAt: cfg.ExpiresAt,
|
|
1369
|
+
Transport: cfg.Transport,
|
|
1370
|
+
RelayUrl: cfg.RelayUrl,
|
|
1174
1371
|
SessionConfigJson: JSON.stringify(cfg.SessionConfig),
|
|
1175
1372
|
ModelName: prep.ModelName,
|
|
1176
1373
|
DriverClass: prep.DriverClass,
|
|
1177
1374
|
NarrationInstructionsTemplate: prep.NarrationInstructionsTemplate,
|
|
1178
1375
|
NarrationPaceMs: prep.NarrationPaceMs,
|
|
1179
1376
|
EffectiveConfigJson: prep.EffectiveConfig ? JSON.stringify(prep.EffectiveConfig) : undefined,
|
|
1377
|
+
ClientPolicyJson: prep.ClientPolicy ? JSON.stringify(prep.ClientPolicy) : undefined,
|
|
1378
|
+
AvatarStatusJson: prep.AvatarStatus ? JSON.stringify(prep.AvatarStatus) : undefined,
|
|
1180
1379
|
};
|
|
1181
1380
|
}
|
|
1381
|
+
/**
|
|
1382
|
+
* Drops the server-side channel plugins of channels the resolved scope left out. Server plugins
|
|
1383
|
+
* start when the session row is created — before the scope is known — so an excluded channel's
|
|
1384
|
+
* server half would otherwise keep running. A no-op when the caller reported no candidates.
|
|
1385
|
+
*/
|
|
1386
|
+
pruneExcludedServerChannels(agentSessionID, policy) {
|
|
1387
|
+
const excluded = (policy?.ExcludedChannels ?? []).map((e) => e.Key);
|
|
1388
|
+
if (excluded.length === 0) {
|
|
1389
|
+
return;
|
|
1390
|
+
}
|
|
1391
|
+
const dropped = RealtimeChannelServerHost.Instance.PruneSessionChannels(agentSessionID, excluded);
|
|
1392
|
+
if (dropped.length > 0) {
|
|
1393
|
+
LogStatus(`StartRealtimeClientSession: dropped the server plugins of out-of-scope channel(s) [${dropped.join(', ')}] for session ${agentSessionID}.`);
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1182
1396
|
/**
|
|
1183
1397
|
* Writes the co-agent observability run ids into the session's `Config_` alongside the
|
|
1184
|
-
*
|
|
1185
|
-
* finalize the runs (and on relay to nest delegated runs).
|
|
1186
|
-
*
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1398
|
+
* `targetAgentID`, then saves the session. These ids are read back on close to
|
|
1399
|
+
* finalize the runs (and on relay to nest delegated runs). The app awareness (application id,
|
|
1400
|
+
* allowed agents, direct actions, app-context snapshot) is written with them; relays read the
|
|
1401
|
+
* allowed agents and the app context, never the direct actions.
|
|
1402
|
+
* Best-effort: a save failure is logged, not thrown — the voice session still proceeds, it just
|
|
1403
|
+
* won't carry the run ids.
|
|
1404
|
+
*/
|
|
1405
|
+
async persistObservabilityRunIDs(session, targetAgentID, coAgentRunID, promptRunID, coAgentRunStepID, applicationID, allowedAgents, directActions, channels, appContext) {
|
|
1406
|
+
// Start from the RAW stored config so every key set at session start survives — the
|
|
1407
|
+
// server-authoritative voice deadline (`maxSessionDeadlineIso`) and the per-session media-kit
|
|
1408
|
+
// override among them — rather than rebuilding from a whitelist that silently drops the rest.
|
|
1192
1409
|
const config = {
|
|
1410
|
+
...ParseConfigObject(session.Config_),
|
|
1193
1411
|
targetAgentID,
|
|
1194
1412
|
coAgentRunID,
|
|
1195
1413
|
promptRunID,
|
|
1196
1414
|
coAgentRunStepID,
|
|
1197
|
-
maxSessionDeadlineIso: existing?.maxSessionDeadlineIso,
|
|
1198
1415
|
applicationID,
|
|
1416
|
+
appContext: this.appContextToStore(appContext, session.ID),
|
|
1199
1417
|
allowedAgents: allowedAgents && allowedAgents.length > 0 ? allowedAgents : undefined,
|
|
1200
1418
|
directActions,
|
|
1419
|
+
channels,
|
|
1201
1420
|
};
|
|
1202
1421
|
session.Config_ = JSON.stringify(config);
|
|
1203
|
-
|
|
1422
|
+
// Every value written here is server-decided (run ids, the resolved target, the in-scope channel
|
|
1423
|
+
// set), and `channels` is a server-authoritative key the owner may not write — so this save runs in
|
|
1424
|
+
// the trusted scope. Protected keys already in the stored config are carried forward unchanged.
|
|
1425
|
+
const saved = await RunWithTrustedSessionConfigWrites(() => session.Save());
|
|
1204
1426
|
if (!saved) {
|
|
1205
1427
|
LogError(`RealtimeClientSessionResolver.persistObservabilityRunIDs save failed for session ${session.ID}: ${this.describeSaveFailure(session)}`);
|
|
1206
1428
|
}
|
|
1207
1429
|
}
|
|
1430
|
+
/**
|
|
1431
|
+
* Returns the app-context snapshot to store on the session config. A snapshot whose JSON is longer
|
|
1432
|
+
* than {@link MAX_CLIENT_TOOLS_JSON_CHARS} is not stored: a warning is logged and `undefined` is
|
|
1433
|
+
* returned, so the session still starts and its delegated runs get no app context.
|
|
1434
|
+
*
|
|
1435
|
+
* @param appContext The snapshot the browser sent at session start, or undefined.
|
|
1436
|
+
* @param sessionID The session the snapshot belongs to (for the warning).
|
|
1437
|
+
* @returns The snapshot to store, or `undefined`.
|
|
1438
|
+
*/
|
|
1439
|
+
appContextToStore(appContext, sessionID) {
|
|
1440
|
+
if (!appContext) {
|
|
1441
|
+
return undefined;
|
|
1442
|
+
}
|
|
1443
|
+
const chars = JSON.stringify(appContext).length;
|
|
1444
|
+
if (chars > MAX_CLIENT_TOOLS_JSON_CHARS) {
|
|
1445
|
+
LogErrorEx({
|
|
1446
|
+
message: `RealtimeClientSessionResolver: app context for session ${sessionID} not stored — ${chars} chars ` +
|
|
1447
|
+
`exceeds the ${MAX_CLIENT_TOOLS_JSON_CHARS} cap; delegated runs get no app context.`,
|
|
1448
|
+
severity: 'warning',
|
|
1449
|
+
});
|
|
1450
|
+
return undefined;
|
|
1451
|
+
}
|
|
1452
|
+
return appContext;
|
|
1453
|
+
}
|
|
1208
1454
|
/**
|
|
1209
1455
|
* Loads a session, enforcing inbound ownership (`UserID === contextUser.ID`) and that it is not
|
|
1210
1456
|
* `Closed`. Throws on any violation. Returns the loaded session for the caller to read from.
|
|
@@ -1399,6 +1645,10 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
1399
1645
|
* as the resumed transcript are removed, so the prompt never carries two accounts of one
|
|
1400
1646
|
* exchange.
|
|
1401
1647
|
*
|
|
1648
|
+
* Only the rows on the session's path that no rerun replaced are read: the fork it started on,
|
|
1649
|
+
* or Main, in `Sequence` order. When that path cannot be built — its branch rows are missing or invalid — the session
|
|
1650
|
+
* starts with no history, never with every branch's rows.
|
|
1651
|
+
*
|
|
1402
1652
|
* Strictly tolerant — no conversation, a failed read, or any throw yields `[]`, never a failed
|
|
1403
1653
|
* session start. The call still works; it just starts cold.
|
|
1404
1654
|
*
|
|
@@ -1406,19 +1656,28 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
1406
1656
|
* @param coveredSessionIDs Session ids already framed via `PriorTranscript`.
|
|
1407
1657
|
* @param contextUser The calling user (RunView scope — the read is row-level-secured).
|
|
1408
1658
|
* @param provider The request-scoped metadata provider.
|
|
1659
|
+
* @param branchId The fork the session started on; null is Main.
|
|
1409
1660
|
* @returns The capped history, oldest first.
|
|
1410
1661
|
*/
|
|
1411
|
-
async loadConversationHistory(conversationId, coveredSessionIDs, contextUser, provider) {
|
|
1662
|
+
async loadConversationHistory(conversationId, coveredSessionIDs, contextUser, provider, branchId) {
|
|
1412
1663
|
if (!conversationId) {
|
|
1413
1664
|
return [];
|
|
1414
1665
|
}
|
|
1666
|
+
const rv = RunView.FromMetadataProvider(provider);
|
|
1667
|
+
let pathFilter;
|
|
1668
|
+
try {
|
|
1669
|
+
pathFilter = await ConversationEngine.BranchPathFilterFresh(conversationId, branchId, contextUser, provider);
|
|
1670
|
+
}
|
|
1671
|
+
catch (error) {
|
|
1672
|
+
LogError(`StartRealtimeClientSession: could not build the path of the session's view for conversation ${conversationId}: ${error.message}`);
|
|
1673
|
+
return [];
|
|
1674
|
+
}
|
|
1415
1675
|
try {
|
|
1416
|
-
const rv = RunView.FromMetadataProvider(provider);
|
|
1417
1676
|
const result = await rv.RunView({
|
|
1418
1677
|
EntityName: CONVERSATION_DETAIL_ENTITY,
|
|
1419
|
-
ExtraFilter:
|
|
1678
|
+
ExtraFilter: ConversationEngine.LiveRowsFilter(pathFilter),
|
|
1420
1679
|
Fields: ['ID', 'Role', 'Message', 'HiddenToUser', 'AgentSessionID', '__mj_CreatedAt'],
|
|
1421
|
-
OrderBy: '
|
|
1680
|
+
OrderBy: 'Sequence ASC',
|
|
1422
1681
|
ResultType: 'simple',
|
|
1423
1682
|
}, contextUser);
|
|
1424
1683
|
if (!result.Success) {
|
|
@@ -1707,6 +1966,7 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
1707
1966
|
const detail = await provider.GetEntityObject(CONVERSATION_DETAIL_ENTITY, callerUser);
|
|
1708
1967
|
detail.NewRecord();
|
|
1709
1968
|
detail.ConversationID = session.ConversationID;
|
|
1969
|
+
detail.BranchID = this.sessionBranchIdOf(session);
|
|
1710
1970
|
detail.Role = 'AI';
|
|
1711
1971
|
detail.HiddenToUser = true;
|
|
1712
1972
|
detail.Message = 'Artifacts produced during a realtime session (system anchor).';
|
|
@@ -1835,18 +2095,50 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
1835
2095
|
}
|
|
1836
2096
|
/** Shape check for one client tool declaration: Name/Description non-empty strings, ParametersSchema a plain object. */
|
|
1837
2097
|
isValidClientTool(candidate) {
|
|
1838
|
-
|
|
2098
|
+
return IsValidRealtimeToolDefinition(candidate);
|
|
2099
|
+
}
|
|
2100
|
+
/**
|
|
2101
|
+
* Tolerantly parses the channel candidates the browser reported at mint (the same caps and
|
|
2102
|
+
* tolerance as {@link parseClientTools}: capped, never throws, anything wrong is dropped and
|
|
2103
|
+
* logged). A caller that sent none yields `undefined` — an unscoped session, exactly as before.
|
|
2104
|
+
*/
|
|
2105
|
+
parseChannelCandidates(channelCandidatesJson) {
|
|
2106
|
+
const parsed = ParseRealtimeChannelCandidates(channelCandidatesJson);
|
|
2107
|
+
for (const reason of parsed.Rejected) {
|
|
2108
|
+
LogError(`StartRealtimeClientSession: ${reason}.`);
|
|
2109
|
+
}
|
|
2110
|
+
return parsed.Candidates.length > 0 ? parsed.Candidates : undefined;
|
|
2111
|
+
}
|
|
2112
|
+
/**
|
|
2113
|
+
* Whether a session's persisted scope allows a server-side call to name `channelName`. A session
|
|
2114
|
+
* with no persisted scope (it reported no candidates) is unscoped and allows everything; a scoped
|
|
2115
|
+
* one allows only the channels its policy put in the session.
|
|
2116
|
+
*/
|
|
2117
|
+
isChannelInSessionScope(session, channelName) {
|
|
2118
|
+
// Read the RAW config: an absent `channels` key means an unscoped session, but anything present
|
|
2119
|
+
// and malformed fails CLOSED — a scope that cannot be read must not widen to "everything".
|
|
2120
|
+
// (`channels` is a server-authoritative key, so the owner cannot delete it to unscope a session.)
|
|
2121
|
+
const channels = ParseConfigObject(session.Config_)['channels'];
|
|
2122
|
+
if (channels === undefined) {
|
|
2123
|
+
return true;
|
|
2124
|
+
}
|
|
2125
|
+
if (!Array.isArray(channels)) {
|
|
2126
|
+
LogError(`RealtimeClientSessionResolver: session ${session.ID} has a malformed channel scope; refusing channel '${channelName}'.`);
|
|
1839
2127
|
return false;
|
|
1840
2128
|
}
|
|
1841
|
-
const
|
|
1842
|
-
return (typeof
|
|
1843
|
-
|
|
1844
|
-
|
|
2129
|
+
const wanted = NormalizeChannelKey(channelName);
|
|
2130
|
+
return channels.some((key) => typeof key === 'string' && NormalizeChannelKey(key) === wanted);
|
|
2131
|
+
}
|
|
2132
|
+
/** The fork the session was started on (its config's `conversationBranchID`); null is Main. */
|
|
2133
|
+
sessionBranchIdOf(session) {
|
|
2134
|
+
const branchId = ParseConfigObject(session.Config_)['conversationBranchID'];
|
|
2135
|
+
return typeof branchId === 'string' && branchId.length > 0 ? branchId : null;
|
|
1845
2136
|
}
|
|
1846
2137
|
/**
|
|
1847
|
-
* Parses the session's persisted config, returning
|
|
1848
|
-
*
|
|
1849
|
-
*
|
|
2138
|
+
* Parses the session's persisted config, returning its `targetAgentID` plus any observability run
|
|
2139
|
+
* ids (`coAgentRunID`/`promptRunID`). Throws when the config is missing/malformed or carries no
|
|
2140
|
+
* target — a relay cannot proceed without a target. The owner can edit the config, so callers
|
|
2141
|
+
* re-check what they act on.
|
|
1850
2142
|
*/
|
|
1851
2143
|
readSessionConfig(session) {
|
|
1852
2144
|
const raw = session.Config_;
|
|
@@ -1862,10 +2154,16 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
1862
2154
|
pendingFeedbackRunID: typeof parsed.pendingFeedbackRunID === 'string' ? parsed.pendingFeedbackRunID : undefined,
|
|
1863
2155
|
maxSessionDeadlineIso: typeof parsed.maxSessionDeadlineIso === 'string' ? parsed.maxSessionDeadlineIso : undefined,
|
|
1864
2156
|
applicationID: typeof parsed.applicationID === 'string' ? parsed.applicationID : undefined,
|
|
2157
|
+
appContext: parsed.appContext && typeof parsed.appContext === 'object' && !Array.isArray(parsed.appContext)
|
|
2158
|
+
? parsed.appContext
|
|
2159
|
+
: undefined,
|
|
1865
2160
|
allowedAgents: Array.isArray(parsed.allowedAgents) ? parsed.allowedAgents : undefined,
|
|
1866
2161
|
directActions: parsed.directActions && typeof parsed.directActions === 'object' && typeof parsed.directActions.enabled === 'boolean'
|
|
1867
2162
|
? parsed.directActions
|
|
1868
2163
|
: undefined,
|
|
2164
|
+
conversationBranchID: typeof parsed.conversationBranchID === 'string' ? parsed.conversationBranchID : undefined,
|
|
2165
|
+
mediaCollectionID: typeof parsed.mediaCollectionID === 'string' ? parsed.mediaCollectionID : undefined,
|
|
2166
|
+
channels: Array.isArray(parsed.channels) ? parsed.channels.filter((k) => typeof k === 'string') : undefined,
|
|
1869
2167
|
};
|
|
1870
2168
|
}
|
|
1871
2169
|
}
|
|
@@ -1931,6 +2229,7 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
1931
2229
|
const detail = await provider.GetEntityObject(CONVERSATION_DETAIL_ENTITY, contextUser);
|
|
1932
2230
|
detail.NewRecord();
|
|
1933
2231
|
detail.ConversationID = session.ConversationID;
|
|
2232
|
+
detail.BranchID = this.sessionBranchIdOf(session);
|
|
1934
2233
|
detail.Role = this.mapTranscriptRole(role);
|
|
1935
2234
|
detail.Message = text;
|
|
1936
2235
|
detail.AgentSessionID = session.ID;
|
|
@@ -2068,6 +2367,7 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
2068
2367
|
const detail = await provider.GetEntityObject(CONVERSATION_DETAIL_ENTITY, contextUser);
|
|
2069
2368
|
detail.NewRecord();
|
|
2070
2369
|
detail.ConversationID = session.ConversationID;
|
|
2370
|
+
detail.BranchID = this.sessionBranchIdOf(session);
|
|
2071
2371
|
detail.Role = 'AI';
|
|
2072
2372
|
detail.HiddenToUser = true;
|
|
2073
2373
|
detail.AgentSessionID = session.ID;
|
|
@@ -2129,8 +2429,11 @@ __decorate([
|
|
|
2129
2429
|
__param(10, Arg('mediaCollectionId', () => String, { nullable: true })),
|
|
2130
2430
|
__param(11, Arg('applicationId', () => String, { nullable: true })),
|
|
2131
2431
|
__param(12, Arg('appContextJson', () => String, { nullable: true })),
|
|
2432
|
+
__param(13, Arg('channelCandidatesJson', () => String, { nullable: true })),
|
|
2433
|
+
__param(14, Arg('showsAgentVideo', () => Boolean, { nullable: true })),
|
|
2434
|
+
__param(15, Arg('conversationBranchId', () => String, { nullable: true })),
|
|
2132
2435
|
__metadata("design:type", Function),
|
|
2133
|
-
__metadata("design:paramtypes", [String, Object, String, String, String, String, String, String, String, Boolean, String, String, String]),
|
|
2436
|
+
__metadata("design:paramtypes", [String, Object, String, String, String, String, String, String, String, Boolean, String, String, String, String, Boolean, String]),
|
|
2134
2437
|
__metadata("design:returntype", Promise)
|
|
2135
2438
|
], RealtimeClientSessionResolver.prototype, "StartRealtimeClientSession", null);
|
|
2136
2439
|
__decorate([
|
|
@@ -2208,8 +2511,9 @@ __decorate([
|
|
|
2208
2511
|
__param(1, Arg('inputTokens', () => Int)),
|
|
2209
2512
|
__param(2, Arg('outputTokens', () => Int)),
|
|
2210
2513
|
__param(3, Ctx()),
|
|
2514
|
+
__param(4, Arg('usageDetailsJson', () => String, { nullable: true })),
|
|
2211
2515
|
__metadata("design:type", Function),
|
|
2212
|
-
__metadata("design:paramtypes", [String, Number, Number, Object]),
|
|
2516
|
+
__metadata("design:paramtypes", [String, Number, Number, Object, String]),
|
|
2213
2517
|
__metadata("design:returntype", Promise)
|
|
2214
2518
|
], RealtimeClientSessionResolver.prototype, "RelayRealtimeUsage", null);
|
|
2215
2519
|
__decorate([
|