@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,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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"}