@xema/omni-protocol 0.1.87 → 0.1.89

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/dist/index.d.ts CHANGED
@@ -244,20 +244,20 @@ export interface TeamCapabilities {
244
244
  /** This lead may join a member's call on request. Requires `executeTeamLeadAssist`. */
245
245
  leadAssistControl?: true;
246
246
  /**
247
- * This lead may listen to a member's call unasked, in these modes and no others. Whisper and
248
- * join-call begin from a monitor, so the list always includes `monitor`. Nothing of it reaches the
249
- * member's task. Requires `executeTeamMonitor`.
247
+ * This lead may listen to a member's call unasked, in these modes and no others. Coach and
248
+ * join-call begin with silent listening, so the list always includes `listen`. Nothing of it reaches the
249
+ * member's task. Requires `executeTeamListen`.
250
250
  */
251
- monitorControl?: MonitorMode[];
251
+ listeningControl?: ListeningMode[];
252
252
  /** This lead sets the team's policy per capability -- on, off, or the agent's -- within what the queue allows. Requires `executeTeamPolicy`. */
253
253
  policyControl?: true;
254
254
  }
255
255
  /**
256
- * How a lead listening to a member's call is heard: `monitor` hears both sides in silence,
257
- * `whisper` is heard by the agent alone, `join-call` by everyone on the call.
256
+ * How a lead listening to a member's call is heard: `listen` hears both sides in silence,
257
+ * `coach` is heard by the agent alone, `join-call` by everyone on the call.
258
258
  */
259
- export type MonitorMode = "monitor" | "whisper" | "join-call";
260
- export declare const MONITOR_MODES: readonly ["monitor", "whisper", "join-call"];
259
+ export type ListeningMode = "listen" | "coach" | "join-call";
260
+ export declare const LISTENING_MODES: readonly ["listen", "coach", "join-call"];
261
261
  /**
262
262
  * What this login may do, beyond any one task. It travels with the identity because it is part
263
263
  * of who the agent is on this provider: the provider knows the roles and says so at sign-in.
@@ -891,13 +891,13 @@ export interface TaskAssisting {
891
891
  * lead is heard, restated on every change of mode. Nothing of it reaches the member's task. There
892
892
  * is no take-over here; a lead who wants the call uses lead assist. It is the lead's only task:
893
893
  * a lead listens while holding no work of their own, idle or on a break of a kind in
894
- * `MONITORING_BREAK_KINDS`.
894
+ * `LISTENING_BREAK_KINDS`.
895
895
  */
896
- export interface TaskMonitoring {
896
+ export interface TaskListening {
897
897
  memberId: UserId;
898
898
  taskId: TaskId;
899
899
  allocationId: AllocationId;
900
- mode: MonitorMode;
900
+ mode: ListeningMode;
901
901
  since: IsoTimestamp;
902
902
  }
903
903
  /**
@@ -976,7 +976,7 @@ export type Task<C extends Channel = Channel> = {
976
976
  * snapshot says it too: an offer the host never received is not accepted on the person's behalf
977
977
  * for want of a word. Required while `pending` when Omni said it may auto-accept
978
978
  * (`autoAcceptTasks: true`) or the work is the agent's own -- a dial, a connect-back, a join, a
979
- * monitor, which then say `automatic` under either provisioning -- forbidden on routed work when
979
+ * listen, which then say `automatic` under either provisioning -- forbidden on routed work when
980
980
  * Omni said not, and absent past `pending`.
981
981
  */
982
982
  acceptance?: AcceptanceMode;
@@ -999,14 +999,14 @@ export type Task<C extends Channel = Channel> = {
999
999
  onCall?: OnCall[];
1000
1000
  leadAssist?: TaskLeadAssist;
1001
1001
  assisting?: TaskAssisting;
1002
- monitoring?: TaskMonitoring;
1002
+ listening?: TaskListening;
1003
1003
  media?: TaskMediaState;
1004
1004
  } : {
1005
1005
  recording?: never;
1006
1006
  onCall?: never;
1007
1007
  leadAssist?: never;
1008
1008
  assisting?: never;
1009
- monitoring?: never;
1009
+ listening?: never;
1010
1010
  media?: never;
1011
1011
  });
1012
1012
  /**
@@ -1236,10 +1236,10 @@ export declare const BREAK_KINDS: readonly ["short-break", "meal", "rest", "trai
1236
1236
  export type BreakKind = (typeof BREAK_KINDS)[number];
1237
1237
  /**
1238
1238
  * The breaks during which a lead may listen to a member's call: work of another sort, not rest.
1239
- * A monitoring task is the one task a break in effect may hold, and only on one of these.
1239
+ * A listening task is the one task a break in effect may hold, and only on one of these.
1240
1240
  */
