@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.
Files changed (101) hide show
  1. package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
  2. package/dist/agentSessions/SessionJanitor.js +5 -1
  3. package/dist/agentSessions/SessionJanitor.js.map +1 -1
  4. package/dist/agentSessions/SessionManager.d.ts +15 -2
  5. package/dist/agentSessions/SessionManager.d.ts.map +1 -1
  6. package/dist/agentSessions/SessionManager.js +53 -11
  7. package/dist/agentSessions/SessionManager.js.map +1 -1
  8. package/dist/agentSessions/index.d.ts.map +1 -1
  9. package/dist/agentSessions/index.js +3 -1
  10. package/dist/agentSessions/index.js.map +1 -1
  11. package/dist/generated/generated.d.ts +1180 -51
  12. package/dist/generated/generated.d.ts.map +1 -1
  13. package/dist/generated/generated.js +35564 -29262
  14. package/dist/generated/generated.js.map +1 -1
  15. package/dist/index.d.ts +2 -0
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +14 -1
  18. package/dist/index.js.map +1 -1
  19. package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts +20 -1
  20. package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts.map +1 -1
  21. package/dist/resolvers/ExecuteRemoteOperationResolver.js +61 -5
  22. package/dist/resolvers/ExecuteRemoteOperationResolver.js.map +1 -1
  23. package/dist/resolvers/FileResolver.d.ts +74 -0
  24. package/dist/resolvers/FileResolver.d.ts.map +1 -1
  25. package/dist/resolvers/FileResolver.js +211 -2
  26. package/dist/resolvers/FileResolver.js.map +1 -1
  27. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
  28. package/dist/resolvers/IntegrationDiscoveryResolver.js +98 -29
  29. package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
  30. package/dist/resolvers/QueryResolver.d.ts +6 -4
  31. package/dist/resolvers/QueryResolver.d.ts.map +1 -1
  32. package/dist/resolvers/QueryResolver.js +29 -14
  33. package/dist/resolvers/QueryResolver.js.map +1 -1
  34. package/dist/resolvers/RealtimeBridgeResolver.d.ts +17 -1
  35. package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
  36. package/dist/resolvers/RealtimeBridgeResolver.js +78 -7
  37. package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
  38. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +81 -2
  39. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  40. package/dist/resolvers/RealtimeClientSessionResolver.js +293 -17
  41. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  42. package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
  43. package/dist/resolvers/RemoteBrowserActionResolver.js +17 -2
  44. package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
  45. package/dist/resolvers/RunAIAgentResolver.d.ts +12 -3
  46. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  47. package/dist/resolvers/RunAIAgentResolver.js +37 -15
  48. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  49. package/dist/resolvers/RunTemplateResolver.d.ts +8 -0
  50. package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
  51. package/dist/resolvers/RunTemplateResolver.js +8 -0
  52. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  53. package/dist/resolvers/meetingRecordingRegistration.d.ts +124 -0
  54. package/dist/resolvers/meetingRecordingRegistration.d.ts.map +1 -0
  55. package/dist/resolvers/meetingRecordingRegistration.js +311 -0
  56. package/dist/resolvers/meetingRecordingRegistration.js.map +1 -0
  57. package/dist/resolvers/peaksSidecar.d.ts +30 -0
  58. package/dist/resolvers/peaksSidecar.d.ts.map +1 -0
  59. package/dist/resolvers/peaksSidecar.js +51 -0
  60. package/dist/resolvers/peaksSidecar.js.map +1 -0
  61. package/dist/rest/MediaAccessKeys.d.ts +68 -0
  62. package/dist/rest/MediaAccessKeys.d.ts.map +1 -0
  63. package/dist/rest/MediaAccessKeys.js +96 -0
  64. package/dist/rest/MediaAccessKeys.js.map +1 -0
  65. package/dist/rest/MediaStreamHandler.d.ts +26 -0
  66. package/dist/rest/MediaStreamHandler.d.ts.map +1 -0
  67. package/dist/rest/MediaStreamHandler.js +192 -0
  68. package/dist/rest/MediaStreamHandler.js.map +1 -0
  69. package/dist/rest/mediaRange.d.ts +41 -0
  70. package/dist/rest/mediaRange.d.ts.map +1 -0
  71. package/dist/rest/mediaRange.js +60 -0
  72. package/dist/rest/mediaRange.js.map +1 -0
  73. package/package.json +83 -82
  74. package/src/__tests__/FileResolverPeaks.test.ts +64 -0
  75. package/src/__tests__/MediaAccessKeys.test.ts +68 -0
  76. package/src/__tests__/MediaStreamHandler.test.ts +91 -0
  77. package/src/__tests__/RealtimeBridgeResolver.test.ts +10 -1
  78. package/src/__tests__/RealtimeClientSessionResolver.test.ts +69 -0
  79. package/src/__tests__/RealtimeCoAgentAppAwareness.integration.test.ts +191 -0
  80. package/src/__tests__/RemoteBrowserSnapshot.test.ts +123 -0
  81. package/src/__tests__/SessionManager.test.ts +2 -0
  82. package/src/__tests__/meetingRecordingRegistration.test.ts +248 -0
  83. package/src/agentSessions/SessionJanitor.ts +15 -1
  84. package/src/agentSessions/SessionManager.ts +62 -10
  85. package/src/agentSessions/index.ts +3 -1
  86. package/src/generated/generated.ts +17659 -13322
  87. package/src/index.ts +15 -1
  88. package/src/resolvers/ExecuteRemoteOperationResolver.ts +60 -4
  89. package/src/resolvers/FileResolver.ts +199 -1
  90. package/src/resolvers/IntegrationDiscoveryResolver.ts +108 -30
  91. package/src/resolvers/QueryResolver.ts +33 -19
  92. package/src/resolvers/RealtimeBridgeResolver.ts +82 -7
  93. package/src/resolvers/RealtimeClientSessionResolver.ts +326 -9
  94. package/src/resolvers/RemoteBrowserActionResolver.ts +18 -2
  95. package/src/resolvers/RunAIAgentResolver.ts +44 -11
  96. package/src/resolvers/RunTemplateResolver.ts +8 -0
  97. package/src/resolvers/meetingRecordingRegistration.ts +432 -0
  98. package/src/resolvers/peaksSidecar.ts +52 -0
  99. package/src/rest/MediaAccessKeys.ts +121 -0
  100. package/src/rest/MediaStreamHandler.ts +220 -0
  101. package/src/rest/mediaRange.ts +76 -0
