@xema/omni-protocol 0.1.1 → 0.1.3
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 +24 -5
- package/dist/index.js +1 -1
- package/dist/testing.d.ts +11 -4
- package/dist/testing.js +19 -3
- package/dist/validation.js +10 -2
- package/guide.md +70 -9
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -290,6 +290,8 @@ export type TaskCapabilities<C extends Channel = Channel> = C extends "voice" ?
|
|
|
290
290
|
mute?: true;
|
|
291
291
|
hold?: true;
|
|
292
292
|
agentDisconnect?: true;
|
|
293
|
+
/** Reach the party again while `completing`; the task returns to `in-progress`. */
|
|
294
|
+
callback?: true;
|
|
293
295
|
blindTransfer?: true | DestinationDirectory;
|
|
294
296
|
conference?: true | DestinationDirectory;
|
|
295
297
|
recording?: true;
|
|
@@ -375,7 +377,22 @@ export interface TaskHandlingStep {
|
|
|
375
377
|
*/
|
|
376
378
|
by?: UserId;
|
|
377
379
|
}
|
|
378
|
-
|
|
380
|
+
/**
|
|
381
|
+
* How the task ends, and how long after handling the provider allows for it.
|
|
382
|
+
*
|
|
383
|
+
* The allowance is coupled to the mode. Under `provider-automatic` the provider acts on it, so it
|
|
384
|
+
* is required. Under `agent-command` the provider will not complete the task itself, so it may
|
|
385
|
+
* omit the allowance to say it imposes no deadline -- and Omni then counts nothing down. Omitted
|
|
386
|
+
* and `0` are different claims: no deadline to see, and a deadline of now.
|
|
387
|
+
*/
|
|
388
|
+
export type TaskCompletion = {
|
|
389
|
+
completionMode: "agent-command";
|
|
390
|
+
completionAllowance?: DurationSeconds;
|
|
391
|
+
} | {
|
|
392
|
+
completionMode: "provider-automatic";
|
|
393
|
+
completionAllowance: DurationSeconds;
|
|
394
|
+
};
|
|
395
|
+
export type Task<C extends Channel = Channel> = {
|
|
379
396
|
id: TaskId;
|
|
380
397
|
title: string;
|
|
381
398
|
channel: C;
|
|
@@ -387,11 +404,9 @@ export interface Task<C extends Channel = Channel> {
|
|
|
387
404
|
phase: TaskPhase;
|
|
388
405
|
/** The identifier an agent reads back to a customer, where the provider has one. */
|
|
389
406
|
reference?: string;
|
|
390
|
-
completionMode: CompletionMode;
|
|
391
|
-
completionAllowance: DurationSeconds;
|
|
392
407
|
attributes?: TaskAttribute[];
|
|
393
408
|
handlingHistory?: TaskHandlingStep[];
|
|
394
|
-
}
|
|
409
|
+
} & TaskCompletion;
|
|
395
410
|
/** What the provider wants of Omni's acceptance policy for one offer. */
|
|
396
411
|
export type AcceptanceMode = "no-preference" | "require-agent-acceptance" | "require-automatic-acceptance";
|
|
397
412
|
export type TaskOutcome = {
|
|
@@ -413,7 +428,7 @@ export type TaskOutcome = {
|
|
|
413
428
|
failure: ProtocolFailure;
|
|
414
429
|
};
|
|
415
430
|
export declare const TASK_COMMAND_NAMES: {
|
|
416
|
-
readonly voice: readonly ["answer", "decline", "start-call", "mute", "hold", "resume", "disconnect", "transfer", "conference", "recording", "complete"];
|
|
431
|
+
readonly voice: readonly ["answer", "decline", "start-call", "mute", "hold", "resume", "disconnect", "callback", "transfer", "conference", "recording", "complete"];
|
|
417
432
|
readonly chat: readonly ["accept", "reject", "pause", "resume", "complete"];
|
|
418
433
|
readonly email: readonly ["accept", "reject", "complete"];
|
|
419
434
|
};
|
|
@@ -437,6 +452,10 @@ export type VoiceTaskCommand = {
|
|
|
437
452
|
type: "resume";
|
|
438
453
|
} | {
|
|
439
454
|
type: "disconnect";
|
|
455
|
+
}
|
|
456
|
+
/** Issuable only in `completing`, under the `callback` capability. Carries no destination. */
|
|
457
|
+
| {
|
|
458
|
+
type: "callback";
|
|
440
459
|
} | {
|
|
441
460
|
type: "transfer";
|
|
442
461
|
destination: string;
|
package/dist/index.js
CHANGED
|
@@ -55,7 +55,7 @@ export function isAllowedBrowserUrl(url) {
|
|
|
55
55
|
// ---------------------------------------------------------------------------
|
|
56
56
|
export const TASK_COMMAND_NAMES = {
|
|
57
57
|
voice: ["answer", "decline", "start-call", "mute", "hold", "resume", "disconnect",
|
|
58
|
-
"transfer", "conference", "recording", "complete"],
|
|
58
|
+
"callback", "transfer", "conference", "recording", "complete"],
|
|
59
59
|
chat: ["accept", "reject", "pause", "resume", "complete"],
|
|
60
60
|
email: ["accept", "reject", "complete"],
|
|
61
61
|
};
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Adapter, type AuthenticationState, type ProviderEventEnvelope, type Connection, type Snapshot, type
|
|
1
|
+
import { type Adapter, type AuthenticationState, type ProviderEventEnvelope, type Connection, type Snapshot, type TaskCompletion, type BreakApproval, type BrowserSessionKeyInput, type Channel, type ConnectContext, type DialRequest, type TaskCommandRequest } from "./index.js";
|
|
2
2
|
import { type ProtocolViolation } from "./validation.js";
|
|
3
3
|
export { ProtocolConformanceError, assertNoViolations, type ProtocolViolation } from "./validation.js";
|
|
4
4
|
export interface AdapterContractResult {
|
|
@@ -39,16 +39,23 @@ export declare function assertReconnectWithMissedAssignments<C extends Channel>(
|
|
|
39
39
|
* There is no `denied` approval: a refusal returns the agent to `not-requested`, because a
|
|
40
40
|
* pending request nobody is coming to decide is worse than none. So the scenario is a request
|
|
41
41
|
* that goes back to not-requested, and a later one that is granted.
|
|
42
|
+
*
|
|
43
|
+
* A request shows as `awaiting-decision` where a person decides, and as `granted` at once where
|
|
44
|
+
* the provider decides alone; either is the request being made, and the rule is the same for
|
|
45
|
+
* both.
|
|
42
46
|
*/
|
|
43
47
|
export declare function assertDeniedAndRetriedBreak(approvals: readonly BreakApproval[]): void;
|
|
44
48
|
/** Verifies that retrying one dial command cannot place a second call. */
|
|
45
49
|
export declare function assertDialIdempotency(connection: Pick<Connection, "dial">, request: DialRequest): Promise<void>;
|
|
46
50
|
/**
|
|
47
51
|
* Validates the deadline derived from media end and the task's fixed wrap allowance.
|
|
48
|
-
*
|
|
49
|
-
*
|
|
52
|
+
*
|
|
53
|
+
* A task with no allowance has no deadline, so `observedDeadline` must then be `undefined`: a
|
|
54
|
+
* host counting down what the provider left open is the violation, and so is a host counting
|
|
55
|
+
* nothing down when the provider set a clock. `toleranceMs` absorbs scheduler jitter in a real
|
|
56
|
+
* implementation; pass 0 to demand an exact match.
|
|
50
57
|
*/
|
|
51
|
-
export declare function assertWrapTimeout(task: Pick<
|
|
58
|
+
export declare function assertWrapTimeout(task: Pick<TaskCompletion, "completionMode" | "completionAllowance">, mediaEndedAt: string, observedDeadline: string | undefined, toleranceMs?: number): void;
|
|
52
59
|
/** One browser in one task of one provider. `providerId` is `Manifest.id`, never `displayName`. */
|
|
53
60
|
export type BrowserIsolationScenario = BrowserSessionKeyInput;
|
|
54
61
|
/** Validates whether two task-browser definitions should share one browser session. */
|
package/dist/testing.js
CHANGED
|
@@ -209,9 +209,13 @@ export function assertReconnectWithMissedAssignments(before, reconnect, missedTa
|
|
|
209
209
|
* There is no `denied` approval: a refusal returns the agent to `not-requested`, because a
|
|
210
210
|
* pending request nobody is coming to decide is worse than none. So the scenario is a request
|
|
211
211
|
* that goes back to not-requested, and a later one that is granted.
|
|
212
|
+
*
|
|
213
|
+
* A request shows as `awaiting-decision` where a person decides, and as `granted` at once where
|
|
214
|
+
* the provider decides alone; either is the request being made, and the rule is the same for
|
|
215
|
+
* both.
|
|
212
216
|
*/
|
|
213
217
|
export function assertDeniedAndRetriedBreak(approvals) {
|
|
214
|
-
const asked = approvals.
|
|
218
|
+
const asked = approvals.findIndex(approval => approval === "awaiting-decision" || approval === "granted");
|
|
215
219
|
if (asked < 0)
|
|
216
220
|
throw new Error("Break retry scenario requires an initial request");
|
|
217
221
|
const refused = approvals.indexOf("not-requested", asked + 1);
|
|
@@ -245,10 +249,22 @@ export async function assertDialIdempotency(connection, request) {
|
|
|
245
249
|
}
|
|
246
250
|
/**
|
|
247
251
|
* Validates the deadline derived from media end and the task's fixed wrap allowance.
|
|
248
|
-
*
|
|
249
|
-
*
|
|
252
|
+
*
|
|
253
|
+
* A task with no allowance has no deadline, so `observedDeadline` must then be `undefined`: a
|
|
254
|
+
* host counting down what the provider left open is the violation, and so is a host counting
|
|
255
|
+
* nothing down when the provider set a clock. `toleranceMs` absorbs scheduler jitter in a real
|
|
256
|
+
* implementation; pass 0 to demand an exact match.
|
|
250
257
|
*/
|
|
251
258
|
export function assertWrapTimeout(task, mediaEndedAt, observedDeadline, toleranceMs = 1_000) {
|
|
259
|
+
if (task.completionAllowance === undefined) {
|
|
260
|
+
if (observedDeadline !== undefined) {
|
|
261
|
+
throw new Error(`Wrap deadline mismatch: the task states no allowance, so there is no deadline, received ${observedDeadline}`);
|
|
262
|
+
}
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
if (observedDeadline === undefined) {
|
|
266
|
+
throw new Error(`Wrap deadline mismatch: the task allows ${task.completionAllowance}s, but no deadline was observed`);
|
|
267
|
+
}
|
|
252
268
|
const ended = Date.parse(mediaEndedAt);
|
|
253
269
|
const deadline = Date.parse(observedDeadline);
|
|
254
270
|
if (Number.isNaN(ended) || Number.isNaN(deadline))
|
package/dist/validation.js
CHANGED
|
@@ -79,7 +79,7 @@ const ISOLATION_SCHEME_VALUES = Object.values(BROWSER_ISOLATION_SCHEMES);
|
|
|
79
79
|
const TASK_CAPABILITIES = {
|
|
80
80
|
voice: membersOf({
|
|
81
81
|
browsers: true, dispositions: true, custom: true, decline: true, mute: true, hold: true,
|
|
82
|
-
agentDisconnect: true, blindTransfer: true, conference: true, recording: true,
|
|
82
|
+
agentDisconnect: true, callback: true, blindTransfer: true, conference: true, recording: true,
|
|
83
83
|
}),
|
|
84
84
|
chat: membersOf({ browsers: true, dispositions: true, custom: true, reject: true, hold: true }),
|
|
85
85
|
email: membersOf({ browsers: true, dispositions: true, custom: true, reject: true }),
|
|
@@ -517,7 +517,15 @@ function validateTaskInto(task, context, path, into) {
|
|
|
517
517
|
into.filled(task.taskType, "task.taskType", `${path}.taskType`, "a task needs a task type");
|
|
518
518
|
into.oneOf(task.phase, TASK_PHASES, "task.phase", `${path}.phase`);
|
|
519
519
|
into.oneOf(task.completionMode, COMPLETION_MODES, "task.completionMode", `${path}.completionMode`);
|
|
520
|
-
|
|
520
|
+
// The allowance is coupled to the mode: a provider that will complete the task itself is going
|
|
521
|
+
// to act on the allowance, so it must state one; a provider waiting for `complete` may omit it
|
|
522
|
+
// to say it imposes no deadline. Present, it is a duration either way.
|
|
523
|
+
if (task.completionAllowance === undefined) {
|
|
524
|
+
into.require(task.completionMode !== "provider-automatic", "task.completionAllowance.required", `${path}.completionAllowance`, "provider-automatic completion needs an allowance to act on");
|
|
525
|
+
}
|
|
526
|
+
else {
|
|
527
|
+
into.require(isDurationSeconds(task.completionAllowance), "task.completionAllowance", `${path}.completionAllowance`, "completionAllowance must be a whole number of seconds, zero or more, or omitted under agent-command");
|
|
528
|
+
}
|
|
521
529
|
// The channel is fixed per provider by its manifest, so a task claiming another one is a
|
|
522
530
|
// task Omni would render with the wrong controls.
|
|
523
531
|
into.require(task.channel === context.channel, "task.channel", `${path}.channel`, `a ${context.channel} provider may not publish a ${String(task.channel)} task`);
|
package/guide.md
CHANGED
|
@@ -335,6 +335,7 @@ type TaskCapabilities<C extends Channel = Channel> =
|
|
|
335
335
|
mute?: true;
|
|
336
336
|
hold?: true;
|
|
337
337
|
agentDisconnect?: true;
|
|
338
|
+
callback?: true;
|
|
338
339
|
blindTransfer?: true | DestinationDirectory;
|
|
339
340
|
conference?: true | DestinationDirectory;
|
|
340
341
|
recording?: true;
|
|
@@ -416,6 +417,10 @@ type TaskHandlingStep = {
|
|
|
416
417
|
by?: UserId;
|
|
417
418
|
};
|
|
418
419
|
|
|
420
|
+
type TaskCompletion =
|
|
421
|
+
| { completionMode: "agent-command"; completionAllowance?: DurationSeconds }
|
|
422
|
+
| { completionMode: "provider-automatic"; completionAllowance: DurationSeconds };
|
|
423
|
+
|
|
419
424
|
type Task<C extends Channel = Channel> = {
|
|
420
425
|
id: TaskId;
|
|
421
426
|
title: string;
|
|
@@ -426,11 +431,9 @@ type Task<C extends Channel = Channel> = {
|
|
|
426
431
|
contact?: Contact;
|
|
427
432
|
phase: TaskPhase;
|
|
428
433
|
reference?: string;
|
|
429
|
-
completionMode: CompletionMode;
|
|
430
|
-
completionAllowance: DurationSeconds;
|
|
431
434
|
attributes?: TaskAttribute[];
|
|
432
435
|
handlingHistory?: TaskHandlingStep[];
|
|
433
|
-
};
|
|
436
|
+
} & TaskCompletion;
|
|
434
437
|
|
|
435
438
|
type AcceptanceMode =
|
|
436
439
|
| "no-preference"
|
|
@@ -457,6 +460,7 @@ const TASK_COMMAND_NAMES = {
|
|
|
457
460
|
"hold",
|
|
458
461
|
"resume",
|
|
459
462
|
"disconnect",
|
|
463
|
+
"callback",
|
|
460
464
|
"transfer",
|
|
461
465
|
"conference",
|
|
462
466
|
"recording",
|
|
@@ -479,6 +483,7 @@ type VoiceTaskCommand =
|
|
|
479
483
|
| { type: "hold" }
|
|
480
484
|
| { type: "resume" }
|
|
481
485
|
| { type: "disconnect" }
|
|
486
|
+
| { type: "callback" }
|
|
482
487
|
| { type: "transfer"; destination: string }
|
|
483
488
|
| { type: "conference"; participant: string; action: "add" | "remove" }
|
|
484
489
|
| { type: "recording"; action: "start" | "pause" | "resume" | "stop" }
|
|
@@ -1616,7 +1621,7 @@ time. Runtime conformance checks also require the task channel to match its prov
|
|
|
1616
1621
|
| `phase` | Current canonical task phase: `pending`, `confirmed`, `preparing`, `in-progress`, `paused`, or `completing`. |
|
|
1617
1622
|
| `reference` | Optional agent-facing reference such as a case, call, conversation, ticket, or message number. It is distinct from the protocol `id`. |
|
|
1618
1623
|
| `completionMode` | `agent-command` waits for the channel's `complete` command; `provider-automatic` completes without one. |
|
|
1619
|
-
| `completionAllowance` | Fixed time allowed to complete the task after primary handling ends. For real-time media, it begins after `task-media-ended`. |
|
|
1624
|
+
| `completionAllowance` | 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. |
|
|
1620
1625
|
| `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. |
|
|
1621
1626
|
| `handlingHistory` | Optional ordered handling history for this currently open task. It is live task data, not a permanent archive. |
|
|
1622
1627
|
|
|
@@ -1656,6 +1661,7 @@ The canonical task transitions are:
|
|
|
1656
1661
|
| `in-progress` | Provider or agent pauses the task | `paused` |
|
|
1657
1662
|
| `paused` | Provider or agent resumes the task | `in-progress` |
|
|
1658
1663
|
| `in-progress` or `paused` | Contact handling ends and follow-up work remains | `completing` |
|
|
1664
|
+
| `completing` | Agent calls the party back (`callback`) | `in-progress` |
|
|
1659
1665
|
| Any phase | Provider emits `task-ended` | Removed |
|
|
1660
1666
|
|
|
1661
1667
|
Allocation, acceptance, and progress are distinct. Acceptance follows `autoAcceptTasks` and the
|
|
@@ -1690,9 +1696,61 @@ In this example, the agent has two minutes after sending the email to add notes,
|
|
|
1690
1696
|
disposition, and complete the task.
|
|
1691
1697
|
|
|
1692
1698
|
`0` means completion may happen immediately. With `provider-automatic`, the provider may complete
|
|
1693
|
-
without waiting for a command; with `agent-command`, it still waits for `complete`.
|
|
1694
|
-
|
|
1695
|
-
|
|
1699
|
+
without waiting for a command; with `agent-command`, it still waits for `complete`.
|
|
1700
|
+
|
|
1701
|
+
There is no value meaning "unlimited", because a number that is not a duration would be read as
|
|
1702
|
+
one. A provider that imposes no deadline says so by **omitting** `completionAllowance`, which
|
|
1703
|
+
only `agent-command` permits: the provider will not complete the task itself, so there is nothing
|
|
1704
|
+
for a deadline to trigger, and Omni counts nothing down. **Omitted and empty are different
|
|
1705
|
+
claims** applies -- omitted says there is no deadline to see, where `0` says the deadline is now.
|
|
1706
|
+
Under `provider-automatic` the field is required, because the provider is going to act on it.
|
|
1707
|
+
|
|
1708
|
+
```ts
|
|
1709
|
+
const untimedWrap = {
|
|
1710
|
+
completionMode: "agent-command",
|
|
1711
|
+
} satisfies Pick<Task<"voice">, "completionMode" | "completionAllowance">;
|
|
1712
|
+
```
|
|
1713
|
+
|
|
1714
|
+
Here the customer has hung up, `task-media-ended` has been sent on time, the task is `completing`,
|
|
1715
|
+
and the agent takes as long as the work needs. Moving the task to `completing` late to avoid a
|
|
1716
|
+
deadline is not an alternative on a media channel: the clock starts at a real event, and delaying
|
|
1717
|
+
that event would falsify the phase and everything timed from it.
|
|
1718
|
+
|
|
1719
|
+
#### Calling back during completion
|
|
1720
|
+
|
|
1721
|
+
A task that declares `callback` lets the agent reach the party again while the task is
|
|
1722
|
+
`completing` -- to finish what the call left unfinished, on the same task rather than a new one.
|
|
1723
|
+
Omni issues `{ type: "callback" }`; it is issuable only in `completing`, and only where the
|
|
1724
|
+
capability is declared. The provider knows who the party is; the command carries no destination.
|
|
1725
|
+
|
|
1726
|
+
On `applied` the provider is placing the call and the task returns to `in-progress`: the agent is
|
|
1727
|
+
working again, and the completion allowance is **discarded, not paused**. From there the call is
|
|
1728
|
+
reported as any call is -- `paused`, `in-progress`, and when its media ends, `task-media-ended`
|
|
1729
|
+
again, which starts a fresh allowance from that instant. A party who does not answer is a call
|
|
1730
|
+
whose media ended: the task returns to `completing` through the same event and the clock starts
|
|
1731
|
+
again from there. At no point is an agent dialling against a deadline.
|
|
1732
|
+
|
|
1733
|
+
**The control exists only while there is a window to use it in.** Under `agent-command` the task
|
|
1734
|
+
stays `completing` until the agent completes it, so the window is open for as long as they need.
|
|
1735
|
+
Under `provider-automatic` the window is the allowance -- and with `completionAllowance: 0` there
|
|
1736
|
+
is none: the provider disposes the task at provider end, and Omni does not offer Call back, whatever
|
|
1737
|
+
the task declares. A capability names a control that can be used; on a task with no `completing`
|
|
1738
|
+
window it cannot, and declaring it there changes nothing.
|
|
1739
|
+
|
|
1740
|
+
```ts
|
|
1741
|
+
const callbackCapable = {
|
|
1742
|
+
channel: "voice",
|
|
1743
|
+
capabilities: { hold: true, callback: true, dispositions: true },
|
|
1744
|
+
phase: "completing",
|
|
1745
|
+
completionMode: "provider-automatic",
|
|
1746
|
+
completionAllowance: 30,
|
|
1747
|
+
} satisfies Pick<Task<"voice">, "channel" | "capabilities" | "phase" | "completionMode" | "completionAllowance">;
|
|
1748
|
+
```
|
|
1749
|
+
|
|
1750
|
+
With ten seconds of the thirty left, the agent presses Call back: `execute({ command: { type:
|
|
1751
|
+
"callback" } })` returns `applied`, the task is `in-progress`, and the thirty seconds are gone.
|
|
1752
|
+
The second call ends: `task-media-ended`, the task is `completing`, and a new thirty seconds runs
|
|
1753
|
+
from that instant.
|
|
1696
1754
|
|
|
1697
1755
|
```ts
|
|
1698
1756
|
const immediateProviderCompletion = {
|
|
@@ -1887,6 +1945,7 @@ const taskCapabilities = {
|
|
|
1887
1945
|
| `mute` | Primary toggle: Mute | Omni may mute and unmute the agent's outbound audio. |
|
|
1888
1946
|
| `hold` | Primary toggle: Hold | Omni may issue voice-task `hold` and `resume` commands. |
|
|
1889
1947
|
| `agentDisconnect` | Primary button: Disconnect | Omni may disconnect real-time media without disposing the task. |
|
|
1948
|
+
| `callback` | Completing-task button: Call back | Omni may have the provider call the task's party back while the task is `completing`, returning it to `in-progress` on the same task. Not offered where there is no `completing` window: `provider-automatic` with a zero allowance disposes at provider end. See **Calling back during completion**. |
|
|
1890
1949
|
| `blindTransfer` | Secondary menu item: Blind transfer | Omni may transfer the caller directly to a destination. |
|
|
1891
1950
|
| `conference` | Secondary button: Conference | Omni may add or remove participants from the active call. |
|
|
1892
1951
|
| `recording` | Overflow menu item: Recording | Omni may expose start, pause, resume, and stop recording controls. |
|
|
@@ -2601,6 +2660,7 @@ declared:
|
|
|
2601
2660
|
| `decline`, `reject` | The channel's decline or reject capability, **and** Omni provisioning permitting rejection. |
|
|
2602
2661
|
| `start-call` | The `preparing` phase. It starts the contact a preview gave the agent time to read, so the phase is the gate and there is no capability. |
|
|
2603
2662
|
| `complete` | `completionMode: "agent-command"`. The `dispositions` capability decides whether a code travels with the command, never whether the command exists — a task Omni cannot complete never ends. |
|
|
2663
|
+
| `callback` | The `callback` capability **and** the `completing` phase. It exists to reach the party again after the call, so it has no meaning while the call is up. |
|
|
2604
2664
|
| Everything else | Its own named capability. |
|
|
2605
2665
|
|
|
2606
2666
|
Declining or rejecting a pending offer ends it without accepting or completing it. The provider
|
|
@@ -2666,7 +2726,8 @@ They are published as `OMNI_FAILURE_CODES`.
|
|
|
2666
2726
|
#### Provider instants are read against a provider clock
|
|
2667
2727
|
|
|
2668
2728
|
Every deadline in this contract is a provider instant that Omni counts down: `allocationExpiresAt`,
|
|
2669
|
-
`preparationEndsAt`, and the wrap deadline of `task-media-ended` plus `completionAllowance
|
|
2729
|
+
`preparationEndsAt`, and the wrap deadline of `task-media-ended` plus `completionAllowance` where
|
|
2730
|
+
one is stated.
|
|
2670
2731
|
Comparing those against the host clock is wrong by whatever the two machines disagree by, and the
|
|
2671
2732
|
damaging direction is early — **Accept** withdrawn from an offer still ringing, a wrap timer
|
|
2672
2733
|
expiring before the agent has finished.
|
|
@@ -2913,7 +2974,7 @@ cannot be established from TypeScript structure alone.
|
|
|
2913
2974
|
| `assertDeniedAndRetriedBreak(states)` | A denial transitions directly to `not-requested`; a later request can still be granted. |
|
|
2914
2975
|
| `assertCommandIdempotency(connection, request)` | Retrying a task command does not repeat its side effect. |
|
|
2915
2976
|
| `assertDialIdempotency(connection, request)` | Retrying a dial command does not place another call. |
|
|
2916
|
-
| `assertWrapTimeout(task, mediaEndedAt, deadline, toleranceMs?)` | The wrap deadline equals media end plus the task allowance, within a tolerance that defaults to 1000ms. |
|
|
2977
|
+
| `assertWrapTimeout(task, mediaEndedAt, deadline, toleranceMs?)` | The wrap deadline equals media end plus the task allowance, within a tolerance that defaults to 1000ms; a task with no allowance has no deadline, and one observed is the violation. |
|
|
2917
2978
|
| `assertBrowserIsolationAndReuse(left, right, expected)` | Browser reuse follows only the declared isolation scheme. |
|
|
2918
2979
|
| `assertNoBrowserSessionKeyCollisions(scenarios)` | No two distinct scenarios derive the same session key. Feed it adversarial names. |
|
|
2919
2980
|
|