@memberjunction/server 5.43.0 → 5.45.0
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/agentSessions/ReturningVisitorRecap.d.ts +39 -0
- package/dist/agentSessions/ReturningVisitorRecap.d.ts.map +1 -0
- package/dist/agentSessions/ReturningVisitorRecap.js +198 -0
- package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -0
- package/dist/agentSessions/SessionJanitor.d.ts +13 -0
- package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
- package/dist/agentSessions/SessionJanitor.js +65 -7
- package/dist/agentSessions/SessionJanitor.js.map +1 -1
- package/dist/agentSessions/SessionManager.d.ts.map +1 -1
- package/dist/agentSessions/SessionManager.js +40 -0
- package/dist/agentSessions/SessionManager.js.map +1 -1
- package/dist/agentSessions/index.d.ts.map +1 -1
- package/dist/agentSessions/index.js +3 -1
- package/dist/agentSessions/index.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.d.ts +10 -0
- package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.js +16 -0
- package/dist/auth/magicLink/MagicLinkRouter.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/types.d.ts +26 -0
- package/dist/auth/magicLink/types.d.ts.map +1 -1
- package/dist/config.d.ts +1883 -144
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +160 -36
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +45 -2
- package/dist/context.js.map +1 -1
- package/dist/generated/generated.d.ts +1684 -54
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +57105 -48029
- package/dist/generated/generated.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +13 -0
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +22 -0
- 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 +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +110 -3
- package/dist/index.js.map +1 -1
- package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
- package/dist/integration/CustomColumnPromoter.js +6 -0
- package/dist/integration/CustomColumnPromoter.js.map +1 -1
- package/dist/realtimeWidget/WidgetRouter.d.ts +26 -0
- package/dist/realtimeWidget/WidgetRouter.d.ts.map +1 -0
- package/dist/realtimeWidget/WidgetRouter.js +182 -0
- package/dist/realtimeWidget/WidgetRouter.js.map +1 -0
- package/dist/realtimeWidget/WidgetSessionService.d.ts +265 -0
- package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -0
- package/dist/realtimeWidget/WidgetSessionService.js +477 -0
- package/dist/realtimeWidget/WidgetSessionService.js.map +1 -0
- package/dist/realtimeWidget/host-identity.d.ts +40 -0
- package/dist/realtimeWidget/host-identity.d.ts.map +1 -0
- package/dist/realtimeWidget/host-identity.js +58 -0
- package/dist/realtimeWidget/host-identity.js.map +1 -0
- package/dist/realtimeWidget/index.d.ts +10 -0
- package/dist/realtimeWidget/index.d.ts.map +1 -0
- package/dist/realtimeWidget/index.js +9 -0
- package/dist/realtimeWidget/index.js.map +1 -0
- package/dist/realtimeWidget/visitorIdentity.d.ts +76 -0
- package/dist/realtimeWidget/visitorIdentity.d.ts.map +1 -0
- package/dist/realtimeWidget/visitorIdentity.js +202 -0
- package/dist/realtimeWidget/visitorIdentity.js.map +1 -0
- package/dist/realtimeWidget/widgetCore.d.ts +106 -0
- package/dist/realtimeWidget/widgetCore.d.ts.map +1 -0
- package/dist/realtimeWidget/widgetCore.js +173 -0
- package/dist/realtimeWidget/widgetCore.js.map +1 -0
- package/dist/realtimeWidget/widgetGuestElevation.d.ts +51 -0
- package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -0
- package/dist/realtimeWidget/widgetGuestElevation.js +79 -0
- package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -0
- package/dist/resolvers/ComponentRegistryResolver.d.ts +7 -0
- package/dist/resolvers/ComponentRegistryResolver.d.ts.map +1 -1
- package/dist/resolvers/ComponentRegistryResolver.js +31 -8
- package/dist/resolvers/ComponentRegistryResolver.js.map +1 -1
- package/dist/resolvers/EntityPermissionResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityPermissionResolver.js +1 -2
- package/dist/resolvers/EntityPermissionResolver.js.map +1 -1
- package/dist/resolvers/FileResolver.d.ts +74 -0
- package/dist/resolvers/FileResolver.d.ts.map +1 -1
- package/dist/resolvers/FileResolver.js +211 -2
- package/dist/resolvers/FileResolver.js.map +1 -1
- package/dist/resolvers/QueryResolver.d.ts +6 -4
- package/dist/resolvers/QueryResolver.d.ts.map +1 -1
- package/dist/resolvers/QueryResolver.js +29 -14
- package/dist/resolvers/QueryResolver.js.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.d.ts +9 -6
- package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.js +15 -13
- package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.d.ts +8 -1
- package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.js +28 -4
- package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +91 -2
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +337 -17
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js +17 -2
- package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
- package/dist/resolvers/RingCentralTelephonyResolver.d.ts +27 -0
- package/dist/resolvers/RingCentralTelephonyResolver.d.ts.map +1 -0
- package/dist/resolvers/RingCentralTelephonyResolver.js +87 -0
- package/dist/resolvers/RingCentralTelephonyResolver.js.map +1 -0
- package/dist/resolvers/RunAIAgentResolver.d.ts +12 -3
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +50 -17
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/SearchKnowledgeResolver.d.ts.map +1 -1
- package/dist/resolvers/SearchKnowledgeResolver.js +2 -0
- package/dist/resolvers/SearchKnowledgeResolver.js.map +1 -1
- package/dist/resolvers/TeamsMeetingsResolver.d.ts +28 -0
- package/dist/resolvers/TeamsMeetingsResolver.d.ts.map +1 -0
- package/dist/resolvers/TeamsMeetingsResolver.js +91 -0
- package/dist/resolvers/TeamsMeetingsResolver.js.map +1 -0
- package/dist/resolvers/TelephonyResolver.d.ts +26 -0
- package/dist/resolvers/TelephonyResolver.d.ts.map +1 -0
- package/dist/resolvers/TelephonyResolver.js +86 -0
- package/dist/resolvers/TelephonyResolver.js.map +1 -0
- package/dist/resolvers/TestQuerySQLResolver.d.ts +1 -1
- package/dist/resolvers/TestQuerySQLResolver.d.ts.map +1 -1
- package/dist/resolvers/TestQuerySQLResolver.js +2 -3
- package/dist/resolvers/TestQuerySQLResolver.js.map +1 -1
- package/dist/resolvers/VonageTelephonyResolver.d.ts +26 -0
- package/dist/resolvers/VonageTelephonyResolver.d.ts.map +1 -0
- package/dist/resolvers/VonageTelephonyResolver.js +86 -0
- package/dist/resolvers/VonageTelephonyResolver.js.map +1 -0
- package/dist/resolvers/meetingRecordingRegistration.d.ts +124 -0
- package/dist/resolvers/meetingRecordingRegistration.d.ts.map +1 -0
- package/dist/resolvers/meetingRecordingRegistration.js +311 -0
- package/dist/resolvers/meetingRecordingRegistration.js.map +1 -0
- package/dist/resolvers/peaksSidecar.d.ts +30 -0
- package/dist/resolvers/peaksSidecar.d.ts.map +1 -0
- package/dist/resolvers/peaksSidecar.js +51 -0
- package/dist/resolvers/peaksSidecar.js.map +1 -0
- package/dist/rest/MediaAccessKeys.d.ts +68 -0
- package/dist/rest/MediaAccessKeys.d.ts.map +1 -0
- package/dist/rest/MediaAccessKeys.js +96 -0
- package/dist/rest/MediaAccessKeys.js.map +1 -0
- package/dist/rest/MediaStreamHandler.d.ts +26 -0
- package/dist/rest/MediaStreamHandler.d.ts.map +1 -0
- package/dist/rest/MediaStreamHandler.js +195 -0
- package/dist/rest/MediaStreamHandler.js.map +1 -0
- package/dist/rest/mediaRange.d.ts +41 -0
- package/dist/rest/mediaRange.d.ts.map +1 -0
- package/dist/rest/mediaRange.js +60 -0
- package/dist/rest/mediaRange.js.map +1 -0
- package/dist/telephony/RingCentralTelephonyService.d.ts +115 -0
- package/dist/telephony/RingCentralTelephonyService.d.ts.map +1 -0
- package/dist/telephony/RingCentralTelephonyService.js +262 -0
- package/dist/telephony/RingCentralTelephonyService.js.map +1 -0
- package/dist/telephony/TeamsMeetingsRouter.d.ts +40 -0
- package/dist/telephony/TeamsMeetingsRouter.d.ts.map +1 -0
- package/dist/telephony/TeamsMeetingsRouter.js +96 -0
- package/dist/telephony/TeamsMeetingsRouter.js.map +1 -0
- package/dist/telephony/TeamsMeetingsService.d.ts +113 -0
- package/dist/telephony/TeamsMeetingsService.d.ts.map +1 -0
- package/dist/telephony/TeamsMeetingsService.js +196 -0
- package/dist/telephony/TeamsMeetingsService.js.map +1 -0
- package/dist/telephony/TwilioTelephonyRouter.d.ts +36 -0
- package/dist/telephony/TwilioTelephonyRouter.d.ts.map +1 -0
- package/dist/telephony/TwilioTelephonyRouter.js +143 -0
- package/dist/telephony/TwilioTelephonyRouter.js.map +1 -0
- package/dist/telephony/TwilioTelephonyService.d.ts +95 -0
- package/dist/telephony/TwilioTelephonyService.d.ts.map +1 -0
- package/dist/telephony/TwilioTelephonyService.js +193 -0
- package/dist/telephony/TwilioTelephonyService.js.map +1 -0
- package/dist/telephony/VonageTelephonyRouter.d.ts +41 -0
- package/dist/telephony/VonageTelephonyRouter.d.ts.map +1 -0
- package/dist/telephony/VonageTelephonyRouter.js +201 -0
- package/dist/telephony/VonageTelephonyRouter.js.map +1 -0
- package/dist/telephony/VonageTelephonyService.d.ts +90 -0
- package/dist/telephony/VonageTelephonyService.d.ts.map +1 -0
- package/dist/telephony/VonageTelephonyService.js +191 -0
- package/dist/telephony/VonageTelephonyService.js.map +1 -0
- package/dist/telephony/calendar-scheduler.d.ts +67 -0
- package/dist/telephony/calendar-scheduler.d.ts.map +1 -0
- package/dist/telephony/calendar-scheduler.js +133 -0
- package/dist/telephony/calendar-scheduler.js.map +1 -0
- package/dist/telephony/index.d.ts +29 -0
- package/dist/telephony/index.d.ts.map +1 -0
- package/dist/telephony/index.js +38 -0
- package/dist/telephony/index.js.map +1 -0
- package/dist/telephony/media-upgrade-router.d.ts +33 -0
- package/dist/telephony/media-upgrade-router.d.ts.map +1 -0
- package/dist/telephony/media-upgrade-router.js +56 -0
- package/dist/telephony/media-upgrade-router.js.map +1 -0
- package/dist/telephony/ringcentral-runtime.d.ts +14 -0
- package/dist/telephony/ringcentral-runtime.d.ts.map +1 -0
- package/dist/telephony/ringcentral-runtime.js +18 -0
- package/dist/telephony/ringcentral-runtime.js.map +1 -0
- package/dist/telephony/teams-meetings-runtime.d.ts +16 -0
- package/dist/telephony/teams-meetings-runtime.d.ts.map +1 -0
- package/dist/telephony/teams-meetings-runtime.js +20 -0
- package/dist/telephony/teams-meetings-runtime.js.map +1 -0
- package/dist/telephony/teamsAcsMediaRegistry.d.ts +69 -0
- package/dist/telephony/teamsAcsMediaRegistry.d.ts.map +1 -0
- package/dist/telephony/teamsAcsMediaRegistry.js +118 -0
- package/dist/telephony/teamsAcsMediaRegistry.js.map +1 -0
- package/dist/telephony/telephony-runtime.d.ts +14 -0
- package/dist/telephony/telephony-runtime.d.ts.map +1 -0
- package/dist/telephony/telephony-runtime.js +18 -0
- package/dist/telephony/telephony-runtime.js.map +1 -0
- package/dist/telephony/twilioMediaRegistry.d.ts +60 -0
- package/dist/telephony/twilioMediaRegistry.d.ts.map +1 -0
- package/dist/telephony/twilioMediaRegistry.js +106 -0
- package/dist/telephony/twilioMediaRegistry.js.map +1 -0
- package/dist/telephony/vonage-runtime.d.ts +14 -0
- package/dist/telephony/vonage-runtime.d.ts.map +1 -0
- package/dist/telephony/vonage-runtime.js +18 -0
- package/dist/telephony/vonage-runtime.js.map +1 -0
- package/dist/telephony/vonageMediaRegistry.d.ts +78 -0
- package/dist/telephony/vonageMediaRegistry.d.ts.map +1 -0
- package/dist/telephony/vonageMediaRegistry.js +158 -0
- package/dist/telephony/vonageMediaRegistry.js.map +1 -0
- package/package.json +88 -82
- package/src/__tests__/FileResolverPeaks.test.ts +64 -0
- package/src/__tests__/MediaAccessKeys.test.ts +68 -0
- package/src/__tests__/MediaStreamHandler.test.ts +91 -0
- package/src/__tests__/RealtimeBridgeResolver.test.ts +10 -1
- package/src/__tests__/RealtimeClientSessionResolver.test.ts +69 -0
- package/src/__tests__/RealtimeCoAgentAppAwareness.integration.test.ts +191 -0
- package/src/__tests__/RemoteBrowserSnapshot.test.ts +123 -0
- package/src/__tests__/SessionManager.test.ts +2 -0
- package/src/__tests__/meetingRecordingRegistration.test.ts +248 -0
- package/src/__tests__/search-knowledge-tags.test.ts +9 -9
- package/src/__tests__/teams-meetings-service.test.ts +65 -0
- package/src/__tests__/teamsAcsMediaRegistry.test.ts +82 -0
- package/src/__tests__/twilio-telephony-service.test.ts +23 -0
- package/src/__tests__/twilioMediaRegistry.test.ts +103 -0
- package/src/__tests__/vonageMediaRegistry.test.ts +180 -0
- package/src/__tests__/widget.test.ts +204 -0
- package/src/__tests__/widgetGuestElevation.test.ts +57 -0
- package/src/__tests__/widgetHostIdentity.test.ts +117 -0
- package/src/agentSessions/ReturningVisitorRecap.ts +242 -0
- package/src/agentSessions/SessionJanitor.ts +66 -6
- package/src/agentSessions/SessionManager.ts +41 -0
- package/src/agentSessions/index.ts +3 -1
- package/src/auth/magicLink/MagicLinkRouter.ts +17 -0
- package/src/auth/magicLink/index.ts +1 -0
- package/src/auth/magicLink/types.ts +26 -0
- package/src/config.ts +172 -38
- package/src/context.ts +50 -3
- package/src/generated/generated.ts +26726 -20464
- package/src/generic/ResolverBase.ts +28 -0
- package/src/generic/RunViewResolver.ts +4 -0
- package/src/index.ts +119 -3
- package/src/integration/CustomColumnPromoter.ts +6 -0
- package/src/realtimeWidget/WidgetRouter.ts +204 -0
- package/src/realtimeWidget/WidgetSessionService.ts +714 -0
- package/src/realtimeWidget/host-identity.ts +84 -0
- package/src/realtimeWidget/index.ts +15 -0
- package/src/realtimeWidget/visitorIdentity.ts +258 -0
- package/src/realtimeWidget/widgetCore.ts +231 -0
- package/src/realtimeWidget/widgetGuestElevation.ts +115 -0
- package/src/resolvers/ComponentRegistryResolver.ts +36 -10
- package/src/resolvers/EntityPermissionResolver.ts +1 -2
- package/src/resolvers/FileResolver.ts +199 -1
- package/src/resolvers/QueryResolver.ts +33 -19
- package/src/resolvers/QuerySystemUserResolver.ts +14 -10
- package/src/resolvers/RealtimeBridgeResolver.ts +36 -4
- package/src/resolvers/RealtimeClientSessionResolver.ts +383 -9
- package/src/resolvers/RemoteBrowserActionResolver.ts +18 -2
- package/src/resolvers/RingCentralTelephonyResolver.ts +64 -0
- package/src/resolvers/RunAIAgentResolver.ts +60 -15
- package/src/resolvers/SearchKnowledgeResolver.ts +2 -0
- package/src/resolvers/TeamsMeetingsResolver.ts +68 -0
- package/src/resolvers/TelephonyResolver.ts +63 -0
- package/src/resolvers/TestQuerySQLResolver.ts +2 -3
- package/src/resolvers/VonageTelephonyResolver.ts +63 -0
- package/src/resolvers/meetingRecordingRegistration.ts +432 -0
- package/src/resolvers/peaksSidecar.ts +52 -0
- package/src/rest/MediaAccessKeys.ts +121 -0
- package/src/rest/MediaStreamHandler.ts +223 -0
- package/src/rest/mediaRange.ts +76 -0
- package/src/telephony/RingCentralTelephonyService.ts +342 -0
- package/src/telephony/TeamsMeetingsRouter.ts +124 -0
- package/src/telephony/TeamsMeetingsService.ts +268 -0
- package/src/telephony/TwilioTelephonyRouter.ts +184 -0
- package/src/telephony/TwilioTelephonyService.ts +261 -0
- package/src/telephony/VonageTelephonyRouter.ts +239 -0
- package/src/telephony/VonageTelephonyService.ts +259 -0
- package/src/telephony/calendar-scheduler.ts +176 -0
- package/src/telephony/index.ts +43 -0
- package/src/telephony/media-upgrade-router.ts +62 -0
- package/src/telephony/ringcentral-runtime.ts +22 -0
- package/src/telephony/teams-meetings-runtime.ts +24 -0
- package/src/telephony/teamsAcsMediaRegistry.ts +152 -0
- package/src/telephony/telephony-runtime.ts +22 -0
- package/src/telephony/twilioMediaRegistry.ts +137 -0
- package/src/telephony/vonage-runtime.ts +22 -0
- package/src/telephony/vonageMediaRegistry.ts +200 -0
- package/dist/agents/skip-agent.d.ts +0 -111
- package/dist/agents/skip-agent.d.ts.map +0 -1
- package/dist/agents/skip-agent.js +0 -1487
- package/dist/agents/skip-agent.js.map +0 -1
- package/dist/agents/skip-sdk.d.ts +0 -261
- package/dist/agents/skip-sdk.d.ts.map +0 -1
- package/dist/agents/skip-sdk.js +0 -909
- package/dist/agents/skip-sdk.js.map +0 -1
- package/src/agents/skip-agent.ts +0 -1594
- package/src/agents/skip-sdk.ts +0 -1210
|
@@ -24,11 +24,12 @@
|
|
|
24
24
|
*
|
|
25
25
|
* @module @memberjunction/server
|
|
26
26
|
*/
|
|
27
|
-
import { Resolver, Mutation, Arg, Ctx, Int, ObjectType, Field, PubSub, PubSubEngine } from 'type-graphql';
|
|
27
|
+
import { Resolver, Mutation, Arg, Ctx, Int, Float, ObjectType, Field, PubSub, PubSubEngine } from 'type-graphql';
|
|
28
28
|
import { AppContext, UserPayload } from '../types.js';
|
|
29
29
|
import { AuthorizationEvaluator, UserInfo, IMetadataProvider, LogError, LogStatus, RunView } from '@memberjunction/core';
|
|
30
|
-
import { UUIDsEqual } from '@memberjunction/global';
|
|
30
|
+
import { UUIDsEqual, IsValidUUID } from '@memberjunction/global';
|
|
31
31
|
import {
|
|
32
|
+
MJAIAgentEntity,
|
|
32
33
|
MJAIAgentSessionEntity,
|
|
33
34
|
MJAIAgentSessionChannelEntity,
|
|
34
35
|
MJArtifactEntity,
|
|
@@ -46,14 +47,20 @@ import {
|
|
|
46
47
|
EvaluateRuntimeOverrideAuthorization,
|
|
47
48
|
ParseRealtimeTypeConfiguration,
|
|
48
49
|
ResolveEffectiveRealtimeConfig,
|
|
50
|
+
RealtimeAllowedAgent,
|
|
49
51
|
REALTIME_ADVANCED_SESSION_CONTROLS_AUTHORIZATION,
|
|
52
|
+
resolveRecordingStorageAccountID,
|
|
53
|
+
storeRealtimeRecording,
|
|
54
|
+
writeRealtimeRecordingSegment,
|
|
55
|
+
deleteRealtimeRecordingSegments,
|
|
50
56
|
} from '@memberjunction/ai-agents';
|
|
51
|
-
import { AgentExecutionProgressCallback, MJAIAgentEntityExtended } from '@memberjunction/ai-core-plus';
|
|
57
|
+
import { AgentExecutionProgressCallback, MJAIAgentEntityExtended, AppContextSnapshot } from '@memberjunction/ai-core-plus';
|
|
52
58
|
import { RealtimeToolDefinition } from '@memberjunction/ai';
|
|
53
59
|
import { ResolverBase } from '../generic/ResolverBase.js';
|
|
54
60
|
import { PUSH_STATUS_UPDATES_TOPIC } from '../generic/PushStatusResolver.js';
|
|
55
61
|
import { GetReadWriteProvider } from '../util.js';
|
|
56
62
|
import { SessionManager } from '../agentSessions/index.js';
|
|
63
|
+
import { resolveWidgetGuestRunContext } from '../realtimeWidget/widgetGuestElevation.js';
|
|
57
64
|
|
|
58
65
|
/**
|
|
59
66
|
* Progress steps worth narrating to the realtime model — mirrors the normal agent-run path's filter
|
|
@@ -155,6 +162,31 @@ interface RealtimeSessionConfig {
|
|
|
155
162
|
* rather than starting fresh. Re-stored if the resumed run pauses again.
|
|
156
163
|
*/
|
|
157
164
|
pendingFeedbackRunID?: string;
|
|
165
|
+
/**
|
|
166
|
+
* Absolute server-authoritative deadline (ISO 8601) past which this session is hard-closed by the
|
|
167
|
+
* {@link SessionJanitor}, independent of idle activity. Stamped at session start for abuse-sensitive
|
|
168
|
+
* deployments (a public web-widget voice guest's `VoiceMaxSessionMinutes`); absent for uncapped
|
|
169
|
+
* sessions. Preserved across config rewrites so the cap survives run-id / feedback updates.
|
|
170
|
+
*/
|
|
171
|
+
maxSessionDeadlineIso?: string;
|
|
172
|
+
/**
|
|
173
|
+
* The application the session runs in (sourced the app cascade layer + RelevantAgents). Persisted
|
|
174
|
+
* for observability / continuity; absent when the session carries no app context.
|
|
175
|
+
*/
|
|
176
|
+
applicationID?: string;
|
|
177
|
+
/**
|
|
178
|
+
* The session's effective allowed delegation targets (union from the config cascade, incl. the app's
|
|
179
|
+
* `RelevantAgents`). Persisted at start so each relayed `invoke-target-agent` call can validate a
|
|
180
|
+
* model-named colleague against it without re-resolving the cascade. Absent ⇒ single-target behavior.
|
|
181
|
+
*/
|
|
182
|
+
allowedAgents?: RealtimeAllowedAgent[];
|
|
183
|
+
/**
|
|
184
|
+
* Per-session override for the agent's media kit — a `MJ: Collections` id that takes precedence
|
|
185
|
+
* over the agent's `DefaultMediaCollectionID` when the server-side `MediaChannelServer` resolves
|
|
186
|
+
* the media manifest at session start. Validated as a UUID at the mutation boundary; absent ⇒ the
|
|
187
|
+
* agent default kit is used (the common case).
|
|
188
|
+
*/
|
|
189
|
+
mediaCollectionID?: string;
|
|
158
190
|
}
|
|
159
191
|
|
|
160
192
|
/**
|
|
@@ -289,6 +321,27 @@ export class CancelRealtimeSessionToolResult {
|
|
|
289
321
|
ErrorMessage?: string;
|
|
290
322
|
}
|
|
291
323
|
|
|
324
|
+
/**
|
|
325
|
+
* Result of {@link RealtimeClientSessionResolver.UploadRealtimeRecording} — a structured
|
|
326
|
+
* success/failure envelope. A recording-storage failure must never throw to the browser (the
|
|
327
|
+
* session it belongs to has already ended), so problems come back as `Success: false` with a
|
|
328
|
+
* human-readable reason; only ownership/authn violations (from `loadOwnedSession`) still throw.
|
|
329
|
+
*/
|
|
330
|
+
@ObjectType()
|
|
331
|
+
export class UploadRealtimeRecordingResult {
|
|
332
|
+
/** True when the audio was stored and the session was stamped with the recording file. */
|
|
333
|
+
@Field(() => Boolean)
|
|
334
|
+
Success: boolean;
|
|
335
|
+
|
|
336
|
+
/** Human-readable failure reason. Null on success. */
|
|
337
|
+
@Field(() => String, { nullable: true })
|
|
338
|
+
ErrorMessage?: string;
|
|
339
|
+
|
|
340
|
+
/** ID of the created `MJ: Files` row holding the uploaded recording. Null on failure. */
|
|
341
|
+
@Field(() => String, { nullable: true })
|
|
342
|
+
FileID?: string;
|
|
343
|
+
}
|
|
344
|
+
|
|
292
345
|
/**
|
|
293
346
|
* Resolver for the client-direct realtime voice topology. A single {@link SessionManager} and a
|
|
294
347
|
* single {@link RealtimeClientSessionService} are shared across requests — neither holds per-user or
|
|
@@ -348,6 +401,11 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
348
401
|
@Arg('clientToolsJson', () => String, { nullable: true }) clientToolsJson?: string,
|
|
349
402
|
@Arg('coAgentId', () => String, { nullable: true }) coAgentId?: string,
|
|
350
403
|
@Arg('configOverridesJson', () => String, { nullable: true }) configOverridesJson?: string,
|
|
404
|
+
@Arg('recordingStartedAt', () => String, { nullable: true }) recordingStartedAt?: string,
|
|
405
|
+
@Arg('recordingConsent', () => Boolean, { nullable: true }) recordingConsent?: boolean,
|
|
406
|
+
@Arg('mediaCollectionId', () => String, { nullable: true }) mediaCollectionId?: string,
|
|
407
|
+
@Arg('applicationId', () => String, { nullable: true }) applicationId?: string,
|
|
408
|
+
@Arg('appContextJson', () => String, { nullable: true }) appContextJson?: string,
|
|
351
409
|
): Promise<StartRealtimeClientSessionResult> {
|
|
352
410
|
const { contextUser, provider } = this.requireUserAndProvider(userPayload, providers);
|
|
353
411
|
|
|
@@ -362,7 +420,29 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
362
420
|
// silent ignore). Plain starts and within-pairing target selection are never gated here.
|
|
363
421
|
await this.assertRuntimeOverridesAuthorized(coAgentID, configOverridesJson, preferredModelId, contextUser, provider);
|
|
364
422
|
|
|
423
|
+
// PUBLIC WEB-WIDGET VOICE CAP (public-web-widget.md W4): when the caller is a widget guest,
|
|
424
|
+
// resolve the deployment's hard duration ceiling and stamp an absolute deadline on the session.
|
|
425
|
+
// The SessionJanitor force-closes (finalizing runs) past the deadline — a server-authoritative
|
|
426
|
+
// bound that does not depend on the client honoring its own abuse guard or the ephemeral token TTL.
|
|
427
|
+
const maxSessionSeconds = await this.resolveWidgetVoiceCapSeconds(userPayload, providers);
|
|
365
428
|
const config: RealtimeSessionConfig = { targetAgentID: effectiveTargetId };
|
|
429
|
+
if (maxSessionSeconds) {
|
|
430
|
+
config.maxSessionDeadlineIso = new Date(Date.now() + maxSessionSeconds * 1000).toISOString();
|
|
431
|
+
}
|
|
432
|
+
// Per-session media-kit override: stored on the session config so the server-side MediaChannelServer
|
|
433
|
+
// resolves it (over the agent default) at start. Validate the id here — a malformed value is
|
|
434
|
+
// dropped (logged), never interpolated; the session still starts and falls back to the agent kit.
|
|
435
|
+
const mediaOverride = mediaCollectionId?.trim();
|
|
436
|
+
if (mediaOverride) {
|
|
437
|
+
if (IsValidUUID(mediaOverride)) {
|
|
438
|
+
config.mediaCollectionID = mediaOverride;
|
|
439
|
+
} else {
|
|
440
|
+
LogStatus(
|
|
441
|
+
`StartRealtimeClientSession: ignoring malformed mediaCollectionId '${mediaCollectionId}' — ` +
|
|
442
|
+
'using the agent default media kit.',
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
366
446
|
const session = await this.sessionManager.CreateSession(
|
|
367
447
|
{
|
|
368
448
|
agentID: coAgentID,
|
|
@@ -375,6 +455,9 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
375
455
|
provider,
|
|
376
456
|
);
|
|
377
457
|
|
|
458
|
+
// Best-effort: stamp recording-start metadata when the browser captured (with consent) at start.
|
|
459
|
+
await this.stampRecordingStart(session, recordingConsent, recordingStartedAt);
|
|
460
|
+
|
|
378
461
|
const clientTools = this.parseClientTools(clientToolsJson);
|
|
379
462
|
// Best-effort model-context hydration: the PRIOR session chain's transcript (ownership-
|
|
380
463
|
// checked, capped) is framed into the system prompt so the model REMEMBERS the last leg.
|
|
@@ -382,7 +465,7 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
382
465
|
const priorTranscript = await this.loadPriorTranscript(lastSessionId, contextUser, provider);
|
|
383
466
|
const result = await this.prepareClientSessionOrClose(
|
|
384
467
|
session, coAgentID, effectiveTargetId, contextUser, provider, preferredModelId, clientTools, priorTranscript,
|
|
385
|
-
configOverridesJson,
|
|
468
|
+
configOverridesJson, maxSessionSeconds, applicationId, this.parseAppContext(appContextJson),
|
|
386
469
|
);
|
|
387
470
|
// Best-effort restore of the PRIOR session's persisted channel states (e.g. the whiteboard
|
|
388
471
|
// board). Strictly tolerant — any problem yields a null field, never a failed start.
|
|
@@ -416,6 +499,9 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
416
499
|
{
|
|
417
500
|
AgentSessionID: agentSessionId,
|
|
418
501
|
TargetAgentID: config.targetAgentID,
|
|
502
|
+
// Multi-target (Move 4): the session's persisted allowed-agent union — a model-named
|
|
503
|
+
// colleague in the call is validated against this; absent ⇒ single-target behavior.
|
|
504
|
+
AllowedAgents: config.allowedAgents,
|
|
419
505
|
// Nest the delegated target-agent run under the co-agent observability run (when present).
|
|
420
506
|
ParentRunID: config.coAgentRunID,
|
|
421
507
|
Call: { CallID: callId, ToolName: toolName, Arguments: argsJson },
|
|
@@ -492,6 +578,10 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
492
578
|
promptRunID: config.promptRunID,
|
|
493
579
|
coAgentRunStepID: config.coAgentRunStepID,
|
|
494
580
|
pendingFeedbackRunID: pausedRunID,
|
|
581
|
+
// Preserve the server-authoritative voice deadline across config rewrites.
|
|
582
|
+
maxSessionDeadlineIso: config.maxSessionDeadlineIso,
|
|
583
|
+
applicationID: config.applicationID,
|
|
584
|
+
allowedAgents: config.allowedAgents,
|
|
495
585
|
};
|
|
496
586
|
session.Config_ = JSON.stringify(next);
|
|
497
587
|
if (!(await session.Save())) {
|
|
@@ -521,13 +611,15 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
521
611
|
@Arg('text', () => String) text: string,
|
|
522
612
|
@Ctx() { userPayload, providers }: AppContext,
|
|
523
613
|
@Arg('replacesPrevious', () => Boolean, { nullable: true }) replacesPrevious?: boolean,
|
|
614
|
+
@Arg('utteranceStartMs', () => Int, { nullable: true }) utteranceStartMs?: number,
|
|
615
|
+
@Arg('utteranceEndMs', () => Int, { nullable: true }) utteranceEndMs?: number,
|
|
524
616
|
): Promise<boolean> {
|
|
525
617
|
const { contextUser, provider } = this.requireUserAndProvider(userPayload, providers);
|
|
526
618
|
const session = await this.loadOwnedActiveSession(agentSessionId, contextUser, provider);
|
|
527
619
|
|
|
528
620
|
const saved = replacesPrevious
|
|
529
|
-
? await this.replacePreviousTranscriptTurn(session, role, text, contextUser, provider)
|
|
530
|
-
: await this.persistTranscriptTurn(session, role, text, contextUser, provider);
|
|
621
|
+
? await this.replacePreviousTranscriptTurn(session, role, text, contextUser, provider, utteranceStartMs, utteranceEndMs)
|
|
622
|
+
: await this.persistTranscriptTurn(session, role, text, contextUser, provider, utteranceStartMs, utteranceEndMs);
|
|
531
623
|
if (!saved) {
|
|
532
624
|
return false;
|
|
533
625
|
}
|
|
@@ -557,6 +649,145 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
557
649
|
return role.trim().toLowerCase() === 'user' ? 'user' : 'assistant';
|
|
558
650
|
}
|
|
559
651
|
|
|
652
|
+
/**
|
|
653
|
+
* Upload a CLIENT-DIRECT session audio recording, store it in MJStorage, link it to the owning
|
|
654
|
+
* `AIAgentSession`, and stamp the session's recording fields. The browser records locally during
|
|
655
|
+
* the call and uploads the assembled blob (base64) once the session ends.
|
|
656
|
+
*
|
|
657
|
+
* Hard gates (in order):
|
|
658
|
+
* 1. Ownership — `loadOwnedSession` enforces `UserID === contextUser.ID` (throws on violation).
|
|
659
|
+
* 2. Consent — `consent !== true` is a hard refusal: no audio is ever stored without it.
|
|
660
|
+
* 3. Storage configuration — the session's agent must resolve a recording storage account (the
|
|
661
|
+
* agent's `RecordingStorageProviderID`, else `AttachmentStorageProviderID`).
|
|
662
|
+
*
|
|
663
|
+
* Storage failures (and any unexpected throw) come back as `Success: false` with a reason — they
|
|
664
|
+
* NEVER throw to the browser, mirroring the shared {@link storeRealtimeRecording} contract.
|
|
665
|
+
*
|
|
666
|
+
* @param agentSessionId The session the recording belongs to (ownership-gated).
|
|
667
|
+
* @param audioBase64 The base64-encoded recording bytes (client-direct: seekable WAV/PCM).
|
|
668
|
+
* @param mimeType The audio MIME type (`audio/wav` for client-direct; legacy `audio/webm`/`ogg`/`mp4`).
|
|
669
|
+
* @param durationMs Optional client-measured recording duration (ms) — informational.
|
|
670
|
+
* @param consent Whether the user consented to recording. MUST be `true` or the upload is refused.
|
|
671
|
+
* @param peaks Optional capture-time waveform peaks (max-abs per bucket, normalized 0..1). When
|
|
672
|
+
* present, persisted as a `peaks.json` sidecar beside the recording for fast waveform rendering
|
|
673
|
+
* without re-decoding the audio.
|
|
674
|
+
* @returns A structured {@link UploadRealtimeRecordingResult} with the created `MJ: Files` id.
|
|
675
|
+
*/
|
|
676
|
+
@Mutation(() => UploadRealtimeRecordingResult)
|
|
677
|
+
async UploadRealtimeRecording(
|
|
678
|
+
@Arg('agentSessionId', () => String) agentSessionId: string,
|
|
679
|
+
@Arg('audioBase64', () => String) audioBase64: string,
|
|
680
|
+
@Arg('mimeType', () => String) mimeType: string,
|
|
681
|
+
@Ctx() ctx: AppContext,
|
|
682
|
+
@Arg('durationMs', () => Int, { nullable: true }) durationMs?: number,
|
|
683
|
+
@Arg('consent', () => Boolean, { nullable: true }) consent?: boolean,
|
|
684
|
+
@Arg('peaks', () => [Float], { nullable: true }) peaks?: number[],
|
|
685
|
+
): Promise<UploadRealtimeRecordingResult> {
|
|
686
|
+
const { contextUser, provider } = this.requireUserAndProvider(ctx.userPayload, ctx.providers);
|
|
687
|
+
const session = await this.loadOwnedSession(agentSessionId, contextUser, provider);
|
|
688
|
+
|
|
689
|
+
// HARD consent gate — no audio is ever stored without an explicit grant.
|
|
690
|
+
if (consent !== true) {
|
|
691
|
+
return { Success: false, ErrorMessage: 'Recording consent was not granted.' };
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
try {
|
|
695
|
+
const agent = await provider.GetEntityObject<MJAIAgentEntity>('MJ: AI Agents', contextUser);
|
|
696
|
+
if (!(await agent.Load(session.AgentID))) {
|
|
697
|
+
return { Success: false, ErrorMessage: `Co-agent ${session.AgentID} for the session could not be loaded.` };
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
const accountID = await resolveRecordingStorageAccountID(agent, contextUser, provider);
|
|
701
|
+
if (!accountID) {
|
|
702
|
+
return { Success: false, ErrorMessage: 'No recording storage account is configured for this agent.' };
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
const buffer = Buffer.from(audioBase64, 'base64');
|
|
706
|
+
if (buffer.length === 0) {
|
|
707
|
+
return { Success: false, ErrorMessage: 'The uploaded recording was empty.' };
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
const fileID = await storeRealtimeRecording({
|
|
711
|
+
Audio: buffer,
|
|
712
|
+
MimeType: mimeType,
|
|
713
|
+
Media: 'Audio',
|
|
714
|
+
StartedAt: session.RecordingStartedAt ?? new Date(),
|
|
715
|
+
StorageAccountID: accountID,
|
|
716
|
+
SessionID: agentSessionId,
|
|
717
|
+
ContextUser: contextUser,
|
|
718
|
+
Provider: provider,
|
|
719
|
+
// Sanitized capture-time waveform peaks → persisted as a peaks.json sidecar.
|
|
720
|
+
Peaks: this.sanitizePeaks(peaks),
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
// Canonical consolidated file written — drop the crash-recovery shards (best-effort).
|
|
724
|
+
if (fileID) {
|
|
725
|
+
await deleteRealtimeRecordingSegments(agentSessionId, accountID, contextUser);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
return {
|
|
729
|
+
Success: !!fileID,
|
|
730
|
+
FileID: fileID ?? undefined,
|
|
731
|
+
ErrorMessage: fileID ? undefined : 'Storage upload failed.',
|
|
732
|
+
};
|
|
733
|
+
} catch (error) {
|
|
734
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
735
|
+
LogError(`RealtimeClientSessionResolver.UploadRealtimeRecording failed for session ${agentSessionId}: ${message}`);
|
|
736
|
+
return { Success: false, ErrorMessage: message };
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* Uploads ONE crash-recovery audio shard (~15s) for an IN-PROGRESS recording into the session's
|
|
742
|
+
* folder (`realtime-recordings/<sessionId>/seg-NNNN.<ext>`) as a raw storage object. Durability
|
|
743
|
+
* insurance during a live call so a browser/tab death loses at most the last window; the shards are
|
|
744
|
+
* deleted once the canonical consolidated file lands via {@link UploadRealtimeRecording}. Ownership-
|
|
745
|
+
* gated; consent was already established at session start. Best-effort — returns `false` (never
|
|
746
|
+
* throws) on any problem so a failed shard never disrupts the live call.
|
|
747
|
+
*
|
|
748
|
+
* @param agentSessionId The in-progress session (ownership-gated).
|
|
749
|
+
* @param segmentIndex 0-based shard index within the session.
|
|
750
|
+
* @param audioBase64 The base64-encoded shard bytes.
|
|
751
|
+
* @param mimeType The audio MIME type.
|
|
752
|
+
* @returns `true` when the shard was stored.
|
|
753
|
+
*/
|
|
754
|
+
@Mutation(() => Boolean)
|
|
755
|
+
async UploadRealtimeRecordingSegment(
|
|
756
|
+
@Arg('agentSessionId', () => String) agentSessionId: string,
|
|
757
|
+
@Arg('segmentIndex', () => Int) segmentIndex: number,
|
|
758
|
+
@Arg('audioBase64', () => String) audioBase64: string,
|
|
759
|
+
@Arg('mimeType', () => String) mimeType: string,
|
|
760
|
+
@Ctx() ctx: AppContext,
|
|
761
|
+
): Promise<boolean> {
|
|
762
|
+
try {
|
|
763
|
+
const { contextUser, provider } = this.requireUserAndProvider(ctx.userPayload, ctx.providers);
|
|
764
|
+
const session = await this.loadOwnedSession(agentSessionId, contextUser, provider);
|
|
765
|
+
const agent = await provider.GetEntityObject<MJAIAgentEntity>('MJ: AI Agents', contextUser);
|
|
766
|
+
if (!(await agent.Load(session.AgentID))) {
|
|
767
|
+
return false;
|
|
768
|
+
}
|
|
769
|
+
const accountID = await resolveRecordingStorageAccountID(agent, contextUser, provider);
|
|
770
|
+
if (!accountID) {
|
|
771
|
+
return false;
|
|
772
|
+
}
|
|
773
|
+
const buffer = Buffer.from(audioBase64, 'base64');
|
|
774
|
+
if (buffer.length === 0) {
|
|
775
|
+
return false;
|
|
776
|
+
}
|
|
777
|
+
return await writeRealtimeRecordingSegment({
|
|
778
|
+
SessionID: agentSessionId,
|
|
779
|
+
SegmentIndex: segmentIndex,
|
|
780
|
+
Audio: buffer,
|
|
781
|
+
MimeType: mimeType,
|
|
782
|
+
StorageAccountID: accountID,
|
|
783
|
+
ContextUser: contextUser,
|
|
784
|
+
});
|
|
785
|
+
} catch (error) {
|
|
786
|
+
LogError(`RealtimeClientSessionResolver.UploadRealtimeRecordingSegment failed for session ${agentSessionId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
787
|
+
return false;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
560
791
|
/**
|
|
561
792
|
* Relays a co-agent CHANNEL tool-call (browser_ / Whiteboard_ etc.) onto the session's co-agent
|
|
562
793
|
* AIPromptRun.Messages — run-only observability so the run captures what the co-agent DID, not just
|
|
@@ -690,6 +921,23 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
690
921
|
return Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
|
|
691
922
|
}
|
|
692
923
|
|
|
924
|
+
/**
|
|
925
|
+
* Sanitizes client-supplied waveform peaks before they're persisted: drops anything that isn't a
|
|
926
|
+
* finite number, clamps each to `[0, 1]`, and caps the array length so a hostile client can't bloat
|
|
927
|
+
* the sidecar. Returns `undefined` for a missing/empty array (no sidecar written).
|
|
928
|
+
*/
|
|
929
|
+
private sanitizePeaks(peaks: number[] | undefined): number[] | undefined {
|
|
930
|
+
if (!Array.isArray(peaks) || peaks.length === 0) {
|
|
931
|
+
return undefined;
|
|
932
|
+
}
|
|
933
|
+
const MAX_PEAKS = 4096; // generous ceiling above the client's ~600-bucket target
|
|
934
|
+
const cleaned = peaks
|
|
935
|
+
.slice(0, MAX_PEAKS)
|
|
936
|
+
.filter((v) => Number.isFinite(v))
|
|
937
|
+
.map((v) => (v < 0 ? 0 : v > 1 ? 1 : v));
|
|
938
|
+
return cleaned.length > 0 ? cleaned : undefined;
|
|
939
|
+
}
|
|
940
|
+
|
|
693
941
|
/**
|
|
694
942
|
* Reads the co-agent `AIPromptRun` id from the session config WITHOUT the relay path's
|
|
695
943
|
* throw-on-missing-target semantics — usage relay is best-effort, so a missing/malformed
|
|
@@ -1305,6 +1553,9 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
1305
1553
|
clientTools?: RealtimeToolDefinition[],
|
|
1306
1554
|
priorTranscript?: string,
|
|
1307
1555
|
configOverridesJson?: string,
|
|
1556
|
+
maxSessionSeconds?: number,
|
|
1557
|
+
applicationId?: string,
|
|
1558
|
+
appContext?: AppContextSnapshot,
|
|
1308
1559
|
): Promise<StartRealtimeClientSessionResult> {
|
|
1309
1560
|
const prep = await this.clientSessionService.PrepareClientSession(
|
|
1310
1561
|
{
|
|
@@ -1312,6 +1563,9 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
1312
1563
|
TargetAgentID: targetAgentId,
|
|
1313
1564
|
AgentSessionID: session.ID,
|
|
1314
1565
|
ConversationID: session.ConversationID ?? undefined,
|
|
1566
|
+
// Server-authoritative voice duration cap (widget guests) — bounds the driver session
|
|
1567
|
+
// where supported; the janitor enforces the session deadline regardless.
|
|
1568
|
+
MaxSessionSeconds: maxSessionSeconds,
|
|
1315
1569
|
// MVP: conversation history is not yet hydrated into ChatMessage[]; the co-agent
|
|
1316
1570
|
// companion prompt runs without prior turns. A later phase loads the session's
|
|
1317
1571
|
// Conversation into ChatMessage[] for richer context.
|
|
@@ -1325,6 +1579,10 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
1325
1579
|
PriorTranscript: priorTranscript,
|
|
1326
1580
|
// Pre-authorized runtime override layer (assertRuntimeOverridesAuthorized gated it).
|
|
1327
1581
|
ConfigOverridesJson: configOverridesJson,
|
|
1582
|
+
// App awareness (Move 1/3/4): the app sources the app cascade layer + RelevantAgents
|
|
1583
|
+
// (allowed-agent union); the snapshot is injected into the system prompt at mint.
|
|
1584
|
+
ApplicationID: applicationId,
|
|
1585
|
+
AppContext: appContext,
|
|
1328
1586
|
},
|
|
1329
1587
|
contextUser,
|
|
1330
1588
|
provider,
|
|
@@ -1336,7 +1594,10 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
1336
1594
|
throw new Error(prep.ErrorMessage ?? 'Failed to prepare the client realtime session.');
|
|
1337
1595
|
}
|
|
1338
1596
|
|
|
1339
|
-
await this.persistObservabilityRunIDs(
|
|
1597
|
+
await this.persistObservabilityRunIDs(
|
|
1598
|
+
session, targetAgentId, prep.CoAgentRunID, prep.PromptRunID, prep.CoAgentRunStepID,
|
|
1599
|
+
applicationId, prep.EffectiveConfig?.realtime?.allowedAgents,
|
|
1600
|
+
);
|
|
1340
1601
|
|
|
1341
1602
|
const cfg = prep.ClientConfig;
|
|
1342
1603
|
return {
|
|
@@ -1366,8 +1627,21 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
1366
1627
|
coAgentRunID?: string,
|
|
1367
1628
|
promptRunID?: string,
|
|
1368
1629
|
coAgentRunStepID?: string,
|
|
1630
|
+
applicationID?: string,
|
|
1631
|
+
allowedAgents?: RealtimeAllowedAgent[],
|
|
1369
1632
|
): Promise<void> {
|
|
1370
|
-
|
|
1633
|
+
// Preserve any server-authoritative voice deadline stamped at session start (this rebuilds the
|
|
1634
|
+
// full config, so read the existing value forward rather than dropping it).
|
|
1635
|
+
const existing = this.tryReadSessionConfig(session);
|
|
1636
|
+
const config: RealtimeSessionConfig = {
|
|
1637
|
+
targetAgentID,
|
|
1638
|
+
coAgentRunID,
|
|
1639
|
+
promptRunID,
|
|
1640
|
+
coAgentRunStepID,
|
|
1641
|
+
maxSessionDeadlineIso: existing?.maxSessionDeadlineIso,
|
|
1642
|
+
applicationID,
|
|
1643
|
+
allowedAgents: allowedAgents && allowedAgents.length > 0 ? allowedAgents : undefined,
|
|
1644
|
+
};
|
|
1371
1645
|
session.Config_ = JSON.stringify(config);
|
|
1372
1646
|
const saved = await session.Save();
|
|
1373
1647
|
if (!saved) {
|
|
@@ -1997,6 +2271,28 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
1997
2271
|
}
|
|
1998
2272
|
}
|
|
1999
2273
|
|
|
2274
|
+
/**
|
|
2275
|
+
* Tolerantly parses the `appContextJson` mutation arg into an {@link AppContextSnapshot}. Returns
|
|
2276
|
+
* `undefined` for absent/blank/malformed/non-object payloads — app context is best-effort enrichment
|
|
2277
|
+
* and must never fail a session start.
|
|
2278
|
+
*
|
|
2279
|
+
* @param appContextJson The raw JSON string the browser sent, or undefined.
|
|
2280
|
+
* @returns The parsed snapshot, or `undefined`.
|
|
2281
|
+
*/
|
|
2282
|
+
private parseAppContext(appContextJson?: string): AppContextSnapshot | undefined {
|
|
2283
|
+
if (!appContextJson || !appContextJson.trim()) {
|
|
2284
|
+
return undefined;
|
|
2285
|
+
}
|
|
2286
|
+
try {
|
|
2287
|
+
const parsed: unknown = JSON.parse(appContextJson);
|
|
2288
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
2289
|
+
? (parsed as AppContextSnapshot)
|
|
2290
|
+
: undefined;
|
|
2291
|
+
} catch {
|
|
2292
|
+
return undefined;
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2295
|
+
|
|
2000
2296
|
/**
|
|
2001
2297
|
* Tolerantly parses + validates client-declared UI tool definitions (see the SECURITY NOTE on
|
|
2002
2298
|
* {@link StartRealtimeClientSession}). Never throws — any rejection logs and returns
|
|
@@ -2069,6 +2365,10 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
2069
2365
|
coAgentRunStepID: typeof parsed.coAgentRunStepID === 'string' ? parsed.coAgentRunStepID : undefined,
|
|
2070
2366
|
pendingFeedbackRunID:
|
|
2071
2367
|
typeof parsed.pendingFeedbackRunID === 'string' ? parsed.pendingFeedbackRunID : undefined,
|
|
2368
|
+
maxSessionDeadlineIso:
|
|
2369
|
+
typeof parsed.maxSessionDeadlineIso === 'string' ? parsed.maxSessionDeadlineIso : undefined,
|
|
2370
|
+
applicationID: typeof parsed.applicationID === 'string' ? parsed.applicationID : undefined,
|
|
2371
|
+
allowedAgents: Array.isArray(parsed.allowedAgents) ? parsed.allowedAgents : undefined,
|
|
2072
2372
|
};
|
|
2073
2373
|
}
|
|
2074
2374
|
} catch {
|
|
@@ -2078,18 +2378,76 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
2078
2378
|
throw new Error(`Realtime session ${session.ID} has no target agent configured`);
|
|
2079
2379
|
}
|
|
2080
2380
|
|
|
2381
|
+
/** Like {@link readSessionConfig} but returns `undefined` instead of throwing (for best-effort reads). */
|
|
2382
|
+
private tryReadSessionConfig(session: MJAIAgentSessionEntity): RealtimeSessionConfig | undefined {
|
|
2383
|
+
try {
|
|
2384
|
+
return this.readSessionConfig(session);
|
|
2385
|
+
} catch {
|
|
2386
|
+
return undefined;
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
/**
|
|
2391
|
+
* Resolves the server-authoritative voice duration cap (seconds) for a session start, or `undefined`
|
|
2392
|
+
* when no cap applies (the common, uncapped case). A cap applies only when the caller is a public
|
|
2393
|
+
* web-widget guest with a configured `VoiceMaxSessionMinutes` on its widget instance (the same value
|
|
2394
|
+
* surfaced to the client guard at mint). Never throws — a resolution failure yields no cap rather
|
|
2395
|
+
* than blocking the start.
|
|
2396
|
+
*/
|
|
2397
|
+
private async resolveWidgetVoiceCapSeconds(
|
|
2398
|
+
userPayload: UserPayload,
|
|
2399
|
+
providers: AppContext['providers'],
|
|
2400
|
+
): Promise<number | undefined> {
|
|
2401
|
+
const elevation = await resolveWidgetGuestRunContext(userPayload, GetReadWriteProvider(providers));
|
|
2402
|
+
const minutes = elevation?.widget.VoiceMaxSessionMinutes;
|
|
2403
|
+
return minutes && minutes > 0 ? minutes * 60 : undefined;
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2081
2406
|
/**
|
|
2082
2407
|
* Persists a single transcript turn as a `Conversation Detail` stamped with the session's
|
|
2083
2408
|
* conversation, the mapped role, the turn text, the session id, and the owning user.
|
|
2084
2409
|
*
|
|
2085
2410
|
* @returns The boolean save result (logs `CompleteMessage` on failure).
|
|
2086
2411
|
*/
|
|
2412
|
+
/**
|
|
2413
|
+
* Stamps `RecordingStartedAt` (the recording `t0` alignment origin) + `RecordingMedia` on a
|
|
2414
|
+
* just-started session when the browser captured WITH consent. Best-effort: a parse/save failure
|
|
2415
|
+
* is logged and swallowed — a recording-metadata problem must never fail the session start.
|
|
2416
|
+
*
|
|
2417
|
+
* @param session The freshly created/loaded session.
|
|
2418
|
+
* @param recordingConsent Whether the user consented to recording — only `true` stamps anything.
|
|
2419
|
+
* @param recordingStartedAt ISO-8601 recording start timestamp from the browser.
|
|
2420
|
+
*/
|
|
2421
|
+
private async stampRecordingStart(
|
|
2422
|
+
session: MJAIAgentSessionEntity,
|
|
2423
|
+
recordingConsent: boolean | undefined,
|
|
2424
|
+
recordingStartedAt: string | undefined,
|
|
2425
|
+
): Promise<void> {
|
|
2426
|
+
if (recordingConsent !== true || !recordingStartedAt) {
|
|
2427
|
+
return;
|
|
2428
|
+
}
|
|
2429
|
+
const startedAt = new Date(recordingStartedAt);
|
|
2430
|
+
if (Number.isNaN(startedAt.getTime())) {
|
|
2431
|
+
LogError(`RealtimeClientSessionResolver.stampRecordingStart: invalid recordingStartedAt '${recordingStartedAt}' for session ${session.ID}`);
|
|
2432
|
+
return;
|
|
2433
|
+
}
|
|
2434
|
+
session.RecordingStartedAt = startedAt;
|
|
2435
|
+
session.RecordingMedia = 'Audio';
|
|
2436
|
+
if (!(await session.Save())) {
|
|
2437
|
+
LogError(
|
|
2438
|
+
`RealtimeClientSessionResolver.stampRecordingStart save failed: ${session.LatestResult?.CompleteMessage ?? 'unknown error'}`,
|
|
2439
|
+
);
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2087
2443
|
private async persistTranscriptTurn(
|
|
2088
2444
|
session: MJAIAgentSessionEntity,
|
|
2089
2445
|
role: string,
|
|
2090
2446
|
text: string,
|
|
2091
2447
|
contextUser: UserInfo,
|
|
2092
2448
|
provider: IMetadataProvider,
|
|
2449
|
+
utteranceStartMs?: number,
|
|
2450
|
+
utteranceEndMs?: number,
|
|
2093
2451
|
): Promise<boolean> {
|
|
2094
2452
|
const detail = await provider.GetEntityObject<MJConversationDetailEntity>(
|
|
2095
2453
|
CONVERSATION_DETAIL_ENTITY,
|
|
@@ -2101,6 +2459,12 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
2101
2459
|
detail.Message = text;
|
|
2102
2460
|
detail.AgentSessionID = session.ID;
|
|
2103
2461
|
detail.UserID = contextUser.ID;
|
|
2462
|
+
if (typeof utteranceStartMs === 'number' && utteranceStartMs >= 0) {
|
|
2463
|
+
detail.UtteranceStartMs = utteranceStartMs;
|
|
2464
|
+
}
|
|
2465
|
+
if (typeof utteranceEndMs === 'number' && utteranceEndMs >= 0) {
|
|
2466
|
+
detail.UtteranceEndMs = utteranceEndMs;
|
|
2467
|
+
}
|
|
2104
2468
|
|
|
2105
2469
|
const saved = await detail.Save();
|
|
2106
2470
|
if (!saved) {
|
|
@@ -2124,6 +2488,8 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
2124
2488
|
text: string,
|
|
2125
2489
|
contextUser: UserInfo,
|
|
2126
2490
|
provider: IMetadataProvider,
|
|
2491
|
+
utteranceStartMs?: number,
|
|
2492
|
+
utteranceEndMs?: number,
|
|
2127
2493
|
): Promise<boolean> {
|
|
2128
2494
|
const mappedRole = this.mapTranscriptRole(role);
|
|
2129
2495
|
const rv = RunView.FromMetadataProvider(provider);
|
|
@@ -2139,9 +2505,17 @@ export class RealtimeClientSessionResolver extends ResolverBase {
|
|
|
2139
2505
|
);
|
|
2140
2506
|
const previous = result.Success ? (result.Results?.[0] ?? null) : null;
|
|
2141
2507
|
if (!previous) {
|
|
2142
|
-
return this.persistTranscriptTurn(session, role, text, contextUser, provider);
|
|
2508
|
+
return this.persistTranscriptTurn(session, role, text, contextUser, provider, utteranceStartMs, utteranceEndMs);
|
|
2143
2509
|
}
|
|
2144
2510
|
previous.Message = text;
|
|
2511
|
+
// The correction extends the existing turn: always refresh the end boundary when provided,
|
|
2512
|
+
// but only set the start when it wasn't already captured on the superseded turn.
|
|
2513
|
+
if (typeof utteranceEndMs === 'number' && utteranceEndMs >= 0) {
|
|
2514
|
+
previous.UtteranceEndMs = utteranceEndMs;
|
|
2515
|
+
}
|
|
2516
|
+
if (typeof utteranceStartMs === 'number' && utteranceStartMs >= 0 && previous.UtteranceStartMs == null) {
|
|
2517
|
+
previous.UtteranceStartMs = utteranceStartMs;
|
|
2518
|
+
}
|
|
2145
2519
|
const saved = await previous.Save();
|
|
2146
2520
|
if (!saved) {
|
|
2147
2521
|
LogError(
|
|
@@ -408,8 +408,24 @@ export class RemoteBrowserActionResolver extends ResolverBase {
|
|
|
408
408
|
if (!liveSession) {
|
|
409
409
|
return {};
|
|
410
410
|
}
|
|
411
|
-
|
|
412
|
-
|
|
411
|
+
// The snapshot is a best-effort PERCEPTION poll (the surface fires it every ~700ms). A session
|
|
412
|
+
// whose underlying browser adapter has been torn down — a stale/dead handle still in the live
|
|
413
|
+
// map, a mid-poll teardown race, or a recycled backend container — makes CaptureScreenshot()
|
|
414
|
+
// throw "Browser not launched". That must degrade to an empty snapshot (the surface keeps its
|
|
415
|
+
// last good frame), NOT surface as a recurring GraphQL error the client logs on every tick —
|
|
416
|
+
// exactly as this query's contract above promises ("null rather than an error"). The live
|
|
417
|
+
// navigate/click path (ExecuteRemoteBrowserAction) is where a genuine browser failure is
|
|
418
|
+
// reported to the agent; the read-only view poll never should be.
|
|
419
|
+
try {
|
|
420
|
+
const screenshot = await liveSession.CaptureScreenshot();
|
|
421
|
+
return { ScreenshotBase64: screenshot, CurrentUrl: liveSession.GetCurrentUrl() };
|
|
422
|
+
} catch (err) {
|
|
423
|
+
LogError(
|
|
424
|
+
`[RemoteBrowserActionResolver] Snapshot capture failed for agent session ${agentSessionID} ` +
|
|
425
|
+
`(returning empty snapshot): ${err instanceof Error ? err.message : String(err)}`,
|
|
426
|
+
);
|
|
427
|
+
return {};
|
|
428
|
+
}
|
|
413
429
|
}
|
|
414
430
|
|
|
415
431
|
/**
|