@xema/omni-protocol 0.1.95 → 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 +1 -1
- package/dist/testing.js +9 -9
- package/dist/validation.d.ts +2 -2
- package/dist/validation.js +5 -5
- package/guide.md +369 -364
- 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,7 +39,7 @@ 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
|
|
|
@@ -55,6 +55,11 @@ state use `status`; name the object when discussing them to avoid ambiguity.
|
|
|
55
55
|
| `status` | The agent’s break lifecycle | `not-requested`, `awaiting-approval`, `granted`, `starting-after-task`, `on-break` |
|
|
56
56
|
| `availability` | A team member | `ready`, `on-task`, `on-break`, `signed-out` |
|
|
57
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
|
+
|
|
58
63
|
## Versioning
|
|
59
64
|
|
|
60
65
|
### `OMNI_PROTOCOL_VERSION`
|
|
@@ -67,12 +72,12 @@ An adapter declares **every** version it can speak, not just the one it was comp
|
|
|
67
72
|
|
|
68
73
|
```ts
|
|
69
74
|
supportedProtocolVersions: [1] // v1 only
|
|
70
|
-
supportedProtocolVersions: [1, 2] // can serve either
|
|
75
|
+
supportedProtocolVersions: [1, 2] // can serve either agent application
|
|
71
76
|
```
|
|
72
77
|
|
|
73
78
|
A single pinned version would make migration impossible: recompiling against a newer package
|
|
74
79
|
would silently move an adapter to the new version with no window in which both sides
|
|
75
|
-
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
|
|
76
81
|
can be deployed independently.
|
|
77
82
|
|
|
78
83
|
### `negotiateProtocolVersion(adapterVersions, hostVersions?)`
|
|
@@ -488,7 +493,7 @@ type Snapshot = {
|
|
|
488
493
|
};
|
|
489
494
|
|
|
490
495
|
type AgentCapacity = {
|
|
491
|
-
count: number; // absolute ceiling, zero or more; zero is
|
|
496
|
+
count: number; // absolute ceiling, zero or more; zero is agent application-stopped
|
|
492
497
|
};
|
|
493
498
|
|
|
494
499
|
type CapacityResult = { status: "applied" };
|
|
@@ -870,7 +875,7 @@ leaves out does not exist — a structure with no site level declares `org`, `te
|
|
|
870
875
|
at: a label with no policy behind it decides nothing. A manifest that declares none has exactly
|
|
871
876
|
the four. `lockedBy` is any level in force except `person`, who never locks their own value;
|
|
872
877
|
`setBy` is any level in force, or `provider`, the protocol's word for "no level has said
|
|
873
|
-
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
|
|
874
879
|
declared labels and needs no others, and validates every republished `authenticated` state
|
|
875
880
|
against them, not only the sign-in. What the wire carries is the resolution:
|
|
876
881
|
|
|
@@ -897,7 +902,7 @@ The adapter is the one that knows the value, so it is the one held to it: given
|
|
|
897
902
|
queue locked, the validator refuses any other field carrying one, digits compared as digits so no
|
|
898
903
|
formatting hides them (`task.locked.leak`), and a conformance run whose tasks lock a party's number
|
|
899
904
|
or email states those values in `lockedValues`, since a run that cannot ask the question is not a
|
|
900
|
-
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
|
|
901
906
|
controls — is content, and is never locked.
|
|
902
907
|
|
|
903
908
|
**A lead sets the team's policy from their team member list.** A login that declares
|
|
@@ -921,9 +926,9 @@ person's preference from their own screen, which arrives the same way. A prefere
|
|
|
921
926
|
the capability's own name because it is the same capability at another level: effective in
|
|
922
927
|
`Task.capabilities`, set for the team in `policies`, left to the person in `preferences`. The
|
|
923
928
|
command `hold` acts on one call; the preference `hold` says whether the person wants the control
|
|
924
|
-
at all, and
|
|
925
|
-
these ladders: the microphone is the
|
|
926
|
-
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**.
|
|
927
932
|
|
|
928
933
|
## Breaks
|
|
929
934
|
|
|
@@ -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. |
|
|
@@ -1930,7 +1935,7 @@ them from what arrives later.
|
|
|
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
|
|
@@ -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
|
|
@@ -3707,11 +3712,11 @@ thing as a break the platform forced on its own. Where a platform applies one au
|
|
|
3707
3712
|
executing a preference somebody configured, and that person is the owner of the action — `by` names
|
|
3708
3713
|
them, not the machinery that carried it out.
|
|
3709
3714
|
|
|
3710
|
-
Omni resolves the name to show with `
|
|
3715
|
+
Omni resolves the name to show with `getUserDetails()`, so a provider sends the identifier and never
|
|
3711
3716
|
a display name.
|
|
3712
3717
|
|
|
3713
3718
|
**A forced break travels with `on-break` or `starting-after-task`, and nothing else.** It is a
|
|
3714
|
-
break in progress or about to be; beside `granted` or `awaiting-approval` the
|
|
3719
|
+
break in progress or about to be; beside `granted` or `awaiting-approval` the agent application would read a
|
|
3715
3720
|
request the agent never made and commit it (`break.forced.status`). Where the provider publishes
|
|
3716
3721
|
`reasons`, the lead's `force-break` named one, and the member's state carries it as `activeReasonId`.
|
|
3717
3722
|
|
|
@@ -3724,18 +3729,18 @@ forced: {
|
|
|
3724
3729
|
}
|
|
3725
3730
|
```
|
|
3726
3731
|
|
|
3727
|
-
The
|
|
3728
|
-
Resume; the
|
|
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
|
|
3729
3734
|
work. An expected duration is a positive finite number of milliseconds, measured from actual
|
|
3730
3735
|
entry into `on-break`, excluding any `starting-after-task` wait. Omission means no expected
|
|
3731
|
-
duration was supplied. Neither duration expiry nor an overdue indication authorizes the
|
|
3736
|
+
duration was supplied. Neither duration expiry nor an overdue indication authorizes the agent application
|
|
3732
3737
|
or provider to end the break, restore availability, or route work to the agent.
|
|
3733
3738
|
|
|
3734
3739
|
**A lead lifting the restriction does not resume the agent.** On an applied
|
|
3735
3740
|
`end-forced-break`, the provider clears `BreakState.forced` while preserving the current
|
|
3736
3741
|
`on-break` or `starting-after-task` status and `activeReasonId`. This command cannot publish
|
|
3737
3742
|
`not-requested`, restore readiness, or start routing work. Ordinary progress from
|
|
3738
|
-
`starting-after-task` to `on-break` still happens when work finishes. The
|
|
3743
|
+
`starting-after-task` to `on-break` still happens when work finishes. The agent application follows the
|
|
3739
3744
|
published state and waits for the agent's explicit Resume, even after reconnect. Historical
|
|
3740
3745
|
attribution is not rewritten by clearing the current forced marker.
|
|
3741
3746
|
|
|
@@ -3745,7 +3750,7 @@ must correlate resumption to the authenticated agent's action; a state-only orde
|
|
|
3745
3750
|
cannot establish the cause of a transition. The usual multi-provider end/reconciliation flow
|
|
3746
3751
|
still applies, and work resumes only on confirmed provider state.
|
|
3747
3752
|
|
|
3748
|
-
|
|
3753
|
+
An agent application may display the expected duration. A countdown requires an evidenced actual start;
|
|
3749
3754
|
a received snapshot, replay or reconnect is not a new start and cannot restart the duration.
|
|
3750
3755
|
Without that evidence, show the duration without inventing a start or return time.
|
|
3751
3756
|
|
|
@@ -3789,7 +3794,7 @@ States how many tasks this provider may have allocated to the agent **at once**.
|
|
|
3789
3794
|
`count` is an absolute ceiling, not an increment, a whole number of zero or more
|
|
3790
3795
|
(`capacity.count`). An agent's capacity is a property of the agent, not of the moment: it is
|
|
3791
3796
|
stated when the agent is set up and restated only when it genuinely changes, which is a
|
|
3792
|
-
|
|
3797
|
+
local policy change rather than a task starting or ending -- with one exception below.
|
|
3793
3798
|
|
|
3794
3799
|
**The provider counts its own outstanding tasks against it.** Allocate while you hold fewer than
|
|
3795
3800
|
`count` tasks for this agent, and stop when you hold that many; when one of yours ends you have
|
|
@@ -3798,22 +3803,22 @@ go, and a provider that waits for it will stall.
|
|
|
3798
3803
|
|
|
3799
3804
|
Your own tasks are the only ones you count. What the agent holds at other providers is not your
|
|
3800
3805
|
concern — Omni set `count` knowing it, and this is how: the agent is one person on several
|
|
3801
|
-
providers, and the
|
|
3802
|
-
provider that has none of it for now is told **`count: 0`,
|
|
3803
|
-
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,
|
|
3804
3809
|
a fact this provider holds, where `on-task` would assert work it cannot see -- and take the next
|
|
3805
|
-
count as any other when the
|
|
3806
|
-
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.
|
|
3807
3812
|
|
|
3808
3813
|
**A capacity is taken, never refused.** `setCapacity` answers `applied` and nothing else: a
|
|
3809
|
-
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
|
|
3810
3815
|
readings of which ceiling stood after it. A provider that cannot carry the count allocates within
|
|
3811
3816
|
what it can and says so on a `diagnostic`, so the gap is visible and nothing is inferred from a
|
|
3812
3817
|
refusal.
|
|
3813
3818
|
|
|
3814
3819
|
Capacity supersedes rather than accumulates: the latest value is the ceiling, and a decrease is as
|
|
3815
3820
|
ordinary as an increase. A provider whose ceiling can only rise -- one that keeps the highest count
|
|
3816
|
-
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
|
|
3817
3822
|
taking capacity away is answered `applied` while the work keeps coming. The harness moves the axis
|
|
3818
3823
|
both ways after the drive, two then one then nought, and an offer after a lower count is caught
|
|
3819
3824
|
against it (`stream.taskOffered.overCapacity`).
|
|
@@ -3852,7 +3857,7 @@ These validators report violations without dispatching, rewriting state or provi
|
|
|
3852
3857
|
Recheck prerequisites atomically at the provider; client validation cannot prevent a race.
|
|
3853
3858
|
Continue to use `validateResult` for each method's response vocabulary. A request acknowledgment
|
|
3854
3859
|
is not a grant, and no method response overwrites a newer state emission. Rejected promises
|
|
3855
|
-
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,
|
|
3856
3861
|
durable mutually exclusive commit/cancel decision and reconciliation obligations above remain
|
|
3857
3862
|
required; per-provider validation does not implement the multi-provider coordinator.
|
|
3858
3863
|
|
|
@@ -3979,7 +3984,7 @@ request and is ready to stop the agent when Omni asks. It does not reveal that o
|
|
|
3979
3984
|
or why Omni has not committed yet. While reporting `granted`, the provider remains available,
|
|
3980
3985
|
continues to honour Omni's current capacity, and continues offering work normally.
|
|
3981
3986
|
|
|
3982
|
-
Omni separately tracks the aggregate
|
|
3987
|
+
Omni separately tracks the aggregate agent application states `working`, `requesting-break`, `committing-break`,
|
|
3983
3988
|
`cancelling-break`, and `on-break`. While `requesting-break`, Omni shows which providers remain
|
|
3984
3989
|
outstanding and offers **Cancel break request**, but does not tell the agent that their break has
|
|
3985
3990
|
begun.
|
|
@@ -3996,7 +4001,7 @@ Omni coordinates one break attempt as follows:
|
|
|
3996
4001
|
from and is not asked: nothing is asked of it, the break proceeds without it, and when the
|
|
3997
4002
|
login is restored it is reconciled from its snapshot as a set-aside provider is, with no
|
|
3998
4003
|
capacity until then. `refreshing` keeps a provider in — its identity and capabilities remain
|
|
3999
|
-
available and work continues. `assertBreakAttemptProviders` holds
|
|
4004
|
+
available and work continues. `assertBreakAttemptProviders` holds an agent application to this set.
|
|
4000
4005
|
2. Enter `requesting-break`. Keep the agent's normal capacity in place throughout this phase.
|
|
4001
4006
|
3. Send one `requestBreak` to every asked provider. A provider reports `awaiting-approval` or
|
|
4002
4007
|
`granted`; neither state stops work. A denial transitions directly to `not-requested` and
|
|
@@ -4128,7 +4133,7 @@ an absent `team` still means the login is not entitled to that contribution.
|
|
|
4128
4133
|
|
|
4129
4134
|
| `TeamMember` field | Contract |
|
|
4130
4135
|
| --- | --- |
|
|
4131
|
-
| `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()`. |
|
|
4132
4137
|
| `availability` | Required. What the member is doing now. |
|
|
4133
4138
|
| `since` | Optional. When the current `availability` began — not when they signed in, and not when the team member list was read. |
|
|
4134
4139
|
| `break` | Present only while the member has an outstanding break request. See **A member waiting for a break**. |
|
|
@@ -4140,7 +4145,7 @@ Each availability value means one thing:
|
|
|
4140
4145
|
| `ready` | Signed in, able to take work, none assigned. |
|
|
4141
4146
|
| `on-task` | Working on at least one task. It says nothing about how many, and nothing about whether more will fit. |
|
|
4142
4147
|
| `on-break` | Stopped and not taking work, whether they asked or somebody stopped them. The reason lives on their own `BreakState`, not here. |
|
|
4143
|
-
| `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**. |
|
|
4144
4149
|
| `signed-out` | Known to this team but not signed in to this provider. |
|
|
4145
4150
|
|
|
4146
4151
|
**Always publish the complete team member list, never a change to it.** Team presence typically reaches an
|
|
@@ -4183,7 +4188,7 @@ One method, `executeTeamBreak`, taking a discriminated command exactly as `execu
|
|
|
4183
4188
|
|
|
4184
4189
|
`memberId` is this provider's own identifier for the member, as published on its team member list. It is
|
|
4185
4190
|
never an identifier from another provider, and Omni does not translate between them; names come
|
|
4186
|
-
from `
|
|
4191
|
+
from `getUserDetails()`.
|
|
4187
4192
|
|
|
4188
4193
|
`requests-suspended` means requests are **rejected outright** rather than left pending — nobody is coming to
|
|
4189
4194
|
approve them. A provider that suspends break requests must also publish `canRequestBreak: false` to the team's
|
|
@@ -4272,7 +4277,7 @@ const leadAssistCapable = {
|
|
|
4272
4277
|
```
|
|
4273
4278
|
|
|
4274
4279
|
Lead and member alike are `UserId`s of this provider, so an adapter publishing them implements
|
|
4275
|
-
`
|
|
4280
|
+
`getUserDetails()`; names never travel on a task or a team member list.
|
|
4276
4281
|
|
|
4277
4282
|
### Listening to a call
|
|
4278
4283
|
|
|
@@ -4413,12 +4418,12 @@ decision or it is not.
|
|
|
4413
4418
|
## Real-time media
|
|
4414
4419
|
|
|
4415
4420
|
Every voice provider has media: `channel: "voice"` says audio exists. Where the agent hears it
|
|
4416
|
-
depends on the `phone` the
|
|
4421
|
+
depends on the `phone` the agent application selected at authentication and connect from the manifest's
|
|
4417
4422
|
`phones`. On a softphone, audio lands in Omni; on a desk phone, it lands on the handset and Omni
|
|
4418
4423
|
opens no audio. See **How the agent hears the call**. Media transitions are voice-only;
|
|
4419
4424
|
chat and email publish none (`event.media.channel`, `stream.taskMedia.channel`).
|
|
4420
4425
|
|
|
4421
|
-
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
|
|
4422
4427
|
declared phone mode; it does not enumerate or reconfigure the platform's devices. The selected
|
|
4423
4428
|
mode stays with the login rather than being inferred from a snapshot or a device failure.
|
|
4424
4429
|
|
|
@@ -4427,11 +4432,11 @@ phase follow the provider's reports about the work, and the media — attaching,
|
|
|
4427
4432
|
hold, a consult, a conference or a transfer, and ending — is transient beside it. See **A task is
|
|
4428
4433
|
never its audio** under **Task allocation lifecycle**.
|
|
4429
4434
|
|
|
4430
|
-
### The
|
|
4435
|
+
### The agent application reports, the adapter decides
|
|
4431
4436
|
|
|
4432
4437
|
The adapter runs inside Omni and sees nothing of the station for itself: the devices, the
|
|
4433
|
-
permissions, the audio element and the network are the
|
|
4434
|
-
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
|
|
4435
4440
|
declares the agent ready to its platform, and again whenever the report changes**, and it decides
|
|
4436
4441
|
what any of it means and what to relay upstream — go not-ready, refuse calls, carry on because the
|
|
4437
4442
|
platform's audio lands elsewhere. Omni facilitates and does not take responsibility: it captures
|
|
@@ -4439,29 +4444,29 @@ the microphone once as the voice connection opens, so the permission prompt land
|
|
|
4439
4444
|
is signing in rather than over a contact, prompts, retries on the agent's request, tells the agent
|
|
4440
4445
|
what failed, and reports. It never decides for the adapter what a missing microphone means.
|
|
4441
4446
|
|
|
4442
|
-
**The
|
|
4443
|
-
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
|
|
4444
4449
|
browser's `urlVisibility`, and taking a `consent` offer only on the person's own press — and more
|
|
4445
4450
|
than one desk speaks this contract: a browser and a native application differ in what they can
|
|
4446
4451
|
reach, and a provider never branches on which it is talking to, only on what it has declared —
|
|
4447
|
-
here, and in `host.mute` (see **The station is the
|
|
4448
|
-
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
|
|
4449
4454
|
the permission everywhere else:
|
|
4450
4455
|
|
|
4451
4456
|
| Guarantee | Contract |
|
|
4452
4457
|
| --- | --- |
|
|
4453
|
-
| `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. |
|
|
4454
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. |
|
|
4455
4460
|
|
|
4456
|
-
A guarantee the
|
|
4461
|
+
A guarantee the agent application does not make is an absent key, never `false` — `validateHostGuarantees`
|
|
4457
4462
|
refuses a false one, as it refuses a name this contract does not list.
|
|
4458
4463
|
|
|
4459
4464
|
| Field | Contract |
|
|
4460
4465
|
| --- | --- |
|
|
4461
|
-
| `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. |
|
|
4462
4467
|
| `audio` | Present on a voice connection, absent where there is no audio. |
|
|
4463
|
-
| `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
|
|
4464
|
-
| `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. |
|
|
4465
4470
|
|
|
4466
4471
|
Omni republishes the report whenever it changes — a permission granted late, a headset unplugged,
|
|
4467
4472
|
a network gone — and it publishes a state, not a flicker: a change that resolves within moments is
|
|
@@ -4469,13 +4474,13 @@ not reported, so an adapter may act on what it reads without debouncing it again
|
|
|
4469
4474
|
report is a task fact or a capacity fact; it is what the station can do, and the adapter's platform
|
|
4470
4475
|
is the one that knows whether an agent without a microphone, or without a speaker, can work.
|
|
4471
4476
|
|
|
4472
|
-
The
|
|
4477
|
+
The agent application's own obligations here — asking at connect, never publishing `not-asked` when it does,
|
|
4473
4478
|
publishing a state and not a flicker — are Omni's tests' to hold. `exerciseAdapter` holds the
|
|
4474
|
-
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
|
|
4475
4480
|
a softphone login and none elsewhere -- not on a desk phone, not off voice
|
|
4476
4481
|
(`context.host.audio.required` / `.unexpected`), holds `host.mute` to the same line (`host.mute.required`
|
|
4477
4482
|
/ `.unexpected`, `host.mute` for a third word) -- and refuses a voice adapter that never asked the
|
|
4478
|
-
|
|
4483
|
+
agent application anything (`connection.host.consulted`), and one that subscribed to nothing will never hear of a
|
|
4479
4484
|
change (`connection.host.subscribed`): the obligation has two halves, and each is held.
|
|
4480
4485
|
|
|
4481
4486
|
### Capacity around setup
|
|
@@ -4486,8 +4491,8 @@ set up — the adapter's own registration incomplete, its credentials not yet re
|
|
|
4486
4491
|
|
|
4487
4492
|
Nothing closes that window, because nothing opens it: **Omni states no capacity until the
|
|
4488
4493
|
connection is established**, and **Work is pulled, never pushed** makes an allocation with none
|
|
4489
|
-
stated a violation. Capacity does not wait on the microphone: whether an agent without
|
|
4490
|
-
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
|
|
4491
4496
|
Omni withholding capacity for every platform alike.
|
|
4492
4497
|
|
|
4493
4498
|
Capacity follows **automatically** once the connection is established; the agent does not press
|
|
@@ -4496,7 +4501,7 @@ anything to become available.
|
|
|
4496
4501
|
| Situation | What Omni sends |
|
|
4497
4502
|
| --- | --- |
|
|
4498
4503
|
| Connecting | Nothing. No capacity has been stated, so nothing may be allocated. |
|
|
4499
|
-
| 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. |
|
|
4500
4505
|
| A task starts or ends | Nothing. The provider counts its own against the ceiling. |
|
|
4501
4506
|
| The agent's provisioned capacity changes | `setCapacity({ count: n })` |
|
|
4502
4507
|
| Agent asks for a break | `requestBreak`. Capacity is unchanged and work continues. |
|
|
@@ -4512,57 +4517,57 @@ a return.
|
|
|
4512
4517
|
|
|
4513
4518
|
### How the agent hears the call
|
|
4514
4519
|
|
|
4515
|
-
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
|
|
4516
4521
|
and Omni owns the microphone and the playback, or on a **desk phone**, a handset on the platform's
|
|
4517
|
-
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
|
|
4518
4523
|
these a platform can do is a fact about the platform, and which one a login is on is a choice the
|
|
4519
|
-
|
|
4524
|
+
agent application makes for that agent -- so the manifest declares and the agent application chooses.
|
|
4520
4525
|
|
|
4521
4526
|
```ts
|
|
4522
4527
|
// Manifest: what the platform can do
|
|
4523
4528
|
phones: ["softphone", "deskPhone"]
|
|
4524
|
-
// The
|
|
4529
|
+
// The agent application, at authentication and again at connect: which one this login is on
|
|
4525
4530
|
{ phone: "softphone" }
|
|
4526
4531
|
```
|
|
4527
4532
|
|
|
4528
4533
|
A voice manifest lists its `phones` and any other channel lists none (`manifest.phones.required`,
|
|
4529
|
-
`manifest.phones.channel`). The
|
|
4534
|
+
`manifest.phones.channel`). The agent application's `phone` is required on a voice login, absent on any other,
|
|
4530
4535
|
and one the manifest listed (`context.phone.required`, `.unexpected`, `.unsupported`). Everything
|
|
4531
|
-
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
|
|
4532
4537
|
audio, the adapter implements `openMedia`, and `task-media-started` is the word to open it; on a
|
|
4533
|
-
desk phone the
|
|
4538
|
+
desk phone the agent application reports no audio, opens nothing, and `task-media-started` still marks the
|
|
4534
4539
|
moment the call is live so the desk shows it, with the sound on the handset. A platform that lists
|
|
4535
4540
|
`deskPhone` alone never implements `openMedia`; one that lists `softphone` always does.
|
|
4536
4541
|
|
|
4537
|
-
**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
|
|
4538
4543
|
of station the agent signed in at, because the person chose it, and nothing else can know that.
|
|
4539
4544
|
The provider knows whether that station can carry a call right now -- whether a handset is
|
|
4540
|
-
registered -- which
|
|
4545
|
+
registered -- which an agent application in a browser cannot see. So the agent application's `phone` selects the branch, and
|
|
4541
4546
|
the provider's own knowledge of the device decides readiness within it. A provider never overrides
|
|
4542
|
-
the
|
|
4547
|
+
the agent application's declaration from its device record: treating a desk-phone login as a softphone because
|
|
4543
4548
|
the handset is momentarily unregistered, and demanding a microphone of it, is the reading this
|
|
4544
4549
|
sentence exists to refuse. What the provider does when the handset is not registered is what it
|
|
4545
4550
|
does for any station that cannot take a call -- hold the agent not-ready and say why.
|
|
4546
4551
|
|
|
4547
|
-
**On a desk-phone login the
|
|
4548
|
-
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
|
|
4549
4554
|
answers `unavailable` with a non-retryable failure, since waiting changes nothing about a station
|
|
4550
4555
|
that is a telephone. The harness requires no `openMedia` of such an adapter and never calls it.
|
|
4551
4556
|
|
|
4552
4557
|
**Which handset a desk-phone login rings is the platform's configuration for that agent**, and
|
|
4553
|
-
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
|
|
4554
4559
|
asks on surfaces of its own is its own decision.
|
|
4555
4560
|
|
|
4556
4561
|
**A declared phone the platform does not permit for this agent is a login that cannot be
|
|
4557
4562
|
established.** The manifest says what the platform can do; the agent's record, kept by an
|
|
4558
|
-
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
|
|
4559
4564
|
declaration -- a softphone declared for an agent whose record says desk phone. Honouring that
|
|
4560
4565
|
declaration on a switch that allows one registration per endpoint would evict the handset: the
|
|
4561
4566
|
agent's phone stops ringing and their calls land in a tab, and nobody is told. So the provider
|
|
4562
|
-
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
|
|
4563
4568
|
-- "this agent is configured for a desk phone" -- and refusing is correct, not an override of the
|
|
4564
|
-
|
|
4565
|
-
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
|
|
4566
4571
|
(`validateAuthenticationResult`) refuses a phone refusal marked otherwise
|
|
4567
4572
|
(`authentication.failure.phone.retryable`). And **a
|
|
4568
4573
|
provider never makes a declared phone true by changing the platform's configuration**: the record
|
|
@@ -4581,9 +4586,9 @@ openMedia({ taskId, localAudio }): Promise<OpenMediaResult>
|
|
|
4581
4586
|
The adapter speaks whatever its platform speaks — SIP over WebSocket, a vendor SDK, plain
|
|
4582
4587
|
WebRTC — and **none of that appears in this contract**. Registration, signalling, credential
|
|
4583
4588
|
renewal and reconnect are the adapter's, exactly as its authentication and transport already
|
|
4584
|
-
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
|
|
4585
4590
|
session ends — owning the microphone meaning capturing it, prompting, retrying and reporting how
|
|
4586
|
-
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
|
|
4587
4592
|
adapter decides**).
|
|
4588
4593
|
|
|
4589
4594
|
| Member | Contract |
|
|
@@ -4592,9 +4597,9 @@ adapter decides**).
|
|
|
4592
4597
|
| `setMuted(muted)` | Mutes the agent's microphone on this session. |
|
|
4593
4598
|
| `close()` | Releases the session. Omni calls it when the task ends. |
|
|
4594
4599
|
|
|
4595
|
-
`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
|
|
4596
4601
|
carries as `audio.input.localAudio`, and absent while that input is `unavailable`. A provider that
|
|
4597
|
-
bridges audio without
|
|
4602
|
+
bridges audio without an application-side input may ignore it; one that needs it and finds it absent
|
|
4598
4603
|
answers `unavailable` with a failure Omni shows the agent.
|
|
4599
4604
|
|
|
4600
4605
|
**When to ask is the provider's word, not Omni's guess.** On a softphone login, Omni opens media on `task-media-started`,
|
|
@@ -4632,87 +4637,87 @@ command asks the provider to **perform** something: `hold`, `transfer`, `confere
|
|
|
4632
4637
|
Provider-targeted `recording` commands and the rest act on the platform's own call leg, its bridge, or its record of the
|
|
4633
4638
|
task. Nothing has happened until the provider applies them, and `failed` means nothing happened.
|
|
4634
4639
|
|
|
4635
|
-
**The provider performs every action; the
|
|
4636
|
-
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
|
|
4637
4642
|
answers `failed` for a command whose capability it did not publish, and that is the provider
|
|
4638
|
-
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
|
|
4639
4644
|
its own reading of a queue's flag has decided something that was never its to decide. What a
|
|
4640
4645
|
command means on the platform is the provider's to work out: `end-call` disconnects the caller channel, and
|
|
4641
|
-
which legs on which bridge that touches is a fact about the switch, never a choice the
|
|
4642
|
-
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.
|
|
4643
4648
|
|
|
4644
|
-
### The station is the
|
|
4649
|
+
### The station is the agent application's
|
|
4645
4650
|
|
|
4646
4651
|
The microphone, the speaker, the headset and its buttons are the station's, and the station is the
|
|
4647
|
-
|
|
4652
|
+
agent application's. So mute is not a capability a provider declares, not a control a queue allows or a team
|
|
4648
4653
|
locks, not a preference the person keeps with the provider, and not a command: nothing about the
|
|
4649
|
-
station crosses to the provider except two things -- the station's condition, in the
|
|
4650
|
-
and the record of when the agent could not be heard, through `recordStep`.
|
|
4651
|
-
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`
|
|
4652
4657
|
alone, and performs it through `VoiceMediaSession.setMuted()`. A task carrying
|
|
4653
4658
|
`capabilities.mute` is refused (`task.capability.unknown`), and so is a `mute` command
|
|
4654
4659
|
(`command.type`), a `mute` preference (`preference.id`) and a `mute` policy (`team.policy.key`).
|
|
4655
4660
|
|
|
4656
|
-
**Two mutes, and the
|
|
4657
|
-
|
|
4658
|
-
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
|
|
4659
4664
|
the operating system: every application on the machine goes quiet, the system shows it, and a
|
|
4660
|
-
headset that follows the system follows it. Only a native
|
|
4661
|
-
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
|
|
4662
4667
|
microphone, and absent on a desk phone and off voice (`host.mute.required` / `.unexpected`). A
|
|
4663
|
-
browser says `stream`. A native
|
|
4664
|
-
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.
|
|
4665
4670
|
|
|
4666
4671
|
**A silenced device says who silenced it.** `audio.input.flowing: false` and
|
|
4667
|
-
`audio.output.flowing: false` carry `mutedBy`: `host` when the
|
|
4668
|
-
only ever true of
|
|
4669
|
-
flowing -- and `station` when a slider on the headset or the operating system did, which the
|
|
4670
|
-
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
|
|
4671
4676
|
and an adapter would take the agent out of ready in the middle of a call. A browser reads a station
|
|
4672
|
-
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
|
|
4673
4678
|
the endpoint and can clear an operating-system mute. A hardware slider is nobody's to clear.
|
|
4674
4679
|
|
|
4675
4680
|
**The record carries the same word.** A `muted` interaction leg is a period the agent could not be
|
|
4676
|
-
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
|
|
4677
4682
|
through `recordStep` -- its own Mute, and a station mute it observed during a call -- with
|
|
4678
4683
|
`mutedBy` saying whose the silence was, and the provider writes the word into the entry
|
|
4679
|
-
(`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
|
|
4680
4685
|
the provider knows who that is: it attributes the leg to the login's agent in `by`, a fact it
|
|
4681
|
-
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
|
|
4682
4687
|
`muted` entry is refused (`task.interactionHistory.muted.by`); a shared handset's unattributed hold
|
|
4683
4688
|
is a different claim, and stands. A supervisor reading the record then sees "the agent muted for
|
|
4684
4689
|
forty seconds" and "the agent's headset was muted for forty seconds" as the different things they
|
|
4685
|
-
are. See **The
|
|
4690
|
+
are. See **The agent application records what it performs**.
|
|
4686
4691
|
|
|
4687
4692
|
**Mute has a lifecycle, and none of it is inferred.** A call that starts while the station is
|
|
4688
4693
|
already muted begins a `muted` leg the moment its media starts, `mutedBy: "station"`. Media that
|
|
4689
|
-
ends while any leg is open ends the leg at that instant, as every
|
|
4690
|
-
|
|
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
|
|
4691
4696
|
opens a second call on the same task with no offer -- so an agent is never muted by the call before.
|
|
4692
4697
|
|
|
4693
|
-
**Every press on a headset is the agent's own press**, performed the
|
|
4694
|
-
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
|
|
4695
4700
|
as by a click. What a call-control headset sends, and what becomes of it:
|
|
4696
4701
|
|
|
4697
|
-
| Input | Where it comes from | What the
|
|
4702
|
+
| Input | Where it comes from | What the agent application does | What crosses to the provider |
|
|
4698
4703
|
| --- | --- | --- | --- |
|
|
4699
|
-
| Mute button | The headset, on the HID Telephony usage page: a Phone Mute report on the press, a Mute LED the
|
|
4700
|
-
| 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 |
|
|
4701
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 |
|
|
4702
|
-
| 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 |
|
|
4703
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 |
|
|
4704
|
-
| 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 |
|
|
4705
4710
|
| Device changed, unplugged, permission revoked | The operating system | Re-capture, prompt, report | Already `audio.input` and `audio.output` with `lost`, `denied`, `no-device` |
|
|
4706
4711
|
|
|
4707
|
-
**What the
|
|
4708
|
-
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
|
|
4709
4714
|
Mute control does not pretend: while the station is muted it shows the agent cannot be heard and
|
|
4710
|
-
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
|
|
4711
4716
|
states stay separate and the agent is not unmuted by surprise when the slider moves back. Before a
|
|
4712
4717
|
call, the same line in the ready state, so an agent does not learn of it from a silent customer;
|
|
4713
4718
|
whether work is held back is the adapter's decision from the report, as it is for every station
|
|
4714
4719
|
fact. Where a provider's own platform holds a mute of its own -- a bridge that silences a leg --
|
|
4715
|
-
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.
|
|
4716
4721
|
|
|
4717
4722
|
### Which commands need a capability
|
|
4718
4723
|
|
|
@@ -4725,7 +4730,7 @@ declared:
|
|
|
4725
4730
|
| `answer`, `accept` | Nothing. A task that was offered can be accepted, or offering it meant nothing. |
|
|
4726
4731
|
| `end-call` | The `endCall` capability. |
|
|
4727
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`. |
|
|
4728
|
-
| `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. |
|
|
4729
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`. |
|
|
4730
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`). |
|
|
4731
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. |
|
|
@@ -4751,10 +4756,10 @@ The commands with a phase of their own -- `answer`, `accept` and `decline` in `p
|
|
|
4751
4756
|
|
|
4752
4757
|
`validateTaskCommand(command, task)` holds a command to this table at runtime, both ways: the
|
|
4753
4758
|
capability it needs, the phase it belongs to, and the state that has to stand. The task it wants
|
|
4754
|
-
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
|
|
4755
4760
|
mapped shape has nothing honest to pass, and then checks shape alone, which is still worth doing
|
|
4756
4761
|
-- it names a command the wire never had -- but is not the table. Keeping the published task
|
|
4757
|
-
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
|
|
4758
4763
|
validator holds a command only to a task that stands: a task handed in that is not one the wire
|
|
4759
4764
|
published is named (`command.task`) rather than checked against.
|
|
4760
4765
|
|
|
@@ -4806,7 +4811,7 @@ react rather than only display the message:
|
|
|
4806
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. |
|
|
4807
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. |
|
|
4808
4813
|
| `omni.task-not-found` | The provider-local task id is unknown, typically after the task already ended. |
|
|
4809
|
-
| `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**. |
|
|
4810
4815
|
| `omni.destination-not-permitted` | The dialled number, or the `destinationId` named, is not one the provider offers this agent. |
|
|
4811
4816
|
| `omni.rate-limited` | The action was throttled. Pair with `retryAfterMs`. |
|
|
4812
4817
|
| `omni.unavailable` | The provider is temporarily unable to serve the action, including any command sent while `transport-status` is not `active`. |
|
|
@@ -4820,7 +4825,7 @@ They are published as `OMNI_FAILURE_CODES`.
|
|
|
4820
4825
|
|
|
4821
4826
|
| Field | Contract |
|
|
4822
4827
|
| --- | --- |
|
|
4823
|
-
| `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. |
|
|
4824
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. |
|
|
4825
4830
|
| `occurredAt` | Valid RFC-3339 timestamp with an explicit timezone, representing provider observation time. |
|
|
4826
4831
|
| `event` | Typed `ProviderEvent` payload. |
|
|
@@ -4835,7 +4840,7 @@ instant that the source cannot establish makes that evidence unavailable, not pe
|
|
|
4835
4840
|
invent a timestamp or silently omit a required field.
|
|
4836
4841
|
|
|
4837
4842
|
Clock estimation is an exception, not the standard interpretation of events. An ISO timestamp
|
|
4838
|
-
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.
|
|
4839
4844
|
Use an explicitly trusted clock in the relevant domain. Do not infer a general clock offset
|
|
4840
4845
|
from event arrivals, shift source timestamps, or use an estimate to order events or create history.
|
|
4841
4846
|
|
|
@@ -4843,29 +4848,29 @@ The current exceptions and their reasons are:
|
|
|
4843
4848
|
|
|
4844
4849
|
| Case | Exception and reason |
|
|
4845
4850
|
| --- | --- |
|
|
4846
|
-
|
|
|
4847
|
-
|
|
|
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. |
|
|
4848
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. |
|
|
4849
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. |
|
|
4850
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. |
|
|
4851
|
-
|
|
|
4852
|
-
| Provider receipt timestamps | The provider may use its own receipt instant for final records of receipt/processing because
|
|
4853
|
-
| 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. |
|
|
4854
4859
|
|
|
4855
4860
|
Every additional exception must be listed with its scope and reason before adoption. Estimates
|
|
4856
4861
|
must specify their uncertainty and validity; elapsed time should use monotonic aging, and a clock
|
|
4857
4862
|
jump invalidates the estimate. The optional check below samples provider time without changing event timestamps. Report
|
|
4858
4863
|
source-measured durations where required; timestamp subtraction is not a substitute.
|
|
4859
4864
|
|
|
4860
|
-
#### Optional periodic provider time checks and
|
|
4865
|
+
#### Optional periodic provider time checks and agent application estimates
|
|
4861
4866
|
|
|
4862
4867
|
A provider may declare `Manifest.timeCheck: true` and implement `Connection.checkTime(request)`.
|
|
4863
|
-
Omission means unsupported. The
|
|
4864
|
-
|
|
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
|
|
4865
4870
|
safe integer milliseconds, with `maxRoundTripMs <= timeoutMs <= intervalMs`. No default interval
|
|
4866
4871
|
is assumed. Validate settings with `validateProviderTimeCheckPolicy`.
|
|
4867
4872
|
|
|
4868
|
-
While the connection is active, the
|
|
4873
|
+
While the connection is active, the agent application checks immediately on opt-in and then at `intervalMs`
|
|
4869
4874
|
using a monotonic scheduler. Only one check may be outstanding. Missed ticks are skipped, never
|
|
4870
4875
|
replayed in a burst. Each request has a fresh ID. The provider samples its own event/deadline
|
|
4871
4876
|
clock after receiving the request and before returning `providerTime` as an ISO/RFC3339 instant,
|
|
@@ -4874,25 +4879,25 @@ may masquerade as provider time. A provider unable to sample that domain must no
|
|
|
4874
4879
|
Clock authority replacement or a discontinuity changes `clockId`.
|
|
4875
4880
|
|
|
4876
4881
|
Validate requests and responses with `validateProviderTimeCheckRequest` and
|
|
4877
|
-
`validateProviderTimeCheckResult`. The
|
|
4882
|
+
`validateProviderTimeCheckResult`. The agent application also checks the outstanding request, provider/connection
|
|
4878
4883
|
and login, monotonic elapsed time and cancellation after the await: a late successful response is
|
|
4879
4884
|
still rejected. Reject samples at or beyond `timeoutMs`, or above `maxRoundTripMs`. Cancel local
|
|
4880
4885
|
tracking and invalidate estimates on disconnect, logout, policy disable, clock change or clock
|
|
4881
4886
|
jump; late results cannot restore them. Resume with a fresh check after reconnection. Expire a
|
|
4882
|
-
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
|
|
4883
4888
|
existing diagnostic path, with the estimate unavailable; the next scheduled check can recover.
|
|
4884
4889
|
Checking failure alone does not assert call/recording state or require a healthy connection to close.
|
|
4885
4890
|
This read-only check is not a task command. Protocol supplies declarations and validators; the
|
|
4886
|
-
|
|
4891
|
+
agent application implements the scheduler and the provider implements the clock read.
|
|
4887
4892
|
|
|
4888
|
-
The
|
|
4893
|
+
The agent application may also expose `Host.estimateProviderTime({ providerId, loginId })`. It returns an ISO
|
|
4889
4894
|
`at` in that provider's clock domain, the same explicit scope and `clockId`, or `undefined` when
|
|
4890
4895
|
unavailable. Optional `uncertaintyMs` is an estimate, not a guaranteed bound. The method is optional
|
|
4891
|
-
at the
|
|
4892
|
-
checks does not require the
|
|
4893
|
-
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.
|
|
4894
4899
|
|
|
4895
|
-
To estimate the difference, the
|
|
4900
|
+
To estimate the difference, the agent application brackets the request with local send/receive instants and
|
|
4896
4901
|
measures elapsed time monotonically. For provider sample P and local bracketing instants H0/H1,
|
|
4897
4902
|
the offset under stable clocks lies between P-H1 and P-H0, before clock error and timestamp
|
|
4898
4903
|
precision are considered. A midpoint is only an estimate; one-way delays need not be symmetric.
|
|
@@ -4901,22 +4906,22 @@ sample with monotonic elapsed time, and return unavailable after its configured
|
|
|
4901
4906
|
`validateProviderTimeEstimate` checks shape and scope, not actual accuracy or freshness.
|
|
4902
4907
|
|
|
4903
4908
|
A provider may declare `Manifest.timestampAuthority: "provider"`: its own timestamps are
|
|
4904
|
-
used for final records, and
|
|
4905
|
-
of time-check support and the optional
|
|
4906
|
-
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
|
|
4907
4912
|
is inferred. Successful `recordStep` responses identify the provider-selected history instant;
|
|
4908
|
-
see **The
|
|
4913
|
+
see **The agent application records what it performs** for correlation and retained bindings.
|
|
4909
4914
|
|
|
4910
|
-
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
|
|
4911
4916
|
message using its own clock at receipt and use that for its own processing/accounting. The reason
|
|
4912
|
-
for this exception is that
|
|
4913
|
-
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
|
|
4914
4919
|
action, recorder capture boundary, or proof an operation applied. An existing field with a
|
|
4915
4920
|
specific occurrence meaning still requires that evidence; this option does not silently redefine
|
|
4916
|
-
it.
|
|
4921
|
+
it. Agent application-only instants remain advisory input; the provider owns its authoritative publication.
|
|
4917
4922
|
|
|
4918
|
-
This
|
|
4919
|
-
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,
|
|
4920
4925
|
may not replace event/history timestamps, and alone cannot authorize deadline actions or prove
|
|
4921
4926
|
recording freshness. Uses requiring a trustworthy bound still need independently established
|
|
4922
4927
|
clock accuracy/drift assumptions. It neither synchronizes the operating-system clock nor changes
|
|
@@ -4979,8 +4984,8 @@ safe for the agent to see.
|
|
|
4979
4984
|
| `active` | The transport is up and the provider is serving this session. It is the only value under which work arrives. |
|
|
4980
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. |
|
|
4981
4986
|
|
|
4982
|
-
**An error names its recovery, and the
|
|
4983
|
-
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:
|
|
4984
4989
|
|
|
4985
4990
|
- **`reconnect`** — the login is good and this connection is not: a backend restart, a session the
|
|
4986
4991
|
platform no longer recognises. Omni calls `disconnect()` on the dead connection and then
|
|
@@ -4995,7 +5000,7 @@ died; the host knows how to run a login. `recovery` joins the two:
|
|
|
4995
5000
|
`reconnect`, and the fresh snapshot re-establishes state. The adapter tears nothing down itself
|
|
4996
5001
|
and expects nothing to carry on: the connection that reported `error` is finished.
|
|
4997
5002
|
|
|
4998
|
-
**Patience is the
|
|
5003
|
+
**Patience is the agent application's.** An adapter in `connecting` retries for as long as it takes and never
|
|
4999
5004
|
has to decide when to stop. Omni owns giving up: after however long it chooses to wait, it may
|
|
5000
5005
|
call `disconnect()` and either `connect()` afresh or surface the failure — so neither side waits
|
|
5001
5006
|
for the other to blink.
|
|
@@ -5011,7 +5016,7 @@ authentication state, not here.
|
|
|
5011
5016
|
adapter that leaves a stale `active` in place is telling Omni to keep waiting for work that cannot
|
|
5012
5017
|
come — see **Liveness**.
|
|
5013
5018
|
|
|
5014
|
-
**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
|
|
5015
5020
|
works — `connecting` from the moment it is reported, `error` with what revives it — and never a
|
|
5016
5021
|
healthy workspace over a transport that cannot serve it: an agent talking on a desk whose transport
|
|
5017
5022
|
died was an incident, and it happened because the desk knew and did not say. The same obligation
|
|
@@ -5046,9 +5051,9 @@ snapshot. `seed(snapshot)` returns violations, which callers must check. This is
|
|
|
5046
5051
|
tracker, not a complete production reducer: snapshot freshness, login/connection fencing and
|
|
5047
5052
|
full task consistency remain separately required.
|
|
5048
5053
|
|
|
5049
|
-
**Both sides must enforce the order.** The
|
|
5054
|
+
**Both sides must enforce the order.** The agent application validates immediately before dispatch; the
|
|
5050
5055
|
provider rechecks current permission and state atomically before acting. The provider validates
|
|
5051
|
-
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
|
|
5052
5057
|
validates the envelope, transition and full task consistency before replacing local state. A
|
|
5053
5058
|
request result does not optimistically advance break state, and a delayed command cannot apply
|
|
5054
5059
|
to a later attempt merely because its status happens to look compatible. Keep at most one
|
|
@@ -5061,7 +5066,7 @@ Normal order is `not-requested` → `awaiting-approval` → `granted` →
|
|
|
5061
5066
|
returns a precommit request to `not-requested`; an explicit agent end returns a committed
|
|
5062
5067
|
break there. Same-state restatements are allowed. An evidenced forced break is the explicit
|
|
5063
5068
|
exception to requesting/granting, and must carry its forced actor/state. Neither skipped
|
|
5064
|
-
publication nor
|
|
5069
|
+
publication nor an agent application-local guess creates another exception. A later normal attempt starts
|
|
5065
5070
|
from `not-requested`, never by regressing an active break into a request.
|
|
5066
5071
|
|
|
5067
5072
|
A fresh authoritative snapshot establishes a new baseline; do not run this event-transition
|
|
@@ -5102,8 +5107,8 @@ The provider's word that the task's audio should now attach. Omni calls `openMed
|
|
|
5102
5107
|
a task carried with `media: "started"`, which is how a reconnect snapshot reattaches audio an
|
|
5103
5108
|
earlier event brought — and renders the call as live from that word, never from its own senses. It
|
|
5104
5109
|
precedes `openMedia` and is never a reply to it: a provider whose media state comes from the
|
|
5105
|
-
platform, a station going in use the moment a call is answered, sends it then, before any
|
|
5106
|
-
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
|
|
5107
5112
|
names a task whose work has begun, and it alternates with `task-media-ended`: media that never
|
|
5108
5113
|
started cannot end, so a live call whose provider says nothing about its audio is a provider in
|
|
5109
5114
|
breach, not a state a desk fills in from its own devices.
|
|
@@ -5138,7 +5143,7 @@ A successful `complete` or `transfer` command does not clear the task. Omni wait
|
|
|
5138
5143
|
and not for ever: `applied` to a completion -- `complete`, or a lead's `take-over-call` -- says the
|
|
5139
5144
|
provider has completed the task, and its `task-ended` follows within the
|
|
5140
5145
|
manifest's `completionSettleMs`. A provider never answers `applied` for a completion it has not yet
|
|
5141
|
-
performed. Past the bound the
|
|
5146
|
+
performed. Past the bound the agent application calls `snapshot()`: a snapshot still carrying the task is a task
|
|
5142
5147
|
held open by a provider that said it was done, and the desk shows it as unsettled -- "Completing...
|
|
5143
5148
|
the provider has not confirmed" -- naming the command; a snapshot no longer carrying it clears the
|
|
5144
5149
|
task, since the ending was owed and lost. The drive holds a provider to the same bound
|
|
@@ -5150,7 +5155,7 @@ recognised as the late event it is, never applied to the life now open under the
|
|
|
5150
5155
|
|
|
5151
5156
|
### `dial-outcome`
|
|
5152
5157
|
|
|
5153
|
-
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`,
|
|
5154
5159
|
or one of the declared ways of not reaching the destination, with the switch's `reason` where it
|
|
5155
5160
|
gave one. `taskId` names the task the dial was on, where there was one, and that task may already
|
|
5156
5161
|
have ended. Omni shows it to the agent against the call it belonged to and does nothing else: no
|
|
@@ -5170,9 +5175,9 @@ state read that contradicted itself, a `task-ended` naming a task nobody held, a
|
|
|
5170
5175
|
without a name. `expected` is the rule as a sentence, `observed` is what came instead, and
|
|
5171
5176
|
`taskId` names the task where there is one. **Informational, never behavioural**: the provider has
|
|
5172
5177
|
already done the safe thing before it speaks, and nothing in the task or break machinery reacts to
|
|
5173
|
-
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
|
|
5174
5179
|
agent works and keeps a count an operator can read, because a healthy workspace over a shouting
|
|
5175
|
-
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
|
|
5176
5181
|
provider does not batch. `exerciseAdapter` treats a diagnostic delivered during a run as a
|
|
5177
5182
|
violation (`diagnostic.raised`): a conformance run against a platform that is breaking its rules
|
|
5178
5183
|
fails loudly rather than passing with a note. A task published under `capabilitySource:
|
|
@@ -5252,18 +5257,18 @@ same exported checks are used by Omni and adapter tests so their interpretations
|
|
|
5252
5257
|
| `validateEventEnvelope(envelope, manifest)` | Envelope identity, timestamp, and the payload for each event type. |
|
|
5253
5258
|
| `validateContact(contact)` | Contact field shapes and attribute keys. Every field is optional, so this checks what is present rather than what is missing. |
|
|
5254
5259
|
| `validateScheduledActivity(activity)` | Required activity fields and start/end ordering. |
|
|
5255
|
-
| `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. |
|
|
5256
5261
|
| `validateProviderTimeCheckPolicy(policy)` | Explicit optional polling settings, positive safe-integer durations and round-trip/timeout/interval ordering. |
|
|
5257
|
-
| `validateProviderTimeCheckRequest(request)` | Fresh-request shape; the
|
|
5262
|
+
| `validateProviderTimeCheckRequest(request)` | Fresh-request shape; the agent application enforces actual uniqueness and outstanding-request lifetime. |
|
|
5258
5263
|
| `validateProviderTimeCheckResult(result, request, loginId)` | ISO timestamp, clock identity, exact request/login correlation; timing and source accuracy remain runtime checks. |
|
|
5259
|
-
| `validateProviderTimeEstimate(estimate, scope)` | Optional
|
|
5260
|
-
| `validateInteractionReport(report, path?, manifest?)` | What the
|
|
5261
|
-
| `validateHostReport(report)` | The
|
|
5262
|
-
| `validateHostMute(mute, softphone)` | What the
|
|
5263
|
-
| `validateLoginStore(store)` | The login's store the
|
|
5264
|
-
| `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`). |
|
|
5265
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. |
|
|
5266
|
-
| `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. |
|
|
5267
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. |
|
|
5268
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. |
|
|
5269
5274
|
|
|
@@ -5289,25 +5294,25 @@ and may be compiled against a different protocol version, so its output is untru
|
|
|
5289
5294
|
Validating a snapshot before it replaces provider state is what stops a malformed task from
|
|
5290
5295
|
reaching the workspace.
|
|
5291
5296
|
|
|
5292
|
-
### What the
|
|
5297
|
+
### What the agent application does with what it refuses
|
|
5293
5298
|
|
|
5294
5299
|
A refusal has an aftermath on the desk and a report to the provider, and both are stated.
|
|
5295
5300
|
|
|
5296
|
-
- **A refused snapshot replaces nothing.** The
|
|
5301
|
+
- **A refused snapshot replaces nothing.** The agent application keeps the last state it took from this
|
|
5297
5302
|
provider whole, and shows the provider as faulted -- the transport as it stands, and the words
|
|
5298
5303
|
"last update refused" with the rule -- so the agent knows the view is standing still rather than
|
|
5299
5304
|
believing it current. It does not adopt the good half of a snapshot: a task the agent is on would
|
|
5300
5305
|
vanish from the desk while the call is up.
|
|
5301
|
-
- **A refused event is dropped and counted.** The state the
|
|
5302
|
-
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
|
|
5303
5308
|
nothing. The desk shows nothing for it: the view kept moving for every other event, so "last
|
|
5304
5309
|
update refused" would be false of it, and a chip that said nothing would be right about the
|
|
5305
|
-
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.
|
|
5306
5311
|
- **The provider is told, every time.** `refused(report)` carries the artefact, the envelope id for
|
|
5307
5312
|
an event, and every violation with its rule and path. An adapter logs it at error and treats it as
|
|
5308
|
-
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
|
|
5309
5314
|
provider that hears nothing runs a whole shift beside a frozen desk with nothing wrong on its
|
|
5310
|
-
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
|
|
5311
5316
|
(`connection.refused.required`, `connection.refused.rejected`).
|
|
5312
5317
|
|
|
5313
5318
|
## Conformance helpers
|
|
@@ -5352,18 +5357,18 @@ in each adopter's own tests. `{ drive: true }` closes that: the exercise takes t
|
|
|
5352
5357
|
provider offers through one ordinary lifecycle -- accept it, wait for its media and open it on a
|
|
5353
5358
|
softphone, hold and resume where the task offers `hold`, end the call where it offers `endCall`,
|
|
5354
5359
|
complete it with an outcome where the agent completes -- and holds every step to the rules a
|
|
5355
|
-
|
|
5360
|
+
agent application holds a provider to. Each command is validated against the task as published
|
|
5356
5361
|
(`drive.command.*`), each answer for its method, a refusal of a control the task offered is a
|
|
5357
5362
|
violation (`drive.command.failed`), and an event the provider owes and never sends is one too
|
|
5358
5363
|
(`drive.timeout`, after `driveTimeoutMs`, 5000 by default). Around the drive the exercise holds
|
|
5359
|
-
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
|
|
5360
5365
|
capacity, or beyond the count with nothing dialled on it, is named
|
|
5361
5366
|
(`stream.taskOffered.beforeCapacity`, `.overCapacity`); every user the snapshot names is looked up
|
|
5362
|
-
through `
|
|
5363
|
-
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
|
|
5364
5369
|
the second adapter a `rebuild` gives comes up signed in as the same login from the secrets alone
|
|
5365
5370
|
before it reads anything (`drive.reload.login`), and is then held to everything the first was on
|
|
5366
|
-
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
|
|
5367
5372
|
snapshot read as the connect snapshot was, and its media opened afresh on a task carried with
|
|
5368
5373
|
`media: "started"`, since the first client's session died with it. The result also says which rules the
|
|
5369
5374
|
run evaluated, pass or fail, in `rulesEvaluated`: the validators' as each was applied, the stream's
|
|
@@ -5377,8 +5382,8 @@ in the completing publication or the open entry is refused (`task.interactionHis
|
|
|
5377
5382
|
and the drive's closing report after the media ended is expected `recorded` and to change nothing:
|
|
5378
5383
|
a record restated afterwards with that leg's duration altered is named (`drive.recordStep.overwritten`).
|
|
5379
5384
|
Where the provider restates the task's record afterwards, each leg is in it or the hole is named
|
|
5380
|
-
(`drive.recordStep.history`). Given `rebuild`, a way to build the adapter again as
|
|
5381
|
-
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,
|
|
5382
5387
|
disconnected and its session closed (`drive.reload.handover`), and only then is a second adapter
|
|
5383
5388
|
built and connected for the same login with the same context and the same `store`; its snapshot
|
|
5384
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
|
|
@@ -5397,8 +5402,8 @@ first client is gone before the second connects, there is no client for the plat
|
|
|
5397
5402
|
open task back to, and nothing about the reload is exempt from any rule. The second adapter opens its session from the same
|
|
5398
5403
|
`secrets`, so the reload is a restore before it is anything else: an adapter that holds a session
|
|
5399
5404
|
has already put what would rebuild it into the secrets store, from the moment it was handed one,
|
|
5400
|
-
not only when a flow completes -- the session is the adapter's, the store is the
|
|
5401
|
-
|
|
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
|
|
5402
5407
|
handed the adapter holds no key naming the task's id, delimited, never as a run of characters inside
|
|
5403
5408
|
another id (`drive.store.retained`); an adapter whose keys never named the task leaves the rule
|
|
5404
5409
|
unevaluated, and the result says so rather than passing it: a task's keys go
|
|
@@ -5456,8 +5461,8 @@ cannot be established from TypeScript structure alone.
|
|
|
5456
5461
|
| `assertReached(result, subjects)` | The exercise met every subject named; throws listing those it did not. Pair it with a clean `exerciseAdapter` result. |
|
|
5457
5462
|
| `assertAuthenticationRestoreAndExpiry(states)` | A restored authenticated session can refresh and ends in expiry. Every state is validated. |
|
|
5458
5463
|
| `assertReconnectWithMissedAssignments(before, reconnect, ids)` | A reconnect snapshot restores assignments received while offline. |
|
|
5459
|
-
| `stillHost(report?, guarantees?, mute?)` |
|
|
5460
|
-
| `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. |
|
|
5461
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. |
|
|
5462
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. |
|
|
5463
5468
|
| `assertBreakAttemptProviders(candidates, asked)` | A break attempt asks every usable provider holding capacity, `refreshing` included, and nothing of a provider whose login is `expired`. |
|
|
@@ -5475,7 +5480,7 @@ Adapters should run the relevant scenarios against deterministic test state befo
|
|
|
5475
5480
|
|
|
5476
5481
|
## A provider does not style the workspace
|
|
5477
5482
|
|
|
5478
|
-
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
|
|
5479
5484
|
belongs in this contract is the boundary. A provider says what a control **is** through its
|
|
5480
5485
|
capabilities and what its work is **called** through `phaseLabels` and `taskTypePresentation`; how
|
|
5481
5486
|
any of it is drawn is Omni's. A task cannot select a design language, inject a component, or
|
|
@@ -5485,21 +5490,21 @@ override the agent's theme and font preferences.
|
|
|
5485
5490
|
|
|
5486
5491
|
Recording is voice-only. A task can arrive already recording, including while pending, and offer
|
|
5487
5492
|
no recording controls. The provider publishes only its own current state on
|
|
5488
|
-
the provider field of the task’s `recording` state. The
|
|
5489
|
-
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
|
|
5490
5495
|
establishes recording. Missing state, lost recorder observation, transport loss and expired
|
|
5491
5496
|
observation mean unknown; they never mean stopped. Paused means a recording remains open without
|
|
5492
5497
|
capturing. Task hold, task pause and recording pause are independent.
|
|
5493
5498
|
|
|
5494
|
-
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
|
|
5495
5500
|
action sets. The outer capability may be locked. Absence grants no permission, and a state may
|
|
5496
5501
|
exist without any permission. A provider may offer only stop for a recording started automatically,
|
|
5497
5502
|
or withdraw a control on a later task update. There is no global recording mode and no automatic
|
|
5498
|
-
start from a capability declaration.
|
|
5499
|
-
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;
|
|
5500
5505
|
unknown destinations or unsupported actions are refused visibly, never redirected to provider
|
|
5501
|
-
recording or a default upload location. Initial
|
|
5502
|
-
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.
|
|
5503
5508
|
This package declares that contract; it supplies no recorder, media mixing, storage or upload.
|
|
5504
5509
|
|
|
5505
5510
|
| Action | Required current recording state | Confirmed outcome |
|
|
@@ -5520,7 +5525,7 @@ Partial success (capture stopped but storage outcome unknown) cannot return fail
|
|
|
5520
5525
|
of no effect. It rejects with unknown outcome, reports the failure visibly and publishes whatever
|
|
5521
5526
|
current capture state is actually known. Retention is not a promise of sample-perfect audio.
|
|
5522
5527
|
|
|
5523
|
-
Provider commands go exclusively to `Connection.execute`;
|
|
5528
|
+
Provider commands go exclusively to `Connection.execute`; agent application commands go exclusively to
|
|
5524
5529
|
`HostRecording.execute`. Both include task, allocation, request and observation identities; all
|
|
5525
5530
|
non-start commands identify the particular recording. IDs are opaque and scoped by provider login,
|
|
5526
5531
|
task and recorder owner. They are never inferred from filenames or current agent identity. A
|
|
@@ -5528,8 +5533,8 @@ recording ID survives pause/resume and reallocation only where the same recorder
|
|
|
5528
5533
|
commands always name the current allocation. A later start gets a different ID. Reconnect does not
|
|
5529
5534
|
create a new recording or fresh evidence. After lost continuity, use unknown until reconciled.
|
|
5530
5535
|
There is no automatic restart, transfer to another recorder, or stop on task hold/disconnect.
|
|
5531
|
-
Task removal does not prove recording stopped: outstanding
|
|
5532
|
-
|
|
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.
|
|
5533
5538
|
|
|
5534
5539
|
Only start/resume require an in-progress or paused task with started media. Pause/stop/cancel may
|
|
5535
5540
|
also finish an independently observed recorder while the task is completing. A pending task may
|
|
@@ -5550,34 +5555,34 @@ identity or observation time publish unknown and offer no issuable controls.
|
|
|
5550
5555
|
|
|
5551
5556
|
Use `validateTask` and `validateTaskCommand` for published shape and static permissions.
|
|
5552
5557
|
Immediately before dispatch, additionally use `validateRecordingRequest` against the full current
|
|
5553
|
-
task and explicit observer-domain time. For
|
|
5554
|
-
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;
|
|
5555
5560
|
client validation alone is not authorization. The executor serializes operations on each recorder
|
|
5556
5561
|
and durably correlates request identity with scope, action and outcome: an exact retry refers to the
|
|
5557
5562
|
same operation; reuse with different contents is refused. It never silently retries an ambiguous
|
|
5558
5563
|
operation. Implementations without reliable request correlation must refuse controls.
|
|
5559
5564
|
|
|
5560
|
-
Keep command progress in
|
|
5561
|
-
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
|
|
5562
5567
|
resolving applied. Failed means confirmed no effect. Rejected promise means outcome unknown:
|
|
5563
5568
|
show the failure, reconcile that path and do not automatically retry or pretend inactive. Authoritative
|
|
5564
|
-
updates remain full current task/
|
|
5569
|
+
updates remain full current task/agent application views, not replayed provider events. These current-state fields
|
|
5565
5570
|
create no interaction-history entries and infer no actors, durations or historical capture boundaries.
|
|
5566
5571
|
|
|
5567
5572
|
Migration is explicit: replace the old true recording capability with per-path action policies,
|
|
5568
5573
|
replace untargeted commands with scoped commands, and retain protocol version 1. During pre-release
|
|
5569
5574
|
development, hosts and adapters must align their recording contracts; negotiation does not detect
|
|
5570
5575
|
differences between package revisions that share that protocol number. No legacy-shape fallback is provided.
|
|
5571
|
-
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.
|
|
5572
5577
|
|
|
5573
5578
|
`validateRecordingOutcome` checks confirming observations against recording-specific applied/failed
|
|
5574
5579
|
semantics, including pause/resume identity and rejection of a dialling result. It cannot prove audio
|
|
5575
5580
|
retention/completion or source truth from a status flag; those remain executor obligations.
|
|
5576
5581
|
|
|
5577
|
-
|
|
5582
|
+
Agent application destination selection binds storage when start actually creates a recording. Existing recordings
|
|
5578
5583
|
keep that binding through pause/resume/stop/cancel; a later policy cannot redirect their stored audio.
|
|
5579
5584
|
The executor rejects a mismatched destination rather than moving or discarding another binding.
|
|
5580
|
-
Action permission does not authorize unattended invocation:
|
|
5585
|
+
Action permission does not authorize unattended invocation: agent application controls require the agent's explicit
|
|
5581
5586
|
act, and provider authorization remains enforced at its authenticated command boundary.
|
|
5582
5587
|
|
|
5583
5588
|
|
|
@@ -5589,31 +5594,31 @@ capability restrictions from disappearing at dispatch. The standalone `validateR
|
|
|
5589
5594
|
requires an affirmative permission; false, malformed permission declarations and unknown actions grant nothing.
|
|
5590
5595
|
|
|
5591
5596
|
|
|
5592
|
-
###
|
|
5597
|
+
### Agent application recording announcements to the caller
|
|
5593
5598
|
|
|
5594
|
-
`HostRecording.announcesToCaller`, when true, is a guarantee that the
|
|
5599
|
+
`HostRecording.announcesToCaller`, when true, is a guarantee that the agent application delivers audible recording
|
|
5595
5600
|
status messages to the remote party over the call's outgoing audio. It belongs inside the optional
|
|
5596
|
-
|
|
5601
|
+
agent application recording capability: without that capability there is no such guarantee. Omission makes no
|
|
5597
5602
|
promise; false is invalid. It does not belong in the provider manifest, the task's recording policy
|
|
5598
|
-
or the general `HostGuarantees` object. A provider can inspect this
|
|
5599
|
-
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.
|
|
5600
5605
|
|
|
5601
|
-
The guarantee applies only to
|
|
5602
|
-
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
|
|
5603
5608
|
recordings. Both recorders can operate independently on the same call.
|
|
5604
5609
|
|
|
5605
|
-
On a confirmed
|
|
5610
|
+
On a confirmed agent application start, the remote party hears that recording started. On a confirmed stop or
|
|
5606
5611
|
cancel, they hear that recording stopped. If pause/resume is supported, say paused/resumed so a
|
|
5607
5612
|
pause is not presented as a finished recording. Announcements follow actual capture transitions,
|
|
5608
5613
|
not button clicks, accepted requests or task-policy changes. When attaching to an already-active
|
|
5609
|
-
|
|
5614
|
+
agent application recording, announce that recording is active, without inventing its original start time.
|
|
5610
5615
|
Repeated observations of the same state do not repeat announcements. Unknown or expired evidence
|
|
5611
5616
|
must never produce a stopped announcement.
|
|
5612
5617
|
|
|
5613
5618
|
A local sound, UI indicator, screen-reader message to the agent, or sound leaking through the
|
|
5614
|
-
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
|
|
5615
5620
|
sent to the remote party, even when the agent microphone is muted. It may also notify the agent.
|
|
5616
|
-
Only declare the guarantee when the
|
|
5621
|
+
Only declare the guarantee when the agent application can provide that outgoing audio path. An announcement
|
|
5617
5622
|
that cannot be delivered, including after the remote party has disconnected, is a visible failure;
|
|
5618
5623
|
never claim delivery or replay the notice into a different call.
|
|
5619
5624
|
|
|
@@ -5623,4 +5628,4 @@ report the announcement failure visibly and reject with unknown command outcome.
|
|
|
5623
5628
|
effect, automatically repeat the recording action or silently switch to an agent-only notice.
|
|
5624
5629
|
|
|
5625
5630
|
`validateHostRecording` checks the declaration, including its true-or-absent guarantee. Actual audio
|
|
5626
|
-
delivery remains the
|
|
5631
|
+
delivery remains the agent application implementation's responsibility and must be exercised in its own tests.
|