@@ -116,6 +116,20 @@ export declare class CancelRealtimeSessionToolResult {
116
116
  /** Human-readable failure reason. Null on success. */
117
117
  ErrorMessage?: string;
118
118
  }
119
+ /**
120
+ * Result of {@link RealtimeClientSessionResolver.UploadRealtimeRecording} — a structured
121
+ * success/failure envelope. A recording-storage failure must never throw to the browser (the
122
+ * session it belongs to has already ended), so problems come back as `Success: false` with a
123
+ * human-readable reason; only ownership/authn violations (from `loadOwnedSession`) still throw.
124
+ */
125
+ export declare class UploadRealtimeRecordingResult {
126
+ /** True when the audio was stored and the session was stamped with the recording file. */
127
+ Success: boolean;
128
+ /** Human-readable failure reason. Null on success. */
129
+ ErrorMessage?: string;
130
+ /** ID of the created `MJ: Files` row holding the uploaded recording. Null on failure. */
131
+ FileID?: string;
132
+ }
119
133
  /**
120
134
  * Resolver for the client-direct realtime voice topology. A single {@link SessionManager} and a
121
135
  * single {@link RealtimeClientSessionService} are shared across requests — neither holds per-user or
@@ -163,7 +177,7 @@ export declare class RealtimeClientSessionResolver extends ResolverBase {
163
177
  *
164
178
  * @returns The ephemeral config + session linkage the browser needs to open its socket.
165
179
  */
166
- StartRealtimeClientSession(targetAgentId: string | undefined, { userPayload, providers }: AppContext, conversationId?: string, lastSessionId?: string, preferredModelId?: string, clientToolsJson?: string, coAgentId?: string, configOverridesJson?: string): Promise<StartRealtimeClientSessionResult>;
180
+ StartRealtimeClientSession(targetAgentId: string | undefined, { userPayload, providers }: AppContext, conversationId?: string, lastSessionId?: string, preferredModelId?: string, clientToolsJson?: string, coAgentId?: string, configOverridesJson?: string, recordingStartedAt?: string, recordingConsent?: boolean, mediaCollectionId?: string, applicationId?: string, appContextJson?: string): Promise<StartRealtimeClientSessionResult>;
167
181
  /**
168
182
  * Execute a single tool call the browser relayed from its provider socket and return the
169
183
  * serialized result for the browser to relay back to the model.
@@ -202,13 +216,53 @@ export declare class RealtimeClientSessionResolver extends ResolverBase {
202
216
  *
203
217
  * @returns `true` when the transcript turn was persisted.
204
218
  */
205
- RelayRealtimeTranscript(agentSessionId: string, role: string, text: string, { userPayload, providers }: AppContext, replacesPrevious?: boolean): Promise<boolean>;
219
+ RelayRealtimeTranscript(agentSessionId: string, role: string, text: string, { userPayload, providers }: AppContext, replacesPrevious?: boolean, utteranceStartMs?: number, utteranceEndMs?: number): Promise<boolean>;
206
220
  /**
207
221
  * Maps the relayed transcript role (`'User'`/`'AI'`/`'Assistant'`, case-insensitive) to the
208
222
  * standard chat-message role stored in `AIPromptRun.Messages`. Anything that isn't an explicit
209
223
  * user turn is treated as the assistant (the co-agent's own speech).
210
224
  */
211
225
  private mapTranscriptRoleToChatRole;
226
+ /**
227
+ * Upload a CLIENT-DIRECT session audio recording, store it in MJStorage, link it to the owning
228
+ * `AIAgentSession`, and stamp the session's recording fields. The browser records locally during
229
+ * the call and uploads the assembled blob (base64) once the session ends.
230
+ *
231
+ * Hard gates (in order):
232
+ * 1. Ownership — `loadOwnedSession` enforces `UserID === contextUser.ID` (throws on violation).
233
+ * 2. Consent — `consent !== true` is a hard refusal: no audio is ever stored without it.
234
+ * 3. Storage configuration — the session's agent must resolve a recording storage account (the
235
+ * agent's `RecordingStorageProviderID`, else `AttachmentStorageProviderID`).
236
+ *
237
+ * Storage failures (and any unexpected throw) come back as `Success: false` with a reason — they
238
+ * NEVER throw to the browser, mirroring the shared {@link storeRealtimeRecording} contract.
239
+ *
240
+ * @param agentSessionId The session the recording belongs to (ownership-gated).
241
+ * @param audioBase64 The base64-encoded recording bytes (client-direct: seekable WAV/PCM).
242
+ * @param mimeType The audio MIME type (`audio/wav` for client-direct; legacy `audio/webm`/`ogg`/`mp4`).
243
+ * @param durationMs Optional client-measured recording duration (ms) — informational.
244
+ * @param consent Whether the user consented to recording. MUST be `true` or the upload is refused.
245
+ * @param peaks Optional capture-time waveform peaks (max-abs per bucket, normalized 0..1). When
246
+ * present, persisted as a `peaks.json` sidecar beside the recording for fast waveform rendering
247
+ * without re-decoding the audio.
248
+ * @returns A structured {@link UploadRealtimeRecordingResult} with the created `MJ: Files` id.
249
+ */
250
+ UploadRealtimeRecording(agentSessionId: string, audioBase64: string, mimeType: string, ctx: AppContext, durationMs?: number, consent?: boolean, peaks?: number[]): Promise<UploadRealtimeRecordingResult>;
251
+ /**
252
+ * Uploads ONE crash-recovery audio shard (~15s) for an IN-PROGRESS recording into the session's
253
+ * folder (`realtime-recordings/<sessionId>/seg-NNNN.<ext>`) as a raw storage object. Durability
254
+ * insurance during a live call so a browser/tab death loses at most the last window; the shards are
255
+ * deleted once the canonical consolidated file lands via {@link UploadRealtimeRecording}. Ownership-
256
+ * gated; consent was already established at session start. Best-effort — returns `false` (never
257
+ * throws) on any problem so a failed shard never disrupts the live call.
258
+ *
259
+ * @param agentSessionId The in-progress session (ownership-gated).
260
+ * @param segmentIndex 0-based shard index within the session.
261
+ * @param audioBase64 The base64-encoded shard bytes.
262
+ * @param mimeType The audio MIME type.
263
+ * @returns `true` when the shard was stored.
264
+ */
265
+ UploadRealtimeRecordingSegment(agentSessionId: string, segmentIndex: number, audioBase64: string, mimeType: string, ctx: AppContext): Promise<boolean>;
212
266
  /**
213
267
  * Relays a co-agent CHANNEL tool-call (browser_ / Whiteboard_ etc.) onto the session's co-agent
214
268
  * AIPromptRun.Messages — run-only observability so the run captures what the co-agent DID, not just
@@ -266,6 +320,12 @@ export declare class RealtimeClientSessionResolver extends ResolverBase {
266
320
  RelayRealtimeUsage(agentSessionId: string, inputTokens: number, outputTokens: number, { userPayload, providers }: AppContext): Promise<boolean>;
267
321
  /** Clamps a relayed token delta: negative / non-finite values become 0. */
