@memberjunction/server 5.40.2 → 5.42.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 (129) hide show
  1. package/dist/agentSessions/HostInstance.d.ts +19 -0
  2. package/dist/agentSessions/HostInstance.d.ts.map +1 -0
  3. package/dist/agentSessions/HostInstance.js +48 -0
  4. package/dist/agentSessions/HostInstance.js.map +1 -0
  5. package/dist/agentSessions/SessionJanitor.d.ts +97 -0
  6. package/dist/agentSessions/SessionJanitor.d.ts.map +1 -0
  7. package/dist/agentSessions/SessionJanitor.js +222 -0
  8. package/dist/agentSessions/SessionJanitor.js.map +1 -0
  9. package/dist/agentSessions/SessionManager.d.ts +142 -0
  10. package/dist/agentSessions/SessionManager.d.ts.map +1 -0
  11. package/dist/agentSessions/SessionManager.js +308 -0
  12. package/dist/agentSessions/SessionManager.js.map +1 -0
  13. package/dist/agentSessions/index.d.ts +5 -0
  14. package/dist/agentSessions/index.d.ts.map +1 -0
  15. package/dist/agentSessions/index.js +31 -0
  16. package/dist/agentSessions/index.js.map +1 -0
  17. package/dist/agentSessions/remoteBrowserGoalEngine.d.ts +102 -0
  18. package/dist/agentSessions/remoteBrowserGoalEngine.d.ts.map +1 -0
  19. package/dist/agentSessions/remoteBrowserGoalEngine.js +164 -0
  20. package/dist/agentSessions/remoteBrowserGoalEngine.js.map +1 -0
  21. package/dist/agentSessions/remoteBrowserGoalRegistry.d.ts +66 -0
  22. package/dist/agentSessions/remoteBrowserGoalRegistry.d.ts.map +1 -0
  23. package/dist/agentSessions/remoteBrowserGoalRegistry.js +86 -0
  24. package/dist/agentSessions/remoteBrowserGoalRegistry.js.map +1 -0
  25. package/dist/auth/initializeProviders.d.ts.map +1 -1
  26. package/dist/auth/initializeProviders.js +4 -2
  27. package/dist/auth/initializeProviders.js.map +1 -1
  28. package/dist/config.d.ts +7 -0
  29. package/dist/config.d.ts.map +1 -1
  30. package/dist/config.js +12 -2
  31. package/dist/config.js.map +1 -1
  32. package/dist/context.d.ts.map +1 -1
  33. package/dist/context.js +41 -7
  34. package/dist/context.js.map +1 -1
  35. package/dist/generated/generated.d.ts +1296 -49
  36. package/dist/generated/generated.d.ts.map +1 -1
  37. package/dist/generated/generated.js +50343 -43421
  38. package/dist/generated/generated.js.map +1 -1
  39. package/dist/generic/ResolverBase.js +1 -1
  40. package/dist/generic/ResolverBase.js.map +1 -1
  41. package/dist/generic/RunViewResolver.js +9 -10
  42. package/dist/generic/RunViewResolver.js.map +1 -1
  43. package/dist/index.d.ts +4 -0
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +127 -54
  46. package/dist/index.js.map +1 -1
  47. package/dist/integration/CustomColumnPromoter.d.ts +108 -0
  48. package/dist/integration/CustomColumnPromoter.d.ts.map +1 -0
  49. package/dist/integration/CustomColumnPromoter.js +508 -0
  50. package/dist/integration/CustomColumnPromoter.js.map +1 -0
  51. package/dist/logging/StartupLogger.d.ts +177 -0
  52. package/dist/logging/StartupLogger.d.ts.map +1 -0
  53. package/dist/logging/StartupLogger.js +354 -0
  54. package/dist/logging/StartupLogger.js.map +1 -0
  55. package/dist/logging/variablesLoggingMiddleware.d.ts.map +1 -1
  56. package/dist/logging/variablesLoggingMiddleware.js +21 -2
  57. package/dist/logging/variablesLoggingMiddleware.js.map +1 -1
  58. package/dist/resolvers/AgentSessionResolver.d.ts +42 -0
  59. package/dist/resolvers/AgentSessionResolver.d.ts.map +1 -0
  60. package/dist/resolvers/AgentSessionResolver.js +152 -0
  61. package/dist/resolvers/AgentSessionResolver.js.map +1 -0
  62. package/dist/resolvers/EntityPermissionResolver.d.ts +16 -0
  63. package/dist/resolvers/EntityPermissionResolver.d.ts.map +1 -0
  64. package/dist/resolvers/EntityPermissionResolver.js +95 -0
  65. package/dist/resolvers/EntityPermissionResolver.js.map +1 -0
  66. package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts +27 -0
  67. package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts.map +1 -0
  68. package/dist/resolvers/ExecuteRemoteOperationResolver.js +136 -0
  69. package/dist/resolvers/ExecuteRemoteOperationResolver.js.map +1 -0
  70. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts +101 -1
  71. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
  72. package/dist/resolvers/IntegrationDiscoveryResolver.js +544 -42
  73. package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
  74. package/dist/resolvers/RealtimeBridgeResolver.d.ts +121 -0
  75. package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -0
  76. package/dist/resolvers/RealtimeBridgeResolver.js +533 -0
  77. package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -0
  78. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +688 -0
  79. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -0
  80. package/dist/resolvers/RealtimeClientSessionResolver.js +1774 -0
  81. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -0
  82. package/dist/resolvers/RemoteBrowserActionResolver.d.ts +432 -0
  83. package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -0
  84. package/dist/resolvers/RemoteBrowserActionResolver.js +1108 -0
  85. package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -0
  86. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  87. package/dist/resolvers/RunAIAgentResolver.js +0 -7
  88. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  89. package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
  90. package/dist/rest/setupRESTEndpoints.js +5 -3
  91. package/dist/rest/setupRESTEndpoints.js.map +1 -1
  92. package/dist/services/ScheduledJobsService.d.ts.map +1 -1
  93. package/dist/services/ScheduledJobsService.js +6 -5
  94. package/dist/services/ScheduledJobsService.js.map +1 -1
  95. package/package.json +83 -74
  96. package/src/__tests__/RealtimeBridgeResolver.test.ts +100 -0
  97. package/src/__tests__/RealtimeClientSessionResolver.test.ts +2605 -0
  98. package/src/__tests__/RemoteBrowserAudioStream.test.ts +181 -0
  99. package/src/__tests__/RemoteBrowserGoalEngine.test.ts +144 -0
  100. package/src/__tests__/SessionJanitor.test.ts +234 -0
  101. package/src/__tests__/SessionManager.test.ts +465 -0
  102. package/src/__tests__/remoteBrowserGoalRegistry.test.ts +81 -0
  103. package/src/__tests__/subscriptionRedaction.test.ts +5 -0
  104. package/src/agentSessions/HostInstance.ts +53 -0
  105. package/src/agentSessions/SessionJanitor.ts +267 -0
  106. package/src/agentSessions/SessionManager.ts +446 -0
  107. package/src/agentSessions/index.ts +32 -0
  108. package/src/agentSessions/remoteBrowserGoalEngine.ts +191 -0
  109. package/src/agentSessions/remoteBrowserGoalRegistry.ts +122 -0
  110. package/src/auth/initializeProviders.ts +4 -2
  111. package/src/config.ts +13 -2
  112. package/src/context.ts +42 -7
  113. package/src/generated/generated.ts +33808 -29025
  114. package/src/generic/ResolverBase.ts +1 -1
  115. package/src/generic/RunViewResolver.ts +9 -9
  116. package/src/index.ts +128 -54
  117. package/src/integration/CustomColumnPromoter.ts +597 -0
  118. package/src/logging/StartupLogger.ts +441 -0
  119. package/src/logging/variablesLoggingMiddleware.ts +25 -5
  120. package/src/resolvers/AgentSessionResolver.ts +138 -0
  121. package/src/resolvers/EntityPermissionResolver.ts +73 -0
  122. package/src/resolvers/ExecuteRemoteOperationResolver.ts +108 -0
  123. package/src/resolvers/IntegrationDiscoveryResolver.ts +396 -37
  124. package/src/resolvers/RealtimeBridgeResolver.ts +450 -0
  125. package/src/resolvers/RealtimeClientSessionResolver.ts +2162 -0
  126. package/src/resolvers/RemoteBrowserActionResolver.ts +1122 -0
  127. package/src/resolvers/RunAIAgentResolver.ts +1 -9
  128. package/src/rest/setupRESTEndpoints.ts +5 -3
  129. package/src/services/ScheduledJobsService.ts +6 -5
