@memberjunction/server 6.2.0-edge.3 → 6.2.0-edge.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/agentSessions/BridgeHostInstance.d.ts +25 -0
- package/dist/agentSessions/BridgeHostInstance.d.ts.map +1 -0
- package/dist/agentSessions/BridgeHostInstance.js +30 -0
- package/dist/agentSessions/BridgeHostInstance.js.map +1 -0
- package/dist/agentSessions/HostInstance.d.ts +75 -11
- package/dist/agentSessions/HostInstance.d.ts.map +1 -1
- package/dist/agentSessions/HostInstance.js +99 -28
- package/dist/agentSessions/HostInstance.js.map +1 -1
- package/dist/agentSessions/ReturningVisitorRecap.d.ts.map +1 -1
- package/dist/agentSessions/ReturningVisitorRecap.js +7 -3
- package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -1
- package/dist/agentSessions/SessionJanitor.d.ts +173 -25
- package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
- package/dist/agentSessions/SessionJanitor.js +452 -54
- package/dist/agentSessions/SessionJanitor.js.map +1 -1
- package/dist/agentSessions/SessionManager.d.ts +43 -2
- package/dist/agentSessions/SessionManager.d.ts.map +1 -1
- package/dist/agentSessions/SessionManager.js +99 -8
- package/dist/agentSessions/SessionManager.js.map +1 -1
- package/dist/agentSessions/SessionRunIDVerifier.d.ts +63 -0
- package/dist/agentSessions/SessionRunIDVerifier.d.ts.map +1 -0
- package/dist/agentSessions/SessionRunIDVerifier.js +129 -0
- package/dist/agentSessions/SessionRunIDVerifier.js.map +1 -0
- package/dist/agentSessions/index.d.ts +2 -0
- package/dist/agentSessions/index.d.ts.map +1 -1
- package/dist/agentSessions/index.js +10 -3
- package/dist/agentSessions/index.js.map +1 -1
- package/dist/auth/actionAuthorization.d.ts +40 -0
- package/dist/auth/actionAuthorization.d.ts.map +1 -0
- package/dist/auth/actionAuthorization.js +94 -0
- package/dist/auth/actionAuthorization.js.map +1 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +3 -10
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.js +6 -3
- package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
- package/dist/auth/newUsers.d.ts +10 -0
- package/dist/auth/newUsers.d.ts.map +1 -1
- package/dist/auth/newUsers.js +80 -11
- package/dist/auth/newUsers.js.map +1 -1
- package/dist/config.d.ts +2378 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +123 -0
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +8 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +14 -11
- package/dist/context.js.map +1 -1
- package/dist/generated/graphql-schemas/__mj.d.ts +135 -0
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +706 -0
- package/dist/generated/graphql-schemas/__mj.js.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.js +3 -3
- package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +20 -4
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +39 -10
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +4 -0
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +120 -131
- package/dist/index.js.map +1 -1
- package/dist/isolatedProvider.d.ts +20 -0
- package/dist/isolatedProvider.d.ts.map +1 -0
- package/dist/isolatedProvider.js +22 -0
- package/dist/isolatedProvider.js.map +1 -0
- package/dist/realtimeProxy/RealtimeProxyServer.d.ts +44 -4
- package/dist/realtimeProxy/RealtimeProxyServer.d.ts.map +1 -1
- package/dist/realtimeProxy/RealtimeProxyServer.js +121 -16
- package/dist/realtimeProxy/RealtimeProxyServer.js.map +1 -1
- package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts +171 -0
- package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts.map +1 -0
- package/dist/realtimeProxy/RealtimeRelayTunnel.js +532 -0
- package/dist/realtimeProxy/RealtimeRelayTunnel.js.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.d.ts +148 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.d.ts.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.js +191 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.js.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts +66 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.js +138 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.js.map +1 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts +38 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts.map +1 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.js +98 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.js.map +1 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts +61 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts.map +1 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.js +149 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.js.map +1 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts +57 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts.map +1 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.js +69 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.js.map +1 -0
- package/dist/realtimeSessions/index.d.ts +17 -0
- package/dist/realtimeSessions/index.d.ts.map +1 -0
- package/dist/realtimeSessions/index.js +17 -0
- package/dist/realtimeSessions/index.js.map +1 -0
- package/dist/realtimeSessions/sessionAccess.d.ts +86 -0
- package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -0
- package/dist/realtimeSessions/sessionAccess.js +90 -0
- package/dist/realtimeSessions/sessionAccess.js.map +1 -0
- package/dist/realtimeSessions/sessionConfigStamps.d.ts +70 -0
- package/dist/realtimeSessions/sessionConfigStamps.d.ts.map +1 -0
- package/dist/realtimeSessions/sessionConfigStamps.js +93 -0
- package/dist/realtimeSessions/sessionConfigStamps.js.map +1 -0
- package/dist/realtimeSessions/subscriptionAuthorization.d.ts +65 -0
- package/dist/realtimeSessions/subscriptionAuthorization.d.ts.map +1 -0
- package/dist/realtimeSessions/subscriptionAuthorization.js +71 -0
- package/dist/realtimeSessions/subscriptionAuthorization.js.map +1 -0
- package/dist/realtimeSessions/verificationCore.d.ts +243 -0
- package/dist/realtimeSessions/verificationCore.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationCore.js +356 -0
- package/dist/realtimeSessions/verificationCore.js.map +1 -0
- package/dist/realtimeSessions/verificationPages.d.ts +66 -0
- package/dist/realtimeSessions/verificationPages.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationPages.js +110 -0
- package/dist/realtimeSessions/verificationPages.js.map +1 -0
- package/dist/realtimeSessions/verificationPolicy.d.ts +147 -0
- package/dist/realtimeSessions/verificationPolicy.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationPolicy.js +249 -0
- package/dist/realtimeSessions/verificationPolicy.js.map +1 -0
- package/dist/realtimeSessions/verificationWorkflow.d.ts +263 -0
- package/dist/realtimeSessions/verificationWorkflow.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationWorkflow.js +435 -0
- package/dist/realtimeSessions/verificationWorkflow.js.map +1 -0
- package/dist/realtimeWidget/widgetGuestElevation.d.ts +20 -2
- package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.js +31 -2
- package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
- package/dist/resolvers/ActionResolver.d.ts +31 -5
- package/dist/resolvers/ActionResolver.d.ts.map +1 -1
- package/dist/resolvers/ActionResolver.js +75 -14
- package/dist/resolvers/ActionResolver.js.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.d.ts +14 -1
- package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.js +21 -5
- package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
- package/dist/resolvers/CacheStatsResolver.d.ts +27 -0
- package/dist/resolvers/CacheStatsResolver.d.ts.map +1 -1
- package/dist/resolvers/CacheStatsResolver.js +113 -2
- package/dist/resolvers/CacheStatsResolver.js.map +1 -1
- package/dist/resolvers/ClientToolRequestResolver.d.ts +4 -2
- package/dist/resolvers/ClientToolRequestResolver.d.ts.map +1 -1
- package/dist/resolvers/ClientToolRequestResolver.js +28 -10
- package/dist/resolvers/ClientToolRequestResolver.js.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts +12 -1
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.js +83 -2
- package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
- package/dist/resolvers/HumanHandoffResolver.d.ts.map +1 -1
- package/dist/resolvers/HumanHandoffResolver.js +7 -1
- package/dist/resolvers/HumanHandoffResolver.js.map +1 -1
- package/dist/resolvers/MCPResolver.d.ts.map +1 -1
- package/dist/resolvers/MCPResolver.js +9 -4
- package/dist/resolvers/MCPResolver.js.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.d.ts +54 -2
- package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.js +155 -10
- package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +155 -25
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +394 -90
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RealtimeSessionEventsResolver.d.ts +61 -0
- package/dist/resolvers/RealtimeSessionEventsResolver.d.ts.map +1 -0
- package/dist/resolvers/RealtimeSessionEventsResolver.js +130 -0
- package/dist/resolvers/RealtimeSessionEventsResolver.js.map +1 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts +85 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts.map +1 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.js +188 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.js.map +1 -0
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts +2 -0
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js +5 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts +10 -0
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +68 -11
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTemplateResolver.js +12 -11
- package/dist/resolvers/RunTemplateResolver.js.map +1 -1
- package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncDataResolver.js +6 -2
- package/dist/resolvers/SyncDataResolver.js.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.d.ts +2 -0
- package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.js +23 -6
- package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
- package/dist/resolvers/UserAvatarResolver.d.ts +16 -0
- package/dist/resolvers/UserAvatarResolver.d.ts.map +1 -0
- package/dist/resolvers/UserAvatarResolver.js +124 -0
- package/dist/resolvers/UserAvatarResolver.js.map +1 -0
- package/dist/resolvers/UserResolver.d.ts.map +1 -1
- package/dist/resolvers/UserResolver.js +2 -17
- package/dist/resolvers/UserResolver.js.map +1 -1
- package/dist/resolvers/agentVisionConsentAudit.d.ts +37 -0
- package/dist/resolvers/agentVisionConsentAudit.d.ts.map +1 -0
- package/dist/resolvers/agentVisionConsentAudit.js +63 -0
- package/dist/resolvers/agentVisionConsentAudit.js.map +1 -0
- package/dist/resolvers/avatarInputValidation.d.ts +40 -0
- package/dist/resolvers/avatarInputValidation.d.ts.map +1 -0
- package/dist/resolvers/avatarInputValidation.js +97 -0
- package/dist/resolvers/avatarInputValidation.js.map +1 -0
- package/dist/resolvers/currentUserRoles.d.ts +29 -0
- package/dist/resolvers/currentUserRoles.d.ts.map +1 -1
- package/dist/resolvers/currentUserRoles.js +30 -0
- package/dist/resolvers/currentUserRoles.js.map +1 -1
- package/dist/resolvers/meetings/meetingMutations.d.ts +6 -0
- package/dist/resolvers/meetings/meetingMutations.d.ts.map +1 -1
- package/dist/resolvers/meetings/meetingMutations.js +21 -0
- package/dist/resolvers/meetings/meetingMutations.js.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.d.ts +4 -3
- package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.js +13 -7
- package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
- package/dist/sharedCache.d.ts +105 -0
- package/dist/sharedCache.d.ts.map +1 -0
- package/dist/sharedCache.js +276 -0
- package/dist/sharedCache.js.map +1 -0
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +97 -95
- package/src/__tests__/ActionResolver.authorization.test.ts +353 -0
- package/src/__tests__/AdhocQueryResolver.sqlAuthorizer.test.ts +104 -0
- package/src/__tests__/BridgeHostInstance.test.ts +232 -0
- package/src/__tests__/ClientToolRequestResolver.test.ts +116 -0
- package/src/__tests__/FetchEntityVectorsResolver.test.ts +285 -0
- package/src/__tests__/HostInstance.test.ts +84 -0
- package/src/__tests__/HumanHandoffResolver.test.ts +17 -0
- package/src/__tests__/MeetingResolver.test.ts +75 -1
- package/src/__tests__/RealtimeBridgeResolver.test.ts +415 -6
- package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +383 -0
- package/src/__tests__/RealtimeClientSessionResolver.test.ts +682 -58
- package/src/__tests__/RealtimeProxyServer.test.ts +16 -1
- package/src/__tests__/RealtimeRelay.test.ts +876 -0
- package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +129 -0
- package/src/__tests__/ResolverBase.aggregateScreen.test.ts +174 -0
- package/src/__tests__/ResolverBase.clientOldValues.test.ts +6 -3
- package/src/__tests__/ReturningVisitorRecap.test.ts +115 -0
- package/src/__tests__/RunAIAgentResolver.branch.test.ts +169 -0
- package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +4 -4
- package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +166 -0
- package/src/__tests__/RunTemplateResolver.test.ts +132 -0
- package/src/__tests__/RunViewResolver.cacheCheckForwarding.test.ts +63 -0
- package/src/__tests__/SessionJanitor.test.ts +845 -18
- package/src/__tests__/SessionManager.test.ts +213 -17
- package/src/__tests__/SessionRunIDVerifier.test.ts +251 -0
- package/src/__tests__/SyncRolesUsersResolver.userRoles.test.ts +97 -0
- package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +50 -3
- package/src/__tests__/UserAvatarResolver.test.ts +255 -0
- package/src/__tests__/UserResolver.currentUser.test.ts +16 -1
- package/src/__tests__/actionAuthorization.test.ts +130 -0
- package/src/__tests__/agentVisionConsentAudit.test.ts +118 -0
- package/src/__tests__/avatarInputValidation.test.ts +160 -0
- package/src/__tests__/isolatedProviderTransactions.test.ts +169 -0
- package/src/__tests__/magicLinkService.consume.test.ts +6 -0
- package/src/__tests__/magicLinkService.provisionLog.test.ts +5 -0
- package/src/__tests__/newUsers.template.test.ts +151 -0
- package/src/__tests__/newUsers.test.ts +74 -5
- package/src/__tests__/orphanedConversationDetailReconciler.test.ts +17 -2
- package/src/__tests__/realtimeSessions/RealtimeSessionEventService.test.ts +201 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionEventsResolver.test.ts +215 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionVerificationResolver.test.ts +102 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionVerificationService.test.ts +349 -0
- package/src/__tests__/realtimeSessions/RealtimeVerifyRouter.test.ts +131 -0
- package/src/__tests__/realtimeSessions/SlidingWindowRateLimiter.test.ts +56 -0
- package/src/__tests__/realtimeSessions/helpers.ts +4 -0
- package/src/__tests__/realtimeSessions/sessionAccess.test.ts +129 -0
- package/src/__tests__/realtimeSessions/sessionConfigStamps.test.ts +124 -0
- package/src/__tests__/realtimeSessions/verificationCore.test.ts +355 -0
- package/src/__tests__/realtimeSessions/verificationPages.test.ts +84 -0
- package/src/__tests__/realtimeSessions/verificationPolicy.test.ts +226 -0
- package/src/__tests__/realtimeSessions/verificationWorkflow.test.ts +633 -0
- package/src/__tests__/resolverBase.fls.test.ts +87 -4
- package/src/__tests__/serverExtensionReservedRoots.test.ts +2 -0
- package/src/__tests__/sharedCache.test.ts +310 -0
- package/src/__tests__/widgetGuestElevation.test.ts +44 -0
- package/src/agentSessions/BridgeHostInstance.ts +32 -0
- package/src/agentSessions/HostInstance.ts +124 -28
- package/src/agentSessions/ReturningVisitorRecap.ts +7 -3
- package/src/agentSessions/SessionJanitor.ts +543 -57
- package/src/agentSessions/SessionManager.ts +125 -25
- package/src/agentSessions/SessionRunIDVerifier.ts +171 -0
- package/src/agentSessions/index.ts +10 -3
- package/src/auth/actionAuthorization.ts +116 -0
- package/src/auth/index.ts +3 -8
- package/src/auth/magicLink/MagicLinkService.ts +6 -3
- package/src/auth/newUsers.ts +99 -11
- package/src/config.ts +125 -0
- package/src/context.ts +15 -10
- package/src/generated/graphql-schemas/__mj.ts +496 -1
- package/src/generic/OrphanedConversationDetailReconciler.ts +3 -3
- package/src/generic/ResolverBase.ts +39 -9
- package/src/generic/RunViewResolver.ts +4 -0
- package/src/index.ts +132 -143
- package/src/isolatedProvider.ts +22 -0
- package/src/realtimeProxy/RealtimeProxyServer.ts +148 -16
- package/src/realtimeProxy/RealtimeRelayTunnel.ts +645 -0
- package/src/realtimeSessions/README.md +179 -0
- package/src/realtimeSessions/RealtimeSessionEventService.ts +274 -0
- package/src/realtimeSessions/RealtimeSessionVerificationService.ts +167 -0
- package/src/realtimeSessions/RealtimeVerifyRouter.ts +108 -0
- package/src/realtimeSessions/SessionVerificationPortsBase.ts +189 -0
- package/src/realtimeSessions/SlidingWindowRateLimiter.ts +92 -0
- package/src/realtimeSessions/index.ts +16 -0
- package/src/realtimeSessions/sessionAccess.ts +141 -0
- package/src/realtimeSessions/sessionConfigStamps.ts +123 -0
- package/src/realtimeSessions/subscriptionAuthorization.ts +116 -0
- package/src/realtimeSessions/verificationCore.ts +502 -0
- package/src/realtimeSessions/verificationPages.ts +149 -0
- package/src/realtimeSessions/verificationPolicy.ts +321 -0
- package/src/realtimeSessions/verificationWorkflow.ts +702 -0
- package/src/realtimeWidget/widgetGuestElevation.ts +34 -2
- package/src/resolvers/ActionResolver.ts +84 -21
- package/src/resolvers/AdhocQueryResolver.ts +24 -6
- package/src/resolvers/CacheStatsResolver.ts +89 -2
- package/src/resolvers/ClientToolRequestResolver.ts +25 -7
- package/src/resolvers/FetchEntityVectorsResolver.ts +97 -2
- package/src/resolvers/HumanHandoffResolver.ts +8 -0
- package/src/resolvers/MCPResolver.ts +8 -5
- package/src/resolvers/RealtimeBridgeResolver.ts +163 -11
- package/src/resolvers/RealtimeClientSessionResolver.ts +501 -96
- package/src/resolvers/RealtimeSessionEventsResolver.ts +133 -0
- package/src/resolvers/RealtimeSessionVerificationResolver.ts +174 -0
- package/src/resolvers/RemoteBrowserActionResolver.ts +11 -1
- package/src/resolvers/RunAIAgentResolver.ts +78 -11
- package/src/resolvers/RunTemplateResolver.ts +13 -12
- package/src/resolvers/SyncDataResolver.ts +6 -2
- package/src/resolvers/SyncRolesUsersResolver.ts +24 -6
- package/src/resolvers/UserAvatarResolver.ts +111 -0
- package/src/resolvers/UserResolver.ts +2 -16
- package/src/resolvers/agentVisionConsentAudit.ts +82 -0
- package/src/resolvers/avatarInputValidation.ts +123 -0
- package/src/resolvers/currentUserRoles.ts +32 -1
- package/src/resolvers/meetings/meetingMutations.ts +21 -0
- package/src/services/TaskGraphContinuationDeliverer.ts +13 -7
- package/src/sharedCache.ts +302 -0
- package/src/types.ts +7 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Self-service avatar mutation: `UpdateMyAvatar(ImageURL, IconClass)`.
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS EXISTS. Explorer's My Profile photo used to be a plain entity save of the caller's own
|
|
5
|
+
* `MJ: Users` row, which needs Update permission on `MJ: Users`. A deployment that locks its
|
|
6
|
+
* security model down cannot give ordinary users that permission: a row-level "own row only"
|
|
7
|
+
* filter restricts ROWS, not COLUMNS, so a user who may update their own row may also rewrite
|
|
8
|
+
* their own `Email` — and many read filters key on `{{UserEmail}}`, so the new value would match
|
|
9
|
+
* another person's memberships. MJ has no per-role field permissions (`MJUserEntityServer` in
|
|
10
|
+
* core-entities-server documents the same limit for `Type`). On such deployments every avatar save
|
|
11
|
+
* failed, and the login avatar sync failed silently.
|
|
12
|
+
*
|
|
13
|
+
* WHAT IT DOES. The caller comes ONLY from the request context — there is no user-ID argument. The
|
|
14
|
+
* input is validated at this trust boundary (`ValidateAvatarInput`). The caller's own row is then
|
|
15
|
+
* loaded AS THE SYSTEM USER, any state other than the two avatar columns is discarded, and only
|
|
16
|
+
* `UserImageURL` and `UserImageIconClass` are set before saving. Because the write is made as the
|
|
17
|
+
* system user, the caller's ID is logged. It works the same whether or not the caller holds Update
|
|
18
|
+
* on `MJ: Users`.
|
|
19
|
+
*
|
|
20
|
+
* Scope-limited sessions are refused (`IsScopeLimitedPrincipal`). An anonymous magic-link guest IS
|
|
21
|
+
* the shared Anonymous principal, so "their own row" belongs to every guest, and a system-user save
|
|
22
|
+
* goes around the entity permissions and RLS that confine any magic-link session.
|
|
23
|
+
*
|
|
24
|
+
* `MJUserEntityServer` accepts this save: the system user is the seeded `Type='Owner'` user, which
|
|
25
|
+
* is exempt from its invariants, and the save changes neither `Type` nor `Name` in any case.
|
|
26
|
+
*
|
|
27
|
+
* @module @memberjunction/server/resolvers/UserAvatarResolver
|
|
28
|
+
*/
|
|
29
|
+
import { Arg, Ctx, Field, Mutation, ObjectType, Resolver } from 'type-graphql';
|
|
30
|
+
import { LogError, LogStatus, UserInfo } from '@memberjunction/core';
|
|
31
|
+
import type { MJUserEntity } from '@memberjunction/core-entities';
|
|
32
|
+
import { AppContext } from '../types.js';
|
|
33
|
+
import { GetReadWriteProvider } from '../util.js';
|
|
34
|
+
import { GetSystemUser } from '../auth/index.js';
|
|
35
|
+
import { IsScopeLimitedPrincipal } from '../auth/scopeLimitedPrincipal.js';
|
|
36
|
+
import { ResolverBase } from '../generic/ResolverBase.js';
|
|
37
|
+
import { ValidateAvatarInput } from './avatarInputValidation.js';
|
|
38
|
+
|
|
39
|
+
/** What the browser sees when the save fails. The server's detail goes to the log only. */
|
|
40
|
+
const SAVE_FAILED_MESSAGE = 'Could not save your avatar. Please try again or contact your administrator.';
|
|
41
|
+
|
|
42
|
+
@ObjectType()
|
|
43
|
+
export class UpdateMyAvatarResult {
|
|
44
|
+
@Field()
|
|
45
|
+
Success: boolean;
|
|
46
|
+
|
|
47
|
+
@Field({ nullable: true })
|
|
48
|
+
ErrorMessage?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@Resolver()
|
|
52
|
+
export class UserAvatarResolver extends ResolverBase {
|
|
53
|
+
/**
|
|
54
|
+
* Sets the CALLER's avatar. Null or empty clears a column; both null reverts to the default.
|
|
55
|
+
* Validation failures and save failures are returned as `Success: false` with a message.
|
|
56
|
+
*/
|
|
57
|
+
@Mutation(() => UpdateMyAvatarResult)
|
|
58
|
+
async UpdateMyAvatar(
|
|
59
|
+
@Ctx() ctx: AppContext,
|
|
60
|
+
@Arg('ImageURL', () => String, { nullable: true }) imageURL?: string | null,
|
|
61
|
+
@Arg('IconClass', () => String, { nullable: true }) iconClass?: string | null,
|
|
62
|
+
): Promise<UpdateMyAvatarResult> {
|
|
63
|
+
const caller = ctx.userPayload?.userRecord as UserInfo | undefined;
|
|
64
|
+
if (!caller?.ID) {
|
|
65
|
+
throw new Error('User is not authenticated');
|
|
66
|
+
}
|
|
67
|
+
if (IsScopeLimitedPrincipal(caller)) {
|
|
68
|
+
return { Success: false, ErrorMessage: 'Changing your avatar is not permitted for scope-limited sessions.' };
|
|
69
|
+
}
|
|
70
|
+
await this.CheckAPIKeyScopeAuthorization('entity:update', 'MJ: Users', ctx.userPayload);
|
|
71
|
+
|
|
72
|
+
const input = ValidateAvatarInput(imageURL, iconClass);
|
|
73
|
+
if (!input.Valid) {
|
|
74
|
+
return { Success: false, ErrorMessage: input.ErrorMessage };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
return await this.saveAvatar(ctx, caller, input.ImageURL, input.IconClass);
|
|
79
|
+
} catch (e) {
|
|
80
|
+
LogError(`UpdateMyAvatar: failed for user ${caller.ID}: ${e instanceof Error ? e.message : String(e)}`);
|
|
81
|
+
return { Success: false, ErrorMessage: SAVE_FAILED_MESSAGE };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Loads the caller's own row as the system user and writes ONLY the two avatar columns. */
|
|
86
|
+
private async saveAvatar(
|
|
87
|
+
ctx: AppContext,
|
|
88
|
+
caller: UserInfo,
|
|
89
|
+
imageURL: string | null,
|
|
90
|
+
iconClass: string | null,
|
|
91
|
+
): Promise<UpdateMyAvatarResult> {
|
|
92
|
+
const systemUser = await GetSystemUser();
|
|
93
|
+
const provider = GetReadWriteProvider(ctx.providers);
|
|
94
|
+
const user = await provider.GetEntityObject<MJUserEntity>('MJ: Users', systemUser);
|
|
95
|
+
if (!(await user.Load(caller.ID))) {
|
|
96
|
+
LogError(`UpdateMyAvatar: could not load the MJ: Users row for user ${caller.ID}`);
|
|
97
|
+
return { Success: false, ErrorMessage: 'Could not load your user record.' };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
user.Revert(); // nothing but the two avatar columns may reach the write
|
|
101
|
+
user.UserImageURL = imageURL;
|
|
102
|
+
user.UserImageIconClass = iconClass;
|
|
103
|
+
LogStatus(`UpdateMyAvatar: user ${caller.ID} requested an avatar change; saving as the system user`);
|
|
104
|
+
if (!(await user.Save())) {
|
|
105
|
+
const detail = user.LatestResult?.CompleteMessage ?? 'unknown error';
|
|
106
|
+
LogError(`UpdateMyAvatar: save failed for user ${caller.ID}: ${detail}`);
|
|
107
|
+
return { Success: false, ErrorMessage: SAVE_FAILED_MESSAGE };
|
|
108
|
+
}
|
|
109
|
+
return { Success: true };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
RequireContextUser,
|
|
9
9
|
RequireLoadedUser,
|
|
10
10
|
RequireRoleProvider,
|
|
11
|
+
SessionUserFields,
|
|
11
12
|
UserIdExtraFilter,
|
|
12
13
|
} from './currentUserRoles.js';
|
|
13
14
|
|
|
@@ -42,22 +43,7 @@ export class UserResolver extends MJUserResolverBase {
|
|
|
42
43
|
|
|
43
44
|
if (context.userPayload.userRecord) {
|
|
44
45
|
const userRecord = context.userPayload.userRecord;
|
|
45
|
-
const userData =
|
|
46
|
-
ID: userRecord.ID,
|
|
47
|
-
Name: userRecord.Name,
|
|
48
|
-
FirstName: userRecord.FirstName,
|
|
49
|
-
LastName: userRecord.LastName,
|
|
50
|
-
Title: userRecord.Title,
|
|
51
|
-
Email: userRecord.Email,
|
|
52
|
-
Type: userRecord.Type,
|
|
53
|
-
IsActive: userRecord.IsActive,
|
|
54
|
-
LinkedRecordType: userRecord.LinkedRecordType,
|
|
55
|
-
EmployeeID: userRecord.EmployeeID,
|
|
56
|
-
LinkedEntityID: userRecord.LinkedEntityID,
|
|
57
|
-
LinkedEntityRecordID: userRecord.LinkedEntityRecordID,
|
|
58
|
-
__mj_CreatedAt: userRecord.__mj_CreatedAt,
|
|
59
|
-
__mj_UpdatedAt: userRecord.__mj_UpdatedAt,
|
|
60
|
-
};
|
|
46
|
+
const userData = SessionUserFields(userRecord);
|
|
61
47
|
console.log('CurrentUser (from userRecord)', userData.Email);
|
|
62
48
|
const provider = GetReadOnlyProvider(context.providers, { allowFallbackToReadWrite: true });
|
|
63
49
|
const mapped = await this.MapFieldNamesToCodeNames('MJ: Users', userData, userRecord, provider) as MJUser_;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The audit trail for agent-vision consent: one `MJ: Audit Logs` row each time a person allows, or stops
|
|
3
|
+
* letting, agents see their camera and shared screen in a meeting room, including when LiveKit refuses the change.
|
|
4
|
+
*
|
|
5
|
+
* The row is saved by the system user, as MJ's other server-written audit rows are, because the person may not be
|
|
6
|
+
* allowed to create audit rows themselves; `UserID` and `RecordID` are the person. When the audit log type has not
|
|
7
|
+
* been seeded, the entry goes to the server log instead, so a choice is never left unrecorded.
|
|
8
|
+
*
|
|
9
|
+
* Invoked from `RealtimeBridgeResolver.SetLiveKitAgentVision`.
|
|
10
|
+
*
|
|
11
|
+
* @module @memberjunction/server
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { LogError, type IMetadataProvider, type UserInfo } from '@memberjunction/core';
|
|
15
|
+
import type { MJAuditLogEntity } from '@memberjunction/core-entities';
|
|
16
|
+
import { UserCache } from '@memberjunction/generic-database-provider';
|
|
17
|
+
|
|
18
|
+
/** The seeded audit log type (see `metadata/audit-log-types/.realtime-session-audit-types.json`). */
|
|
19
|
+
export const REALTIME_AGENT_VISION_CONSENT_AUDIT_TYPE = 'Realtime Agent Vision Consent';
|
|
20
|
+
|
|
21
|
+
const AUDIT_LOG_ENTITY = 'MJ: Audit Logs';
|
|
22
|
+
const USERS_ENTITY = 'MJ: Users';
|
|
23
|
+
|
|
24
|
+
/** One consent change to record. */
|
|
25
|
+
export interface AgentVisionConsentEntry {
|
|
26
|
+
/** The person who chose. */
|
|
27
|
+
User: UserInfo;
|
|
28
|
+
/** The room they chose for. */
|
|
29
|
+
RoomName: string;
|
|
30
|
+
/** `true` to let agents see their camera and shared screen, `false` to stop. */
|
|
31
|
+
Allow: boolean;
|
|
32
|
+
/** Whether LiveKit applied the change. */
|
|
33
|
+
Applied: boolean;
|
|
34
|
+
/** Why it was not applied. */
|
|
35
|
+
ErrorMessage?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Writes the audit row for one consent change. Never throws: a failure to audit is logged, and the person's choice
|
|
40
|
+
* stands as LiveKit applied it.
|
|
41
|
+
*
|
|
42
|
+
* @param entry The change to record.
|
|
43
|
+
* @param provider The request's provider.
|
|
44
|
+
*/
|
|
45
|
+
export async function WriteAgentVisionConsentAudit(entry: AgentVisionConsentEntry, provider: IMetadataProvider | null): Promise<void> {
|
|
46
|
+
try {
|
|
47
|
+
const writer = UserCache.Instance.GetSystemUser();
|
|
48
|
+
const type = provider?.AuditLogTypes?.find((t) => t.Name?.trim().toLowerCase() === REALTIME_AGENT_VISION_CONSENT_AUDIT_TYPE.toLowerCase());
|
|
49
|
+
if (!provider || !writer || !type) {
|
|
50
|
+
LogError(`Agent vision consent not audited (${missingReason(provider, writer)}): user ${entry.User.ID}: ${describeChange(entry)}`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const row = await provider.GetEntityObject<MJAuditLogEntity>(AUDIT_LOG_ENTITY, writer);
|
|
54
|
+
row.NewRecord();
|
|
55
|
+
row.UserID = entry.User.ID;
|
|
56
|
+
row.AuditLogTypeID = type.ID;
|
|
57
|
+
row.Status = entry.Applied ? 'Success' : 'Failed';
|
|
58
|
+
row.EntityID = provider.EntityByName(USERS_ENTITY)?.ID ?? null;
|
|
59
|
+
row.RecordID = entry.User.ID;
|
|
60
|
+
row.Description = describeChange(entry);
|
|
61
|
+
row.Details = JSON.stringify({ roomName: entry.RoomName, allow: entry.Allow, error: entry.ErrorMessage ?? null });
|
|
62
|
+
if (!(await row.Save())) {
|
|
63
|
+
LogError(`Agent vision consent audit row not saved for user ${entry.User.ID}: ${row.LatestResult?.CompleteMessage ?? 'unknown error'}`);
|
|
64
|
+
}
|
|
65
|
+
} catch (error) {
|
|
66
|
+
LogError(`Agent vision consent audit write threw for user ${entry.User.ID}: ${error instanceof Error ? error.message : String(error)}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Why the audit row can't be written: no provider, no system user, or the audit log type isn't seeded. */
|
|
71
|
+
function missingReason(provider: IMetadataProvider | null, writer: UserInfo | undefined | null): string {
|
|
72
|
+
if (!provider) {
|
|
73
|
+
return 'no provider';
|
|
74
|
+
}
|
|
75
|
+
return writer ? `audit log type '${REALTIME_AGENT_VISION_CONSENT_AUDIT_TYPE}' is not seeded` : 'no system user';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** One line saying what the person chose, and whether it was applied. */
|
|
79
|
+
function describeChange(entry: AgentVisionConsentEntry): string {
|
|
80
|
+
const choice = entry.Allow ? 'Let agents see their camera and shared screen' : 'Stopped letting agents see their camera and shared screen';
|
|
81
|
+
return `${choice} in room ${entry.RoomName}${entry.Applied ? '' : ' (not applied)'}.`;
|
|
82
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Trust-boundary validation for the self-service avatar mutation
|
|
3
|
+
* (`UpdateMyAvatar` in `UserAvatarResolver`).
|
|
4
|
+
*
|
|
5
|
+
* The browser checks these values too, but its URL check is `new URL(...)`, which happily accepts
|
|
6
|
+
* `javascript:` and every `data:` type. The server is the boundary, so everything that reaches
|
|
7
|
+
* `MJ: Users.UserImageURL` / `UserImageIconClass` through the mutation passes through here first.
|
|
8
|
+
*
|
|
9
|
+
* Pure: no I/O, no provider, no entity — so it is unit-testable on its own.
|
|
10
|
+
*
|
|
11
|
+
* @module @memberjunction/server/resolvers/avatarInputValidation
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** Largest DECODED image accepted in a `data:` URI. Matches the Explorer upload cap. */
|
|
15
|
+
export const AVATAR_MAX_IMAGE_BYTES = 200 * 1024;
|
|
16
|
+
|
|
17
|
+
/** Longest `http:` / `https:` image URL accepted. */
|
|
18
|
+
export const AVATAR_MAX_URL_LENGTH = 2048;
|
|
19
|
+
|
|
20
|
+
/** Longest icon class string accepted. */
|
|
21
|
+
export const AVATAR_MAX_ICON_CLASS_LENGTH = 100;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Longest `data:` value that can still decode to {@link AVATAR_MAX_IMAGE_BYTES}: the longest accepted
|
|
25
|
+
* prefix plus the padded base64 payload. Anything longer is refused before the pattern scans it.
|
|
26
|
+
*/
|
|
27
|
+
const AVATAR_MAX_DATA_URI_LENGTH = 'data:image/jpeg;base64,'.length + Math.ceil(AVATAR_MAX_IMAGE_BYTES / 3) * 4;
|
|
28
|
+
|
|
29
|
+
/** `data:image/<raster type>;base64,<payload>` — SVG is deliberately absent (it can carry script). */
|
|
30
|
+
const DATA_URI_PATTERN = /^data:image\/(png|jpeg|jpg|gif|webp);base64,([A-Za-z0-9+/]+={0,2})$/;
|
|
31
|
+
|
|
32
|
+
/** Lower-case Font Awesome class lists only, e.g. `fa-solid fa-user`. */
|
|
33
|
+
const ICON_CLASS_PATTERN = /^[a-z0-9 -]+$/;
|
|
34
|
+
const FA_TOKEN_PATTERN = /^fa-[a-z0-9-]+$/;
|
|
35
|
+
|
|
36
|
+
/** The outcome of {@link ValidateAvatarInput}. Empty strings are normalised to `null` (clear). */
|
|
37
|
+
export interface AvatarInputValidationResult {
|
|
38
|
+
Valid: boolean;
|
|
39
|
+
/** Why the input was refused. Set only when `Valid` is false. */
|
|
40
|
+
ErrorMessage?: string;
|
|
41
|
+
/** The value to store in `UserImageURL`; `null` clears it. */
|
|
42
|
+
ImageURL: string | null;
|
|
43
|
+
/** The value to store in `UserImageIconClass`; `null` clears it. */
|
|
44
|
+
IconClass: string | null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Validates a requested avatar. Null or empty values clear the corresponding column, so both
|
|
49
|
+
* null is a valid "revert to default".
|
|
50
|
+
*
|
|
51
|
+
* - `imageURL`: a `data:image/(png|jpeg|jpg|gif|webp);base64,...` URI whose decoded size is at most
|
|
52
|
+
* {@link AVATAR_MAX_IMAGE_BYTES}, or an absolute `http:`/`https:` URL of at most
|
|
53
|
+
* {@link AVATAR_MAX_URL_LENGTH} characters with no whitespace. Everything else is refused.
|
|
54
|
+
* - `iconClass`: at most {@link AVATAR_MAX_ICON_CLASS_LENGTH} characters of `[a-z0-9 -]`,
|
|
55
|
+
* containing at least one `fa-` token.
|
|
56
|
+
*/
|
|
57
|
+
export function ValidateAvatarInput(
|
|
58
|
+
imageURL: string | null | undefined,
|
|
59
|
+
iconClass: string | null | undefined
|
|
60
|
+
): AvatarInputValidationResult {
|
|
61
|
+
const image = imageURL ? imageURL : null;
|
|
62
|
+
const icon = iconClass ? iconClass : null;
|
|
63
|
+
const error = (image === null ? undefined : imageUrlError(image)) ?? (icon === null ? undefined : iconClassError(icon));
|
|
64
|
+
return error ? { Valid: false, ErrorMessage: error, ImageURL: null, IconClass: null } : { Valid: true, ImageURL: image, IconClass: icon };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Returns why an image value is refused, or `undefined` when it is acceptable. */
|
|
68
|
+
function imageUrlError(value: string): string | undefined {
|
|
69
|
+
if (value.startsWith('data:')) {
|
|
70
|
+
return dataUriError(value);
|
|
71
|
+
}
|
|
72
|
+
return httpUrlError(value);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function dataUriError(value: string): string | undefined {
|
|
76
|
+
if (value.length > AVATAR_MAX_DATA_URI_LENGTH) {
|
|
77
|
+
return tooLargeError(Math.floor((value.length * 3) / 4));
|
|
78
|
+
}
|
|
79
|
+
const match = DATA_URI_PATTERN.exec(value);
|
|
80
|
+
if (!match || match[2].length % 4 !== 0) {
|
|
81
|
+
return 'Avatar image must be a base64 PNG, JPEG, GIF or WEBP data URI.';
|
|
82
|
+
}
|
|
83
|
+
const decodedBytes = decodedBase64Length(match[2]);
|
|
84
|
+
return decodedBytes > AVATAR_MAX_IMAGE_BYTES ? tooLargeError(decodedBytes) : undefined;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function tooLargeError(bytes: number): string {
|
|
88
|
+
return `Avatar image must be ${AVATAR_MAX_IMAGE_BYTES / 1024}KB or smaller (this one is about ${Math.ceil(bytes / 1024)}KB).`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Decoded byte length of a padded base64 string, without decoding it. */
|
|
92
|
+
function decodedBase64Length(base64: string): number {
|
|
93
|
+
const padding = base64.endsWith('==') ? 2 : base64.endsWith('=') ? 1 : 0;
|
|
94
|
+
return (base64.length / 4) * 3 - padding;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function httpUrlError(value: string): string | undefined {
|
|
98
|
+
const refusal = 'Avatar image URL must be an absolute http or https URL.';
|
|
99
|
+
if (value.length > AVATAR_MAX_URL_LENGTH) {
|
|
100
|
+
return `Avatar image URL must be ${AVATAR_MAX_URL_LENGTH} characters or fewer.`;
|
|
101
|
+
}
|
|
102
|
+
if (/\s/.test(value) || !/^https?:\/\//i.test(value)) {
|
|
103
|
+
return refusal;
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
const url = new URL(value);
|
|
107
|
+
return url.protocol === 'http:' || url.protocol === 'https:' ? undefined : refusal;
|
|
108
|
+
} catch {
|
|
109
|
+
return refusal;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Returns why an icon class is refused, or `undefined` when it is acceptable. */
|
|
114
|
+
function iconClassError(value: string): string | undefined {
|
|
115
|
+
const valid =
|
|
116
|
+
value.length <= AVATAR_MAX_ICON_CLASS_LENGTH &&
|
|
117
|
+
ICON_CLASS_PATTERN.test(value) &&
|
|
118
|
+
value.split(' ').some((token) => FA_TOKEN_PATTERN.test(token));
|
|
119
|
+
return valid
|
|
120
|
+
? undefined
|
|
121
|
+
: `Avatar icon must be a Font Awesome class list (lower-case letters, digits, spaces and hyphens, ` +
|
|
122
|
+
`${AVATAR_MAX_ICON_CLASS_LENGTH} characters or fewer, including an fa- class).`;
|
|
123
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LogError } from '@memberjunction/core';
|
|
1
|
+
import { LogError, UserInfo } from '@memberjunction/core';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Guards and filter builders for CurrentUser role loading.
|
|
@@ -6,6 +6,37 @@ import { LogError } from '@memberjunction/core';
|
|
|
6
6
|
* without importing generated GraphQL types or the Apollo config bootstrap.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* The session user's own fields, as CurrentUser returns them. The client asks for every field of
|
|
11
|
+
* `UserInfo` (GraphQLDataProvider builds its query from it), so each one belongs here; anything
|
|
12
|
+
* left out reaches the client as null (as IsTemplate once did, hiding the template-user banner).
|
|
13
|
+
*/
|
|
14
|
+
export function SessionUserFields(userRecord: UserInfo) {
|
|
15
|
+
return {
|
|
16
|
+
ID: userRecord.ID,
|
|
17
|
+
Name: userRecord.Name,
|
|
18
|
+
FirstName: userRecord.FirstName,
|
|
19
|
+
LastName: userRecord.LastName,
|
|
20
|
+
Title: userRecord.Title,
|
|
21
|
+
Email: userRecord.Email,
|
|
22
|
+
Type: userRecord.Type,
|
|
23
|
+
IsActive: userRecord.IsActive,
|
|
24
|
+
IsTemplate: userRecord.IsTemplate,
|
|
25
|
+
LinkedRecordType: userRecord.LinkedRecordType,
|
|
26
|
+
EmployeeID: userRecord.EmployeeID,
|
|
27
|
+
LinkedEntityID: userRecord.LinkedEntityID,
|
|
28
|
+
LinkedEntityRecordID: userRecord.LinkedEntityRecordID,
|
|
29
|
+
__mj_CreatedAt: userRecord.__mj_CreatedAt,
|
|
30
|
+
__mj_UpdatedAt: userRecord.__mj_UpdatedAt,
|
|
31
|
+
FirstLast: userRecord.FirstLast,
|
|
32
|
+
EmployeeFirstLast: userRecord.EmployeeFirstLast,
|
|
33
|
+
EmployeeEmail: userRecord.EmployeeEmail,
|
|
34
|
+
EmployeeTitle: userRecord.EmployeeTitle,
|
|
35
|
+
EmployeeSupervisor: userRecord.EmployeeSupervisor,
|
|
36
|
+
EmployeeSupervisorEmail: userRecord.EmployeeSupervisorEmail,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
9
40
|
export function RequireLoadedUser<T extends { ID?: string }>(
|
|
10
41
|
user: T | null | undefined,
|
|
11
42
|
email: string | undefined,
|
|
@@ -305,6 +305,12 @@ export async function ExecuteUpdateMeeting(
|
|
|
305
305
|
}
|
|
306
306
|
}
|
|
307
307
|
|
|
308
|
+
/**
|
|
309
|
+
* Starts a meeting for the `StartMeeting` mutation (`MeetingResolver`): marks it `Live`, ensures its Interaction, starts
|
|
310
|
+
* each agent participant in the room under an agent session created for it, starts an automatic recording, and mints the
|
|
311
|
+
* caller's client token. An agent that fails to start doesn't fail the meeting; the session created for it is closed
|
|
312
|
+
* (`Error`).
|
|
313
|
+
*/
|
|
308
314
|
export async function ExecuteStartMeeting(
|
|
309
315
|
provider: IMetadataProvider,
|
|
310
316
|
user: UserInfo,
|
|
@@ -343,12 +349,15 @@ export async function ExecuteStartMeeting(
|
|
|
343
349
|
const agentParticipants = participants.filter((p) => p.Role === 'Agent' && p.AgentID);
|
|
344
350
|
|
|
345
351
|
for (const ap of agentParticipants) {
|
|
352
|
+
// Set once this agent's session exists, so a start that fails after it was created can close it.
|
|
353
|
+
let createdSessionID: string | undefined;
|
|
346
354
|
try {
|
|
347
355
|
const createdSession = await sessionManager.CreateSession(
|
|
348
356
|
{ agentID: ap.AgentID!, userID: user.ID },
|
|
349
357
|
user,
|
|
350
358
|
provider,
|
|
351
359
|
);
|
|
360
|
+
createdSessionID = createdSession.ID;
|
|
352
361
|
await LiveKitAgentRoomCoordinator.Instance.StartAgentRoomSession({
|
|
353
362
|
AgentSessionID: createdSession.ID,
|
|
354
363
|
RoomName: meeting.RoomName,
|
|
@@ -357,9 +366,21 @@ export async function ExecuteStartMeeting(
|
|
|
357
366
|
MetadataProvider: provider,
|
|
358
367
|
TurnMode: 'Active',
|
|
359
368
|
TurnAddressing: 'Auto',
|
|
369
|
+
// The session exists for this agent's time in the room alone: it closes when the agent leaves (the meeting is
|
|
370
|
+
// ended, everyone leaves, the agent is stopped), not when the janitor finds it idle.
|
|
371
|
+
Host: {
|
|
372
|
+
OnSessionEnded: async (reason) => {
|
|
373
|
+
await sessionManager.CloseSessionForEndedBridge(createdSession.ID, reason, user, provider);
|
|
374
|
+
},
|
|
375
|
+
},
|
|
360
376
|
});
|
|
361
377
|
} catch (agentErr) {
|
|
362
378
|
LogError(`[MeetingResolver] Failed to start agent participant ${ap.AgentID} in room ${meeting.RoomName}: ${agentErr instanceof Error ? agentErr.message : String(agentErr)}`);
|
|
379
|
+
// The agent never joined, so the hook above never runs: close its session now (Error), not when the janitor finds
|
|
380
|
+
// it idle. The coordinator has already closed any model session the start opened (#5308).
|
|
381
|
+
if (createdSessionID) {
|
|
382
|
+
await sessionManager.CloseSessionForFailedStart(createdSessionID, user, provider);
|
|
383
|
+
}
|
|
363
384
|
}
|
|
364
385
|
}
|
|
365
386
|
|
|
@@ -46,7 +46,8 @@ export class TaskGraphContinuationDeliverer implements TaskContinuationDeliverer
|
|
|
46
46
|
) {}
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
|
-
* Posts the roll-up as an AI-role message in the graph's conversation
|
|
49
|
+
* Posts the roll-up as an AI-role message in the graph's conversation, on the fork of the
|
|
50
|
+
* message that started the graph (Main when that message is in Main).
|
|
50
51
|
*
|
|
51
52
|
* Never throws. The dispatcher calls this inside the compare-and-swap that marks a completion
|
|
52
53
|
* delivered; an error escaping would either abort that guard or leave the graph looking
|
|
@@ -62,8 +63,8 @@ export class TaskGraphContinuationDeliverer implements TaskContinuationDeliverer
|
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
const provider = await this.providerFactory.CreateProvider();
|
|
65
|
-
const
|
|
66
|
-
if (!
|
|
66
|
+
const origin = await this.resolveOrigin(params.ConversationDetailID, provider);
|
|
67
|
+
if (!origin) {
|
|
67
68
|
LogError(
|
|
68
69
|
`[TaskGraphContinuationDeliverer] Conversation detail ${params.ConversationDetailID} could not be loaded — ` +
|
|
69
70
|
`"${params.WorkflowName}" has nowhere to post its outcome.`
|
|
@@ -73,7 +74,8 @@ export class TaskGraphContinuationDeliverer implements TaskContinuationDeliverer
|
|
|
73
74
|
|
|
74
75
|
const detail = await provider.GetEntityObject<MJConversationDetailEntity>('MJ: Conversation Details', this.contextUser);
|
|
75
76
|
detail.NewRecord();
|
|
76
|
-
detail.ConversationID =
|
|
77
|
+
detail.ConversationID = origin.ConversationID;
|
|
78
|
+
detail.BranchID = origin.BranchID;
|
|
77
79
|
detail.Role = 'AI';
|
|
78
80
|
detail.Status = 'Complete';
|
|
79
81
|
detail.HiddenToUser = false;
|
|
@@ -125,11 +127,15 @@ export class TaskGraphContinuationDeliverer implements TaskContinuationDeliverer
|
|
|
125
127
|
return;
|
|
126
128
|
}
|
|
127
129
|
|
|
130
|
+
// Run on the fork of the message that started the graph, as PostMessage posts into it:
|
|
131
|
+
// RunAgent derives the conversation from the detail, but not the fork.
|
|
132
|
+
const origin = params.ConversationDetailID ? await this.resolveOrigin(params.ConversationDetailID, provider) : null;
|
|
128
133
|
await new AgentRunner().RunAgent({
|
|
129
134
|
agent,
|
|
130
135
|
conversationMessages: [{ role: ChatMessageRole.user, content: this.renderMessage(params) }],
|
|
131
136
|
contextUser: this.contextUser,
|
|
132
137
|
conversationDetailId: params.ConversationDetailID ?? undefined,
|
|
138
|
+
ConversationBranchID: origin?.BranchID ?? undefined,
|
|
133
139
|
// The load-bearing value: without it the next graph this run submits restarts the
|
|
134
140
|
// chain at zero and the cap never fires.
|
|
135
141
|
continuationDepth: params.ReinvokeDepth + 1,
|
|
@@ -149,10 +155,10 @@ export class TaskGraphContinuationDeliverer implements TaskContinuationDeliverer
|
|
|
149
155
|
}
|
|
150
156
|
}
|
|
151
157
|
|
|
152
|
-
/** The conversation
|
|
153
|
-
private async
|
|
158
|
+
/** The conversation and fork (null is Main) of the detail that started the graph; null when it cannot be loaded. */
|
|
159
|
+
private async resolveOrigin(conversationDetailID: string, provider: IMetadataProvider): Promise<{ ConversationID: string; BranchID: string | null } | null> {
|
|
154
160
|
const detail = await provider.GetEntityObject<MJConversationDetailEntity>('MJ: Conversation Details', this.contextUser);
|
|
155
|
-
return (await detail.Load(conversationDetailID)) ? detail.ConversationID : null;
|
|
161
|
+
return (await detail.Load(conversationDetailID)) ? { ConversationID: detail.ConversationID, BranchID: detail.BranchID ?? null } : null;
|
|
156
162
|
}
|
|
157
163
|
|
|
158
164
|
/**
|