1241
- export declare const MONITORING_BREAK_KINDS: readonly ["coaching", "administrative", "training"];
1242
- export declare const breakKindAllowsMonitoring: (kind: BreakKind | undefined) => boolean;
1241
+ export declare const LISTENING_BREAK_KINDS: readonly ["coaching", "administrative", "training"];
1242
+ export declare const breakKindAllowsListening: (kind: BreakKind | undefined) => boolean;
1243
1243
  export interface BreakReason {
1244
1244
  id: string;
1245
1245
  label: string;
@@ -1372,22 +1372,22 @@ export interface TeamLeadAssistCommandRequest {
1372
1372
  command: TeamLeadAssistCommand;
1373
1373
  }
1374
1374
  /**
1375
- * A lead listening to a member's call. `monitor` starts one, silent, on a member from the team member list;
1376
- * `whisper` and `join-call` change how the standing one is heard; `leave` ends it. One member at a
1377
- * time, and only the modes the login's `monitorControl` lists.
1375
+ * A lead listening to a member's call. `listen` starts one, silent, on a member from the team member list;
1376
+ * `coach` and `join-call` change how the standing one is heard; `leave` ends it. One member at a
1377
+ * time, and only the modes the login's `listeningControl` lists.
1378
1378
  */
1379
- export type TeamMonitorCommand = {
1380
- type: "monitor";
1379
+ export type TeamListenCommand = {
1380
+ type: "listen";
1381
1381
  memberId: UserId;
1382
1382
  } | {
1383
- type: "whisper";
1383
+ type: "coach";
1384
1384
  } | {
1385
1385
  type: "join-call";
1386
1386
  } | {
1387
1387
  type: "leave";
1388
1388
  };
1389
- export interface TeamMonitorCommandRequest {
1390
- command: TeamMonitorCommand;
1389
+ export interface TeamListenCommandRequest {
1390
+ command: TeamListenCommand;
1391
1391
  }
1392
1392
  export type TeamBreakCommand = {
1393
1393
  type: "decide";
@@ -1709,8 +1709,8 @@ export interface Connection<C extends Channel = Channel> {
1709
1709
  executeTeamBreak?(request: TeamBreakCommandRequest): Promise<TeamCommandResult>;
1710
1710
  /** Required when the login declares `capabilities.team.leadAssistControl`. */
1711
1711
  executeTeamLeadAssist?(request: TeamLeadAssistCommandRequest): Promise<TeamCommandResult>;
1712
- /** Required when the login declares `capabilities.team.monitorControl`. */
1713
- executeTeamMonitor?(request: TeamMonitorCommandRequest): Promise<TeamCommandResult>;
1712
+ /** Required when the login declares `capabilities.team.listeningControl`. */
1713
+ executeTeamListen?(request: TeamListenCommandRequest): Promise<TeamCommandResult>;
1714
1714
  /** Required when the login declares `capabilities.team.policyControl`. */
1715
1715
  executeTeamPolicy?(request: TeamPolicyCommandRequest): Promise<TeamCommandResult>;
1716
1716
  /** Required of a voice adapter whose manifest lists `softphone`: on one, the call's audio lands in Omni. */
package/dist/index.js CHANGED
@@ -26,7 +26,7 @@ export const IDLE_CAPABILITY_UI = {
26
26
  calendar: "Calendar",
27
27
  contacts: "Contacts",
28
28
  };
29
- export const MONITOR_MODES = ["monitor", "whisper", "join-call"];
29
+ export const LISTENING_MODES = ["listen", "coach", "join-call"];
30
30
  export const MUTED_BY = ["host", "station"];
31
31
  export const HOST_MUTES = ["stream", "station"];
32
32
  export const RECORDING_ACTIONS = ["start", "pause", "resume", "stop", "cancel"];
@@ -97,10 +97,10 @@ export const BREAK_KINDS = [
97
97
  ];
98
98
  /**
99
99
  * The breaks during which a lead may listen to a member's call: work of another sort, not rest.
100
- * A monitoring task is the one task a break in effect may hold, and only on one of these.
100
+ * A listening task is the one task a break in effect may hold, and only on one of these.
101
101
  */
102
- export const MONITORING_BREAK_KINDS = ["coaching", "administrative", "training"];
103
- export const breakKindAllowsMonitoring = (kind) => kind !== undefined && MONITORING_BREAK_KINDS.includes(kind);
102
+ export const LISTENING_BREAK_KINDS = ["coaching", "administrative", "training"];
103
+ export const breakKindAllowsListening = (kind) => kind !== undefined && LISTENING_BREAK_KINDS.includes(kind);
104
104
  export const OMNI_FAILURE_CODES = [
105
105
  "omni.not-authenticated",
106
106
  "omni.capability-not-enabled",
@@ -180,15 +180,15 @@ export function interactionStepExpectsAPerson(step) {
180
180
  * not the thing being compared. Key order does not matter, and `team: {}` is not `team` absent.
181
181
  */
182
182
  export function sameCapabilities(a, b) {
183
- const modes = (team) => team?.monitorControl === undefined ? undefined : [...team.monitorControl].sort().join(",");
183
+ const modes = (team) => team?.listeningControl === undefined ? undefined : [...team.listeningControl].sort().join(",");
184
184
  // Every field, so a capability added later cannot be missed here: `satisfies` pins the field
185
185
  // lists to the types, and a new key is a compile error until it is compared.
186
- const teamKeys = { breakControl: true, leadAssistControl: true, monitorControl: true, policyControl: true };
186
+ const teamKeys = { breakControl: true, leadAssistControl: true, listeningControl: true, policyControl: true };
187
187
  void { breaks: true, preferences: true, team: true };
188
188
  const preferences = (list) => list === undefined ? undefined : list.map(p => [p.id, p.label, p.enabled, p.setBy, p.lockedBy ?? "", p.reason ?? ""].join("\u0000")).sort().join("\u0001");
189
189
  return a.breaks === b.breaks &&
190
190
  (a.team === undefined) === (b.team === undefined) &&
191
- Object.keys(teamKeys).every(key => key === "monitorControl" || a.team?.[key] === b.team?.[key]) &&
191
+ Object.keys(teamKeys).every(key => key === "listeningControl" || a.team?.[key] === b.team?.[key]) &&
192
192
  modes(a.team) === modes(b.team) &&
193
193
  preferences(a.preferences) === preferences(b.preferences);
194
194
  }
package/dist/testing.d.ts CHANGED
@@ -7,7 +7,7 @@ export { ProtocolConformanceError, assertNoViolations, type ProtocolViolation }
7
7
  * rules -- each optional part of a task, each optional part of the break state and team member list, each
8
8
  * declared contribution, and each event type.
9
9
  */
10
- declare const STATE_SUBJECTS: readonly ["tasks", "task.browsers", "task.attributes", "task.interactionHistory", "task.onCall", "task.leadAssist", "task.assisting", "task.monitoring", "task.media", "task.acceptance", "task.outcomes", "task.destinations", "task.custom", "task.locked", "break.reasons", "break.imposed", "team.members", "team.requests", "contacts", "scheduledActivities", "team.policies"];
10
+ declare const STATE_SUBJECTS: readonly ["tasks", "task.browsers", "task.attributes", "task.interactionHistory", "task.onCall", "task.leadAssist", "task.assisting", "task.listening", "task.media", "task.acceptance", "task.outcomes", "task.destinations", "task.custom", "task.locked", "break.reasons", "break.imposed", "team.members", "team.requests", "contacts", "scheduledActivities", "team.policies"];
11
11
  export type ContractSubject = (typeof STATE_SUBJECTS)[number] | `event.${ProviderEvent["type"]}`;
12
12
  export interface AdapterContractResult {
13
13
  events: ProviderEventEnvelope[];
package/dist/testing.js CHANGED
@@ -15,7 +15,7 @@ const STATE_SUBJECTS = [
15
15
  "task.onCall",
16
16
  "task.leadAssist",
17
17
  "task.assisting",
18
- "task.monitoring",
18
+ "task.listening",
19
19
  "task.media",
20
20
  "task.acceptance",
21
21
  "task.outcomes",
@@ -61,8 +61,8 @@ function observeTask(value, seen) {
61
61
  seen.add("task.leadAssist");
62
62
  if (value.assisting !== undefined)
63
63
  seen.add("task.assisting");
64
- if (value.monitoring !== undefined)
65
- seen.add("task.monitoring");
64
+ if (value.listening !== undefined)
65
+ seen.add("task.listening");
66
66
  if (value.media !== undefined)
67
67
  seen.add("task.media");
68
68
  if (value.acceptance !== undefined)
@@ -233,8 +233,8 @@ export async function exerciseAdapter(adapter, context, options = {}) {
233
233
  requireMethod(on, "executeTeamBreak", "the login declares capabilities.team.breakControl");
234
234
  if (capabilities.team?.leadAssistControl === true)
235
235
  requireMethod(on, "executeTeamLeadAssist", "the login declares capabilities.team.leadAssistControl");
236
- if (capabilities.team?.monitorControl !== undefined)
237
- requireMethod(on, "executeTeamMonitor", "the login declares capabilities.team.monitorControl");
236
+ if (capabilities.team?.listeningControl !== undefined)
237
+ requireMethod(on, "executeTeamListen", "the login declares capabilities.team.listeningControl");
238
238
  if (capabilities.team?.policyControl === true)
239
239
  requireMethod(on, "executeTeamPolicy", "the login declares capabilities.team.policyControl");
240
240
  if (some(capabilities.preferences))
@@ -673,9 +673,9 @@ function userIdsIn(snapshot) {
673
673
  add(t.leadAssist.leadId);
674
674
  if (isRecord(t.assisting))
675
675
  add(t.assisting.memberId);
676
- if (isRecord(t.monitoring)) {
677
- add(t.monitoring.memberId);
678
- add(t.monitoring.agentId);
676
+ if (isRecord(t.listening)) {
677
+ add(t.listening.memberId);
678
+ add(t.listening.agentId);
679
679
  }
680
680
  }
681
681
  return [...ids];
@@ -684,7 +684,7 @@ const teamNamesUsers = (team) => isRecord(team) && (some(team.members) || some(t
684
684
  const taskNamesUsers = (task) => isRecord(task) && ((isRecord(task.interactionHistory) && Array.isArray(task.interactionHistory.steps) && task.interactionHistory.steps.some(step => isRecord(step) && step.by !== undefined)) ||
685
685
  (isRecord(task.leadAssist) && task.leadAssist.leadId !== undefined) ||
686
686
  isRecord(task.assisting) ||
687
- isRecord(task.monitoring));
687
+ isRecord(task.listening));
688
688
  /** Whether an event publishes a `UserId`, on a team member list, a task, or the snapshot a reconnect carries. */
689
689
  function eventNamesUsers(envelope) {
690
690
  const event = isRecord(envelope) ? envelope.event : undefined;
@@ -818,7 +818,7 @@ export function assertCapabilityWithdrawal(states, snapshot, manifest) {
818
818
  (before.team !== undefined && after.team === undefined) ||
819
819
  (before.team?.breakControl === true && after.team?.breakControl !== true) ||
820
820
  (before.team?.leadAssistControl === true && after.team?.leadAssistControl !== true) ||
821
- (before.team?.monitorControl !== undefined && after.team?.monitorControl === undefined) ||
821
+ (before.team?.listeningControl !== undefined && after.team?.listeningControl === undefined) ||
822
822
  (before.team?.policyControl === true && after.team?.policyControl !== true) ||
823
823
  (before.preferences ?? []).some(was => !(after.preferences ?? []).some(now => now.id === was.id));
824
824
  if (!withdrawn) {
@@ -135,7 +135,7 @@ export declare function validateTaskCommandRequest(request: unknown, task: unkno
135
135
  */
136
136
  export declare function validateTaskCommand(command: unknown, task?: unknown, path?: string, taskContext?: Omit<TaskValidationContext, "channel">): ProtocolViolation[];
137
137
  /** The connection methods whose results `validateResult` knows. */
138
- export type ResultMethod = "execute" | "dial" | "setCapacity" | "requestBreak" | "commitBreak" | "cancelBreak" | "endBreak" | "executeTeamBreak" | "executeTeamLeadAssist" | "executeTeamMonitor" | "openMedia" | "setPreference" | "recordStep" | "executeTeamPolicy";
138
+ export type ResultMethod = "execute" | "dial" | "setCapacity" | "requestBreak" | "commitBreak" | "cancelBreak" | "endBreak" | "executeTeamBreak" | "executeTeamLeadAssist" | "executeTeamListen" | "openMedia" | "setPreference" | "recordStep" | "executeTeamPolicy";
139
139
  /**
140
140
  * Validates what a connection method answered. A result is untrusted for the same reason a
141
141
  * snapshot is: it comes from an adapter that may be compiled against another version, and Omni
@@ -9,7 +9,7 @@
9
9
  // Each list is pinned to its type both ways -- a member the type lacks, or a member the list
10
10
  // lacks, fails to compile -- so what the validators accept cannot drift from what the
11
11
  // declarations say.
12
- import { RECORDING_ACTIONS, ALLOWED_BROWSER_URL_SCHEMES, BREAK_KINDS, MONITORING_BREAK_KINDS, BROWSER_ISOLATION_SCHEMES, INTERACTION_STEPS_THAT_DIAL, IDLE_CAPABILITIES, OMNI_FAILURE_CODES, OMNI_SUPPORTED_PROTOCOL_VERSIONS, TASK_COMMAND_NAMES, negotiateProtocolVersion, DEFAULT_LEVELS, effectiveLevels, } from "./index.js";
12
+ import { RECORDING_ACTIONS, ALLOWED_BROWSER_URL_SCHEMES, BREAK_KINDS, LISTENING_BREAK_KINDS, BROWSER_ISOLATION_SCHEMES, INTERACTION_STEPS_THAT_DIAL, IDLE_CAPABILITIES, OMNI_FAILURE_CODES, OMNI_SUPPORTED_PROTOCOL_VERSIONS, TASK_COMMAND_NAMES, negotiateProtocolVersion, DEFAULT_LEVELS, effectiveLevels, } from "./index.js";
13
13
  export class ProtocolConformanceError extends Error {
14
14
  violations;
15
15
  constructor(violations, summary = "Adapter violates the Omni protocol") {
@@ -87,8 +87,8 @@ const OFFERABLE_PHASES = membersOf({
87
87
  });
88
88
  const PREVIEW_DEADLINES = membersOf({ calls: true, "host-calls": true, waits: true });
89
89
  const PHONES = membersOf({ softphone: true, deskPhone: true });
90
- const TEAM_CAPABILITIES = membersOf({ breakControl: true, leadAssistControl: true, policyControl: true, monitorControl: true });
91
- const MONITOR_MODES = membersOf({ monitor: true, whisper: true, "join-call": true });
90
+ const TEAM_CAPABILITIES = membersOf({ breakControl: true, leadAssistControl: true, policyControl: true, listeningControl: true });
91
+ const LISTENING_MODES = membersOf({ listen: true, coach: true, "join-call": true });
92
92
  const COMPLETED_BY = membersOf({ agent: true, provider: true });
93
93
  const CANCELLED_BY = membersOf({ agent: true, provider: true, party: true });
94
94
  const EXPIRABLE_PHASES = membersOf({
@@ -1090,26 +1090,26 @@ function validateAssisting(value, channel, path, into) {
1090
1090
  into.timestamp(value.since, "task.assisting.since", `${path}.since`);
1091
1091
  }
1092
1092
  /** The lead's own task while they listen: whose call, which call, and how they are heard. Voice only. */
1093
- function validateMonitoring(value, channel, path, into) {
1093
+ function validateListening(value, channel, path, into) {
1094
1094
  if (value === undefined)
1095
1095
  return;
1096
- if (!into.require(channel === "voice", "task.monitoring.channel", path, `a ${channel} task has no call to listen to`))
1096
+ if (!into.require(channel === "voice", "task.listening.channel", path, `a ${channel} task has no call to listen to`))
1097
1097
  return;
1098
1098
  if (!isPlainObject(value)) {
1099
- into.add("task.monitoring.shape", path, "monitoring must be an object when present");
1099
+ into.add("task.listening.shape", path, "listening must be an object when present");
1100
1100
  return;
1101
1101
  }
1102
- into.require(isUserId(value.memberId), "task.monitoring.memberId", `${path}.memberId`, "a monitored call names the member on it");
1103
- into.require(isTaskId(value.taskId), "task.monitoring.taskId", `${path}.taskId`, "a monitored call names the member's task");
1104
- into.require(isAllocationId(value.allocationId), "task.monitoring.allocationId", `${path}.allocationId`, "a monitored call names the member's task by its allocation too");
1105
- into.oneOf(value.mode, MONITOR_MODES, "task.monitoring.mode", `${path}.mode`);
1106
- into.timestamp(value.since, "task.monitoring.since", `${path}.since`);
1102
+ into.require(isUserId(value.memberId), "task.listening.memberId", `${path}.memberId`, "a call being listened to names the member on it");
1103
+ into.require(isTaskId(value.taskId), "task.listening.taskId", `${path}.taskId`, "a call being listened to names the member's task");
1104
+ into.require(isAllocationId(value.allocationId), "task.listening.allocationId", `${path}.allocationId`, "a call being listened to names the member's task by its allocation too");
1105
+ into.oneOf(value.mode, LISTENING_MODES, "task.listening.mode", `${path}.mode`);
1106
+ into.timestamp(value.since, "task.listening.since", `${path}.since`);
1107
1107
  }
1108
- /** Whether a task is the agent's own doing: a dial the host placed on its onCall, a lead's join, a lead's monitor. */
1108
+ /** Whether a task is the agent's own doing: a dial the host placed on its onCall, a lead's join, a lead's listen. */
1109
1109
  function originatedByTheAgent(task) {
1110
1110
  if (Array.isArray(task.onCall) && task.onCall.some(entry => isPlainObject(entry) && typeof entry.dialId === "string"))
1111
1111
  return true;
1112
- return task.assisting !== undefined || task.monitoring !== undefined;
1112
+ return task.assisting !== undefined || task.listening !== undefined;
1113
1113
  }
1114
1114
  export function validateTask(task, context, path = "task") {
1115
1115
  const into = new Collector();
@@ -1121,6 +1121,7 @@ function validateTaskInto(task, context, path, into) {
1121
1121
  into.add("task.shape", path, "a task must be an object");
1122
1122
  return;
1123
1123
  }
1124
+ into.require(!Object.hasOwn(task, "monitoring"), "task.listening.renamed", `${path}.monitoring`, "use listening; the former field is not accepted");
1124
1125
  into.require(!Object.hasOwn(task, "handlingHistory"), "task.interactionHistory.renamed", `${path}.handlingHistory`, "use interactionHistory; the former field is not accepted");
1125
1126
  into.require(isTaskId(task.id), "task.id", `${path}.id`, "a task needs a non-empty id");
1126
1127
  into.require(isAllocationId(task.allocationId), "task.allocationId", `${path}.allocationId`, "a task names this life of itself: an allocation id minted once per offer, never reused for the life of the login");
@@ -1151,7 +1152,7 @@ function validateTaskInto(task, context, path, into) {
1151
1152
  // Acceptance is the offer's word, carried on the pending task so a snapshot can say it: it
1152
1153
  // travels exactly when Omni said tasks may be auto-accepted, and only while the task is pending.
1153
1154
  // Work the agent originated -- a dial or connect-back, carrying the host's dialId on onCall; a
1154
- // lead's join, carrying assisting; a monitor, carrying monitoring -- was accepted by the command
1155
+ // lead's join, carrying assisting; a listen, carrying listening -- was accepted by the command
1155
1156
  // that created it, and says so whatever the provisioning: the desk shows no Accept for a call
1156
1157
  // the agent placed. The provisioning governs work the queue routes, and nothing else.
1157
1158
  const originated = task.phase === "pending" && originatedByTheAgent(task);
@@ -1162,14 +1163,14 @@ function validateTaskInto(task, context, path, into) {
1162
1163
  into.add("task.acceptance.unexpected", `${path}.acceptance`, "acceptance is an offer's word; a task past pending has been accepted");
1163
1164
  }
1164
1165
  else if (originated) {
1165
- into.require(task.acceptance === "automatic", "task.acceptance.originated", `${path}.acceptance`, "work the agent originated -- a dial, a connect-back, a join, a monitor -- was accepted by the command that created it, and says automatic whatever the provisioning");
1166
+ into.require(task.acceptance === "automatic", "task.acceptance.originated", `${path}.acceptance`, "work the agent originated -- a dial, a connect-back, a join, a listen -- was accepted by the command that created it, and says automatic whatever the provisioning");
1166
1167
  }
1167
1168
  else if (context.autoAcceptTasks === false) {
1168
1169
  into.add("task.acceptance.unexpected", `${path}.acceptance`, "autoAcceptTasks is off, so every task the queue routes requires agent acceptance and a pending task carries no acceptance");
1169
1170
  }
1170
1171
  }
1171
1172
  else if (originated) {
1172
- into.add("task.acceptance.originated", `${path}.acceptance`, "work the agent originated -- a dial, a connect-back, a join, a monitor -- says acceptance: automatic whatever the provisioning: the command that created it accepted it");
1173
+ into.add("task.acceptance.originated", `${path}.acceptance`, "work the agent originated -- a dial, a connect-back, a join, a listen -- says acceptance: automatic whatever the provisioning: the command that created it accepted it");
1173
1174
  }
1174
1175
  else if (task.phase === "pending" && context.autoAcceptTasks === true) {
1175
1176
  into.add("task.acceptance.required", `${path}.acceptance`, "autoAcceptTasks is on, so a pending task states how it is accepted");
@@ -1222,10 +1223,10 @@ function validateTaskInto(task, context, path, into) {
1222
1223
  validateTaskMedia(task, task.media, context.channel, task.phase, `${path}.media`, into);
1223
1224
  validateLeadAssist(task.leadAssist, context.channel, `${path}.leadAssist`, into);
1224
1225
  validateAssisting(task.assisting, context.channel, `${path}.assisting`, into);
1225
- validateMonitoring(task.monitoring, context.channel, `${path}.monitoring`, into);
1226
+ validateListening(task.listening, context.channel, `${path}.listening`, into);
1226
1227
  // A lead's task is one thing: a call they joined on request, or a call they listen to unasked.
1227
- if (task.assisting !== undefined && task.monitoring !== undefined) {
1228
- into.add("task.monitoring.assisting", `${path}.monitoring`, "a task is a joined call or a monitored one, never both");
1228
+ if (task.assisting !== undefined && task.listening !== undefined) {
1229
+ into.add("task.listening.assisting", `${path}.listening`, "a task is a joined call or a listened one, never both");
1229
1230
  }
1230
1231
  // Where the terms came from is stated with them: a host cannot tell "the platform permits
1231
1232
  // nothing" from "the terms could not be read" from the set alone, and the provider knows which.
@@ -1334,7 +1335,7 @@ export function validateBreakStatus(state, tasks, path = "snapshot") {
1334
1335
  }
1335
1336
  if (isPlainObject(state) && state.approval === "in-effect"
1336
1337
  && Array.isArray(tasks) && tasks.length > 0) {
1337
- const listening = tasks.every((task) => isPlainObject(task) && task.monitoring !== undefined);
1338
+ const listening = tasks.every((task) => isPlainObject(task) && task.listening !== undefined);
1338
1339
  if (!listening) {
1339
1340
  into.add("break.in-effect.tasks", `${path}.tasks`, "a break in effect holds no task: it begins when the work ends, and until then the state is starting-after-task");
1340
1341
  }
@@ -1342,7 +1343,7 @@ export function validateBreakStatus(state, tasks, path = "snapshot") {
1342
1343
  const reasons = Array.isArray(state.reasons) ? state.reasons : [];
1343
1344
  const active = reasons.find((reason) => isPlainObject(reason) && reason.id === state.activeReasonId);
1344
1345
  const kind = isPlainObject(active) ? active.kind : undefined;
1345
- into.require(typeof kind === "string" && MONITORING_BREAK_KINDS.includes(kind), "snapshot.monitoring.break", `${path}.tasks`, `a lead listens during a ${MONITORING_BREAK_KINDS.join(", ")} break and no other; the active reason is ${kind === undefined ? "unstated" : describeValue(kind)}`);
1346
+ into.require(typeof kind === "string" && LISTENING_BREAK_KINDS.includes(kind), "snapshot.listening.break", `${path}.tasks`, `a lead listens during a ${LISTENING_BREAK_KINDS.join(", ")} break and no other; the active reason is ${kind === undefined ? "unstated" : describeValue(kind)}`);
1346
1347
  }
1347
1348
  }
1348
1349
  return into.violations;
@@ -1556,7 +1557,7 @@ function validateBreakState(value, path, into) {
1556
1557
  }
1557
1558
  // A break in effect, or about to be, on a provider that publishes reasons is on one of them: an
1558
1559
  // imposed one included, since the lead's place named it. A break of no kind is a break whose
1559
- // rules -- who may monitor through it, whether it counts -- nobody can apply.
1560
+ // rules -- who may listen through it, whether it counts -- nobody can apply.
1560
1561
  ruleEvaluated("break.activeReasonId.required");
1561
1562
  if (seen.size > 0 && (value.approval === "in-effect" || value.approval === "starting-after-task")) {
1562
1563
  into.require(typeof value.activeReasonId === "string" && value.activeReasonId.length > 0, "break.activeReasonId.required", `${path}.activeReasonId`, `a break ${describeValue(value.approval)} on a provider that publishes reasons names the one it is on`);
@@ -1683,7 +1684,7 @@ export function validateSnapshot(snapshot, manifest, path = "snapshot", context
1683
1684
  else {
1684
1685
  const seen = new Set();
1685
1686
  let assisting;
1686
- let monitoring;
1687
+ let listening;
1687
1688
  snapshot.tasks.forEach((task, index) => {
1688
1689
  validateTaskInto(task, { channel, levels, autoAcceptTasks: context.autoAcceptTasks, dialOutcomesDeclared: manifestDials(manifest), locked: context.locked }, `${path}.tasks[${index}]`, into);
1689
1690
  // A lead assists one call at a time, and listens to one at a time.
@@ -1692,10 +1693,10 @@ export function validateSnapshot(snapshot, manifest, path = "snapshot", context
1692
1693
  into.add("snapshot.assisting.single", `${path}.tasks[${index}].assisting`, "a lead assists one call at a time");
1693
1694
  assisting = index;
1694
1695
  }
1695
- if (isPlainObject(task) && task.monitoring !== undefined) {
1696
- if (monitoring !== undefined)
1697
- into.add("snapshot.monitoring.single", `${path}.tasks[${index}].monitoring`, "a lead listens to one call at a time");
1698
- monitoring = index;
1696
+ if (isPlainObject(task) && task.listening !== undefined) {
1697
+ if (listening !== undefined)
1698
+ into.add("snapshot.listening.single", `${path}.tasks[${index}].listening`, "a lead listens to one call at a time");
1699
+ listening = index;
1699
1700
  }
1700
1701
  if (isPlainObject(task) && isTaskId(task.id)) {
1701
1702
  if (seen.has(task.id))
@@ -1704,11 +1705,11 @@ export function validateSnapshot(snapshot, manifest, path = "snapshot", context
1704
1705
  }
1705
1706
  });
1706
1707
  }
1707
- // A lead listens while holding no work of their own: a monitoring task is the only task.
1708
+ // A lead listens while holding no work of their own: a listening task is the only task.
1708
1709
  if (Array.isArray(snapshot.tasks) && snapshot.tasks.length > 1) {
1709
1710
  snapshot.tasks.forEach((task, index) => {
1710
- if (isPlainObject(task) && task.monitoring !== undefined) {
1711
- into.add("snapshot.monitoring.alone", `${path}.tasks[${index}].monitoring`, "a lead listens only while holding no task of their own");
1711
+ if (isPlainObject(task) && task.listening !== undefined) {
1712
+ into.add("snapshot.listening.alone", `${path}.tasks[${index}].listening`, "a lead listens only while holding no task of their own");
1712
1713
  }
1713
1714
  });
1714
1715
  }
@@ -2553,7 +2554,7 @@ const RESULT_STATUSES = {
2553
2554
  endBreak: { success: "ended", failure: "failed" },
2554
2555
  executeTeamBreak: { success: "applied", failure: "failed" },
2555
2556
  executeTeamLeadAssist: { success: "applied", failure: "failed" },
2556
- executeTeamMonitor: { success: "applied", failure: "failed" },
2557
+ executeTeamListen: { success: "applied", failure: "failed" },
2557
2558
  openMedia: { success: "opened", failure: "unavailable" },
2558
2559
  setPreference: { success: "applied", failure: "failed" },
2559
2560
  recordStep: { success: "recorded", failure: "failed" },
@@ -2704,8 +2705,8 @@ function validateUserCapabilitiesInto(value, path, into, levels) {
2704
2705
  continue;
2705
2706
  if (!into.require(TEAM_CAPABILITIES.includes(control), "authentication.capability.team.unknown", `${path}.team.${control}`, `unsupported team capability: ${control}`))
2706
2707
  continue;
2707
- if (control === "monitorControl") {
2708
- validateMonitorControl(on, `${path}.team.monitorControl`, into);
2708
+ if (control === "listeningControl") {
2709
+ validateListeningControl(on, `${path}.team.listeningControl`, into);
2709
2710
  continue;
2710
2711
  }
2711
2712
  into.require(on === true, "authentication.capability.value", `${path}.team.${control}`, `${control} is declared by presence: send true or omit it`);
@@ -2716,20 +2717,20 @@ function validateUserCapabilitiesInto(value, path, into, levels) {
2716
2717
  }
2717
2718
  }
2718
2719
  /**
2719
- * The modes a lead may listen in, as a list: whisper and join-call begin from a monitor, so a list
2720
- * without `monitor` names modes the lead could never reach.
2720
+ * The modes a lead may listen in, as a list: coach and join-call begin with silent listening, so a list
2721
+ * without `listen` names modes the lead could never reach.
2721
2722
  */
2722
- function validateMonitorControl(value, path, into) {
2723
+ function validateListeningControl(value, path, into) {
2723
2724
  if (!Array.isArray(value) || value.length === 0) {
2724
- into.add("authentication.capability.team.monitorControl.shape", path, "monitorControl lists the modes this lead may listen in: a non-empty array, or omitted for a lead who may not");
2725
+ into.add("authentication.capability.team.listeningControl.shape", path, "listeningControl lists the modes this lead may listen in: a non-empty array, or omitted for a lead who may not");
2725
2726
  return;
2726
2727
  }
2727
2728
  value.forEach((mode, index) => {
2728
- if (into.oneOf(mode, MONITOR_MODES, "authentication.capability.team.monitorControl.mode", `${path}[${index}]`) && value.indexOf(mode) !== index) {
2729
- into.add("authentication.capability.team.monitorControl.unique", `${path}[${index}]`, `duplicate monitor mode: ${describeValue(mode)}`);
2729
+ if (into.oneOf(mode, LISTENING_MODES, "authentication.capability.team.listeningControl.mode", `${path}[${index}]`) && value.indexOf(mode) !== index) {
2730
+ into.add("authentication.capability.team.listeningControl.unique", `${path}[${index}]`, `duplicate listen mode: ${describeValue(mode)}`);
2730
2731
  }
2731
2732
  });
2732
- into.require(value.includes("monitor"), "authentication.capability.team.monitorControl.monitor", path, "whisper and join-call begin from a monitor: a lead who may listen in any mode may monitor");
2733
+ into.require(value.includes("listen"), "authentication.capability.team.listeningControl.listen", path, "coach and join-call begin with silent listening: a lead who may listen in any mode may listen");
2733
2734
  }
2734
2735
  /**
2735
2736
  * A failure an authentication reports: on a `rejected` start or completion, or on an `expired`
package/guide.md CHANGED
@@ -40,7 +40,7 @@ are used precisely throughout and mean nothing looser here.
40
40
  | **Break** | A reported, supervised state in which the agent is not working — one with a reason, a decision behind it and a return. It covers what a platform may call *not-ready*, including equipment trouble. An agent who is merely at capacity is not on a break. |
41
41
  | **Workspace** | What Omni shows the agent. The **task workspace** holds the selected task, its controls and its browsers; the **idle workspace** holds what a provider contributes when no task is selected — dialpad, contacts, calendar, team member list. |
42
42
  | **Dial** | One outbound call the host asks a provider to place — from the idle dialpad, a cold or warm transfer, a conference add, or a connect-back. Identified by the host's `dialId`, accepted as `dialling`, and ended by exactly one `dial-outcome`. See **Every dial has an outcome**. |
43
- | **Monitor** | A lead listening to a member's call unasked, from the team member list: `monitor` in silence, `whisper` heard by the agent alone, `join-call` heard by everyone. Nothing of it reaches the member's task, and there is no take-over in it. See **Monitoring a call**. |
43
+ | **Listen** | A lead listening to a member's call unasked, from the team member list: `listen` in silence, `coach` heard by the agent alone, `join-call` heard by everyone. Nothing of it reaches the member's task, and there is no take-over in it. See **Listening to a call**. |
44
44
  | **On the call** | Who a voice task's audio joins, or is bringing in, as the provider states it on `Task.onCall`: the party, the agents, and anyone consulted or conferenced in from the moment their dial is placed. |
45
45
 
46
46
  Six words describe *what state a thing is in*, and they are not interchangeable: each belongs to
@@ -246,12 +246,12 @@ type AuthenticationContext = {
246
246
  log?: (entry: unknown) => void;
247
247
  };
248
248
 
249
- type MonitorMode = "monitor" | "whisper" | "join-call";
249
+ type ListeningMode = "listen" | "coach" | "join-call";
250
250
 
251
251
  type TeamCapabilities = {
252
252
  breakControl?: true;
253
253
  leadAssistControl?: true;
254
- monitorControl?: MonitorMode[];
254
+ listeningControl?: ListeningMode[];
255
255
  policyControl?: true;
256
256
  };
257
257
 
@@ -702,11 +702,11 @@ type TaskAssisting = {
702
702
  since: IsoTimestamp;
703
703
  };
704
704
 
705
- type TaskMonitoring = {
705
+ type TaskListening = {
706
706
  memberId: UserId;
707
707
  taskId: TaskId;
708
708
  allocationId: AllocationId;
709
- mode: MonitorMode;
709
+ mode: ListeningMode;
710
710
  since: IsoTimestamp;
711
711
  };
712
712
 
@@ -747,8 +747,8 @@ type Task<C extends Channel = Channel> = {
747
747
  interactionHistory?: TaskInteractionHistory;
748
748
  } & TaskCompletion & (
749
749
  C extends "voice"
750
- ? { recording?: { provider?: RecordingState }; onCall?: OnCall[]; leadAssist?: TaskLeadAssist; assisting?: TaskAssisting; monitoring?: TaskMonitoring; media?: TaskMediaState }
751
- : { recording?: never; onCall?: never; leadAssist?: never; assisting?: never; monitoring?: never; media?: never }
750
+ ? { recording?: { provider?: RecordingState }; onCall?: OnCall[]; leadAssist?: TaskLeadAssist; assisting?: TaskAssisting; listening?: TaskListening; media?: TaskMediaState }
751
+ : { recording?: never; onCall?: never; leadAssist?: never; assisting?: never; listening?: never; media?: never }
752
752
  );
753
753
 
754
754
  type PreviewDeadline = "calls" | "host-calls" | "waits";
@@ -1047,14 +1047,14 @@ type TeamLeadAssistCommandRequest = {
1047
1047
  command: TeamLeadAssistCommand;
1048
1048
  };
1049
1049
 
1050
- type TeamMonitorCommand =
1051
- | { type: "monitor"; memberId: UserId }
1052
- | { type: "whisper" }
1050
+ type TeamListenCommand =
1051
+ | { type: "listen"; memberId: UserId }
1052
+ | { type: "coach" }
1053
1053
  | { type: "join-call" }
1054
1054
  | { type: "leave" };
1055
1055
 
1056
- type TeamMonitorCommandRequest = {
1057
- command: TeamMonitorCommand;
1056
+ type TeamListenCommandRequest = {
1057
+ command: TeamListenCommand;
1058
1058
  };
1059
1059
 
1060
1060
  type TeamCommandResult =
@@ -1173,7 +1173,7 @@ type Connection<C extends Channel = Channel> = {
1173
1173
 
1174
1174
  executeTeamBreak?(request: TeamBreakCommandRequest): Promise<TeamCommandResult>;
1175
1175
  executeTeamLeadAssist?(request: TeamLeadAssistCommandRequest): Promise<TeamCommandResult>;
1176
- executeTeamMonitor?(request: TeamMonitorCommandRequest): Promise<TeamCommandResult>;
1176
+ executeTeamListen?(request: TeamListenCommandRequest): Promise<TeamCommandResult>;
1177
1177
  openMedia?(request: OpenMediaRequest): Promise<OpenMediaResult>;
1178
1178
  setPreference?(request: SetPreferenceRequest): Promise<PreferenceResult>;
1179
1179
  recordStep?(report: InteractionReport): Promise<InteractionReportResult>;
@@ -1926,7 +1926,7 @@ them from what arrives later.
1926
1926
  | `team` | This login leads a team. The provider publishes a `TeamMembers` object to it on every snapshot — `members: []` when nobody is in it — and to nobody else. |
1927
1927
  | `team.breakControl` | This lead may act on their team's breaks through `executeTeamBreak` — place, release, decide, set policy — as far as the provider supports; a command it lacks answers `omni.capability-not-enabled`. Omni asks for a decision only against a member whose `break` is `awaiting-decision`, so a provider that grants on request is never asked to decide. Requires `executeTeamBreak`. |
1928
1928
  | `team.leadAssistControl` | This lead may join a member's call on request. Requires `executeTeamLeadAssist`. |
1929
- | `team.monitorControl` | This lead may listen to a member's call unasked, in the listed modes and no others: `monitor`, `whisper`, `join-call`. The list always includes `monitor`, since the other two begin from one. Requires `executeTeamMonitor`. See **Monitoring a call**. |
1929
+ | `team.listeningControl` | This lead may listen to a member's call unasked, in the listed modes and no others: `listen`, `coach`, `join-call`. The list always includes `listen`, since the other two begin from one. Requires `executeTeamListen`. See **Listening to a call**. |
1930
1930
  | `team.policyControl` | This lead sets the team's policy per capability — on, off, or the person's — within what the queue allows. Requires `executeTeamPolicy`; the team member list carries `policies`. |
1931
1931
  | `preferences` | What the team left to this person, with where each stands and who set it. Omitted when nothing was. Requires `setPreference`. See **Who decides what an agent may do**. |
1932
1932
 
@@ -2161,7 +2161,7 @@ surface in one place, and what obliges an adapter to implement each one.
2161
2161
  | `endBreak()` | The login declares `capabilities.breaks`. |
2162
2162
  | `executeTeamBreak(command)` | The login declares `capabilities.team.breakControl`. |
2163
2163
  | `executeTeamLeadAssist(command)` | The login declares `capabilities.team.leadAssistControl`. |
2164
- | `executeTeamMonitor(command)` | The login declares `capabilities.team.monitorControl`. |
2164
+ | `executeTeamListen(command)` | The login declares `capabilities.team.listeningControl`. |
2165
2165
  | `setPreference(request)` | The login declares `capabilities.preferences`: the person's choice has to have somewhere to go. |
2166
2166
  | `recordStep(report)` | The manifest lists `softphone` among its `phones`. On a softphone the host mutes its own microphone on any call, and the provider's record has to have somewhere to take that leg; a desk phone's microphone is the phone's. See **The host records what it performs**. |
2167
2167
  | `executeTeamPolicy(command)` | The login declares `capabilities.team.policyControl`. |
@@ -2297,7 +2297,7 @@ An automatically accepted task still arrives through `task-offered`.
2297
2297
 
2298
2298
  **Work the agent originated is accepted by the command that created it.** A task born of the
2299
2299
  agent's own act — a dialpad call or a connect-back, recognisable by the host's `dialId` on
2300
- `onCall`; a lead's join, carrying `assisting`; a lead's monitor, carrying `monitoring` — arrives
2300
+ `onCall`; a lead's join, carrying `assisting`; a lead's listen, carrying `listening` — arrives
2301
2301
  through `task-offered` with `acceptance: "automatic"` whatever `autoAcceptTasks` says, and the
2302
2302
  validator holds it there under either provisioning and under none (`task.acceptance.originated`):
2303
2303
  the desk shows no **Accept** for a call the agent placed. The provisioning governs work the queue
@@ -2395,7 +2395,7 @@ time. Runtime conformance checks also require the task channel to match its prov
2395
2395
  | `onCall` | Voice only. Who is on the call, or being brought onto it, as the provider states it, replaced whole with the task: `party` is the customer -- carrying a `stage` while being dialled again on the same task, a connect-back with the host's `dialId` or a platform's callback without, ringing from the moment the dial is placed and joined on its answered outcome --, `agent` a person by user id, `consulted` and `conferenced` somebody a dial is bringing in, listed from the moment the dial is placed -- with the `destinationId` dialled, the `dialId` where a host placed it, the `stage` reached (`ringing` until answered, `joined` after), and `held: true` on anyone joined and parked. A `consulted` entry is what makes `transfer` `complete` and `cancel` issuable. `label` names a destination -- a person, a queue -- not a phrase; the host supplies the verb. Present when the provider knows the room, absent when it does not. See **Every dial has an outcome**. |
2396
2396
  | `leadAssist` | Voice only. Present from the agent's request for a lead until the lead leaves or the request ends: `requested` while nobody has joined, `joined` with the lead's `leadId` once somebody has. See **Lead assist**. |
2397
2397
  | `assisting` | Voice only, on the lead's own task for a call they joined: which member asked, with their note. Its presence is what makes `lead-assist` `take-over` and `leave` issuable. See **Lead assist**. |
2398
- | `monitoring` | Voice only, on the lead's own task while they listen to a member's call: whose call, which call, and the `mode` they are heard in, restated on every change. Never on the member's task, and never together with `assisting`. See **Monitoring a call**. |
2398
+ | `listening` | Voice only, on the lead's own task while they listen to a member's call: whose call, which call, and the `mode` they are heard in, restated on every change. Never on the member's task, and never together with `assisting`. See **Listening to a call**. |
2399
2399
 
2400
2400
  `TaskAttribute` entries carry typed detail alongside the task:
2401
2401
 
@@ -3625,7 +3625,7 @@ rendering one as the other tells an agent to wait for somebody who is never comi
3625
3625
  | `awaiting-decision` | A person has to decide. The agent is waiting on somebody. |
3626
3626
  | `granted` | A person decided yes. Omni may now tell this provider to stop the agent; until it does, work continues normally, and this says nothing about why Omni has not. |
3627
3627
  | `starting-after-task` | Omni has told the provider to stop; the break begins when the current task ends. No new work arrives meanwhile, and nobody needs to act. It waits on a task, so beside no task it is refused (`break.starting-after-task.tasks`): a committed break with nothing outstanding is `in-effect`. |
3628
- | `in-effect` | The agent is on the break now. It holds no task: a break begins when the work ends, so a snapshot reporting `in-effect` beside a task is refused as `break.in-effect.tasks`. The one exception is a lead's monitoring task during a `coaching`, `administrative` or `training` break -- see **Monitoring a call**. |
3628
+ | `in-effect` | The agent is on the break now. It holds no task: a break begins when the work ends, so a snapshot reporting `in-effect` beside a task is refused as `break.in-effect.tasks`. The one exception is a lead's listening task during a `coaching`, `administrative` or `training` break -- see **Listening to a call**. |
3629
3629
 
3630
3630
  A denial is a decision, not a standing approval state. The provider transitions the request directly
3631
3631
  to `not-requested`; Omni returns the agent to idle and never asks again on their behalf. They saw the
@@ -3771,7 +3771,7 @@ that association and backend authorization are provider responsibilities.
3771
3771
 
3772
3772
  Use `validateBreakStatus(state, tasks)` on the complete retained task view after each transaction,
3773
3773
  as well as `validateBreakTransition` for event ordering. Snapshot validation shares the status
3774
- checks. Pending and completing tasks still count as work. Only the existing monitoring exception
3774
+ checks. Pending and completing tasks still count as work. Only the existing listening exception
3775
3775
  allows tasks beside a break in effect. Validate full task shapes and the envelope separately.
3776
3776
 
3777
3777
  These validators report violations without dispatching, rewriting state or proving freshness.
@@ -4183,13 +4183,40 @@ const leadAssistCapable = {
4183
4183
  Lead and member alike are `UserId`s of this provider, so an adapter publishing them implements
4184
4184
  `describeUsers()`; names never travel on a task or a team member list.
4185
4185
 
4186
- ### Monitoring a call
4186
+ ### Listening to a call
4187
4187
 
4188
- The user-facing action is **Join call**. The command and monitoring mode are `join-call`.
4189
- Migration: replace the former barge literal in `TeamMonitorCommand`, `MonitorMode`,
4190
- `team.monitorControl`, and the task’s `monitoring.mode`. `MONITOR_MODES` exports the new literal;
4188
+ **Listen** lets the lead hear the call without being heard. The modes are `listen`, `coach`
4189
+ (agent-only lead audio), and `join-call` (lead audio heard by everyone).
4190
+
4191
+ | Former API | Current API |
4192
+ | --- | --- |
4193
+ | MonitorMode / MONITOR_MODES | `ListeningMode` / `LISTENING_MODES` |
4194
+ | TeamMonitorCommand / TeamMonitorCommandRequest | `TeamListenCommand` / `TeamListenCommandRequest` |
4195
+ | executeTeamMonitor | `executeTeamListen` |
4196
+ | team.monitorControl | `team.listeningControl` |
4197
+ | TaskMonitoring / task.monitoring | `TaskListening` / the task’s `listening` field |
4198
+ | MONITORING_BREAK_KINDS / breakKindAllowsMonitoring | `LISTENING_BREAK_KINDS` / `breakKindAllowsListening` |
4199
+ | monitor action and mode | `listen` action and mode |
4200
+
4201
+ Update command dispatch, allowed modes, task snapshots, imports and diagnostics together.
4202
+ The former names are not aliases. Old task and permission fields are rejected even alongside
4203
+ new fields. Validation rule segments now use `listening`, `listeningControl`, and `listen`.
4204
+ The existing login permissions, single-call limits, allowed break kinds and audio effects
4205
+ remain unchanged. Listening includes its coach/join-call modes and does not imply takeover.
4206
+
4207
+
4208
+ **Coach** lets the lead speak privately to the agent; the caller cannot hear the lead.
4209
+ The command and listening mode are `coach`. Migration: replace the former whisper literal
4210
+ in `TeamListenCommand`, `ListeningMode`, `team.listeningControl`, and the task’s `listening.mode`.
4211
+ `LISTENING_MODES` exports `coach`; update command producers, permissions and retained snapshots
4212
+ together. No legacy alias is accepted. The lead must already be listening and permitted to
4213
+ coach. This mode does not join the audible caller conversation or take over the task.
4214
+
4215
+ The user-facing action is **Join call**. The command and listening mode are `join-call`.
4216
+ Migration: replace the former barge literal in `TeamListenCommand`, `ListeningMode`,
4217
+ `team.listeningControl`, and the task’s `listening.mode`. `LISTENING_MODES` exports the new literal;
4191
4218
  no legacy alias is accepted. Update commands, capability declarations and retained snapshots
4192
- together. The lead must already be monitoring and permitted to use this mode. Everyone on the
4219
+ together. The lead must already be listening and permitted to use this mode. Everyone on the
4193
4220
  call hears the lead; this does not transfer ownership or invoke lead-assist takeover.
4194
4221
 
4195
4222
  A lead may listen to a member's call without being asked -- to coach, to check quality, to step in
@@ -4199,63 +4226,63 @@ three modes determine who hears the lead:
4199
4226
 
4200
4227
  | Mode | Who hears the lead |
4201
4228
  | --- | --- |
4202
- | `monitor` | Nobody. The lead hears both sides in silence. |
4203
- | `whisper` | The agent alone. The customer hears nothing. |
4229
+ | `listen` | Nobody. The lead hears both sides in silence. |
4230
+ | `coach` | The agent alone. The customer hears nothing. |
4204
4231
  | `join-call` | Everyone on the call. |
4205
4232
 
4206
- The login says which modes this lead has, as a list on `team.monitorControl`, and it always
4207
- includes `monitor`, because whisper and join-call begin from one
4208
- (`authentication.capability.team.monitorControl.monitor`). A centre that lets every lead listen but
4209
- reserves joining the call declares `["monitor", "whisper"]`, and Omni offers no Join call to that lead. The
4210
- declaration requires `executeTeamMonitor`, gated exactly as `executeTeamLeadAssist` is by
4233
+ The login says which modes this lead has, as a list on `team.listeningControl`, and it always
4234
+ includes `listen`, because coach and join-call begin from one
4235
+ (`authentication.capability.team.listeningControl.listen`). A centre that lets every lead listen but
4236
+ reserves joining the call declares `["listen", "coach"]`, and Omni offers no Join call to that lead. The
4237
+ declaration requires `executeTeamListen`, gated exactly as `executeTeamLeadAssist` is by
4211
4238
  `team.leadAssistControl`.
4212
4239
 
4213
4240
  ```ts
4214
4241
  // 1. The lead picks a member from the team member list and starts silent.
4215
- executeTeamMonitor({ command: { type: "monitor", memberId: "A-1" } })
4242
+ executeTeamListen({ command: { type: "listen", memberId: "A-1" } })
4216
4243
 
4217
4244
  // 2. The lead's own task arrives -- task-offered with `automatic`, as a joined call does -- and
4218
- // carries `monitoring`; the task id is the lead's, the member's call is named inside it.
4219
- // monitoring: { memberId: "A-1", taskId: "call-42", allocationId: "alloc-42", mode: "monitor", since }
4245
+ // carries `listening`; the task id is the lead's, the member's call is named inside it.
4246
+ // listening: { memberId: "A-1", taskId: "call-42", allocationId: "alloc-42", mode: "listen", since }
4220
4247
 
4221
4248
  // 3. The lead changes how they are heard; the provider restates the task with the new mode.
4222
- executeTeamMonitor({ command: { type: "whisper" } })
4223
- executeTeamMonitor({ command: { type: "join-call" } })
4249
+ executeTeamListen({ command: { type: "coach" } })
4250
+ executeTeamListen({ command: { type: "join-call" } })
4224
4251
 
4225
4252
  // 4. The lead leaves; their task ends with `{ type: "left" }`. The member's call goes on.
4226
- executeTeamMonitor({ command: { type: "leave" } })
4253
+ executeTeamListen({ command: { type: "leave" } })
4227
4254
  ```
4228
4255
 
4229
4256
  **The lead's task is what gives them audio**: Omni opens media on it as on any voice task, and the
4230
- provider bridges the lead's leg into the member's call in the mode stated. `monitoring.mode` is
4231
- the state, restated on every change, and a `whisper` or `join-call` the login's list does not include
4232
- is answered `failed`. **A lead listens to one call at a time** (`snapshot.monitoring.single`), and
4233
- a task carries lead assistance or monitoring, never both (`task.monitoring.assisting`).
4234
- The `join-call` mode still belongs to monitoring; it does not create `assisting`. The member's
4257
+ provider bridges the lead's leg into the member's call in the mode stated. `listening.mode` is
4258
+ the state, restated on every change, and a `coach` or `join-call` the login's list does not include
4259
+ is answered `failed`. **A lead listens to one call at a time** (`snapshot.listening.single`), and
4260
+ a task carries lead assistance or listening, never both (`task.listening.assisting`).
4261
+ The `join-call` mode still belongs to listening; it does not create `assisting`. The member's
4235
4262
  call ending ends the lead's task as it ends the member's, with `left`, since the lead was never
4236
4263
  working on it.
4237
4264
 
4238
- **A lead listens only while holding no work of their own.** A monitoring task is the lead's only
4239
- task, and a snapshot carrying one beside any other is refused (`snapshot.monitoring.alone`); Omni
4240
- offers Monitor to a lead with no task. That includes a lead on a break -- unlike joining, which a
4265
+ **A lead listens only while holding no work of their own.** A listening task is the lead's only
4266
+ task, and a snapshot carrying one beside any other is refused (`snapshot.listening.alone`); Omni
4267
+ offers Listen to a lead with no task. That includes a lead on a break -- unlike joining, which a
4241
4268
  break forbids -- but only a break that is work of another sort: `coaching`, `administrative` or
4242
- `training`, the kinds in `MONITORING_BREAK_KINDS`, and never a meal or a rest. A break in effect
4243
- otherwise holds no task; the monitoring task is its one exception, and a snapshot carrying one
4269
+ `training`, the kinds in `LISTENING_BREAK_KINDS`, and never a meal or a rest. A break in effect
4270
+ otherwise holds no task; the listening task is its one exception, and a snapshot carrying one
4244
4271
  during a break whose active reason is of any other kind, or of no stated kind, is refused
4245
- (`snapshot.monitoring.break`). The kind decides, not the reason's name: a centre that wants leads
4272
+ (`snapshot.listening.break`). The kind decides, not the reason's name: a centre that wants leads
4246
4273
  listening during a break declares that break `coaching`, `administrative` or `training`.
4247
4274
 
4248
- **Nothing reaches the member.** The member's task carries no trace of a monitoring lead in any
4249
- mode: not on `onCall`, not in the record. Whether a whisper is announced to the agent is the
4275
+ **Nothing reaches the member.** The member's task carries no trace of a listening lead in any
4276
+ mode: not on `onCall`, not in the record. Whether coaching is announced to the agent is the
4250
4277
  platform's business and travels on the audio, not on this wire.
4251
4278
 
4252
4279
  ```ts
4253
- const monitoringLead = {
4280
+ const listeningLead = {
4254
4281
  channel: "voice",
4255
4282
  capabilities: {},
4256
4283
  phase: "in-progress",
4257
- monitoring: { memberId: "A-1", taskId: "call-42", allocationId: "alloc-42", mode: "whisper", since: "2026-08-21T09:04:00Z" },
4258
- } satisfies Pick<Task<"voice">, "channel" | "capabilities" | "phase" | "monitoring">;
4284
+ listening: { memberId: "A-1", taskId: "call-42", allocationId: "alloc-42", mode: "coach", since: "2026-08-21T09:04:00Z" },
4285
+ } satisfies Pick<Task<"voice">, "channel" | "capabilities" | "phase" | "listening">;
4259
4286
  ```
4260
4287
 
4261
4288
  ### A member waiting for a break
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xema/omni-protocol",
3
- "version": "0.1.87",
3
+ "version": "0.1.89",
4
4
  "description": "The Omni protocol: the contract every provider adapter implements",
5
5
  "type": "module",
6
6
  "license": "MIT",