@@ -0,0 +1,688 @@
1
+ /**
2
+ * @fileoverview GraphQL resolvers for the CLIENT-DIRECT realtime voice topology (P5b-ii).
3
+ *
4
+ * These resolvers are **thin**: they wire two already-built pieces together —
5
+ * {@link RealtimeClientSessionService} (in `@memberjunction/ai-agents`, the server-side half of the
6
+ * client-direct contract) and {@link SessionManager} (the durable `AIAgentSession` record manager) —
7
+ * over a request's `contextUser` + request-scoped `IMetadataProvider`.
8
+ *
9
+ * In the client-direct topology the browser opens its OWN provider socket (e.g. WebRTC) using a
10
+ * server-minted ephemeral token, but the server retains authority over the system prompt + tool set
11
+ * and **executes** every tool call the browser relays back. The three mutations here cover the MVP:
12
+ *
13
+ * 1. {@link RealtimeClientSessionResolver.StartRealtimeClientSession} — authorize, create the
14
+ * session (storing the target agent id server-side, authoritatively), mint the ephemeral config.
15
+ * 2. {@link RealtimeClientSessionResolver.ExecuteRealtimeSessionTool} — execute a relayed tool call,
16
+ * reading the target agent id from the session (NOT the client) so the browser can't swap targets.
17
+ * 3. {@link RealtimeClientSessionResolver.RelayRealtimeTranscript} — persist a transcript turn as a
18
+ * `Conversation Detail`.
19
+ *
20
+ * Authorization model:
21
+ * - **Start**: the caller must have `CanRun` on the *target* agent (the Realtime Co-Agent is an internal
22
+ * orchestration agent, so the meaningful gate is the agent doing the real work).
23
+ * - **Execute / Relay**: inbound ownership — the session's `UserID` must equal `contextUser.ID`.
24
+ *
25
+ * @module @memberjunction/server
26
+ */
27
+ import { PubSubEngine } from 'type-graphql';
28
+ import { AppContext } from '../types.js';
29
+ import { ResolverBase } from '../generic/ResolverBase.js';
30
+ /**
31
+ * Result of {@link RealtimeClientSessionResolver.StartRealtimeClientSession} — everything the
32
+ * browser needs to open its own provider socket plus the durable session linkage.
33
+ */
34
+ export declare class StartRealtimeClientSessionResult {
35
+ /** ID of the newly created `AIAgentSession` record. */
36
+ AgentSessionId: string;
37
+ /** ID of the conversation the session is attached to (supplied or freshly created). */
38
+ ConversationId: string;
39
+ /** The provider that minted the credential (e.g. `'openai'`). */
40
+ Provider: string;
41
+ /** The provider realtime model id the session is scoped to (e.g. `gpt-realtime`). */
42
+ Model: string;
43
+ /** The short-lived client secret the browser presents to the provider to authenticate. */
44
+ EphemeralToken: string;
45
+ /** ISO-8601 timestamp at which {@link StartRealtimeClientSessionResult.EphemeralToken} expires. */
46
+ ExpiresAt: string;
47
+ /** JSON string of the provider-native session config the browser applies verbatim. */
48
+ SessionConfigJson: string;
49
+ /** Display name of the realtime model the session uses (e.g. `GPT Realtime 2`). Null when unknown. */
50
+ ModelName?: string;
51
+ /**
52
+ * DB-driven progress-narration instruction template (contains a `{{ progressMessage }}`
53
+ * placeholder). Null when the narration prompt is not present in this deployment's metadata —
54
+ * the browser falls back to its built-in narration text.
55
+ */
56
+ NarrationInstructionsTemplate?: string;
57
+ /**
58
+ * JSON object string keyed by channel NAME mapping to that channel's persisted state JSON from
59
+ * the caller's PRIOR session (`lastSessionId`) — e.g. `{"Whiteboard":"{...board scene...}"}`.
60
+ * Null when no `lastSessionId` was supplied, the prior session has no persisted channel state,
61
+ * the prior session is not owned by the caller, or the restore failed for any reason (restore
62
+ * is strictly best-effort — a session start NEVER fails because of it).
63
+ */
64
+ PriorChannelStatesJson?: string;
65
+ /**
66
+ * The effective narration pace (`realtime.narration.paceMs` from the co-agent's effective
67
+ * configuration) — minimum gap in ms between spoken progress updates. Null when not
68
+ * configured (the browser uses its built-in pacing default). In the client-direct topology
69
+ * narration pacing is enforced CLIENT-side, so the server surfaces the configured value here.
70
+ */
71
+ NarrationPaceMs?: number;
72
+ /**
73
+ * JSON of the RESOLVED effective realtime configuration for this session (type
74
+ * `DefaultConfiguration` ← agent `TypeConfiguration` ← authorized runtime overrides,
75
+ * deep-merged + normalized server-side). The browser uses it to apply client-side concerns
76
+ * (e.g. per-provider voice settings consumed by client drivers, narration pacing). Null only
77
+ * when the prepare service did not resolve a config (back-compat).
78
+ */
79
+ EffectiveConfigJson?: string;
80
+ }
81
+ /**
82
+ * Result of {@link RealtimeClientSessionResolver.SaveSessionChannelArtifact} — a structured
83
+ * success/failure envelope (graceful failures like a missing artifact type must not throw).
84
+ */
85
+ export declare class SaveSessionChannelArtifactResult {
86
+ /** True when the artifact + first version were both persisted. */
87
+ Success: boolean;
88
+ /** Human-readable failure reason. Null on success. */
89
+ ErrorMessage?: string;
90
+ /** ID of the created `MJ: Artifacts` row. Null when creation failed before the header saved. */
91
+ ArtifactID?: string;
92
+ /** ID of the created `MJ: Artifact Versions` row (version 1). Null on failure. */
93
+ ArtifactVersionID?: string;
94
+ /**
95
+ * True when the version was also linked into conversation history via a
96
+ * `MJ: Conversation Detail Artifacts` junction row. False when the session has no
97
+ * conversation, no conversation detail was stamped with this session id yet, or the
98
+ * (best-effort) junction save failed — none of which fail the overall save.
99
+ */
100
+ ConversationDetailLinked: boolean;
101
+ }
102
+ /**
103
+ * Result of {@link RealtimeClientSessionResolver.CancelRealtimeSessionTool} — a structured
104
+ * success/failure envelope, mirroring {@link SaveSessionChannelArtifactResult}: tolerated
105
+ * problems (an unexpected registry error) come back as `Success: false`, never a throw, while
106
+ * authn/ownership violations still throw like the sibling mutations.
107
+ */
108
+ export declare class CancelRealtimeSessionToolResult {
109
+ /**
110
+ * How many in-flight delegations were aborted. `0` is a legitimate SUCCESS outcome — the
111
+ * work the user wanted dead may simply have finished (or never started) already.
112
+ */
113
+ AbortedCount: number;
114
+ /** True when the cancel request was processed (even when nothing was in flight). */
115
+ Success: boolean;
116
+ /** Human-readable failure reason. Null on success. */
117
+ ErrorMessage?: string;
118
+ }
119
+ /**
120
+ * Resolver for the client-direct realtime voice topology. A single {@link SessionManager} and a
121
+ * single {@link RealtimeClientSessionService} are shared across requests — neither holds per-user or
122
+ * per-provider state; every method is passed the request `contextUser` + provider explicitly.
123
+ */
124
+ export declare class RealtimeClientSessionResolver extends ResolverBase {
125
+ private readonly sessionManager;
126
+ private readonly clientSessionService;
127
+ /**
128
+ * Start a client-direct realtime voice session targeting `targetAgentId`.
129
+ *
130
+ * Flow:
131
+ * 1. Authorize — the caller must have `CanRun` on the **target** agent; denial throws and no
132
+ * session is created.
133
+ * 2. Resolve the co-agent id via the metadata-driven resolution chain (runtime `coAgentId` →
134
+ * agent's `DefaultCoAgentID` → type-level `AIAgentCoAgent` default row → global Realtime Co-Agent) —
135
+ * see {@link RealtimeClientSessionResolver.resolveCoAgentID} for the full contract.
136
+ * 3. Create the durable `AIAgentSession` (run by the co-agent), storing `targetAgentID` in its
137
+ * config server-side — this is the authoritative target for all later relays.
138
+ * 4. Mint the {@link import('@memberjunction/ai').ClientRealtimeSessionConfig} via the service.
139
+ * On failure the just-created session is closed so no half-open session leaks.
140
+ *
141
+ * **SECURITY NOTE — `clientToolsJson`:** these are CLIENT-EXECUTED UI tools (e.g. the live
142
+ * whiteboard's `Whiteboard.*` surface). The server only *declares* them to the realtime model
143
+ * so it can call them — the server NEVER executes them, and a relayed call for one of these
144
+ * names falls into the standard "not available" path on the server side. They grant no
145
+ * server-side capability whatsoever; server-executed tools remain exclusively server-declared
146
+ * (`invoke-target-agent` + future action wiring). The declarations are still validated
147
+ * (count cap, size cap, per-tool shape) so a hostile client can't bloat the session config.
148
+ *
149
+ * @param targetAgentId The agent the co-agent voices. OPTIONAL when the resolved co-agent
150
+ * has pairing rows with an `IsDefault` target (`MJ: AI Agent Co Agents`) — the default
151
+ * pairing stands in. Required for a universal co-agent (zero pairing rows). When the
152
+ * co-agent HAS pairing rows, a supplied target must be in that list (clear error otherwise).
153
+ * @param coAgentId Optional EXPLICIT co-agent choice (`MJ: AI Agents.ID` of an Active,
154
+ * Realtime-type agent). When set, the server uses exactly that co-agent and FAILS with a
155
+ * clear reason if it can't (no silent fallback — mirroring `preferredModelId`'s contract).
156
+ * Omit to let metadata drive the choice: the target agent's `DefaultCoAgentID`, then the
157
+ * type-level `AIAgentCoAgent` default row for its agent type, then the global Realtime Co-Agent.
158
+ * @param configOverridesJson Optional RUNTIME configuration-override layer (the most-specific
159
+ * layer of the effective-config merge: type `DefaultConfiguration` ← agent
160
+ * `TypeConfiguration` ← this). **Authorization-gated**: requires the
161
+ * `Realtime: Advanced Session Controls` authorization — unauthorized callers receive a
162
+ * structured rejection (never a silent ignore). Must be a JSON object.
163
+ *
164
+ * @returns The ephemeral config + session linkage the browser needs to open its socket.
165
+ */
166
+ StartRealtimeClientSession(targetAgentId: string | undefined, { userPayload, providers }: AppContext, conversationId?: string, lastSessionId?: string, preferredModelId?: string, clientToolsJson?: string, coAgentId?: string, configOverridesJson?: string): Promise<StartRealtimeClientSessionResult>;
167
+ /**
168
+ * Execute a single tool call the browser relayed from its provider socket and return the
169
+ * serialized result for the browser to relay back to the model.
170
+ *
171
+ * Ownership-gated; the target agent id is read from the **session config** (authoritative), never
172
+ * from the client. Heartbeats the session on success.
173
+ *
174
+ * @returns The serialized tool result JSON.
175
+ */
176
+ ExecuteRealtimeSessionTool(agentSessionId: string, callId: string, toolName: string, argsJson: string, { userPayload, providers }: AppContext, pubSub: PubSubEngine): Promise<string>;
177
+ /**
178
+ * Builds the `OnProgress` callback threaded into the delegated target-agent run. Each significant
179
+ * progress event (see {@link SIGNIFICANT_PROGRESS_STEPS}) is published to
180
+ * {@link PUSH_STATUS_UPDATES_TOPIC} on this user's `sessionId` so the browser can correlate it to
181
+ * THIS voice session (via `agentSessionID` + `callID`) and feed it to the realtime model to
182
+ * narrate — distinct from a normal `RunAIAgentResolver` agent-run stream by `resolver`/`type`.
183
+ */
184
+ private buildDelegationProgressCallback;
185
+ /**
186
+ * Rolls the session's `pendingFeedbackRunID` forward after a relayed tool call. The id we just
187
+ * consumed (resumed or none) is dropped; a new paused run id is stored only when the run paused
188
+ * again awaiting feedback. When neither the old nor new value is set, this is a no-op (no save).
189
+ * Best-effort: a save failure is logged, not thrown.
190
+ */
191
+ private updatePendingFeedbackRunID;
192
+ /**
193
+ * Persist a transcript turn (user or assistant) as a `Conversation Detail` on the session's
194
+ * conversation. Ownership-gated; heartbeats the session on success.
195
+ *
196
+ * The co-agent observability `AIAgentRun`/`AIPromptRun` are created at session start (see
197
+ * {@link RealtimeClientSessionResolver.StartRealtimeClientSession}) and finalized on close;
198
+ * incremental usage telemetry lands on the `AIPromptRun` via
199
+ * {@link RealtimeClientSessionResolver.RelayRealtimeUsage}. Each persisted turn is ALSO appended
200
+ * to the co-agent `AIPromptRun.Messages` (best-effort), so the run captures the full conversation
201
+ * — observability parity with every other MJ agent run, not just token totals.
202
+ *
203
+ * @returns `true` when the transcript turn was persisted.
204
+ */
205
+ RelayRealtimeTranscript(agentSessionId: string, role: string, text: string, { userPayload, providers }: AppContext, replacesPrevious?: boolean): Promise<boolean>;
206
+ /**
207
+ * Maps the relayed transcript role (`'User'`/`'AI'`/`'Assistant'`, case-insensitive) to the
208
+ * standard chat-message role stored in `AIPromptRun.Messages`. Anything that isn't an explicit
209
+ * user turn is treated as the assistant (the co-agent's own speech).
210
+ */
211
+ private mapTranscriptRoleToChatRole;
212
+ /**
213
+ * Relays a co-agent CHANNEL tool-call (browser_ / Whiteboard_ etc.) onto the session's co-agent
214
+ * AIPromptRun.Messages — run-only observability so the run captures what the co-agent DID, not just
215
+ * what it said. Deliberately NOT a ConversationDetail turn (the chat thread stays speech-only).
216
+ * Ownership-gated; best-effort (a missing prompt run / save failure simply returns false).
217
+ *
218
+ * @returns `true` when the tool turn was recorded on the run.
219
+ */
220
+ RelayRealtimeToolTurn(agentSessionId: string, toolName: string, { userPayload, providers }: AppContext, argsJson?: string, resultJson?: string): Promise<boolean>;
221
+ /**
222
+ * Formats a co-agent tool call into a compact, human-readable run line: `🔧 <tool> <args> → <result>`,
223
+ * clipping args/result so a verbose payload never bloats the Messages blob.
224
+ */
225
+ private formatToolTurn;
226
+ /**
227
+ * Cancel in-flight relayed tool delegations for a session — the CLIENT-DIRECT cancel channel.
228
+ *
229
+ * The browser calls this on an EXPLICIT user cancel (the call overlay's per-card ✕ — a
230
+ * deliberate host policy: true barge-in alone never aborts delegations, since the narration
231
+ * design expects the user to keep talking while delegated work runs). The service-side
232
+ * in-flight registry aborts the matching `AbortController`(s), which fires the delegated
233
+ * run's `cancellationToken`; the original `ExecuteRealtimeSessionTool` mutation then resolves
234
+ * with the run's cancelled/failed result through its normal path.
235
+ *
236
+ * Ownership-gated like the sibling mutations. A **Closed** session is accepted — a cancel can
237
+ * legitimately race teardown, and aborting stragglers is exactly what the caller wants then.
238
+ *
239
+ * @param callId When supplied, only the delegation for that provider call id is aborted;
240
+ * omit to abort ALL in-flight delegations for the session.
241
+ * @returns A structured {@link CancelRealtimeSessionToolResult}. Tolerant: an unknown call id
242
+ * or a session with nothing in flight is `Success: true, AbortedCount: 0` (the work may
243
+ * simply have finished already); an unexpected registry error is a structured failure.
244
+ */
245
+ CancelRealtimeSessionTool(agentSessionId: string, { userPayload, providers }: AppContext, callId?: string): Promise<CancelRealtimeSessionToolResult>;
246
+ /**
247
+ * Relay incremental usage telemetry from the browser's provider socket onto the co-agent's
248
+ * observability `AIPromptRun` (the run created at session start).
249
+ *
250
+ * The browser accumulates the realtime client's `OnUsage` token DELTAS and flushes them here
251
+ * debounced (~10s) plus once at teardown; this mutation ACCUMULATES the deltas onto the
252
+ * prompt run's `TokensPrompt` / `TokensCompletion` (and recomputes `TokensUsed`). Status is
253
+ * deliberately untouched — `FinalizeCoAgentRun` keeps stamping Success/Completed at close,
254
+ * and a post-close flush still lands (accumulation has no Status gate).
255
+ *
256
+ * Ownership-gated like the sibling mutations; a **Closed** session is accepted (the final
257
+ * teardown flush can land after `CloseAgentSession`). Everything PAST the ownership gate is
258
+ * best-effort and tolerant: a missing/malformed session config, an absent `promptRunID`
259
+ * (observability creation was skipped), a failed load, or a failed save all log and return
260
+ * `false` — usage relay must never break a live call.
261
+ *
262
+ * @param inputTokens Input-token DELTA to add (negative/non-finite values are clamped to 0).
263
+ * @param outputTokens Output-token DELTA to add (negative/non-finite values are clamped to 0).
264
+ * @returns `true` when the accumulated usage was persisted; `false` on any tolerated failure.
265
+ */
266
+ RelayRealtimeUsage(agentSessionId: string, inputTokens: number, outputTokens: number, { userPayload, providers }: AppContext): Promise<boolean>;
267
+ /** Clamps a relayed token delta: negative / non-finite values become 0. */
268
+ private clampTokenDelta;
269
+ /**
270
+ * Reads the co-agent `AIPromptRun` id from the session config WITHOUT the relay path's
271
+ * throw-on-missing-target semantics — usage relay is best-effort, so a missing/malformed
272
+ * config or absent id just logs and returns `null`.
273
+ */
274
+ private readPromptRunID;
275
+ /**
276
+ * Persist an interactive channel's state of record (e.g. the live whiteboard's serialized
277
+ * scene) onto the session's `MJ: AI Agent Session Channels` row.
278
+ *
279
+ * Flow:
280
+ * 1. Ownership gate — like the sibling mutations, the session's `UserID` must equal the
281
+ * caller's. Unlike Execute/Relay, a **Closed** session is accepted: the client's final
282
+ * on-end flush legitimately lands after `CloseAgentSession` has run.
283
+ * 2. Resolve the channel definition (`MJ: AI Agent Channels`) by `channelName`. When no
284
+ * active definition row exists (the deployment hasn't synced the channel seed yet),
285
+ * return `false` gracefully and log — never throw for a missing definition.
286
+ * 3. Offer the payload to the session's SERVER-SIDE channel plugin (the registry row's
287
+ * `ServerPluginClass`, held per-session by {@link RealtimeChannelServerHost}) for
288
+ * validation/normalization — strictly best-effort: no plugin, a plugin failure, or an
289
+ * oversized replacement all fall back to persisting the original payload.
290
+ * 4. Upsert the session-channel row: create it (Status `Connected`) when missing, store
291
+ * the (possibly normalized) state in its `Config` field, and stamp `LastActiveAt`.
292
+ *
293
+ * @returns `true` when the state was persisted; `false` on any tolerated failure (missing
294
+ * channel definition, oversized state, save failure) — all logged.
295
+ */
296
+ SaveSessionChannelState(agentSessionId: string, channelName: string, stateJson: string, { userPayload, providers }: AppContext): Promise<boolean>;
297
+ /**
298
+ * Routes a landed channel-state save through the session's server-side channel plugin (when
299
+ * one resolved at session start) so it can validate/normalize the payload PRE-persistence.
300
+ * Strictly best-effort: any host/plugin problem — including a replacement that exceeds the
301
+ * channel-state size cap — falls back to the client's original payload, which already passed
302
+ * the cap. A plugin can transform a save; it can never lose or block one.
303
+ */
304
+ private applyChannelServerPlugin;
305
+ /**
306
+ * Persist an interactive channel's current state (e.g. the live whiteboard's board JSON) as a
307
+ * durable, user-owned **artifact** — distinct from {@link SaveSessionChannelState}, which only
308
+ * stores the session-scoped state of record. The artifact survives the session and shows up in
309
+ * the user's artifact library (and, when possible, in the conversation's history).
310
+ *
311
+ * Flow:
312
+ * 1. Ownership gate — like the sibling mutations (`UserID === contextUser.ID`). A **Closed**
313
+ * session is accepted: "save my board" legitimately arrives as/after the call ends.
314
+ * 2. Size cap — `contentJson` beyond {@link MAX_CHANNEL_STATE_CHARS} is a structured failure.
315
+ * 3. Resolve the {@link WHITEBOARD_ARTIFACT_TYPE_NAME} artifact type by name; when the seed is
316
+ * absent/disabled in this deployment, return a structured failure (never throw).
317
+ * 4. Create the `MJ: Artifacts` header (user-owned, Visibility `Always`) + its
318
+ * `MJ: Artifact Versions` v1 row carrying `contentJson`.
319
+ * 5. Best-effort extras that never fail the save:
320
+ * - when the session has a conversation AND a `Conversation Detail` stamped with this
321
+ * session id exists (the transcript relay stamps `AgentSessionID`), link the version into
322
+ * history via a `MJ: Conversation Detail Artifacts` junction row (Direction `Output`);
323
+ * - stamp `LastActiveAt` on the session-channel row when one exists.
324
+ *
325
+ * @returns A structured {@link SaveSessionChannelArtifactResult} — graceful failures (missing
326
+ * type seed, oversized content, save failure) come back as `Success: false`, while
327
+ * authn/ownership violations throw like the sibling mutations.
328
+ */
329
+ SaveSessionChannelArtifact(agentSessionId: string, channelName: string, name: string, contentJson: string, { userPayload, providers }: AppContext): Promise<SaveSessionChannelArtifactResult>;
330
+ /** Resolve the request user + read-write provider, throwing a clear error if unauthenticated. */
331
+ private requireUserAndProvider;
332
+ /**
333
+ * Authorization gate for {@link RealtimeClientSessionResolver.StartRealtimeClientSession}: the
334
+ * caller must be able to run the **target** agent. Denial throws (no session is created).
335
+ */
336
+ private assertCanRunTarget;
337
+ /**
338
+ * Resolves the AUTHORITATIVE target agent id under the co-agent's PAIRING CONSTRAINTS
339
+ * (`MJ: AI Agent Co Agents`, ordered by `Sequence`):
340
+ *
341
+ * - **Zero pairing rows (universal co-agent)** — today's behavior untouched: the runtime
342
+ * `targetAgentId` is required (clear error when absent) and used as-is. Pairings are NEVER
343
+ * mandated; zero-config deployments keep working with zero metadata.
344
+ * - **Rows + runtime target** — the supplied target must be IN the paired list; a target
345
+ * outside the list is a clear structured error (the UX builds its picker from the same rows).
346
+ * - **Rows + no runtime target** — the `IsDefault` row stands in; when no default row exists,
347
+ * a clear error asks for an explicit target.
348
+ *
349
+ * Pairing rows are a TARGETING constraint layered on top of — never a replacement for — the
350
+ * `CanRun` security gate, which the caller applies to the resolved target id immediately
351
+ * after. A failed/erroring pairing query therefore degrades to the universal behavior
352
+ * (logged), it never breaks session starts.
353
+ *
354
+ * @param coAgentID The resolved co-agent id.
355
+ * @param requestedTargetId The runtime `targetAgentId` argument, when supplied.
356
+ * @returns The effective target agent id (canonical casing from the pairing row when matched).
357
+ */
358
+ private resolveConstrainedTargetAgentID;
359
+ /**
360
+ * Loads the co-agent's pairing rows from {@link AIEngineBase}'s cached
361
+ * `MJ: AI Agent Co Agents` metadata (provider-scoped engine instance, lazy `Config`),
362
+ * ordered by `Sequence`. Only **Active** rows of relationship **Type `'CoAgent'`** with a
363
+ * SPECIFIC agent target participate — type-level rows (`TargetAgentTypeID`) express the
364
+ * type-default in the resolution chain, not a target restriction, and reserved relationship
365
+ * types are ignored until their features ship. Tolerant — a failed cache load logs and
366
+ * returns `[]` (degrading to the universal behavior; pairing is a targeting constraint,
367
+ * `CanRun` remains the security gate), never throws.
368
+ */
369
+ private loadPairingRows;
370
+ /**
371
+ * The provider-scoped {@link AIEngineBase} instance for this request's connection, lazily
372
+ * configured (`Config(false, ...)` is a no-op when the cache is already loaded). Pairing rows
373
+ * and channel definitions are small metadata tables the engine caches — reading them here
374
+ * replaces per-call RunViews.
375
+ */
376
+ private configuredAIEngineBase;
377
+ /**
378
+ * The RUNTIME-OVERRIDE AUTHORIZATION GATE: `configOverridesJson` and a DEVIATING explicit
379
+ * `preferredModelId` both require the `Realtime: Advanced Session Controls` authorization
380
+ * (evaluated hierarchy-aware over the caller's roles); a deviating model is additionally
381
+ * subject to the effective `realtime.allowUserModelOverride` policy (which blocks even
382
+ * authorized callers when `false`). Denial THROWS a structured reason — never a silent
383
+ * ignore. Plain starts (no overrides, no explicit model) pass through untouched; an explicit
384
+ * model EQUAL to the co-agent's metadata-configured preference is not a deviation.
385
+ *
386
+ * Judgment calls baked in (per the approved product rules): co-agent selection (`coAgentId`)
387
+ * and target selection within a pairing list / for a universal co-agent are NORMAL user flow
388
+ * — they stay behind the existing `CanRun` gate only and are not touched here.
389
+ */
390
+ private assertRuntimeOverridesAuthorized;
391
+ /**
392
+ * The co-agent's BASELINE effective configuration — type `DefaultConfiguration` ← agent
393
+ * `TypeConfiguration`, WITHOUT the runtime layer (this feeds the gate that decides whether
394
+ * the runtime layer is even allowed). Tolerant of an unloaded metadata cache.
395
+ */
396
+ private resolveBaselineEffectiveConfig;
397
+ /**
398
+ * Resolves the baseline config's `realtime.modelPreference` (Name or ID) to an
399
+ * `MJ: AI Models.ID`, so a `preferredModelId` equal to it is recognized as a NON-deviation.
400
+ * When the preference matches no cached model (or the cache is unavailable), the RAW
401
+ * preference string is returned — an ID-style preference then still compares equal to the
402
+ * matching `preferredModelId` (case-insensitive), while a name-style preference simply won't
403
+ * match an id (and the gate treats the request as a deviation — the fail-safe direction).
404
+ * `null` only when no preference is configured at all.
405
+ */
406
+ private resolveMetadataPreferredModelID;
407
+ /**
408
+ * Hierarchy-aware check for the `Realtime: Advanced Session Controls` authorization against
409
+ * the request provider's cached Authorizations + the caller's roles. FAIL-CLOSED: an absent
410
+ * authorization row (un-synced seed) or an evaluation error denies — runtime overrides are a
411
+ * privileged path, and unauthorized callers still get a fully working session without them.
412
+ */
413
+ private userHasAdvancedSessionControls;
414
+ /**
415
+ * Resolves the co-agent (the Realtime-type agent that voices the target agent) for a new
416
+ * client-direct session via the metadata-driven **CO-AGENT RESOLUTION CHAIN** — first match
417
+ * wins, evaluated in precedence order:
418
+ *
419
+ * 1. **Runtime parameter** — the mutation's explicit `coAgentId`. A per-call override; an
420
+ * invalid candidate (unknown, not Active, or not of the Realtime agent type) **throws**
421
+ * (fail loud — the caller asked for something specific, mirroring `preferredModelId`).
422
+ * 2. **Per-agent persona** — the target agent's `AIAgent.DefaultCoAgentID`. An invalid
423
+ * reference logs a warning and **falls through** to the next step (stale metadata should
424
+ * degrade gracefully, never break calls).
425
+ * 3. **Per-type default** — an Active `AIAgentCoAgent` row of Type `'CoAgent'` whose
426
+ * `TargetAgentTypeID` is the target agent's type and `IsDefault = 1` (lowest `Sequence`
427
+ * wins a tie). Same tolerant warn-and-fall-through semantics as step 2.
428
+ * 4. **Global default** — the seeded {@link REALTIME_CO_AGENT_NAME} agent, looked up by name
429
+ * (with a deprecated fallback to {@link LEGACY_REALTIME_CO_AGENT_NAME}). Throws when absent
430
+ * entirely (the realtime feature is unconfigured in this deployment).
431
+ *
432
+ * Every candidate from steps 1–3 is validated by {@link findValidCoAgent}: it must exist in
433
+ * {@link AIEngine}'s cached agents, have Status `Active`, and be of the
434
+ * {@link REALTIME_AGENT_TYPE_NAME} agent type. Step 4 keeps its original name-lookup contract.
435
+ *
436
+ * @param targetAgentId The agent the co-agent will voice on behalf of (drives steps 2–3).
437
+ * @param explicitCoAgentId The runtime `coAgentId` mutation argument, when supplied (step 1).
438
+ * @returns The resolved co-agent's id (canonical casing from the metadata cache).
439
+ */
440
+ private resolveCoAgentID;
441
+ /**
442
+ * Chain step 3 lookup: the TYPE-LEVEL default co-agent for an agent type — the Active
443
+ * `AIAgentCoAgent` row of Type `'CoAgent'` whose `TargetAgentTypeID` matches, with
444
+ * `IsDefault = 1` preferred and the lowest `Sequence` breaking ties (so a deployment can
445
+ * stage multiple type-level candidates deterministically). Reads {@link AIEngineBase}'s
446
+ * cached rows — no RunView. Tolerant: a failed cache read logs and returns `undefined`
447
+ * (the chain falls through to the global default).
448
+ */
449
+ private findTypeDefaultCoAgentID;
450
+ /**
451
+ * Validates one metadata-level co-agent default (chain steps 2/3). Returns the resolved
452
+ * co-agent id when the reference is valid; logs a warning and returns `null` (caller falls
453
+ * through to the next chain step) when the reference is set but invalid — metadata drift must
454
+ * degrade, not break live calls. A `null`/absent reference returns `null` silently.
455
+ */
456
+ private resolveMetadataDefault;
457
+ /**
458
+ * Validates a co-agent candidate against {@link AIEngine}'s cached metadata. A valid co-agent
459
+ * must (a) exist, (b) have Status `Active`, and (c) be of the {@link REALTIME_AGENT_TYPE_NAME}
460
+ * agent type. Returns the cached agent on success, or a human-readable `problem` on failure —
461
+ * the CALLER decides whether that's fatal (explicit runtime param) or tolerated (metadata default).
462
+ */
463
+ private findValidCoAgent;
464
+ /**
465
+ * Resolves the seeded Realtime Co-Agent's id from {@link AIEngine}'s cached agents — the GLOBAL
466
+ * DEFAULT (step 4) of the co-agent resolution chain. The engine is already configured by
467
+ * {@link resolveCoAgentID}. Looks up the current {@link REALTIME_CO_AGENT_NAME} first, then
468
+ * falls back to the DEPRECATED {@link LEGACY_REALTIME_CO_AGENT_NAME} (pre-rename seed) with a
469
+ * deprecation log so un-resynced deployments keep working. Throws a clear error when neither
470
+ * name is present in metadata.
471
+ */
472
+ private resolveGlobalCoAgentID;
473
+ /** Case/whitespace-insensitive agent lookup by Name in {@link AIEngine}'s cached agents. */
474
+ private findAgentByName;
475
+ /**
476
+ * Mints the client session config for a just-created session. On failure, closes the session so
477
+ * no half-open record leaks, then throws the service's error message.
478
+ *
479
+ * @param preferredModelId Optional explicit realtime model choice — threaded to the service,
480
+ * which FAILS (no silent fallback) when the chosen model cannot be satisfied.
481
+ * @param priorTranscript Optional capped, role-tagged transcript of the PRIOR session chain
482
+ * (from {@link loadPriorTranscript}) — the service frames it into the system prompt so a
483
+ * resumed session remembers the previous leg(s).
484
+ */
485
+ private prepareClientSessionOrClose;
486
+ /**
487
+ * Writes the co-agent observability run ids into the session's `Config_` alongside the
488
+ * authoritative `targetAgentID`, then saves the session. These ids are read back on close to
489
+ * finalize the runs (and on relay to nest delegated runs). Best-effort: a save failure is logged,
490
+ * not thrown — the voice session still proceeds, it just won't carry the run ids.
491
+ */
492
+ private persistObservabilityRunIDs;
493
+ /**
494
+ * Loads a session, enforcing inbound ownership (`UserID === contextUser.ID`) and that it is not
495
+ * `Closed`. Throws on any violation. Returns the loaded session for the caller to read from.
496
+ */
497
+ private loadOwnedActiveSession;
498
+ /**
499
+ * Loads a session and enforces inbound ownership (`UserID === contextUser.ID`) WITHOUT
500
+ * rejecting `Closed` sessions — {@link SaveSessionChannelState}'s final on-end flush
501
+ * legitimately arrives after the session closed. Throws when not found / not owned.
502
+ */
503
+ private loadOwnedSession;
504
+ /**
505
+ * Resolves an ACTIVE channel definition (`MJ: AI Agent Channels`) by name from
506
+ * {@link AIEngineBase}'s cached `AgentChannels` (provider-scoped engine instance, lazy
507
+ * `Config` — no per-call RunView; name matching is trim + case-insensitive, parity with the
508
+ * previous SQL-collation lookup). Returns its id, or `null` (logged) when no active
509
+ * definition exists — the channel seed is deployed separately, so its absence is tolerated,
510
+ * never thrown.
511
+ */
512
+ private resolveChannelID;
513
+ /**
514
+ * Upserts the session-channel row for `(agentSessionID, channelID)`: creates it with Status
515
+ * `Connected` when missing, stores `stateJson` in `Config`, and stamps `LastActiveAt`.
516
+ * Returns the boolean save result (logged on failure).
517
+ */
518
+ private upsertSessionChannelState;
519
+ /**
520
+ * Loads the PRIOR session's persisted channel states and serializes them as a JSON object
521
+ * string keyed by channel NAME (the session-channel view denormalizes the channel name as the
522
+ * `Channel` virtual field). Strictly best-effort — every failure path logs and returns `null`,
523
+ * a session start NEVER fails because of restore:
524
+ * - no `lastSessionId` → `null` (silently — restore simply wasn't requested);
525
+ * - prior session missing OR owned by a different user → `null` (logged — never leak another
526
+ * user's channel state);
527
+ * - rows with an empty `Config` are skipped;
528
+ * - an individual state larger than {@link MAX_CHANNEL_STATE_CHARS} is dropped (logged), and
529
+ * states that would push the accumulated payload past that same cap are dropped too (logged)
530
+ * — the surviving states still restore;
531
+ * - no surviving states → `null`.
532
+ */
533
+ private loadPriorChannelStatesJson;
534
+ /**
535
+ * Loads the PRIOR session chain's transcript for model-context hydration: when a new session
536
+ * carries `lastSessionId`, the chain's persisted, session-stamped `Conversation Details` are
537
+ * folded into capped, role-tagged lines (`User: …` / `Assistant: …`) the prepare service
538
+ * frames into the system prompt — so the model REMEMBERS the previous live leg(s).
539
+ *
540
+ * Chain walk + caps:
541
+ * - follows `LastSessionID` BACKWARDS from `lastSessionId`, at most
542
+ * {@link MAX_PRIOR_TRANSCRIPT_LEGS} legs, with a visited-set cycle guard (A→B→A stops);
543
+ * - EVERY leg is ownership-checked like the channel-state restore — the FIRST leg failing
544
+ * the check aborts hydration entirely (first leg) or ends the walk (deeper legs), so
545
+ * another user's transcript can never leak;
546
+ * - one details query covers all collected legs, chronological;
547
+ * - hidden/error/empty rows are skipped, then the NEWEST {@link MAX_PRIOR_TRANSCRIPT_TURNS}
548
+ * turns are kept and the total is capped at {@link MAX_PRIOR_TRANSCRIPT_CHARS} chars
549
+ * (oldest dropped first).
550
+ *
551
+ * Strictly best-effort: every failure path logs and returns `undefined` — hydration NEVER
552
+ * blocks a session start.
553
+ */
554
+ private loadPriorTranscript;
555
+ /**
556
+ * Walks the prior-session chain backwards from `lastSessionId`, returning the OWNED leg ids
557
+ * (newest first). The first leg must exist and be owned by the caller, else `[]` (logged);
558
+ * deeper-leg problems (missing, unowned, cycle) just end the walk.
559
+ */
560
+ private collectOwnedPriorLegIDs;
561
+ /**
562
+ * Loads the chain legs' visible transcript turns (session-stamped `Conversation Details`,
563
+ * `Role` User/AI, not hidden, non-empty) in ONE chronological query across all legs.
564
+ */
565
+ private loadChainTranscriptTurns;
566
+ /**
567
+ * Maps visible turns to role-tagged lines and applies the hydration caps: the NEWEST
568
+ * {@link MAX_PRIOR_TRANSCRIPT_TURNS} turns, then a total budget of
569
+ * {@link MAX_PRIOR_TRANSCRIPT_CHARS} chars — oldest lines dropped first in both passes,
570
+ * so the model always keeps the freshest context.
571
+ */
572
+ private capTranscriptLines;
573
+ /**
574
+ * Folds prior session-channel rows into a `{ channelName: stateJson }` map, skipping empty
575
+ * states and enforcing the {@link MAX_CHANNEL_STATE_CHARS} cap both per-state and on the
576
+ * accumulated total (oversized states are dropped with a log note; the rest survive).
577
+ */
578
+ private collectChannelStates;
579
+ /**
580
+ * Resolves the ENABLED {@link WHITEBOARD_ARTIFACT_TYPE_NAME} artifact type's id. Returns `null`
581
+ * (logged) when the type seed is absent or disabled — the artifact-type seed is deployed
582
+ * separately, so its absence is a graceful structured failure, never a throw.
583
+ */
584
+ private resolveWhiteboardArtifactTypeID;
585
+ /**
586
+ * Creates the user-owned artifact header + its v1 version for a channel-state save, then runs
587
+ * the best-effort extras (conversation-history junction + session-channel `LastActiveAt`
588
+ * stamp). Header/version save failures come back as structured failures; the extras NEVER
589
+ * fail the save.
590
+ */
591
+ private createChannelArtifact;
592
+ /**
593
+ * Best-effort: links an artifact version into conversation history the way chat does — via a
594
+ * `MJ: Conversation Detail Artifacts` junction row (Direction `Output`) against the LATEST
595
+ * `Conversation Detail` stamped with this session's id (the transcript relay stamps
596
+ * `AgentSessionID` on every persisted turn). Skips silently (returns `false`) when the session
597
+ * has no conversation or no stamped detail exists yet; logs (and returns `false`) when the
598
+ * lookup or junction save fails. Never throws.
599
+ */
600
+ private linkVersionToLatestSessionDetail;
601
+ /**
602
+ * Best-effort: junction-links the artifacts a DELEGATED target-agent run produced into the
603
+ * session's conversation history — closing the gap where voice-path artifacts were created
604
+ * (`RealtimeClientSessionService.createDelegatedRunArtifacts`) but never reached
605
+ * `MJ: Conversation Detail Artifacts`, leaving chat, session review, and resume carryover
606
+ * blind to them.
607
+ *
608
+ * ANCHOR CHOICE: the junction needs a `Conversation Detail`. We anchor to the MOST RECENT
609
+ * detail stamped with this session's id (the transcript relay stamps `AgentSessionID` on every
610
+ * persisted turn) — the turn closest to the tool call that produced the artifact. When NO
611
+ * stamped detail exists yet (the delegated run finished before the browser relayed any
612
+ * transcript — the relay is asynchronous), we create a minimal HIDDEN anchor detail
613
+ * (`Role: 'AI'`, `HiddenToUser: true`, stamped with the session id + conversation + user)
614
+ * rather than dropping the link: `HiddenToUser` keeps it out of the visible chat thread and
615
+ * out of review-mode captions (both filter hidden rows), while conversation-level artifact
616
+ * queries (which scan junctions across ALL details) still surface the artifact. This is the
617
+ * least-invasive correct anchor — no fake visible message, no orphaned artifact.
618
+ *
619
+ * Strictly best-effort: every failure path logs and returns; a relayed tool call NEVER fails
620
+ * because history linking did.
621
+ */
622
+ private linkDelegatedArtifactsToConversation;
623
+ /**
624
+ * Finds the LATEST `Conversation Detail` stamped with this session's id (the transcript relay
625
+ * stamps `AgentSessionID` on every persisted turn). Returns its id, or `null` when none exists.
626
+ */
627
+ private findLatestSessionDetailID;
628
+ /**
629
+ * Creates the minimal HIDDEN anchor `Conversation Detail` for artifact junction rows when no
630
+ * session-stamped transcript turn exists yet (see the anchor-choice rationale on
631
+ * {@link linkDelegatedArtifactsToConversation}). Returns the new detail's id, or `null`
632
+ * (logged) when the save fails.
633
+ */
634
+ private createHiddenSessionAnchorDetail;
635
+ /**
636
+ * Saves one `MJ: Conversation Detail Artifacts` junction row (Direction `Output`) linking an
637
+ * artifact version to a conversation detail. Returns the boolean save result (logged on failure).
638
+ */
639
+ private saveDetailArtifactJunction;
640
+ /**
641
+ * Best-effort: stamps `LastActiveAt` on the session-channel row for `(session, channelName)`
642
+ * when one exists — saving an artifact IS channel activity. Missing channel definition or
643
+ * session-channel row is a silent no-op; failures log and never throw.
644
+ */
645
+ private stampSessionChannelLastActive;
646
+ /**
647
+ * Tolerantly parses + validates client-declared UI tool definitions (see the SECURITY NOTE on
648
+ * {@link StartRealtimeClientSession}). Never throws — any rejection logs and returns
649
+ * `undefined` (the session simply minted without client tools):
650
+ * - non-JSON / non-array payloads are rejected wholesale;
651
+ * - payloads larger than {@link MAX_CLIENT_TOOLS_JSON_CHARS} are rejected wholesale;
652
+ * - more than {@link MAX_CLIENT_TOOLS} declarations are rejected wholesale (a legitimate
653
+ * client never sends that many — a flood is a misbehaving caller, not a trim candidate);
654
+ * - individual entries failing the shape check (`Name`/`Description` non-empty strings,
655
+ * `ParametersSchema` a plain object) are skipped, the rest survive.
656
+ */
657
+ private parseClientTools;
658
+ /** Shape check for one client tool declaration: Name/Description non-empty strings, ParametersSchema a plain object. */
659
+ private isValidClientTool;
660
+ /**
661
+ * Parses the session's persisted config, returning the authoritative `targetAgentID` plus any
662
+ * observability run ids (`coAgentRunID`/`promptRunID`). Throws when the config is missing/malformed
663
+ * or carries no target — a relay cannot proceed without a target.
664
+ */
665
+ private readSessionConfig;
666
+ /**
667
+ * Persists a single transcript turn as a `Conversation Detail` stamped with the session's
668
+ * conversation, the mapped role, the turn text, the session id, and the owning user.
669
+ *
670
+ * @returns The boolean save result (logs `CompleteMessage` on failure).
671
+ */
672
+ private persistTranscriptTurn;
673
+ /**
674
+ * CORRECTION persistence (the client transcript's `ReplacesPrevious` marker — e.g.
675
+ * ElevenLabs' post-barge-in `agent_response_correction`): UPDATES the session's most
676
+ * recently persisted turn of the same role IN PLACE with the corrected text, instead of
677
+ * appending a near-duplicate. Falls back to a plain insert when no prior turn exists
678
+ * (the superseded turn may have failed to persist) — a correction is never dropped.
679
+ */
680
+ private replacePreviousTranscriptTurn;
681
+ /**
682
+ * Maps a transport-level transcript role (`'user'` / `'assistant'`) onto the
683
+ * `Conversation Detail` entity's `Role` union (`'User'` / `'AI'`). Anything not the user is
684
+ * treated as the assistant/AI side.
685
+ */
686
+ private mapTranscriptRole;
687
+ }
688
+ //# sourceMappingURL=RealtimeClientSessionResolver.d.ts.map