@jambonz/schema 0.3.18 → 0.3.19

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.
@@ -0,0 +1,186 @@
1
+ # Conference Monitoring, Coaching & Transcription
2
+
3
+ This guide covers building supervision features on top of jambonz conferences:
4
+ silently monitoring a live room, whisper-coaching the agents in it, barging in
5
+ as a full participant, and tapping the room's audio for transcription or AI
6
+ analysis — all without interrupting or re-dialing any call.
7
+
8
+ A complete, working reference application (React console + Node backend) built
9
+ on these patterns is at **https://github.com/jambonz/room-monitor** — start
10
+ with its `docs/ADAPTING.md`. The `conference-supervision` SDK example
11
+ (`get_sdk_example`) is a distilled version of the same patterns.
12
+
13
+ ## The building blocks
14
+
15
+ | Capability | Mechanism |
16
+ |---|---|
17
+ | Classify participants (who is an "agent") | `memberTag` on each conference member — set at join or changed mid-call |
18
+ | Silent monitor | join the conference muted (`joinMuted: true`) |
19
+ | Coach / whisper | the supervisor's audio is delivered **only** to members carrying a given tag (`speakOnlyTo` at join, or the `coach` participant action mid-call) |
20
+ | Barge-in | `uncoach` + unmute — the supervisor is heard by everyone |
21
+ | Room audio out (transcription / AI / recording) | a **conference listen fork**: jambonz streams the room's mixed audio (L16 PCM) to a WebSocket you host |
22
+ | Discover live conferences | `GET /Accounts/{sid}/Conferences` (add `?expand=participants` for members + tags on systems that support it) |
23
+
24
+ Mode changes are mid-call commands on an existing leg — a supervisor moves
25
+ between monitor → coach → barge instantly, with no re-INVITE and no
26
+ interruption to the room.
27
+
28
+ ## Tagging members
29
+
30
+ Tags drive everything: coaching targets, UI classification, and per-tag audio
31
+ flows. Set a tag when a member joins:
32
+
33
+ ```typescript
34
+ session.conference({
35
+ name: 'support-room-42',
36
+ memberTag: 'agent',
37
+ }).send();
38
+ ```
39
+
40
+ Tags are **fully dynamic** — add or remove them on a participant who is already
41
+ in the conference (no re-join). From the application controlling that leg:
42
+
43
+ ```typescript
44
+ // promote a live participant (e.g. warm transfer completes, human takes over from AI)
45
+ session.injectCommand('conf:participant-action', { action: 'tag', tag: 'agent' });
46
+
47
+ // demote — clears the member's tag
48
+ session.injectCommand('conf:participant-action', { action: 'untag' });
49
+
50
+ // third argument targets another leg this session controls (e.g. a dialed B-leg)
51
+ session.injectCommand('conf:participant-action', { action: 'tag', tag: 'agent' }, bLegCallSid);
52
+ ```
53
+
54
+ Or from anywhere via the REST client:
55
+
56
+ ```typescript
57
+ import { JambonzClient } from '@jambonz/sdk/client';
58
+
59
+ const client = new JambonzClient({ baseUrl, accountSid, apiKey });
60
+ await client.calls.update(callSid, {
61
+ conferenceParticipantAction: { action: 'tag', tag: 'agent' },
62
+ });
63
+ ```
64
+
65
+ The full mid-call action set is `tag | untag | coach | uncoach | mute | unmute
66
+ | hold | unhold`. Tag changes take effect live: an active coach automatically
67
+ starts/stops reaching the member as its tag changes.
68
+
69
+ ## The supervisor leg
70
+
71
+ The supervisor is just another conference member whose join options and
72
+ participant actions produce the three modes. Join for silent monitoring:
73
+
74
+ ```typescript
75
+ session
76
+ .answer()
77
+ .conference({
78
+ name: roomName,
79
+ joinMuted: true, // hears everything, heard by no one
80
+ memberTag: 'supervisor', // lets other tooling filter this leg out
81
+ startConferenceOnEnter: false, // never create/destroy the room being watched
82
+ endConferenceOnExit: false,
83
+ actionHook: '/conf-done',
84
+ })
85
+ .send();
86
+ ```
87
+
88
+ To join directly in coach mode instead, add `speakOnlyTo: 'agent'` and omit
89
+ `joinMuted`. Then switch modes mid-call (from any service, via REST):
90
+
91
+ ```typescript
92
+ // coach: audio delivered only to members tagged 'agent'
93
+ await client.calls.update(supervisorCallSid, {
94
+ conferenceParticipantAction: { action: 'coach', tag: 'agent' },
95
+ });
96
+ await client.calls.update(supervisorCallSid, { conf_mute_status: 'unmute' });
97
+
98
+ // barge-in: heard by everyone
99
+ await client.calls.update(supervisorCallSid, {
100
+ conferenceParticipantAction: { action: 'uncoach' },
101
+ });
102
+
103
+ // back to silent monitoring
104
+ await client.calls.update(supervisorCallSid, { conf_mute_status: 'mute' });
105
+ ```
106
+
107
+ Coach semantics to rely on:
108
+
109
+ - Coached audio reaches **only** members carrying the target tag — including
110
+ members (and audio forks) that join *after* coaching started.
111
+ - Audio forks are never tagged, so a transcription or recording tap **cannot
112
+ hear private coaching**; it hears the coach again after `uncoach`.
113
+ - If the last tagged member leaves, fall back to monitoring in your
114
+ application logic (the reference app polls participants and downgrades
115
+ coach → monitor automatically).
116
+
117
+ ## Tapping the room's audio (transcription, AI, recording)
118
+
119
+ A **conference listen fork** streams the room's mixed audio to a WebSocket you
120
+ operate. jambonz only transports audio — what you do with it (STT, sentiment,
121
+ archiving) is entirely your side of the socket.
122
+
123
+ ```bash
124
+ # start — jambonz dials out to your wsUrl and streams L16 PCM
125
+ curl -X POST "$BASE_URL/v1/Accounts/$ACCOUNT_SID/Conferences/support-room-42/listen" \
126
+ -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
127
+ -d '{"url": "wss://your-host/fork", "sampleRate": 16000,
128
+ "metadata": {"room": "support-room-42", "sampleRate": 16000}}'
129
+
130
+ # stop
131
+ curl -X DELETE "$BASE_URL/v1/Accounts/$ACCOUNT_SID/Conferences/support-room-42/listen" \
132
+ -H "Authorization: Bearer $API_KEY"
133
+ ```
134
+
135
+ Consume it in the same `@jambonz/sdk` app that runs your call logic:
136
+
137
+ ```typescript
138
+ const makeService = createEndpoint({ server, port: 3000 });
139
+ const audio = makeService.audio({ path: '/fork' });
140
+
141
+ audio.on('connection', (stream) => {
142
+ // your `metadata` arrives verbatim as the fork's first text frame and is
143
+ // exposed as stream.metadata — make it self-describing (include sampleRate)
144
+ console.log('fork connected', stream.metadata);
145
+ stream.on('audio', (pcm: Buffer) => {
146
+ // feed your STT / AI / recorder here
147
+ });
148
+ });
149
+ ```
150
+
151
+ Lifecycle guarantees: the fork is a media-server-owned bot member — it is
152
+ **excluded from participant counts, never keeps a room alive, and is torn down
153
+ automatically when the conference ends**. Starting a fork requires no
154
+ participant leg (you can transcribe a room nobody is monitoring), and repeated
155
+ starts for the same conference are idempotent.
156
+
157
+ > Conference-level listen (`/Conferences/{name}/listen`) and the
158
+ > `?expand=participants` listing require a jambonz release with MediaJam-based
159
+ > conferencing that includes these endpoints.
160
+
161
+ ## Discovering live rooms
162
+
163
+ ```typescript
164
+ // names only (all releases)
165
+ const rooms = await client.conferences.list();
166
+
167
+ // with participants + tags (releases with the enriched listing)
168
+ const res = await fetch(
169
+ `${baseUrl}/v1/Accounts/${accountSid}/Conferences?expand=participants`,
170
+ { headers: { Authorization: `Bearer ${apiKey}` } });
171
+ // → [{ id, name, durationSec, participants: [{ call_sid, label, memberTag, isAgent }] }]
172
+ ```
173
+
174
+ Filter out your own supervisor legs (`memberTag === 'supervisor'`) before
175
+ displaying counts, and derive "can I coach?" from agent presence.
176
+
177
+ ## Putting it together
178
+
179
+ A typical supervision backend is three small pieces: a poller on the
180
+ conferences listing (rooms → your UI), a jambonz application that joins
181
+ supervisor legs and applies participant actions, and an audio-fork consumer
182
+ feeding your STT. The reference app implements exactly this — and ships a
183
+ closed-loop end-to-end test (`tools/e2e/`) that verifies the audio-visibility
184
+ contracts, including coach privacy, using scripted fake-microphone browsers.
185
+ If you adapt the patterns, adapt the test too: it will keep verifying who can
186
+ hear whom as your code evolves.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jambonz/schema",
3
- "version": "0.3.18",
3
+ "version": "0.3.19",
4
4
  "description": "JSON Schema definitions and validation for jambonz verb applications",
5
5
  "main": "index.js",
6
6
  "scripts": {