@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.
- package/README.md +53 -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 +386 -39
- package/dist/base-agent.d.ts.map +1 -1
- package/dist/base-agent.js +1121 -261
- package/dist/base-agent.js.map +1 -1
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -1
- package/dist/memory-manager-agent.d.ts +99 -4
- package/dist/memory-manager-agent.d.ts.map +1 -1
- package/dist/memory-manager-agent.js +349 -117
- package/dist/memory-manager-agent.js.map +1 -1
- package/dist/realtime/bridge-realtime-session-factory.d.ts +111 -0
- package/dist/realtime/bridge-realtime-session-factory.d.ts.map +1 -0
- package/dist/realtime/bridge-realtime-session-factory.js +163 -0
- package/dist/realtime/bridge-realtime-session-factory.js.map +1 -0
- package/dist/realtime/bridge-room-transcript-sink.d.ts +58 -0
- package/dist/realtime/bridge-room-transcript-sink.d.ts.map +1 -0
- package/dist/realtime/bridge-room-transcript-sink.js +127 -0
- package/dist/realtime/bridge-room-transcript-sink.js.map +1 -0
- 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 +1026 -0
- package/dist/realtime/realtime-client-session-service.d.ts.map +1 -0
- package/dist/realtime/realtime-client-session-service.js +1607 -0
- package/dist/realtime/realtime-client-session-service.js.map +1 -0
- package/dist/realtime/realtime-coagent-config.d.ts +258 -0
- package/dist/realtime/realtime-coagent-config.d.ts.map +1 -0
- package/dist/realtime/realtime-coagent-config.js +408 -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 +294 -0
- package/dist/realtime/realtime-tool-broker.d.ts.map +1 -0
- package/dist/realtime/realtime-tool-broker.js +206 -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,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview `CreateBridgeRealtimeSession` — the **provider-agnostic** server-side factory that turns an
|
|
3
|
+
* agent reference into a live {@link IRealtimeSession} for a Realtime Bridge. Given an agent id (or name)
|
|
4
|
+
* + a context user + a metadata provider, it:
|
|
5
|
+
* 1. resolves the `MJ: AI Agents` entity from the {@link AIEngine} cache,
|
|
6
|
+
* 2. instantiates the correct {@link BaseAgent} subclass via the `ClassFactory` (the same path
|
|
7
|
+
* `AgentRunner` uses), and
|
|
8
|
+
* 3. calls {@link BaseAgent.StartBridgeRealtimeSession} to open the raw model session.
|
|
9
|
+
*
|
|
10
|
+
* This is the seam every bridge needs — `LiveKitAgentRoomCoordinator.SetSessionFactory`, and the
|
|
11
|
+
* (forthcoming) Teams/Zoom harnesses, all bind THIS one function. It deliberately lives in
|
|
12
|
+
* `@memberjunction/ai-agents` (which owns the agent + realtime-model lifecycle) and depends on NO bridge
|
|
13
|
+
* package, so a bridge package never has to reach back into the agent runtime — the consumer binds the
|
|
14
|
+
* factory at startup instead.
|
|
15
|
+
*
|
|
16
|
+
* @module @memberjunction/ai-agents
|
|
17
|
+
* @author MemberJunction.com
|
|
18
|
+
*/
|
|
19
|
+
import { BaseRealtimeModel, GetAIAPIKey } from '@memberjunction/ai';
|
|
20
|
+
import { Metadata } from '@memberjunction/core';
|
|
21
|
+
import { MJGlobal, UUIDsEqual } from '@memberjunction/global';
|
|
22
|
+
import { AIEngine } from '@memberjunction/aiengine';
|
|
23
|
+
import { BaseAgent } from '../base-agent.js';
|
|
24
|
+
import { RealtimeClientSessionService } from './realtime-client-session-service.js';
|
|
25
|
+
/**
|
|
26
|
+
* Opens a raw {@link IRealtimeSession} for the agent named in `ctx`. Bind this onto a bridge's
|
|
27
|
+
* session-factory seam (e.g. `LiveKitAgentRoomCoordinator.Instance.SetSessionFactory(CreateBridgeRealtimeSession)`).
|
|
28
|
+
*
|
|
29
|
+
* @param ctx The bridge session context (agent id/name + user + provider).
|
|
30
|
+
* @returns The live realtime session to hand to `AIBridgeEngine.StartBridgeSession`.
|
|
31
|
+
* @throws When the agent can't be resolved, has no DriverClass, the driver can't be instantiated, or no
|
|
32
|
+
* usable Realtime model is configured (surfaced from {@link BaseAgent.StartBridgeRealtimeSession}).
|
|
33
|
+
*/
|
|
34
|
+
export async function CreateBridgeRealtimeSession(ctx) {
|
|
35
|
+
const provider = ctx.MetadataProvider ?? Metadata.Provider;
|
|
36
|
+
await AIEngine.Instance.Config(false, ctx.ContextUser, provider);
|
|
37
|
+
const agent = resolveAgentEntity(ctx);
|
|
38
|
+
if (!agent) {
|
|
39
|
+
throw new Error(`CreateBridgeRealtimeSession: no agent found for AgentID='${ctx.AgentID ?? ''}' / ` +
|
|
40
|
+
`AgentName='${ctx.AgentName ?? ''}'. Ensure the agent exists and the engine is configured.`);
|
|
41
|
+
}
|
|
42
|
+
// Instantiate the right BaseAgent subclass exactly as AgentRunner does (agent DriverClass, else its type's).
|
|
43
|
+
const agentType = AIEngine.Instance.AgentTypes.find((t) => UUIDsEqual(t.ID, agent.TypeID));
|
|
44
|
+
const driverClass = agent.DriverClass || agentType?.DriverClass;
|
|
45
|
+
if (!driverClass) {
|
|
46
|
+
throw new Error(`CreateBridgeRealtimeSession: agent '${agent.Name}' has no DriverClass (and its type none either).`);
|
|
47
|
+
}
|
|
48
|
+
const instance = MJGlobal.Instance.ClassFactory.CreateInstance(BaseAgent, driverClass);
|
|
49
|
+
if (!instance) {
|
|
50
|
+
throw new Error(`CreateBridgeRealtimeSession: ClassFactory could not create a BaseAgent for DriverClass '${driverClass}'.`);
|
|
51
|
+
}
|
|
52
|
+
return instance.StartBridgeRealtimeSession({
|
|
53
|
+
agent,
|
|
54
|
+
contextUser: ctx.ContextUser,
|
|
55
|
+
provider,
|
|
56
|
+
// A fresh bridge session starts with no prior turns; memory context degrades gracefully to empty.
|
|
57
|
+
conversationMessages: [],
|
|
58
|
+
// Realtime extras ride params.data: the TARGET agent the co-agent voices via `invoke-target-agent`
|
|
59
|
+
// (without it the co-agent stays idle), plus optional per-session dev overrides for the model/voice
|
|
60
|
+
// so two agents in the same room can sound distinct. Omitted keys are simply absent.
|
|
61
|
+
data: buildRealtimeData(ctx),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Builds the `params.data` bag from the bridge context — the realtime extras BaseAgent reads at session
|
|
66
|
+
* start. Returns `undefined` when nothing is set so the param stays cleanly absent.
|
|
67
|
+
*/
|
|
68
|
+
function buildRealtimeData(ctx) {
|
|
69
|
+
const data = {};
|
|
70
|
+
if (ctx.TargetAgentID) {
|
|
71
|
+
data.targetAgentID = ctx.TargetAgentID;
|
|
72
|
+
}
|
|
73
|
+
if (ctx.AgentSessionID && ctx.AgentSessionID.trim().length > 0) {
|
|
74
|
+
// Drives the co-agent observability run's session grouping (see WireBridgeRealtimeSession).
|
|
75
|
+
data.agentSessionId = ctx.AgentSessionID.trim();
|
|
76
|
+
}
|
|
77
|
+
if (ctx.RealtimeModelID && ctx.RealtimeModelID.trim().length > 0) {
|
|
78
|
+
data.realtimeModelID = ctx.RealtimeModelID.trim();
|
|
79
|
+
}
|
|
80
|
+
if (ctx.RealtimeVoice && ctx.RealtimeVoice.trim().length > 0) {
|
|
81
|
+
data.realtimeVoice = ctx.RealtimeVoice.trim();
|
|
82
|
+
}
|
|
83
|
+
if (ctx.MeetingMode === true) {
|
|
84
|
+
data.realtimeMeetingMode = true;
|
|
85
|
+
}
|
|
86
|
+
if (ctx.SelfNames && ctx.SelfNames.length > 0) {
|
|
87
|
+
data.realtimeSelfNames = ctx.SelfNames;
|
|
88
|
+
}
|
|
89
|
+
return Object.keys(data).length > 0 ? data : undefined;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The bridge-engine session-run finalizer (matches `BridgeSessionRunFinalizer` in
|
|
93
|
+
* `@memberjunction/ai-bridge-server`). Bind this onto `AIBridgeEngine.SetSessionRunFinalizer(...)` at
|
|
94
|
+
* startup so a bridge reaped WITHOUT a live session (a prior-boot orphan, a cross-host teardown) still
|
|
95
|
+
* finalizes its co-agent observability run — the only path where the `Close()`-wrapped finalizer can't run.
|
|
96
|
+
* Lives here (in ai-agents, which owns the realtime runtime) so the engine stays decoupled from it.
|
|
97
|
+
*
|
|
98
|
+
* @param agentSessionID The reaped session's id.
|
|
99
|
+
* @param success Whether to mark the run(s) `Completed` (true) or `Failed` (false).
|
|
100
|
+
* @param contextUser The user the writes run as (required — skipped without it).
|
|
101
|
+
* @param provider The metadata provider (required — skipped without it).
|
|
102
|
+
*/
|
|
103
|
+
export async function FinalizeBridgeCoAgentRuns(agentSessionID, success, contextUser, provider) {
|
|
104
|
+
if (!contextUser || !provider) {
|
|
105
|
+
return; // a server-side finalize needs a user + provider; a later reap with context catches it
|
|
106
|
+
}
|
|
107
|
+
await new RealtimeClientSessionService().FinalizeCoAgentRunsBySession(agentSessionID, success, contextUser, provider);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Enumerates the active Realtime models with each driver's supported voices — the source for the dev
|
|
111
|
+
* model/voice picker. Only models with an Active vendor + resolvable API key + ClassFactory driver are
|
|
112
|
+
* returned (a model you can't actually run isn't worth offering). Voices come from the driver
|
|
113
|
+
* ({@link BaseRealtimeModel.SupportedVoices}) — the near-term, driver-owned source of truth.
|
|
114
|
+
*
|
|
115
|
+
* @param contextUser The user the engine config runs as (server-side).
|
|
116
|
+
* @param provider The request-scoped metadata provider (multi-provider safe).
|
|
117
|
+
* @returns Active realtime models, each with its driver's voices.
|
|
118
|
+
*/
|
|
119
|
+
export async function GetRealtimeModelVoices(contextUser, provider) {
|
|
120
|
+
await AIEngine.Instance.Config(false, contextUser, provider);
|
|
121
|
+
const isRealtime = (t) => typeof t === 'string' && t.trim().toLowerCase() === 'realtime';
|
|
122
|
+
const models = AIEngine.Instance.Models
|
|
123
|
+
.filter((m) => m.IsActive && isRealtime(m.AIModelType))
|
|
124
|
+
.sort((a, b) => (b.PowerRank ?? 0) - (a.PowerRank ?? 0));
|
|
125
|
+
const out = [];
|
|
126
|
+
for (const model of models) {
|
|
127
|
+
const driverClass = resolveRealtimeDriverClass(model.ID);
|
|
128
|
+
if (!driverClass) {
|
|
129
|
+
continue; // no active vendor with a resolvable key — not runnable, so omit
|
|
130
|
+
}
|
|
131
|
+
const instance = MJGlobal.Instance.ClassFactory.CreateInstance(BaseRealtimeModel, driverClass, GetAIAPIKey(driverClass));
|
|
132
|
+
out.push({ ModelID: model.ID, ModelName: model.Name ?? '', Voices: instance?.SupportedVoices ?? [] });
|
|
133
|
+
}
|
|
134
|
+
return out;
|
|
135
|
+
}
|
|
136
|
+
/** The DriverClass of the highest-priority Active vendor (with a resolvable API key) for a model, or null. */
|
|
137
|
+
function resolveRealtimeDriverClass(modelID) {
|
|
138
|
+
const vendors = AIEngine.Instance.ModelVendors
|
|
139
|
+
.filter((mv) => UUIDsEqual(mv.ModelID, modelID) && mv.Status === 'Active' && mv.DriverClass != null)
|
|
140
|
+
.sort((a, b) => (b.Priority ?? 0) - (a.Priority ?? 0));
|
|
141
|
+
for (const v of vendors) {
|
|
142
|
+
if (GetAIAPIKey(v.DriverClass)) {
|
|
143
|
+
return v.DriverClass;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
/** Resolves the agent entity from the engine cache by id (preferred), then by case-insensitive name. */
|
|
149
|
+
function resolveAgentEntity(ctx) {
|
|
150
|
+
const agents = AIEngine.Instance.Agents;
|
|
151
|
+
if (ctx.AgentID) {
|
|
152
|
+
const byId = agents.find((a) => UUIDsEqual(a.ID, ctx.AgentID));
|
|
153
|
+
if (byId) {
|
|
154
|
+
return byId;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (ctx.AgentName) {
|
|
158
|
+
const wanted = ctx.AgentName.trim().toLowerCase();
|
|
159
|
+
return agents.find((a) => a.Name?.trim().toLowerCase() === wanted);
|
|
160
|
+
}
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=bridge-realtime-session-factory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge-realtime-session-factory.js","sourceRoot":"","sources":["../../src/realtime/bridge-realtime-session-factory.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview `CreateBridgeRoomTranscriptSink` — the app-layer implementation of the bridge engine's
|
|
3
|
+
* transcript sink. Persists a **unified per-room meeting transcript** into `MJ: Conversations` (one
|
|
4
|
+
* "Meeting Room" conversation per LiveKit room, keyed by the room's external id) + `MJ: Conversation Details`
|
|
5
|
+
* (one row per final utterance). The bridge engine elects ONE scribe per room and feeds its final transcript
|
|
6
|
+
* lines here, so the room records a single copy — not N (one per agent).
|
|
7
|
+
*
|
|
8
|
+
* Lives in `@memberjunction/ai-agents` (which owns the realtime/Conversations knowledge), NOT the bridge
|
|
9
|
+
* engine — the engine only emits neutral lines + the `(ConversationType, ApplicationScope, ...)` choices come
|
|
10
|
+
* from the binding site (the MJServer resolver), so nothing about "Meeting Room" or app scoping leaks into
|
|
11
|
+
* the generic engine. See `plans/realtime/realtime-session-lifecycle-and-followups.md` §5.
|
|
12
|
+
*
|
|
13
|
+
* @module @memberjunction/ai-agents
|
|
14
|
+
* @author MemberJunction.com
|
|
15
|
+
*/
|
|
16
|
+
import { IMetadataProvider, UserInfo } from '@memberjunction/core';
|
|
17
|
+
/** The conversation `ApplicationScope` values (mirrors the entity union). `'Application'` hides it from the main chat list. */
|
|
18
|
+
export type ConversationApplicationScope = 'Global' | 'Both' | 'Application';
|
|
19
|
+
/** Options that shape the room conversation — supplied by the binding site so the engine stays generic. */
|
|
20
|
+
export interface BridgeRoomTranscriptSinkOptions {
|
|
21
|
+
/** The `MJ: Conversations.Type` to stamp (e.g. `'Meeting Room'`) — the semantic label for room transcripts. */
|
|
22
|
+
ConversationType: string;
|
|
23
|
+
/** Scope of the room conversation. `'Application'` keeps it OUT of the normal chat list. Default `'Application'`. */
|
|
24
|
+
ApplicationScope?: ConversationApplicationScope;
|
|
25
|
+
/** Optional owning application id (e.g. the Meet app) so its surface can list its rooms. */
|
|
26
|
+
ApplicationID?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* One transcript line the engine emits (structurally matches `BridgeTranscriptLine` in
|
|
30
|
+
* `@memberjunction/ai-bridge-server`; mirrored here to avoid a package dependency, resolved structurally at
|
|
31
|
+
* the resolver binding site).
|
|
32
|
+
*/
|
|
33
|
+
export interface BridgeTranscriptLineInput {
|
|
34
|
+
/** The room grouping (driver external connection id) — one room = one unified transcript. */
|
|
35
|
+
RoomKey: string;
|
|
36
|
+
/** The scribe's agent-session id (attribution for its own speech). */
|
|
37
|
+
AgentSessionID: string;
|
|
38
|
+
/** The scribe's agent id, when known. */
|
|
39
|
+
AgentID?: string;
|
|
40
|
+
/** `true` for the scribe's OWN speech, `false` for anything it heard. */
|
|
41
|
+
IsAgentSpeech: boolean;
|
|
42
|
+
/** The speaker's participant id (the scribe's own bot id for its speech; absent for heard speech). */
|
|
43
|
+
SpeakerParticipantID?: string;
|
|
44
|
+
/** The final transcript text. */
|
|
45
|
+
Text: string;
|
|
46
|
+
}
|
|
47
|
+
/** The sink signature (matches the engine's `BridgeTranscriptSink`). */
|
|
48
|
+
export type BridgeRoomTranscriptSink = (line: BridgeTranscriptLineInput, contextUser?: UserInfo, provider?: IMetadataProvider) => Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Builds a {@link BridgeRoomTranscriptSink}. Bind the result onto `AIBridgeEngine.SetTranscriptSink(...)` at
|
|
51
|
+
* startup. Internally caches the room→conversation mapping (lazy get-or-create), dedupes concurrent creates
|
|
52
|
+
* per room, and serializes detail writes per conversation so transcript order is preserved.
|
|
53
|
+
*
|
|
54
|
+
* @param options The room-conversation shape (`Type`, scope, optional app id).
|
|
55
|
+
* @returns The sink function.
|
|
56
|
+
*/
|
|
57
|
+
export declare function CreateBridgeRoomTranscriptSink(options: BridgeRoomTranscriptSinkOptions): BridgeRoomTranscriptSink;
|
|
58
|
+
//# sourceMappingURL=bridge-room-transcript-sink.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge-room-transcript-sink.d.ts","sourceRoot":"","sources":["../../src/realtime/bridge-room-transcript-sink.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAgC,MAAM,sBAAsB,CAAC;AAGjG,+HAA+H;AAC/H,MAAM,MAAM,4BAA4B,GAAG,QAAQ,GAAG,MAAM,GAAG,aAAa,CAAC;AAE7E,2GAA2G;AAC3G,MAAM,WAAW,+BAA+B;IAC5C,+GAA+G;IAC/G,gBAAgB,EAAE,MAAM,CAAC;IACzB,qHAAqH;IACrH,gBAAgB,CAAC,EAAE,4BAA4B,CAAC;IAChD,4FAA4F;IAC5F,aAAa,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;GAIG;AACH,MAAM,WAAW,yBAAyB;IACtC,6FAA6F;IAC7F,OAAO,EAAE,MAAM,CAAC;IAChB,sEAAsE;IACtE,cAAc,EAAE,MAAM,CAAC;IACvB,yCAAyC;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yEAAyE;IACzE,aAAa,EAAE,OAAO,CAAC;IACvB,sGAAsG;IACtG,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAC;CAChB;AAED,wEAAwE;AACxE,MAAM,MAAM,wBAAwB,GAAG,CACnC,IAAI,EAAE,yBAAyB,EAC/B,WAAW,CAAC,EAAE,QAAQ,EACtB,QAAQ,CAAC,EAAE,iBAAiB,KAC3B,OAAO,CAAC,IAAI,CAAC,CAAC;AAKnB;;;;;;;GAOG;AACH,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,+BAA+B,GAAG,wBAAwB,CAsBjH"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview `CreateBridgeRoomTranscriptSink` — the app-layer implementation of the bridge engine's
|
|
3
|
+
* transcript sink. Persists a **unified per-room meeting transcript** into `MJ: Conversations` (one
|
|
4
|
+
* "Meeting Room" conversation per LiveKit room, keyed by the room's external id) + `MJ: Conversation Details`
|
|
5
|
+
* (one row per final utterance). The bridge engine elects ONE scribe per room and feeds its final transcript
|
|
6
|
+
* lines here, so the room records a single copy — not N (one per agent).
|
|
7
|
+
*
|
|
8
|
+
* Lives in `@memberjunction/ai-agents` (which owns the realtime/Conversations knowledge), NOT the bridge
|
|
9
|
+
* engine — the engine only emits neutral lines + the `(ConversationType, ApplicationScope, ...)` choices come
|
|
10
|
+
* from the binding site (the MJServer resolver), so nothing about "Meeting Room" or app scoping leaks into
|
|
11
|
+
* the generic engine. See `plans/realtime/realtime-session-lifecycle-and-followups.md` §5.
|
|
12
|
+
*
|
|
13
|
+
* @module @memberjunction/ai-agents
|
|
14
|
+
* @author MemberJunction.com
|
|
15
|
+
*/
|
|
16
|
+
import { RunView, LogError, LogStatus } from '@memberjunction/core';
|
|
17
|
+
const CONVERSATION_ENTITY = 'MJ: Conversations';
|
|
18
|
+
const CONVERSATION_DETAIL_ENTITY = 'MJ: Conversation Details';
|
|
19
|
+
/**
|
|
20
|
+
* Builds a {@link BridgeRoomTranscriptSink}. Bind the result onto `AIBridgeEngine.SetTranscriptSink(...)` at
|
|
21
|
+
* startup. Internally caches the room→conversation mapping (lazy get-or-create), dedupes concurrent creates
|
|
22
|
+
* per room, and serializes detail writes per conversation so transcript order is preserved.
|
|
23
|
+
*
|
|
24
|
+
* @param options The room-conversation shape (`Type`, scope, optional app id).
|
|
25
|
+
* @returns The sink function.
|
|
26
|
+
*/
|
|
27
|
+
export function CreateBridgeRoomTranscriptSink(options) {
|
|
28
|
+
const scope = options.ApplicationScope ?? 'Application';
|
|
29
|
+
/** roomKey(lower) → resolved ConversationID (populated once get-or-create settles). */
|
|
30
|
+
const roomToConversation = new Map();
|
|
31
|
+
/** roomKey(lower) → in-flight get-or-create promise (dedupes concurrent first lines for a room). */
|
|
32
|
+
const ensureInFlight = new Map();
|
|
33
|
+
/** ConversationID → serial write chain (preserves detail ordering, error-isolated). */
|
|
34
|
+
const writeChains = new Map();
|
|
35
|
+
return async (line, contextUser, provider) => {
|
|
36
|
+
if (!contextUser || !provider) {
|
|
37
|
+
return; // a server-side write needs a user + provider; nothing to do without them
|
|
38
|
+
}
|
|
39
|
+
const conversationID = await ensureRoomConversation(line.RoomKey, options.ConversationType, scope, options.ApplicationID, roomToConversation, ensureInFlight, contextUser, provider);
|
|
40
|
+
if (!conversationID) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const prior = writeChains.get(conversationID) ?? Promise.resolve();
|
|
44
|
+
const next = prior.then(() => writeTranscriptDetail(conversationID, line, contextUser, provider));
|
|
45
|
+
writeChains.set(conversationID, next.then(() => undefined, () => undefined));
|
|
46
|
+
await next;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** Resolves (cache → existing row → create) the room's `MJ: Conversations` id, deduping concurrent creates. */
|
|
50
|
+
async function ensureRoomConversation(roomKey, conversationType, scope, applicationID, cache, inFlight, contextUser, provider) {
|
|
51
|
+
const key = roomKey.trim().toLowerCase();
|
|
52
|
+
const cached = cache.get(key);
|
|
53
|
+
if (cached) {
|
|
54
|
+
return cached;
|
|
55
|
+
}
|
|
56
|
+
const pending = inFlight.get(key);
|
|
57
|
+
if (pending) {
|
|
58
|
+
return pending;
|
|
59
|
+
}
|
|
60
|
+
const task = resolveOrCreateConversation(roomKey, conversationType, scope, applicationID, contextUser, provider)
|
|
61
|
+
.then((id) => {
|
|
62
|
+
if (id) {
|
|
63
|
+
cache.set(key, id);
|
|
64
|
+
}
|
|
65
|
+
return id;
|
|
66
|
+
})
|
|
67
|
+
.finally(() => inFlight.delete(key));
|
|
68
|
+
inFlight.set(key, task);
|
|
69
|
+
return task;
|
|
70
|
+
}
|
|
71
|
+
/** Finds an existing non-archived room conversation by `ExternalID` + `Type`, else creates one. */
|
|
72
|
+
async function resolveOrCreateConversation(roomKey, conversationType, scope, applicationID, contextUser, provider) {
|
|
73
|
+
const rv = new RunView();
|
|
74
|
+
const found = await rv.RunView({
|
|
75
|
+
EntityName: CONVERSATION_ENTITY,
|
|
76
|
+
ExtraFilter: `ExternalID='${escapeSql(roomKey)}' AND Type='${escapeSql(conversationType)}' AND (IsArchived IS NULL OR IsArchived=0)`,
|
|
77
|
+
Fields: ['ID'],
|
|
78
|
+
OrderBy: '__mj_CreatedAt DESC',
|
|
79
|
+
MaxRows: 1,
|
|
80
|
+
ResultType: 'simple',
|
|
81
|
+
}, contextUser);
|
|
82
|
+
if (found.Success && found.Results.length > 0) {
|
|
83
|
+
return found.Results[0].ID;
|
|
84
|
+
}
|
|
85
|
+
const conversation = await provider.GetEntityObject(CONVERSATION_ENTITY, contextUser);
|
|
86
|
+
conversation.NewRecord();
|
|
87
|
+
conversation.UserID = contextUser.ID;
|
|
88
|
+
conversation.Name = `Meeting Room ${roomKey}`;
|
|
89
|
+
conversation.Type = conversationType;
|
|
90
|
+
conversation.ExternalID = roomKey;
|
|
91
|
+
conversation.ApplicationScope = scope;
|
|
92
|
+
if (applicationID) {
|
|
93
|
+
conversation.ApplicationID = applicationID;
|
|
94
|
+
}
|
|
95
|
+
if (await conversation.Save()) {
|
|
96
|
+
LogStatus(`CreateBridgeRoomTranscriptSink: created '${conversationType}' conversation ${conversation.ID} for room ${roomKey}.`);
|
|
97
|
+
return conversation.ID;
|
|
98
|
+
}
|
|
99
|
+
LogError(`CreateBridgeRoomTranscriptSink: failed to create room conversation: ${conversation.LatestResult?.CompleteMessage ?? 'unknown error'}`);
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
/** Writes one `MJ: Conversation Details` row for a transcript line (Role mapped; agent speech attributed). */
|
|
103
|
+
async function writeTranscriptDetail(conversationID, line, contextUser, provider) {
|
|
104
|
+
const detail = await provider.GetEntityObject(CONVERSATION_DETAIL_ENTITY, contextUser);
|
|
105
|
+
detail.NewRecord();
|
|
106
|
+
detail.ConversationID = conversationID;
|
|
107
|
+
// The scribe's own speech is the agent ('AI'); everything it heard (humans + other agents) is 'User'.
|
|
108
|
+
detail.Role = line.IsAgentSpeech ? 'AI' : 'User';
|
|
109
|
+
detail.Message = line.Text;
|
|
110
|
+
if (line.SpeakerParticipantID) {
|
|
111
|
+
detail.ExternalID = line.SpeakerParticipantID;
|
|
112
|
+
}
|
|
113
|
+
if (line.IsAgentSpeech) {
|
|
114
|
+
detail.AgentSessionID = line.AgentSessionID;
|
|
115
|
+
if (line.AgentID) {
|
|
116
|
+
detail.AgentID = line.AgentID;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (!(await detail.Save())) {
|
|
120
|
+
LogError(`CreateBridgeRoomTranscriptSink: failed to write transcript detail: ${detail.LatestResult?.CompleteMessage ?? 'unknown error'}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/** Escapes single quotes for safe embedding in an `ExtraFilter` literal. */
|
|
124
|
+
function escapeSql(value) {
|
|
125
|
+
return value.replace(/'/g, "''");
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=bridge-room-transcript-sink.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge-room-transcript-sink.js","sourceRoot":"","sources":["../../src/realtime/bridge-room-transcript-sink.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The **Meeting Controls** server-side channel — a SERVER-ONLY interactive channel
|
|
3
|
+
* (no Angular client surface) that turns a realtime agent into a meeting **facilitator**.
|
|
4
|
+
*
|
|
5
|
+
* It contributes a facilitator tool vocabulary the agent invokes server-side (`RaiseHand`,
|
|
6
|
+
* `LowerHand`, `CallOnParticipant`, `MuteParticipant`, `SetTimer`) and feeds back the perception that
|
|
7
|
+
* makes facilitation possible: the ordered **hand-raise queue** (who raised, in order, how long
|
|
8
|
+
* they've waited), **who is speaking**, the **roster**, and the agenda **timer** (elapsed / remaining).
|
|
9
|
+
* With that intel an agent can call on people in order, enforce time, and move the agenda — all
|
|
10
|
+
* through one channel's tools + perception.
|
|
11
|
+
*
|
|
12
|
+
* The channel has **no platform code**: the signals (roster / speaking / hand-raise) arrive from an
|
|
13
|
+
* INJECTED {@link IMeetingControlsEventSource} that each bridge driver adapts from its native
|
|
14
|
+
* participant/event stream. The queue + timer logic is the pure, fully unit-testable
|
|
15
|
+
* {@link MeetingControlsState}. This is the §4b "bridge contributes a channel" pattern realized as an
|
|
16
|
+
* MJ channel: dynamic tool vocabulary + perception, routed through the same channel plane as the
|
|
17
|
+
* MJ-native whiteboard.
|
|
18
|
+
*
|
|
19
|
+
* @module @memberjunction/ai-agents
|
|
20
|
+
* @author MemberJunction.com
|
|
21
|
+
*/
|
|
22
|
+
import { BaseRealtimeChannelServer, RealtimeToolDefinition, ServerChannelToolResult } from '@memberjunction/ai';
|
|
23
|
+
import { MeetingControlsState, MeetingControlsClock, MeetingParticipant } from './meeting-controls-state.js';
|
|
24
|
+
/** The channel name — matches the (seedable) `MJ: AI Agent Channels` row's `Name`. */
|
|
25
|
+
export declare const MEETING_CONTROLS_CHANNEL_NAME = "Meeting Controls";
|
|
26
|
+
/**
|
|
27
|
+
* The shared name prefix of every Meeting Controls tool. The host routes any tool call beginning with
|
|
28
|
+
* this prefix back to this channel's {@link MeetingControlsChannelServer.ExecuteServerTool}.
|
|
29
|
+
*/
|
|
30
|
+
export declare const MEETING_CONTROLS_TOOL_PREFIX = "MeetingControls_";
|
|
31
|
+
/**
|
|
32
|
+
* The capability the channel gates `MuteParticipant` on. The injected event source advertises which
|
|
33
|
+
* facilitator actions the platform supports; muting is the only gated one (raising/lowering a hand,
|
|
34
|
+
* calling on, and timers are channel-internal and always available).
|
|
35
|
+
*/
|
|
36
|
+
export type MeetingControlsCapability = 'Mute';
|
|
37
|
+
/**
|
|
38
|
+
* The signaling/perception + action surface a bridge driver supplies to the Meeting Controls channel.
|
|
39
|
+
*
|
|
40
|
+
* The channel SUBSCRIBES to the three perception streams and the channel maps the agent's tool calls
|
|
41
|
+
* back onto {@link MuteParticipant} (the one action that must reach the platform). Everything else
|
|
42
|
+
* (the queue, the timer, who is speaking) is maintained inside {@link MeetingControlsState} from these
|
|
43
|
+
* streams. Each bridge driver implements this by adapting its native participant/event stream — the
|
|
44
|
+
* channel itself never names a platform.
|
|
45
|
+
*
|
|
46
|
+
* All `On*` registrations are "latest handler wins" (one channel instance per session).
|
|
47
|
+
*/
|
|
48
|
+
export interface IMeetingControlsEventSource {
|
|
49
|
+
/**
|
|
50
|
+
* Registers a handler for roster changes. The channel rebuilds its roster from each snapshot and
|
|
51
|
+
* prunes departed participants from the queue / speaking / mute state.
|
|
52
|
+
*
|
|
53
|
+
* @param handler Invoked with the full current roster on every change.
|
|
54
|
+
*/
|
|
55
|
+
OnRosterChange(handler: (participants: MeetingParticipant[]) => void): void;
|
|
56
|
+
/**
|
|
57
|
+
* Registers a handler for diarized speaking changes — the set of participant ids currently
|
|
58
|
+
* speaking.
|
|
59
|
+
*
|
|
60
|
+
* @param handler Invoked with the speaking participant ids on every change.
|
|
61
|
+
*/
|
|
62
|
+
OnSpeakingChange(handler: (participantIds: string[]) => void): void;
|
|
63
|
+
/**
|
|
64
|
+
* Registers a handler for hand-raise/lower signals the PLATFORM surfaces (e.g. a participant uses
|
|
65
|
+
* Zoom's native "raise hand"). `raised = true` enqueues, `false` dequeues — mirroring the agent's
|
|
66
|
+
* own `RaiseHand`/`LowerHand` tools so platform-raised and agent-raised hands share one queue.
|
|
67
|
+
*
|
|
68
|
+
* @param handler Invoked with the participant id and whether the hand is now raised.
|
|
69
|
+
*/
|
|
70
|
+
OnHandRaiseChange(handler: (participantId: string, raised: boolean) => void): void;
|
|
71
|
+
/**
|
|
72
|
+
* Mutes a participant on the platform (the actuation behind the agent's `MuteParticipant` tool).
|
|
73
|
+
* Capability-gated by {@link Capabilities}; the channel checks the capability before calling this.
|
|
74
|
+
*
|
|
75
|
+
* @param participantId The participant to mute.
|
|
76
|
+
* @returns A promise resolving once the mute request has been issued.
|
|
77
|
+
*/
|
|
78
|
+
MuteParticipant(participantId: string): Promise<void>;
|
|
79
|
+
/**
|
|
80
|
+
* The facilitator capabilities this platform supports (e.g. `['Mute']`). The channel gates the
|
|
81
|
+
* `MuteParticipant` tool on `'Mute'` being present; absent capabilities make the corresponding
|
|
82
|
+
* tool return a structured "not supported" result instead of calling the source.
|
|
83
|
+
*/
|
|
84
|
+
readonly Capabilities: ReadonlyArray<MeetingControlsCapability>;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Dependencies injected into a {@link MeetingControlsChannelServer} — the platform event source and an
|
|
88
|
+
* optional deterministic clock. Injected (rather than resolved) so the channel is fully unit-testable
|
|
89
|
+
* with a fake source and a controllable clock, and so the platform wiring is the driver's concern.
|
|
90
|
+
*/
|
|
91
|
+
export interface MeetingControlsChannelDeps {
|
|
92
|
+
/** The bridge-supplied perception/action surface. */
|
|
93
|
+
EventSource: IMeetingControlsEventSource;
|
|
94
|
+
/** Optional clock for the queue/timer math (defaults to `Date.now`). */
|
|
95
|
+
Clock?: MeetingControlsClock;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Server-only Meeting Controls channel — the facilitator surface. ONE instance per session.
|
|
99
|
+
*
|
|
100
|
+
* Unlike the whiteboard (resolved from the registry by the host via a parameterless ClassFactory
|
|
101
|
+
* construction), this channel needs a per-session {@link IMeetingControlsEventSource} from the bridge,
|
|
102
|
+
* so a bridge driver constructs it directly with {@link MeetingControlsChannelDeps} and hands it to
|
|
103
|
+
* the host's per-session plugin set. It has **no client surface** — the contract's
|
|
104
|
+
* `GetServerToolDefinitions` / `ExecuteServerTool` are the entire interface; there is no
|
|
105
|
+
* `GetSurfaceComponent` on the server half at all.
|
|
106
|
+
*
|
|
107
|
+
* Registered under {@link MEETING_CONTROLS_CHANNEL_NAME} so a seeded registry row can resolve a
|
|
108
|
+
* default (event-source-less) instance for discovery; a real session always injects the driver's
|
|
109
|
+
* event source.
|
|
110
|
+
*/
|
|
111
|
+
export declare class MeetingControlsChannelServer extends BaseRealtimeChannelServer {
|
|
112
|
+
/** The pure facilitator state (queue / speaking / roster / mute / timer). */
|
|
113
|
+
private readonly state;
|
|
114
|
+
/** The injected platform event source, or `null` for a registry-resolved discovery instance. */
|
|
115
|
+
private readonly eventSource;
|
|
116
|
+
/**
|
|
117
|
+
* @param deps Optional per-session dependencies. Omitted only for a registry-resolved discovery
|
|
118
|
+
* instance (the host's ClassFactory path) — such an instance maintains no live state and
|
|
119
|
+
* contributes its tool vocabulary but cannot actuate platform mutes.
|
|
120
|
+
*/
|
|
121
|
+
constructor(deps?: MeetingControlsChannelDeps);
|
|
122
|
+
/** @inheritdoc */
|
|
123
|
+
get ChannelName(): string;
|
|
124
|
+
/** @inheritdoc */
|
|
125
|
+
get ToolNamePrefix(): string;
|
|
126
|
+
/** Read-only access to the live facilitator state (for the channel's own perception + tests). */
|
|
127
|
+
get State(): MeetingControlsState;
|
|
128
|
+
/**
|
|
129
|
+
* Subscribes the injected event source's perception streams into {@link MeetingControlsState} and
|
|
130
|
+
* emits an initial perception snapshot. A discovery instance (no event source) is inert.
|
|
131
|
+
*/
|
|
132
|
+
OnSessionStarted(): Promise<void>;
|
|
133
|
+
/**
|
|
134
|
+
* The facilitator tool vocabulary. `MuteParticipant` is included only when the platform advertises
|
|
135
|
+
* the `'Mute'` capability — so the contributed tool set is **runtime-computed** from the connected
|
|
136
|
+
* platform's abilities (the §4b dynamic-tool point), not a fixed constant.
|
|
137
|
+
*
|
|
138
|
+
* @returns The contributed server-executed tool definitions.
|
|
139
|
+
*/
|
|
140
|
+
GetServerToolDefinitions(): RealtimeToolDefinition[];
|
|
141
|
+
/**
|
|
142
|
+
* Executes one facilitator tool and returns the result (the model narrates it). Tool dispatch is
|
|
143
|
+
* by the bare tool name (after the {@link MEETING_CONTROLS_TOOL_PREFIX}). Never throws — bad args
|
|
144
|
+
* or an unknown tool resolve to a structured failure result.
|
|
145
|
+
*
|
|
146
|
+
* @param toolName The full tool name (prefixed).
|
|
147
|
+
* @param argsJson The raw arguments JSON.
|
|
148
|
+
* @returns The execution result.
|
|
149
|
+
*/
|
|
150
|
+
ExecuteServerTool(toolName: string, argsJson: string): Promise<ServerChannelToolResult>;
|
|
151
|
+
/** `RaiseHand(participantId)` — enqueue a participant; emits perception on a real change. */
|
|
152
|
+
private execRaiseHand;
|
|
153
|
+
/** `LowerHand(participantId)` — dequeue a participant. */
|
|
154
|
+
private execLowerHand;
|
|
155
|
+
/** `CallOnParticipant(participantId?)` — call on a specific person or the front of the queue. */
|
|
156
|
+
private execCallOn;
|
|
157
|
+
/** `MuteParticipant(participantId)` — capability-gated; actuates the platform mute. */
|
|
158
|
+
private execMute;
|
|
159
|
+
/** `SetTimer(seconds)` — start/clear the agenda timer. */
|
|
160
|
+
private execSetTimer;
|
|
161
|
+
/**
|
|
162
|
+
* Serializes the current facilitator state and feeds it to the model as a background context note
|
|
163
|
+
* (no spoken reply forced). No-op when the host context supplies no perception sink (provider
|
|
164
|
+
* can't inject mid-session, or no live session yet).
|
|
165
|
+
*
|
|
166
|
+
* @param reason A short label for the change that triggered the perception (logging only).
|
|
167
|
+
*/
|
|
168
|
+
private emitPerception;
|
|
169
|
+
/** Whether the platform advertises mute support. */
|
|
170
|
+
private muteSupported;
|
|
171
|
+
/** Builds one tool definition with the prefixed name. */
|
|
172
|
+
private tool;
|
|
173
|
+
/** JSON schema for a `{ participantId }` tool; `required` controls whether the id is mandatory. */
|
|
174
|
+
private participantSchema;
|
|
175
|
+
/** JSON schema for the `SetTimer` tool. */
|
|
176
|
+
private setTimerSchema;
|
|
177
|
+
/** Parses a participant-targeting tool's args, tolerating malformed JSON. */
|
|
178
|
+
private parseParticipantArgs;
|
|
179
|
+
/** Parses the `SetTimer` tool's args, tolerating malformed JSON. */
|
|
180
|
+
private parseSetTimerArgs;
|
|
181
|
+
/** Returns the required `participantId` or `null` when absent/blank. */
|
|
182
|
+
private requireParticipantId;
|
|
183
|
+
/** A standard "missing participantId" failure result for a tool. */
|
|
184
|
+
private missingParticipant;
|
|
185
|
+
/** A participant's display name, falling back to their id. */
|
|
186
|
+
private name;
|
|
187
|
+
/** 1-based queue position of a participant (0 when not queued). */
|
|
188
|
+
private queuePosition;
|
|
189
|
+
/** Parses JSON into `T`, returning an empty object on any error (channels never throw on bad args). */
|
|
190
|
+
private safeParse;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Tree-shaking prevention for {@link MeetingControlsChannelServer}'s `@RegisterClass` registration.
|
|
194
|
+
* Call from a static code path so the registration is never eliminated by the bundler — mirroring
|
|
195
|
+
* every other `Load…()` in the realtime stack.
|
|
196
|
+
*/
|
|
197
|
+
export declare function LoadMeetingControlsChannelServer(): void;
|
|
198
|
+
//# sourceMappingURL=meeting-controls-channel-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meeting-controls-channel-server.d.ts","sourceRoot":"","sources":["../../src/realtime/meeting-controls-channel-server.ts"],"names":[],"mappings":"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"}
|