@memberjunction/server 6.2.0-edge.1 → 6.2.0-edge.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/config.d.ts +2189 -71
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +123 -3
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +11 -23
- package/dist/context.js.map +1 -1
- package/dist/generated/graphql-schemas/__mj.d.ts +2143 -205
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +24441 -14433
- package/dist/generated/graphql-schemas/__mj.js.map +1 -1
- package/dist/generic/FireAndForgetHeartbeat.d.ts +15 -1
- package/dist/generic/FireAndForgetHeartbeat.d.ts.map +1 -1
- package/dist/generic/FireAndForgetHeartbeat.js +16 -2
- package/dist/generic/FireAndForgetHeartbeat.js.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.d.ts +31 -0
- package/dist/generic/OrphanedConversationDetailReconciler.d.ts.map +1 -0
- package/dist/generic/OrphanedConversationDetailReconciler.js +215 -0
- package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -0
- package/dist/generic/PushStatusResolver.d.ts +38 -0
- package/dist/generic/PushStatusResolver.d.ts.map +1 -1
- package/dist/generic/PushStatusResolver.js +84 -1
- package/dist/generic/PushStatusResolver.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +56 -4
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +191 -74
- 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 +33 -1
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +204 -50
- package/dist/index.js.map +1 -1
- package/dist/postgresqlCompat.d.ts +8 -0
- package/dist/postgresqlCompat.d.ts.map +1 -0
- package/dist/postgresqlCompat.js +19 -0
- package/dist/postgresqlCompat.js.map +1 -0
- package/dist/postgresqlPoolSettings.d.ts +108 -0
- package/dist/postgresqlPoolSettings.d.ts.map +1 -0
- package/dist/postgresqlPoolSettings.js +134 -0
- package/dist/postgresqlPoolSettings.js.map +1 -0
- package/dist/resolvers/AdhocQueryResolver.d.ts +23 -18
- package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.js +77 -167
- package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
- package/dist/resolvers/AgentRunStatusPublisher.d.ts +55 -0
- package/dist/resolvers/AgentRunStatusPublisher.d.ts.map +1 -0
- package/dist/resolvers/AgentRunStatusPublisher.js +141 -0
- package/dist/resolvers/AgentRunStatusPublisher.js.map +1 -0
- package/dist/resolvers/ConversationRunEventsResolver.d.ts +90 -0
- package/dist/resolvers/ConversationRunEventsResolver.d.ts.map +1 -0
- package/dist/resolvers/ConversationRunEventsResolver.js +283 -0
- package/dist/resolvers/ConversationRunEventsResolver.js.map +1 -0
- package/dist/resolvers/DuplicateEntryCheckResolver.d.ts +98 -0
- package/dist/resolvers/DuplicateEntryCheckResolver.d.ts.map +1 -0
- package/dist/resolvers/DuplicateEntryCheckResolver.js +227 -0
- package/dist/resolvers/DuplicateEntryCheckResolver.js.map +1 -0
- package/dist/resolvers/EntityRecordNameResolver.d.ts +7 -23
- package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.js +9 -47
- package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.js +2 -1
- package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
- package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataResolver.js +9 -0
- package/dist/resolvers/GetDataResolver.js.map +1 -1
- package/dist/resolvers/HumanHandoffResolver.d.ts +107 -0
- package/dist/resolvers/HumanHandoffResolver.d.ts.map +1 -0
- package/dist/resolvers/HumanHandoffResolver.js +324 -0
- package/dist/resolvers/HumanHandoffResolver.js.map +1 -0
- package/dist/resolvers/MeetingResolver.d.ts +69 -0
- package/dist/resolvers/MeetingResolver.d.ts.map +1 -0
- package/dist/resolvers/MeetingResolver.js +381 -0
- package/dist/resolvers/MeetingResolver.js.map +1 -0
- package/dist/resolvers/MergeRecordsResolver.d.ts +7 -0
- package/dist/resolvers/MergeRecordsResolver.d.ts.map +1 -1
- package/dist/resolvers/MergeRecordsResolver.js +16 -2
- package/dist/resolvers/MergeRecordsResolver.js.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.d.ts +34 -1
- package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.js +202 -7
- package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +45 -89
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +94 -240
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts +0 -16
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +23 -153
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunDecisionResolver.d.ts +149 -0
- package/dist/resolvers/RunDecisionResolver.d.ts.map +1 -0
- package/dist/resolvers/RunDecisionResolver.js +396 -0
- package/dist/resolvers/RunDecisionResolver.js.map +1 -0
- package/dist/resolvers/VectorizeEntityResolver.d.ts +1 -1
- package/dist/resolvers/VectorizeEntityResolver.d.ts.map +1 -1
- package/dist/resolvers/VectorizeEntityResolver.js +14 -10
- package/dist/resolvers/VectorizeEntityResolver.js.map +1 -1
- package/dist/resolvers/adhocTimeout.d.ts +7 -0
- package/dist/resolvers/adhocTimeout.d.ts.map +1 -0
- package/dist/resolvers/adhocTimeout.js +12 -0
- package/dist/resolvers/adhocTimeout.js.map +1 -0
- package/dist/resolvers/meetings/index.d.ts +5 -0
- package/dist/resolvers/meetings/index.d.ts.map +1 -0
- package/dist/resolvers/meetings/index.js +5 -0
- package/dist/resolvers/meetings/index.js.map +1 -0
- package/dist/resolvers/meetings/meetingHelpers.d.ts +17 -0
- package/dist/resolvers/meetings/meetingHelpers.d.ts.map +1 -0
- package/dist/resolvers/meetings/meetingHelpers.js +212 -0
- package/dist/resolvers/meetings/meetingHelpers.js.map +1 -0
- package/dist/resolvers/meetings/meetingMutations.d.ts +12 -0
- package/dist/resolvers/meetings/meetingMutations.d.ts.map +1 -0
- package/dist/resolvers/meetings/meetingMutations.js +418 -0
- package/dist/resolvers/meetings/meetingMutations.js.map +1 -0
- package/dist/resolvers/meetings/meetingQueries.d.ts +7 -0
- package/dist/resolvers/meetings/meetingQueries.d.ts.map +1 -0
- package/dist/resolvers/meetings/meetingQueries.js +113 -0
- package/dist/resolvers/meetings/meetingQueries.js.map +1 -0
- package/dist/resolvers/meetings/meetingTypes.d.ts +89 -0
- package/dist/resolvers/meetings/meetingTypes.d.ts.map +1 -0
- package/dist/resolvers/meetings/meetingTypes.js +345 -0
- package/dist/resolvers/meetings/meetingTypes.js.map +1 -0
- package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
- package/dist/rest/ViewOperationsHandler.js +8 -5
- package/dist/rest/ViewOperationsHandler.js.map +1 -1
- package/dist/services/TaskGraphActionRunner.d.ts +8 -0
- package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphActionRunner.js +27 -1
- package/dist/services/TaskGraphActionRunner.js.map +1 -1
- package/dist/services/WorkQueueHostService.d.ts +36 -0
- package/dist/services/WorkQueueHostService.d.ts.map +1 -0
- package/dist/services/WorkQueueHostService.js +97 -0
- package/dist/services/WorkQueueHostService.js.map +1 -0
- package/dist/services/workQueueConfig.d.ts +103 -0
- package/dist/services/workQueueConfig.d.ts.map +1 -0
- package/dist/services/workQueueConfig.js +48 -0
- package/dist/services/workQueueConfig.js.map +1 -0
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +95 -92
- package/src/__tests__/AdhocQueryResolver.bugs.test.ts +14 -35
- package/src/__tests__/AdhocQueryResolver.pagination.test.ts +6 -2
- package/src/__tests__/AdhocQueryResolver.scopeGuard.test.ts +3 -3
- package/src/__tests__/AgentRunStatusPublisher.test.ts +175 -0
- package/src/__tests__/DuplicateEntryCheckResolver.test.ts +300 -0
- package/src/__tests__/EntityRecordNameResolver.fieldSecurity.test.ts +27 -72
- package/src/__tests__/HumanHandoffResolver.test.ts +233 -0
- package/src/__tests__/MeetingResolver.test.ts +374 -0
- package/src/__tests__/RealtimeBridgeResolver.test.ts +240 -2
- package/src/__tests__/RealtimeClientSessionResolver.test.ts +216 -2
- package/src/__tests__/RemoteBrowserActionRecovery.test.ts +7 -1
- package/src/__tests__/RemoteBrowserAudioStream.test.ts +7 -1
- package/src/__tests__/RemoteBrowserSnapshot.test.ts +7 -1
- package/src/__tests__/ResolverBase.adhocReadScope.test.ts +96 -0
- package/src/__tests__/ResolverBase.clientOldValues.test.ts +52 -0
- package/src/__tests__/ResolverBase.filterEscaping.test.ts +21 -2
- package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +291 -0
- package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +182 -12
- package/src/__tests__/RunAIAgentResolver.streaming.test.ts +18 -27
- package/src/__tests__/RunDecisionResolver.test.ts +699 -0
- package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +43 -0
- package/src/__tests__/VectorizeEntityResolver.test.ts +106 -0
- package/src/__tests__/WorkQueueHostService.test.ts +133 -0
- package/src/__tests__/adhocTimeoutClamp.test.ts +26 -0
- package/src/__tests__/conversationRunEvents.test.ts +399 -0
- package/src/__tests__/orphanedConversationDetailReconciler.test.ts +283 -0
- package/src/__tests__/postgresqlPoolSettings.test.ts +131 -0
- package/src/__tests__/pushStatusFanOut.test.ts +206 -0
- package/src/__tests__/translateBracketsToPG.test.ts +20 -0
- package/src/auth/__tests__/backward-compatibility.test.ts +1 -0
- package/src/config.ts +130 -3
- package/src/context.ts +11 -24
- package/src/generated/graphql-schemas/__mj.ts +24183 -17182
- package/src/generic/FireAndForgetHeartbeat.ts +16 -2
- package/src/generic/OrphanedConversationDetailReconciler.ts +267 -0
- package/src/generic/PushStatusResolver.ts +97 -1
- package/src/generic/ResolverBase.ts +220 -76
- package/src/generic/RunViewResolver.ts +33 -2
- package/src/index.ts +228 -51
- package/src/postgresqlCompat.ts +21 -0
- package/src/postgresqlPoolSettings.ts +213 -0
- package/src/resolvers/AdhocQueryResolver.ts +88 -188
- package/src/resolvers/AgentRunStatusPublisher.ts +175 -0
- package/src/resolvers/ConversationRunEventsResolver.ts +308 -0
- package/src/resolvers/DuplicateEntryCheckResolver.ts +232 -0
- package/src/resolvers/EntityRecordNameResolver.ts +9 -51
- package/src/resolvers/FetchEntityVectorsResolver.ts +2 -1
- package/src/resolvers/GetDataResolver.ts +10 -0
- package/src/resolvers/HumanHandoffResolver.ts +304 -0
- package/src/resolvers/MeetingResolver.ts +438 -0
- package/src/resolvers/MergeRecordsResolver.ts +14 -2
- package/src/resolvers/RealtimeBridgeResolver.ts +227 -13
- package/src/resolvers/RealtimeClientSessionResolver.ts +99 -287
- package/src/resolvers/RunAIAgentResolver.ts +28 -174
- package/src/resolvers/RunDecisionResolver.ts +435 -0
- package/src/resolvers/VectorizeEntityResolver.ts +16 -11
- package/src/resolvers/__tests__/MergeRecordsResolver.test.ts +93 -0
- package/src/resolvers/adhocTimeout.ts +12 -0
- package/src/resolvers/meetings/index.ts +4 -0
- package/src/resolvers/meetings/meetingHelpers.ts +307 -0
- package/src/resolvers/meetings/meetingMutations.ts +535 -0
- package/src/resolvers/meetings/meetingQueries.ts +172 -0
- package/src/resolvers/meetings/meetingTypes.ts +238 -0
- package/src/rest/ViewOperationsHandler.ts +8 -5
- package/src/services/TaskGraphActionRunner.ts +29 -1
- package/src/services/WorkQueueHostService.ts +129 -0
- package/src/services/workQueueConfig.ts +52 -0
- package/src/types.ts +5 -0
- package/dist/resolvers/adhoc-query-helpers.d.ts +0 -33
- package/dist/resolvers/adhoc-query-helpers.d.ts.map +0 -1
- package/dist/resolvers/adhoc-query-helpers.js +0 -50
- package/dist/resolvers/adhoc-query-helpers.js.map +0 -1
- package/src/__tests__/adhoc-query-helpers.test.ts +0 -50
- package/src/resolvers/adhoc-query-helpers.ts +0 -67
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
import { Resolver, Mutation, Query, Arg, Ctx, ObjectType, InputType, Field } from 'type-graphql';
|
|
2
2
|
import { randomUUID } from 'crypto';
|
|
3
|
-
import { LogError, LogStatusEx, UserInfo, IMetadataProvider } from '@memberjunction/core';
|
|
4
|
-
import {
|
|
3
|
+
import { LogError, LogStatusEx, UserInfo, IMetadataProvider, RunView } from '@memberjunction/core';
|
|
4
|
+
import { EscapeSQLString } from '@memberjunction/global';
|
|
5
|
+
import type { TurnAddressingMode } from '@memberjunction/ai-bridge-base';
|
|
6
|
+
import {
|
|
7
|
+
LiveKitTokenService,
|
|
8
|
+
LiveKitAgentRoomCoordinator,
|
|
9
|
+
LiveKitEgressService,
|
|
10
|
+
LiveKitUserIdentity,
|
|
11
|
+
RoomHandoffEngine,
|
|
12
|
+
} from '@memberjunction/livekit-room-server';
|
|
5
13
|
import { AppContext } from '../types.js';
|
|
6
14
|
import { ResolverBase } from '../generic/ResolverBase.js';
|
|
7
15
|
import { GetReadWriteProvider } from '../util.js';
|
|
8
|
-
import { CreateBridgeRealtimeSession, FinalizeBridgeCoAgentRuns, GetRealtimeModelVoices, CreateBridgeRoomTranscriptSink, RealtimeTurnModeratorDecision } from '@memberjunction/ai-agents';
|
|
16
|
+
import { CreateBridgeRealtimeSession, FinalizeBridgeCoAgentRuns, GetRealtimeModelVoices, CreateBridgeRoomTranscriptSink, RealtimeTurnModeratorDecision, GetBridgeRealtimeRuntime } from '@memberjunction/ai-agents';
|
|
9
17
|
import { AIBridgeEngine } from '@memberjunction/ai-bridge-server';
|
|
10
18
|
import { SessionManager } from '../agentSessions/SessionManager.js';
|
|
11
19
|
import { NotificationEngine } from '@memberjunction/notifications';
|
|
12
20
|
import { RegisterMeetingRecordingFile, CorrelateRecordingStart } from './meetingRecordingRegistration.js';
|
|
21
|
+
import { RoomAuthorizationService } from '@memberjunction/livekit-room-server';
|
|
13
22
|
|
|
14
23
|
/**
|
|
15
24
|
* Binds the agent realtime-session factory onto the LiveKit room coordinator's model-session creation seam.
|
|
@@ -21,6 +30,20 @@ import { RegisterMeetingRecordingFile, CorrelateRecordingStart } from './meeting
|
|
|
21
30
|
*/
|
|
22
31
|
LiveKitAgentRoomCoordinator.Instance.SetSessionFactory(CreateBridgeRealtimeSession);
|
|
23
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Binds the turn-taking tool handler seam (same module-load rationale as the factory above). A full-duplex model
|
|
35
|
+
* in a shared room calls `i_am_addressed` / `yield_turn`; the bridge engine owns what those mean, and this installs
|
|
36
|
+
* the engine's handler as the model session runtime's local tool handler (`@memberjunction/ai-agents`).
|
|
37
|
+
*/
|
|
38
|
+
LiveKitAgentRoomCoordinator.Instance.SetTurnToolBinder((session, handler) => {
|
|
39
|
+
const runtime = GetBridgeRealtimeRuntime(session);
|
|
40
|
+
if (!runtime) {
|
|
41
|
+
LogError('[RealtimeBridge] the model session has no bridge runtime; i_am_addressed / yield_turn will not execute for it.');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
runtime.SetLocalToolHandler(handler);
|
|
45
|
+
});
|
|
46
|
+
|
|
24
47
|
/**
|
|
25
48
|
* Binds the co-agent run finalizer onto the bridge engine (same module-load rationale as the factory above).
|
|
26
49
|
* Lets the engine finalize a session's dangling co-agent observability run when it reaps a bridge WITHOUT a
|
|
@@ -127,6 +150,35 @@ export class StartLiveKitAgentRoomSessionInput {
|
|
|
127
150
|
|
|
128
151
|
@Field(() => String, { nullable: true })
|
|
129
152
|
TurnMode?: string;
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Start the agent able to bring someone into this room: a person (who is offered the conversation and may accept), a
|
|
156
|
+
* phone number, or another agent. Opt-in, and honoured only when this server has the handoff collaborators configured
|
|
157
|
+
* (the LiveKit SIP extension); otherwise the agent starts as it always did.
|
|
158
|
+
*/
|
|
159
|
+
@Field(() => Boolean, { nullable: true })
|
|
160
|
+
EnableHandoff?: boolean;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* How this agent decides it was addressed: `Auto` (default — the model's own judgement when it is full-duplex,
|
|
164
|
+
* name matching otherwise), `ModelSide`, or `Regex`.
|
|
165
|
+
*/
|
|
166
|
+
@Field(() => String, { nullable: true })
|
|
167
|
+
TurnAddressing?: string;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** The live turn-taking state of a room (floor holder, human speaking, hand-off, loop cap, recent events, seated agents). */
|
|
171
|
+
@ObjectType()
|
|
172
|
+
export class LiveKitRoomTurnStateResult {
|
|
173
|
+
@Field(() => Boolean)
|
|
174
|
+
Success: boolean;
|
|
175
|
+
|
|
176
|
+
@Field(() => String, { nullable: true })
|
|
177
|
+
ErrorMessage?: string;
|
|
178
|
+
|
|
179
|
+
/** JSON of the room's `RoomTurnSnapshot`; `null` when the room holds no agents. Parsed by the typed client. */
|
|
180
|
+
@Field(() => String, { nullable: true })
|
|
181
|
+
StateJSON?: string;
|
|
130
182
|
}
|
|
131
183
|
|
|
132
184
|
@ObjectType()
|
|
@@ -231,6 +283,11 @@ export class RealtimeBridgeResolver extends ResolverBase {
|
|
|
231
283
|
if (!user) {
|
|
232
284
|
return failure('Unable to determine current user.');
|
|
233
285
|
}
|
|
286
|
+
const provider = GetReadWriteProvider(context.providers) as IMetadataProvider;
|
|
287
|
+
const auth = await RoomAuthorizationService.Instance.AuthorizeRoomAccess(input.RoomName, user, provider);
|
|
288
|
+
if (!auth.Authorized) {
|
|
289
|
+
return failure(auth.Reason ?? 'Unauthorized room access.');
|
|
290
|
+
}
|
|
234
291
|
const tokenService = new LiveKitTokenService();
|
|
235
292
|
const minted = await tokenService.MintClientToken(input.RoomName, this.participantIdentity(user), input.DisplayName ?? user.Name ?? user.Email);
|
|
236
293
|
return { Success: true, ...minted };
|
|
@@ -264,9 +321,31 @@ export class RealtimeBridgeResolver extends ResolverBase {
|
|
|
264
321
|
if (!user) {
|
|
265
322
|
return failure('Unable to determine current user.');
|
|
266
323
|
}
|
|
267
|
-
const provider = GetReadWriteProvider(context.providers) as
|
|
324
|
+
const provider = GetReadWriteProvider(context.providers) as IMetadataProvider;
|
|
268
325
|
const roomName = input.RoomName?.trim() || `mj-${randomUUID()}`;
|
|
269
326
|
|
|
327
|
+
if (input.RoomName?.trim()) {
|
|
328
|
+
const auth = await RoomAuthorizationService.Instance.AuthorizeRoomAccess(roomName, user, provider);
|
|
329
|
+
if (!auth.Authorized) {
|
|
330
|
+
return failure(auth.Reason ?? 'Unauthorized room access.', roomName);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Opt-in: an agent that can bring a person, a number or another agent into this room (the same room, so a visitor
|
|
335
|
+
// is escalated without leaving it). Falls through to the standard start when the server cannot do handoffs.
|
|
336
|
+
const handoffSessionBridgeID = await this.startWithHandoff(input, roomName, user, provider);
|
|
337
|
+
if (handoffSessionBridgeID) {
|
|
338
|
+
const handoffToken = await new LiveKitTokenService().MintClientToken(roomName, this.participantIdentity(user), user.Name ?? user.Email);
|
|
339
|
+
return {
|
|
340
|
+
Success: true,
|
|
341
|
+
SessionBridgeID: handoffSessionBridgeID,
|
|
342
|
+
RoomName: roomName,
|
|
343
|
+
ServerUrl: handoffToken.ServerUrl,
|
|
344
|
+
ClientToken: handoffToken.Token,
|
|
345
|
+
Identity: handoffToken.Identity,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
270
349
|
// Resolve the AIAgentSession the bridge will reference. The bridge row FK-references
|
|
271
350
|
// AIAgentSession(ID), so we must use an EXISTING session — either one the caller supplied, or a
|
|
272
351
|
// freshly-created one. Previously this minted a bare random UUID with no backing row, so the
|
|
@@ -293,6 +372,7 @@ export class RealtimeBridgeResolver extends ResolverBase {
|
|
|
293
372
|
RealtimeModelID: input.RealtimeModelID,
|
|
294
373
|
RealtimeVoice: input.RealtimeVoice,
|
|
295
374
|
TurnMode: this.normalizeTurnMode(input.TurnMode),
|
|
375
|
+
TurnAddressing: this.normalizeTurnAddressing(input.TurnAddressing),
|
|
296
376
|
ContextUser: user,
|
|
297
377
|
MetadataProvider: provider,
|
|
298
378
|
});
|
|
@@ -332,7 +412,15 @@ export class RealtimeBridgeResolver extends ResolverBase {
|
|
|
332
412
|
if (!user) {
|
|
333
413
|
return false;
|
|
334
414
|
}
|
|
335
|
-
const provider = GetReadWriteProvider(context.providers) as
|
|
415
|
+
const provider = GetReadWriteProvider(context.providers) as IMetadataProvider;
|
|
416
|
+
const roomName = LiveKitAgentRoomCoordinator.Instance.GetRoomForBridge(sessionBridgeID);
|
|
417
|
+
if (roomName) {
|
|
418
|
+
const auth = await RoomAuthorizationService.Instance.AuthorizeRoomAccess(roomName, user, provider);
|
|
419
|
+
if (!auth.Authorized) {
|
|
420
|
+
LogError(`StopLiveKitAgentRoomSession unauthorized: user ${user.ID} cannot access room ${roomName} (${auth.Reason})`);
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
336
424
|
return await LiveKitAgentRoomCoordinator.Instance.StopAgentRoomSession(sessionBridgeID, 'Explicit', user, provider);
|
|
337
425
|
} catch (error) {
|
|
338
426
|
LogError(`StopLiveKitAgentRoomSession failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -358,7 +446,12 @@ export class RealtimeBridgeResolver extends ResolverBase {
|
|
|
358
446
|
if (!user) {
|
|
359
447
|
return false;
|
|
360
448
|
}
|
|
361
|
-
const provider = GetReadWriteProvider(context.providers) as
|
|
449
|
+
const provider = GetReadWriteProvider(context.providers) as IMetadataProvider;
|
|
450
|
+
const auth = await RoomAuthorizationService.Instance.AuthorizeRoomAccess(roomName, user, provider);
|
|
451
|
+
if (!auth.Authorized) {
|
|
452
|
+
LogError(`EndLiveKitRoom unauthorized: user ${user.ID} cannot access room ${roomName} (${auth.Reason})`);
|
|
453
|
+
return false;
|
|
454
|
+
}
|
|
362
455
|
await LiveKitAgentRoomCoordinator.Instance.StopAllAgentsInRoom(roomName, 'Explicit', user, provider);
|
|
363
456
|
return true;
|
|
364
457
|
} catch (error) {
|
|
@@ -367,6 +460,37 @@ export class RealtimeBridgeResolver extends ResolverBase {
|
|
|
367
460
|
}
|
|
368
461
|
}
|
|
369
462
|
|
|
463
|
+
/**
|
|
464
|
+
* A room's live turn-taking state — who holds the floor, whether a person is speaking, any pending hand-off,
|
|
465
|
+
* the agent-to-agent loop-cap progress, the most recent floor/backchannel/yield events, and who is seated. The
|
|
466
|
+
* agent test bed polls it. Read-only. `StateJSON` is `null` when no agent is in the room.
|
|
467
|
+
*
|
|
468
|
+
* @param roomName The LiveKit room.
|
|
469
|
+
*/
|
|
470
|
+
@Query(() => LiveKitRoomTurnStateResult)
|
|
471
|
+
async GetLiveKitRoomTurnState(
|
|
472
|
+
@Arg('roomName', () => String) roomName: string,
|
|
473
|
+
@Ctx() context: AppContext = {} as AppContext,
|
|
474
|
+
): Promise<LiveKitRoomTurnStateResult> {
|
|
475
|
+
try {
|
|
476
|
+
const user = this.GetUserFromPayload(context.userPayload);
|
|
477
|
+
if (!user) {
|
|
478
|
+
return { Success: false, ErrorMessage: 'Unable to determine current user.' };
|
|
479
|
+
}
|
|
480
|
+
const provider = GetReadWriteProvider(context.providers) as IMetadataProvider;
|
|
481
|
+
const auth = await RoomAuthorizationService.Instance.AuthorizeRoomAccess(roomName, user, provider);
|
|
482
|
+
if (!auth.Authorized) {
|
|
483
|
+
return { Success: false, ErrorMessage: auth.Reason ?? 'Unauthorized room access.' };
|
|
484
|
+
}
|
|
485
|
+
const snapshot = LiveKitAgentRoomCoordinator.Instance.GetRoomTurnState(roomName);
|
|
486
|
+
return { Success: true, StateJSON: snapshot ? JSON.stringify(snapshot) : undefined };
|
|
487
|
+
} catch (error) {
|
|
488
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
489
|
+
LogError(`GetLiveKitRoomTurnState failed: ${msg}`);
|
|
490
|
+
return { Success: false, ErrorMessage: msg };
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
370
494
|
/**
|
|
371
495
|
* Lists active Realtime models with the voices each driver supports — the source for the dev model/voice
|
|
372
496
|
* picker (gated client-side by the `Realtime: Advanced Session Controls` authorization). Read-only; returns
|
|
@@ -381,7 +505,7 @@ export class RealtimeBridgeResolver extends ResolverBase {
|
|
|
381
505
|
if (!user) {
|
|
382
506
|
return [];
|
|
383
507
|
}
|
|
384
|
-
const provider = GetReadWriteProvider(context.providers) as
|
|
508
|
+
const provider = GetReadWriteProvider(context.providers) as IMetadataProvider;
|
|
385
509
|
return await GetRealtimeModelVoices(user, provider);
|
|
386
510
|
} catch (error) {
|
|
387
511
|
LogError(`GetRealtimeModelVoices failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -411,7 +535,12 @@ export class RealtimeBridgeResolver extends ResolverBase {
|
|
|
411
535
|
if (!user) {
|
|
412
536
|
return false;
|
|
413
537
|
}
|
|
414
|
-
const provider = GetReadWriteProvider(context.providers) as
|
|
538
|
+
const provider = GetReadWriteProvider(context.providers) as IMetadataProvider;
|
|
539
|
+
const auth = await RoomAuthorizationService.Instance.AuthorizeRoomAccess(roomName, user, provider);
|
|
540
|
+
if (!auth.Authorized) {
|
|
541
|
+
LogError(`InviteUsersToLiveKitRoom unauthorized: user ${user.ID} cannot access room ${roomName} (${auth.Reason})`);
|
|
542
|
+
return false;
|
|
543
|
+
}
|
|
415
544
|
await NotificationEngine.Instance.Config(false, user, provider);
|
|
416
545
|
|
|
417
546
|
const inviter = user.Name?.trim() || user.Email || 'Someone';
|
|
@@ -453,11 +582,15 @@ export class RealtimeBridgeResolver extends ResolverBase {
|
|
|
453
582
|
if (!user) {
|
|
454
583
|
return { Success: false, ErrorMessage: 'Unable to determine current user.', EgressID: '', Status: '' };
|
|
455
584
|
}
|
|
585
|
+
const provider = GetReadWriteProvider(context.providers) as IMetadataProvider;
|
|
586
|
+
const auth = await RoomAuthorizationService.Instance.AuthorizeRoomAccess(input.RoomName, user, provider);
|
|
587
|
+
if (!auth.Authorized) {
|
|
588
|
+
return { Success: false, ErrorMessage: auth.Reason ?? 'Unauthorized room access.', EgressID: '', Status: '' };
|
|
589
|
+
}
|
|
456
590
|
const info = await new LiveKitEgressService().StartRoomRecording({ RoomName: input.RoomName, Layout: input.Layout });
|
|
457
591
|
|
|
458
592
|
// Best-effort: correlate the live recording with the room's Meeting-Room Conversation (if it exists
|
|
459
593
|
// yet) by stamping its EgressID. Never fail the start on this — the stop-flow resolves/creates it.
|
|
460
|
-
const provider = GetReadWriteProvider(context.providers) as unknown as IMetadataProvider;
|
|
461
594
|
void CorrelateRecordingStart(input.RoomName, info.EgressID, user, provider);
|
|
462
595
|
|
|
463
596
|
return { Success: true, EgressID: info.EgressID, Status: info.Status };
|
|
@@ -481,8 +614,43 @@ export class RealtimeBridgeResolver extends ResolverBase {
|
|
|
481
614
|
if (!user) {
|
|
482
615
|
return { Success: false, ErrorMessage: 'Unable to determine current user.', EgressID: egressID, Status: '' };
|
|
483
616
|
}
|
|
484
|
-
const
|
|
485
|
-
|
|
617
|
+
const provider = GetReadWriteProvider(context.providers) as IMetadataProvider;
|
|
618
|
+
|
|
619
|
+
// Map egress ID to room name: check LiveKit egress service first, fallback to Conversation if available.
|
|
620
|
+
const egressService = new LiveKitEgressService();
|
|
621
|
+
let roomName = await egressService.GetRoomForEgress(egressID);
|
|
622
|
+
if (!roomName && provider) {
|
|
623
|
+
try {
|
|
624
|
+
const rv = RunView.FromMetadataProvider(provider);
|
|
625
|
+
const convResult = await rv.RunView<{ ExternalID: string }>(
|
|
626
|
+
{
|
|
627
|
+
EntityName: 'MJ: Conversations',
|
|
628
|
+
ExtraFilter: `EgressID='${EscapeSQLString(egressID)}' AND (IsArchived IS NULL OR IsArchived=0)`,
|
|
629
|
+
Fields: ['ExternalID'],
|
|
630
|
+
OrderBy: '__mj_CreatedAt DESC',
|
|
631
|
+
MaxRows: 1,
|
|
632
|
+
ResultType: 'simple',
|
|
633
|
+
},
|
|
634
|
+
user,
|
|
635
|
+
);
|
|
636
|
+
if (convResult.Success && convResult.Results.length > 0 && convResult.Results[0].ExternalID) {
|
|
637
|
+
roomName = convResult.Results[0].ExternalID;
|
|
638
|
+
}
|
|
639
|
+
} catch (err) {
|
|
640
|
+
LogError(`StopLiveKitRecording: error looking up Conversation for egress ID '${egressID}'`, undefined, err);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
if (roomName) {
|
|
645
|
+
const auth = await RoomAuthorizationService.Instance.AuthorizeRoomAccess(roomName, user, provider);
|
|
646
|
+
if (!auth.Authorized) {
|
|
647
|
+
return { Success: false, ErrorMessage: auth.Reason ?? 'Unauthorized room access.', EgressID: egressID, Status: '' };
|
|
648
|
+
}
|
|
649
|
+
} else {
|
|
650
|
+
return { Success: false, ErrorMessage: `Unable to determine room for egress ID '${egressID}'.`, EgressID: egressID, Status: '' };
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
const info = await egressService.StopRecording(egressID);
|
|
486
654
|
|
|
487
655
|
// Register the completed egress MP4 as a Files row on the Meeting-Room Conversation. Best-effort:
|
|
488
656
|
// any failure (e.g. storage provider not configured) leaves RecordingFileID unset but still
|
|
@@ -504,9 +672,55 @@ export class RealtimeBridgeResolver extends ResolverBase {
|
|
|
504
672
|
}
|
|
505
673
|
}
|
|
506
674
|
|
|
507
|
-
/**
|
|
675
|
+
/**
|
|
676
|
+
* Starts the agent through the handoff-capable room starter when the caller asked for it and the server can do it.
|
|
677
|
+
* Returns the bridge id, or `undefined` to mean "use the standard start" (not requested, no starter, no agent named).
|
|
678
|
+
*/
|
|
679
|
+
private async startWithHandoff(
|
|
680
|
+
input: StartLiveKitAgentRoomSessionInput,
|
|
681
|
+
roomName: string,
|
|
682
|
+
user: UserInfo,
|
|
683
|
+
provider: IMetadataProvider,
|
|
684
|
+
): Promise<string | undefined> {
|
|
685
|
+
if (!input.EnableHandoff) {
|
|
686
|
+
return undefined;
|
|
687
|
+
}
|
|
688
|
+
const starter = RoomHandoffEngine.Instance.Deps.AgentStarter;
|
|
689
|
+
const agentID = input.TargetAgentID?.trim() || input.AgentID?.trim();
|
|
690
|
+
if (!starter || !agentID) {
|
|
691
|
+
LogError('StartLiveKitAgentRoomSession: handoff was requested but this server has no handoff-capable agent starter; starting the agent without it.');
|
|
692
|
+
return undefined;
|
|
693
|
+
}
|
|
694
|
+
const started = await starter({
|
|
695
|
+
RoomName: roomName,
|
|
696
|
+
AgentID: agentID,
|
|
697
|
+
AgentName: input.AgentName?.trim() ?? '',
|
|
698
|
+
CallerLabel: user.Name?.trim() || user.Email || 'Web visitor',
|
|
699
|
+
ContextUser: user,
|
|
700
|
+
Provider: provider,
|
|
701
|
+
RealtimeModelID: input.RealtimeModelID,
|
|
702
|
+
RealtimeVoice: input.RealtimeVoice,
|
|
703
|
+
});
|
|
704
|
+
return started.SessionBridgeID;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/** Builds a stable, lowercased participant identity from the authenticated user (shared with the handoff engine, which watches for it). */
|
|
508
708
|
private participantIdentity(user: UserInfo): string {
|
|
509
|
-
return
|
|
709
|
+
return LiveKitUserIdentity(user.ID);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/** Normalizes an addressing-mode string; unknown/absent leaves the coordinator's default (`Auto`). */
|
|
713
|
+
private normalizeTurnAddressing(mode?: string): TurnAddressingMode | undefined {
|
|
714
|
+
switch ((mode ?? '').trim().toLowerCase()) {
|
|
715
|
+
case 'auto':
|
|
716
|
+
return 'Auto';
|
|
717
|
+
case 'modelside':
|
|
718
|
+
return 'ModelSide';
|
|
719
|
+
case 'regex':
|
|
720
|
+
return 'Regex';
|
|
721
|
+
default:
|
|
722
|
+
return undefined;
|
|
723
|
+
}
|
|
510
724
|
}
|
|
511
725
|
|
|
512
726
|
/** Normalizes a turn-mode string to the bridge's accepted values. */
|