@xema/omni-protocol 0.1.101 → 0.1.103
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 +92 -81
- package/dist/index.js +8 -6
- package/dist/testing.d.ts +26 -1
- package/dist/testing.js +224 -35
- package/dist/validation.d.ts +8 -3
- package/dist/validation.js +178 -159
- package/guide.md +316 -210
- package/package.json +1 -1
package/guide.md
CHANGED
|
@@ -22,7 +22,7 @@ are used precisely throughout and mean nothing looser here.
|
|
|
22
22
|
| **Agent application** | The software the agent uses, such as Omni, whether web or desktop. It presents provider tasks and controls and manages local audio where supported. Existing API names use `Host` and `host` for this role. |
|
|
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
|
-
| **Agent** | The person signed in and taking work. On `onCall`, `agent` is a person on the call by user id;
|
|
25
|
+
| **Agent** | The person signed in and taking work. On `onCall`, `agent` is a person on the call by user id; a conference directory may include an agent when the provider publishes that directory item. |
|
|
26
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. |
|
|
@@ -40,9 +40,9 @@ are used precisely throughout and mean nothing looser here.
|
|
|
40
40
|
| **Event** | One completed transaction reported after a snapshot established the baseline. |
|
|
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
|
-
| **Dial** | One outbound call the agent application asks a provider to place — from the idle dialpad, a
|
|
43
|
+
| **Dial** | One outbound call the agent application asks a provider to place — from the idle dialpad, a preview's Call, 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
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
|
-
| **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
|
|
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 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
|
|
48
48
|
state use `status`; name the object when discussing them to avoid ambiguity.
|
|
@@ -203,7 +203,7 @@ type Manifest<C extends Channel = Channel> = {
|
|
|
203
203
|
dialOutcomes?: C extends "voice" ? DialOutcome[] : never;
|
|
204
204
|
phones?: C extends "voice" ? Phone[] : never;
|
|
205
205
|
runningStepReports?: true;
|
|
206
|
-
|
|
206
|
+
settleMs: number;
|
|
207
207
|
};
|
|
208
208
|
```
|
|
209
209
|
|
|
@@ -313,7 +313,6 @@ type RecordingState =
|
|
|
313
313
|
type RecordingCommand = {
|
|
314
314
|
type: "recording";
|
|
315
315
|
source: RecordingSource;
|
|
316
|
-
requestId: string;
|
|
317
316
|
observationId: string;
|
|
318
317
|
} & (
|
|
319
318
|
| { action: "start"; recordingId?: never }
|
|
@@ -564,15 +563,14 @@ type TaskCapabilities<C extends Channel = Channel> =
|
|
|
564
563
|
endCall?: Lockable<true>;
|
|
565
564
|
terminateCall?: Lockable<true>;
|
|
566
565
|
connectBack?: Lockable<true>;
|
|
567
|
-
|
|
568
|
-
warmTransfer?: Lockable<DestinationDirectory>;
|
|
566
|
+
schedule?: Lockable<true>;
|
|
569
567
|
leadAssist?: Lockable<true>;
|
|
570
568
|
conference?: Lockable<DestinationDirectory>;
|
|
571
569
|
recording?: Lockable<TaskRecordingPolicy>;
|
|
572
570
|
}
|
|
573
571
|
: C extends "chat"
|
|
574
|
-
? SharedTaskCapabilities & { decline?: Lockable<true>; hold?: Lockable<true> }
|
|
575
|
-
: SharedTaskCapabilities & { decline?: Lockable<true> };
|
|
572
|
+
? SharedTaskCapabilities & { decline?: Lockable<true>; hold?: Lockable<true>; schedule?: Lockable<true> }
|
|
573
|
+
: SharedTaskCapabilities & { decline?: Lockable<true>; schedule?: Lockable<true> };
|
|
576
574
|
```
|
|
577
575
|
|
|
578
576
|
The channel arms are why `Task<"email">` rejects `hold` at compile time rather than at runtime.
|
|
@@ -675,7 +673,7 @@ type TaskCompletion =
|
|
|
675
673
|
| { completionMode: "agent-command"; wrapAllowance?: DurationSeconds }
|
|
676
674
|
| { completionMode: "provider-automatic"; wrapAllowance: DurationSeconds };
|
|
677
675
|
|
|
678
|
-
type OnCallRole = "party" | "agent" | "
|
|
676
|
+
type OnCallRole = "party" | "agent" | "conferenced";
|
|
679
677
|
|
|
680
678
|
type OnCallStage = "ringing" | "joined";
|
|
681
679
|
|
|
@@ -683,7 +681,7 @@ type OnCall = { since: IsoTimestamp; held?: true } & (
|
|
|
683
681
|
| { role: "party"; dialId?: never; stage?: never }
|
|
684
682
|
| { role: "party"; stage: OnCallStage; dialId?: DialId }
|
|
685
683
|
| { role: "agent"; userId: UserId }
|
|
686
|
-
| { role: "
|
|
684
|
+
| { role: "conferenced"; destinationId: string; stage: OnCallStage; dialId?: DialId; label?: string }
|
|
687
685
|
);
|
|
688
686
|
|
|
689
687
|
type TaskLeadAssist = { note?: string; since: IsoTimestamp } & (
|
|
@@ -725,7 +723,8 @@ type Task<C extends Channel = Channel> = {
|
|
|
725
723
|
party?: Contact;
|
|
726
724
|
phase: TaskPhase;
|
|
727
725
|
acceptance?: AcceptanceMode;
|
|
728
|
-
|
|
726
|
+
expiresInSeconds?: DurationSeconds;
|
|
727
|
+
previewEndsInSeconds?: DurationSeconds;
|
|
729
728
|
atDeadline?: PreviewDeadline;
|
|
730
729
|
reference?: string;
|
|
731
730
|
attributes?: TaskAttribute[];
|
|
@@ -745,10 +744,9 @@ type AcceptanceMode =
|
|
|
745
744
|
|
|
746
745
|
type TaskOutcome =
|
|
747
746
|
| { type: "completed"; by: "agent" | "provider" }
|
|
748
|
-
| { type: "transferred"; destinationId?: string }
|
|
749
747
|
| { type: "taken-over"; leadId: UserId }
|
|
750
748
|
| { type: "cancelled"; by: "agent" | "provider" | "party"; reason?: string }
|
|
751
|
-
| { type: "expired"; phase: "pending" | "confirmed"
|
|
749
|
+
| { type: "expired"; phase: "pending" | "confirmed" }
|
|
752
750
|
| { type: "failed"; failure: ProtocolFailure };
|
|
753
751
|
```
|
|
754
752
|
|
|
@@ -765,14 +763,14 @@ const TASK_COMMAND_NAMES = {
|
|
|
765
763
|
"end-call",
|
|
766
764
|
"terminate-call",
|
|
767
765
|
"connect-back",
|
|
768
|
-
"transfer",
|
|
769
766
|
"lead-assist",
|
|
770
767
|
"conference",
|
|
771
768
|
"recording",
|
|
769
|
+
"schedule",
|
|
772
770
|
"complete",
|
|
773
771
|
],
|
|
774
|
-
chat: ["accept", "decline", "pause", "resume", "complete"],
|
|
775
|
-
email: ["accept", "decline", "complete"],
|
|
772
|
+
chat: ["accept", "decline", "pause", "resume", "schedule", "complete"],
|
|
773
|
+
email: ["accept", "decline", "schedule", "complete"],
|
|
776
774
|
} as const;
|
|
777
775
|
|
|
778
776
|
type TaskCommandName<C extends keyof typeof TASK_COMMAND_NAMES> =
|
|
@@ -789,10 +787,7 @@ type VoiceTaskCommand =
|
|
|
789
787
|
| { type: "end-call" }
|
|
790
788
|
| { type: "terminate-call" }
|
|
791
789
|
| { type: "connect-back"; dialId: DialId }
|
|
792
|
-
|
|
|
793
|
-
| { type: "transfer"; action: "warm"; dialId: DialId; destinationId: string }
|
|
794
|
-
| { type: "transfer"; action: "complete" }
|
|
795
|
-
| { type: "transfer"; action: "cancel" }
|
|
790
|
+
| ScheduleCommand
|
|
796
791
|
| { type: "lead-assist"; action: "request"; note?: string }
|
|
797
792
|
| { type: "lead-assist"; action: "cancel" }
|
|
798
793
|
| { type: "conference"; action: "add"; dialId: DialId; destinationId: string }
|
|
@@ -801,16 +796,20 @@ type VoiceTaskCommand =
|
|
|
801
796
|
| (RecordingCommand & { source: "provider" })
|
|
802
797
|
| ({ type: "complete" } & OutcomePayload);
|
|
803
798
|
|
|
799
|
+
type ScheduleCommand = { type: "schedule"; at: IsoTimestamp; note?: string };
|
|
800
|
+
|
|
804
801
|
type ChatTaskCommand =
|
|
805
802
|
| { type: "accept" }
|
|
806
803
|
| { type: "decline" }
|
|
807
804
|
| { type: "pause" }
|
|
808
805
|
| { type: "resume" }
|
|
806
|
+
| ScheduleCommand
|
|
809
807
|
| ({ type: "complete" } & OutcomePayload);
|
|
810
808
|
|
|
811
809
|
type EmailTaskCommand =
|
|
812
810
|
| { type: "accept" }
|
|
813
811
|
| { type: "decline" }
|
|
812
|
+
| ScheduleCommand
|
|
814
813
|
| ({ type: "complete" } & OutcomePayload);
|
|
815
814
|
|
|
816
815
|
type CustomTaskCommand = { type: "custom"; name: string; [key: string]: unknown };
|
|
@@ -933,7 +932,7 @@ type BreakRequest = {
|
|
|
933
932
|
};
|
|
934
933
|
|
|
935
934
|
type ForcedBreak = {
|
|
936
|
-
by: UserId;
|
|
935
|
+
by: UserId | "provider";
|
|
937
936
|
expectedDurationMs?: number;
|
|
938
937
|
};
|
|
939
938
|
|
|
@@ -978,10 +977,10 @@ type TeamMember = {
|
|
|
978
977
|
tasks?: MemberTask[];
|
|
979
978
|
listening?: MemberListening;
|
|
980
979
|
shift?: MemberShift;
|
|
980
|
+
request?: MemberRequest;
|
|
981
981
|
};
|
|
982
982
|
|
|
983
|
-
type
|
|
984
|
-
memberId: UserId;
|
|
983
|
+
type MemberRequest = {
|
|
985
984
|
assignmentId: AssignmentId;
|
|
986
985
|
note?: string;
|
|
987
986
|
since: IsoTimestamp;
|
|
@@ -1031,7 +1030,6 @@ type MemberShift = {
|
|
|
1031
1030
|
|
|
1032
1031
|
type TeamMembers = {
|
|
1033
1032
|
members: TeamMember[];
|
|
1034
|
-
requests: LeadRequest[];
|
|
1035
1033
|
policies?: TeamPolicies;
|
|
1036
1034
|
};
|
|
1037
1035
|
|
|
@@ -1122,7 +1120,6 @@ type ProviderEvent =
|
|
|
1122
1120
|
| {
|
|
1123
1121
|
type: "task-offered";
|
|
1124
1122
|
task: Task;
|
|
1125
|
-
assignmentExpiresAt?: IsoTimestamp;
|
|
1126
1123
|
}
|
|
1127
1124
|
| { type: "task-updated"; task: Task }
|
|
1128
1125
|
| { type: "task-audio-started"; assignmentId: AssignmentId }
|
|
@@ -1133,6 +1130,9 @@ type ProviderEvent =
|
|
|
1133
1130
|
| { type: "queue-summary"; summary: QueueSummary }
|
|
1134
1131
|
| { type: "diagnostic"; expected: string; observed: string; assignmentId?: AssignmentId }
|
|
1135
1132
|
| { type: "team-updated"; team: TeamMembers }
|
|
1133
|
+
| { type: "team-member-updated"; member: TeamMember }
|
|
1134
|
+
| { type: "team-member-removed"; memberId: UserId }
|
|
1135
|
+
| { type: "team-policies-updated"; policies: TeamPolicies }
|
|
1136
1136
|
| { type: "contacts-updated"; contacts: Contact[] }
|
|
1137
1137
|
| { type: "calendar-updated"; calendar: ScheduledActivity[] };
|
|
1138
1138
|
|
|
@@ -1257,6 +1257,8 @@ const OMNI_FAILURE_CODES = [
|
|
|
1257
1257
|
"omni.rate-limited",
|
|
1258
1258
|
"omni.unavailable",
|
|
1259
1259
|
"omni.break-already-committed",
|
|
1260
|
+
"omni.recording-unsettled",
|
|
1261
|
+
"omni.break-forced-by-provider",
|
|
1260
1262
|
] as const;
|
|
1261
1263
|
type OmniFailureCode = (typeof OMNI_FAILURE_CODES)[number];
|
|
1262
1264
|
```
|
|
@@ -1428,8 +1430,8 @@ only where the corresponding capability is declared; an absent capability means
|
|
|
1428
1430
|
there is no separate permission flag.
|
|
1429
1431
|
|
|
1430
1432
|
The commands every task has are authorized by other fields the provider declared — a task that was
|
|
1431
|
-
offered can be accepted, one in `preview` can be called, one
|
|
1432
|
-
|
|
1433
|
+
offered can be accepted, one in `preview` can be called, one with a wrap can be completed.
|
|
1434
|
+
Nothing is issuable that the provider did not publish; only
|
|
1433
1435
|
which field says so varies. See **Which commands need a capability**.
|
|
1434
1436
|
|
|
1435
1437
|
### 5. Snapshots establish state; events report transactions
|
|
@@ -1589,7 +1591,7 @@ export default defineAdapter({
|
|
|
1589
1591
|
channel: "voice",
|
|
1590
1592
|
supportedProtocolVersions: [OMNI_PROTOCOL_VERSION],
|
|
1591
1593
|
authenticationMethods: ["browser-sso"],
|
|
1592
|
-
|
|
1594
|
+
settleMs: 5000,
|
|
1593
1595
|
idleCapabilities: {
|
|
1594
1596
|
dial: { destinations: "any-number" },
|
|
1595
1597
|
},
|
|
@@ -1623,11 +1625,11 @@ compile time.
|
|
|
1623
1625
|
| `taskTypePresentation` | Optional static adapter-defined presentation keyed by exact `taskType`. It names the item and its optional agent-facing reference. |
|
|
1624
1626
|
| `orgLevels` | The organisation's whole ladder as the provider calls it, each level with the label a desk shows for "who decided". Stated outright, `person` included: what it leaves out does not exist. Omitted for the typical four, `DEFAULT_LEVELS`. See **Who decides what an agent may do**. |
|
|
1625
1627
|
| `phones` | Voice only, and required there: the phones this platform can put an agent on, `softphone` (the call's audio lands in the agent application) and/or `deskPhone` (a handset the platform rings; the agent application shows the call and opens nothing). The agent application picks one per login. See **How the agent hears the call**. |
|
|
1626
|
-
| `dialOutcomes` | Voice only. How a dial can end on this platform, as it distinguishes them: `answered` and at least one way of not reaching the destination. Required of a provider that dials at all — an idle dialpad, or tasks that
|
|
1628
|
+
| `dialOutcomes` | Voice only. How a dial can end on this platform, as it distinguishes them: `answered` and at least one way of not reaching the destination. Required of a provider that dials at all — an idle dialpad, or tasks that conference or call back — and a `dial-outcome` carries only a declared member. See **Every dial has an outcome**. |
|
|
1627
1629
|
| `timeCheck` | Optional `true`: implements `checkTime` for fresh provider-clock samples; agent application polling is independently opt-in. |
|
|
1628
1630
|
| `timestampAuthority` | Optional `"provider"`: provider timestamps are final; agent application instants are advisory. Omission makes no trust promise. |
|
|
1629
1631
|
| `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**. |
|
|
1630
|
-
| `
|
|
1632
|
+
| `settleMs` | Required. How long after `applied` the wire shows what the provider did: the `task-ended` after a `complete`, the `calendar-updated` after a `schedule`. A positive whole number of milliseconds (`manifest.settleMs`). Stated per provider, since platforms settle at different speeds. See **`task-ended`**. |
|
|
1631
1633
|
|
|
1632
1634
|
### Authentication methods
|
|
1633
1635
|
|
|
@@ -2162,7 +2164,7 @@ surface in one place, and what obliges an adapter to implement each one.
|
|
|
2162
2164
|
| `refused(report)` | Always. The agent application tells the adapter what it would not take -- a snapshot it did not replace its state with, an event it dropped -- with every rule broken, so a refusal is visible on both sides. See **What the agent application does with what it refuses**. |
|
|
2163
2165
|
| `setCapacity(capacity)` | Always. Nothing may be assigned until a capacity is stated, so there is no connection that does not receive it. |
|
|
2164
2166
|
| `execute(request)` | Always. Every channel has commands no capability gates — see **Which commands need a capability**. |
|
|
2165
|
-
| `getUserDetails(ids)` | The adapter publishes any `UserId`: on `ForcedBreak.by
|
|
2167
|
+
| `getUserDetails(ids)` | The adapter publishes any `UserId`: on `ForcedBreak.by` where a person forced it, a team member list, or `history[].by`. Each `User` carries its `timeZone`; a person whose zone the provider cannot name is omitted from the answer, as any unresolvable id is. |
|
|
2166
2168
|
| `dial(request)` | The manifest declares `idleCapabilities.dial`, and with it `dialOutcomes`. |
|
|
2167
2169
|
| `requestBreak(request)` | The login declares `capabilities.breaks`. |
|
|
2168
2170
|
| `commitBreak()` | The login declares `capabilities.breaks`. Commit and cancel are not optional halves of it. |
|
|
@@ -2217,8 +2219,8 @@ Turns `UserId` values into something an agent can read.
|
|
|
2217
2219
|
getUserDetails(ids: UserId[]): Promise<User[]>
|
|
2218
2220
|
```
|
|
2219
2221
|
|
|
2220
|
-
Required of any adapter that publishes a `UserId` — on `ForcedBreak.by
|
|
2221
|
-
`history[].by`. Publishing an identifier Omni cannot resolve puts a name on screen
|
|
2222
|
+
Required of any adapter that publishes a `UserId` — on `ForcedBreak.by` where a person forced it, a
|
|
2223
|
+
team member list, or `history[].by`. Publishing an identifier Omni cannot resolve puts a name on screen
|
|
2222
2224
|
that reads as a database key.
|
|
2223
2225
|
|
|
2224
2226
|
- **Omit an id you cannot resolve; do not invent a name for it.** A missing entry says *I do not
|
|
@@ -2321,8 +2323,7 @@ declare const task: Task;
|
|
|
2321
2323
|
|
|
2322
2324
|
const assignment = {
|
|
2323
2325
|
type: "task-offered",
|
|
2324
|
-
task: { ...task, phase: "pending", acceptance: "consent" },
|
|
2325
|
-
assignmentExpiresAt: "2026-08-25T10:41:07.000Z",
|
|
2326
|
+
task: { ...task, phase: "pending", acceptance: "consent", expiresInSeconds: 30 },
|
|
2326
2327
|
} satisfies Extract<ProviderEvent, { type: "task-offered" }>;
|
|
2327
2328
|
```
|
|
2328
2329
|
|
|
@@ -2340,21 +2341,32 @@ on a desk phone — and is not in question per call. See **How the agent hears t
|
|
|
2340
2341
|
|
|
2341
2342
|
Automatic acceptance still begins with `task-offered`.
|
|
2342
2343
|
|
|
2343
|
-
`
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2344
|
+
`expiresInSeconds` is how long the offer has left, in whole seconds from the publication that
|
|
2345
|
+
carries it. It rides on the pending task, as `acceptance` does, so a reconnect snapshot restates
|
|
2346
|
+
it from what is actually left; it belongs to `pending` alone and is gone once the task is accepted
|
|
2347
|
+
(`task.pending.expiresInSeconds`, `.unexpected`). Where present, Omni counts down from receipt and
|
|
2348
|
+
stops offering **Accept** once it runs out; the provider ends the lapsed offer with `task-ended`
|
|
2349
|
+
and an `expired` outcome naming `pending`, since nobody cancelled it. **Omit it unless the provider can observe it.** A provider that reports only elapsed
|
|
2350
|
+
ring time after the fact cannot say how long an offer has left, and a computed value would have
|
|
2351
|
+
Omni withdraw **Accept** from a task still pending.
|
|
2352
|
+
|
|
2353
|
+
**A deadline is seconds left, never an instant.** The agent application and the provider keep
|
|
2354
|
+
different clocks, and a countdown that compares one against the other is a countdown that runs
|
|
2355
|
+
fast or slow by their skew. Seconds from the publication that carries them are the provider's own
|
|
2356
|
+
arithmetic, counted down on the desk from the moment they arrive, and the transport delay is the
|
|
2357
|
+
only error. Restated on every publication that carries them, so a snapshot after a reconnect
|
|
2358
|
+
starts the countdown afresh from what is actually left (`task.pending.expiresInSeconds`,
|
|
2359
|
+
`task.preview.previewEndsInSeconds`: a whole number, zero or more).
|
|
2360
|
+
|
|
2361
|
+
A preview's deadline is not on the offer. It travels on the task, as `previewEndsInSeconds` with
|
|
2350
2362
|
`atDeadline`, so a snapshot carries it too -- see **Preview: the agent presses Call**.
|
|
2351
2363
|
|
|
2352
2364
|
A provider may withdraw a pending task by emitting `task-ended` with a `cancelled` outcome, `by:
|
|
2353
2365
|
"provider"`. **`cancelled` says who called the work off**, as `completed` says who completed it:
|
|
2354
2366
|
`agent` for a decline, `provider` for a withdrawal or a re-route, `party` for a caller who abandoned
|
|
2355
2367
|
the ring. One word for the three left a supervisor's record unable to tell them apart, so `by` is
|
|
2356
|
-
required and closed (`event.taskEnded.outcome.cancelled.by`). An offer nobody acted on
|
|
2357
|
-
`
|
|
2368
|
+
required and closed (`event.taskEnded.outcome.cancelled.by`). An offer nobody acted on while
|
|
2369
|
+
`expiresInSeconds` ran is not cancelled by anyone: it lapses, and ends `expired` naming `pending`.
|
|
2358
2370
|
|
|
2359
2371
|
### Tasks already in progress
|
|
2360
2372
|
|
|
@@ -2390,14 +2402,15 @@ time. Runtime conformance checks also require the task channel to match its prov
|
|
|
2390
2402
|
| `phase` | Current canonical task phase: `pending`, `confirmed`, `preview`, `in-progress`, `paused`, or `completing`. `preview` is voice only. |
|
|
2391
2403
|
| `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. |
|
|
2392
2404
|
| `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**. |
|
|
2393
|
-
| `
|
|
2394
|
-
| `
|
|
2405
|
+
| `expiresInSeconds` | In `pending` only: how long the offer has left, in whole seconds from this publication, restated on every publication that carries it. Absent, the offer stands until the provider says otherwise. See **Pending**. |
|
|
2406
|
+
| `previewEndsInSeconds` | Voice only, in `preview`: how long the preparation has left, in whole seconds from this publication, restated on every publication that carries it. Absent, the agent has as long as they need without a preparation countdown. Always with `atDeadline`. See **Preview: the agent presses Call**. |
|
|
2407
|
+
| `atDeadline` | Voice only, in `preview`, with `previewEndsInSeconds`: what the system does when it runs out -- `provider-dials` makes the provider initiate dialing, `host-dials` makes the agent application issue Call, `waits` keeps the task in preview awaiting the agent. |
|
|
2395
2408
|
| `reference` | Optional agent-facing reference such as a case, call, conversation, ticket, or message number. It is distinct from the protocol `id`. |
|
|
2396
|
-
| `completionMode` | `agent-command` waits for the channel's `complete` command; `provider-automatic` completes without one. |
|
|
2397
|
-
| `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. |
|
|
2409
|
+
| `completionMode` | `agent-command` waits for the channel's `complete` command; `provider-automatic` completes without one, and takes the agent's `complete` as finishing early. A required outcome is the agent's to give, so it needs `agent-command` (`task.outcomes.required.mode`). |
|
|
2410
|
+
| `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. `0` is no wrap at all, so a task with it publishes no `outcomes` (`task.outcomes.wrapAllowance`). |
|
|
2398
2411
|
| `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. |
|
|
2399
2412
|
| `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**. |
|
|
2400
|
-
| `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, `
|
|
2413
|
+
| `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, `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. `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**. |
|
|
2401
2414
|
| `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**. |
|
|
2402
2415
|
| `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 their capacity like any other call. See **Lead assist**. |
|
|
2403
2416
|
|
|
@@ -2435,12 +2448,10 @@ The canonical task transitions are:
|
|
|
2435
2448
|
| `preview` | The call goes out and nobody answers | `completing` |
|
|
2436
2449
|
| `preview` | The preparation target passes with `atDeadline: "waits"` | Remains `preview`, waiting for the agent to press Call |
|
|
2437
2450
|
| `pending` | Provider withdraws the assignment, the agent declines, or the party abandons the ring | Removed by `task-ended` with `cancelled` outcome, `by` saying which |
|
|
2438
|
-
| `pending` | The offer
|
|
2451
|
+
| `pending` | The offer's `expiresInSeconds` runs out | Removed by `task-ended` with `expired` outcome naming `pending` |
|
|
2439
2452
|
| No task | Snapshot reports work already underway | `in-progress` |
|
|
2440
2453
|
| `in-progress` | Provider or agent pauses the task | `paused` |
|
|
2441
|
-
| `in-progress` | Agent starts a warm transfer (`transfer` `warm`); the customer is parked | `paused` |
|
|
2442
2454
|
| `paused` | Provider or agent resumes the task | `in-progress` |
|
|
2443
|
-
| `paused` | Agent cancels a warm transfer (`transfer` `cancel`) | `in-progress` |
|
|
2444
2455
|
| `in-progress` or `paused` | Contact interaction ends and follow-up work remains | `completing` |
|
|
2445
2456
|
| `completing` | Agent connects back to the party (`connect-back`) | `in-progress` |
|
|
2446
2457
|
| Any phase | Provider emits `task-ended` | Removed |
|
|
@@ -2485,9 +2496,9 @@ const previewed = {
|
|
|
2485
2496
|
capabilities: {},
|
|
2486
2497
|
phase: "preview",
|
|
2487
2498
|
party: { name: "Maya Rao", number: "+919876543210" },
|
|
2488
|
-
|
|
2499
|
+
previewEndsInSeconds: 120,
|
|
2489
2500
|
atDeadline: "provider-dials",
|
|
2490
|
-
} satisfies Pick<Task<"voice">, "channel" | "capabilities" | "phase" | "party" | "
|
|
2501
|
+
} satisfies Pick<Task<"voice">, "channel" | "capabilities" | "phase" | "party" | "previewEndsInSeconds" | "atDeadline">;
|
|
2491
2502
|
|
|
2492
2503
|
// The agent presses Call. It is a dial like any other.
|
|
2493
2504
|
const pressed: TaskCommand<"voice"> = { type: "dial", dialId: "dial-7f2" };
|
|
@@ -2505,8 +2516,9 @@ is work, not a cancellation. A `preview` task therefore needs a manifest that sa
|
|
|
2505
2516
|
(`task.preview.dialOutcomes.required`).
|
|
2506
2517
|
|
|
2507
2518
|
**Preparation and trigger ownership are per task.** Unlimited preparation omits both
|
|
2508
|
-
`
|
|
2509
|
-
carries the provider
|
|
2519
|
+
`previewEndsInSeconds` and `atDeadline`: only an agent pressing Call starts dialing. Fixed
|
|
2520
|
+
preparation carries the seconds the provider says are left, counted down on the desk from the
|
|
2521
|
+
publication that carried them and restated on every later one, and exactly one trigger owner:
|
|
2510
2522
|
|
|
2511
2523
|
| Declaration | At preparation end |
|
|
2512
2524
|
| --- | --- |
|
|
@@ -2551,14 +2563,14 @@ Submission acceptance is never an answered outcome or permission to publish `in-
|
|
|
2551
2563
|
Unsupported source correlation/outcome/audio evidence remains an integration blocker.
|
|
2552
2564
|
|
|
2553
2565
|
**Drop both fields when the phase moves.** A provider that builds the `in-progress` task by
|
|
2554
|
-
spreading the `preview` one carries `
|
|
2566
|
+
spreading the `preview` one carries `previewEndsInSeconds` and `atDeadline` with it, and the agent application refuses
|
|
2555
2567
|
the update (`task.preview.deadline.unexpected`): the desk keeps the task as it last stood, and the
|
|
2556
2568
|
provider is told through `refused` exactly which rule, so the state that looked right on its side
|
|
2557
2569
|
is named on its side. The task past preview has no deadline to wait for, so it carries neither
|
|
2558
2570
|
field.
|
|
2559
2571
|
|
|
2560
|
-
**Provider execution and agent application controls are distinct.** Answer, hold/resume,
|
|
2561
|
-
|
|
2572
|
+
**Provider execution and agent application controls are distinct.** Answer, hold/resume, conference,
|
|
2573
|
+
lead assist and interaction completion are executed by the provider
|
|
2562
2574
|
through the adapter. The task's applicable capability, phase, completion mode and command-specific
|
|
2563
2575
|
prerequisites decide which controls are offered. Caller disconnect also executes at the provider. Microphone mute is agent application-owned, never a provider
|
|
2564
2576
|
TaskCommand or task capability. The separately declared agent application recording facility keeps its existing
|
|
@@ -2586,7 +2598,7 @@ become a caller-disconnect operation.
|
|
|
2586
2598
|
**A task is never its audio.** A voice task represents the agent’s interaction: the call is offered when it is
|
|
2587
2599
|
routed to the agent and accepted as its `acceptance` dictates, and its presence and phase follow
|
|
2588
2600
|
the provider's reports about the work — never the audio. Wherever audio moves — an offer, a hold, a
|
|
2589
|
-
|
|
2601
|
+
conference leg joining or leaving, a lead joining, a take-over, a connect-back — the audio follows
|
|
2590
2602
|
separately, arriving on `task-audio-started`, attaching through `openAudio` and ending with
|
|
2591
2603
|
`task-audio-ended`. Omni does not ring,
|
|
2592
2604
|
bridge, or hold a line. How the phone rings, whether it rings at all, and where legs join and leave
|
|
@@ -2610,8 +2622,29 @@ and `assertAudioFollowsTheTask` holds any sequence.
|
|
|
2610
2622
|
the task open until Omni sends the channel's `complete` command. With `provider-automatic`, the
|
|
2611
2623
|
provider may complete the task without receiving that command.
|
|
2612
2624
|
|
|
2613
|
-
|
|
2614
|
-
|
|
2625
|
+
**The agent may finish early under either mode.** `complete` is issuable wherever there is a wrap
|
|
2626
|
+
to cut short: under `agent-command` it is the end, and under `provider-automatic` it says the agent
|
|
2627
|
+
is done before the allowance ran out, and the provider is free to end the task at once. Only a task
|
|
2628
|
+
with no wrap at all -- `provider-automatic` with `wrapAllowance: 0` -- has nothing to complete, and
|
|
2629
|
+
the command is refused there (`command.complete.wrapAllowance`). An ending the agent asked for says
|
|
2630
|
+
so, `completed` with `by: "agent"`, whichever mode the task was under.
|
|
2631
|
+
|
|
2632
|
+
**A required outcome is the agent's to give.** A task whose `outcomes` says `required: true` waits
|
|
2633
|
+
for a code the agent chooses, so it completes on the agent's command: `provider-automatic` cannot
|
|
2634
|
+
wait for it, and the pair is refused (`task.outcomes.required.mode`). Optional outcomes may ride on
|
|
2635
|
+
a task the provider completes itself; the agent gives one if in time. And a code is given in wrap,
|
|
2636
|
+
so a `provider-automatic` task with `wrapAllowance: 0` publishes no `outcomes` at all
|
|
2637
|
+
(`task.outcomes.wrapAllowance`).
|
|
2638
|
+
|
|
2639
|
+
**The allowance means one thing per mode.** Under `provider-automatic` it is what the provider
|
|
2640
|
+
acts on: the task ends when it runs out. Under `agent-command` it is the expected wrap, stated so
|
|
2641
|
+
the agent can see it: the desk counts it down and, past zero, shows how far over they are, and
|
|
2642
|
+
nothing acts on it -- the task waits for `complete` however long that takes, with its outcomes
|
|
2643
|
+
collected whenever the agent gives them. A provider that would end the task at a time is
|
|
2644
|
+
`provider-automatic`.
|
|
2645
|
+
|
|
2646
|
+
`wrapAllowance` is fixed, and when it starts depends on whether the channel carries real-time
|
|
2647
|
+
audio:
|
|
2615
2648
|
|
|
2616
2649
|
| Channel | Wrap allowance starts at |
|
|
2617
2650
|
| --- | --- |
|
|
@@ -2626,8 +2659,9 @@ is optional: a conversation with nothing to wrap moves from `in-progress` to `ta
|
|
|
2626
2659
|
`completing` is never published. Under `provider-automatic` with a non-zero `wrapAllowance` it is
|
|
2627
2660
|
required, because the clock has to start somewhere: a chat or email task the provider completes
|
|
2628
2661
|
from `in-progress` with an allowance to run gave the agent none of it, and the stream refuses the
|
|
2629
|
-
ending (`stream.taskEnded.unwrapped`)
|
|
2630
|
-
|
|
2662
|
+
ending (`stream.taskEnded.unwrapped`); an ending the agent's own `complete` brought forward says
|
|
2663
|
+
`by: "agent"` and is not that. Under `agent-command` the agent's `complete` is the end, from
|
|
2664
|
+
`in-progress` or from `completing` alike.
|
|
2631
2665
|
|
|
2632
2666
|
```ts
|
|
2633
2667
|
const emailCompletion = {
|
|
@@ -2639,8 +2673,9 @@ const emailCompletion = {
|
|
|
2639
2673
|
In this example, the agent has two minutes after sending the email to add notes, select a
|
|
2640
2674
|
outcome, and complete the task.
|
|
2641
2675
|
|
|
2642
|
-
`0` means
|
|
2643
|
-
|
|
2676
|
+
`0` means no wrap under `provider-automatic`: the provider completes the task at the end of the
|
|
2677
|
+
interaction, and `complete` has nothing to cut short. Under `agent-command` it is an expected wrap
|
|
2678
|
+
of nothing -- the desk shows the overrun from the start -- and the task still waits for `complete`.
|
|
2644
2679
|
|
|
2645
2680
|
There is no value meaning "unlimited", because a number that is not a duration would be read as
|
|
2646
2681
|
one. A provider that imposes no deadline says so by **omitting** `wrapAllowance`, which
|
|
@@ -2680,7 +2715,7 @@ deadline.
|
|
|
2680
2715
|
|
|
2681
2716
|
**The room shows the party being dialled.** From the moment any agent application dial places the party -- a
|
|
2682
2717
|
dialpad call, a preview Call, a connect-back -- the `party` entry carries `stage: "ringing"` and
|
|
2683
|
-
the agent application's `dialId`, exactly as a
|
|
2718
|
+
the agent application's `dialId`, exactly as a conferenced entry does from its dial; `joined` on
|
|
2684
2719
|
the publication that follows the answered `dial-outcome` and never before it
|
|
2685
2720
|
(`stream.taskUpdated.stage`); and no stage from the next publication on, so `joined` is transient
|
|
2686
2721
|
and a later copy still carrying it is stale (`stream.taskUpdated.stage.lingering`). An agent application shows a
|
|
@@ -2728,7 +2763,7 @@ The record is the call's, not the task's: most of what it holds happened before
|
|
|
2728
2763
|
*Interaction* keeps the one meaning **Terms** gives it, the agent's time on the call, which is why
|
|
2729
2764
|
the total of earlier agents' time is `interactionSeconds` and the phase rules are
|
|
2730
2765
|
`command.phase.interaction`. Hosts and providers adopt these names together; no legacy aliases
|
|
2731
|
-
are provided. The manifest's `
|
|
2766
|
+
are provided. The manifest's `settleMs` bounds final task completion, not entry into
|
|
2732
2767
|
the `completing` wrap-up phase. The `recordStep` method and `complete` command keep their names.
|
|
2733
2768
|
|
|
2734
2769
|
Migration from the earlier spellings:
|
|
@@ -2742,16 +2777,27 @@ Migration from the earlier spellings:
|
|
|
2742
2777
|
| HANDLING_STEPS_WITH_A_PERSON, HANDLING_STEPS_THAT_DIAL, and their INTERACTION spellings | `HISTORY_STEPS_WITH_A_PERSON`, `HISTORY_STEPS_THAT_DIAL` |
|
|
2743
2778
|
| handlingStepExpectsAPerson, handlingStepDials, and their interaction spellings | `historyStepExpectsAPerson`, `historyStepDials` |
|
|
2744
2779
|
| handleSeconds | `interactionSeconds` |
|
|
2745
|
-
| Task.allocationId, AllocationId, allocationExpiresAt | `Task.assignmentId`, `AssignmentId`, `
|
|
2780
|
+
| Task.allocationId, AllocationId, allocationExpiresAt | `Task.assignmentId`, `AssignmentId`, `expiresInSeconds` |
|
|
2746
2781
|
| 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 |
|
|
2747
2782
|
| 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`) |
|
|
2748
|
-
| Manifest.disposalSettleMs | `Manifest.
|
|
2783
|
+
| Manifest.disposalSettleMs, Manifest.completionSettleMs | `Manifest.settleMs` (`manifest.settleMs`, `manifest.settleMs.renamed`): the bound after `applied` for `complete` and `schedule` alike |
|
|
2749
2784
|
| the call command, atDeadline calls and host-calls | `dial`, `"provider-dials"`, `"host-dials"` |
|
|
2750
2785
|
| 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`, `audio` on the task, `TaskAudioState`, `openAudio`, `OpenAudioRequest`, `OpenAudioResult`, `CallAudio` on the result's `audio`; there is no team audio, the lead's follows `listening` on the member |
|
|
2751
2786
|
| capabilitySource values ungoverned and undetermined | `nobody`, `not-yet-read` (`capabilitySource.notYetRead`) |
|
|
2752
2787
|
| Snapshot.scheduledActivities, and scheduledActivities on calendar-updated | `calendar` in both |
|
|
2753
2788
|
| availability reserved | `elsewhere` |
|
|
2754
2789
|
| recording destinationId, HostRecording.destinationIds | `storageId`, `storageIds` (`recording.storage`, `recording.host.storage`) |
|
|
2790
|
+
| coldTransfer, warmTransfer, the transfer command and its four actions, the consulted role, the transferred outcome | gone: help arrives by **Lead assist**; the `transferred` history step and the `transfers` total stay, since the platform's own routing still changes hands |
|
|
2791
|
+
| assignmentExpiresAt on task-offered, previewEndsAt on the task | `expiresInSeconds`, `previewEndsInSeconds`: seconds left from the publication, never an instant (`event.taskOffered.expiresInSeconds`, `task.preview.previewEndsInSeconds`) |
|
|
2792
|
+
| TeamMembers.requests, LeadRequest | `TeamMember.request` (`MemberRequest`): the ask rides on the member (`team.member.request.*`) |
|
|
2793
|
+
| team-updated on every change | `team-updated` whole once after the switch and on snapshots; `team-member-updated`, `team-member-removed`, `team-policies-updated` after (`stream.team.baseline`, `stream.teamMember.unknown`) |
|
|
2794
|
+
| RecordingCommand.requestId | gone: no request identity; a partial effect is a settled `failed` under `omni.recording-unsettled` |
|
|
2795
|
+
| command.complete.mode | `command.complete.wrapAllowance`: `complete` under either mode, refused only on `provider-automatic` with no wrap |
|
|
2796
|
+
| expiresInSeconds on task-offered | `Task.expiresInSeconds`, in `pending` (`task.pending.expiresInSeconds`, `.unexpected`; `event.taskOffered.expiresInSeconds.unexpected`) |
|
|
2797
|
+
| the expired outcome naming preview | gone: nothing expires a preview; withdrawn, it is `cancelled` by the provider |
|
|
2798
|
+
| schedule on voice alone | `schedule` on every channel, under a manifest that declares `calendar` (`task.capability.calendar.required`), bounded by `settleMs` (`drive.schedule.unsettled`) |
|
|
2799
|
+
| a forced break requesting breaks on the other providers | `setCapacity({ count: 0 })` on every other usable provider (`assertForcedBreakStopsTheRest`) |
|
|
2800
|
+
| end-forced-break on a break the platform imposed | refused: `team.command.endForcedBreak.provider`, `omni.break-forced-by-provider` |
|
|
2755
2801
|
|
|
2756
2802
|
Update producers, consumers, saved task snapshots, and validation-rule assertions together.
|
|
2757
2803
|
History and report rule names use `history` and `historyReport`; assignment rules use
|
|
@@ -2864,7 +2910,7 @@ observed. No other step has it (`task.history.mutedBy`, `.mutedBy.unexpected`).
|
|
|
2864
2910
|
**A step that dialled says which dial and where.** `transferred`, `conferenced` and `unanswered`
|
|
2865
2911
|
each end a dial, so the entry carries the `destinationId` it went to and, where an agent application placed it, the
|
|
2866
2912
|
`dialId` the agent application minted; no other step dialled, and either field on one is refused
|
|
2867
|
-
(`task.history.dialId.unexpected`). This is how a dial made before a
|
|
2913
|
+
(`task.history.dialId.unexpected`). This is how a dial made before a take-over is placeable
|
|
2868
2914
|
by whoever holds the task now: a `dial-outcome` naming a `dialId` the agent application never minted is looked
|
|
2869
2915
|
up in the record, not discarded.
|
|
2870
2916
|
|
|
@@ -2877,7 +2923,7 @@ Four rules a provider has to keep:
|
|
|
2877
2923
|
- **Omit `seconds` while it is unknown. Never send `0`.** A leg still talking is not a zero-second
|
|
2878
2924
|
conversation, and on live data that is the ordinary case rather than an edge. A zero is rejected.
|
|
2879
2925
|
- **`by` is a bare `UserId`, and not necessarily an agent.** A lead or a manager takes part
|
|
2880
|
-
during an interaction too — a
|
|
2926
|
+
during an interaction too — a call taken over, a call conferenced in — so the field names whoever it was,
|
|
2881
2927
|
the same way `ForcedBreak.by` does. It comes from this provider's own directory, the same
|
|
2882
2928
|
namespace as `AuthenticationState.identity.id` and the team member list, so entries pair
|
|
2883
2929
|
within a provider and never across one.
|
|
@@ -2948,12 +2994,13 @@ can be muted by the agent application, and answers `recorded` with the canonical
|
|
|
2948
2994
|
`.mutedBy.unexpected`). On a desk phone the microphone is the phone's:
|
|
2949
2995
|
the agent application mutes nothing and records nothing.
|
|
2950
2996
|
|
|
2951
|
-
**The
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2997
|
+
**The record is a record.** The microphone is the agent application's, and nothing in the
|
|
2998
|
+
history moves it: a `muted` entry the provider publishes, or closes with a final `seconds`, is the
|
|
2999
|
+
provider's account of a leg the agent application reported, and the mute itself ends only when the
|
|
3000
|
+
agent, or the station, ends it. The agent application keeps its own mute state, reports each leg
|
|
3001
|
+
as it begins and ends, and takes the provider's published entry as the closed account of that leg:
|
|
3002
|
+
it does not reopen the leg, replace the provider's timestamp, or overwrite the provider's final
|
|
3003
|
+
duration with a later report. A later agent mute is a new leg, never a reopening of the ended one.
|
|
2957
3004
|
|
|
2958
3005
|
Correlate the provider-published `muted` entry using the provider-selected `at` acknowledged
|
|
2959
3006
|
for the current report key; a final `seconds` closes that leg under the history contract.
|
|
@@ -3213,8 +3260,7 @@ See **Which commands need a capability**.
|
|
|
3213
3260
|
| `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**. |
|
|
3214
3261
|
| `terminateCall` | Primary button: Terminate call | The provider ends the whole call: every channel on it, the agent's, the caller's, any colleague's and anyone else's. The task goes to its wrap. See **Ending a call, and removing one person from it**. |
|
|
3215
3262
|
| `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**. |
|
|
3216
|
-
| `
|
|
3217
|
-
| `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**. |
|
|
3263
|
+
| `schedule` | Secondary menu item: Schedule | Omni may put a follow-up for this party on the calendar, from the call or from its wrap: a time, and a note. See **Scheduling a follow-up**. |
|
|
3218
3264
|
| `leadAssist` | Secondary menu item: Lead assist | Omni may ask a lead to join this call, with a note. The lead's decision reaches the agent on `Task.leadAssist`. See **Lead assist**. |
|
|
3219
3265
|
| `conference` | Secondary button: Conference | Omni may dial a destination into the active call, and remove one person from it -- a conferenced entry, one still ringing included, which calls the dial off, or the party, leaving the agent with the colleague. See **Ending a call, and removing one person from it**. |
|
|
3220
3266
|
| `recording` | Overflow menu item: Recording | Per-task provider and agent application policies expose only their permitted recording actions; each has independent state and routing. |
|
|
@@ -3222,10 +3268,10 @@ See **Which commands need a capability**.
|
|
|
3222
3268
|
|
|
3223
3269
|
### Publishing codes and destinations
|
|
3224
3270
|
|
|
3225
|
-
|
|
3226
|
-
`outcomes
|
|
3227
|
-
|
|
3228
|
-
|
|
3271
|
+
Two capabilities accept an object when the provider wants Omni to render real choices. For
|
|
3272
|
+
`outcomes`, `true` remains valid and means "offer the control with nothing published"; the one
|
|
3273
|
+
directory control, `conference`, carries its directory or is refused, since once nothing is typed
|
|
3274
|
+
the directory is the control.
|
|
3229
3275
|
|
|
3230
3276
|
#### `outcomes`
|
|
3231
3277
|
|
|
@@ -3271,11 +3317,11 @@ capabilities: {
|
|
|
3271
3317
|
With `outcomes: true` Omni shows a Complete control and sends `complete` with no code, because
|
|
3272
3318
|
the provider published none.
|
|
3273
3319
|
|
|
3274
|
-
#### `
|
|
3320
|
+
#### `conference`
|
|
3275
3321
|
|
|
3276
3322
|
```ts
|
|
3277
3323
|
capabilities: {
|
|
3278
|
-
|
|
3324
|
+
conference: {
|
|
3279
3325
|
destinations: [
|
|
3280
3326
|
{ id: "tier2", label: "Tier 2 support" },
|
|
3281
3327
|
{ id: "main-menu", label: "Back to the main menu" },
|
|
@@ -3287,7 +3333,7 @@ capabilities: {
|
|
|
3287
3333
|
| Field | Contract |
|
|
3288
3334
|
| --- | --- |
|
|
3289
3335
|
| `destinations` | The items the control offers, at least one, with unique `id` values. Each is a button or a menu item on the desk. |
|
|
3290
|
-
| `id` | What Omni sends as `destinationId` on a `
|
|
3336
|
+
| `id` | What Omni sends as `destinationId` on a `conference` command, and what the provider executes. |
|
|
3291
3337
|
| `label` | What the agent reads. |
|
|
3292
3338
|
|
|
3293
3339
|
**The protocol does not say what an item does.** A queue, an IVR menu, an outside line, a
|
|
@@ -3299,7 +3345,7 @@ reason (`task.destinations.shape`): once nothing is typed, the directory is the
|
|
|
3299
3345
|
|
|
3300
3346
|
**The provider chooses the supported destinations.** A published item may route to an IVR,
|
|
3301
3347
|
queue, named agent or another configured destination. No destination kind is inferred from its
|
|
3302
|
-
label. The agent application offers only the directory attached to this task's
|
|
3348
|
+
label. The agent application offers only the directory attached to this task's `conference` capability,
|
|
3303
3349
|
and sends the exact destination ID; it does not invent an agent picker or arbitrary dial target.
|
|
3304
3350
|
The provider rechecks eligibility and executes the routing. Returning to an IVR or queue does not
|
|
3305
3351
|
end the caller journey; a later assignment to the same agent is a new interaction.
|
|
@@ -3317,7 +3363,7 @@ provider. Two of them end the agent's part; they differ in what happens to the c
|
|
|
3317
3363
|
```
|
|
3318
3364
|
|
|
3319
3365
|
**`end-call` ends my part.** The agent's channel and every channel the agent added -- a
|
|
3320
|
-
conferenced colleague, a
|
|
3366
|
+
conferenced colleague, a lead who joined -- end, and the caller continues on the path the
|
|
3321
3367
|
provider decides: another IVR, a queue, a survey, or the end of the call. Gated by `endCall`.
|
|
3322
3368
|
|
|
3323
3369
|
**`terminate-call` ends the whole call.** Every channel on it ends at the provider: the agent's,
|
|
@@ -3339,53 +3385,43 @@ when it acts.
|
|
|
3339
3385
|
for the rest; it is gated by `conference`, since it only means something with a third person on
|
|
3340
3386
|
the line. Removing the party leaves the agent with the colleague, a warm hand-over in reverse. A
|
|
3341
3387
|
remove that would leave the agent alone is not a remove but an `end-call`, and a provider answers
|
|
3342
|
-
it `failed`.
|
|
3343
|
-
`cancel` is its own step, which returns the agent to the parked customer, and a remove never
|
|
3344
|
-
promises that.
|
|
3388
|
+
it `failed`.
|
|
3345
3389
|
|
|
3346
|
-
|
|
3390
|
+
**There is no transfer.** The agent hands the call to nobody, cold or warm: help arrives by
|
|
3391
|
+
**Lead assist**, where a lead joins the call or takes it over, and a call that changes hands does
|
|
3392
|
+
so on the platform's own routing -- an IVR return, a queue, a lead's take-over -- with the record
|
|
3393
|
+
saying so in a `transferred` step and the `transfers` total. Nothing the agent commands moves the
|
|
3394
|
+
caller to somebody else.
|
|
3347
3395
|
|
|
3348
|
-
|
|
3349
|
-
then either hands the customer over or returns to them. Cold and warm are the words agents use;
|
|
3350
|
-
telephony says blind and consult. It is its own capability, distinct from `coldTransfer` (a
|
|
3351
|
-
hand-over with nobody spoken to first) and from `conference` (everybody on one call): a queue may
|
|
3352
|
-
offer any of the three without the others, and each is declared on its own.
|
|
3396
|
+
#### Scheduling a follow-up
|
|
3353
3397
|
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
{ type: "transfer", action: "warm", dialId: "dial-7f2", destinationId: "tier2" }
|
|
3359
|
-
|
|
3360
|
-
// 2a. Hand the customer to the consulted destination and leave.
|
|
3361
|
-
{ type: "transfer", action: "complete" }
|
|
3398
|
+
An agent on a call promises to call back on Thursday, or in wrap writes up that the customer wants
|
|
3399
|
+
a callback once the refund lands. The follow-up goes on the calendar, and the platform owns the
|
|
3400
|
+
calendar: `schedule` asks the provider to put it there. A chat or an email agent promises a
|
|
3401
|
+
callback as often, so the control is on every channel.
|
|
3362
3402
|
|
|
3363
|
-
|
|
3364
|
-
|
|
3403
|
+
```ts
|
|
3404
|
+
// From the call or the conversation, or from its wrap: the time, and a note where the agent wrote one.
|
|
3405
|
+
{ type: "schedule", at: "2026-08-28T10:00:00Z", note: "Call back about the refund" }
|
|
3365
3406
|
```
|
|
3366
3407
|
|
|
3367
|
-
`
|
|
3368
|
-
|
|
3369
|
-
and
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
(`
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
provider's report of both, as it does for every command.
|
|
3385
|
-
|
|
3386
|
-
A destination that does not answer is a dial that ended: the provider reports its `dial-outcome`,
|
|
3387
|
-
returns the task to `in-progress` with no `consulted` entry, and the agent is back with the
|
|
3388
|
-
customer.
|
|
3408
|
+
`schedule` is gated by the `schedule` capability and issuable in `in-progress`, `paused` or
|
|
3409
|
+
`completing` (`command.phase.interaction`): a follow-up is promised on the call and written up in
|
|
3410
|
+
wrap alike, and the task names the party it is for. It lands on the calendar, so a task may offer
|
|
3411
|
+
it only under a manifest that declares the `calendar` idle capability
|
|
3412
|
+
(`task.capability.calendar.required`), as a control that dials needs `dialOutcomes`; a locked
|
|
3413
|
+
`schedule` is still a declared one. The command carries a time and a note and nothing else
|
|
3414
|
+
(`command.schedule.at`, `command.schedule.note`, `command.field`): what the platform makes of a
|
|
3415
|
+
follow-up -- a campaign record, a reminder, a scheduled dial -- is its own, and the agent sees it
|
|
3416
|
+
on the calendar.
|
|
3417
|
+
|
|
3418
|
+
**`applied` says the follow-up is on the calendar**, and the `calendar-updated` that shows it
|
|
3419
|
+
follows within the manifest's `settleMs`, as a `task-ended` follows an applied `complete`. Past
|
|
3420
|
+
the bound the agent application calls `snapshot()`: a calendar carrying an activity at that instant
|
|
3421
|
+
clears the wait; one without it shows the follow-up as unsettled -- "Scheduled... the provider has
|
|
3422
|
+
not confirmed" -- naming the command. The drive schedules one follow-up where the task offers it,
|
|
3423
|
+
at a time it takes from the provider's own publication, and holds the provider to the same bound
|
|
3424
|
+
(`drive.schedule.unsettled`).
|
|
3389
3425
|
|
|
3390
3426
|
### Chat capabilities
|
|
3391
3427
|
|
|
@@ -3393,6 +3429,7 @@ customer.
|
|
|
3393
3429
|
| --- | --- | --- |
|
|
3394
3430
|
| `decline` | Pending-task button: Decline | The provider can decline a pending chat offer. Omni shows it only when local policy also permits declining. |
|
|
3395
3431
|
| `hold` | Primary toggle: Hold | Omni may pause and resume the agent’s interaction in the chat. |
|
|
3432
|
+
| `schedule` | Secondary menu item: Schedule | Omni may put a follow-up for this party on the calendar, from the conversation or from its wrap. See **Scheduling a follow-up**. |
|
|
3396
3433
|
| `outcomes` | Primary button: Complete | Omni may request task completion with a provider outcome and notes. |
|
|
3397
3434
|
|
|
3398
3435
|
### Email capabilities
|
|
@@ -3400,6 +3437,7 @@ customer.
|
|
|
3400
3437
|
| Capability | Omni UI | Contract |
|
|
3401
3438
|
| --- | --- | --- |
|
|
3402
3439
|
| `decline` | Pending-task button: Decline | The provider can decline a pending email offer. Omni shows it only when local policy also permits declining. |
|
|
3440
|
+
| `schedule` | Secondary menu item: Schedule | Omni may put a follow-up for this party on the calendar, from the message or from its wrap. See **Scheduling a follow-up**. |
|
|
3403
3441
|
| `outcomes` | Primary button: Complete | Omni may request task completion with a provider outcome and notes. |
|
|
3404
3442
|
|
|
3405
3443
|
### Custom capabilities
|
|
@@ -3462,7 +3500,7 @@ inside Omni.
|
|
|
3462
3500
|
|
|
3463
3501
|
## Every dial has an outcome
|
|
3464
3502
|
|
|
3465
|
-
|
|
3503
|
+
Four commands place a call: `dial()` from the idle dialpad, the `dial` command from preview, a
|
|
3466
3504
|
`conference` `add`, and `connect-back`. Each is accepted or refused at once, and each then ends later
|
|
3467
3505
|
and apart from its answer -- the destination picks up, is busy, or never does -- and a dial placed
|
|
3468
3506
|
late in a call routinely outlives the call. Nothing in between is reported: the wire says
|
|
@@ -3484,13 +3522,13 @@ expect(result).toEqual({ status: "dialling", dialId: "dial-7f2" });
|
|
|
3484
3522
|
// 3. On the outcome, however late, against the task it belonged to.
|
|
3485
3523
|
const outcome: ProviderEvent<"voice"> = { type: "dial-outcome", dialId: "dial-7f2", assignmentId, destinationId: "tier2", outcome: "no-answer", reason: "No route to destination" };
|
|
3486
3524
|
|
|
3487
|
-
// 4. In the record, so a dial made before a
|
|
3525
|
+
// 4. In the record, so a dial made before a take-over is placeable by whoever holds the task now.
|
|
3488
3526
|
const step: TaskHistoryStep = { step: "unanswered", at: "2026-08-21T09:15:30Z", by: "A-12", dialId: "dial-7f2", destinationId: "tier2" };
|
|
3489
3527
|
```
|
|
3490
3528
|
|
|
3491
3529
|
The identity is a correlation handle between agent application and provider and **is never shown to the
|
|
3492
3530
|
agent**. An agent application holding several tasks places an outcome by the `dialId` it minted; one it did not
|
|
3493
|
-
mint -- a dial made by the previous agent before a
|
|
3531
|
+
mint -- a dial made by the previous agent before a take-over -- it finds on the task's `onCall` or in
|
|
3494
3532
|
its `history`, which is why the steps that dial carry it. The `dialId` is not a retry key:
|
|
3495
3533
|
Omni never repeats a dial, and a command still carries no key for that purpose.
|
|
3496
3534
|
|
|
@@ -3523,7 +3561,7 @@ call it belonged to and never against the next one.
|
|
|
3523
3561
|
The stream knows a dial from three places: `TaskStream.dialled(dialId)`, which an agent application calls when it
|
|
3524
3562
|
places one; an entry on a task's `onCall`; and a step in its `history`. Since a dialled entry
|
|
3525
3563
|
is listed from placement, `dialled()` is load-bearing only for a dial that never has an entry -- a
|
|
3526
|
-
|
|
3564
|
+
connect-back, an idle dialpad call -- or whose entry has already left the room; an
|
|
3527
3565
|
outcome for a dial nobody placed and no task mentions is what `stream.dialOutcome.unknown` refuses.
|
|
3528
3566
|
|
|
3529
3567
|
**`answered` says what happened to the dial; `onCall` says who is in the room.** They are two
|
|
@@ -3541,8 +3579,8 @@ provider does report -- `busy`, `no-answer`, `unreachable`, `rejected` or `cance
|
|
|
3541
3579
|
(`manifest.dialOutcomes.unexplained.alone`): a provider that reports causes may honestly say the
|
|
3542
3580
|
switch gave none, while one that reports none would be sending the generic failure this set exists
|
|
3543
3581
|
to refuse. A provider that dials at all declares it: an idle
|
|
3544
|
-
dialpad requires it (`manifest.dialOutcomes.required`), and a task that declares `connectBack
|
|
3545
|
-
|
|
3582
|
+
dialpad requires it (`manifest.dialOutcomes.required`), and a task that declares `connectBack`
|
|
3583
|
+
or `conference` under a manifest without it is refused
|
|
3546
3584
|
(`task.capability.dialOutcomes.required`). Off voice nothing dials, and the field is a compile
|
|
3547
3585
|
error there.
|
|
3548
3586
|
|
|
@@ -3560,10 +3598,10 @@ const onCall: OnCall[] = [
|
|
|
3560
3598
|
```
|
|
3561
3599
|
|
|
3562
3600
|
`party` is the customer and `agent` a person by user id; neither was dialled from anywhere, so
|
|
3563
|
-
neither carries a destination.
|
|
3601
|
+
neither carries a destination. A `conferenced` colleague was, so the entry carries the
|
|
3564
3602
|
`destinationId` of the directory item and, where an agent application's dial brought them, its `dialId`; a person
|
|
3565
3603
|
the platform added itself carries none. `held: true` is presence as claim. A snapshot establishes state, so an agent application that attaches
|
|
3566
|
-
after a
|
|
3604
|
+
after a take-over reads the room from here rather than inferring it from a sequence of outcomes it
|
|
3567
3605
|
never saw.
|
|
3568
3606
|
|
|
3569
3607
|
**`onCall` describes this agent's current interaction.** When the caller disconnects but the agent
|
|
@@ -3574,7 +3612,7 @@ agents' channels ended. A published room receives a final empty view for this in
|
|
|
3574
3612
|
task may remain open for wrap. Completion is a separate task action, not the caller's disconnect.
|
|
3575
3613
|
|
|
3576
3614
|
**A field that describes the present is cleared by the transition that ends it.** `onCall`,
|
|
3577
|
-
`
|
|
3615
|
+
`previewEndsInSeconds` and `atDeadline` are three instances of one shape, and there will be more: each
|
|
3578
3616
|
describes a state that is true now, and each is carried past the moment it stops being true by the
|
|
3579
3617
|
most natural implementation there is, building the next task by spreading the last one. The
|
|
3580
3618
|
provider's own state looks right, the claim on the wire is stale, and only the agent application sees it. An
|
|
@@ -3585,11 +3623,10 @@ describing the room live was never followed by one saying it had emptied, and ev
|
|
|
3585
3623
|
derived anything from it was wrong in a way that looked like its own bug.
|
|
3586
3624
|
|
|
3587
3625
|
**A dialled entry is listed from the moment the dial is placed, not from the answer.** The entry is
|
|
3588
|
-
what
|
|
3589
|
-
|
|
3590
|
-
`
|
|
3591
|
-
|
|
3592
|
-
still-ringing `destinationId` calls that dial off, with `cancelled` as its outcome.
|
|
3626
|
+
what `conference` `remove` names, and a destination the agent cannot call off while it rings is a
|
|
3627
|
+
customer kept waiting with no way back -- which is the very case `cancelled` exists for. The
|
|
3628
|
+
`conferenced` entry appears with its `dialId` when the dial is placed, and a `conference` `remove`
|
|
3629
|
+
naming a still-ringing `destinationId` calls that dial off, with `cancelled` as its outcome.
|
|
3593
3630
|
|
|
3594
3631
|
**The entry says where it stands.** A dialled entry carries `stage`: `ringing` until its dial is
|
|
3595
3632
|
answered, `joined` after. The `dial-outcome` is the transition and the stage is the state, the same
|
|
@@ -3602,9 +3639,8 @@ whose move the stream cannot hold to an outcome: a clean stream proves nothing a
|
|
|
3602
3639
|
than left to whoever placed the dial because that knowledge lives in one process: after a reload,
|
|
3603
3640
|
on a second session, or on a supervisor's screen the snapshot is the only source, and a room that
|
|
3604
3641
|
cannot tell ringing from joined says it contains someone who may never arrive. Nobody ringing is
|
|
3605
|
-
held (`task.onCall.held.ringing`). A
|
|
3606
|
-
|
|
3607
|
-
transfer** states, so the customer is never left parked by a conforming provider.
|
|
3642
|
+
held (`task.onCall.held.ringing`). A colleague who never answers is a dial that ended: the
|
|
3643
|
+
provider restates the room without the entry, and the agent is back with the customer.
|
|
3608
3644
|
|
|
3609
3645
|
The desk shows a dial as placed on `dialling`, never as reached; shows the person in the room when
|
|
3610
3646
|
they appear on `onCall`; and shows the outcome, with the switch's reason, against the call it
|
|
@@ -3744,6 +3780,14 @@ entry into `on-break`, excluding any `starting-after-task` wait. Omission means
|
|
|
3744
3780
|
duration was supplied. Neither duration expiry nor an overdue indication authorizes the agent application
|
|
3745
3781
|
or provider to end the break, restore availability, or route work to the agent.
|
|
3746
3782
|
|
|
3783
|
+
**A forced break is the provider's act.** A lead's `force-break` is a command to the provider,
|
|
3784
|
+
which forces the break on the member; `by` names the lead who asked it to. A break the platform
|
|
3785
|
+
imposed on its own -- a schedule, a compliance hold -- says `by: "provider"`, and is the platform's
|
|
3786
|
+
to lift: a lead's `end-forced-break` on it is refused before it is sent
|
|
3787
|
+
(`team.command.endForcedBreak.provider`), and a provider that receives one answers `failed` with
|
|
3788
|
+
`omni.break-forced-by-provider`. The desk offers End forced break on a member only where
|
|
3789
|
+
`forced.by` names a lead.
|
|
3790
|
+
|
|
3747
3791
|
**A lead lifting the restriction does not resume the agent.** On an applied
|
|
3748
3792
|
`end-forced-break`, the provider clears `BreakState.forced` while preserving the current
|
|
3749
3793
|
`on-break` or `starting-after-task` status and `activeReasonId`. This command cannot publish
|
|
@@ -3762,9 +3806,25 @@ An agent application may display the expected duration. A countdown requires an
|
|
|
3762
3806
|
a received snapshot, replay or reconnect is not a new start and cannot restart the duration.
|
|
3763
3807
|
Without that evidence, show the duration without inventing a start or return time.
|
|
3764
3808
|
|
|
3765
|
-
A break applies to the **agent**, not to one provider. When a provider forces one, Omni
|
|
3766
|
-
|
|
3767
|
-
is
|
|
3809
|
+
A break applies to the **agent**, not to one provider. When a provider forces one, Omni stops the
|
|
3810
|
+
agent everywhere else at once, and not by asking: it states `setCapacity({ count: 0 })` on every
|
|
3811
|
+
other usable provider holding capacity, which is taken and never refused, and which their team
|
|
3812
|
+
lists show as `elsewhere`. Nobody on those providers can deny it, and the agent cannot cancel it,
|
|
3813
|
+
because there is nothing to cancel: a break request on them would be the agent's own to withdraw
|
|
3814
|
+
and a lead's to refuse, and a forced break is neither. When the forcing provider ends the forced
|
|
3815
|
+
break and the agent resumes there, the agent application restates its capacity on the rest. A
|
|
3816
|
+
provider whose login is not usable is left alone, as a break attempt leaves it;
|
|
3817
|
+
`assertForcedBreakStopsTheRest` holds an agent application to this set. **The provider keeps its
|
|
3818
|
+
shift totals true.** A member held elsewhere is one the provider sees -- `elsewhere` is a state it
|
|
3819
|
+
publishes -- and the day's `shift` is its own account of the day, so the time the agent was
|
|
3820
|
+
stopped for a break forced on another provider is the provider's to count, as a break of a kind
|
|
3821
|
+
its platform has, or introduces, for exactly this. A `breakSeconds` that leaves it out is a total
|
|
3822
|
+
the provider knew to be short, and **Never report a value you cannot observe** cuts the other way
|
|
3823
|
+
here: the provider observed it.
|
|
3824
|
+
|
|
3825
|
+
`ForcedBreak.by` says who forced it: a lead, by user id, or `provider` where the platform itself
|
|
3826
|
+
did -- on its own rule, a schedule, a compliance hold. The agent sees who, and `getUserDetails()`
|
|
3827
|
+
is owed for a person, never for `provider` (`break.forced.by`).
|
|
3768
3828
|
|
|
3769
3829
|
## Capacity and break actions
|
|
3770
3830
|
|
|
@@ -4132,15 +4192,16 @@ with the authority the flag carries. Nothing the lead does here is a task on the
|
|
|
4132
4192
|
who also takes calls is an agent like any other on the agent side, and a lead who does not want
|
|
4133
4193
|
the agent role for now places themself on a break of a working kind and keeps the team feature on.
|
|
4134
4194
|
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4195
|
+
The team reaches the lead whole once, on `team-updated` after the lead switches the feature on
|
|
4196
|
+
and on every snapshot while it is on, and from then on one member at a time: when a member is
|
|
4197
|
+
offered a call, answers, holds, mutes or is taken over, the provider publishes that member whole
|
|
4198
|
+
on `team-member-updated`, so the lead's screen moves when the member's does and a team of two
|
|
4199
|
+
hundred is never resent because one of them held. The agent application asks for nothing and
|
|
4200
|
+
computes nothing. See **`team-updated`** and its companions.
|
|
4139
4201
|
|
|
4140
4202
|
| Field | Contract |
|
|
4141
4203
|
| --- | --- |
|
|
4142
4204
|
| `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. |
|
|
4143
|
-
| `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**. |
|
|
4144
4205
|
| `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**. |
|
|
4145
4206
|
|
|
4146
4207
|
| `TeamMember` field | Contract |
|
|
@@ -4151,6 +4212,7 @@ for nothing and computes nothing.
|
|
|
4151
4212
|
| `break` | Present only while the member has an outstanding break request. See **A member waiting for a break**. |
|
|
4152
4213
|
| `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 and audio are what the lead reads; the task's history is carried in the same shape as on the member's desk, as much of it as the provider chooses to send; 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. |
|
|
4153
4214
|
| `listening` | Present while this lead's channel is in this member's call — listening, coaching or joined — naming which of the member's calls by `assignmentId`, the `mode` they are heard in, and since when. Its appearance is what the lead's audio opens on. One member's call at a time (`team.listening.single`). |
|
|
4215
|
+
| `request` | Present while the member is asking a lead to join one of their calls: which assignment, the note they wrote, since when. The call named is one their `tasks` carry (`team.member.request.assignment`). Absent when they are not asking. See **Lead assist**. |
|
|
4154
4216
|
| `shift` | The member's own history for the day, about the person rather than any one call: `signedInAt`, `signedOutAt` once it has happened, today's totals as the provider counts them — `talkSeconds`, `holdSeconds`, `breakSeconds`, `tasksHandled`, each present only when the provider knows it — and the day's sign-in, sign-out and break `events`, oldest first. Omitted where the provider cannot say. |
|
|
4155
4217
|
|
|
4156
4218
|
Each availability value means one thing:
|
|
@@ -4163,11 +4225,16 @@ Each availability value means one thing:
|
|
|
4163
4225
|
| `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**. |
|
|
4164
4226
|
| `signed-out` | Known to this team but not signed in to this provider. |
|
|
4165
4227
|
|
|
4166
|
-
**
|
|
4167
|
-
adapter over a best-effort channel with no ordering and no delivery guarantee, so
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4228
|
+
**Publish the whole team once, then each member whole.** Team presence typically reaches an
|
|
4229
|
+
adapter over a best-effort channel with no ordering and no delivery guarantee, so the adapter
|
|
4230
|
+
reconciles against its own authoritative read before it publishes anything. What it publishes is
|
|
4231
|
+
the whole team on `team-updated` -- after the switch, and on every snapshot -- and after that one
|
|
4232
|
+
member at a time on `team-member-updated`, carrying the member whole: their availability, their
|
|
4233
|
+
tasks, their listening, their shift, their request, as they now stand. Applying the same member
|
|
4234
|
+
twice changes nothing, so a redelivery is harmless, and a member the team never carried is added
|
|
4235
|
+
by it, as a colleague signing in is. `team-member-removed` takes one off. Never a field of a
|
|
4236
|
+
member, never a task of theirs on its own: a hold on a member's desk is that member republished
|
|
4237
|
+
whole on the lead's, and nothing else on the lead's screen moves.
|
|
4171
4238
|
|
|
4172
4239
|
**Omit `since` rather than inventing one.** Omni renders it as a duration, so a timestamp
|
|
4173
4240
|
synthesised from the adapter's own clock at seed time reads as "on task for 0 seconds" for
|
|
@@ -4176,23 +4243,32 @@ knows when the state actually began. It times the current `availability`, so it
|
|
|
4176
4243
|
that value does.
|
|
4177
4244
|
|
|
4178
4245
|
**The login is the permission.** A team member list goes to a login that declares
|
|
4179
|
-
`capabilities.lead`,
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
rendering for an agent who leads nobody (`team.required`, `team.unentitled`). What
|
|
4183
|
-
with it comes with the flag: there is no per-action permission on the login or on
|
|
4246
|
+
`capabilities.lead`, once the team feature is on, and to nobody else. Omni never decides who
|
|
4247
|
+
leads a team: the provider said so at sign-in, and the team member list agrees with it — owed,
|
|
4248
|
+
`members: []` included, once a lead has switched the feature on; absent for everybody else, which
|
|
4249
|
+
is the correct rendering for an agent who leads nobody (`team.required`, `team.unentitled`). What
|
|
4250
|
+
the lead may do with it comes with the flag: there is no per-action permission on the login or on
|
|
4251
|
+
the list.
|
|
4184
4252
|
|
|
4185
4253
|
**The lead switches the feature on and off, and the provider is told.** A lead may work as an
|
|
4186
4254
|
agent alone, as agent and lead, or as lead alone, and moves between them on the fly. The
|
|
4187
4255
|
agent application says which with `{ type: "lead-features", enabled }`: while it is on, the
|
|
4188
4256
|
provider sends team events scoped to the lead's team and the lead may act; while it is off, the
|
|
4189
|
-
lead is an ordinary agent,
|
|
4257
|
+
lead is an ordinary agent, nothing of the team is owed or expected (`team.unexpected`,
|
|
4190
4258
|
`event.team.features`), and no lead act is possible, since the lead learns their members'
|
|
4191
|
-
assignments only from the list.
|
|
4192
|
-
|
|
4193
|
-
**The
|
|
4194
|
-
|
|
4195
|
-
|
|
4259
|
+
assignments only from the list.
|
|
4260
|
+
|
|
4261
|
+
**The provider assumes nothing.** The switch is not remembered across connections: the agent
|
|
4262
|
+
application sends `lead-features` on every connect, after it has stated capacity, and until it has
|
|
4263
|
+
the provider treats the feature as off. The connect snapshot therefore carries no team, whatever
|
|
4264
|
+
the login declares; the whole team arrives on `team-updated` once the switch is on, and every
|
|
4265
|
+
snapshot after that carries it. A conformance run switches the feature on for a lead and holds the
|
|
4266
|
+
provider to that first `team-updated` (`team.required`); a team on the connect snapshot is the
|
|
4267
|
+
provider assuming (`team.unexpected`).
|
|
4268
|
+
|
|
4269
|
+
**The team member list never carries the agent it is published to.** A lead does not report to
|
|
4270
|
+
themself: their own break request and their own ask for a lead go up to whoever leads them and
|
|
4271
|
+
appear on *that* person's team member list, as a member with a `request`, while the requester sees
|
|
4196
4272
|
only their own `BreakState` and their task's `leadAssist` move. An adapter whose platform lists the lead
|
|
4197
4273
|
among their own members filters the signed-in identity out before publishing. **Being a lead is a
|
|
4198
4274
|
role the provider knows, never inferred from who is listed:** it is declared at sign-in, a lead
|
|
@@ -4212,7 +4288,7 @@ assignment from the member alone.
|
|
|
4212
4288
|
| `{ 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`. |
|
|
4213
4289
|
| `{ type: "set-break-policy", policy }` | `approval-required`, `automatically-approved`, or `requests-suspended`. |
|
|
4214
4290
|
| `{ 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. |
|
|
4215
|
-
| `{ type: "end-forced-break", memberId }` |
|
|
4291
|
+
| `{ type: "end-forced-break", memberId }` | Asks the provider to lift a forced-break restriction a lead asked for, by clearing `BreakState.forced`; one the platform imposed is its own to lift (`team.command.endForcedBreak.provider`, `omni.break-forced-by-provider`). The committed break continues; only the agent resumes work. |
|
|
4216
4292
|
| `{ type: "join", memberId, assignmentId? }` | Answers the member's request: the provider bridges the lead's channel into the call. See **Lead assist**. |
|
|
4217
4293
|
| `{ type: "decline", memberId, assignmentId?, reason? }` | Refuses the member's request. |
|
|
4218
4294
|
| `{ type: "listen", memberId, assignmentId? }` | The lead's channel joins the member's call unasked, in silence. See **Listening to a call**. |
|
|
@@ -4248,8 +4324,8 @@ flow, in order:
|
|
|
4248
4324
|
execute({ assignmentId: "alloc-42", command: { type: "lead-assist", action: "request", note: "Refund dispute, needs approval" } })
|
|
4249
4325
|
// task.leadAssist = { stage: "requested", note: "Refund dispute, needs approval", since }
|
|
4250
4326
|
|
|
4251
|
-
// 2. Every lead with the team feature on sees the request on
|
|
4252
|
-
// team-updated:
|
|
4327
|
+
// 2. Every lead with the team feature on sees the request on the member.
|
|
4328
|
+
// team-member-updated: member: { id: "A-1", ..., request: { assignmentId: "alloc-42", note, since } }
|
|
4253
4329
|
|
|
4254
4330
|
// 3. A lead joins, or declines.
|
|
4255
4331
|
executeTeam({ command: { type: "join", memberId: "A-1", assignmentId: "alloc-42" } })
|
|
@@ -4341,13 +4417,13 @@ The flow, end to end:
|
|
|
4341
4417
|
// 2. The lead's channel joins it in silence. Conditions: the lead's voice channel is free --
|
|
4342
4418
|
// not on a call, not listening elsewhere. Capacity is not involved: nothing is assigned.
|
|
4343
4419
|
executeTeam({ command: { type: "listen", memberId: "A-1", assignmentId: "alloc-42" } })
|
|
4344
|
-
// 3. The provider
|
|
4420
|
+
// 3. The provider publishes the member whole on team-member-updated; the member carries
|
|
4345
4421
|
// listening: { assignmentId: "alloc-42", mode: "listen", since }
|
|
4346
4422
|
// -- the application opens openAudio({ assignmentId: "alloc-42" }) on the lead's connection.
|
|
4347
|
-
// 4. The lead changes how they are heard; the
|
|
4423
|
+
// 4. The lead changes how they are heard; the member is published again with the new listening.mode. The audio stays open.
|
|
4348
4424
|
executeTeam({ command: { type: "coach", memberId: "A-1" } })
|
|
4349
4425
|
executeTeam({ command: { type: "join-call", memberId: "A-1" } })
|
|
4350
|
-
// 5. The lead leaves: the provider
|
|
4426
|
+
// 5. The lead leaves: the provider publishes the member without listening; the application closes the audio.
|
|
4351
4427
|
executeTeam({ command: { type: "leave", memberId: "A-1" } })
|
|
4352
4428
|
// 6. The member's call ends: the same as leave, from the provider's side.
|
|
4353
4429
|
// 7. The lead takes the call: the member loses listening no later than the offer, the application
|
|
@@ -4357,8 +4433,8 @@ executeTeam({ command: { type: "leave", memberId: "A-1" } })
|
|
|
4357
4433
|
executeTeam({ command: { type: "take-over-call", memberId: "A-1" } })
|
|
4358
4434
|
// 8. A join on the member's request is the same flow entered at step 3 with mode: "join-call",
|
|
4359
4435
|
// and the member's task shows leadAssist: joined.
|
|
4360
|
-
// 9. A reload of the lead's application: the
|
|
4361
|
-
// so the audio is reopened.
|
|
4436
|
+
// 9. A reload of the lead's application: the switch is sent again, the team-updated that follows
|
|
4437
|
+
// still carries listening on the member, so the audio is reopened.
|
|
4362
4438
|
```
|
|
4363
4439
|
|
|
4364
4440
|
**`listening` on the member is the state**, restated on every change of mode, naming the call so
|
|
@@ -4423,7 +4499,7 @@ mode stays with the login rather than being inferred from a snapshot or a device
|
|
|
4423
4499
|
|
|
4424
4500
|
Nor does the audio ever stand in for the task: a task's presence and
|
|
4425
4501
|
phase follow the provider's reports about the work, and the audio — attaching, moving through a
|
|
4426
|
-
hold, a
|
|
4502
|
+
hold, a conference, a lead joining or a take-over, and ending — is transient beside it. See **A task is
|
|
4427
4503
|
never its audio** under **Task assignment lifecycle**.
|
|
4428
4504
|
|
|
4429
4505
|
### The agent application reports, the adapter decides
|
|
@@ -4627,7 +4703,7 @@ capability's `notes` setting. A task publishing no codes still receives `complet
|
|
|
4627
4703
|
### Where a command executes
|
|
4628
4704
|
|
|
4629
4705
|
Every command reaches the provider through `execute`, with no branch at the call site, and every
|
|
4630
|
-
command asks the provider to **perform** something: `hold`, `
|
|
4706
|
+
command asks the provider to **perform** something: `hold`, `conference`, `schedule`, `end-call`,
|
|
4631
4707
|
Provider-targeted `recording` commands and the rest act on the platform's own call leg, its bridge, or its record of the
|
|
4632
4708
|
task. Nothing has happened until the provider applies them, and `failed` means nothing happened.
|
|
4633
4709
|
|
|
@@ -4697,7 +4773,7 @@ as by a click. What a call-control headset sends, and what becomes of it:
|
|
|
4697
4773
|
| --- | --- | --- | --- |
|
|
4698
4774
|
| Mute button | The headset, on the HID Telephony usage page: a Phone Mute report on the press, a Mute LED the agent application writes back | A press of the agent application's Mute, performed the agent application's way; the LED follows the agent application's state, so button, light and control are one state with one owner | The `muted` leg, `mutedBy: "host"` |
|
|
4699
4775
|
| Hook switch | The headset, HID Telephony | A press of the agent application's Answer or End call, only where the task is in a phase that has one; the off-hook and ring lights follow the task | The existing `answer` and `end-call` commands |
|
|
4700
|
-
| Flash, redial, speed dial | Older headsets and desk-phone style devices | Flash is Hold and Resume where the task offers `hold`; redial and speed dial map to nothing on a desk
|
|
4776
|
+
| Flash, redial, speed dial | Older headsets and desk-phone style devices | Flash is Hold and Resume where the task offers `hold`; redial and speed dial map to nothing on a desk that never redials, and are ignored | Nothing new |
|
|
4701
4777
|
| Volume up and down | The headset's own amplifier, or the operating system through the consumer-control keys | Nothing: the device and the system handle it | Nothing, except that a speaker at nought is `audio.output.flowing: false` where the agent application can know it |
|
|
4702
4778
|
| Microphone gain and level | The operating system, the headset's own boost | A level meter, so the agent can see they are heard | Nothing: a level is a flicker, not a state |
|
|
4703
4779
|
| Hardware or operating-system mute | A slider on the headset, the system's input mute | Observes it, publishes `flowing: false, mutedBy: "station"`, records the leg during a call, and tells the agent which it is where it knows -- headset or system -- and what to do; an agent application whose mute is `station` clears a system mute itself, a browser can only say so | The report and the leg |
|
|
@@ -4727,18 +4803,17 @@ declared:
|
|
|
4727
4803
|
| `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`. |
|
|
4728
4804
|
| `decline` | The `decline` capability on any channel, **and** Omni local policy permitting it. One word for refusing an offer, whatever the channel. |
|
|
4729
4805
|
| `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`. |
|
|
4730
|
-
| `complete` | `
|
|
4806
|
+
| `complete` | A wrap to cut short: any task but a `provider-automatic` one with `wrapAllowance: 0`, under either completion mode (`command.complete.wrapAllowance`). 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`). |
|
|
4731
4807
|
| `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. |
|
|
4732
|
-
| `
|
|
4733
|
-
| `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`. |
|
|
4808
|
+
| `schedule` | The `schedule` capability, on any channel, in `in-progress`, `paused` or `completing`: a follow-up is promised on the call and written up in wrap alike. The manifest declares `calendar` for it to land on. |
|
|
4734
4809
|
| `lead-assist` with `action: "request"` or `"cancel"` | The `leadAssist` capability. `cancel` needs a request standing -- `Task.leadAssist` with status `requested`. |
|
|
4735
|
-
| `
|
|
4810
|
+
| `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`). |
|
|
4736
4811
|
| `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`). |
|
|
4737
4812
|
| Everything else | Its own named capability. |
|
|
4738
4813
|
|
|
4739
4814
|
**A control on the contact belongs to the interaction phases**, `in-progress` and `paused`:
|
|
4740
|
-
`hold`, `resume` and `pause`, `end-call` and `terminate-call`, every `
|
|
4741
|
-
every `lead-assist` action
|
|
4815
|
+
`hold`, `resume` and `pause`, `end-call` and `terminate-call`, every `conference` action, and
|
|
4816
|
+
every `lead-assist` action; `schedule` alone reaches into `completing`. These controls act within this agent's current interaction.
|
|
4742
4817
|
In `completing`, that interaction has ended and its wrap work remains. The caller may still be in
|
|
4743
4818
|
an IVR, queue or another agent's interaction, and other channels may remain connected. Completion
|
|
4744
4819
|
of this interaction does not establish that the caller or bridge ended. The capability stays
|
|
@@ -4842,7 +4917,7 @@ The current exceptions and their reasons are:
|
|
|
4842
4917
|
|
|
4843
4918
|
| Case | Exception and reason |
|
|
4844
4919
|
| --- | --- |
|
|
4845
|
-
| Agent application display of provider deadlines (`
|
|
4920
|
+
| Agent application display of provider deadlines (`expiresInSeconds`, `previewEndsInSeconds`, wrap deadline) | Each is seconds from the publication that carried it, counted down on the desk from receipt: the provider's own arithmetic, so no clock is compared and the transport delay is the only error. The countdown estimates the display only; it does not establish that the provider acted. Without usable time, show timing uncertainty. |
|
|
4846
4921
|
| Agent application-triggered preview deadline | A bounded clock estimate with monotonic aging may schedule the agent application's Call command because the provider owns the deadline but the agent application owns the trigger. Do not trigger before the deadline is known to have passed; invalidate on clock discontinuity and reconcile when uncertain. |
|
|
4847
4922
|
| Recording evidence expiry | A bounded observer-domain clock estimate with monotonic aging may assess freshness because observation and expiry belong to the recorder's clock. If time cannot be trusted, recording state is unknown; never renew evidence from receipt or replay. |
|
|
4848
4923
|
| Unknown recording state | `observedAt` and `validUntil` are absent because there is no confirmed observation. The containing provider event still has its own `occurredAt`; that publication is not a recorder observation. |
|
|
@@ -5087,9 +5162,15 @@ multi-provider break**.
|
|
|
5087
5162
|
Offers a task to Omni without a separate offer acknowledgement. An offer does not accept
|
|
5088
5163
|
the task: when its phase is `pending`, Omni applies `autoAcceptTasks` and the task's
|
|
5089
5164
|
`acceptance`. `task-offered` must not introduce a task as `in-progress`; only a reconnect or
|
|
5090
|
-
resync snapshot may report work already in progress. The provider
|
|
5165
|
+
resync snapshot may report work already in progress. The provider includes the task in later
|
|
5091
5166
|
snapshots until it ends.
|
|
5092
5167
|
|
|
5168
|
+
**Every offer is owed an ending.** An assignment the provider introduced is an assignment it ends,
|
|
5169
|
+
with `task-ended` and an outcome, whatever became of the call: answered and completed, declined,
|
|
5170
|
+
withdrawn, abandoned in the ring, lapsed, taken over. An offer that is simply never mentioned
|
|
5171
|
+
again leaves the desk holding a task nobody will close, and the conformance drive names it
|
|
5172
|
+
(`stream.taskOffered.unended`).
|
|
5173
|
+
|
|
5093
5174
|
### `task-updated`
|
|
5094
5175
|
|
|
5095
5176
|
Replaces the current representation of one provider-local task. It is a full task value, not a
|
|
@@ -5133,10 +5214,10 @@ Every outcome ends the task for this agent. On `task-ended`, Omni:
|
|
|
5133
5214
|
A `taken-over` outcome arrives at the member's own completion, after the wrap that follows a
|
|
5134
5215
|
take-over as it follows an `end-call` -- see **Lead assist**.
|
|
5135
5216
|
|
|
5136
|
-
A successful `complete`
|
|
5217
|
+
A successful `complete` command does not clear the task. Omni waits for `task-ended`,
|
|
5137
5218
|
and not for ever: `applied` to a `complete` says the
|
|
5138
5219
|
provider has completed the task, and its `task-ended` follows within the
|
|
5139
|
-
manifest's `
|
|
5220
|
+
manifest's `settleMs`. A provider never answers `applied` for a completion it has not yet
|
|
5140
5221
|
performed. Past the bound the agent application calls `snapshot()`: a snapshot still carrying the task is a task
|
|
5141
5222
|
held open by a provider that said it was done, and the desk shows it as unsettled -- "Completing...
|
|
5142
5223
|
the provider has not confirmed" -- naming the command; a snapshot no longer carrying it clears the
|
|
@@ -5199,10 +5280,31 @@ each connected provider.
|
|
|
5199
5280
|
|
|
5200
5281
|
### `team-updated`
|
|
5201
5282
|
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5283
|
+
The whole `TeamMembers`, replacing what the lead had. It is emitted once, after the lead switches
|
|
5284
|
+
the team feature on, and to nobody the provider does not publish a team to; every snapshot while
|
|
5285
|
+
the feature is on carries the same whole team. The changes that follow arrive one member at a
|
|
5286
|
+
time, below; the whole team is never resent for one of them. Nothing of the team goes to a login
|
|
5287
|
+
that no longer declares `capabilities.lead` on a republished `authenticated`, or to a lead who
|
|
5288
|
+
turned the feature off (`event.team.features`).
|
|
5289
|
+
|
|
5290
|
+
### `team-member-updated`
|
|
5291
|
+
|
|
5292
|
+
One member, whole, as they now stand: their availability, their tasks, their listening, their
|
|
5293
|
+
shift, their request. It replaces the member of that `id` on the lead's list, or adds them where
|
|
5294
|
+
the list did not carry them, so applying it twice changes nothing. It comes only after the whole
|
|
5295
|
+
team has (`stream.team.baseline`), and it is what a member's hold, answer, mute or take-over
|
|
5296
|
+
publishes to the lead.
|
|
5297
|
+
|
|
5298
|
+
### `team-member-removed`
|
|
5299
|
+
|
|
5300
|
+
One member taken off the lead's list, by `memberId`: somebody who left the team, not somebody who
|
|
5301
|
+
signed out, which is an availability the member is published with. It names a member the list
|
|
5302
|
+
carried (`stream.teamMember.unknown`).
|
|
5303
|
+
|
|
5304
|
+
### `team-policies-updated`
|
|
5305
|
+
|
|
5306
|
+
The team's policies, whole, replacing what the lead had -- as `TeamMembers.policies` carried them.
|
|
5307
|
+
Emitted where the provider offers policy control, after the whole team has (`stream.team.baseline`).
|
|
5206
5308
|
|
|
5207
5309
|
### `contacts-updated`
|
|
5208
5310
|
|
|
@@ -5263,7 +5365,7 @@ same exported checks are used by Omni and adapter tests so their interpretations
|
|
|
5263
5365
|
| `validateLoginStore(store)` | The login's store the agent application hands every connection: an object with `get`, `set` and `delete` (`store.shape`, `store.get`, `.set`, `.delete`). The harness holds `ConnectContext.store` to it. |
|
|
5264
5366
|
| `validateCapacity(capacity)` | What the agent application states as capacity: a whole number of zero or more (`capacity.count`), zero being agent application-stopped. The harness states one on connect and two, one and zero after the drive, each answered `applied`, and holds any offer to the count in force (`stream.taskOffered.overCapacity`). |
|
|
5265
5367
|
| `validateAuthenticationResult(result, method)` | What `start()` or `complete()` answered: a challenge or a rejection, a login or a rejection. A rejection's failure is held to its rules -- an `omni.` code the contract lists, and `omni.phone-not-permitted` never retryable, since the agent's station is configuration. `validateAuthenticationFailure(failure)` is the same check on a failure alone. |
|
|
5266
|
-
| `validateTaskCommand(command, task?)` | What a command needs to be issuable, against the task it names: its own shape -- a dial's `dialId`, a
|
|
5368
|
+
| `validateTaskCommand(command, task?)` | What a command needs to be issuable, against the task it names: its own shape -- a dial's `dialId`, a conference's item, a schedule's time, a remove naming exactly one person -- and, with the task, the capability the table above gates it on (`command.capability.<name>`, `.locked`), the phase it belongs to (`command.phase.*`, `command.phase.interaction` for every control on the call or the conversation), and the state that has to stand: a lead requested, somebody else still on the call (`command.conference.remove.alone`). An agent application validates before sending and an adapter before acting. |
|
|
5267
5369
|
| `validateResult(result, method)` | What a connection method answered: the status it gives, a failure where the status says so and nowhere else, the failure's shape, and that an `omni.` code is one this contract names. |
|
|
5268
5370
|
| `validateAuthenticationState(state)` | The identity each state must carry, the capabilities a usable login declares, and the expiry that only `authenticated` may. Omni applies it to every state a session publishes — the republished as much as the first. |
|
|
5269
5371
|
|
|
@@ -5274,12 +5376,11 @@ found at such as `snapshot.tasks[0].browsers[1].url`, and a `message`.
|
|
|
5274
5376
|
Some rules need to know who is reading. `validateTeamMembers`, `validateSnapshot`, and
|
|
5275
5377
|
`validateEventEnvelope` take an optional final `{ self, capabilities }` — the signed-in agent's
|
|
5276
5378
|
`AuthenticationState.identity.id` and their login's `capabilities`. Given `self`, a team member list that
|
|
5277
|
-
carries that agent reports `team.member.self`
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
`
|
|
5282
|
-
`exerciseAdapter` always passes both.
|
|
5379
|
+
carries that agent reports `team.member.self`, on a `team-member-updated` and a `team-member-removed` as on the list. Given `capabilities`, a team member list published to a login that does not lead
|
|
5380
|
+
reports `team.unentitled`, and a team event to such a login reports `event.team.capability`; given
|
|
5381
|
+
`leadFeatures: true`, a snapshot without a team reports `team.required`, and given `leadFeatures: false`,
|
|
5382
|
+
anything of the team reaching the lead reports `team.unexpected` or `event.team.features`. Without them those rules are not checked, because they cannot be.
|
|
5383
|
+
`exerciseAdapter` passes all three, holding the switch off until it has sent it.
|
|
5283
5384
|
|
|
5284
5385
|
`assertNoViolations(violations)` throws `ProtocolConformanceError` — which carries the full
|
|
5285
5386
|
`violations` array — when the list is non-empty.
|
|
@@ -5340,7 +5441,7 @@ A capability granted by a later login requires its methods just as one declared
|
|
|
5340
5441
|
|
|
5341
5442
|
`result.notExercised` lists what the run never reached — one subject per family of rules: each
|
|
5342
5443
|
optional part of a task (`task.browsers`, `task.history`, `task.leadAssist`, …), the break's
|
|
5343
|
-
`reasons` and `forced`, the team member list's `members` and `
|
|
5444
|
+
`reasons` and `forced`, the team member list's `members` and a member's `request`, each declared contribution, and
|
|
5344
5445
|
each event type (`event.task-ended`, …) — and so what a clean `violations` says nothing about.
|
|
5345
5446
|
Nothing there is a violation: an adapter with no team has nothing to exercise. But a fixture with
|
|
5346
5447
|
no tasks exercises no task rule, and a pass over it reads as coverage it is not.
|
|
@@ -5461,6 +5562,7 @@ cannot be established from TypeScript structure alone.
|
|
|
5461
5562
|
| `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. |
|
|
5462
5563
|
| `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. |
|
|
5463
5564
|
| `assertBreakAttemptProviders(candidates, asked)` | A break attempt asks every usable provider holding capacity, `refreshing` included, and nothing of a provider whose login is `expired`. |
|
|
5565
|
+
| `assertForcedBreakStopsTheRest(forcedOn, candidates, stopped)` | A forced break on one provider stops the agent everywhere else: capacity zero stated on every other usable provider holding capacity, nothing on the forcing one, nothing on a dead login. See **Forced breaks**. |
|
|
5464
5566
|
| `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. |
|
|
5465
5567
|
| `assertDeniedAndRetriedBreak(states)` | A denial transitions directly to `not-requested`; a later request can still be granted. |
|
|
5466
5568
|
| `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. |
|
|
@@ -5516,18 +5618,22 @@ effect. A recorder that cannot confirm completion must not offer Cancel; it can
|
|
|
5516
5618
|
Cancel never means cancelling an in-flight start request or deleting arbitrary past recordings.
|
|
5517
5619
|
Stop can be applied only after finalization/retention succeeds; Cancel only after both cessation
|
|
5518
5620
|
and completion of this recording's audio succeed under the recorder's storage contract.
|
|
5519
|
-
Partial success
|
|
5520
|
-
|
|
5521
|
-
|
|
5621
|
+
Partial success -- capture stopped but the storage outcome unknown, a start the recorder cannot
|
|
5622
|
+
vouch for either way -- is a settled `failed`, under the code `omni.recording-unsettled`: the
|
|
5623
|
+
command did not do what it promised, and the state the provider publishes with it is whatever
|
|
5624
|
+
capture state is actually known. It is never `applied`, and never an unsettled promise the agent
|
|
5625
|
+
application would be left to resync, since the provider knows exactly what it could not settle.
|
|
5626
|
+
There is no request identity on a recording command: the outcome is the state, and what the
|
|
5627
|
+
agent sees is the state. Retention is not a promise of sample-perfect audio.
|
|
5522
5628
|
|
|
5523
5629
|
Provider commands go exclusively to `Connection.execute`; agent application commands go exclusively to
|
|
5524
|
-
`HostRecording.execute`. Both include the assignment
|
|
5630
|
+
`HostRecording.execute`. Both include the assignment and observation identities; all
|
|
5525
5631
|
non-start commands identify the particular recording. IDs are opaque and scoped by provider login,
|
|
5526
5632
|
task and recorder owner. They are never inferred from filenames or current agent identity. A
|
|
5527
5633
|
recording ID survives pause/resume and reassignment only where the same recorder confirms continuity;
|
|
5528
5634
|
commands always name the current assignment. A later start gets a different ID. Reconnect does not
|
|
5529
5635
|
create a new recording or fresh evidence. After lost continuity, use unknown until reconciled.
|
|
5530
|
-
There is no automatic restart,
|
|
5636
|
+
There is no automatic restart, hand-over to another recorder, or stop on task hold/disconnect.
|
|
5531
5637
|
Task removal does not prove recording stopped: outstanding agent application recorders remain tracked by the
|
|
5532
5638
|
agent application until its executor reconciles/finishes them, with visible unresolved cleanup failures.
|
|
5533
5639
|
|