@memberjunction/server 6.2.0-edge.6 → 6.2.0-edge.8
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 +69 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/agentSessions/BridgeHostInstance.d.ts +8 -3
- package/dist/agentSessions/BridgeHostInstance.d.ts.map +1 -1
- package/dist/agentSessions/BridgeHostInstance.js +14 -4
- package/dist/agentSessions/BridgeHostInstance.js.map +1 -1
- package/dist/agentSessions/HandoffOfferExpiry.d.ts +10 -0
- package/dist/agentSessions/HandoffOfferExpiry.d.ts.map +1 -0
- package/dist/agentSessions/HandoffOfferExpiry.js +18 -0
- package/dist/agentSessions/HandoffOfferExpiry.js.map +1 -0
- package/dist/agentSessions/ReturningVisitorRecap.js +1 -0
- package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -1
- package/dist/agentSessions/SessionJanitor.d.ts +31 -8
- package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
- package/dist/agentSessions/SessionJanitor.js +85 -41
- package/dist/agentSessions/SessionJanitor.js.map +1 -1
- package/dist/agentSessions/SessionManager.d.ts +14 -4
- package/dist/agentSessions/SessionManager.d.ts.map +1 -1
- package/dist/agentSessions/SessionManager.js +28 -4
- package/dist/agentSessions/SessionManager.js.map +1 -1
- package/dist/agentSessions/conversationBrowsers.d.ts +61 -0
- package/dist/agentSessions/conversationBrowsers.d.ts.map +1 -0
- package/dist/agentSessions/conversationBrowsers.js +118 -0
- package/dist/agentSessions/conversationBrowsers.js.map +1 -0
- package/dist/agentSessions/sessionDeadline.d.ts +43 -0
- package/dist/agentSessions/sessionDeadline.d.ts.map +1 -0
- package/dist/agentSessions/sessionDeadline.js +68 -0
- package/dist/agentSessions/sessionDeadline.js.map +1 -0
- package/dist/auth/APIKeyScopeAuth.d.ts.map +1 -1
- package/dist/auth/APIKeyScopeAuth.js +5 -4
- package/dist/auth/APIKeyScopeAuth.js.map +1 -1
- package/dist/auth/loginEmailResolution.d.ts +41 -0
- package/dist/auth/loginEmailResolution.d.ts.map +1 -0
- package/dist/auth/loginEmailResolution.js +47 -0
- package/dist/auth/loginEmailResolution.js.map +1 -0
- package/dist/auth/magicLink/MagicLinkKeys.d.ts +50 -2
- package/dist/auth/magicLink/MagicLinkKeys.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkKeys.js +129 -12
- package/dist/auth/magicLink/MagicLinkKeys.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.d.ts +3 -0
- package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.js +23 -5
- package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.d.ts +39 -5
- package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.js +221 -9
- package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
- package/dist/auth/magicLink/index.d.ts +1 -1
- package/dist/auth/magicLink/index.d.ts.map +1 -1
- package/dist/auth/magicLink/index.js +1 -1
- package/dist/auth/magicLink/index.js.map +1 -1
- package/dist/auth/magicLink/redeemLanding.d.ts +2 -2
- package/dist/auth/magicLink/redeemLanding.d.ts.map +1 -1
- package/dist/auth/magicLink/redeemLanding.js +7 -4
- package/dist/auth/magicLink/redeemLanding.js.map +1 -1
- package/dist/auth/magicLink/types.d.ts +27 -3
- package/dist/auth/magicLink/types.d.ts.map +1 -1
- package/dist/auth/magicLink/types.js +0 -4
- package/dist/auth/magicLink/types.js.map +1 -1
- package/dist/auth/newUsers.d.ts.map +1 -1
- package/dist/auth/newUsers.js +1 -0
- package/dist/auth/newUsers.js.map +1 -1
- package/dist/backgroundSweepConfigUnits.d.ts +231 -0
- package/dist/backgroundSweepConfigUnits.d.ts.map +1 -0
- package/dist/backgroundSweepConfigUnits.js +180 -0
- package/dist/backgroundSweepConfigUnits.js.map +1 -0
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts.map +1 -1
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js +2 -1
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js.map +1 -1
- package/dist/config.d.ts +906 -8
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +59 -0
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +2 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +41 -12
- package/dist/context.js.map +1 -1
- package/dist/coreRoutes.d.ts +3 -1
- package/dist/coreRoutes.d.ts.map +1 -1
- package/dist/coreRoutes.js +6 -4
- package/dist/coreRoutes.js.map +1 -1
- package/dist/generated/graphql-schemas/__mj.d.ts +1072 -5
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +10174 -4618
- package/dist/generated/graphql-schemas/__mj.js.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.d.ts +11 -0
- package/dist/generic/OrphanedConversationDetailReconciler.d.ts.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.js +27 -1
- package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +50 -5
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +329 -132
- 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 +47 -7
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/globalCors.d.ts.map +1 -1
- package/dist/globalCors.js +1 -0
- package/dist/globalCors.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +201 -43
- package/dist/index.js.map +1 -1
- package/dist/integration/ConnectionPause.d.ts +121 -0
- package/dist/integration/ConnectionPause.d.ts.map +1 -0
- package/dist/integration/ConnectionPause.js +183 -0
- package/dist/integration/ConnectionPause.js.map +1 -0
- package/dist/integration/CustomColumnPromoter.d.ts +72 -6
- package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
- package/dist/integration/CustomColumnPromoter.js +87 -46
- package/dist/integration/CustomColumnPromoter.js.map +1 -1
- package/dist/integration/EntityMapLifecycle.d.ts +78 -4
- package/dist/integration/EntityMapLifecycle.d.ts.map +1 -1
- package/dist/integration/EntityMapLifecycle.js +199 -5
- package/dist/integration/EntityMapLifecycle.js.map +1 -1
- package/dist/integration/InactiveRowWarnings.d.ts +31 -0
- package/dist/integration/InactiveRowWarnings.d.ts.map +1 -1
- package/dist/integration/InactiveRowWarnings.js +29 -0
- package/dist/integration/InactiveRowWarnings.js.map +1 -1
- package/dist/integration/ObjectNamesByID.d.ts +28 -0
- package/dist/integration/ObjectNamesByID.d.ts.map +1 -0
- package/dist/integration/ObjectNamesByID.js +87 -0
- package/dist/integration/ObjectNamesByID.js.map +1 -0
- package/dist/integration/PostRestartIntrospect.d.ts +26 -0
- package/dist/integration/PostRestartIntrospect.d.ts.map +1 -0
- package/dist/integration/PostRestartIntrospect.js +27 -0
- package/dist/integration/PostRestartIntrospect.js.map +1 -0
- package/dist/integration/RSUPostRestartProgress.d.ts +176 -0
- package/dist/integration/RSUPostRestartProgress.d.ts.map +1 -0
- package/dist/integration/RSUPostRestartProgress.js +323 -0
- package/dist/integration/RSUPostRestartProgress.js.map +1 -0
- package/dist/integration/RSUProgressBridge.d.ts +41 -2
- package/dist/integration/RSUProgressBridge.d.ts.map +1 -1
- package/dist/integration/RSUProgressBridge.js +84 -1
- package/dist/integration/RSUProgressBridge.js.map +1 -1
- package/dist/integration/RunArtifactAuthorization.d.ts +39 -0
- package/dist/integration/RunArtifactAuthorization.d.ts.map +1 -0
- package/dist/integration/RunArtifactAuthorization.js +45 -0
- package/dist/integration/RunArtifactAuthorization.js.map +1 -0
- package/dist/integration/SchemaRefreshLaunch.d.ts +9 -0
- package/dist/integration/SchemaRefreshLaunch.d.ts.map +1 -1
- package/dist/integration/SchemaRefreshLaunch.js +3 -2
- package/dist/integration/SchemaRefreshLaunch.js.map +1 -1
- package/dist/integration/ScopedFieldsForBuild.d.ts +46 -0
- package/dist/integration/ScopedFieldsForBuild.d.ts.map +1 -0
- package/dist/integration/ScopedFieldsForBuild.js +91 -0
- package/dist/integration/ScopedFieldsForBuild.js.map +1 -0
- package/dist/integration/TargetConfigGuard.d.ts +40 -0
- package/dist/integration/TargetConfigGuard.d.ts.map +1 -0
- package/dist/integration/TargetConfigGuard.js +40 -0
- package/dist/integration/TargetConfigGuard.js.map +1 -0
- package/dist/logging/StartupLogger.d.ts +50 -0
- package/dist/logging/StartupLogger.d.ts.map +1 -1
- package/dist/logging/StartupLogger.js +15 -22
- package/dist/logging/StartupLogger.js.map +1 -1
- package/dist/middleware/BaseServerMiddleware.d.ts +21 -3
- package/dist/middleware/BaseServerMiddleware.d.ts.map +1 -1
- package/dist/middleware/BaseServerMiddleware.js +4 -2
- package/dist/middleware/BaseServerMiddleware.js.map +1 -1
- package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -1
- package/dist/realtimeSessions/ownedRealtimeSession.js +2 -3
- package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -1
- package/dist/realtimeSessions/sessionAccess.d.ts +15 -0
- package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -1
- package/dist/realtimeSessions/sessionAccess.js +22 -0
- package/dist/realtimeSessions/sessionAccess.js.map +1 -1
- package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -1
- package/dist/realtimeWidget/WidgetSessionService.js +2 -0
- package/dist/realtimeWidget/WidgetSessionService.js.map +1 -1
- package/dist/realtimeWidget/visitorIdentity.d.ts.map +1 -1
- package/dist/realtimeWidget/visitorIdentity.js +1 -0
- package/dist/realtimeWidget/visitorIdentity.js.map +1 -1
- package/dist/realtimeWidget/widgetCore.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetCore.js +4 -7
- package/dist/realtimeWidget/widgetCore.js.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.js +1 -0
- package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
- package/dist/requestOrigin.d.ts +22 -0
- package/dist/requestOrigin.d.ts.map +1 -0
- package/dist/requestOrigin.js +60 -0
- package/dist/requestOrigin.js.map +1 -0
- package/dist/resolvers/AgentRunStatusPublisher.d.ts +10 -0
- package/dist/resolvers/AgentRunStatusPublisher.d.ts.map +1 -1
- package/dist/resolvers/AgentRunStatusPublisher.js +18 -2
- package/dist/resolvers/AgentRunStatusPublisher.js.map +1 -1
- package/dist/resolvers/AgentSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/AgentSessionResolver.js +6 -2
- package/dist/resolvers/AgentSessionResolver.js.map +1 -1
- package/dist/resolvers/AutotagPipelineResolver.d.ts +1 -1
- package/dist/resolvers/AutotagPipelineResolver.d.ts.map +1 -1
- package/dist/resolvers/AutotagPipelineResolver.js +61 -16
- package/dist/resolvers/AutotagPipelineResolver.js.map +1 -1
- package/dist/resolvers/ConversationRunEventsResolver.d.ts.map +1 -1
- package/dist/resolvers/ConversationRunEventsResolver.js +2 -0
- package/dist/resolvers/ConversationRunEventsResolver.js.map +1 -1
- package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts +6 -0
- package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts.map +1 -1
- package/dist/resolvers/ExecuteRemoteOperationResolver.js +30 -7
- package/dist/resolvers/ExecuteRemoteOperationResolver.js.map +1 -1
- package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataResolver.js +34 -13
- package/dist/resolvers/GetDataResolver.js.map +1 -1
- package/dist/resolvers/InfoResolver.js +1 -1
- package/dist/resolvers/InfoResolver.js.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts +401 -11
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.js +2690 -1305
- package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
- package/dist/resolvers/MCPResolver.d.ts.map +1 -1
- package/dist/resolvers/MCPResolver.js +13 -2
- package/dist/resolvers/MCPResolver.js.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.d.ts +38 -1
- package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.js +181 -88
- package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.js +1 -0
- package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +6 -0
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +36 -5
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts +62 -10
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js +217 -34
- package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts +24 -8
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +185 -67
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunAIPromptResolver.d.ts +5 -3
- package/dist/resolvers/RunAIPromptResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIPromptResolver.js +19 -18
- package/dist/resolvers/RunAIPromptResolver.js.map +1 -1
- package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTemplateResolver.js +3 -1
- package/dist/resolvers/RunTemplateResolver.js.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.js +7 -8
- package/dist/resolvers/SqlLoggingConfigResolver.js.map +1 -1
- package/dist/resolvers/TagGovernanceResolver.d.ts +12 -0
- package/dist/resolvers/TagGovernanceResolver.d.ts.map +1 -1
- package/dist/resolvers/TagGovernanceResolver.js +84 -0
- package/dist/resolvers/TagGovernanceResolver.js.map +1 -1
- package/dist/resolvers/TelemetryResolver.d.ts +13 -8
- package/dist/resolvers/TelemetryResolver.d.ts.map +1 -1
- package/dist/resolvers/TelemetryResolver.js +40 -9
- package/dist/resolvers/TelemetryResolver.js.map +1 -1
- package/dist/resolvers/meetingRecordingRegistration.d.ts.map +1 -1
- package/dist/resolvers/meetingRecordingRegistration.js +2 -0
- package/dist/resolvers/meetingRecordingRegistration.js.map +1 -1
- package/dist/resolvers/meetings/meetingMutations.d.ts.map +1 -1
- package/dist/resolvers/meetings/meetingMutations.js +34 -4
- package/dist/resolvers/meetings/meetingMutations.js.map +1 -1
- package/dist/rest/MediaAccessKeys.d.ts.map +1 -1
- package/dist/rest/MediaAccessKeys.js +6 -2
- package/dist/rest/MediaAccessKeys.js.map +1 -1
- package/dist/rest/OAuthCallbackHandler.d.ts.map +1 -1
- package/dist/rest/OAuthCallbackHandler.js +11 -5
- package/dist/rest/OAuthCallbackHandler.js.map +1 -1
- package/dist/rest/RealtimeRecordingHandler.d.ts +3 -3
- package/dist/rest/RealtimeRecordingHandler.d.ts.map +1 -1
- package/dist/rest/RealtimeRecordingHandler.js +2 -4
- package/dist/rest/RealtimeRecordingHandler.js.map +1 -1
- package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
- package/dist/rest/ViewOperationsHandler.js +2 -1
- package/dist/rest/ViewOperationsHandler.js.map +1 -1
- package/dist/rest/setupRESTEndpoints.d.ts +3 -1
- package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
- package/dist/rest/setupRESTEndpoints.js +11 -2
- package/dist/rest/setupRESTEndpoints.js.map +1 -1
- package/dist/services/DurableEntityActionTaskSubmitter.d.ts.map +1 -1
- package/dist/services/DurableEntityActionTaskSubmitter.js +6 -1
- package/dist/services/DurableEntityActionTaskSubmitter.js.map +1 -1
- package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphActionRunner.js +2 -26
- package/dist/services/TaskGraphActionRunner.js.map +1 -1
- package/dist/services/TaskGraphAgentRunner.d.ts +6 -0
- package/dist/services/TaskGraphAgentRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphAgentRunner.js +38 -0
- package/dist/services/TaskGraphAgentRunner.js.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.js +1 -0
- package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
- package/dist/services/WorkQueueHostService.d.ts +2 -1
- package/dist/services/WorkQueueHostService.d.ts.map +1 -1
- package/dist/services/WorkQueueHostService.js +19 -4
- package/dist/services/WorkQueueHostService.js.map +1 -1
- package/dist/services/workQueueConfig.d.ts +2 -2
- package/dist/services/workQueueConfig.js +5 -5
- package/dist/services/workQueueConfig.js.map +1 -1
- package/dist/sharedCache.js +1 -1
- package/dist/sharedCache.js.map +1 -1
- package/dist/types.d.ts +17 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +55 -2
- package/dist/util.js.map +1 -1
- package/package.json +107 -100
- package/src/__tests__/APIKeyScopeAuth.test.ts +4 -8
- package/src/__tests__/ActiveOperations.test.ts +68 -0
- package/src/__tests__/AgentSessionResolver.ownership.test.ts +13 -0
- package/src/__tests__/AssertTargetConfigs.test.ts +75 -0
- package/src/__tests__/BatchApplyParity.test.ts +109 -0
- package/src/__tests__/BridgeHostInstance.test.ts +42 -0
- package/src/__tests__/CollectInactiveRowWarnings.test.ts +115 -0
- package/src/__tests__/ConnectionPause.test.ts +192 -0
- package/src/__tests__/ConnectionPauseResolver.test.ts +166 -0
- package/src/__tests__/ConnectionPauseSave.test.ts +143 -0
- package/src/__tests__/CustomColumnPromoterCompletion.test.ts +142 -0
- package/src/__tests__/DeleteConnectionRemovesPerConnectionCatalog.test.ts +51 -0
- package/src/__tests__/DetachedDiscoveryStart.test.ts +65 -0
- package/src/__tests__/DurableEntityActionTaskSubmitter.test.ts +73 -0
- package/src/__tests__/EntityMapLifecycle.rekey.test.ts +252 -0
- package/src/__tests__/ExecuteRemoteOperationResolver.test.ts +213 -0
- package/src/__tests__/HandoffOfferExpiry.test.ts +40 -0
- package/src/__tests__/IntegrationRunArtifactAuthorization.test.ts +126 -0
- package/src/__tests__/MagicLinkKeys.test.ts +102 -0
- package/src/__tests__/MediaAccessKeys.test.ts +57 -10
- package/src/__tests__/NewArrivalsDefaultDisabled.test.ts +50 -0
- package/src/__tests__/ObjectNamesByID.test.ts +176 -0
- package/src/__tests__/PostRestartMapsInCatalogScope.test.ts +146 -0
- package/src/__tests__/QuerySystemUserResolver.childRows.test.ts +80 -0
- package/src/__tests__/QuerySystemUserResolver.collision.test.ts +200 -0
- package/src/__tests__/QuerySystemUserResolver.permissions.test.ts +132 -0
- package/src/__tests__/RSUPostRestartProgress.test.ts +343 -0
- package/src/__tests__/RSUProgressBridge.test.ts +188 -5
- package/src/__tests__/RSUStartupSweepWiring.test.ts +30 -0
- package/src/__tests__/ReactivateRefreshMessage.test.ts +10 -3
- package/src/__tests__/RealtimeClientSessionResolver.test.ts +22 -0
- package/src/__tests__/{RefreshAdoptsNewColumns.test.ts → RefreshReportsNewColumns.test.ts} +25 -16
- package/src/__tests__/RemoteBrowserActionRecovery.test.ts +287 -0
- package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +3 -0
- package/src/__tests__/RemoteBrowserActionResolver.ownership.test.ts +6 -4
- package/src/__tests__/RemoteBrowserAudioStream.test.ts +3 -0
- package/src/__tests__/RemoteBrowserSnapshot.test.ts +3 -0
- package/src/__tests__/ResolverBase.clientOldValues.test.ts +327 -26
- package/src/__tests__/ResolverBase.diagnosticSenderRegistrations.test.ts +26 -0
- package/src/__tests__/ResolverBase.encryptedApiZeroReencrypt.test.ts +307 -0
- package/src/__tests__/ResolverBase.entityMessageRelay.test.ts +53 -2
- package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +60 -0
- package/src/__tests__/RunAIAgentResolver.branch.test.ts +4 -0
- package/src/__tests__/RunAIAgentResolver.consultPredecessor.test.ts +289 -0
- package/src/__tests__/RunAIAgentResolver.handoff.test.ts +649 -0
- package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +2 -0
- package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +2 -0
- package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +87 -12
- package/src/__tests__/RunAIAgentResolver.streaming.test.ts +28 -4
- package/src/__tests__/SchemaEvolutionCustomColumns.test.ts +119 -0
- package/src/__tests__/SchemaRefreshLaunch.test.ts +24 -2
- package/src/__tests__/ScopedFieldsForBuild.test.ts +192 -0
- package/src/__tests__/SessionJanitor.test.ts +195 -4
- package/src/__tests__/SessionManager.test.ts +69 -0
- package/src/__tests__/TagGovernanceAndAutotagResolvers.test.ts +261 -0
- package/src/__tests__/TagGovernanceResolver.test.ts +60 -2
- package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +2 -2
- package/src/__tests__/TaskGraphAgentRunner.test.ts +272 -16
- package/src/__tests__/ViewOperationsHandler.test.ts +2 -5
- package/src/__tests__/WorkQueueHostService.test.ts +25 -7
- package/src/__tests__/backgroundSweepConfigUnits.test.ts +153 -0
- package/src/__tests__/contextRequestOrigin.test.ts +105 -0
- package/src/__tests__/conversationBrowsers.test.ts +62 -0
- package/src/__tests__/globalCors.test.ts +12 -0
- package/src/__tests__/magicLinkExtensionCapabilities.test.ts +413 -0
- package/src/__tests__/orphanedConversationDetailReconciler.start.test.ts +82 -0
- package/src/__tests__/ownedRealtimeSession.test.ts +14 -0
- package/src/__tests__/realtimeRecordingHandler.test.ts +7 -2
- package/src/__tests__/realtimeSessions/sessionAccess.test.ts +79 -1
- package/src/__tests__/requestOrigin.test.ts +104 -0
- package/src/__tests__/review-capture-valid-token-log.test.ts +98 -0
- package/src/__tests__/runStreams.fieldSecurity.test.ts +5 -5
- package/src/__tests__/securityHardeningPr6034.test.ts +120 -0
- package/src/__tests__/sessionDeadline.test.ts +51 -0
- package/src/__tests__/sharedCache.test.ts +11 -0
- package/src/__tests__/unifiedAuth.test.ts +73 -1
- package/src/agentSessions/BridgeHostInstance.ts +20 -5
- package/src/agentSessions/HandoffOfferExpiry.ts +18 -0
- package/src/agentSessions/ReturningVisitorRecap.ts +1 -0
- package/src/agentSessions/SessionJanitor.ts +92 -42
- package/src/agentSessions/SessionManager.ts +38 -8
- package/src/agentSessions/conversationBrowsers.ts +143 -0
- package/src/agentSessions/sessionDeadline.ts +81 -0
- package/src/auth/APIKeyScopeAuth.ts +5 -4
- package/src/auth/__tests__/loginEmailResolution.test.ts +67 -0
- package/src/auth/loginEmailResolution.ts +67 -0
- package/src/auth/magicLink/MagicLinkKeys.ts +146 -14
- package/src/auth/magicLink/MagicLinkRouter.ts +33 -6
- package/src/auth/magicLink/MagicLinkService.ts +250 -10
- package/src/auth/magicLink/index.ts +1 -1
- package/src/auth/magicLink/redeemLanding.ts +7 -4
- package/src/auth/magicLink/types.ts +28 -3
- package/src/auth/newUsers.ts +1 -0
- package/src/backgroundSweepConfigUnits.ts +213 -0
- package/src/broadcast/BaseCacheInvalidationBroadcastProvider.ts +2 -1
- package/src/config.ts +69 -0
- package/src/context.ts +46 -15
- package/src/coreRoutes.ts +14 -7
- package/src/generated/graphql-schemas/__mj.ts +10360 -6479
- package/src/generic/OrphanedConversationDetailReconciler.ts +32 -1
- package/src/generic/ResolverBase.ts +422 -143
- package/src/generic/RunViewResolver.ts +96 -49
- package/src/globalCors.ts +1 -0
- package/src/index.ts +246 -44
- package/src/integration/ConnectionPause.ts +222 -0
- package/src/integration/CustomColumnPromoter.ts +123 -53
- package/src/integration/EntityMapLifecycle.ts +229 -6
- package/src/integration/InactiveRowWarnings.ts +50 -0
- package/src/integration/ObjectNamesByID.ts +97 -0
- package/src/integration/PostRestartIntrospect.ts +49 -0
- package/src/integration/RSUPostRestartProgress.ts +359 -0
- package/src/integration/RSUProgressBridge.ts +85 -2
- package/src/integration/RunArtifactAuthorization.ts +65 -0
- package/src/integration/SchemaRefreshLaunch.ts +12 -2
- package/src/integration/ScopedFieldsForBuild.ts +113 -0
- package/src/integration/TargetConfigGuard.ts +71 -0
- package/src/logging/StartupLogger.ts +24 -0
- package/src/middleware/BaseServerMiddleware.ts +20 -3
- package/src/realtimeSessions/ownedRealtimeSession.ts +2 -6
- package/src/realtimeSessions/sessionAccess.ts +30 -0
- package/src/realtimeWidget/WidgetSessionService.ts +2 -0
- package/src/realtimeWidget/visitorIdentity.ts +1 -0
- package/src/realtimeWidget/widgetCore.ts +4 -8
- package/src/realtimeWidget/widgetGuestElevation.ts +1 -0
- package/src/requestOrigin.ts +64 -0
- package/src/resolvers/AgentRunStatusPublisher.ts +27 -2
- package/src/resolvers/AgentSessionResolver.ts +6 -2
- package/src/resolvers/AutotagPipelineResolver.ts +59 -14
- package/src/resolvers/ConversationRunEventsResolver.ts +2 -0
- package/src/resolvers/ExecuteRemoteOperationResolver.ts +41 -9
- package/src/resolvers/GetDataResolver.ts +35 -13
- package/src/resolvers/InfoResolver.ts +1 -1
- package/src/resolvers/IntegrationDiscoveryResolver.ts +1522 -203
- package/src/resolvers/MCPResolver.ts +15 -2
- package/src/resolvers/QuerySystemUserResolver.ts +211 -100
- package/src/resolvers/RealtimeBridgeResolver.ts +1 -0
- package/src/resolvers/RealtimeClientSessionResolver.ts +40 -7
- package/src/resolvers/RemoteBrowserActionResolver.ts +242 -32
- package/src/resolvers/RunAIAgentResolver.ts +265 -86
- package/src/resolvers/RunAIPromptResolver.ts +18 -18
- package/src/resolvers/RunTemplateResolver.ts +3 -1
- package/src/resolvers/SqlLoggingConfigResolver.ts +7 -9
- package/src/resolvers/TagGovernanceResolver.ts +65 -0
- package/src/resolvers/TelemetryResolver.ts +44 -9
- package/src/resolvers/__tests__/GetDataResolver.tokens.test.ts +52 -0
- package/src/resolvers/meetingRecordingRegistration.ts +2 -0
- package/src/resolvers/meetings/meetingMutations.ts +37 -7
- package/src/rest/MediaAccessKeys.ts +6 -2
- package/src/rest/OAuthCallbackHandler.ts +13 -5
- package/src/rest/RealtimeRecordingHandler.ts +5 -9
- package/src/rest/ViewOperationsHandler.ts +2 -1
- package/src/rest/setupRESTEndpoints.ts +11 -2
- package/src/services/DurableEntityActionTaskSubmitter.ts +10 -2
- package/src/services/TaskGraphActionRunner.ts +2 -23
- package/src/services/TaskGraphAgentRunner.ts +43 -0
- package/src/services/TaskGraphContinuationDeliverer.ts +1 -0
- package/src/services/WorkQueueHostService.ts +26 -4
- package/src/services/workQueueConfig.ts +5 -5
- package/src/sharedCache.ts +1 -1
- package/src/types.ts +18 -0
- package/src/util.ts +55 -3
- package/src/services/TaskOrchestration-Integration.md +0 -188
|
@@ -37,9 +37,12 @@ import { configInfo, type MagicLinkConfig } from '../../config.js';
|
|
|
37
37
|
import { CreateIsolatedProvider } from '../../isolatedProvider.js';
|
|
38
38
|
import { MagicLinkKeyManager } from './MagicLinkKeys.js';
|
|
39
39
|
import { GenerateRawToken, GenerateSessionId, HashToken, EvaluateInvite, BuildSessionClaims, BuildConsumeInvitePostgresSQL, CanIssueInvites, IsRoleGrantable, MAGIC_LINK_TOKEN_PREFIX } from './magicLinkCore.js';
|
|
40
|
+
import type { IServerExtensionMagicLinkService } from '@memberjunction/server-extensions-core';
|
|
40
41
|
import type {
|
|
41
42
|
CreateMagicLinkInviteParams,
|
|
42
43
|
CreateMagicLinkInviteResult,
|
|
44
|
+
ManageMagicLinkInviteResult,
|
|
45
|
+
MagicLinkJWTClaims,
|
|
43
46
|
RedeemMagicLinkResult,
|
|
44
47
|
RedeemAuditContext,
|
|
45
48
|
} from './types.js';
|
|
@@ -75,11 +78,53 @@ interface ProvisionResult {
|
|
|
75
78
|
error?: string;
|
|
76
79
|
}
|
|
77
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Validates that a candidate `redirectUrl` is a well-formed `http:` or `https:` URL whose
|
|
83
|
+
* origin matches at least one concrete normalized entry in `allowedOrigins`.
|
|
84
|
+
* Wildcard `'*'` entries are explicitly ignored/rejected so session tokens can never be
|
|
85
|
+
* redirected to unvalidated origins.
|
|
86
|
+
*/
|
|
87
|
+
export function IsMagicLinkRedirectUrlAllowed(redirectUrl: string, allowedOrigins: readonly string[]): boolean {
|
|
88
|
+
const trimmed = redirectUrl.trim();
|
|
89
|
+
if (!trimmed) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
let parsed: URL;
|
|
93
|
+
try {
|
|
94
|
+
parsed = new URL(trimmed);
|
|
95
|
+
} catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
const normalizedTargetOrigin = parsed.origin.toLowerCase();
|
|
102
|
+
for (const rawAllowed of allowedOrigins) {
|
|
103
|
+
const candidate = (rawAllowed ?? '').trim();
|
|
104
|
+
if (!candidate || candidate === '*') {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
const allowedUrl = new URL(candidate);
|
|
109
|
+
if (allowedUrl.origin.toLowerCase() === normalizedTargetOrigin) {
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
} catch {
|
|
113
|
+
if (candidate.replace(/\/$/, '').toLowerCase() === normalizedTargetOrigin) {
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
|
|
78
121
|
/**
|
|
79
122
|
* Magic-link service. One instance per server, constructed with the resolved
|
|
80
|
-
* public URL and the validated config.
|
|
123
|
+
* public URL and the validated config. Implements {@link IServerExtensionMagicLinkService}
|
|
124
|
+
* so `BaseServerExtension` implementations can mint, revoke, extend, and verify invites
|
|
125
|
+
* via `context.services.GetMagicLinkService?.()`.
|
|
81
126
|
*/
|
|
82
|
-
export class MagicLinkService {
|
|
127
|
+
export class MagicLinkService implements IServerExtensionMagicLinkService {
|
|
83
128
|
constructor(
|
|
84
129
|
private readonly publicUrl: string,
|
|
85
130
|
private readonly config: MagicLinkConfig,
|
|
@@ -87,7 +132,7 @@ export class MagicLinkService {
|
|
|
87
132
|
|
|
88
133
|
/**
|
|
89
134
|
* Creates a single-use, app-scoped invite, persists its hashed token, and
|
|
90
|
-
* (when a communication provider is configured) emails the link.
|
|
135
|
+
* (when a communication provider is configured and `params.SendEmail !== false`) emails the link.
|
|
91
136
|
*
|
|
92
137
|
* @param params invite parameters
|
|
93
138
|
* @param creatingUser the authenticated internal user issuing the invite
|
|
@@ -110,6 +155,15 @@ export class MagicLinkService {
|
|
|
110
155
|
return { success: false, errorCode: 'forbidden', error: 'Not authorized to issue magic-link invites.' };
|
|
111
156
|
}
|
|
112
157
|
|
|
158
|
+
const validatedRedirectUrl = params.RedirectUrl?.trim() ? params.RedirectUrl.trim() : undefined;
|
|
159
|
+
if (validatedRedirectUrl && !IsMagicLinkRedirectUrlAllowed(validatedRedirectUrl, this.allowedRedirectOrigins())) {
|
|
160
|
+
return {
|
|
161
|
+
success: false,
|
|
162
|
+
errorCode: 'invalid_redirect_url',
|
|
163
|
+
error: `RedirectUrl '${validatedRedirectUrl}' is not permitted by the server's allowed frontend origins.`,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
113
167
|
const roleId = params.RoleId ?? this.resolveRestrictedRoleId(md);
|
|
114
168
|
if (!roleId) {
|
|
115
169
|
return { success: false, error: `Restricted role '${this.config.restrictedRoleName}' not found and no roleId supplied.` };
|
|
@@ -136,12 +190,13 @@ export class MagicLinkService {
|
|
|
136
190
|
}
|
|
137
191
|
|
|
138
192
|
const rawToken = GenerateRawToken();
|
|
193
|
+
const tokenHash = HashToken(rawToken);
|
|
139
194
|
const expiresInHours = params.ExpiresInHours ?? this.config.defaultExpiresInHours;
|
|
140
195
|
const expiresAt = new Date(Date.now() + expiresInHours * 3600 * 1000);
|
|
141
196
|
|
|
142
197
|
const invite = await md.GetEntityObject<MJMagicLinkInviteEntity>(INVITE_ENTITY, creatingUser);
|
|
143
198
|
invite.NewRecord();
|
|
144
|
-
invite.TokenHash =
|
|
199
|
+
invite.TokenHash = tokenHash;
|
|
145
200
|
invite.Email = params.email;
|
|
146
201
|
invite.ApplicationID = params.ApplicationId;
|
|
147
202
|
invite.RoleID = roleId;
|
|
@@ -161,10 +216,22 @@ export class MagicLinkService {
|
|
|
161
216
|
// multi-scope reads. Best-effort so a child-row hiccup never fails issuance.
|
|
162
217
|
await this.writeInviteScopeChildRows(invite.ID, params.ApplicationId, roleId, creatingUser, md);
|
|
163
218
|
|
|
164
|
-
|
|
219
|
+
let redirectBindingToken: string | undefined;
|
|
220
|
+
if (validatedRedirectUrl) {
|
|
221
|
+
if (!MagicLinkKeyManager.Instance.IsInitialized) {
|
|
222
|
+
MagicLinkKeyManager.Instance.Initialize(this.config.rsaPrivateKey);
|
|
223
|
+
}
|
|
224
|
+
redirectBindingToken = MagicLinkKeyManager.Instance.SignRedirectBinding(
|
|
225
|
+
tokenHash,
|
|
226
|
+
validatedRedirectUrl,
|
|
227
|
+
Math.floor(expiresAt.getTime() / 1000),
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const redemptionUrl = this.buildRedemptionUrl(rawToken, redirectBindingToken);
|
|
165
232
|
|
|
166
233
|
let emailSent = false;
|
|
167
|
-
if (this.config.communicationProvider) {
|
|
234
|
+
if (params.SendEmail !== false && this.config.communicationProvider) {
|
|
168
235
|
emailSent = await this.sendInviteEmail(params.email, redemptionUrl, app.Name, creatingUser);
|
|
169
236
|
}
|
|
170
237
|
|
|
@@ -172,7 +239,7 @@ export class MagicLinkService {
|
|
|
172
239
|
success: true,
|
|
173
240
|
inviteId: invite.ID,
|
|
174
241
|
redemptionUrl,
|
|
175
|
-
// Surface the raw token only when we couldn't deliver it ourselves.
|
|
242
|
+
// Surface the raw token only when we couldn't (or were asked not to) deliver it ourselves.
|
|
176
243
|
rawToken: emailSent ? undefined : rawToken,
|
|
177
244
|
emailSent,
|
|
178
245
|
expiresAt: expiresAt.toISOString(),
|
|
@@ -183,6 +250,158 @@ export class MagicLinkService {
|
|
|
183
250
|
}
|
|
184
251
|
}
|
|
185
252
|
|
|
253
|
+
/**
|
|
254
|
+
* Revokes an existing invite so subsequent redemptions fail with `'revoked'`.
|
|
255
|
+
* Requires the acting user to hold invite-issuance authority (`CanIssueInvites`).
|
|
256
|
+
*/
|
|
257
|
+
public async RevokeInvite(
|
|
258
|
+
inviteId: string,
|
|
259
|
+
actingUser: UserInfo,
|
|
260
|
+
provider?: IMetadataProvider,
|
|
261
|
+
): Promise<ManageMagicLinkInviteResult> {
|
|
262
|
+
try {
|
|
263
|
+
const callerRoleNames = (actingUser.UserRoles ?? []).map((r) => r.Role).filter((n): n is string => !!n);
|
|
264
|
+
if (!CanIssueInvites(actingUser.Type, callerRoleNames, this.config.inviteIssuerRoleNames)) {
|
|
265
|
+
return { success: false, errorCode: 'forbidden', error: 'Not authorized to revoke magic-link invites.' };
|
|
266
|
+
}
|
|
267
|
+
if (!inviteId?.trim()) {
|
|
268
|
+
return { success: false, errorCode: 'not_found', error: 'Invite ID is required.' };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const md = provider ?? Metadata.Provider;
|
|
272
|
+
const invite = await md.GetEntityObject<MJMagicLinkInviteEntity>(INVITE_ENTITY, actingUser);
|
|
273
|
+
const loaded = await invite.Load(inviteId.trim());
|
|
274
|
+
if (!loaded) {
|
|
275
|
+
return { success: false, errorCode: 'not_found', error: `Invite '${inviteId}' not found.` };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (invite.Status === 'Revoked') {
|
|
279
|
+
return {
|
|
280
|
+
success: true,
|
|
281
|
+
inviteId: invite.ID,
|
|
282
|
+
status: invite.Status,
|
|
283
|
+
expiresAt: invite.ExpiresAt.toISOString(),
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
invite.Status = 'Revoked';
|
|
288
|
+
if (!(await invite.Save())) {
|
|
289
|
+
return {
|
|
290
|
+
success: false,
|
|
291
|
+
errorCode: 'server_error',
|
|
292
|
+
error: `Failed to revoke invite: ${invite.LatestResult?.CompleteMessage ?? 'unknown error'}`,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
success: true,
|
|
298
|
+
inviteId: invite.ID,
|
|
299
|
+
status: invite.Status,
|
|
300
|
+
expiresAt: invite.ExpiresAt.toISOString(),
|
|
301
|
+
};
|
|
302
|
+
} catch (e) {
|
|
303
|
+
LogError(e);
|
|
304
|
+
return { success: false, errorCode: 'server_error', error: e instanceof Error ? e.message : String(e) };
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Updates the expiration timestamp of an existing non-revoked, non-consumed invite.
|
|
310
|
+
* Requires the acting user to hold invite-issuance authority (`CanIssueInvites`).
|
|
311
|
+
*/
|
|
312
|
+
public async UpdateInviteExpiry(
|
|
313
|
+
inviteId: string,
|
|
314
|
+
expiresAt: Date,
|
|
315
|
+
actingUser: UserInfo,
|
|
316
|
+
provider?: IMetadataProvider,
|
|
317
|
+
): Promise<ManageMagicLinkInviteResult> {
|
|
318
|
+
try {
|
|
319
|
+
const callerRoleNames = (actingUser.UserRoles ?? []).map((r) => r.Role).filter((n): n is string => !!n);
|
|
320
|
+
if (!CanIssueInvites(actingUser.Type, callerRoleNames, this.config.inviteIssuerRoleNames)) {
|
|
321
|
+
return { success: false, errorCode: 'forbidden', error: 'Not authorized to update magic-link invites.' };
|
|
322
|
+
}
|
|
323
|
+
if (!(expiresAt instanceof Date) || Number.isNaN(expiresAt.getTime())) {
|
|
324
|
+
return { success: false, errorCode: 'invalid_expiry', error: 'expiresAt must be a valid Date.' };
|
|
325
|
+
}
|
|
326
|
+
if (!inviteId?.trim()) {
|
|
327
|
+
return { success: false, errorCode: 'not_found', error: 'Invite ID is required.' };
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const md = provider ?? Metadata.Provider;
|
|
331
|
+
const invite = await md.GetEntityObject<MJMagicLinkInviteEntity>(INVITE_ENTITY, actingUser);
|
|
332
|
+
const loaded = await invite.Load(inviteId.trim());
|
|
333
|
+
if (!loaded) {
|
|
334
|
+
return { success: false, errorCode: 'not_found', error: `Invite '${inviteId}' not found.` };
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (invite.Status === 'Revoked' || invite.Status === 'Consumed') {
|
|
338
|
+
return {
|
|
339
|
+
success: false,
|
|
340
|
+
errorCode: 'invalid_state',
|
|
341
|
+
error: `Cannot update expiry on invite '${invite.ID}' because its status is '${invite.Status}'.`,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const nowMs = Date.now();
|
|
346
|
+
invite.ExpiresAt = expiresAt;
|
|
347
|
+
if (invite.Status === 'Expired' && expiresAt.getTime() > nowMs && invite.UseCount < invite.MaxUses) {
|
|
348
|
+
invite.Status = 'Active';
|
|
349
|
+
} else if (invite.Status === 'Active' && expiresAt.getTime() <= nowMs) {
|
|
350
|
+
invite.Status = 'Expired';
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (!(await invite.Save())) {
|
|
354
|
+
return {
|
|
355
|
+
success: false,
|
|
356
|
+
errorCode: 'server_error',
|
|
357
|
+
error: `Failed to update invite expiry: ${invite.LatestResult?.CompleteMessage ?? 'unknown error'}`,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return {
|
|
362
|
+
success: true,
|
|
363
|
+
inviteId: invite.ID,
|
|
364
|
+
status: invite.Status,
|
|
365
|
+
expiresAt: invite.ExpiresAt.toISOString(),
|
|
366
|
+
};
|
|
367
|
+
} catch (e) {
|
|
368
|
+
LogError(e);
|
|
369
|
+
return { success: false, errorCode: 'server_error', error: e instanceof Error ? e.message : String(e) };
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Verifies a magic-link session JWT against this service's configured issuer and audience.
|
|
375
|
+
*/
|
|
376
|
+
public VerifySessionToken(token: string): MagicLinkJWTClaims | null {
|
|
377
|
+
if (!token || !MagicLinkKeyManager.Instance.IsInitialized) {
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
return MagicLinkKeyManager.Instance.VerifySessionToken(token, {
|
|
381
|
+
issuer: this.publicUrl,
|
|
382
|
+
audience: this.config.audience,
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Verifies a signed redirect binding (`rt`) against the raw invite token's hash AND the host's
|
|
388
|
+
* allowed frontend origins. Returns the verified redirect URL, or `undefined` if absent/invalid.
|
|
389
|
+
*/
|
|
390
|
+
public ResolveVerifiedRedirectUrl(rawToken: string, redirectBindingToken: string | undefined): string | undefined {
|
|
391
|
+
if (!rawToken || !redirectBindingToken || !MagicLinkKeyManager.Instance.IsInitialized) {
|
|
392
|
+
return undefined;
|
|
393
|
+
}
|
|
394
|
+
const tokenHash = HashToken(rawToken);
|
|
395
|
+
const boundUrl = MagicLinkKeyManager.Instance.VerifyRedirectBinding(redirectBindingToken, tokenHash);
|
|
396
|
+
if (!boundUrl) {
|
|
397
|
+
return undefined;
|
|
398
|
+
}
|
|
399
|
+
if (!IsMagicLinkRedirectUrlAllowed(boundUrl, this.allowedRedirectOrigins())) {
|
|
400
|
+
return undefined;
|
|
401
|
+
}
|
|
402
|
+
return boundUrl;
|
|
403
|
+
}
|
|
404
|
+
|
|
186
405
|
/**
|
|
187
406
|
* Redeems a raw token: validates the invite, atomically consumes one use,
|
|
188
407
|
* provisions/links the user with the restricted role + app, and mints a
|
|
@@ -420,10 +639,31 @@ export class MagicLinkService {
|
|
|
420
639
|
return role?.ID;
|
|
421
640
|
}
|
|
422
641
|
|
|
423
|
-
/** Builds the shareable redemption URL. */
|
|
424
|
-
private buildRedemptionUrl(rawToken: string): string {
|
|
642
|
+
/** Builds the shareable redemption URL (including `&rt=...` when a per-invite RedirectUrl was signed). */
|
|
643
|
+
private buildRedemptionUrl(rawToken: string, redirectBindingToken?: string): string {
|
|
425
644
|
const base = this.publicUrl.replace(/\/$/, '');
|
|
426
|
-
|
|
645
|
+
const rtParam = redirectBindingToken ? `&rt=${encodeURIComponent(redirectBindingToken)}` : '';
|
|
646
|
+
return `${base}/magic-link/redeem?token=${encodeURIComponent(rawToken)}${rtParam}`;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Collects concrete frontend origins a caller-supplied per-invite `RedirectUrl` is permitted
|
|
651
|
+
* to target (`explorerUrl`, `magicLink.allowedRedirectOrigins`, and non-wildcard `cors.allowedOrigins`).
|
|
652
|
+
*/
|
|
653
|
+
private allowedRedirectOrigins(): string[] {
|
|
654
|
+
const origins: string[] = [];
|
|
655
|
+
const candidates: readonly (string | undefined)[] = [
|
|
656
|
+
this.config.explorerUrl ?? configInfo.magicLink?.explorerUrl,
|
|
657
|
+
...(this.config.allowedRedirectOrigins ?? configInfo.magicLink?.allowedRedirectOrigins ?? []),
|
|
658
|
+
...(configInfo.cors?.allowedOrigins ?? []),
|
|
659
|
+
];
|
|
660
|
+
for (const raw of candidates) {
|
|
661
|
+
const trimmed = raw?.trim();
|
|
662
|
+
if (trimmed && trimmed !== '*') {
|
|
663
|
+
origins.push(trimmed);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
return origins;
|
|
427
667
|
}
|
|
428
668
|
|
|
429
669
|
/**
|
|
@@ -8,7 +8,7 @@ export {
|
|
|
8
8
|
type InviteEvaluationInput,
|
|
9
9
|
} from './magicLinkCore.js';
|
|
10
10
|
export { MagicLinkKeyManager } from './MagicLinkKeys.js';
|
|
11
|
-
export { MagicLinkService } from './MagicLinkService.js';
|
|
11
|
+
export { MagicLinkService, IsMagicLinkRedirectUrlAllowed } from './MagicLinkService.js';
|
|
12
12
|
export {
|
|
13
13
|
CreateMagicLinkHandler, createMagicLinkHandler,
|
|
14
14
|
CreateMagicLinkJwksRouter, createMagicLinkJwksRouter,
|
|
@@ -32,9 +32,12 @@ export function escapeHtml(s: string): string {
|
|
|
32
32
|
* @param token the raw magic-link token (embedded in a hidden field)
|
|
33
33
|
* @param actionPath the same-origin path the form POSTs to (e.g. `/magic-link/redeem`)
|
|
34
34
|
*/
|
|
35
|
-
export function BuildRedeemLandingHtml(token: string, actionPath: string): string {
|
|
35
|
+
export function BuildRedeemLandingHtml(token: string, actionPath: string, redirectBindingToken?: string): string {
|
|
36
36
|
const t = EscapeHtml(token);
|
|
37
37
|
const action = EscapeHtml(actionPath);
|
|
38
|
+
const rtInput = redirectBindingToken
|
|
39
|
+
? `\n <input type="hidden" name="rt" value="${EscapeHtml(redirectBindingToken)}">`
|
|
40
|
+
: '';
|
|
38
41
|
// Hardcoded colors are intentional here — this is a standalone server-rendered
|
|
39
42
|
// HTML response, not Angular component CSS, so the design-token rule doesn't apply.
|
|
40
43
|
return `<!doctype html>
|
|
@@ -58,7 +61,7 @@ export function BuildRedeemLandingHtml(token: string, actionPath: string): strin
|
|
|
58
61
|
<h1>Sign in to continue</h1>
|
|
59
62
|
<p>Click below to securely sign in with your invitation link.</p>
|
|
60
63
|
<form method="POST" action="${action}">
|
|
61
|
-
<input type="hidden" name="token" value="${t}"
|
|
64
|
+
<input type="hidden" name="token" value="${t}">${rtInput}
|
|
62
65
|
<button type="submit">Continue to sign in</button>
|
|
63
66
|
</form>
|
|
64
67
|
</div>
|
|
@@ -67,6 +70,6 @@ export function BuildRedeemLandingHtml(token: string, actionPath: string): strin
|
|
|
67
70
|
}
|
|
68
71
|
|
|
69
72
|
/** @deprecated Use {@link BuildRedeemLandingHtml}. */
|
|
70
|
-
export function buildRedeemLandingHtml(token: string, actionPath: string): string {
|
|
71
|
-
return BuildRedeemLandingHtml(token, actionPath);
|
|
73
|
+
export function buildRedeemLandingHtml(token: string, actionPath: string, redirectBindingToken?: string): string {
|
|
74
|
+
return BuildRedeemLandingHtml(token, actionPath, redirectBindingToken);
|
|
72
75
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* @fileoverview Types for the MemberJunction magic-link feature.
|
|
3
3
|
* @module @memberjunction/server/auth/magicLink
|
|
4
4
|
*/
|
|
5
|
+
import type { MJMagicLinkInviteEntity } from '@memberjunction/core-entities';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* One unit of scope a magic-link session holds. The union of these across all links
|
|
@@ -113,6 +114,19 @@ export interface CreateMagicLinkInviteParams {
|
|
|
113
114
|
/** Optional given/family name to seed the provisioned user. */
|
|
114
115
|
firstName?: string; // case-violation-ok-legacy-back-compat: optional, and the old name is also read off a value the checker cannot type; renaming it stays assignable and silently yields undefined
|
|
115
116
|
lastName?: string; // case-violation-ok-legacy-back-compat: optional, and the old name is also read off a value the checker cannot type; renaming it stays assignable and silently yields undefined
|
|
117
|
+
/**
|
|
118
|
+
* Optional per-invite post-redemption redirect URL. Must match an explicit allowed frontend
|
|
119
|
+
* origin (`magicLink.explorerUrl`, `magicLink.allowedRedirectOrigins`, or non-wildcard
|
|
120
|
+
* `cors.allowedOrigins`; wildcard `'*'` is always rejected). Cryptographically bound to the
|
|
121
|
+
* invite's token hash via RS256 so it cannot be tampered with or swapped across invites.
|
|
122
|
+
*/
|
|
123
|
+
RedirectUrl?: string;
|
|
124
|
+
/**
|
|
125
|
+
* When `false`, skips MJServer's default invite email delivery and returns `rawToken` +
|
|
126
|
+
* `redemptionUrl` so the caller (e.g. a `BaseServerExtension`) can send its own branded message.
|
|
127
|
+
* Defaults to `true` when a communication provider is configured.
|
|
128
|
+
*/
|
|
129
|
+
SendEmail?: boolean;
|
|
116
130
|
}
|
|
117
131
|
|
|
118
132
|
/** Result of creating an invite. */
|
|
@@ -133,11 +147,22 @@ export interface CreateMagicLinkInviteResult {
|
|
|
133
147
|
expiresAt?: string; // case-violation-ok-legacy-back-compat: the type crosses a serialization boundary (JSON / HTTP body), so this member name is part of a wire or on-disk shape
|
|
134
148
|
error?: string; // case-violation-ok-legacy-back-compat: the type crosses a serialization boundary (JSON / HTTP body), so this member name is part of a wire or on-disk shape
|
|
135
149
|
/**
|
|
136
|
-
* Set when the request was rejected for authorization reasons:
|
|
150
|
+
* Set when the request was rejected for authorization or validation reasons:
|
|
137
151
|
* `forbidden` — caller may not issue invites; `invalid_role` — the requested
|
|
138
|
-
* role is not grantable via magic-link
|
|
152
|
+
* role is not grantable via magic-link; `invalid_redirect_url` — `RedirectUrl`
|
|
153
|
+
* failed HTTP(S) or origin allowlist validation. Drives the HTTP status in the router.
|
|
139
154
|
*/
|
|
140
|
-
errorCode?: 'forbidden' | 'invalid_role'; // case-violation-ok-legacy-back-compat: the type crosses a serialization boundary (JSON / HTTP body), so this member name is part of a wire or on-disk shape
|
|
155
|
+
errorCode?: 'forbidden' | 'invalid_role' | 'invalid_redirect_url'; // case-violation-ok-legacy-back-compat: the type crosses a serialization boundary (JSON / HTTP body), so this member name is part of a wire or on-disk shape
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Result of revoking or updating expiry on an existing invite. */
|
|
159
|
+
export interface ManageMagicLinkInviteResult {
|
|
160
|
+
success: boolean; // case-violation-ok-legacy-back-compat: matches MagicLink result wire conventions
|
|
161
|
+
inviteId?: string; // case-violation-ok-legacy-back-compat: matches MagicLink result wire conventions
|
|
162
|
+
status?: MJMagicLinkInviteEntity['Status']; // case-violation-ok-legacy-back-compat: matches MagicLink result wire conventions
|
|
163
|
+
expiresAt?: string; // case-violation-ok-legacy-back-compat: matches MagicLink result wire conventions
|
|
164
|
+
error?: string; // case-violation-ok-legacy-back-compat: matches MagicLink result wire conventions
|
|
165
|
+
errorCode?: 'forbidden' | 'not_found' | 'invalid_state' | 'invalid_expiry' | 'server_error'; // case-violation-ok-legacy-back-compat: matches MagicLink result wire conventions
|
|
141
166
|
}
|
|
142
167
|
|
|
143
168
|
/** Why a redemption failed. */
|
package/src/auth/newUsers.ts
CHANGED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Settings for the in-process background sweeps that keep an idle MJAPI talking to its database:
|
|
5
|
+
* the AI Agent Session janitor, the realtime bridge orphan reconciler, the human-handoff offer
|
|
6
|
+
* expiry sweep, the orphaned conversation detail reconciler and the agent-run watchdog. Each used to start unconditionally on a fixed
|
|
7
|
+
* interval, so a serverless database (Azure SQL serverless, for one) could never auto-pause. Every
|
|
8
|
+
* switch is described, with what turning it off costs, in the Serverless Database Auto-Pause guide
|
|
9
|
+
* (`guides/SERVERLESS_DATABASE_AUTO_PAUSE_GUIDE.md`).
|
|
10
|
+
*
|
|
11
|
+
* Like the telemetry and realtime units, the schemas and env-var readers live here rather than in
|
|
12
|
+
* config.ts so they are unit-testable without config.ts's full dependency graph. The env readers
|
|
13
|
+
* feed `DEFAULT_SERVER_CONFIG` (the base of the merge), so `mj.config.cjs` still wins over them.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** Shipped janitor defaults (the values MJAPI has always used). */
|
|
17
|
+
export const DEFAULT_SESSION_JANITOR_SETTINGS = {
|
|
18
|
+
enabled: true,
|
|
19
|
+
sweepIntervalMs: 60_000,
|
|
20
|
+
closeThresholdMinutes: 15,
|
|
21
|
+
} as const;
|
|
22
|
+
|
|
23
|
+
/** Shipped realtime bridge orphan reconciler defaults (the values MJAPI has always used). */
|
|
24
|
+
export const DEFAULT_BRIDGE_RECONCILER_SETTINGS = {
|
|
25
|
+
enabled: true,
|
|
26
|
+
sweepIntervalMs: 600_000,
|
|
27
|
+
} as const;
|
|
28
|
+
|
|
29
|
+
/** Shipped human-handoff offer expiry sweep defaults (the values MJAPI has always used). */
|
|
30
|
+
export const DEFAULT_HANDOFF_OFFER_SWEEP_SETTINGS = {
|
|
31
|
+
enabled: true,
|
|
32
|
+
sweepIntervalMs: 15_000,
|
|
33
|
+
} as const;
|
|
34
|
+
|
|
35
|
+
/** Shipped orphaned conversation detail reconciler defaults (the values MJAPI has always used). */
|
|
36
|
+
export const DEFAULT_CONVERSATION_DETAIL_RECONCILER_SETTINGS = {
|
|
37
|
+
enabled: true,
|
|
38
|
+
sweepIntervalMs: 300_000,
|
|
39
|
+
} as const;
|
|
40
|
+
|
|
41
|
+
/** Shipped watchdog defaults (the values MJAPI has always used). */
|
|
42
|
+
export const DEFAULT_AGENT_RUN_WATCHDOG_SETTINGS = {
|
|
43
|
+
sweepIntervalMs: 300_000,
|
|
44
|
+
heartbeatIntervalMs: 30_000,
|
|
45
|
+
staleThresholdMinutes: 5,
|
|
46
|
+
} as const;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* `agentSessions.janitor` (env `MJ_SESSION_JANITOR_ENABLED`, `MJ_SESSION_JANITOR_SWEEP_INTERVAL_MS`).
|
|
50
|
+
* `enabled: false` turns off the periodic tick (stale-session and max-duration close sweeps, then
|
|
51
|
+
* realtime recording recovery); the one-time startup recovery and the graceful-shutdown drain still
|
|
52
|
+
* run, so this instance's own orphaned sessions are closed on restart. See the Serverless Database
|
|
53
|
+
* Auto-Pause guide.
|
|
54
|
+
*/
|
|
55
|
+
export const SessionJanitorSchema = z.object({
|
|
56
|
+
enabled: z.boolean().optional().default(DEFAULT_SESSION_JANITOR_SETTINGS.enabled),
|
|
57
|
+
sweepIntervalMs: z.coerce.number().positive().optional().default(DEFAULT_SESSION_JANITOR_SETTINGS.sweepIntervalMs),
|
|
58
|
+
closeThresholdMinutes: z.coerce.number().positive().optional().default(DEFAULT_SESSION_JANITOR_SETTINGS.closeThresholdMinutes),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* `agentSessions.bridgeReconciler` (env `MJ_BRIDGE_RECONCILER_ENABLED`,
|
|
63
|
+
* `MJ_BRIDGE_RECONCILER_SWEEP_INTERVAL_MS`): closes the realtime bridges an earlier boot of this
|
|
64
|
+
* instance left open. `enabled: false` keeps the one pass at startup and schedules no repeat. See
|
|
65
|
+
* the Serverless Database Auto-Pause guide.
|
|
66
|
+
*/
|
|
67
|
+
export const BridgeReconcilerSchema = z.object({
|
|
68
|
+
enabled: z.boolean().optional().default(DEFAULT_BRIDGE_RECONCILER_SETTINGS.enabled),
|
|
69
|
+
sweepIntervalMs: z.coerce.number().positive().optional().default(DEFAULT_BRIDGE_RECONCILER_SETTINGS.sweepIntervalMs),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* `agentSessions.handoffOfferExpiry` (env `MJ_HANDOFF_OFFER_EXPIRY_ENABLED`,
|
|
74
|
+
* `MJ_HANDOFF_OFFER_EXPIRY_SWEEP_INTERVAL_MS`): marks agent-to-person handoff offers (`MJ: Interaction
|
|
75
|
+
* Offers`) `Expired` once past their deadline and tells the person's browser. `enabled: false` stops
|
|
76
|
+
* the periodic read; overdue offers still read as expired everywhere, but their rows stay `Pending`
|
|
77
|
+
* and no expiry notice is pushed. See the Serverless Database Auto-Pause guide.
|
|
78
|
+
*/
|
|
79
|
+
export const HandoffOfferSweepSchema = z.object({
|
|
80
|
+
enabled: z.boolean().optional().default(DEFAULT_HANDOFF_OFFER_SWEEP_SETTINGS.enabled),
|
|
81
|
+
sweepIntervalMs: z.coerce.number().positive().optional().default(DEFAULT_HANDOFF_OFFER_SWEEP_SETTINGS.sweepIntervalMs),
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
/** `agentSessions`: settings for AI Agent Sessions (realtime/voice and other long-lived agent sessions). */
|
|
85
|
+
export const AgentSessionsSchema = z.object({
|
|
86
|
+
janitor: SessionJanitorSchema.optional().default({}),
|
|
87
|
+
bridgeReconciler: BridgeReconcilerSchema.optional().default({}),
|
|
88
|
+
handoffOfferExpiry: HandoffOfferSweepSchema.optional().default({}),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* `conversationDetailReconciler` (env `MJ_CONVERSATION_DETAIL_RECONCILER_ENABLED`,
|
|
93
|
+
* `MJ_CONVERSATION_DETAIL_RECONCILER_SWEEP_INTERVAL_MS`): closes conversation details a run that
|
|
94
|
+
* already ended left `In-Progress`. `enabled: false` keeps the one pass at startup and schedules no
|
|
95
|
+
* repeat. See the Serverless Database Auto-Pause guide.
|
|
96
|
+
*/
|
|
97
|
+
export const ConversationDetailReconcilerSchema = z.object({
|
|
98
|
+
enabled: z.boolean().optional().default(DEFAULT_CONVERSATION_DETAIL_RECONCILER_SETTINGS.enabled),
|
|
99
|
+
sweepIntervalMs: z.coerce.number().positive().optional().default(DEFAULT_CONVERSATION_DETAIL_RECONCILER_SETTINGS.sweepIntervalMs),
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* `agentRunWatchdog` (env `MJ_AGENT_RUN_WATCHDOG_SWEEP_INTERVAL_MS`). The heartbeat runs only while
|
|
104
|
+
* this process has an agent run in flight; once any run has been tracked, the stale-run sweep keeps
|
|
105
|
+
* running every `sweepIntervalMs` so it also closes runs other, crashed instances left `Running`.
|
|
106
|
+
* `sweepIntervalMs: 0` turns the periodic sweep off; the startup sweep still runs. See the
|
|
107
|
+
* Serverless Database Auto-Pause guide.
|
|
108
|
+
*/
|
|
109
|
+
export const AgentRunWatchdogSchema = z.object({
|
|
110
|
+
sweepIntervalMs: z.coerce.number().nonnegative().optional().default(DEFAULT_AGENT_RUN_WATCHDOG_SETTINGS.sweepIntervalMs),
|
|
111
|
+
heartbeatIntervalMs: z.coerce.number().positive().optional().default(DEFAULT_AGENT_RUN_WATCHDOG_SETTINGS.heartbeatIntervalMs),
|
|
112
|
+
staleThresholdMinutes: z.coerce.number().positive().optional().default(DEFAULT_AGENT_RUN_WATCHDOG_SETTINGS.staleThresholdMinutes),
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
export type SessionJanitorSettings = z.infer<typeof SessionJanitorSchema>;
|
|
116
|
+
export type BridgeReconcilerSettings = z.infer<typeof BridgeReconcilerSchema>;
|
|
117
|
+
export type HandoffOfferSweepSettings = z.infer<typeof HandoffOfferSweepSchema>;
|
|
118
|
+
export type AgentSessionsSettings = z.infer<typeof AgentSessionsSchema>;
|
|
119
|
+
export type ConversationDetailReconcilerSettings = z.infer<typeof ConversationDetailReconcilerSchema>;
|
|
120
|
+
export type AgentRunWatchdogSettings = z.infer<typeof AgentRunWatchdogSchema>;
|
|
121
|
+
|
|
122
|
+
const TRUE_VALUES: ReadonlySet<string> = new Set(['true', '1', 'yes', 'y', 'on', 't']);
|
|
123
|
+
const FALSE_VALUES: ReadonlySet<string> = new Set(['false', '0', 'no', 'n', 'off', 'f']);
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Reads an on/off switch from an env var, returning `fallback` when it is unset or blank (so an
|
|
127
|
+
* empty variable exported by a deployment script cannot silently flip it). Accepts
|
|
128
|
+
* `true/false`, `1/0`, `yes/no`, `y/n`, `on/off`, `t/f` in any case; throws, naming the variable,
|
|
129
|
+
* on anything else, so a typo fails at startup instead of silently turning a sweep off.
|
|
130
|
+
*
|
|
131
|
+
* @param name The variable's name, for the error message.
|
|
132
|
+
* @param rawEnvValue The variable's raw value.
|
|
133
|
+
* @param fallback The value when the variable is unset or blank.
|
|
134
|
+
*/
|
|
135
|
+
export function EnabledFromEnv(name: string, rawEnvValue: string | undefined | null, fallback: boolean): boolean {
|
|
136
|
+
const normalized = rawEnvValue?.trim().toLowerCase();
|
|
137
|
+
if (!normalized) {
|
|
138
|
+
return fallback;
|
|
139
|
+
}
|
|
140
|
+
if (TRUE_VALUES.has(normalized)) {
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
if (FALSE_VALUES.has(normalized)) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
throw new Error(`Invalid ${name} "${rawEnvValue}". Expected true or false.`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Reads a millisecond interval from an env var, returning `fallback` when it is unset or blank.
|
|
151
|
+
* Throws, naming the variable, on anything that is not a finite number in range, so a deployment
|
|
152
|
+
* typo fails at startup instead of silently running the default (or a zero-delay timer).
|
|
153
|
+
*
|
|
154
|
+
* @param name The variable's name, for the error message.
|
|
155
|
+
* @param rawEnvValue The variable's raw value.
|
|
156
|
+
* @param fallback The value when the variable is unset or blank.
|
|
157
|
+
* @param allowZero Whether `0` is accepted (it means "off" for the settings that allow it).
|
|
158
|
+
*/
|
|
159
|
+
export function IntervalMsFromEnv(name: string, rawEnvValue: string | undefined | null, fallback: number, allowZero: boolean): number {
|
|
160
|
+
const trimmed = rawEnvValue?.trim();
|
|
161
|
+
if (!trimmed) {
|
|
162
|
+
return fallback;
|
|
163
|
+
}
|
|
164
|
+
const value = Number(trimmed);
|
|
165
|
+
const inRange = allowZero ? value >= 0 : value > 0;
|
|
166
|
+
if (!Number.isFinite(value) || !inRange) {
|
|
167
|
+
const rule = allowZero ? 'a number of milliseconds, 0 or greater' : 'a number of milliseconds greater than 0';
|
|
168
|
+
throw new Error(`Invalid ${name} "${rawEnvValue}". Expected ${rule}.`);
|
|
169
|
+
}
|
|
170
|
+
return value;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** The env vars each background sweep reads, keyed by what they set. */
|
|
174
|
+
type SweepEnv = Readonly<Record<string, string | undefined>>;
|
|
175
|
+
|
|
176
|
+
/** `enabled` + `sweepIntervalMs` for one sweep, read from `<prefix>_ENABLED` and `<prefix>_SWEEP_INTERVAL_MS`. */
|
|
177
|
+
function enabledSweepFromEnv(env: SweepEnv, prefix: string, defaults: { enabled: boolean; sweepIntervalMs: number }): { enabled: boolean; sweepIntervalMs: number } {
|
|
178
|
+
return {
|
|
179
|
+
enabled: EnabledFromEnv(`${prefix}_ENABLED`, env[`${prefix}_ENABLED`], defaults.enabled),
|
|
180
|
+
sweepIntervalMs: IntervalMsFromEnv(`${prefix}_SWEEP_INTERVAL_MS`, env[`${prefix}_SWEEP_INTERVAL_MS`], defaults.sweepIntervalMs, false),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** The `agentSessions` block of `DEFAULT_SERVER_CONFIG`, from the environment and the shipped defaults. */
|
|
185
|
+
export function AgentSessionsDefaultsFromEnv(env: SweepEnv): AgentSessionsSettings {
|
|
186
|
+
return {
|
|
187
|
+
janitor: {
|
|
188
|
+
...enabledSweepFromEnv(env, 'MJ_SESSION_JANITOR', DEFAULT_SESSION_JANITOR_SETTINGS),
|
|
189
|
+
closeThresholdMinutes: DEFAULT_SESSION_JANITOR_SETTINGS.closeThresholdMinutes,
|
|
190
|
+
},
|
|
191
|
+
bridgeReconciler: enabledSweepFromEnv(env, 'MJ_BRIDGE_RECONCILER', DEFAULT_BRIDGE_RECONCILER_SETTINGS),
|
|
192
|
+
handoffOfferExpiry: enabledSweepFromEnv(env, 'MJ_HANDOFF_OFFER_EXPIRY', DEFAULT_HANDOFF_OFFER_SWEEP_SETTINGS),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** The `conversationDetailReconciler` block of `DEFAULT_SERVER_CONFIG`. */
|
|
197
|
+
export function ConversationDetailReconcilerDefaultsFromEnv(env: SweepEnv): ConversationDetailReconcilerSettings {
|
|
198
|
+
return enabledSweepFromEnv(env, 'MJ_CONVERSATION_DETAIL_RECONCILER', DEFAULT_CONVERSATION_DETAIL_RECONCILER_SETTINGS);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** The `agentRunWatchdog` block of `DEFAULT_SERVER_CONFIG`. */
|
|
202
|
+
export function AgentRunWatchdogDefaultsFromEnv(env: SweepEnv): AgentRunWatchdogSettings {
|
|
203
|
+
return {
|
|
204
|
+
sweepIntervalMs: IntervalMsFromEnv(
|
|
205
|
+
'MJ_AGENT_RUN_WATCHDOG_SWEEP_INTERVAL_MS',
|
|
206
|
+
env.MJ_AGENT_RUN_WATCHDOG_SWEEP_INTERVAL_MS,
|
|
207
|
+
DEFAULT_AGENT_RUN_WATCHDOG_SETTINGS.sweepIntervalMs,
|
|
208
|
+
true
|
|
209
|
+
),
|
|
210
|
+
heartbeatIntervalMs: DEFAULT_AGENT_RUN_WATCHDOG_SETTINGS.heartbeatIntervalMs,
|
|
211
|
+
staleThresholdMinutes: DEFAULT_AGENT_RUN_WATCHDOG_SETTINGS.staleThresholdMinutes,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
@@ -153,7 +153,8 @@ export const DEFAULT_HIGH_CHURN_SYSTEM_ENTITIES: readonly string[] = [
|
|
|
153
153
|
'MJ: AI Prompt Runs',
|
|
154
154
|
'MJ: AI Agent Runs',
|
|
155
155
|
'MJ: AI Agent Run Steps',
|
|
156
|
-
'MJ:
|
|
156
|
+
'MJ: Diagnostic Session Chunks',
|
|
157
|
+
'MJ: Diagnostic Bundles',
|
|
157
158
|
];
|
|
158
159
|
|
|
159
160
|
/**
|