268
322
  private clampTokenDelta;
323
+ /**
324
+ * Sanitizes client-supplied waveform peaks before they're persisted: drops anything that isn't a
325
+ * finite number, clamps each to `[0, 1]`, and caps the array length so a hostile client can't bloat
326
+ * the sidecar. Returns `undefined` for a missing/empty array (no sidecar written).
327
+ */
328
+ private sanitizePeaks;
269
329
  /**
270
330
  * Reads the co-agent `AIPromptRun` id from the session config WITHOUT the relay path's
271
331
  * throw-on-missing-target semantics — usage relay is best-effort, so a missing/malformed
@@ -643,6 +703,15 @@ export declare class RealtimeClientSessionResolver extends ResolverBase {
643
703
  * session-channel row is a silent no-op; failures log and never throw.
644
704
  */
645
705
  private stampSessionChannelLastActive;
706
+ /**
707
+ * Tolerantly parses the `appContextJson` mutation arg into an {@link AppContextSnapshot}. Returns
708
+ * `undefined` for absent/blank/malformed/non-object payloads — app context is best-effort enrichment
709
+ * and must never fail a session start.
710
+ *
711
+ * @param appContextJson The raw JSON string the browser sent, or undefined.
712
+ * @returns The parsed snapshot, or `undefined`.
713
+ */
714
+ private parseAppContext;
646
715
  /**
647
716
  * Tolerantly parses + validates client-declared UI tool definitions (see the SECURITY NOTE on
648
717
  * {@link StartRealtimeClientSession}). Never throws — any rejection logs and returns
@@ -669,6 +738,16 @@ export declare class RealtimeClientSessionResolver extends ResolverBase {
669
738
  *
670
739
  * @returns The boolean save result (logs `CompleteMessage` on failure).
671
740
  */
741
+ /**
742
+ * Stamps `RecordingStartedAt` (the recording `t0` alignment origin) + `RecordingMedia` on a
743
+ * just-started session when the browser captured WITH consent. Best-effort: a parse/save failure
744
+ * is logged and swallowed — a recording-metadata problem must never fail the session start.
745
+ *
746
+ * @param session The freshly created/loaded session.
747
+ * @param recordingConsent Whether the user consented to recording — only `true` stamps anything.
748
+ * @param recordingStartedAt ISO-8601 recording start timestamp from the browser.
749
+ */
750
+ private stampRecordingStart;
672
751
  private persistTranscriptTurn;
673
752
  /**
674
753
  * CORRECTION persistence (the client transcript's `ReplacesPrevious` marker — e.g.
@@ -1 +1 @@
1
- {"version":3,"file":"RealtimeClientSessionResolver.d.ts","sourceRoot":"","sources":["../../src/resolvers/RealtimeClientSessionResolver.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"RealtimeClientSessionResolver.d.ts","sourceRoot":"","sources":["../../src/resolvers/RealtimeClientSessionResolver.ts"],"names":[],"mappings":"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"}
@@ -36,12 +36,12 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
36
36
  *
37
37
  * @module @memberjunction/server
38
38
  */
39
- import { Resolver, Mutation, Arg, Ctx, Int, ObjectType, Field, PubSub, PubSubEngine } from 'type-graphql';
39
+ import { Resolver, Mutation, Arg, Ctx, Int, Float, ObjectType, Field, PubSub, PubSubEngine } from 'type-graphql';
40
40
  import { AuthorizationEvaluator, LogError, LogStatus, RunView } from '@memberjunction/core';
41
- import { UUIDsEqual } from '@memberjunction/global';
41
+ import { UUIDsEqual, IsValidUUID } from '@memberjunction/global';
42
42
  import { AIEngine } from '@memberjunction/aiengine';
43
43
  import { AIAgentPermissionHelper, AIEngineBase } from '@memberjunction/ai-engine-base';
44
- import { RealtimeClientSessionService, RealtimeChannelServerHost, EvaluateRuntimeOverrideAuthorization, ParseRealtimeTypeConfiguration, ResolveEffectiveRealtimeConfig, REALTIME_ADVANCED_SESSION_CONTROLS_AUTHORIZATION, } from '@memberjunction/ai-agents';
44
+ import { RealtimeClientSessionService, RealtimeChannelServerHost, EvaluateRuntimeOverrideAuthorization, ParseRealtimeTypeConfiguration, ResolveEffectiveRealtimeConfig, REALTIME_ADVANCED_SESSION_CONTROLS_AUTHORIZATION, resolveRecordingStorageAccountID, storeRealtimeRecording, writeRealtimeRecordingSegment, deleteRealtimeRecordingSegments, } from '@memberjunction/ai-agents';
45
45
  import { ResolverBase } from '../generic/ResolverBase.js';
