@memberjunction/ai-agents 5.40.1 → 5.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -0
- package/dist/AgentRunner.d.ts +5 -2
- package/dist/AgentRunner.d.ts.map +1 -1
- package/dist/AgentRunner.js +14 -4
- package/dist/AgentRunner.js.map +1 -1
- package/dist/MemoryWriteManager.d.ts +188 -0
- package/dist/MemoryWriteManager.d.ts.map +1 -0
- package/dist/MemoryWriteManager.js +299 -0
- package/dist/MemoryWriteManager.js.map +1 -0
- package/dist/agent-context-injector.d.ts +29 -0
- package/dist/agent-context-injector.d.ts.map +1 -1
- package/dist/agent-context-injector.js +90 -32
- package/dist/agent-context-injector.js.map +1 -1
- package/dist/agent-memory-context-builder.d.ts +100 -0
- package/dist/agent-memory-context-builder.d.ts.map +1 -0
- package/dist/agent-memory-context-builder.js +172 -0
- package/dist/agent-memory-context-builder.js.map +1 -0
- package/dist/agent-types/index.d.ts +1 -0
- package/dist/agent-types/index.d.ts.map +1 -1
- package/dist/agent-types/index.js +1 -0
- package/dist/agent-types/index.js.map +1 -1
- package/dist/agent-types/loop-agent-response-type.d.ts +12 -1
- package/dist/agent-types/loop-agent-response-type.d.ts.map +1 -1
- package/dist/agent-types/loop-agent-response-type.js.map +1 -1
- package/dist/agent-types/loop-agent-type.d.ts.map +1 -1
- package/dist/agent-types/loop-agent-type.js +4 -0
- package/dist/agent-types/loop-agent-type.js.map +1 -1
- package/dist/agent-types/realtime-agent-type.d.ts +146 -0
- package/dist/agent-types/realtime-agent-type.d.ts.map +1 -0
- package/dist/agent-types/realtime-agent-type.js +176 -0
- package/dist/agent-types/realtime-agent-type.js.map +1 -0
- package/dist/base-agent.d.ts +365 -24
- package/dist/base-agent.d.ts.map +1 -1
- package/dist/base-agent.js +995 -175
- package/dist/base-agent.js.map +1 -1
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -1
- package/dist/memory-manager-agent.d.ts +55 -2
- package/dist/memory-manager-agent.d.ts.map +1 -1
- package/dist/memory-manager-agent.js +261 -62
- package/dist/memory-manager-agent.js.map +1 -1
- package/dist/realtime/meeting-controls-channel-server.d.ts +198 -0
- package/dist/realtime/meeting-controls-channel-server.d.ts.map +1 -0
- package/dist/realtime/meeting-controls-channel-server.js +319 -0
- package/dist/realtime/meeting-controls-channel-server.js.map +1 -0
- package/dist/realtime/meeting-controls-state.d.ts +191 -0
- package/dist/realtime/meeting-controls-state.d.ts.map +1 -0
- package/dist/realtime/meeting-controls-state.js +219 -0
- package/dist/realtime/meeting-controls-state.js.map +1 -0
- package/dist/realtime/realtime-channel-server-host.d.ts +166 -0
- package/dist/realtime/realtime-channel-server-host.d.ts.map +1 -0
- package/dist/realtime/realtime-channel-server-host.js +378 -0
- package/dist/realtime/realtime-channel-server-host.js.map +1 -0
- package/dist/realtime/realtime-client-session-service.d.ts +884 -0
- package/dist/realtime/realtime-client-session-service.d.ts.map +1 -0
- package/dist/realtime/realtime-client-session-service.js +1401 -0
- package/dist/realtime/realtime-client-session-service.js.map +1 -0
- package/dist/realtime/realtime-coagent-config.d.ts +202 -0
- package/dist/realtime/realtime-coagent-config.d.ts.map +1 -0
- package/dist/realtime/realtime-coagent-config.js +334 -0
- package/dist/realtime/realtime-coagent-config.js.map +1 -0
- package/dist/realtime/realtime-narration.d.ts +67 -0
- package/dist/realtime/realtime-narration.d.ts.map +1 -0
- package/dist/realtime/realtime-narration.js +127 -0
- package/dist/realtime/realtime-narration.js.map +1 -0
- package/dist/realtime/realtime-session-runner.d.ts +383 -0
- package/dist/realtime/realtime-session-runner.d.ts.map +1 -0
- package/dist/realtime/realtime-session-runner.js +532 -0
- package/dist/realtime/realtime-session-runner.js.map +1 -0
- package/dist/realtime/realtime-tool-broker.d.ts +279 -0
- package/dist/realtime/realtime-tool-broker.d.ts.map +1 -0
- package/dist/realtime/realtime-tool-broker.js +184 -0
- package/dist/realtime/realtime-tool-broker.js.map +1 -0
- package/dist/realtime/whiteboard-channel-server.d.ts +50 -0
- package/dist/realtime/whiteboard-channel-server.d.ts.map +1 -0
- package/dist/realtime/whiteboard-channel-server.js +85 -0
- package/dist/realtime/whiteboard-channel-server.js.map +1 -0
- package/package.json +17 -17
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Process-wide host for SERVER-SIDE interactive-channel plugins — the resolution +
|
|
3
|
+
* per-session lifecycle half of the `BaseRealtimeChannelServer` contract (`@memberjunction/ai`).
|
|
4
|
+
*
|
|
5
|
+
* The base class is deliberately MJ-core-free; this host is the DB-aware piece: it reads the ACTIVE
|
|
6
|
+
* `MJ: AI Agent Channels` registry rows at session start, resolves each row's `ServerPluginClass`
|
|
7
|
+
* through the MJ ClassFactory into a fresh per-session plugin instance (the exact mirror of how
|
|
8
|
+
* `VoiceSessionService.loadActiveChannels` resolves `ClientPluginClass` in the browser), and routes
|
|
9
|
+
* the session lifecycle events into those instances:
|
|
10
|
+
*
|
|
11
|
+
* - **session started** — `SessionManager.CreateSession` (MJServer) → {@link RealtimeChannelServerHost.OnSessionStarted}
|
|
12
|
+
* - **channel state saved** — the `SaveSessionChannelState` mutation → {@link RealtimeChannelServerHost.OnChannelStateSave}
|
|
13
|
+
* (pre-persistence; the plugin may normalize the payload)
|
|
14
|
+
* - **session closed** — `SessionManager.CloseSession` (every close path: explicit, janitor,
|
|
15
|
+
* shutdown drain, error teardown) → {@link RealtimeChannelServerHost.OnSessionClosed}
|
|
16
|
+
*
|
|
17
|
+
* **Failure posture:** nothing in here ever throws to a caller. Registry failures degrade to "no
|
|
18
|
+
* server plugins"; a throwing plugin hook is logged and skipped; an unknown session is a no-op.
|
|
19
|
+
* Channel plugins can never break a live call or block persistence.
|
|
20
|
+
*
|
|
21
|
+
* **`BaseSingleton` (per MJ rule #7):** plugin instances are in-memory, per-process state shared
|
|
22
|
+
* between the resolver layer and `SessionManager`/`SessionJanitor` call paths — a plain instance
|
|
23
|
+
* field on either would split the state, so the host is a Global-Object-Store-backed singleton.
|
|
24
|
+
*
|
|
25
|
+
* @module @memberjunction/ai-agents
|
|
26
|
+
* @author MemberJunction.com
|
|
27
|
+
*/
|
|
28
|
+
import { BaseRealtimeChannelServer, } from '@memberjunction/ai';
|
|
29
|
+
import { BaseSingleton, MJGlobal } from '@memberjunction/global';
|
|
30
|
+
import { LogError, LogStatus } from '@memberjunction/core';
|
|
31
|
+
import { AIEngineBase } from '@memberjunction/ai-engine-base';
|
|
32
|
+
/** Entity name — kept in sync with the session machinery's `MJ:`-prefix convention. */
|
|
33
|
+
const CHANNEL_ENTITY = 'MJ: AI Agent Channels';
|
|
34
|
+
/**
|
|
35
|
+
* Default post-close linger (ms) before plugin instances are disposed. The client's channel-state
|
|
36
|
+
* pipeline debounces saves (~3s) and flushes once at teardown — that final flush legitimately lands
|
|
37
|
+
* AFTER `CloseAgentSession`, and it is the state of record a future resume restores, so it must
|
|
38
|
+
* still route through the plugin's normalization hook. 15s comfortably covers the flush window.
|
|
39
|
+
*/
|
|
40
|
+
const DEFAULT_DISPOSE_LINGER_MS = 15_000;
|
|
41
|
+
/**
|
|
42
|
+
* Singleton host that owns every live `BaseRealtimeChannelServer` instance in this process —
|
|
43
|
+
* one instance per (session × active channel row with a resolvable `ServerPluginClass`).
|
|
44
|
+
*
|
|
45
|
+
* ### Lifecycle routing (who calls what)
|
|
46
|
+
* | Host method | Invoked from | Plugin hook(s) fired |
|
|
47
|
+
* |---|---|---|
|
|
48
|
+
* | {@link OnSessionStarted} | `SessionManager.CreateSession` after the session row persists | `Initialize(ctx)` then `OnSessionStarted()` per resolved plugin |
|
|
49
|
+
* | {@link OnChannelStateSave} | `SaveSessionChannelState` resolver, pre-persistence | the matching channel's `OnChannelStateSave(stateJson)` |
|
|
50
|
+
* | {@link OnSessionClosed} | `SessionManager.CloseSession` (all close provenances) | `OnSessionClosed(reason)` per plugin, then deferred `Dispose()` |
|
|
51
|
+
*
|
|
52
|
+
* ### Janitor / cross-host notes
|
|
53
|
+
* - The janitor's sweeps run on EVERY instance and funnel through `SessionManager.CloseSession`,
|
|
54
|
+
* so a session whose plugins live in this process is eventually cleaned up here even when its
|
|
55
|
+
* client vanished — including when ANOTHER instance won the close race (`CloseSession`'s
|
|
56
|
+
* already-closed path still notifies this host, and an unknown session is a no-op).
|
|
57
|
+
* - A close for a session minted on a different host/boot simply finds no local instances.
|
|
58
|
+
*/
|
|
59
|
+
export class RealtimeChannelServerHost extends BaseSingleton {
|
|
60
|
+
constructor() {
|
|
61
|
+
super();
|
|
62
|
+
/** session id (lowercased) → that session's plugin instances + disposal state. */
|
|
63
|
+
this.sessions = new Map();
|
|
64
|
+
/**
|
|
65
|
+
* Post-close linger (ms) before {@link BaseRealtimeChannelServer.Dispose} runs, so the client's
|
|
66
|
+
* legitimate post-close state flush still routes through the plugin. `0` disposes immediately
|
|
67
|
+
* (used by tests). Mutable on purpose — a deployment-level knob, not per-session state.
|
|
68
|
+
*/
|
|
69
|
+
this.DisposeLingerMs = DEFAULT_DISPOSE_LINGER_MS;
|
|
70
|
+
}
|
|
71
|
+
/** Process-wide singleton accessor (Global Object Store backed via {@link BaseSingleton}). */
|
|
72
|
+
static get Instance() {
|
|
73
|
+
return super.getInstance();
|
|
74
|
+
}
|
|
75
|
+
/** Number of sessions currently holding live (or close-lingering) plugin instances. */
|
|
76
|
+
get ActiveSessionCount() {
|
|
77
|
+
return this.sessions.size;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Returns the live plugin instance for `(agentSessionID, channelName)`, or `null` when none
|
|
81
|
+
* exists (no plugin resolved for that channel, unknown session, or already disposed).
|
|
82
|
+
* Lookup is case/whitespace-insensitive on both keys.
|
|
83
|
+
*/
|
|
84
|
+
GetSessionPlugin(agentSessionID, channelName) {
|
|
85
|
+
const entry = this.sessions.get(this.sessionKey(agentSessionID));
|
|
86
|
+
return entry?.plugins.get(this.channelKey(channelName)) ?? null;
|
|
87
|
+
}
|
|
88
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
89
|
+
// Server-executed channel tools — the deferred "channel tool contribution feeding
|
|
90
|
+
// RealtimeSessionRunner.ExtraTools" the realtime guide flagged. The host aggregates every
|
|
91
|
+
// live plugin's GetServerToolDefinitions and routes each prefixed tool call back to the owner.
|
|
92
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
93
|
+
/**
|
|
94
|
+
* Aggregates the **server-executed** tool definitions every live plugin of a session contributes
|
|
95
|
+
* (each plugin's {@link BaseRealtimeChannelServer.GetServerToolDefinitions}, possibly
|
|
96
|
+
* runtime-computed) into one flat set — the per-session server-channel tool vocabulary fed into
|
|
97
|
+
* `RealtimeSessionRunner.ServerChannelTools`.
|
|
98
|
+
*
|
|
99
|
+
* Tolerant by contract: a plugin whose `GetServerToolDefinitions` throws is logged and skipped,
|
|
100
|
+
* never breaking the assembly. An unknown session returns `[]`.
|
|
101
|
+
*
|
|
102
|
+
* @param agentSessionID The session whose channels' server tools to collect.
|
|
103
|
+
* @returns The aggregated tool definitions across all of the session's live channel plugins.
|
|
104
|
+
*/
|
|
105
|
+
GetSessionServerTools(agentSessionID) {
|
|
106
|
+
const entry = this.sessions.get(this.sessionKey(agentSessionID));
|
|
107
|
+
if (!entry) {
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
const tools = [];
|
|
111
|
+
for (const [name, plugin] of entry.plugins) {
|
|
112
|
+
try {
|
|
113
|
+
const channelTools = plugin.GetServerToolDefinitions();
|
|
114
|
+
if (Array.isArray(channelTools)) {
|
|
115
|
+
tools.push(...channelTools);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
LogError(`[RealtimeChannelServerHost] GetServerToolDefinitions failed for session ${agentSessionID} / ` +
|
|
120
|
+
`channel '${name}' — skipping its tools: ${this.message(error)}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return tools;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Routes ONE server-executed tool call to the session's plugin that owns it, matched by the
|
|
127
|
+
* plugin's {@link BaseRealtimeChannelServer.ToolNamePrefix}, and returns its result. Resolution
|
|
128
|
+
* picks the plugin whose (non-empty) prefix the tool name starts with — the longest matching
|
|
129
|
+
* prefix wins, so overlapping prefixes resolve deterministically.
|
|
130
|
+
*
|
|
131
|
+
* Never throws: an unowned tool (no channel prefix matches), an unknown session, or a plugin that
|
|
132
|
+
* throws all resolve to a structured `{ Success: false }` result so the model always receives a
|
|
133
|
+
* consistent `tool_response`.
|
|
134
|
+
*
|
|
135
|
+
* @param agentSessionID The session the tool call belongs to.
|
|
136
|
+
* @param toolName The full tool name the model invoked.
|
|
137
|
+
* @param argsJson The raw arguments JSON the model emitted.
|
|
138
|
+
* @returns The execution result (or a structured error).
|
|
139
|
+
*/
|
|
140
|
+
async ExecuteSessionServerTool(agentSessionID, toolName, argsJson) {
|
|
141
|
+
const plugin = this.resolveToolOwner(agentSessionID, toolName);
|
|
142
|
+
if (!plugin) {
|
|
143
|
+
return { Success: false, Output: `No active channel owns the server tool '${toolName}' for session ${agentSessionID}.` };
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
return await plugin.ExecuteServerTool(toolName, argsJson);
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
LogError(`[RealtimeChannelServerHost] ExecuteServerTool failed for session ${agentSessionID} / tool '${toolName}': ${this.message(error)}`);
|
|
150
|
+
return { Success: false, Output: `Server tool '${toolName}' failed: ${this.message(error)}` };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/** Returns `true` when the given tool name is owned by a live channel of the session. */
|
|
154
|
+
OwnsServerTool(agentSessionID, toolName) {
|
|
155
|
+
return this.resolveToolOwner(agentSessionID, toolName) !== null;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Finds the live plugin of a session whose {@link BaseRealtimeChannelServer.ToolNamePrefix} the
|
|
159
|
+
* tool name starts with. The longest matching prefix wins so overlapping prefixes
|
|
160
|
+
* (`'Meeting_'` vs `'MeetingControls_'`) resolve to the most specific channel.
|
|
161
|
+
*/
|
|
162
|
+
resolveToolOwner(agentSessionID, toolName) {
|
|
163
|
+
const entry = this.sessions.get(this.sessionKey(agentSessionID));
|
|
164
|
+
if (!entry) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
let best = null;
|
|
168
|
+
let bestPrefixLength = -1;
|
|
169
|
+
for (const plugin of entry.plugins.values()) {
|
|
170
|
+
const prefix = plugin.ToolNamePrefix;
|
|
171
|
+
if (prefix && toolName.startsWith(prefix) && prefix.length > bestPrefixLength) {
|
|
172
|
+
best = plugin;
|
|
173
|
+
bestPrefixLength = prefix.length;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return best;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Session-started entry point. Loads the ACTIVE channel registry rows, resolves each row's
|
|
180
|
+
* `ServerPluginClass` through the ClassFactory into ONE fresh instance for this session, and
|
|
181
|
+
* brackets each with `Initialize(ctx)` + `OnSessionStarted()`. Rows with no registered plugin
|
|
182
|
+
* are skipped with a log; a plugin whose start bracket throws is dropped (logged) without
|
|
183
|
+
* touching its siblings. Never throws — any registry/host failure degrades to "no plugins".
|
|
184
|
+
*
|
|
185
|
+
* Idempotent per session: a second start notification for a session that already holds
|
|
186
|
+
* instances is ignored (logged) rather than double-initializing.
|
|
187
|
+
*/
|
|
188
|
+
async OnSessionStarted(ctx, contextUser, provider) {
|
|
189
|
+
try {
|
|
190
|
+
const key = this.sessionKey(ctx.AgentSessionID);
|
|
191
|
+
if (this.sessions.has(key)) {
|
|
192
|
+
LogStatus(`[RealtimeChannelServerHost] Session ${ctx.AgentSessionID} already has server channel plugins — ignoring duplicate start.`);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const rows = await this.fetchChannelDefinitions(contextUser, provider);
|
|
196
|
+
const plugins = await this.instantiateSessionPlugins(rows, ctx);
|
|
197
|
+
if (plugins.size > 0) {
|
|
198
|
+
this.sessions.set(key, { plugins, disposeTimer: null });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
LogError(`[RealtimeChannelServerHost] OnSessionStarted failed for session ${ctx.AgentSessionID}: ${this.message(error)}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Channel-state-save entry point, invoked PRE-persistence. Routes the payload to the session's
|
|
207
|
+
* matching plugin and returns the string to persist:
|
|
208
|
+
* - no live plugin for the channel (or unknown session) → the original `stateJson`;
|
|
209
|
+
* - the plugin returns a non-empty replacement string → that replacement;
|
|
210
|
+
* - the plugin returns `null`/empty/non-string, or throws (logged) → the original `stateJson`.
|
|
211
|
+
*
|
|
212
|
+
* A plugin can therefore only ever *transform* a save — it can never lose or block one.
|
|
213
|
+
*/
|
|
214
|
+
async OnChannelStateSave(agentSessionID, channelName, stateJson) {
|
|
215
|
+
const plugin = this.GetSessionPlugin(agentSessionID, channelName);
|
|
216
|
+
if (!plugin) {
|
|
217
|
+
return stateJson;
|
|
218
|
+
}
|
|
219
|
+
try {
|
|
220
|
+
const replacement = await plugin.OnChannelStateSave(stateJson);
|
|
221
|
+
if (typeof replacement === 'string' && replacement.length > 0) {
|
|
222
|
+
return replacement;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
LogError(`[RealtimeChannelServerHost] OnChannelStateSave plugin failure for session ${agentSessionID} / ` +
|
|
227
|
+
`channel '${channelName}' — persisting the original state: ${this.message(error)}`);
|
|
228
|
+
}
|
|
229
|
+
return stateJson;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Session-closed entry point — invoked from EVERY close provenance (explicit, janitor sweeps,
|
|
233
|
+
* shutdown drain, error teardown; `SessionManager.CloseSession` is the single funnel). Fires
|
|
234
|
+
* each plugin's `OnSessionClosed(reason)` (failures logged, siblings unaffected), then defers
|
|
235
|
+
* `Dispose()` by {@link DisposeLingerMs} so the client's post-close state flush still routes
|
|
236
|
+
* through the plugins. Idempotent: an unknown session, or one whose close hooks already fired
|
|
237
|
+
* (disposal pending), is a no-op. Never throws.
|
|
238
|
+
*/
|
|
239
|
+
async OnSessionClosed(agentSessionID, closeReason) {
|
|
240
|
+
const key = this.sessionKey(agentSessionID);
|
|
241
|
+
const entry = this.sessions.get(key);
|
|
242
|
+
if (!entry || entry.disposeTimer) {
|
|
243
|
+
return; // unknown session, or close hooks already fired and disposal is pending
|
|
244
|
+
}
|
|
245
|
+
for (const [name, plugin] of entry.plugins) {
|
|
246
|
+
try {
|
|
247
|
+
await plugin.OnSessionClosed(closeReason);
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
LogError(`[RealtimeChannelServerHost] OnSessionClosed plugin failure for session ${agentSessionID} / ` +
|
|
251
|
+
`channel '${name}': ${this.message(error)}`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
this.scheduleDisposal(key, entry);
|
|
255
|
+
}
|
|
256
|
+
// ----- internals -------------------------------------------------------------------------
|
|
257
|
+
/** Disposes the session's plugins now, or after the linger window when one is configured. */
|
|
258
|
+
scheduleDisposal(key, entry) {
|
|
259
|
+
if (this.DisposeLingerMs <= 0) {
|
|
260
|
+
this.disposeSession(key);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
entry.disposeTimer = setTimeout(() => this.disposeSession(key), this.DisposeLingerMs);
|
|
264
|
+
// Never keep the process alive for a disposal sweep.
|
|
265
|
+
entry.disposeTimer.unref?.();
|
|
266
|
+
}
|
|
267
|
+
/** Disposes and forgets every plugin instance of one session (failures logged per plugin). */
|
|
268
|
+
disposeSession(key) {
|
|
269
|
+
const entry = this.sessions.get(key);
|
|
270
|
+
if (!entry) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
this.sessions.delete(key);
|
|
274
|
+
for (const [name, plugin] of entry.plugins) {
|
|
275
|
+
try {
|
|
276
|
+
plugin.Dispose();
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
LogError(`[RealtimeChannelServerHost] Dispose failure for channel '${name}': ${this.message(error)}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Reads the ACTIVE `MJ: AI Agent Channels` rows from {@link AIEngineBase}'s cached
|
|
285
|
+
* `AgentChannels` (provider-scoped engine instance, lazy `Config` — no per-session RunView;
|
|
286
|
+
* the engine's BaseEntity-event reactivity keeps the registry fresh) — the server-side mirror
|
|
287
|
+
* of the client's `fetchChannelDefinitions`. Failures are logged and degrade to an empty
|
|
288
|
+
* list; channel availability must never block a session.
|
|
289
|
+
*/
|
|
290
|
+
async fetchChannelDefinitions(contextUser, provider) {
|
|
291
|
+
try {
|
|
292
|
+
const engine = AIEngineBase.GetProviderInstance(provider, AIEngineBase);
|
|
293
|
+
await engine.Config(false, contextUser, provider);
|
|
294
|
+
return (engine.AgentChannels ?? [])
|
|
295
|
+
.filter((c) => c.IsActive)
|
|
296
|
+
.map((c) => ({
|
|
297
|
+
ID: c.ID,
|
|
298
|
+
Name: c.Name,
|
|
299
|
+
ServerPluginClass: c.ServerPluginClass ?? null,
|
|
300
|
+
}));
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
LogError(`[RealtimeChannelServerHost] Failed to load the channel registry from ${CHANNEL_ENTITY}: ${this.message(error)}`);
|
|
304
|
+
return [];
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
/** Resolves + start-brackets one fresh plugin instance per resolvable registry row. */
|
|
308
|
+
async instantiateSessionPlugins(rows, ctx) {
|
|
309
|
+
const plugins = new Map();
|
|
310
|
+
for (const row of rows) {
|
|
311
|
+
const plugin = this.resolveChannelPlugin(row);
|
|
312
|
+
if (!plugin) {
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
try {
|
|
316
|
+
plugin.Initialize(ctx);
|
|
317
|
+
await plugin.OnSessionStarted();
|
|
318
|
+
plugins.set(this.channelKey(row.Name), plugin);
|
|
319
|
+
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
LogError(`[RealtimeChannelServerHost] Server plugin for channel '${row.Name}' failed during session start ` +
|
|
322
|
+
`— dropping it for session ${ctx.AgentSessionID}: ${this.message(error)}`);
|
|
323
|
+
this.safeDispose(plugin, row.Name);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return plugins;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Resolves one registry row's `ServerPluginClass` via the ClassFactory (registration checked
|
|
330
|
+
* first, exactly like the client half and the realtime model drivers) and instantiates a fresh
|
|
331
|
+
* per-session plugin. Returns `null` (logged) when the key is blank, unregistered (e.g. its
|
|
332
|
+
* `Load...()` function was never called server-side), or fails to instantiate.
|
|
333
|
+
*/
|
|
334
|
+
resolveChannelPlugin(row) {
|
|
335
|
+
const key = row.ServerPluginClass?.trim();
|
|
336
|
+
if (!key) {
|
|
337
|
+
LogStatus(`[RealtimeChannelServerHost] Channel '${row.Name}' has no ServerPluginClass — skipping.`);
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
const registration = MJGlobal.Instance.ClassFactory.GetRegistration(BaseRealtimeChannelServer, key);
|
|
341
|
+
if (!registration) {
|
|
342
|
+
LogStatus(`[RealtimeChannelServerHost] No server plugin registered for channel '${row.Name}' (key '${key}') — skipping.`);
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
const plugin = MJGlobal.Instance.ClassFactory.CreateInstance(BaseRealtimeChannelServer, key);
|
|
346
|
+
if (!plugin) {
|
|
347
|
+
LogError(`[RealtimeChannelServerHost] Failed to instantiate server plugin for channel '${row.Name}' (key '${key}').`);
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
if (this.channelKey(plugin.ChannelName) !== this.channelKey(row.Name)) {
|
|
351
|
+
LogError(`[RealtimeChannelServerHost] Server plugin '${key}' reports ChannelName '${plugin.ChannelName}' but is ` +
|
|
352
|
+
`registered on channel row '${row.Name}' — routing by the row name; fix the plugin/registry mismatch.`);
|
|
353
|
+
}
|
|
354
|
+
return plugin;
|
|
355
|
+
}
|
|
356
|
+
/** Disposes a plugin defensively, swallowing (logging) any error. */
|
|
357
|
+
safeDispose(plugin, channelName) {
|
|
358
|
+
try {
|
|
359
|
+
plugin.Dispose();
|
|
360
|
+
}
|
|
361
|
+
catch (error) {
|
|
362
|
+
LogError(`[RealtimeChannelServerHost] Dispose failure for channel '${channelName}': ${this.message(error)}`);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
/** Canonical map key for a session id (UUID case differs across DB platforms). */
|
|
366
|
+
sessionKey(agentSessionID) {
|
|
367
|
+
return agentSessionID.trim().toLowerCase();
|
|
368
|
+
}
|
|
369
|
+
/** Canonical map key for a channel name. */
|
|
370
|
+
channelKey(channelName) {
|
|
371
|
+
return channelName.trim().toLowerCase();
|
|
372
|
+
}
|
|
373
|
+
/** Normalizes an unknown thrown value into a loggable message. */
|
|
374
|
+
message(error) {
|
|
375
|
+
return error instanceof Error ? error.message : String(error);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
//# sourceMappingURL=realtime-channel-server-host.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"realtime-channel-server-host.js","sourceRoot":"","sources":["../../src/realtime/realtime-channel-server-host.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EACH,yBAAyB,GAK5B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAA+B,QAAQ,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACxF,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAE9D,uFAAuF;AACvF,MAAM,cAAc,GAAG,uBAAuB,CAAC;AAiB/C;;;;;GAKG;AACH,MAAM,yBAAyB,GAAG,MAAM,CAAC;AAEzC;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,OAAO,yBAA0B,SAAQ,aAAwC;IAWnF;QACI,KAAK,EAAE,CAAC;QAXZ,kFAAkF;QAC1E,aAAQ,GAAG,IAAI,GAAG,EAA8B,CAAC;QAEzD;;;;WAIG;QACI,oBAAe,GAAG,yBAAyB,CAAC;IAInD,CAAC;IAED,8FAA8F;IACvF,MAAM,KAAK,QAAQ;QACtB,OAAO,KAAK,CAAC,WAAW,EAA6B,CAAC;IAC1D,CAAC;IAED,uFAAuF;IACvF,IAAW,kBAAkB;QACzB,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;IAC9B,CAAC;IAED;;;;OAIG;IACI,gBAAgB,CAAC,cAAsB,EAAE,WAAmB;QAC/D,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC,CAAC;QACjE,OAAO,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,IAAI,IAAI,CAAC;IACpE,CAAC;IAED,iFAAiF;IACjF,kFAAkF;IAClF,0FAA0F;IAC1F,+FAA+F;IAC/F,iFAAiF;IAEjF;;;;;;;;;;;OAWG;IACI,qBAAqB,CAAC,cAAsB;QAC/C,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC,CAAC;QACjE,IAAI,CAAC,KAAK,EAAE,CAAC;YACT,OAAO,EAAE,CAAC;QACd,CAAC;QACD,MAAM,KAAK,GAA6B,EAAE,CAAC;QAC3C,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;YACzC,IAAI,CAAC;gBACD,MAAM,YAAY,GAAG,MAAM,CAAC,wBAAwB,EAAE,CAAC;gBACvD,IAAI,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;oBAC9B,KAAK,CAAC,IAAI,CAAC,GAAG,YAAY,CAAC,CAAC;gBAChC,CAAC;YACL,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACb,QAAQ,CACJ,2EAA2E,cAAc,KAAK;oBAC1F,YAAY,IAAI,2BAA2B,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CACvE,CAAC;YACN,CAAC;QACL,CAAC;QACD,OAAO,KAAK,CAAC;IACjB,CAAC;IAED;;;;;;;;;;;;;;OAcG;IACI,KAAK,CAAC,wBAAwB,CACjC,cAAsB,EACtB,QAAgB,EAChB,QAAgB;QAEhB,MAAM,MAAM,GAAG,IAAI,CAAC,gBAAgB,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC;QAC/D,IAAI,CAAC,MAAM,EAAE,CAAC;YACV,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,2CAA2C,QAAQ,iBAAiB,cAAc,GAAG,EAAE,CAAC;QAC7H,CAAC;QACD,IAAI,CAAC;YACD,OAAO,MAAM,MAAM,CAAC,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAC9D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,QAAQ,CACJ,oEAAoE,cAAc,YAAY,QAAQ,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CACpI,CAAC;YACF,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,gBAAgB,QAAQ,aAAa,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;QAClG,CAAC;IACL,CAAC;IAED,yFAAyF;IAClF,cAAc,CAAC,cAAsB,EAAE,QAAgB;QAC1D,OAAO,IAAI,CAAC,gBAAgB,CAAC,cAAc,EAAE,QAAQ,CAAC,KAAK,IAAI,CAAC;IACpE,CAAC;IAED;;;;OAIG;IACK,gBAAgB,CAAC,cAAsB,EAAE,QAAgB;QAC7D,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC,CAAC;QACjE,IAAI,CAAC,KAAK,EAAE,CAAC;YACT,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,IAAI,IAAI,GAAqC,IAAI,CAAC;QAClD,IAAI,gBAAgB,GAAG,CAAC,CAAC,CAAC;QAC1B,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;YAC1C,MAAM,MAAM,GAAG,MAAM,CAAC,cAAc,CAAC;YACrC,IAAI,MAAM,IAAI,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,GAAG,gBAAgB,EAAE,CAAC;gBAC5E,IAAI,GAAG,MAAM,CAAC;gBACd,gBAAgB,GAAG,MAAM,CAAC,MAAM,CAAC;YACrC,CAAC;QACL,CAAC;QACD,OAAO,IAAI,CAAC;IAChB,CAAC;IAED;;;;;;;;;OASG;IACI,KAAK,CAAC,gBAAgB,CACzB,GAAiC,EACjC,WAAqB,EACrB,QAA2B;QAE3B,IAAI,CAAC;YACD,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;YAChD,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBACzB,SAAS,CAAC,uCAAuC,GAAG,CAAC,cAAc,iEAAiE,CAAC,CAAC;gBACtI,OAAO;YACX,CAAC;YACD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,uBAAuB,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;YACvE,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,yBAAyB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YAChE,IAAI,OAAO,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;gBACnB,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;YAC5D,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,QAAQ,CAAC,mEAAmE,GAAG,CAAC,cAAc,KAAK,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC9H,CAAC;IACL,CAAC;IAED;;;;;;;;OAQG;IACI,KAAK,CAAC,kBAAkB,CAAC,cAAsB,EAAE,WAAmB,EAAE,SAAiB;QAC1F,MAAM,MAAM,GAAG,IAAI,CAAC,gBAAgB,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC;QAClE,IAAI,CAAC,MAAM,EAAE,CAAC;YACV,OAAO,SAAS,CAAC;QACrB,CAAC;QACD,IAAI,CAAC;YACD,MAAM,WAAW,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC;YAC/D,IAAI,OAAO,WAAW,KAAK,QAAQ,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC5D,OAAO,WAAW,CAAC;YACvB,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,QAAQ,CACJ,6EAA6E,cAAc,KAAK;gBAC5F,YAAY,WAAW,sCAAsC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CACzF,CAAC;QACN,CAAC;QACD,OAAO,SAAS,CAAC;IACrB,CAAC;IAED;;;;;;;OAOG;IACI,KAAK,CAAC,eAAe,CAAC,cAAsB,EAAE,WAA8C;QAC/F,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;QAC5C,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC;YAC/B,OAAO,CAAC,wEAAwE;QACpF,CAAC;QACD,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;YACzC,IAAI,CAAC;gBACD,MAAM,MAAM,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC;YAC9C,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACb,QAAQ,CACJ,0EAA0E,cAAc,KAAK;oBACzF,YAAY,IAAI,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAClD,CAAC;YACN,CAAC;QACL,CAAC;QACD,IAAI,CAAC,gBAAgB,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACtC,CAAC;IAED,4FAA4F;IAE5F,6FAA6F;IACrF,gBAAgB,CAAC,GAAW,EAAE,KAAyB;QAC3D,IAAI,IAAI,CAAC,eAAe,IAAI,CAAC,EAAE,CAAC;YAC5B,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;YACzB,OAAO;QACX,CAAC;QACD,KAAK,CAAC,YAAY,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC;QACtF,qDAAqD;QACrD,KAAK,CAAC,YAAY,CAAC,KAAK,EAAE,EAAE,CAAC;IACjC,CAAC;IAED,8FAA8F;IACtF,cAAc,CAAC,GAAW;QAC9B,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,CAAC,KAAK,EAAE,CAAC;YACT,OAAO;QACX,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC1B,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;YACzC,IAAI,CAAC;gBACD,MAAM,CAAC,OAAO,EAAE,CAAC;YACrB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACb,QAAQ,CAAC,4DAA4D,IAAI,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAC1G,CAAC;QACL,CAAC;IACL,CAAC;IAED;;;;;;OAMG;IACK,KAAK,CAAC,uBAAuB,CACjC,WAAqB,EACrB,QAA2B;QAE3B,IAAI,CAAC;YACD,MAAM,MAAM,GAAG,YAAY,CAAC,mBAAmB,CAAe,QAAQ,EAAE,YAAY,CAAiB,CAAC;YACtG,MAAM,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAC;YAClD,OAAO,CAAC,MAAM,CAAC,aAAa,IAAI,EAAE,CAAC;iBAC9B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC;iBACzB,GAAG,CAAqC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBAC7C,EAAE,EAAE,CAAC,CAAC,EAAE;gBACR,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,iBAAiB,EAAE,CAAC,CAAC,iBAAiB,IAAI,IAAI;aACjD,CAAC,CAAC,CAAC;QACZ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,QAAQ,CAAC,wEAAwE,cAAc,KAAK,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAC3H,OAAO,EAAE,CAAC;QACd,CAAC;IACL,CAAC;IAED,uFAAuF;IAC/E,KAAK,CAAC,yBAAyB,CACnC,IAA0C,EAC1C,GAAiC;QAEjC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAqC,CAAC;QAC7D,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACrB,MAAM,MAAM,GAAG,IAAI,CAAC,oBAAoB,CAAC,GAAG,CAAC,CAAC;YAC9C,IAAI,CAAC,MAAM,EAAE,CAAC;gBACV,SAAS;YACb,CAAC;YACD,IAAI,CAAC;gBACD,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;gBACvB,MAAM,MAAM,CAAC,gBAAgB,EAAE,CAAC;gBAChC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;YACnD,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACb,QAAQ,CACJ,0DAA0D,GAAG,CAAC,IAAI,gCAAgC;oBAC9F,6BAA6B,GAAG,CAAC,cAAc,KAAK,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAChF,CAAC;gBACF,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;YACvC,CAAC;QACL,CAAC;QACD,OAAO,OAAO,CAAC;IACnB,CAAC;IAED;;;;;OAKG;IACK,oBAAoB,CAAC,GAAuC;QAChE,MAAM,GAAG,GAAG,GAAG,CAAC,iBAAiB,EAAE,IAAI,EAAE,CAAC;QAC1C,IAAI,CAAC,GAAG,EAAE,CAAC;YACP,SAAS,CAAC,wCAAwC,GAAG,CAAC,IAAI,wCAAwC,CAAC,CAAC;YACpG,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,MAAM,YAAY,GAAG,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC,eAAe,CAAC,yBAAyB,EAAE,GAAG,CAAC,CAAC;QACpG,IAAI,CAAC,YAAY,EAAE,CAAC;YAChB,SAAS,CAAC,wEAAwE,GAAG,CAAC,IAAI,WAAW,GAAG,gBAAgB,CAAC,CAAC;YAC1H,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,MAAM,MAAM,GAAG,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC,cAAc,CAA4B,yBAAyB,EAAE,GAAG,CAAC,CAAC;QACxH,IAAI,CAAC,MAAM,EAAE,CAAC;YACV,QAAQ,CAAC,gFAAgF,GAAG,CAAC,IAAI,WAAW,GAAG,KAAK,CAAC,CAAC;YACtH,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACpE,QAAQ,CACJ,8CAA8C,GAAG,0BAA0B,MAAM,CAAC,WAAW,WAAW;gBACpG,8BAA8B,GAAG,CAAC,IAAI,gEAAgE,CAC7G,CAAC;QACN,CAAC;QACD,OAAO,MAAM,CAAC;IAClB,CAAC;IAED,qEAAqE;IAC7D,WAAW,CAAC,MAAiC,EAAE,WAAmB;QACtE,IAAI,CAAC;YACD,MAAM,CAAC,OAAO,EAAE,CAAC;QACrB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,QAAQ,CAAC,4DAA4D,WAAW,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACjH,CAAC;IACL,CAAC;IAED,kFAAkF;IAC1E,UAAU,CAAC,cAAsB;QACrC,OAAO,cAAc,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC/C,CAAC;IAED,4CAA4C;IACpC,UAAU,CAAC,WAAmB;QAClC,OAAO,WAAW,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC5C,CAAC;IAED,kEAAkE;IAC1D,OAAO,CAAC,KAAc;QAC1B,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAClE,CAAC;CACJ"}
|