@xema/omni-protocol 0.1.114 → 0.1.116
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 +25 -19
- package/dist/index.js +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +30 -10
- package/dist/validation.d.ts +4 -4
- package/dist/validation.js +38 -21
- package/guide/agent.md +22 -19
- package/guide/breaks.md +2 -2
- package/guide/lead.md +11 -11
- package/guide/phone.md +36 -27
- package/guide/queue.md +6 -6
- package/guide/voice.md +8 -8
- package/guide.md +52 -49
- package/package.json +1 -1
package/guide.md
CHANGED
|
@@ -20,9 +20,9 @@ every cross-reference names the file it points into.
|
|
|
20
20
|
|
|
21
21
|
| File | |
|
|
22
22
|
| --- | --- |
|
|
23
|
-
| `guide/agent.md` | **The agent's work.** What an agent does on any channel: the assignment from offer to wrap, the task and its record, the controls a task offers, the commands the
|
|
23
|
+
| `guide/agent.md` | **The agent's work.** What an agent does on any channel: the assignment from offer to wrap, the task and its record, the controls a task offers, the commands the agent computer sends. |
|
|
24
24
|
| `guide/voice.md` | **Voice.** What is true of a call and of nothing else: preview, connecting back, ending a call, conference, every dial and its outcome, recording. |
|
|
25
|
-
| `guide/phone.md` | **The phone.** How the agent hears the call: softphone and
|
|
25
|
+
| `guide/phone.md` | **The phone.** How the agent hears the call: softphone and hardphone, the station and its headset, opening the audio, the microphone and the record. |
|
|
26
26
|
| `guide/chat.md` | **Chat.** What is true of a chat and of nothing else. |
|
|
27
27
|
| `guide/email.md` | **Email.** What is true of an email and of nothing else. |
|
|
28
28
|
| `guide/queue.md` | **The agent's own queue.** The next call, lined up for this agent while they finish the one they are on: the ask, the lined-up call, and the release. |
|
|
@@ -44,9 +44,9 @@ are used precisely throughout and mean nothing looser here.
|
|
|
44
44
|
| **Lead** | An agent whose login declares `capabilities.lead`. The flag lets the agent application offer the team feature; the application switches it on with `lead-features` on every connect, and only then does the provider publish the team -- whole on `team-updated`, then one member at a time -- to that login and to nobody else: **the login is the permission**. The lead sees their members with their tasks and stats, and acts on them with the authority the flag carries. |
|
|
45
45
|
| **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. |
|
|
46
46
|
| **Call** | The caller’s complete phone call, which may continue through IVRs, queues and several agents. The protocol never describes the IVR or the queue; it carries the call's history and details to whichever agent holds it now. |
|
|
47
|
-
| **Assignment** | The call routed to this agent, from the offer until the task ends. It is the one thing that crosses: the provider assigns, names the assignment with `assignmentId`, and that is the only name the
|
|
47
|
+
| **Assignment** | The call routed to this agent, from the offer until the task ends. It is the one thing that crosses: the provider assigns, names the assignment with `assignmentId`, and that is the only name the agent computer ever uses back. A call that comes back is a new assignment on the same call, and a declined or lapsed offer is an assignment that became nothing more. What the platform calls its own record, and whether it reuses that name, is the adapter's business and unknown to the agent computer. |
|
|
48
48
|
| **Interaction** | The agent's time on the call, from answer until their audio ends: the `in-progress` and `paused` phases. An assignment produces at most one interaction, and a call that returns produces a new assignment and so a new interaction. |
|
|
49
|
-
| **Task** in `guide/agent.md` | The
|
|
49
|
+
| **Task** in `guide/agent.md` | The agent computer's record of one assignment, from offer through wrap: the workspace, controls and completion work the agent has for it, described by the provider and held by the agent computer. A task is identified by the assignment it is the record of. `pending`, `confirmed` and `preview` are assigned and not yet interacting; `in-progress` and `paused` are the interaction; `completing` is the assignment outliving its interaction. A voice task is one assignment, never the caller's whole call. **Task is the word on every channel**: voice, chat and email each hand an agent the same unit, offered, worked and wrapped under one assignment, and chat and email have no call. So a channel platform's own name for one agent's part of a contact -- an agent call on a phone platform -- is a task here, and the platform's record of the whole contact across agents and callbacks is the backend's, outside this protocol. |
|
|
50
50
|
| **Channel** | The kind of work a provider carries: `voice`, `chat`, or `email`. Fixed per provider by its manifest. |
|
|
51
51
|
| **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. |
|
|
52
52
|
| **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** in `guide/agent.md`. |
|
|
@@ -59,7 +59,7 @@ are used precisely throughout and mean nothing looser here.
|
|
|
59
59
|
| **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. |
|
|
60
60
|
| **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. |
|
|
61
61
|
| **Dial** | One outbound call the agent application asks a provider to place — from the idle dialpad, a preview's Call, a conference add, or a connect-back. Identified by the agent application's `dialId`, accepted as `dialling`, and ended by exactly one `dial-outcome`. See **Every dial has an outcome** in `guide/voice.md`. |
|
|
62
|
-
| **Listen** | A lead's channel in a member's call, unasked, from the team member list: `listen` in silence, `coach` heard by the agent alone, `join-call` heard by everyone. It is a team act naming the member, never a task on the lead's
|
|
62
|
+
| **Listen** | A lead's channel in a member's call, unasked, from the team member list: `listen` in silence, `coach` heard by the agent alone, `join-call` heard by everyone. It is a team act naming the member, never a task on the lead's computer; `listen` and `coach` leave no trace on the member's task, while a lead heard by everyone is on its `onCall` as an `agent`. Every lead sees every lead on the call. A lead who wants the call takes it over. See **Listening to a call** in `guide/lead.md`. |
|
|
63
63
|
| **On the call** | Who a voice task's audio joins, or is bringing in, as the provider states it on `Task.onCall`: the party, the agents, and anyone conferenced in from the moment their dial is placed. |
|
|
64
64
|
|
|
65
65
|
These fields describe state within their containing objects. Both authentication and break
|
|
@@ -198,7 +198,7 @@ type DialCapability = { destinations: DialDestinations };
|
|
|
198
198
|
|
|
199
199
|
type DialOutcome = "answered" | "busy" | "no-answer" | "unreachable" | "rejected" | "cancelled" | "unexplained";
|
|
200
200
|
|
|
201
|
-
type Phone = "softphone" | "
|
|
201
|
+
type Phone = "softphone" | "hardphone";
|
|
202
202
|
|
|
203
203
|
type PhoneStatus = "ready" | "unregistered" | "do-not-disturb" | "off-hook";
|
|
204
204
|
|
|
@@ -384,6 +384,7 @@ type HostReport = {
|
|
|
384
384
|
type HostGuarantees = {
|
|
385
385
|
browserUrlVisibility?: true;
|
|
386
386
|
personConsent?: true;
|
|
387
|
+
muteUnavailableOnHold?: true;
|
|
387
388
|
};
|
|
388
389
|
|
|
389
390
|
type ProviderTimeScope = { providerId: string; loginId: string };
|
|
@@ -420,7 +421,7 @@ type ConnectContext = {
|
|
|
420
421
|
log?: (entry: unknown) => void;
|
|
421
422
|
} & (
|
|
422
423
|
| { phone: "softphone"; host: { mute: HostMute } }
|
|
423
|
-
| { phone?: "
|
|
424
|
+
| { phone?: "hardphone"; host: { mute?: never; recording?: never } }
|
|
424
425
|
);
|
|
425
426
|
|
|
426
427
|
type TransportStatus = "connecting" | "active" | "error";
|
|
@@ -875,7 +876,7 @@ type TaskCommandResult =
|
|
|
875
876
|
|
|
876
877
|
### Who decides what an agent may do
|
|
877
878
|
|
|
878
|
-
An agent
|
|
879
|
+
An agent agent computer has two managers, not one. **The queue** — a process, a work type — is owned by a
|
|
879
880
|
process manager and **allows** a set of capabilities: hold, connect back, dial, conference,
|
|
880
881
|
whether the number is visible, the actions it offers, the skills it needs. **The people** are
|
|
881
882
|
managed through the organisation's structure — a team, a location, the organisation itself, in
|
|
@@ -890,7 +891,7 @@ the person settles the value for everyone below it, and where nothing enforces t
|
|
|
890
891
|
level that says anything wins. The protocol names a level by the id the manifest declares for it and
|
|
891
892
|
never describes the chain between them: which levels a person passes through is the structure's to
|
|
892
893
|
know. A typical organisation has four, and they are the defaults — `DEFAULT_LEVELS`: `org`, `site`,
|
|
893
|
-
`team`, `person`, each with the label
|
|
894
|
+
`team`, `person`, each with the label an agent computer shows. A structure that differs states its whole
|
|
894
895
|
ladder in `Manifest.orgLevels`, `person` included: what the list carries is in force, and what it
|
|
895
896
|
leaves out does not exist — a structure with no site level declares `org`, `team`, `person`, and
|
|
896
897
|
`site` is refused on its wire. A declared level is one the provider's own store actually resolves
|
|
@@ -916,10 +917,10 @@ to ask their lead or their site. `lockedBy` is the discriminant: a value that ca
|
|
|
916
917
|
lock, so nothing that can be locked — a directory, a number — may carry that key itself. A
|
|
917
918
|
contact's number and email are the same, since each identifies a person: where the queue says
|
|
918
919
|
the agent may not see it, the provider sends `{ lockedBy }` in its place — the last digits or
|
|
919
|
-
nothing — and a CRM link carries a token, never the value with a flag the
|
|
920
|
+
nothing — and a CRM link carries a token, never the value with a flag the agent computer is asked to honour.
|
|
920
921
|
**What the queue locks is locked on the whole task.** A number the agent may not see appears
|
|
921
922
|
nowhere else they read — not in the title, the reference, an attribute, a custom control's label,
|
|
922
|
-
or a browser URL the
|
|
923
|
+
or a browser URL the agent computer shows; a browser whose URL must carry it says `urlVisibility: "hidden"`.
|
|
923
924
|
The adapter is the one that knows the value, so it is the one held to it: given the values the
|
|
924
925
|
queue locked, the validator refuses any other field carrying one, digits compared as digits so no
|
|
925
926
|
formatting hides them (`task.locked.leak`), and a conformance run whose tasks lock a party's number
|
|
@@ -927,7 +928,7 @@ or email states those values in `lockedValues`, since a run that cannot ask the
|
|
|
927
928
|
pass. An agent application never has the value and never asks. A name is not locked.
|
|
928
929
|
|
|
929
930
|
**The lock is per audience.** Who sees the number is the provider's choice for each copy of the
|
|
930
|
-
task: the member's, on their
|
|
931
|
+
task: the member's, on their agent computer, and the leads', on the team member list. Hidden from the agent
|
|
931
932
|
and shown to the lead, shown to the agent and hidden from the lead, hidden from both, shown to
|
|
932
933
|
both -- any of the four, as the platform's policy says, and each copy is the provider's statement
|
|
933
934
|
to that audience. Each copy keeps its own lock whole: where the lead's copy locks the number,
|
|
@@ -1709,11 +1710,11 @@ compile time.
|
|
|
1709
1710
|
| `idleCapabilities` | Declares actions Omni may offer while the agent has no active task, such as voice dialing. Task controls do not belong here. |
|
|
1710
1711
|
| `phaseLabels` | Optional static adapter-defined display names for canonical `TaskPhase` values. They cannot vary at runtime. |
|
|
1711
1712
|
| `taskTypePresentation` | Optional static adapter-defined presentation keyed by exact `taskType`. It names the item and its optional agent-facing reference. |
|
|
1712
|
-
| `orgLevels` | The organisation's whole ladder as the provider calls it, each level with the label
|
|
1713
|
-
| `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 `
|
|
1713
|
+
| `orgLevels` | The organisation's whole ladder as the provider calls it, each level with the label an agent computer 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**. |
|
|
1714
|
+
| `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 `hardphone` (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** in `guide/phone.md`. |
|
|
1714
1715
|
| `phoneStatus` | Voice only, declared by presence as `true`: the platform sees the phone itself -- its registration, its do-not-disturb, its hook, its calls -- and publishes it as `Snapshot.phone` and `phone-updated`. A platform that cannot see the phone omits it, and then publishes no phone (`manifest.phoneStatus`). See **The phone's own view** in `guide/phone.md`. |
|
|
1715
1716
|
| `dialOutcomes` | Voice only. How a dial can end on this platform, as it distinguishes them: `answered` and at least one way of not reaching the destination. Required of a provider that dials at all — an idle dialpad, or tasks that conference or call back — and a `dial-outcome` carries only a declared member. See **Every dial has an outcome** in `guide/voice.md`. |
|
|
1716
|
-
| `timeCheck` | `true`: implements `checkTime` and states `providerTime` on every snapshot. Required of a provider that publishes an instant the
|
|
1717
|
+
| `timeCheck` | `true`: implements `checkTime` and states `providerTime` on every snapshot. Required of a provider that publishes an instant the agent computer renders as a running duration -- a member's `since`, a `listening[].since`, an `onCall.since`, a shift's `signedInAt` -- so every screen counts from the provider's clock (`manifest.timeCheck.required`); agent application polling is independently opt-in. See **Every screen counts from the provider's clock**. |
|
|
1717
1718
|
| `timestampAuthority` | Optional `"provider"`: provider timestamps are final; agent application instants are advisory. Omission makes no trust promise. |
|
|
1718
1719
|
| `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** in `guide/agent.md`. |
|
|
1719
1720
|
| `settleMs` | Required. How long after `applied` the wire shows what the provider did: the `task-ended` after a `complete`, the `calendar-updated` after a `schedule`. A positive whole number of milliseconds (`manifest.settleMs`). Stated per provider, since platforms settle at different speeds. See **`task-ended`**. |
|
|
@@ -2158,9 +2159,9 @@ Creates one live provider connection for the signed-in agent.
|
|
|
2158
2159
|
- May reject for authentication, configuration, or startup failure.
|
|
2159
2160
|
- Must not create a second agent session merely because the underlying transport reconnects.
|
|
2160
2161
|
- **One client at a time per login, and the later one wins.** A second `connect()` on a login
|
|
2161
|
-
that already has a live client is the agent moving
|
|
2162
|
+
that already has a live client is the agent moving agent computers -- an agent computer left open at work, signed in
|
|
2162
2163
|
from home. The provider takes the new connection, and ends the first with `transport-status`
|
|
2163
|
-
`error` and `recovery: "displaced"`, so the first
|
|
2164
|
+
`error` and `recovery: "displaced"`, so the first agent computer stops stating capacity and reporting legs
|
|
2164
2165
|
and shows the agent they are signed in elsewhere. Two live clients on one login would be two
|
|
2165
2166
|
sources for one agent, and the lead's list would follow whichever spoke last.
|
|
2166
2167
|
- The returned connection owns reconnect until Omni calls `disconnect()` or aborts `context.signal`.
|
|
@@ -2179,7 +2180,7 @@ Creates one live provider connection for the signed-in agent.
|
|
|
2179
2180
|
| `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. |
|
|
2180
2181
|
| `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()`. |
|
|
2181
2182
|
| `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**. |
|
|
2182
|
-
| `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
|
|
2183
|
+
| `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 hardphone 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** in `guide/phone.md`. |
|
|
2183
2184
|
| `store` | The login's operational store, kept by the agent application for the life of the login, across a reload of the agent application, and cleared at sign-out: where an adapter that composes a record keeps what its platform cannot hold for it, such as the interaction legs an agent application reported. Three functions, by key. Never for anything sensitive, which is `AuthenticationContext.secrets`, a store an agent application may clear aggressively. **A task's keys carry its assignment id and go with the task**: a key written about a task names the assignment id in the key, and is deleted before the task's end is published, so that nothing of a closed task survives its ending and nothing is left for whatever comes next -- a record with legs the agent application never reported against it. A login-scoped key carries no assignment id and outlives any task. The harness requires the store of every connection (`store.shape`, `store.get`, `.set`, `.delete`), watches the one it hands over, and names a task's key still held after `task-ended` (`test.store.retained`) or written about the task after its end -- a persist hung off a timer that saw the task as it was (`test.store.late`). The ordinary late write is the agent application's, not the adapter's: an agent application reports a leg without waiting for the answer, so an unmute can follow `complete` by a tick, and the adapter answers a report about a task that has ended `failed` with `omni.assignment-not-found` and writes nothing -- the agent application ends its own open legs at the task's end, so such a report is the agent application's error to see, and an agent application reads every `recordStep` answer and awaits the one for the leg it closes at a task's end, the only moment a refusal is expected, since a report nobody waits for is an error nobody can see; an adapter that names no task in its keys gets no cleanup check, which is a gap rather than a pass, never an exemption: the obligation is that nothing of a closed task survives its ending, and an adapter that keeps every open task in one login-scoped value owes exactly that inside the value, where the harness cannot look. One key per task, named for it, is the shape the harness can hold, and the shape to reach for. The store lists nothing, so an adapter that needs to find its tasks keeps a login-scoped index of ids beside them; at the task's end it deletes the body first and reindexes after, since a crash between the two then leaves an index naming a task with no body, which a reader skips, where the other order leaves a body for a task that has ended, which is the hazard itself. A reader of the index tolerates an id with no body as an ending that was underway, not as corruption. |
|
|
2184
2185
|
| `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** in `guide/phone.md`. |
|
|
2185
2186
|
| `signal` | Optional cancellation signal. Stop startup promptly when aborted and do not begin new work. |
|
|
@@ -2188,7 +2189,7 @@ Creates one live provider connection for the signed-in agent.
|
|
|
2188
2189
|
### The agent's day
|
|
2189
2190
|
|
|
2190
2191
|
Every instant on this wire carries an explicit offset, so a moment is unambiguous everywhere and
|
|
2191
|
-
|
|
2192
|
+
an agent computer renders it in the viewer's clock without help. A **day** is different: hours toward
|
|
2192
2193
|
target, an answer streak, a per-queue count for today -- anything bucketed by day -- is bucketed
|
|
2193
2194
|
by somebody's day, and a platform that was never told whose uses its own. An agent in Chennai
|
|
2194
2195
|
then finds their day rolling at 05:30, and a night shift in Chicago lands in two buckets.
|
|
@@ -2236,7 +2237,7 @@ a capability it agrees with the login: a lead's snapshot carries `team`, nobody
|
|
|
2236
2237
|
| --- | --- |
|
|
2237
2238
|
| `transport` | Current `TransportStatus` — whether this provider's transport can serve the login. Defined under **`transport-status`**. |
|
|
2238
2239
|
| `loginId` | Identity of this login. It must match the connection context. |
|
|
2239
|
-
| `providerTime` | The provider's own instant of this read. Required of a provider that declares `timeCheck` (`snapshot.providerTime`), forbidden of one that does not (`snapshot.providerTime.unexpected`): a clock sample the
|
|
2240
|
+
| `providerTime` | The provider's own instant of this read. Required of a provider that declares `timeCheck` (`snapshot.providerTime`), forbidden of one that does not (`snapshot.providerTime.unexpected`): a clock sample the agent computer counts durations from, saving it a `checkTime`. |
|
|
2240
2241
|
| `break` | Complete break state, including status, whether the agent may ask, reasons, retry details, and any forced break. |
|
|
2241
2242
|
| `tasks` | Complete set of tasks currently offered to or owned by this agent. |
|
|
2242
2243
|
| `taskCount` | The provider's own count of those tasks, stated rather than inferred, and it must equal `tasks.length`. A snapshot with no work says `taskCount: 0` in so many words — a blank or unanswered state lacks the count and cannot pass as a confirmed empty. |
|
|
@@ -2272,8 +2273,8 @@ surface in one place, and what obliges an adapter to implement each one.
|
|
|
2272
2273
|
| `requestNextCall()`, `cancelNextCall()`, `releaseLinedUp()` | The login declares `capabilities.nextCall`. The three stand together: a lined-up call the agent could neither withdraw nor let go is a promise with no way out. See **The agent's own queue** in `guide/queue.md`. |
|
|
2273
2274
|
| `executeTeam(request)` | The login declares `capabilities.lead`: every lead act, on the team surface and nowhere else. See **Lead commands** in `guide/lead.md`. |
|
|
2274
2275
|
| `setPreference(request)` | The login declares `capabilities.preferences`: the person's choice has to have somewhere to go. |
|
|
2275
|
-
| `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
|
|
2276
|
-
| `openAudio(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
|
|
2276
|
+
| `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 hardphone's microphone is the phone's. See **The agent application records what it performs** in `guide/agent.md`. |
|
|
2277
|
+
| `openAudio(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 hardphones alone never does. |
|
|
2277
2278
|
|
|
2278
2279
|
**The four break methods stand or fall together.** Declaring `capabilities.breaks` at login and then
|
|
2279
2280
|
implementing `requestBreak` without `commitBreak` leaves an agent granted a break that can never
|
|
@@ -2376,8 +2377,8 @@ The current exceptions and their reasons are:
|
|
|
2376
2377
|
|
|
2377
2378
|
| Case | Exception and reason |
|
|
2378
2379
|
| --- | --- |
|
|
2379
|
-
| Agent application display of provider deadlines (`expiresInSeconds`, `previewEndsInSeconds`, `wrapEndsInSeconds`) | Each is seconds from the publication that carried it, counted down on the
|
|
2380
|
-
| Agent application-triggered preview end | The
|
|
2380
|
+
| Agent application display of provider deadlines (`expiresInSeconds`, `previewEndsInSeconds`, `wrapEndsInSeconds`) | Each is seconds from the publication that carried it, counted down on the agent computer from receipt: the provider's own arithmetic, so no clock is compared and the transport delay is the only error. The countdown estimates the display only; it does not establish that the provider acted. Without usable time, show timing uncertainty. |
|
|
2381
|
+
| Agent application-triggered preview end | The agent computer counts `previewEndsInSeconds` down from receipt and issues `dial` when it reaches zero: the provider owns the deadline and the agent computer owns the trigger. It does not dial before the countdown has run out, and a reload restarts it from the next publication. |
|
|
2381
2382
|
| 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. |
|
|
2382
2383
|
| 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. |
|
|
2383
2384
|
| 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. |
|
|
@@ -2392,24 +2393,24 @@ source-measured durations where required; timestamp subtraction is not a substit
|
|
|
2392
2393
|
|
|
2393
2394
|
#### Every screen counts from the provider's clock
|
|
2394
2395
|
|
|
2395
|
-
Two screens watching the same agent -- their own
|
|
2396
|
+
Two screens watching the same agent -- their own computer, their lead's, a manager's on another floor
|
|
2396
2397
|
-- must show the same numbers. Delay between them is fine; arithmetic that differs is not. So
|
|
2397
|
-
nothing the
|
|
2398
|
+
nothing the agent computer shows is its own count, with one exception: the running duration of the active
|
|
2398
2399
|
task or call, which a screen times itself from the moment it saw the call begin -- the
|
|
2399
|
-
`task-audio-started` on a softphone, the answer it sent on a
|
|
2400
|
+
`task-audio-started` on a softphone, the answer it sent on a hardphone -- and which two such
|
|
2400
2401
|
screens may therefore disagree on, since their clocks differ. That is the whole exception. A call
|
|
2401
2402
|
a screen found already up -- on a connect or reconnect snapshot, on a lead's list, after a reload
|
|
2402
2403
|
-- is counted from the provider's `answered` step's `at` against the provider's clock, like every
|
|
2403
|
-
other duration, so a reloaded
|
|
2404
|
+
other duration, so a reloaded agent computer shows 12:40 beside the lead's 12:40 and not 0:05. And a leg
|
|
2404
2405
|
that has closed shows the provider's `seconds` on every screen, never the count a screen kept.
|
|
2405
2406
|
Everything else comes from the wire.
|
|
2406
2407
|
|
|
2407
2408
|
Durations off the active call -- how long a member has been `on-break` from their `since`, how
|
|
2408
2409
|
long a lead has been listening, how long since `signedInAt` -- are counted from the provider's
|
|
2409
|
-
clock, never the screen's. The
|
|
2410
|
+
clock, never the screen's. The agent computer keeps the latest provider instant it has received -- a
|
|
2410
2411
|
snapshot's `providerTime`, an envelope's `occurredAt`, a `checkTime` result -- with the monotonic
|
|
2411
2412
|
elapsed time since receipt, and renders every such duration from that; a sample older than
|
|
2412
|
-
`maxSampleAgeMs` is refreshed with `checkTime`. Without a usable sample the
|
|
2413
|
+
`maxSampleAgeMs` is refreshed with `checkTime`. Without a usable sample the agent computer shows the instant
|
|
2413
2414
|
and no count. A provider that publishes any of these instants therefore declares `timeCheck`
|
|
2414
2415
|
(`manifest.timeCheck.required`) and states `providerTime` on every snapshot
|
|
2415
2416
|
(`snapshot.providerTime`). Two screens then differ by their delay and nothing else.
|
|
@@ -2417,7 +2418,7 @@ and no count. A provider that publishes any of these instants therefore declares
|
|
|
2417
2418
|
**Seconds left are worked out at each publication.** `expiresInSeconds`, `previewEndsInSeconds`,
|
|
2418
2419
|
`wrapEndsInSeconds` and `expectedEndsInSeconds` are each the provider's arithmetic at the instant
|
|
2419
2420
|
of the publication that carries them, never a value copied from an earlier one. A provider that
|
|
2420
|
-
builds the next publication by spreading the last one forward hands a reloaded
|
|
2421
|
+
builds the next publication by spreading the last one forward hands a reloaded agent computer a countdown
|
|
2421
2422
|
already spent -- sixty seconds shown beside a lead's screen showing twenty -- and the stream
|
|
2422
2423
|
names it: between two publications of one countdown the value loses at least the seconds the
|
|
2423
2424
|
provider's own clock says passed, within a second of rounding (`stream.countdown.copied`, on an
|
|
@@ -2431,7 +2432,7 @@ overruns; that is the true value, not a copy.
|
|
|
2431
2432
|
the `expired` ending within the seconds plus `settleMs`, then tests the next offer
|
|
2432
2433
|
(`test.offer.expired`). A preview's deadline is held to its `atDeadline` the same way: under
|
|
2433
2434
|
`provider-dials` the provider dials when it runs out, under `host-dials` and `waits` the preview
|
|
2434
|
-
stands until the
|
|
2435
|
+
stands until the agent computer dials (`test.preview.deadline`).
|
|
2435
2436
|
|
|
2436
2437
|
#### Optional periodic provider time checks and agent application estimates
|
|
2437
2438
|
|
|
@@ -2570,9 +2571,9 @@ died; the agent application knows how to run a login. `recovery` joins the two:
|
|
|
2570
2571
|
disposes the errored connection with `disconnect()` and calls `connect()` afresh, as for
|
|
2571
2572
|
`reconnect`, and the fresh snapshot re-establishes state. The adapter tears nothing down itself
|
|
2572
2573
|
and expects nothing to carry on: the connection that reported `error` is finished.
|
|
2573
|
-
- **`displaced`** — another client connected on this login and took it: the agent moved
|
|
2574
|
+
- **`displaced`** — another client connected on this login and took it: the agent moved agent computers.
|
|
2574
2575
|
Nothing revives this connection on its own -- Omni does not reconnect, or it would take the
|
|
2575
|
-
login straight back from the
|
|
2576
|
+
login straight back from the agent computer the agent just sat down at -- and the agent computer shows the agent
|
|
2576
2577
|
they are signed in elsewhere, with a way to take the login back, which is a `connect()` that
|
|
2577
2578
|
displaces the other. See **`Adapter.connect(context)`**.
|
|
2578
2579
|
|
|
@@ -2594,8 +2595,8 @@ come — see **Liveness**.
|
|
|
2594
2595
|
|
|
2595
2596
|
**The status is seen, not merely known.** An agent application renders the transport's status where the agent
|
|
2596
2597
|
works — `connecting` from the moment it is reported, `error` with what revives it — and never a
|
|
2597
|
-
healthy workspace over a transport that cannot serve it: an agent talking on
|
|
2598
|
-
died was an incident, and it happened because the
|
|
2598
|
+
healthy workspace over a transport that cannot serve it: an agent talking on an agent computer whose transport
|
|
2599
|
+
died was an incident, and it happened because the agent computer knew and did not say. The same obligation
|
|
2599
2600
|
covers a `diagnostic`: shown where the agent works, and counted.
|
|
2600
2601
|
|
|
2601
2602
|
### `break-state`
|
|
@@ -2675,7 +2676,7 @@ snapshots until it ends.
|
|
|
2675
2676
|
**Every offer is owed an ending.** An assignment the provider introduced is an assignment it ends,
|
|
2676
2677
|
with `task-ended` and an outcome, whatever became of the call: answered and completed, declined,
|
|
2677
2678
|
withdrawn, abandoned in the ring, lapsed, taken over. An offer that is simply never mentioned
|
|
2678
|
-
again leaves the
|
|
2679
|
+
again leaves the agent computer holding a task nobody will close, and the test names it
|
|
2679
2680
|
(`stream.taskOffered.unended`).
|
|
2680
2681
|
|
|
2681
2682
|
### `task-updated`
|
|
@@ -2693,7 +2694,7 @@ platform, a station going in use the moment a call is answered, sends it then, b
|
|
|
2693
2694
|
opened anything, and `openAudio` has its own answer for what the agent application did. It
|
|
2694
2695
|
names a task whose work has begun, and it alternates with `task-audio-ended`: audio that never
|
|
2695
2696
|
started cannot end, so a live call whose provider says nothing about its audio is a provider in
|
|
2696
|
-
breach, not a state
|
|
2697
|
+
breach, not a state an agent computer fills in from its own devices.
|
|
2697
2698
|
|
|
2698
2699
|
The event is the transition and the task's `audio` field is the state. A `task-updated` re-states
|
|
2699
2700
|
the audio its task already holds — republishing `started` on a hold is a statement, not a second
|
|
@@ -2728,7 +2729,7 @@ and not for ever: `applied` to a `complete` says the
|
|
|
2728
2729
|
provider has completed the task, and its `task-ended` follows within the
|
|
2729
2730
|
manifest's `settleMs`. A provider never answers `applied` for a completion it has not yet
|
|
2730
2731
|
performed. Past the bound the agent application calls `snapshot()`: a snapshot still carrying the task is a task
|
|
2731
|
-
held open by a provider that said it was done, and the
|
|
2732
|
+
held open by a provider that said it was done, and the agent computer shows it as unsettled -- "Completing...
|
|
2732
2733
|
the provider has not confirmed" -- naming the command; a snapshot no longer carrying it clears the
|
|
2733
2734
|
task, since the ending was owed and lost. The test holds a provider to the same bound
|
|
2734
2735
|
(`test.completion.unsettled`). The `task-audio-ended` event and the `completing` phase are likewise
|
|
@@ -2841,7 +2842,7 @@ agent's own queue** in `guide/queue.md`.
|
|
|
2841
2842
|
### `phone-updated`
|
|
2842
2843
|
|
|
2843
2844
|
The phone as the platform now sees it, whole, replacing `Snapshot.phone`: a registration lost or
|
|
2844
|
-
back, do-not-disturb pressed or cleared, the handset lifted with no call or replaced, the
|
|
2845
|
+
back, do-not-disturb pressed or cleared, the handset lifted with no call or replaced, the agent computer
|
|
2845
2846
|
phone's own mute. Emitted only by a provider whose manifest declares `phoneStatus`
|
|
2846
2847
|
(`event.phone.capability`). The lead's member carries the same `PhoneState`, republished with the
|
|
2847
2848
|
member. See **The phone's own view** in `guide/phone.md`.
|
|
@@ -2891,12 +2892,12 @@ same exported checks are used by Omni and adapter tests so their interpretations
|
|
|
2891
2892
|
| `validateEventEnvelope(envelope, manifest)` | Envelope identity, timestamp, and the payload for each event type. |
|
|
2892
2893
|
| `validateContact(contact)` | Contact field shapes and attribute keys. Every field is optional, so this checks what is present rather than what is missing. |
|
|
2893
2894
|
| `validateScheduledActivity(activity)` | Required activity fields and start/end ordering. |
|
|
2894
|
-
| `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. |
|
|
2895
|
+
| `validateHostGuarantees(guarantees, path?, phone?)` | What an agent application promises: only the guarantees this contract names, each declared by presence and never `false`. Given the login's phone, the mute promise is held to it: `muteUnavailableOnHold` required of a softphone login's agent application and refused of any other (`host.guarantee.muteUnavailableOnHold.required` / `.unexpected`). The harness validates the guarantees of whatever agent application a test hands the adapter, against the phone it chose. |
|
|
2895
2896
|
| `validateProviderTimeCheckPolicy(policy)` | Explicit optional polling settings, positive safe-integer durations and round-trip/timeout/interval ordering. |
|
|
2896
2897
|
| `validateProviderTimeCheckRequest(request)` | Fresh-request shape; the agent application enforces actual uniqueness and outstanding-request lifetime. |
|
|
2897
2898
|
| `validateProviderTimeCheckResult(result, request, loginId)` | ISO timestamp, clock identity, exact request/login correlation; timing and source accuracy remain runtime checks. |
|
|
2898
2899
|
| `validateProviderTimeEstimate(estimate, scope)` | Optional agent application estimate shape and provider/login scope; no accuracy guarantee. |
|
|
2899
|
-
| `validateHistoryReport(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`. |
|
|
2900
|
+
| `validateHistoryReport(report, path?, manifest?, task?)` | 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`. Given the task, the agent application's own mute beginning on a `paused` task is refused (`historyReport.muted.held`): the Mute waits for resume. |
|
|
2900
2901
|
| `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. |
|
|
2901
2902
|
| `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. |
|
|
2902
2903
|
| `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. |
|
|
@@ -2918,7 +2919,7 @@ reports `team.unentitled`, and a team event to such a login reports `event.team.
|
|
|
2918
2919
|
`leadFeatures: true`, a snapshot without a team reports `team.required`, and given `leadFeatures: false`,
|
|
2919
2920
|
anything of the team reaching the lead reports `team.unexpected` or `event.team.features`. Without them those rules are not checked, because they cannot be.
|
|
2920
2921
|
`testAdapter` passes all three, holding the switch off until it has sent it. Given the manifest, a
|
|
2921
|
-
snapshot or event that carries an instant the
|
|
2922
|
+
snapshot or event that carries an instant the agent computer renders as a running duration under a manifest
|
|
2922
2923
|
without `timeCheck` reports `manifest.timeCheck.required`.
|
|
2923
2924
|
|
|
2924
2925
|
`assertNoViolations(violations)` throws `ProtocolConformanceError` — which carries the full
|
|
@@ -2931,22 +2932,22 @@ reaching the workspace.
|
|
|
2931
2932
|
|
|
2932
2933
|
### What the agent application does with what it refuses
|
|
2933
2934
|
|
|
2934
|
-
A refusal has an aftermath on the
|
|
2935
|
+
A refusal has an aftermath on the agent computer and a report to the provider, and both are stated.
|
|
2935
2936
|
|
|
2936
2937
|
- **A refused snapshot replaces nothing.** The agent application keeps the last state it took from this
|
|
2937
2938
|
provider whole, and shows the provider as faulted -- the transport as it stands, and the words
|
|
2938
2939
|
"last update refused" with the rule -- so the agent knows the view is standing still rather than
|
|
2939
2940
|
believing it current. It does not adopt the good half of a snapshot: a task the agent is on would
|
|
2940
|
-
vanish from the
|
|
2941
|
+
vanish from the agent computer while the call is up.
|
|
2941
2942
|
- **A refused event is dropped and counted.** The state the agent application holds does not move for it. The
|
|
2942
2943
|
provider is told, and may republish a corrected state or raise a `diagnostic`; the agent application retries
|
|
2943
|
-
nothing. The
|
|
2944
|
+
nothing. The agent computer shows nothing for it: the view kept moving for every other event, so "last
|
|
2944
2945
|
update refused" would be false of it, and a chip that said nothing would be right about the
|
|
2945
2946
|
transport. Counted, and the provider told, is all an agent application can truthfully do for one event.
|
|
2946
2947
|
- **The provider is told, every time.** `refused(report)` carries the artefact, the envelope id for
|
|
2947
2948
|
an event, and every violation with its rule and path. An adapter logs it at error and treats it as
|
|
2948
2949
|
its own defect until shown otherwise: what the agent application refused never reached the agent, and a
|
|
2949
|
-
provider that hears nothing runs a whole shift beside a frozen
|
|
2950
|
+
provider that hears nothing runs a whole shift beside a frozen agent computer with nothing wrong on its
|
|
2950
2951
|
side. The harness tells an adapter under test exactly as an agent application does
|
|
2951
2952
|
(`connection.refused.required`, `connection.refused.rejected`).
|
|
2952
2953
|
|
|
@@ -2988,7 +2989,7 @@ no tasks exercises no task rule, and a pass over it reads as coverage it is not.
|
|
|
2988
2989
|
**A static run never answers a call.** It states a capacity and disconnects, so everything
|
|
2989
2990
|
downstream of `answer` -- the room, the stage, audio, every phase past `pending`, every dial
|
|
2990
2991
|
outcome -- stays in `notTested` for every adapter, and a rule about a live call is enforced only
|
|
2991
|
-
in each adopter's own tests. `{ withCall: true }` closes that: the
|
|
2992
|
+
in each adopter's own tests. `{ withCall: true }` closes that: the test takes the first task the
|
|
2992
2993
|
provider offers through one ordinary lifecycle -- accept it, wait for its audio and open it on a
|
|
2993
2994
|
softphone, hold and resume where the task offers `hold`, end the call where it offers `endCall`,
|
|
2994
2995
|
complete it with an outcome where the agent completes -- and holds every step to the rules a
|
|
@@ -3014,6 +3015,8 @@ as each case was considered, so a test that needs a rule to have run asserts it
|
|
|
3014
3015
|
inferring it from an empty `violations`, and a rule absent from it was never looked at, which is a
|
|
3015
3016
|
gap and not a pass. With the audio open on a softphone,
|
|
3016
3017
|
the test mutes it for one second and reports the leg through `recordStep`, begun and then ended,
|
|
3018
|
+
and with the task on hold sends a mute begun there past the validator and expects `failed`, naming an
|
|
3019
|
+
adapter that records it (`test.recordStep.held`);
|
|
3017
3020
|
expecting each report `recorded` with the provider-selected history `at` (`test.recordStep.failed`, `.rejected`, `result.recordStep.at`); then it mutes again and
|
|
3018
3021
|
ends the call muted, as agents do, so the leg is open when the audio ends, the provider closes it
|
|
3019
3022
|
in the completing publication or the open entry is refused (`task.history.muted.open`),
|
|
@@ -3101,7 +3104,7 @@ cannot be established from TypeScript structure alone.
|
|
|
3101
3104
|
| `assertReached(result, subjects)` | The exercise met every subject named; throws listing those it did not. Pair it with a clean `testAdapter` result. |
|
|
3102
3105
|
| `assertAuthenticationRestoreAndExpiry(states)` | A restored authenticated session can refresh and ends in expiry. Every state is validated. |
|
|
3103
3106
|
| `assertReconnectWithMissedAssignments(before, reconnect, ids)` | A reconnect snapshot restores assignments received while offline. |
|
|
3104
|
-
| `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
|
|
3107
|
+
| `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 hardphone. |
|
|
3105
3108
|
| `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. |
|
|
3106
3109
|
| `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. |
|
|
3107
3110
|
| `assertAudioFollowsTheTask(envelopes, snapshot?)` | The audio follows the task and never decides it: every task is introduced once, `task-audio-started` and `task-audio-ended` alternate on work that has begun, audio ends only where it arrived, and what follows the audio ending is `completing` or `task-ended`. The harness applies the same rules to every event after the connect snapshot (`stream.*`). A sequence with no audio satisfies it by never testing it — pair it with the assertion that the audio end is present. |
|