@xema/omni-protocol 0.1.84 → 0.1.86
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/README.md +3 -3
- package/dist/index.d.ts +15 -15
- package/dist/testing.d.ts +4 -4
- package/dist/testing.js +13 -13
- package/dist/validation.d.ts +5 -5
- package/dist/validation.js +68 -68
- package/guide.md +114 -87
- package/package.json +1 -1
package/guide.md
CHANGED
|
@@ -23,7 +23,7 @@ are used precisely throughout and mean nothing looser here.
|
|
|
23
23
|
| **Provider** | One independently connected external system: a voice platform, a chat platform, a mail platform. |
|
|
24
24
|
| **Adapter** | The package implementing this contract for one provider. One adapter is one provider, so the words are often interchangeable; *provider* names the system, *adapter* the code speaking for it. |
|
|
25
25
|
| **Agent** | The person signed in and taking work. On `onCall`, `agent` is a person on the call by user id; transfer destinations may include an agent when the provider publishes that directory item. |
|
|
26
|
-
| **Lead** | An agent whose login declares `capabilities.team`. The provider publishes a `
|
|
26
|
+
| **Lead** | An agent whose login declares `capabilities.team`. The provider publishes a `TeamMembers` object to them and to nobody else: **the login is the permission**. |
|
|
27
27
|
| **Provisioning** | Omni-side policy about this agent, configured outside the 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 provisioning disagree, the stricter wins. |
|
|
28
28
|
| **Call** | The caller’s complete phone call, which may continue through IVRs, queues and several agents. |
|
|
29
29
|
| **Interaction** | One agent’s part in the call or conversation. The same call may return to that agent for a new interaction. |
|
|
@@ -38,9 +38,9 @@ are used precisely throughout and mean nothing looser here.
|
|
|
38
38
|
| **Snapshot** | The provider's complete state at one moment. It replaces what Omni holds; it is never a patch. |
|
|
39
39
|
| **Event** | One completed transaction reported after a snapshot established the baseline. |
|
|
40
40
|
| **Break** | A reported, supervised state in which the agent is not working — one with a reason, a decision behind it and a return. It covers what a platform may call *not-ready*, including equipment trouble. An agent who is merely at capacity is not on a break. |
|
|
41
|
-
| **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,
|
|
41
|
+
| **Workspace** | What Omni shows the agent. The **task workspace** holds the selected task, its controls and its browsers; the **idle workspace** holds what a provider contributes when no task is selected — dialpad, contacts, calendar, team member list. |
|
|
42
42
|
| **Dial** | One outbound call the host asks a provider to place — from the idle dialpad, a cold or warm transfer, a conference add, or a connect-back. Identified by the host's `dialId`, accepted as `dialling`, and ended by exactly one `dial-outcome`. See **Every dial has an outcome**. |
|
|
43
|
-
| **Monitor** | A lead listening to a member's call unasked, from the
|
|
43
|
+
| **Monitor** | A lead listening to a member's call unasked, from the team member list: `monitor` in silence, `whisper` heard by the agent alone, `barge` heard by everyone. Nothing of it reaches the member's task, and there is no take-over in it. See **Monitoring a call**. |
|
|
44
44
|
| **On the call** | Who a voice task's audio joins, or is bringing in, as the provider states it on `Task.onCall`: the party, the agents, and anyone consulted or conferenced in from the moment their dial is placed. |
|
|
45
45
|
|
|
46
46
|
Six words describe *what state a thing is in*, and they are not interchangeable: each belongs to
|
|
@@ -54,7 +54,7 @@ transport, a task, a break and a call each have a word of their own:
|
|
|
54
54
|
| `transport` | A connection | `connecting`, `active`, `error` |
|
|
55
55
|
| `status` | An authentication session | `signed-out`, `authenticating`, `authenticated`, `refreshing`, `expired` |
|
|
56
56
|
| `approval` | A break request | `not-requested`, `awaiting-decision`, `granted`, `starting-after-task`, `in-effect` |
|
|
57
|
-
| `availability` | A
|
|
57
|
+
| `availability` | A team member | `ready`, `on-task`, `on-break`, `signed-out` |
|
|
58
58
|
|
|
59
59
|
## Versioning
|
|
60
60
|
|
|
@@ -120,7 +120,7 @@ identifier of its own for an agent or a manager, and none crosses this boundary
|
|
|
120
120
|
operating-system account, not a directory identity, not a licence.
|
|
121
121
|
|
|
122
122
|
So a `UserId` means nothing outside the provider that issued it. Provider A's
|
|
123
|
-
`interactionHistory[].by` and provider B's
|
|
123
|
+
`interactionHistory[].by` and provider B's team member list `memberId` are unrelated strings that will
|
|
124
124
|
eventually collide, and one person on several providers has several identities that nothing here
|
|
125
125
|
pairs. Scope every user identifier with its provider ID before storing or comparing it, exactly as
|
|
126
126
|
`taskKey()` already does for tasks — see `userKey()` under **Utilities**.
|
|
@@ -485,7 +485,7 @@ type Snapshot = {
|
|
|
485
485
|
taskCount: number;
|
|
486
486
|
contacts?: Contact[];
|
|
487
487
|
scheduledActivities?: ScheduledActivity[];
|
|
488
|
-
team?:
|
|
488
|
+
team?: TeamMembers;
|
|
489
489
|
};
|
|
490
490
|
|
|
491
491
|
type AgentCapacity = {
|
|
@@ -527,12 +527,12 @@ type DialResult =
|
|
|
527
527
|
### Task capabilities
|
|
528
528
|
|
|
529
529
|
```ts
|
|
530
|
-
type
|
|
530
|
+
type OutcomeCode = { id: string; label: string; group?: string };
|
|
531
531
|
|
|
532
|
-
type
|
|
532
|
+
type OutcomeRules = {
|
|
533
533
|
required?: boolean;
|
|
534
534
|
notes?: "required" | "optional" | "none";
|
|
535
|
-
codes?:
|
|
535
|
+
codes?: OutcomeCode[];
|
|
536
536
|
};
|
|
537
537
|
|
|
538
538
|
type Destination = {
|
|
@@ -557,7 +557,7 @@ type CustomCapability = {
|
|
|
557
557
|
|
|
558
558
|
type SharedTaskCapabilities = {
|
|
559
559
|
browsers?: true;
|
|
560
|
-
|
|
560
|
+
outcomes?: true | OutcomeRules;
|
|
561
561
|
custom?: CustomCapability[];
|
|
562
562
|
};
|
|
563
563
|
|
|
@@ -793,7 +793,7 @@ const TASK_COMMAND_NAMES = {
|
|
|
793
793
|
type TaskCommandName<C extends keyof typeof TASK_COMMAND_NAMES> =
|
|
794
794
|
(typeof TASK_COMMAND_NAMES)[C][number];
|
|
795
795
|
|
|
796
|
-
type
|
|
796
|
+
type OutcomePayload = { outcome?: string; notes?: string };
|
|
797
797
|
|
|
798
798
|
type VoiceTaskCommand =
|
|
799
799
|
| { type: "answer" }
|
|
@@ -815,19 +815,19 @@ type VoiceTaskCommand =
|
|
|
815
815
|
| { type: "conference"; action: "remove"; destinationId: string; party?: never }
|
|
816
816
|
| { type: "conference"; action: "remove"; party: true; destinationId?: never }
|
|
817
817
|
| (RecordingCommand & { source: "provider" })
|
|
818
|
-
| ({ type: "complete" } &
|
|
818
|
+
| ({ type: "complete" } & OutcomePayload);
|
|
819
819
|
|
|
820
820
|
type ChatTaskCommand =
|
|
821
821
|
| { type: "accept" }
|
|
822
822
|
| { type: "decline" }
|
|
823
823
|
| { type: "pause" }
|
|
824
824
|
| { type: "resume" }
|
|
825
|
-
| ({ type: "complete" } &
|
|
825
|
+
| ({ type: "complete" } & OutcomePayload);
|
|
826
826
|
|
|
827
827
|
type EmailTaskCommand =
|
|
828
828
|
| { type: "accept" }
|
|
829
829
|
| { type: "decline" }
|
|
830
|
-
| ({ type: "complete" } &
|
|
830
|
+
| ({ type: "complete" } & OutcomePayload);
|
|
831
831
|
|
|
832
832
|
type CustomTaskCommand = { type: "custom"; name: string; [key: string]: unknown };
|
|
833
833
|
|
|
@@ -898,14 +898,14 @@ The adapter is the one that knows the value, so it is the one held to it: given
|
|
|
898
898
|
queue locked, the validator refuses any other field carrying one, digits compared as digits so no
|
|
899
899
|
formatting hides them (`task.locked.leak`), and a conformance run whose tasks lock a party's number
|
|
900
900
|
or email states those values in `lockedValues`, since a run that cannot ask the question is not a
|
|
901
|
-
pass. A host never has the value and never asks. A name is not locked. What the queue provides rather than permits — browsers,
|
|
901
|
+
pass. A host never has the value and never asks. A name is not locked. What the queue provides rather than permits — browsers, outcomes, custom
|
|
902
902
|
controls — is content, and is never locked.
|
|
903
903
|
|
|
904
|
-
**A lead sets the team's policy from their
|
|
904
|
+
**A lead sets the team's policy from their team member list.** A login that declares
|
|
905
905
|
`capabilities.team.policyControl` may `executeTeamPolicy({ type: "set", capability, setting })`
|
|
906
906
|
with `on`, `off`, or `person`, for any task control, `dial`, or a skill — and only `hold`
|
|
907
907
|
and skills may be `person`; connect back and new call are the team's, on or off, within what the queue
|
|
908
|
-
allows. The
|
|
908
|
+
allows. The team member list carries `policies` for such a login: every policy as it stands, who set it, and
|
|
909
909
|
`lockedBy` where a level above the team made it theirs to keep, which the lead sees and cannot
|
|
910
910
|
change — `executeTeamPolicy` on it answers `failed` with `omni.capability-not-enabled`.
|
|
911
911
|
|
|
@@ -1002,7 +1002,7 @@ type LeadRequest = {
|
|
|
1002
1002
|
since: IsoTimestamp;
|
|
1003
1003
|
};
|
|
1004
1004
|
|
|
1005
|
-
type
|
|
1005
|
+
type TeamMembers = {
|
|
1006
1006
|
members: TeamMember[];
|
|
1007
1007
|
requests?: LeadRequest[];
|
|
1008
1008
|
policies?: TeamPolicies;
|
|
@@ -1115,7 +1115,7 @@ type ProviderEvent =
|
|
|
1115
1115
|
| { type: "announcement"; text: string; html?: string; announcedAt: IsoTimestamp; expiresAt?: IsoTimestamp }
|
|
1116
1116
|
| { type: "queue-summary"; summary: QueueSummary }
|
|
1117
1117
|
| { type: "diagnostic"; expected: string; observed: string; taskId?: TaskId; allocationId?: AllocationId }
|
|
1118
|
-
| { type: "team-updated"; team:
|
|
1118
|
+
| { type: "team-updated"; team: TeamMembers }
|
|
1119
1119
|
| { type: "contacts-updated"; contacts: Contact[] }
|
|
1120
1120
|
| { type: "calendar-updated"; scheduledActivities: ScheduledActivity[] };
|
|
1121
1121
|
|
|
@@ -1499,7 +1499,7 @@ normative definition; matching names alone do not establish shared meaning.
|
|
|
1499
1499
|
|
|
1500
1500
|
### 10. Order on the wire is display order
|
|
1501
1501
|
|
|
1502
|
-
A list the provider publishes — a destination directory,
|
|
1502
|
+
A list the provider publishes — a destination directory, outcome codes, a task's custom
|
|
1503
1503
|
controls, its browsers — is in the order the provider wants it shown, and a host keeps that order.
|
|
1504
1504
|
A reader will find meaning in position unless something says there is none, so the provider
|
|
1505
1505
|
decides: where it has an intention, an administrator's sequence, it lists in that order; where it
|
|
@@ -1923,11 +1923,11 @@ them from what arrives later.
|
|
|
1923
1923
|
| Field | Contract |
|
|
1924
1924
|
| --- | --- |
|
|
1925
1925
|
| `breaks` | This login may request a break. Requires the four break methods on the connection. |
|
|
1926
|
-
| `team` | This login leads a team. The provider publishes a `
|
|
1926
|
+
| `team` | This login leads a team. The provider publishes a `TeamMembers` object to it on every snapshot — `members: []` when nobody is in it — and to nobody else. |
|
|
1927
1927
|
| `team.breakControl` | This lead may act on their team's breaks through `executeTeamBreak` — place, release, decide, set policy — as far as the provider supports; a command it lacks answers `omni.capability-not-enabled`. Omni asks for a decision only against a member whose `break` is `awaiting-decision`, so a provider that grants on request is never asked to decide. Requires `executeTeamBreak`. |
|
|
1928
1928
|
| `team.leadAssistControl` | This lead may join a member's call on request. Requires `executeTeamLeadAssist`. |
|
|
1929
1929
|
| `team.monitorControl` | This lead may listen to a member's call unasked, in the listed modes and no others: `monitor`, `whisper`, `barge`. The list always includes `monitor`, since the other two begin from one. Requires `executeTeamMonitor`. See **Monitoring a call**. |
|
|
1930
|
-
| `team.policyControl` | This lead sets the team's policy per capability — on, off, or the person's — within what the queue allows. Requires `executeTeamPolicy`; the
|
|
1930
|
+
| `team.policyControl` | This lead sets the team's policy per capability — on, off, or the person's — within what the queue allows. Requires `executeTeamPolicy`; the team member list carries `policies`. |
|
|
1931
1931
|
| `preferences` | What the team left to this person, with where each stands and who set it. Omitted when nothing was. Requires `setPreference`. See **Who decides what an agent may do**. |
|
|
1932
1932
|
|
|
1933
1933
|
A session action is available only when both the capability and Omni provisioning permit it.
|
|
@@ -1937,7 +1937,7 @@ state. A provider that reads roles live — a lead demoted mid-shift — republi
|
|
|
1937
1937
|
with the new set through `subscribe()` on the authentication session, which Omni keeps open for
|
|
1938
1938
|
the life of the connection for exactly this reason, and the next snapshot agrees with it. Omni
|
|
1939
1939
|
provisions what the capabilities call for at sign-in — a team panel for a lead, empty until the
|
|
1940
|
-
|
|
1940
|
+
team member list arrives, and nothing for anybody else — and withdraws it on the next render when the
|
|
1941
1941
|
capability goes. A command that arrives after its capability was withdrawn is answered `failed`
|
|
1942
1942
|
with `omni.capability-not-enabled`: the provider names it, so Omni never has to infer from a
|
|
1943
1943
|
capability change it may not have rendered yet that "you are no longer a lead" is the message
|
|
@@ -2023,7 +2023,7 @@ failure:
|
|
|
2023
2023
|
```
|
|
2024
2024
|
|
|
2025
2025
|
The `User` it carries is the **root of this provider's user namespace**. Every other person this
|
|
2026
|
-
provider names — a
|
|
2026
|
+
provider names — a team member, the manager on an imposed break, the agent on an interaction step —
|
|
2027
2027
|
is identified from the same directory and carries the same `UserId` type.
|
|
2028
2028
|
|
|
2029
2029
|
| Field | Contract |
|
|
@@ -2136,7 +2136,7 @@ a capability it agrees with the login: a lead's snapshot carries `team`, nobody
|
|
|
2136
2136
|
| `taskCount` | The provider's own count of those tasks, stated rather than inferred, and it must equal `tasks.length`. A snapshot with no work says `taskCount: 0` in so many words — a blank or unanswered state lacks the count and cannot pass as a confirmed empty. |
|
|
2137
2137
|
| `contacts` | Required complete contact contribution when the manifest declares `contacts`; `[]` clears it. Omitted only when it does not. |
|
|
2138
2138
|
| `scheduledActivities` | Required complete calendar contribution when the manifest declares `calendar`; `[]` clears it. Omitted only when it does not. |
|
|
2139
|
-
| `team` | Required `
|
|
2139
|
+
| `team` | Required `TeamMembers` when the login declares `capabilities.team`, `members: []` when nobody is in it. Forbidden otherwise — the login is the permission. |
|
|
2140
2140
|
|
|
2141
2141
|
## Live connection
|
|
2142
2142
|
|
|
@@ -2153,7 +2153,7 @@ surface in one place, and what obliges an adapter to implement each one.
|
|
|
2153
2153
|
| `refused(report)` | Always. The host 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 host does with what it refuses**. |
|
|
2154
2154
|
| `setCapacity(capacity)` | Always. Nothing may be allocated until a capacity is stated, so there is no connection that does not receive it. |
|
|
2155
2155
|
| `execute(request)` | Always. Every channel has commands no capability gates — see **Which commands need a capability**. |
|
|
2156
|
-
| `describeUsers(ids)` | The adapter publishes any `UserId`: on `ImposedBreak.by`, a
|
|
2156
|
+
| `describeUsers(ids)` | The adapter publishes any `UserId`: on `ImposedBreak.by`, a team member list, or `interactionHistory[].by`. Each `User` carries its `timeZone`; a person whose zone the provider cannot name is omitted from the answer, as any unresolvable id is. |
|
|
2157
2157
|
| `dial(request)` | The manifest declares `idleCapabilities.dial`, and with it `dialOutcomes`. |
|
|
2158
2158
|
| `requestBreak(request)` | The login declares `capabilities.breaks`. |
|
|
2159
2159
|
| `commitBreak()` | The login declares `capabilities.breaks`. Commit and cancel are not optional halves of it. |
|
|
@@ -2211,14 +2211,14 @@ Turns `UserId` values into something an agent can read.
|
|
|
2211
2211
|
describeUsers(ids: UserId[]): Promise<User[]>
|
|
2212
2212
|
```
|
|
2213
2213
|
|
|
2214
|
-
Required of any adapter that publishes a `UserId` — on `ImposedBreak.by`, a team
|
|
2214
|
+
Required of any adapter that publishes a `UserId` — on `ImposedBreak.by`, a team member list, or
|
|
2215
2215
|
`interactionHistory[].by`. Publishing an identifier Omni cannot resolve puts a name on screen
|
|
2216
2216
|
that reads as a database key.
|
|
2217
2217
|
|
|
2218
2218
|
- **Omit an id you cannot resolve; do not invent a name for it.** A missing entry says *I do not
|
|
2219
2219
|
know this person*, which Omni renders as such. Ordering is not significant and the response may
|
|
2220
2220
|
be shorter than the request.
|
|
2221
|
-
- **Take the whole list in one call.** Omni resolves a
|
|
2221
|
+
- **Take the whole list in one call.** Omni resolves a team member list or an interaction history as a batch, and
|
|
2222
2222
|
a per-id round trip multiplies that by its length.
|
|
2223
2223
|
- **Omni caches a result for one hour, then resolves it again.** The identifier is stable across
|
|
2224
2224
|
logins but the name behind it is not, so the cache expires on a clock rather than living for the
|
|
@@ -2368,7 +2368,7 @@ into a new session.
|
|
|
2368
2368
|
`task-offered` introduces a new task and does not imply acceptance.
|
|
2369
2369
|
|
|
2370
2370
|
`Task<C>` is channel-discriminated. For example, `Task<"email">` accepts
|
|
2371
|
-
`browsers` and `
|
|
2371
|
+
`browsers` and `outcomes`, but rejects voice-only controls such as `hold` and `endCall` at compile
|
|
2372
2372
|
time. Runtime conformance checks also require the task channel to match its provider manifest.
|
|
2373
2373
|
|
|
2374
2374
|
| Field | Contract |
|
|
@@ -2493,7 +2493,7 @@ ring-back is audio the agent hears -- so `task-media-started` arrives on the `pr
|
|
|
2493
2493
|
its party ringing. On `answered` the task is `in-progress`; on any other outcome the media ends,
|
|
2494
2494
|
`task-media-ended` starts the wrap clock as on every voice task, and the task goes to
|
|
2495
2495
|
`completing`, so the agent records the
|
|
2496
|
-
no-answer as the
|
|
2496
|
+
no-answer as the outcome it is -- in a campaign that is the commonest outcome there is, and it
|
|
2497
2497
|
is work, not a cancellation. A `preview` task therefore needs a manifest that says how a dial ends
|
|
2498
2498
|
(`task.preview.dialOutcomes.required`).
|
|
2499
2499
|
|
|
@@ -2631,7 +2631,7 @@ const emailCompletion = {
|
|
|
2631
2631
|
```
|
|
2632
2632
|
|
|
2633
2633
|
In this example, the agent has two minutes after sending the email to add notes, select a
|
|
2634
|
-
|
|
2634
|
+
outcome, and complete the task.
|
|
2635
2635
|
|
|
2636
2636
|
`0` means completion may happen immediately. With `provider-automatic`, the provider may complete
|
|
2637
2637
|
without waiting for a command; with `agent-command`, it still waits for `complete`.
|
|
@@ -2694,7 +2694,7 @@ window it cannot, and declaring it there changes nothing.
|
|
|
2694
2694
|
```ts
|
|
2695
2695
|
const connectBackCapable = {
|
|
2696
2696
|
channel: "voice",
|
|
2697
|
-
capabilities: { hold: true, connectBack: true,
|
|
2697
|
+
capabilities: { hold: true, connectBack: true, outcomes: true },
|
|
2698
2698
|
phase: "completing",
|
|
2699
2699
|
completionMode: "provider-automatic",
|
|
2700
2700
|
wrapAllowance: 30,
|
|
@@ -2737,8 +2737,7 @@ Migration from the earlier spelling:
|
|
|
2737
2737
|
Update producers, consumers, saved task snapshots, and validation-rule assertions together.
|
|
2738
2738
|
History/report rule names now use `interactionHistory`/`interactionReport`; phase and conformance
|
|
2739
2739
|
rules use `interaction` and final-task rules use `completion`. Legacy fields are rejected even
|
|
2740
|
-
when the new field is also supplied.
|
|
2741
|
-
`dispositions`, and `disposition` still describe outcome codes and are unchanged.
|
|
2740
|
+
when the new field is also supplied.
|
|
2742
2741
|
|
|
2743
2742
|
|
|
2744
2743
|
`Task.interactionHistory` is the call record: the steps that brought the task to the agent, oldest
|
|
@@ -2858,7 +2857,7 @@ Four rules a provider has to keep:
|
|
|
2858
2857
|
- **`by` is a bare `UserId`, and not necessarily an agent.** A lead or a manager takes part
|
|
2859
2858
|
during an interaction too — a transfer accepted, a call conferenced in — so the field names whoever it was,
|
|
2860
2859
|
the same way `ImposedBreak.by` does. It comes from this provider's own directory, the same
|
|
2861
|
-
namespace as `AuthenticationState.identity.id` and the team
|
|
2860
|
+
namespace as `AuthenticationState.identity.id` and the team member list, so entries pair
|
|
2862
2861
|
within a provider and never across one.
|
|
2863
2862
|
- **A task carries no names.** Omni resolves what to display with `describeUsers()`. Two people
|
|
2864
2863
|
called Arun on one site is ordinary, and anything pairing entries on a display name pairs them
|
|
@@ -3079,7 +3078,7 @@ source is one more published fact about them, and the one that lets a host tell
|
|
|
3079
3078
|
|
|
3080
3079
|
**Completing the task is not a capability, and no capability set withholds it.** `complete` is
|
|
3081
3080
|
governed by `completionMode` alone: under `agent-command` it is always available, whatever the set
|
|
3082
|
-
says, and the `
|
|
3081
|
+
says, and the `outcomes` capability decides only whether a code travels with it. Likewise
|
|
3083
3082
|
Answer on a pending task and Call on a preview are the phase's controls, not the set's. The
|
|
3084
3083
|
capability set governs what the agent may do *with* the task; completing it is never on the
|
|
3085
3084
|
list.
|
|
@@ -3173,7 +3172,7 @@ const taskCapabilities = {
|
|
|
3173
3172
|
channel: "voice",
|
|
3174
3173
|
capabilities: {
|
|
3175
3174
|
hold: true,
|
|
3176
|
-
|
|
3175
|
+
outcomes: true,
|
|
3177
3176
|
},
|
|
3178
3177
|
browsers: [],
|
|
3179
3178
|
} satisfies Pick<Task<"voice">, "channel" | "capabilities" | "browsers">;
|
|
@@ -3181,7 +3180,7 @@ const taskCapabilities = {
|
|
|
3181
3180
|
|
|
3182
3181
|
A capability says the control may be offered; the task's phase says whether there is anything to use
|
|
3183
3182
|
it on. Every control below that acts on the call or the conversation -- everything but `decline`,
|
|
3184
|
-
`connectBack` and `
|
|
3183
|
+
`connectBack` and `outcomes` -- is offered only while the task is `in-progress` or `paused`.
|
|
3185
3184
|
See **Which commands need a capability**.
|
|
3186
3185
|
|
|
3187
3186
|
### Voice capabilities
|
|
@@ -3197,20 +3196,40 @@ See **Which commands need a capability**.
|
|
|
3197
3196
|
| `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**. |
|
|
3198
3197
|
| `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**. |
|
|
3199
3198
|
| `recording` | Overflow menu item: Recording | Per-task provider and host policies expose only their permitted recording actions; each has independent state and routing. |
|
|
3200
|
-
| `
|
|
3199
|
+
| `outcomes` | Primary button: Complete | Omni may request task completion with a provider outcome and notes. |
|
|
3201
3200
|
|
|
3202
3201
|
### Publishing codes and destinations
|
|
3203
3202
|
|
|
3204
3203
|
Four capabilities accept an object when the provider wants Omni to render real choices. For
|
|
3205
|
-
`
|
|
3204
|
+
`outcomes` alone, `true` remains valid and means "offer the control with nothing published";
|
|
3206
3205
|
the three directory controls -- `coldTransfer`, `warmTransfer`, `conference` -- carry their
|
|
3207
3206
|
directory or are refused, since once nothing is typed the directory is the control.
|
|
3208
3207
|
|
|
3209
|
-
#### `
|
|
3208
|
+
#### `outcomes`
|
|
3209
|
+
|
|
3210
|
+
An outcome describes what happened; completion finishes the task. Outcome codes are selected
|
|
3211
|
+
from the task's own published list. They are distinct from `DialOutcome`, which reports the
|
|
3212
|
+
result of a dial attempt.
|
|
3213
|
+
|
|
3214
|
+
Migration requires providers and hosts to adopt the renamed types and fields together:
|
|
3215
|
+
|
|
3216
|
+
| Former API | Current API |
|
|
3217
|
+
| --- | --- |
|
|
3218
|
+
| DispositionCode | `OutcomeCode` |
|
|
3219
|
+
| DispositionRules | `OutcomeRules` |
|
|
3220
|
+
| DispositionPayload | `OutcomePayload` |
|
|
3221
|
+
| Task.capabilities.dispositions | `Task.capabilities.outcomes` |
|
|
3222
|
+
| complete.disposition | `complete.outcome` |
|
|
3223
|
+
|
|
3224
|
+
Validation and conformance diagnostics use `task.outcomes`, `task.outcome`, and
|
|
3225
|
+
`command.complete.outcome` in place of the former names. Update retained task snapshots and
|
|
3226
|
+
command producers as well as imports. Old capability and command fields are rejected, including
|
|
3227
|
+
payloads containing both names; there are no compatibility aliases. Code IDs, notes, required
|
|
3228
|
+
selection rules, and completion behavior are unchanged.
|
|
3210
3229
|
|
|
3211
3230
|
```ts
|
|
3212
3231
|
capabilities: {
|
|
3213
|
-
|
|
3232
|
+
outcomes: {
|
|
3214
3233
|
required: true,
|
|
3215
3234
|
notes: "optional",
|
|
3216
3235
|
codes: [
|
|
@@ -3225,9 +3244,9 @@ capabilities: {
|
|
|
3225
3244
|
| --- | --- |
|
|
3226
3245
|
| `required` | When `true`, Omni must collect a code before issuing `complete`. A required policy must publish at least one code. |
|
|
3227
3246
|
| `notes` | `required`, `optional`, or `none`; controls the free-text field beside the code. |
|
|
3228
|
-
| `codes` | Codes Omni offers. `id` values are non-empty and unique; Omni sends the chosen `id` as `TaskCommand.complete.
|
|
3247
|
+
| `codes` | Codes Omni offers. `id` values are non-empty and unique; Omni sends the chosen `id` as `TaskCommand.complete.outcome`. |
|
|
3229
3248
|
|
|
3230
|
-
With `
|
|
3249
|
+
With `outcomes: true` Omni shows a Complete control and sends `complete` with no code, because
|
|
3231
3250
|
the provider published none.
|
|
3232
3251
|
|
|
3233
3252
|
#### `coldTransfer`, `warmTransfer` and `conference`
|
|
@@ -3360,14 +3379,14 @@ customer.
|
|
|
3360
3379
|
| --- | --- | --- |
|
|
3361
3380
|
| `decline` | Pending-task button: Decline | The provider can decline a pending chat offer. Omni shows it only when provisioning also permits declining. |
|
|
3362
3381
|
| `hold` | Primary toggle: Hold | Omni may pause and resume the agent’s interaction in the chat. |
|
|
3363
|
-
| `
|
|
3382
|
+
| `outcomes` | Primary button: Complete | Omni may request task completion with a provider outcome and notes. |
|
|
3364
3383
|
|
|
3365
3384
|
### Email capabilities
|
|
3366
3385
|
|
|
3367
3386
|
| Capability | Omni UI | Contract |
|
|
3368
3387
|
| --- | --- | --- |
|
|
3369
3388
|
| `decline` | Pending-task button: Decline | The provider can decline a pending email offer. Omni shows it only when provisioning also permits declining. |
|
|
3370
|
-
| `
|
|
3389
|
+
| `outcomes` | Primary button: Complete | Omni may request task completion with a provider outcome and notes. |
|
|
3371
3390
|
|
|
3372
3391
|
### Custom capabilities
|
|
3373
3392
|
|
|
@@ -3707,7 +3726,7 @@ Your own tasks are the only ones you count. What the agent holds at other provid
|
|
|
3707
3726
|
concern — Omni set `count` knowing it, and this is how: the agent is one person on several
|
|
3708
3727
|
providers, and the host divides their capacity among them rather than telling each the whole. A
|
|
3709
3728
|
provider that has none of it for now is told **`count: 0`, host-stopped**: allocate nothing, show
|
|
3710
|
-
the member as `reserved` on the
|
|
3729
|
+
the member as `reserved` on the team member list -- signed in here, capacity held by the host for elsewhere,
|
|
3711
3730
|
a fact this provider holds, where `on-task` would assert work it cannot see -- and take the next
|
|
3712
3731
|
count as any other when the host has capacity for this provider again. Zero is the one restatement
|
|
3713
3732
|
that follows work rather than provisioning.
|
|
@@ -3745,7 +3764,7 @@ request object only to the request method, and undefined to the other three.
|
|
|
3745
3764
|
|
|
3746
3765
|
Use `validateTeamBreakCommand(request, context)` for lead decisions, placement, release and
|
|
3747
3766
|
policy commands. It requires the live lead capability and active transport, a current target
|
|
3748
|
-
|
|
3767
|
+
team member list for member commands, and the target's complete break state for placement/release.
|
|
3749
3768
|
Approve/deny requires an awaiting decision; placement uses the target's reason codes; release
|
|
3750
3769
|
requires an imposed committed break. The context's target state must belong to the named member;
|
|
3751
3770
|
that association and backend authorization are provider responsibilities.
|
|
@@ -4007,19 +4026,27 @@ so there is nothing on it to resume.
|
|
|
4007
4026
|
## Team leads
|
|
4008
4027
|
|
|
4009
4028
|
A lead who also takes calls sees their team on the idle dashboard. `Snapshot.team` carries a
|
|
4010
|
-
`
|
|
4029
|
+
`TeamMembers` object, replaced whole by `team-updated`.
|
|
4030
|
+
|
|
4031
|
+
Migration: TeamRoster is now `TeamMembers`, and validateTeamRoster is now
|
|
4032
|
+
`validateTeamMembers`. Update type imports and validator calls; no legacy export aliases exist.
|
|
4033
|
+
The object still contains `members` and the permitted `requests` and `policies`; it is not a
|
|
4034
|
+
bare array. `Snapshot.team`, the `team-updated` event and its `team` payload, login permissions,
|
|
4035
|
+
and `team.*` validation rules keep their names and semantics. An empty team has `members: []`;
|
|
4036
|
+
an absent `team` still means the login is not entitled to that contribution.
|
|
4037
|
+
|
|
4011
4038
|
|
|
4012
4039
|
| Field | Contract |
|
|
4013
4040
|
| --- | --- |
|
|
4014
|
-
| `members` | Every member of this lead's team, whatever their state. `[]` says the lead has a team with nobody in it; omitting the
|
|
4041
|
+
| `members` | Every member of this lead's team, whatever their state. `[]` says the lead has a team with nobody in it; omitting the team member list says something else entirely — see **The login is the permission** below. |
|
|
4015
4042
|
| `requests` | The members currently asking this lead to join a call, each with the task and the note. Required when the login declares `team.leadAssistControl`, `[]` when nobody is asking; omitted when it does not. See **Lead assist**. |
|
|
4016
4043
|
| `policies` | The team's policy per capability as it stands — the setting, who set it, and `lockedBy` where a level above the team made it theirs to keep. Required when the login declares `team.policyControl`; omitted when it does not. See **Who decides what an agent may do**. |
|
|
4017
4044
|
|
|
4018
4045
|
| `TeamMember` field | Contract |
|
|
4019
4046
|
| --- | --- |
|
|
4020
|
-
| `id` | Required `UserId`. A task carries no names and neither does a
|
|
4047
|
+
| `id` | Required `UserId`. A task carries no names and neither does a team member list: Omni resolves what to display with `describeUsers()`. |
|
|
4021
4048
|
| `availability` | Required. What the member is doing now. |
|
|
4022
|
-
| `since` | Optional. When the current `availability` began — not when they signed in, and not when the
|
|
4049
|
+
| `since` | Optional. When the current `availability` began — not when they signed in, and not when the team member list was read. |
|
|
4023
4050
|
| `break` | Present only while the member has an outstanding break request. See **A member waiting for a break**. |
|
|
4024
4051
|
|
|
4025
4052
|
Each availability value means one thing:
|
|
@@ -4032,7 +4059,7 @@ Each availability value means one thing:
|
|
|
4032
4059
|
| `reserved` | Signed in here, and the host holds this agent's capacity for another provider (`count: 0`, host-stopped): not receiving this provider's work, and not on a break. See **Capacity**. |
|
|
4033
4060
|
| `signed-out` | Known to this team but not signed in to this provider. |
|
|
4034
4061
|
|
|
4035
|
-
**Always publish the complete
|
|
4062
|
+
**Always publish the complete team member list, never a change to it.** Team presence typically reaches an
|
|
4036
4063
|
adapter over a best-effort channel with no ordering and no delivery guarantee, so a stream of deltas
|
|
4037
4064
|
cannot be trusted to reconstruct the truth. The adapter reconciles against its own authoritative
|
|
4038
4065
|
read and publishes the result.
|
|
@@ -4043,15 +4070,15 @@ everybody — worse than showing nothing, because it looks like data. Send it on
|
|
|
4043
4070
|
knows when the state actually began. It times the current `availability`, so it moves every time
|
|
4044
4071
|
that value does.
|
|
4045
4072
|
|
|
4046
|
-
**The login is the permission.** A
|
|
4073
|
+
**The login is the permission.** A team member list goes to a login that declares `capabilities.team`, on
|
|
4047
4074
|
every snapshot, and to nobody else. Omni never decides who leads a team: the provider said so at
|
|
4048
|
-
sign-in, and the
|
|
4075
|
+
sign-in, and the team member list agrees with it — present, `[]` included, for a lead; absent for everybody
|
|
4049
4076
|
else, which is the correct rendering for an agent who leads nobody. What the lead may do with the
|
|
4050
|
-
|
|
4077
|
+
team member list is on the login too, `team.breakControl` and `team.leadAssistControl`, never on the team member list.
|
|
4051
4078
|
|
|
4052
|
-
**The
|
|
4079
|
+
**The team member list never carries the agent it is published to — not in `members`, and not in
|
|
4053
4080
|
`requests`.** A lead does not report to themself: their own break request and their own ask for a
|
|
4054
|
-
lead go up to whoever leads them and appear on *that* person's
|
|
4081
|
+
lead go up to whoever leads them and appear on *that* person's team member list, while the requester sees
|
|
4055
4082
|
only their own `BreakState` and their task's `leadAssist` move. An adapter whose platform lists the lead
|
|
4056
4083
|
among their own members filters the signed-in identity out before publishing. **Being a lead is a
|
|
4057
4084
|
role the provider knows, never inferred from who is listed:** it is declared at sign-in, a lead
|
|
@@ -4070,7 +4097,7 @@ One method, `executeTeamBreak`, taking a discriminated command exactly as `execu
|
|
|
4070
4097
|
| `{ type: "place", memberId: UserId, reasonId?, reason? }` | 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 imposed break carries it as `activeReasonId`, so its kind is known. |
|
|
4071
4098
|
| `{ type: "release", memberId: UserId }` | Ends an imposed break on that member, whoever placed it. |
|
|
4072
4099
|
|
|
4073
|
-
`memberId` is this provider's own identifier for the member, as published on its
|
|
4100
|
+
`memberId` is this provider's own identifier for the member, as published on its team member list. It is
|
|
4074
4101
|
never an identifier from another provider, and Omni does not translate between them; names come
|
|
4075
4102
|
from `describeUsers()`.
|
|
4076
4103
|
|
|
@@ -4086,7 +4113,7 @@ never expressed here.
|
|
|
4086
4113
|
|
|
4087
4114
|
An agent on a call may ask a lead to join it -- a dispute that needs approval, a customer who
|
|
4088
4115
|
asks for a manager, a moment the agent wants a second pair of ears. A call centre calls this
|
|
4089
|
-
assistance or escalation, and the name says who assists: the capability is `leadAssist` on the task; the lead's side is the
|
|
4116
|
+
assistance or escalation, and the name says who assists: the capability is `leadAssist` on the task; the lead's side is the team member list, which is already the lead's view of the
|
|
4090
4117
|
team, and a second lead method beside `executeTeamBreak`:
|
|
4091
4118
|
|
|
4092
4119
|
```ts
|
|
@@ -4101,7 +4128,7 @@ Required when the login declares `capabilities.team.leadAssistControl`, and gate
|
|
|
4101
4128
|
execute({ taskId: "call-42", command: { type: "lead-assist", action: "request", note: "Refund dispute, needs approval" } })
|
|
4102
4129
|
// task.leadAssist = { stage: "requested", note: "Refund dispute, needs approval", since }
|
|
4103
4130
|
|
|
4104
|
-
// 2. Every lead entitled to it sees the request on their
|
|
4131
|
+
// 2. Every lead entitled to it sees the request on their team member list.
|
|
4105
4132
|
// team-updated: requests: [{ id: "req-7", memberId: "A-1", taskId: "call-42", allocationId: "alloc-42", note, since }]
|
|
4106
4133
|
|
|
4107
4134
|
// 3. A lead joins, or declines.
|
|
@@ -4128,7 +4155,7 @@ finish is not given more -- and a provider answers a `join` from a lead on such
|
|
|
4128
4155
|
The request stands for another lead, as it does when this one is already on a call.
|
|
4129
4156
|
|
|
4130
4157
|
**On `decline`, or a request the agent withdraws with `{ type: "lead-assist", action: "cancel" }`, the
|
|
4131
|
-
provider clears `leadAssist` from the agent's task** and drops the request from every
|
|
4158
|
+
provider clears `leadAssist` from the agent's task** and drops the request from every team member list. Nothing
|
|
4132
4159
|
else changes; the agent is still on the call.
|
|
4133
4160
|
|
|
4134
4161
|
The lead then has two commands on their copy, gated by `assisting` being present, and a third
|
|
@@ -4138,7 +4165,7 @@ choice that is no command at all:
|
|
|
4138
4165
|
| --- | --- | --- |
|
|
4139
4166
|
| `{ type: "lead-assist", action: "take-over" }` | `task-media-ended`, then `task-ended` with `{ type: "taken-over", leadId }`: **no `completing` window**, the agent is idle at once. The audio ends first, as before every voice ending (`stream.taskEnded.mediaOpen`), and the lead is named by user id, since a lead is not a directory item | Continues alone, and ends as any call does |
|
|
4140
4167
|
| `{ type: "lead-assist", action: "leave" }` | Continues; `leadAssist` is cleared | `task-media-ended`, then `task-ended` with `{ type: "left" }` -- the call goes on without them |
|
|
4141
|
-
| Stays until the customer hangs up | `task-media-ended`, `completing`, its own
|
|
4168
|
+
| Stays until the customer hangs up | `task-media-ended`, `completing`, its own outcome | The same, independently: **both have the completion window** |
|
|
4142
4169
|
|
|
4143
4170
|
`left` is the one outcome that ends a task without ending the call: this agent left a call that
|
|
4144
4171
|
continues without them. It reads as neither a completion nor a cancellation, because it is
|
|
@@ -4147,14 +4174,14 @@ neither.
|
|
|
4147
4174
|
```ts
|
|
4148
4175
|
const leadAssistCapable = {
|
|
4149
4176
|
channel: "voice",
|
|
4150
|
-
capabilities: { hold: true, leadAssist: true,
|
|
4177
|
+
capabilities: { hold: true, leadAssist: true, outcomes: true },
|
|
4151
4178
|
phase: "in-progress",
|
|
4152
4179
|
leadAssist: { stage: "joined", leadId: "L-9", note: "Refund dispute, needs approval", since: "2026-08-21T09:04:00Z" },
|
|
4153
4180
|
} satisfies Pick<Task<"voice">, "channel" | "capabilities" | "phase" | "leadAssist">;
|
|
4154
4181
|
```
|
|
4155
4182
|
|
|
4156
4183
|
Lead and member alike are `UserId`s of this provider, so an adapter publishing them implements
|
|
4157
|
-
`describeUsers()`; names never travel on a task or a
|
|
4184
|
+
`describeUsers()`; names never travel on a task or a team member list.
|
|
4158
4185
|
|
|
4159
4186
|
### Monitoring a call
|
|
4160
4187
|
|
|
@@ -4177,7 +4204,7 @@ declaration requires `executeTeamMonitor`, gated exactly as `executeTeamLeadAssi
|
|
|
4177
4204
|
`team.leadAssistControl`.
|
|
4178
4205
|
|
|
4179
4206
|
```ts
|
|
4180
|
-
// 1. The lead picks a member from the
|
|
4207
|
+
// 1. The lead picks a member from the team member list and starts silent.
|
|
4181
4208
|
executeTeamMonitor({ command: { type: "monitor", memberId: "A-1" } })
|
|
4182
4209
|
|
|
4183
4210
|
// 2. The lead's own task arrives -- task-offered with `automatic`, as a joined call does -- and
|
|
@@ -4245,7 +4272,7 @@ Omni offers Approve and Deny only while a member is `awaiting-decision`, shows `
|
|
|
4245
4272
|
but not started, and shows `starting-after-task` as settled.
|
|
4246
4273
|
|
|
4247
4274
|
**Live status, not a record.** The provider derives it from what is true now — not stored, not
|
|
4248
|
-
historical, carrying no decision made earlier. Like the
|
|
4275
|
+
historical, carrying no decision made earlier. Like the team member list it belongs to, it is published
|
|
4249
4276
|
whole and replaced whole, and a provider that cannot say omits it.
|
|
4250
4277
|
|
|
4251
4278
|
**An agent is not waiting on one person.** Authority is held by several, everyone who holds it
|
|
@@ -4468,8 +4495,8 @@ with a stale view: a flip against a state the provider has already changed turns
|
|
|
4468
4495
|
then off again. A custom `toggle` control therefore carries its own boolean. `hold` and `resume`,
|
|
4469
4496
|
`pause` and `resume` need no flag, being pairs rather than toggles.
|
|
4470
4497
|
|
|
4471
|
-
**`complete` sends
|
|
4472
|
-
`
|
|
4498
|
+
**`complete` sends an outcome only where one was published.** `outcome` is a
|
|
4499
|
+
`OutcomeCode.id` from the task's own `outcomes` capability, and `notes` obeys that
|
|
4473
4500
|
capability's `notes` setting. A task publishing no codes still receives `complete`, with neither.
|
|
4474
4501
|
|
|
4475
4502
|
### Where a command executes
|
|
@@ -4574,7 +4601,7 @@ declared:
|
|
|
4574
4601
|
| `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`. |
|
|
4575
4602
|
| `decline` | The `decline` capability on any channel, **and** Omni provisioning permitting it. One word for refusing an offer, whatever the channel. |
|
|
4576
4603
|
| `call` | The `preview` phase. A record put in front of an agent is there to be called, so the phase is the gate and there is no capability. It is a dial, with a `dialId` and a `dial-outcome`. |
|
|
4577
|
-
| `complete` | `completionMode: "agent-command"`. The `
|
|
4604
|
+
| `complete` | `completionMode: "agent-command"`. The `outcomes` capability decides whether a code travels with the command, never whether the command exists — a task Omni cannot complete never ends. What travels is what the capability published: a code from its list where it has one (`command.complete.outcome.unknown`), a code at all where it requires one (`.outcome.required`), notes as it said (`.notes.required`, `.notes.unexpected`), and neither where the task declares no outcomes (`.outcome.unexpected`). |
|
|
4578
4605
|
| `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. |
|
|
4579
4606
|
| `transfer` with `action: "warm"` | The `warmTransfer` capability. `action: "cold"` is gated by `coldTransfer`; the two are declared and offered separately. |
|
|
4580
4607
|
| `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`. |
|
|
@@ -4810,8 +4837,8 @@ direction — the provider asking Omni to reconcile — and neither replaces the
|
|
|
4810
4837
|
|
|
4811
4838
|
Carries a complete `Snapshot` after reconnect or when the provider explicitly requests
|
|
4812
4839
|
reconciliation. `reason` is `reconnected` or `provider-requested`. Omni replaces the provider's
|
|
4813
|
-
current status, break state, tasks, contacts, scheduled activities and team
|
|
4814
|
-
snapshot. It carries what the login's capabilities call for — a
|
|
4840
|
+
current status, break state, tasks, contacts, scheduled activities and team member list with this
|
|
4841
|
+
snapshot. It carries what the login's capabilities call for — a team member list for a lead, on every
|
|
4815
4842
|
snapshot — and nothing they do not; a capability is withdrawn by a republished `authenticated`,
|
|
4816
4843
|
never by an omission from a snapshot.
|
|
4817
4844
|
|
|
@@ -5047,9 +5074,9 @@ each connected provider.
|
|
|
5047
5074
|
|
|
5048
5075
|
### `team-updated`
|
|
5049
5076
|
|
|
5050
|
-
Replaces this provider's complete `
|
|
5051
|
-
publishes a
|
|
5052
|
-
reason set out under **Team leads**. A lead's snapshot always carries the
|
|
5077
|
+
Replaces this provider's complete `TeamMembers`. It is emitted only for an agent the provider
|
|
5078
|
+
publishes a team member list to, and it carries the whole team every time — never a change to it, for the
|
|
5079
|
+
reason set out under **Team leads**. A lead's snapshot always carries the team member list; it goes only when
|
|
5053
5080
|
a republished `authenticated` no longer declares `capabilities.team`.
|
|
5054
5081
|
|
|
5055
5082
|
### `contacts-updated`
|
|
@@ -5075,7 +5102,7 @@ The same treatment for a `UserId`, and needed for the same reason: user identifi
|
|
|
5075
5102
|
issued by each provider independently, so two providers will eventually issue the same string for
|
|
5076
5103
|
different people. Encode and join before storing or comparing.
|
|
5077
5104
|
|
|
5078
|
-
Use it for every `UserId` — `interactionHistory[].by`,
|
|
5105
|
+
Use it for every `UserId` — `interactionHistory[].by`, team members, `memberId` on a
|
|
5079
5106
|
lead command, `ImposedBreak.by`. A bare one is only ever compared against another from the **same** provider; anything
|
|
5080
5107
|
wider goes through this key.
|
|
5081
5108
|
|
|
@@ -5095,7 +5122,7 @@ same exported checks are used by Omni and adapter tests so their interpretations
|
|
|
5095
5122
|
| --- | --- |
|
|
5096
5123
|
| `validateManifest(manifest)` | Identity, protocol-version interoperability, authentication methods, and idle-capability shapes. |
|
|
5097
5124
|
| `validateTask(task, { channel, locked? })` | Identity, channel agreement, phase, wrap allowance, capability shapes, custom controls, and browsers. Given `locked`, the values the queue locked, a task whose party stands locked carries none of them anywhere else (`task.locked.leak`). |
|
|
5098
|
-
| `validateSnapshot(snapshot, manifest)` | Status, break state, break reasons, team
|
|
5125
|
+
| `validateSnapshot(snapshot, manifest)` | Status, break state, break reasons, team member list, the stated `taskCount` reconciled against the tasks carried, and every task, contact, and activity, including idle-capability gating both ways: a contribution the manifest never declared is refused, and one it declares is required, `[]` included. |
|
|
5099
5126
|
| `validateEventEnvelope(envelope, manifest)` | Envelope identity, timestamp, and the payload for each event type. |
|
|
5100
5127
|
| `validateContact(contact)` | Contact field shapes and attribute keys. Every field is optional, so this checks what is present rather than what is missing. |
|
|
5101
5128
|
| `validateScheduledActivity(activity)` | Required activity fields and start/end ordering. |
|
|
@@ -5118,13 +5145,13 @@ Each returns `ProtocolViolation[]` rather than throwing, so a caller can report
|
|
|
5118
5145
|
once. A violation carries a stable `rule` id such as `task.browser.url.scheme`, the `path` it was
|
|
5119
5146
|
found at such as `snapshot.tasks[0].browsers[1].url`, and a `message`.
|
|
5120
5147
|
|
|
5121
|
-
Some rules need to know who is reading. `
|
|
5148
|
+
Some rules need to know who is reading. `validateTeamMembers`, `validateSnapshot`, and
|
|
5122
5149
|
`validateEventEnvelope` take an optional final `{ self, capabilities }` — the signed-in agent's
|
|
5123
|
-
`AuthenticationState.identity.id` and their login's `capabilities`. Given `self`, a
|
|
5150
|
+
`AuthenticationState.identity.id` and their login's `capabilities`. Given `self`, a team member list that
|
|
5124
5151
|
carries that agent reports `team.member.self` or `team.request.self`. Given `capabilities`, a lead's
|
|
5125
|
-
snapshot without a
|
|
5126
|
-
reports `team.unentitled`, `requests` on a
|
|
5127
|
-
`team.requests.capability`, and a
|
|
5152
|
+
snapshot without a team member list reports `team.required`, a team member list published to a login that does not lead
|
|
5153
|
+
reports `team.unentitled`, `requests` on a team member list whose login lacks `team.leadAssistControl` reports
|
|
5154
|
+
`team.requests.capability`, and a team member list without them on a login that declares it reports
|
|
5128
5155
|
`team.requests.required`. Without them those rules are not checked, because they cannot be.
|
|
5129
5156
|
`exerciseAdapter` always passes both.
|
|
5130
5157
|
|
|
@@ -5187,7 +5214,7 @@ A capability granted by a later login requires its methods just as one declared
|
|
|
5187
5214
|
|
|
5188
5215
|
`result.notExercised` lists what the run never reached — one subject per family of rules: each
|
|
5189
5216
|
optional part of a task (`task.browsers`, `task.interactionHistory`, `task.leadAssist`, …), the break's
|
|
5190
|
-
`reasons` and `imposed`, the
|
|
5217
|
+
`reasons` and `imposed`, the team member list's `members` and `requests`, each declared contribution, and
|
|
5191
5218
|
each event type (`event.task-ended`, …) — and so what a clean `violations` says nothing about.
|
|
5192
5219
|
Nothing there is a violation: an adapter with no team has nothing to exercise. But a fixture with
|
|
5193
5220
|
no tasks exercises no task rule, and a pass over it reads as coverage it is not.
|
|
@@ -5198,7 +5225,7 @@ outcome -- stays in `notExercised` for every adapter, and a rule about a live ca
|
|
|
5198
5225
|
in each adopter's own tests. `{ drive: true }` closes that: the exercise takes the first task the
|
|
5199
5226
|
provider offers through one ordinary lifecycle -- accept it, wait for its media and open it on a
|
|
5200
5227
|
softphone, hold and resume where the task offers `hold`, end the call where it offers `endCall`,
|
|
5201
|
-
complete it with
|
|
5228
|
+
complete it with an outcome where the agent completes -- and holds every step to the rules a
|
|
5202
5229
|
host holds a provider to. Each command is validated against the task as published
|
|
5203
5230
|
(`drive.command.*`), each answer for its method, a refusal of a control the task offered is a
|
|
5204
5231
|
violation (`drive.command.failed`), and an event the provider owes and never sends is one too
|
|
@@ -5272,7 +5299,7 @@ expect(driven.violations).toEqual([]);
|
|
|
5272
5299
|
assertReached(driven, ["task.onCall", "task.media", "event.task-ended"]);
|
|
5273
5300
|
```
|
|
5274
5301
|
`assertReached(result, subjects)` is the paired assertion: it throws naming every subject the run
|
|
5275
|
-
never met, so a test that meant to check a
|
|
5302
|
+
never met, so a test that meant to check a team member list cannot pass on a fixture that never produced one.
|
|
5276
5303
|
It reads like a guarantee and is a claim the adopter keeps making: it catches an adapter that
|
|
5277
5304
|
stopped reaching a subject, not a list that stopped asking, so a list can rot to nothing and stay
|
|
5278
5305
|
green. Keep it honest with a control beside it -- one subject the run genuinely cannot reach,
|
|
@@ -5297,7 +5324,7 @@ cannot be established from TypeScript structure alone.
|
|
|
5297
5324
|
|
|
5298
5325
|
| Helper | Contract checked |
|
|
5299
5326
|
| --- | --- |
|
|
5300
|
-
| `assertCapabilityWithdrawal(states, snapshot, manifest)` | A capability withdrawn by a later `authenticated` state is gone from the next snapshot: no
|
|
5327
|
+
| `assertCapabilityWithdrawal(states, snapshot, manifest)` | A capability withdrawn by a later `authenticated` state is gone from the next snapshot: no team member list for a login that no longer leads, no requests for one that may no longer join. Every state is validated on the way, `refreshing` must carry the login over, and the sequence passes only through usable states. |
|
|
5301
5328
|
| `assertTaskCapabilityWithdrawal(tasks, manifest, command)` | A capability withdrawn by a republish of the task is gone from the task: every task in the sequence is validated, all carry the offer's id, at least one capability the offer declared is absent at the end (a locked control is present, not withdrawn), and `command` is clean against the first task and refused against the last for want of a withdrawn capability and nothing else. Pair it with `assertCommandRefusedAfterWithdrawal` on the provider's answer. |
|
|
5302
5329
|
| `assertCommandRefusedAfterWithdrawal(result)` | A command that arrives after its capability was withdrawn fails with `omni.capability-not-enabled`, named by the provider. The same assertion serves a command the provider never supported under a capability it declares. |
|
|
5303
5330
|
| `assertReached(result, subjects)` | The exercise met every subject named; throws listing those it did not. Pair it with a clean `exerciseAdapter` result. |
|
|
@@ -5405,7 +5432,7 @@ same operation; reuse with different contents is refused. It never silently retr
|
|
|
5405
5432
|
operation. Implementations without reliable request correlation must refuse controls.
|
|
5406
5433
|
|
|
5407
5434
|
Keep command progress in host UI separately from authoritative state. Applied means the requested
|
|
5408
|
-
transition and
|
|
5435
|
+
recording transition and any retain/discard effect actually completed; publish the confirming task or host report before
|
|
5409
5436
|
resolving applied. Failed means confirmed no effect. Rejected promise means outcome unknown:
|
|
5410
5437
|
show the failure, reconcile that path and do not automatically retry or pretend inactive. Authoritative
|
|
5411
5438
|
updates remain full current task/host views, not replayed provider events. These current-state fields
|