@memberjunction/ai-agents 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 (88) hide show
  1. package/README.md +53 -0
  2. package/dist/AgentRunner.d.ts +5 -2
  3. package/dist/AgentRunner.d.ts.map +1 -1
  4. package/dist/AgentRunner.js +14 -4
  5. package/dist/AgentRunner.js.map +1 -1
  6. package/dist/MemoryWriteManager.d.ts +188 -0
  7. package/dist/MemoryWriteManager.d.ts.map +1 -0
  8. package/dist/MemoryWriteManager.js +299 -0
  9. package/dist/MemoryWriteManager.js.map +1 -0
  10. package/dist/agent-context-injector.d.ts +29 -0
  11. package/dist/agent-context-injector.d.ts.map +1 -1
  12. package/dist/agent-context-injector.js +90 -32
  13. package/dist/agent-context-injector.js.map +1 -1
  14. package/dist/agent-memory-context-builder.d.ts +100 -0
  15. package/dist/agent-memory-context-builder.d.ts.map +1 -0
  16. package/dist/agent-memory-context-builder.js +172 -0
  17. package/dist/agent-memory-context-builder.js.map +1 -0
  18. package/dist/agent-types/index.d.ts +1 -0
  19. package/dist/agent-types/index.d.ts.map +1 -1
  20. package/dist/agent-types/index.js +1 -0
  21. package/dist/agent-types/index.js.map +1 -1
  22. package/dist/agent-types/loop-agent-response-type.d.ts +12 -1
  23. package/dist/agent-types/loop-agent-response-type.d.ts.map +1 -1
  24. package/dist/agent-types/loop-agent-response-type.js.map +1 -1
  25. package/dist/agent-types/loop-agent-type.d.ts.map +1 -1
  26. package/dist/agent-types/loop-agent-type.js +4 -0
  27. package/dist/agent-types/loop-agent-type.js.map +1 -1
  28. package/dist/agent-types/realtime-agent-type.d.ts +146 -0
  29. package/dist/agent-types/realtime-agent-type.d.ts.map +1 -0
  30. package/dist/agent-types/realtime-agent-type.js +176 -0
  31. package/dist/agent-types/realtime-agent-type.js.map +1 -0
  32. package/dist/base-agent.d.ts +386 -39
  33. package/dist/base-agent.d.ts.map +1 -1
  34. package/dist/base-agent.js +1121 -261
  35. package/dist/base-agent.js.map +1 -1
  36. package/dist/index.d.ts +13 -0
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +17 -0
  39. package/dist/index.js.map +1 -1
  40. package/dist/memory-manager-agent.d.ts +99 -4
  41. package/dist/memory-manager-agent.d.ts.map +1 -1
  42. package/dist/memory-manager-agent.js +349 -117
  43. package/dist/memory-manager-agent.js.map +1 -1
  44. package/dist/realtime/bridge-realtime-session-factory.d.ts +111 -0
  45. package/dist/realtime/bridge-realtime-session-factory.d.ts.map +1 -0
  46. package/dist/realtime/bridge-realtime-session-factory.js +163 -0
  47. package/dist/realtime/bridge-realtime-session-factory.js.map +1 -0
  48. package/dist/realtime/bridge-room-transcript-sink.d.ts +58 -0
  49. package/dist/realtime/bridge-room-transcript-sink.d.ts.map +1 -0
  50. package/dist/realtime/bridge-room-transcript-sink.js +127 -0
  51. package/dist/realtime/bridge-room-transcript-sink.js.map +1 -0
  52. package/dist/realtime/meeting-controls-channel-server.d.ts +198 -0
  53. package/dist/realtime/meeting-controls-channel-server.d.ts.map +1 -0
  54. package/dist/realtime/meeting-controls-channel-server.js +319 -0
  55. package/dist/realtime/meeting-controls-channel-server.js.map +1 -0
  56. package/dist/realtime/meeting-controls-state.d.ts +191 -0
  57. package/dist/realtime/meeting-controls-state.d.ts.map +1 -0
  58. package/dist/realtime/meeting-controls-state.js +219 -0
  59. package/dist/realtime/meeting-controls-state.js.map +1 -0
  60. package/dist/realtime/realtime-channel-server-host.d.ts +166 -0
  61. package/dist/realtime/realtime-channel-server-host.d.ts.map +1 -0
  62. package/dist/realtime/realtime-channel-server-host.js +378 -0
  63. package/dist/realtime/realtime-channel-server-host.js.map +1 -0
  64. package/dist/realtime/realtime-client-session-service.d.ts +1026 -0
  65. package/dist/realtime/realtime-client-session-service.d.ts.map +1 -0
  66. package/dist/realtime/realtime-client-session-service.js +1607 -0
  67. package/dist/realtime/realtime-client-session-service.js.map +1 -0
  68. package/dist/realtime/realtime-coagent-config.d.ts +258 -0
  69. package/dist/realtime/realtime-coagent-config.d.ts.map +1 -0
  70. package/dist/realtime/realtime-coagent-config.js +408 -0
  71. package/dist/realtime/realtime-coagent-config.js.map +1 -0
  72. package/dist/realtime/realtime-narration.d.ts +67 -0
  73. package/dist/realtime/realtime-narration.d.ts.map +1 -0
  74. package/dist/realtime/realtime-narration.js +127 -0
  75. package/dist/realtime/realtime-narration.js.map +1 -0
  76. package/dist/realtime/realtime-session-runner.d.ts +383 -0
  77. package/dist/realtime/realtime-session-runner.d.ts.map +1 -0
  78. package/dist/realtime/realtime-session-runner.js +532 -0
  79. package/dist/realtime/realtime-session-runner.js.map +1 -0
  80. package/dist/realtime/realtime-tool-broker.d.ts +294 -0
  81. package/dist/realtime/realtime-tool-broker.d.ts.map +1 -0
  82. package/dist/realtime/realtime-tool-broker.js +206 -0
  83. package/dist/realtime/realtime-tool-broker.js.map +1 -0
  84. package/dist/realtime/whiteboard-channel-server.d.ts +50 -0
  85. package/dist/realtime/whiteboard-channel-server.d.ts.map +1 -0
  86. package/dist/realtime/whiteboard-channel-server.js +85 -0
  87. package/dist/realtime/whiteboard-channel-server.js.map +1 -0
  88. package/package.json +17 -17
