@xema/omni-protocol 0.1.34 → 0.1.36
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 +18 -10
- package/dist/index.js +1 -1
- package/dist/validation.js +14 -2
- package/guide.md +54 -14
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -77,11 +77,13 @@ export interface DialCapability {
|
|
|
77
77
|
/**
|
|
78
78
|
* How a dial ended, as the switch distinguishes it. `answered` is the one success; the rest are
|
|
79
79
|
* the ways a destination was not reached -- `cancelled` because the dialler called it off before
|
|
80
|
-
* anyone answered, not because the destination failed to
|
|
81
|
-
*
|
|
80
|
+
* anyone answered, not because the destination failed to, and `unexplained` because the switch
|
|
81
|
+
* dropped the call and gave no cause, a statement about the switch rather than a shrug from the
|
|
82
|
+
* adapter. A manifest declares which of these its platform can tell apart (`dialOutcomes`), and a
|
|
83
|
+
* `dial-outcome` carries only a declared one.
|
|
82
84
|
*/
|
|
83
|
-
export type DialOutcome = "answered" | "busy" | "no-answer" | "unreachable" | "rejected" | "cancelled";
|
|
84
|
-
export declare const DIAL_OUTCOMES: readonly ["answered", "busy", "no-answer", "unreachable", "rejected", "cancelled"];
|
|
85
|
+
export type DialOutcome = "answered" | "busy" | "no-answer" | "unreachable" | "rejected" | "cancelled" | "unexplained";
|
|
86
|
+
export declare const DIAL_OUTCOMES: readonly ["answered", "busy", "no-answer", "unreachable", "rejected", "cancelled", "unexplained"];
|
|
85
87
|
/** Every idle capability a provider may declare. Only voice may `dial`; the channel arm says so. */
|
|
86
88
|
export declare const IDLE_CAPABILITIES: readonly ["dial", "personalBrowser", "calendar", "contacts"];
|
|
87
89
|
export type IdleCapability = (typeof IDLE_CAPABILITIES)[number];
|
|
@@ -574,13 +576,18 @@ export type TaskCompletion = {
|
|
|
574
576
|
wrapAllowance: DurationSeconds;
|
|
575
577
|
};
|
|
576
578
|
export type OnCallRole = "party" | "agent" | "consulted" | "conferenced";
|
|
579
|
+
/** Where a dialled entry stands: `ringing` until its dial is answered, `joined` after. */
|
|
580
|
+
export type OnCallStage = "ringing" | "joined";
|
|
577
581
|
export declare const ON_CALL_ROLES: readonly ["party", "agent", "consulted", "conferenced"];
|
|
578
582
|
/**
|
|
579
|
-
* Somebody on the call
|
|
580
|
-
* are held aside. `party` is the customer; `agent` a person, by user id; `consulted`
|
|
581
|
-
* `conferenced` someone a dial
|
|
582
|
-
*
|
|
583
|
-
*
|
|
583
|
+
* Somebody on the call, or being brought onto it, as the provider states it: who, since when, and
|
|
584
|
+
* whether they are held aside. `party` is the customer; `agent` a person, by user id; `consulted`
|
|
585
|
+
* and `conferenced` someone a dial is bringing in -- listed from the moment the dial is placed, so
|
|
586
|
+
* the agent can call it off while it rings -- carrying the dial when a host placed it, the address
|
|
587
|
+
* either way, and the `stage` it has reached: `ringing` until the dial is answered, `joined` after,
|
|
588
|
+
* stated so a snapshot says who is present without anyone having seen the outcome. Nobody ringing is
|
|
589
|
+
* held. `label` names a destination -- a person, a queue -- not a phrase. At most one `party` and
|
|
590
|
+
* one `consulted`: the consult commands name neither because there is exactly one.
|
|
584
591
|
*/
|
|
585
592
|
export type OnCall = {
|
|
586
593
|
since: IsoTimestamp;
|
|
@@ -593,6 +600,7 @@ export type OnCall = {
|
|
|
593
600
|
} | {
|
|
594
601
|
role: "consulted" | "conferenced";
|
|
595
602
|
destination: string;
|
|
603
|
+
stage: OnCallStage;
|
|
596
604
|
dialId?: DialId;
|
|
597
605
|
label?: string;
|
|
598
606
|
});
|
|
@@ -804,7 +812,7 @@ export type VoiceTaskCommand = {
|
|
|
804
812
|
dialId: DialId;
|
|
805
813
|
destination: string;
|
|
806
814
|
}
|
|
807
|
-
/** Drop the `conferenced` entry on `Task.onCall` with this destination. */
|
|
815
|
+
/** Drop the `conferenced` entry on `Task.onCall` with this destination; while it still rings, this calls the dial off. */
|
|
808
816
|
| {
|
|
809
817
|
type: "conference";
|
|
810
818
|
action: "remove";
|
package/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ export function negotiateProtocolVersion(adapterVersions, hostVersions = OMNI_SU
|
|
|
15
15
|
const shared = adapterVersions.filter(version => host.has(version));
|
|
16
16
|
return shared.length === 0 ? undefined : Math.max(...shared);
|
|
17
17
|
}
|
|
18
|
-
export const DIAL_OUTCOMES = ["answered", "busy", "no-answer", "unreachable", "rejected", "cancelled"];
|
|
18
|
+
export const DIAL_OUTCOMES = ["answered", "busy", "no-answer", "unreachable", "rejected", "cancelled", "unexplained"];
|
|
19
19
|
/** Every idle capability a provider may declare. Only voice may `dial`; the channel arm says so. */
|
|
20
20
|
export const IDLE_CAPABILITIES = ["dial", "personalBrowser", "calendar", "contacts"];
|
|
21
21
|
/** What Omni calls each idle capability. */
|
package/dist/validation.js
CHANGED
|
@@ -67,8 +67,11 @@ const ACCESS_APPLIES_TO = membersOf({
|
|
|
67
67
|
"initial-url": true, "all-navigation": true,
|
|
68
68
|
});
|
|
69
69
|
const DIAL_DESTINATION_POLICIES = membersOf({ "contacts-only": true, "any-number": true });
|
|
70
|
-
const DIAL_OUTCOMES = membersOf({ answered: true, busy: true, "no-answer": true, unreachable: true, rejected: true, cancelled: true });
|
|
70
|
+
const DIAL_OUTCOMES = membersOf({ answered: true, busy: true, "no-answer": true, unreachable: true, rejected: true, cancelled: true, unexplained: true });
|
|
71
|
+
/** The outcomes that name a cause. `unexplained` is declarable only beside one of these. */
|
|
72
|
+
const EXPLAINED_FAILURES = ["busy", "no-answer", "unreachable", "rejected", "cancelled"];
|
|
71
73
|
const ON_CALL_ROLES = membersOf({ party: true, agent: true, consulted: true, conferenced: true });
|
|
74
|
+
const ON_CALL_STAGES = membersOf({ ringing: true, joined: true });
|
|
72
75
|
/** The task capabilities under which a command dials, and so need the manifest to say how a dial ends. */
|
|
73
76
|
const DIALLING_CAPABILITIES = ["callback", "blindTransfer", "consultTransfer", "conference"];
|
|
74
77
|
const SNAPSHOT_REASONS = membersOf({
|
|
@@ -297,6 +300,11 @@ function validateDialOutcomes(manifest, path, into) {
|
|
|
297
300
|
});
|
|
298
301
|
into.require(declared.includes("answered"), "manifest.dialOutcomes.answered", path, "a dial that reached its destination is stated, never inferred: dialOutcomes includes answered");
|
|
299
302
|
into.require(declared.some((outcome) => outcome !== "answered"), "manifest.dialOutcomes.failure", path, "a provider that can only say answered can never say a destination was not reached: declare at least one other outcome");
|
|
303
|
+
// "The switch gave no cause" is honest only from a provider that reports the cause when there is
|
|
304
|
+
// one; declared alone it is the generic failure every unsure provider would send.
|
|
305
|
+
if (declared.includes("unexplained")) {
|
|
306
|
+
into.require(declared.some((outcome) => EXPLAINED_FAILURES.includes(outcome)), "manifest.dialOutcomes.unexplained.alone", path, "unexplained says the switch gave no cause, which is a claim only a provider that reports causes can make: declare at least one of busy, no-answer, unreachable, rejected, cancelled beside it");
|
|
307
|
+
}
|
|
300
308
|
}
|
|
301
309
|
/** Whether a manifest says how a dial ends, and so may publish tasks that dial. `undefined` where there is no manifest to ask. */
|
|
302
310
|
function manifestDials(manifest) {
|
|
@@ -771,13 +779,17 @@ function validateOnCall(value, channel, path, into) {
|
|
|
771
779
|
}
|
|
772
780
|
if (role === "consulted" || role === "conferenced") {
|
|
773
781
|
into.filled(entry.destination, "task.onCall.destination", `${at}.destination`, `a ${role} entry names the destination that was dialled`);
|
|
782
|
+
// Stated, so a snapshot says who is present without anyone having seen the dial's outcome.
|
|
783
|
+
if (into.oneOf(entry.stage, ON_CALL_STAGES, "task.onCall.stage", `${at}.stage`)) {
|
|
784
|
+
into.require(!(entry.stage === "ringing" && entry.held === true), "task.onCall.held.ringing", `${at}.held`, "nobody ringing is held; held belongs to somebody who has joined");
|
|
785
|
+
}
|
|
774
786
|
if (entry.dialId !== undefined)
|
|
775
787
|
into.filled(entry.dialId, "task.onCall.dialId", `${at}.dialId`, "dialId must not be empty when present");
|
|
776
788
|
if (entry.label !== undefined)
|
|
777
789
|
into.filled(entry.label, "task.onCall.label", `${at}.label`, "a label must not be empty when present");
|
|
778
790
|
}
|
|
779
791
|
else {
|
|
780
|
-
for (const field of ["destination", "dialId", "label"]) {
|
|
792
|
+
for (const field of ["destination", "dialId", "label", "stage"]) {
|
|
781
793
|
into.require(entry[field] === undefined, `task.onCall.${field}.unexpected`, `${at}.${field}`, `a ${role} was dialled from nowhere; ${field} belongs on consulted or conferenced`);
|
|
782
794
|
}
|
|
783
795
|
}
|
package/guide.md
CHANGED
|
@@ -36,7 +36,7 @@ are used precisely throughout and mean nothing looser here.
|
|
|
36
36
|
| **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. |
|
|
37
37
|
| **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, roster. |
|
|
38
38
|
| **Dial** | One outbound call the host asks a provider to place — from the idle dialpad, a transfer, a conference add, or a callback. Identified by the host's `dialId`, accepted as `dialling`, and ended by exactly one `dial-outcome`. See **Every dial has an outcome**. |
|
|
39
|
-
| **On the call** | Who a voice task's audio joins
|
|
39
|
+
| **On the call** | Who a voice task's audio joins, or is bringing in, as the provider states it on `Task.onCall`: the party, the agents, and anyone consulted or conferenced in from the moment their dial is placed. |
|
|
40
40
|
|
|
41
41
|
Six words describe *what state a thing is in*, and they are not interchangeable: each belongs to
|
|
42
42
|
one thing, so a bare "status" in conversation is always the authentication session's, and a
|
|
@@ -165,7 +165,7 @@ type DialDestinations = "contacts-only" | "any-number";
|
|
|
165
165
|
|
|
166
166
|
type DialCapability = { destinations: DialDestinations };
|
|
167
167
|
|
|
168
|
-
type DialOutcome = "answered" | "busy" | "no-answer" | "unreachable" | "rejected" | "cancelled";
|
|
168
|
+
type DialOutcome = "answered" | "busy" | "no-answer" | "unreachable" | "rejected" | "cancelled" | "unexplained";
|
|
169
169
|
|
|
170
170
|
type IdleCapabilities<C extends Channel = Channel> = {
|
|
171
171
|
personalBrowser?: PersonalBrowserCapability;
|
|
@@ -589,10 +589,12 @@ type TaskCompletion =
|
|
|
589
589
|
|
|
590
590
|
type OnCallRole = "party" | "agent" | "consulted" | "conferenced";
|
|
591
591
|
|
|
592
|
+
type OnCallStage = "ringing" | "joined";
|
|
593
|
+
|
|
592
594
|
type OnCall = { since: IsoTimestamp; held?: true } & (
|
|
593
595
|
| { role: "party" }
|
|
594
596
|
| { role: "agent"; userId: UserId }
|
|
595
|
-
| { role: "consulted" | "conferenced"; destination: string; dialId?: DialId; label?: string }
|
|
597
|
+
| { role: "consulted" | "conferenced"; destination: string; stage: OnCallStage; dialId?: DialId; label?: string }
|
|
596
598
|
);
|
|
597
599
|
|
|
598
600
|
type TaskLead = {
|
|
@@ -1399,6 +1401,10 @@ export default defineAdapter({
|
|
|
1399
1401
|
});
|
|
1400
1402
|
```
|
|
1401
1403
|
|
|
1404
|
+
`dialOutcomes` is a compile error off voice, as `dial` is. An adapter written once over
|
|
1405
|
+
`C extends Channel` therefore sets it the way it already sets `idleCapabilities`, with
|
|
1406
|
+
`as Pick<Manifest<C>, "dialOutcomes">`; an adapter written for one channel needs no cast.
|
|
1407
|
+
|
|
1402
1408
|
### `Adapter.manifest`
|
|
1403
1409
|
|
|
1404
1410
|
Static metadata that Omni can inspect before connecting.
|
|
@@ -2129,7 +2135,7 @@ time. Runtime conformance checks also require the task channel to match its prov
|
|
|
2129
2135
|
| `wrapAllowance` | Fixed time allowed to complete the task after primary handling ends. For real-time media, it begins after `task-media-ended`. Required under `provider-automatic`, where the provider acts on it. Optional under `agent-command`: omitted says the provider imposes no deadline, and Omni counts nothing down. |
|
|
2130
2136
|
| `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. |
|
|
2131
2137
|
| `handlingHistory` | The call record: `steps` — the ordered handling history of this open task, one entry per occurrence, oldest first — and what they add up to before this agent, `handleSeconds`, `holdSeconds`, `queueSeconds`, `transfers`, each present when the provider knows it. Live task data restated with the task, not a permanent archive. See **How a task has been handled**. |
|
|
2132
|
-
| `onCall` | Voice only. Who is on the call
|
|
2138
|
+
| `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, `agent` a person by user id, `consulted` and `conferenced` somebody a dial is bringing in, listed from the moment the dial is placed -- with the `destination` dialled, the `dialId` where a host placed it, the `stage` reached (`ringing` until answered, `joined` after), and `held: true` on anyone joined and parked. A `consulted` entry is what makes `transfer` `complete` and `cancel` issuable. `label` names a destination -- a person, a queue -- not a phrase; the host supplies the verb. Present when the provider knows the room, absent when it does not. See **Every dial has an outcome**. |
|
|
2133
2139
|
| `lead` | Voice only. Present from the agent's request for a lead until the lead leaves or the request ends: `requested` while nobody has joined, `joined` with the lead's `leadId` once somebody has. See **Consulting a lead**. |
|
|
2134
2140
|
| `assisting` | Voice only, on the lead's own task for a call they joined: which member asked, with their note. Its presence is what makes `lead` `take-over` and `leave` issuable. See **Consulting a lead**. |
|
|
2135
2141
|
|
|
@@ -2558,7 +2564,7 @@ const taskCapabilities = {
|
|
|
2558
2564
|
| `blindTransfer` | Secondary menu item: Blind transfer | Omni may transfer the caller directly to a destination. |
|
|
2559
2565
|
| `consultTransfer` | Secondary menu item: Consult transfer | Omni may park the customer and call a destination first, then hand the customer over or cancel back. See **Consult transfer**. |
|
|
2560
2566
|
| `consultLead` | Secondary menu item: Consult lead | Omni may ask a lead to join this call, with a note. The lead's decision reaches the agent on `Task.lead`. See **Consulting a lead**. |
|
|
2561
|
-
| `conference` | Secondary button: Conference | Omni may dial a destination into the active call, and drop a `conferenced` entry from it. |
|
|
2567
|
+
| `conference` | Secondary button: Conference | Omni may dial a destination into the active call, and drop a `conferenced` entry from it -- one still ringing included, which calls the dial off. |
|
|
2562
2568
|
| `recording` | Overflow menu item: Recording | Omni may expose start, pause, resume, and stop recording controls. |
|
|
2563
2569
|
| `dispositions` | Primary button: Complete | Omni may request task disposal with a provider disposition and notes. |
|
|
2564
2570
|
|
|
@@ -2779,12 +2785,17 @@ nothing, is refused (`result.status`). `validateResult(result, method, path, dia
|
|
|
2779
2785
|
|
|
2780
2786
|
**The outcome is stated, once, either way.** A `dial-outcome` names the `dialId`, the task where
|
|
2781
2787
|
there was one, the destination where the provider knows it, and how the dial ended -- from a closed
|
|
2782
|
-
set: `answered`, `busy`, `no-answer`, `unreachable`, `rejected`, `cancelled
|
|
2783
|
-
rather than read off somebody appearing on the call, because a host that infers
|
|
2784
|
-
"reached" from "still ringing". `cancelled` is the dialler calling the dial off
|
|
2785
|
-
answered -- the agent hanging up while a consult still rings -- which is not a
|
|
2786
|
-
destination never gave. `
|
|
2787
|
-
|
|
2788
|
+
set: `answered`, `busy`, `no-answer`, `unreachable`, `rejected`, `cancelled`, `unexplained`.
|
|
2789
|
+
`answered` is stated rather than read off somebody appearing on the call, because a host that infers
|
|
2790
|
+
success cannot tell "reached" from "still ringing". `cancelled` is the dialler calling the dial off
|
|
2791
|
+
before anyone answered -- the agent hanging up while a consult still rings -- which is not a
|
|
2792
|
+
`no-answer` the destination never gave. `unexplained` is the switch dropping the call with no cause among
|
|
2793
|
+
those named here: a statement about the switch, sent instead of the nearest cause it did not give.
|
|
2794
|
+
`reason` is the switch's own words, optional, and shown to the agent attributed to the switch rather
|
|
2795
|
+
than to Omni. The two are different things and may travel together: a switch can say "could not
|
|
2796
|
+
create dialog" in words and still name no cause a code would carry, so `unexplained` with a
|
|
2797
|
+
`reason` is a dial the switch described but did not classify, and a desk renders the words without
|
|
2798
|
+
inventing the class.
|
|
2788
2799
|
|
|
2789
2800
|
**`taskId` is the task the dial was placed on**, resolved from the dial's own identity and never from
|
|
2790
2801
|
whatever task the agent is looking at when the outcome arrives. That task may already have ended,
|
|
@@ -2792,6 +2803,12 @@ and the harness places the outcome all the same (`TaskStream`, `stream.dialOutco
|
|
|
2792
2803
|
`stream.dialOutcome.duplicate`); an agent who moved on still learns nobody was reached, against the
|
|
2793
2804
|
call it belonged to and never against the next one.
|
|
2794
2805
|
|
|
2806
|
+
The stream knows a dial from three places: `TaskStream.dialled(dialId)`, which a host calls when it
|
|
2807
|
+
places one; an entry on a task's `onCall`; and a step in its `handlingHistory`. Since a dialled entry
|
|
2808
|
+
is listed from placement, `dialled()` is load-bearing only for a dial that never has an entry -- a
|
|
2809
|
+
blind transfer, a callback, an idle dialpad call -- or whose entry has already left the room; an
|
|
2810
|
+
outcome for a dial nobody placed and no task mentions is what `stream.dialOutcome.unknown` refuses.
|
|
2811
|
+
|
|
2795
2812
|
**`answered` says what happened to the dial; `onCall` says who is in the room.** They are two
|
|
2796
2813
|
claims, and both are true at once when a leg answers and never joins. A desk shows `answered` as the
|
|
2797
2814
|
dial's conclusion and says "on the call" only when the person appears on `Task.onCall`; the obvious
|
|
@@ -2802,7 +2819,11 @@ provider that cannot tell busy from unreachable must not pick one. The manifest
|
|
|
2802
2819
|
`dialOutcomes`, and a `dial-outcome` carries only a declared member
|
|
2803
2820
|
(`event.dialOutcome.undeclared`). The list includes `answered`, or the provider could never state a
|
|
2804
2821
|
success (`manifest.dialOutcomes.answered`), and at least one other member, or it could never state
|
|
2805
|
-
a failure (`manifest.dialOutcomes.failure`).
|
|
2822
|
+
a failure (`manifest.dialOutcomes.failure`). `unexplained` is declarable only beside a cause the
|
|
2823
|
+
provider does report -- `busy`, `no-answer`, `unreachable`, `rejected` or `cancelled`
|
|
2824
|
+
(`manifest.dialOutcomes.unexplained.alone`): a provider that reports causes may honestly say the
|
|
2825
|
+
switch gave none, while one that reports none would be sending the generic failure this set exists
|
|
2826
|
+
to refuse. A provider that dials at all declares it: an idle
|
|
2806
2827
|
dialpad requires it (`manifest.dialOutcomes.required`), and a task that declares `callback`,
|
|
2807
2828
|
`blindTransfer`, `consultTransfer` or `conference` under a manifest without it is refused
|
|
2808
2829
|
(`task.capability.dialOutcomes.required`). Off voice nothing dials, and the field is a compile
|
|
@@ -2816,7 +2837,8 @@ knows the room:
|
|
|
2816
2837
|
const onCall: OnCall[] = [
|
|
2817
2838
|
{ role: "party", since: "2026-08-21T09:12:00Z" },
|
|
2818
2839
|
{ role: "agent", userId: "A-12", since: "2026-08-21T09:12:04Z" },
|
|
2819
|
-
{ role: "conferenced", destination: "+14155550111", dialId: "dial-7f2", held: true, since: "2026-08-21T09:15:30Z" },
|
|
2840
|
+
{ role: "conferenced", destination: "+14155550111", dialId: "dial-7f2", stage: "joined", held: true, since: "2026-08-21T09:15:30Z" },
|
|
2841
|
+
{ role: "conferenced", destination: "+14155550199", dialId: "dial-3c9", stage: "ringing", since: "2026-08-21T09:16:02Z" },
|
|
2820
2842
|
];
|
|
2821
2843
|
```
|
|
2822
2844
|
|
|
@@ -2825,7 +2847,25 @@ neither carries a destination. `consulted` and `conferenced` were, so both carry
|
|
|
2825
2847
|
and, where a host's dial brought them, its `dialId`; a person the platform added itself carries
|
|
2826
2848
|
none. `held: true` is presence as claim. A snapshot establishes state, so a host that attaches
|
|
2827
2849
|
after a transfer reads the room from here rather than inferring it from a sequence of outcomes it
|
|
2828
|
-
never saw.
|
|
2850
|
+
never saw.
|
|
2851
|
+
|
|
2852
|
+
**A dialled entry is listed from the moment the dial is placed, not from the answer.** The entry is
|
|
2853
|
+
what makes `transfer` `cancel` issuable and what `conference` `remove` names, and a destination the
|
|
2854
|
+
agent cannot call off while it rings is a parked customer with no way back -- which is the very case
|
|
2855
|
+
`cancelled` exists for. Consult and conference behave alike here: the `consulted` or `conferenced`
|
|
2856
|
+
entry appears with its `dialId` when the dial is placed, and a `conference` `remove` naming a
|
|
2857
|
+
still-ringing `destination` calls that dial off, with `cancelled` as its outcome.
|
|
2858
|
+
|
|
2859
|
+
**The entry says where it stands.** A dialled entry carries `stage`: `ringing` until its dial is
|
|
2860
|
+
answered, `joined` after. The `dial-outcome` is the transition and the stage is the state, the same
|
|
2861
|
+
pairing as `task-media-started` and `media`: an `answered` outcome and the task restated with the
|
|
2862
|
+
entry `joined` say one thing twice, and any other outcome removes the entry. It is stated rather
|
|
2863
|
+
than left to whoever placed the dial because that knowledge lives in one process: after a reload,
|
|
2864
|
+
on a second session, or on a supervisor's screen the snapshot is the only source, and a room that
|
|
2865
|
+
cannot tell ringing from joined says it contains someone who may never arrive. Nobody ringing is
|
|
2866
|
+
held (`task.onCall.held.ringing`). A consult destination that never answers is a dial that
|
|
2867
|
+
ended: the provider returns the task to `in-progress` with no `consulted` entry, as **Consult
|
|
2868
|
+
transfer** states, so the customer is never left parked by a conforming provider.
|
|
2829
2869
|
|
|
2830
2870
|
The desk shows a dial as placed on `dialling`, never as reached; shows the person in the room when
|
|
2831
2871
|
they appear on `onCall`; and shows the outcome, with the switch's reason, against the call it
|