@xema/omni-protocol 0.1.113 → 0.1.114
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 +4 -1
- package/dist/testing.js +23 -1
- package/dist/validation.js +2 -0
- package/guide/agent.md +4 -2
- package/guide/voice.md +7 -0
- package/guide.md +4 -2
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1106,7 +1106,10 @@ export type VoiceTaskCommand = {
|
|
|
1106
1106
|
} | {
|
|
1107
1107
|
type: "resume";
|
|
1108
1108
|
}
|
|
1109
|
-
/**
|
|
1109
|
+
/**
|
|
1110
|
+
* End the agent's channel and every channel the agent added; the caller continues on the provider's
|
|
1111
|
+
* path. Gated by `endCall`, and never from hold: a `paused` task is resumed first (`command.endCall.held`).
|
|
1112
|
+
*/
|
|
1110
1113
|
| {
|
|
1111
1114
|
type: "end-call";
|
|
1112
1115
|
}
|
package/dist/testing.js
CHANGED
|
@@ -1959,11 +1959,33 @@ async function testOneCall(call) {
|
|
|
1959
1959
|
return found;
|
|
1960
1960
|
}
|
|
1961
1961
|
}
|
|
1962
|
+
// Ending my part from hold would leave the caller in the hold with nobody coming back to them: a
|
|
1963
|
+
// host holds it back (command.endCall.held), and an adapter that receives it anyway must refuse
|
|
1964
|
+
// it -- so the test sends it past the validator and expects failed.
|
|
1965
|
+
const endCallRefusedOnHold = async () => {
|
|
1966
|
+
ruleTested("test.command.rejected", "test.command.held");
|
|
1967
|
+
let answer;
|
|
1968
|
+
try {
|
|
1969
|
+
answer = await call.connection.execute({ assignmentId: taskId, command: { type: "end-call" } });
|
|
1970
|
+
}
|
|
1971
|
+
catch (error) {
|
|
1972
|
+
refuse("test.command.rejected", "test.command.end-call", `execute rejected rather than answered: ${String(error)}`);
|
|
1973
|
+
return;
|
|
1974
|
+
}
|
|
1975
|
+
if (isRecord(answer) && answer.status !== "failed") {
|
|
1976
|
+
refuse("test.command.held", "test.command.end-call", "the provider ended the agent's part with the caller on hold: end-call waits for resume, and the adapter is the second gate");
|
|
1977
|
+
// Applied, the task leaves paused; the run reads where the adapter put it rather than resuming a call it ended.
|
|
1978
|
+
await updated(t => t.phase !== "paused", "the task after an end-call applied from hold");
|
|
1979
|
+
}
|
|
1980
|
+
};
|
|
1962
1981
|
// 4. Hold and resume, where offered.
|
|
1963
1982
|
if (latestTask().phase === "in-progress" && offers("hold")) {
|
|
1964
1983
|
if (await send({ type: call.channel === "chat" ? "pause" : "hold" }) !== undefined) {
|
|
1965
1984
|
if (await updated(t => t.phase === "paused", "the task pausing after hold") !== undefined) {
|
|
1966
|
-
if (
|
|
1985
|
+
if (call.channel === "voice" && offers("endCall"))
|
|
1986
|
+
await endCallRefusedOnHold();
|
|
1987
|
+
// An adapter that ended the agent's part from hold has nothing left to resume; the run goes on from where it put the task.
|
|
1988
|
+
if (latestTask().phase === "paused" && await send({ type: "resume" }) !== undefined) {
|
|
1967
1989
|
await updated(t => t.phase === "in-progress", "the task resuming after resume");
|
|
1968
1990
|
}
|
|
1969
1991
|
}
|
package/dist/validation.js
CHANGED
|
@@ -2758,6 +2758,8 @@ export function validateTaskCommand(command, task, path = "command", taskContext
|
|
|
2758
2758
|
case "end-call":
|
|
2759
2759
|
offered("endCall");
|
|
2760
2760
|
interaction();
|
|
2761
|
+
// Ending my part with the caller on hold leaves them in the hold, with nobody coming back to them.
|
|
2762
|
+
into.require(task.phase !== "paused", "command.endCall.held", path, "the caller is on hold: resume before ending your part");
|
|
2761
2763
|
break;
|
|
2762
2764
|
case "terminate-call":
|
|
2763
2765
|
offered("terminateCall");
|
package/guide/agent.md
CHANGED
|
@@ -384,6 +384,7 @@ Migration from the earlier spellings:
|
|
|
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 desk 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 |
|
|
387
388
|
|
|
388
389
|
Update producers, consumers, saved task snapshots, and validation-rule assertions together.
|
|
389
390
|
History and report rule names use `history` and `historyReport`; assignment rules use
|
|
@@ -1005,7 +1006,7 @@ declared:
|
|
|
1005
1006
|
| Command | What makes it available |
|
|
1006
1007
|
| --- | --- |
|
|
1007
1008
|
| `answer`, `accept` | Nothing. A task that was offered can be accepted, or offering it meant nothing. |
|
|
1008
|
-
| `end-call` | The `endCall` capability: ends my part, the caller continues. |
|
|
1009
|
+
| `end-call` | The `endCall` capability: ends my part, the caller continues. Never from hold: a `paused` task is resumed first (`command.endCall.held`), or the caller is left in the hold. |
|
|
1009
1010
|
| `terminate-call` | The `terminateCall` capability: ends the whole call, every channel on it. |
|
|
1010
1011
|
| `conference` with `action: "remove"` | The `conference` capability, and somebody else on the call: a remove that would leave the agent alone is `end-call`, and a provider answers it `failed`. |
|
|
1011
1012
|
| `decline` | The `decline` capability on any channel, **and** Omni local policy permitting it. One word for refusing an offer, whatever the channel. |
|
|
@@ -1020,7 +1021,8 @@ declared:
|
|
|
1020
1021
|
|
|
1021
1022
|
**A control on the contact belongs to the interaction phases**, `in-progress` and `paused`:
|
|
1022
1023
|
`hold`, `resume` and `pause`, `end-call` and `terminate-call`, every `conference` action, and
|
|
1023
|
-
every `lead-assist` action; `schedule` alone reaches into `completing
|
|
1024
|
+
every `lead-assist` action; `schedule` alone reaches into `completing`, and `end-call` alone stops
|
|
1025
|
+
short of `paused` (`command.endCall.held`), since ending my part from hold leaves the caller in it. These controls act within this agent's current interaction.
|
|
1024
1026
|
In `completing`, that interaction has ended and its wrap work remains. The caller may still be in
|
|
1025
1027
|
an IVR, queue or another agent's interaction, and other channels may remain connected. Completion
|
|
1026
1028
|
of this interaction does not establish that the caller or bridge ended. The capability stays
|
package/guide/voice.md
CHANGED
|
@@ -258,6 +258,13 @@ provider. Two of them end the agent's part; they differ in what happens to the c
|
|
|
258
258
|
conferenced colleague, a lead who joined -- end, and the caller continues on the path the
|
|
259
259
|
provider decides: another IVR, a queue, a survey, or the end of the call. Gated by `endCall`.
|
|
260
260
|
|
|
261
|
+
**Never from hold.** An `end-call` while the task is `paused` would leave the caller in the hold
|
|
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 desk holds the command back while the task is `paused`
|
|
264
|
+
(`command.endCall.held`) and shows End call disabled on hold, and a provider that receives it
|
|
265
|
+
anyway answers `failed`. Resume, then end. `terminate-call` needs no such gate: it ends the
|
|
266
|
+
caller's channel with the rest, held or not, and nobody is left waiting.
|
|
267
|
+
|
|
261
268
|
**`terminate-call` ends the whole call.** Every channel on it ends at the provider: the agent's,
|
|
262
269
|
the caller's, any colleague the agent added, and anyone else on the call. Nobody is left on the
|
|
263
270
|
line. Gated by `terminateCall`, a separate permission, since ending a customer's call is a
|
package/guide.md
CHANGED
|
@@ -2902,7 +2902,7 @@ same exported checks are used by Omni and adapter tests so their interpretations
|
|
|
2902
2902
|
| `validateLoginStore(store)` | The login's store the agent application hands every connection: an object with `get`, `set` and `delete` (`store.shape`, `store.get`, `.set`, `.delete`). The harness holds `ConnectContext.store` to it. |
|
|
2903
2903
|
| `validateCapacity(capacity)` | What the agent application states as capacity: a whole number of zero or more (`capacity.count`), zero being agent application-stopped. The harness states one on connect and two, one and zero after the test, each answered `applied`, and holds any offer to the count in force (`stream.taskOffered.overCapacity`). |
|
|
2904
2904
|
| `validateAuthenticationResult(result, method)` | What `start()` or `complete()` answered: a challenge or a rejection, a login or a rejection. A rejection's failure is held to its rules -- an `omni.` code the contract lists, and `omni.phone-not-permitted` never retryable, since the agent's station is configuration. `validateAuthenticationFailure(failure)` is the same check on a failure alone. |
|
|
2905
|
-
| `validateTaskCommand(command, task?)` | What a command needs to be issuable, against the task it names: its own shape -- a dial's `dialId`, a conference's item, a schedule's time, a remove naming exactly one person -- and, with the task, the capability the table above gates it on (`command.capability.<name>`, `.locked`), the phase it belongs to (`command.phase.*`, `command.phase.interaction` for every control on the call or the conversation), and the state that has to stand: a lead requested, somebody else still on the call (`command.conference.remove.alone`). An agent application validates before sending and an adapter before acting. |
|
|
2905
|
+
| `validateTaskCommand(command, task?)` | What a command needs to be issuable, against the task it names: its own shape -- a dial's `dialId`, a conference's item, a schedule's time, a remove naming exactly one person -- and, with the task, the capability the table above gates it on (`command.capability.<name>`, `.locked`), the phase it belongs to (`command.phase.*`, `command.phase.interaction` for every control on the call or the conversation), and the state that has to stand: a lead requested, somebody else still on the call (`command.conference.remove.alone`), the caller off hold before the agent's part ends (`command.endCall.held`). An agent application validates before sending and an adapter before acting. |
|
|
2906
2906
|
| `validateResult(result, method)` | What a connection method answered: the status it gives, a failure where the status says so and nowhere else, the failure's shape, and that an `omni.` code is one this contract names. |
|
|
2907
2907
|
| `validateAuthenticationState(state)` | The identity each state must carry, the capabilities a usable login declares, and the expiry that only `authenticated` may. Omni applies it to every state a session publishes — the republished as much as the first. |
|
|
2908
2908
|
|
|
@@ -3050,7 +3050,9 @@ interaction phases with `hold` still declared -- in
|
|
|
3050
3050
|
`confirmed`, where the provider publishes it, and in `completing` once this agent's interaction has ended -- the
|
|
3051
3051
|
test sends `hold` past the validator that would hold it back, and expects `failed`: the adapter
|
|
3052
3052
|
is the second gate on a control on the contact, and one that applies it outside this task's
|
|
3053
|
-
current interaction is named (`test.command.interaction`).
|
|
3053
|
+
current interaction is named (`test.command.interaction`). With the task on hold, the test sends `end-call` past the
|
|
3054
|
+
validator the same way and expects `failed`, naming an adapter that ends the agent's part with the caller in the
|
|
3055
|
+
hold (`test.command.held`). The test cannot put a task into a phase the provider
|
|
3054
3056
|
never publishes, so a provider that goes straight from `pending` to `in-progress` is checked in
|
|
3055
3057
|
`completing` alone. A task the agent completes is
|
|
3056
3058
|
completed from wherever it stands once the test has nothing left to do on it -- from `completing`
|