46
46
  import { PUSH_STATUS_UPDATES_TOPIC } from '../generic/PushStatusResolver.js';
47
47
  import { GetReadWriteProvider } from '../util.js';
@@ -222,6 +222,30 @@ CancelRealtimeSessionToolResult = __decorate([
222
222
  ObjectType()
223
223
  ], CancelRealtimeSessionToolResult);
224
224
  export { CancelRealtimeSessionToolResult };
225
+ /**
226
+ * Result of {@link RealtimeClientSessionResolver.UploadRealtimeRecording} — a structured
227
+ * success/failure envelope. A recording-storage failure must never throw to the browser (the
228
+ * session it belongs to has already ended), so problems come back as `Success: false` with a
229
+ * human-readable reason; only ownership/authn violations (from `loadOwnedSession`) still throw.
230
+ */
231
+ let UploadRealtimeRecordingResult = class UploadRealtimeRecordingResult {
232
+ };
233
+ __decorate([
234
+ Field(() => Boolean),
235
+ __metadata("design:type", Boolean)
236
+ ], UploadRealtimeRecordingResult.prototype, "Success", void 0);
237
+ __decorate([
238
+ Field(() => String, { nullable: true }),
239
+ __metadata("design:type", String)
240
+ ], UploadRealtimeRecordingResult.prototype, "ErrorMessage", void 0);
241
+ __decorate([
242
+ Field(() => String, { nullable: true }),
243
+ __metadata("design:type", String)
244
+ ], UploadRealtimeRecordingResult.prototype, "FileID", void 0);
245
+ UploadRealtimeRecordingResult = __decorate([
246
+ ObjectType()
247
+ ], UploadRealtimeRecordingResult);
248
+ export { UploadRealtimeRecordingResult };
225
249
  /**
226
250
  * Resolver for the client-direct realtime voice topology. A single {@link SessionManager} and a
227
251
  * single {@link RealtimeClientSessionService} are shared across requests — neither holds per-user or
@@ -272,7 +296,7 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
272
296
  *
273
297
  * @returns The ephemeral config + session linkage the browser needs to open its socket.
274
298
  */