@@ -0,0 +1,1026 @@
1
+ /**
2
+ * @fileoverview Server-agnostic preparer + tool relay for a CLIENT-DIRECT realtime session
3
+ * (the Realtime Co-Agent dual-topology design).
4
+ *
5
+ * In the client-direct topology the browser opens its OWN provider socket (e.g. WebRTC) using a
6
+ * server-minted ephemeral token, but the **server** still owns the system prompt and tool set and
7
+ * **executes** every tool call the browser relays back. This service is the server-side half of
8
+ * that contract. It does two things:
9
+ *
10
+ * 1. {@link RealtimeClientSessionService.PrepareClientSession} — resolves the Realtime model,
11
+ * assembles the companion system prompt (co-agent prompt + target identity + history + memory),
12
+ * builds the stable, target-independent tool set (always including `invoke-target-agent`), and
13
+ * asks the model to mint a {@link ClientRealtimeSessionConfig} (ephemeral token + provider
14
+ * session config) the browser applies verbatim.
15
+ * 2. {@link RealtimeClientSessionService.ExecuteRelayedTool} — executes a single tool call the
16
+ * browser relayed, routing it through the shared {@link RealtimeToolBroker} so the result is
17
+ * byte-for-byte identical to the server-bridged path. `invoke-target-agent` delegates to the
18
+ * target agent via {@link AgentRunner.RunAgent}; every other tool returns a structured
19
+ * "not available" result for now (action wiring is a later phase).
20
+ *
21
+ * **Why this duplicates BaseAgent.** The private helpers in `BaseAgent.executeRealtimeSession`
22
+ * (model resolution, companion-prompt assembly, target-agent resolution, delegation) are the
23
+ * server-bridged equivalents of the logic here, but they are `private` to `BaseAgent` and bound to
24
+ * an in-flight `AIAgentRun`/`StartSession` lifecycle. This service mirrors that logic for the
25
+ * client-direct topology, which has no server-side session loop. **A future refactor should extract
26
+ * a shared `RealtimeSessionPreparer`** that both `BaseAgent` and this service consume, eliminating
27
+ * the duplication. Until then, keep the two in sync intentionally.
28
+ *
29
+ * @module @memberjunction/ai-agents
30
+ * @author MemberJunction.com
31
+ */
32
+ import { UserInfo, IMetadataProvider } from '@memberjunction/core';
33
+ import { BaseRealtimeModel, ChatMessage, ClientRealtimeSessionConfig, IRealtimeSession, JSONObject, RealtimeSessionParams, RealtimeToolCall, RealtimeToolDefinition } from '@memberjunction/ai';
34
+ import { MJAIAgentEntityExtended, MJAIModelEntityExtended, MJAIAgentRunEntityExtended, AgentExecutionProgressCallback, ExecuteAgentResult } from '@memberjunction/ai-core-plus';
35
+ import { RealtimeToolBroker, DelegateToTargetRequest, DelegatedResult, DelegatedRunArtifact, ToolExecutionResult } from './realtime-tool-broker.js';
36
+ import { RealtimeCoAgentConfig } from './realtime-coagent-config.js';
37
+ /**
38
+ * Input for {@link RealtimeClientSessionService.PrepareClientSession}.
39
+ *
40
+ * The co-agent may be supplied either as a fully-loaded entity (`CoAgent`) or by id (`CoAgentID`),
41
+ * which is resolved from {@link AIEngine}'s cached agents. The target agent is always supplied by
42
+ * id — it is a runtime choice made when the voice session starts.
43
+ */
44
+ export interface PrepareClientSessionInput {
45
+ /** The Realtime Co-Agent entity. Provide this OR {@link PrepareClientSessionInput.CoAgentID}. */
46
+ CoAgent?: MJAIAgentEntityExtended;
47
+ /** The Realtime Co-Agent id (resolved from cached metadata). Provide this OR {@link PrepareClientSessionInput.CoAgent}. */
48
+ CoAgentID?: string;
49
+ /** The top-level target agent the co-agent voices on behalf of (a runtime parameter). */
50
+ TargetAgentID: string;
51
+ /** The shared session id grouping this voice session's runs. */
52
+ AgentSessionID: string;
53
+ /** Optional conversation id the session is attached to — stamped on the co-agent observability run. */
54
+ ConversationID?: string;
55
+ /** Prior conversation history to seed the model's context. Optional. */
56
+ ConversationMessages?: ChatMessage[];
57
+ /**
58
+ * Pre-formatted, role-tagged transcript lines (`User: …` / `Assistant: …`, newline-separated)
59
+ * from the caller's PRIOR session leg(s) when this session RESUMES one (`lastSessionId`).
60
+ * The transport layer (the MJServer resolver) loads, ownership-checks, and caps this
61
+ * (~30 turns / ~8k chars, oldest dropped) before threading it here; the service only
62
+ * FRAMES it into the system prompt as a clearly-labeled prior-conversation section so the
63
+ * model remembers the previous leg. Optional — absent for fresh sessions, and any
64
+ * upstream load failure simply omits it (hydration never blocks a start).
65
+ */
66
+ PriorTranscript?: string;
67
+ /** Optional user-scope id for memory/context retrieval (falls back to the context user). */
68
+ UserID?: string;
69
+ /** Optional company-scope id for memory/context retrieval. */
70
+ CompanyID?: string;
71
+ /** Optional provider-specific session config bag (voice, language, turn detection, etc.). */
72
+ Config?: JSONObject;
73
+ /** Optional extra, target-independent tools to expose in addition to `invoke-target-agent`. */
74
+ ExtraTools?: RealtimeToolDefinition[];
75
+ /**
76
+ * Optional EXPLICIT realtime model choice (`MJ: AI Models.ID`). When set, that exact model is
77
+ * used — it must be Active, of AIModelType `Realtime`, and have an active vendor whose
78
+ * `DriverClass` resolves an API key. If the preferred model cannot be satisfied the prepare
79
+ * FAILS with a clear reason (no silent fallback — the user explicitly chose). When omitted,
80
+ * the default highest-PowerRank resolution applies.
81
+ */
82
+ PreferredModelID?: string;
83
+ /**
84
+ * Optional RUNTIME configuration-override layer (the most-specific layer of the effective
85
+ * configuration merge: type `DefaultConfiguration` ← agent `TypeConfiguration` ← this).
86
+ * **Pre-authorized by the transport layer** — the MJServer resolver gates it behind the
87
+ * `Realtime: Advanced Session Controls` authorization BEFORE threading it here; the service
88
+ * trusts the input. Malformed JSON is tolerated (it simply contributes nothing to the merge).
89
+ */
90
+ ConfigOverridesJson?: string;
91
+ /**
92
+ * **Multi-agent meeting mode.** When `true`, the agent joins as one of several voices in a shared
93
+ * room: its model's **blind auto-response is disabled** (the session Config carries
94
+ * `disableAutoResponse`, which providers translate to e.g. OpenAI `turn_detection.create_response=false`)
95
+ * and a meeting-aware clause is added to the prompt so it **hears everything but speaks only when
96
+ * addressed**. The bridge becomes the sole speech trigger (gated by its turn policy). Absent/`false`
97
+ * = a 1:1 call with the model's normal auto-response. See
98
+ * `plans/realtime/multi-agent-meeting-turn-taking.md`.
99
+ */
100
+ DisableAutoResponse?: boolean;
101
+ /**
102
+ * The names the meeting-aware prompt tells the agent it answers to (its own display name + aliases).
103
+ * Used ONLY to phrase the "you are addressed when someone says one of these" guidance; the actual
104
+ * addressing GATE is the bridge's `RegexAddressedMatcher`. Ignored unless {@link DisableAutoResponse}.
105
+ */
106
+ SelfNames?: string[];
107
+ }
108
+ /**
109
+ * Result of {@link RealtimeClientSessionService.PrepareClientSession}.
110
+ *
111
+ * On success, {@link RealtimeClientSessionPrepResult.ClientConfig} is the server-minted config the
112
+ * browser applies, and {@link RealtimeClientSessionPrepResult.SessionParams} is the params the
113
+ * server used to mint it (handy for the resolver to echo/persist). On failure, `Success` is `false`
114
+ * and `ErrorMessage` explains why — this method never throws for an unresolvable model/key.
115
+ */
116
+ export interface RealtimeClientSessionPrepResult {
117
+ /** Whether the client session config was minted successfully. */
118
+ Success: boolean;
119
+ /** The minted client-direct session config (token + provider session config). Present on success. */
120
+ ClientConfig?: ClientRealtimeSessionConfig;
121
+ /** The session params the server built (system prompt, model, tools). Present on success. */
122
+ SessionParams?: RealtimeSessionParams;
123
+ /**
124
+ * ID of the server-side co-agent observability `AIAgentRun` created for this session. Present
125
+ * when the run was created successfully; absent when run creation was skipped or failed
126
+ * (observability is best-effort and never fails the prepare). Delegated target-agent runs nest
127
+ * under this run via `ParentRunID`, and {@link RealtimeClientSessionService.FinalizeCoAgentRun}
128
+ * closes it when the session ends.
129
+ */
130
+ CoAgentRunID?: string;
131
+ /**
132
+ * ID of the server-side co-agent `AIPromptRun` linked to {@link RealtimeClientSessionPrepResult.CoAgentRunID}.
133
+ * Present only when the co-agent's system prompt resolved (so a prompt run could be created).
134
+ */
135
+ PromptRunID?: string;
136
+ /**
137
+ * ID of the single `MJ: AI Agent Run Steps` row created under {@link RealtimeClientSessionPrepResult.CoAgentRunID}
138
+ * for the realtime session's system prompt (StepType `Prompt`, TargetID = the system prompt,
139
+ * TargetLogID = {@link RealtimeClientSessionPrepResult.PromptRunID}). It makes the co-agent run's
140
+ * Timeline non-empty. Present only when the co-agent's system prompt resolved AND the step saved
141
+ * (step creation is best-effort, like the runs themselves). Finalized alongside the runs by
142
+ * {@link RealtimeClientSessionService.FinalizeCoAgentRun}.
143
+ */
144
+ CoAgentRunStepID?: string;
145
+ /** A human-readable failure reason. Present on failure. */
146
+ ErrorMessage?: string;
147
+ /** The `MJ: AI Models` row id of the realtime model the session was minted with. Present on success. */
148
+ ModelID?: string;
149
+ /** The display name of the realtime model the session was minted with. Present on success. */
150
+ ModelName?: string;
151
+ /**
152
+ * The DB-driven progress-narration instruction template (the `Realtime Co-Agent - Progress
153
+ * Narration` prompt's `TemplateText`, containing a `{{ progressMessage }}` placeholder).
154
+ * `undefined` when that prompt is not present in metadata — clients fall back to their
155
+ * built-in narration instruction text.
156
+ */
157
+ NarrationInstructionsTemplate?: string;
158
+ /**
159
+ * The RESOLVED effective realtime configuration for this session (type defaults ← agent
160
+ * config ← runtime overrides, deep-merged + normalized). Present on success — `{}` when no
161
+ * layer configured anything. Surfaced so the transport layer can echo it to the client
162
+ * (client drivers apply provider voice settings client-side in the client-direct topology).
163
+ */
164
+ EffectiveConfig?: RealtimeCoAgentConfig;
165
+ /**
166
+ * The effective narration pace (`realtime.narration.paceMs`) — minimum gap in ms between
167
+ * spoken progress updates. `undefined` when not configured (clients/runners use their
168
+ * built-in default). In the CLIENT-DIRECT topology narration pacing is enforced client-side,
169
+ * so this is surfaced for the browser; the server-bridged runner consumes it directly via
170
+ * `RealtimeSessionRunnerDeps.NarrationPaceMs`.
171
+ */
172
+ NarrationPaceMs?: number;
173
+ }
174
+ /**
175
+ * The resolved co-agent system prompt text plus the id of the prompt it came from, returned by
176
+ * {@link RealtimeClientSessionService.resolveCoAgentSystemPrompt}.
177
+ */
178
+ export interface CoAgentSystemPromptResolution {
179
+ /** The co-agent's system prompt template text (empty string when none is configured). */
180
+ Text: string;
181
+ /** The `MJ: AI Prompts` row id, or `null` when the co-agent has no active prompt. */
182
+ PromptID: string | null;
183
+ }
184
+ /**
185
+ * Input for {@link RealtimeClientSessionService.ExecuteRelayedTool}.
186
+ *
187
+ * Carries the single tool call the browser relayed plus the linkage needed to run a delegated
188
+ * target-agent run under the same session.
189
+ */
190
+ export interface ExecuteRelayedToolInput {
191
+ /** The shared session id grouping this voice session's runs. */
192
+ AgentSessionID: string;
193
+ /** The id of the (co-agent) run that owns this session, used as the delegated run's parent. Optional. */
194
+ ParentRunID?: string;
195
+ /** The top-level target agent id for `invoke-target-agent` delegation. */
196
+ TargetAgentID: string;
197
+ /** The tool call the browser relayed from the provider. */
198
+ Call: RealtimeToolCall;
199
+ /**
200
+ * Optional abort signal so a barge-in on the browser can cancel an in-flight delegated run.
201
+ * Threaded into the delegated agent run's `cancellationToken`.
202
+ */
203
+ AbortSignal?: AbortSignal;
204
+ /**
205
+ * Optional progress callback invoked with each delegated-run progress event (mirrors the normal
206
+ * agent-run path's `onProgress`). The transport layer (the MJServer resolver) publishes these so
207
+ * the realtime model can narrate the target agent's progress while it runs. When omitted, the
208
+ * delegated run streams nothing and the model only receives the final tool result.
209
+ */
210
+ OnProgress?: AgentExecutionProgressCallback;
211
+ /**
212
+ * Optional id of a previously-paused delegated run (Status `AwaitingFeedback`) to RESUME instead
213
+ * of starting a fresh run. When set, {@link delegateToTarget} passes it as `lastRunId` (with
214
+ * `autoPopulateLastRunPayload`) to {@link AgentRunner.RunAgent}, so the user's answer continues
215
+ * the SAME interactive run (e.g. confirming a Query Builder task graph).
216
+ */
217
+ ResumeRunID?: string;
218
+ }
219
+ /**
220
+ * The resolved Realtime model plus its identifiers, returned by the model-resolution seam.
221
+ */
222
+ export interface RealtimeModelResolution {
223
+ /** The instantiated realtime driver. */
224
+ Model: BaseRealtimeModel;
225
+ /** The `MJ: AI Models` row id. */
226
+ ModelID: string;
227
+ /** The chosen vendor id. */
228
+ VendorID: string;
229
+ /** The vendor API name passed to the provider as the model id. */
230
+ APIName: string;
231
+ /** The model's display name (`MJ: AI Models.Name`). Optional for back-compat with test seams. */
232
+ ModelName?: string;
233
+ /**
234
+ * The chosen vendor's `DriverClass` (e.g. `OpenAIRealtime`). Used to match the effective
235
+ * config's per-provider voice settings (`realtime.voice.providers`) onto the driver's open
236
+ * `Config` bag. Optional for back-compat with test seams.
237
+ */
238
+ DriverClass?: string;
239
+ }
240
+ /**
241
+ * Output of {@link RealtimeClientSessionService.PrepareRealtimeSessionParams} — the host-agnostic prep that
242
+ * every realtime surface consumes before opening a session its own way. Carries the assembled
243
+ * {@link RealtimeSessionParams} plus the resolved co-agent / model / effective config the openers need.
244
+ */
245
+ export interface RealtimeSessionParamsPrep {
246
+ /** Whether prep succeeded. When false, only {@link ErrorMessage} is set. */
247
+ Success: boolean;
248
+ /** Failure reason (present only when {@link Success} is false). */
249
+ ErrorMessage?: string;
250
+ /** The resolved co-agent (the Realtime-type agent that does the voicing). */
251
+ CoAgent?: MJAIAgentEntityExtended;
252
+ /** The resolved realtime model + identifiers. */
253
+ Resolution?: RealtimeModelResolution;
254
+ /** The effective config from the full precedence cascade (type-default < co-agent < target < override). */
255
+ EffectiveConfig?: RealtimeCoAgentConfig;
256
+ /** The assembled session params (TARGET-identity prompt, stable tools incl. invoke-target, voice, memory). */
257
+ SessionParams?: RealtimeSessionParams;
258
+ }
259
+ /**
260
+ * The runtime handle returned by {@link RealtimeClientSessionService.WireBridgeRealtimeSession} — the
261
+ * server long-lived (bridged) counterpart to what `PrepareClientSession` returns for the browser. The
262
+ * bridge holds this for the life of the session: the observability run ids (for nesting + correlation)
263
+ * and an **idempotent** {@link Finalize} the bridge MUST call on teardown so the co-agent run + prompt
264
+ * run don't dangle in `Running`. Finalize also runs automatically when the session's `Close()` is invoked
265
+ * or the connection drops — calling it again is a safe no-op.
266
+ */
267
+ export interface BridgeRealtimeRuntime {
268
+ /** The `MJ: AI Agent Runs` row id created for this voice session (delegated runs nest under it). */
269
+ CoAgentRunID?: string;
270
+ /** The `MJ: AI Prompt Runs` row id for the session's system prompt. */
271
+ PromptRunID?: string;
272
+ /** Finalizes the co-agent + prompt run. Idempotent; safe to call from multiple teardown paths. */
273
+ Finalize: (success: boolean) => Promise<void>;
274
+ }
275
+ /**
276
+ * Outcome of resolving the realtime model for a session: either a usable {@link RealtimeModelResolution}
277
+ * or a specific, human-readable failure reason (used for explicit preferred-model failures, where the
278
+ * generic "no model" message would hide WHY the user's chosen model couldn't be used).
279
+ */
280
+ export interface RealtimeModelResolutionOutcome {
281
+ /** The resolved model. Present on success. */
282
+ Resolution?: RealtimeModelResolution;
283
+ /** Why resolution failed. Present on failure. */
284
+ ErrorMessage?: string;
285
+ }
286
+ /**
287
+ * Server-agnostic service that prepares a client-direct realtime session and executes the tool
288
+ * calls the browser relays back. Constructed per-request (a normal injectable service — NOT a
289
+ * singleton) so the {@link UserInfo} and {@link IMetadataProvider} are always request-scoped.
290
+ *
291
+ * Every public method takes the `contextUser` and `provider` explicitly — this service never
292
+ * reaches for the global default provider, so it is safe in multi-provider/multi-tenant servers.
293
+ */
294
+ export declare class RealtimeClientSessionService {
295
+ /**
296
+ * The seeded name of the `MJ: AI Prompts` row whose `TemplateText` carries the first-person
297
+ * progress-narration instructions (with a `{{ progressMessage }}` placeholder). Resolved at
298
+ * session prepare time so the browser narrates with DB-driven, product-tunable wording.
299
+ * Canonical value lives in `realtime-narration.ts` (shared with the server-bridged runner path).
300
+ */
301
+ static readonly NarrationPromptName = "Realtime Co-Agent - Progress Narration";
302
+ /**
303
+ * DEPRECATED legacy name of the narration prompt, from before the co-agent's rename from
304
+ * "Voice Co-Agent" to "Realtime Co-Agent". Deployments that have not re-synced the prompt seed
305
+ * still carry this name, so {@link resolveNarrationInstructionsTemplate} falls back to it
306
+ * (with a deprecation log) when {@link RealtimeClientSessionService.NarrationPromptName} is absent.
307
+ */
308
+ static readonly LegacyNarrationPromptName = "Voice Co-Agent - Progress Narration";
309
+ /**
310
+ * IN-FLIGHT DELEGATION REGISTRY — the server half of the client-direct CANCEL channel.
311
+ *
312
+ * Every relayed tool call registers an {@link AbortController} under
313
+ * `(agentSessionID, callID)` for the duration of {@link ExecuteRelayedTool}; the
314
+ * `CancelRealtimeSessionTool` mutation aborts entries via
315
+ * {@link CancelInFlightDelegations} so an explicit user cancel (the overlay's per-card ✕)
316
+ * kills the delegated target-agent run mid-flight. Entries are removed on completion
317
+ * (success, failure, or abort), so the registry only ever holds truly in-flight calls.
318
+ *
319
+ * Keys are normalized (trimmed, lowercased) so SQL Server's uppercase UUIDs and
320
+ * PostgreSQL's lowercase UUIDs address the same entry.
321
+ *
322
+ * NOTE: this registry is per-service-instance state (the resolver holds ONE shared service
323
+ * per server process), not per-request state — it deliberately spans requests so the cancel
324
+ * mutation can reach the execute mutation's in-flight controller.
325
+ */
326
+ private readonly inFlightDelegations;
327
+ /**
328
+ * Per-`AIPromptRun` write serialization. Both the high-frequency usage checkpoint
329
+ * ({@link AccumulatePromptRunUsage}) and the per-turn message append ({@link AppendPromptRunMessage})
330
+ * do load-modify-save on the SAME run row. Run concurrently, the frequent usage save would rewrite the
331
+ * whole row — including the STALE `Messages` it loaded — and perpetually clobber freshly-appended turns
332
+ * back to an empty snapshot (the "transcript never persists" bug). Funnelling every write for a given
333
+ * run through a single promise chain makes each load happen AFTER the prior save committed, so no writer
334
+ * overwrites another's field. Keyed by promptRunID; the entry is dropped on {@link finalizePromptRun}.
335
+ */
336
+ private readonly promptRunWriteChains;
337
+ /**
338
+ * Serializes `task` against all other writes to the same `AIPromptRun` (see {@link promptRunWriteChains}).
339
+ * Tasks run in call order; a failing task never breaks the chain for the next one. Returns the task's result.
340
+ */
341
+ private serializePromptRunWrite;
342
+ /**
343
+ * Prepares a client-direct realtime session: resolves the model, assembles the companion
344
+ * system prompt + stable tool set, and mints the {@link ClientRealtimeSessionConfig}.
345
+ *
346
+ * Returns a failure result (never throws) when no Realtime model/key resolves or the provider
347
+ * cannot mint a client-direct session.
348
+ *
349
+ * @param input The co-agent/target/session inputs.
350
+ * @param contextUser The calling user (threaded to metadata + memory retrieval).
351
+ * @param provider The request-scoped metadata provider.
352
+ * @returns The prep result (Success + ClientConfig/SessionParams, or Success: false + ErrorMessage).
353
+ */
354
+ PrepareClientSession(input: PrepareClientSessionInput, contextUser: UserInfo, provider: IMetadataProvider): Promise<RealtimeClientSessionPrepResult>;
355
+ /**
356
+ * Wires a **server long-lived (bridged)** realtime session onto the SAME core machinery the
357
+ * client-direct path uses — so a LiveKit (or future Zoom/Teams) agent does real work and is tracked
358
+ * identically, with **zero host-local re-implementation**. This is the Phase 2 counterpart to
359
+ * {@link PrepareClientSession}: the browser relays tool calls back over GraphQL to `ExecuteRelayedTool`,
360
+ * whereas here the server holds the live {@link IRealtimeSession} and we wire its `OnToolCall` directly to
361
+ * the SAME {@link ExecuteRelayedTool} (so `invoke-target-agent` runs the target via `AgentRunner`, nests
362
+ * under the co-agent run, supports barge-in cancel + paused-run resume — all of it, for free).
363
+ *
364
+ * Responsibilities, in order:
365
+ * 1. Create the co-agent observability run (+ prompt run + step) so the voice session shows up in the
366
+ * agent-run timeline and delegated runs nest under it (best-effort; a failure just omits the ids).
367
+ * 2. Wire `session.OnToolCall` → `ExecuteRelayedTool` → `session.SendToolResult`.
368
+ * 3. Guarantee finalize-once: wrap `session.Close()` and listen for an unexpected drop (`OnClose`), both
369
+ * routed through one idempotent finalizer. The bridge teardown calls `Close()`, so the run finalizes
370
+ * on graceful end; a dropped socket finalizes via `OnClose`.
371
+ *
372
+ * @param session The live realtime session the bridge owns (from `model.StartSession`).
373
+ * @param input The same prep input used to build the session (carries AgentSessionID, TargetAgentID, …).
374
+ * @param prep The successful {@link PrepareRealtimeSessionParams} result (CoAgent + Resolution).
375
+ * @param contextUser The calling user (threaded into observability + delegated runs).
376
+ * @param provider The request-scoped metadata provider.
377
+ * @returns A {@link BridgeRealtimeRuntime} the bridge holds for the session lifetime.
378
+ */
379
+ WireBridgeRealtimeSession(session: IRealtimeSession, input: PrepareClientSessionInput, prep: RealtimeSessionParamsPrep, contextUser: UserInfo, provider: IMetadataProvider): Promise<BridgeRealtimeRuntime>;
380
+ /**
381
+ * Degenerate {@link BridgeRealtimeRuntime} for the rare case wiring is attempted without a resolved
382
+ * co-agent: answer every tool call with a clear "not available" error and a no-op finalize. Keeps the
383
+ * bridge from hanging on a tool call when prep was incomplete.
384
+ */
385
+ private wireBridgeFallbackRuntime;
386
+ /**
387
+ * **The single source of truth for realtime session prep.** Builds the {@link RealtimeSessionParams}
388
+ * for a co-agent voicing a target: resolves the co-agent, the effective config via the full precedence
389
+ * cascade (type-default < co-agent < **target** < runtime override), the realtime model, then assembles
390
+ * the companion system prompt (**first-person as the TARGET** — this is what gives every host the right
391
+ * identity), the stable tool set (**always including `invoke-target-agent`**), voice, and memory.
392
+ *
393
+ * EVERY realtime host consumes this — native chat via {@link PrepareClientSession} → `CreateClientSession`,
394
+ * and the server-bridged hosts (LiveKit, future Zoom/Teams) via `StartSession`. Hosts differ ONLY in how
395
+ * they OPEN the session and their media transport; identity/precedence/prompt/tools live here, once. Do
396
+ * NOT re-implement this in a host. See `plans/realtime/realtime-core-host-convergence.md`.
397
+ *
398
+ * Pure-ish and side-effect-free (no session opened, no observability run created) — those are the
399
+ * opener's concern. Never throws — returns `Success: false` on failure.
400
+ *
401
+ * @param input The co-agent/target/session inputs (the runtime override rides `ConfigOverridesJson`).
402
+ * @param contextUser The calling user (threaded to metadata + memory retrieval).
403
+ * @param provider The request-scoped metadata provider.
404
+ * @returns The prep result: `Success` + co-agent/resolution/effective-config/session-params, or `Success: false`.
405
+ */
406
+ PrepareRealtimeSessionParams(input: PrepareClientSessionInput, contextUser: UserInfo, provider: IMetadataProvider): Promise<RealtimeSessionParamsPrep>;
407
+ /**
408
+ * Resolves the EFFECTIVE realtime configuration via the surface-agnostic precedence cascade:
409
+ * agent-TYPE `DefaultConfiguration` (base) < **co-agent** `TypeConfiguration` < **target agent**
410
+ * `TypeConfiguration` < (pre-authorized) runtime override — deep-merged per key and normalized.
411
+ * The target layer is what makes a voiced agent (Sage, Marketing Agent, …) carry its own voice/model
412
+ * regardless of host. Tolerant end-to-end: malformed layers contribute nothing and an unloaded metadata
413
+ * cache yields no type defaults. See `plans/realtime/realtime-core-host-convergence.md`.
414
+ *
415
+ * @param coAgent The resolved co-agent.
416
+ * @param overridesJson The pre-authorized runtime override layer, when present.
417
+ * @param targetAgent The TARGET agent being voiced, when distinct from the co-agent — contributes the
418
+ * per-voiced-agent layer (above the co-agent, below the runtime override). Omit when there is none.
419
+ * @returns The normalized effective configuration (possibly empty, never `null`).
420
+ */
421
+ protected resolveEffectiveConfig(coAgent: MJAIAgentEntityExtended, overridesJson?: string, targetAgent?: MJAIAgentEntityExtended | null): RealtimeCoAgentConfig;
422
+ /**
423
+ * Reads the co-agent's TYPE-level `DefaultConfiguration` from {@link AIEngine}'s cached agent
424
+ * types. **Overridable seam**; tolerant — an absent type or unloaded cache returns `null`.
425
+ */
426
+ protected getAgentTypeDefaultConfiguration(coAgent: MJAIAgentEntityExtended): string | null;
427
+ /**
428
+ * Creates the server-side co-agent observability runs for a voice session: an `AIAgentRun`
429
+ * (Status `Running`), and — when a co-agent system prompt resolved — a linked `AIPromptRun`
430
+ * (Status `Running`, `AgentRunID` = the co-agent run, `AgentID` = the co-agent) plus a single
431
+ * `MJ: AI Agent Run Steps` row (StepType `Prompt`) so the co-agent run's Timeline is non-empty.
432
+ * Delegated target-agent runs nest under the returned `CoAgentRunID` via `ParentRunID`.
433
+ *
434
+ * Best-effort: returns `null` (and logs) when the co-agent run cannot be saved, so callers can
435
+ * continue without observability rather than failing the whole prepare. A failed prompt-run or
436
+ * run-step save just omits that id.
437
+ *
438
+ * @param coAgent The resolved co-agent (its id stamps `AgentID` on both runs).
439
+ * @param promptID The co-agent system prompt id, or `null` to skip the prompt run + run step.
440
+ * @param modelID The resolved realtime model id (stamps the prompt run's `ModelID`).
441
+ * @param userID Optional owning user id for the agent run.
442
+ * @param agentSessionID The session id grouping this voice session's runs.
443
+ * @param contextUser The calling user.
444
+ * @param provider The request-scoped metadata provider.
445
+ * @returns The `{ CoAgentRunID, PromptRunID, CoAgentRunStepID }` ids, or `null` when the agent run failed.
446
+ */
447
+ protected createCoAgentObservabilityRun(coAgent: MJAIAgentEntityExtended, promptID: string | null, modelID: string, vendorID: string, userID: string | undefined, agentSessionID: string, contextUser: UserInfo, provider: IMetadataProvider, conversationID?: string): Promise<{
448
+ CoAgentRunID: string;
449
+ PromptRunID?: string;
450
+ CoAgentRunStepID?: string;
451
+ } | null>;
452
+ /**
453
+ * Creates the co-agent `AIAgentRun` row (Status `Running`). Returns its id, or `null` (logging
454
+ * `CompleteMessage`) when the save fails.
455
+ */
456
+ private createCoAgentRun;
457
+ /**
458
+ * Creates the co-agent `AIPromptRun` row (Status `Running`) linked to the co-agent run via
459
+ * `AgentRunID` AND to the co-agent itself via `AgentID` — so the run shows up both on the
460
+ * prompt's run history (`PromptID`) and in agent-scoped prompt-run views. Returns its id, or
461
+ * `null` when `promptID` is absent (skipped) or the save fails (logged).
462
+ */
463
+ private createCoAgentPromptRun;
464
+ /**
465
+ * Creates the single `MJ: AI Agent Run Steps` row for the co-agent observability run — the
466
+ * realtime session has no iterative loop, so its Timeline carries exactly one step
467
+ * representing the session's system prompt (StepNumber 1, StepType `Prompt`, Status `Running`,
468
+ * `TargetID` = the system `AIPrompt`, `TargetLogID` = the linked `AIPromptRun` when one was
469
+ * created). Skipped (returns `null`) when no system prompt resolved. Best-effort: a save
470
+ * failure is logged and returns `null` — it never breaks the session.
471
+ */
472
+ private createCoAgentRunStep;
473
+ /**
474
+ * Finalizes the server-side co-agent observability records when a voice session ends. Loads
475
+ * each (when its id is supplied) and, **only if it is still `Running`**, sets it to `Completed`
476
+ * (or `Failed` when `success` is false) with a `CompletedAt` + `Success` stamp. Idempotent and
477
+ * tolerant: a missing/already-finalized record is a no-op; a load/save failure is logged,
478
+ * never thrown.
479
+ *
480
+ * @param coAgentRunID The co-agent run id, or `null` to skip.
481
+ * @param promptRunID The co-agent prompt run id, or `null` to skip.
482
+ * @param contextUser The calling user.
483
+ * @param provider The request-scoped metadata provider.
484
+ * @param success Whether the session ended successfully (controls Completed vs Failed).
485
+ * @param coAgentRunStepID The co-agent run's single `MJ: AI Agent Run Steps` row id, or `null` to skip.
486
+ */
487
+ FinalizeCoAgentRun(coAgentRunID: string | null, promptRunID: string | null, contextUser: UserInfo, provider: IMetadataProvider, success?: boolean, coAgentRunStepID?: string | null): Promise<void>;
488
+ /**
489
+ * Finalizes the **co-agent observability run(s)** for an agent session that were left `Running` because
490
+ * the session was reaped WITHOUT a live in-memory handle — a prior-boot orphan or a cross-host teardown,
491
+ * where the `Close()`-wrapped finalizer never ran. This is the by-`AgentSessionID` analogue of
492
+ * {@link FinalizeCoAgentRun}: the same-process path already knows its run ids (no query), but here that
493
+ * state died with the prior process, so we locate the session's TOP-LEVEL co-agent run (delegated target
494
+ * runs nest under it and finalize on their own runner) and finalize it + its prompt run + step via the
495
+ * same idempotent helpers. A clean teardown already marked them `Completed`, so this finds nothing.
496
+ *
497
+ * `MJ: AI Agent Runs` is a high-volume transactional table no engine caches, so a narrow ids-only query
498
+ * is the right tool (not a cache reuse). Tolerant — never throws.
499
+ *
500
+ * @param agentSessionID The agent session whose dangling co-agent runs to finalize.
501
+ * @param success Mark them `Completed` (true) or `Failed` (false).
502
+ * @param contextUser The user the writes run as.
503
+ * @param provider The request-scoped metadata provider.
504
+ * @returns The number of co-agent runs finalized (0 when none were dangling).
505
+ */
506
+ FinalizeCoAgentRunsBySession(agentSessionID: string, success: boolean, contextUser: UserInfo, provider: IMetadataProvider): Promise<number>;
507
+ /** Finds the still-`Running` prompt-run + run-step ids for a co-agent run (orphan finalize path). */
508
+ private findCoAgentChildLogIds;
509
+ /** Escapes single quotes for safe embedding in an `ExtraFilter` literal. */
510
+ private escapeSqlLiteral;
511
+ /** Loads + finalizes the co-agent `AIAgentRun` if still `Running`. Tolerant: logs, never throws. */
512
+ private finalizeAgentRun;
513
+ /**
514
+ * Loads + finalizes the co-agent run's single system-prompt `MJ: AI Agent Run Steps` row if
515
+ * still `Running` (Status `Completed`/`Failed`, `CompletedAt`, `Success`). Tolerant: a
516
+ * missing/already-finalized step is a no-op; a load/save failure is logged, never thrown.
517
+ */
518
+ private finalizeRunStep;
519
+ /** Loads + finalizes the co-agent `AIPromptRun` if still `Running`. Tolerant: logs, never throws. */
520
+ private finalizePromptRun;
521
+ /**
522
+ * Appends (or replaces) one transcript turn onto the co-agent's long-lived `AIPromptRun.Messages`,
523
+ * so the realtime co-agent's conversation is captured on its run exactly like every other MJ agent
524
+ * run — closing the observability gap where the run held only token totals, never the turns. The
525
+ * run viewer can then show what the co-agent heard and said. Mirrors {@link accumulatePromptRunUsage}'s
526
+ * load/append/save pattern; best-effort and tolerant (logs, never throws).
527
+ *
528
+ * `replacePrevious` swaps the last same-role message instead of appending — the streaming-correction
529
+ * case (an interim assistant turn finalized into its full text). The stored shape is the standard
530
+ * chat-message array (`[{ role, content }, …]`) the rest of MJ already reads from `Messages`.
531
+ *
532
+ * NOTE: load-append-save carries the same benign race as usage accumulation; realtime turns are
533
+ * sequential per session so collisions are rare. A dedicated child turn-row entity would remove the
534
+ * race (and the blob rewrite) entirely — a future increment. Tool-call turns (the browser_ and
535
+ * Whiteboard_ channel tools) are a separate increment that requires the client to relay them.
536
+ *
537
+ * @returns `true` when the turn was persisted onto the prompt run.
538
+ */
539
+ AppendPromptRunMessage(promptRunID: string, role: 'user' | 'assistant' | 'system', content: string, replacePrevious: boolean, contextUser: UserInfo, provider: IMetadataProvider): Promise<boolean>;
540
+ /**
541
+ * Accumulates relayed usage DELTAS onto the co-agent `AIPromptRun`'s `TokensPrompt` / `TokensCompletion`
542
+ * (recomputing `TokensUsed`). Serialized against {@link AppendPromptRunMessage} on the same run so the
543
+ * high-frequency usage checkpoint never overwrites freshly-appended transcript turns (and vice-versa).
544
+ * Best-effort: load/save failures log and return `false`, never throw.
545
+ *
546
+ * @param promptRunID The co-agent observability prompt run.
547
+ * @param inputDelta Input-token delta to add (caller clamps to >= 0).
548
+ * @param outputDelta Output-token delta to add (caller clamps to >= 0).
549
+ * @returns `true` when the accumulated usage was persisted.
550
+ */
551
+ AccumulatePromptRunUsage(promptRunID: string, inputDelta: number, outputDelta: number, contextUser: UserInfo, provider: IMetadataProvider): Promise<boolean>;
552
+ /** Parses the prompt run's `Messages` JSON into a mutable chat-message array (tolerant: `[]` on empty/malformed). */
553
+ private parsePromptRunMessages;
554
+ /**
555
+ * Executes a single tool call relayed from the browser and returns its serialized result.
556
+ *
557
+ * Builds a {@link RealtimeToolBroker} whose `DelegateToTarget` runs the target agent (threading
558
+ * the abort signal, parent run, and session id) and whose `ExecuteTool` returns a structured
559
+ * "not available" result for non-target tools (action wiring is a later phase). The broker
560
+ * routes the call and always resolves with structured JSON — failures become `tool_response`
561
+ * errors the model can narrate rather than thrown exceptions.
562
+ *
563
+ * @param input The relayed tool call plus delegation linkage.
564
+ * @param contextUser The calling user (threaded into the delegated agent run).
565
+ * @param provider The request-scoped metadata provider (threaded into the delegated agent run).
566
+ * @returns `{ ResultJson, Success, PausedRunID?, Artifacts? }` — the serialized tool result for
567
+ * the browser to relay back, the paused run id when the delegated target agent paused awaiting
568
+ * feedback (so the resolver can persist it and resume that run on the next answer), and the
569
+ * artifacts the delegated run produced (so the resolver can junction-link them into the
570
+ * session's conversation history — the same info is embedded in `ResultJson` for the client).
571
+ */
572
+ ExecuteRelayedTool(input: ExecuteRelayedToolInput, contextUser: UserInfo, provider: IMetadataProvider): Promise<{
573
+ ResultJson: string;
574
+ Success: boolean;
575
+ PausedRunID?: string;
576
+ Artifacts?: DelegatedRunArtifact[];
577
+ }>;
578
+ /**
579
+ * Aborts in-flight relayed delegations for a session — the server half of the client-direct
580
+ * CANCEL channel (see the registry note on {@link inFlightDelegations}).
581
+ *
582
+ * @param agentSessionID The session whose in-flight delegations to abort.
583
+ * @param callID When supplied, only the delegation for this specific call is aborted; when
584
+ * omitted, EVERY in-flight delegation for the session is aborted.
585
+ * @returns The number of in-flight delegations aborted. **Tolerant by design**: an unknown
586
+ * session, an unknown call id, or a session with nothing in flight returns `0` — never throws
587
+ * (the call the user wanted dead may simply have finished already, which is a fine outcome).
588
+ */
589
+ CancelInFlightDelegations(agentSessionID: string, callID?: string): number;
590
+ /** Normalized (trim + lowercase) registry key so UUID casing differences can't split entries. */
591
+ private registryKey;
592
+ /** Creates + registers the abort controller for one in-flight relayed call. */
593
+ private registerInFlightDelegation;
594
+ /**
595
+ * Removes one call's registry entry on completion — but only when the stored controller is
596
+ * STILL the one this execution registered (a cancel may already have removed it, and a
597
+ * same-callID retry may have replaced it).
598
+ */
599
+ private unregisterInFlightDelegation;
600
+ /**
601
+ * Ensures {@link AIEngine} metadata is loaded before resolution. **Overridable seam** so tests
602
+ * can skip the DB-backed config load.
603
+ *
604
+ * @param contextUser The calling user.
605
+ * @param provider The request-scoped metadata provider.
606
+ */
607
+ protected configureEngine(contextUser: UserInfo, provider: IMetadataProvider): Promise<void>;
608
+ /**
609
+ * Resolves the co-agent from either the supplied entity or its id (from cached metadata).
610
+ *
611
+ * @param input The prepare-session input.
612
+ * @returns The co-agent entity, or `null` when neither form resolves.
613
+ */
614
+ protected resolveCoAgent(input: PrepareClientSessionInput): MJAIAgentEntityExtended | null;
615
+ /**
616
+ * Resolves the realtime model for a session, honoring an explicit user choice when present.
617
+ *
618
+ * - With {@link PrepareClientSessionInput.PreferredModelID}: resolve THAT model strictly via
619
+ * {@link resolvePreferredRealtimeModel} — failures return a specific reason and never fall
620
+ * back to another model (the user explicitly chose). (The transport layer has already
621
+ * authorization-gated a deviating explicit choice.)
622
+ * - Else, with an effective-config `realtime.modelPreference` (name or id): resolve it via
623
+ * {@link resolveConfiguredModelPreference}. METADATA preferences degrade gracefully — an
624
+ * unsatisfiable preference logs and FALLS THROUGH to the default (mirroring the co-agent
625
+ * resolution chain's tolerant metadata steps), it never breaks calls.
626
+ * - Without either: the existing default behavior via {@link resolveRealtimeModel}
627
+ * (highest-PowerRank active Realtime model), with the generic {@link noModelMessage} on failure.
628
+ *
629
+ * @param input The prepare-session input (carries the optional preferred model id).
630
+ * @param coAgent The resolved co-agent (threaded to the default-resolution seam).
631
+ * @param effectiveConfig The resolved effective configuration (carries `modelPreference`).
632
+ * @returns The resolution outcome (resolution or failure reason).
633
+ */
634
+ protected resolveModelForSession(input: PrepareClientSessionInput, coAgent: MJAIAgentEntityExtended, effectiveConfig?: RealtimeCoAgentConfig): Promise<RealtimeModelResolutionOutcome>;
635
+ /**
636
+ * Resolves the effective config's `realtime.modelPreference` (an `MJ: AI Models` Name OR ID)
637
+ * into a usable realtime model. TOLERANT by design — this is a METADATA preference, so any
638
+ * failure (unknown model, inactive, wrong type, no vendor/key) logs a warning and returns
639
+ * `null`, falling through to the default highest-PowerRank resolution. Contrast with the
640
+ * explicit runtime choice ({@link resolvePreferredRealtimeModel}), which fails loud.
641
+ *
642
+ * @param effectiveConfig The resolved effective configuration.
643
+ * @returns The resolution, or `null` when no preference is configured or it can't be satisfied.
644
+ */
645
+ protected resolveConfiguredModelPreference(effectiveConfig?: RealtimeCoAgentConfig): RealtimeModelResolution | null;
646
+ /**
647
+ * Looks up a model by ID (UUID-insensitive) or, failing that, by case/whitespace-insensitive
648
+ * Name in {@link AIEngine}'s cached models. **Overridable seam**; tolerant of an unloaded cache.
649
+ *
650
+ * @param preference The `MJ: AI Models` ID or Name.
651
+ * @returns The model entity, or `null`.
652
+ */
653
+ protected findModelByIDOrName(preference: string): MJAIModelEntityExtended | null;
654
+ /**
655
+ * Strictly resolves an EXPLICITLY requested realtime model. Each precondition failure returns
656
+ * a clear, user-facing reason naming the model — there is NO fallback to another model, because
657
+ * the caller's user explicitly chose this one.
658
+ *
659
+ * @param preferredModelID The `MJ: AI Models.ID` the user chose.
660
+ * @returns The resolution outcome (resolution or a specific failure reason).
661
+ */
662
+ protected resolvePreferredRealtimeModel(preferredModelID: string): RealtimeModelResolutionOutcome;
663
+ /**
664
+ * Looks up a model by id in {@link AIEngine}'s cached models. **Overridable seam** for tests.
665
+ *
666
+ * @param modelID The `MJ: AI Models.ID` to find.
667
+ * @returns The model entity, or `null` when not present.
668
+ */
669
+ protected findModelByID(modelID: string): MJAIModelEntityExtended | null;
670
+ /** True when the model's denormalized `AIModelType` name is `Realtime` (case/whitespace-insensitive). */
671
+ private isRealtimeModel;
672
+ /**
673
+ * Resolves the Realtime model + vendor driver + API key, mirroring `BaseAgent`'s server-bridged
674
+ * resolution: highest-power active model of AIModelType `Realtime`; highest-priority active
675
+ * vendor whose `DriverClass` has a resolvable API key; instantiated via the `ClassFactory`.
676
+ *
677
+ * **Overridable seam.** Test subclasses override this to return a mock model so the service can
678
+ * be exercised without provider SDKs or DB metadata. Returns `null` (never throws) when any
679
+ * step can't be satisfied.
680
+ *
681
+ * @param coAgent The co-agent being voiced (reserved for future per-agent model preference).
682
+ * @returns The resolved model + identifiers, or `null`.
683
+ */
684
+ protected resolveRealtimeModel(coAgent: MJAIAgentEntityExtended): Promise<RealtimeModelResolution | null>;
685
+ /**
686
+ * Shared tail of model resolution: picks the vendor (with a usable API key) for an
687
+ * already-chosen model entity and instantiates its realtime driver.
688
+ *
689
+ * @param model The chosen model entity.
690
+ * @returns The full resolution, or `null` when no vendor/key/driver can be satisfied.
691
+ */
692
+ protected resolveVendorAndInstantiate(model: MJAIModelEntityExtended): RealtimeModelResolution | null;
693
+ /**
694
+ * Resolves the API key for a vendor driver class. **Overridable seam** (wraps the module-level
695
+ * {@link GetAIAPIKey}) so tests can simulate present/absent keys without environment setup.
696
+ *
697
+ * @param driverClass The vendor's `DriverClass`.
698
+ * @returns The API key, or a falsy value when none is configured.
699
+ */
700
+ protected getAPIKeyForDriver(driverClass: string): string | undefined;
701
+ /**
702
+ * Instantiates the realtime driver for a vendor driver class via the ClassFactory.
703
+ * **Overridable seam** so tests can return a mock driver.
704
+ *
705
+ * @param driverClass The vendor's `DriverClass` (the ClassFactory key).
706
+ * @param apiKey The resolved API key (constructor argument).
707
+ * @returns The driver instance, or `null` when the factory cannot create one.
708
+ */
709
+ protected createModelInstance(driverClass: string, apiKey: string): BaseRealtimeModel | null;
710
+ /**
711
+ * The active models of AIModelType `Realtime`, sorted highest-PowerRank first — the candidate
712
+ * list {@link resolveRealtimeModel} walks until one yields a usable client-direct driver.
713
+ * Returns ALL candidates (not just the top pick) so a keyless or non-client-direct top model
714
+ * falls through to the next usable one instead of dead-ending the whole resolution.
715
+ *
716
+ * @param coAgent The co-agent (reserved for future per-agent model preference).
717
+ * @returns The candidate models in resolution order (empty array when none are active).
718
+ */
719
+ private selectRealtimeModelCandidates;
720
+ /**
721
+ * Selects the highest-priority active vendor for a model whose `DriverClass` has a resolvable
722
+ * API key. Mirrors `BaseAgent.selectRealtimeVendor`.
723
+ *
724
+ * @param modelID The chosen model's id.
725
+ * @returns The vendor driver/api identifiers, or `null` when none has a usable key.
726
+ */
727
+ protected selectRealtimeVendor(modelID: string): {
728
+ VendorID: string;
729
+ DriverClass: string;
730
+ APIName: string;
731
+ } | null;
732
+ /**
733
+ * Resolves the DB-driven progress-narration instruction template: the Active `MJ: AI Prompts`
734
+ * row named {@link RealtimeClientSessionService.NarrationPromptName}, read from
735
+ * {@link AIEngine}'s cached prompts. When the current name is absent, falls back to the
736
+ * DEPRECATED {@link RealtimeClientSessionService.LegacyNarrationPromptName} (pre-rename seed)
737
+ * with a deprecation log. **Tolerant**: returns `null` (never throws) when neither prompt is
738
+ * present, the text is empty, or the engine cache is unavailable — clients fall back to their
739
+ * built-in narration instruction text.
740
+ *
741
+ * @returns The template text (containing a `{{ progressMessage }}` placeholder), or `null`.
742
+ */
743
+ protected resolveNarrationInstructionsTemplate(): string | null;
744
+ /**
745
+ * Builds the {@link RealtimeSessionParams} for the client-direct session: the companion system
746
+ * prompt plus the stable, target-independent tool set.
747
+ *
748
+ * @param input The prepare-session input.
749
+ * @param coAgent The resolved co-agent.
750
+ * @param modelApiName The vendor API name of the resolved realtime model.
751
+ * @param contextUser The calling user.
752
+ * @param provider The request-scoped metadata provider.
753
+ * @param effectiveConfig The resolved effective configuration (voice persona + provider settings).
754
+ * @param driverClass The resolved vendor's DriverClass — matches per-provider voice settings.
755
+ * @returns The assembled session params.
756
+ */
757
+ protected buildSessionParams(input: PrepareClientSessionInput, coAgent: MJAIAgentEntityExtended, modelApiName: string, contextUser: UserInfo, provider: IMetadataProvider, effectiveConfig?: RealtimeCoAgentConfig, driverClass?: string): Promise<RealtimeSessionParams>;
758
+ /**
759
+ * Builds the provider-pact `Config` bag for the session: the effective config's matching
760
+ * per-provider voice settings (`realtime.voice.providers.<provider>`) merged UNDER any
761
+ * caller-supplied {@link PrepareClientSessionInput.Config} (the runtime bag wins per key).
762
+ * The settings objects are OPAQUE driver pacts — each server driver consumes its own keys
763
+ * exactly as it consumes any other entry of the open config bag (OpenAI spreads it into
764
+ * `session.update`, AssemblyAI reads `voice`, Gemini merges it last). Returns the original
765
+ * `input.Config` (possibly `undefined`) when no provider settings match, preserving the
766
+ * pre-config behavior byte-for-byte.
767
+ *
768
+ * @param input The prepare-session input (carries the runtime config bag).
769
+ * @param effectiveConfig The resolved effective configuration.
770
+ * @param driverClass The resolved vendor's DriverClass.
771
+ * @returns The merged config bag, or `undefined` when nothing contributes.
772
+ */
773
+ protected buildSessionConfigBag(input: PrepareClientSessionInput, effectiveConfig?: RealtimeCoAgentConfig, driverClass?: string): JSONObject | undefined;
774
+ /**
775
+ * Assembles the companion system prompt: the framing ("you are the voice for the target"), the
776
+ * co-agent's own system prompt text, the TARGET agent's identity/capabilities (Name +
777
+ * Description), the conversation history, and the same memory/context a loop agent assembles.
778
+ *
779
+ * When the effective configuration carries a voice persona (`realtime.voice.default`), a
780
+ * short "Voice & manner" section (tone / speaking style) is appended after the co-agent's
781
+ * own prompt so the model speaks in the configured manner.
782
+ *
783
+ * @param input The prepare-session input.
784
+ * @param coAgent The resolved co-agent.
785
+ * @param contextUser The calling user.
786
+ * @param provider The request-scoped metadata provider.
787
+ * @param effectiveConfig The resolved effective configuration (voice persona source).
788
+ * @returns The concatenated system prompt (never empty — the framing is always present).
789
+ */
790
+ protected buildCompanionSystemPrompt(input: PrepareClientSessionInput, coAgent: MJAIAgentEntityExtended, contextUser: UserInfo, provider: IMetadataProvider, effectiveConfig?: RealtimeCoAgentConfig): Promise<string>;
791
+ /**
792
+ * Builds the **meeting-mode** discipline clause — present only for a multi-agent meeting session
793
+ * ({@link PrepareClientSessionInput.DisableAutoResponse}). It tells the agent to hear the whole
794
+ * conversation but speak only when addressed (named) or clearly called on, and never to talk over
795
+ * others. This is the *prompt* half of "hear always, speak selectively"; the enforcement half is the
796
+ * model's disabled auto-response + the bridge's addressing gate. Empty for a 1:1 call (prompt unchanged).
797
+ * See `plans/realtime/multi-agent-meeting-turn-taking.md`.
798
+ *
799
+ * @param input The prepare-session input (carries the meeting flag + self names).
800
+ * @returns The meeting clause, or `''` for a non-meeting session.
801
+ */
802
+ protected buildMeetingFraming(input: PrepareClientSessionInput): string;
803
+ /**
804
+ * Builds the "interactive-surface tools" exception clause appended to the co-agent framing when
805
+ * the client supplied channel tools (browser_*, Whiteboard_*, …) as ExtraTools. Without it the
806
+ * co-agent — told to route ALL work through invoke-target-agent — delegates browser/whiteboard
807
+ * requests to the target agent (which has no live channel of its own) instead of driving the
808
+ * surface itself, then hallucinates a "missing session id". The tools ARE already in its set
809
+ * ({@link buildStableToolSet} merges `[invokeTarget, ...extraTools]`); this clause tells the model
810
+ * to USE them directly. Returns empty for pure-voice sessions (no ExtraTools), keeping that
811
+ * framing untouched. Generic by design — it names browser_ and Whiteboard_ tools only as
812
+ * examples, so any future client channel is covered automatically.
813
+ *
814
+ * @param extraTools The client-supplied channel tools, when any.
815
+ * @returns The exception clause (leading space included), or '' when there are no extra tools.
816
+ */
817
+ protected buildInteractiveSurfaceFraming(extraTools?: RealtimeToolDefinition[]): string;
818
+ /**
819
+ * Frames the prior-leg transcript (when a session resumes via `lastSessionId`) as a clearly
820
+ * labeled PRIOR-CONVERSATION section of the system prompt, so the model REMEMBERS the last
821
+ * live session rather than greeting the user cold. The transport layer supplies the
822
+ * already-capped, role-tagged lines (see {@link PrepareClientSessionInput.PriorTranscript});
823
+ * this method only adds the framing. Empty/whitespace input yields an empty section.
824
+ *
825
+ * @param priorTranscript The role-tagged transcript lines, or undefined.
826
+ * @returns The framed section, or empty string when there is nothing to frame.
827
+ */
828
+ private formatPriorTranscript;
829
+ /**
830
+ * Resolves the target agent entity from cached metadata.
831
+ *
832
+ * @param targetAgentID The target agent id.
833
+ * @returns The target agent entity, or `null` when not found.
834
+ */
835
+ protected resolveTargetAgent(targetAgentID: string): MJAIAgentEntityExtended | null;
836
+ /**
837
+ * Reads the co-agent's own system prompt text from its highest-priority active agent prompt,
838
+ * mirroring `BaseAgent.loadAgentConfiguration`'s child-prompt resolution.
839
+ *
840
+ * @param coAgent The resolved co-agent.
841
+ * @returns The co-agent's system prompt template text, or empty string when none is configured.
842
+ */
843
+ protected getCoAgentSystemPromptText(coAgent: MJAIAgentEntityExtended): string;
844
+ /**
845
+ * Resolves the co-agent's highest-priority active system prompt, returning both its template
846
+ * text and its prompt id. The id is surfaced so {@link PrepareClientSession} can create a linked
847
+ * co-agent `AIPromptRun` for observability. Mirrors `BaseAgent.loadAgentConfiguration`'s
848
+ * child-prompt resolution.
849
+ *
850
+ * @param coAgent The resolved co-agent.
851
+ * @returns The prompt text + id, or `{ Text: '', PromptID: null }` when none is configured.
852
+ */
853
+ protected resolveCoAgentSystemPrompt(coAgent: MJAIAgentEntityExtended): CoAgentSystemPromptResolution;
854
+ /**
855
+ * Formats the target agent's identity + capabilities block for the system prompt.
856
+ *
857
+ * @param target The target agent, or `null`.
858
+ * @returns The formatted block, or empty string when no target resolved.
859
+ */
860
+ private formatTargetIdentity;
861
+ /**
862
+ * Formats prior conversation history as a plain-text block for the system prompt.
863
+ *
864
+ * @param messages The conversation messages, or undefined.
865
+ * @returns The formatted history block, or empty string when there is none.
866
+ */
867
+ private formatConversationHistory;
868
+ /**
869
+ * Assembles the same memory/context block a loop agent injects, reusing
870
+ * {@link AgentMemoryContextBuilder} so there is no duplicated retrieval logic. The builder
871
+ * unshifts a system message onto a throwaway array, which we pull back out as plain text.
872
+ *
873
+ * @param input The prepare-session input.
874
+ * @param coAgent The resolved co-agent.
875
+ * @param contextUser The calling user.
876
+ * @returns The concatenated context text (empty string when nothing was injected).
877
+ */
878
+ protected assembleMemoryContext(input: PrepareClientSessionInput, coAgent: MJAIAgentEntityExtended, contextUser: UserInfo): Promise<string>;
879
+ /**
880
+ * Builds the stable, target-independent tool set every voice session exposes: the single
881
+ * `invoke-target-agent` tool plus any caller-supplied extra tools. The target is a runtime
882
+ * argument *inside* the call, never a per-target tool — this keeps the provider contract
883
+ * identical across targets.
884
+ *
885
+ * @param extraTools Optional additional target-independent tools.
886
+ * @returns The tools to register at session start.
887
+ */
888
+ protected buildStableToolSet(extraTools?: RealtimeToolDefinition[]): RealtimeToolDefinition[];
889
+ /**
890
+ * Builds the {@link RealtimeToolBroker} for a relayed tool call, wiring `DelegateToTarget` to a
891
+ * target-agent run and `ExecuteTool` to a structured "not available" placeholder.
892
+ *
893
+ * @param input The relayed tool input.
894
+ * @param contextUser The calling user.
895
+ * @param provider The request-scoped metadata provider.
896
+ * @returns The constructed broker.
897
+ */
898
+ protected buildToolBroker(input: ExecuteRelayedToolInput, contextUser: UserInfo, provider: IMetadataProvider): RealtimeToolBroker;
899
+ /**
900
+ * Delegates an `invoke-target-agent` call to the target agent via {@link AgentRunner.RunAgent}.
901
+ *
902
+ * Threads the broker-owned abort signal (combined with any caller signal) into the child run's
903
+ * `cancellationToken`, links the child run to the co-agent run via `parentRunID`, and propagates
904
+ * `agentSessionID` so both runs group under the same session. Mirrors
905
+ * `BaseAgent.delegateRealtimeToTarget`.
906
+ *
907
+ * @param input The relayed tool input (target id + linkage).
908
+ * @param request The broker's delegation request (call id + arguments + abort signal).
909
+ * @param contextUser The calling user.
910
+ * @param provider The request-scoped metadata provider.
911
+ * @returns The delegated result for the model's tool_response.
912
+ */
913
+ protected delegateToTarget(input: ExecuteRelayedToolInput, request: DelegateToTargetRequest, contextUser: UserInfo, provider: IMetadataProvider): Promise<DelegatedResult>;
914
+ /**
915
+ * Creates artifact(s) from a completed delegated run's payload — the voice-path equivalent of
916
+ * the chat path's artifact step in `AgentRunner.RunAgentInConversation`. Delegated voice runs
917
+ * execute via `AgentRunner.RunAgent` directly (no conversation detail), so without this step
918
+ * they would never produce artifacts at all.
919
+ *
920
+ * Eligibility guards (all must hold, mirroring the chat path's `processArtifacts`):
921
+ * - the run succeeded and did NOT pause awaiting feedback (a paused run has no deliverable yet);
922
+ * - the run returned a non-empty payload.
923
+ *
924
+ * The DB work is delegated to {@link processRunArtifacts} (an overridable seam), which reuses
925
+ * `AgentRunner.ProcessAgentArtifacts` — so ArtifactCreationMode, DefaultArtifactTypeID,
926
+ * name extraction, and duplicate-version dedup all behave exactly as in chat. **Best-effort:**
927
+ * any failure is logged and returns `undefined`; artifact surfacing never fails the delegation.
928
+ *
929
+ * @param result The delegated agent execution result.
930
+ * @param contextUser The calling user.
931
+ * @param provider The request-scoped metadata provider.
932
+ * @returns The produced artifact descriptor(s), or `undefined` when none were created.
933
+ */
934
+ protected createDelegatedRunArtifacts(result: ExecuteAgentResult, contextUser: UserInfo, provider: IMetadataProvider): Promise<DelegatedRunArtifact[] | undefined>;
935
+ /**
936
+ * The DB-backed artifact-creation seam: runs `AgentRunner.ProcessAgentArtifacts` WITHOUT a
937
+ * conversation detail (the voice path has none — the artifact + version are created and the
938
+ * junction link is skipped), then loads the artifact header for its display name.
939
+ *
940
+ * Artifacts whose Visibility resolved to `System Only` (the agent's ArtifactCreationMode) are
941
+ * created but NOT surfaced to the overlay — matching how chat hides them from users.
942
+ *
943
+ * **Overridable seam** so tests can exercise {@link createDelegatedRunArtifacts}' eligibility
944
+ * guards without a DB.
945
+ *
946
+ * @param result The delegated agent execution result (payload + agentRun).
947
+ * @param contextUser The calling user.
948
+ * @param provider The request-scoped metadata provider.
949
+ * @returns The produced artifact descriptor(s), or `undefined`.
950
+ */
951
+ protected processRunArtifacts(result: ExecuteAgentResult, contextUser: UserInfo, provider: IMetadataProvider): Promise<DelegatedRunArtifact[] | undefined>;
952
+ /**
953
+ * Runs (or resumes) the target agent for a delegation. Threads the combined abort signal, parent
954
+ * run linkage, session id, and the `OnProgress` callback so the resolver can stream progress.
955
+ * When {@link ExecuteRelayedToolInput.ResumeRunID} is set, resumes that paused run via
956
+ * `lastRunId` + `autoPopulateLastRunPayload` (the user's answer continues the same interactive
957
+ * run) instead of starting fresh.
958
+ *
959
+ * @param input The relayed tool input (linkage, progress callback, optional resume id).
960
+ * @param request The broker's delegation request (call id + arguments + abort signal).
961
+ * @param target The resolved target agent.
962
+ * @param contextUser The calling user.
963
+ * @param provider The request-scoped metadata provider.
964
+ * @returns The agent execution result.
965
+ */
966
+ private runDelegatedAgent;
967
+ /**
968
+ * Maps an {@link ExecuteAgentResult} onto the broker's {@link DelegatedResult}, special-casing a
969
+ * run that paused awaiting feedback. An `AwaitingFeedback` run is a valid intermediate outcome,
970
+ * not an error: we return its clarifying QUESTION (the run's `Message`) as the tool Output —
971
+ * phrased so the realtime model relays it as a question to the user — set `Success: true`, and
972
+ * surface the paused run id so the resolver can resume that run on the user's next answer.
973
+ *
974
+ * @param callID The provider call id this result corresponds to.
975
+ * @param result The agent execution result.
976
+ * @param artifacts Artifacts the run produced (from {@link createDelegatedRunArtifacts}),
977
+ * threaded into the result so the broker serializes them for the call overlay.
978
+ * @returns The delegated result for the model's tool_response.
979
+ */
980
+ private buildDelegatedResult;
981
+ /**
982
+ * Loads the co-agent run entity behind {@link ExecuteRelayedToolInput.ParentRunID} so the
983
+ * delegated run can link to it via `parentRun` (→ `ParentRunID`). Returns `null` when no id was
984
+ * supplied or the run cannot be loaded (delegation proceeds without parent linkage rather than
985
+ * failing the whole call).
986
+ *
987
+ * @param parentRunID The co-agent run id, or undefined.
988
+ * @param contextUser The calling user.
989
+ * @param provider The request-scoped metadata provider.
990
+ * @returns The loaded parent run entity, or `null`.
991
+ */
992
+ protected loadParentRun(parentRunID: string | undefined, contextUser: UserInfo, provider: IMetadataProvider): Promise<MJAIAgentRunEntityExtended | null>;
993
+ /**
994
+ * Routes a non-target tool call. For now this returns a structured "not available" result —
995
+ * the richer client/UI/action routing is wired in a later phase. Documented minimal seam.
996
+ *
997
+ * @param call The non-target tool call.
998
+ * @returns A failed {@link ToolExecutionResult} the model can narrate.
999
+ */
1000
+ protected executeNonTargetTool(call: RealtimeToolCall): Promise<ToolExecutionResult>;
1001
+ /**
1002
+ * Parses the natural-language request text out of an `invoke-target-agent` call's arguments.
1003
+ * Falls back to the raw argument string when it is not the expected `{ request: string }` JSON.
1004
+ *
1005
+ * @param argumentsJson The raw arguments string emitted by the model.
1006
+ * @returns The request text to hand to the target agent.
1007
+ */
1008
+ private parseDelegateRequestText;
1009
+ /**
1010
+ * Combines the broker's per-call abort signal with an optional caller-supplied signal so either
1011
+ * source can cancel the delegated run. Returns the broker signal alone when no caller signal is
1012
+ * present (the common case), avoiding an unnecessary controller.
1013
+ *
1014
+ * @param brokerSignal The broker-owned per-call abort signal (always present).
1015
+ * @param callerSignal An optional caller signal (e.g. a request-scoped barge-in).
1016
+ * @returns A single abort signal that fires when either source aborts.
1017
+ */
1018
+ private combineSignals;
1019
+ /**
1020
+ * The clear, actionable message returned when no usable Realtime model can be resolved.
1021
+ *
1022
+ * @returns The failure message.
1023
+ */
1024
+ private noModelMessage;
1025
+ }
1026
+ //# sourceMappingURL=realtime-client-session-service.d.ts.map