@memberjunction/server 5.42.0 → 5.44.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/SessionJanitor.d.ts.map +1 -1
- package/dist/agentSessions/SessionJanitor.js +5 -1
- package/dist/agentSessions/SessionJanitor.js.map +1 -1
- package/dist/agentSessions/SessionManager.d.ts +15 -2
- package/dist/agentSessions/SessionManager.d.ts.map +1 -1
- package/dist/agentSessions/SessionManager.js +53 -11
- 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/generated/generated.d.ts +1180 -51
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +35564 -29262
- package/dist/generated/generated.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -1
- package/dist/index.js.map +1 -1
- package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts +20 -1
- package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts.map +1 -1
- package/dist/resolvers/ExecuteRemoteOperationResolver.js +61 -5
- package/dist/resolvers/ExecuteRemoteOperationResolver.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/IntegrationDiscoveryResolver.d.ts.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.js +98 -29
- package/dist/resolvers/IntegrationDiscoveryResolver.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/RealtimeBridgeResolver.d.ts +17 -1
- package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.js +78 -7
- package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +81 -2
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +293 -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/RunAIAgentResolver.d.ts +12 -3
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +37 -15
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunTemplateResolver.d.ts +8 -0
- package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTemplateResolver.js +8 -0
- package/dist/resolvers/RunTemplateResolver.js.map +1 -1
- 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 +192 -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/package.json +83 -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/agentSessions/SessionJanitor.ts +15 -1
- package/src/agentSessions/SessionManager.ts +62 -10
- package/src/agentSessions/index.ts +3 -1
- package/src/generated/generated.ts +17659 -13322
- package/src/index.ts +15 -1
- package/src/resolvers/ExecuteRemoteOperationResolver.ts +60 -4
- package/src/resolvers/FileResolver.ts +199 -1
- package/src/resolvers/IntegrationDiscoveryResolver.ts +108 -30
- package/src/resolvers/QueryResolver.ts +33 -19
- package/src/resolvers/RealtimeBridgeResolver.ts +82 -7
- package/src/resolvers/RealtimeClientSessionResolver.ts +326 -9
- package/src/resolvers/RemoteBrowserActionResolver.ts +18 -2
- package/src/resolvers/RunAIAgentResolver.ts +44 -11
- package/src/resolvers/RunTemplateResolver.ts +8 -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 +220 -0
- package/src/rest/mediaRange.ts +76 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Arg, Ctx, ObjectType, Query, Resolver, Field, Int, InputType } from 'type-graphql';
|
|
2
|
-
import { RunQuery, IRunQueryProvider, IMetadataProvider, RunQueryParams, LogError, RunQueryWithCacheCheckParams, RunQueriesWithCacheCheckResponse, RunQueryWithCacheCheckResult } from '@memberjunction/core';
|
|
2
|
+
import { RunQuery, IRunQueryProvider, IMetadataProvider, RunQueryParams, LogError, RunQueryWithCacheCheckParams, RunQueriesWithCacheCheckResponse, RunQueryWithCacheCheckResult, RunQueryEnrichment } from '@memberjunction/core';
|
|
3
3
|
import { AppContext } from '../types.js';
|
|
4
4
|
import { RequireSystemUser } from '../directives/RequireSystemUser.js';
|
|
5
5
|
import { GraphQLJSONObject } from 'graphql-type-json';
|
|
@@ -40,6 +40,9 @@ export class RunQueryInput {
|
|
|
40
40
|
|
|
41
41
|
@Field(() => String, { nullable: true, description: 'Description to use in audit log' })
|
|
42
42
|
AuditLogDescription?: string;
|
|
43
|
+
|
|
44
|
+
@Field(() => GraphQLJSONObject, { nullable: true, description: 'Optional runtime-only directive to post-process result rows through a registered query result enricher ({ EnricherKey, Config })' })
|
|
45
|
+
Enrichment?: RunQueryEnrichment;
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
@ObjectType()
|
|
@@ -191,7 +194,8 @@ export class RunQueryResolver extends ResolverBase {
|
|
|
191
194
|
@Arg('MaxRows', () => Int, {nullable: true}) MaxRows?: number,
|
|
192
195
|
@Arg('StartRow', () => Int, {nullable: true}) StartRow?: number,
|
|
193
196
|
@Arg('ForceAuditLog', () => Boolean, {nullable: true}) ForceAuditLog?: boolean,
|
|
194
|
-
@Arg('AuditLogDescription', () => String, {nullable: true}) AuditLogDescription?: string
|
|
197
|
+
@Arg('AuditLogDescription', () => String, {nullable: true}) AuditLogDescription?: string,
|
|
198
|
+
@Arg('Enrichment', () => GraphQLJSONObject, {nullable: true}) Enrichment?: RunQueryEnrichment): Promise<RunQueryResultType> {
|
|
195
199
|
// Check API key scope authorization for query execution
|
|
196
200
|
await this.CheckAPIKeyScopeAuthorization('query:run', QueryID, context.userPayload);
|
|
197
201
|
|
|
@@ -200,7 +204,7 @@ export class RunQueryResolver extends ResolverBase {
|
|
|
200
204
|
const rq = new RunQuery(provider as unknown as IRunQueryProvider);
|
|
201
205
|
console.log('GetQueryData called with:', { QueryID, Parameters, MaxRows, StartRow, ForceAuditLog, AuditLogDescription });
|
|
202
206
|
const result = await rq.RunQuery(
|
|
203
|
-
{
|
|
207
|
+
{
|
|
204
208
|
QueryID: QueryID,
|
|
205
209
|
CategoryID: CategoryID,
|
|
206
210
|
CategoryPath: CategoryPath,
|
|
@@ -208,8 +212,9 @@ export class RunQueryResolver extends ResolverBase {
|
|
|
208
212
|
MaxRows: MaxRows,
|
|
209
213
|
StartRow: StartRow,
|
|
210
214
|
ForceAuditLog: ForceAuditLog,
|
|
211
|
-
AuditLogDescription: AuditLogDescription
|
|
212
|
-
|
|
215
|
+
AuditLogDescription: AuditLogDescription,
|
|
216
|
+
Enrichment: Enrichment
|
|
217
|
+
},
|
|
213
218
|
context.userPayload.userRecord);
|
|
214
219
|
console.log('RunQuery result:', {
|
|
215
220
|
Success: result.Success,
|
|
@@ -256,22 +261,24 @@ export class RunQueryResolver extends ResolverBase {
|
|
|
256
261
|
@Arg('MaxRows', () => Int, {nullable: true}) MaxRows?: number,
|
|
257
262
|
@Arg('StartRow', () => Int, {nullable: true}) StartRow?: number,
|
|
258
263
|
@Arg('ForceAuditLog', () => Boolean, {nullable: true}) ForceAuditLog?: boolean,
|
|
259
|
-
@Arg('AuditLogDescription', () => String, {nullable: true}) AuditLogDescription?: string
|
|
264
|
+
@Arg('AuditLogDescription', () => String, {nullable: true}) AuditLogDescription?: string,
|
|
265
|
+
@Arg('Enrichment', () => GraphQLJSONObject, {nullable: true}) Enrichment?: RunQueryEnrichment): Promise<RunQueryResultType> {
|
|
260
266
|
// Check API key scope authorization for query execution
|
|
261
267
|
await this.CheckAPIKeyScopeAuthorization('query:run', QueryName, context.userPayload);
|
|
262
268
|
|
|
263
269
|
const provider = GetReadOnlyProvider(context.providers, {allowFallbackToReadWrite: true});
|
|
264
270
|
const rq = new RunQuery(provider as unknown as IRunQueryProvider);
|
|
265
271
|
const result = await rq.RunQuery(
|
|
266
|
-
{
|
|
267
|
-
QueryName: QueryName,
|
|
272
|
+
{
|
|
273
|
+
QueryName: QueryName,
|
|
268
274
|
CategoryID: CategoryID,
|
|
269
275
|
CategoryPath: CategoryPath,
|
|
270
276
|
Parameters: Parameters,
|
|
271
277
|
MaxRows: MaxRows,
|
|
272
278
|
StartRow: StartRow,
|
|
273
279
|
ForceAuditLog: ForceAuditLog,
|
|
274
|
-
AuditLogDescription: AuditLogDescription
|
|
280
|
+
AuditLogDescription: AuditLogDescription,
|
|
281
|
+
Enrichment: Enrichment
|
|
275
282
|
},
|
|
276
283
|
context.userPayload.userRecord);
|
|
277
284
|
|
|
@@ -302,13 +309,14 @@ export class RunQueryResolver extends ResolverBase {
|
|
|
302
309
|
@Arg('MaxRows', () => Int, {nullable: true}) MaxRows?: number,
|
|
303
310
|
@Arg('StartRow', () => Int, {nullable: true}) StartRow?: number,
|
|
304
311
|
@Arg('ForceAuditLog', () => Boolean, {nullable: true}) ForceAuditLog?: boolean,
|
|
305
|
-
@Arg('AuditLogDescription', () => String, {nullable: true}) AuditLogDescription?: string
|
|
312
|
+
@Arg('AuditLogDescription', () => String, {nullable: true}) AuditLogDescription?: string,
|
|
313
|
+
@Arg('Enrichment', () => GraphQLJSONObject, {nullable: true}) Enrichment?: RunQueryEnrichment): Promise<RunQueryResultType> {
|
|
306
314
|
const provider = GetReadOnlyProvider(context.providers, {allowFallbackToReadWrite: true});
|
|
307
315
|
const md = provider as unknown as IMetadataProvider;
|
|
308
316
|
const rq = new RunQuery(provider as unknown as IRunQueryProvider);
|
|
309
317
|
|
|
310
318
|
const result = await rq.RunQuery(
|
|
311
|
-
{
|
|
319
|
+
{
|
|
312
320
|
QueryID: QueryID,
|
|
313
321
|
CategoryID: CategoryID,
|
|
314
322
|
CategoryPath: CategoryPath,
|
|
@@ -316,8 +324,9 @@ export class RunQueryResolver extends ResolverBase {
|
|
|
316
324
|
MaxRows: MaxRows,
|
|
317
325
|
StartRow: StartRow,
|
|
318
326
|
ForceAuditLog: ForceAuditLog,
|
|
319
|
-
AuditLogDescription: AuditLogDescription
|
|
320
|
-
|
|
327
|
+
AuditLogDescription: AuditLogDescription,
|
|
328
|
+
Enrichment: Enrichment
|
|
329
|
+
},
|
|
321
330
|
context.userPayload.userRecord);
|
|
322
331
|
|
|
323
332
|
// If QueryName is not populated by the provider, use efficient lookup
|
|
@@ -360,12 +369,13 @@ export class RunQueryResolver extends ResolverBase {
|
|
|
360
369
|
@Arg('MaxRows', () => Int, {nullable: true}) MaxRows?: number,
|
|
361
370
|
@Arg('StartRow', () => Int, {nullable: true}) StartRow?: number,
|
|
362
371
|
@Arg('ForceAuditLog', () => Boolean, {nullable: true}) ForceAuditLog?: boolean,
|
|
363
|
-
@Arg('AuditLogDescription', () => String, {nullable: true}) AuditLogDescription?: string
|
|
372
|
+
@Arg('AuditLogDescription', () => String, {nullable: true}) AuditLogDescription?: string,
|
|
373
|
+
@Arg('Enrichment', () => GraphQLJSONObject, {nullable: true}) Enrichment?: RunQueryEnrichment): Promise<RunQueryResultType> {
|
|
364
374
|
const provider = GetReadOnlyProvider(context.providers, {allowFallbackToReadWrite: true});
|
|
365
375
|
const rq = new RunQuery(provider as unknown as IRunQueryProvider);
|
|
366
376
|
|
|
367
377
|
const result = await rq.RunQuery(
|
|
368
|
-
{
|
|
378
|
+
{
|
|
369
379
|
QueryName: QueryName,
|
|
370
380
|
CategoryID: CategoryID,
|
|
371
381
|
CategoryPath: CategoryPath,
|
|
@@ -373,8 +383,9 @@ export class RunQueryResolver extends ResolverBase {
|
|
|
373
383
|
MaxRows: MaxRows,
|
|
374
384
|
StartRow: StartRow,
|
|
375
385
|
ForceAuditLog: ForceAuditLog,
|
|
376
|
-
AuditLogDescription: AuditLogDescription
|
|
377
|
-
|
|
386
|
+
AuditLogDescription: AuditLogDescription,
|
|
387
|
+
Enrichment: Enrichment
|
|
388
|
+
},
|
|
378
389
|
context.userPayload.userRecord);
|
|
379
390
|
|
|
380
391
|
return {
|
|
@@ -420,7 +431,8 @@ export class RunQueryResolver extends ResolverBase {
|
|
|
420
431
|
MaxRows: i.MaxRows,
|
|
421
432
|
StartRow: i.StartRow,
|
|
422
433
|
ForceAuditLog: i.ForceAuditLog,
|
|
423
|
-
AuditLogDescription: i.AuditLogDescription
|
|
434
|
+
AuditLogDescription: i.AuditLogDescription,
|
|
435
|
+
Enrichment: i.Enrichment
|
|
424
436
|
}));
|
|
425
437
|
|
|
426
438
|
// Execute all queries in parallel using the batch method
|
|
@@ -469,7 +481,8 @@ export class RunQueryResolver extends ResolverBase {
|
|
|
469
481
|
MaxRows: i.MaxRows,
|
|
470
482
|
StartRow: i.StartRow,
|
|
471
483
|
ForceAuditLog: i.ForceAuditLog,
|
|
472
|
-
AuditLogDescription: i.AuditLogDescription
|
|
484
|
+
AuditLogDescription: i.AuditLogDescription,
|
|
485
|
+
Enrichment: i.Enrichment
|
|
473
486
|
}));
|
|
474
487
|
|
|
475
488
|
// Execute all queries in parallel using the batch method
|
|
@@ -532,6 +545,7 @@ export class RunQueryResolver extends ResolverBase {
|
|
|
532
545
|
StartRow: item.params.StartRow,
|
|
533
546
|
ForceAuditLog: item.params.ForceAuditLog,
|
|
534
547
|
AuditLogDescription: item.params.AuditLogDescription,
|
|
548
|
+
Enrichment: item.params.Enrichment,
|
|
535
549
|
},
|
|
536
550
|
cacheStatus: item.cacheStatus ? {
|
|
537
551
|
maxUpdatedAt: item.cacheStatus.maxUpdatedAt,
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { Resolver, Mutation, Query, Arg, Ctx, ObjectType, InputType, Field } from 'type-graphql';
|
|
2
2
|
import { randomUUID } from 'crypto';
|
|
3
|
-
import { LogError, UserInfo, IMetadataProvider } from '@memberjunction/core';
|
|
3
|
+
import { LogError, LogStatusEx, UserInfo, IMetadataProvider } from '@memberjunction/core';
|
|
4
4
|
import { LiveKitTokenService, LiveKitAgentRoomCoordinator, LiveKitEgressService } from '@memberjunction/livekit-room-server';
|
|
5
5
|
import { AppContext } from '../types.js';
|
|
6
6
|
import { ResolverBase } from '../generic/ResolverBase.js';
|
|
7
7
|
import { GetReadWriteProvider } from '../util.js';
|
|
8
|
-
import { CreateBridgeRealtimeSession, FinalizeBridgeCoAgentRuns, GetRealtimeModelVoices, CreateBridgeRoomTranscriptSink } from '@memberjunction/ai-agents';
|
|
8
|
+
import { CreateBridgeRealtimeSession, FinalizeBridgeCoAgentRuns, GetRealtimeModelVoices, CreateBridgeRoomTranscriptSink, RealtimeTurnModeratorDecision } from '@memberjunction/ai-agents';
|
|
9
9
|
import { AIBridgeEngine } from '@memberjunction/ai-bridge-server';
|
|
10
10
|
import { SessionManager } from '../agentSessions/SessionManager.js';
|
|
11
11
|
import { NotificationEngine } from '@memberjunction/notifications';
|
|
12
|
+
import { registerMeetingRecordingFile, correlateRecordingStart } from './meetingRecordingRegistration.js';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Binds the agent realtime-session factory onto the LiveKit room coordinator's model-session creation seam.
|
|
@@ -36,9 +37,25 @@ AIBridgeEngine.Instance.SetSessionRunFinalizer(FinalizeBridgeCoAgentRuns);
|
|
|
36
37
|
* "Meeting Room"/scope choices live HERE (the Meet composition layer), keeping the engine generic.
|
|
37
38
|
*/
|
|
38
39
|
AIBridgeEngine.Instance.SetTranscriptSink(
|
|
39
|
-
CreateBridgeRoomTranscriptSink({ ConversationType: 'Meeting Room', ApplicationScope: 'Application' }),
|
|
40
|
+
CreateBridgeRoomTranscriptSink({ ConversationType: 'Meeting Room', ApplicationScope: 'Application', ApplicationName: 'Meet' }),
|
|
40
41
|
);
|
|
41
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Binds the room **turn moderator** onto the bridge engine — **OPT-IN, off by default**. When
|
|
45
|
+
* `MJ_REALTIME_MODERATOR_MODE=on`, a multi-agent room routes each turn through a fast LLM prompt that decides
|
|
46
|
+
* who speaks (see `RealtimeTurnModerator` in `@memberjunction/ai-agents`). By default it's OFF: agents run in
|
|
47
|
+
* plain auto-response, hear everything, and self-moderate (no STT-driven router in the loop) — the
|
|
48
|
+
* coordinator likewise skips meeting mode when the flag is off, so the two stay consistent. We keep the
|
|
49
|
+
* moderator wired-but-toggleable for controlled scenarios (webinars, large rooms, weaker models).
|
|
50
|
+
*/
|
|
51
|
+
if (process.env.MJ_REALTIME_MODERATOR_MODE === 'on') {
|
|
52
|
+
AIBridgeEngine.Instance.SetTurnModerator(RealtimeTurnModeratorDecision);
|
|
53
|
+
console.log('[RealtimeBridge] turn MODERATOR mode is ON (MJ_REALTIME_MODERATOR_MODE=on) — multi-agent rooms use the LLM router.');
|
|
54
|
+
} else {
|
|
55
|
+
// Default mode — only surface this at startup when verbose (MJ_VERBOSE) is on; it's the expected state and otherwise just noise.
|
|
56
|
+
LogStatusEx({ message: '[RealtimeBridge] turn moderator mode is OFF (default) — multi-agent rooms run free-for-all: all agents auto-respond + hear everything.', verboseOnly: true });
|
|
57
|
+
}
|
|
58
|
+
|
|
42
59
|
/**
|
|
43
60
|
* GraphQL surface for the MJ-native LiveKit room: mints scoped client access tokens and starts an
|
|
44
61
|
* agent's presence in a room. The thin resolver delegates to `@memberjunction/livekit-room-server`
|
|
@@ -158,6 +175,10 @@ export class LiveKitRecordingResult {
|
|
|
158
175
|
|
|
159
176
|
@Field(() => String)
|
|
160
177
|
Status: string;
|
|
178
|
+
|
|
179
|
+
/** The `MJ: Files` row id of the registered recording (set on stop, once the egress MP4 is registered). */
|
|
180
|
+
@Field(() => String, { nullable: true })
|
|
181
|
+
RecordingFileID?: string;
|
|
161
182
|
}
|
|
162
183
|
|
|
163
184
|
/** A selectable provider-native voice for the dev voice picker. */
|
|
@@ -319,6 +340,33 @@ export class RealtimeBridgeResolver extends ResolverBase {
|
|
|
319
340
|
}
|
|
320
341
|
}
|
|
321
342
|
|
|
343
|
+
/**
|
|
344
|
+
* **Ends the meeting for everyone**: stops EVERY agent bot bridged into a room (by room name, via the
|
|
345
|
+
* coordinator's server-side roster). This is the "End meeting" half of the Zoom-style leave control —
|
|
346
|
+
* usable by any participant, including one who only *joined* the room and never tracked the bridge ids.
|
|
347
|
+
* Returns `true` when the teardown ran (even if the room held zero agents). Best-effort: any error → `false`.
|
|
348
|
+
*
|
|
349
|
+
* @param roomName The LiveKit room to end.
|
|
350
|
+
*/
|
|
351
|
+
@Mutation(() => Boolean)
|
|
352
|
+
async EndLiveKitRoom(
|
|
353
|
+
@Arg('roomName', () => String) roomName: string,
|
|
354
|
+
@Ctx() context: AppContext = {} as AppContext,
|
|
355
|
+
): Promise<boolean> {
|
|
356
|
+
try {
|
|
357
|
+
const user = this.GetUserFromPayload(context.userPayload);
|
|
358
|
+
if (!user) {
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
361
|
+
const provider = GetReadWriteProvider(context.providers) as unknown as IMetadataProvider;
|
|
362
|
+
await LiveKitAgentRoomCoordinator.Instance.StopAllAgentsInRoom(roomName, 'Explicit', user, provider);
|
|
363
|
+
return true;
|
|
364
|
+
} catch (error) {
|
|
365
|
+
LogError(`EndLiveKitRoom failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
322
370
|
/**
|
|
323
371
|
* Lists active Realtime models with the voices each driver supports — the source for the dev model/voice
|
|
324
372
|
* picker (gated client-side by the `Realtime: Advanced Session Controls` authorization). Read-only; returns
|
|
@@ -401,10 +449,17 @@ export class RealtimeBridgeResolver extends ResolverBase {
|
|
|
401
449
|
@Ctx() context: AppContext = {} as AppContext,
|
|
402
450
|
): Promise<LiveKitRecordingResult> {
|
|
403
451
|
try {
|
|
404
|
-
|
|
452
|
+
const user = this.GetUserFromPayload(context.userPayload);
|
|
453
|
+
if (!user) {
|
|
405
454
|
return { Success: false, ErrorMessage: 'Unable to determine current user.', EgressID: '', Status: '' };
|
|
406
455
|
}
|
|
407
456
|
const info = await new LiveKitEgressService().StartRoomRecording({ RoomName: input.RoomName, Layout: input.Layout });
|
|
457
|
+
|
|
458
|
+
// Best-effort: correlate the live recording with the room's Meeting-Room Conversation (if it exists
|
|
459
|
+
// 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
|
+
void correlateRecordingStart(input.RoomName, info.EgressID, user, provider);
|
|
462
|
+
|
|
408
463
|
return { Success: true, EgressID: info.EgressID, Status: info.Status };
|
|
409
464
|
} catch (error) {
|
|
410
465
|
const msg = error instanceof Error ? error.message : String(error);
|
|
@@ -413,15 +468,35 @@ export class RealtimeBridgeResolver extends ResolverBase {
|
|
|
413
468
|
}
|
|
414
469
|
}
|
|
415
470
|
|
|
416
|
-
/**
|
|
471
|
+
/**
|
|
472
|
+
* Stops a recording by egress id, then REGISTERS the completed egress MP4 into MJStorage as an
|
|
473
|
+
* `MJ: Files` row linked to the room's Meeting-Room Conversation (`Conversation.RecordingFileID`).
|
|
474
|
+
* Registration is best-effort: a missing storage config or any failure returns the stop result with no
|
|
475
|
+
* `RecordingFileID` (the recording still stopped) — it never throws.
|
|
476
|
+
*/
|
|
417
477
|
@Mutation(() => LiveKitRecordingResult)
|
|
418
478
|
async StopLiveKitRecording(@Arg('egressID', () => String) egressID: string, @Ctx() context: AppContext = {} as AppContext): Promise<LiveKitRecordingResult> {
|
|
419
479
|
try {
|
|
420
|
-
|
|
480
|
+
const user = this.GetUserFromPayload(context.userPayload);
|
|
481
|
+
if (!user) {
|
|
421
482
|
return { Success: false, ErrorMessage: 'Unable to determine current user.', EgressID: egressID, Status: '' };
|
|
422
483
|
}
|
|
423
484
|
const info = await new LiveKitEgressService().StopRecording(egressID);
|
|
424
|
-
|
|
485
|
+
const provider = GetReadWriteProvider(context.providers) as unknown as IMetadataProvider;
|
|
486
|
+
|
|
487
|
+
// Register the completed egress MP4 as a Files row on the Meeting-Room Conversation. Best-effort:
|
|
488
|
+
// any failure (e.g. storage provider not configured) leaves RecordingFileID unset but still
|
|
489
|
+
// returns the successful stop result.
|
|
490
|
+
const registration = await registerMeetingRecordingFile(
|
|
491
|
+
{ EgressID: info.EgressID, RoomName: info.RoomName, OutputLocation: info.OutputLocation, OutputSizeBytes: info.OutputSizeBytes },
|
|
492
|
+
user,
|
|
493
|
+
provider,
|
|
494
|
+
);
|
|
495
|
+
if (!registration.Success) {
|
|
496
|
+
LogError(`StopLiveKitRecording: recording stopped but registration did not complete: ${registration.ErrorMessage ?? 'unknown'}`);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
return { Success: true, EgressID: info.EgressID, Status: info.Status, RecordingFileID: registration.RecordingFileID };
|
|
425
500
|
} catch (error) {
|
|
426
501
|
const msg = error instanceof Error ? error.message : String(error);
|
|
427
502
|
LogError(`StopLiveKitRecording failed: ${msg}`);
|