@xema/omni-protocol 0.1.98 → 0.1.100

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/guide.md CHANGED
@@ -23,7 +23,7 @@ are used precisely throughout and mean nothing looser here.
23
23
  | **Provider** | One independently connected external system: a voice platform, a chat platform, a mail platform. |
24
24
  | **Adapter** | The package implementing this contract for one provider. One adapter is one provider, so the words are often interchangeable; *provider* names the system, *adapter* the code speaking for it. |
25
25
  | **Agent** | The person signed in and taking work. On `onCall`, `agent` is a person on the call by user id; transfer destinations may include an agent when the provider publishes that directory item. |
26
- | **Lead** | An agent whose login declares `capabilities.team`. The provider publishes a `TeamMembers` object to them and to nobody else: **the login is the permission**. |
26
+ | **Lead** | An agent whose login declares `capabilities.lead`. The flag alone turns on the team feature in the agent application: the lead sees their members with their tasks and stats, and acts on them with the authority the flag carries. While the feature is on, the provider publishes a `TeamMembers` object to them and to nobody else: **the login is the permission**. |
27
27
  | **Local policy** | Rules configured by the agent application about this agent, outside the provider protocol and never sent to a provider. It gates whether an offer may be rejected, whether the agent goes ready on login, and whether tasks are auto-accepted. Where a capability and local policy disagree, the stricter wins. |
28
28
  | **Call** | The caller’s complete phone call, which may continue through IVRs, queues and several agents. The protocol never describes the IVR or the queue; it carries the call's history and details to whichever agent holds it now. |
29
29
  | **Assignment** | The call routed to this agent, from the offer until the task ends. It is the one thing that crosses: the provider assigns, names the assignment with `assignmentId`, and that is the only name the desk ever uses back. A call that comes back is a new assignment on the same call, and a declined or lapsed offer is an assignment that became nothing more. What the platform calls its own record, and whether it reuses that name, is the adapter's business and unknown to the desk. |
@@ -41,7 +41,7 @@ are used precisely throughout and mean nothing looser here.
41
41
  | **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. |
42
42
  | **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. |
43
43
  | **Dial** | One outbound call the agent application asks a provider to place — from the idle dialpad, a cold or warm transfer, a conference add, or a connect-back. Identified by the agent application's `dialId`, accepted as `dialling`, and ended by exactly one `dial-outcome`. See **Every dial has an outcome**. |
44
- | **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
+ | **Listen** | A lead's channel in a member's call, unasked, from the team member list: `listen` in silence, `coach` heard by the agent alone, `join-call` heard by everyone. It is a team act naming the member, never a task on the lead's desk; nothing of it reaches the member's task. A lead who wants the call takes it over. See **Listening to a call**. |
45
45
  | **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. |
46
46
 
47
47
  These fields describe state within their containing objects. Both authentication and break
@@ -50,7 +50,7 @@ state use `status`; name the object when discussing them to avoid ambiguity.
50
50
  | Word | Belongs to | Values |
51
51
  | --- | --- | --- |
52
52
  | `phase` | A task | `pending`, `confirmed`, `preview`, `in-progress`, `paused`, `completing` |
53
- | `media` | A task's audio | `started`, `ended` |
53
+ | `audio` | A task's audio | `started`, `ended` |
54
54
  | `transport` | A connection | `connecting`, `active`, `error` |
55
55
  | `status` | An authentication session | `signed-out`, `authenticating`, `authenticated`, `refreshing`, `expired` |
56
56
  | `status` | The agent’s break lifecycle | `not-requested`, `awaiting-approval`, `granted`, `starting-after-task`, `on-break` |
