@xema/omni-protocol 0.1.34 → 0.1.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +14 -10
- package/dist/index.js +1 -1
- package/dist/validation.js +8 -1
- package/guide.md +38 -12
- 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];
|
|
@@ -576,11 +578,13 @@ export type TaskCompletion = {
|
|
|
576
578
|
export type OnCallRole = "party" | "agent" | "consulted" | "conferenced";
|
|
577
579
|
export declare const ON_CALL_ROLES: readonly ["party", "agent", "consulted", "conferenced"];
|
|
578
580
|
/**
|
|
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
|
-
*
|
|
581
|
+
* Somebody on the call, or being brought onto it, as the provider states it: who, since when, and
|
|
582
|
+
* whether they are held aside. `party` is the customer; `agent` a person, by user id; `consulted`
|
|
583
|
+
* and `conferenced` someone a dial is bringing in -- listed from the moment the dial is placed, so
|
|
584
|
+
* the agent can call it off while it rings -- carrying the dial when a host placed it and the
|
|
585
|
+
* address either way. Whether they answered is the `dial-outcome`'s to say. `label` names a
|
|
586
|
+
* destination -- a person, a queue -- not a phrase. At most one `party` and one `consulted`: the
|
|
587
|
+
* consult commands name neither because there is exactly one.
|
|
584
588
|
*/
|
|
585
589
|
export type OnCall = {
|
|
586
590
|
since: IsoTimestamp;
|
|
@@ -804,7 +808,7 @@ export type VoiceTaskCommand = {
|
|
|
804
808
|
dialId: DialId;
|
|
805
809
|
destination: string;
|
|
806
810
|
}
|
|
807
|
-
/** Drop the `conferenced` entry on `Task.onCall` with this destination. */
|
|
811
|
+
/** Drop the `conferenced` entry on `Task.onCall` with this destination; while it still rings, this calls the dial off. */
|
|
808
812
|
| {
|
|
809
813
|
type: "conference";
|
|
810
814
|
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,7 +67,9 @@ 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 });
|
|
72
74
|
/** The task capabilities under which a command dials, and so need the manifest to say how a dial ends. */
|
|
73
75
|
const DIALLING_CAPABILITIES = ["callback", "blindTransfer", "consultTransfer", "conference"];
|
|
@@ -297,6 +299,11 @@ function validateDialOutcomes(manifest, path, into) {
|
|
|
297
299
|
});
|
|
298
300
|
into.require(declared.includes("answered"), "manifest.dialOutcomes.answered", path, "a dial that reached its destination is stated, never inferred: dialOutcomes includes answered");
|
|
299
301
|
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");
|
|
302
|
+
// "The switch gave no cause" is honest only from a provider that reports the cause when there is
|
|
303
|
+
// one; declared alone it is the generic failure every unsure provider would send.
|
|
304
|
+
if (declared.includes("unexplained")) {
|
|
305
|
+
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");
|
|
306
|
+
}
|
|
300
307
|
}
|
|
301
308
|
/** Whether a manifest says how a dial ends, and so may publish tasks that dial. `undefined` where there is no manifest to ask. */
|
|
302
309
|
function manifestDials(manifest) {
|
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;
|
|
@@ -1399,6 +1399,10 @@ export default defineAdapter({
|
|
|
1399
1399
|
});
|
|
1400
1400
|
```
|
|
1401
1401
|
|
|
1402
|
+
`dialOutcomes` is a compile error off voice, as `dial` is. An adapter written once over
|
|
1403
|
+
`C extends Channel` therefore sets it the way it already sets `idleCapabilities`, with
|
|
1404
|
+
`as Pick<Manifest<C>, "dialOutcomes">`; an adapter written for one channel needs no cast.
|
|
1405
|
+
|
|
1402
1406
|
### `Adapter.manifest`
|
|
1403
1407
|
|
|
1404
1408
|
Static metadata that Omni can inspect before connecting.
|
|
@@ -2129,7 +2133,7 @@ time. Runtime conformance checks also require the task channel to match its prov
|
|
|
2129
2133
|
| `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
2134
|
| `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
2135
|
| `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
|
|
2136
|
+
| `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, and `held: true` on anyone 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
2137
|
| `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
2138
|
| `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
2139
|
|
|
@@ -2558,7 +2562,7 @@ const taskCapabilities = {
|
|
|
2558
2562
|
| `blindTransfer` | Secondary menu item: Blind transfer | Omni may transfer the caller directly to a destination. |
|
|
2559
2563
|
| `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
2564
|
| `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. |
|
|
2565
|
+
| `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
2566
|
| `recording` | Overflow menu item: Recording | Omni may expose start, pause, resume, and stop recording controls. |
|
|
2563
2567
|
| `dispositions` | Primary button: Complete | Omni may request task disposal with a provider disposition and notes. |
|
|
2564
2568
|
|
|
@@ -2779,12 +2783,14 @@ nothing, is refused (`result.status`). `validateResult(result, method, path, dia
|
|
|
2779
2783
|
|
|
2780
2784
|
**The outcome is stated, once, either way.** A `dial-outcome` names the `dialId`, the task where
|
|
2781
2785
|
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
|
-
|
|
2786
|
+
set: `answered`, `busy`, `no-answer`, `unreachable`, `rejected`, `cancelled`, `unexplained`.
|
|
2787
|
+
`answered` is stated rather than read off somebody appearing on the call, because a host that infers
|
|
2788
|
+
success cannot tell "reached" from "still ringing". `cancelled` is the dialler calling the dial off
|
|
2789
|
+
before anyone answered -- the agent hanging up while a consult still rings -- which is not a
|
|
2790
|
+
`no-answer` the destination never gave. `unexplained` is the switch dropping the call and giving no
|
|
2791
|
+
cause: a statement about the switch, sent instead of the nearest cause it did not give. `reason` is
|
|
2792
|
+
the switch's own words, optional, and shown to the agent attributed to the switch rather than to
|
|
2793
|
+
Omni.
|
|
2788
2794
|
|
|
2789
2795
|
**`taskId` is the task the dial was placed on**, resolved from the dial's own identity and never from
|
|
2790
2796
|
whatever task the agent is looking at when the outcome arrives. That task may already have ended,
|
|
@@ -2792,6 +2798,12 @@ and the harness places the outcome all the same (`TaskStream`, `stream.dialOutco
|
|
|
2792
2798
|
`stream.dialOutcome.duplicate`); an agent who moved on still learns nobody was reached, against the
|
|
2793
2799
|
call it belonged to and never against the next one.
|
|
2794
2800
|
|
|
2801
|
+
The stream knows a dial from three places: `TaskStream.dialled(dialId)`, which a host calls when it
|
|
2802
|
+
places one; an entry on a task's `onCall`; and a step in its `handlingHistory`. Since a dialled entry
|
|
2803
|
+
is listed from placement, `dialled()` is load-bearing only for a dial that never has an entry -- a
|
|
2804
|
+
blind transfer, a callback, an idle dialpad call -- or whose entry has already left the room; an
|
|
2805
|
+
outcome for a dial nobody placed and no task mentions is what `stream.dialOutcome.unknown` refuses.
|
|
2806
|
+
|
|
2795
2807
|
**`answered` says what happened to the dial; `onCall` says who is in the room.** They are two
|
|
2796
2808
|
claims, and both are true at once when a leg answers and never joins. A desk shows `answered` as the
|
|
2797
2809
|
dial's conclusion and says "on the call" only when the person appears on `Task.onCall`; the obvious
|
|
@@ -2802,7 +2814,11 @@ provider that cannot tell busy from unreachable must not pick one. The manifest
|
|
|
2802
2814
|
`dialOutcomes`, and a `dial-outcome` carries only a declared member
|
|
2803
2815
|
(`event.dialOutcome.undeclared`). The list includes `answered`, or the provider could never state a
|
|
2804
2816
|
success (`manifest.dialOutcomes.answered`), and at least one other member, or it could never state
|
|
2805
|
-
a failure (`manifest.dialOutcomes.failure`).
|
|
2817
|
+
a failure (`manifest.dialOutcomes.failure`). `unexplained` is declarable only beside a cause the
|
|
2818
|
+
provider does report -- `busy`, `no-answer`, `unreachable`, `rejected` or `cancelled`
|
|
2819
|
+
(`manifest.dialOutcomes.unexplained.alone`): a provider that reports causes may honestly say the
|
|
2820
|
+
switch gave none, while one that reports none would be sending the generic failure this set exists
|
|
2821
|
+
to refuse. A provider that dials at all declares it: an idle
|
|
2806
2822
|
dialpad requires it (`manifest.dialOutcomes.required`), and a task that declares `callback`,
|
|
2807
2823
|
`blindTransfer`, `consultTransfer` or `conference` under a manifest without it is refused
|
|
2808
2824
|
(`task.capability.dialOutcomes.required`). Off voice nothing dials, and the field is a compile
|
|
@@ -2825,7 +2841,17 @@ neither carries a destination. `consulted` and `conferenced` were, so both carry
|
|
|
2825
2841
|
and, where a host's dial brought them, its `dialId`; a person the platform added itself carries
|
|
2826
2842
|
none. `held: true` is presence as claim. A snapshot establishes state, so a host that attaches
|
|
2827
2843
|
after a transfer reads the room from here rather than inferring it from a sequence of outcomes it
|
|
2828
|
-
never saw.
|
|
2844
|
+
never saw.
|
|
2845
|
+
|
|
2846
|
+
**A dialled entry is listed from the moment the dial is placed, not from the answer.** The entry is
|
|
2847
|
+
what makes `transfer` `cancel` issuable and what `conference` `remove` names, and a destination the
|
|
2848
|
+
agent cannot call off while it rings is a parked customer with no way back -- which is the very case
|
|
2849
|
+
`cancelled` exists for. Consult and conference behave alike here: the `consulted` or `conferenced`
|
|
2850
|
+
entry appears with its `dialId` when the dial is placed, whether it has answered is the
|
|
2851
|
+
`dial-outcome`'s to say, and a `conference` `remove` naming a still-ringing `destination` calls that
|
|
2852
|
+
dial off, with `cancelled` as its outcome. A consult destination that never answers is a dial that
|
|
2853
|
+
ended: the provider returns the task to `in-progress` with no `consulted` entry, as **Consult
|
|
2854
|
+
transfer** states, so the customer is never left parked by a conforming provider.
|
|
2829
2855
|
|
|
2830
2856
|
The desk shows a dial as placed on `dialling`, never as reached; shows the person in the room when
|
|
2831
2857
|
they appear on `onCall`; and shows the outcome, with the switch's reason, against the call it
|