@memberjunction/server 6.2.0-edge.2 → 6.2.0-edge.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/agentSessions/BridgeHostInstance.d.ts +25 -0
- package/dist/agentSessions/BridgeHostInstance.d.ts.map +1 -0
- package/dist/agentSessions/BridgeHostInstance.js +30 -0
- package/dist/agentSessions/BridgeHostInstance.js.map +1 -0
- package/dist/agentSessions/HostInstance.d.ts +75 -11
- package/dist/agentSessions/HostInstance.d.ts.map +1 -1
- package/dist/agentSessions/HostInstance.js +99 -28
- package/dist/agentSessions/HostInstance.js.map +1 -1
- package/dist/agentSessions/ReturningVisitorRecap.d.ts.map +1 -1
- package/dist/agentSessions/ReturningVisitorRecap.js +7 -3
- package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -1
- package/dist/agentSessions/SessionJanitor.d.ts +173 -25
- package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
- package/dist/agentSessions/SessionJanitor.js +452 -54
- package/dist/agentSessions/SessionJanitor.js.map +1 -1
- package/dist/agentSessions/SessionManager.d.ts +43 -2
- package/dist/agentSessions/SessionManager.d.ts.map +1 -1
- package/dist/agentSessions/SessionManager.js +99 -8
- package/dist/agentSessions/SessionManager.js.map +1 -1
- package/dist/agentSessions/SessionRunIDVerifier.d.ts +63 -0
- package/dist/agentSessions/SessionRunIDVerifier.d.ts.map +1 -0
- package/dist/agentSessions/SessionRunIDVerifier.js +129 -0
- package/dist/agentSessions/SessionRunIDVerifier.js.map +1 -0
- package/dist/agentSessions/index.d.ts +2 -0
- package/dist/agentSessions/index.d.ts.map +1 -1
- package/dist/agentSessions/index.js +10 -3
- package/dist/agentSessions/index.js.map +1 -1
- package/dist/auth/actionAuthorization.d.ts +40 -0
- package/dist/auth/actionAuthorization.d.ts.map +1 -0
- package/dist/auth/actionAuthorization.js +94 -0
- package/dist/auth/actionAuthorization.js.map +1 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +3 -10
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.js +6 -3
- package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
- package/dist/auth/newUsers.d.ts +10 -0
- package/dist/auth/newUsers.d.ts.map +1 -1
- package/dist/auth/newUsers.js +80 -11
- package/dist/auth/newUsers.js.map +1 -1
- package/dist/config.d.ts +4567 -71
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +246 -3
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +8 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +25 -34
- package/dist/context.js.map +1 -1
- package/dist/generated/graphql-schemas/__mj.d.ts +1073 -10
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +9188 -3649
- package/dist/generated/graphql-schemas/__mj.js.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.js +3 -3
- package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +62 -6
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +179 -35
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts +4 -0
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +37 -1
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +214 -159
- package/dist/index.js.map +1 -1
- package/dist/isolatedProvider.d.ts +20 -0
- package/dist/isolatedProvider.d.ts.map +1 -0
- package/dist/isolatedProvider.js +22 -0
- package/dist/isolatedProvider.js.map +1 -0
- package/dist/postgresqlCompat.d.ts +8 -0
- package/dist/postgresqlCompat.d.ts.map +1 -0
- package/dist/postgresqlCompat.js +19 -0
- package/dist/postgresqlCompat.js.map +1 -0
- package/dist/postgresqlPoolSettings.d.ts +108 -0
- package/dist/postgresqlPoolSettings.d.ts.map +1 -0
- package/dist/postgresqlPoolSettings.js +134 -0
- package/dist/postgresqlPoolSettings.js.map +1 -0
- package/dist/realtimeProxy/RealtimeProxyServer.d.ts +44 -4
- package/dist/realtimeProxy/RealtimeProxyServer.d.ts.map +1 -1
- package/dist/realtimeProxy/RealtimeProxyServer.js +121 -16
- package/dist/realtimeProxy/RealtimeProxyServer.js.map +1 -1
- package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts +171 -0
- package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts.map +1 -0
- package/dist/realtimeProxy/RealtimeRelayTunnel.js +532 -0
- package/dist/realtimeProxy/RealtimeRelayTunnel.js.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.d.ts +148 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.d.ts.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.js +191 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.js.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts +66 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.js +138 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.js.map +1 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts +38 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts.map +1 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.js +98 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.js.map +1 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts +61 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts.map +1 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.js +149 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.js.map +1 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts +57 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts.map +1 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.js +69 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.js.map +1 -0
- package/dist/realtimeSessions/index.d.ts +17 -0
- package/dist/realtimeSessions/index.d.ts.map +1 -0
- package/dist/realtimeSessions/index.js +17 -0
- package/dist/realtimeSessions/index.js.map +1 -0
- package/dist/realtimeSessions/sessionAccess.d.ts +86 -0
- package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -0
- package/dist/realtimeSessions/sessionAccess.js +90 -0
- package/dist/realtimeSessions/sessionAccess.js.map +1 -0
- package/dist/realtimeSessions/sessionConfigStamps.d.ts +70 -0
- package/dist/realtimeSessions/sessionConfigStamps.d.ts.map +1 -0
- package/dist/realtimeSessions/sessionConfigStamps.js +93 -0
- package/dist/realtimeSessions/sessionConfigStamps.js.map +1 -0
- package/dist/realtimeSessions/subscriptionAuthorization.d.ts +65 -0
- package/dist/realtimeSessions/subscriptionAuthorization.d.ts.map +1 -0
- package/dist/realtimeSessions/subscriptionAuthorization.js +71 -0
- package/dist/realtimeSessions/subscriptionAuthorization.js.map +1 -0
- package/dist/realtimeSessions/verificationCore.d.ts +243 -0
- package/dist/realtimeSessions/verificationCore.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationCore.js +356 -0
- package/dist/realtimeSessions/verificationCore.js.map +1 -0
- package/dist/realtimeSessions/verificationPages.d.ts +66 -0
- package/dist/realtimeSessions/verificationPages.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationPages.js +110 -0
- package/dist/realtimeSessions/verificationPages.js.map +1 -0
- package/dist/realtimeSessions/verificationPolicy.d.ts +147 -0
- package/dist/realtimeSessions/verificationPolicy.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationPolicy.js +249 -0
- package/dist/realtimeSessions/verificationPolicy.js.map +1 -0
- package/dist/realtimeSessions/verificationWorkflow.d.ts +263 -0
- package/dist/realtimeSessions/verificationWorkflow.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationWorkflow.js +435 -0
- package/dist/realtimeSessions/verificationWorkflow.js.map +1 -0
- package/dist/realtimeWidget/widgetGuestElevation.d.ts +20 -2
- package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.js +31 -2
- package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
- package/dist/resolvers/ActionResolver.d.ts +31 -5
- package/dist/resolvers/ActionResolver.d.ts.map +1 -1
- package/dist/resolvers/ActionResolver.js +75 -14
- package/dist/resolvers/ActionResolver.js.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.d.ts +35 -17
- package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.js +92 -166
- package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
- package/dist/resolvers/CacheStatsResolver.d.ts +27 -0
- package/dist/resolvers/CacheStatsResolver.d.ts.map +1 -1
- package/dist/resolvers/CacheStatsResolver.js +113 -2
- package/dist/resolvers/CacheStatsResolver.js.map +1 -1
- package/dist/resolvers/ClientToolRequestResolver.d.ts +4 -2
- package/dist/resolvers/ClientToolRequestResolver.d.ts.map +1 -1
- package/dist/resolvers/ClientToolRequestResolver.js +28 -10
- package/dist/resolvers/ClientToolRequestResolver.js.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.d.ts +7 -23
- package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.js +9 -47
- package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts +12 -1
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.js +83 -2
- package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
- package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataResolver.js +9 -0
- package/dist/resolvers/GetDataResolver.js.map +1 -1
- package/dist/resolvers/HumanHandoffResolver.d.ts +107 -0
- package/dist/resolvers/HumanHandoffResolver.d.ts.map +1 -0
- package/dist/resolvers/HumanHandoffResolver.js +330 -0
- package/dist/resolvers/HumanHandoffResolver.js.map +1 -0
- package/dist/resolvers/MCPResolver.d.ts.map +1 -1
- package/dist/resolvers/MCPResolver.js +9 -4
- package/dist/resolvers/MCPResolver.js.map +1 -1
- package/dist/resolvers/MeetingResolver.d.ts +69 -0
- package/dist/resolvers/MeetingResolver.d.ts.map +1 -0
- package/dist/resolvers/MeetingResolver.js +381 -0
- package/dist/resolvers/MeetingResolver.js.map +1 -0
- package/dist/resolvers/RealtimeBridgeResolver.d.ts +88 -3
- package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.js +352 -12
- package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +156 -108
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +395 -293
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RealtimeSessionEventsResolver.d.ts +61 -0
- package/dist/resolvers/RealtimeSessionEventsResolver.d.ts.map +1 -0
- package/dist/resolvers/RealtimeSessionEventsResolver.js +130 -0
- package/dist/resolvers/RealtimeSessionEventsResolver.js.map +1 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts +85 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts.map +1 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.js +188 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.js.map +1 -0
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts +2 -0
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js +5 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts +10 -0
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +68 -11
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTemplateResolver.js +12 -11
- package/dist/resolvers/RunTemplateResolver.js.map +1 -1
- package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncDataResolver.js +6 -2
- package/dist/resolvers/SyncDataResolver.js.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.d.ts +2 -0
- package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.js +23 -6
- package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
- package/dist/resolvers/UserAvatarResolver.d.ts +16 -0
- package/dist/resolvers/UserAvatarResolver.d.ts.map +1 -0
- package/dist/resolvers/UserAvatarResolver.js +124 -0
- package/dist/resolvers/UserAvatarResolver.js.map +1 -0
- package/dist/resolvers/UserResolver.d.ts.map +1 -1
- package/dist/resolvers/UserResolver.js +2 -17
- package/dist/resolvers/UserResolver.js.map +1 -1
- package/dist/resolvers/adhocTimeout.d.ts +7 -0
- package/dist/resolvers/adhocTimeout.d.ts.map +1 -0
- package/dist/resolvers/adhocTimeout.js +12 -0
- package/dist/resolvers/adhocTimeout.js.map +1 -0
- package/dist/resolvers/agentVisionConsentAudit.d.ts +37 -0
- package/dist/resolvers/agentVisionConsentAudit.d.ts.map +1 -0
- package/dist/resolvers/agentVisionConsentAudit.js +63 -0
- package/dist/resolvers/agentVisionConsentAudit.js.map +1 -0
- package/dist/resolvers/avatarInputValidation.d.ts +40 -0
- package/dist/resolvers/avatarInputValidation.d.ts.map +1 -0
- package/dist/resolvers/avatarInputValidation.js +97 -0
- package/dist/resolvers/avatarInputValidation.js.map +1 -0
- package/dist/resolvers/currentUserRoles.d.ts +29 -0
- package/dist/resolvers/currentUserRoles.d.ts.map +1 -1
- package/dist/resolvers/currentUserRoles.js +30 -0
- package/dist/resolvers/currentUserRoles.js.map +1 -1
- package/dist/resolvers/meetings/index.d.ts +5 -0
- package/dist/resolvers/meetings/index.d.ts.map +1 -0
- package/dist/resolvers/meetings/index.js +5 -0
- package/dist/resolvers/meetings/index.js.map +1 -0
- package/dist/resolvers/meetings/meetingHelpers.d.ts +17 -0
- package/dist/resolvers/meetings/meetingHelpers.d.ts.map +1 -0
- package/dist/resolvers/meetings/meetingHelpers.js +212 -0
- package/dist/resolvers/meetings/meetingHelpers.js.map +1 -0
- package/dist/resolvers/meetings/meetingMutations.d.ts +18 -0
- package/dist/resolvers/meetings/meetingMutations.d.ts.map +1 -0
- package/dist/resolvers/meetings/meetingMutations.js +439 -0
- package/dist/resolvers/meetings/meetingMutations.js.map +1 -0
- package/dist/resolvers/meetings/meetingQueries.d.ts +7 -0
- package/dist/resolvers/meetings/meetingQueries.d.ts.map +1 -0
- package/dist/resolvers/meetings/meetingQueries.js +113 -0
- package/dist/resolvers/meetings/meetingQueries.js.map +1 -0
- package/dist/resolvers/meetings/meetingTypes.d.ts +89 -0
- package/dist/resolvers/meetings/meetingTypes.d.ts.map +1 -0
- package/dist/resolvers/meetings/meetingTypes.js +345 -0
- package/dist/resolvers/meetings/meetingTypes.js.map +1 -0
- package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
- package/dist/rest/ViewOperationsHandler.js +8 -5
- package/dist/rest/ViewOperationsHandler.js.map +1 -1
- package/dist/services/TaskGraphActionRunner.d.ts +8 -0
- package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphActionRunner.js +27 -1
- package/dist/services/TaskGraphActionRunner.js.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.d.ts +4 -3
- package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.js +13 -7
- package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
- package/dist/services/WorkQueueHostService.d.ts +36 -0
- package/dist/services/WorkQueueHostService.d.ts.map +1 -0
- package/dist/services/WorkQueueHostService.js +97 -0
- package/dist/services/WorkQueueHostService.js.map +1 -0
- package/dist/services/workQueueConfig.d.ts +103 -0
- package/dist/services/workQueueConfig.d.ts.map +1 -0
- package/dist/services/workQueueConfig.js +48 -0
- package/dist/services/workQueueConfig.js.map +1 -0
- package/dist/sharedCache.d.ts +105 -0
- package/dist/sharedCache.d.ts.map +1 -0
- package/dist/sharedCache.js +276 -0
- package/dist/sharedCache.js.map +1 -0
- package/dist/types.d.ts +11 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +97 -94
- package/src/__tests__/ActionResolver.authorization.test.ts +353 -0
- package/src/__tests__/AdhocQueryResolver.bugs.test.ts +14 -35
- package/src/__tests__/AdhocQueryResolver.pagination.test.ts +6 -2
- package/src/__tests__/AdhocQueryResolver.scopeGuard.test.ts +3 -3
- package/src/__tests__/AdhocQueryResolver.sqlAuthorizer.test.ts +104 -0
- package/src/__tests__/BridgeHostInstance.test.ts +232 -0
- package/src/__tests__/ClientToolRequestResolver.test.ts +116 -0
- package/src/__tests__/EntityRecordNameResolver.fieldSecurity.test.ts +27 -72
- package/src/__tests__/FetchEntityVectorsResolver.test.ts +285 -0
- package/src/__tests__/HostInstance.test.ts +84 -0
- package/src/__tests__/HumanHandoffResolver.test.ts +250 -0
- package/src/__tests__/MeetingResolver.test.ts +448 -0
- package/src/__tests__/RealtimeBridgeResolver.test.ts +651 -4
- package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +383 -0
- package/src/__tests__/RealtimeClientSessionResolver.test.ts +682 -58
- package/src/__tests__/RealtimeProxyServer.test.ts +16 -1
- package/src/__tests__/RealtimeRelay.test.ts +876 -0
- package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +129 -0
- package/src/__tests__/ResolverBase.adhocReadScope.test.ts +96 -0
- package/src/__tests__/ResolverBase.aggregateScreen.test.ts +174 -0
- package/src/__tests__/ResolverBase.clientOldValues.test.ts +6 -3
- package/src/__tests__/ResolverBase.filterEscaping.test.ts +21 -2
- package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +291 -0
- package/src/__tests__/ReturningVisitorRecap.test.ts +115 -0
- package/src/__tests__/RunAIAgentResolver.branch.test.ts +169 -0
- package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +4 -4
- package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +166 -0
- package/src/__tests__/RunTemplateResolver.test.ts +132 -0
- package/src/__tests__/RunViewResolver.cacheCheckForwarding.test.ts +63 -0
- package/src/__tests__/SessionJanitor.test.ts +845 -18
- package/src/__tests__/SessionManager.test.ts +213 -17
- package/src/__tests__/SessionRunIDVerifier.test.ts +251 -0
- package/src/__tests__/SyncRolesUsersResolver.userRoles.test.ts +97 -0
- package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +41 -0
- package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +50 -3
- package/src/__tests__/UserAvatarResolver.test.ts +255 -0
- package/src/__tests__/UserResolver.currentUser.test.ts +16 -1
- package/src/__tests__/WorkQueueHostService.test.ts +133 -0
- package/src/__tests__/actionAuthorization.test.ts +130 -0
- package/src/__tests__/adhocTimeoutClamp.test.ts +26 -0
- package/src/__tests__/agentVisionConsentAudit.test.ts +118 -0
- package/src/__tests__/avatarInputValidation.test.ts +160 -0
- package/src/__tests__/isolatedProviderTransactions.test.ts +169 -0
- package/src/__tests__/magicLinkService.consume.test.ts +6 -0
- package/src/__tests__/magicLinkService.provisionLog.test.ts +5 -0
- package/src/__tests__/newUsers.template.test.ts +151 -0
- package/src/__tests__/newUsers.test.ts +74 -5
- package/src/__tests__/orphanedConversationDetailReconciler.test.ts +17 -2
- package/src/__tests__/postgresqlPoolSettings.test.ts +131 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionEventService.test.ts +201 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionEventsResolver.test.ts +215 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionVerificationResolver.test.ts +102 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionVerificationService.test.ts +349 -0
- package/src/__tests__/realtimeSessions/RealtimeVerifyRouter.test.ts +131 -0
- package/src/__tests__/realtimeSessions/SlidingWindowRateLimiter.test.ts +56 -0
- package/src/__tests__/realtimeSessions/helpers.ts +4 -0
- package/src/__tests__/realtimeSessions/sessionAccess.test.ts +129 -0
- package/src/__tests__/realtimeSessions/sessionConfigStamps.test.ts +124 -0
- package/src/__tests__/realtimeSessions/verificationCore.test.ts +355 -0
- package/src/__tests__/realtimeSessions/verificationPages.test.ts +84 -0
- package/src/__tests__/realtimeSessions/verificationPolicy.test.ts +226 -0
- package/src/__tests__/realtimeSessions/verificationWorkflow.test.ts +633 -0
- package/src/__tests__/resolverBase.fls.test.ts +87 -4
- package/src/__tests__/serverExtensionReservedRoots.test.ts +2 -0
- package/src/__tests__/sharedCache.test.ts +310 -0
- package/src/__tests__/translateBracketsToPG.test.ts +20 -0
- package/src/__tests__/widgetGuestElevation.test.ts +44 -0
- package/src/agentSessions/BridgeHostInstance.ts +32 -0
- package/src/agentSessions/HostInstance.ts +124 -28
- package/src/agentSessions/ReturningVisitorRecap.ts +7 -3
- package/src/agentSessions/SessionJanitor.ts +543 -57
- package/src/agentSessions/SessionManager.ts +125 -25
- package/src/agentSessions/SessionRunIDVerifier.ts +171 -0
- package/src/agentSessions/index.ts +10 -3
- package/src/auth/actionAuthorization.ts +116 -0
- package/src/auth/index.ts +3 -8
- package/src/auth/magicLink/MagicLinkService.ts +6 -3
- package/src/auth/newUsers.ts +99 -11
- package/src/config.ts +255 -3
- package/src/context.ts +26 -34
- package/src/generated/graphql-schemas/__mj.ts +22701 -18838
- package/src/generic/OrphanedConversationDetailReconciler.ts +3 -3
- package/src/generic/ResolverBase.ts +210 -36
- package/src/generic/RunViewResolver.ts +37 -2
- package/src/index.ts +236 -170
- package/src/isolatedProvider.ts +22 -0
- package/src/postgresqlCompat.ts +21 -0
- package/src/postgresqlPoolSettings.ts +213 -0
- package/src/realtimeProxy/RealtimeProxyServer.ts +148 -16
- package/src/realtimeProxy/RealtimeRelayTunnel.ts +645 -0
- package/src/realtimeSessions/README.md +179 -0
- package/src/realtimeSessions/RealtimeSessionEventService.ts +274 -0
- package/src/realtimeSessions/RealtimeSessionVerificationService.ts +167 -0
- package/src/realtimeSessions/RealtimeVerifyRouter.ts +108 -0
- package/src/realtimeSessions/SessionVerificationPortsBase.ts +189 -0
- package/src/realtimeSessions/SlidingWindowRateLimiter.ts +92 -0
- package/src/realtimeSessions/index.ts +16 -0
- package/src/realtimeSessions/sessionAccess.ts +141 -0
- package/src/realtimeSessions/sessionConfigStamps.ts +123 -0
- package/src/realtimeSessions/subscriptionAuthorization.ts +116 -0
- package/src/realtimeSessions/verificationCore.ts +502 -0
- package/src/realtimeSessions/verificationPages.ts +149 -0
- package/src/realtimeSessions/verificationPolicy.ts +321 -0
- package/src/realtimeSessions/verificationWorkflow.ts +702 -0
- package/src/realtimeWidget/widgetGuestElevation.ts +34 -2
- package/src/resolvers/ActionResolver.ts +84 -21
- package/src/resolvers/AdhocQueryResolver.ts +105 -187
- package/src/resolvers/CacheStatsResolver.ts +89 -2
- package/src/resolvers/ClientToolRequestResolver.ts +25 -7
- package/src/resolvers/EntityRecordNameResolver.ts +9 -51
- package/src/resolvers/FetchEntityVectorsResolver.ts +97 -2
- package/src/resolvers/GetDataResolver.ts +10 -0
- package/src/resolvers/HumanHandoffResolver.ts +312 -0
- package/src/resolvers/MCPResolver.ts +8 -5
- package/src/resolvers/MeetingResolver.ts +438 -0
- package/src/resolvers/RealtimeBridgeResolver.ts +385 -19
- package/src/resolvers/RealtimeClientSessionResolver.ts +503 -344
- package/src/resolvers/RealtimeSessionEventsResolver.ts +133 -0
- package/src/resolvers/RealtimeSessionVerificationResolver.ts +174 -0
- package/src/resolvers/RemoteBrowserActionResolver.ts +11 -1
- package/src/resolvers/RunAIAgentResolver.ts +78 -11
- package/src/resolvers/RunTemplateResolver.ts +13 -12
- package/src/resolvers/SyncDataResolver.ts +6 -2
- package/src/resolvers/SyncRolesUsersResolver.ts +24 -6
- package/src/resolvers/UserAvatarResolver.ts +111 -0
- package/src/resolvers/UserResolver.ts +2 -16
- package/src/resolvers/adhocTimeout.ts +12 -0
- package/src/resolvers/agentVisionConsentAudit.ts +82 -0
- package/src/resolvers/avatarInputValidation.ts +123 -0
- package/src/resolvers/currentUserRoles.ts +32 -1
- package/src/resolvers/meetings/index.ts +4 -0
- package/src/resolvers/meetings/meetingHelpers.ts +307 -0
- package/src/resolvers/meetings/meetingMutations.ts +556 -0
- package/src/resolvers/meetings/meetingQueries.ts +172 -0
- package/src/resolvers/meetings/meetingTypes.ts +238 -0
- package/src/rest/ViewOperationsHandler.ts +8 -5
- package/src/services/TaskGraphActionRunner.ts +29 -1
- package/src/services/TaskGraphContinuationDeliverer.ts +13 -7
- package/src/services/WorkQueueHostService.ts +129 -0
- package/src/services/workQueueConfig.ts +52 -0
- package/src/sharedCache.ts +302 -0
- package/src/types.ts +12 -0
- package/dist/resolvers/adhoc-query-helpers.d.ts +0 -33
- package/dist/resolvers/adhoc-query-helpers.d.ts.map +0 -1
- package/dist/resolvers/adhoc-query-helpers.js +0 -50
- package/dist/resolvers/adhoc-query-helpers.js.map +0 -1
- package/src/__tests__/adhoc-query-helpers.test.ts +0 -50
- package/src/resolvers/adhoc-query-helpers.ts +0 -67
|
@@ -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,19 +33,33 @@ 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, 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
|
|
@@ -53,28 +67,10 @@ import { ResolveWidgetGuestRunContext, ResolveScopedAnonymousRunUser } from '../
|
|
|
53
67
|
*/
|
|
54
68
|
const SIGNIFICANT_PROGRESS_STEPS = ['prompt_execution', 'action_execution', 'subagent_execution', 'decision_processing'];
|
|
55
69
|
/**
|
|
56
|
-
* The
|
|
57
|
-
*
|
|
58
|
-
* the co-agent resolution chain (see {@link RealtimeClientSessionResolver.resolveCoAgentID}).
|
|
59
|
-
* Deployments can override it per agent (`AIAgent.DefaultCoAgentID`), per agent type (an
|
|
60
|
-
* `AIAgentCoAgent` row with `TargetAgentTypeID` + `IsDefault`), or per call (the `coAgentId`
|
|
61
|
-
* mutation argument) without
|
|
62
|
-
* touching this seed.
|
|
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.
|
|
63
72
|
*/
|
|
64
|
-
const
|
|
65
|
-
/**
|
|
66
|
-
* DEPRECATED legacy seed name of {@link REALTIME_CO_AGENT_NAME}, from before the agent's rename
|
|
67
|
-
* from "Voice Co-Agent" to "Realtime Co-Agent". Deployments that have not re-synced the agent seed
|
|
68
|
-
* still carry this name, so {@link RealtimeClientSessionResolver.resolveGlobalCoAgentID} falls
|
|
69
|
-
* back to it (with a deprecation log) when no agent named {@link REALTIME_CO_AGENT_NAME} exists.
|
|
70
|
-
*/
|
|
71
|
-
const LEGACY_REALTIME_CO_AGENT_NAME = 'Voice Co-Agent';
|
|
72
|
-
/**
|
|
73
|
-
* The seeded name of the Realtime agent TYPE. Every co-agent candidate (explicit or metadata
|
|
74
|
-
* default) must be an Active agent of this type — a co-agent drives a streaming full-duplex
|
|
75
|
-
* realtime session, which only `RealtimeAgentType`-driven agents support.
|
|
76
|
-
*/
|
|
77
|
-
const REALTIME_AGENT_TYPE_NAME = 'Realtime';
|
|
73
|
+
const MAX_USAGE_DETAILS_JSON_LENGTH = 4096;
|
|
78
74
|
/** Entity name — centralised so the `MJ:`-prefix convention is applied in exactly one place. */
|
|
79
75
|
const SESSION_ENTITY = 'MJ: AI Agent Sessions';
|
|
80
76
|
const CO_AGENT_ENTITY = 'MJ: AI Agent Co Agents';
|
|
@@ -100,7 +96,8 @@ const WHITEBOARD_ARTIFACT_TYPE_NAME = 'Whiteboard';
|
|
|
100
96
|
*/
|
|
101
97
|
const MAX_CLIENT_TOOLS = 64;
|
|
102
98
|
/** 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.
|
|
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. */
|
|
104
101
|
const MAX_CLIENT_TOOLS_JSON_CHARS = 256_000;
|
|
105
102
|
/** Maximum accepted size (chars) of a persisted channel state blob. */
|
|
106
103
|
const MAX_CHANNEL_STATE_CHARS = 2_000_000;
|
|
@@ -144,6 +141,14 @@ __decorate([
|
|
|
144
141
|
Field(() => String),
|
|
145
142
|
__metadata("design:type", String)
|
|
146
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);
|
|
147
152
|
__decorate([
|
|
148
153
|
Field(() => String),
|
|
149
154
|
__metadata("design:type", String)
|
|
@@ -172,6 +177,14 @@ __decorate([
|
|
|
172
177
|
Field(() => String, { nullable: true }),
|
|
173
178
|
__metadata("design:type", String)
|
|
174
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);
|
|
175
188
|
StartRealtimeClientSessionResult = __decorate([
|
|
176
189
|
ObjectType()
|
|
177
190
|
], StartRealtimeClientSessionResult);
|
|
@@ -269,6 +282,7 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
269
282
|
// state on — otherwise that cleanup silently no-ops on a freshly-constructed instance.
|
|
270
283
|
this.clientSessionService = new RealtimeClientSessionService();
|
|
271
284
|
this.sessionManager = new SessionManager(this.clientSessionService);
|
|
285
|
+
this.runIDVerifier = new SessionRunIDVerifier();
|
|
272
286
|
}
|
|
273
287
|
/**
|
|
274
288
|
* The reason a `Save()`/`Delete()` returned false, for a log line. `CompleteMessage` renders
|
|
@@ -302,9 +316,9 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
302
316
|
* session is created.
|
|
303
317
|
* 2. Resolve the co-agent id via the metadata-driven resolution chain (runtime `coAgentId` →
|
|
304
318
|
* agent's `DefaultCoAgentID` → type-level `AIAgentCoAgent` default row → global Realtime Co-Agent) —
|
|
305
|
-
* see {@link
|
|
319
|
+
* see {@link ResolveRealtimeCoAgentID} for the full contract.
|
|
306
320
|
* 3. Create the durable `AIAgentSession` (run by the co-agent), storing `targetAgentID` in its
|
|
307
|
-
* config
|
|
321
|
+
* config — the target later relays use, re-authorized for the caller on each relay.
|
|
308
322
|
* 4. Mint the {@link import('@memberjunction/ai').ClientRealtimeSessionConfig} via the service.
|
|
309
323
|
* On failure the just-created session is closed so no half-open session leaks.
|
|
310
324
|
*
|
|
@@ -330,12 +344,18 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
330
344
|
* `TypeConfiguration` ← this). **Authorization-gated**: requires the
|
|
331
345
|
* `Realtime: Advanced Session Controls` authorization — unauthorized callers receive a
|
|
332
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.
|
|
333
353
|
*
|
|
334
354
|
* @returns The ephemeral config + session linkage the browser needs to open its socket.
|
|
335
355
|
*/
|
|
336
|
-
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) {
|
|
337
357
|
const { contextUser, provider } = this.requireUserAndProvider(userPayload, providers);
|
|
338
|
-
const coAgentID = await
|
|
358
|
+
const coAgentID = await ResolveRealtimeCoAgentID(targetAgentId, coAgentId, contextUser, provider);
|
|
339
359
|
// PAIRING CONSTRAINTS: a co-agent with pairing rows is restricted to that target list
|
|
340
360
|
// (with the IsDefault row standing in when no runtime target was supplied); zero rows =
|
|
341
361
|
// universal, today's behavior untouched. Resolves the AUTHORITATIVE target id.
|
|
@@ -367,16 +387,24 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
367
387
|
'using the agent default media kit.');
|
|
368
388
|
}
|
|
369
389
|
}
|
|
370
|
-
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({
|
|
371
398
|
agentID: coAgentID,
|
|
372
399
|
userID: contextUser.ID,
|
|
373
400
|
conversationID: conversationId,
|
|
374
401
|
lastSessionID: lastSessionId,
|
|
375
402
|
config: JSON.stringify(config),
|
|
376
|
-
}, contextUser, provider);
|
|
403
|
+
}, contextUser, provider));
|
|
377
404
|
// Best-effort: stamp recording-start metadata when the browser captured (with consent) at start.
|
|
378
405
|
await this.stampRecordingStart(session, recordingConsent, recordingStartedAt);
|
|
379
406
|
const clientTools = this.parseClientTools(clientToolsJson);
|
|
407
|
+
const channelCandidates = this.parseChannelCandidates(channelCandidatesJson);
|
|
380
408
|
// Best-effort model-context hydration: the PRIOR session chain's transcript (ownership-
|
|
381
409
|
// checked, capped) is framed into the system prompt so the model REMEMBERS the last leg.
|
|
382
410
|
// Strictly tolerant — any problem yields no hydration, never a failed start.
|
|
@@ -385,19 +413,83 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
385
413
|
// mid-thread knows what was already typed. The legs above are excluded: they are already
|
|
386
414
|
// framed as the resumed transcript, and injecting the same turns twice puts two accounts of
|
|
387
415
|
// the same exchange in one prompt.
|
|
388
|
-
const conversationMessages = await this.loadConversationHistory(conversationId, prior?.LegIDs ?? [], contextUser, provider);
|
|
389
|
-
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);
|
|
390
419
|
// Best-effort restore of the PRIOR session's persisted channel states (e.g. the whiteboard
|
|
391
420
|
// board). Strictly tolerant — any problem yields a null field, never a failed start.
|
|
392
421
|
result.PriorChannelStatesJson = (await this.loadPriorChannelStatesJson(lastSessionId, contextUser, provider)) ?? undefined;
|
|
393
422
|
return result;
|
|
394
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
|
+
}
|
|
395
485
|
/**
|
|
396
486
|
* Execute a single tool call the browser relayed from its provider socket and return the
|
|
397
487
|
* serialized result for the browser to relay back to the model.
|
|
398
488
|
*
|
|
399
|
-
* Ownership-gated
|
|
400
|
-
*
|
|
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.
|
|
401
493
|
*
|
|
402
494
|
* @returns The serialized tool result JSON.
|
|
403
495
|
*/
|
|
@@ -405,12 +497,17 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
405
497
|
const { contextUser, provider } = this.requireUserAndProvider(userPayload, providers);
|
|
406
498
|
const session = await this.loadOwnedActiveSession(agentSessionId, contextUser, provider);
|
|
407
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
|
+
}
|
|
408
504
|
// SCOPED-ANONYMOUS ELEVATION (issue #3371): once ownership is proven above, the delegated
|
|
409
505
|
// run + its AI-run-entity writes execute as the system user for a scoped anonymous caller
|
|
410
506
|
// (the caller's role deliberately holds no grants on the run entities). The lead
|
|
411
|
-
// targetAgentID
|
|
412
|
-
//
|
|
507
|
+
// targetAgentID is CanRun-gated against the CALLER just above, and the colleague union
|
|
508
|
+
// just below, so elevation never widens agent authority.
|
|
413
509
|
const runUser = ResolveScopedAnonymousRunUser(contextUser);
|
|
510
|
+
const runIDs = await this.runIDVerifier.Verify(session.ID, { CoAgentRunID: config.coAgentRunID, PendingFeedbackRunID: config.pendingFeedbackRunID }, runUser, provider);
|
|
414
511
|
if (runUser !== contextUser) {
|
|
415
512
|
LogStatus(`ExecuteRealtimeSessionTool: dispatching relayed tool '${toolName}' for session ${agentSessionId} ` +
|
|
416
513
|
'under the system user (scoped-anonymous caller).');
|
|
@@ -422,21 +519,28 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
422
519
|
TargetAgentID: config.targetAgentID,
|
|
423
520
|
// Multi-target (Move 4): the session's persisted allowed-agent union — a model-named
|
|
424
521
|
// colleague in the call is validated against this; absent ⇒ single-target behavior.
|
|
425
|
-
AllowedAgents: await
|
|
426
|
-
DirectActions:
|
|
522
|
+
AllowedAgents: await FilterAllowedAgentsByCanRun(config.allowedAgents, contextUser),
|
|
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.
|
|
427
525
|
// Attribution follows the VISITOR even when `runUser` is elevated: the delegated run
|
|
428
526
|
// row and its context-memory scope must stay the person's, not the system user's.
|
|
429
527
|
AttributionUserID: contextUser.ID,
|
|
430
528
|
// Nest the delegated target-agent run under the co-agent observability run (when present).
|
|
431
|
-
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,
|
|
432
536
|
Call: { CallID: callId, ToolName: toolName, Arguments: argsJson },
|
|
433
537
|
OnProgress: this.buildDelegationProgressCallback(pubSub, userPayload, agentSessionId, callId),
|
|
434
538
|
// Resume a previously-paused delegated run (if any) with the user's answer.
|
|
435
|
-
ResumeRunID:
|
|
539
|
+
ResumeRunID: runIDs.PendingFeedbackRunID,
|
|
436
540
|
}, runUser, provider);
|
|
437
541
|
const durationMs = Date.now() - startTime;
|
|
438
542
|
LogStatus(`ExecuteRealtimeSessionTool: completed relayed tool '${toolName}' (callId: ${callId}) in ${durationMs}ms`);
|
|
439
|
-
if (toolName !==
|
|
543
|
+
if (toolName !== INVOKE_TARGET_AGENT_TOOL_NAME) {
|
|
440
544
|
await this.persistDirectActionTurn(session, callId, toolName, argsJson, ResultJson, durationMs, contextUser, provider);
|
|
441
545
|
}
|
|
442
546
|
// Roll the paused-run id forward in the session config: clear the one we just consumed, and
|
|
@@ -474,6 +578,18 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
474
578
|
}), userPayload);
|
|
475
579
|
};
|
|
476
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
|
+
}
|
|
477
593
|
/**
|
|
478
594
|
* Rolls the session's `pendingFeedbackRunID` forward after a relayed tool call. The id we just
|
|
479
595
|
* consumed (resumed or none) is dropped; a new paused run id is stored only when the run paused
|
|
@@ -485,17 +601,10 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
485
601
|
if (!previous && !pausedRunID) {
|
|
486
602
|
return;
|
|
487
603
|
}
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
coAgentRunStepID: config.coAgentRunStepID,
|
|
493
|
-
pendingFeedbackRunID: pausedRunID,
|
|
494
|
-
// Preserve the server-authoritative voice deadline across config rewrites.
|
|
495
|
-
maxSessionDeadlineIso: config.maxSessionDeadlineIso,
|
|
496
|
-
applicationID: config.applicationID,
|
|
497
|
-
allowedAgents: config.allowedAgents,
|
|
498
|
-
};
|
|
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 };
|
|
499
608
|
session.Config_ = JSON.stringify(next);
|
|
500
609
|
if (!(await session.Save())) {
|
|
501
610
|
LogError(`RealtimeClientSessionResolver.updatePendingFeedbackRunID save failed for session ${session.ID}: ${this.describeSaveFailure(session)}`);
|
|
@@ -527,9 +636,10 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
527
636
|
// conversation (run-viewer observability parity). Best-effort — never fails the transcript relay.
|
|
528
637
|
// The prompt-run write runs as the scoped-anonymous elevated user (issue #3371) — the visible
|
|
529
638
|
// Conversation Detail above deliberately stays on the caller.
|
|
530
|
-
const
|
|
639
|
+
const runUser = ResolveScopedAnonymousRunUser(contextUser);
|
|
640
|
+
const promptRunID = await this.readPromptRunID(session, runUser, provider);
|
|
531
641
|
if (promptRunID) {
|
|
532
|
-
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);
|
|
533
643
|
}
|
|
534
644
|
await this.sessionManager.Heartbeat(agentSessionId, contextUser, provider);
|
|
535
645
|
return true;
|
|
@@ -573,11 +683,11 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
573
683
|
if (consent !== true) {
|
|
574
684
|
return { Success: false, ErrorMessage: 'Recording consent was not granted.' };
|
|
575
685
|
}
|
|
576
|
-
//
|
|
577
|
-
//
|
|
578
|
-
//
|
|
579
|
-
// session link, so nothing here depends on the caller's identity.
|
|
580
|
-
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);
|
|
581
691
|
try {
|
|
582
692
|
const agent = await provider.GetEntityObject('MJ: AI Agents', runUser);
|
|
583
693
|
if (!(await agent.Load(session.AgentID))) {
|
|
@@ -623,11 +733,14 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
623
733
|
}
|
|
624
734
|
/**
|
|
625
735
|
* Uploads ONE crash-recovery audio shard (~15s) for an IN-PROGRESS recording into the session's
|
|
626
|
-
* folder (`realtime-recordings/<sessionId>/seg-NNNN
|
|
627
|
-
*
|
|
628
|
-
*
|
|
629
|
-
*
|
|
630
|
-
*
|
|
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.
|
|
631
744
|
*
|
|
632
745
|
* @param agentSessionId The in-progress session (ownership-gated).
|
|
633
746
|
* @param segmentIndex 0-based shard index within the session.
|
|
@@ -639,6 +752,12 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
639
752
|
try {
|
|
640
753
|
const { contextUser, provider } = this.requireUserAndProvider(ctx.userPayload, ctx.providers);
|
|
641
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
|
+
}
|
|
642
761
|
// Scoped-anonymous elevation (issue #3371) — same rationale as UploadRealtimeRecording.
|
|
643
762
|
const runUser = ResolveScopedAnonymousRunUser(contextUser);
|
|
644
763
|
const agent = await provider.GetEntityObject('MJ: AI Agents', runUser);
|
|
@@ -682,11 +801,12 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
682
801
|
// Persist the tool turn facts into a hidden ConversationDetail turn so session review
|
|
683
802
|
// can reconstruct the tool card dynamically on load (Option 1). Best-effort.
|
|
684
803
|
await this.persistDirectActionTurn(session, undefined, toolName, argsJson, resultJson, undefined, contextUser, provider);
|
|
685
|
-
const
|
|
804
|
+
const runUser = ResolveScopedAnonymousRunUser(contextUser);
|
|
805
|
+
const promptRunID = await this.readPromptRunID(session, runUser, provider);
|
|
686
806
|
if (!promptRunID) {
|
|
687
807
|
return false;
|
|
688
808
|
}
|
|
689
|
-
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);
|
|
690
810
|
}
|
|
691
811
|
/**
|
|
692
812
|
* Formats a co-agent tool call into a compact, human-readable run line: `🔧 <tool> <args> → <result>`,
|
|
@@ -749,31 +869,56 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
749
869
|
* (observability creation was skipped), a failed load, or a failed save all log and return
|
|
750
870
|
* `false` — usage relay must never break a live call.
|
|
751
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
|
+
*
|
|
752
878
|
* @param inputTokens Input-token DELTA to add (negative/non-finite values are clamped to 0).
|
|
753
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 }`).
|
|
754
881
|
* @returns `true` when the accumulated usage was persisted; `false` on any tolerated failure.
|
|
755
882
|
*/
|
|
756
|
-
async RelayRealtimeUsage(agentSessionId, inputTokens, outputTokens, { userPayload, providers }) {
|
|
883
|
+
async RelayRealtimeUsage(agentSessionId, inputTokens, outputTokens, { userPayload, providers }, usageDetailsJson) {
|
|
757
884
|
const { contextUser, provider } = this.requireUserAndProvider(userPayload, providers);
|
|
758
885
|
const session = await this.loadOwnedSession(agentSessionId, contextUser, provider);
|
|
759
886
|
const inputDelta = this.clampTokenDelta(inputTokens);
|
|
760
887
|
const outputDelta = this.clampTokenDelta(outputTokens);
|
|
761
|
-
|
|
888
|
+
const details = this.readUsageDetails(usageDetailsJson);
|
|
889
|
+
if (inputDelta === 0 && outputDelta === 0 && !details) {
|
|
762
890
|
return true; // nothing to add — a no-op flush is a success, not a failure
|
|
763
891
|
}
|
|
764
|
-
|
|
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);
|
|
765
895
|
if (!promptRunID) {
|
|
766
896
|
return false; // no observability prompt run for this session — logged in the helper
|
|
767
897
|
}
|
|
768
898
|
// Delegate to the service so usage writes share the per-run serialization with transcript-message
|
|
769
899
|
// appends — otherwise the frequent usage save clobbers freshly-appended Messages (and vice-versa).
|
|
770
|
-
|
|
771
|
-
return this.clientSessionService.AccumulatePromptRunUsage(promptRunID, inputDelta, outputDelta, ResolveScopedAnonymousRunUser(contextUser), provider);
|
|
900
|
+
return this.clientSessionService.AccumulatePromptRunUsage(promptRunID, inputDelta, outputDelta, runUser, provider, details ?? undefined);
|
|
772
901
|
}
|
|
773
902
|
/** Clamps a relayed token delta: negative / non-finite values become 0. */
|
|
774
903
|
clampTokenDelta(value) {
|
|
775
904
|
return Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
|
|
776
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
|
+
}
|
|
777
922
|
/**
|
|
778
923
|
* Sanitizes client-supplied waveform peaks before they're persisted: drops anything that isn't a
|
|
779
924
|
* finite number, clamps each to `[0, 1]`, and caps the array length so a hostile client can't bloat
|
|
@@ -792,23 +937,29 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
792
937
|
}
|
|
793
938
|
/**
|
|
794
939
|
* Reads the co-agent `AIPromptRun` id from the session config WITHOUT the relay path's
|
|
795
|
-
* throw-on-missing-target semantics
|
|
796
|
-
*
|
|
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.
|
|
797
945
|
*/
|
|
798
|
-
readPromptRunID(session) {
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
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) {
|
|
804
953
|
LogStatus(`RelayRealtimeUsage: session ${session.ID} has no co-agent promptRunID (observability run ` +
|
|
805
954
|
'creation was skipped or failed) — usage delta dropped.');
|
|
806
955
|
return null;
|
|
807
956
|
}
|
|
808
|
-
|
|
809
|
-
|
|
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.`);
|
|
810
960
|
return null;
|
|
811
961
|
}
|
|
962
|
+
return verified.PromptRunID;
|
|
812
963
|
}
|
|
813
964
|
/**
|
|
814
965
|
* Persist an interactive channel's state of record (e.g. the live whiteboard's serialized
|
|
@@ -834,6 +985,10 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
834
985
|
async SaveSessionChannelState(agentSessionId, channelName, stateJson, { userPayload, providers }) {
|
|
835
986
|
const { contextUser, provider } = this.requireUserAndProvider(userPayload, providers);
|
|
836
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
|
+
}
|
|
837
992
|
if (stateJson.length > MAX_CHANNEL_STATE_CHARS) {
|
|
838
993
|
LogError(`SaveSessionChannelState: rejected oversized state for session ${agentSessionId} / channel '${channelName}' ` +
|
|
839
994
|
`(${stateJson.length} chars > ${MAX_CHANNEL_STATE_CHARS}).`);
|
|
@@ -896,6 +1051,11 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
896
1051
|
async SaveSessionChannelArtifact(agentSessionId, channelName, name, contentJson, { userPayload, providers }) {
|
|
897
1052
|
const { contextUser, provider } = this.requireUserAndProvider(userPayload, providers);
|
|
898
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
|
+
}
|
|
899
1059
|
if (contentJson.length > MAX_CHANNEL_STATE_CHARS) {
|
|
900
1060
|
const message = `SaveSessionChannelArtifact: rejected oversized content for session ${agentSessionId} / channel '${channelName}' ` +
|
|
901
1061
|
`(${contentJson.length} chars > ${MAX_CHANNEL_STATE_CHARS}).`;
|
|
@@ -923,8 +1083,10 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
923
1083
|
return { contextUser, provider: GetReadWriteProvider(providers) };
|
|
924
1084
|
}
|
|
925
1085
|
/**
|
|
926
|
-
* Authorization gate for
|
|
927
|
-
*
|
|
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.
|
|
928
1090
|
*/
|
|
929
1091
|
async assertCanRunTarget(targetAgentId, contextUser, _provider) {
|
|
930
1092
|
const canRun = await AIAgentPermissionHelper.HasPermission(targetAgentId, contextUser, 'run');
|
|
@@ -932,33 +1094,6 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
932
1094
|
throw new Error(`Not authorized: you are not permitted to run the target agent ${targetAgentId}`);
|
|
933
1095
|
}
|
|
934
1096
|
}
|
|
935
|
-
/**
|
|
936
|
-
* Narrows a session's colleague union (`allowedAgents`) to the agents the CALLER may run.
|
|
937
|
-
*
|
|
938
|
-
* {@link assertCanRunTarget} gates the LEAD target at session start, but the union it travels
|
|
939
|
-
* with was never gated at all — a model-named colleague resolves straight to a delegated run.
|
|
940
|
-
* That was survivable while the run carried the caller's own identity, because base-agent
|
|
941
|
-
* re-checks `CanRun` against `contextUser`. Once the run user is elevated for a scoped anonymous
|
|
942
|
-
* caller (issue #3371) that check sees the SYSTEM user, so this is the only remaining place the
|
|
943
|
-
* caller's own authority is applied to a colleague. It therefore runs for EVERY caller, elevated
|
|
944
|
-
* or not — the authorization identity must never depend on the elevation decision.
|
|
945
|
-
*
|
|
946
|
-
* `HasPermission` reads AIEngineBase's in-memory caches (no DB round trip) and already fails
|
|
947
|
-
* closed on error, so an unresolvable agent drops OUT of the union rather than becoming runnable.
|
|
948
|
-
* A filtered-out colleague is not an error: the delegation layer reports it as "not available in
|
|
949
|
-
* this session" and lists what remains, which is the same answer the model gets for a typo.
|
|
950
|
-
*
|
|
951
|
-
* @param allowedAgents The session's persisted colleague union (absent/empty ⇒ single-target).
|
|
952
|
-
* @param contextUser The ORIGINAL caller — never the elevated run user.
|
|
953
|
-
* @returns The subset the caller may run, preserving order.
|
|
954
|
-
*/
|
|
955
|
-
async filterAllowedAgentsByCanRun(allowedAgents, contextUser) {
|
|
956
|
-
if (!allowedAgents || allowedAgents.length === 0) {
|
|
957
|
-
return allowedAgents;
|
|
958
|
-
}
|
|
959
|
-
const verdicts = await Promise.all(allowedAgents.map((a) => AIAgentPermissionHelper.HasPermission(a.agentId, contextUser, 'run')));
|
|
960
|
-
return allowedAgents.filter((_, i) => verdicts[i]);
|
|
961
|
-
}
|
|
962
1097
|
/**
|
|
963
1098
|
* Resolves the AUTHORITATIVE target agent id under the co-agent's PAIRING CONSTRAINTS
|
|
964
1099
|
* (`MJ: AI Agent Co Agents`, ordered by `Sequence`):
|
|
@@ -1162,158 +1297,6 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
1162
1297
|
return false;
|
|
1163
1298
|
}
|
|
1164
1299
|
}
|
|
1165
|
-
/**
|
|
1166
|
-
* Resolves the co-agent (the Realtime-type agent that voices the target agent) for a new
|
|
1167
|
-
* client-direct session via the metadata-driven **CO-AGENT RESOLUTION CHAIN** — first match
|
|
1168
|
-
* wins, evaluated in precedence order:
|
|
1169
|
-
*
|
|
1170
|
-
* 1. **Runtime parameter** — the mutation's explicit `coAgentId`. A per-call override; an
|
|
1171
|
-
* invalid candidate (unknown, not Active, or not of the Realtime agent type) **throws**
|
|
1172
|
-
* (fail loud — the caller asked for something specific, mirroring `preferredModelId`).
|
|
1173
|
-
* 2. **Per-agent persona** — the target agent's `AIAgent.DefaultCoAgentID`. An invalid
|
|
1174
|
-
* reference logs a warning and **falls through** to the next step (stale metadata should
|
|
1175
|
-
* degrade gracefully, never break calls).
|
|
1176
|
-
* 3. **Per-type default** — an Active `AIAgentCoAgent` row of Type `'CoAgent'` whose
|
|
1177
|
-
* `TargetAgentTypeID` is the target agent's type and `IsDefault = 1` (lowest `Sequence`
|
|
1178
|
-
* wins a tie). Same tolerant warn-and-fall-through semantics as step 2.
|
|
1179
|
-
* 4. **Global default** — the seeded {@link REALTIME_CO_AGENT_NAME} agent, looked up by name
|
|
1180
|
-
* (with a deprecated fallback to {@link LEGACY_REALTIME_CO_AGENT_NAME}). Throws when absent
|
|
1181
|
-
* entirely (the realtime feature is unconfigured in this deployment).
|
|
1182
|
-
*
|
|
1183
|
-
* Every candidate from steps 1–3 is validated by {@link findValidCoAgent}: it must exist in
|
|
1184
|
-
* {@link AIEngine}'s cached agents, have Status `Active`, and be of the
|
|
1185
|
-
* {@link REALTIME_AGENT_TYPE_NAME} agent type. Step 4 keeps its original name-lookup contract.
|
|
1186
|
-
*
|
|
1187
|
-
* @param targetAgentId The agent the co-agent will voice on behalf of (drives steps 2–3).
|
|
1188
|
-
* @param explicitCoAgentId The runtime `coAgentId` mutation argument, when supplied (step 1).
|
|
1189
|
-
* @returns The resolved co-agent's id (canonical casing from the metadata cache).
|
|
1190
|
-
*/
|
|
1191
|
-
async resolveCoAgentID(targetAgentId, explicitCoAgentId, contextUser, provider) {
|
|
1192
|
-
await AIEngine.Instance.Config(false, contextUser, provider);
|
|
1193
|
-
// Step 1 — explicit runtime parameter: fail LOUD on any problem.
|
|
1194
|
-
if (explicitCoAgentId) {
|
|
1195
|
-
const { agent, problem } = this.findValidCoAgent(explicitCoAgentId);
|
|
1196
|
-
if (!agent) {
|
|
1197
|
-
throw new Error(`Invalid coAgentId '${explicitCoAgentId}': ${problem}`);
|
|
1198
|
-
}
|
|
1199
|
-
return agent.ID;
|
|
1200
|
-
}
|
|
1201
|
-
const targetAgent = targetAgentId
|
|
1202
|
-
? (AIEngine.Instance.Agents ?? []).find(a => UUIDsEqual(a.ID, targetAgentId))
|
|
1203
|
-
: undefined;
|
|
1204
|
-
// Step 2 — the target agent's own DefaultCoAgentID (per-agent persona): warn + fall through.
|
|
1205
|
-
const fromAgent = this.resolveMetadataDefault(targetAgent?.DefaultCoAgentID, `agent '${targetAgent?.Name}' (DefaultCoAgentID)`);
|
|
1206
|
-
if (fromAgent) {
|
|
1207
|
-
return fromAgent;
|
|
1208
|
-
}
|
|
1209
|
-
// Step 3 — the type-level AIAgentCoAgent default row (per-type default): warn + fall through.
|
|
1210
|
-
const agentType = targetAgent?.TypeID
|
|
1211
|
-
? (AIEngine.Instance.AgentTypes ?? []).find(t => UUIDsEqual(t.ID, targetAgent.TypeID))
|
|
1212
|
-
: undefined;
|
|
1213
|
-
const typeDefaultCoAgentID = agentType
|
|
1214
|
-
? this.findTypeDefaultCoAgentID(agentType.ID, contextUser, provider)
|
|
1215
|
-
: undefined;
|
|
1216
|
-
const fromType = this.resolveMetadataDefault(await typeDefaultCoAgentID, `agent type '${agentType?.Name}' (AIAgentCoAgent type-default row)`);
|
|
1217
|
-
if (fromType) {
|
|
1218
|
-
return fromType;
|
|
1219
|
-
}
|
|
1220
|
-
// Step 4 — global default: the seeded Realtime Co-Agent, by name (original behavior).
|
|
1221
|
-
return this.resolveGlobalCoAgentID();
|
|
1222
|
-
}
|
|
1223
|
-
/**
|
|
1224
|
-
* Chain step 3 lookup: the TYPE-LEVEL default co-agent for an agent type — the Active
|
|
1225
|
-
* `AIAgentCoAgent` row of Type `'CoAgent'` whose `TargetAgentTypeID` matches, with
|
|
1226
|
-
* `IsDefault = 1` preferred and the lowest `Sequence` breaking ties (so a deployment can
|
|
1227
|
-
* stage multiple type-level candidates deterministically). Reads {@link AIEngineBase}'s
|
|
1228
|
-
* cached rows — no RunView. Tolerant: a failed cache read logs and returns `undefined`
|
|
1229
|
-
* (the chain falls through to the global default).
|
|
1230
|
-
*/
|
|
1231
|
-
async findTypeDefaultCoAgentID(agentTypeID, contextUser, provider) {
|
|
1232
|
-
try {
|
|
1233
|
-
const engine = await this.configuredAIEngineBase(contextUser, provider);
|
|
1234
|
-
const candidates = (engine.AgentCoAgents ?? [])
|
|
1235
|
-
.filter((r) => r.Type === 'CoAgent' &&
|
|
1236
|
-
r.Status === 'Active' &&
|
|
1237
|
-
r.TargetAgentTypeID != null &&
|
|
1238
|
-
UUIDsEqual(r.TargetAgentTypeID, agentTypeID))
|
|
1239
|
-
.sort((a, b) => (a.IsDefault === b.IsDefault ? 0 : a.IsDefault ? -1 : 1) ||
|
|
1240
|
-
(a.Sequence ?? 0) - (b.Sequence ?? 0));
|
|
1241
|
-
return candidates[0]?.CoAgentID;
|
|
1242
|
-
}
|
|
1243
|
-
catch (error) {
|
|
1244
|
-
LogError(`StartRealtimeClientSession: ${CO_AGENT_ENTITY} cache read failed while resolving the type-level ` +
|
|
1245
|
-
`default co-agent for agent type ${agentTypeID} (${error.message}) — falling through.`);
|
|
1246
|
-
return undefined;
|
|
1247
|
-
}
|
|
1248
|
-
}
|
|
1249
|
-
/**
|
|
1250
|
-
* Validates one metadata-level co-agent default (chain steps 2/3). Returns the resolved
|
|
1251
|
-
* co-agent id when the reference is valid; logs a warning and returns `null` (caller falls
|
|
1252
|
-
* through to the next chain step) when the reference is set but invalid — metadata drift must
|
|
1253
|
-
* degrade, not break live calls. A `null`/absent reference returns `null` silently.
|
|
1254
|
-
*/
|
|
1255
|
-
resolveMetadataDefault(candidateId, source) {
|
|
1256
|
-
if (!candidateId) {
|
|
1257
|
-
return null;
|
|
1258
|
-
}
|
|
1259
|
-
const { agent, problem } = this.findValidCoAgent(candidateId);
|
|
1260
|
-
if (agent) {
|
|
1261
|
-
return agent.ID;
|
|
1262
|
-
}
|
|
1263
|
-
LogError(`StartRealtimeClientSession: ignoring co-agent default '${candidateId}' from ${source} — ${problem} ` +
|
|
1264
|
-
'Falling through to the next step of the co-agent resolution chain.');
|
|
1265
|
-
return null;
|
|
1266
|
-
}
|
|
1267
|
-
/**
|
|
1268
|
-
* Validates a co-agent candidate against {@link AIEngine}'s cached metadata. A valid co-agent
|
|
1269
|
-
* must (a) exist, (b) have Status `Active`, and (c) be of the {@link REALTIME_AGENT_TYPE_NAME}
|
|
1270
|
-
* agent type. Returns the cached agent on success, or a human-readable `problem` on failure —
|
|
1271
|
-
* the CALLER decides whether that's fatal (explicit runtime param) or tolerated (metadata default).
|
|
1272
|
-
*/
|
|
1273
|
-
findValidCoAgent(candidateId) {
|
|
1274
|
-
const agent = (AIEngine.Instance.Agents ?? []).find(a => UUIDsEqual(a.ID, candidateId));
|
|
1275
|
-
if (!agent) {
|
|
1276
|
-
return { problem: 'no agent with that ID exists.' };
|
|
1277
|
-
}
|
|
1278
|
-
if (agent.Status !== 'Active') {
|
|
1279
|
-
return { problem: `agent '${agent.Name}' is not Active (Status: ${agent.Status}).` };
|
|
1280
|
-
}
|
|
1281
|
-
const realtimeType = (AIEngine.Instance.AgentTypes ?? []).find(t => t.Name?.trim().toLowerCase() === REALTIME_AGENT_TYPE_NAME.toLowerCase());
|
|
1282
|
-
if (!realtimeType) {
|
|
1283
|
-
return { problem: `the '${REALTIME_AGENT_TYPE_NAME}' agent type is not configured in this deployment.` };
|
|
1284
|
-
}
|
|
1285
|
-
if (!agent.TypeID || !UUIDsEqual(agent.TypeID, realtimeType.ID)) {
|
|
1286
|
-
return { problem: `agent '${agent.Name}' is not of the '${REALTIME_AGENT_TYPE_NAME}' agent type.` };
|
|
1287
|
-
}
|
|
1288
|
-
return { agent };
|
|
1289
|
-
}
|
|
1290
|
-
/**
|
|
1291
|
-
* Resolves the seeded Realtime Co-Agent's id from {@link AIEngine}'s cached agents — the GLOBAL
|
|
1292
|
-
* DEFAULT (step 4) of the co-agent resolution chain. The engine is already configured by
|
|
1293
|
-
* {@link resolveCoAgentID}. Looks up the current {@link REALTIME_CO_AGENT_NAME} first, then
|
|
1294
|
-
* falls back to the DEPRECATED {@link LEGACY_REALTIME_CO_AGENT_NAME} (pre-rename seed) with a
|
|
1295
|
-
* deprecation log so un-resynced deployments keep working. Throws a clear error when neither
|
|
1296
|
-
* name is present in metadata.
|
|
1297
|
-
*/
|
|
1298
|
-
resolveGlobalCoAgentID() {
|
|
1299
|
-
const coAgent = this.findAgentByName(REALTIME_CO_AGENT_NAME);
|
|
1300
|
-
if (coAgent) {
|
|
1301
|
-
return coAgent.ID;
|
|
1302
|
-
}
|
|
1303
|
-
const legacy = this.findAgentByName(LEGACY_REALTIME_CO_AGENT_NAME);
|
|
1304
|
-
if (legacy) {
|
|
1305
|
-
LogStatus(`StartRealtimeClientSession: resolved the global co-agent via its DEPRECATED legacy name ` +
|
|
1306
|
-
`'${LEGACY_REALTIME_CO_AGENT_NAME}'. Re-sync the agent seed metadata to rename it to ` +
|
|
1307
|
-
`'${REALTIME_CO_AGENT_NAME}'.`);
|
|
1308
|
-
return legacy.ID;
|
|
1309
|
-
}
|
|
1310
|
-
throw new Error(`The '${REALTIME_CO_AGENT_NAME}' agent is not configured; cannot start a realtime voice session.`);
|
|
1311
|
-
}
|
|
1312
|
-
/** Case/whitespace-insensitive agent lookup by Name in {@link AIEngine}'s cached agents. */
|
|
1313
|
-
findAgentByName(name) {
|
|
1314
|
-
const wanted = name.toLowerCase();
|
|
1315
|
-
return (AIEngine.Instance.Agents ?? []).find(a => a.Name?.trim().toLowerCase() === wanted);
|
|
1316
|
-
}
|
|
1317
1300
|
/**
|
|
1318
1301
|
* Mints the client session config for a just-created session. On failure, closes the session so
|
|
1319
1302
|
* no half-open record leaks, then throws the service's error message.
|
|
@@ -1325,8 +1308,9 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
1325
1308
|
* resumed session remembers the previous leg(s).
|
|
1326
1309
|
* @param conversationMessages Optional capped history of the TEXT conversation this session is
|
|
1327
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`).
|
|
1328
1312
|
*/
|
|
1329
|
-
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) {
|
|
1330
1314
|
const prep = await this.clientSessionService.PrepareClientSession({
|
|
1331
1315
|
CoAgentID: coAgentID,
|
|
1332
1316
|
TargetAgentID: targetAgentId,
|
|
@@ -1352,6 +1336,14 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
1352
1336
|
// (allowed-agent union); the snapshot is injected into the system prompt at mint.
|
|
1353
1337
|
ApplicationID: applicationId,
|
|
1354
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,
|
|
1355
1347
|
},
|
|
1356
1348
|
// SCOPED-ANONYMOUS ELEVATION (issue #3371): the prepare creates the co-agent
|
|
1357
1349
|
// observability AIAgentRun/AIPromptRun/run-step, which a scoped anonymous caller's role
|
|
@@ -1364,7 +1356,8 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
1364
1356
|
await this.sessionManager.CloseSession(session.ID, contextUser, provider, 'Error');
|
|
1365
1357
|
throw new Error(prep.ErrorMessage ?? 'Failed to prepare the client realtime session.');
|
|
1366
1358
|
}
|
|
1367
|
-
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);
|
|
1368
1361
|
const cfg = prep.ClientConfig;
|
|
1369
1362
|
return {
|
|
1370
1363
|
AgentSessionId: session.ID,
|
|
@@ -1373,40 +1366,91 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
1373
1366
|
Model: cfg.Model,
|
|
1374
1367
|
EphemeralToken: cfg.EphemeralToken,
|
|
1375
1368
|
ExpiresAt: cfg.ExpiresAt,
|
|
1369
|
+
Transport: cfg.Transport,
|
|
1370
|
+
RelayUrl: cfg.RelayUrl,
|
|
1376
1371
|
SessionConfigJson: JSON.stringify(cfg.SessionConfig),
|
|
1377
1372
|
ModelName: prep.ModelName,
|
|
1378
1373
|
DriverClass: prep.DriverClass,
|
|
1379
1374
|
NarrationInstructionsTemplate: prep.NarrationInstructionsTemplate,
|
|
1380
1375
|
NarrationPaceMs: prep.NarrationPaceMs,
|
|
1381
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,
|
|
1382
1379
|
};
|
|
1383
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
|
+
}
|
|
1384
1396
|
/**
|
|
1385
1397
|
* Writes the co-agent observability run ids into the session's `Config_` alongside the
|
|
1386
|
-
*
|
|
1387
|
-
* finalize the runs (and on relay to nest delegated runs).
|
|
1388
|
-
*
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
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.
|
|
1394
1409
|
const config = {
|
|
1410
|
+
...ParseConfigObject(session.Config_),
|
|
1395
1411
|
targetAgentID,
|
|
1396
1412
|
coAgentRunID,
|
|
1397
1413
|
promptRunID,
|
|
1398
1414
|
coAgentRunStepID,
|
|
1399
|
-
maxSessionDeadlineIso: existing?.maxSessionDeadlineIso,
|
|
1400
1415
|
applicationID,
|
|
1416
|
+
appContext: this.appContextToStore(appContext, session.ID),
|
|
1401
1417
|
allowedAgents: allowedAgents && allowedAgents.length > 0 ? allowedAgents : undefined,
|
|
1402
1418
|
directActions,
|
|
1419
|
+
channels,
|
|
1403
1420
|
};
|
|
1404
1421
|
session.Config_ = JSON.stringify(config);
|
|
1405
|
-
|
|
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());
|
|
1406
1426
|
if (!saved) {
|
|
1407
1427
|
LogError(`RealtimeClientSessionResolver.persistObservabilityRunIDs save failed for session ${session.ID}: ${this.describeSaveFailure(session)}`);
|
|
1408
1428
|
}
|
|
1409
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
|
+
}
|
|
1410
1454
|
/**
|
|
1411
1455
|
* Loads a session, enforcing inbound ownership (`UserID === contextUser.ID`) and that it is not
|
|
1412
1456
|
* `Closed`. Throws on any violation. Returns the loaded session for the caller to read from.
|
|
@@ -1601,6 +1645,10 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
1601
1645
|
* as the resumed transcript are removed, so the prompt never carries two accounts of one
|
|
1602
1646
|
* exchange.
|
|
1603
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
|
+
*
|
|
1604
1652
|
* Strictly tolerant — no conversation, a failed read, or any throw yields `[]`, never a failed
|
|
1605
1653
|
* session start. The call still works; it just starts cold.
|
|
1606
1654
|
*
|
|
@@ -1608,19 +1656,28 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
1608
1656
|
* @param coveredSessionIDs Session ids already framed via `PriorTranscript`.
|
|
1609
1657
|
* @param contextUser The calling user (RunView scope — the read is row-level-secured).
|
|
1610
1658
|
* @param provider The request-scoped metadata provider.
|
|
1659
|
+
* @param branchId The fork the session started on; null is Main.
|
|
1611
1660
|
* @returns The capped history, oldest first.
|
|
1612
1661
|
*/
|
|
1613
|
-
async loadConversationHistory(conversationId, coveredSessionIDs, contextUser, provider) {
|
|
1662
|
+
async loadConversationHistory(conversationId, coveredSessionIDs, contextUser, provider, branchId) {
|
|
1614
1663
|
if (!conversationId) {
|
|
1615
1664
|
return [];
|
|
1616
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
|
+
}
|
|
1617
1675
|
try {
|
|
1618
|
-
const rv = RunView.FromMetadataProvider(provider);
|
|
1619
1676
|
const result = await rv.RunView({
|
|
1620
1677
|
EntityName: CONVERSATION_DETAIL_ENTITY,
|
|
1621
|
-
ExtraFilter:
|
|
1678
|
+
ExtraFilter: ConversationEngine.LiveRowsFilter(pathFilter),
|
|
1622
1679
|
Fields: ['ID', 'Role', 'Message', 'HiddenToUser', 'AgentSessionID', '__mj_CreatedAt'],
|
|
1623
|
-
OrderBy: '
|
|
1680
|
+
OrderBy: 'Sequence ASC',
|
|
1624
1681
|
ResultType: 'simple',
|
|
1625
1682
|
}, contextUser);
|
|
1626
1683
|
if (!result.Success) {
|
|
@@ -1909,6 +1966,7 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
1909
1966
|
const detail = await provider.GetEntityObject(CONVERSATION_DETAIL_ENTITY, callerUser);
|
|
1910
1967
|
detail.NewRecord();
|
|
1911
1968
|
detail.ConversationID = session.ConversationID;
|
|
1969
|
+
detail.BranchID = this.sessionBranchIdOf(session);
|
|
1912
1970
|
detail.Role = 'AI';
|
|
1913
1971
|
detail.HiddenToUser = true;
|
|
1914
1972
|
detail.Message = 'Artifacts produced during a realtime session (system anchor).';
|
|
@@ -2037,18 +2095,50 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
2037
2095
|
}
|
|
2038
2096
|
/** Shape check for one client tool declaration: Name/Description non-empty strings, ParametersSchema a plain object. */
|
|
2039
2097
|
isValidClientTool(candidate) {
|
|
2040
|
-
|
|
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}'.`);
|
|
2041
2127
|
return false;
|
|
2042
2128
|
}
|
|
2043
|
-
const
|
|
2044
|
-
return (typeof
|
|
2045
|
-
|
|
2046
|
-
|
|
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;
|
|
2047
2136
|
}
|
|
2048
2137
|
/**
|
|
2049
|
-
* Parses the session's persisted config, returning
|
|
2050
|
-
*
|
|
2051
|
-
*
|
|
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.
|
|
2052
2142
|
*/
|
|
2053
2143
|
readSessionConfig(session) {
|
|
2054
2144
|
const raw = session.Config_;
|
|
@@ -2064,10 +2154,16 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
2064
2154
|
pendingFeedbackRunID: typeof parsed.pendingFeedbackRunID === 'string' ? parsed.pendingFeedbackRunID : undefined,
|
|
2065
2155
|
maxSessionDeadlineIso: typeof parsed.maxSessionDeadlineIso === 'string' ? parsed.maxSessionDeadlineIso : undefined,
|
|
2066
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,
|
|
2067
2160
|
allowedAgents: Array.isArray(parsed.allowedAgents) ? parsed.allowedAgents : undefined,
|
|
2068
2161
|
directActions: parsed.directActions && typeof parsed.directActions === 'object' && typeof parsed.directActions.enabled === 'boolean'
|
|
2069
2162
|
? parsed.directActions
|
|
2070
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,
|
|
2071
2167
|
};
|
|
2072
2168
|
}
|
|
2073
2169
|
}
|
|
@@ -2133,6 +2229,7 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
2133
2229
|
const detail = await provider.GetEntityObject(CONVERSATION_DETAIL_ENTITY, contextUser);
|
|
2134
2230
|
detail.NewRecord();
|
|
2135
2231
|
detail.ConversationID = session.ConversationID;
|
|
2232
|
+
detail.BranchID = this.sessionBranchIdOf(session);
|
|
2136
2233
|
detail.Role = this.mapTranscriptRole(role);
|
|
2137
2234
|
detail.Message = text;
|
|
2138
2235
|
detail.AgentSessionID = session.ID;
|
|
@@ -2270,6 +2367,7 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
|
|
|
2270
2367
|
const detail = await provider.GetEntityObject(CONVERSATION_DETAIL_ENTITY, contextUser);
|
|
2271
2368
|
detail.NewRecord();
|
|
2272
2369
|
detail.ConversationID = session.ConversationID;
|
|
2370
|
+
detail.BranchID = this.sessionBranchIdOf(session);
|
|
2273
2371
|
detail.Role = 'AI';
|
|
2274
2372
|
detail.HiddenToUser = true;
|
|
2275
2373
|
detail.AgentSessionID = session.ID;
|
|
@@ -2331,8 +2429,11 @@ __decorate([
|
|
|
2331
2429
|
__param(10, Arg('mediaCollectionId', () => String, { nullable: true })),
|
|
2332
2430
|
__param(11, Arg('applicationId', () => String, { nullable: true })),
|
|
2333
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 })),
|
|
2334
2435
|
__metadata("design:type", Function),
|
|
2335
|
-
__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]),
|
|
2336
2437
|
__metadata("design:returntype", Promise)
|
|
2337
2438
|
], RealtimeClientSessionResolver.prototype, "StartRealtimeClientSession", null);
|
|
2338
2439
|
__decorate([
|
|
@@ -2410,8 +2511,9 @@ __decorate([
|
|
|
2410
2511
|
__param(1, Arg('inputTokens', () => Int)),
|
|
2411
2512
|
__param(2, Arg('outputTokens', () => Int)),
|
|
2412
2513
|
__param(3, Ctx()),
|
|
2514
|
+
__param(4, Arg('usageDetailsJson', () => String, { nullable: true })),
|
|
2413
2515
|
__metadata("design:type", Function),
|
|
2414
|
-
__metadata("design:paramtypes", [String, Number, Number, Object]),
|
|
2516
|
+
__metadata("design:paramtypes", [String, Number, Number, Object, String]),
|
|
2415
2517
|
__metadata("design:returntype", Promise)
|
|
2416
2518
|
], RealtimeClientSessionResolver.prototype, "RelayRealtimeUsage", null);
|
|
2417
2519
|
__decorate([
|