@@ -252,17 +252,10 @@ type AuthenticationContext = {
252
252
 
253
253
  type ListeningMode = "listen" | "coach" | "join-call";
254
254
 
255
- type TeamCapabilities = {
256
- breakControl?: true;
257
- leadAssistControl?: true;
258
- listeningControl?: ListeningMode[];
259
- policyControl?: true;
260
- };
261
-
262
255
  type UserCapabilities = {
263
256
  breaks?: true;
264
257
  preferences?: AgentPreference[];
265
- team?: TeamCapabilities;
258
+ lead?: true;
266
259
  };
267
260
 
268
261
  type AuthenticationState =
@@ -309,7 +302,7 @@ interface RecordingActions {
309
302
  }
310
303
  interface TaskRecordingPolicy {
311
304
  provider?: RecordingActions;
312
- host?: RecordingActions & { destinationId: string };
305
+ host?: RecordingActions & { storageId: string };
313
306
  }
314
307
  type RecordingState =
315
308
  | { status: "unknown"; observationId?: never; observedAt?: never; validUntil?: never; recordingId?: never }
@@ -333,7 +326,7 @@ interface HostRecordingReport {
333
326
  interface HostRecording {
334
327
  announcesToCaller?: true;
335
328
  actions: RecordingAction[];
336
- destinationIds: string[];
329
+ storageIds: string[];
337
330
  execute(request: HostRecordingRequest): Promise<RecordingCommandResult>;
338
331
  }
339
332
  type RecordingCommandResult = Exclude<TaskCommandResult, { status: "dialling" }>;
@@ -486,7 +479,7 @@ type Snapshot = {
486
479
  tasks: Task[];
487
480
  taskCount: number;
488
481
  contacts?: Contact[];
489
- scheduledActivities?: ScheduledActivity[];
482
+ calendar?: ScheduledActivity[];
490
483
  team?: TeamMembers;
491
484
  };
492
485
 
@@ -569,6 +562,7 @@ type TaskCapabilities<C extends Channel = Channel> =
569
562
  decline?: Lockable<true>;
570
563
  hold?: Lockable<true>;
571
564
  endCall?: Lockable<true>;
565
+ terminateCall?: Lockable<true>;
572
566
  connectBack?: Lockable<true>;
573
567
  coldTransfer?: Lockable<DestinationDirectory>;
574
568
  warmTransfer?: Lockable<DestinationDirectory>;
@@ -697,17 +691,8 @@ type TaskLeadAssist = { note?: string; since: IsoTimestamp } & (
697
691
  | { stage: "joined"; leadId: UserId }
698
692
  );
699
693
 
700
- type TaskAssisting = {
694
+ type TaskTakenOver = {
701
695
  memberId: UserId;
702
- assignmentId: AssignmentId;
703
- note?: string;
704
- since: IsoTimestamp;
705
- };
706
-
707
- type TaskListening = {
708
- memberId: UserId;
709
- assignmentId: AssignmentId;
710
- mode: ListeningMode;
711
696
  since: IsoTimestamp;
712
697
  };
713
698
 
@@ -725,9 +710,9 @@ type Locked = {
725
710
 
726
711
  type Lockable<T> = T | Locked;
727
712
 
728
- type TaskMediaState = "started" | "ended";
713
+ type TaskAudioState = "started" | "ended";
729
714
 
730
- type CapabilitySource = "queue" | "ungoverned" | "undetermined";
715
+ type CapabilitySource = "queue" | "nobody" | "not-yet-read";
731
716
 
732
717
  type Task<C extends Channel = Channel> = {
733
718
  assignmentId: AssignmentId;
@@ -747,11 +732,11 @@ type Task<C extends Channel = Channel> = {
747
732
  history?: TaskHistory;
748
733
  } & TaskCompletion & (
749
734
  C extends "voice"
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 }
735
+ ? { recording?: { provider?: RecordingState }; onCall?: OnCall[]; leadAssist?: TaskLeadAssist; takenOver?: TaskTakenOver; audio?: TaskAudioState }
736
+ : { recording?: never; onCall?: never; leadAssist?: never; takenOver?: never; audio?: never }
752
737
  );
753
738
 
754
- type PreviewDeadline = "calls" | "host-calls" | "waits";
739
+ type PreviewDeadline = "provider-dials" | "host-dials" | "waits";
755
740
 
756
741
  type AcceptanceMode =
757
742
  | "no-preference"
@@ -764,7 +749,6 @@ type TaskOutcome =
764
749
  | { type: "taken-over"; leadId: UserId }
765
750
  | { type: "cancelled"; by: "agent" | "provider" | "party"; reason?: string }
766
751
  | { type: "expired"; phase: "pending" | "confirmed" | "preview" }
767
- | { type: "left" }
768
752
  | { type: "failed"; failure: ProtocolFailure };
769
753
  ```
770
754
 
@@ -775,10 +759,11 @@ const TASK_COMMAND_NAMES = {
775
759
  voice: [
776
760
  "answer",
777
761
  "decline",
778
- "call",
762
+ "dial",
779
763
  "hold",
780
764
  "resume",
781
765
  "end-call",
766
+ "terminate-call",
782
767
  "connect-back",
783
768
  "transfer",
784
769
  "lead-assist",
@@ -798,10 +783,11 @@ type OutcomePayload = { outcome?: string; notes?: string };
798
783
  type VoiceTaskCommand =
799
784
  | { type: "answer" }
800
785
  | { type: "decline" }
801
- | { type: "call"; dialId: DialId }
786
+ | { type: "dial"; dialId: DialId }
802
787
  | { type: "hold" }
803
788
  | { type: "resume" }
804
789
  | { type: "end-call" }
790
+ | { type: "terminate-call" }
805
791
  | { type: "connect-back"; dialId: DialId }
806
792
  | { type: "transfer"; action: "cold"; dialId: DialId; destinationId: string }
807
793
  | { type: "transfer"; action: "warm"; dialId: DialId; destinationId: string }
@@ -809,8 +795,6 @@ type VoiceTaskCommand =
809
795
  | { type: "transfer"; action: "cancel" }
810
796
  | { type: "lead-assist"; action: "request"; note?: string }
811
797
  | { type: "lead-assist"; action: "cancel" }
812
- | { type: "lead-assist"; action: "take-over-call" }
813
- | { type: "lead-assist"; action: "leave" }
814
798
  | { type: "conference"; action: "add"; dialId: DialId; destinationId: string }
815
799
  | { type: "conference"; action: "remove"; destinationId: string; party?: never }
816
800
  | { type: "conference"; action: "remove"; party: true; destinationId?: never }
@@ -901,12 +885,12 @@ pass. An agent application never has the value and never asks. A name is not loc
901
885
  controls — is content, and is never locked.
902
886
 
903
887
  **A lead sets the team's policy from their team member list.** A login that declares
904
- `capabilities.team.policyControl` may `executeTeamPolicy({ type: "set", capability, setting })`
888
+ `capabilities.lead` may `executeTeam({ command: { type: "set-policy", capability, setting } })`, where the provider offers policy control and says so by publishing `policies`,
905
889
  with `on`, `off`, or `person`, for any task control, `dial`, or a skill — and only `hold`
906
890
  and skills may be `person`; connect back and new call are the team's, on or off, within what the queue
907
891
  allows. The team member list carries `policies` for such a login: every policy as it stands, who set it, and
908
892
  `lockedBy` where a level above the team made it theirs to keep, which the lead sees and cannot
909
- change — `executeTeamPolicy` on it answers `failed` with `omni.capability-not-enabled`.
893
+ change — `set-policy` on it answers `failed` with `omni.capability-not-enabled`.
910
894
 
911
895
  **What the team left to the person is the person's, and the provider keeps it.** The login's
912
896
  `capabilities.preferences` lists every preference the person may hold — `hold`, a skill —
@@ -984,26 +968,51 @@ type BreakEndResult =
984
968
  ### Team
985
969
 
986
970
  ```ts
987
- type TeamMemberAvailability = "ready" | "on-task" | "on-break" | "reserved" | "signed-out";
971
+ type TeamMemberAvailability = "ready" | "on-task" | "on-break" | "elsewhere" | "signed-out";
988
972
 
989
973
  type TeamMember = {
990
974
  id: UserId;
991
975
  availability: TeamMemberAvailability;
992
976
  since?: IsoTimestamp;
993
977
  break?: Extract<BreakStatus, "awaiting-approval" | "granted" | "starting-after-task">;
978
+ tasks?: MemberTask[];
979
+ listening?: MemberListening;
994
980
  };
995
981
 
996
982
  type LeadRequest = {
997
- id: string;
998
983
  memberId: UserId;
999
984
  assignmentId: AssignmentId;
1000
985
  note?: string;
1001
986
  since: IsoTimestamp;
1002
987
  };
1003
988
 
989
+ type MemberTask<C extends Channel = Channel> = {
990
+ assignmentId: AssignmentId;
991
+ title: string;
992
+ channel: C;
993
+ taskType: string;
994
+ phase: TaskPhase;
995
+ party?: Contact;
996
+ reference?: string;
997
+ attributes?: TaskAttribute[];
998
+ history?: TaskHistory;
999
+ capabilities?: TaskCapabilities<C>;
1000
+ capabilitySource?: CapabilitySource;
1001
+ browsers?: TaskBrowser[];
1002
+ completionMode?: CompletionMode;
1003
+ wrapAllowance?: DurationSeconds;
1004
+ } & (C extends "voice"
1005
+ ? { onCall?: OnCall[]; leadAssist?: TaskLeadAssist; takenOver?: TaskTakenOver; audio?: TaskAudioState }
1006
+ : { onCall?: never; leadAssist?: never; takenOver?: never; audio?: never });
1007
+
1008
+ type MemberListening = {
1009
+ mode: ListeningMode;
1010
+ since: IsoTimestamp;
1011
+ };
1012
+
1004
1013
  type TeamMembers = {
1005
1014
  members: TeamMember[];
1006
- requests?: LeadRequest[];
1015
+ requests: LeadRequest[];
1007
1016
  policies?: TeamPolicies;
1008
1017
  };
1009
1018
 
@@ -1022,15 +1031,18 @@ type TeamPolicy = Resolved & {
1022
1031
 
1023
1032
  type TeamPolicies = Partial<Record<PolicyKey, TeamPolicy>>;
1024
1033
 
1025
- type TeamPolicyCommand = { type: "set"; capability: PolicyKey; setting: TeamPolicySetting };
1034
+ type TeamPolicyCommand = { type: "set-policy"; capability: PolicyKey; setting: TeamPolicySetting };
1026
1035
 
1027
- type TeamPolicyCommandRequest = {
1028
- command: TeamPolicyCommand;
1029
- };
1036
+ type TeamFeatureCommand = { type: "lead-features"; enabled: boolean };
1030
1037
 
1031
- type TeamLeadAssistCommand =
1032
- | { type: "join"; requestId: string }
1033
- | { type: "decline"; requestId: string; reason?: string };
1038
+ type TeamCallCommand =
1039
+ | { type: "join"; memberId: UserId; assignmentId?: AssignmentId }
1040
+ | { type: "decline"; memberId: UserId; assignmentId?: AssignmentId; reason?: string }
1041
+ | { type: "listen"; memberId: UserId; assignmentId?: AssignmentId }
1042
+ | { type: "coach"; memberId: UserId; assignmentId?: AssignmentId }
1043
+ | { type: "join-call"; memberId: UserId; assignmentId?: AssignmentId }
1044
+ | { type: "leave"; memberId: UserId; assignmentId?: AssignmentId }
1045
+ | { type: "take-over-call"; memberId: UserId; assignmentId?: AssignmentId };
1034
1046
 
1035
1047
  type TeamBreakCommand =
1036
1048
  | { type: "decide-break-request"; memberId: UserId; decision: "granted" | "denied"; reason?: string }
@@ -1038,22 +1050,10 @@ type TeamBreakCommand =
1038
1050
  | { type: "force-break"; memberId: UserId; reasonId?: string; reason?: string; expectedDurationMs?: number }
1039
1051
  | { type: "end-forced-break"; memberId: UserId };
1040
1052
 
1041
- type TeamBreakCommandRequest = {
1042
- command: TeamBreakCommand;
1043
- };
1053
+ type TeamCommand = TeamFeatureCommand | TeamBreakCommand | TeamCallCommand | TeamPolicyCommand;
1044
1054
 
1045
- type TeamLeadAssistCommandRequest = {
1046
- command: TeamLeadAssistCommand;
1047
- };
1048
-
1049
- type TeamListenCommand =
1050
- | { type: "listen"; memberId: UserId }
1051
- | { type: "coach" }
1052
- | { type: "join-call" }
1053
- | { type: "leave" };
1054
-
1055
- type TeamListenCommandRequest = {
1056
- command: TeamListenCommand;
1055
+ type TeamCommandRequest = {
1056
+ command: TeamCommand;
1057
1057
  };
1058
1058
 
1059
1059
  type TeamCommandResult =
@@ -1061,20 +1061,20 @@ type TeamCommandResult =
1061
1061
  | { status: "failed"; failure: ProtocolFailure };
1062
1062
  ```
1063
1063
 
1064
- ### Media
1064
+ ### Audio
1065
1065
 
1066
1066
  ```ts
1067
- type VoiceMediaSession = {
1067
+ type CallAudio = {
1068
1068
  remoteAudio: MediaStream;
1069
1069
  setMuted(muted: boolean): void;
1070
1070
  close(): void;
1071
1071
  };
1072
1072
 
1073
- type OpenMediaResult =
1074
- | { status: "opened"; session: VoiceMediaSession }
1073
+ type OpenAudioResult =
1074
+ | { status: "opened"; audio: CallAudio }
1075
1075
  | { status: "unavailable"; failure: ProtocolFailure };
1076
1076
 
1077
- type OpenMediaRequest = {
1077
+ type OpenAudioRequest = {
1078
1078
  assignmentId: AssignmentId;
1079
1079
  localAudio?: MediaStream;
1080
1080
  };
@@ -1106,8 +1106,10 @@ type ProviderEvent =
1106
1106
  assignmentExpiresAt?: IsoTimestamp;
1107
1107
  }
1108
1108
  | { type: "task-updated"; task: Task }
1109
- | { type: "task-media-started"; assignmentId: AssignmentId }
1110
- | { type: "task-media-ended"; assignmentId: AssignmentId }
1109
+ | { type: "task-audio-started"; assignmentId: AssignmentId }
1110
+ | { type: "task-audio-ended"; assignmentId: AssignmentId }
1111
+ | { type: "team-audio-started"; memberId: UserId; assignmentId: AssignmentId }
1112
+ | { type: "team-audio-ended"; memberId: UserId; assignmentId: AssignmentId }
1111
1113
  | { type: "task-ended"; assignmentId: AssignmentId; outcome: TaskOutcome }
1112
1114
  | { type: "dial-outcome"; dialId: DialId; outcome: DialOutcome; assignmentId?: AssignmentId; destinationId?: string; reason?: string }
1113
1115
  | { type: "announcement"; text: string; html?: string; announcedAt: IsoTimestamp; expiresAt?: IsoTimestamp }
@@ -1115,7 +1117,7 @@ type ProviderEvent =
1115
1117
  | { type: "diagnostic"; expected: string; observed: string; assignmentId?: AssignmentId }
1116
1118
  | { type: "team-updated"; team: TeamMembers }
1117
1119
  | { type: "contacts-updated"; contacts: Contact[] }
1118
- | { type: "calendar-updated"; scheduledActivities: ScheduledActivity[] };
1120
+ | { type: "calendar-updated"; calendar: ScheduledActivity[] };
1119
1121
 
1120
1122
  type ProviderEventEnvelope = {
1121
1123
  id: string;
@@ -1169,13 +1171,10 @@ type Connection<C extends Channel = Channel> = {
1169
1171
  cancelBreak?(): Promise<BreakCancelResult>;
1170
1172
  endBreak?(): Promise<BreakEndResult>;
1171
1173
 
1172
- executeTeamBreak?(request: TeamBreakCommandRequest): Promise<TeamCommandResult>;
1173
- executeTeamLeadAssist?(request: TeamLeadAssistCommandRequest): Promise<TeamCommandResult>;
1174
- executeTeamListen?(request: TeamListenCommandRequest): Promise<TeamCommandResult>;
1175
- openMedia?(request: OpenMediaRequest): Promise<OpenMediaResult>;
1174
+ executeTeam?(request: TeamCommandRequest): Promise<TeamCommandResult>;
1175
+ openAudio?(request: OpenAudioRequest): Promise<OpenAudioResult>;
1176
1176
  setPreference?(request: SetPreferenceRequest): Promise<PreferenceResult>;
1177
1177
  recordStep?(report: HistoryReport): Promise<HistoryReportResult>;
1178
- executeTeamPolicy?(request: TeamPolicyCommandRequest): Promise<TeamCommandResult>;
1179
1178
  };
1180
1179
 
1181
1180
  type Adapter<C extends Channel = Channel> = {
@@ -1532,7 +1531,7 @@ Request/response polling does not have those properties and is not a transport f
1532
1531
 
1533
1532
  Everything an adapter publishes about a task is derived from what the platform states, or kept in
1534
1533
  the login's store where the platform cannot hold it. Per offer, the `assignmentId` comes from what
1535
- the offer itself states; per call, the media follows `task-media-started` and `task-media-ended`,
1534
+ the offer itself states; per call, the audio follows `task-audio-started` and `task-audio-ended`,
1536
1535
  never a flag set when the audio was opened; per session, the phone the agent holds is what the
1537
1536
  platform restates on activation, not a mapping read once over HTTP. What the platform cannot hold
1538
1537
  -- an agent application's muted leg -- goes in `ConnectContext.store`, keyed by the task and gone with it.
@@ -1540,11 +1539,11 @@ platform restates on activation, not a mapping read once over HTTP. What the pla
1540
1539
  A fact read once over a connection and kept in the adapter object dies with the client. An agent application
1541
1540
  reload is the first client dying and a second coming up for the same login, and the second
1542
1541
  publishes the same truth about the same task as the first did, or the first was publishing
1543
- something it made up: an assignment minted for the connection, media remembered as a boolean, a
1542
+ something it made up: an assignment minted for the connection, audio remembered as a boolean, a
1544
1543
  phone the second never read. Three pieces of exactly that state were found in one adapter in an
1545
1544
  afternoon, and none of it was visible until the adapter was built twice. The drive's `rebuild` is the test of this
1546
1545
  principle: given a way to build the adapter again, the run hands the login over and holds the
1547
- second to what the first published (`drive.reload.snapshot`, `.history`, `.openMedia`, and the
1546
+ second to what the first published (`drive.reload.snapshot`, `.history`, `.openAudio`, and the
1548
1547
  stream's own rules across the resync). An adapter that cannot be built twice against its platform
1549
1548
  and publish the same task is not a conformant adapter yet, whatever a single run says.
1550
1549
 
@@ -1611,7 +1610,7 @@ compile time.
1611
1610
  | `timeCheck` | Optional `true`: implements `checkTime` for fresh provider-clock samples; agent application polling is independently opt-in. |
1612
1611
  | `timestampAuthority` | Optional `"provider"`: provider timestamps are final; agent application instants are advisory. Omission makes no trust promise. |
1613
1612
  | `runningStepReports` | The provider takes running reports of an agent application-performed step — `recordStep` with `seconds` so far and no `ended`. Omitted, the agent application sends exactly two reports per leg, when it began and when it ended, and a running one is refused. See **The agent application records what it performs**. |
1614
- | `completionSettleMs` | Required. How long after an applied completion -- `complete`, or a lead's `take-over-call` -- the provider's `task-ended` is owed, a positive whole number of milliseconds (`manifest.completionSettleMs`). A warm transfer's `complete` is not final task completion: the agent's wrap runs after it. Stated per provider, since platforms settle at different speeds. See **`task-ended`**. |
1613
+ | `completionSettleMs` | Required. How long after an applied `complete` the provider's `task-ended` is owed, a positive whole number of milliseconds (`manifest.completionSettleMs`). A warm transfer's `complete` is not final task completion: the agent's wrap runs after it. Stated per provider, since platforms settle at different speeds. See **`task-ended`**. |
1615
1614
 
1616
1615
  ### Authentication methods
1617
1616
 
@@ -1754,7 +1753,7 @@ contributions from active providers into one agent-facing calendar while retaini
1754
1753
  provider identity for each activity.
1755
1754
 
1756
1755
  When declared, the provider publishes its authoritative list through
1757
- `Snapshot.scheduledActivities` and replaces it with a `calendar-updated` event when it
1756
+ `Snapshot.calendar` and replaces it with a `calendar-updated` event when it
1758
1757
  changes.
1759
1758
 
1760
1759
  | Field | Contract |
@@ -1912,20 +1911,15 @@ replaces the login" describes is a new `AuthenticationSession` under a new `logi
1912
1911
  ### What the login may do
1913
1912
 
1914
1913
  `capabilities` declares provider actions available to this login rather than to one task: whether
1915
- the agent may ask for a break, and whether they lead a team — and if so, whether they decide its
1916
- breaks and whether they may join a member's call. It is declared by presence, like every capability
1917
- in this contract, and it travels with the identity because it is part of who the agent is on this
1918
- provider: the provider knows the roles, and says so at sign-in rather than leaving Omni to infer
1919
- them from what arrives later.
1914
+ the agent may ask for a break, and whether they lead a team. It is declared by presence, like every
1915
+ capability in this contract, and it travels with the identity because it is part of who the agent
1916
+ is on this provider: the provider knows the roles, and says so at sign-in rather than leaving Omni
1917
+ to infer them from what arrives later.
1920
1918
 
1921
1919
  | Field | Contract |
1922
1920
  | --- | --- |
1923
1921
  | `breaks` | This login may request a break. Requires the four break methods on the connection. |
1924
- | `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. |
1925
- | `team.breakControl` | This lead may act on their team's breaks through `executeTeamBreak` — force-break, end-forced-break, decide-break-request, set-break-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-approval`, so a provider that grants on request is never asked to decide. Requires `executeTeamBreak`. |
1926
- | `team.leadAssistControl` | This lead may join a member's call on request. Requires `executeTeamLeadAssist`. |
1927
- | `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**. |
1928
- | `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`. |
1922
+ | `lead` | This login leads a team. The flag alone turns on the team feature in the agent application, and every lead act comes with it; there is no per-action permission beside it. While the lead has the feature on, the provider publishes a `TeamMembers` object to them on every snapshot — `members: []` when nobody is in it — and to nobody else. Requires `executeTeam`. See **Team leads**. |
1929
1923
  | `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**. |
1930
1924
 
1931
1925
  A session action is available only when both the capability and Omni local policy permit it.
@@ -2133,13 +2127,13 @@ a capability it agrees with the login: a lead's snapshot carries `team`, nobody
2133
2127
  | `tasks` | Complete set of tasks currently offered to or owned by this agent. |
2134
2128
  | `taskCount` | The provider's own count of those tasks, stated rather than inferred, and it must equal `tasks.length`. A snapshot with no work says `taskCount: 0` in so many words — a blank or unanswered state lacks the count and cannot pass as a confirmed empty. |
2135
2129
  | `contacts` | Required complete contact contribution when the manifest declares `contacts`; `[]` clears it. Omitted only when it does not. |
2136
- | `scheduledActivities` | Required complete calendar contribution when the manifest declares `calendar`; `[]` clears it. Omitted only when it does not. |
2137
- | `team` | Required `TeamMembers` when the login declares `capabilities.team`, `members: []` when nobody is in it. Forbidden otherwise — the login is the permission. |
2130
+ | `calendar` | Required complete calendar contribution when the manifest declares `calendar`; `[]` clears it. Omitted only when it does not. |
2131
+ | `team` | Required `TeamMembers` when the login declares `capabilities.lead` and the lead has the team feature on, `members: []` when nobody is in it. Forbidden otherwise — the login is the permission, and a lead who turned the feature off gets nothing of the team. |
2138
2132
 
2139
2133
  ## Live connection
2140
2134
 
2141
2135
  `Connection` is what `connect()` returns. Its methods are documented in the sections that follow
2142
- and under **Breaks**, **Team leads**, **Real-time media** and **Task commands**; this is the whole
2136
+ and under **Breaks**, **Team leads**, **Real-time audio** and **Task commands**; this is the whole
2143
2137
  surface in one place, and what obliges an adapter to implement each one.
2144
2138
 
2145
2139
  | Method | Implement it when |
@@ -2157,13 +2151,10 @@ surface in one place, and what obliges an adapter to implement each one.
2157
2151
  | `commitBreak()` | The login declares `capabilities.breaks`. Commit and cancel are not optional halves of it. |
2158
2152
  | `cancelBreak()` | The login declares `capabilities.breaks`. |
2159
2153
  | `endBreak()` | The login declares `capabilities.breaks`. |
2160
- | `executeTeamBreak(command)` | The login declares `capabilities.team.breakControl`. |
2161
- | `executeTeamLeadAssist(command)` | The login declares `capabilities.team.leadAssistControl`. |
2162
- | `executeTeamListen(command)` | The login declares `capabilities.team.listeningControl`. |
2154
+ | `executeTeam(request)` | The login declares `capabilities.lead`: every lead act, on the team surface and nowhere else. See **Lead commands**. |
2163
2155
  | `setPreference(request)` | The login declares `capabilities.preferences`: the person's choice has to have somewhere to go. |
2164
2156
  | `recordStep(report)` | The manifest lists `softphone` among its `phones`. On a softphone the agent application 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 agent application records what it performs**. |
2165
- | `executeTeamPolicy(command)` | The login declares `capabilities.team.policyControl`. |
2166
- | `openMedia(request)` | The manifest lists `softphone` among its `phones`. On a softphone the call's audio lands in Omni, so the adapter has to open it; a platform of desk phones alone never does. |
2157
+ | `openAudio(request)` | The manifest lists `softphone` among its `phones`. On a softphone the call's audio lands in Omni, so the adapter has to open it; a platform of desk phones alone never does. |
2167
2158
 
2168
2159
  **The four break methods stand or fall together.** Declaring `capabilities.breaks` at login and then
2169
2160
  implementing `requestBreak` without `commitBreak` leaves an agent granted a break that can never
@@ -2177,7 +2168,7 @@ Returns the provider's complete authoritative state at one point in time.
2177
2168
  - Omni registers `subscribe()` before awaiting the initial snapshot. An event delivered while the
2178
2169
  snapshot is read is held until it lands, and what happens to it then depends on what it is. A
2179
2170
  snapshot restates state, so an event of a state-replacing kind -- `task-offered`, `task-updated`,
2180
- `task-ended`, `task-media-started`, `task-media-ended`, `break-state`, `team-updated`,
2171
+ `task-ended`, `task-audio-started`, `task-audio-ended`, `break-state`, `team-updated`,
2181
2172
  `contacts-updated`, `calendar-updated`, `transport-status`, `snapshot` -- is dropped, because the
2182
2173
  snapshot accounts for it. An event that reports a transaction no snapshot carries --
2183
2174
  `dial-outcome`, `diagnostic`, `announcement`, `queue-summary` -- is applied after the snapshot,
@@ -2233,7 +2224,7 @@ Stops the connection and releases adapter-owned resources.
2233
2224
 
2234
2225
  - Must be safe after partial startup and safe to call once during normal shutdown.
2235
2226
  - Must stop automatic reconnect.
2236
- - Must remove event handlers and release media resources owned by the adapter.
2227
+ - Must remove event handlers and release audio resources owned by the adapter.
2237
2228
  - Does not imply that active tasks were completed or removed.
2238
2229
 
2239
2230
  ## Task assignment lifecycle
@@ -2295,7 +2286,7 @@ An automatically accepted task still arrives through `task-offered`.
2295
2286
 
2296
2287
  **Work the agent originated is accepted by the command that created it.** A task born of the
2297
2288
  agent's own act — a dialpad call or a connect-back, recognisable by the agent application's `dialId` on
2298
- `onCall`; a lead's join, carrying `assisting`; a lead's listen, carrying `listening` — arrives
2289
+ `onCall`; a call a lead took over, carrying `takenOver` — arrives
2299
2290
  through `task-offered` with `acceptance: "automatic"` whatever `autoAcceptTasks` says, and the
2300
2291
  validator holds it there under either local policy and under none (`task.acceptance.originated`):
2301
2292
  the desk shows no **Accept** for a call the agent placed. The local policy governs work the queue
@@ -2319,7 +2310,7 @@ const assignment = {
2319
2310
  ```
2320
2311
 
2321
2312
  The rule the phase exists to express: **nothing is acquired on the agent's behalf while a task
2322
- is pending.** An agent application that carries media must not open the microphone until the task is
2313
+ is pending.** An agent application that carries audio must not open the microphone until the task is
2323
2314
  accepted. Omni does not open the task's browsers either — a task that rings out costs nothing.
2324
2315
 
2325
2316
  When consent is required, Omni offers the agent an **Accept** control. The call is the
@@ -2371,28 +2362,27 @@ time. Runtime conformance checks also require the task channel to match its prov
2371
2362
 
2372
2363
  | Field | Contract |
2373
2364
  | --- | --- |
2374
- | `assignmentId` | Required `AssignmentId`: the assignment this task is the record of, and its one identity. Issued by the provider, unique within the provider, never reused. Every event, command and report that names a task names it by this, so a late dial outcome or a late history report for one customer never lands on the next. The stream refuses an assignment introduced twice (`stream.taskOffered.duplicate`) and a media or ending event naming one that has ended or that nobody has seen (`stream.assignment.ended`, `.unknown`); a `dial-outcome` may name an ended assignment, since a dial placed late routinely outlives its call, and the agent application routes it there. See **A task's life on the wire**. |
2365
+ | `assignmentId` | Required `AssignmentId`: the assignment this task is the record of, and its one identity. Issued by the provider, unique within the provider, never reused. Every event, command and report that names a task names it by this, so a late dial outcome or a late history report for one customer never lands on the next. The stream refuses an assignment introduced twice (`stream.taskOffered.duplicate`) and an audio or ending event naming one that has ended or that nobody has seen (`stream.assignment.ended`, `.unknown`); a `dial-outcome` may name an ended assignment, since a dial placed late routinely outlives its call, and the agent application routes it there. See **A task's life on the wire**. |
2375
2366
  | `title` | Agent-facing task title. |
2376
2367
  | `channel` | Channel used for this task. It must equal the source provider's manifest channel. |
2377
2368
  | `taskType` | Required provider-defined source or category of work, such as a voice `Queue Name`, `Mailbox Folder`, `Chat Source`, `Support`, `Billing`, or `Returns`. |
2378
2369
  | `capabilities` | Controls and workspace features available for this specific task. |
2379
- | `capabilitySource` | Required. Who chose the capabilities: `queue` when somebody configured these terms, `ungoverned` when nothing handed the work over -- an agent's own outbound -- and `undetermined` when a queue was named and its terms could not be read, in which case `capabilities` is what the provider will honour, not what the platform permits. An agent application shows `undetermined` where the agent works. See **Task capabilities**. |
2370
+ | `capabilitySource` | Required. Who chose the capabilities: `queue` when somebody configured these terms, `nobody` when nothing handed the work over -- an agent's own outbound -- and `not-yet-read` when a queue was named and its terms could not be read, in which case `capabilities` is what the provider will honour, not what the platform permits. An agent application shows `not-yet-read` where the agent works. See **Task capabilities**. |
2380
2371
  | `browsers` | Named browser definitions for the task workspace: at least one when the task declares the `browsers` capability, empty when it does not. |
2381
2372
  | `party` | The person or entity on the other end of this task, as a `Contact`: often a name and one address; a withheld caller ID may leave nothing to send at all. Optional. The party is who the task is *with*; `contacts` is the directory. |
2382
2373
  | `phase` | Current canonical task phase: `pending`, `confirmed`, `preview`, `in-progress`, `paused`, or `completing`. `preview` is voice only. |
2383
- | `media` | Voice only. The task's real-time audio as the provider holds it: `started` while audio is attached, `ended` once it ended, omitted while none is. The provider's word — see **`task-media-started`**. Media names a task whose work has begun, or whose party the agent application is dialling: on a `pending`, `confirmed` or `preview` task with nobody ringing it is refused (`task.media.beforeWork`), on a snapshot as on the event, since an agent application opens the microphone on it; with the party ringing by an agent application dial or provider-triggered preview dial, actual ring-back media may precede answer. |
2374
+ | `audio` | Voice only. The task's real-time audio as the provider holds it: `started` while audio is attached, `ended` once it ended, omitted while none is. The provider's word — see **`task-audio-started`**. Audio names a task whose work has begun, or whose party the agent application is dialling: on a `pending`, `confirmed` or `preview` task with nobody ringing it is refused (`task.audio.beforeWork`), on a snapshot as on the event, since an agent application opens the microphone on it; with the party ringing by an agent application dial or provider-triggered preview dial, actual ring-back audio may precede answer. |
2384
2375
  | `acceptance` | How this offer is accepted — `no-preference`, `consent`, or `automatic` — stated on the pending task so a reconnect snapshot says it too. Required while `pending` when `autoAcceptTasks` was `true`, forbidden when it was `false`, and absent past `pending`. See **Acceptance modes**. |
2385
2376
  | `previewEndsAt` | Voice only, in `preview`: the preparation target instant. Absent, the agent has as long as they need without a preparation countdown. Always with `atDeadline`. See **Preview: the agent presses Call**. |
2386
- | `atDeadline` | Voice only, in `preview`, with `previewEndsAt`: what the system does at the deadline -- `calls` makes the provider initiate dialing, `host-calls` makes the agent application issue Call, `waits` keeps the task in preview awaiting the agent. |
2377
+ | `atDeadline` | Voice only, in `preview`, with `previewEndsAt`: what the system does at the deadline -- `provider-dials` makes the provider initiate dialing, `host-dials` makes the agent application issue Call, `waits` keeps the task in preview awaiting the agent. |
2387
2378
  | `reference` | Optional agent-facing reference such as a case, call, conversation, ticket, or message number. It is distinct from the protocol `id`. |
2388
2379
  | `completionMode` | `agent-command` waits for the channel's `complete` command; `provider-automatic` completes without one. |
2389
- | `wrapAllowance` | Fixed time allowed to complete the task after primary interaction ends. For real-time media, it begins after `task-media-ended`. Required under `provider-automatic`, where the provider acts on it. Optional under `agent-command`: omitted says the provider imposes no deadline, and Omni counts nothing down. |
2380
+ | `wrapAllowance` | Fixed time allowed to complete the task after primary interaction ends. For real-time audio, it begins after `task-audio-ended`. Required under `provider-automatic`, where the provider acts on it. Optional under `agent-command`: omitted says the provider imposes no deadline, and Omni counts nothing down. |
2390
2381
  | `attributes` | Optional ordered, typed `TaskAttribute` entries with keys unique within the task. Each contact or timestamp is a separate array item; new attribute shapes require new union members. |
2391
2382
  | `history` | The call record: `steps` — the ordered interaction history of this open task, one entry per occurrence, oldest first — and what they add up to before this agent, `interactionSeconds`, `holdSeconds`, `queueSeconds`, `transfers`, each present when the provider knows it. Live task data restated with the task, not a permanent archive. See **Interaction history**. |
2392
2383
  | `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 agent application'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 an agent application 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 agent application supplies the verb. Present when the provider knows the room, absent when it does not. See **Every dial has an outcome**. |
2393
2384
  | `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**. |
2394
- | `assisting` | Voice only, on the lead's own task for a call they joined: which member asked, the member's `assignmentId`, and their note. The lead's task has an assignment of its own, so the member's is named, or the copy no longer says which call it joined. Its presence is what makes `lead-assist` `take-over-call` and `leave` issuable. See **Lead assist**. |
2395
- | `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**. |
2385
+ | `takenOver` | Voice only, on a task that reached this agent by a lead's take-over: which member the call was taken from, and when. It is an ordinary assignment with the call's history, offered whatever break the lead is on and counted against no capacity. See **Lead assist**. |
2396
2386
 
2397
2387
  `TaskAttribute` entries carry typed detail alongside the task:
2398
2388
 
@@ -2424,7 +2414,7 @@ The canonical task transitions are:
2424
2414
  | `pending` | Task is accepted | `confirmed` |
2425
2415
  | `confirmed` | The customer's record is put in front of the agent before any call goes out (voice) | `preview` |
2426
2416
  | `confirmed` | Work begins | `in-progress` |
2427
- | `preview` | Agent presses Call (`call`) and the customer answers, or the deadline `calls` and they answer | `in-progress` |
2417
+ | `preview` | Agent presses Call (`dial`) and the customer answers, or the deadline `provider-dials` and they answer | `in-progress` |
2428
2418
  | `preview` | The call goes out and nobody answers | `completing` |
2429
2419
  | `preview` | The preparation target passes with `atDeadline: "waits"` | Remains `preview`, waiting for the agent to press Call |
2430
2420
  | `pending` | Provider withdraws the assignment, the agent declines, or the party abandons the ring | Removed by `task-ended` with `cancelled` outcome, `by` saying which |
@@ -2443,7 +2433,7 @@ The canonical task transitions are:
2443
2433
  A task has one name, the assignment it is the record of, and the provider issues it. Platforms
2444
2434
  reuse their own handles: a closed one is retired minutes later, a requeue takes seconds, and on
2445
2435
  one platform a call was offered twice, thirteen seconds apart, under one handle, through a close
2446
- and a re-offer. Everything that names a task after the fact -- a dial outcome, a media event, an
2436
+ and a re-offer. Everything that names a task after the fact -- a dial outcome, an audio event, an
2447
2437
  ending, a history report, a command -- would land on whichever assignment is open when it arrives.
2448
2438
  So the assignment id is unique within the provider and never reused: where the platform's handle
2449
2439
  never comes back, the adapter passes it through; where it does, the adapter mints the assignment id
@@ -2452,7 +2442,7 @@ belongs to or is refused, and a task-scoped browser session (`PROVIDER_NAME__ASS
2452
2442
  lives one assignment, never the next customer's cookies. A dial the agent application placed was
2453
2443
  already safe, since its outcome is placed by the agent application's own `dialId`; where the
2454
2444
  assignment earns its place is everything the agent application does not mint --
2455
- `task-media-started`, `task-media-ended`, `task-ended`, a `recordStep` naming a task -- any of which
2445
+ `task-audio-started`, `task-audio-ended`, `task-ended`, a `recordStep` naming a task -- any of which
2456
2446
  would otherwise find the next customer under a reused handle and act on them. A minted id is part
2457
2447
  of the task the adapter keeps, not a field held in memory beside it: it lives in the login's
2458
2448
  `store` with the task, or is derived from a platform handle that itself never reuses, so a rebuilt
@@ -2479,19 +2469,19 @@ const previewed = {
2479
2469
  phase: "preview",
2480
2470
  party: { name: "Maya Rao", number: "+919876543210" },
2481
2471
  previewEndsAt: "2026-08-25T10:40:37.000Z",
2482
- atDeadline: "calls",
2472
+ atDeadline: "provider-dials",
2483
2473
  } satisfies Pick<Task<"voice">, "channel" | "capabilities" | "phase" | "party" | "previewEndsAt" | "atDeadline">;
2484
2474
 
2485
2475
  // The agent presses Call. It is a dial like any other.
2486
- const pressed: TaskCommand<"voice"> = { type: "call", dialId: "dial-7f2" };
2476
+ const pressed: TaskCommand<"voice"> = { type: "dial", dialId: "dial-7f2" };
2487
2477
  ```
2488
2478
 
2489
2479
  **Call is a dial.** It carries the agent application's `dialId`, is answered `dialling`, and ends in exactly one
2490
2480
  `dial-outcome`; the phase is its gate and there is no capability, since a record put in front of an
2491
- agent is there to be called. Its media starts on `dialling`, as every agent application-placed dial's does --
2492
- ring-back is audio the agent hears -- so `task-media-started` arrives on the `preview` task with
2493
- its party ringing. On `answered` the task is `in-progress`; on any other outcome the media ends,
2494
- `task-media-ended` starts the wrap clock as on every voice task, and the task goes to
2481
+ agent is there to be called. Its audio starts on `dialling`, as every agent application-placed dial's does --
2482
+ ring-back is audio the agent hears -- so `task-audio-started` arrives on the `preview` task with
2483
+ its party ringing. On `answered` the task is `in-progress`; on any other outcome the audio ends,
2484
+ `task-audio-ended` starts the wrap clock as on every voice task, and the task goes to
2495
2485
  `completing`, so the agent records the
2496
2486
  no-answer as the outcome it is -- in a campaign that is the commonest outcome there is, and it
2497
2487
  is work, not a cancellation. A `preview` task therefore needs a manifest that says how a dial ends
@@ -2503,8 +2493,8 @@ carries the provider's authoritative preparation-end instant and exactly one tri
2503
2493
 
2504
2494
  | Declaration | At preparation end |
2505
2495
  | --- | --- |
2506
- | `atDeadline: "calls"` | The provider initiates dialing. The agent application never sends a timer-triggered Call. |
2507
- | `atDeadline: "host-calls"` | The agent application sends the ordinary `call` command with a fresh agent application `dialId`. The provider does not independently auto-dial this task. |
2496
+ | `atDeadline: "provider-dials"` | The provider initiates dialing. The agent application never sends a timer-triggered Call. |
2497
+ | `atDeadline: "host-dials"` | The agent application sends the ordinary `dial` command with a fresh agent application `dialId`. The provider does not independently auto-dial this task. |
2508
2498
  | `atDeadline: "waits"` | Neither side auto-dials. The task remains in preview until the agent presses Call. |
2509
2499
 
2510
2500
  The agent may press Call early in either fixed-preparation dialing mode, or at any time in
@@ -2512,7 +2502,7 @@ The agent may press Call early in either fixed-preparation dialing mode, or at a
2512
2502
  zero the UI says "Waiting for agent" and keeps Call available. There is no automatic task-ended,
2513
2503
  completion, dialing or phase change. No repeating timer action occurs on later snapshots. This differs
2514
2504
  from unlimited preparation only by displaying a preparation target. The former `expires` deadline
2515
- value is not supported; elapsed preparation must not withdraw the task. For `calls` and `host-calls`, the deadline ends
2505
+ value is not supported; elapsed preparation must not withdraw the task. For `provider-dials` and `host-dials`, the deadline ends
2516
2506
  preparation and triggers initiation; it does not promise ringing, playable audio or customer
2517
2507
  answer at that exact instant. Scheduling/dispatch delay must remain visible, and a failed or
2518
2508
  prevented initiation must be reported rather than leaving a silently expired countdown.
@@ -2523,7 +2513,7 @@ time; clock estimation is allowed only under the explicit agent application-trig
2523
2513
  Without usable provider-domain time
2524
2514
  it cannot safely auto-trigger and must expose the uncertainty and reconcile. No zero-duration,
2525
2515
  receipt-time or local-default deadline is inferred. Neither elapsed time nor submission changes
2526
- the task phase: subsequent authoritative events state ringing, answer, media and completion.
2516
+ the task phase: subsequent authoritative events state ringing, answer, audio and completion.
2527
2517
 
2528
2518
  An agent application must serialize manual clicks and its timer under the exact provider/login/task/assignment
2529
2519
  scope, allowing at most one unresolved submission. Recheck the current phase, deadline owner,
@@ -2537,11 +2527,11 @@ command as ownership of an already-running independent attempt.
2537
2527
  Agent application-triggered automatic dialing is an ordinary agent application dial: its result is `dialling` with the same
2538
2528
  agent application `dialId`, followed by exactly one correlated terminal `dial-outcome`. A provider-triggered
2539
2529
  dial has no invented agent application ID or agent application dial outcome. It reports source-evidenced party ringing,
2540
- actual media and customer answer separately. In preview with `atDeadline: "calls"`, a ringing
2541
- party without an agent application ID may carry actual pre-answer media; the deadline alone permits no media.
2542
- Media is still introduced/ended by the corresponding events and must not imply customer answer.
2530
+ actual audio and customer answer separately. In preview with `atDeadline: "provider-dials"`, a ringing
2531
+ party without an agent application ID may carry actual pre-answer audio; the deadline alone permits no audio.
2532
+ Audio is still introduced/ended by the corresponding events and must not imply customer answer.
2543
2533
  Submission acceptance is never an answered outcome or permission to publish `in-progress` early.
2544
- Unsupported source correlation/outcome/media evidence remains an integration blocker.
2534
+ Unsupported source correlation/outcome/audio evidence remains an integration blocker.
2545
2535
 
2546
2536
  **Drop both fields when the phase moves.** A provider that builds the `in-progress` task by
2547
2537
  spreading the `preview` one carries `previewEndsAt` and `atDeadline` with it, and the agent application refuses
@@ -2559,7 +2549,7 @@ recording contract; this distinction does not remove it or move provider call op
2559
2549
 
2560
2550
  A caller journey may revisit the same agent while an earlier interaction still wraps. Each is its
2561
2551
  own assignment, and two open at once carry two assignment ids. Restore them unchanged on
2562
- reconnect. Commands, media, endings and history reports name the exact assignment; never retarget
2552
+ reconnect. Commands, audio, endings and history reports name the exact assignment; never retarget
2563
2553
  an old command to the newest interaction of a journey. `validateTaskCommandRequest` checks the
2564
2554
  request's assignment against the supplied published task and then its command prerequisites. The
2565
2555
  caller must select that task within the correct provider/login and recheck at the provider; the
@@ -2569,33 +2559,33 @@ portion must not be cleared because this task ended. Snapshot counts include all
2569
2559
  and active interactions; task completion is not caller hangup.
2570
2560
 
2571
2561
  **Voice describes the agent's interaction within the wider call.** The task defines that agent's
2572
- workspace, tools, permissions, media participation and completion work. The caller's channel may
2562
+ workspace, tools, permissions, audio participation and completion work. The caller's channel may
2573
2563
  continue through IVR, queues, other agents, holds or conferences while this interaction ends or wraps.
2574
2564
  Completing this task ends this interaction responsibility; it is not evidence that the caller's channel
2575
- or journey ended. Task media describes this agent's attachment, not the lifetime of every party's
2565
+ or journey ended. Task audio describes this agent's attachment, not the lifetime of every party's
2576
2566
  connection. Commands retain their explicit targets and effects; task completion must not silently
2577
2567
  become a caller-disconnect operation.
2578
2568
 
2579
2569
  **A task is never its audio.** A voice task represents the agent’s interaction: the call is offered when it is
2580
2570
  routed to the agent and accepted as its `acceptance` dictates, and its presence and phase follow
2581
2571
  the provider's reports about the work — never the audio. Wherever audio moves — an offer, a hold, a
2582
- consult, a conference leg joining or leaving, a transfer, a connect-back — the media follows
2583
- separately, arriving on `task-media-started`, attaching through `openMedia` and ending with
2584
- `task-media-ended`. Omni does not ring,
2572
+ consult, a conference leg joining or leaving, a transfer, a connect-back — the audio follows
2573
+ separately, arriving on `task-audio-started`, attaching through `openAudio` and ending with
2574
+ `task-audio-ended`. Omni does not ring,
2585
2575
  bridge, or hold a line. How the phone rings, whether it rings at all, and where legs join and leave
2586
2576
  are the adapter's and the platform's, transient, and decide neither when a task exists nor what
2587
2577
  phase it is in.
2588
2578
 
2589
- The line runs between the provider's word and Omni's own senses. `task-media-ended` is the
2579
+ The line runs between the provider's word and Omni's own senses. `task-audio-ended` is the
2590
2580
  provider's report that primary interaction ended — a fact about the work, which is why the completion
2591
2581
  allowance starts on it and the Connect back control appears on it — and Omni follows that report as it
2592
- follows any other. What Omni never does is derive a task's state from its own media session: a
2582
+ follows any other. What Omni never does is derive a task's state from its own audio session: a
2593
2583
  stream that drops, a track that ends, a transport that disconnects, a microphone that fails, an
2594
2584
  endpoint re-registering change nothing about the task until the provider says so. Structurally:
2595
- `task-media-started` and `task-media-ended` alternate on a task whose work has begun, media ends
2596
- only where it arrived, what follows the media ending is `completing` or `task-ended`, and every
2585
+ `task-audio-started` and `task-audio-ended` alternate on a task whose work has begun, audio ends
2586
+ only where it arrived, what follows the audio ending is `completing` or `task-ended`, and every
2597
2587
  task is introduced once — `exerciseAdapter` holds the stream to that from the connect snapshot on,
2598
- and `assertMediaFollowsTheTask` holds any sequence.
2588
+ and `assertAudioFollowsTheTask` holds any sequence.
2599
2589
 
2600
2590
  #### Completion timing
2601
2591
 
@@ -2604,16 +2594,16 @@ the task open until Omni sends the channel's `complete` command. With `provider-
2604
2594
  provider may complete the task without receiving that command.
2605
2595
 
2606
2596
  `wrapAllowance` is independent of that decision. It is fixed, and when it starts depends on
2607
- whether the channel carries real-time media:
2597
+ whether the channel carries real-time audio:
2608
2598
 
2609
2599
  | Channel | Wrap allowance starts at |
2610
2600
  | --- | --- |
2611
- | Voice and any channel with real-time media | The `task-media-ended` event |
2601
+ | Voice and any channel with real-time audio | The `task-audio-ended` event |
2612
2602
  | Chat | The `task-updated` that moves the task to `completing`: the provider's word that the conversation is closed on its side, the allowance running from that publication's `occurredAt` |
2613
2603
  | Email | The `task-updated` that moves the task to `completing`: the provider's word that the platform has accepted the outgoing message, the allowance running from that publication's `occurredAt` |
2614
- | Other non-media channels | The `task-updated` that moves the task to `completing` |
2604
+ | Other non-audio channels | The `task-updated` that moves the task to `completing` |
2615
2605
 
2616
- **Off voice, `completing` is the provider's word that interaction ended.** There is no media event to
2606
+ **Off voice, `completing` is the provider's word that interaction ended.** There is no audio event to
2617
2607
  carry it, so the phase does; an agent application starts the wrap clock at that publication and nowhere else. It
2618
2608
  is optional: a conversation with nothing to wrap moves from `in-progress` to `task-ended` and
2619
2609
  `completing` is never published. Under `provider-automatic` with a non-zero `wrapAllowance` it is
@@ -2648,9 +2638,9 @@ const untimedWrap = {
2648
2638
  } satisfies Pick<Task<"voice">, "completionMode" | "wrapAllowance">;
2649
2639
  ```
2650
2640
 
2651
- Here the customer has hung up, `task-media-ended` has been sent on time, the task is `completing`,
2641
+ Here the customer has hung up, `task-audio-ended` has been sent on time, the task is `completing`,
2652
2642
  and the agent takes as long as the work needs. Moving the task to `completing` late to avoid a
2653
- deadline is not an alternative on a media channel: the clock starts at a real event, and delaying
2643
+ deadline is not an alternative on an audio channel: the clock starts at a real event, and delaying
2654
2644
  that event would falsify the phase and everything timed from it.
2655
2645
 
2656
2646
  #### Connecting back during completion
@@ -2665,9 +2655,9 @@ is; the command carries no destination, and it is a dial like any other, so it c
2665
2655
 
2666
2656
  On `dialling` the provider is placing the call and the task returns to `in-progress`: the agent is
2667
2657
  working again, and the wrap allowance is **discarded, not paused**. From there the call is
2668
- reported as any call is -- `paused`, `in-progress`, and when its media ends, `task-media-ended`
2658
+ reported as any call is -- `paused`, `in-progress`, and when its audio ends, `task-audio-ended`
2669
2659
  again, which starts a fresh allowance from that instant. A party who does not answer is a dial
2670
- whose outcome says so and a call whose media ended: the task returns to `completing` through the
2660
+ whose outcome says so and a call whose audio ended: the task returns to `completing` through the
2671
2661
  same event and the clock starts again from there. At no point is an agent dialling against a
2672
2662
  deadline.
2673
2663
 
@@ -2703,7 +2693,7 @@ const connectBackCapable = {
2703
2693
  With ten seconds of the thirty left, the agent presses Connect back: `execute({ command: { type:
2704
2694
  "connect-back", dialId } })` returns `dialling`, the task is `in-progress`, and the thirty seconds
2705
2695
  are gone.
2706
- The second call ends: `task-media-ended`, the task is `completing`, and a new thirty seconds runs
2696
+ The second call ends: `task-audio-ended`, the task is `completing`, and a new thirty seconds runs
2707
2697
  from that instant.
2708
2698
 
2709
2699
  ```ts
@@ -2739,6 +2729,12 @@ Migration from the earlier spellings:
2739
2729
  | Task.id, TaskId, and `taskId` on every event, command, report and lead request | gone: a task is named by `assignmentId` alone, and `assignmentKey(providerId, assignmentId)` scopes it |
2740
2730
  | taskKey, omni.task-not-found, PROVIDER_NAME__TASK_ID__TAB_NAME (ProviderName.TaskId.TabName) | `assignmentKey`, `omni.assignment-not-found`, `PROVIDER_NAME__ASSIGNMENT_ID__TAB_NAME` (`ProviderName.AssignmentId.TabName`) |
2741
2731
  | Manifest.disposalSettleMs | `Manifest.completionSettleMs` |
2732
+ | the call command, atDeadline calls and host-calls | `dial`, `"provider-dials"`, `"host-dials"` |
2733
+ | media: task-media-started/-ended, team-media-*, Task.media, TaskMediaState, openMedia, OpenMediaRequest/Result, VoiceMediaSession and its session field | audio: `task-audio-started`, `task-audio-ended`, `team-audio-started`, `team-audio-ended`, `audio` on the task, `TaskAudioState`, `openAudio`, `OpenAudioRequest`, `OpenAudioResult`, `CallAudio` on the result's `audio` |
2734
+ | capabilitySource values ungoverned and undetermined | `nobody`, `not-yet-read` (`capabilitySource.notYetRead`) |
2735
+ | Snapshot.scheduledActivities, and scheduledActivities on calendar-updated | `calendar` in both |
2736
+ | availability reserved | `elsewhere` |
2737
+ | recording destinationId, HostRecording.destinationIds | `storageId`, `storageIds` (`recording.storage`, `recording.host.storage`) |
2742
2738
 
2743
2739
  Update producers, consumers, saved task snapshots, and validation-rule assertions together.
2744
2740
  History and report rule names use `history` and `historyReport`; assignment rules use
@@ -2782,16 +2778,16 @@ Steps are `queued`, `offered`, `answered`, `held`, `muted`, `transferred`, `conf
2782
2778
  when it began, `seconds` once it ended and omitted while it runs — and a second hold is a second
2783
2779
  entry after the first, never a revision of it. A leg that has ended states its duration, and the
2784
2780
  task says whether a leg can still be running: a hold runs only while the task is `paused`, a mute
2785
- only while its media is up, so a `held` entry without `seconds` on a task that is not paused, or a
2781
+ only while its audio is up, so a `held` entry without `seconds` on a task that is not paused, or a
2786
2782
  `muted` one on a call that is over, is a leg nobody closed and reads exactly like a leg running now
2787
2783
  (`task.history.held.open`, `.muted.open`). Whoever performs the leg closes it — the
2788
2784
  provider whose platform parks the caller, the agent application whose microphone it is — by restating the entry
2789
2785
  with its duration, and **an entry that cannot be closed is not written**: open for ever is a
2790
- plausible nought one level down from a total. **A leg still open when the media ends is closed by
2791
- the provider**, at that instant, in the same publication that says the media ended: the provider
2786
+ plausible nought one level down from a total. **A leg still open when the audio ends is closed by
2787
+ the provider**, at that instant, in the same publication that says the audio ended: the provider
2792
2788
  is the one that knows the instant, and the agent application can only learn of it afterwards. Agents end calls
2793
- muted, so the agent application's leg is routinely open at media end; the provider closes it with the duration
2794
- from the leg's `at` to the media's end, and the agent application's own closing report for that leg, which
2789
+ muted, so the agent application's leg is routinely open at audio end; the provider closes it with the duration
2790
+ from the leg's `at` to the audio's end, and the agent application's own closing report for that leg, which
2795
2791
  follows what it hears, is answered `recorded` and changes nothing, the entry standing as the
2796
2792
  provider closed it. One publisher of the closed leg, and no order between them to get right. There is no resumed step; a resume is the end of a
2797
2793
  hold, at the entry's `at` plus its `seconds`, and nothing is lost by not naming it twice. The same goes for every step: two mutes are two
@@ -2838,7 +2834,7 @@ or combine them: report the representation conflict. No ambiguous event is silen
2838
2834
 
2839
2835
  **Handle time is anchored, not restarted.** It runs from the
2840
2836
  `answered` step's `at` — from the task's first `in-progress` where the provider reports no
2841
- history — until the task's media ends, and a hold neither pauses nor resets it: the hold's own
2837
+ history — until the task's audio ends, and a hold neither pauses nor resets it: the hold's own
2842
2838
  duration is the `held` entry's `seconds`, and a desk that restarts its counter on resume is
2843
2839
  counting the wrong thing.
2844
2840
 
@@ -2938,7 +2934,7 @@ the agent application mutes nothing and records nothing.
2938
2934
  **The provider's confirmed end is decisive.** The agent application keeps local state for interaction and
2939
2935
  reporting, but follows the provider's authoritative state. When the provider publishes the end
2940
2936
  of the current mute leg, the agent application ends that local mute and releases its agent application-controlled mute
2941
- on the matching task's media. It does not wait for its own timer, reopen the leg, replace the
2937
+ on the matching task's audio. It does not wait for its own timer, reopen the leg, replace the
2942
2938
  provider timestamp, or overwrite the provider's final duration with a later agent application report.
2943
2939
  A later agent mute is a new leg, never a reopening of the ended one.
2944
2940
 
@@ -2950,7 +2946,7 @@ retain the current provider view and apply the matching closure once correlation
2950
2946
  never guess a match from arrival order or nearest timestamp. Failure to release an agent application-controlled
2951
2947
  mute is reported visibly; local device reports still describe the actual device state.
2952
2948
 
2953
- At a provider-confirmed task/media end, the agent application also stops the associated local mute and reports
2949
+ At a provider-confirmed task/audio end, the agent application also stops the associated local mute and reports
2954
2950
  its observed ending where the report is still accepted. An agent application closing report repeats its original
2955
2951
  key and may include its measured duration, but cannot reverse an already confirmed provider end.
2956
2952
  The provider acknowledges a known closed leg without rewriting its final record; after the task
@@ -3078,7 +3074,7 @@ from no other source -- not the queue the task came from, not the login, not its
3078
3074
  about this task, and a client with two sources and a rule for choosing between them is the shape
3079
3075
  that produces two consumers disagreeing about one fact. The task is the one source, and the
3080
3076
  provider puts on the task what the platform permits for it -- and says, in `capabilitySource`, who
3081
- chose those terms. `queue` says somebody configured them. `ungoverned` says nothing handed the work
3077
+ chose those terms. `queue` says somebody configured them. `nobody` says nothing handed the work
3082
3078
  over: an agent's own outbound call has no queue behind it, and the provider states what it permits
3083
3079
  for such a call. Neither changes where an agent application reads the capabilities from, which is the task; the
3084
3080
  source is one more published fact about them, and the one that lets an agent application tell a fact from a fault
@@ -3116,15 +3112,15 @@ direction: the task as offered and as republished, and one command that was issu
3116
3112
  first and is refused under the last for want of the capability withdrawn -- and nothing else.
3117
3113
 
3118
3114
  **An empty capability set is a statement, not a shrug.** `capabilities: {}` under `queue` or
3119
- `ungoverned` says the platform permits nothing capability-gated on this task, and an agent application draws
3115
+ `nobody` says the platform permits nothing capability-gated on this task, and an agent application draws
3120
3116
  nothing beyond what the phase and `completionMode` require. A provider that has not yet learned
3121
3117
  what the platform permits -- a queue's configuration that has not reached it -- knows nothing of
3122
3118
  the kind, and must not publish the task as if it did, neither as `{}` nor as every control it has.
3123
- It publishes the task under `capabilitySource: "undetermined"`, with the capabilities it will
3119
+ It publishes the task under `capabilitySource: "not-yet-read"`, with the capabilities it will
3124
3120
  honour until it knows, and an agent application shows that where the agent works, beside the controls it draws
3125
3121
  from them: "no queue governs this call" is a fact, "the configuration has not arrived" is a fault,
3126
3122
  and the set alone cannot tell them apart, so the provider says which. What a provider honours
3127
- under undetermined terms is its own call -- a floor that would rather an agent briefly hold a
3123
+ under not-yet-read terms is its own call -- a floor that would rather an agent briefly hold a
3128
3124
  control the platform might not have granted than lose hold or hang-up mid-call over a slow
3129
3125
  configuration read publishes those -- and the protocol chooses no default set for it. The fault is
3130
3126
  also reported as a `diagnostic` naming the task, one per occurrence, so an operator counts it. When
@@ -3132,12 +3128,12 @@ the terms arrive, the task is republished under `queue` with the set as it now s
3132
3128
  republish as any other permission that changed while the task was open. The move goes one way.
3133
3129
  Terms once read stay read: a re-read that fails mid-task is not a new fact about the task, so the
3134
3130
  last statement stands and the failure is a `diagnostic`, and a task that was published under
3135
- `queue` or `ungoverned` never returns to `undetermined`, on an update (`stream.taskUpdated.capabilitySource`)
3131
+ `queue` or `nobody` never returns to `not-yet-read`, on an update (`stream.taskUpdated.capabilitySource`)
3136
3132
  or on a resync snapshot (`stream.snapshot.capabilitySource`); a record once read never loses an
3137
3133
  entry the same two ways (`stream.taskUpdated.history`, `stream.snapshot.history`);
3138
- a snapshot carrying a task still at work does not forget the audio the stream held up, since media
3139
- ends on `task-media-ended` and the call moves on (`stream.snapshot.media`); a voice task ends after
3140
- its audio ends, never around it, whatever the outcome (`stream.taskEnded.mediaOpen`); and a task does not go
3134
+ a snapshot carrying a task still at work does not forget the audio the stream held up, since audio
3135
+ ends on `task-audio-ended` and the call moves on (`stream.snapshot.audio`); a voice task ends after
3136
+ its audio ends, never around it, whatever the outcome (`stream.taskEnded.audioOpen`); and a task does not go
3141
3137
  backwards, on an update or on a resync (`stream.taskUpdated.phase`, `stream.snapshot.phase`): the
3142
3138
  stream sees publications, not
3143
3139
  transitions, and a task may pass through a phase between two, so `pending` to `in-progress` stands
@@ -3146,32 +3142,32 @@ the transition table -- back to `pending`, back to `confirmed` or `preview` once
3146
3142
  `completing` except by the party being dialled again, a connect-back or a platform's callback,
3147
3143
  which the update itself shows: the party ringing, or joined by a dial whose answered outcome the
3148
3144
  stream saw in this life. A completing task republished as `in-progress` from a stale copy carries
3149
- no such stage, and that is the ending the agent never saw. Audio arrives only on a task at work: `task-media-started` on a `completing` task
3150
- is refused as it is on a pending one (`stream.taskMediaStarted.beforeWork`), since a connect-back
3151
- returns the task to `in-progress` before any media. And a task completes after its audio ends,
3152
- never around it: an update moving a task to `completing` while the stream holds its media as
3153
- started is refused (`stream.taskUpdated.mediaOpen`), whoever caused the ending, and a task stating
3154
- `completing` with `media: "started"` contradicts itself on any snapshot or update
3155
- (`task.media.completing`). The consequence the rule exists for is concrete: the customer's audio
3145
+ no such stage, and that is the ending the agent never saw. Audio arrives only on a task at work: `task-audio-started` on a `completing` task
3146
+ is refused as it is on a pending one (`stream.taskAudioStarted.beforeWork`), since a connect-back
3147
+ returns the task to `in-progress` before any audio. And a task completes after its audio ends,
3148
+ never around it: an update moving a task to `completing` while the stream holds its audio as
3149
+ started is refused (`stream.taskUpdated.audioOpen`), whoever caused the ending, and a task stating
3150
+ `completing` with `audio: "started"` contradicts itself on any snapshot or update
3151
+ (`task.audio.completing`). The consequence the rule exists for is concrete: the customer's audio
3156
3152
  keeps playing through the agent's wrap-up.
3157
3153
 
3158
3154
  What the agent is told differs by source, and only one source tells them anything. Under `queue`
3159
- and `ungoverned` the agent sees controls and nothing about where they came from: both are facts,
3155
+ and `nobody` the agent sees controls and nothing about where they came from: both are facts,
3160
3156
  and an agent working a call has no use for the name of the rule behind its buttons. Under
3161
- `undetermined` the agent is told, beside the controls, that these are what the provider will honour
3157
+ `not-yet-read` the agent is told, beside the controls, that these are what the provider will honour
3162
3158
  until the queue's terms arrive, and that the controls may change when they do -- a statement about
3163
3159
  the buttons in front of them now, not about the provider, because that is what changes when the
3164
3160
  republish lands. It stays for as long as the set is provisional, beside the controls it qualifies;
3165
3161
  a message that shows and clears has said nothing about the buttons still on the screen. The two
3166
- words are close in English and far apart on the desk: `ungoverned` is silence, `undetermined` is a
3162
+ words are close in English and far apart on the desk: `nobody` is silence, `not-yet-read` is a
3167
3163
  standing notice.
3168
3164
 
3169
- One consequence for whoever builds the agent application: because a conformance run fails on `undetermined`, a
3170
- conformant adapter never shows an agent application `undetermined` under test, and a clean `exerciseAdapter`
3165
+ One consequence for whoever builds the agent application: because a conformance run fails on `not-yet-read`, a
3166
+ conformant adapter never shows an agent application `not-yet-read` under test, and a clean `exerciseAdapter`
3171
3167
  result says nothing about how the agent application renders it. That rendering is tested against a fixture --
3172
- a task published under `undetermined` shows the notice, the same task under `queue` shows none --
3168
+ a task published under `not-yet-read` shows the notice, the same task under `queue` shows none --
3173
3169
  and never against an adapter, even in principle. `exerciseAdapter` treats a
3174
- task published under `undetermined` as a violation (`capabilitySource.undetermined`), as it treats
3170
+ task published under `not-yet-read` as a violation (`capabilitySource.notYetRead`), as it treats
3175
3171
  a diagnostic: a conformance run against a platform that cannot say what it permits fails loudly
3176
3172
  rather than passing with a note.
3177
3173
 
@@ -3197,7 +3193,8 @@ See **Which commands need a capability**.
3197
3193
  | --- | --- | --- |
3198
3194
  | `decline` | Pending-task button: Decline | The provider can decline a pending voice offer. Omni shows it only when local policy also permits declining. |
3199
3195
  | `hold` | Primary toggle: Hold | Omni may issue voice-task `hold` and `resume` commands. |
3200
- | `endCall` | Primary button: End call | The provider ends the caller connection and all owned/inherited agent-added channels; wrap/completion remain separate. See **Ending a call, and removing one person from it**. |
3196
+ | `endCall` | Primary button: End call | The provider ends the agent's channel and every channel the agent added; the caller continues on the provider's path. The task goes to its wrap. See **Ending a call, and removing one person from it**. |
3197
+ | `terminateCall` | Primary button: Terminate call | The provider ends the caller channel. The task goes to its wrap. See **Ending a call, and removing one person from it**. |
3201
3198
  | `connectBack` | Completing-task button: Connect back | Omni may have the provider connect the agent back to the task's party while the task is `completing`, returning it to `in-progress` on the same task. Not offered where there is no `completing` window: `provider-automatic` with a zero allowance completes the task at provider end. See **Connecting back during completion**. |
3202
3199
  | `coldTransfer` | Secondary menu item: Cold transfer | Omni may hand the customer straight to a destination, with nobody spoken to first. |
3203
3200
  | `warmTransfer` | Secondary menu item: Warm transfer | Omni may park the customer and call a destination first, then hand the customer over or cancel back. See **Warm transfer**. |
@@ -3292,50 +3289,41 @@ end the caller journey; a later assignment to the same agent is a new interactio
3292
3289
 
3293
3290
  #### Ending a call, and removing one person from it
3294
3291
 
3295
- A call has everyone on `Task.onCall`, and two commands take people off it, both performed by the
3296
- provider.
3292
+ A call has everyone on `Task.onCall`, and three commands take people off it, all performed by the
3293
+ provider. Two of them end the agent's part; they differ in what happens to the caller.
3297
3294
 
3298
3295
  ```ts
3299
- { type: "end-call" } // provider ends caller and owned/inherited agent-added channels; wrap/completion are separate
3296
+ { type: "end-call" } // the agent's channel and the channels the agent added end; the caller continues
3297
+ { type: "terminate-call" } // the caller channel ends
3300
3298
  { type: "conference", action: "remove", party: true } // the customer leaves; the agent stays with the colleague
3301
3299
  { type: "conference", action: "remove", destinationId: "tier2" } // the conferenced person leaves; ringing, this calls the dial off
3302
3300
  ```
3303
3301
 
3304
- The agent application sends the ordinary task-scoped `execute` request with `command: { type: "end-call" }`.
3305
- The provider knows the caller and agent-added channel bindings; no channel list or owner details
3306
- are sent by the agent application. The task's `endCall` capability is permission, not an executor selection.
3307
- `validateTaskCommandRequest` checks the exact interaction/assignment and delegates the command's
3308
- policy/phase checks. The provider rechecks current ownership atomically; no client validator can
3309
- prove that its view is fresh. The agent application does not implement agent end-call; low-level removal of individual channels is not
3310
- this agent action. There is no agent application end-call execution API or fallback route.
3311
-
3312
- `end-call` is gated by `endCall` on the current interaction. It ends the caller connection and
3313
- all agent-added channels owned by that interaction, including channels originally added by previous
3314
- agents and inherited through transfer or takeover. Ownership is not inferred from who originally
3315
- dialed, the current UI selection, or a person's label. The provider maintains that authority
3316
- and checks the exact interaction/assignment when acting. Agent-added channels are not left behind
3317
- merely because another agent added them.
3318
-
3319
- On a successful transfer or takeover, ownership of those channels passes to the receiving
3320
- interaction (lead or another agent), not just ownership of the caller leg. The outgoing interaction may
3321
- still have its allowed wrap work, but cannot disconnect channels it no longer owns. A pending or
3322
- unknown handover is not a confirmed ownership change: retain source reservations and reconcile.
3323
- The provider must serialize handover and end-call so a delayed outgoing command cannot tear down
3324
- the recipient's call. No public channel-owner field or guessed identity is introduced here;
3325
- the source must provide authoritative ownership and command fencing before declaring permission.
3326
-
3327
- The resulting end of this agent's media is published as `task-media-ended`. Any wrap remains
3328
- until its own completion; `end-call` is not `task-ended`, and `complete` is not an implicit
3329
- caller/channel disconnect. Other agents' independent tasks end or wrap only on their own source
3330
- transitions. Neither completion nor transfer itself ends the continuing caller journey.
3331
-
3332
- `conference` `remove` remains a person-specific provider operation, gated by `conference`.
3333
- It does not implicitly complete any other interaction. Its person/consultation prerequisites
3334
- remain separate from the permission to disconnect the caller. A remove that would leave the
3335
- agent alone is not a remove but an `end-call`, and the provider answers it `failed`.
3336
- The consulted destination of a warm transfer is not removed this way:
3337
- `transfer` `cancel` is its own step, which returns the agent to the parked customer, and a remove
3338
- never promises that.
3302
+ **`end-call` ends my part.** The agent's channel and every channel the agent added -- a
3303
+ conferenced colleague, a consulted destination -- end, and the caller continues on the path the
3304
+ provider decides: another IVR, a queue, a survey, or the end of the call. Gated by `endCall`.
3305
+
3306
+ **`terminate-call` ends the caller channel.** The call is over for the caller. Gated by
3307
+ `terminateCall`, a separate permission, since ending a customer's call is a different thing from
3308
+ putting my own side down.
3309
+
3310
+ Both are commands to the provider through `execute`, and nothing happens on the desk until the
3311
+ provider reports it: the agent's audio ends on `task-audio-ended`, the task moves to `completing`,
3312
+ any wrap allowance runs, and the agent completes. Neither is `task-ended`, and `complete` is
3313
+ neither of them. Both capabilities are the queue's to grant and a level's to lock, and both may
3314
+ change while the task is open: the provider republishes the task at the moment a permission
3315
+ changes, and a command that arrives after its capability was withdrawn is refused, as **Task
3316
+ capabilities** sets out. The provider knows which channels are the agent's and which were added;
3317
+ the agent application sends the command and no channel list, and the provider rechecks ownership
3318
+ when it acts.
3319
+
3320
+ `conference` `remove` takes one person off, named as the room names them, and the call goes on
3321
+ for the rest; it is gated by `conference`, since it only means something with a third person on
3322
+ the line. Removing the party leaves the agent with the colleague, a warm hand-over in reverse. A
3323
+ remove that would leave the agent alone is not a remove but an `end-call`, and a provider answers
3324
+ it `failed`. The consulted destination of a warm transfer is not removed this way: `transfer`
3325
+ `cancel` is its own step, which returns the agent to the parked customer, and a remove never
3326
+ promises that.
3339
3327
 
3340
3328
  #### Warm transfer
3341
3329
 
@@ -3370,7 +3358,7 @@ that offers `warmTransfer` implements all three.
3370
3358
 
3371
3359
  `applied` on `complete` says the provider is bridging the customer to the destination and
3372
3360
  dropping the agent's leg. What follows is what follows any call the agent leaves: the agent's
3373
- media ends and the provider reports `task-media-ended`, the task moves to `completing`, any wrap
3361
+ audio ends and the provider reports `task-audio-ended`, the task moves to `completing`, any wrap
3374
3362
  allowance runs, and the task ends `completed` as any call does. A warm complete is not a completion:
3375
3363
  the agent has a wrap to do, so nothing is owed within `completionSettleMs`. `transferred` names a
3376
3364
  cold transfer alone, where the agent had no wrap. `applied` on `cancel` says the destination is
@@ -3456,7 +3444,7 @@ inside Omni.
3456
3444
 
3457
3445
  ## Every dial has an outcome
3458
3446
 
3459
- Five commands place a call: `dial` from the idle dialpad, `call` from preview, a cold or warm `transfer`, a
3447
+ Five commands place a call: `dial()` from the idle dialpad, the `dial` command from preview, a cold or warm `transfer`, a
3460
3448
  `conference` `add`, and `connect-back`. Each is accepted or refused at once, and each then ends later
3461
3449
  and apart from its answer -- the destination picks up, is busy, or never does -- and a dial placed
3462
3450
  late in a call routinely outlives the call. Nothing in between is reported: the wire says
@@ -3562,7 +3550,7 @@ never saw.
3562
3550
 
3563
3551
  **`onCall` describes this agent's current interaction.** When the caller disconnects but the agent
3564
3552
  and added channels remain connected, the remaining room is still valid. When this interaction's
3565
- media ends or the task enters `completing`, clear its `onCall` view; absence is used only by a
3553
+ audio ends or the task enters `completing`, clear its `onCall` view; absence is used only by a
3566
3554
  provider that never publishes the room. This does not assert that the caller, bridge or other
3567
3555
  agents' channels ended. A published room receives a final empty view for this interaction, and the
3568
3556
  task may remain open for wrap. Completion is a separate task action, not the caller's disconnect.
@@ -3587,10 +3575,10 @@ still-ringing `destinationId` calls that dial off, with `cancelled` as its outco
3587
3575
 
3588
3576
  **The entry says where it stands.** A dialled entry carries `stage`: `ringing` until its dial is
3589
3577
  answered, `joined` after. The `dial-outcome` is the transition and the stage is the state, the same
3590
- pairing as `task-media-started` and `media`: an `answered` outcome and the task restated with the
3578
+ pairing as `task-audio-started` and `audio`: an `answered` outcome and the task restated with the
3591
3579
  entry `joined` say one thing twice, and any other outcome removes the entry. The outcome comes
3592
3580
  first: a `task-updated` that itself moves an entry from `ringing` to `joined` before an `answered`
3593
- outcome for its dial is refused (`stream.taskUpdated.stage`), as an update that moves `media` is.
3581
+ outcome for its dial is refused (`stream.taskUpdated.stage`), as an update that moves `audio` is.
3594
3582
  The rule keys on the entry's `dialId`, so an entry the platform added itself, with none, is one
3595
3583
  whose move the stream cannot hold to an outcome: a clean stream proves nothing about ordering there. It is stated rather
3596
3584
  than left to whoever placed the dial because that knowledge lives in one process: after a reload,
@@ -3632,7 +3620,7 @@ rendering one as the other tells an agent to wait for somebody who is never comi
3632
3620
  | `awaiting-approval` | A person has to decide. The agent is waiting on somebody. |
3633
3621
  | `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. |
3634
3622
  | `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 `on-break`. |
3635
- | `on-break` | The agent is on the break now. It holds no task: a break begins when the work ends, so a snapshot reporting `on-break` beside a task is refused as `break.on-break.tasks`. The one exception is a lead's listening task during a `coaching`, `administrative` or `training` break -- see **Listening to a call**. |
3623
+ | `on-break` | The agent is on the break now. It holds no task: a break begins when the work ends, so a snapshot reporting `on-break` beside a task is refused as `break.on-break.tasks`. The one exception is a call a lead took over, which the provider assigns whatever break the lead is on -- see **Lead assist**. |
3636
3624
 
3637
3625
  A denial is a decision, not a standing approval state. The provider transitions the request directly
3638
3626
  to `not-requested`; Omni returns the agent to idle and never asks again on their behalf. They saw the
@@ -3807,7 +3795,7 @@ Your own tasks are the only ones you count. What the agent holds at other provid
3807
3795
  concern — Omni set `count` knowing it, and this is how: the agent is one person on several
3808
3796
  providers, and the agent application divides their capacity among them rather than telling each the whole. A
3809
3797
  provider that has none of it for now is told **`count: 0`, agent application-stopped**: assign nothing, show
3810
- the member as `reserved` on the team member list -- signed in here, capacity held by the agent application for elsewhere,
3798
+ the member as `elsewhere` on the team member list -- signed in here, capacity held by the agent application for elsewhere,
3811
3799
  a fact this provider holds, where `on-task` would assert work it cannot see -- and take the next
3812
3800
  count as any other when the agent application has capacity for this provider again. Zero is the one restatement
3813
3801
  that follows work rather than local policy.
@@ -3843,12 +3831,15 @@ request object only to the request method, and undefined to the other three.
3843
3831
  | `cancelBreak` | `awaiting-approval` or `granted`. A concurrent commit winning still answers `omni.break-already-committed` and requires recovery. |
3844
3832
  | `endBreak` | `on-break` or `starting-after-task` during reconciliation; the agent may explicitly end a requested or forced break. |
3845
3833
 
3846
- Use `validateTeamBreakCommand(request, context)` for lead decisions, forcing a break, ending a forced break and
3847
- policy commands. It requires the live lead capability and active transport, a current target
3848
- team member list for member commands, and the target's complete break state for forcing/ending a break.
3849
- Approve/deny requires an awaiting decision; forcing a break uses the target's reason codes; ending a forced break
3850
- requires a forced committed break. The context's target state must belong to the named member;
3851
- that association and backend authorization are provider responsibilities.
3834
+ Use `validateTeamCommand(request, context)` before every lead act: the team feature switch, break
3835
+ decisions, forcing and ending a forced break, policy, and every act on a member's call. It requires
3836
+ the live `lead` flag and active transport, a current team member list for any command naming a
3837
+ member, and the target's complete break state for forcing/ending a break. Approve/deny requires an
3838
+ awaiting decision; forcing a break uses the target's reason codes; ending a forced break requires a
3839
+ forced committed break; join and decline need the member's request on the list; coach, join-call
3840
+ and leave need the lead already on that member's call; an assignment named must be one the list
3841
+ shows the member holding. The context's target state must belong to the named member; that
3842
+ association and backend authorization are provider responsibilities.
3852
3843
 
3853
3844
  Use `validateBreakStatus(state, tasks)` on the complete retained task view after each transaction,
3854
3845
  as well as `validateBreakTransition` for event ordering. Snapshot validation shares the status
@@ -4116,22 +4107,23 @@ so there is nothing on it to resume.
4116
4107
 
4117
4108
  ## Team leads
4118
4109
 
4119
- A lead who also takes calls sees their team on the idle dashboard. `Snapshot.team` carries a
4120
- `TeamMembers` object, replaced whole by `team-updated`.
4121
-
4122
- Migration: TeamRoster is now `TeamMembers`, and validateTeamRoster is now
4123
- `validateTeamMembers`. Update type imports and validator calls; no legacy export aliases exist.
4124
- The object still contains `members` and the permitted `requests` and `policies`; it is not a
4125
- bare array. `Snapshot.team`, the `team-updated` event and its `team` payload, login permissions,
4126
- and `team.*` validation rules keep their names and semantics. An empty team has `members: []`;
4127
- an absent `team` still means the login is not entitled to that contribution.
4110
+ The team feature is the lead's own surface, beside the agent's and never mixed with it. The
4111
+ login's `lead` flag turns it on in the agent application; the lead retrieves their members and
4112
+ sees, for each, their assignments as they stand and the full history of each, and acts on them
4113
+ with the authority the flag carries. Nothing the lead does here is a task on their desk. A lead
4114
+ who also takes calls is an agent like any other on the agent side, and a lead who does not want
4115
+ the agent role for now places themself on a break of a working kind and keeps the team feature on.
4128
4116
 
4117
+ `Snapshot.team` carries a `TeamMembers` object, replaced whole by `team-updated`, and the provider
4118
+ keeps it current: when a member is offered a call, answers, holds, mutes or is transferred, the
4119
+ list is republished, so the lead's screen moves when the member's does. The agent application asks
4120
+ for nothing and computes nothing.
4129
4121
 
4130
4122
  | Field | Contract |
4131
4123
  | --- | --- |
4132
- | `members` | Every member of this lead's team, whatever their state. `[]` says the lead has a team with nobody in it; omitting the team member list says something else entirely — see **The login is the permission** below. |
4133
- | `requests` | The members currently asking this lead to join a call, each with the task and the note. Required when the login declares `team.leadAssistControl`, `[]` when nobody is asking; omitted when it does not. See **Lead assist**. |
4134
- | `policies` | The team's policy per capability as it stands — the setting, who set it, and `lockedBy` where a level above the team made it theirs to keep. Required when the login declares `team.policyControl`; omitted when it does not. See **Who decides what an agent may do**. |
4124
+ | `members` | Every member of this lead's team, whatever their state, each with their open tasks. `[]` says the lead has a team with nobody in it; omitting the team member list says something else entirely — see **The login is the permission** below. |
4125
+ | `requests` | The members currently asking a lead to join their call, each naming the member, the assignment and the note. Always carried, `[]` when nobody is asking. See **Lead assist**. |
4126
+ | `policies` | The team's policy per capability as it stands — the setting, who set it, and `lockedBy` where a level above the team made it theirs to keep. Present where the provider offers policy control; absent where it does not. See **Who decides what an agent may do**. |
4135
4127
 
4136
4128
  | `TeamMember` field | Contract |
4137
4129
  | --- | --- |
@@ -4139,6 +4131,8 @@ an absent `team` still means the login is not entitled to that contribution.
4139
4131
  | `availability` | Required. What the member is doing now. |
4140
4132
  | `since` | Optional. When the current `availability` began — not when they signed in, and not when the team member list was read. |
4141
4133
  | `break` | Present only while the member has an outstanding break request. See **A member waiting for a break**. |
4134
+ | `tasks` | The member's open tasks as `MemberTask`s: the same task the member's desk holds, trimmed by the provider. The assignment, title, task type, phase, party, room, audio and the full history are what the lead reads; the workspace — controls, their source, browsers, completion terms — is the member's and travels only if the provider sends it. `[]` when the member holds none; omitted only where the provider cannot see them. |
4135
+ | `listening` | Present while this lead's channel is in this member's call — listening, coaching or joined — with the `mode` they are heard in and since when. One member's call at a time (`team.listening.single`). |
4142
4136
 
4143
4137
  Each availability value means one thing:
4144
4138
 
@@ -4147,13 +4141,14 @@ Each availability value means one thing:
4147
4141
  | `ready` | Signed in, able to take work, none assigned. |
4148
4142
  | `on-task` | Working on at least one task. It says nothing about how many, and nothing about whether more will fit. |
4149
4143
  | `on-break` | Stopped and not taking work, whether they asked or somebody stopped them. The reason lives on their own `BreakState`, not here. |
4150
- | `reserved` | Signed in here, and the agent application holds this agent's capacity for another provider (`count: 0`, agent application-stopped): not receiving this provider's work, and not on a break. See **Capacity**. |
4144
+ | `elsewhere` | Signed in here, and the agent application holds this agent's capacity for another provider (`count: 0`, agent application-stopped): not receiving this provider's work, and not on a break. See **Capacity**. |
4151
4145
  | `signed-out` | Known to this team but not signed in to this provider. |
4152
4146
 
4153
4147
  **Always publish the complete team member list, never a change to it.** Team presence typically reaches an
4154
4148
  adapter over a best-effort channel with no ordering and no delivery guarantee, so a stream of deltas
4155
4149
  cannot be trusted to reconstruct the truth. The adapter reconciles against its own authoritative
4156
- read and publishes the result.
4150
+ read and publishes the result. That includes the members' tasks: a hold on a member's desk is a
4151
+ republished list on the lead's.
4157
4152
 
4158
4153
  **Omit `since` rather than inventing one.** Omni renders it as a duration, so a timestamp
4159
4154
  synthesised from the adapter's own clock at seed time reads as "on task for 0 seconds" for
@@ -4161,11 +4156,20 @@ everybody — worse than showing nothing, because it looks like data. Send it on
4161
4156
  knows when the state actually began. It times the current `availability`, so it moves every time
4162
4157
  that value does.
4163
4158
 
4164
- **The login is the permission.** A team member list goes to a login that declares `capabilities.team`, on
4165
- every snapshot, and to nobody else. Omni never decides who leads a team: the provider said so at
4166
- sign-in, and the team member list agrees with it — present, `[]` included, for a lead; absent for everybody
4167
- else, which is the correct rendering for an agent who leads nobody. What the lead may do with the
4168
- team member list is on the login too, `team.breakControl` and `team.leadAssistControl`, never on the team member list.
4159
+ **The login is the permission.** A team member list goes to a login that declares
4160
+ `capabilities.lead`, on every snapshot while the team feature is on, and to nobody else. Omni never
4161
+ decides who leads a team: the provider said so at sign-in, and the team member list agrees with it
4162
+ — present, `members: []` included, for a lead; absent for everybody else, which is the correct
4163
+ rendering for an agent who leads nobody (`team.required`, `team.unentitled`). What the lead may do
4164
+ with it comes with the flag: there is no per-action permission on the login or on the list.
4165
+
4166
+ **The lead switches the feature on and off, and the provider is told.** A lead may work as an
4167
+ agent alone, as agent and lead, or as lead alone, and moves between them on the fly. The
4168
+ agent application says which with `{ type: "lead-features", enabled }`: while it is on, the
4169
+ provider sends team events scoped to the lead's team and the lead may act; while it is off, the
4170
+ lead is an ordinary agent, no team member list is owed or expected (`team.unexpected`,
4171
+ `event.team.features`), and no lead act is possible, since the lead learns their members'
4172
+ assignments only from the list. It is on from sign-in for a lead until the lead turns it off.
4169
4173
 
4170
4174
  **The team member list never carries the agent it is published to — not in `members`, and not in
4171
4175
  `requests`.** A lead does not report to themself: their own break request and their own ask for a
@@ -4173,24 +4177,36 @@ lead go up to whoever leads them and appear on *that* person's team member list,
4173
4177
  only their own `BreakState` and their task's `leadAssist` move. An adapter whose platform lists the lead
4174
4178
  among their own members filters the signed-in identity out before publishing. **Being a lead is a
4175
4179
  role the provider knows, never inferred from who is listed:** it is declared at sign-in, a lead
4176
- with nobody in their team publishes `[]`, an agent with no such role publishes nothing, and no
4180
+ with nobody in their team publishes `members: []`, an agent with no such role publishes nothing, and no
4177
4181
  member count can tell those two apart.
4178
4182
 
4179
4183
  ### Lead commands
4180
4184
 
4181
- One method, `executeTeamBreak`, taking a discriminated command exactly as `execute` takes a
4182
- `TaskCommand`:
4185
+ One method, `executeTeam`, taking a discriminated command exactly as `execute` takes a
4186
+ `TaskCommand`. Every act on a member's call names the member, `memberId`, and may name the
4187
+ assignment, `assignmentId`, where the lead has it; the provider resolves the member's current
4188
+ assignment from the member alone.
4183
4189
 
4184
4190
  | Command | Effect |
4185
4191
  | --- | --- |
4186
- | `{ type: "decide-break-request", memberId: UserId, decision, reason? }` | Settles one pending request. `decision` is `granted` or `denied`. A grant moves the member to `granted`; a denial ends the request and moves it directly to `not-requested`. |
4192
+ | `{ type: "lead-features", enabled }` | The team feature on or off. See **The lead switches the feature on and off** above. |
4193
+ | `{ type: "decide-break-request", memberId, decision, reason? }` | Settles one pending request. `decision` is `granted` or `denied`. A grant moves the member to `granted`; a denial ends the request and moves it directly to `not-requested`. |
4187
4194
  | `{ type: "set-break-policy", policy }` | `approval-required`, `automatically-approved`, or `requests-suspended`. |
4188
- | `{ type: "force-break", memberId: UserId, reasonId?, reason?, expectedDurationMs? }` | Puts a member on a break they did not ask for. `reasonId` names a published `BreakReason.id` and is required whenever the provider publishes `reasons`; the member's forced break carries it as `activeReasonId`, so its kind is known. Optional `expectedDurationMs` is advisory and is published on the resulting forced break. |
4189
- | `{ type: "end-forced-break", memberId: UserId }` | Lifts the forced-break restriction, whoever forced it, by clearing `BreakState.forced`. The committed break continues; only the agent resumes work. |
4195
+ | `{ type: "force-break", memberId, reasonId?, reason?, expectedDurationMs? }` | Puts a member on a break they did not ask for. `reasonId` names a published `BreakReason.id` and is required whenever the provider publishes `reasons`; the member's forced break carries it as `activeReasonId`, so its kind is known. Optional `expectedDurationMs` is advisory and is published on the resulting forced break. |
4196
+ | `{ type: "end-forced-break", memberId }` | Lifts the forced-break restriction, whoever forced it, by clearing `BreakState.forced`. The committed break continues; only the agent resumes work. |
4197
+ | `{ type: "join", memberId, assignmentId? }` | Answers the member's request: the provider bridges the lead's channel into the call. See **Lead assist**. |
4198
+ | `{ type: "decline", memberId, assignmentId?, reason? }` | Refuses the member's request. |
4199
+ | `{ type: "listen", memberId, assignmentId? }` | The lead's channel joins the member's call unasked, in silence. See **Listening to a call**. |
4200
+ | `{ type: "coach", memberId, assignmentId? }` | The lead is heard by the member alone. Needs the lead on that member's call. |
4201
+ | `{ type: "join-call", memberId, assignmentId? }` | The lead is heard by everyone on the call. Needs the lead on that member's call. |
4202
+ | `{ type: "leave", memberId, assignmentId? }` | The lead's channel leaves the member's call, however it got there. The member's call goes on. |
4203
+ | `{ type: "take-over-call", memberId, assignmentId? }` | The provider moves the call to the lead as its new agent, from a joined call or a listened one alike. See **Lead assist**. |
4204
+ | `{ type: "set-policy", capability, setting }` | Sets the team's policy for one capability: `on`, `off`, or `person`. See **Who decides what an agent may do**. |
4190
4205
 
4191
4206
  `memberId` is this provider's own identifier for the member, as published on its team member list. It is
4192
4207
  never an identifier from another provider, and Omni does not translate between them; names come
4193
- from `getUserDetails()`.
4208
+ from `getUserDetails()`. `validateTeamCommand` holds each command to the team member list as it
4209
+ stands -- see **Runtime break prerequisite checks**.
4194
4210
 
4195
4211
  `requests-suspended` means requests are **rejected outright** rather than left pending — nobody is coming to
4196
4212
  approve them. A provider that suspends break requests must also publish `canRequestBreak: false` to the team's
@@ -4204,71 +4220,56 @@ never expressed here.
4204
4220
 
4205
4221
  An agent on a call may ask a lead to join it -- a dispute that needs approval, a customer who
4206
4222
  asks for a manager, a moment the agent wants a second pair of ears. A call centre calls this
4207
- assistance or escalation, and the name says who assists: the capability is `leadAssist` on the task; the lead's side is the team member list, which is already the lead's view of the
4208
- team, and a second lead method beside `executeTeamBreak`:
4209
-
4210
- ```ts
4211
- executeTeamLeadAssist({ command: TeamLeadAssistCommand }): Promise<TeamCommandResult>
4212
- ```
4213
-
4214
- Required when the login declares `capabilities.team.leadAssistControl`, and gated by it exactly as
4215
- `executeTeamBreak` is by `team.breakControl`. The flow, in order:
4223
+ assistance or escalation, and the name says who assists: the capability is `leadAssist` on the
4224
+ task; the lead's side is the team member list, which is already the lead's view of the team. The
4225
+ flow, in order:
4216
4226
 
4217
4227
  ```ts
4218
4228
  // 1. The agent asks, with a small note. Their task carries `leadAssist` from here on.
4219
4229
  execute({ assignmentId: "alloc-42", command: { type: "lead-assist", action: "request", note: "Refund dispute, needs approval" } })
4220
4230
  // task.leadAssist = { stage: "requested", note: "Refund dispute, needs approval", since }
4221
4231
 
4222
- // 2. Every lead entitled to it sees the request on their team member list.
4223
- // team-updated: requests: [{ id: "req-7", memberId: "A-1", assignmentId: "alloc-42", note, since }]
4232
+ // 2. Every lead with the team feature on sees the request on their team member list.
4233
+ // team-updated: requests: [{ memberId: "A-1", assignmentId: "alloc-42", note, since }]
4224
4234
 
4225
4235
  // 3. A lead joins, or declines.
4226
- executeTeamLeadAssist({ command: { type: "join", requestId: "req-7" } })
4227
- executeTeamLeadAssist({ command: { type: "decline", requestId: "req-7", reason: "In a call" } })
4236
+ executeTeam({ command: { type: "join", memberId: "A-1", assignmentId: "alloc-42" } })
4237
+ executeTeam({ command: { type: "decline", memberId: "A-1", reason: "In a call" } })
4228
4238
  ```
4229
4239
 
4230
- **On `join` the provider bridges three parties and the lead is on a task of their own**, an
4231
- assignment of its own, arriving on the lead's connection as `task-offered` with `automatic`
4232
- -- the way a call an agent placed themselves arrives -- and carrying `assisting`, which names the
4233
- member and the member's assignment. The agent's task
4234
- moves to `leadAssist: { stage: "joined", leadId }`. A join is the lead's own act, so capacity does not
4235
- trigger it; but from then on it is an outstanding task the provider counts against the lead's
4236
- stated ceiling like any other, nothing more is assigned to the lead while it stands, and a
4237
- provider whose lead is already at the ceiling answers the join `failed`.
4238
-
4239
- **A lead assists one call at a time.** A `join` from a lead already on a call -- their own or one
4240
- they joined -- is answered `failed`, whatever their ceiling; the request stands for another lead,
4241
- or until it is withdrawn or declined.
4242
-
4243
- **A lead on a break does not join.** A break is a reported state in which the agent is not working,
4244
- and a join is work. Omni offers Join to a lead only while their own `BreakState.status` is neither
4245
- `starting-after-task` nor `on-break` -- a committed break waiting for the lead's current work to
4246
- finish is not given more -- and a provider answers a `join` from a lead on such a break `failed`.
4247
- The request stands for another lead, as it does when this one is already on a call.
4240
+ **On `join` the provider bridges the lead's channel into the call.** The member's task moves to
4241
+ `leadAssist: { stage: "joined", leadId }`, the member on the team member list carries
4242
+ `listening: { mode: "join-call", since }`, and the lead's audio arrives as **Listening to a call**
4243
+ describes. Nothing is a task on the lead's desk: a join is the lead's act on the team surface, not
4244
+ work assigned to them.
4245
+
4246
+ **A lead is on one member's call at a time.** A `join` from a lead already on a call -- their own
4247
+ or a member's -- is answered `failed`; the request stands for another lead, or until it is
4248
+ withdrawn or declined. A join is work, so Omni offers Join only to a lead whose voice channel is
4249
+ free, and a provider answers a `join` from one whose channel is not `failed`.
4248
4250
 
4249
4251
  **On `decline`, or a request the agent withdraws with `{ type: "lead-assist", action: "cancel" }`, the
4250
4252
  provider clears `leadAssist` from the agent's task** and drops the request from every team member list. Nothing
4251
4253
  else changes; the agent is still on the call.
4252
4254
 
4253
- **Take over call** uses `{ type: "lead-assist", action: "take-over-call" }`.
4254
- The lead assumes the agent’s interaction; the caller’s wider journey continues.
4255
- Migration: replace the former take-over action literal with `take-over-call` in hosts and
4256
- providers together. The former spelling is rejected, with no compatibility alias.
4257
- The `taken-over` task outcome and existing completion behavior remain unchanged.
4258
- This action is distinct from `join-call`, which lets everyone hear a lead listening to a call.
4255
+ **On `leave`, the lead's channel drops and the call goes on**: the member's task loses its
4256
+ `leadAssist`, the member loses `listening`, and the lead is free. Nothing ended for the member.
4259
4257
 
4260
- The lead then has two commands on their copy, gated by `assisting` being present, and a third
4261
- choice that is no command at all:
4258
+ **On `take-over-call`, the provider moves the call from the member to the lead**, and the lead is
4259
+ its new agent. There are two paths to it -- the agent asked and the lead joined, or the lead was
4260
+ listening unasked -- and one act at the end of either. What follows is the same on both:
4262
4261
 
4263
- | The lead | The agent's task | The lead's task |
4262
+ | | The member's task | The lead's desk |
4264
4263
  | --- | --- | --- |
4265
- | `{ type: "lead-assist", action: "take-over-call" }` | `task-media-ended`, then `task-ended` with `{ type: "taken-over", leadId }`: **no `completing` window**, the agent is idle at once. The audio ends first, as before every voice ending (`stream.taskEnded.mediaOpen`), and the lead is named by user id, since a lead is not a directory item | Continues alone, and ends as any call does |
4266
- | `{ type: "lead-assist", action: "leave" }` | Continues; `leadAssist` is cleared | `task-media-ended`, then `task-ended` with `{ type: "left" }` -- the call goes on without them |
4267
- | Stays until the customer hangs up | `task-media-ended`, `completing`, its own outcome | The same, independently: **both have the completion window** |
4264
+ | Take-over | `task-audio-ended`, then `completing`: the member's interaction is over and their wrap runs, exactly as after `end-call`. The audio ends first, as before every voice ending (`stream.taskEnded.audioOpen`). | An ordinary assignment arrives: `task-offered` with `acceptance: "automatic"`, carrying the call's history and `takenOver: { memberId, since }`. |
4265
+ | The member completes | `task-ended` with `{ type: "taken-over", leadId }`, at the member's own completion. The lead is named by user id, since a lead is not a directory item. | The lead works the call as any agent would, and ends it as any call ends. |
4268
4266
 
4269
- `left` is the one outcome that ends a task without ending the call: this agent left a call that
4270
- continues without them. It reads as neither a completion nor a cancellation, because it is
4271
- neither.
4267
+ **The taken-over call is offered whatever break the lead is on.** A lead working as lead alone is
4268
+ on a break of a working kind, and the provider assigns the call regardless: it is the one task a
4269
+ break in effect may hold (`break.on-break.tasks` allows it, and nothing else). It is the lead's own
4270
+ act, so it counts against no capacity, like the agent's own dial; the only conditions are that the
4271
+ lead is not already on another call and their voice channel is free. A lead with the team feature
4272
+ off cannot take over, since they have no member's assignment to name.
4272
4273
 
4273
4274
  ```ts
4274
4275
  const leadAssistCapable = {
@@ -4277,6 +4278,13 @@ const leadAssistCapable = {
4277
4278
  phase: "in-progress",
4278
4279
  leadAssist: { stage: "joined", leadId: "L-9", note: "Refund dispute, needs approval", since: "2026-08-21T09:04:00Z" },
4279
4280
  } satisfies Pick<Task<"voice">, "channel" | "capabilities" | "phase" | "leadAssist">;
4281
+
4282
+ const takenOver = {
4283
+ channel: "voice",
4284
+ capabilities: { hold: true, endCall: true, outcomes: true },
4285
+ phase: "in-progress",
4286
+ takenOver: { memberId: "A-1", since: "2026-08-21T09:06:00Z" },
4287
+ } satisfies Pick<Task<"voice">, "channel" | "capabilities" | "phase" | "takenOver">;
4280
4288
  ```
4281
4289
 
4282
4290
  Lead and member alike are `UserId`s of this provider, so an adapter publishing them implements
@@ -4284,44 +4292,10 @@ Lead and member alike are `UserId`s of this provider, so an adapter publishing t
4284
4292
 
4285
4293
  ### Listening to a call
4286
4294
 
4287
- **Listen** lets the lead hear the call without being heard. The modes are `listen`, `coach`
4288
- (agent-only lead audio), and `join-call` (lead audio heard by everyone).
4289
-
4290
- | Former API | Current API |
4291
- | --- | --- |
4292
- | MonitorMode / MONITOR_MODES | `ListeningMode` / `LISTENING_MODES` |
4293
- | TeamMonitorCommand / TeamMonitorCommandRequest | `TeamListenCommand` / `TeamListenCommandRequest` |
4294
- | executeTeamMonitor | `executeTeamListen` |
4295
- | team.monitorControl | `team.listeningControl` |
4296
- | TaskMonitoring / task.monitoring | `TaskListening` / the task’s `listening` field |
4297
- | MONITORING_BREAK_KINDS / breakKindAllowsMonitoring | `LISTENING_BREAK_KINDS` / `breakKindAllowsListening` |
4298
- | monitor action and mode | `listen` action and mode |
4299
-
4300
- Update command dispatch, allowed modes, task snapshots, imports and diagnostics together.
4301
- The former names are not aliases. Old task and permission fields are rejected even alongside
4302
- new fields. Validation rule segments now use `listening`, `listeningControl`, and `listen`.
4303
- The existing login permissions, single-call limits, allowed break kinds and audio effects
4304
- remain unchanged. Listening includes its coach/join-call modes and does not imply takeover.
4305
-
4306
-
4307
- **Coach** lets the lead speak privately to the agent; the caller cannot hear the lead.
4308
- The command and listening mode are `coach`. Migration: replace the former whisper literal
4309
- in `TeamListenCommand`, `ListeningMode`, `team.listeningControl`, and the task’s `listening.mode`.
4310
- `LISTENING_MODES` exports `coach`; update command producers, permissions and retained snapshots
4311
- together. No legacy alias is accepted. The lead must already be listening and permitted to
4312
- coach. This mode does not join the audible caller conversation or take over the task.
4313
-
4314
- The user-facing action is **Join call**. The command and listening mode are `join-call`.
4315
- Migration: replace the former barge literal in `TeamListenCommand`, `ListeningMode`,
4316
- `team.listeningControl`, and the task’s `listening.mode`. `LISTENING_MODES` exports the new literal;
4317
- no legacy alias is accepted. Update commands, capability declarations and retained snapshots
4318
- together. The lead must already be listening and permitted to use this mode. Everyone on the
4319
- call hears the lead; this does not transfer ownership or invoke lead-assist takeover.
4320
-
4321
4295
  A lead may listen to a member's call without being asked -- to coach, to check quality, to step in
4322
- when it goes wrong. This is not lead assist: nobody requested it, the member's task says nothing
4323
- about it, and there is no take-over in it. A lead who wants the call itself uses lead assist. The
4324
- three modes determine who hears the lead:
4296
+ when it goes wrong. It is a team act naming the member, and no task on the lead's desk carries it:
4297
+ the lead's view of the call is the member's task on the team member list, with its room and its
4298
+ history. The three modes determine who hears the lead:
4325
4299
 
4326
4300
  | Mode | Who hears the lead |
4327
4301
  | --- | --- |
@@ -4329,61 +4303,44 @@ three modes determine who hears the lead:
4329
4303
  | `coach` | The agent alone. The customer hears nothing. |
4330
4304
  | `join-call` | Everyone on the call. |
4331
4305
 
4332
- The login says which modes this lead has, as a list on `team.listeningControl`, and it always
4333
- includes `listen`, because coach and join-call begin from one
4334
- (`authentication.capability.team.listeningControl.listen`). A centre that lets every lead listen but
4335
- reserves joining the call declares `["listen", "coach"]`, and Omni offers no Join call to that lead. The
4336
- declaration requires `executeTeamListen`, gated exactly as `executeTeamLeadAssist` is by
4337
- `team.leadAssistControl`.
4306
+ All three come with the `lead` flag; a centre reserves none of them per lead. The flow:
4338
4307
 
4339
4308
  ```ts
4340
4309
  // 1. The lead picks a member from the team member list and starts silent.
4341
- executeTeamListen({ command: { type: "listen", memberId: "A-1" } })
4310
+ executeTeam({ command: { type: "listen", memberId: "A-1" } })
4311
+ // team-updated: the member carries listening: { mode: "listen", since }
4312
+ // team-audio-started: { memberId: "A-1", assignmentId: "alloc-42" } -- the lead's audio attaches
4342
4313
 
4343
- // 2. The lead's own task arrives -- task-offered with `automatic`, as a joined call does -- and
4344
- // carries `listening`; the assignment is the lead's own, the member's is named inside it.
4345
- // listening: { memberId: "A-1", assignmentId: "alloc-42", mode: "listen", since }
4314
+ // 2. The lead changes how they are heard; the provider restates the member with the new mode.
4315
+ executeTeam({ command: { type: "coach", memberId: "A-1" } })
4316
+ executeTeam({ command: { type: "join-call", memberId: "A-1" } })
4346
4317
 
4347
- // 3. The lead changes how they are heard; the provider restates the task with the new mode.
4348
- executeTeamListen({ command: { type: "coach" } })
4349
- executeTeamListen({ command: { type: "join-call" } })
4350
-
4351
- // 4. The lead leaves; their task ends with `{ type: "left" }`. The member's call goes on.
4352
- executeTeamListen({ command: { type: "leave" } })
4318
+ // 3. The lead leaves, or takes the call.
4319
+ executeTeam({ command: { type: "leave", memberId: "A-1" } })
4320
+ executeTeam({ command: { type: "take-over-call", memberId: "A-1" } })
4353
4321
  ```
4354
4322
 
4355
- **The lead's task is what gives them audio**: Omni opens media on it as on any voice task, and the
4356
- provider bridges the lead's leg into the member's call in the mode stated. `listening.mode` is
4357
- the state, restated on every change, and a `coach` or `join-call` the login's list does not include
4358
- is answered `failed`. **A lead listens to one call at a time** (`snapshot.listening.single`), and
4359
- a task carries lead assistance or listening, never both (`task.listening.assisting`).
4360
- The `join-call` mode still belongs to listening; it does not create `assisting`. The member's
4361
- call ending ends the lead's task as it ends the member's, with `left`, since the lead was never
4362
- working on it.
4363
-
4364
- **A lead listens only while holding no work of their own.** A listening task is the lead's only
4365
- task, and a snapshot carrying one beside any other is refused (`snapshot.listening.alone`); Omni
4366
- offers Listen to a lead with no task. That includes a lead on a break -- unlike joining, which a
4367
- break forbids -- but only a break that is work of another sort: `coaching`, `administrative` or
4368
- `training`, the kinds in `LISTENING_BREAK_KINDS`, and never a meal or a rest. A break in effect
4369
- otherwise holds no task; the listening task is its one exception, and a snapshot carrying one
4370
- during a break whose active reason is of any other kind, or of no stated kind, is refused
4371
- (`snapshot.listening.break`). The kind decides, not the reason's name: a centre that wants leads
4372
- listening during a break declares that break `coaching`, `administrative` or `training`.
4323
+ **The lead's audio arrives through team audio.** While the lead's channel is in a member's call
4324
+ -- listening, coaching or joined on request -- the lead's connection receives `team-audio-started`
4325
+ naming the member and the member's assignment, and on a softphone the agent application opens
4326
+ `openAudio` on that assignment as it does for a task; `team-audio-ended` closes it. Voice only, and
4327
+ to a login that declares `lead` (`event.teamAudio.*`). The member's own audio events are the
4328
+ member's and never reach the lead.
4329
+
4330
+ **`listening` on the member is the state**, restated on every change of mode, and a lead is on
4331
+ one member's call at a time (`team.listening.single`). The member's call ending ends the lead's
4332
+ listening with it: the provider republishes the member without `listening` and sends
4333
+ `team-audio-ended`.
4334
+
4335
+ **A lead listens while holding no call of their own.** Omni offers Listen to a lead whose voice
4336
+ channel is free, and a provider answers a `listen` from one whose channel is not `failed`. That
4337
+ includes a lead on a break: a lead working as lead alone is on a break of a working kind, and
4338
+ listens through it.
4373
4339
 
4374
4340
  **Nothing reaches the member.** The member's task carries no trace of a listening lead in any
4375
4341
  mode: not on `onCall`, not in the record. Whether coaching is announced to the agent is the
4376
4342
  platform's business and travels on the audio, not on this wire.
4377
4343
 
4378
- ```ts
4379
- const listeningLead = {
4380
- channel: "voice",
4381
- capabilities: {},
4382
- phase: "in-progress",
4383
- listening: { memberId: "A-1", assignmentId: "alloc-42", mode: "coach", since: "2026-08-21T09:04:00Z" },
4384
- } satisfies Pick<Task<"voice">, "channel" | "capabilities" | "phase" | "listening">;
4385
- ```
4386
-
4387
4344
  ### A member waiting for a break
4388
4345
 
4389
4346
  A member who has asked for a break **keeps working** until Omni commits it, so asking is not an
@@ -4411,27 +4368,27 @@ whole and replaced whole, and a provider that cannot say omits it.
4411
4368
 
4412
4369
  **An agent is not waiting on one person.** Authority is held by several, everyone who holds it
4413
4370
  sees the request on their own console, and **any one of them settles it**. Omni offers the
4414
- decision to every login that declares `team.breakControl` — which is how the provider already
4371
+ decision to every login that declares `lead` and has the team feature on — which is how the provider already
4415
4372
  says who may decide — and does not try to work out whose turn it is.
4416
4373
 
4417
4374
  A request needing *more than one* approval is not something this contract describes. There is
4418
4375
  no partial state to report and no progress to display: a request is either still owed a
4419
4376
  decision or it is not.
4420
4377
 
4421
- ## Real-time media
4378
+ ## Real-time audio
4422
4379
 
4423
- Every voice provider has media: `channel: "voice"` says audio exists. Where the agent hears it
4380
+ Every voice provider has audio: `channel: "voice"` says audio exists. Where the agent hears it
4424
4381
  depends on the `phone` the agent application selected at authentication and connect from the manifest's
4425
4382
  `phones`. On a softphone, audio lands in Omni; on a desk phone, it lands on the handset and Omni
4426
- opens no audio. See **How the agent hears the call**. Media transitions are voice-only;
4427
- chat and email publish none (`event.media.channel`, `stream.taskMedia.channel`).
4383
+ opens no audio. See **How the agent hears the call**. Audio transitions are voice-only;
4384
+ chat and email publish none (`event.audio.channel`, `stream.taskAudio.channel`).
4428
4385
 
4429
4386
  The provider owns signalling and the platform's endpoint configuration. The agent application selects the
4430
4387
  declared phone mode; it does not enumerate or reconfigure the platform's devices. The selected
4431
4388
  mode stays with the login rather than being inferred from a snapshot or a device failure.
4432
4389
 
4433
4390
  Nor does the audio ever stand in for the task: a task's presence and
4434
- phase follow the provider's reports about the work, and the media — attaching, moving through a
4391
+ phase follow the provider's reports about the work, and the audio — attaching, moving through a
4435
4392
  hold, a consult, a conference or a transfer, and ending — is transient beside it. See **A task is
4436
4393
  never its audio** under **Task assignment lifecycle**.
4437
4394
 
@@ -4468,7 +4425,7 @@ refuses a false one, as it refuses a name this contract does not list.
4468
4425
  | --- | --- |
4469
4426
  | `online` | Whether the agent application has a network interface up. Not a claim that anything is reachable — the adapter knows whether it can reach its own platform far better than the agent application does — so `false` is a reason not to go ready and `true` is not a reason to. |
4470
4427
  | `audio` | Present on a voice connection, absent where there is no audio. |
4471
- | `audio.input` | `available` with `localAudio` — the microphone as captured, the same stream `openMedia` receives — and `flowing`, false while no audio moves through it, when `mutedBy` says who stopped it: `host`, the agent application's own Mute on an agent application that mutes the station, or `station`, a slider on the headset or the operating system, which the agent application observed and did not do. An adapter treats `host` as the agent's act on a call and `station` as a condition of the station. `unavailable` with `reason`, since each wants a different fix from the agent: `no-device`; `denied`; `not-asked`, which an agent application that asks at connect never publishes; `in-use`, a device present and permitted that another application holds — on an agent desktop the commonest of all; `lost`, a capture that ended. An agent application decides the reason from the devices before the error name: a browser can report a permission error on a machine with no microphone at all, and "grant permission" is the wrong instruction for an agent who needs to plug one in. `failure` carries the words Omni showed them. |
4428
+ | `audio.input` | `available` with `localAudio` — the microphone as captured, the same stream `openAudio` receives — and `flowing`, false while no audio moves through it, when `mutedBy` says who stopped it: `host`, the agent application's own Mute on an agent application that mutes the station, or `station`, a slider on the headset or the operating system, which the agent application observed and did not do. An adapter treats `host` as the agent's act on a call and `station` as a condition of the station. `unavailable` with `reason`, since each wants a different fix from the agent: `no-device`; `denied`; `not-asked`, which an agent application that asks at connect never publishes; `in-use`, a device present and permitted that another application holds — on an agent desktop the commonest of all; `lost`, a capture that ended. An agent application decides the reason from the devices before the error name: a browser can report a permission error on a machine with no microphone at all, and "grant permission" is the wrong instruction for an agent who needs to plug one in. `failure` carries the words Omni showed them. |
4472
4429
  | `audio.output` | `available`, with `flowing` where the agent application can know whether audio reaches the speaker — a browser mostly cannot, and omits it; a native agent application reads the endpoint — false while it is silenced, when `mutedBy` says who did it, as on input. Or `unavailable` with `reason` — `no-device`, or `lost` for one removed — and `failure`: an agent who cannot hear is as unable to take a call as one who cannot speak. |
4473
4430
 
4474
4431
  Omni republishes the report whenever it changes — a permission granted late, a headset unplugged,
@@ -4537,10 +4494,10 @@ A voice manifest lists its `phones` and any other channel lists none (`manifest.
4537
4494
  `manifest.phones.channel`). The agent application's `phone` is required on a voice login, absent on any other,
4538
4495
  and one the manifest listed (`context.phone.required`, `.unexpected`, `.unsupported`). Everything
4539
4496
  about audio then follows the phone rather than the channel: on a softphone the agent application reports its
4540
- audio, the adapter implements `openMedia`, and `task-media-started` is the word to open it; on a
4541
- desk phone the agent application reports no audio, opens nothing, and `task-media-started` still marks the
4497
+ audio, the adapter implements `openAudio`, and `task-audio-started` is the word to open it; on a
4498
+ desk phone the agent application reports no audio, opens nothing, and `task-audio-started` still marks the
4542
4499
  moment the call is live so the desk shows it, with the sound on the handset. A platform that lists
4543
- `deskPhone` alone never implements `openMedia`; one that lists `softphone` always does.
4500
+ `deskPhone` alone never implements `openAudio`; one that lists `softphone` always does.
4544
4501
 
4545
4502
  **The mode comes from the agent application; the status comes from the provider.** The agent application knows which kind
4546
4503
  of station the agent signed in at, because the person chose it, and nothing else can know that.
@@ -4552,10 +4509,10 @@ the handset is momentarily unregistered, and demanding a microphone of it, is th
4552
4509
  sentence exists to refuse. What the provider does when the handset is not registered is what it
4553
4510
  does for any station that cannot take a call -- hold the agent not-ready and say why.
4554
4511
 
4555
- **On a desk-phone login the agent application never calls `openMedia`.** There is no stream to hand over and
4512
+ **On a desk-phone login the agent application never calls `openAudio`.** There is no stream to hand over and
4556
4513
  no audio to attach; an agent application that calls it anyway is in error, and an adapter that receives the call
4557
4514
  answers `unavailable` with a non-retryable failure, since waiting changes nothing about a station
4558
- that is a telephone. The harness requires no `openMedia` of such an adapter and never calls it.
4515
+ that is a telephone. The harness requires no `openAudio` of such an adapter and never calls it.
4559
4516
 
4560
4517
  **Which handset a desk-phone login rings is the platform's configuration for that agent**, and
4561
4518
  this wire never asks the agent for it: an agent application declares `phone` and nothing more. What a platform
@@ -4578,11 +4535,11 @@ is the administrator's, and a login is not a request to reconfigure an agent.
4578
4535
 
4579
4536
  ### Opening the audio
4580
4537
 
4581
- `openMedia` hands Omni the remote audio for one task. Every adapter whose manifest lists
4538
+ `openAudio` hands Omni the remote audio for one task. Every adapter whose manifest lists
4582
4539
  `softphone` implements it, because on a softphone the call's audio lands in Omni:
4583
4540
 
4584
4541
  ```ts
4585
- openMedia({ assignmentId, localAudio }): Promise<OpenMediaResult>
4542
+ openAudio({ assignmentId, localAudio }): Promise<OpenAudioResult>
4586
4543
  // { status: "opened", session } | { status: "unavailable", failure }
4587
4544
  ```
4588
4545
 
@@ -4605,8 +4562,8 @@ carries as `audio.input.localAudio`, and absent while that input is `unavailable
4605
4562
  bridges audio without an application-side input may ignore it; one that needs it and finds it absent
4606
4563
  answers `unavailable` with a failure Omni shows the agent.
4607
4564
 
4608
- **When to ask is the provider's word, not Omni's guess.** On a softphone login, Omni opens media on `task-media-started`,
4609
- and on a task arriving with `media: "started"` on a snapshot; it closes on `task-media-ended` and
4565
+ **When to ask is the provider's word, not Omni's guess.** On a softphone login, Omni opens audio on `task-audio-started`,
4566
+ and on a task arriving with `audio: "started"` on a snapshot; it closes on `task-audio-ended` and
4610
4567
  when the task ends. Between those words, nothing Omni's own senses report — a stream that drops, a
4611
4568
  track that ends — moves the task or its audio.
4612
4569
 
@@ -4619,7 +4576,7 @@ platform is the adapter's business.
4619
4576
  ## Task commands
4620
4577
 
4621
4578
  Command names follow the channel's operational vocabulary, and each channel's command is a union
4622
- discriminated by `type` — the same discriminant `executeTeamBreak` and `custom` already use. The
4579
+ discriminated by `type` — the same discriminant `executeTeam` and `custom` already use. The
4623
4580
  unions are declared under **Shapes**.
4624
4581
 
4625
4582
  `assignmentId` is not repeated on the command. It travels on the `TaskCommandRequest` around it.
@@ -4656,8 +4613,8 @@ agent application's. So mute is not a capability a provider declares, not a cont
4656
4613
  locks, not a preference the person keeps with the provider, and not a command: nothing about the
4657
4614
  station crosses to the provider except two things -- the station's condition, in the agent application report,
4658
4615
  and the record of when the agent could not be heard, through `recordStep`. An agent application offers Mute on
4659
- every voice task with media open, under its own local policy, in `in-progress` and `paused`
4660
- alone, and performs it through `VoiceMediaSession.setMuted()`. A task carrying
4616
+ every voice task with audio open, under its own local policy, in `in-progress` and `paused`
4617
+ alone, and performs it through `CallAudio.setMuted()`. A task carrying
4661
4618
  `capabilities.mute` is refused (`task.capability.unknown`), and so is a `mute` command
4662
4619
  (`command.type`), a `mute` preference (`preference.id`) and a `mute` policy (`team.policy.key`).
4663
4620
 
@@ -4693,9 +4650,9 @@ forty seconds" and "the agent's headset was muted for forty seconds" as the diff
4693
4650
  are. See **The agent application records what it performs**.
4694
4651
 
4695
4652
  **Mute has a lifecycle, and none of it is inferred.** A call that starts while the station is
4696
- already muted begins a `muted` leg the moment its media starts, `mutedBy: "station"`. Media that
4653
+ already muted begins a `muted` leg the moment its audio starts, `mutedBy: "station"`. Audio that
4697
4654
  ends while any leg is open ends the leg at that instant, as every agent application-performed leg ends. And the
4698
- agent application's own Mute starts off on every call, which is every `task-media-started` -- a connect-back
4655
+ agent application's own Mute starts off on every call, which is every `task-audio-started` -- a connect-back
4699
4656
  opens a second call on the same task with no offer -- so an agent is never muted by the call before.
4700
4657
 
4701
4658
  **Every press on a headset is the agent's own press**, performed the agent application's way, and the lights
@@ -4731,22 +4688,22 @@ declared:
4731
4688
  | Command | What makes it available |
4732
4689
  | --- | --- |
4733
4690
  | `answer`, `accept` | Nothing. A task that was offered can be accepted, or offering it meant nothing. |
4734
- | `end-call` | The `endCall` capability. |
4691
+ | `end-call` | The `endCall` capability: ends my part, the caller continues. |
4692
+ | `terminate-call` | The `terminateCall` capability: ends the caller channel. |
4735
4693
  | `conference` with `action: "remove"` | The `conference` capability, and somebody else on the call: a remove that would leave the agent alone is `end-call`, and a provider answers it `failed`. |
4736
4694
  | `decline` | The `decline` capability on any channel, **and** Omni local policy permitting it. One word for refusing an offer, whatever the channel. |
4737
- | `call` | The `preview` phase. A record put in front of an agent is there to be called, so the phase is the gate and there is no capability. It is a dial, with a `dialId` and a `dial-outcome`. |
4695
+ | `dial` | The `preview` phase. A record put in front of an agent is there to be called, so the phase is the gate and there is no capability. It is a dial, with a `dialId` and a `dial-outcome`. |
4738
4696
  | `complete` | `completionMode: "agent-command"`. The `outcomes` capability decides whether a code travels with the command, never whether the command exists — a task Omni cannot complete never ends. What travels is what the capability published: a code from its list where it has one (`command.complete.outcome.unknown`), a code at all where it requires one (`.outcome.required`), notes as it said (`.notes.required`, `.notes.unexpected`), and neither where the task declares no outcomes (`.outcome.unexpected`). |
4739
4697
  | `connect-back` | The `connectBack` capability **and** the `completing` phase. It exists to reach the party again after the call, so it has no meaning while the call is up. |
4740
4698
  | `transfer` with `action: "warm"` | The `warmTransfer` capability. `action: "cold"` is gated by `coldTransfer`; the two are declared and offered separately. |
4741
4699
  | `transfer` with `action: "complete"` or `"cancel"` | A consultation in progress -- a `consulted` entry on `Task.onCall`. Without one there is nothing to complete or cancel, and a provider that receives either answers `failed`. |
4742
4700
  | `lead-assist` with `action: "request"` or `"cancel"` | The `leadAssist` capability. `cancel` needs a request standing -- `Task.leadAssist` with status `requested`. |
4743
- | `lead-assist` with `action: "take-over-call"` or `"leave"` | The lead's own task, on a call they joined -- `Task.assisting` present. An agent's task never has it, and a provider that receives either without it answers `failed`. |
4744
4701
  | `transfer` with a destination, `conference` with `action: "add"` | Its capability, and a `destinationId` the directory offered: the id Omni sends is the id the provider published (`command.destination.unknown`). |
4745
4702
  | `custom` | A control the task published under `capabilities.custom`, by its `id` (`command.capability.custom`), carrying a non-empty string for each `required` prompt field and strings for any optional fields supplied (`command.custom.prompt`). A toggle carries its target `on` boolean (`command.custom.on`). |
4746
4703
  | Everything else | Its own named capability. |
4747
4704
 
4748
4705
  **A control on the contact belongs to the interaction phases**, `in-progress` and `paused`:
4749
- `hold`, `resume` and `pause`, `end-call`, every `transfer` and `conference` action, and
4706
+ `hold`, `resume` and `pause`, `end-call` and `terminate-call`, every `transfer` and `conference` action, and
4750
4707
  every `lead-assist` action. These controls act within this agent's current interaction.
4751
4708
  In `completing`, that interaction has ended and its wrap work remains. The caller may still be in
4752
4709
  an IVR, queue or another agent's interaction, and other channels may remain connected. Completion
@@ -4755,7 +4712,7 @@ declared while the task remains open; the phase prevents this task from controll
4755
4712
  it no longer owns. Omni shows these controls only in the two interaction phases, and
4756
4713
  `validateTaskCommand` refuses them outside those phases (`command.phase.interaction`).
4757
4714
  The commands with a phase of their own -- `answer`, `accept` and `decline` in `pending`,
4758
- `call` in `preview`, `connect-back` in `completing`, `complete` in any -- are not among them.
4715
+ `dial` in `preview`, `connect-back` in `completing`, `complete` in any -- are not among them.
4759
4716
 
4760
4717
  `validateTaskCommand(command, task)` holds a command to this table at runtime, both ways: the
4761
4718
  capability it needs, the phase it belongs to, and the state that has to stand. The task it wants
@@ -5104,30 +5061,30 @@ snapshots until it ends.
5104
5061
  Replaces the current representation of one provider-local task. It is a full task value, not a
5105
5062
  partial patch.
5106
5063
 
5107
- ### `task-media-started`
5064
+ ### `task-audio-started`
5108
5065
 
5109
- The provider's word that the task's audio should now attach. Omni calls `openMedia` on it — and on
5110
- a task carried with `media: "started"`, which is how a reconnect snapshot reattaches audio an
5066
+ The provider's word that the task's audio should now attach. Omni calls `openAudio` on it — and on
5067
+ a task carried with `audio: "started"`, which is how a reconnect snapshot reattaches audio an
5111
5068
  earlier event brought — and renders the call as live from that word, never from its own senses. It
5112
- precedes `openMedia` and is never a reply to it: a provider whose media state comes from the
5069
+ precedes `openAudio` and is never a reply to it: a provider whose audio state comes from the
5113
5070
  platform, a station going in use the moment a call is answered, sends it then, before any agent application has
5114
- opened anything, and `openMedia` has its own answer for what the agent application did. It
5115
- names a task whose work has begun, and it alternates with `task-media-ended`: media that never
5071
+ opened anything, and `openAudio` has its own answer for what the agent application did. It
5072
+ names a task whose work has begun, and it alternates with `task-audio-ended`: audio that never
5116
5073
  started cannot end, so a live call whose provider says nothing about its audio is a provider in
5117
5074
  breach, not a state a desk fills in from its own devices.
5118
5075
 
5119
- The event is the transition and the task's `media` field is the state. A `task-updated` re-states
5120
- the media its task already holds — republishing `started` on a hold is a statement, not a second
5076
+ The event is the transition and the task's `audio` field is the state. A `task-updated` re-states
5077
+ the audio its task already holds — republishing `started` on a hold is a statement, not a second
5121
5078
  arrival — but it does not move it: an update that itself flips the field is refused
5122
- (`stream.taskUpdated.media`), and the pairing at the moment audio arrives is the phase change
5079
+ (`stream.taskUpdated.audio`), and the pairing at the moment audio arrives is the phase change
5123
5080
  without the field, then the event. Releasing `ended` is the one move an update may make, since
5124
5081
  wrapped audio has nothing left to end.
5125
5082
 
5126
- ### `task-media-ended`
5083
+ ### `task-audio-ended`
5127
5084
 
5128
- Signals that a task's real-time media ended. For voice and similar channels, this starts the fixed
5129
- completion timer. It does not remove the task, and it ends only audio that `task-media-started` — or
5130
- a task carried with `media: "started"` — attached.
5085
+ Signals that a task's real-time audio ended. For voice and similar channels, this starts the fixed
5086
+ completion timer. It does not remove the task, and it ends only audio that `task-audio-started` — or
5087
+ a task carried with `audio: "started"` — attached.
5131
5088
 
5132
5089
  ### `task-ended`
5133
5090
 
@@ -5135,22 +5092,22 @@ Every outcome ends the task for this agent. On `task-ended`, Omni:
5135
5092
 
5136
5093
  - removes the task from its current provider view;
5137
5094
  - clears the task workspace when it is selected;
5138
- - stops task timers and media;
5095
+ - stops task timers and audio;
5139
5096
  - releases task-scoped resources; and
5140
5097
  - selects another task or returns to the idle workspace.
5141
5098
 
5142
- A `left` outcome ends the task for this agent alone: the call continues without them, as it does
5143
- when a lead who joined it leaves -- see **Lead assist**.
5099
+ A `taken-over` outcome arrives at the member's own completion, after the wrap that follows a
5100
+ take-over as it follows an `end-call` -- see **Lead assist**.
5144
5101
 
5145
5102
  A successful `complete` or `transfer` command does not clear the task. Omni waits for `task-ended`,
5146
- and not for ever: `applied` to a completion -- `complete`, or a lead's `take-over-call` -- says the
5103
+ and not for ever: `applied` to a `complete` says the
5147
5104
  provider has completed the task, and its `task-ended` follows within the
5148
5105
  manifest's `completionSettleMs`. A provider never answers `applied` for a completion it has not yet
5149
5106
  performed. Past the bound the agent application calls `snapshot()`: a snapshot still carrying the task is a task
5150
5107
  held open by a provider that said it was done, and the desk shows it as unsettled -- "Completing...
5151
5108
  the provider has not confirmed" -- naming the command; a snapshot no longer carrying it clears the
5152
5109
  task, since the ending was owed and lost. The drive holds a provider to the same bound
5153
- (`drive.completion.unsettled`). The `task-media-ended` event and the `completing` phase are likewise
5110
+ (`drive.completion.unsettled`). The `task-audio-ended` event and the `completing` phase are likewise
5154
5111
  non-terminal. A replacement
5155
5112
  snapshot that no longer contains the task also clears it. Repeated `task-ended` delivery with the
5156
5113
  same envelope ID is harmless, and a `task-ended` naming an assignment that has already ended is
@@ -5184,8 +5141,8 @@ console is the silence problem one layer up. One event per occurrence; the agent
5184
5141
  provider does not batch. `exerciseAdapter` treats a diagnostic delivered during a run as a
5185
5142
  violation (`diagnostic.raised`): a conformance run against a platform that is breaking its rules
5186
5143
  fails loudly rather than passing with a note. A task published under `capabilitySource:
5187
- "undetermined"` is the same kind of fact, stated on the task instead, and fails a run the same way
5188
- (`capabilitySource.undetermined`); see **Task capabilities**.
5144
+ "not-yet-read"` is the same kind of fact, stated on the task instead, and fails a run the same way
5145
+ (`capabilitySource.notYetRead`); see **Task capabilities**.
5189
5146
 
5190
5147
  | Field | Contract |
5191
5148
  | --- | --- |
@@ -5211,7 +5168,7 @@ each connected provider.
5211
5168
  Replaces this provider's complete `TeamMembers`. It is emitted only for an agent the provider
5212
5169
  publishes a team member list to, and it carries the whole team every time — never a change to it, for the
5213
5170
  reason set out under **Team leads**. A lead's snapshot always carries the team member list; it goes only when
5214
- a republished `authenticated` no longer declares `capabilities.team`.
5171
+ a republished `authenticated` no longer declares `capabilities.lead`, or the lead turns the team feature off (`event.team.features`).
5215
5172
 
5216
5173
  ### `contacts-updated`
5217
5174
 
@@ -5285,9 +5242,9 @@ Some rules need to know who is reading. `validateTeamMembers`, `validateSnapshot
5285
5242
  `AuthenticationState.identity.id` and their login's `capabilities`. Given `self`, a team member list that
5286
5243
  carries that agent reports `team.member.self` or `team.request.self`. Given `capabilities`, a lead's
5287
5244
  snapshot without a team member list reports `team.required`, a team member list published to a login that does not lead
5288
- reports `team.unentitled`, `requests` on a team member list whose login lacks `team.leadAssistControl` reports
5289
- `team.requests.capability`, and a team member list without them on a login that declares it reports
5290
- `team.requests.required`. Without them those rules are not checked, because they cannot be.
5245
+ reports `team.unentitled`, and a `team-updated` to such a login reports `event.team.capability`; given
5246
+ `leadFeatures: false`, a team member list reaching a lead who turned the feature off reports
5247
+ `team.unexpected` or `event.team.features`. Without them those rules are not checked, because they cannot be.
5291
5248
  `exerciseAdapter` always passes both.
5292
5249
 
5293
5250
  `assertNoViolations(violations)` throws `ProtocolConformanceError` — which carries the full
@@ -5355,10 +5312,10 @@ Nothing there is a violation: an adapter with no team has nothing to exercise. B
5355
5312
  no tasks exercises no task rule, and a pass over it reads as coverage it is not.
5356
5313
 
5357
5314
  **A static run never answers a call.** It states a capacity and disconnects, so everything
5358
- downstream of `answer` -- the room, the stage, media, every phase past `pending`, every dial
5315
+ downstream of `answer` -- the room, the stage, audio, every phase past `pending`, every dial
5359
5316
  outcome -- stays in `notExercised` for every adapter, and a rule about a live call is enforced only
5360
5317
  in each adopter's own tests. `{ drive: true }` closes that: the exercise takes the first task the
5361
- provider offers through one ordinary lifecycle -- accept it, wait for its media and open it on a
5318
+ provider offers through one ordinary lifecycle -- accept it, wait for its audio and open it on a
5362
5319
  softphone, hold and resume where the task offers `hold`, end the call where it offers `endCall`,
5363
5320
  complete it with an outcome where the agent completes -- and holds every step to the rules a
5364
5321
  agent application holds a provider to. Each command is validated against the task as published
@@ -5373,17 +5330,17 @@ nothing (`getUserDetails.user.*`, `getUserDetails.unasked`, `connection.getUserD
5373
5330
  the second adapter a `rebuild` gives comes up signed in as the same login from the secrets alone
5374
5331
  before it reads anything (`drive.reload.login`), and is then held to everything the first was on
5375
5332
  connect: the methods its declarations call for, the agent application's report, a capacity stated to it, its
5376
- snapshot read as the connect snapshot was, and its media opened afresh on a task carried with
5377
- `media: "started"`, since the first client's session died with it. The result also says which rules the
5333
+ snapshot read as the connect snapshot was, and its audio opened afresh on a task carried with
5334
+ `audio: "started"`, since the first client's session died with it. The result also says which rules the
5378
5335
  run evaluated, pass or fail, in `rulesEvaluated`: the validators' as each was applied, the stream's
5379
5336
  as each case was considered, so a test that needs a rule to have run asserts it there rather than
5380
5337
  inferring it from an empty `violations`, and a rule absent from it was never looked at, which is a
5381
5338
  gap and not a pass. With the audio open on a softphone,
5382
5339
  the drive mutes it for one second and reports the leg through `recordStep`, begun and then ended,
5383
5340
  expecting each report `recorded` with the provider-selected history `at` (`drive.recordStep.failed`, `.rejected`, `result.recordStep.at`); then it mutes again and
5384
- ends the call muted, as agents do, so the leg is open when the media ends, the provider closes it
5341
+ ends the call muted, as agents do, so the leg is open when the audio ends, the provider closes it
5385
5342
  in the completing publication or the open entry is refused (`task.history.muted.open`),
5386
- and the drive's closing report after the media ended is expected `recorded` and to change nothing:
5343
+ and the drive's closing report after the audio ended is expected `recorded` and to change nothing:
5387
5344
  a record restated afterwards with that leg's duration altered is named (`drive.recordStep.overwritten`).
5388
5345
  Where the provider restates the task's record afterwards, each leg is in it or the hole is named
5389
5346
  (`drive.recordStep.history`). Given `rebuild`, a way to build the adapter again as an agent application reload
@@ -5396,7 +5353,7 @@ the record in memory has nothing and is named (`drive.reload.snapshot`, `drive.r
5396
5353
  `.rejected`). The second adapter's snapshot is taken as any resync is: held to what the stream knew
5397
5354
  before it replaces it, so a phase gone backwards, a record that shrank or audio forgotten on a task
5398
5355
  still at work is named by the stream's own rules (`stream.snapshot.phase`, `.history`,
5399
- `.media`) and a reload is a place those rules keep working, not one where they stop. The second
5356
+ `.audio`) and a reload is a place those rules keep working, not one where they stop. The second
5400
5357
  adapter is held to what the first was: the same provider
5401
5358
  (`drive.reload.manifest`), a snapshot that stands as any snapshot must, and a record that lost none
5402
5359
  of the entries the first had published -- a record once read is not unread across a reload either.
@@ -5423,15 +5380,15 @@ completed from wherever it stands once the drive has nothing left to do on it --
5423
5380
  after an `end-call`, or from `in-progress` where there is no call to end, which is every chat and
5424
5381
  email and a voice task offering no `endCall` -- so a conversation reaches its end as a call does.
5425
5382
  The drive stops where the task offers no way on and says nothing about what it could not reach. A
5426
- softphone adapter written for a browser needs its media APIs supplied by whatever runs the
5427
- harness: the drive opens media outside a browser, and a provider that quietly stopped carrying audio
5383
+ softphone adapter written for a browser needs its audio APIs supplied by whatever runs the
5384
+ harness: the drive opens audio outside a browser, and a provider that quietly stopped carrying audio
5428
5385
  where `AudioContext` was missing would be lying about the one thing the channel is for. It is off by default because it issues
5429
5386
  commands against whatever platform the adapter is connected to: turn it on against a test backend.
5430
5387
 
5431
5388
  ```ts
5432
5389
  const driven = await exerciseAdapter(adapter, context, { collectOnly: true, drive: true });
5433
5390
  expect(driven.violations).toEqual([]);
5434
- assertReached(driven, ["task.onCall", "task.media", "event.task-ended"]);
5391
+ assertReached(driven, ["task.onCall", "task.audio", "event.task-ended"]);
5435
5392
  ```
5436
5393
  `assertReached(result, subjects)` is the paired assertion: it throws naming every subject the run
5437
5394
  never met, so a test that meant to check a team member list cannot pass on a fixture that never produced one.
@@ -5459,7 +5416,7 @@ cannot be established from TypeScript structure alone.
5459
5416
 
5460
5417
  | Helper | Contract checked |
5461
5418
  | --- | --- |
5462
- | `assertCapabilityWithdrawal(states, snapshot, manifest)` | A capability withdrawn by a later `authenticated` state is gone from the next snapshot: no team member list for a login that no longer leads, no requests for one that may no longer join. Every state is validated on the way, `refreshing` must carry the login over, and the sequence passes only through usable states. |
5419
+ | `assertCapabilityWithdrawal(states, snapshot, manifest)` | A capability withdrawn by a later `authenticated` state is gone from the next snapshot: no team member list for a login that no longer leads. Every state is validated on the way, `refreshing` must carry the login over, and the sequence passes only through usable states. |
5463
5420
  | `assertTaskCapabilityWithdrawal(tasks, manifest, command)` | A capability withdrawn by a republish of the task is gone from the task: every task in the sequence is validated, all carry the offer's id, at least one capability the offer declared is absent at the end (a locked control is present, not withdrawn), and `command` is clean against the first task and refused against the last for want of a withdrawn capability and nothing else. Pair it with `assertCommandRefusedAfterWithdrawal` on the provider's answer. |
5464
5421
  | `assertCommandRefusedAfterWithdrawal(result)` | A command that arrives after its capability was withdrawn fails with `omni.capability-not-enabled`, named by the provider. The same assertion serves a command the provider never supported under a capability it declares. |
5465
5422
  | `assertReached(result, subjects)` | The exercise met every subject named; throws listing those it did not. Pair it with a clean `exerciseAdapter` result. |
@@ -5468,11 +5425,11 @@ cannot be established from TypeScript structure alone.
5468
5425
  | `stillHost(report?, guarantees?, mute?)` | An agent application that reports one thing and never changes, for a test context: `{ online: true }` by default, a report with audio for a softphone voice adapter, and never for a desk phone. |
5469
5426
  | `TaskStream`, `BreakStream` | The cross-event models the harness applies after the connect snapshot, exported for an agent application that wants the same rules at its boundary: `seed(snapshot)`, then `apply(envelope)` returns the violations. |
5470
5427
  | `assertBreakFollowsItsRequests(envelopes, snapshot?)` | A break follows its requests: a commit's states only after a grant, never backwards, and a forced break arriving in effect with `forced`. The harness applies the same rules after the connect snapshot. |
5471
- | `assertMediaFollowsTheTask(envelopes, snapshot?)` | The media follows the task and never decides it: every task is introduced once, `task-media-started` and `task-media-ended` alternate on work that has begun, media ends only where it arrived, and what follows the media ending is `completing` or `task-ended`. The harness applies the same rules to every event after the connect snapshot (`stream.*`). A sequence with no media satisfies it by never testing it — pair it with the assertion that the media end is present. |
5428
+ | `assertAudioFollowsTheTask(envelopes, snapshot?)` | The audio follows the task and never decides it: every task is introduced once, `task-audio-started` and `task-audio-ended` alternate on work that has begun, audio ends only where it arrived, and what follows the audio ending is `completing` or `task-ended`. The harness applies the same rules to every event after the connect snapshot (`stream.*`). A sequence with no audio satisfies it by never testing it — pair it with the assertion that the audio end is present. |
5472
5429
  | `assertBreakAttemptProviders(candidates, asked)` | A break attempt asks every usable provider holding capacity, `refreshing` included, and nothing of a provider whose login is `expired`. |
5473
5430
  | `assertBreakBeginsAfterTask(steps)` | A break asked for on a task is committed as `starting-after-task` while work remains and reaches `on-break` only once nothing is outstanding — never beside a task, never later than the step that has none. |
5474
5431
  | `assertDeniedAndRetriedBreak(states)` | A denial transitions directly to `not-requested`; a later request can still be granted. |
5475
- | `assertWrapTimeout(task, mediaEndedAt, deadline, toleranceMs?)` | The wrap deadline equals media end plus the task allowance, within a tolerance that defaults to 1000ms; a task with no allowance has no deadline, and one observed is the violation. |
5432
+ | `assertWrapTimeout(task, audioEndedAt, deadline, toleranceMs?)` | The wrap deadline equals audio end plus the task allowance, within a tolerance that defaults to 1000ms; a task with no allowance has no deadline, and one observed is the violation. |
5476
5433
  | `assertBrowserSessionIsolation(left, right, expected)` | Browser reuse follows only the declared isolation scheme. |
5477
5434
  | `assertNoBrowserSessionKeyCollisions(scenarios)` | No two distinct scenarios derive the same session key. Feed it adversarial names. |
5478
5435
 
@@ -5505,11 +5462,11 @@ action sets. The outer capability may be locked. Absence grants no permission, a
5505
5462
  exist without any permission. A provider may offer only stop for a recording started automatically,
5506
5463
  or withdraw a control on a later task update. There is no global recording mode and no automatic
5507
5464
  start from a capability declaration. Agent application support is declared on `ConnectContext.host.recording`,
5508
- not the provider-owned manifest. The task chooses an explicitly provisioned agent application destination;
5509
- unknown destinations or unsupported actions are refused visibly, never redirected to provider
5510
- recording or a default upload location. Initial agent application support requires voice softphone media and
5465
+ not the provider-owned manifest. The task names one of the storage places the agent application provisioned, by `storageId`;
5466
+ an unknown storage place or an unsupported action is refused visibly, never redirected to provider
5467
+ recording or a default upload location. Initial agent application support requires voice softphone audio and
5511
5468
  capture of both local and remote audio; an agent application unable to capture either must refuse start/resume.
5512
- This package declares that contract; it supplies no recorder, media mixing, storage or upload.
5469
+ This package declares that contract; it supplies no recorder, audio mixing, storage or upload.
5513
5470
 
5514
5471
  | Action | Required current recording state | Confirmed outcome |
5515
5472
  | --- | --- | --- |
@@ -5540,7 +5497,7 @@ There is no automatic restart, transfer to another recorder, or stop on task hol
5540
5497
  Task removal does not prove recording stopped: outstanding agent application recorders remain tracked by the
5541
5498
  agent application until its executor reconciles/finishes them, with visible unresolved cleanup failures.
5542
5499
 
5543
- Only start/resume require an in-progress or paused task with started media. Pause/stop/cancel may
5500
+ Only start/resume require an in-progress or paused task with started audio. Pause/stop/cancel may
5544
5501
  also finish an independently observed recorder while the task is completing. A pending task may
5545
5502
  show active recording, but agent controls wait until interaction begins. The two recording paths may
5546
5503
  both be active. A command to either path has no implied effect on the other.
@@ -5583,9 +5540,9 @@ This change does not publish a package or enable recording in any existing agent
5583
5540
  semantics, including pause/resume identity and rejection of a dialling result. It cannot prove audio
5584
5541
  retention/completion or source truth from a status flag; those remain executor obligations.
5585
5542
 
5586
- Agent application destination selection binds storage when start actually creates a recording. Existing recordings
5543
+ The storage place named on the task is bound when start actually creates a recording. Existing recordings
5587
5544
  keep that binding through pause/resume/stop/cancel; a later policy cannot redirect their stored audio.
5588
- The executor rejects a mismatched destination rather than moving or discarding another binding.
5545
+ The executor rejects a mismatched storage place rather than moving or discarding another binding.
5589
5546
  Action permission does not authorize unattended invocation: agent application controls require the agent's explicit
5590
5547
  act, and provider authorization remains enforced at its authenticated command boundary.
5591
5548