@xema/omni-protocol 0.1.114 → 0.1.115
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/agent.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# `@xema/omni-protocol`: The agent's work
|
|
2
2
|
|
|
3
|
-
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
|
|
3
|
+
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. Part of the contract in `guide.md`, which holds the terms, the shapes and the rules every file here relies on; a reference in bold names the file it points into where that is not this one.
|
|
4
4
|
|
|
5
5
|
## Task assignment lifecycle
|
|
6
6
|
|
|
@@ -66,7 +66,7 @@ agent's own act — a dialpad call or a connect-back, recognisable by the agent
|
|
|
66
66
|
`onCall`; a call a lead took over, carrying `takenOver` — arrives
|
|
67
67
|
through `task-offered` with `acceptance: "automatic"` whatever `autoAcceptTasks` says, and the
|
|
68
68
|
validator holds it there under either local policy and under none (`task.acceptance.originated`):
|
|
69
|
-
the
|
|
69
|
+
the agent computer shows no **Accept** for a call the agent placed. The local policy governs work the queue
|
|
70
70
|
routes to the agent, and nothing else.
|
|
71
71
|
|
|
72
72
|
### Pending
|
|
@@ -95,7 +95,7 @@ medium the task arrives on, not a separate decision.
|
|
|
95
95
|
**Once a task is accepted, the call that comes with it is answered.** Omni has no discretion
|
|
96
96
|
there and the provider is not consulted twice: one decision about the work, and the medium
|
|
97
97
|
follows it. Where the audio lands was settled at sign-in — in Omni on a softphone, on the handset
|
|
98
|
-
on a
|
|
98
|
+
on a hardphone — and is not in question per call. See **How the agent hears the call** in `guide/phone.md`.
|
|
99
99
|
|
|
100
100
|
Automatic acceptance still begins with `task-offered`.
|
|
101
101
|
|
|
@@ -111,7 +111,7 @@ Omni withdraw **Accept** from a task still pending.
|
|
|
111
111
|
**A deadline is seconds left, never an instant.** The agent application and the provider keep
|
|
112
112
|
different clocks, and a countdown that compares one against the other is a countdown that runs
|
|
113
113
|
fast or slow by their skew. Seconds from the publication that carries them are the provider's own
|
|
114
|
-
arithmetic, counted down on the
|
|
114
|
+
arithmetic, counted down on the agent computer from the moment they arrive, and the transport delay is the
|
|
115
115
|
only error. Restated on every publication that carries them, so a snapshot after a reconnect
|
|
116
116
|
starts the countdown afresh from what is actually left (`task.pending.expiresInSeconds`,
|
|
117
117
|
`task.preview.previewEndsInSeconds`: a whole number, zero or more).
|
|
@@ -166,7 +166,7 @@ time. Runtime conformance checks also require the task channel to match its prov
|
|
|
166
166
|
| `reference` | Optional agent-facing reference such as a case, call, conversation, ticket, or message number. It is distinct from the protocol `id`. |
|
|
167
167
|
| `completionMode` | `agent-command` waits for the channel's `complete` command; `provider-automatic` completes without one, and takes the agent's `complete` as finishing early. A required outcome is the agent's to give, so it needs `agent-command` (`task.outcomes.required.mode`). |
|
|
168
168
|
| `wrapAllowance` | Fixed time allowed to complete the task after primary interaction ends. For real-time audio, it begins after `task-audio-ended`. Required under `provider-automatic`, where the provider acts on it. Optional under `agent-command`: omitted says the provider imposes no deadline, and Omni counts nothing down; stated, it is the expected wrap, shown and never acted on. `0` under `provider-automatic` is no wrap at all, so such a task publishes no `outcomes` (`task.outcomes.wrapAllowance`). |
|
|
169
|
-
| `wrapEndsInSeconds` | In `completing`, wherever `wrapAllowance` is stated: how much of the wrap is left, in whole seconds from this publication, restated on every publication that carries it (`task.completing.wrapEndsInSeconds`, `.required`, `.unexpected`). A reloaded
|
|
169
|
+
| `wrapEndsInSeconds` | In `completing`, wherever `wrapAllowance` is stated: how much of the wrap is left, in whole seconds from this publication, restated on every publication that carries it (`task.completing.wrapEndsInSeconds`, `.required`, `.unexpected`). A reloaded agent computer and a lead's screen count down the same number. See **Completion timing**. |
|
|
170
170
|
| `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. |
|
|
171
171
|
| `history` | The call record: `steps` — the ordered interaction history of this open task, one entry per occurrence, oldest first — and what they add up to before this agent, `interactionSeconds`, `holdSeconds`, `queueSeconds`, `transfers`, each present when the provider knows it. Live task data restated with the task, not a permanent archive. See **Interaction history**. |
|
|
172
172
|
| `onCall` | Voice only. Who is on the call, or being brought onto it, as the provider states it, replaced whole with the task: `party` is the customer -- carrying a `stage` while being dialled again on the same task, a connect-back with the agent application's `dialId` or a platform's callback without, ringing from the moment the dial is placed and joined on its answered outcome --, `agent` a person by user id, `conferenced` somebody a dial is bringing in, listed from the moment the dial is placed -- with the `destinationId` dialled, the `dialId` where an agent application placed it, the `stage` reached (`ringing` until answered, `joined` after), and `held: true` on anyone joined and parked. `label` names a destination -- a person, a queue -- not a phrase; the agent application supplies the verb. Present when the provider knows the room, absent when it does not. See **Every dial has an outcome** in `guide/voice.md`. |
|
|
@@ -224,7 +224,7 @@ and a re-offer. Everything that names a task after the fact -- a dial outcome, a
|
|
|
224
224
|
ending, a history report, a command -- would land on whichever assignment is open when it arrives.
|
|
225
225
|
So the assignment id is unique within the provider and never reused: where the platform's handle
|
|
226
226
|
never comes back, the adapter passes it through; where it does, the adapter mints the assignment id
|
|
227
|
-
and keeps the mapping, and the
|
|
227
|
+
and keeps the mapping, and the agent computer never learns which. A late event finds the assignment it
|
|
228
228
|
belongs to or is refused, and a task-scoped browser session (`PROVIDER_NAME__ASSIGNMENT_ID__TAB_NAME`)
|
|
229
229
|
lives one assignment, never the next customer's cookies. A dial the agent application placed was
|
|
230
230
|
already safe, since its outcome is placed by the agent application's own `dialId`; where the
|
|
@@ -263,14 +263,14 @@ so a `provider-automatic` task with `wrapAllowance: 0` publishes no `outcomes` a
|
|
|
263
263
|
|
|
264
264
|
**The allowance means one thing per mode.** Under `provider-automatic` it is what the provider
|
|
265
265
|
acts on: the task ends when it runs out. Under `agent-command` it is the expected wrap, stated so
|
|
266
|
-
the agent can see it: the
|
|
266
|
+
the agent can see it: the agent computer counts it down and, past zero, shows how far over they are, and
|
|
267
267
|
nothing acts on it -- the task waits for `complete` however long that takes, with its outcomes
|
|
268
268
|
collected whenever the agent gives them. A provider that would end the task at a time is
|
|
269
269
|
`provider-automatic`.
|
|
270
270
|
|
|
271
271
|
**What is left travels with the task.** The allowance is the length; `wrapEndsInSeconds` is how
|
|
272
272
|
much of it remains, stated on the `completing` task and restated on every publication that
|
|
273
|
-
carries it, as `previewEndsInSeconds` is on a preview.
|
|
273
|
+
carries it, as `previewEndsInSeconds` is on a preview. An agent computer that reloads mid-wrap finds the
|
|
274
274
|
number on the snapshot; a lead's screen finds it on the member; neither starts a clock of its own,
|
|
275
275
|
and both count down from receipt. Under `provider-automatic` the provider ends the task when it
|
|
276
276
|
reaches zero; under `agent-command` it is the expected wrap left, `0` once overrun, and nothing
|
|
@@ -309,7 +309,7 @@ outcome, and complete the task.
|
|
|
309
309
|
|
|
310
310
|
`0` means no wrap under `provider-automatic`: the provider completes the task at the end of the
|
|
311
311
|
interaction, and `complete` has nothing to cut short. Under `agent-command` it is an expected wrap
|
|
312
|
-
of nothing -- the
|
|
312
|
+
of nothing -- the agent computer shows the overrun from the start -- and the task still waits for `complete`.
|
|
313
313
|
|
|
314
314
|
There is no value meaning "unlimited", because a number that is not a duration would be read as
|
|
315
315
|
one. A provider that imposes no deadline says so by **omitting** `wrapAllowance`, which
|
|
@@ -375,16 +375,19 @@ Migration from the earlier spellings:
|
|
|
375
375
|
| ForcedBreak.expectedDurationMs | `expectedEndsInSeconds`: seconds left, restated (`break.forced.expectedEndsInSeconds`, `.expectedDurationMs.renamed`); the command keeps `expectedDurationMs` |
|
|
376
376
|
| MemberShift; the day on the lead's list alone | `Shift`, on `Snapshot.shift` and `shift-updated` for the agent, on `TeamMember.shift` for the lead |
|
|
377
377
|
| TeamMember.listening as this lead's one entry | `MemberListening[]`, every lead's, each with `leadId` (`team.member.listening.leadId`, `.unique`, `.empty`, `.entry`, `.heard`) |
|
|
378
|
-
| a member's history trimmed on the lead's list | the record whole, as the member's
|
|
378
|
+
| a member's history trimmed on the lead's list | the record whole, as the member's computer holds it |
|
|
379
379
|
| durations counted from the screen's clock | the provider's: `timeCheck` required of a provider publishing a running instant (`manifest.timeCheck.required`), `Snapshot.providerTime` with it (`snapshot.providerTime`, `.unexpected`) |
|
|
380
|
-
| a wrap the
|
|
380
|
+
| a wrap the agent computer timed from an event's receipt | `Task.wrapEndsInSeconds` in `completing`, restated (`task.completing.wrapEndsInSeconds`, `.required`, `.unexpected`) |
|
|
381
381
|
| MemberTask.capabilities, capabilitySource, browsers | gone: the workspace never travels to a lead (`team.member.task.workspace`); the record and the completion terms do |
|
|
382
382
|
| a member republished on a hold, a mute, a take-over | on every publication of the member's task to the member |
|
|
383
383
|
| two clients on one login | the later wins; the first is ended with `recovery: "displaced"` |
|
|
384
384
|
| the phone unspoken | `PhoneState` -- the device, its mute, its channels -- on `Snapshot.phone`, `phone-updated` and `TeamMember.phone`, from a manifest declaring `phoneStatus` (`snapshot.phone.required`, `.unexpected`, `phone.*`, `phone.channel.*`, `event.phone.capability`) |
|
|
385
385
|
| the agent's readiness for the next call read into setCapacity | the agent's own queue: `capabilities.nextCall`, `requestNextCall`/`cancelNextCall`/`releaseLinedUp`, `Snapshot.nextCall` and `linedUp`, the `next-call` and `lined-up` events (`snapshot.nextCall.idle`, `nextCall.*`, `linedUp.*`) |
|
|
386
386
|
| a harness that exercised an adapter, drove a call and evaluated rules | the harness reads as a test: `testAdapter`, `withCall`, `timeoutMs`, `notTested`, `rulesTested`, rules `test.*` (`test.timeout`, `test.offer.expired`, `test.preview.deadline`, ...) |
|
|
387
|
-
| an `end-call` while the caller was on hold | refused: a `paused` task is resumed first (`command.endCall.held`), the
|
|
387
|
+
| an `end-call` while the caller was on hold | refused: a `paused` task is resumed first (`command.endCall.held`), the agent computer shows End call disabled on hold, the provider answers `failed`, and the test names an adapter that ends from hold (`test.command.held`); `terminate-call` is not gated, it ends the caller's channel too |
|
|
388
|
+
| the handset spelled by its place on a desk | `hardphone`, on the manifest's `phones` and the login's `phone`; the former spelling is refused |
|
|
389
|
+
| the desk | the agent computer, in the guide's prose |
|
|
390
|
+
| the agent application's Mute on hold | unavailable while the task is `paused`, promised at login by a softphone login's agent application (`host.guarantees.muteUnavailableOnHold`, required there and refused of a hardphone login); a host mute beginning on a paused task is refused (`historyReport.muted.held`) and the test names an adapter that records one (`test.recordStep.held`) |
|
|
388
391
|
|
|
389
392
|
Update producers, consumers, saved task snapshots, and validation-rule assertions together.
|
|
390
393
|
History and report rule names use `history` and `historyReport`; assignment rules use
|
|
@@ -428,7 +431,7 @@ Steps are `queued`, `offered`, `answered`, `held`, `muted`, `transferred`, `conf
|
|
|
428
431
|
when it began, `seconds` once it ended and omitted while it runs — and a second hold is a second
|
|
429
432
|
entry after the first, never a revision of it. A leg that has ended states its duration, and the
|
|
430
433
|
task says whether a leg can still be running: a hold runs only while the task is `paused`, a mute
|
|
431
|
-
only while its audio is up, so a `held` entry without `seconds` on a task that is not paused, or a
|
|
434
|
+
only while its audio is up and the agent application's own never begins on hold (`historyReport.muted.held`), so a `held` entry without `seconds` on a task that is not paused, or a
|
|
432
435
|
`muted` one on a call that is over, is a leg nobody closed and reads exactly like a leg running now
|
|
433
436
|
(`task.history.held.open`, `.muted.open`). Whoever performs the leg closes it — the
|
|
434
437
|
provider whose platform parks the caller, the agent application whose microphone it is — by restating the entry
|
|
@@ -485,7 +488,7 @@ or combine them: report the representation conflict. No ambiguous event is silen
|
|
|
485
488
|
**Interaction time is anchored, not restarted.** It runs from the
|
|
486
489
|
`answered` step's `at` — from the task's first `in-progress` where the provider reports no
|
|
487
490
|
history — until the task's audio ends, and a hold neither pauses nor resets it: the hold's own
|
|
488
|
-
duration is the `held` entry's `seconds`, and
|
|
491
|
+
duration is the `held` entry's `seconds`, and an agent computer that restarts its counter on resume is
|
|
489
492
|
counting the wrong thing.
|
|
490
493
|
|
|
491
494
|
`muted` is there because the mute is the agent application's and never the provider's — see **The station is
|
|
@@ -541,10 +544,10 @@ different claim — it says the task has had none.
|
|
|
541
544
|
|
|
542
545
|
**What the record adds up to is stated, not summed.** A call that has changed hands arrives
|
|
543
546
|
carrying what others already used of it, and the agent reads that before they say hello: the
|
|
544
|
-
totals are the provider's own — never
|
|
547
|
+
totals are the provider's own — never an agent computer summing instants that may be rounded or skewed, for
|
|
545
548
|
the same reason `seconds` is reported and not derived — and each is present when the provider
|
|
546
549
|
knows it and absent when it does not, never a plausible nought. A fresh call from the queue has
|
|
547
|
-
`steps` with no prior `answered` and no totals to state, and
|
|
550
|
+
`steps` with no prior `answered` and no totals to state, and an agent computer shows nothing rather than
|
|
548
551
|
zeros. The record is restated with the task on every `task-updated` and snapshot, so an entry
|
|
549
552
|
that arrives late corrects the sums. These four are what every platform reports; more will be
|
|
550
553
|
added here as a need is shown, not invented ahead of one.
|
|
@@ -579,7 +582,7 @@ did ask for them forwards upstream, and how often, is its own business. The step
|
|
|
579
582
|
`recordStep` is required of every softphone login's connection, since every call on a softphone
|
|
580
583
|
can be muted by the agent application, and answers `recorded` with the canonical history `at`. A `muted` report says whose the silence was,
|
|
581
584
|
`mutedBy: "host"` or `"station"`, and no other report has the word (`historyReport.mutedBy`,
|
|
582
|
-
`.mutedBy.unexpected`). On a
|
|
585
|
+
`.mutedBy.unexpected`). On a hardphone the microphone is the phone's:
|
|
583
586
|
the agent application mutes nothing and records nothing.
|
|
584
587
|
|
|
585
588
|
**The record is a record.** The microphone is the agent application's, and nothing in the
|
|
@@ -816,7 +819,7 @@ until the queue's terms arrive, and that the controls may change when they do --
|
|
|
816
819
|
the buttons in front of them now, not about the provider, because that is what changes when the
|
|
817
820
|
republish lands. It stays for as long as the set is provisional, beside the controls it qualifies;
|
|
818
821
|
a message that shows and clears has said nothing about the buttons still on the screen. The two
|
|
819
|
-
words are close in English and far apart on the
|
|
822
|
+
words are close in English and far apart on the agent computer: `nobody` is silence, `not-yet-read` is a
|
|
820
823
|
standing notice.
|
|
821
824
|
|
|
822
825
|
One consequence for whoever builds the agent application: because a conformance run fails on `not-yet-read`, a
|
|
@@ -1089,7 +1092,7 @@ react rather than only display the message:
|
|
|
1089
1092
|
| `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. |
|
|
1090
1093
|
| `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. |
|
|
1091
1094
|
| `omni.assignment-not-found` | The assignment named is not one the provider holds, typically after the task already ended. |
|
|
1092
|
-
| `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
|
|
1095
|
+
| `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 hardphone, 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** in `guide/phone.md`. |
|
|
1093
1096
|
| `omni.destination-not-permitted` | The dialled number, or the `destinationId` named, is not one the provider offers this agent. |
|
|
1094
1097
|
| `omni.rate-limited` | The action was throttled. Pair with `retryAfterMs`. |
|
|
1095
1098
|
| `omni.unavailable` | The provider is temporarily unable to serve the action, including any command sent while `transport-status` is not `active`. |
|
package/guide/breaks.md
CHANGED
|
@@ -140,7 +140,7 @@ which forces the break on the member; `by` names the lead who asked it to. A bre
|
|
|
140
140
|
imposed on its own -- a schedule, a compliance hold -- says `by: "provider"`, and is the platform's
|
|
141
141
|
to lift: a lead's `end-forced-break` on it is refused before it is sent
|
|
142
142
|
(`team.command.endForcedBreak.provider`), and a provider that receives one answers `failed` with
|
|
143
|
-
`omni.break-forced-by-provider`. The
|
|
143
|
+
`omni.break-forced-by-provider`. The agent computer offers End forced break on a member only where
|
|
144
144
|
`forced.by` names a lead.
|
|
145
145
|
|
|
146
146
|
**A lead lifting the restriction does not resume the agent.** On an applied
|
|
@@ -250,7 +250,7 @@ against it (`stream.taskOffered.overCapacity`).
|
|
|
250
250
|
idle dialpad arrives through `task-offered` like any other task, and a full agent does not forbid
|
|
251
251
|
it: the ceiling binds assignment, not the agent's own hand.
|
|
252
252
|
|
|
253
|
-
**Capacity is not the agent's readiness.** It is the
|
|
253
|
+
**Capacity is not the agent's readiness.** It is the agent computer's division of one person across
|
|
254
254
|
providers, a ceiling on assignments at once, and it says nothing about the moment. An agent who
|
|
255
255
|
is about to finish a call and wants the next one lined up has a different thing to say, and says
|
|
256
256
|
it as **The agent's own queue** in `guide/queue.md` sets out; the ceiling stands unchanged while they do,
|
package/guide/lead.md
CHANGED
|
@@ -7,7 +7,7 @@ The lead's own surface: the team member list, the lead's commands, lead assist,
|
|
|
7
7
|
The team feature is the lead's own surface, beside the agent's and never mixed with it. The
|
|
8
8
|
login's `lead` flag turns it on in the agent application; the lead retrieves their members and
|
|
9
9
|
sees, for each, their assignments as they stand and the full history of each, and acts on them
|
|
10
|
-
with the authority the flag carries. Nothing the lead does here is a task on their
|
|
10
|
+
with the authority the flag carries. Nothing the lead does here is a task on their agent computer. A lead
|
|
11
11
|
who also takes calls is an agent like any other on the agent side, and a lead who does not want
|
|
12
12
|
the agent role for now places themself on a break of a working kind and keeps the team feature on.
|
|
13
13
|
|
|
@@ -29,11 +29,11 @@ computes nothing. See **`team-updated`** in `guide.md` and its companions.
|
|
|
29
29
|
| `availability` | Required. What the member is doing now. |
|
|
30
30
|
| `since` | Optional. When the current `availability` began — not when they signed in, and not when the team member list was read. |
|
|
31
31
|
| `break` | Present only while the member has an outstanding break request. See **A member waiting for a break**. |
|
|
32
|
-
| `tasks` | The member's open tasks as `MemberTask`s: the same task the member's
|
|
32
|
+
| `tasks` | The member's open tasks as `MemberTask`s: the same task the member's computer holds, less the workspace. The assignment, title, task type, phase, party, room, audio and completion terms -- `completionMode`, `wrapAllowance`, `wrapEndsInSeconds` -- are what the lead reads, and the task's record -- `history`, steps and totals -- travels whole, exactly as the member's computer holds it, so the lead's screen and the member's show the same record. The workspace -- `capabilities`, `capabilitySource`, `browsers` and their URLs -- is the member's computer's alone and never travels (`team.member.task.workspace`). The party's number and email are shown to leads or locked as the provider decides for leads, apart from what it decides for the member -- see **What the queue locks is locked on the whole task**. `[]` when the member holds none; omitted only where the provider cannot see them. |
|
|
33
33
|
| `listening` | Every lead on this member's call — listening, coaching or joined — published to every lead alike: each entry names the lead by `leadId`, which of the member's calls by `assignmentId`, the `mode` they are heard in, and since when. The entry naming the signed-in lead is what their own audio opens on. Absent while nobody is on the call, never `[]` (`team.member.listening.*`); a lead is on one member's call at a time (`team.listening.single`), and a lead who is heard is also in the task's `onCall` (`team.member.listening.heard`). |
|
|
34
34
|
| `request` | Present while the member is asking a lead to join one of their calls: which assignment, the note they wrote, since when. The call named is one their `tasks` carry (`team.member.request.assignment`). Absent when they are not asking. See **Lead assist**. |
|
|
35
35
|
| `nextCall`, `linedUp` | The member's own queue as their own snapshot carries it: their standing ask for the next call, and the call lined up for them. See **The agent's own queue** in `guide/queue.md`. |
|
|
36
|
-
| `phone` | The member's phone as the platform sees it: the same `PhoneState` the member's own snapshot carries, so a lead sees a
|
|
36
|
+
| `phone` | The member's phone as the platform sees it: the same `PhoneState` the member's own snapshot carries, so a lead sees a hardphone that is down, or a call on it that is no task, as the member does. Present where the provider declares `phoneStatus`. See **The phone's own view** in `guide/phone.md`. |
|
|
37
37
|
| `shift` | The member's own history for the day, about the person rather than any one call: `signedInAt`, `signedOutAt` once it has happened, today's totals as the provider counts them — `talkSeconds`, `holdSeconds`, `breakSeconds`, `tasksHandled`, each present only when the provider knows it — and the day's sign-in, sign-out and break `events`, oldest first. The same `Shift` the member's own snapshot carries, the same numbers from the same count. Omitted where the provider cannot say. |
|
|
38
38
|
|
|
39
39
|
Each availability value means one thing:
|
|
@@ -54,10 +54,10 @@ member at a time on `team-member-updated`, carrying the member whole: their avai
|
|
|
54
54
|
tasks, their listening, their shift, their request, as they now stand. Applying the same member
|
|
55
55
|
twice changes nothing, so a redelivery is harmless, and a member the team never carried is added
|
|
56
56
|
by it, as a colleague signing in is. `team-member-removed` takes one off. Never a field of a
|
|
57
|
-
member, never a task of theirs on its own: a hold on a member's
|
|
57
|
+
member, never a task of theirs on its own: a hold on a member's computer is that member republished
|
|
58
58
|
whole on the lead's, and nothing else on the lead's screen moves.
|
|
59
59
|
|
|
60
|
-
**Whatever the member's
|
|
60
|
+
**Whatever the member's computer hears of a task, every lead hears of the member.** The trigger is
|
|
61
61
|
not a list of changes but the publication itself: each `task-offered`, `task-updated`,
|
|
62
62
|
`task-audio-started`, `task-audio-ended` and `task-ended` the provider sends to the member is
|
|
63
63
|
matched by a `team-member-updated` to every lead with the feature on, carrying the member whole
|
|
@@ -68,7 +68,7 @@ part for longer than the delay between them.
|
|
|
68
68
|
|
|
69
69
|
**The agent's day is on the wire.** The lead sees `shift` totals as the provider counts them, and
|
|
70
70
|
the agent sees the same: `Snapshot.shift` and `shift-updated` carry the agent's own day in the same
|
|
71
|
-
shape, from the same count, so "calls today: 12" is one number on both screens. The
|
|
71
|
+
shape, from the same count, so "calls today: 12" is one number on both screens. The agent computer computes
|
|
72
72
|
nothing of the day; `tasksHandled` moves when a task ends and `talkSeconds` when a leg closes, and
|
|
73
73
|
the active call's running time is the screen's own until then, as **Every screen counts from the
|
|
74
74
|
provider's clock** sets out.
|
|
@@ -174,7 +174,7 @@ executeTeam({ command: { type: "decline", memberId: "A-1", reason: "In a call" }
|
|
|
174
174
|
`leadAssist: { stage: "joined", leadId }` and its `onCall` gains the lead as an `agent`, since a
|
|
175
175
|
lead who is heard is in the room; the member on the team member list carries
|
|
176
176
|
`listening: [{ leadId, assignmentId, mode: "join-call", since }]`, and the lead's audio opens on
|
|
177
|
-
it as **Listening to a call** describes. Nothing is a task on the lead's
|
|
177
|
+
it as **Listening to a call** describes. Nothing is a task on the lead's computer: a join is the lead's act on the team surface, not
|
|
178
178
|
work assigned to them.
|
|
179
179
|
|
|
180
180
|
**A lead is on one member's call at a time.** A `join` from a lead already on a call -- their own
|
|
@@ -193,7 +193,7 @@ else changes; the agent is still on the call.
|
|
|
193
193
|
its new agent. There are two paths to it -- the agent asked and the lead joined, or the lead was
|
|
194
194
|
listening unasked -- and one act at the end of either. What follows is the same on both:
|
|
195
195
|
|
|
196
|
-
| | The member's task | The lead's
|
|
196
|
+
| | The member's task | The lead's computer |
|
|
197
197
|
| --- | --- | --- |
|
|
198
198
|
| Take-over | `task-audio-ended`, then `completing`: the member's interaction is over and their wrap runs, exactly as after `end-call`. The audio ends first, as before every voice ending (`stream.taskEnded.audioOpen`). The member loses this lead's `listening` entry on the team member list no later than the offer, and the lead's listening audio closes. | An ordinary assignment arrives: `task-offered` with `acceptance: "automatic"`, carrying the call's history and `takenOver: { memberId, since }`, within the lead's capacity. |
|
|
199
199
|
| The member completes | `task-ended` with `{ type: "taken-over", leadId }`, at the member's own completion. The lead is named by user id, since a lead is not a directory item. | The lead works the call as any agent would, and ends it as any call ends. |
|
|
@@ -201,7 +201,7 @@ listening unasked -- and one act at the end of either. What follows is the same
|
|
|
201
201
|
**The taken-over call is offered whatever break the lead is on, and counted like any other.** A
|
|
202
202
|
lead working as lead alone is on a break of a working kind, and the provider assigns the call
|
|
203
203
|
regardless: it is the one task a break in effect may hold (`break.on-break.tasks` allows it, and
|
|
204
|
-
nothing else). It is a call landing on the lead's
|
|
204
|
+
nothing else). It is a call landing on the lead's computer, so it counts against their capacity as
|
|
205
205
|
every call does, with no exemption: a lead at capacity cannot take over, and the provider answers
|
|
206
206
|
`failed`. The other conditions are that the lead is not already on another call and their voice
|
|
207
207
|
channel is free. A lead with the team feature off cannot take over, since they have no member's
|
|
@@ -229,7 +229,7 @@ Lead and member alike are `UserId`s of this provider, so an adapter publishing t
|
|
|
229
229
|
### Listening to a call
|
|
230
230
|
|
|
231
231
|
A lead may listen to a member's call without being asked -- to coach, to check quality, to step in
|
|
232
|
-
when it goes wrong. It is a team act naming the member, and no task on the lead's
|
|
232
|
+
when it goes wrong. It is a team act naming the member, and no task on the lead's computer carries it:
|
|
233
233
|
the lead's view of the call is the member's task on the team member list, with its room and its
|
|
234
234
|
history. The three modes determine who hears the lead:
|
|
235
235
|
|
|
@@ -296,7 +296,7 @@ listens through it.
|
|
|
296
296
|
no trace of the lead: not on `onCall`, not in the record. Whether coaching is announced to the
|
|
297
297
|
agent is the platform's business and travels on the audio, not on this wire. In `join-call`, and
|
|
298
298
|
on a `join` answering a request, the lead is heard by everyone and is in the room: the task's
|
|
299
|
-
`onCall` carries them as an `agent` by user id, on the member's
|
|
299
|
+
`onCall` carries them as an `agent` by user id, on the member's computer and on every lead's copy of
|
|
300
300
|
the task alike, and `leave` takes the entry off as it takes the channel off
|
|
301
301
|
(`team.member.listening.heard`).
|
|
302
302
|
|
package/guide/phone.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# `@xema/omni-protocol`: The phone
|
|
2
2
|
|
|
3
|
-
How the agent hears the call: softphone and
|
|
3
|
+
How the agent hears the call: softphone and hardphone, the station and its headset, opening the audio, the microphone and the record. Part of the contract in `guide.md`, which holds the terms, the shapes and the rules every file here relies on; a reference in bold names the file it points into where that is not this one.
|
|
4
4
|
|
|
5
5
|
## Real-time audio
|
|
6
6
|
|
|
7
7
|
Every voice provider has audio: `channel: "voice"` says audio exists. Where the agent hears it
|
|
8
8
|
depends on the `phone` the agent application selected at authentication and connect from the manifest's
|
|
9
|
-
`phones`. On a softphone, audio lands in Omni; on a
|
|
9
|
+
`phones`. On a softphone, audio lands in Omni; on a hardphone, it lands on the handset and Omni
|
|
10
10
|
opens no audio. See **How the agent hears the call**. Audio transitions are voice-only;
|
|
11
11
|
chat and email publish none (`event.audio.channel`, `stream.taskAudio.channel`).
|
|
12
12
|
|
|
@@ -34,7 +34,7 @@ what failed, and reports. It never decides for the adapter what a missing microp
|
|
|
34
34
|
**The agent application also guarantees, and a promise the provider cannot see is not one it can rely on.**
|
|
35
35
|
Two of this contract's obligations fall on the agent application rather than the provider — honouring a task
|
|
36
36
|
browser's `urlVisibility`, and taking a `consent` offer only on the person's own press — and more
|
|
37
|
-
than one
|
|
37
|
+
than one agent computer speaks this contract: a browser and a native application differ in what they can
|
|
38
38
|
reach, and a provider never branches on which it is talking to, only on what it has declared —
|
|
39
39
|
here, and in `host.mute` (see **The station is the agent application's**). So `ConnectContext.host.guarantees` says which promises the
|
|
40
40
|
connected agent application makes, declared once per connection, presence being the guarantee exactly as it is
|
|
@@ -44,6 +44,7 @@ the permission everywhere else:
|
|
|
44
44
|
| --- | --- |
|
|
45
45
|
| `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. |
|
|
46
46
|
| `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. |
|
|
47
|
+
| `muteUnavailableOnHold` | The agent application's Mute is its own control on any call and is never offered while the task is `paused`: a mute standing when a hold begins stands through it, and the agent lifts it after resume. Held to the login's phone at connect: required of a softphone login's agent application, whose microphone it is (`host.guarantee.muteUnavailableOnHold.required`), and refused of a hardphone login's, whose mute is the handset's (`.unexpected`). So an agent application that cannot control its mute fails at login, not on a call. |
|
|
47
48
|
|
|
48
49
|
A guarantee the agent application does not make is an absent key, never `false` — `validateHostGuarantees`
|
|
49
50
|
refuses a false one, as it refuses a name this contract does not list.
|
|
@@ -64,7 +65,7 @@ is the one that knows whether an agent without a microphone, or without a speake
|
|
|
64
65
|
The agent application's own obligations here — asking at connect, never publishing `not-asked` when it does,
|
|
65
66
|
publishing a state and not a flicker — are Omni's tests' to hold. `testAdapter` holds the
|
|
66
67
|
other side: it validates the shape of whatever agent application a test hands the adapter, requires `audio` on
|
|
67
|
-
a softphone login and none elsewhere -- not on a
|
|
68
|
+
a softphone login and none elsewhere -- not on a hardphone, not off voice
|
|
68
69
|
(`context.host.audio.required` / `.unexpected`), holds `host.mute` to the same line (`host.mute.required`
|
|
69
70
|
/ `.unexpected`, `host.mute` for a third word) -- and refuses a voice adapter that never asked the
|
|
70
71
|
agent application anything (`connection.host.consulted`), and one that subscribed to nothing will never hear of a
|
|
@@ -105,14 +106,14 @@ a return.
|
|
|
105
106
|
### How the agent hears the call
|
|
106
107
|
|
|
107
108
|
A voice platform can put an agent on a **softphone**, where the call's audio lands in the agent application
|
|
108
|
-
and Omni owns the microphone and the playback, or on a **
|
|
109
|
+
and Omni owns the microphone and the playback, or on a **hardphone**, a handset on the platform's
|
|
109
110
|
switch that the platform rings, where the agent application owns no audio and only shows the call. Which of
|
|
110
111
|
these a platform can do is a fact about the platform, and which one a login is on is a choice the
|
|
111
112
|
agent application makes for that agent -- so the manifest declares and the agent application chooses.
|
|
112
113
|
|
|
113
114
|
```ts
|
|
114
115
|
// Manifest: what the platform can do
|
|
115
|
-
phones: ["softphone", "
|
|
116
|
+
phones: ["softphone", "hardphone"]
|
|
116
117
|
// The agent application, at authentication and again at connect: which one this login is on
|
|
117
118
|
{ phone: "softphone" }
|
|
118
119
|
```
|
|
@@ -122,37 +123,37 @@ A voice manifest lists its `phones` and any other channel lists none (`manifest.
|
|
|
122
123
|
and one the manifest listed (`context.phone.required`, `.unexpected`, `.unsupported`). Everything
|
|
123
124
|
about audio then follows the phone rather than the channel: on a softphone the agent application reports its
|
|
124
125
|
audio, the adapter implements `openAudio`, and `task-audio-started` is the word to open it; on a
|
|
125
|
-
|
|
126
|
-
moment the call is live so the
|
|
127
|
-
`
|
|
126
|
+
hardphone the agent application reports no audio, opens nothing, and `task-audio-started` still marks the
|
|
127
|
+
moment the call is live so the agent computer shows it, with the sound on the handset. A platform that lists
|
|
128
|
+
`hardphone` alone never implements `openAudio`; one that lists `softphone` always does.
|
|
128
129
|
|
|
129
130
|
**The mode comes from the agent application; the status comes from the provider.** The agent application knows which kind
|
|
130
131
|
of station the agent signed in at, because the person chose it, and nothing else can know that.
|
|
131
132
|
The provider knows whether that station can carry a call right now -- whether a handset is
|
|
132
133
|
registered -- which an agent application in a browser cannot see. So the agent application's `phone` selects the branch, and
|
|
133
134
|
the provider's own knowledge of the device decides readiness within it. A provider never overrides
|
|
134
|
-
the agent application's declaration from its device record: treating a
|
|
135
|
+
the agent application's declaration from its device record: treating a hardphone login as a softphone because
|
|
135
136
|
the handset is momentarily unregistered, and demanding a microphone of it, is the reading this
|
|
136
137
|
sentence exists to refuse. What the provider does when the handset is not registered is what it
|
|
137
138
|
does for any station that cannot take a call -- hold the agent not-ready and say why.
|
|
138
139
|
|
|
139
|
-
**On a
|
|
140
|
+
**On a hardphone login the agent application never calls `openAudio`.** There is no stream to hand over and
|
|
140
141
|
no audio to attach; an agent application that calls it anyway is in error, and an adapter that receives the call
|
|
141
142
|
answers `unavailable` with a non-retryable failure, since waiting changes nothing about a station
|
|
142
143
|
that is a telephone. The harness requires no `openAudio` of such an adapter and never calls it.
|
|
143
144
|
|
|
144
|
-
**Which handset a
|
|
145
|
+
**Which handset a hardphone login rings is the platform's configuration for that agent**, and
|
|
145
146
|
this wire never asks the agent for it: an agent application declares `phone` and nothing more. What a platform
|
|
146
147
|
asks on surfaces of its own is its own decision.
|
|
147
148
|
|
|
148
149
|
**A declared phone the platform does not permit for this agent is a login that cannot be
|
|
149
150
|
established.** The manifest says what the platform can do; the agent's record, kept by an
|
|
150
151
|
administrator, says what this agent is configured for, and the two can disagree with the agent application's
|
|
151
|
-
declaration -- a softphone declared for an agent whose record says
|
|
152
|
+
declaration -- a softphone declared for an agent whose record says hardphone. Honouring that
|
|
152
153
|
declaration on a switch that allows one registration per endpoint would evict the handset: the
|
|
153
154
|
agent's phone stops ringing and their calls land in a tab, and nobody is told. So the provider
|
|
154
155
|
refuses the login at authentication, with `omni.phone-not-permitted` and a message the agent application shows
|
|
155
|
-
-- "this agent is configured for a
|
|
156
|
+
-- "this agent is configured for a hardphone" -- and refusing is correct, not an override of the
|
|
156
157
|
agent application. It is a refusal, never a negotiation afterwards and never a quiet substitution, and it is
|
|
157
158
|
never retryable: trying again does not reconfigure the agent, and an agent application validating the answer
|
|
158
159
|
(`validateAuthenticationResult`) refuses a phone refusal marked otherwise
|
|
@@ -218,7 +219,7 @@ changes, and every softphone agent application can do it. A *station* mute silen
|
|
|
218
219
|
the operating system: every application on the machine goes quiet, the system shows it, and a
|
|
219
220
|
headset that follows the system follows it. Only a native agent application can do it. `Host.mute` says which
|
|
220
221
|
this agent application does -- `stream` or `station` -- stated on a softphone login, where the agent application holds the
|
|
221
|
-
microphone, and absent on a
|
|
222
|
+
microphone, and absent on a hardphone and off voice (`host.mute.required` / `.unexpected`). A
|
|
222
223
|
browser says `stream`. A native agent application says whichever it does. Nothing on the wire says what kind of
|
|
223
224
|
application the agent application is; a provider reads only what the agent application declared.
|
|
224
225
|
|
|
@@ -232,7 +233,15 @@ mute through the capture track's muted state, read-only, and cannot lift it; a n
|
|
|
232
233
|
the endpoint and can clear an operating-system mute. A hardware slider is nobody's to clear.
|
|
233
234
|
|
|
234
235
|
**The record carries the same word.** A `muted` interaction leg is a period the agent could not be
|
|
235
|
-
heard, and the record exists so that period is not a hole. The
|
|
236
|
+
heard, and the record exists so that period is not a hole. **The Mute waits for resume.** On a
|
|
237
|
+
softphone the agent application's own Mute is unavailable while the task is `paused`, as it
|
|
238
|
+
promised at login (`muteUnavailableOnHold`): mute then hold is ordinary, and the muted leg runs on
|
|
239
|
+
across the held one; hold then mute is not, and a `muted` leg with `mutedBy: "host"` beginning on a
|
|
240
|
+
paused task is held back by the agent application (`historyReport.muted.held`) and refused by a
|
|
241
|
+
provider that receives one anyway. The test sends one past the validator while the task is held
|
|
242
|
+
and names an adapter that records it (`test.recordStep.held`). A station mute the agent
|
|
243
|
+
application observes on hold -- the headset's slider, the operating system -- is a fact, reported
|
|
244
|
+
with `mutedBy: "station"`, and stands. The agent application reports every such period
|
|
236
245
|
through `recordStep` -- its own Mute, and a station mute it observed during a call -- with
|
|
237
246
|
`mutedBy` saying whose the silence was, and the provider writes the word into the entry
|
|
238
247
|
(`task.history.mutedBy`). The report names no agent because the agent application has exactly one, and
|
|
@@ -257,7 +266,7 @@ as by a click. What a call-control headset sends, and what becomes of it:
|
|
|
257
266
|
| --- | --- | --- | --- |
|
|
258
267
|
| 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"` |
|
|
259
268
|
| 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 |
|
|
260
|
-
| Flash, redial, speed dial | Older headsets and
|
|
269
|
+
| Flash, redial, speed dial | Older headsets and hardphone style devices | Flash is Hold and Resume where the task offers `hold`; redial and speed dial map to nothing on an agent computer that never redials, and are ignored | Nothing new |
|
|
261
270
|
| 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 |
|
|
262
271
|
| 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 |
|
|
263
272
|
| 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 |
|
|
@@ -279,7 +288,7 @@ There are two views of a call, and the contract carries both. The task side says
|
|
|
279
288
|
the agent's work and where each stands: the phase, the audio, the room. The phone side is the
|
|
280
289
|
device: whether it can take a call at all, its own mute, and the calls on it, which exist whether
|
|
281
290
|
or not a task is behind them -- an internal call on the extension, a lead's listening leg, a call
|
|
282
|
-
the platform put on the phone that the
|
|
291
|
+
the platform put on the phone that the agent computer never saw as a task. The host's report covers neither:
|
|
283
292
|
it says what the station has, a microphone and a speaker, and cannot say whether the phone is
|
|
284
293
|
registered. The platform sees the phone, so this is the provider's to publish, as `Snapshot.phone`
|
|
285
294
|
and `phone-updated`, from a manifest that declares `phoneStatus` (`snapshot.phone.required`,
|
|
@@ -287,14 +296,14 @@ and `phone-updated`, from a manifest that declares `phoneStatus` (`snapshot.phon
|
|
|
287
296
|
|
|
288
297
|
```ts
|
|
289
298
|
// a conference in progress on alloc-42 -- one channel, since the bridge mixes -- with alloc-43 parked
|
|
290
|
-
const busy: PhoneState = { phone: "
|
|
299
|
+
const busy: PhoneState = { phone: "hardphone", status: "ready", muted: true, channels: [
|
|
291
300
|
{ state: "active", since: "2026-09-12T10:30:04Z", assignmentId: "alloc-42" },
|
|
292
301
|
{ state: "held", since: "2026-09-12T10:35:20Z", assignmentId: "alloc-43" },
|
|
293
302
|
] };
|
|
294
|
-
// an internal call on the extension the
|
|
295
|
-
const internal: PhoneState = { phone: "
|
|
296
|
-
// a
|
|
297
|
-
const down: PhoneState = { phone: "
|
|
303
|
+
// an internal call on the extension the agent computer never saw as a task
|
|
304
|
+
const internal: PhoneState = { phone: "hardphone", status: "ready", channels: [{ state: "active", since: "2026-09-12T11:02:00Z" }] };
|
|
305
|
+
// a hardphone the platform lost
|
|
306
|
+
const down: PhoneState = { phone: "hardphone", status: "unregistered", since: "2026-09-12T10:41:00Z", channels: [] };
|
|
298
307
|
```
|
|
299
308
|
|
|
300
309
|
**The device.** `status` is the phone itself: `ready` is registered and usable, idle or busy, and
|
|
@@ -320,12 +329,12 @@ members are on the task's `onCall`, never repeated here. Each channel is `ringin
|
|
|
320
329
|
the two views agree** (`phone.channel.task`): `active` with the task at work and its audio
|
|
321
330
|
started, `held` with the task `paused`, `ringing` with the task `pending` or its party ringing on
|
|
322
331
|
`onCall`; and a task with its audio started has a channel (`phone.channel.missing`), since the
|
|
323
|
-
phone carries every call the
|
|
324
|
-
|
|
332
|
+
phone carries every call the agent computer is on. A channel naming no task is the phone's alone, and the
|
|
333
|
+
agent computer shows it as a call it cannot act on.
|
|
325
334
|
|
|
326
335
|
**The mute is the phone's, and it silences whatever is active.** It is one flag on the phone, not
|
|
327
336
|
a property of a channel: whichever channel is active is silent while it is set, and the held
|
|
328
|
-
channels are unaffected. Who owns it follows the phone. On a
|
|
337
|
+
channels are unaffected. Who owns it follows the phone. On a hardphone the mute button is the
|
|
329
338
|
handset's, and the platform observes it or does not: `muted: true` where it does, absent where it
|
|
330
339
|
does not, never invented. On a softphone the microphone is the host's, its mute is the host's
|
|
331
340
|
report as **The station is the agent application's** sets out, and nothing the provider publishes
|
|
@@ -334,6 +343,6 @@ moves it: `muted` on a softphone's state is the platform echoing the host, and i
|
|
|
334
343
|
never the live flag.
|
|
335
344
|
|
|
336
345
|
**The lead sees the member's phone as the member does.** The member on the team member list
|
|
337
|
-
carries the same `PhoneState`, republished with the member on every change, so a
|
|
346
|
+
carries the same `PhoneState`, republished with the member on every change, so a hardphone that
|
|
338
347
|
is down, or a call on it that is no task, shows the same on both screens, and neither derives it
|
|
339
348
|
from anything else.
|
package/guide/queue.md
CHANGED
|
@@ -14,7 +14,7 @@ the lined-up call, and the release.
|
|
|
14
14
|
// the login declares it, as it declares breaks
|
|
15
15
|
{ status: "authenticated", identity: {...}, capabilities: { breaks: true, nextCall: true } }
|
|
16
16
|
|
|
17
|
-
// 1. The ask. The agent presses Next call during the call or the wrap; the
|
|
17
|
+
// 1. The ask. The agent presses Next call during the call or the wrap; the agent computer offers the button
|
|
18
18
|
// when it sees fit -- past the midpoint of the handling time is one way.
|
|
19
19
|
await connection.requestNextCall()
|
|
20
20
|
// -> { status: "requested" }: the provider holds the ask; nothing is promised
|
|
@@ -41,7 +41,7 @@ await connection.releaseLinedUp()
|
|
|
41
41
|
**The ask lives only as long as its purpose.** It needs an assignment at work to be next after,
|
|
42
42
|
so an idle agent has nothing to press (`snapshot.nextCall.idle`), and the provider clears it on
|
|
43
43
|
its own when that assignment ends, when a call is lined up to meet it, and when the agent
|
|
44
|
-
withdraws it with `cancelNextCall()`. The
|
|
44
|
+
withdraws it with `cancelNextCall()`. The agent computer never keeps it past any of those. `requested` says
|
|
45
45
|
the provider holds the ask, not that a call will come: a queue may be empty.
|
|
46
46
|
|
|
47
47
|
**The queue lives within capacity, and never beside a break.** The ask and the lined-up call are
|
|
@@ -50,13 +50,13 @@ promises to assign, and a promise the provider can no longer keep goes:
|
|
|
50
50
|
- **Told `count: 0`**, host-stopped, the provider assigns nothing, so it clears the ask and lets the
|
|
51
51
|
lined-up call go back to the queue for anyone; an entry published after that is refused
|
|
52
52
|
(`stream.nextCall.stopped`, `stream.linedUp.stopped`). The queue is per provider, as the ask
|
|
53
|
-
was made on that provider's call, and the
|
|
53
|
+
was made on that provider's call, and the agent computer's division of capacity is what it was.
|
|
54
54
|
- **A break wins.** An agent who has asked for a break, been granted one starting after this
|
|
55
55
|
task, or been put on one has said the opposite of "line up my next call", and the two never
|
|
56
56
|
stand together: while a break is in flight or in effect -- any status but `not-requested` --
|
|
57
57
|
`requestNextCall()` is refused with `omni.unavailable`, and a break arriving after a
|
|
58
58
|
call was lined up drops the call, the caller going back to the queue for anyone
|
|
59
|
-
(`snapshot.nextCall.break`, `snapshot.linedUp.break`). The
|
|
59
|
+
(`snapshot.nextCall.break`, `snapshot.linedUp.break`). The agent computer shows Next call or the break
|
|
60
60
|
controls, never both.
|
|
61
61
|
- **A lined-up call needs an agent at work** as the ask does: a call lined up for an agent with
|
|
62
62
|
nothing at work is an offer that has not been made, and travels as `task-offered`
|
|
@@ -65,7 +65,7 @@ promises to assign, and a promise the provider can no longer keep goes:
|
|
|
65
65
|
**The lined-up call is not a task, and rides no task event.** Until the offer there is no
|
|
66
66
|
assignment and nothing to accept, so it has no phase, no capabilities and no history, and it
|
|
67
67
|
rings nothing: the caller is still in the platform's queue, the phone has no channel for it, and
|
|
68
|
-
the
|
|
68
|
+
the agent computer shows "next for you" and nothing to act on but the release. It becomes a task by the
|
|
69
69
|
ordinary `task-offered`, with `acceptance: "automatic"` since the agent asked, and counts against
|
|
70
70
|
capacity from that moment as any assignment does; the `queued` step in its history carries the
|
|
71
71
|
caller's whole wait. One at a time: the phone only ever takes the next one, so `linedUp` is an
|
|
@@ -79,7 +79,7 @@ the platform's routing: releasing one call, or withdrawing the ask, ends nothing
|
|
|
79
79
|
and the provider may line a repeat caller up again.
|
|
80
80
|
|
|
81
81
|
**Letting go is the provider's say.** `release: true` on the lined-up call grants the agent Let
|
|
82
|
-
go; absent, the platform means them to take the call and the
|
|
82
|
+
go; absent, the platform means them to take the call and the agent computer offers nothing
|
|
83
83
|
(`linedUp.release`). `releaseLinedUp()` against an entry without it is refused with
|
|
84
84
|
`omni.capability-not-enabled`, as any control the provider did not grant. `cancelNextCall()`
|
|
85
85
|
withdraws the ask alone: once a call is lined up the ask is met, and what happens to that call is
|
package/guide/voice.md
CHANGED
|
@@ -37,7 +37,7 @@ is work, not a cancellation. A `preview` task therefore needs a manifest that sa
|
|
|
37
37
|
|
|
38
38
|
**Preparation and trigger ownership are per task.** Unlimited preparation omits both
|
|
39
39
|
`previewEndsInSeconds` and `atDeadline`: only an agent pressing Call starts dialing. Fixed
|
|
40
|
-
preparation carries the seconds the provider says are left, counted down on the
|
|
40
|
+
preparation carries the seconds the provider says are left, counted down on the agent computer from the
|
|
41
41
|
publication that carried them and restated on every later one, and exactly one trigger owner:
|
|
42
42
|
|
|
43
43
|
| Declaration | At preparation end |
|
|
@@ -82,7 +82,7 @@ Unsupported source correlation/outcome/audio evidence remains an integration blo
|
|
|
82
82
|
|
|
83
83
|
**Drop both fields when the phase moves.** A provider that builds the `in-progress` task by
|
|
84
84
|
spreading the `preview` one carries `previewEndsInSeconds` and `atDeadline` with it, and the agent application refuses
|
|
85
|
-
the update (`task.preview.deadline.unexpected`): the
|
|
85
|
+
the update (`task.preview.deadline.unexpected`): the agent computer keeps the task as it last stood, and the
|
|
86
86
|
provider is told through `refused` exactly which rule, so the state that looked right on its side
|
|
87
87
|
is named on its side. The task past preview has no deadline to wait for, so it carries neither
|
|
88
88
|
field.
|
|
@@ -224,7 +224,7 @@ capabilities: {
|
|
|
224
224
|
|
|
225
225
|
| Field | Contract |
|
|
226
226
|
| --- | --- |
|
|
227
|
-
| `destinations` | The items the control offers, at least one, with unique `id` values. Each is a button or a menu item on the
|
|
227
|
+
| `destinations` | The items the control offers, at least one, with unique `id` values. Each is a button or a menu item on the agent computer. |
|
|
228
228
|
| `id` | What Omni sends as `destinationId` on a `conference` command, and what the provider executes. |
|
|
229
229
|
| `label` | What the agent reads. |
|
|
230
230
|
|
|
@@ -260,7 +260,7 @@ provider decides: another IVR, a queue, a survey, or the end of the call. Gated
|
|
|
260
260
|
|
|
261
261
|
**Never from hold.** An `end-call` while the task is `paused` would leave the caller in the hold
|
|
262
262
|
with nobody coming back to them, and the provider's path for a caller who continues never begins.
|
|
263
|
-
So the caller is taken off hold first: the
|
|
263
|
+
So the caller is taken off hold first: the agent computer holds the command back while the task is `paused`
|
|
264
264
|
(`command.endCall.held`) and shows End call disabled on hold, and a provider that receives it
|
|
265
265
|
anyway answers `failed`. Resume, then end. `terminate-call` needs no such gate: it ends the
|
|
266
266
|
caller's channel with the rest, held or not, and nobody is left waiting.
|
|
@@ -270,7 +270,7 @@ the caller's, any colleague the agent added, and anyone else on the call. Nobody
|
|
|
270
270
|
line. Gated by `terminateCall`, a separate permission, since ending a customer's call is a
|
|
271
271
|
different thing from putting my own side down.
|
|
272
272
|
|
|
273
|
-
Both are commands to the provider through `execute`, and nothing happens on the
|
|
273
|
+
Both are commands to the provider through `execute`, and nothing happens on the agent computer until the
|
|
274
274
|
provider reports it: the agent's audio ends on `task-audio-ended`, the task moves to `completing`,
|
|
275
275
|
any wrap allowance runs, and the agent completes. Neither is `task-ended`, and `complete` is
|
|
276
276
|
neither of them. Both capabilities are the queue's to grant and a level's to lock, and both may
|
|
@@ -362,7 +362,7 @@ those named here: a statement about the switch, sent instead of the nearest caus
|
|
|
362
362
|
`reason` is the switch's own words, optional, and shown to the agent attributed to the switch rather
|
|
363
363
|
than to Omni. The two are different things and may travel together: a switch can say "could not
|
|
364
364
|
create dialog" in words and still name no cause a code would carry, so `unexplained` with a
|
|
365
|
-
`reason` is a dial the switch described but did not classify, and
|
|
365
|
+
`reason` is a dial the switch described but did not classify, and an agent computer renders the words without
|
|
366
366
|
inventing the class.
|
|
367
367
|
|
|
368
368
|
**`assignmentId` is the task the dial was placed on**, resolved from the dial's own identity and never from
|
|
@@ -378,7 +378,7 @@ idle dialpad call, which has no task yet -- or whose entry has already left the
|
|
|
378
378
|
outcome for a dial nobody placed and no task mentions is what `stream.dialOutcome.unknown` refuses.
|
|
379
379
|
|
|
380
380
|
**`answered` says what happened to the dial; `onCall` says who is in the room.** They are two
|
|
381
|
-
claims, and both are true at once when a leg answers and never joins.
|
|
381
|
+
claims, and both are true at once when a leg answers and never joins. An agent computer shows `answered` as the
|
|
382
382
|
dial's conclusion and says "on the call" only when the person appears on `Task.onCall`; the obvious
|
|
383
383
|
reading of `answered` -- that they are on the line -- is the one it does not make.
|
|
384
384
|
|
|
@@ -455,7 +455,7 @@ cannot tell ringing from joined says it contains someone who may never arrive. N
|
|
|
455
455
|
held (`task.onCall.held.ringing`). A colleague who never answers is a dial that ended: the
|
|
456
456
|
provider restates the room without the entry, and the agent is back with the customer.
|
|
457
457
|
|
|
458
|
-
The
|
|
458
|
+
The agent computer shows a dial as placed on `dialling`, never as reached; shows the person in the room when
|
|
459
459
|
they appear on `onCall`; and shows the outcome, with the switch's reason, against the call it
|
|
460
460
|
belonged to -- with no retry, since a wrong number is not worth redialling and the decision is the
|
|
461
461
|
agent's.
|