@xema/omni-protocol 0.1.94 → 0.1.96
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 +14 -20
- package/dist/testing.d.ts +4 -4
- package/dist/testing.js +15 -14
- package/dist/validation.d.ts +2 -2
- package/dist/validation.js +39 -43
- package/guide.md +457 -413
- package/package.json +1 -1
package/guide.md
CHANGED
|
@@ -19,18 +19,18 @@ are used precisely throughout and mean nothing looser here.
|
|
|
19
19
|
| Word | What it means |
|
|
20
20
|
| --- | --- |
|
|
21
21
|
| **Omni** | The desktop application an agent works in. It composes several providers into one agent-facing experience and owns everything outside a provider's own system. |
|
|
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
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
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
|
+
| **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. |
|
|
29
29
|
| **Interaction** | One agent’s part in the call or conversation. The same call may return to that agent for a new interaction. |
|
|
30
30
|
| **Task** | The agent’s assigned work and its workspace, controls and completion work. A voice task represents one interaction, not the caller’s whole call. |
|
|
31
31
|
| **Channel** | The kind of work a provider carries: `voice`, `chat`, or `email`. Fixed per provider by its manifest. |
|
|
32
32
|
| **Task type** | The provider's own name for a category of work — a queue, a mailbox folder, a chat source. Free-form, and finer-grained than a channel. |
|
|
33
|
-
| **Capability** | A provider's declaration that a control exists for a task or a session. It says *offer this*; the provider performs it, and the
|
|
33
|
+
| **Capability** | A provider's declaration that a control exists for a task or a session. It says *offer this*; the provider performs it, and the agent application only offers it — see **Where a command executes**. |
|
|
34
34
|
| **Login** | One authenticated sign-in to one provider, identified by `loginId`. A transport reconnect keeps it; signing in again replaces it, and nothing tied to the old `loginId` survives. |
|
|
35
35
|
| **Transport** | The adapter's connection to its platform: a WebSocket or SignalR connection, required to be persistent and ordered. Which one and how it reconnects are the adapter's business; losing it does not end a login. |
|
|
36
36
|
| **Connection** | The `Connection` object Omni holds for one login: the methods it can call and the events it receives. |
|
|
@@ -39,13 +39,12 @@ are used precisely throughout and mean nothing looser here.
|
|
|
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
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
|
-
| **Dial** | One outbound call the
|
|
42
|
+
| **Dial** | One outbound call the agent application asks a provider to place — from the idle dialpad, a cold or warm transfer, a conference add, or a connect-back. Identified by the agent application's `dialId`, accepted as `dialling`, and ended by exactly one `dial-outcome`. See **Every dial has an outcome**. |
|
|
43
43
|
| **Listen** | A lead listening to a member's call unasked, from the team member list: `listen` in silence, `coach` heard by the agent alone, `join-call` heard by everyone. Nothing of it reaches the member's task, and there is no take-over in it. See **Listening to a call**. |
|
|
44
44
|
| **On the call** | Who a voice task's audio joins, or is bringing in, as the provider states it on `Task.onCall`: the party, the agents, and anyone consulted or conferenced in from the moment their dial is placed. |
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
transport, a task, a break and a call each have a word of their own:
|
|
46
|
+
These fields describe state within their containing objects. Both authentication and break
|
|
47
|
+
state use `status`; name the object when discussing them to avoid ambiguity.
|
|
49
48
|
|
|
50
49
|
| Word | Belongs to | Values |
|
|
51
50
|
| --- | --- | --- |
|
|
@@ -53,9 +52,14 @@ transport, a task, a break and a call each have a word of their own:
|
|
|
53
52
|
| `media` | A task's audio | `started`, `ended` |
|
|
54
53
|
| `transport` | A connection | `connecting`, `active`, `error` |
|
|
55
54
|
| `status` | An authentication session | `signed-out`, `authenticating`, `authenticated`, `refreshing`, `expired` |
|
|
56
|
-
| `
|
|
55
|
+
| `status` | The agent’s break lifecycle | `not-requested`, `awaiting-approval`, `granted`, `starting-after-task`, `on-break` |
|
|
57
56
|
| `availability` | A team member | `ready`, `on-task`, `on-break`, `signed-out` |
|
|
58
57
|
|
|
58
|
+
Migration: describeUsers is now `getUserDetails`, and validateDescribedUsers is now
|
|
59
|
+
`validateUserDetails`. Related diagnostics use getUserDetails. Hosts and providers must
|
|
60
|
+
update together; the old method and validator have no compatibility aliases. The requested
|
|
61
|
+
user IDs, returned user details and directory requirements are unchanged.
|
|
62
|
+
|
|
59
63
|
## Versioning
|
|
60
64
|
|
|
61
65
|
### `OMNI_PROTOCOL_VERSION`
|
|
@@ -68,12 +72,12 @@ An adapter declares **every** version it can speak, not just the one it was comp
|
|
|
68
72
|
|
|
69
73
|
```ts
|
|
70
74
|
supportedProtocolVersions: [1] // v1 only
|
|
71
|
-
supportedProtocolVersions: [1, 2] // can serve either
|
|
75
|
+
supportedProtocolVersions: [1, 2] // can serve either agent application
|
|
72
76
|
```
|
|
73
77
|
|
|
74
78
|
A single pinned version would make migration impossible: recompiling against a newer package
|
|
75
79
|
would silently move an adapter to the new version with no window in which both sides
|
|
76
|
-
interoperate. Declaring a set lets an adapter support the old and new
|
|
80
|
+
interoperate. Declaring a set lets an adapter support the old and new agent application at once, so the two
|
|
77
81
|
can be deployed independently.
|
|
78
82
|
|
|
79
83
|
### `negotiateProtocolVersion(adapterVersions, hostVersions?)`
|
|
@@ -489,7 +493,7 @@ type Snapshot = {
|
|
|
489
493
|
};
|
|
490
494
|
|
|
491
495
|
type AgentCapacity = {
|
|
492
|
-
count: number; // absolute ceiling, zero or more; zero is
|
|
496
|
+
count: number; // absolute ceiling, zero or more; zero is agent application-stopped
|
|
493
497
|
};
|
|
494
498
|
|
|
495
499
|
type CapacityResult = { status: "applied" };
|
|
@@ -871,7 +875,7 @@ leaves out does not exist — a structure with no site level declares `org`, `te
|
|
|
871
875
|
at: a label with no policy behind it decides nothing. A manifest that declares none has exactly
|
|
872
876
|
the four. `lockedBy` is any level in force except `person`, who never locks their own value;
|
|
873
877
|
`setBy` is any level in force, or `provider`, the protocol's word for "no level has said
|
|
874
|
-
anything and the provider's own configuration supplied the value".
|
|
878
|
+
anything and the provider's own configuration supplied the value". An agent application renders "who decided" from the
|
|
875
879
|
declared labels and needs no others, and validates every republished `authenticated` state
|
|
876
880
|
against them, not only the sign-in. What the wire carries is the resolution:
|
|
877
881
|
|
|
@@ -898,7 +902,7 @@ The adapter is the one that knows the value, so it is the one held to it: given
|
|
|
898
902
|
queue locked, the validator refuses any other field carrying one, digits compared as digits so no
|
|
899
903
|
formatting hides them (`task.locked.leak`), and a conformance run whose tasks lock a party's number
|
|
900
904
|
or email states those values in `lockedValues`, since a run that cannot ask the question is not a
|
|
901
|
-
pass.
|
|
905
|
+
pass. An agent application never has the value and never asks. A name is not locked. What the queue provides rather than permits — browsers, outcomes, custom
|
|
902
906
|
controls — is content, and is never locked.
|
|
903
907
|
|
|
904
908
|
**A lead sets the team's policy from their team member list.** A login that declares
|
|
@@ -922,16 +926,16 @@ person's preference from their own screen, which arrives the same way. A prefere
|
|
|
922
926
|
the capability's own name because it is the same capability at another level: effective in
|
|
923
927
|
`Task.capabilities`, set for the team in `policies`, left to the person in `preferences`. The
|
|
924
928
|
command `hold` acts on one call; the preference `hold` says whether the person wants the control
|
|
925
|
-
at all, and
|
|
926
|
-
these ladders: the microphone is the
|
|
927
|
-
keeps a choice about it. See **The station is the
|
|
929
|
+
at all, and an agent application renders it in its settings, never as the button on a call. Mute is in none of
|
|
930
|
+
these ladders: the microphone is the agent application's, and nobody on the provider's side allows, locks or
|
|
931
|
+
keeps a choice about it. See **The station is the agent application's**.
|
|
928
932
|
|
|
929
933
|
## Breaks
|
|
930
934
|
|
|
931
935
|
```ts
|
|
932
|
-
type
|
|
936
|
+
type BreakStatus =
|
|
933
937
|
| "not-requested"
|
|
934
|
-
| "awaiting-
|
|
938
|
+
| "awaiting-approval"
|
|
935
939
|
| "granted"
|
|
936
940
|
| "starting-after-task"
|
|
937
941
|
| "on-break";
|
|
@@ -949,12 +953,13 @@ type BreakRequest = {
|
|
|
949
953
|
reasonId?: string;
|
|
950
954
|
};
|
|
951
955
|
|
|
952
|
-
type ForcedBreak =
|
|
953
|
-
|
|
954
|
-
|
|
956
|
+
type ForcedBreak = {
|
|
957
|
+
by: UserId;
|
|
958
|
+
expectedDurationMs?: number;
|
|
959
|
+
};
|
|
955
960
|
|
|
956
961
|
type BreakState = {
|
|
957
|
-
|
|
962
|
+
status: BreakStatus;
|
|
958
963
|
canRequestBreak: boolean;
|
|
959
964
|
requestUnavailableReason?: string;
|
|
960
965
|
decisionReason?: string;
|
|
@@ -990,7 +995,7 @@ type TeamMember = {
|
|
|
990
995
|
id: UserId;
|
|
991
996
|
availability: TeamMemberAvailability;
|
|
992
997
|
since?: IsoTimestamp;
|
|
993
|
-
break?: Extract<
|
|
998
|
+
break?: Extract<BreakStatus, "awaiting-approval" | "granted" | "starting-after-task">;
|
|
994
999
|
};
|
|
995
1000
|
|
|
996
1001
|
type LeadRequest = {
|
|
@@ -1036,7 +1041,7 @@ type TeamLeadAssistCommand =
|
|
|
1036
1041
|
type TeamBreakCommand =
|
|
1037
1042
|
| { type: "decide-break-request"; memberId: UserId; decision: "granted" | "denied"; reason?: string }
|
|
1038
1043
|
| { type: "set-break-policy"; policy: "approval-required" | "automatically-approved" | "requests-suspended" }
|
|
1039
|
-
| { type: "force-break"; memberId: UserId; reasonId?: string; reason?: string }
|
|
1044
|
+
| { type: "force-break"; memberId: UserId; reasonId?: string; reason?: string; expectedDurationMs?: number }
|
|
1040
1045
|
| { type: "end-forced-break"; memberId: UserId };
|
|
1041
1046
|
|
|
1042
1047
|
type TeamBreakCommandRequest = {
|
|
@@ -1163,7 +1168,7 @@ type Connection<C extends Channel = Channel> = {
|
|
|
1163
1168
|
execute(request: TaskCommandRequest<C>): Promise<TaskCommandResult>;
|
|
1164
1169
|
disconnect(): Promise<void>;
|
|
1165
1170
|
|
|
1166
|
-
|
|
1171
|
+
getUserDetails?(ids: UserId[]): Promise<User[]>;
|
|
1167
1172
|
dial?(request: DialRequest): Promise<DialResult>;
|
|
1168
1173
|
|
|
1169
1174
|
requestBreak?(request: BreakRequest): Promise<BreakRequestResult>;
|
|
@@ -1401,7 +1406,7 @@ fact.
|
|
|
1401
1406
|
|
|
1402
1407
|
Omitting a field says *I cannot see this*. An empty value says *I looked, and there is nothing*.
|
|
1403
1408
|
|
|
1404
|
-
Send whichever is true.
|
|
1409
|
+
Send whichever is true. An agent application renders them differently and cannot recover the distinction once
|
|
1405
1410
|
it is lost.
|
|
1406
1411
|
|
|
1407
1412
|
The same distinction applies to nested fields. Omit a nested field only when its value is unknown;
|
|
@@ -1500,7 +1505,7 @@ normative definition; matching names alone do not establish shared meaning.
|
|
|
1500
1505
|
### 10. Order on the wire is display order
|
|
1501
1506
|
|
|
1502
1507
|
A list the provider publishes — a destination directory, outcome codes, a task's custom
|
|
1503
|
-
controls, its browsers — is in the order the provider wants it shown, and
|
|
1508
|
+
controls, its browsers — is in the order the provider wants it shown, and an agent application keeps that order.
|
|
1504
1509
|
A reader will find meaning in position unless something says there is none, so the provider
|
|
1505
1510
|
decides: where it has an intention, an administrator's sequence, it lists in that order; where it
|
|
1506
1511
|
has none, it orders by the label, so the order is visibly arbitrary and does not move under the
|
|
@@ -1537,9 +1542,9 @@ the login's store where the platform cannot hold it. Per offer, the `allocationI
|
|
|
1537
1542
|
the offer itself states; per call, the media follows `task-media-started` and `task-media-ended`,
|
|
1538
1543
|
never a flag set when the audio was opened; per session, the phone the agent holds is what the
|
|
1539
1544
|
platform restates on activation, not a mapping read once over HTTP. What the platform cannot hold
|
|
1540
|
-
--
|
|
1545
|
+
-- an agent application's muted leg -- goes in `ConnectContext.store`, keyed by the task and gone with it.
|
|
1541
1546
|
|
|
1542
|
-
A fact read once over a connection and kept in the adapter object dies with the client.
|
|
1547
|
+
A fact read once over a connection and kept in the adapter object dies with the client. An agent application
|
|
1543
1548
|
reload is the first client dying and a second coming up for the same login, and the second
|
|
1544
1549
|
publishes the same truth about the same task as the first did, or the first was publishing
|
|
1545
1550
|
something it made up: an allocation minted for the connection, media remembered as a boolean, a
|
|
@@ -1608,11 +1613,11 @@ compile time.
|
|
|
1608
1613
|
| `phaseLabels` | Optional static adapter-defined display names for canonical `TaskPhase` values. They cannot vary at runtime. |
|
|
1609
1614
|
| `taskTypePresentation` | Optional static adapter-defined presentation keyed by exact `taskType`. It names the item and its optional agent-facing reference. |
|
|
1610
1615
|
| `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**. |
|
|
1611
|
-
| `phones` | Voice only, and required there: the phones this platform can put an agent on, `softphone` (the call's audio lands in the
|
|
1616
|
+
| `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**. |
|
|
1612
1617
|
| `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 transfer, conference or call back — and a `dial-outcome` carries only a declared member. See **Every dial has an outcome**. |
|
|
1613
|
-
| `timeCheck` | Optional `true`: implements `checkTime` for fresh provider-clock samples;
|
|
1614
|
-
| `timestampAuthority` | Optional `"provider"`: provider timestamps are final;
|
|
1615
|
-
| `runningStepReports` | The provider takes running reports of
|
|
1618
|
+
| `timeCheck` | Optional `true`: implements `checkTime` for fresh provider-clock samples; agent application polling is independently opt-in. |
|
|
1619
|
+
| `timestampAuthority` | Optional `"provider"`: provider timestamps are final; agent application instants are advisory. Omission makes no trust promise. |
|
|
1620
|
+
| `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**. |
|
|
1616
1621
|
| `completionSettleMs` | Required. How long after an applied completion -- `complete`, or a lead's `take-over-call` -- the provider's `task-ended` is owed, a positive whole number of milliseconds (`manifest.completionSettleMs`). A warm transfer's `complete` is not final task completion: the agent's wrap runs after it. Stated per provider, since platforms settle at different speeds. See **`task-ended`**. |
|
|
1617
1622
|
|
|
1618
1623
|
### Authentication methods
|
|
@@ -1867,8 +1872,8 @@ Creates the provider-scoped authentication session.
|
|
|
1867
1872
|
| --- | --- |
|
|
1868
1873
|
| `protocolVersion` | The negotiated version, fixed for this login. |
|
|
1869
1874
|
| `loginId` | Omni-generated identity for this login. The same value Omni later passes as `ConnectContext.loginId`, and how an adapter ties a connection back to the session that authenticated it. |
|
|
1870
|
-
| `timeZone` | The zone the agent's day is reckoned in, as an IANA name from the
|
|
1871
|
-
| `phone` | How this login hears its calls, chosen by the
|
|
1875
|
+
| `timeZone` | The zone the agent's day is reckoned in, as an IANA name from the agent application's clock, stated before any identity exists so the first `authenticated` state already carries it. The same value Omni later passes as `ConnectContext.timeZone`. See **The agent's day**. |
|
|
1876
|
+
| `phone` | How this login hears its calls, chosen by the agent application from the manifest's `phones`: required for a voice provider, absent for any other. The same value Omni later passes as `ConnectContext.phone`. See **How the agent hears the call**. |
|
|
1872
1877
|
| `secrets` | Omni-provided `SecretStore`, scoped to this provider's manifest id. |
|
|
1873
1878
|
| `signal` | Optional cancellation signal. |
|
|
1874
1879
|
| `log` | Optional structured logging callback. Never include credentials, tokens, or sensitive contact data. |
|
|
@@ -1924,13 +1929,13 @@ them from what arrives later.
|
|
|
1924
1929
|
| --- | --- |
|
|
1925
1930
|
| `breaks` | This login may request a break. Requires the four break methods on the connection. |
|
|
1926
1931
|
| `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
|
-
| `team.breakControl` | This lead may act on their team's breaks through `executeTeamBreak` — force-break, end-forced-break, decide-break-request, set-break-policy — as far as the provider supports; a command it lacks answers `omni.capability-not-enabled`. Omni asks for a decision only against a member whose `break` is `awaiting-
|
|
1932
|
+
| `team.breakControl` | This lead may act on their team's breaks through `executeTeamBreak` — force-break, end-forced-break, decide-break-request, set-break-policy — as far as the provider supports; a command it lacks answers `omni.capability-not-enabled`. Omni asks for a decision only against a member whose `break` is `awaiting-approval`, so a provider that grants on request is never asked to decide. Requires `executeTeamBreak`. |
|
|
1928
1933
|
| `team.leadAssistControl` | This lead may join a member's call on request. Requires `executeTeamLeadAssist`. |
|
|
1929
1934
|
| `team.listeningControl` | This lead may listen to a member's call unasked, in the listed modes and no others: `listen`, `coach`, `join-call`. The list always includes `listen`, since the other two begin from one. Requires `executeTeamListen`. See **Listening to a call**. |
|
|
1930
1935
|
| `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
1936
|
| `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
1937
|
|
|
1933
|
-
A session action is available only when both the capability and Omni
|
|
1938
|
+
A session action is available only when both the capability and Omni local policy permit it.
|
|
1934
1939
|
|
|
1935
1940
|
**Capabilities are current, not fixed.** They describe the login as of its latest `authenticated`
|
|
1936
1941
|
state. A provider that reads roles live — a lead demoted mid-shift — republishes `authenticated`
|
|
@@ -2072,11 +2077,11 @@ Creates one live provider connection for the signed-in agent.
|
|
|
2072
2077
|
| --- | --- |
|
|
2073
2078
|
| `protocolVersion` | Version negotiated before authentication. Fixed for this login. |
|
|
2074
2079
|
| `loginId` | Omni-generated identity for this login. It is the same value passed as `AuthenticationContext.loginId`, so an adapter can correlate this connection with the session that authenticated it. Stable across transport reconnects and changed only by a new login. |
|
|
2075
|
-
| `autoAcceptTasks` | Agent
|
|
2080
|
+
| `autoAcceptTasks` | Agent local policy policy relayed to the provider at login, stated by the agent application on every connection and never assumed from its absence. When `true`, a pending task states its `acceptance`; when `false`, every task requires agent acceptance. Fixed for this connection, like everything else here: the provider states or omits `acceptance` by the value it was sent, and Omni validates by that same value, not by a policy that has since moved — a change reaches the provider through a fresh `connect()`. |
|
|
2076
2081
|
| `timeZone` | The same value passed as `AuthenticationContext.timeZone`. The provider stores it on the agent and carries it on the identity. See **The agent's day**. |
|
|
2077
|
-
| `phone` | The same value passed as `AuthenticationContext.phone`: how this login hears its calls. The type ties `host.mute` to it: a `softphone` login's
|
|
2078
|
-
| `store` | The login's operational store, kept by the
|
|
2079
|
-
| `host` | The
|
|
2082
|
+
| `phone` | The same value passed as `AuthenticationContext.phone`: how this login hears its calls. The type ties `host.mute` to it: a `softphone` login's agent application states what its Mute does, and a desk-phone or conversation login's agent application cannot, so the omission is a compile error rather than a live seat's discovery. See **The station is the agent application's**. |
|
|
2083
|
+
| `store` | The login's operational store, kept by the agent application for the life of the login, across a reload of the agent application, and cleared at sign-out: where an adapter that composes a record keeps what its platform cannot hold for it, such as the interaction legs an agent application reported. Three functions, by key. Never for anything sensitive, which is `AuthenticationContext.secrets`, a store an agent application may clear aggressively. **A task's keys carry the task id and go with the task**: a key written about a task names the task's id in the key, and is deleted before the task's end is published, because a platform retires a task id minutes after closing it and a requeue takes seconds, so a key that outlives its task is inherited by the next offer of the same id -- a record with legs the agent application never reported against it. A login-scoped key carries no task id and outlives any task. The harness requires the store of every connection (`store.shape`, `store.get`, `.set`, `.delete`), watches the one it hands over, and names a task's key still held after `task-ended` (`drive.store.retained`) or written about the task after its end -- a persist hung off a timer that saw the task as it was (`drive.store.late`). The ordinary late write is the agent application's, not the adapter's: an agent application reports a leg without waiting for the answer, so an unmute can follow `complete` by a tick, and the adapter answers a report about a task that has ended `failed` with `omni.task-not-found` and writes nothing -- the agent application ends its own open legs at the task's end, so such a report is the agent application's error to see, and an agent application reads every `recordStep` answer and awaits the one for the leg it closes at a task's end, the only moment a refusal is expected, since a report nobody waits for is an error nobody can see; an adapter that names no task in its keys gets no cleanup check, which is a gap rather than a pass, never an exemption: the obligation is that nothing of a closed task survives its ending, and an adapter that keeps every open task in one login-scoped value owes exactly that inside the value, where the harness cannot look. One key per task, named for it, is the shape the harness can hold, and the shape to reach for. The store lists nothing, so an adapter that needs to find its tasks keeps a login-scoped index of ids beside them; at the task's end it deletes the body first and reindexes after, since a crash between the two then leaves an index naming a task with no body, which a reader skips, where the other order leaves a body for a task that has ended, which is the hazard itself. A reader of the index tolerates an id with no body as an ending that was underway, not as corruption. |
|
|
2084
|
+
| `host` | The agent application's report of the agent's station — devices, permissions, network — to consult before declaring the agent ready to the platform, and on every change. See **The agent application reports, the adapter decides**. |
|
|
2080
2085
|
| `signal` | Optional cancellation signal. Stop startup promptly when aborted and do not begin new work. |
|
|
2081
2086
|
| `log` | Optional structured logging callback. Never include credentials, tokens, or sensitive contact data. |
|
|
2082
2087
|
|
|
@@ -2088,12 +2093,12 @@ target, an answer streak, a per-queue count for today -- anything bucketed by da
|
|
|
2088
2093
|
by somebody's day, and a platform that was never told whose uses its own. An agent in Chennai
|
|
2089
2094
|
then finds their day rolling at 05:30, and a night shift in Chicago lands in two buckets.
|
|
2090
2095
|
|
|
2091
|
-
So the
|
|
2096
|
+
So the agent application says whose day it is, and it says so first. `AuthenticationContext.timeZone` is the
|
|
2092
2097
|
agent's zone as an IANA name -- `Asia/Kolkata`, `America/Chicago` -- never an offset, since an
|
|
2093
2098
|
offset cannot survive a daylight-saving boundary and a day boundary is exactly where that bites
|
|
2094
2099
|
(`context.timeZone`). It is stated before any identity exists, and `ConnectContext.timeZone` is the
|
|
2095
2100
|
same value again. The provider **stores it on the agent** and carries it as `identity.timeZone` on
|
|
2096
|
-
every `authenticated` state and on every `User` it returns from `
|
|
2101
|
+
every `authenticated` state and on every `User` it returns from `getUserDetails()`, so a lead
|
|
2097
2102
|
reading a colleague's yesterday sees the colleague's yesterday and a summary is bucketed by the
|
|
2098
2103
|
right day after a session has ended. A roaming agent corrects it by signing in from where they are.
|
|
2099
2104
|
|
|
@@ -2102,12 +2107,12 @@ field to fill in later: an identity without one is refused (`authentication.iden
|
|
|
2102
2107
|
so is a zone that is not an IANA name, wherever it appears. Nothing is ever assumed in its place --
|
|
2103
2108
|
not the viewer's browser, which gives a different answer per reader for the same record, and not the
|
|
2104
2109
|
provider's clock. The harness holds a provider to the round trip: the identity carries the zone the
|
|
2105
|
-
|
|
2110
|
+
agent application stated (`authentication.identity.timeZone.republished`), judged by what the name denotes:
|
|
2106
2111
|
`Asia/Kolkata` and `Asia/Calcutta` are one zone, and a provider that keeps the canonical name has
|
|
2107
2112
|
kept the zone. **The round trip is not the store.**
|
|
2108
2113
|
An adapter that echoes the stated zone back onto the identity passes that check with nothing kept,
|
|
2109
2114
|
and a lead reading a colleague's day would still get the wrong one. What proves the store is a zone
|
|
2110
|
-
the run never sent: a colleague's `User` from `
|
|
2115
|
+
the run never sent: a colleague's `User` from `getUserDetails()` carrying theirs. A provider's own
|
|
2111
2116
|
tests are where that is shown, with a second agent whose zone arrived through another session.
|
|
2112
2117
|
|
|
2113
2118
|
### Who the agent is
|
|
@@ -2131,7 +2136,7 @@ a capability it agrees with the login: a lead's snapshot carries `team`, nobody
|
|
|
2131
2136
|
| --- | --- |
|
|
2132
2137
|
| `transport` | Current `TransportStatus` — whether this provider's transport can serve the login. Defined under **`transport-status`**. |
|
|
2133
2138
|
| `loginId` | Identity of this login. It must match the connection context. |
|
|
2134
|
-
| `break` | Complete break state, including
|
|
2139
|
+
| `break` | Complete break state, including status, whether the agent may ask, reasons, retry details, and any forced break. |
|
|
2135
2140
|
| `tasks` | Complete set of tasks currently offered to or owned by this agent. |
|
|
2136
2141
|
| `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
2142
|
| `contacts` | Required complete contact contribution when the manifest declares `contacts`; `[]` clears it. Omitted only when it does not. |
|
|
@@ -2150,10 +2155,10 @@ surface in one place, and what obliges an adapter to implement each one.
|
|
|
2150
2155
|
| `checkTime(request)` | The manifest declares `timeCheck: true`. Read-only clock check; no task command or accuracy guarantee. |
|
|
2151
2156
|
| `subscribe(listener)` | Always. |
|
|
2152
2157
|
| `disconnect()` | Always. |
|
|
2153
|
-
| `refused(report)` | Always. The
|
|
2158
|
+
| `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**. |
|
|
2154
2159
|
| `setCapacity(capacity)` | Always. Nothing may be allocated until a capacity is stated, so there is no connection that does not receive it. |
|
|
2155
2160
|
| `execute(request)` | Always. Every channel has commands no capability gates — see **Which commands need a capability**. |
|
|
2156
|
-
| `
|
|
2161
|
+
| `getUserDetails(ids)` | The adapter publishes any `UserId`: on `ForcedBreak.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
2162
|
| `dial(request)` | The manifest declares `idleCapabilities.dial`, and with it `dialOutcomes`. |
|
|
2158
2163
|
| `requestBreak(request)` | The login declares `capabilities.breaks`. |
|
|
2159
2164
|
| `commitBreak()` | The login declares `capabilities.breaks`. Commit and cancel are not optional halves of it. |
|
|
@@ -2163,7 +2168,7 @@ surface in one place, and what obliges an adapter to implement each one.
|
|
|
2163
2168
|
| `executeTeamLeadAssist(command)` | The login declares `capabilities.team.leadAssistControl`. |
|
|
2164
2169
|
| `executeTeamListen(command)` | The login declares `capabilities.team.listeningControl`. |
|
|
2165
2170
|
| `setPreference(request)` | The login declares `capabilities.preferences`: the person's choice has to have somewhere to go. |
|
|
2166
|
-
| `recordStep(report)` | The manifest lists `softphone` among its `phones`. On a softphone the
|
|
2171
|
+
| `recordStep(report)` | The manifest lists `softphone` among its `phones`. On a softphone the agent application mutes its own microphone on any call, and the provider's record has to have somewhere to take that leg; a desk phone's microphone is the phone's. See **The agent application records what it performs**. |
|
|
2167
2172
|
| `executeTeamPolicy(command)` | The login declares `capabilities.team.policyControl`. |
|
|
2168
2173
|
| `openMedia(request)` | The manifest lists `softphone` among its `phones`. On a softphone the call's audio lands in Omni, so the adapter has to open it; a platform of desk phones alone never does. |
|
|
2169
2174
|
|
|
@@ -2203,12 +2208,12 @@ Registers a listener for provider changes and returns an idempotent unsubscribe
|
|
|
2203
2208
|
while disconnected without requiring a durable event log.
|
|
2204
2209
|
- After unsubscribe, the listener must receive no further events.
|
|
2205
2210
|
|
|
2206
|
-
### `Connection.
|
|
2211
|
+
### `Connection.getUserDetails(ids)`
|
|
2207
2212
|
|
|
2208
2213
|
Turns `UserId` values into something an agent can read.
|
|
2209
2214
|
|
|
2210
2215
|
```ts
|
|
2211
|
-
|
|
2216
|
+
getUserDetails(ids: UserId[]): Promise<User[]>
|
|
2212
2217
|
```
|
|
2213
2218
|
|
|
2214
2219
|
Required of any adapter that publishes a `UserId` — on `ForcedBreak.by`, a team member list, or
|
|
@@ -2243,14 +2248,14 @@ Stops the connection and releases adapter-owned resources.
|
|
|
2243
2248
|
The task-allocation lifecycle has five ordered stages:
|
|
2244
2249
|
|
|
2245
2250
|
**1. The agent signs in.** Nothing on the wire says ready or not-ready: the agent is not ready
|
|
2246
|
-
until the
|
|
2251
|
+
until the agent application has stated a capacity, and ready once it has. A provider allocates nothing before a
|
|
2247
2252
|
capacity is stated, and needs no other word for it.
|
|
2248
2253
|
|
|
2249
|
-
**2. The agent becomes ready.** The
|
|
2254
|
+
**2. The agent becomes ready.** The agent application states the capacity when its local policy says: at once,
|
|
2250
2255
|
or when the agent presses Ready. A successful connection, a healthy provider, or the absence of a
|
|
2251
2256
|
break does not imply readiness. After that, the only way an agent stops taking work is a break,
|
|
2252
2257
|
under `capabilities.breaks`; a login that declares no breaks cannot go not-ready once its capacity
|
|
2253
|
-
is stated, and `count: 0` is the
|
|
2258
|
+
is stated, and `count: 0` is the agent application's division of capacity, not the agent's choice.
|
|
2254
2259
|
|
|
2255
2260
|
**3. Omni states their concurrent capacity.** Only now does Omni ask providers for work, saying how
|
|
2256
2261
|
much the agent can take at once. It is a standing declaration rather than a poll: Omni restates the
|
|
@@ -2271,13 +2276,13 @@ intent.
|
|
|
2271
2276
|
|
|
2272
2277
|
During login, Omni sends the agent's `autoAcceptTasks` value to the provider. When it is `true`, the
|
|
2273
2278
|
provider states `acceptance` on each pending task — on the task rather than the offer, so a
|
|
2274
|
-
reconnect snapshot says it too and an offer the
|
|
2279
|
+
reconnect snapshot says it too and an offer the agent application never received is not accepted on the
|
|
2275
2280
|
person's behalf for want of a word:
|
|
2276
2281
|
|
|
2277
2282
|
| Directive | Contract |
|
|
2278
2283
|
| --- | --- |
|
|
2279
2284
|
| `no-preference` | The provider leaves acceptance to Omni; with `autoAcceptTasks: true`, Omni accepts automatically. |
|
|
2280
|
-
| `consent` | The provider requires the person's explicit consent: Omni presents **Accept** and waits, whatever its own policy would have done.
|
|
2285
|
+
| `consent` | The provider requires the person's explicit consent: Omni presents **Accept** and waits, whatever its own policy would have done. An agent application that declares `guarantees.personConsent` promises exactly this; a provider checks it before offering work only a person may take. |
|
|
2281
2286
|
| `automatic` | Omni accepts immediately without agent interaction. |
|
|
2282
2287
|
|
|
2283
2288
|
When Omni sent `autoAcceptTasks: false`, the provider omits `acceptance` and every task the
|
|
@@ -2287,20 +2292,20 @@ no-auto-accept policy puts no word on the wire at all — the field is absent, a
|
|
|
2287
2292
|
press is Omni's doing, not the provider's.
|
|
2288
2293
|
|
|
2289
2294
|
**The value is stated, never assumed.** `autoAcceptTasks` is required of every connection: a
|
|
2290
|
-
|
|
2295
|
+
agent application says which policy the deployment provisioned, and a validator that is not told checks
|
|
2291
2296
|
neither rule rather than guess the permissive one (`task.acceptance.required` and
|
|
2292
2297
|
`.unexpected` fire only against a stated value). `acceptance` is the provider's own control and
|
|
2293
2298
|
outranks the policy: `consent` puts the decision back in the agent's hands for any task where it
|
|
2294
|
-
belongs, whatever the
|
|
2299
|
+
belongs, whatever the agent application was configured with.
|
|
2295
2300
|
|
|
2296
2301
|
An automatically accepted task still arrives through `task-offered`.
|
|
2297
2302
|
|
|
2298
2303
|
**Work the agent originated is accepted by the command that created it.** A task born of the
|
|
2299
|
-
agent's own act — a dialpad call or a connect-back, recognisable by the
|
|
2304
|
+
agent's own act — a dialpad call or a connect-back, recognisable by the agent application's `dialId` on
|
|
2300
2305
|
`onCall`; a lead's join, carrying `assisting`; a lead's listen, carrying `listening` — arrives
|
|
2301
2306
|
through `task-offered` with `acceptance: "automatic"` whatever `autoAcceptTasks` says, and the
|
|
2302
|
-
validator holds it there under either
|
|
2303
|
-
the desk shows no **Accept** for a call the agent placed. The
|
|
2307
|
+
validator holds it there under either local policy and under none (`task.acceptance.originated`):
|
|
2308
|
+
the desk shows no **Accept** for a call the agent placed. The local policy governs work the queue
|
|
2304
2309
|
routes to the agent, and nothing else.
|
|
2305
2310
|
|
|
2306
2311
|
### Pending
|
|
@@ -2321,7 +2326,7 @@ const allocation = {
|
|
|
2321
2326
|
```
|
|
2322
2327
|
|
|
2323
2328
|
The rule the phase exists to express: **nothing is acquired on the agent's behalf while a task
|
|
2324
|
-
is pending.**
|
|
2329
|
+
is pending.** An agent application that carries media must not open the microphone until the task is
|
|
2325
2330
|
accepted. Omni does not open the task's browsers either — a task that rings out costs nothing.
|
|
2326
2331
|
|
|
2327
2332
|
When consent is required, Omni offers the agent an **Accept** control. The call is the
|
|
@@ -2374,25 +2379,25 @@ time. Runtime conformance checks also require the task channel to match its prov
|
|
|
2374
2379
|
| Field | Contract |
|
|
2375
2380
|
| --- | --- |
|
|
2376
2381
|
| `id` | Required `TaskId`, the platform's own identity for the task. Unique among the tasks open at once; a platform retires an id minutes after closing it, so the same id comes back for another customer. Omni scopes it with the provider ID. |
|
|
2377
|
-
| `allocationId` | Required `AllocationId`: this life of the task, minted once per offer and never reused for the life of the login, whatever the id does. Every event, command and report that names a task names its allocation too, so a late dial outcome or a late interaction report for the first customer never lands on the next under the same id. The stream refuses an offer reusing one (`stream.taskOffered.allocation`), an update restating another life of the id (`stream.taskUpdated.allocation`), and a media or ending event naming a life that has ended or that nobody has seen (`stream.allocation.ended`, `.unknown`); a `dial-outcome` may name an ended life, since a dial placed late routinely outlives its call, and the
|
|
2382
|
+
| `allocationId` | Required `AllocationId`: this life of the task, minted once per offer and never reused for the life of the login, whatever the id does. Every event, command and report that names a task names its allocation too, so a late dial outcome or a late interaction report for the first customer never lands on the next under the same id. The stream refuses an offer reusing one (`stream.taskOffered.allocation`), an update restating another life of the id (`stream.taskUpdated.allocation`), and a media or ending event naming a life that has ended or that nobody has seen (`stream.allocation.ended`, `.unknown`); a `dial-outcome` may name an ended life, since a dial placed late routinely outlives its call, and the agent application routes it there. See **A task's life on the wire**. |
|
|
2378
2383
|
| `title` | Agent-facing task title. |
|
|
2379
2384
|
| `channel` | Channel used for this task. It must equal the source provider's manifest channel. |
|
|
2380
2385
|
| `taskType` | Required provider-defined source or category of work, such as a voice `Queue Name`, `Mailbox Folder`, `Chat Source`, `Support`, `Billing`, or `Returns`. |
|
|
2381
2386
|
| `capabilities` | Controls and workspace features available for this specific task. |
|
|
2382
|
-
| `capabilitySource` | Required. Who chose the capabilities: `queue` when somebody configured these terms, `ungoverned` when nothing handed the work over -- an agent's own outbound -- and `undetermined` when a queue was named and its terms could not be read, in which case `capabilities` is what the provider will honour, not what the platform permits.
|
|
2387
|
+
| `capabilitySource` | Required. Who chose the capabilities: `queue` when somebody configured these terms, `ungoverned` when nothing handed the work over -- an agent's own outbound -- and `undetermined` when a queue was named and its terms could not be read, in which case `capabilities` is what the provider will honour, not what the platform permits. An agent application shows `undetermined` where the agent works. See **Task capabilities**. |
|
|
2383
2388
|
| `browsers` | Named browser definitions for the task workspace: at least one when the task declares the `browsers` capability, empty when it does not. |
|
|
2384
2389
|
| `party` | The person or entity on the other end of this task, as a `Contact`: often a name and one address; a withheld caller ID may leave nothing to send at all. Optional. The party is who the task is *with*; `contacts` is the directory. |
|
|
2385
2390
|
| `phase` | Current canonical task phase: `pending`, `confirmed`, `preview`, `in-progress`, `paused`, or `completing`. `preview` is voice only. |
|
|
2386
|
-
| `media` | Voice only. The task's real-time audio as the provider holds it: `started` while audio is attached, `ended` once it ended, omitted while none is. The provider's word — see **`task-media-started`**. Media names a task whose work has begun, or whose party the
|
|
2391
|
+
| `media` | Voice only. The task's real-time audio as the provider holds it: `started` while audio is attached, `ended` once it ended, omitted while none is. The provider's word — see **`task-media-started`**. Media names a task whose work has begun, or whose party the agent application is dialling: on a `pending`, `confirmed` or `preview` task with nobody ringing it is refused (`task.media.beforeWork`), on a snapshot as on the event, since an agent application opens the microphone on it; with the party ringing by an agent application dial or provider-triggered preview dial, actual ring-back media may precede answer. |
|
|
2387
2392
|
| `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**. |
|
|
2388
2393
|
| `previewEndsAt` | Voice only, in `preview`: the preparation target instant. Absent, the agent has as long as they need without a preparation countdown. Always with `atDeadline`. See **Preview: the agent presses Call**. |
|
|
2389
|
-
| `atDeadline` | Voice only, in `preview`, with `previewEndsAt`: what the system does at the deadline -- `calls` makes the provider initiate dialing, `host-calls` makes the
|
|
2394
|
+
| `atDeadline` | Voice only, in `preview`, with `previewEndsAt`: what the system does at the deadline -- `calls` makes the provider initiate dialing, `host-calls` makes the agent application issue Call, `waits` keeps the task in preview awaiting the agent. |
|
|
2390
2395
|
| `reference` | Optional agent-facing reference such as a case, call, conversation, ticket, or message number. It is distinct from the protocol `id`. |
|
|
2391
2396
|
| `completionMode` | `agent-command` waits for the channel's `complete` command; `provider-automatic` completes without one. |
|
|
2392
2397
|
| `wrapAllowance` | Fixed time allowed to complete the task after primary interaction ends. For real-time media, it begins after `task-media-ended`. Required under `provider-automatic`, where the provider acts on it. Optional under `agent-command`: omitted says the provider imposes no deadline, and Omni counts nothing down. |
|
|
2393
2398
|
| `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. |
|
|
2394
2399
|
| `interactionHistory` | 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**. |
|
|
2395
|
-
| `onCall` | Voice only. Who is on the call, or being brought onto it, as the provider states it, replaced whole with the task: `party` is the customer -- carrying a `stage` while being dialled again on the same task, a connect-back with the
|
|
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, `consulted` and `conferenced` somebody a dial is bringing in, listed from the moment the dial is placed -- with the `destinationId` dialled, the `dialId` where an agent application placed it, the `stage` reached (`ringing` until answered, `joined` after), and `held: true` on anyone joined and parked. A `consulted` entry is what makes `transfer` `complete` and `cancel` issuable. `label` names a destination -- a person, a queue -- not a phrase; the agent application supplies the verb. Present when the provider knows the room, absent when it does not. See **Every dial has an outcome**. |
|
|
2396
2401
|
| `leadAssist` | Voice only. Present from the agent's request for a lead until the lead leaves or the request ends: `requested` while nobody has joined, `joined` with the lead's `leadId` once somebody has. See **Lead assist**. |
|
|
2397
2402
|
| `assisting` | Voice only, on the lead's own task for a call they joined: which member asked, with their note. Its presence is what makes `lead-assist` `take-over-call` and `leave` issuable. See **Lead assist**. |
|
|
2398
2403
|
| `listening` | Voice only, on the lead's own task while they listen to a member's call: whose call, which call, and the `mode` they are heard in, restated on every change. Never on the member's task, and never together with `assisting`. See **Listening to a call**. |
|
|
@@ -2448,11 +2453,11 @@ requeue takes seconds, and on one platform a call was offered twice, thirteen se
|
|
|
2448
2453
|
one id, through a close and a re-offer. Everything that names a task after the fact -- a dial
|
|
2449
2454
|
outcome, a media event, an ending, an interaction report, a command -- would land on whichever life of
|
|
2450
2455
|
the id is open when it arrives. So every offer mints an `allocationId`, unique for the life of the
|
|
2451
|
-
login, and every one of those names it beside the `taskId`. The
|
|
2456
|
+
login, and every one of those names it beside the `taskId`. The agent application keys its state on the pair, a
|
|
2452
2457
|
late event finds the life it belongs to or is refused, and a task-scoped browser session
|
|
2453
2458
|
(`PROVIDER_NAME__TASK_ID__TAB_NAME`) lives one allocation, never the next customer's cookies under
|
|
2454
|
-
a reused id. A dial the
|
|
2455
|
-
`dialId`; where the allocation earns its place is everything the
|
|
2459
|
+
a reused id. A dial the agent application placed was already safe, since its outcome is placed by the agent application's own
|
|
2460
|
+
`dialId`; where the allocation earns its place is everything the agent application does not mint --
|
|
2456
2461
|
`task-media-started`, `task-media-ended`, `task-ended`, a `recordStep` naming a task -- any of which
|
|
2457
2462
|
would otherwise find the new task under the old id and act on it. The allocation is part of the task
|
|
2458
2463
|
the adapter keeps, not a field held in memory beside it: a rebuilt adapter comes back with the same
|
|
@@ -2486,9 +2491,9 @@ const previewed = {
|
|
|
2486
2491
|
const pressed: TaskCommand<"voice"> = { type: "call", dialId: "dial-7f2" };
|
|
2487
2492
|
```
|
|
2488
2493
|
|
|
2489
|
-
**Call is a dial.** It carries the
|
|
2494
|
+
**Call is a dial.** It carries the agent application's `dialId`, is answered `dialling`, and ends in exactly one
|
|
2490
2495
|
`dial-outcome`; the phase is its gate and there is no capability, since a record put in front of an
|
|
2491
|
-
agent is there to be called. Its media starts on `dialling`, as every
|
|
2496
|
+
agent is there to be called. Its media starts on `dialling`, as every agent application-placed dial's does --
|
|
2492
2497
|
ring-back is audio the agent hears -- so `task-media-started` arrives on the `preview` task with
|
|
2493
2498
|
its party ringing. On `answered` the task is `in-progress`; on any other outcome the media ends,
|
|
2494
2499
|
`task-media-ended` starts the wrap clock as on every voice task, and the task goes to
|
|
@@ -2503,8 +2508,8 @@ carries the provider's authoritative preparation-end instant and exactly one tri
|
|
|
2503
2508
|
|
|
2504
2509
|
| Declaration | At preparation end |
|
|
2505
2510
|
| --- | --- |
|
|
2506
|
-
| `atDeadline: "calls"` | The provider initiates dialing. The
|
|
2507
|
-
| `atDeadline: "host-calls"` | The
|
|
2511
|
+
| `atDeadline: "calls"` | The provider initiates dialing. The agent application never sends a timer-triggered Call. |
|
|
2512
|
+
| `atDeadline: "host-calls"` | The agent application sends the ordinary `call` command with a fresh agent application `dialId`. The provider does not independently auto-dial this task. |
|
|
2508
2513
|
| `atDeadline: "waits"` | Neither side auto-dials. The task remains in preview until the agent presses Call. |
|
|
2509
2514
|
|
|
2510
2515
|
The agent may press Call early in either fixed-preparation dialing mode, or at any time in
|
|
@@ -2518,14 +2523,14 @@ answer at that exact instant. Scheduling/dispatch delay must remain visible, and
|
|
|
2518
2523
|
prevented initiation must be reported rather than leaving a silently expired countdown.
|
|
2519
2524
|
A source eligibility threshold that may never trigger an attempt is not this promise.
|
|
2520
2525
|
|
|
2521
|
-
The two deadline fields travel together and only in preview. The
|
|
2522
|
-
time; clock estimation is allowed only under the explicit
|
|
2526
|
+
The two deadline fields travel together and only in preview. The agent application uses trusted provider-domain
|
|
2527
|
+
time; clock estimation is allowed only under the explicit agent application-triggered preview exception below.
|
|
2523
2528
|
Without usable provider-domain time
|
|
2524
2529
|
it cannot safely auto-trigger and must expose the uncertainty and reconcile. No zero-duration,
|
|
2525
2530
|
receipt-time or local-default deadline is inferred. Neither elapsed time nor submission changes
|
|
2526
2531
|
the task phase: subsequent authoritative events state ringing, answer, media and completion.
|
|
2527
2532
|
|
|
2528
|
-
|
|
2533
|
+
An agent application must serialize manual clicks and its timer under the exact provider/login/task/allocation
|
|
2529
2534
|
scope, allowing at most one unresolved submission. Recheck the current phase, deadline owner,
|
|
2530
2535
|
allocation and whether an attempt already exists before dispatch. A new snapshot or policy update
|
|
2531
2536
|
cancels an obsolete timer; login loss, disconnect, task end and allocation replacement fence its
|
|
@@ -2534,28 +2539,28 @@ blindly submit again. An uncertain prior submission remains unresolved, without
|
|
|
2534
2539
|
The provider atomically arbitrates any residual manual/timer race and does not accept a second
|
|
2535
2540
|
command as ownership of an already-running independent attempt.
|
|
2536
2541
|
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
dial has no invented
|
|
2542
|
+
Agent application-triggered automatic dialing is an ordinary agent application dial: its result is `dialling` with the same
|
|
2543
|
+
agent application `dialId`, followed by exactly one correlated terminal `dial-outcome`. A provider-triggered
|
|
2544
|
+
dial has no invented agent application ID or agent application dial outcome. It reports source-evidenced party ringing,
|
|
2540
2545
|
actual media and customer answer separately. In preview with `atDeadline: "calls"`, a ringing
|
|
2541
|
-
party without
|
|
2546
|
+
party without an agent application ID may carry actual pre-answer media; the deadline alone permits no media.
|
|
2542
2547
|
Media is still introduced/ended by the corresponding events and must not imply customer answer.
|
|
2543
2548
|
Submission acceptance is never an answered outcome or permission to publish `in-progress` early.
|
|
2544
2549
|
Unsupported source correlation/outcome/media evidence remains an integration blocker.
|
|
2545
2550
|
|
|
2546
2551
|
**Drop both fields when the phase moves.** A provider that builds the `in-progress` task by
|
|
2547
|
-
spreading the `preview` one carries `previewEndsAt` and `atDeadline` with it, and the
|
|
2552
|
+
spreading the `preview` one carries `previewEndsAt` and `atDeadline` with it, and the agent application refuses
|
|
2548
2553
|
the update (`task.preview.deadline.unexpected`): the desk keeps the task as it last stood, and the
|
|
2549
2554
|
provider is told through `refused` exactly which rule, so the state that looked right on its side
|
|
2550
2555
|
is named on its side. The task past preview has no deadline to wait for, so it carries neither
|
|
2551
2556
|
field.
|
|
2552
2557
|
|
|
2553
|
-
**Provider execution and
|
|
2558
|
+
**Provider execution and agent application controls are distinct.** Answer, hold/resume, transfer (including
|
|
2554
2559
|
IVR routing), conference and interaction completion are executed by the provider
|
|
2555
2560
|
through the adapter. The task's applicable capability, phase, completion mode and command-specific
|
|
2556
|
-
prerequisites decide which controls are offered. Caller disconnect also executes at the provider. Microphone mute is
|
|
2557
|
-
TaskCommand or task capability. The separately declared
|
|
2558
|
-
recording contract; this distinction does not remove it or move provider call operations to the
|
|
2561
|
+
prerequisites decide which controls are offered. Caller disconnect also executes at the provider. Microphone mute is agent application-owned, never a provider
|
|
2562
|
+
TaskCommand or task capability. The separately declared agent application recording facility keeps its existing
|
|
2563
|
+
recording contract; this distinction does not remove it or move provider call operations to the agent application.
|
|
2559
2564
|
|
|
2560
2565
|
A caller journey may revisit the same agent while an earlier interaction still wraps. Public task
|
|
2561
2566
|
IDs must remain distinct for concurrently open interactions. For source-owned interaction IDs allocated
|
|
@@ -2615,7 +2620,7 @@ whether the channel carries real-time media:
|
|
|
2615
2620
|
| Other non-media channels | The `task-updated` that moves the task to `completing` |
|
|
2616
2621
|
|
|
2617
2622
|
**Off voice, `completing` is the provider's word that interaction ended.** There is no media event to
|
|
2618
|
-
carry it, so the phase does;
|
|
2623
|
+
carry it, so the phase does; an agent application starts the wrap clock at that publication and nowhere else. It
|
|
2619
2624
|
is optional: a conversation with nothing to wrap moves from `in-progress` to `task-ended` and
|
|
2620
2625
|
`completing` is never published. Under `provider-automatic` with a non-zero `wrapAllowance` it is
|
|
2621
2626
|
required, because the clock has to start somewhere: a chat or email task the provider completes
|
|
@@ -2661,7 +2666,7 @@ A task that declares `connectBack` lets the agent connect back to the party whil
|
|
|
2661
2666
|
whoever placed the call in the first place. Most such calls came in, so this is not a redial: the
|
|
2662
2667
|
agent rings a party who rang them. Omni issues `{ type: "connect-back", dialId }`; it is issuable
|
|
2663
2668
|
only in `completing`, and only where the capability is declared. The provider knows who the party
|
|
2664
|
-
is; the command carries no destination, and it is a dial like any other, so it carries the
|
|
2669
|
+
is; the command carries no destination, and it is a dial like any other, so it carries the agent application's
|
|
2665
2670
|
`dialId`, is answered `dialling`, and ends in one `dial-outcome`.
|
|
2666
2671
|
|
|
2667
2672
|
On `dialling` the provider is placing the call and the task returns to `in-progress`: the agent is
|
|
@@ -2672,15 +2677,15 @@ whose outcome says so and a call whose media ended: the task returns to `complet
|
|
|
2672
2677
|
same event and the clock starts again from there. At no point is an agent dialling against a
|
|
2673
2678
|
deadline.
|
|
2674
2679
|
|
|
2675
|
-
**The room shows the party being dialled.** From the moment any
|
|
2680
|
+
**The room shows the party being dialled.** From the moment any agent application dial places the party -- a
|
|
2676
2681
|
dialpad call, a preview Call, a connect-back -- the `party` entry carries `stage: "ringing"` and
|
|
2677
|
-
the
|
|
2682
|
+
the agent application's `dialId`, exactly as a consulted or conferenced entry does from its dial; `joined` on
|
|
2678
2683
|
the publication that follows the answered `dial-outcome` and never before it
|
|
2679
2684
|
(`stream.taskUpdated.stage`); and no stage from the next publication on, so `joined` is transient
|
|
2680
|
-
and a later copy still carrying it is stale (`stream.taskUpdated.stage.lingering`).
|
|
2685
|
+
and a later copy still carrying it is stale (`stream.taskUpdated.stage.lingering`). An agent application shows a
|
|
2681
2686
|
call being placed, not a party it asserts is on a call that is still ringing. A callback the platform places itself on the same task -- the first
|
|
2682
2687
|
call over, the platform dialling the party again without a command -- shows the same thing with no
|
|
2683
|
-
|
|
2688
|
+
agent application `dialId`, as any platform-placed dial does. A party with no stage is on the call, and a dial
|
|
2684
2689
|
with no stage is half a claim (`task.onCall.party.dial`). The stage is also what lets the stream
|
|
2685
2690
|
tell a task coming back from one going backwards (`stream.taskUpdated.phase`).
|
|
2686
2691
|
|
|
@@ -2776,19 +2781,19 @@ task says whether a leg can still be running: a hold runs only while the task is
|
|
|
2776
2781
|
only while its media is up, so a `held` entry without `seconds` on a task that is not paused, or a
|
|
2777
2782
|
`muted` one on a call that is over, is a leg nobody closed and reads exactly like a leg running now
|
|
2778
2783
|
(`task.interactionHistory.held.open`, `.muted.open`). Whoever performs the leg closes it — the
|
|
2779
|
-
provider whose platform parks the caller, the
|
|
2784
|
+
provider whose platform parks the caller, the agent application whose microphone it is — by restating the entry
|
|
2780
2785
|
with its duration, and **an entry that cannot be closed is not written**: open for ever is a
|
|
2781
2786
|
plausible nought one level down from a total. **A leg still open when the media ends is closed by
|
|
2782
2787
|
the provider**, at that instant, in the same publication that says the media ended: the provider
|
|
2783
|
-
is the one that knows the instant, and the
|
|
2784
|
-
muted, so the
|
|
2785
|
-
from the leg's `at` to the media's end, and the
|
|
2788
|
+
is the one that knows the instant, and the agent application can only learn of it afterwards. Agents end calls
|
|
2789
|
+
muted, so the agent application's leg is routinely open at media end; the provider closes it with the duration
|
|
2790
|
+
from the leg's `at` to the media's end, and the agent application's own closing report for that leg, which
|
|
2786
2791
|
follows what it hears, is answered `recorded` and changes nothing, the entry standing as the
|
|
2787
2792
|
provider closed it. One publisher of the closed leg, and no order between them to get right. There is no resumed step; a resume is the end of a
|
|
2788
2793
|
hold, at the entry's `at` plus its `seconds`, and nothing is lost by not naming it twice. The same goes for every step: two mutes are two
|
|
2789
2794
|
`muted` entries, and a call that joined two queues -- a menu's, then this one -- has two `queued`
|
|
2790
2795
|
entries, one per join. Order is enforced: an entry earlier than the one before it is refused
|
|
2791
|
-
(`task.interactionHistory.order`). **Intervals between steps are the
|
|
2796
|
+
(`task.interactionHistory.order`). **Intervals between steps are the agent application's to subtract**, never a
|
|
2792
2797
|
total the provider adds: *time to offer*, from the call's arrival to the agent's screen lighting
|
|
2793
2798
|
up, is the `offered` entry's `at` minus the last `queued` entry's, and the ring is outside it.
|
|
2794
2799
|
`queueSeconds` keeps the industry's meaning -- the whole wait until somebody answered -- and is not
|
|
@@ -2798,32 +2803,32 @@ grain: rounded to the nearest second, and a leg that rounds to nought is stated
|
|
|
2798
2803
|
being under the contract's resolution, where dropping the entry would say the leg never happened.
|
|
2799
2804
|
|
|
2800
2805
|
**A record once read is not unread.** Every restatement of a task's record -- on `task-updated`,
|
|
2801
|
-
on a resync snapshot -- carries every entry the
|
|
2806
|
+
on a resync snapshot -- carries every entry the agent application has already read, by step and instant, and
|
|
2802
2807
|
may add to them; it never has fewer, and never drops the record while the task is open. A fact
|
|
2803
|
-
stated to the
|
|
2808
|
+
stated to the agent application and then withdrawn without an event is a record contradicting itself, exactly
|
|
2804
2809
|
as a task that lost the terms it had read would be, and the stream refuses it the same way
|
|
2805
2810
|
(`stream.taskUpdated.interactionHistory`, `stream.snapshot.interactionHistory`). A provider whose
|
|
2806
|
-
platform cannot hold a leg the
|
|
2807
|
-
task, so a reload of the
|
|
2811
|
+
platform cannot hold a leg the agent application reported keeps it in the login's `store` for the life of the
|
|
2812
|
+
task, so a reload of the agent application restates the same record; the entry is keyed by `step` and `at`, so
|
|
2808
2813
|
a running hold restated with its final `seconds` is the same entry.
|
|
2809
2814
|
|
|
2810
|
-
**The provider owns the timestamps in its record.**
|
|
2811
|
-
may retain
|
|
2812
|
-
copy the
|
|
2815
|
+
**The provider owns the timestamps in its record.** Agent application timestamps are advisory. The provider
|
|
2816
|
+
may retain an agent application instant it accepts or assign its own observation/receipt instant; it need not
|
|
2817
|
+
copy the agent application clock. A provider declaring `timestampAuthority: "provider"` uses its own timestamps
|
|
2813
2818
|
for final records. Receipt time is an observation boundary, not a claim about when the physical
|
|
2814
|
-
|
|
2815
|
-
rewriting of an already-published entry when a later
|
|
2819
|
+
agent application action happened. Records are ordered by their published provider-selected instants, with no
|
|
2820
|
+
rewriting of an already-published entry when a later agent application report arrives.
|
|
2816
2821
|
|
|
2817
|
-
The incoming
|
|
2822
|
+
The incoming agent application `step` and `at`, scoped to the task and its current life, identify one reported
|
|
2818
2823
|
leg for correlation only. The provider retains a binding from that key to its chosen history
|
|
2819
2824
|
`at`. Every successful `recordStep` returns `{ status: "recorded", at }` with that same canonical
|
|
2820
|
-
history instant, even for subsequent duration/end reports. The
|
|
2821
|
-
report key, never the returned history timestamp. A changed
|
|
2825
|
+
history instant, even for subsequent duration/end reports. The agent application keeps sending the original
|
|
2826
|
+
report key, never the returned history timestamp. A changed agent application timestamp is not a correction
|
|
2822
2827
|
to the same report: it names a different leg. Bindings survive reconnect/reload for the retained
|
|
2823
|
-
task lifetime. The
|
|
2828
|
+
task lifetime. The agent application renders provider-published history unchanged. It never compares the
|
|
2824
2829
|
provider instant back to its own clock, substitutes its own timestamp, or adjusts later messages
|
|
2825
|
-
to match an earlier
|
|
2826
|
-
correlation/conformance only; it is not
|
|
2830
|
+
to match an earlier agent application estimate. The acknowledgment identifies the provider record for
|
|
2831
|
+
correlation/conformance only; it is not an application-side timestamp reconciliation instruction. Conflicting reuse is refused visibly, not paired by arrival time or nearest instant.
|
|
2827
2832
|
If two distinct entries would collide under the history's `(step, at)` key, do not invent a time
|
|
2828
2833
|
or combine them: report the representation conflict. No ambiguous event is silently accepted.
|
|
2829
2834
|
|
|
@@ -2833,17 +2838,17 @@ history — until the task's media ends, and a hold neither pauses nor resets it
|
|
|
2833
2838
|
duration is the `held` entry's `seconds`, and a desk that restarts its counter on resume is
|
|
2834
2839
|
counting the wrong thing.
|
|
2835
2840
|
|
|
2836
|
-
`muted` is there because the mute is the
|
|
2837
|
-
the
|
|
2838
|
-
account of a period the agent could not be heard. Each `muted` entry carries `mutedBy`, as the
|
|
2839
|
-
reported it: `host` for the agent's own Mute, `station` for a headset or system mute the
|
|
2841
|
+
`muted` is there because the mute is the agent application's and never the provider's — see **The station is
|
|
2842
|
+
the agent application's** — so without a step the provider, which alone keeps the task's record, would have no
|
|
2843
|
+
account of a period the agent could not be heard. Each `muted` entry carries `mutedBy`, as the agent application
|
|
2844
|
+
reported it: `host` for the agent's own Mute, `station` for a headset or system mute the agent application
|
|
2840
2845
|
observed. No other step has it (`task.interactionHistory.mutedBy`, `.mutedBy.unexpected`).
|
|
2841
2846
|
|
|
2842
2847
|
**A step that dialled says which dial and where.** `transferred`, `conferenced` and `unanswered`
|
|
2843
|
-
each end a dial, so the entry carries the `destinationId` it went to and, where
|
|
2844
|
-
`dialId` the
|
|
2848
|
+
each end a dial, so the entry carries the `destinationId` it went to and, where an agent application placed it, the
|
|
2849
|
+
`dialId` the agent application minted; no other step dialled, and either field on one is refused
|
|
2845
2850
|
(`task.interactionHistory.dialId.unexpected`). This is how a dial made before a transfer is placeable
|
|
2846
|
-
by whoever holds the task now: a `dial-outcome` naming a `dialId` the
|
|
2851
|
+
by whoever holds the task now: a `dial-outcome` naming a `dialId` the agent application never minted is looked
|
|
2847
2852
|
up in the record, not discarded.
|
|
2848
2853
|
|
|
2849
2854
|
Four rules a provider has to keep:
|
|
@@ -2859,7 +2864,7 @@ Four rules a provider has to keep:
|
|
|
2859
2864
|
the same way `ForcedBreak.by` does. It comes from this provider's own directory, the same
|
|
2860
2865
|
namespace as `AuthenticationState.identity.id` and the team member list, so entries pair
|
|
2861
2866
|
within a provider and never across one.
|
|
2862
|
-
- **A task carries no names.** Omni resolves what to display with `
|
|
2867
|
+
- **A task carries no names.** Omni resolves what to display with `getUserDetails()`. Two people
|
|
2863
2868
|
called Arun on one site is ordinary, and anything pairing entries on a display name pairs them
|
|
2864
2869
|
wrongly; carrying the name here would also copy it into every task and leave it to go stale.
|
|
2865
2870
|
|
|
@@ -2867,7 +2872,7 @@ Four rules a provider has to keep:
|
|
|
2867
2872
|
`queued` nobody takes part, so there is nothing to name. On every other step somebody did — see
|
|
2868
2873
|
`INTERACTION_STEPS_WITH_A_PERSON` and `interactionStepExpectsAPerson()` — so an absent `by` there says
|
|
2869
2874
|
*this was handled and the provider cannot say by whom*. The one step that is never unattributed is
|
|
2870
|
-
`muted`: the
|
|
2875
|
+
`muted`: the agent application has exactly one agent and the provider knows who, so `by` is required there
|
|
2871
2876
|
(`task.interactionHistory.muted.by`).
|
|
2872
2877
|
|
|
2873
2878
|
That case is ordinary rather than theoretical: a leg answered on a shared phone, a manager's
|
|
@@ -2876,7 +2881,7 @@ rather than dropping it.** A list missing a real handler looks complete and is w
|
|
|
2876
2881
|
worse than one saying plainly it could not attribute a leg — and far better than publishing
|
|
2877
2882
|
nothing because a single leg could not be named.
|
|
2878
2883
|
|
|
2879
|
-
|
|
2884
|
+
An agent application must render the two differently. Showing an unattributed `answered` the same way as
|
|
2880
2885
|
`queued` tells the agent nobody was involved, which is not what was said. Omni renders it as
|
|
2881
2886
|
*"not recorded"* in the place the name would go.
|
|
2882
2887
|
|
|
@@ -2893,10 +2898,10 @@ zeros. The record is restated with the task on every `task-updated` and snapshot
|
|
|
2893
2898
|
that arrives late corrects the sums. These four are what every platform reports; more will be
|
|
2894
2899
|
added here as a need is shown, not invented ahead of one.
|
|
2895
2900
|
|
|
2896
|
-
#### The
|
|
2901
|
+
#### The agent application records what it performs
|
|
2897
2902
|
|
|
2898
|
-
`muted` is the one leg the
|
|
2899
|
-
provider would have a hole exactly there. So the
|
|
2903
|
+
`muted` is the one leg the agent application performs rather than the provider, and a record kept by the
|
|
2904
|
+
provider would have a hole exactly there. So the agent application reports it, through `recordStep`, and the provider
|
|
2900
2905
|
writes it into its record as it writes every other leg:
|
|
2901
2906
|
|
|
2902
2907
|
```ts
|
|
@@ -2906,12 +2911,12 @@ declare const allocationId: AllocationId;
|
|
|
2906
2911
|
declare const at: IsoTimestamp;
|
|
2907
2912
|
|
|
2908
2913
|
void connection.recordStep?.({ taskId, allocationId, step: "muted", at, mutedBy: "host" }); // the moment the agent mutes
|
|
2909
|
-
void connection.recordStep?.({ taskId, allocationId, step: "muted", at, mutedBy: "host", seconds: 15 }); // still running, if the
|
|
2914
|
+
void connection.recordStep?.({ taskId, allocationId, step: "muted", at, mutedBy: "host", seconds: 15 }); // still running, if the agent application chooses to say
|
|
2910
2915
|
void connection.recordStep?.({ taskId, allocationId, step: "muted", at, mutedBy: "host", seconds: 42, ended: true }); // the moment they unmute
|
|
2911
2916
|
```
|
|
2912
2917
|
|
|
2913
|
-
Every report about one leg repeats its original `step` and
|
|
2914
|
-
the published history uses the provider-selected `at` returned by `recordStep`. The
|
|
2918
|
+
Every report about one leg repeats its original `step` and agent application `at` as a correlation key;
|
|
2919
|
+
the published history uses the provider-selected `at` returned by `recordStep`. The agent application reports
|
|
2915
2920
|
the action it performed and its measured duration, not an authoritative provider timestamp. It may say how long so far — `seconds` is
|
|
2916
2921
|
elapsed while the leg runs and final once it has ended — and **the end is stated, never
|
|
2917
2922
|
inferred**: `ended: true` marks the last report, and it carries the final duration. **What a
|
|
@@ -2922,16 +2927,16 @@ a running one it was never asked for (`interactionReport.running.unexpected`). W
|
|
|
2922
2927
|
did ask for them forwards upstream, and how often, is its own business. The step appears in
|
|
2923
2928
|
`interactionHistory` when the *provider* publishes it: Omni never writes the record itself.
|
|
2924
2929
|
`recordStep` is required of every softphone login's connection, since every call on a softphone
|
|
2925
|
-
can be muted by the
|
|
2930
|
+
can be muted by the agent application, and answers `recorded` with the canonical history `at`. A `muted` report says whose the silence was,
|
|
2926
2931
|
`mutedBy: "host"` or `"station"`, and no other report has the word (`interactionReport.mutedBy`,
|
|
2927
2932
|
`.mutedBy.unexpected`). On a desk phone the microphone is the phone's:
|
|
2928
|
-
the
|
|
2933
|
+
the agent application mutes nothing and records nothing.
|
|
2929
2934
|
|
|
2930
|
-
**The provider's confirmed end is decisive.** The
|
|
2935
|
+
**The provider's confirmed end is decisive.** The agent application keeps local state for interaction and
|
|
2931
2936
|
reporting, but follows the provider's authoritative state. When the provider publishes the end
|
|
2932
|
-
of the current mute leg, the
|
|
2937
|
+
of the current mute leg, the agent application ends that local mute and releases its agent application-controlled mute
|
|
2933
2938
|
on the matching task's media. It does not wait for its own timer, reopen the leg, replace the
|
|
2934
|
-
provider timestamp, or overwrite the provider's final duration with a later
|
|
2939
|
+
provider timestamp, or overwrite the provider's final duration with a later agent application report.
|
|
2935
2940
|
A later agent mute is a new leg, never a reopening of the ended one.
|
|
2936
2941
|
|
|
2937
2942
|
Correlate the provider-published `muted` entry using the provider-selected `at` acknowledged
|
|
@@ -2939,15 +2944,15 @@ for the current report key; a final `seconds` closes that leg under the history
|
|
|
2939
2944
|
This is identity matching, not reconciliation between clocks. An old closed entry, an unrelated
|
|
2940
2945
|
task, or a stale connection cannot end a newer local mute. If publication precedes the acknowledgment,
|
|
2941
2946
|
retain the current provider view and apply the matching closure once correlation is available;
|
|
2942
|
-
never guess a match from arrival order or nearest timestamp. Failure to release
|
|
2947
|
+
never guess a match from arrival order or nearest timestamp. Failure to release an agent application-controlled
|
|
2943
2948
|
mute is reported visibly; local device reports still describe the actual device state.
|
|
2944
2949
|
|
|
2945
|
-
At a provider-confirmed task/media end, the
|
|
2946
|
-
its observed ending where the report is still accepted.
|
|
2950
|
+
At a provider-confirmed task/media end, the agent application also stops the associated local mute and reports
|
|
2951
|
+
its observed ending where the report is still accepted. An agent application closing report repeats its original
|
|
2947
2952
|
key and may include its measured duration, but cannot reverse an already confirmed provider end.
|
|
2948
2953
|
The provider acknowledges a known closed leg without rewriting its final record; after the task
|
|
2949
|
-
has been completed, it may refuse the report as task-not-found. The
|
|
2950
|
-
than retrying or recreating the task. An observed
|
|
2954
|
+
has been completed, it may refuse the report as task-not-found. The agent application reads that response rather
|
|
2955
|
+
than retrying or recreating the task. An observed agent application end before a provider closure is still
|
|
2951
2956
|
reported normally; the provider decides the final timestamp and publishes the record.
|
|
2952
2957
|
|
|
2953
2958
|
### Browser capability
|
|
@@ -2981,7 +2986,7 @@ Each `TaskBrowser` defines one named browser in the task workspace.
|
|
|
2981
2986
|
| `url` | Initial URL. Must use `http:` or `https:`; see below. Later navigation comes from Chromium. |
|
|
2982
2987
|
| `sharedSession` | Required. `false` creates a task-specific browser session. |
|
|
2983
2988
|
| `isolationScheme` | **Required when `sharedSession` is `true`**, and rejected when it is `false`. There is no default: see below. |
|
|
2984
|
-
| `urlVisibility` | What the agent sees of this tab's URL in Omni's chrome: `hidden`, `domain`, or `full`. Omitted, the URL shows as any browser's does; a provider says `hidden` where the URL carries what the agent may not read — a caller's number, a CRM token. Per browser, on the provider's word;
|
|
2989
|
+
| `urlVisibility` | What the agent sees of this tab's URL in Omni's chrome: `hidden`, `domain`, or `full`. Omitted, the URL shows as any browser's does; a provider says `hidden` where the URL carries what the agent may not read — a caller's number, a CRM token. Per browser, on the provider's word; an agent application that declares `guarantees.browserUrlVisibility` honours it tab by tab, and a provider checks that guarantee before it sends such a URL at all. |
|
|
2985
2990
|
|
|
2986
2991
|
##### Choosing an isolation scheme
|
|
2987
2992
|
|
|
@@ -3072,8 +3077,8 @@ that produces two consumers disagreeing about one fact. The task is the one sour
|
|
|
3072
3077
|
provider puts on the task what the platform permits for it -- and says, in `capabilitySource`, who
|
|
3073
3078
|
chose those terms. `queue` says somebody configured them. `ungoverned` says nothing handed the work
|
|
3074
3079
|
over: an agent's own outbound call has no queue behind it, and the provider states what it permits
|
|
3075
|
-
for such a call. Neither changes where
|
|
3076
|
-
source is one more published fact about them, and the one that lets
|
|
3080
|
+
for such a call. Neither changes where an agent application reads the capabilities from, which is the task; the
|
|
3081
|
+
source is one more published fact about them, and the one that lets an agent application tell a fact from a fault
|
|
3077
3082
|
(see below).
|
|
3078
3083
|
|
|
3079
3084
|
**Completing the task is not a capability, and no capability set withholds it.** `complete` is
|
|
@@ -3092,14 +3097,14 @@ task said so both times, at the moment it became true. A call moves queues the s
|
|
|
3092
3097
|
identified as a priority customer is transferred to the priority queue, and the task arrives under
|
|
3093
3098
|
that queue's terms -- a longer wrap allowance, a discount control the general queue never offered --
|
|
3094
3099
|
restated on the task at the hand-over, not inferred from the queue it left. A capability stated
|
|
3095
|
-
once at offer or answer and never corrected is a fact with a shelf life and no expiry, and
|
|
3100
|
+
once at offer or answer and never corrected is a fact with a shelf life and no expiry, and an agent application
|
|
3096
3101
|
draws a control the provider will now refuse -- or withholds one the agent now has. So the provider republishes
|
|
3097
|
-
the task, with its capabilities as they now stand, and
|
|
3102
|
+
the task, with its capabilities as they now stand, and an agent application treats the last statement as current
|
|
3098
3103
|
rather than re-deriving anything. This is the same shape as a room left full after the call ends
|
|
3099
3104
|
-- a field describing the present, carried past the moment it stopped being true -- carried past
|
|
3100
3105
|
this time not by a spread but by nobody sending the correction. A provider whose platform can
|
|
3101
3106
|
change a permission mid-task and does not republish is in breach, however conformant its offer was.
|
|
3102
|
-
A capability set can shrink as well as grow, and
|
|
3107
|
+
A capability set can shrink as well as grow, and an agent application that has only ever seen it grow meets a
|
|
3103
3108
|
control that was there a moment ago and is gone; a command that arrives after its capability was
|
|
3104
3109
|
withdrawn is refused with a reason, never acted on. The asymmetry decides which direction a
|
|
3105
3110
|
provider gets right first: a control gained late is a nicety, a control withdrawn and not
|
|
@@ -3108,12 +3113,12 @@ direction: the task as offered and as republished, and one command that was issu
|
|
|
3108
3113
|
first and is refused under the last for want of the capability withdrawn -- and nothing else.
|
|
3109
3114
|
|
|
3110
3115
|
**An empty capability set is a statement, not a shrug.** `capabilities: {}` under `queue` or
|
|
3111
|
-
`ungoverned` says the platform permits nothing capability-gated on this task, and
|
|
3116
|
+
`ungoverned` says the platform permits nothing capability-gated on this task, and an agent application draws
|
|
3112
3117
|
nothing beyond what the phase and `completionMode` require. A provider that has not yet learned
|
|
3113
3118
|
what the platform permits -- a queue's configuration that has not reached it -- knows nothing of
|
|
3114
3119
|
the kind, and must not publish the task as if it did, neither as `{}` nor as every control it has.
|
|
3115
3120
|
It publishes the task under `capabilitySource: "undetermined"`, with the capabilities it will
|
|
3116
|
-
honour until it knows, and
|
|
3121
|
+
honour until it knows, and an agent application shows that where the agent works, beside the controls it draws
|
|
3117
3122
|
from them: "no queue governs this call" is a fact, "the configuration has not arrived" is a fault,
|
|
3118
3123
|
and the set alone cannot tell them apart, so the provider says which. What a provider honours
|
|
3119
3124
|
under undetermined terms is its own call -- a floor that would rather an agent briefly hold a
|
|
@@ -3158,9 +3163,9 @@ a message that shows and clears has said nothing about the buttons still on the
|
|
|
3158
3163
|
words are close in English and far apart on the desk: `ungoverned` is silence, `undetermined` is a
|
|
3159
3164
|
standing notice.
|
|
3160
3165
|
|
|
3161
|
-
One consequence for whoever builds the
|
|
3162
|
-
conformant adapter never shows
|
|
3163
|
-
result says nothing about how the
|
|
3166
|
+
One consequence for whoever builds the agent application: because a conformance run fails on `undetermined`, a
|
|
3167
|
+
conformant adapter never shows an agent application `undetermined` under test, and a clean `exerciseAdapter`
|
|
3168
|
+
result says nothing about how the agent application renders it. That rendering is tested against a fixture --
|
|
3164
3169
|
a task published under `undetermined` shows the notice, the same task under `queue` shows none --
|
|
3165
3170
|
and never against an adapter, even in principle. `exerciseAdapter` treats a
|
|
3166
3171
|
task published under `undetermined` as a violation (`capabilitySource.undetermined`), as it treats
|
|
@@ -3187,7 +3192,7 @@ See **Which commands need a capability**.
|
|
|
3187
3192
|
|
|
3188
3193
|
| Capability | Omni UI | Contract |
|
|
3189
3194
|
| --- | --- | --- |
|
|
3190
|
-
| `decline` | Pending-task button: Decline | The provider can decline a pending voice offer. Omni shows it only when
|
|
3195
|
+
| `decline` | Pending-task button: Decline | The provider can decline a pending voice offer. Omni shows it only when local policy also permits declining. |
|
|
3191
3196
|
| `hold` | Primary toggle: Hold | Omni may issue voice-task `hold` and `resume` commands. |
|
|
3192
3197
|
| `endCall` | Primary button: End call | The provider ends the caller connection and all owned/inherited agent-added channels; wrap/completion remain separate. See **Ending a call, and removing one person from it**. |
|
|
3193
3198
|
| `connectBack` | Completing-task button: Connect back | Omni may have the provider connect the agent back to the task's party while the task is `completing`, returning it to `in-progress` on the same task. Not offered where there is no `completing` window: `provider-automatic` with a zero allowance completes the task at provider end. See **Connecting back during completion**. |
|
|
@@ -3195,7 +3200,7 @@ See **Which commands need a capability**.
|
|
|
3195
3200
|
| `warmTransfer` | Secondary menu item: Warm transfer | Omni may park the customer and call a destination first, then hand the customer over or cancel back. See **Warm transfer**. |
|
|
3196
3201
|
| `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**. |
|
|
3197
3202
|
| `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**. |
|
|
3198
|
-
| `recording` | Overflow menu item: Recording | Per-task provider and
|
|
3203
|
+
| `recording` | Overflow menu item: Recording | Per-task provider and agent application policies expose only their permitted recording actions; each has independent state and routing. |
|
|
3199
3204
|
| `outcomes` | Primary button: Complete | Omni may request task completion with a provider outcome and notes. |
|
|
3200
3205
|
|
|
3201
3206
|
### Publishing codes and destinations
|
|
@@ -3277,7 +3282,7 @@ reason (`task.destinations.shape`): once nothing is typed, the directory is the
|
|
|
3277
3282
|
|
|
3278
3283
|
**The provider chooses the supported destinations.** A published item may route to an IVR,
|
|
3279
3284
|
queue, named agent or another configured destination. No destination kind is inferred from its
|
|
3280
|
-
label. The
|
|
3285
|
+
label. The agent application offers only the directory attached to this task's specific transfer capability,
|
|
3281
3286
|
and sends the exact destination ID; it does not invent an agent picker or arbitrary dial target.
|
|
3282
3287
|
The provider rechecks eligibility and executes the routing. Returning to an IVR or queue does not
|
|
3283
3288
|
end the caller journey; a later assignment to the same agent is a new interaction.
|
|
@@ -3293,13 +3298,13 @@ provider.
|
|
|
3293
3298
|
{ type: "conference", action: "remove", destinationId: "tier2" } // the conferenced person leaves; ringing, this calls the dial off
|
|
3294
3299
|
```
|
|
3295
3300
|
|
|
3296
|
-
The
|
|
3301
|
+
The agent application sends the ordinary task-scoped `execute` request with `command: { type: "end-call" }`.
|
|
3297
3302
|
The provider knows the caller and agent-added channel bindings; no channel list or owner details
|
|
3298
|
-
are sent by the
|
|
3303
|
+
are sent by the agent application. The task's `endCall` capability is permission, not an executor selection.
|
|
3299
3304
|
`validateTaskCommandRequest` checks the exact interaction/allocation and delegates the command's
|
|
3300
3305
|
policy/phase checks. The provider rechecks current ownership atomically; no client validator can
|
|
3301
|
-
prove that its view is fresh. The
|
|
3302
|
-
this agent action. There is no
|
|
3306
|
+
prove that its view is fresh. The agent application does not implement agent end-call; low-level removal of individual channels is not
|
|
3307
|
+
this agent action. There is no agent application end-call execution API or fallback route.
|
|
3303
3308
|
|
|
3304
3309
|
`end-call` is gated by `endCall` on the current interaction. It ends the caller connection and
|
|
3305
3310
|
all agent-added channels owned by that interaction, including channels originally added by previous
|
|
@@ -3351,7 +3356,7 @@ offer any of the three without the others, and each is declared on its own.
|
|
|
3351
3356
|
```
|
|
3352
3357
|
|
|
3353
3358
|
`warm` is gated by the `warmTransfer` capability and names a directory item exactly as a cold
|
|
3354
|
-
transfer does, from the same kind of directory. It is a dial, so it carries the
|
|
3359
|
+
transfer does, from the same kind of directory. It is a dial, so it carries the agent application's `dialId`
|
|
3355
3360
|
and is answered `dialling`, and its `dial-outcome` says whether the destination was reached. While
|
|
3356
3361
|
the destination is on the line the task carries a `consulted` entry on `onCall`, and that presence
|
|
3357
3362
|
is what makes `complete` and `cancel` issuable -- they name no destination because there is exactly
|
|
@@ -3377,7 +3382,7 @@ customer.
|
|
|
3377
3382
|
|
|
3378
3383
|
| Capability | Omni UI | Contract |
|
|
3379
3384
|
| --- | --- | --- |
|
|
3380
|
-
| `decline` | Pending-task button: Decline | The provider can decline a pending chat offer. Omni shows it only when
|
|
3385
|
+
| `decline` | Pending-task button: Decline | The provider can decline a pending chat offer. Omni shows it only when local policy also permits declining. |
|
|
3381
3386
|
| `hold` | Primary toggle: Hold | Omni may pause and resume the agent’s interaction in the chat. |
|
|
3382
3387
|
| `outcomes` | Primary button: Complete | Omni may request task completion with a provider outcome and notes. |
|
|
3383
3388
|
|
|
@@ -3385,7 +3390,7 @@ customer.
|
|
|
3385
3390
|
|
|
3386
3391
|
| Capability | Omni UI | Contract |
|
|
3387
3392
|
| --- | --- | --- |
|
|
3388
|
-
| `decline` | Pending-task button: Decline | The provider can decline a pending email offer. Omni shows it only when
|
|
3393
|
+
| `decline` | Pending-task button: Decline | The provider can decline a pending email offer. Omni shows it only when local policy also permits declining. |
|
|
3389
3394
|
| `outcomes` | Primary button: Complete | Omni may request task completion with a provider outcome and notes. |
|
|
3390
3395
|
|
|
3391
3396
|
### Custom capabilities
|
|
@@ -3434,7 +3439,7 @@ Custom capabilities must not redefine the meaning of a standard channel capabili
|
|
|
3434
3439
|
Starts one outbound call from the idle dialpad. It is present only when the voice provider
|
|
3435
3440
|
declares `dial`.
|
|
3436
3441
|
|
|
3437
|
-
- `dialId` is the
|
|
3442
|
+
- `dialId` is the agent application's identity for this dial, minted before the request leaves.
|
|
3438
3443
|
- `destination` is the original number selected or entered by the agent.
|
|
3439
3444
|
- The provider holds `destination` to its declared `destinations`: under `contacts-only`, a
|
|
3440
3445
|
number that is not one of its contacts answers `failed` with `omni.destination-not-permitted`.
|
|
@@ -3454,7 +3459,7 @@ and apart from its answer -- the destination picks up, is busy, or never does --
|
|
|
3454
3459
|
late in a call routinely outlives the call. Nothing in between is reported: the wire says
|
|
3455
3460
|
`dialling`, then says how it ended, once.
|
|
3456
3461
|
|
|
3457
|
-
**The
|
|
3462
|
+
**The agent application mints the identity.** Every command that dials carries a `dialId` the agent application made, unique
|
|
3458
3463
|
across the installation, and it travels four ways so no leg is a lookup:
|
|
3459
3464
|
|
|
3460
3465
|
```ts
|
|
@@ -3462,10 +3467,10 @@ declare const connection: Connection<"voice">;
|
|
|
3462
3467
|
declare const taskId: TaskId;
|
|
3463
3468
|
declare const allocationId: AllocationId;
|
|
3464
3469
|
|
|
3465
|
-
// 1. Out, minted by the
|
|
3470
|
+
// 1. Out, minted by the agent application.
|
|
3466
3471
|
const result = await connection.execute({ taskId, allocationId, command: { type: "conference", action: "add", dialId: "dial-7f2", destinationId: "tier2" } });
|
|
3467
3472
|
|
|
3468
|
-
// 2. Back on the result, restated, so the
|
|
3473
|
+
// 2. Back on the result, restated, so the agent application compares and confirms.
|
|
3469
3474
|
expect(result).toEqual({ status: "dialling", dialId: "dial-7f2" });
|
|
3470
3475
|
|
|
3471
3476
|
// 3. On the outcome, however late, against the task it belonged to.
|
|
@@ -3475,22 +3480,22 @@ const outcome: ProviderEvent<"voice"> = { type: "dial-outcome", dialId: "dial-7f
|
|
|
3475
3480
|
const step: TaskInteractionStep = { step: "unanswered", at: "2026-08-21T09:15:30Z", by: "A-12", dialId: "dial-7f2", destinationId: "tier2" };
|
|
3476
3481
|
```
|
|
3477
3482
|
|
|
3478
|
-
The identity is a correlation handle between
|
|
3479
|
-
agent**.
|
|
3483
|
+
The identity is a correlation handle between agent application and provider and **is never shown to the
|
|
3484
|
+
agent**. An agent application holding several tasks places an outcome by the `dialId` it minted; one it did not
|
|
3480
3485
|
mint -- a dial made by the previous agent before a transfer -- it finds on the task's `onCall` or in
|
|
3481
3486
|
its `interactionHistory`, which is why the steps that dial carry it. The `dialId` is not a retry key:
|
|
3482
3487
|
Omni never repeats a dial, and a command still carries no key for that purpose.
|
|
3483
3488
|
|
|
3484
3489
|
**`dialling` is the answer to a dial, and `applied` is not.** A command that dials answers
|
|
3485
|
-
`{ status: "dialling", dialId }`, restating the
|
|
3490
|
+
`{ status: "dialling", dialId }`, restating the agent application's identity (`result.dialId`,
|
|
3486
3491
|
`result.dialId.mismatch`); `applied` from such a command, or `dialling` from one that dialled
|
|
3487
3492
|
nothing, is refused (`result.status`). `validateResult(result, method, path, dialId)` takes the
|
|
3488
|
-
`dialId` the
|
|
3493
|
+
`dialId` the agent application sent for that reason.
|
|
3489
3494
|
|
|
3490
3495
|
**The outcome is stated, once, either way.** A `dial-outcome` names the `dialId`, the task where
|
|
3491
3496
|
there was one, the directory item it went to where there was one, and how the dial ended -- from a closed
|
|
3492
3497
|
set: `answered`, `busy`, `no-answer`, `unreachable`, `rejected`, `cancelled`, `unexplained`.
|
|
3493
|
-
`answered` is stated rather than read off somebody appearing on the call, because
|
|
3498
|
+
`answered` is stated rather than read off somebody appearing on the call, because an agent application that infers
|
|
3494
3499
|
success cannot tell "reached" from "still ringing". `cancelled` is the dialler calling the dial off
|
|
3495
3500
|
before anyone answered -- the agent hanging up while a consult still rings -- which is not a
|
|
3496
3501
|
`no-answer` the destination never gave. `unexplained` is the switch dropping the call with no cause among
|
|
@@ -3507,7 +3512,7 @@ and the harness places the outcome all the same (`TaskStream`, `stream.dialOutco
|
|
|
3507
3512
|
`stream.dialOutcome.duplicate`); an agent who moved on still learns nobody was reached, against the
|
|
3508
3513
|
call it belonged to and never against the next one.
|
|
3509
3514
|
|
|
3510
|
-
The stream knows a dial from three places: `TaskStream.dialled(dialId)`, which
|
|
3515
|
+
The stream knows a dial from three places: `TaskStream.dialled(dialId)`, which an agent application calls when it
|
|
3511
3516
|
places one; an entry on a task's `onCall`; and a step in its `interactionHistory`. Since a dialled entry
|
|
3512
3517
|
is listed from placement, `dialled()` is load-bearing only for a dial that never has an entry -- a
|
|
3513
3518
|
cold transfer, a connect-back, an idle dialpad call -- or whose entry has already left the room; an
|
|
@@ -3548,8 +3553,8 @@ const onCall: OnCall[] = [
|
|
|
3548
3553
|
|
|
3549
3554
|
`party` is the customer and `agent` a person by user id; neither was dialled from anywhere, so
|
|
3550
3555
|
neither carries a destination. `consulted` and `conferenced` were, so both carry the
|
|
3551
|
-
`destinationId` of the directory item and, where
|
|
3552
|
-
the platform added itself carries none. `held: true` is presence as claim. A snapshot establishes state, so
|
|
3556
|
+
`destinationId` of the directory item and, where an agent application's dial brought them, its `dialId`; a person
|
|
3557
|
+
the platform added itself carries none. `held: true` is presence as claim. A snapshot establishes state, so an agent application that attaches
|
|
3553
3558
|
after a transfer reads the room from here rather than inferring it from a sequence of outcomes it
|
|
3554
3559
|
never saw.
|
|
3555
3560
|
|
|
@@ -3564,7 +3569,7 @@ task may remain open for wrap. Completion is a separate task action, not the cal
|
|
|
3564
3569
|
`previewEndsAt` and `atDeadline` are three instances of one shape, and there will be more: each
|
|
3565
3570
|
describes a state that is true now, and each is carried past the moment it stops being true by the
|
|
3566
3571
|
most natural implementation there is, building the next task by spreading the last one. The
|
|
3567
|
-
provider's own state looks right, the claim on the wire is stale, and only the
|
|
3572
|
+
provider's own state looks right, the claim on the wire is stale, and only the agent application sees it. An
|
|
3568
3573
|
adapter that builds each transition from what is true after it, rather than from what was true
|
|
3569
3574
|
before, needs none of these rules named. Two agents were
|
|
3570
3575
|
once shown on calls for the better part of an hour while callers queued, because the last message
|
|
@@ -3607,8 +3612,8 @@ nothing while none are being accepted — so they are not published separately.
|
|
|
3607
3612
|
|
|
3608
3613
|
| Field | Contract |
|
|
3609
3614
|
| --- | --- |
|
|
3610
|
-
| `
|
|
3611
|
-
| `canRequestBreak` | Whether the agent may ask at all. Distinct from `
|
|
3615
|
+
| `status` | Where the agent’s break lifecycle stands. See the states below. |
|
|
3616
|
+
| `canRequestBreak` | Whether the agent may ask at all. Distinct from `status`. |
|
|
3612
3617
|
| `requestUnavailableReason` | Display-ready reason shown when `canRequestBreak` is false — a standing gate that applies to everyone. |
|
|
3613
3618
|
| `decisionReason` | The words whoever decided attached, from `decide.reason`. About one request and one decision, not a standing gate. |
|
|
3614
3619
|
| `retryRequestAfterMs` | Milliseconds until the agent may retry a break request, when the provider can say. |
|
|
@@ -3619,10 +3624,10 @@ nothing while none are being accepted — so they are not published separately.
|
|
|
3619
3624
|
A request can be waiting for two unrelated things, and they are separate values because
|
|
3620
3625
|
rendering one as the other tells an agent to wait for somebody who is never coming:
|
|
3621
3626
|
|
|
3622
|
-
| `
|
|
3627
|
+
| `status` | Meaning |
|
|
3623
3628
|
| --- | --- |
|
|
3624
3629
|
| `not-requested` | No request outstanding. |
|
|
3625
|
-
| `awaiting-
|
|
3630
|
+
| `awaiting-approval` | A person has to decide. The agent is waiting on somebody. |
|
|
3626
3631
|
| `granted` | A person decided yes. Omni may now tell this provider to stop the agent; until it does, work continues normally, and this says nothing about why Omni has not. |
|
|
3627
3632
|
| `starting-after-task` | Omni has told the provider to stop; the break begins when the current task ends. No new work arrives meanwhile, and nobody needs to act. It waits on a task, so beside no task it is refused (`break.starting-after-task.tasks`): a committed break with nothing outstanding is `on-break`. |
|
|
3628
3633
|
| `on-break` | The agent is on the break now. It holds no task: a break begins when the work ends, so a snapshot reporting `on-break` beside a task is refused as `break.on-break.tasks`. The one exception is a lead's listening task during a `coaching`, `administrative` or `training` break -- see **Listening to a call**. |
|
|
@@ -3630,7 +3635,7 @@ rendering one as the other tells an agent to wait for somebody who is never comi
|
|
|
3630
3635
|
A denial is a decision, not a standing approval state. The provider transitions the request directly
|
|
3631
3636
|
to `not-requested`; Omni returns the agent to idle and never asks again on their behalf. They saw the
|
|
3632
3637
|
answer and ask again when they want to. `decisionReason` may carry the words attached to that
|
|
3633
|
-
decision, but `
|
|
3638
|
+
decision, but `status` does not remain denied.
|
|
3634
3639
|
|
|
3635
3640
|
A provider reports `starting-after-task` only after Omni commits a `granted` request while
|
|
3636
3641
|
work is still active. Omni does not send the request again, because asking again would not move
|
|
@@ -3642,6 +3647,18 @@ something a busy hour can cancel, and Omni keeps offering those while the rest a
|
|
|
3642
3647
|
|
|
3643
3648
|
### Forced breaks
|
|
3644
3649
|
|
|
3650
|
+
Migration: the former awaiting-decision value is now `awaiting-approval` in break status
|
|
3651
|
+
and the team member’s pending break state. The old value is rejected without an alias;
|
|
3652
|
+
the request can still be granted or denied, and ordering rules are unchanged.
|
|
3653
|
+
|
|
3654
|
+
Migration: the former BreakApproval type is now `BreakStatus`, matching `BreakState.status`.
|
|
3655
|
+
The former type is not exported as an alias; its values and behavior are unchanged.
|
|
3656
|
+
|
|
3657
|
+
Migration: `BreakState.status` replaces the former approval field. It covers the full break
|
|
3658
|
+
lifecycle. The old field is rejected even alongside status; related diagnostics now use status.
|
|
3659
|
+
The `BreakStatus` union and its values, `reasons`, `activeReasonId`, and the separate testing
|
|
3660
|
+
helper BreakOnTaskStep.approval are unchanged. Hosts and providers must update together.
|
|
3661
|
+
|
|
3645
3662
|
Migration: the break-state field formerly named retryAfterMs is now `BreakState.retryRequestAfterMs`,
|
|
3646
3663
|
with diagnostic `break.retryRequestAfterMs`. It remains an optional non-negative finite number
|
|
3647
3664
|
of milliseconds; the former break field is rejected even alongside the new field.
|
|
@@ -3664,8 +3681,8 @@ mayAsk. Request eligibility remains distinct from approval; existing alwaysAvail
|
|
|
3664
3681
|
exceptions are unchanged. Hosts and providers must update together.
|
|
3665
3682
|
|
|
3666
3683
|
Migration: ImposedBreak is now `ForcedBreak`, and the former imposed field is now
|
|
3667
|
-
`BreakState.forced`. Diagnostic and harness coverage names use `break.forced
|
|
3668
|
-
|
|
3684
|
+
`BreakState.forced`. Diagnostic and harness coverage names use `break.forced`. The former agent-end prohibition
|
|
3685
|
+
has been removed: forced breaks also permit explicit agent resumption. Update hosts and providers together. The old field
|
|
3669
3686
|
is rejected, including when both spellings are sent; no compatibility alias is provided.
|
|
3670
3687
|
The break policy value formerly named auto-approve is now `automatically-approved`.
|
|
3671
3688
|
Requests are approved automatically; approval does not skip commitment or the prerequisites
|
|
@@ -3684,23 +3701,23 @@ The team break command formerly named place is now `force-break`. Hosts and prov
|
|
|
3684
3701
|
adopt the new command together; place and the interim force spelling are rejected without aliases.
|
|
3685
3702
|
The team break command formerly named release is now `end-forced-break`, and its diagnostic is
|
|
3686
3703
|
`team.break.command.endForcedBreak`. The former release command and the interim end spelling are rejected without aliases.
|
|
3687
|
-
|
|
3688
|
-
method still cannot end a forced break. Break ordering and permission rules are unchanged.
|
|
3704
|
+
The agent’s `endBreak` now applies to forced breaks as well as requested breaks.
|
|
3689
3705
|
|
|
3690
|
-
`ForcedBreak`
|
|
3691
|
-
|
|
3706
|
+
`ForcedBreak` names who forced the break and may include an advisory `expectedDurationMs`.
|
|
3707
|
+
The agent must explicitly resume when ready. The retired endsAutomatically and endsAt fields
|
|
3708
|
+
are rejected, including when an expected duration is also provided.
|
|
3692
3709
|
|
|
3693
3710
|
**Every forced break has a person behind it.** A lead or a manager forced it; there is no such
|
|
3694
3711
|
thing as a break the platform forced on its own. Where a platform applies one automatically, it is
|
|
3695
3712
|
executing a preference somebody configured, and that person is the owner of the action — `by` names
|
|
3696
3713
|
them, not the machinery that carried it out.
|
|
3697
3714
|
|
|
3698
|
-
Omni resolves the name to show with `
|
|
3715
|
+
Omni resolves the name to show with `getUserDetails()`, so a provider sends the identifier and never
|
|
3699
3716
|
a display name.
|
|
3700
3717
|
|
|
3701
3718
|
**A forced break travels with `on-break` or `starting-after-task`, and nothing else.** It is a
|
|
3702
|
-
break in progress or about to be; beside `granted` or `awaiting-
|
|
3703
|
-
request the agent never made and commit it (`break.forced.
|
|
3719
|
+
break in progress or about to be; beside `granted` or `awaiting-approval` the agent application would read a
|
|
3720
|
+
request the agent never made and commit it (`break.forced.status`). Where the provider publishes
|
|
3704
3721
|
`reasons`, the lead's `force-break` named one, and the member's state carries it as `activeReasonId`.
|
|
3705
3722
|
|
|
3706
3723
|
For example:
|
|
@@ -3708,17 +3725,34 @@ For example:
|
|
|
3708
3725
|
```ts
|
|
3709
3726
|
forced: {
|
|
3710
3727
|
by: "manager-1042",
|
|
3711
|
-
|
|
3712
|
-
endsAt: "2026-08-21T10:00:00.000Z"
|
|
3728
|
+
expectedDurationMs: 600000
|
|
3713
3729
|
}
|
|
3714
3730
|
```
|
|
3715
3731
|
|
|
3716
|
-
The
|
|
3717
|
-
Resume
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3732
|
+
The agent application keeps **Resume** available for an agent on a forced break. The agent explicitly chooses
|
|
3733
|
+
Resume; the agent application sends `endBreak()` and follows the provider-confirmed state before resuming
|
|
3734
|
+
work. An expected duration is a positive finite number of milliseconds, measured from actual
|
|
3735
|
+
entry into `on-break`, excluding any `starting-after-task` wait. Omission means no expected
|
|
3736
|
+
duration was supplied. Neither duration expiry nor an overdue indication authorizes the agent application
|
|
3737
|
+
or provider to end the break, restore availability, or route work to the agent.
|
|
3738
|
+
|
|
3739
|
+
**A lead lifting the restriction does not resume the agent.** On an applied
|
|
3740
|
+
`end-forced-break`, the provider clears `BreakState.forced` while preserving the current
|
|
3741
|
+
`on-break` or `starting-after-task` status and `activeReasonId`. This command cannot publish
|
|
3742
|
+
`not-requested`, restore readiness, or start routing work. Ordinary progress from
|
|
3743
|
+
`starting-after-task` to `on-break` still happens when work finishes. The agent application follows the
|
|
3744
|
+
published state and waits for the agent's explicit Resume, even after reconnect. Historical
|
|
3745
|
+
attribution is not rewritten by clearing the current forced marker.
|
|
3746
|
+
|
|
3747
|
+
The agent may explicitly resume either before or after the lead lifts the restriction. A lead
|
|
3748
|
+
command, timer, late snapshot or reconnect is never a substitute for that choice. Providers
|
|
3749
|
+
must correlate resumption to the authenticated agent's action; a state-only ordering validator
|
|
3750
|
+
cannot establish the cause of a transition. The usual multi-provider end/reconciliation flow
|
|
3751
|
+
still applies, and work resumes only on confirmed provider state.
|
|
3752
|
+
|
|
3753
|
+
An agent application may display the expected duration. A countdown requires an evidenced actual start;
|
|
3754
|
+
a received snapshot, replay or reconnect is not a new start and cannot restart the duration.
|
|
3755
|
+
Without that evidence, show the duration without inventing a start or return time.
|
|
3722
3756
|
|
|
3723
3757
|
A break applies to the **agent**, not to one provider. When a provider forces one, Omni immediately
|
|
3724
3758
|
requests a break on every other connected provider, or they would keep routing work to somebody who
|
|
@@ -3760,7 +3794,7 @@ States how many tasks this provider may have allocated to the agent **at once**.
|
|
|
3760
3794
|
`count` is an absolute ceiling, not an increment, a whole number of zero or more
|
|
3761
3795
|
(`capacity.count`). An agent's capacity is a property of the agent, not of the moment: it is
|
|
3762
3796
|
stated when the agent is set up and restated only when it genuinely changes, which is a
|
|
3763
|
-
|
|
3797
|
+
local policy change rather than a task starting or ending -- with one exception below.
|
|
3764
3798
|
|
|
3765
3799
|
**The provider counts its own outstanding tasks against it.** Allocate while you hold fewer than
|
|
3766
3800
|
`count` tasks for this agent, and stop when you hold that many; when one of yours ends you have
|
|
@@ -3769,22 +3803,22 @@ go, and a provider that waits for it will stall.
|
|
|
3769
3803
|
|
|
3770
3804
|
Your own tasks are the only ones you count. What the agent holds at other providers is not your
|
|
3771
3805
|
concern — Omni set `count` knowing it, and this is how: the agent is one person on several
|
|
3772
|
-
providers, and the
|
|
3773
|
-
provider that has none of it for now is told **`count: 0`,
|
|
3774
|
-
the member as `reserved` on the team member list -- signed in here, capacity held by the
|
|
3806
|
+
providers, and the agent application divides their capacity among them rather than telling each the whole. A
|
|
3807
|
+
provider that has none of it for now is told **`count: 0`, agent application-stopped**: allocate nothing, show
|
|
3808
|
+
the member as `reserved` on the team member list -- signed in here, capacity held by the agent application for elsewhere,
|
|
3775
3809
|
a fact this provider holds, where `on-task` would assert work it cannot see -- and take the next
|
|
3776
|
-
count as any other when the
|
|
3777
|
-
that follows work rather than
|
|
3810
|
+
count as any other when the agent application has capacity for this provider again. Zero is the one restatement
|
|
3811
|
+
that follows work rather than local policy.
|
|
3778
3812
|
|
|
3779
3813
|
**A capacity is taken, never refused.** `setCapacity` answers `applied` and nothing else: a
|
|
3780
|
-
statement has no failure to report, and a `failed` arm gave four
|
|
3814
|
+
statement has no failure to report, and a `failed` arm gave four agent application-provider pairings four
|
|
3781
3815
|
readings of which ceiling stood after it. A provider that cannot carry the count allocates within
|
|
3782
3816
|
what it can and says so on a `diagnostic`, so the gap is visible and nothing is inferred from a
|
|
3783
3817
|
refusal.
|
|
3784
3818
|
|
|
3785
3819
|
Capacity supersedes rather than accumulates: the latest value is the ceiling, and a decrease is as
|
|
3786
3820
|
ordinary as an increase. A provider whose ceiling can only rise -- one that keeps the highest count
|
|
3787
|
-
it was ever told, or returns early on a small one -- cannot be told to take less work, and
|
|
3821
|
+
it was ever told, or returns early on a small one -- cannot be told to take less work, and an agent application
|
|
3788
3822
|
taking capacity away is answered `applied` while the work keeps coming. The harness moves the axis
|
|
3789
3823
|
both ways after the drive, two then one then nought, and an offer after a lower count is caught
|
|
3790
3824
|
against it (`stream.taskOffered.overCapacity`).
|
|
@@ -3804,8 +3838,8 @@ request object only to the request method, and undefined to the other three.
|
|
|
3804
3838
|
| --- | --- |
|
|
3805
3839
|
| `requestBreak` | `not-requested`; selected current reason code when codes exist; `canRequestBreak` or the selected reason's `alwaysAvailable` exception. Free text does not replace a code. |
|
|
3806
3840
|
| `commitBreak` | `granted`, or already committed for an idempotent repeat. A later change to `canRequestBreak` does not revoke the grant. |
|
|
3807
|
-
| `cancelBreak` | `awaiting-
|
|
3808
|
-
| `endBreak` | `on-break` or `starting-after-task` during reconciliation;
|
|
3841
|
+
| `cancelBreak` | `awaiting-approval` or `granted`. A concurrent commit winning still answers `omni.break-already-committed` and requires recovery. |
|
|
3842
|
+
| `endBreak` | `on-break` or `starting-after-task` during reconciliation; the agent may explicitly end a requested or forced break. |
|
|
3809
3843
|
|
|
3810
3844
|
Use `validateTeamBreakCommand(request, context)` for lead decisions, forcing a break, ending a forced break and
|
|
3811
3845
|
policy commands. It requires the live lead capability and active transport, a current target
|
|
@@ -3823,14 +3857,24 @@ These validators report violations without dispatching, rewriting state or provi
|
|
|
3823
3857
|
Recheck prerequisites atomically at the provider; client validation cannot prevent a race.
|
|
3824
3858
|
Continue to use `validateResult` for each method's response vocabulary. A request acknowledgment
|
|
3825
3859
|
is not a grant, and no method response overwrites a newer state emission. Rejected promises
|
|
3826
|
-
remain unknown and recover by snapshot, not automatic retry. The
|
|
3860
|
+
remain unknown and recover by snapshot, not automatic retry. The agent application's frozen provider set,
|
|
3827
3861
|
durable mutually exclusive commit/cancel decision and reconciliation obligations above remain
|
|
3828
3862
|
required; per-provider validation does not implement the multi-provider coordinator.
|
|
3829
3863
|
|
|
3830
3864
|
### `requestBreak(request)`
|
|
3831
3865
|
|
|
3866
|
+
**Explanatory text is optional.** An agent may omit `BreakRequest.reason` for any break type.
|
|
3867
|
+
A lead may likewise omit reason text from `force-break`. Do not require a text explanation
|
|
3868
|
+
just because a break is agent-requested or forced. If supplied, text must be nonempty; omit
|
|
3869
|
+
it rather than sending an empty string. A selected `reasonId` identifies a published break
|
|
3870
|
+
choice and is separate from this optional text.
|
|
3871
|
+
|
|
3872
|
+
With published choices, `{ reasonId: "lunch" }` is a valid request without explanation;
|
|
3873
|
+
without published choices, `{}` is valid. A lead can similarly send a `force-break` command
|
|
3874
|
+
with its member ID and, where required, the selected reason ID, without reason text.
|
|
3875
|
+
|
|
3832
3876
|
Requests permission to stop the agent later; it does not itself stop work. The provider continues
|
|
3833
|
-
offering work and reports `awaiting-
|
|
3877
|
+
offering work and reports `awaiting-approval` or `granted` through `break-state` events. If the request is denied, the
|
|
3834
3878
|
provider reports `not-requested` directly, with `decisionReason` when one was supplied.
|
|
3835
3879
|
|
|
3836
3880
|
**An agent asks from anywhere — idle or on a task** — and Omni offers the request in the task
|
|
@@ -3940,7 +3984,7 @@ request and is ready to stop the agent when Omni asks. It does not reveal that o
|
|
|
3940
3984
|
or why Omni has not committed yet. While reporting `granted`, the provider remains available,
|
|
3941
3985
|
continues to honour Omni's current capacity, and continues offering work normally.
|
|
3942
3986
|
|
|
3943
|
-
Omni separately tracks the aggregate
|
|
3987
|
+
Omni separately tracks the aggregate agent application states `working`, `requesting-break`, `committing-break`,
|
|
3944
3988
|
`cancelling-break`, and `on-break`. While `requesting-break`, Omni shows which providers remain
|
|
3945
3989
|
outstanding and offers **Cancel break request**, but does not tell the agent that their break has
|
|
3946
3990
|
begun.
|
|
@@ -3957,9 +4001,9 @@ Omni coordinates one break attempt as follows:
|
|
|
3957
4001
|
from and is not asked: nothing is asked of it, the break proceeds without it, and when the
|
|
3958
4002
|
login is restored it is reconciled from its snapshot as a set-aside provider is, with no
|
|
3959
4003
|
capacity until then. `refreshing` keeps a provider in — its identity and capabilities remain
|
|
3960
|
-
available and work continues. `assertBreakAttemptProviders` holds
|
|
4004
|
+
available and work continues. `assertBreakAttemptProviders` holds an agent application to this set.
|
|
3961
4005
|
2. Enter `requesting-break`. Keep the agent's normal capacity in place throughout this phase.
|
|
3962
|
-
3. Send one `requestBreak` to every asked provider. A provider reports `awaiting-
|
|
4006
|
+
3. Send one `requestBreak` to every asked provider. A provider reports `awaiting-approval` or
|
|
3963
4007
|
`granted`; neither state stops work. A denial transitions directly to `not-requested` and
|
|
3964
4008
|
causes Omni to take the cancel path.
|
|
3965
4009
|
4. If every asked provider reports `granted`, durably choose commit, enter `committing-break`,
|
|
@@ -3974,7 +4018,7 @@ Omni coordinates one break attempt as follows:
|
|
|
3974
4018
|
5. If any asked provider fails or denies the request, cannot be reconciled within the bounded
|
|
3975
4019
|
decision timeout, or the agent cancels before commit, durably choose cancel and enter
|
|
3976
4020
|
`cancelling-break`. Send `cancelBreak()` to every provider still reporting
|
|
3977
|
-
`awaiting-
|
|
4021
|
+
`awaiting-approval` or `granted`. Work continues during cancellation because no stop was
|
|
3978
4022
|
committed. Return to `working` only after no provider retains either state.
|
|
3979
4023
|
|
|
3980
4024
|
Commit and cancel are mutually exclusive decisions for one break attempt. Once Omni chooses commit
|
|
@@ -4044,11 +4088,11 @@ asked for, but only until the session ends — after a reload or reconnect, or w
|
|
|
4044
4088
|
the agent on the break itself, the provider is the only one who knows.
|
|
4045
4089
|
|
|
4046
4090
|
Omit it when you cannot say, and when there is no break: reporting a reason alongside
|
|
4047
|
-
`
|
|
4091
|
+
`status: "not-requested"` describes a break that is not happening, and is rejected.
|
|
4048
4092
|
|
|
4049
4093
|
### `cancelBreak()`
|
|
4050
4094
|
|
|
4051
|
-
Cancels the active pre-commit request while its
|
|
4095
|
+
Cancels the active pre-commit request while its status is `awaiting-approval` or `granted`.
|
|
4052
4096
|
Cancellation releases the request but does not restore work because work never stopped. If
|
|
4053
4097
|
commit already won, the provider returns `omni.break-already-committed`. The resulting state is
|
|
4054
4098
|
reported through `break-state`.
|
|
@@ -4089,7 +4133,7 @@ an absent `team` still means the login is not entitled to that contribution.
|
|
|
4089
4133
|
|
|
4090
4134
|
| `TeamMember` field | Contract |
|
|
4091
4135
|
| --- | --- |
|
|
4092
|
-
| `id` | Required `UserId`. A task carries no names and neither does a team member list: Omni resolves what to display with `
|
|
4136
|
+
| `id` | Required `UserId`. A task carries no names and neither does a team member list: Omni resolves what to display with `getUserDetails()`. |
|
|
4093
4137
|
| `availability` | Required. What the member is doing now. |
|
|
4094
4138
|
| `since` | Optional. When the current `availability` began — not when they signed in, and not when the team member list was read. |
|
|
4095
4139
|
| `break` | Present only while the member has an outstanding break request. See **A member waiting for a break**. |
|
|
@@ -4101,7 +4145,7 @@ Each availability value means one thing:
|
|
|
4101
4145
|
| `ready` | Signed in, able to take work, none assigned. |
|
|
4102
4146
|
| `on-task` | Working on at least one task. It says nothing about how many, and nothing about whether more will fit. |
|
|
4103
4147
|
| `on-break` | Stopped and not taking work, whether they asked or somebody stopped them. The reason lives on their own `BreakState`, not here. |
|
|
4104
|
-
| `reserved` | Signed in here, and the
|
|
4148
|
+
| `reserved` | Signed in here, and the agent application holds this agent's capacity for another provider (`count: 0`, agent application-stopped): not receiving this provider's work, and not on a break. See **Capacity**. |
|
|
4105
4149
|
| `signed-out` | Known to this team but not signed in to this provider. |
|
|
4106
4150
|
|
|
4107
4151
|
**Always publish the complete team member list, never a change to it.** Team presence typically reaches an
|
|
@@ -4139,12 +4183,12 @@ One method, `executeTeamBreak`, taking a discriminated command exactly as `execu
|
|
|
4139
4183
|
| --- | --- |
|
|
4140
4184
|
| `{ type: "decide-break-request", memberId: UserId, decision, reason? }` | Settles one pending request. `decision` is `granted` or `denied`. A grant moves the member to `granted`; a denial ends the request and moves it directly to `not-requested`. |
|
|
4141
4185
|
| `{ type: "set-break-policy", policy }` | `approval-required`, `automatically-approved`, or `requests-suspended`. |
|
|
4142
|
-
| `{ type: "force-break", 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 forced break carries it as `activeReasonId`, so its kind is known. |
|
|
4143
|
-
| `{ type: "end-forced-break", memberId: UserId }` |
|
|
4186
|
+
| `{ type: "force-break", memberId: UserId, reasonId?, reason?, expectedDurationMs? }` | Puts a member on a break they did not ask for. `reasonId` names a published `BreakReason.id` and is required whenever the provider publishes `reasons`; the member's forced break carries it as `activeReasonId`, so its kind is known. Optional `expectedDurationMs` is advisory and is published on the resulting forced break. |
|
|
4187
|
+
| `{ type: "end-forced-break", memberId: UserId }` | Lifts the forced-break restriction, whoever forced it, by clearing `BreakState.forced`. The committed break continues; only the agent resumes work. |
|
|
4144
4188
|
|
|
4145
4189
|
`memberId` is this provider's own identifier for the member, as published on its team member list. It is
|
|
4146
4190
|
never an identifier from another provider, and Omni does not translate between them; names come
|
|
4147
|
-
from `
|
|
4191
|
+
from `getUserDetails()`.
|
|
4148
4192
|
|
|
4149
4193
|
`requests-suspended` means requests are **rejected outright** rather than left pending — nobody is coming to
|
|
4150
4194
|
approve them. A provider that suspends break requests must also publish `canRequestBreak: false` to the team's
|
|
@@ -4194,7 +4238,7 @@ they joined -- is answered `failed`, whatever their ceiling; the request stands
|
|
|
4194
4238
|
or until it is withdrawn or declined.
|
|
4195
4239
|
|
|
4196
4240
|
**A lead on a break does not join.** A break is a reported state in which the agent is not working,
|
|
4197
|
-
and a join is work. Omni offers Join to a lead only while their own `BreakState.
|
|
4241
|
+
and a join is work. Omni offers Join to a lead only while their own `BreakState.status` is neither
|
|
4198
4242
|
`starting-after-task` nor `on-break` -- a committed break waiting for the lead's current work to
|
|
4199
4243
|
finish is not given more -- and a provider answers a `join` from a lead on such a break `failed`.
|
|
4200
4244
|
The request stands for another lead, as it does when this one is already on a call.
|
|
@@ -4233,7 +4277,7 @@ const leadAssistCapable = {
|
|
|
4233
4277
|
```
|
|
4234
4278
|
|
|
4235
4279
|
Lead and member alike are `UserId`s of this provider, so an adapter publishing them implements
|
|
4236
|
-
`
|
|
4280
|
+
`getUserDetails()`; names never travel on a task or a team member list.
|
|
4237
4281
|
|
|
4238
4282
|
### Listening to a call
|
|
4239
4283
|
|
|
@@ -4345,17 +4389,17 @@ about whether anybody has to act on it, and the difference is a lead's entire ac
|
|
|
4345
4389
|
|
|
4346
4390
|
| `break` | Means |
|
|
4347
4391
|
| --- | --- |
|
|
4348
|
-
| `awaiting-
|
|
4392
|
+
| `awaiting-approval` | Somebody has to decide. This is the lead's queue. |
|
|
4349
4393
|
| `granted` | Decided yes, but Omni has not told the provider to stop yet. Work continues and nobody needs to decide. |
|
|
4350
4394
|
| `starting-after-task` | Already granted; it begins when their current task ends. Nobody needs to act. |
|
|
4351
4395
|
|
|
4352
4396
|
Those three are the only values that appear here. `not-requested` is absence — omit `break`
|
|
4353
4397
|
instead. `on-break` is `availability: "on-break"`, and a denial transitions to `not-requested`,
|
|
4354
|
-
so neither survives to be reported. It is otherwise the same `
|
|
4398
|
+
so neither survives to be reported. It is otherwise the same `BreakStatus` the member's own
|
|
4355
4399
|
break state uses, rather than a parallel vocabulary for the lead's view, so the two cannot drift
|
|
4356
4400
|
apart.
|
|
4357
4401
|
|
|
4358
|
-
Omni offers Approve and Deny only while a member is `awaiting-
|
|
4402
|
+
Omni offers Approve and Deny only while a member is `awaiting-approval`, shows `granted` as agreed
|
|
4359
4403
|
but not started, and shows `starting-after-task` as settled.
|
|
4360
4404
|
|
|
4361
4405
|
**Live status, not a record.** The provider derives it from what is true now — not stored, not
|
|
@@ -4374,12 +4418,12 @@ decision or it is not.
|
|
|
4374
4418
|
## Real-time media
|
|
4375
4419
|
|
|
4376
4420
|
Every voice provider has media: `channel: "voice"` says audio exists. Where the agent hears it
|
|
4377
|
-
depends on the `phone` the
|
|
4421
|
+
depends on the `phone` the agent application selected at authentication and connect from the manifest's
|
|
4378
4422
|
`phones`. On a softphone, audio lands in Omni; on a desk phone, it lands on the handset and Omni
|
|
4379
4423
|
opens no audio. See **How the agent hears the call**. Media transitions are voice-only;
|
|
4380
4424
|
chat and email publish none (`event.media.channel`, `stream.taskMedia.channel`).
|
|
4381
4425
|
|
|
4382
|
-
The provider owns signalling and the platform's endpoint configuration. The
|
|
4426
|
+
The provider owns signalling and the platform's endpoint configuration. The agent application selects the
|
|
4383
4427
|
declared phone mode; it does not enumerate or reconfigure the platform's devices. The selected
|
|
4384
4428
|
mode stays with the login rather than being inferred from a snapshot or a device failure.
|
|
4385
4429
|
|
|
@@ -4388,11 +4432,11 @@ phase follow the provider's reports about the work, and the media — attaching,
|
|
|
4388
4432
|
hold, a consult, a conference or a transfer, and ending — is transient beside it. See **A task is
|
|
4389
4433
|
never its audio** under **Task allocation lifecycle**.
|
|
4390
4434
|
|
|
4391
|
-
### The
|
|
4435
|
+
### The agent application reports, the adapter decides
|
|
4392
4436
|
|
|
4393
4437
|
The adapter runs inside Omni and sees nothing of the station for itself: the devices, the
|
|
4394
|
-
permissions, the audio element and the network are the
|
|
4395
|
-
the
|
|
4438
|
+
permissions, the audio element and the network are the agent application's, and only the agent application can tell. So
|
|
4439
|
+
the agent application reports, and the adapter asks. **An adapter consults `ConnectContext.host` before it
|
|
4396
4440
|
declares the agent ready to its platform, and again whenever the report changes**, and it decides
|
|
4397
4441
|
what any of it means and what to relay upstream — go not-ready, refuse calls, carry on because the
|
|
4398
4442
|
platform's audio lands elsewhere. Omni facilitates and does not take responsibility: it captures
|
|
@@ -4400,29 +4444,29 @@ the microphone once as the voice connection opens, so the permission prompt land
|
|
|
4400
4444
|
is signing in rather than over a contact, prompts, retries on the agent's request, tells the agent
|
|
4401
4445
|
what failed, and reports. It never decides for the adapter what a missing microphone means.
|
|
4402
4446
|
|
|
4403
|
-
**The
|
|
4404
|
-
Two of this contract's obligations fall on the
|
|
4447
|
+
**The agent application also guarantees, and a promise the provider cannot see is not one it can rely on.**
|
|
4448
|
+
Two of this contract's obligations fall on the agent application rather than the provider — honouring a task
|
|
4405
4449
|
browser's `urlVisibility`, and taking a `consent` offer only on the person's own press — and more
|
|
4406
4450
|
than one desk speaks this contract: a browser and a native application differ in what they can
|
|
4407
4451
|
reach, and a provider never branches on which it is talking to, only on what it has declared —
|
|
4408
|
-
here, and in `host.mute` (see **The station is the
|
|
4409
|
-
connected
|
|
4452
|
+
here, and in `host.mute` (see **The station is the agent application's**). So `ConnectContext.host.guarantees` says which promises the
|
|
4453
|
+
connected agent application makes, declared once per connection, presence being the guarantee exactly as it is
|
|
4410
4454
|
the permission everywhere else:
|
|
4411
4455
|
|
|
4412
4456
|
| Guarantee | Contract |
|
|
4413
4457
|
| --- | --- |
|
|
4414
|
-
| `browserUrlVisibility` | Every task browser's `urlVisibility` is honoured in this
|
|
4458
|
+
| `browserUrlVisibility` | Every task browser's `urlVisibility` is honoured in this agent application's chrome, tab by tab. A provider that would send a caller's number in a URL checks this first and tokenises where the promise is absent. |
|
|
4415
4459
|
| `personConsent` | A `consent` offer is accepted only by the person's own explicit act, never on their behalf. A provider whose work may only be taken by a human checks this first and does not offer it where the promise is absent. |
|
|
4416
4460
|
|
|
4417
|
-
A guarantee the
|
|
4461
|
+
A guarantee the agent application does not make is an absent key, never `false` — `validateHostGuarantees`
|
|
4418
4462
|
refuses a false one, as it refuses a name this contract does not list.
|
|
4419
4463
|
|
|
4420
4464
|
| Field | Contract |
|
|
4421
4465
|
| --- | --- |
|
|
4422
|
-
| `online` | Whether the
|
|
4466
|
+
| `online` | Whether the agent application has a network interface up. Not a claim that anything is reachable — the adapter knows whether it can reach its own platform far better than the agent application does — so `false` is a reason not to go ready and `true` is not a reason to. |
|
|
4423
4467
|
| `audio` | Present on a voice connection, absent where there is no audio. |
|
|
4424
|
-
| `audio.input` | `available` with `localAudio` — the microphone as captured, the same stream `openMedia` receives — and `flowing`, false while no audio moves through it, when `mutedBy` says who stopped it: `host`, the
|
|
4425
|
-
| `audio.output` | `available`, with `flowing` where the
|
|
4468
|
+
| `audio.input` | `available` with `localAudio` — the microphone as captured, the same stream `openMedia` receives — and `flowing`, false while no audio moves through it, when `mutedBy` says who stopped it: `host`, the agent application's own Mute on an agent application that mutes the station, or `station`, a slider on the headset or the operating system, which the agent application observed and did not do. An adapter treats `host` as the agent's act on a call and `station` as a condition of the station. `unavailable` with `reason`, since each wants a different fix from the agent: `no-device`; `denied`; `not-asked`, which an agent application that asks at connect never publishes; `in-use`, a device present and permitted that another application holds — on an agent desktop the commonest of all; `lost`, a capture that ended. An agent application decides the reason from the devices before the error name: a browser can report a permission error on a machine with no microphone at all, and "grant permission" is the wrong instruction for an agent who needs to plug one in. `failure` carries the words Omni showed them. |
|
|
4469
|
+
| `audio.output` | `available`, with `flowing` where the agent application can know whether audio reaches the speaker — a browser mostly cannot, and omits it; a native agent application reads the endpoint — false while it is silenced, when `mutedBy` says who did it, as on input. Or `unavailable` with `reason` — `no-device`, or `lost` for one removed — and `failure`: an agent who cannot hear is as unable to take a call as one who cannot speak. |
|
|
4426
4470
|
|
|
4427
4471
|
Omni republishes the report whenever it changes — a permission granted late, a headset unplugged,
|
|
4428
4472
|
a network gone — and it publishes a state, not a flicker: a change that resolves within moments is
|
|
@@ -4430,13 +4474,13 @@ not reported, so an adapter may act on what it reads without debouncing it again
|
|
|
4430
4474
|
report is a task fact or a capacity fact; it is what the station can do, and the adapter's platform
|
|
4431
4475
|
is the one that knows whether an agent without a microphone, or without a speaker, can work.
|
|
4432
4476
|
|
|
4433
|
-
The
|
|
4477
|
+
The agent application's own obligations here — asking at connect, never publishing `not-asked` when it does,
|
|
4434
4478
|
publishing a state and not a flicker — are Omni's tests' to hold. `exerciseAdapter` holds the
|
|
4435
|
-
other side: it validates the shape of whatever
|
|
4479
|
+
other side: it validates the shape of whatever agent application a test hands the adapter, requires `audio` on
|
|
4436
4480
|
a softphone login and none elsewhere -- not on a desk phone, not off voice
|
|
4437
4481
|
(`context.host.audio.required` / `.unexpected`), holds `host.mute` to the same line (`host.mute.required`
|
|
4438
4482
|
/ `.unexpected`, `host.mute` for a third word) -- and refuses a voice adapter that never asked the
|
|
4439
|
-
|
|
4483
|
+
agent application anything (`connection.host.consulted`), and one that subscribed to nothing will never hear of a
|
|
4440
4484
|
change (`connection.host.subscribed`): the obligation has two halves, and each is held.
|
|
4441
4485
|
|
|
4442
4486
|
### Capacity around setup
|
|
@@ -4447,8 +4491,8 @@ set up — the adapter's own registration incomplete, its credentials not yet re
|
|
|
4447
4491
|
|
|
4448
4492
|
Nothing closes that window, because nothing opens it: **Omni states no capacity until the
|
|
4449
4493
|
connection is established**, and **Work is pulled, never pushed** makes an allocation with none
|
|
4450
|
-
stated a violation. Capacity does not wait on the microphone: whether an agent without
|
|
4451
|
-
can take calls is the platform's question, answered by the adapter from the
|
|
4494
|
+
stated a violation. Capacity does not wait on the microphone: whether an agent without agent application audio
|
|
4495
|
+
can take calls is the platform's question, answered by the adapter from the agent application's report, not by
|
|
4452
4496
|
Omni withholding capacity for every platform alike.
|
|
4453
4497
|
|
|
4454
4498
|
Capacity follows **automatically** once the connection is established; the agent does not press
|
|
@@ -4457,7 +4501,7 @@ anything to become available.
|
|
|
4457
4501
|
| Situation | What Omni sends |
|
|
4458
4502
|
| --- | --- |
|
|
4459
4503
|
| Connecting | Nothing. No capacity has been stated, so nothing may be allocated. |
|
|
4460
|
-
| Connected and idle | `setCapacity({ count: n })`, with the
|
|
4504
|
+
| Connected and idle | `setCapacity({ count: n })`, with the agent application's report already available to consult. |
|
|
4461
4505
|
| A task starts or ends | Nothing. The provider counts its own against the ceiling. |
|
|
4462
4506
|
| The agent's provisioned capacity changes | `setCapacity({ count: n })` |
|
|
4463
4507
|
| Agent asks for a break | `requestBreak`. Capacity is unchanged and work continues. |
|
|
@@ -4473,57 +4517,57 @@ a return.
|
|
|
4473
4517
|
|
|
4474
4518
|
### How the agent hears the call
|
|
4475
4519
|
|
|
4476
|
-
A voice platform can put an agent on a **softphone**, where the call's audio lands in the
|
|
4520
|
+
A voice platform can put an agent on a **softphone**, where the call's audio lands in the agent application
|
|
4477
4521
|
and Omni owns the microphone and the playback, or on a **desk phone**, a handset on the platform's
|
|
4478
|
-
switch that the platform rings, where the
|
|
4522
|
+
switch that the platform rings, where the agent application owns no audio and only shows the call. Which of
|
|
4479
4523
|
these a platform can do is a fact about the platform, and which one a login is on is a choice the
|
|
4480
|
-
|
|
4524
|
+
agent application makes for that agent -- so the manifest declares and the agent application chooses.
|
|
4481
4525
|
|
|
4482
4526
|
```ts
|
|
4483
4527
|
// Manifest: what the platform can do
|
|
4484
4528
|
phones: ["softphone", "deskPhone"]
|
|
4485
|
-
// The
|
|
4529
|
+
// The agent application, at authentication and again at connect: which one this login is on
|
|
4486
4530
|
{ phone: "softphone" }
|
|
4487
4531
|
```
|
|
4488
4532
|
|
|
4489
4533
|
A voice manifest lists its `phones` and any other channel lists none (`manifest.phones.required`,
|
|
4490
|
-
`manifest.phones.channel`). The
|
|
4534
|
+
`manifest.phones.channel`). The agent application's `phone` is required on a voice login, absent on any other,
|
|
4491
4535
|
and one the manifest listed (`context.phone.required`, `.unexpected`, `.unsupported`). Everything
|
|
4492
|
-
about audio then follows the phone rather than the channel: on a softphone the
|
|
4536
|
+
about audio then follows the phone rather than the channel: on a softphone the agent application reports its
|
|
4493
4537
|
audio, the adapter implements `openMedia`, and `task-media-started` is the word to open it; on a
|
|
4494
|
-
desk phone the
|
|
4538
|
+
desk phone the agent application reports no audio, opens nothing, and `task-media-started` still marks the
|
|
4495
4539
|
moment the call is live so the desk shows it, with the sound on the handset. A platform that lists
|
|
4496
4540
|
`deskPhone` alone never implements `openMedia`; one that lists `softphone` always does.
|
|
4497
4541
|
|
|
4498
|
-
**The mode comes from the
|
|
4542
|
+
**The mode comes from the agent application; the status comes from the provider.** The agent application knows which kind
|
|
4499
4543
|
of station the agent signed in at, because the person chose it, and nothing else can know that.
|
|
4500
4544
|
The provider knows whether that station can carry a call right now -- whether a handset is
|
|
4501
|
-
registered -- which
|
|
4545
|
+
registered -- which an agent application in a browser cannot see. So the agent application's `phone` selects the branch, and
|
|
4502
4546
|
the provider's own knowledge of the device decides readiness within it. A provider never overrides
|
|
4503
|
-
the
|
|
4547
|
+
the agent application's declaration from its device record: treating a desk-phone login as a softphone because
|
|
4504
4548
|
the handset is momentarily unregistered, and demanding a microphone of it, is the reading this
|
|
4505
4549
|
sentence exists to refuse. What the provider does when the handset is not registered is what it
|
|
4506
4550
|
does for any station that cannot take a call -- hold the agent not-ready and say why.
|
|
4507
4551
|
|
|
4508
|
-
**On a desk-phone login the
|
|
4509
|
-
no audio to attach;
|
|
4552
|
+
**On a desk-phone login the agent application never calls `openMedia`.** There is no stream to hand over and
|
|
4553
|
+
no audio to attach; an agent application that calls it anyway is in error, and an adapter that receives the call
|
|
4510
4554
|
answers `unavailable` with a non-retryable failure, since waiting changes nothing about a station
|
|
4511
4555
|
that is a telephone. The harness requires no `openMedia` of such an adapter and never calls it.
|
|
4512
4556
|
|
|
4513
4557
|
**Which handset a desk-phone login rings is the platform's configuration for that agent**, and
|
|
4514
|
-
this wire never asks the agent for it:
|
|
4558
|
+
this wire never asks the agent for it: an agent application declares `phone` and nothing more. What a platform
|
|
4515
4559
|
asks on surfaces of its own is its own decision.
|
|
4516
4560
|
|
|
4517
4561
|
**A declared phone the platform does not permit for this agent is a login that cannot be
|
|
4518
4562
|
established.** The manifest says what the platform can do; the agent's record, kept by an
|
|
4519
|
-
administrator, says what this agent is configured for, and the two can disagree with the
|
|
4563
|
+
administrator, says what this agent is configured for, and the two can disagree with the agent application's
|
|
4520
4564
|
declaration -- a softphone declared for an agent whose record says desk phone. Honouring that
|
|
4521
4565
|
declaration on a switch that allows one registration per endpoint would evict the handset: the
|
|
4522
4566
|
agent's phone stops ringing and their calls land in a tab, and nobody is told. So the provider
|
|
4523
|
-
refuses the login at authentication, with `omni.phone-not-permitted` and a message the
|
|
4567
|
+
refuses the login at authentication, with `omni.phone-not-permitted` and a message the agent application shows
|
|
4524
4568
|
-- "this agent is configured for a desk phone" -- and refusing is correct, not an override of the
|
|
4525
|
-
|
|
4526
|
-
never retryable: trying again does not reconfigure the agent, and
|
|
4569
|
+
agent application. It is a refusal, never a negotiation afterwards and never a quiet substitution, and it is
|
|
4570
|
+
never retryable: trying again does not reconfigure the agent, and an agent application validating the answer
|
|
4527
4571
|
(`validateAuthenticationResult`) refuses a phone refusal marked otherwise
|
|
4528
4572
|
(`authentication.failure.phone.retryable`). And **a
|
|
4529
4573
|
provider never makes a declared phone true by changing the platform's configuration**: the record
|
|
@@ -4542,9 +4586,9 @@ openMedia({ taskId, localAudio }): Promise<OpenMediaResult>
|
|
|
4542
4586
|
The adapter speaks whatever its platform speaks — SIP over WebSocket, a vendor SDK, plain
|
|
4543
4587
|
WebRTC — and **none of that appears in this contract**. Registration, signalling, credential
|
|
4544
4588
|
renewal and reconnect are the adapter's, exactly as its authentication and transport already
|
|
4545
|
-
are. Omni owns what belongs to the
|
|
4589
|
+
are. Omni owns what belongs to the agent application: the microphone, the output element, mute, and when a
|
|
4546
4590
|
session ends — owning the microphone meaning capturing it, prompting, retrying and reporting how
|
|
4547
|
-
it stands, never deciding for the adapter what a missing one means (see **The
|
|
4591
|
+
it stands, never deciding for the adapter what a missing one means (see **The agent application reports, the
|
|
4548
4592
|
adapter decides**).
|
|
4549
4593
|
|
|
4550
4594
|
| Member | Contract |
|
|
@@ -4553,9 +4597,9 @@ adapter decides**).
|
|
|
4553
4597
|
| `setMuted(muted)` | Mutes the agent's microphone on this session. |
|
|
4554
4598
|
| `close()` | Releases the session. Omni calls it when the task ends. |
|
|
4555
4599
|
|
|
4556
|
-
`localAudio` is the agent's microphone as Omni captured it, the same stream the
|
|
4600
|
+
`localAudio` is the agent's microphone as Omni captured it, the same stream the agent application's report
|
|
4557
4601
|
carries as `audio.input.localAudio`, and absent while that input is `unavailable`. A provider that
|
|
4558
|
-
bridges audio without
|
|
4602
|
+
bridges audio without an application-side input may ignore it; one that needs it and finds it absent
|
|
4559
4603
|
answers `unavailable` with a failure Omni shows the agent.
|
|
4560
4604
|
|
|
4561
4605
|
**When to ask is the provider's word, not Omni's guess.** On a softphone login, Omni opens media on `task-media-started`,
|
|
@@ -4593,87 +4637,87 @@ command asks the provider to **perform** something: `hold`, `transfer`, `confere
|
|
|
4593
4637
|
Provider-targeted `recording` commands and the rest act on the platform's own call leg, its bridge, or its record of the
|
|
4594
4638
|
task. Nothing has happened until the provider applies them, and `failed` means nothing happened.
|
|
4595
4639
|
|
|
4596
|
-
**The provider performs every action; the
|
|
4597
|
-
an affordance, never the enforcement: the
|
|
4640
|
+
**The provider performs every action; the agent application only offers it.** A control drawn on the agent application is
|
|
4641
|
+
an affordance, never the enforcement: the agent application asks, and the provider does or declines. A provider
|
|
4598
4642
|
answers `failed` for a command whose capability it did not publish, and that is the provider
|
|
4599
|
-
honouring its own declaration, not the
|
|
4643
|
+
honouring its own declaration, not the agent application deciding policy -- an agent application that refuses a command on
|
|
4600
4644
|
its own reading of a queue's flag has decided something that was never its to decide. What a
|
|
4601
4645
|
command means on the platform is the provider's to work out: `end-call` disconnects the caller channel, and
|
|
4602
|
-
which legs on which bridge that touches is a fact about the switch, never a choice the
|
|
4603
|
-
The one thing the
|
|
4646
|
+
which legs on which bridge that touches is a fact about the switch, never a choice the agent application makes.
|
|
4647
|
+
The one thing the agent application performs physically is the microphone, and that is not a command at all.
|
|
4604
4648
|
|
|
4605
|
-
### The station is the
|
|
4649
|
+
### The station is the agent application's
|
|
4606
4650
|
|
|
4607
4651
|
The microphone, the speaker, the headset and its buttons are the station's, and the station is the
|
|
4608
|
-
|
|
4652
|
+
agent application's. So mute is not a capability a provider declares, not a control a queue allows or a team
|
|
4609
4653
|
locks, not a preference the person keeps with the provider, and not a command: nothing about the
|
|
4610
|
-
station crosses to the provider except two things -- the station's condition, in the
|
|
4611
|
-
and the record of when the agent could not be heard, through `recordStep`.
|
|
4612
|
-
every voice task with media open, under its own
|
|
4654
|
+
station crosses to the provider except two things -- the station's condition, in the agent application report,
|
|
4655
|
+
and the record of when the agent could not be heard, through `recordStep`. An agent application offers Mute on
|
|
4656
|
+
every voice task with media open, under its own local policy, in `in-progress` and `paused`
|
|
4613
4657
|
alone, and performs it through `VoiceMediaSession.setMuted()`. A task carrying
|
|
4614
4658
|
`capabilities.mute` is refused (`task.capability.unknown`), and so is a `mute` command
|
|
4615
4659
|
(`command.type`), a `mute` preference (`preference.id`) and a `mute` policy (`team.policy.key`).
|
|
4616
4660
|
|
|
4617
|
-
**Two mutes, and the
|
|
4618
|
-
|
|
4619
|
-
changes, and every softphone
|
|
4661
|
+
**Two mutes, and the agent application states which its Mute performs.** A *stream* mute stops the audio the
|
|
4662
|
+
agent application sends: the microphone keeps capturing, the party hears silence, nothing else on the machine
|
|
4663
|
+
changes, and every softphone agent application can do it. A *station* mute silences the microphone itself at
|
|
4620
4664
|
the operating system: every application on the machine goes quiet, the system shows it, and a
|
|
4621
|
-
headset that follows the system follows it. Only a native
|
|
4622
|
-
this
|
|
4665
|
+
headset that follows the system follows it. Only a native agent application can do it. `Host.mute` says which
|
|
4666
|
+
this agent application does -- `stream` or `station` -- stated on a softphone login, where the agent application holds the
|
|
4623
4667
|
microphone, and absent on a desk phone and off voice (`host.mute.required` / `.unexpected`). A
|
|
4624
|
-
browser says `stream`. A native
|
|
4625
|
-
application the
|
|
4668
|
+
browser says `stream`. A native agent application says whichever it does. Nothing on the wire says what kind of
|
|
4669
|
+
application the agent application is; a provider reads only what the agent application declared.
|
|
4626
4670
|
|
|
4627
4671
|
**A silenced device says who silenced it.** `audio.input.flowing: false` and
|
|
4628
|
-
`audio.output.flowing: false` carry `mutedBy`: `host` when the
|
|
4629
|
-
only ever true of
|
|
4630
|
-
flowing -- and `station` when a slider on the headset or the operating system did, which the
|
|
4631
|
-
observed and did not do. Without the word, a station-muting
|
|
4672
|
+
`audio.output.flowing: false` carry `mutedBy`: `host` when the agent application's own Mute did it -- which is
|
|
4673
|
+
only ever true of an agent application whose mute is `station`, since a stream mute leaves the microphone
|
|
4674
|
+
flowing -- and `station` when a slider on the headset or the operating system did, which the agent application
|
|
4675
|
+
observed and did not do. Without the word, a station-muting agent application's own Mute would read as a fault
|
|
4632
4676
|
and an adapter would take the agent out of ready in the middle of a call. A browser reads a station
|
|
4633
|
-
mute through the capture track's muted state, read-only, and cannot lift it; a native
|
|
4677
|
+
mute through the capture track's muted state, read-only, and cannot lift it; a native agent application reads
|
|
4634
4678
|
the endpoint and can clear an operating-system mute. A hardware slider is nobody's to clear.
|
|
4635
4679
|
|
|
4636
4680
|
**The record carries the same word.** A `muted` interaction leg is a period the agent could not be
|
|
4637
|
-
heard, and the record exists so that period is not a hole. The
|
|
4681
|
+
heard, and the record exists so that period is not a hole. The agent application reports every such period
|
|
4638
4682
|
through `recordStep` -- its own Mute, and a station mute it observed during a call -- with
|
|
4639
4683
|
`mutedBy` saying whose the silence was, and the provider writes the word into the entry
|
|
4640
|
-
(`task.interactionHistory.mutedBy`). The report names no agent because the
|
|
4684
|
+
(`task.interactionHistory.mutedBy`). The report names no agent because the agent application has exactly one, and
|
|
4641
4685
|
the provider knows who that is: it attributes the leg to the login's agent in `by`, a fact it
|
|
4642
|
-
holds and not an inference, for a station mute as much as for the
|
|
4686
|
+
holds and not an inference, for a station mute as much as for the agent application's own. An unattributed
|
|
4643
4687
|
`muted` entry is refused (`task.interactionHistory.muted.by`); a shared handset's unattributed hold
|
|
4644
4688
|
is a different claim, and stands. A supervisor reading the record then sees "the agent muted for
|
|
4645
4689
|
forty seconds" and "the agent's headset was muted for forty seconds" as the different things they
|
|
4646
|
-
are. See **The
|
|
4690
|
+
are. See **The agent application records what it performs**.
|
|
4647
4691
|
|
|
4648
4692
|
**Mute has a lifecycle, and none of it is inferred.** A call that starts while the station is
|
|
4649
4693
|
already muted begins a `muted` leg the moment its media starts, `mutedBy: "station"`. Media that
|
|
4650
|
-
ends while any leg is open ends the leg at that instant, as every
|
|
4651
|
-
|
|
4694
|
+
ends while any leg is open ends the leg at that instant, as every agent application-performed leg ends. And the
|
|
4695
|
+
agent application's own Mute starts off on every call, which is every `task-media-started` -- a connect-back
|
|
4652
4696
|
opens a second call on the same task with no offer -- so an agent is never muted by the call before.
|
|
4653
4697
|
|
|
4654
|
-
**Every press on a headset is the agent's own press**, performed the
|
|
4655
|
-
follow the
|
|
4698
|
+
**Every press on a headset is the agent's own press**, performed the agent application's way, and the lights
|
|
4699
|
+
follow the agent application's state. The `personConsent` guarantee is honoured by a hook-switch press exactly
|
|
4656
4700
|
as by a click. What a call-control headset sends, and what becomes of it:
|
|
4657
4701
|
|
|
4658
|
-
| Input | Where it comes from | What the
|
|
4702
|
+
| Input | Where it comes from | What the agent application does | What crosses to the provider |
|
|
4659
4703
|
| --- | --- | --- | --- |
|
|
4660
|
-
| Mute button | The headset, on the HID Telephony usage page: a Phone Mute report on the press, a Mute LED the
|
|
4661
|
-
| Hook switch | The headset, HID Telephony | A press of the
|
|
4704
|
+
| 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"` |
|
|
4705
|
+
| 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 |
|
|
4662
4706
|
| 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 with no dial pad on a transfer, and are ignored | Nothing new |
|
|
4663
|
-
| 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
|
|
4707
|
+
| 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 |
|
|
4664
4708
|
| 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 |
|
|
4665
|
-
| 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;
|
|
4709
|
+
| 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 |
|
|
4666
4710
|
| Device changed, unplugged, permission revoked | The operating system | Re-capture, prompt, report | Already `audio.input` and `audio.output` with `lost`, `denied`, `no-device` |
|
|
4667
4711
|
|
|
4668
|
-
**What the
|
|
4669
|
-
what to do, with a button that clears a system mute only on
|
|
4712
|
+
**What the agent application shows.** Beside Mute, a standing line while the station is muted, saying which and
|
|
4713
|
+
what to do, with a button that clears a system mute only on an agent application whose mute is `station`. The
|
|
4670
4714
|
Mute control does not pretend: while the station is muted it shows the agent cannot be heard and
|
|
4671
|
-
that pressing it will not change that, and a press still toggles the
|
|
4715
|
+
that pressing it will not change that, and a press still toggles the agent application's own mute, so the two
|
|
4672
4716
|
states stay separate and the agent is not unmuted by surprise when the slider moves back. Before a
|
|
4673
4717
|
call, the same line in the ready state, so an agent does not learn of it from a silent customer;
|
|
4674
4718
|
whether work is held back is the adapter's decision from the report, as it is for every station
|
|
4675
4719
|
fact. Where a provider's own platform holds a mute of its own -- a bridge that silences a leg --
|
|
4676
|
-
that is a fact about the switch, reported as the provider sees fit, and never the
|
|
4720
|
+
that is a fact about the switch, reported as the provider sees fit, and never the agent application's Mute.
|
|
4677
4721
|
|
|
4678
4722
|
### Which commands need a capability
|
|
4679
4723
|
|
|
@@ -4686,7 +4730,7 @@ declared:
|
|
|
4686
4730
|
| `answer`, `accept` | Nothing. A task that was offered can be accepted, or offering it meant nothing. |
|
|
4687
4731
|
| `end-call` | The `endCall` capability. |
|
|
4688
4732
|
| `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`. |
|
|
4689
|
-
| `decline` | The `decline` capability on any channel, **and** Omni
|
|
4733
|
+
| `decline` | The `decline` capability on any channel, **and** Omni local policy permitting it. One word for refusing an offer, whatever the channel. |
|
|
4690
4734
|
| `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`. |
|
|
4691
4735
|
| `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`). |
|
|
4692
4736
|
| `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. |
|
|
@@ -4712,10 +4756,10 @@ The commands with a phase of their own -- `answer`, `accept` and `decline` in `p
|
|
|
4712
4756
|
|
|
4713
4757
|
`validateTaskCommand(command, task)` holds a command to this table at runtime, both ways: the
|
|
4714
4758
|
capability it needs, the phase it belongs to, and the state that has to stand. The task it wants
|
|
4715
|
-
is the one the provider published, not
|
|
4759
|
+
is the one the provider published, not an agent application's own mapping of it: an agent application that keeps only its
|
|
4716
4760
|
mapped shape has nothing honest to pass, and then checks shape alone, which is still worth doing
|
|
4717
4761
|
-- it names a command the wire never had -- but is not the table. Keeping the published task
|
|
4718
|
-
beside the mapped one is what the full check costs
|
|
4762
|
+
beside the mapped one is what the full check costs an agent application; an adapter has it for free. The
|
|
4719
4763
|
validator holds a command only to a task that stands: a task handed in that is not one the wire
|
|
4720
4764
|
published is named (`command.task`) rather than checked against.
|
|
4721
4765
|
|
|
@@ -4767,7 +4811,7 @@ react rather than only display the message:
|
|
|
4767
4811
|
| `omni.not-authenticated` | The provider session is no longer usable. The adapter has published `expired` at or before this answer — the state is what Omni surfaces reauthentication from; the code says why this action failed, and is never the only signal. |
|
|
4768
4812
|
| `omni.capability-not-enabled` | The action targets a capability this task, manifest, or login did not declare — including a lead command from a login whose `capabilities` no longer carry it. |
|
|
4769
4813
|
| `omni.task-not-found` | The provider-local task id is unknown, typically after the task already ended. |
|
|
4770
|
-
| `omni.phone-not-permitted` | The
|
|
4814
|
+
| `omni.phone-not-permitted` | The agent application declared a `phone` the platform does not permit for this agent -- a softphone for an agent configured for a desk phone, or the reverse. The login is refused at authentication, and the provider never reconfigures the agent to make the declaration true. See **How the agent hears the call**. |
|
|
4771
4815
|
| `omni.destination-not-permitted` | The dialled number, or the `destinationId` named, is not one the provider offers this agent. |
|
|
4772
4816
|
| `omni.rate-limited` | The action was throttled. Pair with `retryAfterMs`. |
|
|
4773
4817
|
| `omni.unavailable` | The provider is temporarily unable to serve the action, including any command sent while `transport-status` is not `active`. |
|
|
@@ -4781,7 +4825,7 @@ They are published as `OMNI_FAILURE_CODES`.
|
|
|
4781
4825
|
|
|
4782
4826
|
| Field | Contract |
|
|
4783
4827
|
| --- | --- |
|
|
4784
|
-
| `id` | Required identifier for this event, unique within the login. Omni does not act on it; it exists so
|
|
4828
|
+
| `id` | Required identifier for this event, unique within the login. Omni does not act on it; it exists so an agent application log line and an adapter log line can be matched when something has to be traced. |
|
|
4785
4829
|
| `loginId` | Login session that produced the event. Omni rejects any other value, which only reaches it if an adapter kept an old connection emitting after a re-login. |
|
|
4786
4830
|
| `occurredAt` | Valid RFC-3339 timestamp with an explicit timezone, representing provider observation time. |
|
|
4787
4831
|
| `event` | Typed `ProviderEvent` payload. |
|
|
@@ -4796,7 +4840,7 @@ instant that the source cannot establish makes that evidence unavailable, not pe
|
|
|
4796
4840
|
invent a timestamp or silently omit a required field.
|
|
4797
4841
|
|
|
4798
4842
|
Clock estimation is an exception, not the standard interpretation of events. An ISO timestamp
|
|
4799
|
-
identifies an instant; it does not prove that the
|
|
4843
|
+
identifies an instant; it does not prove that the agent application and provider clocks are synchronized.
|
|
4800
4844
|
Use an explicitly trusted clock in the relevant domain. Do not infer a general clock offset
|
|
4801
4845
|
from event arrivals, shift source timestamps, or use an estimate to order events or create history.
|
|
4802
4846
|
|
|
@@ -4804,29 +4848,29 @@ The current exceptions and their reasons are:
|
|
|
4804
4848
|
|
|
4805
4849
|
| Case | Exception and reason |
|
|
4806
4850
|
| --- | --- |
|
|
4807
|
-
|
|
|
4808
|
-
|
|
|
4851
|
+
| Agent application display of provider deadlines (`allocationExpiresAt`, `previewEndsAt`, wrap deadline) | A countdown may use an explicitly bounded provider-clock estimate when a trusted direct clock is unavailable, because the deadline belongs to another clock domain. This estimates the display only; it does not establish that the provider acted. Without usable time, show timing uncertainty. |
|
|
4852
|
+
| 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. |
|
|
4809
4853
|
| 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. |
|
|
4810
4854
|
| 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. |
|
|
4811
4855
|
| Direct snapshot reads and method requests/results | These are reads/operations, not event envelopes, and their current types have no general event timestamp. A snapshot event still carries `occurredAt`; embedded source instants remain unchanged. Do not treat a method result or read completion time as an occurrence boundary. |
|
|
4812
|
-
|
|
|
4813
|
-
| Provider receipt timestamps | The provider may use its own receipt instant for final records of receipt/processing because
|
|
4814
|
-
| Optional source instants and deadlines | Omit only where the declared type permits absence and the source has no evidence or the policy has no deadline (for example unlimited preview). Do not replace absence with
|
|
4856
|
+
| Agent application-provided provider-clock estimate | Best-effort ISO time may be supplied by the agent application for optional comparisons; it is not a source observation or accuracy guarantee. The reason is that the agent application does not own the provider clock. |
|
|
4857
|
+
| Provider receipt timestamps | The provider may use its own receipt instant for final records of receipt/processing because agent application timestamps are untrusted advisory input. Receipt must not be presented as an earlier action or capture boundary. |
|
|
4858
|
+
| Optional source instants and deadlines | Omit only where the declared type permits absence and the source has no evidence or the policy has no deadline (for example unlimited preview). Do not replace absence with agent application time. |
|
|
4815
4859
|
|
|
4816
4860
|
Every additional exception must be listed with its scope and reason before adoption. Estimates
|
|
4817
4861
|
must specify their uncertainty and validity; elapsed time should use monotonic aging, and a clock
|
|
4818
4862
|
jump invalidates the estimate. The optional check below samples provider time without changing event timestamps. Report
|
|
4819
4863
|
source-measured durations where required; timestamp subtraction is not a substitute.
|
|
4820
4864
|
|
|
4821
|
-
#### Optional periodic provider time checks and
|
|
4865
|
+
#### Optional periodic provider time checks and agent application estimates
|
|
4822
4866
|
|
|
4823
4867
|
A provider may declare `Manifest.timeCheck: true` and implement `Connection.checkTime(request)`.
|
|
4824
|
-
Omission means unsupported. The
|
|
4825
|
-
|
|
4868
|
+
Omission means unsupported. The agent application separately opts in with `ProviderTimeCheckPolicy`; these
|
|
4869
|
+
agent application-local settings are not task policy or a guarantee. All four durations are explicit positive
|
|
4826
4870
|
safe integer milliseconds, with `maxRoundTripMs <= timeoutMs <= intervalMs`. No default interval
|
|
4827
4871
|
is assumed. Validate settings with `validateProviderTimeCheckPolicy`.
|
|
4828
4872
|
|
|
4829
|
-
While the connection is active, the
|
|
4873
|
+
While the connection is active, the agent application checks immediately on opt-in and then at `intervalMs`
|
|
4830
4874
|
using a monotonic scheduler. Only one check may be outstanding. Missed ticks are skipped, never
|
|
4831
4875
|
replayed in a burst. Each request has a fresh ID. The provider samples its own event/deadline
|
|
4832
4876
|
clock after receiving the request and before returning `providerTime` as an ISO/RFC3339 instant,
|
|
@@ -4835,25 +4879,25 @@ may masquerade as provider time. A provider unable to sample that domain must no
|
|
|
4835
4879
|
Clock authority replacement or a discontinuity changes `clockId`.
|
|
4836
4880
|
|
|
4837
4881
|
Validate requests and responses with `validateProviderTimeCheckRequest` and
|
|
4838
|
-
`validateProviderTimeCheckResult`. The
|
|
4882
|
+
`validateProviderTimeCheckResult`. The agent application also checks the outstanding request, provider/connection
|
|
4839
4883
|
and login, monotonic elapsed time and cancellation after the await: a late successful response is
|
|
4840
4884
|
still rejected. Reject samples at or beyond `timeoutMs`, or above `maxRoundTripMs`. Cancel local
|
|
4841
4885
|
tracking and invalidate estimates on disconnect, logout, policy disable, clock change or clock
|
|
4842
4886
|
jump; late results cannot restore them. Resume with a fresh check after reconnection. Expire a
|
|
4843
|
-
sample at `maxSampleAgeMs` using monotonic elapsed time. Failure is visible through the
|
|
4887
|
+
sample at `maxSampleAgeMs` using monotonic elapsed time. Failure is visible through the agent application's
|
|
4844
4888
|
existing diagnostic path, with the estimate unavailable; the next scheduled check can recover.
|
|
4845
4889
|
Checking failure alone does not assert call/recording state or require a healthy connection to close.
|
|
4846
4890
|
This read-only check is not a task command. Protocol supplies declarations and validators; the
|
|
4847
|
-
|
|
4891
|
+
agent application implements the scheduler and the provider implements the clock read.
|
|
4848
4892
|
|
|
4849
|
-
The
|
|
4893
|
+
The agent application may also expose `Host.estimateProviderTime({ providerId, loginId })`. It returns an ISO
|
|
4850
4894
|
`at` in that provider's clock domain, the same explicit scope and `clockId`, or `undefined` when
|
|
4851
4895
|
unavailable. Optional `uncertaintyMs` is an estimate, not a guaranteed bound. The method is optional
|
|
4852
|
-
at the
|
|
4853
|
-
checks does not require the
|
|
4854
|
-
this check if the
|
|
4896
|
+
at the agent application level and is deliberately absent from `HostGuarantees`. A provider declaring time
|
|
4897
|
+
checks does not require the agent application to expose estimates, and an estimate need not originate from
|
|
4898
|
+
this check if the agent application has another explicitly configured source for that provider clock.
|
|
4855
4899
|
|
|
4856
|
-
To estimate the difference, the
|
|
4900
|
+
To estimate the difference, the agent application brackets the request with local send/receive instants and
|
|
4857
4901
|
measures elapsed time monotonically. For provider sample P and local bracketing instants H0/H1,
|
|
4858
4902
|
the offset under stable clocks lies between P-H1 and P-H0, before clock error and timestamp
|
|
4859
4903
|
precision are considered. A midpoint is only an estimate; one-way delays need not be symmetric.
|
|
@@ -4862,22 +4906,22 @@ sample with monotonic elapsed time, and return unavailable after its configured
|
|
|
4862
4906
|
`validateProviderTimeEstimate` checks shape and scope, not actual accuracy or freshness.
|
|
4863
4907
|
|
|
4864
4908
|
A provider may declare `Manifest.timestampAuthority: "provider"`: its own timestamps are
|
|
4865
|
-
used for final records, and
|
|
4866
|
-
of time-check support and the optional
|
|
4867
|
-
Omission makes no promise that
|
|
4909
|
+
used for final records, and agent application-supplied timestamps are advisory. This declaration is independent
|
|
4910
|
+
of time-check support and the optional agent application estimate; neither changes who owns the final record.
|
|
4911
|
+
Omission makes no promise that agent application timestamps will be trusted. No agent application-authoritative default
|
|
4868
4912
|
is inferred. Successful `recordStep` responses identify the provider-selected history instant;
|
|
4869
|
-
see **The
|
|
4913
|
+
see **The agent application records what it performs** for correlation and retained bindings.
|
|
4870
4914
|
|
|
4871
|
-
A provider need not trust or adopt any
|
|
4915
|
+
A provider need not trust or adopt any agent application-supplied timestamp. It may timestamp an incoming
|
|
4872
4916
|
message using its own clock at receipt and use that for its own processing/accounting. The reason
|
|
4873
|
-
for this exception is that
|
|
4874
|
-
Receipt time must be described as receipt/observation time, not relabeled as the original
|
|
4917
|
+
for this exception is that agent application clocks and agent application estimates are not authoritative at the provider.
|
|
4918
|
+
Receipt time must be described as receipt/observation time, not relabeled as the original agent application
|
|
4875
4919
|
action, recorder capture boundary, or proof an operation applied. An existing field with a
|
|
4876
4920
|
specific occurrence meaning still requires that evidence; this option does not silently redefine
|
|
4877
|
-
it.
|
|
4921
|
+
it. Agent application-only instants remain advisory input; the provider owns its authoritative publication.
|
|
4878
4922
|
|
|
4879
|
-
This
|
|
4880
|
-
provider-clock comparisons when the
|
|
4923
|
+
This agent application estimate is another explicit timestamp exception: it helps optional displays and
|
|
4924
|
+
provider-clock comparisons when the agent application lacks that clock directly. It is not a source occurrence,
|
|
4881
4925
|
may not replace event/history timestamps, and alone cannot authorize deadline actions or prove
|
|
4882
4926
|
recording freshness. Uses requiring a trustworthy bound still need independently established
|
|
4883
4927
|
clock accuracy/drift assumptions. It neither synchronizes the operating-system clock nor changes
|
|
@@ -4940,8 +4984,8 @@ safe for the agent to see.
|
|
|
4940
4984
|
| `active` | The transport is up and the provider is serving this session. It is the only value under which work arrives. |
|
|
4941
4985
|
| `error` | The adapter cannot serve the session and is not simply mid-reconnect. Say why in `message`, and say what revives it in `recovery` — required here, forbidden on any other status. It is not terminal: an adapter that recovers on its own still reports `connecting` and then `active`, and one that cannot is revived as `recovery` says. |
|
|
4942
4986
|
|
|
4943
|
-
**An error names its recovery, and the
|
|
4944
|
-
died; the
|
|
4987
|
+
**An error names its recovery, and the agent application acts on that word.** The adapter knows why its session
|
|
4988
|
+
died; the agent application knows how to run a login. `recovery` joins the two:
|
|
4945
4989
|
|
|
4946
4990
|
- **`reconnect`** — the login is good and this connection is not: a backend restart, a session the
|
|
4947
4991
|
platform no longer recognises. Omni calls `disconnect()` on the dead connection and then
|
|
@@ -4956,7 +5000,7 @@ died; the host knows how to run a login. `recovery` joins the two:
|
|
|
4956
5000
|
`reconnect`, and the fresh snapshot re-establishes state. The adapter tears nothing down itself
|
|
4957
5001
|
and expects nothing to carry on: the connection that reported `error` is finished.
|
|
4958
5002
|
|
|
4959
|
-
**Patience is the
|
|
5003
|
+
**Patience is the agent application's.** An adapter in `connecting` retries for as long as it takes and never
|
|
4960
5004
|
has to decide when to stop. Omni owns giving up: after however long it chooses to wait, it may
|
|
4961
5005
|
call `disconnect()` and either `connect()` afresh or surface the failure — so neither side waits
|
|
4962
5006
|
for the other to blink.
|
|
@@ -4972,7 +5016,7 @@ authentication state, not here.
|
|
|
4972
5016
|
adapter that leaves a stale `active` in place is telling Omni to keep waiting for work that cannot
|
|
4973
5017
|
come — see **Liveness**.
|
|
4974
5018
|
|
|
4975
|
-
**The status is seen, not merely known.**
|
|
5019
|
+
**The status is seen, not merely known.** An agent application renders the transport's status where the agent
|
|
4976
5020
|
works — `connecting` from the moment it is reported, `error` with what revives it — and never a
|
|
4977
5021
|
healthy workspace over a transport that cannot serve it: an agent talking on a desk whose transport
|
|
4978
5022
|
died was an incident, and it happened because the desk knew and did not say. The same obligation
|
|
@@ -4980,8 +5024,8 @@ covers a `diagnostic`: shown where the agent works, and counted.
|
|
|
4980
5024
|
|
|
4981
5025
|
### `break-state`
|
|
4982
5026
|
|
|
4983
|
-
Replaces this provider's complete `break` object. Its `
|
|
4984
|
-
`not-requested`, `awaiting-
|
|
5027
|
+
Replaces this provider's complete `break` object. Its `status` uses the canonical
|
|
5028
|
+
`not-requested`, `awaiting-approval`, `granted`, `starting-after-task` and
|
|
4985
5029
|
`on-break` states defined under Breaks; the event also carries the corresponding may-ask state,
|
|
4986
5030
|
reasons, retry details, and any forced break.
|
|
4987
5031
|
|
|
@@ -4990,7 +5034,7 @@ Each state is also held to the one before it. A commit's states, `starting-after
|
|
|
4990
5034
|
break, which says so with `forced`: in effect at once, or `starting-after-task` while the member
|
|
4991
5035
|
finishes the call they are on (`stream.breakState.commitBeforeGrant`); and a break never moves backwards —
|
|
4992
5036
|
from `on-break` or `starting-after-task` to a grant or a request, or from `granted` to
|
|
4993
|
-
`awaiting-
|
|
5037
|
+
`awaiting-approval` — a new request passes through `not-requested` (`stream.breakState.backwards`).
|
|
4994
5038
|
`exerciseAdapter` holds the stream to that from the connect snapshot on;
|
|
4995
5039
|
`assertBreakFollowsItsRequests` holds any sequence.
|
|
4996
5040
|
|
|
@@ -5007,29 +5051,29 @@ snapshot. `seed(snapshot)` returns violations, which callers must check. This is
|
|
|
5007
5051
|
tracker, not a complete production reducer: snapshot freshness, login/connection fencing and
|
|
5008
5052
|
full task consistency remain separately required.
|
|
5009
5053
|
|
|
5010
|
-
**Both sides must enforce the order.** The
|
|
5054
|
+
**Both sides must enforce the order.** The agent application validates immediately before dispatch; the
|
|
5011
5055
|
provider rechecks current permission and state atomically before acting. The provider validates
|
|
5012
|
-
the resulting break/task state before publication and serializes those publications. The
|
|
5056
|
+
the resulting break/task state before publication and serializes those publications. The agent application
|
|
5013
5057
|
validates the envelope, transition and full task consistency before replacing local state. A
|
|
5014
5058
|
request result does not optimistically advance break state, and a delayed command cannot apply
|
|
5015
|
-
to a later attempt merely because its
|
|
5059
|
+
to a later attempt merely because its status happens to look compatible. Keep at most one
|
|
5016
5060
|
unresolved operation per provider/attempt; recovery must resolve uncertainty before another
|
|
5017
5061
|
operation is dispatched. The source must fence delayed operations against its own attempt state.
|
|
5018
5062
|
|
|
5019
|
-
Normal order is `not-requested` → `awaiting-
|
|
5063
|
+
Normal order is `not-requested` → `awaiting-approval` → `granted` →
|
|
5020
5064
|
`starting-after-task` → `on-break` → `not-requested`. Auto-approval may go directly to
|
|
5021
5065
|
`granted`; a commit with no outstanding work may go directly to `on-break`. Denial/cancel
|
|
5022
|
-
returns a precommit request to `not-requested`; an
|
|
5066
|
+
returns a precommit request to `not-requested`; an explicit agent end returns a committed
|
|
5023
5067
|
break there. Same-state restatements are allowed. An evidenced forced break is the explicit
|
|
5024
5068
|
exception to requesting/granting, and must carry its forced actor/state. Neither skipped
|
|
5025
|
-
publication nor
|
|
5069
|
+
publication nor an agent application-local guess creates another exception. A later normal attempt starts
|
|
5026
5070
|
from `not-requested`, never by regressing an active break into a request.
|
|
5027
5071
|
|
|
5028
5072
|
A fresh authoritative snapshot establishes a new baseline; do not run this event-transition
|
|
5029
5073
|
check across it. A snapshot may legitimately establish a later request or an already active
|
|
5030
5074
|
break after reconnect. Its freshness must be established by the source/adapter's ordered
|
|
5031
5075
|
snapshot/publication boundary and fencing of obsolete callbacks and reads. A delayed old
|
|
5032
|
-
snapshot is not detectable from break
|
|
5076
|
+
snapshot is not detectable from break status alone. Neither envelope time nor event ID
|
|
5033
5077
|
supplies a break revision. Do not suppress all requests after an active break: a later attempt
|
|
5034
5078
|
passes through `not-requested`, and then may request again. Do not replay earlier ProviderEvents
|
|
5035
5079
|
after recovery. Raw backend break handlers must preserve source order too; this helper does
|
|
@@ -5063,8 +5107,8 @@ The provider's word that the task's audio should now attach. Omni calls `openMed
|
|
|
5063
5107
|
a task carried with `media: "started"`, which is how a reconnect snapshot reattaches audio an
|
|
5064
5108
|
earlier event brought — and renders the call as live from that word, never from its own senses. It
|
|
5065
5109
|
precedes `openMedia` and is never a reply to it: a provider whose media state comes from the
|
|
5066
|
-
platform, a station going in use the moment a call is answered, sends it then, before any
|
|
5067
|
-
opened anything, and `openMedia` has its own answer for what the
|
|
5110
|
+
platform, a station going in use the moment a call is answered, sends it then, before any agent application has
|
|
5111
|
+
opened anything, and `openMedia` has its own answer for what the agent application did. It
|
|
5068
5112
|
names a task whose work has begun, and it alternates with `task-media-ended`: media that never
|
|
5069
5113
|
started cannot end, so a live call whose provider says nothing about its audio is a provider in
|
|
5070
5114
|
breach, not a state a desk fills in from its own devices.
|
|
@@ -5099,7 +5143,7 @@ A successful `complete` or `transfer` command does not clear the task. Omni wait
|
|
|
5099
5143
|
and not for ever: `applied` to a completion -- `complete`, or a lead's `take-over-call` -- says the
|
|
5100
5144
|
provider has completed the task, and its `task-ended` follows within the
|
|
5101
5145
|
manifest's `completionSettleMs`. A provider never answers `applied` for a completion it has not yet
|
|
5102
|
-
performed. Past the bound the
|
|
5146
|
+
performed. Past the bound the agent application calls `snapshot()`: a snapshot still carrying the task is a task
|
|
5103
5147
|
held open by a provider that said it was done, and the desk shows it as unsettled -- "Completing...
|
|
5104
5148
|
the provider has not confirmed" -- naming the command; a snapshot no longer carrying it clears the
|
|
5105
5149
|
task, since the ending was owed and lost. The drive holds a provider to the same bound
|
|
@@ -5111,7 +5155,7 @@ recognised as the late event it is, never applied to the life now open under the
|
|
|
5111
5155
|
|
|
5112
5156
|
### `dial-outcome`
|
|
5113
5157
|
|
|
5114
|
-
How a dial the
|
|
5158
|
+
How a dial the agent application placed ended, once, either way, named by the `dialId` the agent application sent: `answered`,
|
|
5115
5159
|
or one of the declared ways of not reaching the destination, with the switch's `reason` where it
|
|
5116
5160
|
gave one. `taskId` names the task the dial was on, where there was one, and that task may already
|
|
5117
5161
|
have ended. Omni shows it to the agent against the call it belonged to and does nothing else: no
|
|
@@ -5131,9 +5175,9 @@ state read that contradicted itself, a `task-ended` naming a task nobody held, a
|
|
|
5131
5175
|
without a name. `expected` is the rule as a sentence, `observed` is what came instead, and
|
|
5132
5176
|
`taskId` names the task where there is one. **Informational, never behavioural**: the provider has
|
|
5133
5177
|
already done the safe thing before it speaks, and nothing in the task or break machinery reacts to
|
|
5134
|
-
a diagnostic. It exists so that the loudness reaches a person —
|
|
5178
|
+
a diagnostic. It exists so that the loudness reaches a person — an agent application renders each one where the
|
|
5135
5179
|
agent works and keeps a count an operator can read, because a healthy workspace over a shouting
|
|
5136
|
-
console is the silence problem one layer up. One event per occurrence; the
|
|
5180
|
+
console is the silence problem one layer up. One event per occurrence; the agent application counts, the
|
|
5137
5181
|
provider does not batch. `exerciseAdapter` treats a diagnostic delivered during a run as a
|
|
5138
5182
|
violation (`diagnostic.raised`): a conformance run against a platform that is breaking its rules
|
|
5139
5183
|
fails loudly rather than passing with a note. A task published under `capabilitySource:
|
|
@@ -5213,18 +5257,18 @@ same exported checks are used by Omni and adapter tests so their interpretations
|
|
|
5213
5257
|
| `validateEventEnvelope(envelope, manifest)` | Envelope identity, timestamp, and the payload for each event type. |
|
|
5214
5258
|
| `validateContact(contact)` | Contact field shapes and attribute keys. Every field is optional, so this checks what is present rather than what is missing. |
|
|
5215
5259
|
| `validateScheduledActivity(activity)` | Required activity fields and start/end ordering. |
|
|
5216
|
-
| `validateHostGuarantees(guarantees)` | What
|
|
5260
|
+
| `validateHostGuarantees(guarantees)` | What an agent application promises: only the guarantees this contract names, each declared by presence and never `false`. The harness validates the guarantees of whatever agent application a test hands the adapter. |
|
|
5217
5261
|
| `validateProviderTimeCheckPolicy(policy)` | Explicit optional polling settings, positive safe-integer durations and round-trip/timeout/interval ordering. |
|
|
5218
|
-
| `validateProviderTimeCheckRequest(request)` | Fresh-request shape; the
|
|
5262
|
+
| `validateProviderTimeCheckRequest(request)` | Fresh-request shape; the agent application enforces actual uniqueness and outstanding-request lifetime. |
|
|
5219
5263
|
| `validateProviderTimeCheckResult(result, request, loginId)` | ISO timestamp, clock identity, exact request/login correlation; timing and source accuracy remain runtime checks. |
|
|
5220
|
-
| `validateProviderTimeEstimate(estimate, scope)` | Optional
|
|
5221
|
-
| `validateInteractionReport(report, path?, manifest?)` | What the
|
|
5222
|
-
| `validateHostReport(report)` | The
|
|
5223
|
-
| `validateHostMute(mute, softphone)` | What the
|
|
5224
|
-
| `validateLoginStore(store)` | The login's store the
|
|
5225
|
-
| `validateCapacity(capacity)` | What the
|
|
5264
|
+
| `validateProviderTimeEstimate(estimate, scope)` | Optional agent application estimate shape and provider/login scope; no accuracy guarantee. |
|
|
5265
|
+
| `validateInteractionReport(report, path?, manifest?)` | What the agent application reports of a leg it performed, for an adapter to check before forwarding: a task, a step, when it began, a positive `seconds` where stated, and an explicit `ended` that carries the final duration. Given the manifest, a running report is refused unless it declares `runningStepReports`. |
|
|
5266
|
+
| `validateHostReport(report)` | The agent application's own report as published to an adapter: `online`, and where there is audio, an input that is `available` with the microphone and `flowing`, or `unavailable` with a reason and the failure that says why, and an output that is `available` or `unavailable` with its failure. The harness validates whatever agent application a test hands the adapter; `stillHost(report)` builds one that never changes. |
|
|
5267
|
+
| `validateHostMute(mute, softphone)` | What the agent application's Mute does, stated on a softphone login and nowhere else: `stream` or `station` (`host.mute`), required where the agent application holds a microphone (`host.mute.required`) and refused where it does not (`host.mute.unexpected`). The harness holds `ConnectContext.host.mute` to it. |
|
|
5268
|
+
| `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. |
|
|
5269
|
+
| `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`). |
|
|
5226
5270
|
| `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. |
|
|
5227
|
-
| `validateTaskCommand(command, task?)` | What a command needs to be issuable, against the task it names: its own shape -- a dial's `dialId`, a transfer's item, 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 consulted entry, a lead requested, somebody else still on the call (`command.conference.remove.alone`).
|
|
5271
|
+
| `validateTaskCommand(command, task?)` | What a command needs to be issuable, against the task it names: its own shape -- a dial's `dialId`, a transfer's item, 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 consulted entry, a lead requested, somebody else still on the call (`command.conference.remove.alone`). An agent application validates before sending and an adapter before acting. |
|
|
5228
5272
|
| `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. |
|
|
5229
5273
|
| `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. |
|
|
5230
5274
|
|
|
@@ -5250,25 +5294,25 @@ and may be compiled against a different protocol version, so its output is untru
|
|
|
5250
5294
|
Validating a snapshot before it replaces provider state is what stops a malformed task from
|
|
5251
5295
|
reaching the workspace.
|
|
5252
5296
|
|
|
5253
|
-
### What the
|
|
5297
|
+
### What the agent application does with what it refuses
|
|
5254
5298
|
|
|
5255
5299
|
A refusal has an aftermath on the desk and a report to the provider, and both are stated.
|
|
5256
5300
|
|
|
5257
|
-
- **A refused snapshot replaces nothing.** The
|
|
5301
|
+
- **A refused snapshot replaces nothing.** The agent application keeps the last state it took from this
|
|
5258
5302
|
provider whole, and shows the provider as faulted -- the transport as it stands, and the words
|
|
5259
5303
|
"last update refused" with the rule -- so the agent knows the view is standing still rather than
|
|
5260
5304
|
believing it current. It does not adopt the good half of a snapshot: a task the agent is on would
|
|
5261
5305
|
vanish from the desk while the call is up.
|
|
5262
|
-
- **A refused event is dropped and counted.** The state the
|
|
5263
|
-
provider is told, and may republish a corrected state or raise a `diagnostic`; the
|
|
5306
|
+
- **A refused event is dropped and counted.** The state the agent application holds does not move for it. The
|
|
5307
|
+
provider is told, and may republish a corrected state or raise a `diagnostic`; the agent application retries
|
|
5264
5308
|
nothing. The desk shows nothing for it: the view kept moving for every other event, so "last
|
|
5265
5309
|
update refused" would be false of it, and a chip that said nothing would be right about the
|
|
5266
|
-
transport. Counted, and the provider told, is all
|
|
5310
|
+
transport. Counted, and the provider told, is all an agent application can truthfully do for one event.
|
|
5267
5311
|
- **The provider is told, every time.** `refused(report)` carries the artefact, the envelope id for
|
|
5268
5312
|
an event, and every violation with its rule and path. An adapter logs it at error and treats it as
|
|
5269
|
-
its own defect until shown otherwise: what the
|
|
5313
|
+
its own defect until shown otherwise: what the agent application refused never reached the agent, and a
|
|
5270
5314
|
provider that hears nothing runs a whole shift beside a frozen desk with nothing wrong on its
|
|
5271
|
-
side. The harness tells an adapter under test exactly as
|
|
5315
|
+
side. The harness tells an adapter under test exactly as an agent application does
|
|
5272
5316
|
(`connection.refused.required`, `connection.refused.rejected`).
|
|
5273
5317
|
|
|
5274
5318
|
## Conformance helpers
|
|
@@ -5313,18 +5357,18 @@ in each adopter's own tests. `{ drive: true }` closes that: the exercise takes t
|
|
|
5313
5357
|
provider offers through one ordinary lifecycle -- accept it, wait for its media and open it on a
|
|
5314
5358
|
softphone, hold and resume where the task offers `hold`, end the call where it offers `endCall`,
|
|
5315
5359
|
complete it with an outcome where the agent completes -- and holds every step to the rules a
|
|
5316
|
-
|
|
5360
|
+
agent application holds a provider to. Each command is validated against the task as published
|
|
5317
5361
|
(`drive.command.*`), each answer for its method, a refusal of a control the task offered is a
|
|
5318
5362
|
violation (`drive.command.failed`), and an event the provider owes and never sends is one too
|
|
5319
5363
|
(`drive.timeout`, after `driveTimeoutMs`, 5000 by default). Around the drive the exercise holds
|
|
5320
|
-
the run to what
|
|
5364
|
+
the run to what an agent application holds a provider to between commands: an offer before the agent application stated
|
|
5321
5365
|
capacity, or beyond the count with nothing dialled on it, is named
|
|
5322
5366
|
(`stream.taskOffered.beforeCapacity`, `.overCapacity`); every user the snapshot names is looked up
|
|
5323
|
-
through `
|
|
5324
|
-
nothing (`
|
|
5367
|
+
through `getUserDetails` and the answer held to the shape, nobody unasked, nobody described as
|
|
5368
|
+
nothing (`getUserDetails.user.*`, `getUserDetails.unasked`, `connection.getUserDetails.empty`); and
|
|
5325
5369
|
the second adapter a `rebuild` gives comes up signed in as the same login from the secrets alone
|
|
5326
5370
|
before it reads anything (`drive.reload.login`), and is then held to everything the first was on
|
|
5327
|
-
connect: the methods its declarations call for, the
|
|
5371
|
+
connect: the methods its declarations call for, the agent application's report, a capacity stated to it, its
|
|
5328
5372
|
snapshot read as the connect snapshot was, and its media opened afresh on a task carried with
|
|
5329
5373
|
`media: "started"`, since the first client's session died with it. The result also says which rules the
|
|
5330
5374
|
run evaluated, pass or fail, in `rulesEvaluated`: the validators' as each was applied, the stream's
|
|
@@ -5338,8 +5382,8 @@ in the completing publication or the open entry is refused (`task.interactionHis
|
|
|
5338
5382
|
and the drive's closing report after the media ended is expected `recorded` and to change nothing:
|
|
5339
5383
|
a record restated afterwards with that leg's duration altered is named (`drive.recordStep.overwritten`).
|
|
5340
5384
|
Where the provider restates the task's record afterwards, each leg is in it or the hole is named
|
|
5341
|
-
(`drive.recordStep.history`). Given `rebuild`, a way to build the adapter again as
|
|
5342
|
-
does, the drive reloads the
|
|
5385
|
+
(`drive.recordStep.history`). Given `rebuild`, a way to build the adapter again as an agent application reload
|
|
5386
|
+
does, the drive reloads the agent application as a reload happens: the first client is unsubscribed,
|
|
5343
5387
|
disconnected and its session closed (`drive.reload.handover`), and only then is a second adapter
|
|
5344
5388
|
built and connected for the same login with the same context and the same `store`; its snapshot
|
|
5345
5389
|
must carry the task with that leg at its acknowledged provider timestamp and the reported actor, and the run goes on with the second as its
|
|
@@ -5358,8 +5402,8 @@ first client is gone before the second connects, there is no client for the plat
|
|
|
5358
5402
|
open task back to, and nothing about the reload is exempt from any rule. The second adapter opens its session from the same
|
|
5359
5403
|
`secrets`, so the reload is a restore before it is anything else: an adapter that holds a session
|
|
5360
5404
|
has already put what would rebuild it into the secrets store, from the moment it was handed one,
|
|
5361
|
-
not only when a flow completes -- the session is the adapter's, the store is the
|
|
5362
|
-
|
|
5405
|
+
not only when a flow completes -- the session is the adapter's, the store is the agent application's, and a
|
|
5406
|
+
agent application reload is exactly when no flow will run. And once the task has ended, the store the drive
|
|
5363
5407
|
handed the adapter holds no key naming the task's id, delimited, never as a run of characters inside
|
|
5364
5408
|
another id (`drive.store.retained`); an adapter whose keys never named the task leaves the rule
|
|
5365
5409
|
unevaluated, and the result says so rather than passing it: a task's keys go
|
|
@@ -5417,8 +5461,8 @@ cannot be established from TypeScript structure alone.
|
|
|
5417
5461
|
| `assertReached(result, subjects)` | The exercise met every subject named; throws listing those it did not. Pair it with a clean `exerciseAdapter` result. |
|
|
5418
5462
|
| `assertAuthenticationRestoreAndExpiry(states)` | A restored authenticated session can refresh and ends in expiry. Every state is validated. |
|
|
5419
5463
|
| `assertReconnectWithMissedAssignments(before, reconnect, ids)` | A reconnect snapshot restores assignments received while offline. |
|
|
5420
|
-
| `stillHost(report?, guarantees?, mute?)` |
|
|
5421
|
-
| `TaskStream`, `BreakStream` | The cross-event models the harness applies after the connect snapshot, exported for
|
|
5464
|
+
| `stillHost(report?, guarantees?, mute?)` | An agent application that reports one thing and never changes, for a test context: `{ online: true }` by default, a report with audio for a softphone voice adapter, and never for a desk phone. |
|
|
5465
|
+
| `TaskStream`, `BreakStream` | The cross-event models the harness applies after the connect snapshot, exported for an agent application that wants the same rules at its boundary: `seed(snapshot)`, then `apply(envelope)` returns the violations. |
|
|
5422
5466
|
| `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. |
|
|
5423
5467
|
| `assertMediaFollowsTheTask(envelopes, snapshot?)` | The media follows the task and never decides it: every task is introduced once, `task-media-started` and `task-media-ended` alternate on work that has begun, media ends only where it arrived, and what follows the media ending is `completing` or `task-ended`. The harness applies the same rules to every event after the connect snapshot (`stream.*`). A sequence with no media satisfies it by never testing it — pair it with the assertion that the media end is present. |
|
|
5424
5468
|
| `assertBreakAttemptProviders(candidates, asked)` | A break attempt asks every usable provider holding capacity, `refreshing` included, and nothing of a provider whose login is `expired`. |
|
|
@@ -5436,7 +5480,7 @@ Adapters should run the relevant scenarios against deterministic test state befo
|
|
|
5436
5480
|
|
|
5437
5481
|
## A provider does not style the workspace
|
|
5438
5482
|
|
|
5439
|
-
How a deployment themes Omni is the
|
|
5483
|
+
How a deployment themes Omni is the agent application's concern and is specified with the agent application, not here. What
|
|
5440
5484
|
belongs in this contract is the boundary. A provider says what a control **is** through its
|
|
5441
5485
|
capabilities and what its work is **called** through `phaseLabels` and `taskTypePresentation`; how
|
|
5442
5486
|
any of it is drawn is Omni's. A task cannot select a design language, inject a component, or
|
|
@@ -5446,21 +5490,21 @@ override the agent's theme and font preferences.
|
|
|
5446
5490
|
|
|
5447
5491
|
Recording is voice-only. A task can arrive already recording, including while pending, and offer
|
|
5448
5492
|
no recording controls. The provider publishes only its own current state on
|
|
5449
|
-
the provider field of the task’s `recording` state. The
|
|
5450
|
-
A provider task update cannot replace
|
|
5493
|
+
the provider field of the task’s `recording` state. The agent application publishes its own full scoped view in `HostReport.recordings`.
|
|
5494
|
+
A provider task update cannot replace agent application state. Neither a capability nor an accepted command
|
|
5451
5495
|
establishes recording. Missing state, lost recorder observation, transport loss and expired
|
|
5452
5496
|
observation mean unknown; they never mean stopped. Paused means a recording remains open without
|
|
5453
5497
|
capturing. Task hold, task pause and recording pause are independent.
|
|
5454
5498
|
|
|
5455
|
-
The per-task policy is `Task.capabilities.recording`, with independently optional provider and
|
|
5499
|
+
The per-task policy is `Task.capabilities.recording`, with independently optional provider and agent application
|
|
5456
5500
|
action sets. The outer capability may be locked. Absence grants no permission, and a state may
|
|
5457
5501
|
exist without any permission. A provider may offer only stop for a recording started automatically,
|
|
5458
5502
|
or withdraw a control on a later task update. There is no global recording mode and no automatic
|
|
5459
|
-
start from a capability declaration.
|
|
5460
|
-
not the provider-owned manifest. The task chooses an explicitly provisioned
|
|
5503
|
+
start from a capability declaration. Agent application support is declared on `ConnectContext.host.recording`,
|
|
5504
|
+
not the provider-owned manifest. The task chooses an explicitly provisioned agent application destination;
|
|
5461
5505
|
unknown destinations or unsupported actions are refused visibly, never redirected to provider
|
|
5462
|
-
recording or a default upload location. Initial
|
|
5463
|
-
capture of both local and remote audio;
|
|
5506
|
+
recording or a default upload location. Initial agent application support requires voice softphone media and
|
|
5507
|
+
capture of both local and remote audio; an agent application unable to capture either must refuse start/resume.
|
|
5464
5508
|
This package declares that contract; it supplies no recorder, media mixing, storage or upload.
|
|
5465
5509
|
|
|
5466
5510
|
| Action | Required current recording state | Confirmed outcome |
|
|
@@ -5481,7 +5525,7 @@ Partial success (capture stopped but storage outcome unknown) cannot return fail
|
|
|
5481
5525
|
of no effect. It rejects with unknown outcome, reports the failure visibly and publishes whatever
|
|
5482
5526
|
current capture state is actually known. Retention is not a promise of sample-perfect audio.
|
|
5483
5527
|
|
|
5484
|
-
Provider commands go exclusively to `Connection.execute`;
|
|
5528
|
+
Provider commands go exclusively to `Connection.execute`; agent application commands go exclusively to
|
|
5485
5529
|
`HostRecording.execute`. Both include task, allocation, request and observation identities; all
|
|
5486
5530
|
non-start commands identify the particular recording. IDs are opaque and scoped by provider login,
|
|
5487
5531
|
task and recorder owner. They are never inferred from filenames or current agent identity. A
|
|
@@ -5489,8 +5533,8 @@ recording ID survives pause/resume and reallocation only where the same recorder
|
|
|
5489
5533
|
commands always name the current allocation. A later start gets a different ID. Reconnect does not
|
|
5490
5534
|
create a new recording or fresh evidence. After lost continuity, use unknown until reconciled.
|
|
5491
5535
|
There is no automatic restart, transfer to another recorder, or stop on task hold/disconnect.
|
|
5492
|
-
Task removal does not prove recording stopped: outstanding
|
|
5493
|
-
|
|
5536
|
+
Task removal does not prove recording stopped: outstanding agent application recorders remain tracked by the
|
|
5537
|
+
agent application until its executor reconciles/finishes them, with visible unresolved cleanup failures.
|
|
5494
5538
|
|
|
5495
5539
|
Only start/resume require an in-progress or paused task with started media. Pause/stop/cancel may
|
|
5496
5540
|
also finish an independently observed recorder while the task is completing. A pending task may
|
|
@@ -5511,34 +5555,34 @@ identity or observation time publish unknown and offer no issuable controls.
|
|
|
5511
5555
|
|
|
5512
5556
|
Use `validateTask` and `validateTaskCommand` for published shape and static permissions.
|
|
5513
5557
|
Immediately before dispatch, additionally use `validateRecordingRequest` against the full current
|
|
5514
|
-
task and explicit observer-domain time. For
|
|
5515
|
-
full
|
|
5558
|
+
task and explicit observer-domain time. For agent application commands also supply the current agent application declaration,
|
|
5559
|
+
full agent application report and softphone context. The same checks must run at the executing boundary;
|
|
5516
5560
|
client validation alone is not authorization. The executor serializes operations on each recorder
|
|
5517
5561
|
and durably correlates request identity with scope, action and outcome: an exact retry refers to the
|
|
5518
5562
|
same operation; reuse with different contents is refused. It never silently retries an ambiguous
|
|
5519
5563
|
operation. Implementations without reliable request correlation must refuse controls.
|
|
5520
5564
|
|
|
5521
|
-
Keep command progress in
|
|
5522
|
-
recording transition and any retain/discard effect actually completed; publish the confirming task or
|
|
5565
|
+
Keep command progress in agent application UI separately from authoritative state. Applied means the requested
|
|
5566
|
+
recording transition and any retain/discard effect actually completed; publish the confirming task or agent application report before
|
|
5523
5567
|
resolving applied. Failed means confirmed no effect. Rejected promise means outcome unknown:
|
|
5524
5568
|
show the failure, reconcile that path and do not automatically retry or pretend inactive. Authoritative
|
|
5525
|
-
updates remain full current task/
|
|
5569
|
+
updates remain full current task/agent application views, not replayed provider events. These current-state fields
|
|
5526
5570
|
create no interaction-history entries and infer no actors, durations or historical capture boundaries.
|
|
5527
5571
|
|
|
5528
5572
|
Migration is explicit: replace the old true recording capability with per-path action policies,
|
|
5529
5573
|
replace untargeted commands with scoped commands, and retain protocol version 1. During pre-release
|
|
5530
5574
|
development, hosts and adapters must align their recording contracts; negotiation does not detect
|
|
5531
5575
|
differences between package revisions that share that protocol number. No legacy-shape fallback is provided.
|
|
5532
|
-
This change does not publish a package or enable recording in any existing
|
|
5576
|
+
This change does not publish a package or enable recording in any existing agent application/provider by itself.
|
|
5533
5577
|
|
|
5534
5578
|
`validateRecordingOutcome` checks confirming observations against recording-specific applied/failed
|
|
5535
5579
|
semantics, including pause/resume identity and rejection of a dialling result. It cannot prove audio
|
|
5536
5580
|
retention/completion or source truth from a status flag; those remain executor obligations.
|
|
5537
5581
|
|
|
5538
|
-
|
|
5582
|
+
Agent application destination selection binds storage when start actually creates a recording. Existing recordings
|
|
5539
5583
|
keep that binding through pause/resume/stop/cancel; a later policy cannot redirect their stored audio.
|
|
5540
5584
|
The executor rejects a mismatched destination rather than moving or discarding another binding.
|
|
5541
|
-
Action permission does not authorize unattended invocation:
|
|
5585
|
+
Action permission does not authorize unattended invocation: agent application controls require the agent's explicit
|
|
5542
5586
|
act, and provider authorization remains enforced at its authenticated command boundary.
|
|
5543
5587
|
|
|
5544
5588
|
|
|
@@ -5550,31 +5594,31 @@ capability restrictions from disappearing at dispatch. The standalone `validateR
|
|
|
5550
5594
|
requires an affirmative permission; false, malformed permission declarations and unknown actions grant nothing.
|
|
5551
5595
|
|
|
5552
5596
|
|
|
5553
|
-
###
|
|
5597
|
+
### Agent application recording announcements to the caller
|
|
5554
5598
|
|
|
5555
|
-
`HostRecording.announcesToCaller`, when true, is a guarantee that the
|
|
5599
|
+
`HostRecording.announcesToCaller`, when true, is a guarantee that the agent application delivers audible recording
|
|
5556
5600
|
status messages to the remote party over the call's outgoing audio. It belongs inside the optional
|
|
5557
|
-
|
|
5601
|
+
agent application recording capability: without that capability there is no such guarantee. Omission makes no
|
|
5558
5602
|
promise; false is invalid. It does not belong in the provider manifest, the task's recording policy
|
|
5559
|
-
or the general `HostGuarantees` object. A provider can inspect this
|
|
5560
|
-
whether to permit
|
|
5603
|
+
or the general `HostGuarantees` object. A provider can inspect this agent application guarantee when deciding
|
|
5604
|
+
whether to permit agent application recording on a task.
|
|
5561
5605
|
|
|
5562
|
-
The guarantee applies only to
|
|
5563
|
-
own recordings at their end; provider state updates must not cause the
|
|
5606
|
+
The guarantee applies only to agent application-owned recordings. Providers implement announcements for their
|
|
5607
|
+
own recordings at their end; provider state updates must not cause the agent application to announce those
|
|
5564
5608
|
recordings. Both recorders can operate independently on the same call.
|
|
5565
5609
|
|
|
5566
|
-
On a confirmed
|
|
5610
|
+
On a confirmed agent application start, the remote party hears that recording started. On a confirmed stop or
|
|
5567
5611
|
cancel, they hear that recording stopped. If pause/resume is supported, say paused/resumed so a
|
|
5568
5612
|
pause is not presented as a finished recording. Announcements follow actual capture transitions,
|
|
5569
5613
|
not button clicks, accepted requests or task-policy changes. When attaching to an already-active
|
|
5570
|
-
|
|
5614
|
+
agent application recording, announce that recording is active, without inventing its original start time.
|
|
5571
5615
|
Repeated observations of the same state do not repeat announcements. Unknown or expired evidence
|
|
5572
5616
|
must never produce a stopped announcement.
|
|
5573
5617
|
|
|
5574
5618
|
A local sound, UI indicator, screen-reader message to the agent, or sound leaking through the
|
|
5575
|
-
microphone does not fulfil this guarantee. The
|
|
5619
|
+
microphone does not fulfil this guarantee. The agent application must deliver the message directly in the audio
|
|
5576
5620
|
sent to the remote party, even when the agent microphone is muted. It may also notify the agent.
|
|
5577
|
-
Only declare the guarantee when the
|
|
5621
|
+
Only declare the guarantee when the agent application can provide that outgoing audio path. An announcement
|
|
5578
5622
|
that cannot be delivered, including after the remote party has disconnected, is a visible failure;
|
|
5579
5623
|
never claim delivery or replay the notice into a different call.
|
|
5580
5624
|
|
|
@@ -5584,4 +5628,4 @@ report the announcement failure visibly and reject with unknown command outcome.
|
|
|
5584
5628
|
effect, automatically repeat the recording action or silently switch to an agent-only notice.
|
|
5585
5629
|
|
|
5586
5630
|
`validateHostRecording` checks the declaration, including its true-or-absent guarantee. Actual audio
|
|
5587
|
-
delivery remains the
|
|
5631
|
+
delivery remains the agent application implementation's responsibility and must be exercised in its own tests.
|