275
- async StartRealtimeClientSession(targetAgentId, { userPayload, providers }, conversationId, lastSessionId, preferredModelId, clientToolsJson, coAgentId, configOverridesJson) {
299
+ async StartRealtimeClientSession(targetAgentId, { userPayload, providers }, conversationId, lastSessionId, preferredModelId, clientToolsJson, coAgentId, configOverridesJson, recordingStartedAt, recordingConsent, mediaCollectionId, applicationId, appContextJson) {
276
300
  const { contextUser, provider } = this.requireUserAndProvider(userPayload, providers);
277
301
  const coAgentID = await this.resolveCoAgentID(targetAgentId, coAgentId, contextUser, provider);
278
302
  // PAIRING CONSTRAINTS: a co-agent with pairing rows is restricted to that target list
@@ -285,6 +309,19 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
285
309
  // silent ignore). Plain starts and within-pairing target selection are never gated here.
286
310
  await this.assertRuntimeOverridesAuthorized(coAgentID, configOverridesJson, preferredModelId, contextUser, provider);
287
311
  const config = { targetAgentID: effectiveTargetId };
312
+ // Per-session media-kit override: stored on the session config so the server-side MediaChannelServer
313
+ // resolves it (over the agent default) at start. Validate the id here — a malformed value is
314
+ // dropped (logged), never interpolated; the session still starts and falls back to the agent kit.
315
+ const mediaOverride = mediaCollectionId?.trim();
316
+ if (mediaOverride) {
317
+ if (IsValidUUID(mediaOverride)) {
318
+ config.mediaCollectionID = mediaOverride;
319
+ }
320
+ else {
321
+ LogStatus(`StartRealtimeClientSession: ignoring malformed mediaCollectionId '${mediaCollectionId}' — ` +
322
+ 'using the agent default media kit.');
323
+ }
324
+ }
288
325
  const session = await this.sessionManager.CreateSession({
289
326
  agentID: coAgentID,
290
327
  userID: contextUser.ID,
@@ -292,12 +329,14 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
292
329
  lastSessionID: lastSessionId,
293
330
  config: JSON.stringify(config),
294
331
  }, contextUser, provider);
332
+ // Best-effort: stamp recording-start metadata when the browser captured (with consent) at start.
333
+ await this.stampRecordingStart(session, recordingConsent, recordingStartedAt);
295
334
  const clientTools = this.parseClientTools(clientToolsJson);
296
335
  // Best-effort model-context hydration: the PRIOR session chain's transcript (ownership-
297
336
  // checked, capped) is framed into the system prompt so the model REMEMBERS the last leg.
298
337
  // Strictly tolerant — any problem yields no hydration, never a failed start.
299
338
  const priorTranscript = await this.loadPriorTranscript(lastSessionId, contextUser, provider);
300
- const result = await this.prepareClientSessionOrClose(session, coAgentID, effectiveTargetId, contextUser, provider, preferredModelId, clientTools, priorTranscript, configOverridesJson);
339
+ const result = await this.prepareClientSessionOrClose(session, coAgentID, effectiveTargetId, contextUser, provider, preferredModelId, clientTools, priorTranscript, configOverridesJson, applicationId, this.parseAppContext(appContextJson));
301
340
  // Best-effort restore of the PRIOR session's persisted channel states (e.g. the whiteboard
302
341
  // board). Strictly tolerant — any problem yields a null field, never a failed start.
303
342
  result.PriorChannelStatesJson = (await this.loadPriorChannelStatesJson(lastSessionId, contextUser, provider)) ?? undefined;
@@ -319,6 +358,9 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
319
358
  const { ResultJson, PausedRunID, Artifacts } = await this.clientSessionService.ExecuteRelayedTool({
320
359
  AgentSessionID: agentSessionId,
321
360
  TargetAgentID: config.targetAgentID,
361
+ // Multi-target (Move 4): the session's persisted allowed-agent union — a model-named
362
+ // colleague in the call is validated against this; absent ⇒ single-target behavior.
363
+ AllowedAgents: config.allowedAgents,
322
364
  // Nest the delegated target-agent run under the co-agent observability run (when present).
323
365
  ParentRunID: config.coAgentRunID,
324
366
  Call: { CallID: callId, ToolName: toolName, Arguments: argsJson },
@@ -378,6 +420,8 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
378
420
  promptRunID: config.promptRunID,
379
421
  coAgentRunStepID: config.coAgentRunStepID,
380
422
  pendingFeedbackRunID: pausedRunID,
423
+ applicationID: config.applicationID,
424
+ allowedAgents: config.allowedAgents,
381
425
  };
382
426
  session.Config_ = JSON.stringify(next);
383
427
  if (!(await session.Save())) {
@@ -397,12 +441,12 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
397
441
  *
398
442
  * @returns `true` when the transcript turn was persisted.
399
443
  */
400
- async RelayRealtimeTranscript(agentSessionId, role, text, { userPayload, providers }, replacesPrevious) {
444
+ async RelayRealtimeTranscript(agentSessionId, role, text, { userPayload, providers }, replacesPrevious, utteranceStartMs, utteranceEndMs) {
401
445
  const { contextUser, provider } = this.requireUserAndProvider(userPayload, providers);
402
446
  const session = await this.loadOwnedActiveSession(agentSessionId, contextUser, provider);
403
447
  const saved = replacesPrevious
404
- ? await this.replacePreviousTranscriptTurn(session, role, text, contextUser, provider)
405
- : await this.persistTranscriptTurn(session, role, text, contextUser, provider);
448
+ ? await this.replacePreviousTranscriptTurn(session, role, text, contextUser, provider, utteranceStartMs, utteranceEndMs)
449
+ : await this.persistTranscriptTurn(session, role, text, contextUser, provider, utteranceStartMs, utteranceEndMs);
406
450
  if (!saved) {
407
451
  return false;
408
452
  }
@@ -423,6 +467,122 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
423
467
  mapTranscriptRoleToChatRole(role) {
424
468
  return role.trim().toLowerCase() === 'user' ? 'user' : 'assistant';
425
469
  }
470
+ /**
471
+ * Upload a CLIENT-DIRECT session audio recording, store it in MJStorage, link it to the owning
472
+ * `AIAgentSession`, and stamp the session's recording fields. The browser records locally during
473
+ * the call and uploads the assembled blob (base64) once the session ends.
474
+ *
475
+ * Hard gates (in order):
476
+ * 1. Ownership — `loadOwnedSession` enforces `UserID === contextUser.ID` (throws on violation).
477
+ * 2. Consent — `consent !== true` is a hard refusal: no audio is ever stored without it.
478
+ * 3. Storage configuration — the session's agent must resolve a recording storage account (the
479
+ * agent's `RecordingStorageProviderID`, else `AttachmentStorageProviderID`).
480
+ *
481
+ * Storage failures (and any unexpected throw) come back as `Success: false` with a reason — they
482
+ * NEVER throw to the browser, mirroring the shared {@link storeRealtimeRecording} contract.
483
+ *
484
+ * @param agentSessionId The session the recording belongs to (ownership-gated).
485
+ * @param audioBase64 The base64-encoded recording bytes (client-direct: seekable WAV/PCM).
486
+ * @param mimeType The audio MIME type (`audio/wav` for client-direct; legacy `audio/webm`/`ogg`/`mp4`).
487
+ * @param durationMs Optional client-measured recording duration (ms) — informational.
488
+ * @param consent Whether the user consented to recording. MUST be `true` or the upload is refused.
489
+ * @param peaks Optional capture-time waveform peaks (max-abs per bucket, normalized 0..1). When
490
+ * present, persisted as a `peaks.json` sidecar beside the recording for fast waveform rendering
491
+ * without re-decoding the audio.
492
+ * @returns A structured {@link UploadRealtimeRecordingResult} with the created `MJ: Files` id.
493
+ */
494
+ async UploadRealtimeRecording(agentSessionId, audioBase64, mimeType, ctx, durationMs, consent, peaks) {
495
+ const { contextUser, provider } = this.requireUserAndProvider(ctx.userPayload, ctx.providers);
496
+ const session = await this.loadOwnedSession(agentSessionId, contextUser, provider);
497
+ // HARD consent gate — no audio is ever stored without an explicit grant.
498
+ if (consent !== true) {
499
+ return { Success: false, ErrorMessage: 'Recording consent was not granted.' };
500
+ }
501
+ try {
502
+ const agent = await provider.GetEntityObject('MJ: AI Agents', contextUser);
503
+ if (!(await agent.Load(session.AgentID))) {
504
+ return { Success: false, ErrorMessage: `Co-agent ${session.AgentID} for the session could not be loaded.` };
505
+ }
506
+ const accountID = await resolveRecordingStorageAccountID(agent, contextUser, provider);
507
+ if (!accountID) {
508
+ return { Success: false, ErrorMessage: 'No recording storage account is configured for this agent.' };
509
+ }
510
+ const buffer = Buffer.from(audioBase64, 'base64');
511
+ if (buffer.length === 0) {
512
+ return { Success: false, ErrorMessage: 'The uploaded recording was empty.' };
513
+ }
514
+ const fileID = await storeRealtimeRecording({
515
+ Audio: buffer,
516
+ MimeType: mimeType,
517
+ Media: 'Audio',
518
+ StartedAt: session.RecordingStartedAt ?? new Date(),
519
+ StorageAccountID: accountID,
520
+ SessionID: agentSessionId,
521
+ ContextUser: contextUser,
522
+ Provider: provider,
523
+ // Sanitized capture-time waveform peaks → persisted as a peaks.json sidecar.
524
+ Peaks: this.sanitizePeaks(peaks),
525
+ });
526
+ // Canonical consolidated file written — drop the crash-recovery shards (best-effort).
527
+ if (fileID) {
528
+ await deleteRealtimeRecordingSegments(agentSessionId, accountID, contextUser);
529
+ }
530
+ return {
531
+ Success: !!fileID,
532
+ FileID: fileID ?? undefined,
533
+ ErrorMessage: fileID ? undefined : 'Storage upload failed.',
534
+ };
535
+ }
536
+ catch (error) {
537
+ const message = error instanceof Error ? error.message : String(error);
538
+ LogError(`RealtimeClientSessionResolver.UploadRealtimeRecording failed for session ${agentSessionId}: ${message}`);
539
+ return { Success: false, ErrorMessage: message };
540
+ }
541
+ }
542
+ /**
543
+ * Uploads ONE crash-recovery audio shard (~15s) for an IN-PROGRESS recording into the session's
544
+ * folder (`realtime-recordings/<sessionId>/seg-NNNN.<ext>`) as a raw storage object. Durability
545
+ * insurance during a live call so a browser/tab death loses at most the last window; the shards are
546
+ * deleted once the canonical consolidated file lands via {@link UploadRealtimeRecording}. Ownership-
547
+ * gated; consent was already established at session start. Best-effort — returns `false` (never
548
+ * throws) on any problem so a failed shard never disrupts the live call.
549
+ *
550
+ * @param agentSessionId The in-progress session (ownership-gated).
551
+ * @param segmentIndex 0-based shard index within the session.
552
+ * @param audioBase64 The base64-encoded shard bytes.
553
+ * @param mimeType The audio MIME type.
554
+ * @returns `true` when the shard was stored.
555
+ */
556
+ async UploadRealtimeRecordingSegment(agentSessionId, segmentIndex, audioBase64, mimeType, ctx) {
557
+ try {
558
+ const { contextUser, provider } = this.requireUserAndProvider(ctx.userPayload, ctx.providers);
559
+ const session = await this.loadOwnedSession(agentSessionId, contextUser, provider);
560
+ const agent = await provider.GetEntityObject('MJ: AI Agents', contextUser);
561
+ if (!(await agent.Load(session.AgentID))) {
562
+ return false;
563
+ }
564
+ const accountID = await resolveRecordingStorageAccountID(agent, contextUser, provider);
565
+ if (!accountID) {
566
+ return false;
567
+ }
568
+ const buffer = Buffer.from(audioBase64, 'base64');
569
+ if (buffer.length === 0) {
570
+ return false;
571
+ }
572
+ return await writeRealtimeRecordingSegment({
573
+ SessionID: agentSessionId,
574
+ SegmentIndex: segmentIndex,
575
+ Audio: buffer,
576
+ MimeType: mimeType,
577
+ StorageAccountID: accountID,
578
+ ContextUser: contextUser,
579
+ });
580
+ }
581
+ catch (error) {
582
+ LogError(`RealtimeClientSessionResolver.UploadRealtimeRecordingSegment failed for session ${agentSessionId}: ${error instanceof Error ? error.message : String(error)}`);
583
+ return false;
584
+ }
585
+ }
426
586
  /**
427
587
  * Relays a co-agent CHANNEL tool-call (browser_ / Whiteboard_ etc.) onto the session's co-agent
428
588
  * AIPromptRun.Messages — run-only observability so the run captures what the co-agent DID, not just
@@ -525,6 +685,22 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
525
685
  clampTokenDelta(value) {
526
686
  return Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
527
687
  }
688
+ /**
689
+ * Sanitizes client-supplied waveform peaks before they're persisted: drops anything that isn't a
690
+ * finite number, clamps each to `[0, 1]`, and caps the array length so a hostile client can't bloat
691
+ * the sidecar. Returns `undefined` for a missing/empty array (no sidecar written).
692
+ */
693
+ sanitizePeaks(peaks) {
694
+ if (!Array.isArray(peaks) || peaks.length === 0) {
695
+ return undefined;
696
+ }
697
+ const MAX_PEAKS = 4096; // generous ceiling above the client's ~600-bucket target
698
+ const cleaned = peaks
699
+ .slice(0, MAX_PEAKS)
700
+ .filter((v) => Number.isFinite(v))
701
+ .map((v) => (v < 0 ? 0 : v > 1 ? 1 : v));
702
+ return cleaned.length > 0 ? cleaned : undefined;
703
+ }
528
704
  /**
529
705
  * Reads the co-agent `AIPromptRun` id from the session config WITHOUT the relay path's
530
706
  * throw-on-missing-target semantics — usage relay is best-effort, so a missing/malformed
@@ -1014,7 +1190,7 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
1014
1190
  * (from {@link loadPriorTranscript}) — the service frames it into the system prompt so a
1015
1191
  * resumed session remembers the previous leg(s).
1016
1192
  */
1017
- async prepareClientSessionOrClose(session, coAgentID, targetAgentId, contextUser, provider, preferredModelId, clientTools, priorTranscript, configOverridesJson) {
1193
+ async prepareClientSessionOrClose(session, coAgentID, targetAgentId, contextUser, provider, preferredModelId, clientTools, priorTranscript, configOverridesJson, applicationId, appContext) {
1018
1194
  const prep = await this.clientSessionService.PrepareClientSession({
1019
1195
  CoAgentID: coAgentID,
1020
1196
  TargetAgentID: targetAgentId,
@@ -1033,13 +1209,17 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
1033
1209
  PriorTranscript: priorTranscript,
1034
1210
  // Pre-authorized runtime override layer (assertRuntimeOverridesAuthorized gated it).
1035
1211
  ConfigOverridesJson: configOverridesJson,
1212
+ // App awareness (Move 1/3/4): the app sources the app cascade layer + RelevantAgents
1213
+ // (allowed-agent union); the snapshot is injected into the system prompt at mint.
1214
+ ApplicationID: applicationId,
1215
+ AppContext: appContext,
1036
1216
  }, contextUser, provider);
1037
1217
  if (!prep.Success || !prep.ClientConfig) {
1038
1218
  // Prep failure is an ERROR close, not an explicit user hang-up — stamp it as such.
1039
1219
  await this.sessionManager.CloseSession(session.ID, contextUser, provider, 'Error');
1040
1220
  throw new Error(prep.ErrorMessage ?? 'Failed to prepare the client realtime session.');
1041
1221
  }
1042
- await this.persistObservabilityRunIDs(session, targetAgentId, prep.CoAgentRunID, prep.PromptRunID, prep.CoAgentRunStepID);
1222
+ await this.persistObservabilityRunIDs(session, targetAgentId, prep.CoAgentRunID, prep.PromptRunID, prep.CoAgentRunStepID, applicationId, prep.EffectiveConfig?.realtime?.allowedAgents);
1043
1223
  const cfg = prep.ClientConfig;
1044
1224
  return {
1045
1225
  AgentSessionId: session.ID,
@@ -1061,8 +1241,11 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
1061
1241
  * finalize the runs (and on relay to nest delegated runs). Best-effort: a save failure is logged,
1062
1242
  * not thrown — the voice session still proceeds, it just won't carry the run ids.
1063
1243
  */
1064
- async persistObservabilityRunIDs(session, targetAgentID, coAgentRunID, promptRunID, coAgentRunStepID) {
1065
- const config = { targetAgentID, coAgentRunID, promptRunID, coAgentRunStepID };
1244
+ async persistObservabilityRunIDs(session, targetAgentID, coAgentRunID, promptRunID, coAgentRunStepID, applicationID, allowedAgents) {
1245
+ const config = {
1246
+ targetAgentID, coAgentRunID, promptRunID, coAgentRunStepID, applicationID,
1247
+ allowedAgents: allowedAgents && allowedAgents.length > 0 ? allowedAgents : undefined,
1248
+ };
1066
1249
  session.Config_ = JSON.stringify(config);
1067
1250
  const saved = await session.Save();
1068
1251
  if (!saved) {
@@ -1545,6 +1728,28 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
1545
1728
  LogError(`SaveSessionChannelArtifact: LastActiveAt stamp failed: ${error.message}`);
1546
1729
  }
1547
1730
  }
1731
+ /**
1732
+ * Tolerantly parses the `appContextJson` mutation arg into an {@link AppContextSnapshot}. Returns
1733
+ * `undefined` for absent/blank/malformed/non-object payloads — app context is best-effort enrichment
1734
+ * and must never fail a session start.
1735
+ *
1736
+ * @param appContextJson The raw JSON string the browser sent, or undefined.
1737
+ * @returns The parsed snapshot, or `undefined`.
1738
+ */
1739
+ parseAppContext(appContextJson) {
1740
+ if (!appContextJson || !appContextJson.trim()) {
1741
+ return undefined;
1742
+ }
1743
+ try {
1744
+ const parsed = JSON.parse(appContextJson);
1745
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
1746
+ ? parsed
1747
+ : undefined;
1748
+ }
1749
+ catch {
1750
+ return undefined;
1751
+ }
1752
+ }
1548
1753
  /**
1549
1754
  * Tolerantly parses + validates client-declared UI tool definitions (see the SECURITY NOTE on
1550
1755
  * {@link StartRealtimeClientSession}). Never throws — any rejection logs and returns
@@ -1613,6 +1818,8 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
1613
1818
  promptRunID: typeof parsed.promptRunID === 'string' ? parsed.promptRunID : undefined,
1614
1819
  coAgentRunStepID: typeof parsed.coAgentRunStepID === 'string' ? parsed.coAgentRunStepID : undefined,
1615
1820
  pendingFeedbackRunID: typeof parsed.pendingFeedbackRunID === 'string' ? parsed.pendingFeedbackRunID : undefined,
1821
+ applicationID: typeof parsed.applicationID === 'string' ? parsed.applicationID : undefined,
1822
+ allowedAgents: Array.isArray(parsed.allowedAgents) ? parsed.allowedAgents : undefined,
1616
1823
  };
1617
1824
  }
1618
1825
  }
@@ -1628,7 +1835,31 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
1628
1835
  *
1629
1836
  * @returns The boolean save result (logs `CompleteMessage` on failure).
1630
1837
  */
1631
- async persistTranscriptTurn(session, role, text, contextUser, provider) {
1838
+ /**
1839
+ * Stamps `RecordingStartedAt` (the recording `t0` alignment origin) + `RecordingMedia` on a
1840
+ * just-started session when the browser captured WITH consent. Best-effort: a parse/save failure
1841
+ * is logged and swallowed — a recording-metadata problem must never fail the session start.
1842
+ *
1843
+ * @param session The freshly created/loaded session.
1844
+ * @param recordingConsent Whether the user consented to recording — only `true` stamps anything.
1845
+ * @param recordingStartedAt ISO-8601 recording start timestamp from the browser.
1846
+ */
1847
+ async stampRecordingStart(session, recordingConsent, recordingStartedAt) {
1848
+ if (recordingConsent !== true || !recordingStartedAt) {
1849
+ return;
1850
+ }
1851
+ const startedAt = new Date(recordingStartedAt);
1852
+ if (Number.isNaN(startedAt.getTime())) {
1853
+ LogError(`RealtimeClientSessionResolver.stampRecordingStart: invalid recordingStartedAt '${recordingStartedAt}' for session ${session.ID}`);
1854
+ return;
1855
+ }
1856
+ session.RecordingStartedAt = startedAt;
1857
+ session.RecordingMedia = 'Audio';
1858
+ if (!(await session.Save())) {
1859
+ LogError(`RealtimeClientSessionResolver.stampRecordingStart save failed: ${session.LatestResult?.CompleteMessage ?? 'unknown error'}`);
1860
+ }
1861
+ }
1862
+ async persistTranscriptTurn(session, role, text, contextUser, provider, utteranceStartMs, utteranceEndMs) {
1632
1863
  const detail = await provider.GetEntityObject(CONVERSATION_DETAIL_ENTITY, contextUser);
1633
1864
  detail.NewRecord();
1634
1865
  detail.ConversationID = session.ConversationID;
@@ -1636,6 +1867,12 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
1636
1867
  detail.Message = text;
1637
1868
  detail.AgentSessionID = session.ID;
1638
1869
  detail.UserID = contextUser.ID;
1870
+ if (typeof utteranceStartMs === 'number' && utteranceStartMs >= 0) {
1871
+ detail.UtteranceStartMs = utteranceStartMs;
1872
+ }
1873
+ if (typeof utteranceEndMs === 'number' && utteranceEndMs >= 0) {
1874
+ detail.UtteranceEndMs = utteranceEndMs;
1875
+ }
1639
1876
  const saved = await detail.Save();
1640
1877
  if (!saved) {
1641
1878
  LogError(`RealtimeClientSessionResolver.persistTranscriptTurn save failed: ${detail.LatestResult?.CompleteMessage ?? 'unknown error'}`);
@@ -1649,7 +1886,7 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
1649
1886
  * appending a near-duplicate. Falls back to a plain insert when no prior turn exists
1650
1887
  * (the superseded turn may have failed to persist) — a correction is never dropped.
1651
1888
  */
1652
- async replacePreviousTranscriptTurn(session, role, text, contextUser, provider) {
1889
+ async replacePreviousTranscriptTurn(session, role, text, contextUser, provider, utteranceStartMs, utteranceEndMs) {
1653
1890
  const mappedRole = this.mapTranscriptRole(role);
1654
1891
  const rv = RunView.FromMetadataProvider(provider);
1655
1892
  const result = await rv.RunView({
@@ -1661,9 +1898,17 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
1661
1898
  }, contextUser);
1662
1899
  const previous = result.Success ? (result.Results?.[0] ?? null) : null;
1663
1900
  if (!previous) {
1664
- return this.persistTranscriptTurn(session, role, text, contextUser, provider);
1901
+ return this.persistTranscriptTurn(session, role, text, contextUser, provider, utteranceStartMs, utteranceEndMs);
1665
1902
  }
1666
1903
  previous.Message = text;
1904
+ // The correction extends the existing turn: always refresh the end boundary when provided,
1905
+ // but only set the start when it wasn't already captured on the superseded turn.
1906
+ if (typeof utteranceEndMs === 'number' && utteranceEndMs >= 0) {
1907
+ previous.UtteranceEndMs = utteranceEndMs;
1908
+ }
1909
+ if (typeof utteranceStartMs === 'number' && utteranceStartMs >= 0 && previous.UtteranceStartMs == null) {
1910
+ previous.UtteranceStartMs = utteranceStartMs;
1911
+ }
1667
1912
  const saved = await previous.Save();
1668
1913
  if (!saved) {
1669
1914
  LogError(`RealtimeClientSessionResolver.replacePreviousTranscriptTurn save failed: ${previous.LatestResult?.CompleteMessage ?? 'unknown error'}`);
@@ -1689,8 +1934,13 @@ __decorate([
1689
1934
  __param(5, Arg('clientToolsJson', () => String, { nullable: true })),
1690
1935
  __param(6, Arg('coAgentId', () => String, { nullable: true })),
1691
1936
  __param(7, Arg('configOverridesJson', () => String, { nullable: true })),
1937
+ __param(8, Arg('recordingStartedAt', () => String, { nullable: true })),
1938
+ __param(9, Arg('recordingConsent', () => Boolean, { nullable: true })),
1939
+ __param(10, Arg('mediaCollectionId', () => String, { nullable: true })),
1940
+ __param(11, Arg('applicationId', () => String, { nullable: true })),
1941
+ __param(12, Arg('appContextJson', () => String, { nullable: true })),
1692
1942
  __metadata("design:type", Function),
1693
- __metadata("design:paramtypes", [String, Object, String, String, String, String, String, String]),
1943
+ __metadata("design:paramtypes", [String, Object, String, String, String, String, String, String, String, Boolean, String, String, String]),
1694
1944
  __metadata("design:returntype", Promise)
1695
1945
  ], RealtimeClientSessionResolver.prototype, "StartRealtimeClientSession", null);
1696
1946
  __decorate([
@@ -1712,10 +1962,36 @@ __decorate([
1712
1962
  __param(2, Arg('text', () => String)),
1713
1963
  __param(3, Ctx()),
1714
1964
  __param(4, Arg('replacesPrevious', () => Boolean, { nullable: true })),
1965
+ __param(5, Arg('utteranceStartMs', () => Int, { nullable: true })),
1966
+ __param(6, Arg('utteranceEndMs', () => Int, { nullable: true })),
1715
1967
  __metadata("design:type", Function),
1716
- __metadata("design:paramtypes", [String, String, String, Object, Boolean]),
1968
+ __metadata("design:paramtypes", [String, String, String, Object, Boolean, Number, Number]),
1717
1969
  __metadata("design:returntype", Promise)
1718
1970
  ], RealtimeClientSessionResolver.prototype, "RelayRealtimeTranscript", null);
1971
+ __decorate([
1972
+ Mutation(() => UploadRealtimeRecordingResult),
1973
+ __param(0, Arg('agentSessionId', () => String)),
1974
+ __param(1, Arg('audioBase64', () => String)),
1975
+ __param(2, Arg('mimeType', () => String)),
1976
+ __param(3, Ctx()),
1977
+ __param(4, Arg('durationMs', () => Int, { nullable: true })),
1978
+ __param(5, Arg('consent', () => Boolean, { nullable: true })),
1979
+ __param(6, Arg('peaks', () => [Float], { nullable: true })),
1980
+ __metadata("design:type", Function),
1981
+ __metadata("design:paramtypes", [String, String, String, Object, Number, Boolean, Array]),
1982
+ __metadata("design:returntype", Promise)
1983
+ ], RealtimeClientSessionResolver.prototype, "UploadRealtimeRecording", null);
1984
+ __decorate([
1985
+ Mutation(() => Boolean),
1986
+ __param(0, Arg('agentSessionId', () => String)),
1987
+ __param(1, Arg('segmentIndex', () => Int)),
1988
+ __param(2, Arg('audioBase64', () => String)),
1989
+ __param(3, Arg('mimeType', () => String)),
1990
+ __param(4, Ctx()),
1991
+ __metadata("design:type", Function),
1992
+ __metadata("design:paramtypes", [String, Number, String, String, Object]),
1993
+ __metadata("design:returntype", Promise)
1994
+ ], RealtimeClientSessionResolver.prototype, "UploadRealtimeRecordingSegment", null);
1719
1995
  __decorate([
1720
1996
  Mutation(() => Boolean),
1721
1997
  __param(0, Arg('agentSessionId', () => String)),