@xema/omni-protocol 0.1.97 → 0.1.99
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +132 -110
- package/dist/index.js +9 -19
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +41 -27
- package/dist/validation.d.ts +20 -6
- package/dist/validation.js +175 -202
- package/guide.md +247 -295
- package/package.json +1 -1
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.
|
|
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
|
|
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
|
|
@@ -114,7 +114,7 @@ type DurationSeconds = number;
|
|
|
114
114
|
| --- | --- | --- |
|
|
115
115
|
| `IsoTimestamp` | `string` | An RFC-3339 timestamp with `Z` or an explicit numeric offset. Timezone-less values are invalid. It must pass the shared runtime validator. A JavaScript `Date` never crosses the protocol boundary. |
|
|
116
116
|
| `UserId` | `string` | A non-empty, opaque, stable identifier for a person, **issued by the provider** and drawn from the same directory as `AuthenticationState.identity.id`. It names agents and managers alike; the role is established by where the value appears, not by its type. Compare it exactly and only within one provider; do not parse it or infer meaning from its format. |
|
|
117
|
-
| `AssignmentId` | `string` | A non-empty, opaque identifier for one assignment, **issued by the provider**: unique within the provider and never reused while the provider is still speaking about it. Whether it is the platform's own handle passed through or one the adapter minted is the adapter's business. Omni scopes it with the provider ID -- see `
|
|
117
|
+
| `AssignmentId` | `string` | A non-empty, opaque identifier for one assignment, **issued by the provider**: unique within the provider and never reused while the provider is still speaking about it. Whether it is the platform's own handle passed through or one the adapter minted is the adapter's business. Omni scopes it with the provider ID -- see `assignmentKey()`. |
|
|
118
118
|
| `DurationSeconds` | `number` | A non-negative integer duration measured in seconds. |
|
|
119
119
|
|
|
120
120
|
### There is no Omni-wide user identity
|
|
@@ -127,7 +127,7 @@ So a `UserId` means nothing outside the provider that issued it. Provider A's
|
|
|
127
127
|
`history[].by` and provider B's team member list `memberId` are unrelated strings that will
|
|
128
128
|
eventually collide, and one person on several providers has several identities that nothing here
|
|
129
129
|
pairs. Scope every user identifier with its provider ID before storing or comparing it, exactly as
|
|
130
|
-
`
|
|
130
|
+
`assignmentKey()` already does for tasks — see `userKey()` under **Utilities**.
|
|
131
131
|
|
|
132
132
|
## Shapes
|
|
133
133
|
|
|
@@ -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
|
-
|
|
258
|
+
lead?: true;
|
|
266
259
|
};
|
|
267
260
|
|
|
268
261
|
type AuthenticationState =
|
|
@@ -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>;
|
|
@@ -587,7 +581,7 @@ The channel arms are why `Task<"email">` rejects `hold` at compile time rather t
|
|
|
587
581
|
|
|
588
582
|
```ts
|
|
589
583
|
const BROWSER_ISOLATION_SCHEMES = {
|
|
590
|
-
|
|
584
|
+
PROVIDER_NAME__ASSIGNMENT_ID__TAB_NAME: "ProviderName.AssignmentId.TabName",
|
|
591
585
|
TAB_NAME: "TabName",
|
|
592
586
|
PROVIDER_NAME__TASK_TYPE_NAME__TAB_NAME: "ProviderName.TaskTypeName.TabName",
|
|
593
587
|
PROVIDER_NAME__TAB_NAME: "ProviderName.TabName",
|
|
@@ -697,17 +691,8 @@ type TaskLeadAssist = { note?: string; since: IsoTimestamp } & (
|
|
|
697
691
|
| { stage: "joined"; leadId: UserId }
|
|
698
692
|
);
|
|
699
693
|
|
|
700
|
-
type
|
|
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
|
|
|
@@ -747,8 +732,8 @@ 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;
|
|
751
|
-
: { recording?: never; onCall?: never; leadAssist?: never;
|
|
735
|
+
? { recording?: { provider?: RecordingState }; onCall?: OnCall[]; leadAssist?: TaskLeadAssist; takenOver?: TaskTakenOver; media?: TaskMediaState }
|
|
736
|
+
: { recording?: never; onCall?: never; leadAssist?: never; takenOver?: never; media?: never }
|
|
752
737
|
);
|
|
753
738
|
|
|
754
739
|
type PreviewDeadline = "calls" | "host-calls" | "waits";
|
|
@@ -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
|
|
|
@@ -779,6 +763,7 @@ const TASK_COMMAND_NAMES = {
|
|
|
779
763
|
"hold",
|
|
780
764
|
"resume",
|
|
781
765
|
"end-call",
|
|
766
|
+
"terminate-call",
|
|
782
767
|
"connect-back",
|
|
783
768
|
"transfer",
|
|
784
769
|
"lead-assist",
|
|
@@ -802,6 +787,7 @@ type VoiceTaskCommand =
|
|
|
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.
|
|
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 — `
|
|
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 —
|
|
@@ -991,19 +975,44 @@ type TeamMember = {
|
|
|
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; media?: TaskMediaState }
|
|
1006
|
+
: { onCall?: never; leadAssist?: never; takenOver?: never; media?: never });
|
|
1007
|
+
|
|
1008
|
+
type MemberListening = {
|
|
1009
|
+
mode: ListeningMode;
|
|
1010
|
+
since: IsoTimestamp;
|
|
1011
|
+
};
|
|
1012
|
+
|
|
1004
1013
|
type TeamMembers = {
|
|
1005
1014
|
members: TeamMember[];
|
|
1006
|
-
requests
|
|
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
|
|
1028
|
-
command: TeamPolicyCommand;
|
|
1029
|
-
};
|
|
1036
|
+
type TeamFeatureCommand = { type: "lead-features"; enabled: boolean };
|
|
1030
1037
|
|
|
1031
|
-
type
|
|
1032
|
-
| { type: "join";
|
|
1033
|
-
| { type: "decline";
|
|
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
|
|
1042
|
-
command: TeamBreakCommand;
|
|
1043
|
-
};
|
|
1053
|
+
type TeamCommand = TeamFeatureCommand | TeamBreakCommand | TeamCallCommand | TeamPolicyCommand;
|
|
1044
1054
|
|
|
1045
|
-
type
|
|
1046
|
-
command:
|
|
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 =
|
|
@@ -1108,6 +1108,8 @@ type ProviderEvent =
|
|
|
1108
1108
|
| { type: "task-updated"; task: Task }
|
|
1109
1109
|
| { type: "task-media-started"; assignmentId: AssignmentId }
|
|
1110
1110
|
| { type: "task-media-ended"; assignmentId: AssignmentId }
|
|
1111
|
+
| { type: "team-media-started"; memberId: UserId; assignmentId: AssignmentId }
|
|
1112
|
+
| { type: "team-media-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 }
|
|
@@ -1169,13 +1171,10 @@ type Connection<C extends Channel = Channel> = {
|
|
|
1169
1171
|
cancelBreak?(): Promise<BreakCancelResult>;
|
|
1170
1172
|
endBreak?(): Promise<BreakEndResult>;
|
|
1171
1173
|
|
|
1172
|
-
|
|
1173
|
-
executeTeamLeadAssist?(request: TeamLeadAssistCommandRequest): Promise<TeamCommandResult>;
|
|
1174
|
-
executeTeamListen?(request: TeamListenCommandRequest): Promise<TeamCommandResult>;
|
|
1174
|
+
executeTeam?(request: TeamCommandRequest): Promise<TeamCommandResult>;
|
|
1175
1175
|
openMedia?(request: OpenMediaRequest): Promise<OpenMediaResult>;
|
|
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> = {
|
|
@@ -1235,7 +1234,7 @@ const HISTORY_STEPS_WITH_A_PERSON = [
|
|
|
1235
1234
|
const OMNI_FAILURE_CODES = [
|
|
1236
1235
|
"omni.not-authenticated",
|
|
1237
1236
|
"omni.capability-not-enabled",
|
|
1238
|
-
"omni.
|
|
1237
|
+
"omni.assignment-not-found",
|
|
1239
1238
|
"omni.destination-not-permitted",
|
|
1240
1239
|
"omni.phone-not-permitted",
|
|
1241
1240
|
"omni.rate-limited",
|
|
@@ -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
|
|
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
|
|
|
@@ -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
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
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
|
-
| `
|
|
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.
|
|
@@ -2073,7 +2067,7 @@ Creates one live provider connection for the signed-in agent.
|
|
|
2073
2067
|
| `autoAcceptTasks` | Agent local policy policy relayed to the provider at login, stated by the agent application on every connection and never assumed from its absence. When `true`, a pending task states its `acceptance`; when `false`, every task requires agent acceptance. Fixed for this connection, like everything else here: the provider states or omits `acceptance` by the value it was sent, and Omni validates by that same value, not by a policy that has since moved — a change reaches the provider through a fresh `connect()`. |
|
|
2074
2068
|
| `timeZone` | The same value passed as `AuthenticationContext.timeZone`. The provider stores it on the agent and carries it on the identity. See **The agent's day**. |
|
|
2075
2069
|
| `phone` | The same value passed as `AuthenticationContext.phone`: how this login hears its calls. The type ties `host.mute` to it: a `softphone` login's agent application states what its Mute does, and a desk-phone or conversation login's agent application cannot, so the omission is a compile error rather than a live seat's discovery. See **The station is the agent application's**. |
|
|
2076
|
-
| `store` | The login's operational store, kept by the agent application for the life of the login, across a reload of the agent application, and cleared at sign-out: where an adapter that composes a record keeps what its platform cannot hold for it, such as the interaction legs an agent application reported. Three functions, by key. Never for anything sensitive, which is `AuthenticationContext.secrets`, a store an agent application may clear aggressively. **A task's keys carry its assignment id and go with the task**: a key written about a task names the assignment id in the key, and is deleted before the task's end is published, so that nothing of a closed task survives its ending and nothing is left for whatever comes next -- a record with legs the agent application never reported against it. A login-scoped key carries no assignment id and outlives any task. The harness requires the store of every connection (`store.shape`, `store.get`, `.set`, `.delete`), watches the one it hands over, and names a task's key still held after `task-ended` (`drive.store.retained`) or written about the task after its end -- a persist hung off a timer that saw the task as it was (`drive.store.late`). The ordinary late write is the agent application's, not the adapter's: an agent application reports a leg without waiting for the answer, so an unmute can follow `complete` by a tick, and the adapter answers a report about a task that has ended `failed` with `omni.
|
|
2070
|
+
| `store` | The login's operational store, kept by the agent application for the life of the login, across a reload of the agent application, and cleared at sign-out: where an adapter that composes a record keeps what its platform cannot hold for it, such as the interaction legs an agent application reported. Three functions, by key. Never for anything sensitive, which is `AuthenticationContext.secrets`, a store an agent application may clear aggressively. **A task's keys carry its assignment id and go with the task**: a key written about a task names the assignment id in the key, and is deleted before the task's end is published, so that nothing of a closed task survives its ending and nothing is left for whatever comes next -- a record with legs the agent application never reported against it. A login-scoped key carries no assignment id and outlives any task. The harness requires the store of every connection (`store.shape`, `store.get`, `.set`, `.delete`), watches the one it hands over, and names a task's key still held after `task-ended` (`drive.store.retained`) or written about the task after its end -- a persist hung off a timer that saw the task as it was (`drive.store.late`). The ordinary late write is the agent application's, not the adapter's: an agent application reports a leg without waiting for the answer, so an unmute can follow `complete` by a tick, and the adapter answers a report about a task that has ended `failed` with `omni.assignment-not-found` and writes nothing -- the agent application ends its own open legs at the task's end, so such a report is the agent application's error to see, and an agent application reads every `recordStep` answer and awaits the one for the leg it closes at a task's end, the only moment a refusal is expected, since a report nobody waits for is an error nobody can see; an adapter that names no task in its keys gets no cleanup check, which is a gap rather than a pass, never an exemption: the obligation is that nothing of a closed task survives its ending, and an adapter that keeps every open task in one login-scoped value owes exactly that inside the value, where the harness cannot look. One key per task, named for it, is the shape the harness can hold, and the shape to reach for. The store lists nothing, so an adapter that needs to find its tasks keeps a login-scoped index of ids beside them; at the task's end it deletes the body first and reindexes after, since a crash between the two then leaves an index naming a task with no body, which a reader skips, where the other order leaves a body for a task that has ended, which is the hazard itself. A reader of the index tolerates an id with no body as an ending that was underway, not as corruption. |
|
|
2077
2071
|
| `host` | The agent application's report of the agent's station — devices, permissions, network — to consult before declaring the agent ready to the platform, and on every change. See **The agent application reports, the adapter decides**. |
|
|
2078
2072
|
| `signal` | Optional cancellation signal. Stop startup promptly when aborted and do not begin new work. |
|
|
2079
2073
|
| `log` | Optional structured logging callback. Never include credentials, tokens, or sensitive contact data. |
|
|
@@ -2134,7 +2128,7 @@ a capability it agrees with the login: a lead's snapshot carries `team`, nobody
|
|
|
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
2130
|
| `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
|
|
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
|
|
|
@@ -2157,12 +2151,9 @@ 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
|
-
| `
|
|
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
2157
|
| `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. |
|
|
2167
2158
|
|
|
2168
2159
|
**The four break methods stand or fall together.** Declaring `capabilities.breaks` at login and then
|
|
@@ -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
|
|
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
|
|
@@ -2391,8 +2382,7 @@ time. Runtime conformance checks also require the task channel to match its prov
|
|
|
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
|
-
| `
|
|
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
|
|
|
@@ -2448,7 +2438,7 @@ ending, a history report, a command -- would land on whichever assignment is ope
|
|
|
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
|
|
2450
2440
|
and keeps the mapping, and the desk never learns which. A late event finds the assignment it
|
|
2451
|
-
belongs to or is refused, and a task-scoped browser session (`
|
|
2441
|
+
belongs to or is refused, and a task-scoped browser session (`PROVIDER_NAME__ASSIGNMENT_ID__TAB_NAME`)
|
|
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 --
|
|
@@ -2736,7 +2726,8 @@ Migration from the earlier spellings:
|
|
|
2736
2726
|
| handlingStepExpectsAPerson, handlingStepDials, and their interaction spellings | `historyStepExpectsAPerson`, `historyStepDials` |
|
|
2737
2727
|
| handleSeconds | `interactionSeconds` |
|
|
2738
2728
|
| Task.allocationId, AllocationId, allocationExpiresAt | `Task.assignmentId`, `AssignmentId`, `assignmentExpiresAt` |
|
|
2739
|
-
| Task.id, TaskId, and `taskId` on every event, command, report and lead request | gone: a task is named by `assignmentId` alone, and `
|
|
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 |
|
|
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`) |
|
|
2740
2731
|
| Manifest.disposalSettleMs | `Manifest.completionSettleMs` |
|
|
2741
2732
|
|
|
2742
2733
|
Update producers, consumers, saved task snapshots, and validation-rule assertions together.
|
|
@@ -2953,7 +2944,7 @@ At a provider-confirmed task/media end, the agent application also stops the ass
|
|
|
2953
2944
|
its observed ending where the report is still accepted. An agent application closing report repeats its original
|
|
2954
2945
|
key and may include its measured duration, but cannot reverse an already confirmed provider end.
|
|
2955
2946
|
The provider acknowledges a known closed leg without rewriting its final record; after the task
|
|
2956
|
-
has been completed, it may refuse the report as
|
|
2947
|
+
has been completed, it may refuse the report as assignment-not-found. The agent application reads that response rather
|
|
2957
2948
|
than retrying or recreating the task. An observed agent application end before a provider closure is still
|
|
2958
2949
|
reported normally; the provider decides the final timestamp and publishes the record.
|
|
2959
2950
|
|
|
@@ -3049,7 +3040,7 @@ The supported `BrowserIsolationScheme` values, declared under **Shapes**, key as
|
|
|
3049
3040
|
|
|
3050
3041
|
| Enum member | Example session key |
|
|
3051
3042
|
| --- | --- |
|
|
3052
|
-
| `
|
|
3043
|
+
| `PROVIDER_NAME__ASSIGNMENT_ID__TAB_NAME` | `mailflow.EMAIL-829102%2Ea1.CRM` -- the task's assignment id, so one assignment, never the next customer's cookies |
|
|
3053
3044
|
| `TAB_NAME` | `CRM` |
|
|
3054
3045
|
| `PROVIDER_NAME__TASK_TYPE_NAME__TAB_NAME` | `mailflow.Support.CRM` |
|
|
3055
3046
|
| `PROVIDER_NAME__TAB_NAME` | `mailflow.CRM` |
|
|
@@ -3196,7 +3187,8 @@ See **Which commands need a capability**.
|
|
|
3196
3187
|
| --- | --- | --- |
|
|
3197
3188
|
| `decline` | Pending-task button: Decline | The provider can decline a pending voice offer. Omni shows it only when local policy also permits declining. |
|
|
3198
3189
|
| `hold` | Primary toggle: Hold | Omni may issue voice-task `hold` and `resume` commands. |
|
|
3199
|
-
| `endCall` | Primary button: End call | The provider ends the
|
|
3190
|
+
| `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**. |
|
|
3191
|
+
| `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**. |
|
|
3200
3192
|
| `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**. |
|
|
3201
3193
|
| `coldTransfer` | Secondary menu item: Cold transfer | Omni may hand the customer straight to a destination, with nobody spoken to first. |
|
|
3202
3194
|
| `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**. |
|
|
@@ -3291,50 +3283,41 @@ end the caller journey; a later assignment to the same agent is a new interactio
|
|
|
3291
3283
|
|
|
3292
3284
|
#### Ending a call, and removing one person from it
|
|
3293
3285
|
|
|
3294
|
-
A call has everyone on `Task.onCall`, and
|
|
3295
|
-
provider.
|
|
3286
|
+
A call has everyone on `Task.onCall`, and three commands take people off it, all performed by the
|
|
3287
|
+
provider. Two of them end the agent's part; they differ in what happens to the caller.
|
|
3296
3288
|
|
|
3297
3289
|
```ts
|
|
3298
|
-
{ type: "end-call" } //
|
|
3290
|
+
{ type: "end-call" } // the agent's channel and the channels the agent added end; the caller continues
|
|
3291
|
+
{ type: "terminate-call" } // the caller channel ends
|
|
3299
3292
|
{ type: "conference", action: "remove", party: true } // the customer leaves; the agent stays with the colleague
|
|
3300
3293
|
{ type: "conference", action: "remove", destinationId: "tier2" } // the conferenced person leaves; ringing, this calls the dial off
|
|
3301
3294
|
```
|
|
3302
3295
|
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
the
|
|
3324
|
-
the
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
caller/channel disconnect. Other agents' independent tasks end or wrap only on their own source
|
|
3329
|
-
transitions. Neither completion nor transfer itself ends the continuing caller journey.
|
|
3330
|
-
|
|
3331
|
-
`conference` `remove` remains a person-specific provider operation, gated by `conference`.
|
|
3332
|
-
It does not implicitly complete any other interaction. Its person/consultation prerequisites
|
|
3333
|
-
remain separate from the permission to disconnect the caller. A remove that would leave the
|
|
3334
|
-
agent alone is not a remove but an `end-call`, and the provider answers it `failed`.
|
|
3335
|
-
The consulted destination of a warm transfer is not removed this way:
|
|
3336
|
-
`transfer` `cancel` is its own step, which returns the agent to the parked customer, and a remove
|
|
3337
|
-
never promises that.
|
|
3296
|
+
**`end-call` ends my part.** The agent's channel and every channel the agent added -- a
|
|
3297
|
+
conferenced colleague, a consulted destination -- end, and the caller continues on the path the
|
|
3298
|
+
provider decides: another IVR, a queue, a survey, or the end of the call. Gated by `endCall`.
|
|
3299
|
+
|
|
3300
|
+
**`terminate-call` ends the caller channel.** The call is over for the caller. Gated by
|
|
3301
|
+
`terminateCall`, a separate permission, since ending a customer's call is a different thing from
|
|
3302
|
+
putting my own side down.
|
|
3303
|
+
|
|
3304
|
+
Both are commands to the provider through `execute`, and nothing happens on the desk until the
|
|
3305
|
+
provider reports it: the agent's media ends on `task-media-ended`, the task moves to `completing`,
|
|
3306
|
+
any wrap allowance runs, and the agent completes. Neither is `task-ended`, and `complete` is
|
|
3307
|
+
neither of them. Both capabilities are the queue's to grant and a level's to lock, and both may
|
|
3308
|
+
change while the task is open: the provider republishes the task at the moment a permission
|
|
3309
|
+
changes, and a command that arrives after its capability was withdrawn is refused, as **Task
|
|
3310
|
+
capabilities** sets out. The provider knows which channels are the agent's and which were added;
|
|
3311
|
+
the agent application sends the command and no channel list, and the provider rechecks ownership
|
|
3312
|
+
when it acts.
|
|
3313
|
+
|
|
3314
|
+
`conference` `remove` takes one person off, named as the room names them, and the call goes on
|
|
3315
|
+
for the rest; it is gated by `conference`, since it only means something with a third person on
|
|
3316
|
+
the line. Removing the party leaves the agent with the colleague, a warm hand-over in reverse. A
|
|
3317
|
+
remove that would leave the agent alone is not a remove but an `end-call`, and a provider answers
|
|
3318
|
+
it `failed`. The consulted destination of a warm transfer is not removed this way: `transfer`
|
|
3319
|
+
`cancel` is its own step, which returns the agent to the parked customer, and a remove never
|
|
3320
|
+
promises that.
|
|
3338
3321
|
|
|
3339
3322
|
#### Warm transfer
|
|
3340
3323
|
|
|
@@ -3631,7 +3614,7 @@ rendering one as the other tells an agent to wait for somebody who is never comi
|
|
|
3631
3614
|
| `awaiting-approval` | A person has to decide. The agent is waiting on somebody. |
|
|
3632
3615
|
| `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. |
|
|
3633
3616
|
| `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`. |
|
|
3634
|
-
| `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
|
|
3617
|
+
| `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**. |
|
|
3635
3618
|
|
|
3636
3619
|
A denial is a decision, not a standing approval state. The provider transitions the request directly
|
|
3637
3620
|
to `not-requested`; Omni returns the agent to idle and never asks again on their behalf. They saw the
|
|
@@ -3842,12 +3825,15 @@ request object only to the request method, and undefined to the other three.
|
|
|
3842
3825
|
| `cancelBreak` | `awaiting-approval` or `granted`. A concurrent commit winning still answers `omni.break-already-committed` and requires recovery. |
|
|
3843
3826
|
| `endBreak` | `on-break` or `starting-after-task` during reconciliation; the agent may explicitly end a requested or forced break. |
|
|
3844
3827
|
|
|
3845
|
-
Use `
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3828
|
+
Use `validateTeamCommand(request, context)` before every lead act: the team feature switch, break
|
|
3829
|
+
decisions, forcing and ending a forced break, policy, and every act on a member's call. It requires
|
|
3830
|
+
the live `lead` flag and active transport, a current team member list for any command naming a
|
|
3831
|
+
member, and the target's complete break state for forcing/ending a break. Approve/deny requires an
|
|
3832
|
+
awaiting decision; forcing a break uses the target's reason codes; ending a forced break requires a
|
|
3833
|
+
forced committed break; join and decline need the member's request on the list; coach, join-call
|
|
3834
|
+
and leave need the lead already on that member's call; an assignment named must be one the list
|
|
3835
|
+
shows the member holding. The context's target state must belong to the named member; that
|
|
3836
|
+
association and backend authorization are provider responsibilities.
|
|
3851
3837
|
|
|
3852
3838
|
Use `validateBreakStatus(state, tasks)` on the complete retained task view after each transaction,
|
|
3853
3839
|
as well as `validateBreakTransition` for event ordering. Snapshot validation shares the status
|
|
@@ -4115,22 +4101,23 @@ so there is nothing on it to resume.
|
|
|
4115
4101
|
|
|
4116
4102
|
## Team leads
|
|
4117
4103
|
|
|
4118
|
-
|
|
4119
|
-
`
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
bare array. `Snapshot.team`, the `team-updated` event and its `team` payload, login permissions,
|
|
4125
|
-
and `team.*` validation rules keep their names and semantics. An empty team has `members: []`;
|
|
4126
|
-
an absent `team` still means the login is not entitled to that contribution.
|
|
4104
|
+
The team feature is the lead's own surface, beside the agent's and never mixed with it. The
|
|
4105
|
+
login's `lead` flag turns it on in the agent application; the lead retrieves their members and
|
|
4106
|
+
sees, for each, their assignments as they stand and the full history of each, and acts on them
|
|
4107
|
+
with the authority the flag carries. Nothing the lead does here is a task on their desk. A lead
|
|
4108
|
+
who also takes calls is an agent like any other on the agent side, and a lead who does not want
|
|
4109
|
+
the agent role for now places themself on a break of a working kind and keeps the team feature on.
|
|
4127
4110
|
|
|
4111
|
+
`Snapshot.team` carries a `TeamMembers` object, replaced whole by `team-updated`, and the provider
|
|
4112
|
+
keeps it current: when a member is offered a call, answers, holds, mutes or is transferred, the
|
|
4113
|
+
list is republished, so the lead's screen moves when the member's does. The agent application asks
|
|
4114
|
+
for nothing and computes nothing.
|
|
4128
4115
|
|
|
4129
4116
|
| Field | Contract |
|
|
4130
4117
|
| --- | --- |
|
|
4131
|
-
| `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. |
|
|
4132
|
-
| `requests` | The members currently asking
|
|
4133
|
-
| `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.
|
|
4118
|
+
| `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. |
|
|
4119
|
+
| `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**. |
|
|
4120
|
+
| `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**. |
|
|
4134
4121
|
|
|
4135
4122
|
| `TeamMember` field | Contract |
|
|
4136
4123
|
| --- | --- |
|
|
@@ -4138,6 +4125,8 @@ an absent `team` still means the login is not entitled to that contribution.
|
|
|
4138
4125
|
| `availability` | Required. What the member is doing now. |
|
|
4139
4126
|
| `since` | Optional. When the current `availability` began — not when they signed in, and not when the team member list was read. |
|
|
4140
4127
|
| `break` | Present only while the member has an outstanding break request. See **A member waiting for a break**. |
|
|
4128
|
+
| `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, media 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. |
|
|
4129
|
+
| `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`). |
|
|
4141
4130
|
|
|
4142
4131
|
Each availability value means one thing:
|
|
4143
4132
|
|
|
@@ -4152,7 +4141,8 @@ Each availability value means one thing:
|
|
|
4152
4141
|
**Always publish the complete team member list, never a change to it.** Team presence typically reaches an
|
|
4153
4142
|
adapter over a best-effort channel with no ordering and no delivery guarantee, so a stream of deltas
|
|
4154
4143
|
cannot be trusted to reconstruct the truth. The adapter reconciles against its own authoritative
|
|
4155
|
-
read and publishes the result.
|
|
4144
|
+
read and publishes the result. That includes the members' tasks: a hold on a member's desk is a
|
|
4145
|
+
republished list on the lead's.
|
|
4156
4146
|
|
|
4157
4147
|
**Omit `since` rather than inventing one.** Omni renders it as a duration, so a timestamp
|
|
4158
4148
|
synthesised from the adapter's own clock at seed time reads as "on task for 0 seconds" for
|
|
@@ -4160,11 +4150,20 @@ everybody — worse than showing nothing, because it looks like data. Send it on
|
|
|
4160
4150
|
knows when the state actually began. It times the current `availability`, so it moves every time
|
|
4161
4151
|
that value does.
|
|
4162
4152
|
|
|
4163
|
-
**The login is the permission.** A team member list goes to a login that declares
|
|
4164
|
-
every snapshot, and to nobody else. Omni never
|
|
4165
|
-
sign-in, and the team member list agrees with it
|
|
4166
|
-
|
|
4167
|
-
|
|
4153
|
+
**The login is the permission.** A team member list goes to a login that declares
|
|
4154
|
+
`capabilities.lead`, on every snapshot while the team feature is on, and to nobody else. Omni never
|
|
4155
|
+
decides who leads a team: the provider said so at sign-in, and the team member list agrees with it
|
|
4156
|
+
— present, `members: []` included, for a lead; absent for everybody else, which is the correct
|
|
4157
|
+
rendering for an agent who leads nobody (`team.required`, `team.unentitled`). What the lead may do
|
|
4158
|
+
with it comes with the flag: there is no per-action permission on the login or on the list.
|
|
4159
|
+
|
|
4160
|
+
**The lead switches the feature on and off, and the provider is told.** A lead may work as an
|
|
4161
|
+
agent alone, as agent and lead, or as lead alone, and moves between them on the fly. The
|
|
4162
|
+
agent application says which with `{ type: "lead-features", enabled }`: while it is on, the
|
|
4163
|
+
provider sends team events scoped to the lead's team and the lead may act; while it is off, the
|
|
4164
|
+
lead is an ordinary agent, no team member list is owed or expected (`team.unexpected`,
|
|
4165
|
+
`event.team.features`), and no lead act is possible, since the lead learns their members'
|
|
4166
|
+
assignments only from the list. It is on from sign-in for a lead until the lead turns it off.
|
|
4168
4167
|
|
|
4169
4168
|
**The team member list never carries the agent it is published to — not in `members`, and not in
|
|
4170
4169
|
`requests`.** A lead does not report to themself: their own break request and their own ask for a
|
|
@@ -4172,24 +4171,36 @@ lead go up to whoever leads them and appear on *that* person's team member list,
|
|
|
4172
4171
|
only their own `BreakState` and their task's `leadAssist` move. An adapter whose platform lists the lead
|
|
4173
4172
|
among their own members filters the signed-in identity out before publishing. **Being a lead is a
|
|
4174
4173
|
role the provider knows, never inferred from who is listed:** it is declared at sign-in, a lead
|
|
4175
|
-
with nobody in their team publishes `[]`, an agent with no such role publishes nothing, and no
|
|
4174
|
+
with nobody in their team publishes `members: []`, an agent with no such role publishes nothing, and no
|
|
4176
4175
|
member count can tell those two apart.
|
|
4177
4176
|
|
|
4178
4177
|
### Lead commands
|
|
4179
4178
|
|
|
4180
|
-
One method, `
|
|
4181
|
-
`TaskCommand
|
|
4179
|
+
One method, `executeTeam`, taking a discriminated command exactly as `execute` takes a
|
|
4180
|
+
`TaskCommand`. Every act on a member's call names the member, `memberId`, and may name the
|
|
4181
|
+
assignment, `assignmentId`, where the lead has it; the provider resolves the member's current
|
|
4182
|
+
assignment from the member alone.
|
|
4182
4183
|
|
|
4183
4184
|
| Command | Effect |
|
|
4184
4185
|
| --- | --- |
|
|
4185
|
-
| `{ type: "
|
|
4186
|
+
| `{ type: "lead-features", enabled }` | The team feature on or off. See **The lead switches the feature on and off** above. |
|
|
4187
|
+
| `{ 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`. |
|
|
4186
4188
|
| `{ type: "set-break-policy", policy }` | `approval-required`, `automatically-approved`, or `requests-suspended`. |
|
|
4187
|
-
| `{ type: "force-break", memberId
|
|
4188
|
-
| `{ type: "end-forced-break", memberId
|
|
4189
|
+
| `{ 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. |
|
|
4190
|
+
| `{ 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. |
|
|
4191
|
+
| `{ type: "join", memberId, assignmentId? }` | Answers the member's request: the provider bridges the lead's channel into the call. See **Lead assist**. |
|
|
4192
|
+
| `{ type: "decline", memberId, assignmentId?, reason? }` | Refuses the member's request. |
|
|
4193
|
+
| `{ type: "listen", memberId, assignmentId? }` | The lead's channel joins the member's call unasked, in silence. See **Listening to a call**. |
|
|
4194
|
+
| `{ type: "coach", memberId, assignmentId? }` | The lead is heard by the member alone. Needs the lead on that member's call. |
|
|
4195
|
+
| `{ type: "join-call", memberId, assignmentId? }` | The lead is heard by everyone on the call. Needs the lead on that member's call. |
|
|
4196
|
+
| `{ type: "leave", memberId, assignmentId? }` | The lead's channel leaves the member's call, however it got there. The member's call goes on. |
|
|
4197
|
+
| `{ 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**. |
|
|
4198
|
+
| `{ 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**. |
|
|
4189
4199
|
|
|
4190
4200
|
`memberId` is this provider's own identifier for the member, as published on its team member list. It is
|
|
4191
4201
|
never an identifier from another provider, and Omni does not translate between them; names come
|
|
4192
|
-
from `getUserDetails()`.
|
|
4202
|
+
from `getUserDetails()`. `validateTeamCommand` holds each command to the team member list as it
|
|
4203
|
+
stands -- see **Runtime break prerequisite checks**.
|
|
4193
4204
|
|
|
4194
4205
|
`requests-suspended` means requests are **rejected outright** rather than left pending — nobody is coming to
|
|
4195
4206
|
approve them. A provider that suspends break requests must also publish `canRequestBreak: false` to the team's
|
|
@@ -4203,71 +4214,56 @@ never expressed here.
|
|
|
4203
4214
|
|
|
4204
4215
|
An agent on a call may ask a lead to join it -- a dispute that needs approval, a customer who
|
|
4205
4216
|
asks for a manager, a moment the agent wants a second pair of ears. A call centre calls this
|
|
4206
|
-
assistance or escalation, and the name says who assists: the capability is `leadAssist` on the
|
|
4207
|
-
team,
|
|
4208
|
-
|
|
4209
|
-
```ts
|
|
4210
|
-
executeTeamLeadAssist({ command: TeamLeadAssistCommand }): Promise<TeamCommandResult>
|
|
4211
|
-
```
|
|
4212
|
-
|
|
4213
|
-
Required when the login declares `capabilities.team.leadAssistControl`, and gated by it exactly as
|
|
4214
|
-
`executeTeamBreak` is by `team.breakControl`. The flow, in order:
|
|
4217
|
+
assistance or escalation, and the name says who assists: the capability is `leadAssist` on the
|
|
4218
|
+
task; the lead's side is the team member list, which is already the lead's view of the team. The
|
|
4219
|
+
flow, in order:
|
|
4215
4220
|
|
|
4216
4221
|
```ts
|
|
4217
4222
|
// 1. The agent asks, with a small note. Their task carries `leadAssist` from here on.
|
|
4218
4223
|
execute({ assignmentId: "alloc-42", command: { type: "lead-assist", action: "request", note: "Refund dispute, needs approval" } })
|
|
4219
4224
|
// task.leadAssist = { stage: "requested", note: "Refund dispute, needs approval", since }
|
|
4220
4225
|
|
|
4221
|
-
// 2. Every lead
|
|
4222
|
-
// team-updated: requests: [{
|
|
4226
|
+
// 2. Every lead with the team feature on sees the request on their team member list.
|
|
4227
|
+
// team-updated: requests: [{ memberId: "A-1", assignmentId: "alloc-42", note, since }]
|
|
4223
4228
|
|
|
4224
4229
|
// 3. A lead joins, or declines.
|
|
4225
|
-
|
|
4226
|
-
|
|
4230
|
+
executeTeam({ command: { type: "join", memberId: "A-1", assignmentId: "alloc-42" } })
|
|
4231
|
+
executeTeam({ command: { type: "decline", memberId: "A-1", reason: "In a call" } })
|
|
4227
4232
|
```
|
|
4228
4233
|
|
|
4229
|
-
**On `join` the provider bridges
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
they joined -- is answered `failed`, whatever their ceiling; the request stands for another lead,
|
|
4240
|
-
or until it is withdrawn or declined.
|
|
4241
|
-
|
|
4242
|
-
**A lead on a break does not join.** A break is a reported state in which the agent is not working,
|
|
4243
|
-
and a join is work. Omni offers Join to a lead only while their own `BreakState.status` is neither
|
|
4244
|
-
`starting-after-task` nor `on-break` -- a committed break waiting for the lead's current work to
|
|
4245
|
-
finish is not given more -- and a provider answers a `join` from a lead on such a break `failed`.
|
|
4246
|
-
The request stands for another lead, as it does when this one is already on a call.
|
|
4234
|
+
**On `join` the provider bridges the lead's channel into the call.** The member's task moves to
|
|
4235
|
+
`leadAssist: { stage: "joined", leadId }`, the member on the team member list carries
|
|
4236
|
+
`listening: { mode: "join-call", since }`, and the lead's audio arrives as **Listening to a call**
|
|
4237
|
+
describes. Nothing is a task on the lead's desk: a join is the lead's act on the team surface, not
|
|
4238
|
+
work assigned to them.
|
|
4239
|
+
|
|
4240
|
+
**A lead is on one member's call at a time.** A `join` from a lead already on a call -- their own
|
|
4241
|
+
or a member's -- is answered `failed`; the request stands for another lead, or until it is
|
|
4242
|
+
withdrawn or declined. A join is work, so Omni offers Join only to a lead whose voice channel is
|
|
4243
|
+
free, and a provider answers a `join` from one whose channel is not `failed`.
|
|
4247
4244
|
|
|
4248
4245
|
**On `decline`, or a request the agent withdraws with `{ type: "lead-assist", action: "cancel" }`, the
|
|
4249
4246
|
provider clears `leadAssist` from the agent's task** and drops the request from every team member list. Nothing
|
|
4250
4247
|
else changes; the agent is still on the call.
|
|
4251
4248
|
|
|
4252
|
-
**
|
|
4253
|
-
|
|
4254
|
-
Migration: replace the former take-over action literal with `take-over-call` in hosts and
|
|
4255
|
-
providers together. The former spelling is rejected, with no compatibility alias.
|
|
4256
|
-
The `taken-over` task outcome and existing completion behavior remain unchanged.
|
|
4257
|
-
This action is distinct from `join-call`, which lets everyone hear a lead listening to a call.
|
|
4249
|
+
**On `leave`, the lead's channel drops and the call goes on**: the member's task loses its
|
|
4250
|
+
`leadAssist`, the member loses `listening`, and the lead is free. Nothing ended for the member.
|
|
4258
4251
|
|
|
4259
|
-
|
|
4260
|
-
|
|
4252
|
+
**On `take-over-call`, the provider moves the call from the member to the lead**, and the lead is
|
|
4253
|
+
its new agent. There are two paths to it -- the agent asked and the lead joined, or the lead was
|
|
4254
|
+
listening unasked -- and one act at the end of either. What follows is the same on both:
|
|
4261
4255
|
|
|
4262
|
-
|
|
|
4256
|
+
| | The member's task | The lead's desk |
|
|
4263
4257
|
| --- | --- | --- |
|
|
4264
|
-
|
|
|
4265
|
-
| `{ type: "
|
|
4266
|
-
| Stays until the customer hangs up | `task-media-ended`, `completing`, its own outcome | The same, independently: **both have the completion window** |
|
|
4258
|
+
| Take-over | `task-media-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.mediaOpen`). | An ordinary assignment arrives: `task-offered` with `acceptance: "automatic"`, carrying the call's history and `takenOver: { memberId, since }`. |
|
|
4259
|
+
| 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. |
|
|
4267
4260
|
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4261
|
+
**The taken-over call is offered whatever break the lead is on.** A lead working as lead alone is
|
|
4262
|
+
on a break of a working kind, and the provider assigns the call regardless: it is the one task a
|
|
4263
|
+
break in effect may hold (`break.on-break.tasks` allows it, and nothing else). It is the lead's own
|
|
4264
|
+
act, so it counts against no capacity, like the agent's own dial; the only conditions are that the
|
|
4265
|
+
lead is not already on another call and their voice channel is free. A lead with the team feature
|
|
4266
|
+
off cannot take over, since they have no member's assignment to name.
|
|
4271
4267
|
|
|
4272
4268
|
```ts
|
|
4273
4269
|
const leadAssistCapable = {
|
|
@@ -4276,6 +4272,13 @@ const leadAssistCapable = {
|
|
|
4276
4272
|
phase: "in-progress",
|
|
4277
4273
|
leadAssist: { stage: "joined", leadId: "L-9", note: "Refund dispute, needs approval", since: "2026-08-21T09:04:00Z" },
|
|
4278
4274
|
} satisfies Pick<Task<"voice">, "channel" | "capabilities" | "phase" | "leadAssist">;
|
|
4275
|
+
|
|
4276
|
+
const takenOver = {
|
|
4277
|
+
channel: "voice",
|
|
4278
|
+
capabilities: { hold: true, endCall: true, outcomes: true },
|
|
4279
|
+
phase: "in-progress",
|
|
4280
|
+
takenOver: { memberId: "A-1", since: "2026-08-21T09:06:00Z" },
|
|
4281
|
+
} satisfies Pick<Task<"voice">, "channel" | "capabilities" | "phase" | "takenOver">;
|
|
4279
4282
|
```
|
|
4280
4283
|
|
|
4281
4284
|
Lead and member alike are `UserId`s of this provider, so an adapter publishing them implements
|
|
@@ -4283,44 +4286,10 @@ Lead and member alike are `UserId`s of this provider, so an adapter publishing t
|
|
|
4283
4286
|
|
|
4284
4287
|
### Listening to a call
|
|
4285
4288
|
|
|
4286
|
-
**Listen** lets the lead hear the call without being heard. The modes are `listen`, `coach`
|
|
4287
|
-
(agent-only lead audio), and `join-call` (lead audio heard by everyone).
|
|
4288
|
-
|
|
4289
|
-
| Former API | Current API |
|
|
4290
|
-
| --- | --- |
|
|
4291
|
-
| MonitorMode / MONITOR_MODES | `ListeningMode` / `LISTENING_MODES` |
|
|
4292
|
-
| TeamMonitorCommand / TeamMonitorCommandRequest | `TeamListenCommand` / `TeamListenCommandRequest` |
|
|
4293
|
-
| executeTeamMonitor | `executeTeamListen` |
|
|
4294
|
-
| team.monitorControl | `team.listeningControl` |
|
|
4295
|
-
| TaskMonitoring / task.monitoring | `TaskListening` / the task’s `listening` field |
|
|
4296
|
-
| MONITORING_BREAK_KINDS / breakKindAllowsMonitoring | `LISTENING_BREAK_KINDS` / `breakKindAllowsListening` |
|
|
4297
|
-
| monitor action and mode | `listen` action and mode |
|
|
4298
|
-
|
|
4299
|
-
Update command dispatch, allowed modes, task snapshots, imports and diagnostics together.
|
|
4300
|
-
The former names are not aliases. Old task and permission fields are rejected even alongside
|
|
4301
|
-
new fields. Validation rule segments now use `listening`, `listeningControl`, and `listen`.
|
|
4302
|
-
The existing login permissions, single-call limits, allowed break kinds and audio effects
|
|
4303
|
-
remain unchanged. Listening includes its coach/join-call modes and does not imply takeover.
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
**Coach** lets the lead speak privately to the agent; the caller cannot hear the lead.
|
|
4307
|
-
The command and listening mode are `coach`. Migration: replace the former whisper literal
|
|
4308
|
-
in `TeamListenCommand`, `ListeningMode`, `team.listeningControl`, and the task’s `listening.mode`.
|
|
4309
|
-
`LISTENING_MODES` exports `coach`; update command producers, permissions and retained snapshots
|
|
4310
|
-
together. No legacy alias is accepted. The lead must already be listening and permitted to
|
|
4311
|
-
coach. This mode does not join the audible caller conversation or take over the task.
|
|
4312
|
-
|
|
4313
|
-
The user-facing action is **Join call**. The command and listening mode are `join-call`.
|
|
4314
|
-
Migration: replace the former barge literal in `TeamListenCommand`, `ListeningMode`,
|
|
4315
|
-
`team.listeningControl`, and the task’s `listening.mode`. `LISTENING_MODES` exports the new literal;
|
|
4316
|
-
no legacy alias is accepted. Update commands, capability declarations and retained snapshots
|
|
4317
|
-
together. The lead must already be listening and permitted to use this mode. Everyone on the
|
|
4318
|
-
call hears the lead; this does not transfer ownership or invoke lead-assist takeover.
|
|
4319
|
-
|
|
4320
4289
|
A lead may listen to a member's call without being asked -- to coach, to check quality, to step in
|
|
4321
|
-
when it goes wrong.
|
|
4322
|
-
|
|
4323
|
-
three modes determine who hears the lead:
|
|
4290
|
+
when it goes wrong. It is a team act naming the member, and no task on the lead's desk carries it:
|
|
4291
|
+
the lead's view of the call is the member's task on the team member list, with its room and its
|
|
4292
|
+
history. The three modes determine who hears the lead:
|
|
4324
4293
|
|
|
4325
4294
|
| Mode | Who hears the lead |
|
|
4326
4295
|
| --- | --- |
|
|
@@ -4328,61 +4297,44 @@ three modes determine who hears the lead:
|
|
|
4328
4297
|
| `coach` | The agent alone. The customer hears nothing. |
|
|
4329
4298
|
| `join-call` | Everyone on the call. |
|
|
4330
4299
|
|
|
4331
|
-
|
|
4332
|
-
includes `listen`, because coach and join-call begin from one
|
|
4333
|
-
(`authentication.capability.team.listeningControl.listen`). A centre that lets every lead listen but
|
|
4334
|
-
reserves joining the call declares `["listen", "coach"]`, and Omni offers no Join call to that lead. The
|
|
4335
|
-
declaration requires `executeTeamListen`, gated exactly as `executeTeamLeadAssist` is by
|
|
4336
|
-
`team.leadAssistControl`.
|
|
4300
|
+
All three come with the `lead` flag; a centre reserves none of them per lead. The flow:
|
|
4337
4301
|
|
|
4338
4302
|
```ts
|
|
4339
4303
|
// 1. The lead picks a member from the team member list and starts silent.
|
|
4340
|
-
|
|
4304
|
+
executeTeam({ command: { type: "listen", memberId: "A-1" } })
|
|
4305
|
+
// team-updated: the member carries listening: { mode: "listen", since }
|
|
4306
|
+
// team-media-started: { memberId: "A-1", assignmentId: "alloc-42" } -- the lead's audio attaches
|
|
4341
4307
|
|
|
4342
|
-
// 2. The lead
|
|
4343
|
-
|
|
4344
|
-
|
|
4308
|
+
// 2. The lead changes how they are heard; the provider restates the member with the new mode.
|
|
4309
|
+
executeTeam({ command: { type: "coach", memberId: "A-1" } })
|
|
4310
|
+
executeTeam({ command: { type: "join-call", memberId: "A-1" } })
|
|
4345
4311
|
|
|
4346
|
-
// 3. The lead
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
// 4. The lead leaves; their task ends with `{ type: "left" }`. The member's call goes on.
|
|
4351
|
-
executeTeamListen({ command: { type: "leave" } })
|
|
4312
|
+
// 3. The lead leaves, or takes the call.
|
|
4313
|
+
executeTeam({ command: { type: "leave", memberId: "A-1" } })
|
|
4314
|
+
executeTeam({ command: { type: "take-over-call", memberId: "A-1" } })
|
|
4352
4315
|
```
|
|
4353
4316
|
|
|
4354
|
-
**The lead's
|
|
4355
|
-
|
|
4356
|
-
the
|
|
4357
|
-
|
|
4358
|
-
a
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
(`snapshot.listening.break`). The kind decides, not the reason's name: a centre that wants leads
|
|
4371
|
-
listening during a break declares that break `coaching`, `administrative` or `training`.
|
|
4317
|
+
**The lead's audio arrives through team media.** While the lead's channel is in a member's call
|
|
4318
|
+
-- listening, coaching or joined on request -- the lead's connection receives `team-media-started`
|
|
4319
|
+
naming the member and the member's assignment, and on a softphone the agent application opens
|
|
4320
|
+
`openMedia` on that assignment as it does for a task; `team-media-ended` closes it. Voice only, and
|
|
4321
|
+
to a login that declares `lead` (`event.teamMedia.*`). The member's own media events are the
|
|
4322
|
+
member's and never reach the lead.
|
|
4323
|
+
|
|
4324
|
+
**`listening` on the member is the state**, restated on every change of mode, and a lead is on
|
|
4325
|
+
one member's call at a time (`team.listening.single`). The member's call ending ends the lead's
|
|
4326
|
+
listening with it: the provider republishes the member without `listening` and sends
|
|
4327
|
+
`team-media-ended`.
|
|
4328
|
+
|
|
4329
|
+
**A lead listens while holding no call of their own.** Omni offers Listen to a lead whose voice
|
|
4330
|
+
channel is free, and a provider answers a `listen` from one whose channel is not `failed`. That
|
|
4331
|
+
includes a lead on a break: a lead working as lead alone is on a break of a working kind, and
|
|
4332
|
+
listens through it.
|
|
4372
4333
|
|
|
4373
4334
|
**Nothing reaches the member.** The member's task carries no trace of a listening lead in any
|
|
4374
4335
|
mode: not on `onCall`, not in the record. Whether coaching is announced to the agent is the
|
|
4375
4336
|
platform's business and travels on the audio, not on this wire.
|
|
4376
4337
|
|
|
4377
|
-
```ts
|
|
4378
|
-
const listeningLead = {
|
|
4379
|
-
channel: "voice",
|
|
4380
|
-
capabilities: {},
|
|
4381
|
-
phase: "in-progress",
|
|
4382
|
-
listening: { memberId: "A-1", assignmentId: "alloc-42", mode: "coach", since: "2026-08-21T09:04:00Z" },
|
|
4383
|
-
} satisfies Pick<Task<"voice">, "channel" | "capabilities" | "phase" | "listening">;
|
|
4384
|
-
```
|
|
4385
|
-
|
|
4386
4338
|
### A member waiting for a break
|
|
4387
4339
|
|
|
4388
4340
|
A member who has asked for a break **keeps working** until Omni commits it, so asking is not an
|
|
@@ -4410,7 +4362,7 @@ whole and replaced whole, and a provider that cannot say omits it.
|
|
|
4410
4362
|
|
|
4411
4363
|
**An agent is not waiting on one person.** Authority is held by several, everyone who holds it
|
|
4412
4364
|
sees the request on their own console, and **any one of them settles it**. Omni offers the
|
|
4413
|
-
decision to every login that declares `
|
|
4365
|
+
decision to every login that declares `lead` and has the team feature on — which is how the provider already
|
|
4414
4366
|
says who may decide — and does not try to work out whose turn it is.
|
|
4415
4367
|
|
|
4416
4368
|
A request needing *more than one* approval is not something this contract describes. There is
|
|
@@ -4618,7 +4570,7 @@ platform is the adapter's business.
|
|
|
4618
4570
|
## Task commands
|
|
4619
4571
|
|
|
4620
4572
|
Command names follow the channel's operational vocabulary, and each channel's command is a union
|
|
4621
|
-
discriminated by `type` — the same discriminant `
|
|
4573
|
+
discriminated by `type` — the same discriminant `executeTeam` and `custom` already use. The
|
|
4622
4574
|
unions are declared under **Shapes**.
|
|
4623
4575
|
|
|
4624
4576
|
`assignmentId` is not repeated on the command. It travels on the `TaskCommandRequest` around it.
|
|
@@ -4730,7 +4682,8 @@ declared:
|
|
|
4730
4682
|
| Command | What makes it available |
|
|
4731
4683
|
| --- | --- |
|
|
4732
4684
|
| `answer`, `accept` | Nothing. A task that was offered can be accepted, or offering it meant nothing. |
|
|
4733
|
-
| `end-call` | The `endCall` capability. |
|
|
4685
|
+
| `end-call` | The `endCall` capability: ends my part, the caller continues. |
|
|
4686
|
+
| `terminate-call` | The `terminateCall` capability: ends the caller channel. |
|
|
4734
4687
|
| `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`. |
|
|
4735
4688
|
| `decline` | The `decline` capability on any channel, **and** Omni local policy permitting it. One word for refusing an offer, whatever the channel. |
|
|
4736
4689
|
| `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`. |
|
|
@@ -4739,13 +4692,12 @@ declared:
|
|
|
4739
4692
|
| `transfer` with `action: "warm"` | The `warmTransfer` capability. `action: "cold"` is gated by `coldTransfer`; the two are declared and offered separately. |
|
|
4740
4693
|
| `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`. |
|
|
4741
4694
|
| `lead-assist` with `action: "request"` or `"cancel"` | The `leadAssist` capability. `cancel` needs a request standing -- `Task.leadAssist` with status `requested`. |
|
|
4742
|
-
| `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`. |
|
|
4743
4695
|
| `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`). |
|
|
4744
4696
|
| `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`). |
|
|
4745
4697
|
| Everything else | Its own named capability. |
|
|
4746
4698
|
|
|
4747
4699
|
**A control on the contact belongs to the interaction phases**, `in-progress` and `paused`:
|
|
4748
|
-
`hold`, `resume` and `pause`, `end-call`, every `transfer` and `conference` action, and
|
|
4700
|
+
`hold`, `resume` and `pause`, `end-call` and `terminate-call`, every `transfer` and `conference` action, and
|
|
4749
4701
|
every `lead-assist` action. These controls act within this agent's current interaction.
|
|
4750
4702
|
In `completing`, that interaction has ended and its wrap work remains. The caller may still be in
|
|
4751
4703
|
an IVR, queue or another agent's interaction, and other channels may remain connected. Completion
|
|
@@ -4812,7 +4764,7 @@ react rather than only display the message:
|
|
|
4812
4764
|
| --- | --- |
|
|
4813
4765
|
| `omni.not-authenticated` | The provider session is no longer usable. The adapter has published `expired` at or before this answer — the state is what Omni surfaces reauthentication from; the code says why this action failed, and is never the only signal. |
|
|
4814
4766
|
| `omni.capability-not-enabled` | The action targets a capability this task, manifest, or login did not declare — including a lead command from a login whose `capabilities` no longer carry it. |
|
|
4815
|
-
| `omni.
|
|
4767
|
+
| `omni.assignment-not-found` | The assignment named is not one the provider holds, typically after the task already ended. |
|
|
4816
4768
|
| `omni.phone-not-permitted` | The agent application declared a `phone` the platform does not permit for this agent -- a softphone for an agent configured for a desk phone, or the reverse. The login is refused at authentication, and the provider never reconfigures the agent to make the declaration true. See **How the agent hears the call**. |
|
|
4817
4769
|
| `omni.destination-not-permitted` | The dialled number, or the `destinationId` named, is not one the provider offers this agent. |
|
|
4818
4770
|
| `omni.rate-limited` | The action was throttled. Pair with `retryAfterMs`. |
|
|
@@ -5138,11 +5090,11 @@ Every outcome ends the task for this agent. On `task-ended`, Omni:
|
|
|
5138
5090
|
- releases task-scoped resources; and
|
|
5139
5091
|
- selects another task or returns to the idle workspace.
|
|
5140
5092
|
|
|
5141
|
-
A `
|
|
5142
|
-
|
|
5093
|
+
A `taken-over` outcome arrives at the member's own completion, after the wrap that follows a
|
|
5094
|
+
take-over as it follows an `end-call` -- see **Lead assist**.
|
|
5143
5095
|
|
|
5144
5096
|
A successful `complete` or `transfer` command does not clear the task. Omni waits for `task-ended`,
|
|
5145
|
-
and not for ever: `applied` to a
|
|
5097
|
+
and not for ever: `applied` to a `complete` says the
|
|
5146
5098
|
provider has completed the task, and its `task-ended` follows within the
|
|
5147
5099
|
manifest's `completionSettleMs`. A provider never answers `applied` for a completion it has not yet
|
|
5148
5100
|
performed. Past the bound the agent application calls `snapshot()`: a snapshot still carrying the task is a task
|
|
@@ -5210,7 +5162,7 @@ each connected provider.
|
|
|
5210
5162
|
Replaces this provider's complete `TeamMembers`. It is emitted only for an agent the provider
|
|
5211
5163
|
publishes a team member list to, and it carries the whole team every time — never a change to it, for the
|
|
5212
5164
|
reason set out under **Team leads**. A lead's snapshot always carries the team member list; it goes only when
|
|
5213
|
-
a republished `authenticated` no longer declares `capabilities.team
|
|
5165
|
+
a republished `authenticated` no longer declares `capabilities.lead`, or the lead turns the team feature off (`event.team.features`).
|
|
5214
5166
|
|
|
5215
5167
|
### `contacts-updated`
|
|
5216
5168
|
|
|
@@ -5224,7 +5176,7 @@ declares the `calendar` idle capability.
|
|
|
5224
5176
|
|
|
5225
5177
|
## Utilities
|
|
5226
5178
|
|
|
5227
|
-
### `
|
|
5179
|
+
### `assignmentKey(providerId, assignmentId)`
|
|
5228
5180
|
|
|
5229
5181
|
Returns a collision-safe global task key by encoding and joining the provider id and the
|
|
5230
5182
|
assignment id. Use this key in Omni state; an assignment id is unique within its provider and
|
|
@@ -5284,9 +5236,9 @@ Some rules need to know who is reading. `validateTeamMembers`, `validateSnapshot
|
|
|
5284
5236
|
`AuthenticationState.identity.id` and their login's `capabilities`. Given `self`, a team member list that
|
|
5285
5237
|
carries that agent reports `team.member.self` or `team.request.self`. Given `capabilities`, a lead's
|
|
5286
5238
|
snapshot without a team member list reports `team.required`, a team member list published to a login that does not lead
|
|
5287
|
-
reports `team.unentitled`,
|
|
5288
|
-
`
|
|
5289
|
-
`team.
|
|
5239
|
+
reports `team.unentitled`, and a `team-updated` to such a login reports `event.team.capability`; given
|
|
5240
|
+
`leadFeatures: false`, a team member list reaching a lead who turned the feature off reports
|
|
5241
|
+
`team.unexpected` or `event.team.features`. Without them those rules are not checked, because they cannot be.
|
|
5290
5242
|
`exerciseAdapter` always passes both.
|
|
5291
5243
|
|
|
5292
5244
|
`assertNoViolations(violations)` throws `ProtocolConformanceError` — which carries the full
|
|
@@ -5458,7 +5410,7 @@ cannot be established from TypeScript structure alone.
|
|
|
5458
5410
|
|
|
5459
5411
|
| Helper | Contract checked |
|
|
5460
5412
|
| --- | --- |
|
|
5461
|
-
| `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
|
|
5413
|
+
| `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. |
|
|
5462
5414
|
| `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. |
|
|
5463
5415
|
| `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. |
|
|
5464
5416
|
| `assertReached(result, subjects)` | The exercise met every subject named; throws listing those it did not. Pair it with a clean `exerciseAdapter` result. |
|