@xema/omni-protocol 0.1.81 → 0.1.83
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 +60 -22
- package/dist/index.js +1 -1
- package/dist/testing.d.ts +9 -6
- package/dist/testing.js +67 -56
- package/dist/validation.d.ts +36 -1
- package/dist/validation.js +360 -80
- package/guide.md +453 -97
- package/package.json +1 -1
package/guide.md
CHANGED
|
@@ -58,7 +58,7 @@ transport, a task, a break and a call each have a word of their own:
|
|
|
58
58
|
|
|
59
59
|
### `OMNI_PROTOCOL_VERSION`
|
|
60
60
|
|
|
61
|
-
The exact protocol version implemented by this package. The current value
|
|
61
|
+
The exact protocol version implemented by this package. The current value remains `1` during pre-release development. Recording contract changes do not introduce a new protocol version. Hosts and adapters adopt the current declarations together; this does not provide compatibility mapping for older recording shapes.
|
|
62
62
|
|
|
63
63
|
### `Manifest.supportedProtocolVersions`
|
|
64
64
|
|
|
@@ -189,6 +189,8 @@ type Manifest<C extends Channel = Channel> = {
|
|
|
189
189
|
supportedProtocolVersions: number[];
|
|
190
190
|
authenticationMethods: AuthenticationMethod[];
|
|
191
191
|
idleCapabilities?: IdleCapabilities<C>;
|
|
192
|
+
timeCheck?: true;
|
|
193
|
+
timestampAuthority?: "provider";
|
|
192
194
|
phaseLabels?: TaskPhaseLabels;
|
|
193
195
|
taskTypePresentation?: Record<string, TaskTypePresentation>;
|
|
194
196
|
orgLevels?: LevelDeclaration[];
|
|
@@ -292,13 +294,12 @@ type UrlVisibility = "full" | "domain" | "hidden";
|
|
|
292
294
|
type RecordingSource = "provider" | "host";
|
|
293
295
|
type RecordingAction = "start" | "pause" | "resume" | "stop" | "cancel";
|
|
294
296
|
const RECORDING_ACTIONS = ["start", "pause", "resume", "stop", "cancel"] as const satisfies readonly RecordingAction[];
|
|
295
|
-
type RecordingCancelEffect = "retain" | "discard";
|
|
296
297
|
interface RecordingActions {
|
|
297
298
|
start?: true;
|
|
298
299
|
pause?: true;
|
|
299
300
|
resume?: true;
|
|
300
301
|
stop?: true;
|
|
301
|
-
cancel?:
|
|
302
|
+
cancel?: true;
|
|
302
303
|
}
|
|
303
304
|
interface TaskRecordingPolicy {
|
|
304
305
|
provider?: RecordingActions;
|
|
@@ -316,9 +317,8 @@ type RecordingCommand = {
|
|
|
316
317
|
requestId: string;
|
|
317
318
|
observationId: string;
|
|
318
319
|
} & (
|
|
319
|
-
| { action: "start"; recordingId?: never
|
|
320
|
-
| { action: "pause" | "resume" | "stop"; recordingId: string
|
|
321
|
-
| { action: "cancel"; recordingId: string; cancelEffect: RecordingCancelEffect }
|
|
320
|
+
| { action: "start"; recordingId?: never }
|
|
321
|
+
| { action: "pause" | "resume" | "stop" | "cancel"; recordingId: string }
|
|
322
322
|
);
|
|
323
323
|
interface HostRecordingReport {
|
|
324
324
|
taskId: TaskId;
|
|
@@ -326,8 +326,8 @@ interface HostRecordingReport {
|
|
|
326
326
|
state: RecordingState;
|
|
327
327
|
}
|
|
328
328
|
interface HostRecording {
|
|
329
|
+
announcesToCaller?: true;
|
|
329
330
|
actions: RecordingAction[];
|
|
330
|
-
cancelEffects: RecordingCancelEffect[];
|
|
331
331
|
destinationIds: string[];
|
|
332
332
|
execute(request: HostRecordingRequest): Promise<RecordingCommandResult>;
|
|
333
333
|
}
|
|
@@ -352,7 +352,15 @@ type HostGuarantees = {
|
|
|
352
352
|
personConsent?: true;
|
|
353
353
|
};
|
|
354
354
|
|
|
355
|
+
type ProviderTimeScope = { providerId: string; loginId: string };
|
|
356
|
+
type ProviderTimeEstimate = ProviderTimeScope & {
|
|
357
|
+
at: IsoTimestamp;
|
|
358
|
+
clockId: string;
|
|
359
|
+
uncertaintyMs?: number;
|
|
360
|
+
};
|
|
361
|
+
|
|
355
362
|
type Host = {
|
|
363
|
+
estimateProviderTime?(scope: ProviderTimeScope): ProviderTimeEstimate | undefined;
|
|
356
364
|
recording?: HostRecording;
|
|
357
365
|
guarantees: HostGuarantees;
|
|
358
366
|
mute?: HostMute;
|
|
@@ -460,7 +468,7 @@ type HandlingReport = { taskId: TaskId; allocationId: AllocationId; at: IsoTimes
|
|
|
460
468
|
);
|
|
461
469
|
|
|
462
470
|
type HandlingReportResult =
|
|
463
|
-
| { status: "recorded" }
|
|
471
|
+
| { status: "recorded"; at: IsoTimestamp }
|
|
464
472
|
| { status: "failed"; failure: ProtocolFailure };
|
|
465
473
|
|
|
466
474
|
type PreferenceResult =
|
|
@@ -741,7 +749,7 @@ type Task<C extends Channel = Channel> = {
|
|
|
741
749
|
: { recording?: never; onCall?: never; leadAssist?: never; assisting?: never; monitoring?: never; media?: never }
|
|
742
750
|
);
|
|
743
751
|
|
|
744
|
-
type PreviewDeadline = "calls" | "
|
|
752
|
+
type PreviewDeadline = "calls" | "host-calls" | "waits";
|
|
745
753
|
|
|
746
754
|
type AcceptanceMode =
|
|
747
755
|
| "no-preference"
|
|
@@ -1130,7 +1138,22 @@ type Refusal = {
|
|
|
1130
1138
|
violations: ProtocolViolation[];
|
|
1131
1139
|
};
|
|
1132
1140
|
|
|
1141
|
+
type ProviderTimeCheckPolicy = {
|
|
1142
|
+
intervalMs: number;
|
|
1143
|
+
timeoutMs: number;
|
|
1144
|
+
maxRoundTripMs: number;
|
|
1145
|
+
maxSampleAgeMs: number;
|
|
1146
|
+
};
|
|
1147
|
+
type ProviderTimeCheckRequest = { requestId: string };
|
|
1148
|
+
type ProviderTimeCheckResult = {
|
|
1149
|
+
requestId: string;
|
|
1150
|
+
loginId: string;
|
|
1151
|
+
clockId: string;
|
|
1152
|
+
providerTime: IsoTimestamp;
|
|
1153
|
+
};
|
|
1154
|
+
|
|
1133
1155
|
type Connection<C extends Channel = Channel> = {
|
|
1156
|
+
checkTime?(request: ProviderTimeCheckRequest): Promise<ProviderTimeCheckResult>;
|
|
1134
1157
|
snapshot(): Snapshot<C> | Promise<Snapshot<C>>;
|
|
1135
1158
|
subscribe(listener: (envelope: ProviderEventEnvelope<C>) => void): Unsubscribe;
|
|
1136
1159
|
refused(report: Refusal): void;
|
|
@@ -1585,6 +1608,8 @@ compile time.
|
|
|
1585
1608
|
| `orgLevels` | The organisation's whole ladder as the provider calls it, each level with the label a desk shows for "who decided". Stated outright, `person` included: what it leaves out does not exist. Omitted for the typical four, `DEFAULT_LEVELS`. See **Who decides what an agent may do**. |
|
|
1586
1609
|
| `phones` | Voice only, and required there: the phones this platform can put an agent on, `softphone` (the call's audio lands in the host) and/or `deskPhone` (a handset the platform rings; the host shows the call and opens nothing). The host picks one per login. See **How the agent hears the call**. |
|
|
1587
1610
|
| `dialOutcomes` | Voice only. How a dial can end on this platform, as it distinguishes them: `answered` and at least one way of not reaching the destination. Required of a provider that dials at all — an idle dialpad, or tasks that transfer, conference or call back — and a `dial-outcome` carries only a declared member. See **Every dial has an outcome**. |
|
|
1611
|
+
| `timeCheck` | Optional `true`: implements `checkTime` for fresh provider-clock samples; host polling is independently opt-in. |
|
|
1612
|
+
| `timestampAuthority` | Optional `"provider"`: provider timestamps are final; host instants are advisory. Omission makes no trust promise. |
|
|
1588
1613
|
| `runningStepReports` | The provider takes running reports of a host-performed step — `recordStep` with `seconds` so far and no `ended`. Omitted, the host sends exactly two reports per leg, when it began and when it ended, and a running one is refused. See **The host records what it performs**. |
|
|
1589
1614
|
| `disposalSettleMs` | Required. How long after an applied disposal -- `complete`, or a lead's `take-over` -- the provider's `task-ended` is owed, a positive whole number of milliseconds (`manifest.disposalSettleMs`). A warm transfer's `complete` is not a disposal: the agent's wrap runs after it. Stated per provider, since platforms settle at different speeds. See **`task-ended`**. |
|
|
1590
1615
|
|
|
@@ -2120,6 +2145,7 @@ surface in one place, and what obliges an adapter to implement each one.
|
|
|
2120
2145
|
| Method | Implement it when |
|
|
2121
2146
|
| --- | --- |
|
|
2122
2147
|
| `snapshot()` | Always. |
|
|
2148
|
+
| `checkTime(request)` | The manifest declares `timeCheck: true`. Read-only clock check; no task command or accuracy guarantee. |
|
|
2123
2149
|
| `subscribe(listener)` | Always. |
|
|
2124
2150
|
| `disconnect()` | Always. |
|
|
2125
2151
|
| `refused(report)` | Always. The host tells the adapter what it would not take -- a snapshot it did not replace its state with, an event it dropped -- with every rule broken, so a refusal is visible on both sides. See **What the host does with what it refuses**. |
|
|
@@ -2355,10 +2381,10 @@ time. Runtime conformance checks also require the task channel to match its prov
|
|
|
2355
2381
|
| `browsers` | Named browser definitions for the task workspace: at least one when the task declares the `browsers` capability, empty when it does not. |
|
|
2356
2382
|
| `party` | The person or entity on the other end of this task, as a `Contact`: often a name and one address; a withheld caller ID may leave nothing to send at all. Optional. The party is who the task is *with*; `contacts` is the directory. |
|
|
2357
2383
|
| `phase` | Current canonical task phase: `pending`, `confirmed`, `preview`, `in-progress`, `paused`, or `completing`. `preview` is voice only. |
|
|
2358
|
-
| `media` | Voice only. The task's real-time audio as the provider holds it: `started` while audio is attached, `ended` once it ended, omitted while none is. The provider's word — see **`task-media-started`**. Media names a task whose work has begun, or whose party the host is dialling: on a `pending`, `confirmed` or `preview` task with nobody ringing it is refused (`task.media.beforeWork`), on a snapshot as on the event, since a host opens the microphone on it; with the party ringing by a host dial, ring-back
|
|
2384
|
+
| `media` | Voice only. The task's real-time audio as the provider holds it: `started` while audio is attached, `ended` once it ended, omitted while none is. The provider's word — see **`task-media-started`**. Media names a task whose work has begun, or whose party the host is dialling: on a `pending`, `confirmed` or `preview` task with nobody ringing it is refused (`task.media.beforeWork`), on a snapshot as on the event, since a host opens the microphone on it; with the party ringing by a host dial or provider-triggered preview dial, actual ring-back media may precede answer. |
|
|
2359
2385
|
| `acceptance` | How this offer is accepted — `no-preference`, `consent`, or `automatic` — stated on the pending task so a reconnect snapshot says it too. Required while `pending` when `autoAcceptTasks` was `true`, forbidden when it was `false`, and absent past `pending`. See **Acceptance modes**. |
|
|
2360
|
-
| `previewEndsAt` | Voice only, in `preview`:
|
|
2361
|
-
| `atDeadline` | Voice only, in `preview`, with `previewEndsAt`: what the system does at the deadline -- `calls`
|
|
2386
|
+
| `previewEndsAt` | Voice only, in `preview`: the preparation target instant. Absent, the agent has as long as they need without a preparation countdown. Always with `atDeadline`. See **Preview: the agent presses Call**. |
|
|
2387
|
+
| `atDeadline` | Voice only, in `preview`, with `previewEndsAt`: what the system does at the deadline -- `calls` makes the provider initiate dialing, `host-calls` makes the host issue Call, `waits` keeps the task in preview awaiting the agent. |
|
|
2362
2388
|
| `reference` | Optional agent-facing reference such as a case, call, conversation, ticket, or message number. It is distinct from the protocol `id`. |
|
|
2363
2389
|
| `completionMode` | `agent-command` waits for the channel's `complete` command; `provider-automatic` completes without one. |
|
|
2364
2390
|
| `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. |
|
|
@@ -2401,7 +2427,7 @@ The canonical task transitions are:
|
|
|
2401
2427
|
| `confirmed` | Work begins | `in-progress` |
|
|
2402
2428
|
| `preview` | Agent presses Call (`call`) and the customer answers, or the deadline `calls` and they answer | `in-progress` |
|
|
2403
2429
|
| `preview` | The call goes out and nobody answers | `completing` |
|
|
2404
|
-
| `preview` | The
|
|
2430
|
+
| `preview` | The preparation target passes with `atDeadline: "waits"` | Remains `preview`, waiting for the agent to press Call |
|
|
2405
2431
|
| `pending` | Provider withdraws the allocation, the agent declines, or the party abandons the ring | Removed by `task-ended` with `cancelled` outcome, `by` saying which |
|
|
2406
2432
|
| `pending` | The offer lapses at `allocationExpiresAt` | Removed by `task-ended` with `expired` outcome naming `pending` |
|
|
2407
2433
|
| No task | Snapshot reports work already underway | `in-progress` |
|
|
@@ -2439,8 +2465,9 @@ command.
|
|
|
2439
2465
|
#### Preview: the agent presses Call
|
|
2440
2466
|
|
|
2441
2467
|
An outbound campaign that lets the agent see who they are about to call is a **preview**: the
|
|
2442
|
-
record arrives as a task
|
|
2443
|
-
out. The agent
|
|
2468
|
+
record arrives as a task and sits in `preview` with the party on it before a call goes
|
|
2469
|
+
out. The agent prepares; manual or declared automatic initiation may then begin dialing. It
|
|
2470
|
+
remains preview while ringing until an evidenced answer or non-answer outcome. The agent may press Call. That is the whole phase, and it exists on voice alone --
|
|
2444
2471
|
chat and email have no call to place, and their reading is ordinary work in `in-progress`.
|
|
2445
2472
|
|
|
2446
2473
|
```ts
|
|
@@ -2468,14 +2495,51 @@ no-answer as the disposition it is -- in a campaign that is the commonest outcom
|
|
|
2468
2495
|
is work, not a cancellation. A `preview` task therefore needs a manifest that says how a dial ends
|
|
2469
2496
|
(`task.preview.dialOutcomes.required`).
|
|
2470
2497
|
|
|
2471
|
-
**
|
|
2472
|
-
`previewEndsAt
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2498
|
+
**Preparation and trigger ownership are per task.** Unlimited preparation omits both
|
|
2499
|
+
`previewEndsAt` and `atDeadline`: only an agent pressing Call starts dialing. Fixed preparation
|
|
2500
|
+
carries the provider's authoritative preparation-end instant and exactly one trigger owner:
|
|
2501
|
+
|
|
2502
|
+
| Declaration | At preparation end |
|
|
2503
|
+
| --- | --- |
|
|
2504
|
+
| `atDeadline: "calls"` | The provider initiates dialing. The host never sends a timer-triggered Call. |
|
|
2505
|
+
| `atDeadline: "host-calls"` | The host sends the ordinary `call` command with a fresh host `dialId`. The provider does not independently auto-dial this task. |
|
|
2506
|
+
| `atDeadline: "waits"` | Neither side auto-dials. The task remains in preview until the agent presses Call. |
|
|
2507
|
+
|
|
2508
|
+
The agent may press Call early in either fixed-preparation dialing mode, or at any time in
|
|
2509
|
+
`waits` mode. With `waits`, the countdown is a preparation target, not an expiry: when it reaches
|
|
2510
|
+
zero the UI says "Waiting for agent" and keeps Call available. There is no automatic task-ended,
|
|
2511
|
+
disposal, dialing or phase change. No repeating timer action occurs on later snapshots. This differs
|
|
2512
|
+
from unlimited preparation only by displaying a preparation target. The former `expires` deadline
|
|
2513
|
+
value is not supported; elapsed preparation must not withdraw the task. For `calls` and `host-calls`, the deadline ends
|
|
2514
|
+
preparation and triggers initiation; it does not promise ringing, playable audio or customer
|
|
2515
|
+
answer at that exact instant. Scheduling/dispatch delay must remain visible, and a failed or
|
|
2516
|
+
prevented initiation must be reported rather than leaving a silently expired countdown.
|
|
2517
|
+
A source eligibility threshold that may never trigger an attempt is not this promise.
|
|
2518
|
+
|
|
2519
|
+
The two deadline fields travel together and only in preview. The host uses trusted provider-domain
|
|
2520
|
+
time; clock estimation is allowed only under the explicit host-triggered preview exception below.
|
|
2521
|
+
Without usable provider-domain time
|
|
2522
|
+
it cannot safely auto-trigger and must expose the uncertainty and reconcile. No zero-duration,
|
|
2523
|
+
receipt-time or local-default deadline is inferred. Neither elapsed time nor submission changes
|
|
2524
|
+
the task phase: subsequent authoritative events state ringing, answer, media and completion.
|
|
2525
|
+
|
|
2526
|
+
A host must serialize manual clicks and its timer under the exact provider/login/task/allocation
|
|
2527
|
+
scope, allowing at most one unresolved submission. Recheck the current phase, deadline owner,
|
|
2528
|
+
allocation and whether an attempt already exists before dispatch. A new snapshot or policy update
|
|
2529
|
+
cancels an obsolete timer; login loss, disconnect, task end and allocation replacement fence its
|
|
2530
|
+
callback. Reconnect past a deadline first reconciles current task and attempt state; it must not
|
|
2531
|
+
blindly submit again. An uncertain prior submission remains unresolved, without automatic retry.
|
|
2532
|
+
The provider atomically arbitrates any residual manual/timer race and does not accept a second
|
|
2533
|
+
command as ownership of an already-running independent attempt.
|
|
2534
|
+
|
|
2535
|
+
Host-triggered automatic dialing is an ordinary host dial: its result is `dialling` with the same
|
|
2536
|
+
host `dialId`, followed by exactly one correlated terminal `dial-outcome`. A provider-triggered
|
|
2537
|
+
dial has no invented host ID or host dial outcome. It reports source-evidenced party ringing,
|
|
2538
|
+
actual media and customer answer separately. In preview with `atDeadline: "calls"`, a ringing
|
|
2539
|
+
party without a host ID may carry actual pre-answer media; the deadline alone permits no media.
|
|
2540
|
+
Media is still introduced/ended by the corresponding events and must not imply customer answer.
|
|
2541
|
+
Submission acceptance is never an answered outcome or permission to publish `in-progress` early.
|
|
2542
|
+
Unsupported source correlation/outcome/media evidence remains an integration blocker.
|
|
2479
2543
|
|
|
2480
2544
|
**Drop both fields when the phase moves.** A provider that builds the `in-progress` task by
|
|
2481
2545
|
spreading the `preview` one carries `previewEndsAt` and `atDeadline` with it, and the host refuses
|
|
@@ -2484,6 +2548,33 @@ provider is told through `refused` exactly which rule, so the state that looked
|
|
|
2484
2548
|
is named on its side. The task past preview has no deadline to wait for, so it carries neither
|
|
2485
2549
|
field.
|
|
2486
2550
|
|
|
2551
|
+
**Provider execution and host controls are distinct.** Answer, hold/resume, transfer (including
|
|
2552
|
+
IVR routing), conference and handling completion are executed by the provider
|
|
2553
|
+
through the adapter. The task's applicable capability, phase, completion mode and command-specific
|
|
2554
|
+
prerequisites decide which controls are offered. Caller disconnect also executes at the provider. Microphone mute is host-owned, never a provider
|
|
2555
|
+
TaskCommand or task capability. The separately declared host recording facility keeps its existing
|
|
2556
|
+
recording contract; this distinction does not remove it or move provider call operations to the host.
|
|
2557
|
+
|
|
2558
|
+
A caller journey may revisit the same agent while an earlier handling still wraps. Public task
|
|
2559
|
+
IDs must remain distinct for concurrently open handlings. For source-owned handling IDs allocated
|
|
2560
|
+
before offer and never reused per assignment, both public task ID and allocation ID may equal that
|
|
2561
|
+
handling ID. Restore them unchanged on reconnect. Commands, media, endings and handling reports
|
|
2562
|
+
retain the exact pair; never retarget an old command to the newest handling of a journey.
|
|
2563
|
+
`validateTaskCommandRequest` checks the request's pair against the supplied published task and
|
|
2564
|
+
then its command prerequisites. The caller must select that task within the correct provider/login
|
|
2565
|
+
and recheck at the provider; the validator cannot prove source freshness or authorization.
|
|
2566
|
+
`task-ended` retires only that handling's resources. Another handling, caller channel or IVR/queue
|
|
2567
|
+
portion must not be cleared because this task ended. Snapshot counts include all still-owned wrap
|
|
2568
|
+
and active handlings; task disposal is not caller hangup.
|
|
2569
|
+
|
|
2570
|
+
**Voice describes the agent's handling within the wider call.** The task defines that agent's
|
|
2571
|
+
workspace, tools, permissions, media participation and completion work. The caller's channel may
|
|
2572
|
+
continue through IVR, queues, other agents, holds or conferences while this handling ends or wraps.
|
|
2573
|
+
Disposing this task ends this handling responsibility; it is not evidence that the caller's channel
|
|
2574
|
+
or journey ended. Task media describes this agent's attachment, not the lifetime of every party's
|
|
2575
|
+
connection. Commands retain their explicit targets and effects; task disposal must not silently
|
|
2576
|
+
become a caller-disconnect operation.
|
|
2577
|
+
|
|
2487
2578
|
**A task is never its audio.** A voice task is the allocation: the call is offered when it is
|
|
2488
2579
|
routed to the agent and accepted as its `acceptance` dictates, and its presence and phase follow
|
|
2489
2580
|
the provider's reports about the work — never the audio. Wherever audio moves — an offer, a hold, a
|
|
@@ -2690,12 +2781,27 @@ platform cannot hold a leg the host reported keeps it in the login's `store` for
|
|
|
2690
2781
|
task, so a reload of the host restates the same record; the entry is keyed by `step` and `at`, so
|
|
2691
2782
|
a running hold restated with its final `seconds` is the same entry.
|
|
2692
2783
|
|
|
2693
|
-
**The
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2784
|
+
**The provider owns the timestamps in its record.** Host timestamps are advisory. The provider
|
|
2785
|
+
may retain a host instant it accepts or assign its own observation/receipt instant; it need not
|
|
2786
|
+
copy the host clock. A provider declaring `timestampAuthority: "provider"` uses its own timestamps
|
|
2787
|
+
for final records. Receipt time is an observation boundary, not a claim about when the physical
|
|
2788
|
+
host action happened. Records are ordered by their published provider-selected instants, with no
|
|
2789
|
+
rewriting of an already-published entry when a later host report arrives.
|
|
2790
|
+
|
|
2791
|
+
The incoming host `step` and `at`, scoped to the task and its current life, identify one reported
|
|
2792
|
+
leg for correlation only. The provider retains a binding from that key to its chosen history
|
|
2793
|
+
`at`. Every successful `recordStep` returns `{ status: "recorded", at }` with that same canonical
|
|
2794
|
+
history instant, even for subsequent duration/end reports. The host keeps sending the original
|
|
2795
|
+
report key, never the returned history timestamp. A changed host timestamp is not a correction
|
|
2796
|
+
to the same report: it names a different leg. Bindings survive reconnect/reload for the retained
|
|
2797
|
+
task lifetime. The host renders provider-published history unchanged. It never compares the
|
|
2798
|
+
provider instant back to its own clock, substitutes its own timestamp, or adjusts later messages
|
|
2799
|
+
to match an earlier host estimate. The acknowledgment identifies the provider record for
|
|
2800
|
+
correlation/conformance only; it is not a host-side timestamp reconciliation instruction. Conflicting reuse is refused visibly, not paired by arrival time or nearest instant.
|
|
2801
|
+
If two distinct entries would collide under the history's `(step, at)` key, do not invent a time
|
|
2802
|
+
or combine them: report the representation conflict. No ambiguous event is silently accepted.
|
|
2803
|
+
|
|
2804
|
+
**Handle time is anchored, not restarted.** It runs from the
|
|
2699
2805
|
`answered` step's `at` — from the task's first `in-progress` where the provider reports no
|
|
2700
2806
|
history — until the task's media ends, and a hold neither pauses nor resets it: the hold's own
|
|
2701
2807
|
duration is the `held` entry's `seconds`, and a desk that restarts its counter on resume is
|
|
@@ -2778,8 +2884,9 @@ void connection.recordStep?.({ taskId, allocationId, step: "muted", at, mutedBy:
|
|
|
2778
2884
|
void connection.recordStep?.({ taskId, allocationId, step: "muted", at, mutedBy: "host", seconds: 42, ended: true }); // the moment they unmute
|
|
2779
2885
|
```
|
|
2780
2886
|
|
|
2781
|
-
|
|
2782
|
-
|
|
2887
|
+
Every report about one leg repeats its original `step` and host `at` as a correlation key;
|
|
2888
|
+
the published history uses the provider-selected `at` returned by `recordStep`. The host reports
|
|
2889
|
+
the action it performed and its measured duration, not an authoritative provider timestamp. It may say how long so far — `seconds` is
|
|
2783
2890
|
elapsed while the leg runs and final once it has ended — and **the end is stated, never
|
|
2784
2891
|
inferred**: `ended: true` marks the last report, and it carries the final duration. **What a
|
|
2785
2892
|
provider never asked for never crosses.** The running report is sent only to a provider whose
|
|
@@ -2789,16 +2896,33 @@ a running one it was never asked for (`handlingReport.running.unexpected`). What
|
|
|
2789
2896
|
did ask for them forwards upstream, and how often, is its own business. The step appears in
|
|
2790
2897
|
`handlingHistory` when the *provider* publishes it: Omni never writes the record itself.
|
|
2791
2898
|
`recordStep` is required of every softphone login's connection, since every call on a softphone
|
|
2792
|
-
can be muted by the host, and answers `recorded`. A `muted` report says whose the silence was,
|
|
2899
|
+
can be muted by the host, and answers `recorded` with the canonical history `at`. A `muted` report says whose the silence was,
|
|
2793
2900
|
`mutedBy: "host"` or `"station"`, and no other report has the word (`handlingReport.mutedBy`,
|
|
2794
2901
|
`.mutedBy.unexpected`). On a desk phone the microphone is the phone's:
|
|
2795
2902
|
the host mutes nothing and records nothing.
|
|
2796
2903
|
|
|
2797
|
-
**
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
the
|
|
2801
|
-
the
|
|
2904
|
+
**The provider's confirmed end is decisive.** The host keeps local state for interaction and
|
|
2905
|
+
reporting, but follows the provider's authoritative state. When the provider publishes the end
|
|
2906
|
+
of the current mute leg, the host ends that local mute and releases its host-controlled mute
|
|
2907
|
+
on the matching task's media. It does not wait for its own timer, reopen the leg, replace the
|
|
2908
|
+
provider timestamp, or overwrite the provider's final duration with a later host report.
|
|
2909
|
+
A later agent mute is a new leg, never a reopening of the ended one.
|
|
2910
|
+
|
|
2911
|
+
Correlate the provider-published `muted` entry using the provider-selected `at` acknowledged
|
|
2912
|
+
for the current report key; a final `seconds` closes that leg under the history contract.
|
|
2913
|
+
This is identity matching, not reconciliation between clocks. An old closed entry, an unrelated
|
|
2914
|
+
task, or a stale connection cannot end a newer local mute. If publication precedes the acknowledgment,
|
|
2915
|
+
retain the current provider view and apply the matching closure once correlation is available;
|
|
2916
|
+
never guess a match from arrival order or nearest timestamp. Failure to release a host-controlled
|
|
2917
|
+
mute is reported visibly; local device reports still describe the actual device state.
|
|
2918
|
+
|
|
2919
|
+
At a provider-confirmed task/media end, the host also stops the associated local mute and reports
|
|
2920
|
+
its observed ending where the report is still accepted. A host closing report repeats its original
|
|
2921
|
+
key and may include its measured duration, but cannot reverse an already confirmed provider end.
|
|
2922
|
+
The provider acknowledges a known closed leg without rewriting its final record; after the task
|
|
2923
|
+
has been disposed, it may refuse the report as task-not-found. The host reads that response rather
|
|
2924
|
+
than retrying or recreating the task. An observed host end before a provider closure is still
|
|
2925
|
+
reported normally; the provider decides the final timestamp and publishes the record.
|
|
2802
2926
|
|
|
2803
2927
|
### Browser capability
|
|
2804
2928
|
|
|
@@ -3039,7 +3163,7 @@ See **Which commands need a capability**.
|
|
|
3039
3163
|
| --- | --- | --- |
|
|
3040
3164
|
| `decline` | Pending-task button: Decline | The provider can decline a pending voice offer. Omni shows it only when provisioning also permits declining. |
|
|
3041
3165
|
| `hold` | Primary toggle: Hold | Omni may issue voice-task `hold` and `resume` commands. |
|
|
3042
|
-
| `endCall` | Primary button: End call |
|
|
3166
|
+
| `endCall` | Primary button: End call | The provider ends the caller connection and all owned/inherited agent-added channels; wrap/disposal remain separate. See **Ending a call, and removing one person from it**. |
|
|
3043
3167
|
| `connectBack` | Completing-task button: Connect back | Omni may have the provider connect the agent back to the task's party 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 **Connecting back during completion**. |
|
|
3044
3168
|
| `coldTransfer` | Secondary menu item: Cold transfer | Omni may hand the customer straight to a destination, with nobody spoken to first. |
|
|
3045
3169
|
| `warmTransfer` | Secondary menu item: Warm transfer | Omni may park the customer and call a destination first, then hand the customer over or cancel back. See **Warm transfer**. |
|
|
@@ -3105,12 +3229,12 @@ kind travels, nothing is typed, and a control with an empty directory has nothin
|
|
|
3105
3229
|
refused (`task.destinations.offer`). A control declared as bare `true` is refused for the same
|
|
3106
3230
|
reason (`task.destinations.shape`): once nothing is typed, the directory is the control.
|
|
3107
3231
|
|
|
3108
|
-
**
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
The
|
|
3113
|
-
|
|
3232
|
+
**The provider chooses the supported destinations.** A published item may route to an IVR,
|
|
3233
|
+
queue, named agent or another configured destination. No destination kind is inferred from its
|
|
3234
|
+
label. The host offers only the directory attached to this task's specific transfer capability,
|
|
3235
|
+
and sends the exact destination ID; it does not invent an agent picker or arbitrary dial target.
|
|
3236
|
+
The provider rechecks eligibility and executes the routing. Returning to an IVR or queue does not
|
|
3237
|
+
end the caller journey; a later assignment to the same agent is a new handling.
|
|
3114
3238
|
|
|
3115
3239
|
#### Ending a call, and removing one person from it
|
|
3116
3240
|
|
|
@@ -3118,18 +3242,44 @@ A call has everyone on `Task.onCall`, and two commands take people off it, both
|
|
|
3118
3242
|
provider.
|
|
3119
3243
|
|
|
3120
3244
|
```ts
|
|
3121
|
-
{ type: "end-call" } //
|
|
3245
|
+
{ type: "end-call" } // provider ends caller and owned/inherited agent-added channels; wrap/disposal are separate
|
|
3122
3246
|
{ type: "conference", action: "remove", party: true } // the customer leaves; the agent stays with the colleague
|
|
3123
3247
|
{ type: "conference", action: "remove", destinationId: "tier2" } // the conferenced person leaves; ringing, this calls the dial off
|
|
3124
3248
|
```
|
|
3125
3249
|
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
the
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3250
|
+
The host sends the ordinary task-scoped `execute` request with `command: { type: "end-call" }`.
|
|
3251
|
+
The provider knows the caller and agent-added channel bindings; no channel list or owner details
|
|
3252
|
+
are sent by the host. The task's `endCall` capability is permission, not an executor selection.
|
|
3253
|
+
`validateTaskCommandRequest` checks the exact handling/allocation and delegates the command's
|
|
3254
|
+
policy/phase checks. The provider rechecks current ownership atomically; no client validator can
|
|
3255
|
+
prove that its view is fresh. The host does not implement agent end-call; low-level removal of individual channels is not
|
|
3256
|
+
this agent action. There is no host end-call execution API or fallback route.
|
|
3257
|
+
|
|
3258
|
+
`end-call` is gated by `endCall` on the current handling. It ends the caller connection and
|
|
3259
|
+
all agent-added channels owned by that handling, including channels originally added by previous
|
|
3260
|
+
agents and inherited through transfer or takeover. Ownership is not inferred from who originally
|
|
3261
|
+
dialed, the current UI selection, or a person's label. The provider maintains that authority
|
|
3262
|
+
and checks the exact handling/allocation when acting. Agent-added channels are not left behind
|
|
3263
|
+
merely because another agent added them.
|
|
3264
|
+
|
|
3265
|
+
On a successful transfer or takeover, ownership of those channels passes to the receiving
|
|
3266
|
+
handling (lead or another agent), not just ownership of the caller leg. The outgoing handling may
|
|
3267
|
+
still have its allowed wrap work, but cannot disconnect channels it no longer owns. A pending or
|
|
3268
|
+
unknown handover is not a confirmed ownership change: retain source reservations and reconcile.
|
|
3269
|
+
The provider must serialize handover and end-call so a delayed outgoing command cannot tear down
|
|
3270
|
+
the recipient's call. No public channel-owner field or guessed identity is introduced here;
|
|
3271
|
+
the source must provide authoritative ownership and command fencing before declaring permission.
|
|
3272
|
+
|
|
3273
|
+
The resulting end of this agent's media is published as `task-media-ended`. Any wrap remains
|
|
3274
|
+
until its own completion/disposal; `end-call` is not `task-ended`, and `complete` is not an implicit
|
|
3275
|
+
caller/channel disconnect. Other agents' independent tasks end or wrap only on their own source
|
|
3276
|
+
transitions. Neither disposal nor transfer itself ends the continuing caller journey.
|
|
3277
|
+
|
|
3278
|
+
`conference` `remove` remains a person-specific provider operation, gated by `conference`.
|
|
3279
|
+
It does not implicitly dispose any other handling. Its person/consultation prerequisites
|
|
3280
|
+
remain separate from the permission to disconnect the caller. A remove that would leave the
|
|
3281
|
+
agent alone is not a remove but an `end-call`, and the provider answers it `failed`.
|
|
3282
|
+
The consulted destination of a warm transfer is not removed this way:
|
|
3133
3283
|
`transfer` `cancel` is its own step, which returns the agent to the parked customer, and a remove
|
|
3134
3284
|
never promises that.
|
|
3135
3285
|
|
|
@@ -3357,14 +3507,12 @@ the platform added itself carries none. `held: true` is presence as claim. A sna
|
|
|
3357
3507
|
after a transfer reads the room from here rather than inferring it from a sequence of outcomes it
|
|
3358
3508
|
never saw.
|
|
3359
3509
|
|
|
3360
|
-
**`onCall`
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
say who is, which is a different claim, so a provider that publishes the room publishes one last
|
|
3367
|
-
change: the empty room.
|
|
3510
|
+
**`onCall` describes this agent's current handling.** When the caller disconnects but the agent
|
|
3511
|
+
and added channels remain connected, the remaining room is still valid. When this handling's
|
|
3512
|
+
media ends or the task enters `completing`, clear its `onCall` view; absence is used only by a
|
|
3513
|
+
provider that never publishes the room. This does not assert that the caller, bridge or other
|
|
3514
|
+
agents' channels ended. A published room receives a final empty view for this handling, and the
|
|
3515
|
+
task may remain open for wrap. Disposal is a separate task action, not the caller's disconnect.
|
|
3368
3516
|
|
|
3369
3517
|
**A field that describes the present is cleared by the transition that ends it.** `onCall`,
|
|
3370
3518
|
`previewEndsAt` and `atDeadline` are three instances of one shape, and there will be more: each
|
|
@@ -3554,6 +3702,40 @@ against it (`stream.taskOffered.overCapacity`).
|
|
|
3554
3702
|
idle dialpad arrives through `task-offered` like any other task, and a full agent does not forbid
|
|
3555
3703
|
it: the ceiling binds allocation, not the agent's own hand.
|
|
3556
3704
|
|
|
3705
|
+
### Runtime break prerequisite checks
|
|
3706
|
+
|
|
3707
|
+
Use `validateBreakCommand(method, request, state, context)` from the validation entry point
|
|
3708
|
+
before any agent break dispatch. The context supplies current authentication and transport;
|
|
3709
|
+
all four methods require the live login's break capability and active transport. Pass the
|
|
3710
|
+
request object only to the request method, and undefined to the other three.
|
|
3711
|
+
|
|
3712
|
+
| Method | Required current state |
|
|
3713
|
+
| --- | --- |
|
|
3714
|
+
| `requestBreak` | `not-requested`; selected current reason code when codes exist; `mayAsk` or the selected reason's `alwaysAvailable` exception. Free text does not replace a code. |
|
|
3715
|
+
| `commitBreak` | `granted`, or already committed for an idempotent repeat. A later change to `mayAsk` does not revoke the grant. |
|
|
3716
|
+
| `cancelBreak` | `awaiting-decision` or `granted`. A concurrent commit winning still answers `omni.break-already-committed` and requires recovery. |
|
|
3717
|
+
| `endBreak` | `in-effect` or `starting-after-task` during reconciliation; an agent cannot end an imposed break. |
|
|
3718
|
+
|
|
3719
|
+
Use `validateTeamBreakCommand(request, context)` for lead decisions, placement, release and
|
|
3720
|
+
policy commands. It requires the live lead capability and active transport, a current target
|
|
3721
|
+
roster for member commands, and the target's complete break state for placement/release.
|
|
3722
|
+
Approve/deny requires an awaiting decision; placement uses the target's reason codes; release
|
|
3723
|
+
requires an imposed committed break. The context's target state must belong to the named member;
|
|
3724
|
+
that association and backend authorization are provider responsibilities.
|
|
3725
|
+
|
|
3726
|
+
Use `validateBreakStatus(state, tasks)` on the complete retained task view after each transaction,
|
|
3727
|
+
as well as `validateBreakTransition` for event ordering. Snapshot validation shares the status
|
|
3728
|
+
checks. Pending and completing tasks still count as work. Only the existing monitoring exception
|
|
3729
|
+
allows tasks beside a break in effect. Validate full task shapes and the envelope separately.
|
|
3730
|
+
|
|
3731
|
+
These validators report violations without dispatching, rewriting state or proving freshness.
|
|
3732
|
+
Recheck prerequisites atomically at the provider; client validation cannot prevent a race.
|
|
3733
|
+
Continue to use `validateResult` for each method's response vocabulary. A request acknowledgment
|
|
3734
|
+
is not a grant, and no method response overwrites a newer state emission. Rejected promises
|
|
3735
|
+
remain unknown and recover by snapshot, not automatic retry. The host's frozen provider set,
|
|
3736
|
+
durable mutually exclusive commit/cancel decision and reconciliation obligations above remain
|
|
3737
|
+
required; per-provider validation does not implement the multi-provider coordinator.
|
|
3738
|
+
|
|
3557
3739
|
### `requestBreak(request)`
|
|
3558
3740
|
|
|
3559
3741
|
Requests permission to stop the agent later; it does not itself stop work. The provider continues
|
|
@@ -4275,7 +4457,7 @@ an affordance, never the enforcement: the host asks, and the provider does or de
|
|
|
4275
4457
|
answers `failed` for a command whose capability it did not publish, and that is the provider
|
|
4276
4458
|
honouring its own declaration, not the host deciding policy -- a host that refuses a command on
|
|
4277
4459
|
its own reading of a queue's flag has decided something that was never its to decide. What a
|
|
4278
|
-
command means on the platform is the provider's to work out: `end-call`
|
|
4460
|
+
command means on the platform is the provider's to work out: `end-call` disconnects the caller channel, and
|
|
4279
4461
|
which legs on which bridge that touches is a fact about the switch, never a choice the host makes.
|
|
4280
4462
|
The one thing the host performs physically is the microphone, and that is not a command at all.
|
|
4281
4463
|
|
|
@@ -4377,12 +4559,13 @@ declared:
|
|
|
4377
4559
|
|
|
4378
4560
|
**A control on the contact belongs to the handling phases**, `in-progress` and `paused`:
|
|
4379
4561
|
`hold`, `resume` and `pause`, `end-call`, every `transfer` and `conference` action, and
|
|
4380
|
-
every `lead-assist` action.
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4562
|
+
every `lead-assist` action. These controls act within this agent's current handling.
|
|
4563
|
+
In `completing`, that handling has ended and its wrap work remains. The caller may still be in
|
|
4564
|
+
an IVR, queue or another agent's handling, and other channels may remain connected. Completion
|
|
4565
|
+
of this handling does not establish that the caller or bridge ended. The capability stays
|
|
4566
|
+
declared while the task remains open; the phase prevents this task from controlling a handling
|
|
4567
|
+
it no longer owns. Omni shows these controls only in the two handling phases, and
|
|
4568
|
+
`validateTaskCommand` refuses them outside those phases (`command.phase.handling`).
|
|
4386
4569
|
The commands with a phase of their own -- `answer`, `accept` and `decline` in `pending`,
|
|
4387
4570
|
`call` in `preview`, `connect-back` in `completing`, `complete` in any -- are not among them.
|
|
4388
4571
|
|
|
@@ -4462,22 +4645,102 @@ They are published as `OMNI_FAILURE_CODES`.
|
|
|
4462
4645
|
| `occurredAt` | Valid RFC-3339 timestamp with an explicit timezone, representing provider observation time. |
|
|
4463
4646
|
| `event` | Typed `ProviderEvent` payload. |
|
|
4464
4647
|
|
|
4465
|
-
####
|
|
4648
|
+
#### Timestamp standard and explicit exceptions
|
|
4649
|
+
|
|
4650
|
+
Every provider event envelope carries `occurredAt` as an ISO timestamp in the contract's RFC-3339
|
|
4651
|
+
form, with `Z` or an explicit numeric timezone offset. Preserve the original instant. Payloads
|
|
4652
|
+
carry their source event/observation instants wherever available and declared by their type;
|
|
4653
|
+
publication and receipt time must not be substituted for a missing source instant. A required
|
|
4654
|
+
instant that the source cannot establish makes that evidence unavailable, not permission to
|
|
4655
|
+
invent a timestamp or silently omit a required field.
|
|
4466
4656
|
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
damaging direction is early — **Accept** withdrawn from an offer still ringing, a wrap timer
|
|
4472
|
-
expiring before the agent has finished.
|
|
4657
|
+
Clock estimation is an exception, not the standard interpretation of events. An ISO timestamp
|
|
4658
|
+
identifies an instant; it does not prove that the host and provider clocks are synchronized.
|
|
4659
|
+
Use an explicitly trusted clock in the relevant domain. Do not infer a general clock offset
|
|
4660
|
+
from event arrivals, shift source timestamps, or use an estimate to order events or create history.
|
|
4473
4661
|
|
|
4474
|
-
|
|
4475
|
-
running offset against the `occurredAt` inside it, and translates provider instants through that
|
|
4476
|
-
offset before counting down. What remains is network delay, which biases every deadline later —
|
|
4477
|
-
the direction that costs a second rather than an action.
|
|
4662
|
+
The current exceptions and their reasons are:
|
|
4478
4663
|
|
|
4479
|
-
|
|
4480
|
-
|
|
4664
|
+
| Case | Exception and reason |
|
|
4665
|
+
| --- | --- |
|
|
4666
|
+
| Host display of provider deadlines (`allocationExpiresAt`, `previewEndsAt`, wrap deadline) | A countdown may use an explicitly bounded provider-clock estimate when a trusted direct clock is unavailable, because the deadline belongs to another clock domain. This estimates the display only; it does not establish that the provider acted. Without usable time, show timing uncertainty. |
|
|
4667
|
+
| Host-triggered preview deadline | A bounded clock estimate with monotonic aging may schedule the host's Call command because the provider owns the deadline but the host owns the trigger. Do not trigger before the deadline is known to have passed; invalidate on clock discontinuity and reconcile when uncertain. |
|
|
4668
|
+
| Recording evidence expiry | A bounded observer-domain clock estimate with monotonic aging may assess freshness because observation and expiry belong to the recorder's clock. If time cannot be trusted, recording state is unknown; never renew evidence from receipt or replay. |
|
|
4669
|
+
| Unknown recording state | `observedAt` and `validUntil` are absent because there is no confirmed observation. The containing provider event still has its own `occurredAt`; that publication is not a recorder observation. |
|
|
4670
|
+
| Direct snapshot reads and method requests/results | These are reads/operations, not event envelopes, and their current types have no general event timestamp. A snapshot event still carries `occurredAt`; embedded source instants remain unchanged. Do not treat a method result or read completion time as an occurrence boundary. |
|
|
4671
|
+
| Host-provided provider-clock estimate | Best-effort ISO time may be supplied by the host for optional comparisons; it is not a source observation or accuracy guarantee. The reason is that the host does not own the provider clock. |
|
|
4672
|
+
| Provider receipt timestamps | The provider may use its own receipt instant for final records of receipt/processing because host timestamps are untrusted advisory input. Receipt must not be presented as an earlier action or capture boundary. |
|
|
4673
|
+
| Optional source instants and deadlines | Omit only where the declared type permits absence and the source has no evidence or the policy has no deadline (for example unlimited preview). Do not replace absence with host time. |
|
|
4674
|
+
|
|
4675
|
+
Every additional exception must be listed with its scope and reason before adoption. Estimates
|
|
4676
|
+
must specify their uncertainty and validity; elapsed time should use monotonic aging, and a clock
|
|
4677
|
+
jump invalidates the estimate. The optional check below samples provider time without changing event timestamps. Report
|
|
4678
|
+
source-measured durations where required; timestamp subtraction is not a substitute.
|
|
4679
|
+
|
|
4680
|
+
#### Optional periodic provider time checks and host estimates
|
|
4681
|
+
|
|
4682
|
+
A provider may declare `Manifest.timeCheck: true` and implement `Connection.checkTime(request)`.
|
|
4683
|
+
Omission means unsupported. The host separately opts in with `ProviderTimeCheckPolicy`; these
|
|
4684
|
+
host-local settings are not task policy or a guarantee. All four durations are explicit positive
|
|
4685
|
+
safe integer milliseconds, with `maxRoundTripMs <= timeoutMs <= intervalMs`. No default interval
|
|
4686
|
+
is assumed. Validate settings with `validateProviderTimeCheckPolicy`.
|
|
4687
|
+
|
|
4688
|
+
While the connection is active, the host checks immediately on opt-in and then at `intervalMs`
|
|
4689
|
+
using a monotonic scheduler. Only one check may be outstanding. Missed ticks are skipped, never
|
|
4690
|
+
replayed in a burst. Each request has a fresh ID. The provider samples its own event/deadline
|
|
4691
|
+
clock after receiving the request and before returning `providerTime` as an ISO/RFC3339 instant,
|
|
4692
|
+
with the request ID, authenticated login ID and `clockId`. No cached sample or adapter-local time
|
|
4693
|
+
may masquerade as provider time. A provider unable to sample that domain must not declare support.
|
|
4694
|
+
Clock authority replacement or a discontinuity changes `clockId`.
|
|
4695
|
+
|
|
4696
|
+
Validate requests and responses with `validateProviderTimeCheckRequest` and
|
|
4697
|
+
`validateProviderTimeCheckResult`. The host also checks the outstanding request, provider/connection
|
|
4698
|
+
and login, monotonic elapsed time and cancellation after the await: a late successful response is
|
|
4699
|
+
still rejected. Reject samples at or beyond `timeoutMs`, or above `maxRoundTripMs`. Cancel local
|
|
4700
|
+
tracking and invalidate estimates on disconnect, logout, policy disable, clock change or clock
|
|
4701
|
+
jump; late results cannot restore them. Resume with a fresh check after reconnection. Expire a
|
|
4702
|
+
sample at `maxSampleAgeMs` using monotonic elapsed time. Failure is visible through the host's
|
|
4703
|
+
existing diagnostic path, with the estimate unavailable; the next scheduled check can recover.
|
|
4704
|
+
Checking failure alone does not assert call/recording state or require a healthy connection to close.
|
|
4705
|
+
This read-only check is not a task command. Protocol supplies declarations and validators; the
|
|
4706
|
+
host implements the scheduler and the provider implements the clock read.
|
|
4707
|
+
|
|
4708
|
+
The host may also expose `Host.estimateProviderTime({ providerId, loginId })`. It returns an ISO
|
|
4709
|
+
`at` in that provider's clock domain, the same explicit scope and `clockId`, or `undefined` when
|
|
4710
|
+
unavailable. Optional `uncertaintyMs` is an estimate, not a guaranteed bound. The method is optional
|
|
4711
|
+
at the host level and is deliberately absent from `HostGuarantees`. A provider declaring time
|
|
4712
|
+
checks does not require the host to expose estimates, and an estimate need not originate from
|
|
4713
|
+
this check if the host has another explicitly configured source for that provider clock.
|
|
4714
|
+
|
|
4715
|
+
To estimate the difference, the host brackets the request with local send/receive instants and
|
|
4716
|
+
measures elapsed time monotonically. For provider sample P and local bracketing instants H0/H1,
|
|
4717
|
+
the offset under stable clocks lies between P-H1 and P-H0, before clock error and timestamp
|
|
4718
|
+
precision are considered. A midpoint is only an estimate; one-way delays need not be symmetric.
|
|
4719
|
+
Never treat round-trip uncertainty as proof of the source clock's accuracy. Advance a retained
|
|
4720
|
+
sample with monotonic elapsed time, and return unavailable after its configured validity ends.
|
|
4721
|
+
`validateProviderTimeEstimate` checks shape and scope, not actual accuracy or freshness.
|
|
4722
|
+
|
|
4723
|
+
A provider may declare `Manifest.timestampAuthority: "provider"`: its own timestamps are
|
|
4724
|
+
used for final records, and host-supplied timestamps are advisory. This declaration is independent
|
|
4725
|
+
of time-check support and the optional host estimate; neither changes who owns the final record.
|
|
4726
|
+
Omission makes no promise that host timestamps will be trusted. No host-authoritative default
|
|
4727
|
+
is inferred. Successful `recordStep` responses identify the provider-selected history instant;
|
|
4728
|
+
see **The host records what it performs** for correlation and retained bindings.
|
|
4729
|
+
|
|
4730
|
+
A provider need not trust or adopt any host-supplied timestamp. It may timestamp an incoming
|
|
4731
|
+
message using its own clock at receipt and use that for its own processing/accounting. The reason
|
|
4732
|
+
for this exception is that host clocks and host estimates are not authoritative at the provider.
|
|
4733
|
+
Receipt time must be described as receipt/observation time, not relabeled as the original host
|
|
4734
|
+
action, recorder capture boundary, or proof an operation applied. An existing field with a
|
|
4735
|
+
specific occurrence meaning still requires that evidence; this option does not silently redefine
|
|
4736
|
+
it. Host-only instants remain advisory input; the provider owns its authoritative publication.
|
|
4737
|
+
|
|
4738
|
+
This host estimate is another explicit timestamp exception: it helps optional displays and
|
|
4739
|
+
provider-clock comparisons when the host lacks that clock directly. It is not a source occurrence,
|
|
4740
|
+
may not replace event/history timestamps, and alone cannot authorize deadline actions or prove
|
|
4741
|
+
recording freshness. Uses requiring a trustworthy bound still need independently established
|
|
4742
|
+
clock accuracy/drift assumptions. It neither synchronizes the operating-system clock nor changes
|
|
4743
|
+
ISO timestamps already published by the provider.
|
|
4481
4744
|
|
|
4482
4745
|
#### Nothing is lost until the connection drops
|
|
4483
4746
|
|
|
@@ -4590,6 +4853,48 @@ from `in-effect` or `starting-after-task` to a grant or a request, or from `gran
|
|
|
4590
4853
|
`exerciseAdapter` holds the stream to that from the connect snapshot on;
|
|
4591
4854
|
`assertBreakFollowsItsRequests` holds any sequence.
|
|
4592
4855
|
|
|
4856
|
+
Adapters and hosts can call `validateBreakTransition(before, after)` from the package’s validation entry point
|
|
4857
|
+
with two complete break objects before applying an event. It checks shape and the transition
|
|
4858
|
+
rules above; it does not mutate either state or apply the event. Report a violation visibly,
|
|
4859
|
+
retain the last accepted state without claiming it remains current, and reconcile from the
|
|
4860
|
+
source. Continue to validate the full envelope/login and task/break consistency separately.
|
|
4861
|
+
The conformance `BreakStream` validates its baseline, retains the last accepted state on a
|
|
4862
|
+
rejected delta, and sets `needsRecovery`. Transport loss also requires reseeding; an `active` transport
|
|
4863
|
+
notification alone does not restore a break baseline. Until a fresh validated snapshot is seeded, further
|
|
4864
|
+
break deltas are refused (`stream.breakState.baseline`); the first delta is never an implicit
|
|
4865
|
+
snapshot. `seed(snapshot)` returns violations, which callers must check. This is a conformance
|
|
4866
|
+
tracker, not a complete production reducer: snapshot freshness, login/connection fencing and
|
|
4867
|
+
full task consistency remain separately required.
|
|
4868
|
+
|
|
4869
|
+
**Both sides must enforce the order.** The host validates immediately before dispatch; the
|
|
4870
|
+
provider rechecks current permission and state atomically before acting. The provider validates
|
|
4871
|
+
the resulting break/task state before publication and serializes those publications. The host
|
|
4872
|
+
validates the envelope, transition and full task consistency before replacing local state. A
|
|
4873
|
+
request result does not optimistically advance break state, and a delayed command cannot apply
|
|
4874
|
+
to a later attempt merely because its approval happens to look compatible. Keep at most one
|
|
4875
|
+
unresolved operation per provider/attempt; recovery must resolve uncertainty before another
|
|
4876
|
+
operation is dispatched. The source must fence delayed operations against its own attempt state.
|
|
4877
|
+
|
|
4878
|
+
Normal order is `not-requested` → `awaiting-decision` → `granted` →
|
|
4879
|
+
`starting-after-task` → `in-effect` → `not-requested`. Auto-approval may go directly to
|
|
4880
|
+
`granted`; a commit with no outstanding work may go directly to `in-effect`. Denial/cancel
|
|
4881
|
+
returns a precommit request to `not-requested`; an authorized end/release returns a committed
|
|
4882
|
+
break there. Same-state restatements are allowed. An evidenced imposed break is the explicit
|
|
4883
|
+
exception to requesting/granting, and must carry its imposed actor/state. Neither skipped
|
|
4884
|
+
publication nor a host-local guess creates another exception. A later normal attempt starts
|
|
4885
|
+
from `not-requested`, never by regressing an active break into a request.
|
|
4886
|
+
|
|
4887
|
+
A fresh authoritative snapshot establishes a new baseline; do not run this event-transition
|
|
4888
|
+
check across it. A snapshot may legitimately establish a later request or an already active
|
|
4889
|
+
break after reconnect. Its freshness must be established by the source/adapter's ordered
|
|
4890
|
+
snapshot/publication boundary and fencing of obsolete callbacks and reads. A delayed old
|
|
4891
|
+
snapshot is not detectable from break approval alone. Neither envelope time nor event ID
|
|
4892
|
+
supplies a break revision. Do not suppress all requests after an active break: a later attempt
|
|
4893
|
+
passes through `not-requested`, and then may request again. Do not replay earlier ProviderEvents
|
|
4894
|
+
after recovery. Raw backend break handlers must preserve source order too; this helper does
|
|
4895
|
+
not implement or assume invocation of Protocol's two-phase request/commit coordinator.
|
|
4896
|
+
|
|
4897
|
+
|
|
4593
4898
|
For a multi-provider break attempt, "every provider" is the set of providers frozen when the
|
|
4594
4899
|
attempt entered `requesting-break`. Omni commits only after every asked provider reports `granted` —
|
|
4595
4900
|
that one is unconditional, because nothing has stopped yet and waiting costs only time. It enters
|
|
@@ -4768,6 +5073,10 @@ same exported checks are used by Omni and adapter tests so their interpretations
|
|
|
4768
5073
|
| `validateContact(contact)` | Contact field shapes and attribute keys. Every field is optional, so this checks what is present rather than what is missing. |
|
|
4769
5074
|
| `validateScheduledActivity(activity)` | Required activity fields and start/end ordering. |
|
|
4770
5075
|
| `validateHostGuarantees(guarantees)` | What a host promises: only the guarantees this contract names, each declared by presence and never `false`. The harness validates the guarantees of whatever host a test hands the adapter. |
|
|
5076
|
+
| `validateProviderTimeCheckPolicy(policy)` | Explicit optional polling settings, positive safe-integer durations and round-trip/timeout/interval ordering. |
|
|
5077
|
+
| `validateProviderTimeCheckRequest(request)` | Fresh-request shape; the host enforces actual uniqueness and outstanding-request lifetime. |
|
|
5078
|
+
| `validateProviderTimeCheckResult(result, request, loginId)` | ISO timestamp, clock identity, exact request/login correlation; timing and source accuracy remain runtime checks. |
|
|
5079
|
+
| `validateProviderTimeEstimate(estimate, scope)` | Optional host estimate shape and provider/login scope; no accuracy guarantee. |
|
|
4771
5080
|
| `validateHandlingReport(report, path?, manifest?)` | What the host reports of a leg it performed, for an adapter to check before forwarding: a task, a step, when it began, a positive `seconds` where stated, and an explicit `ended` that carries the final duration. Given the manifest, a running report is refused unless it declares `runningStepReports`. |
|
|
4772
5081
|
| `validateHostReport(report)` | The host's own report as published to an adapter: `online`, and where there is audio, an input that is `available` with the microphone and `flowing`, or `unavailable` with a reason and the failure that says why, and an output that is `available` or `unavailable` with its failure. The harness validates whatever host a test hands the adapter; `stillHost(report)` builds one that never changes. |
|
|
4773
5082
|
| `validateHostMute(mute, softphone)` | What the host's Mute does, stated on a softphone login and nowhere else: `stream` or `station` (`host.mute`), required where the host holds a microphone (`host.mute.required`) and refused where it does not (`host.mute.unexpected`). The harness holds `ConnectContext.host.mute` to it. |
|
|
@@ -4882,7 +5191,7 @@ as each case was considered, so a test that needs a rule to have run asserts it
|
|
|
4882
5191
|
inferring it from an empty `violations`, and a rule absent from it was never looked at, which is a
|
|
4883
5192
|
gap and not a pass. With the audio open on a softphone,
|
|
4884
5193
|
the drive mutes it for one second and reports the leg through `recordStep`, begun and then ended,
|
|
4885
|
-
expecting each report `recorded` (`drive.recordStep.failed`, `.rejected`); then it mutes again and
|
|
5194
|
+
expecting each report `recorded` with the provider-selected history `at` (`drive.recordStep.failed`, `.rejected`, `result.recordStep.at`); then it mutes again and
|
|
4886
5195
|
ends the call muted, as agents do, so the leg is open when the media ends, the provider closes it
|
|
4887
5196
|
in the completing publication or the open entry is refused (`task.handlingHistory.muted.open`),
|
|
4888
5197
|
and the drive's closing report after the media ended is expected `recorded` and to change nothing:
|
|
@@ -4892,7 +5201,7 @@ Where the provider restates the task's record afterwards, each leg is in it or t
|
|
|
4892
5201
|
does, the drive reloads the host as a reload happens: the first client is unsubscribed,
|
|
4893
5202
|
disconnected and its session closed (`drive.reload.handover`), and only then is a second adapter
|
|
4894
5203
|
built and connected for the same login with the same context and the same `store`; its snapshot
|
|
4895
|
-
must carry the task with that leg and the
|
|
5204
|
+
must carry the task with that leg at its acknowledged provider timestamp and the reported actor, and the run goes on with the second as its
|
|
4896
5205
|
connection to the end. A platform that holds the record hands it back, and an adapter that composed
|
|
4897
5206
|
the record in memory has nothing and is named (`drive.reload.snapshot`, `drive.reload.history`,
|
|
4898
5207
|
`.rejected`). The second adapter's snapshot is taken as any resync is: held to what the stream knew
|
|
@@ -4915,10 +5224,10 @@ another id (`drive.store.retained`); an adapter whose keys never named the task
|
|
|
4915
5224
|
unevaluated, and the result says so rather than passing it: a task's keys go
|
|
4916
5225
|
with the task, or the next offer of the same id inherits them. Outside the
|
|
4917
5226
|
handling phases with `hold` still declared -- in
|
|
4918
|
-
`confirmed`, where the provider publishes it, and in `completing` once
|
|
5227
|
+
`confirmed`, where the provider publishes it, and in `completing` once this agent's handling has ended -- the
|
|
4919
5228
|
drive sends `hold` past the validator that would hold it back, and expects `failed`: the adapter
|
|
4920
|
-
is the second gate on a control on the contact, and one that applies it
|
|
4921
|
-
|
|
5229
|
+
is the second gate on a control on the contact, and one that applies it outside this task's
|
|
5230
|
+
current handling is named (`drive.command.handling`). The drive cannot put a task into a phase the provider
|
|
4922
5231
|
never publishes, so a provider that goes straight from `pending` to `in-progress` is checked in
|
|
4923
5232
|
`completing` alone. A task the agent completes is
|
|
4924
5233
|
completed from wherever it stands once the drive has nothing left to do on it -- from `completing`
|
|
@@ -4992,7 +5301,7 @@ capabilities and what its work is **called** through `phaseLabels` and `taskType
|
|
|
4992
5301
|
any of it is drawn is Omni's. A task cannot select a design language, inject a component, or
|
|
4993
5302
|
override the agent's theme and font preferences.
|
|
4994
5303
|
|
|
4995
|
-
## Independent task recording
|
|
5304
|
+
## Independent task recording
|
|
4996
5305
|
|
|
4997
5306
|
Recording is voice-only. A task can arrive already recording, including while pending, and offer
|
|
4998
5307
|
no recording controls. The provider publishes only its own current state on
|
|
@@ -5019,14 +5328,14 @@ This package declares that contract; it supplies no recorder, media mixing, stor
|
|
|
5019
5328
|
| pause | active | paused, preserving recording identity and captured audio |
|
|
5020
5329
|
| resume | paused | active with the same recording identity |
|
|
5021
5330
|
| stop | active or paused | inactive; finish and retain captured audio |
|
|
5022
|
-
| cancel | active or paused | inactive; abandon
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
means cancelling an in-flight start request or deleting arbitrary past recordings.
|
|
5028
|
-
|
|
5029
|
-
|
|
5331
|
+
| cancel | active or paused | inactive; abandon and discard captured audio |
|
|
5332
|
+
|
|
5333
|
+
Stop finishes the recording and retains captured audio. Cancel abandons the recording and discards
|
|
5334
|
+
its captured audio; the task policy offers it with `cancel: true`. There is no configurable cancel
|
|
5335
|
+
effect. A recorder that cannot confirm disposal must not offer Cancel; it can offer Stop instead.
|
|
5336
|
+
Cancel never means cancelling an in-flight start request or deleting arbitrary past recordings.
|
|
5337
|
+
Stop can be applied only after finalization/retention succeeds; Cancel only after both cessation
|
|
5338
|
+
and disposal of this recording's audio succeed under the recorder's storage contract.
|
|
5030
5339
|
Partial success (capture stopped but storage outcome unknown) cannot return failed with a claim
|
|
5031
5340
|
of no effect. It rejects with unknown outcome, reports the failure visibly and publishes whatever
|
|
5032
5341
|
current capture state is actually known. Retention is not a promise of sample-perfect audio.
|
|
@@ -5052,7 +5361,8 @@ observation instant and an exclusive expiry. These times describe current eviden
|
|
|
5052
5361
|
capture boundaries. A trusted observer-domain current time must satisfy observedAt <= now < validUntil.
|
|
5053
5362
|
Use `effectiveRecordingState` with that explicitly trusted time; an unavailable clock yields unknown.
|
|
5054
5363
|
Receipt, replay and task publication never extend freshness. Clock discontinuity invalidates evidence;
|
|
5055
|
-
consumers
|
|
5364
|
+
consumers using the explicit recording-expiry clock-estimation exception must invalidate that
|
|
5365
|
+
estimate and use monotonic aging so clock rollback cannot
|
|
5056
5366
|
revive expired evidence. The executor compares observation identity and recording identity against
|
|
5057
5367
|
its latest state atomically before I/O. An intervening observation or allocation change refuses the
|
|
5058
5368
|
stale command; it never acts on a replacement recorder. Providers lacking trustworthy current-state
|
|
@@ -5075,8 +5385,9 @@ updates remain full current task/host views, not replayed provider events. These
|
|
|
5075
5385
|
create no handling-history entries and infer no actors, durations or historical capture boundaries.
|
|
5076
5386
|
|
|
5077
5387
|
Migration is explicit: replace the old true recording capability with per-path action policies,
|
|
5078
|
-
replace untargeted commands with scoped commands, and
|
|
5079
|
-
|
|
5388
|
+
replace untargeted commands with scoped commands, and retain protocol version 1. During pre-release
|
|
5389
|
+
development, hosts and adapters must align their recording contracts; negotiation does not detect
|
|
5390
|
+
differences between package revisions that share that protocol number. No legacy-shape fallback is provided.
|
|
5080
5391
|
This change does not publish a package or enable recording in any existing host/provider by itself.
|
|
5081
5392
|
|
|
5082
5393
|
`validateRecordingOutcome` checks confirming observations against recording-specific applied/failed
|
|
@@ -5088,3 +5399,48 @@ keep that binding through pause/resume/stop/cancel; a later policy cannot redire
|
|
|
5088
5399
|
The executor rejects a mismatched destination rather than moving or discarding another binding.
|
|
5089
5400
|
Action permission does not authorize unattended invocation: host controls require the agent's explicit
|
|
5090
5401
|
act, and provider authorization remains enforced at its authenticated command boundary.
|
|
5402
|
+
|
|
5403
|
+
|
|
5404
|
+
Recording dispatch must receive the same known task-validation context as the published task.
|
|
5405
|
+
Pass organisation levels and other known restrictions through the optional fourth argument of
|
|
5406
|
+
`validateTaskCommand`, and through `taskContext` in `validateRecordingRequest`. This keeps a valid
|
|
5407
|
+
custom organisation lock from being rejected against the default ladder, and prevents known
|
|
5408
|
+
capability restrictions from disappearing at dispatch. The standalone `validateRecordingCommandState`
|
|
5409
|
+
requires an affirmative permission; false, malformed permission declarations and unknown actions grant nothing.
|
|
5410
|
+
|
|
5411
|
+
|
|
5412
|
+
### Host recording announcements to the caller
|
|
5413
|
+
|
|
5414
|
+
`HostRecording.announcesToCaller`, when true, is a guarantee that the host delivers audible recording
|
|
5415
|
+
status messages to the remote party over the call's outgoing audio. It belongs inside the optional
|
|
5416
|
+
host recording capability: without that capability there is no such guarantee. Omission makes no
|
|
5417
|
+
promise; false is invalid. It does not belong in the provider manifest, the task's recording policy
|
|
5418
|
+
or the general `HostGuarantees` object. A provider can inspect this host guarantee when deciding
|
|
5419
|
+
whether to permit host recording on a task.
|
|
5420
|
+
|
|
5421
|
+
The guarantee applies only to host-owned recordings. Providers implement announcements for their
|
|
5422
|
+
own recordings at their end; provider state updates must not cause the host to announce those
|
|
5423
|
+
recordings. Both recorders can operate independently on the same call.
|
|
5424
|
+
|
|
5425
|
+
On a confirmed host start, the remote party hears that recording started. On a confirmed stop or
|
|
5426
|
+
cancel, they hear that recording stopped. If pause/resume is supported, say paused/resumed so a
|
|
5427
|
+
pause is not presented as a finished recording. Announcements follow actual capture transitions,
|
|
5428
|
+
not button clicks, accepted requests or task-policy changes. When attaching to an already-active
|
|
5429
|
+
host recording, announce that recording is active, without inventing its original start time.
|
|
5430
|
+
Repeated observations of the same state do not repeat announcements. Unknown or expired evidence
|
|
5431
|
+
must never produce a stopped announcement.
|
|
5432
|
+
|
|
5433
|
+
A local sound, UI indicator, screen-reader message to the agent, or sound leaking through the
|
|
5434
|
+
microphone does not fulfil this guarantee. The host must deliver the message directly in the audio
|
|
5435
|
+
sent to the remote party, even when the agent microphone is muted. It may also notify the agent.
|
|
5436
|
+
Only declare the guarantee when the host can provide that outgoing audio path. An announcement
|
|
5437
|
+
that cannot be delivered, including after the remote party has disconnected, is a visible failure;
|
|
5438
|
+
never claim delivery or replay the notice into a different call.
|
|
5439
|
+
|
|
5440
|
+
For a command under this guarantee, applied requires both the recording action and its announcement
|
|
5441
|
+
to succeed. If capture changes but announcement delivery fails, publish the actual recording state,
|
|
5442
|
+
report the announcement failure visibly and reject with unknown command outcome. Do not claim no
|
|
5443
|
+
effect, automatically repeat the recording action or silently switch to an agent-only notice.
|
|
5444
|
+
|
|
5445
|
+
`validateHostRecording` checks the declaration, including its true-or-absent guarantee. Actual audio
|
|
5446
|
+
delivery remains the host implementation's responsibility and must be exercised in its own tests.
|