@xema/omni-protocol 0.1.83 → 0.1.85
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 +38 -38
- package/dist/index.js +7 -7
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +40 -40
- package/dist/validation.d.ts +2 -2
- package/dist/validation.js +94 -91
- package/guide.md +204 -158
- package/package.json +1 -1
package/guide.md
CHANGED
|
@@ -22,10 +22,12 @@ are used precisely throughout and mean nothing looser here.
|
|
|
22
22
|
| **Host** | Omni, named that way where the contrast with a provider is the point — *the host carries the audio*, *host-side input*. |
|
|
23
23
|
| **Provider** | One independently connected external system: a voice platform, a chat platform, a mail platform. |
|
|
24
24
|
| **Adapter** | The package implementing this contract for one provider. One adapter is one provider, so the words are often interchangeable; *provider* names the system, *adapter* the code speaking for it. |
|
|
25
|
-
| **Agent** | The person signed in and taking work. On `onCall`, `agent` is a person on the call by user id;
|
|
25
|
+
| **Agent** | The person signed in and taking work. On `onCall`, `agent` is a person on the call by user id; transfer destinations may include an agent when the provider publishes that directory item. |
|
|
26
26
|
| **Lead** | An agent whose login declares `capabilities.team`. The provider publishes a `TeamRoster` to them and to nobody else: **the login is the permission**. |
|
|
27
27
|
| **Provisioning** | Omni-side policy about this agent, configured outside the protocol and never sent to a provider. It gates whether an offer may be rejected, whether the agent goes ready on login, and whether tasks are auto-accepted. Where a capability and provisioning disagree, the stricter wins. |
|
|
28
|
-
| **
|
|
28
|
+
| **Call** | The caller’s complete phone call, which may continue through IVRs, queues and several agents. |
|
|
29
|
+
| **Interaction** | One agent’s part in the call or conversation. The same call may return to that agent for a new interaction. |
|
|
30
|
+
| **Task** | The agent’s assigned work and its workspace, controls and completion work. A voice task represents one interaction, not the caller’s whole call. |
|
|
29
31
|
| **Channel** | The kind of work a provider carries: `voice`, `chat`, or `email`. Fixed per provider by its manifest. |
|
|
30
32
|
| **Task type** | The provider's own name for a category of work — a queue, a mailbox folder, a chat source. Free-form, and finer-grained than a channel. |
|
|
31
33
|
| **Capability** | A provider's declaration that a control exists for a task or a session. It says *offer this*; the provider performs it, and the host only offers it — see **Where a command executes**. |
|
|
@@ -118,7 +120,7 @@ identifier of its own for an agent or a manager, and none crosses this boundary
|
|
|
118
120
|
operating-system account, not a directory identity, not a licence.
|
|
119
121
|
|
|
120
122
|
So a `UserId` means nothing outside the provider that issued it. Provider A's
|
|
121
|
-
`
|
|
123
|
+
`interactionHistory[].by` and provider B's roster `memberId` are unrelated strings that will
|
|
122
124
|
eventually collide, and one person on several providers has several identities that nothing here
|
|
123
125
|
pairs. Scope every user identifier with its provider ID before storing or comparing it, exactly as
|
|
124
126
|
`taskKey()` already does for tasks — see `userKey()` under **Utilities**.
|
|
@@ -197,7 +199,7 @@ type Manifest<C extends Channel = Channel> = {
|
|
|
197
199
|
dialOutcomes?: C extends "voice" ? DialOutcome[] : never;
|
|
198
200
|
phones?: C extends "voice" ? Phone[] : never;
|
|
199
201
|
runningStepReports?: true;
|
|
200
|
-
|
|
202
|
+
completionSettleMs: number;
|
|
201
203
|
};
|
|
202
204
|
```
|
|
203
205
|
|
|
@@ -459,15 +461,15 @@ type SetPreferenceRequest =
|
|
|
459
461
|
| { id: PreferenceId; enabled: boolean }
|
|
460
462
|
| { id: PreferenceId; inherit: true };
|
|
461
463
|
|
|
462
|
-
type
|
|
464
|
+
type InteractionReport = { taskId: TaskId; allocationId: AllocationId; at: IsoTimestamp } & (
|
|
463
465
|
| { step: "muted"; mutedBy: MutedBy }
|
|
464
|
-
| { step: Exclude<
|
|
466
|
+
| { step: Exclude<InteractionStep, "muted">; mutedBy?: never }
|
|
465
467
|
) & (
|
|
466
468
|
| { ended: true; seconds: DurationSeconds }
|
|
467
469
|
| { ended?: never; seconds?: DurationSeconds }
|
|
468
470
|
);
|
|
469
471
|
|
|
470
|
-
type
|
|
472
|
+
type InteractionReportResult =
|
|
471
473
|
| { status: "recorded"; at: IsoTimestamp }
|
|
472
474
|
| { status: "failed"; failure: ProtocolFailure };
|
|
473
475
|
|
|
@@ -525,12 +527,12 @@ type DialResult =
|
|
|
525
527
|
### Task capabilities
|
|
526
528
|
|
|
527
529
|
```ts
|
|
528
|
-
type
|
|
530
|
+
type OutcomeCode = { id: string; label: string; group?: string };
|
|
529
531
|
|
|
530
|
-
type
|
|
532
|
+
type OutcomeRules = {
|
|
531
533
|
required?: boolean;
|
|
532
534
|
notes?: "required" | "optional" | "none";
|
|
533
|
-
codes?:
|
|
535
|
+
codes?: OutcomeCode[];
|
|
534
536
|
};
|
|
535
537
|
|
|
536
538
|
type Destination = {
|
|
@@ -555,7 +557,7 @@ type CustomCapability = {
|
|
|
555
557
|
|
|
556
558
|
type SharedTaskCapabilities = {
|
|
557
559
|
browsers?: true;
|
|
558
|
-
|
|
560
|
+
outcomes?: true | OutcomeRules;
|
|
559
561
|
custom?: CustomCapability[];
|
|
560
562
|
};
|
|
561
563
|
|
|
@@ -646,7 +648,7 @@ type TaskAttribute = TaskAttributeBase & (
|
|
|
646
648
|
| { type: "timestamp"; at: IsoTimestamp }
|
|
647
649
|
);
|
|
648
650
|
|
|
649
|
-
type
|
|
651
|
+
type InteractionStep =
|
|
650
652
|
| "queued"
|
|
651
653
|
| "offered"
|
|
652
654
|
| "answered"
|
|
@@ -656,16 +658,16 @@ type HandlingStep =
|
|
|
656
658
|
| "conferenced"
|
|
657
659
|
| "unanswered";
|
|
658
660
|
|
|
659
|
-
type
|
|
660
|
-
steps:
|
|
661
|
-
|
|
661
|
+
type TaskInteractionHistory = {
|
|
662
|
+
steps: TaskInteractionStep[];
|
|
663
|
+
interactionSeconds?: DurationSeconds;
|
|
662
664
|
holdSeconds?: DurationSeconds;
|
|
663
665
|
queueSeconds?: DurationSeconds;
|
|
664
666
|
transfers?: number;
|
|
665
667
|
};
|
|
666
668
|
|
|
667
|
-
type
|
|
668
|
-
step:
|
|
669
|
+
type TaskInteractionStep = {
|
|
670
|
+
step: InteractionStep;
|
|
669
671
|
at: IsoTimestamp;
|
|
670
672
|
dialId?: DialId;
|
|
671
673
|
destinationId?: string;
|
|
@@ -742,7 +744,7 @@ type Task<C extends Channel = Channel> = {
|
|
|
742
744
|
atDeadline?: PreviewDeadline;
|
|
743
745
|
reference?: string;
|
|
744
746
|
attributes?: TaskAttribute[];
|
|
745
|
-
|
|
747
|
+
interactionHistory?: TaskInteractionHistory;
|
|
746
748
|
} & TaskCompletion & (
|
|
747
749
|
C extends "voice"
|
|
748
750
|
? { recording?: { provider?: RecordingState }; onCall?: OnCall[]; leadAssist?: TaskLeadAssist; assisting?: TaskAssisting; monitoring?: TaskMonitoring; media?: TaskMediaState }
|
|
@@ -791,7 +793,7 @@ const TASK_COMMAND_NAMES = {
|
|
|
791
793
|
type TaskCommandName<C extends keyof typeof TASK_COMMAND_NAMES> =
|
|
792
794
|
(typeof TASK_COMMAND_NAMES)[C][number];
|
|
793
795
|
|
|
794
|
-
type
|
|
796
|
+
type OutcomePayload = { outcome?: string; notes?: string };
|
|
795
797
|
|
|
796
798
|
type VoiceTaskCommand =
|
|
797
799
|
| { type: "answer" }
|
|
@@ -813,19 +815,19 @@ type VoiceTaskCommand =
|
|
|
813
815
|
| { type: "conference"; action: "remove"; destinationId: string; party?: never }
|
|
814
816
|
| { type: "conference"; action: "remove"; party: true; destinationId?: never }
|
|
815
817
|
| (RecordingCommand & { source: "provider" })
|
|
816
|
-
| ({ type: "complete" } &
|
|
818
|
+
| ({ type: "complete" } & OutcomePayload);
|
|
817
819
|
|
|
818
820
|
type ChatTaskCommand =
|
|
819
821
|
| { type: "accept" }
|
|
820
822
|
| { type: "decline" }
|
|
821
823
|
| { type: "pause" }
|
|
822
824
|
| { type: "resume" }
|
|
823
|
-
| ({ type: "complete" } &
|
|
825
|
+
| ({ type: "complete" } & OutcomePayload);
|
|
824
826
|
|
|
825
827
|
type EmailTaskCommand =
|
|
826
828
|
| { type: "accept" }
|
|
827
829
|
| { type: "decline" }
|
|
828
|
-
| ({ type: "complete" } &
|
|
830
|
+
| ({ type: "complete" } & OutcomePayload);
|
|
829
831
|
|
|
830
832
|
type CustomTaskCommand = { type: "custom"; name: string; [key: string]: unknown };
|
|
831
833
|
|
|
@@ -896,7 +898,7 @@ The adapter is the one that knows the value, so it is the one held to it: given
|
|
|
896
898
|
queue locked, the validator refuses any other field carrying one, digits compared as digits so no
|
|
897
899
|
formatting hides them (`task.locked.leak`), and a conformance run whose tasks lock a party's number
|
|
898
900
|
or email states those values in `lockedValues`, since a run that cannot ask the question is not a
|
|
899
|
-
pass. A host never has the value and never asks. A name is not locked. What the queue provides rather than permits — browsers,
|
|
901
|
+
pass. A host never has the value and never asks. A name is not locked. What the queue provides rather than permits — browsers, outcomes, custom
|
|
900
902
|
controls — is content, and is never locked.
|
|
901
903
|
|
|
902
904
|
**A lead sets the team's policy from their roster.** A login that declares
|
|
@@ -1174,7 +1176,7 @@ type Connection<C extends Channel = Channel> = {
|
|
|
1174
1176
|
executeTeamMonitor?(request: TeamMonitorCommandRequest): Promise<TeamCommandResult>;
|
|
1175
1177
|
openMedia?(request: OpenMediaRequest): Promise<OpenMediaResult>;
|
|
1176
1178
|
setPreference?(request: SetPreferenceRequest): Promise<PreferenceResult>;
|
|
1177
|
-
recordStep?(report:
|
|
1179
|
+
recordStep?(report: InteractionReport): Promise<InteractionReportResult>;
|
|
1178
1180
|
executeTeamPolicy?(request: TeamPolicyCommandRequest): Promise<TeamCommandResult>;
|
|
1179
1181
|
};
|
|
1180
1182
|
|
|
@@ -1222,7 +1224,7 @@ const BREAK_KINDS = [
|
|
|
1222
1224
|
|
|
1223
1225
|
type BreakKind = (typeof BREAK_KINDS)[number];
|
|
1224
1226
|
|
|
1225
|
-
const
|
|
1227
|
+
const INTERACTION_STEPS_WITH_A_PERSON = [
|
|
1226
1228
|
"offered",
|
|
1227
1229
|
"answered",
|
|
1228
1230
|
"held",
|
|
@@ -1245,8 +1247,8 @@ const OMNI_FAILURE_CODES = [
|
|
|
1245
1247
|
type OmniFailureCode = (typeof OMNI_FAILURE_CODES)[number];
|
|
1246
1248
|
```
|
|
1247
1249
|
|
|
1248
|
-
`
|
|
1249
|
-
takes part in. `
|
|
1250
|
+
`INTERACTION_STEPS_WITH_A_PERSON` is every `InteractionStep` except `queued`, which is the one nobody
|
|
1251
|
+
takes part in. `interactionStepExpectsAPerson()` tests membership.
|
|
1250
1252
|
|
|
1251
1253
|
### Failure and validation
|
|
1252
1254
|
|
|
@@ -1481,7 +1483,7 @@ fired into a state that has moved on.
|
|
|
1481
1483
|
Allocate only within the concurrent capacity Omni has stated for this agent.
|
|
1482
1484
|
|
|
1483
1485
|
An allocation beyond that capacity, or with none currently stated, is invalid and Omni rejects it
|
|
1484
|
-
as a protocol violation. Work the agent was already
|
|
1486
|
+
as a protocol violation. Work the agent was already working on when the connection came back is
|
|
1485
1487
|
reported in the snapshot; it is not an allocation.
|
|
1486
1488
|
|
|
1487
1489
|
### 8. State is authoritative at the provider
|
|
@@ -1492,12 +1494,12 @@ replaces its local provider view; it does not overwrite the provider.
|
|
|
1492
1494
|
|
|
1493
1495
|
### 9. Define a vocabulary; do not merely list it
|
|
1494
1496
|
|
|
1495
|
-
Every member of a closed set — break kinds,
|
|
1497
|
+
Every member of a closed set — break kinds, interaction steps, destination kinds — must have a
|
|
1496
1498
|
normative definition; matching names alone do not establish shared meaning.
|
|
1497
1499
|
|
|
1498
1500
|
### 10. Order on the wire is display order
|
|
1499
1501
|
|
|
1500
|
-
A list the provider publishes — a destination directory,
|
|
1502
|
+
A list the provider publishes — a destination directory, outcome codes, a task's custom
|
|
1501
1503
|
controls, its browsers — is in the order the provider wants it shown, and a host keeps that order.
|
|
1502
1504
|
A reader will find meaning in position unless something says there is none, so the provider
|
|
1503
1505
|
decides: where it has an intention, an administrator's sequence, it lists in that order; where it
|
|
@@ -1573,7 +1575,7 @@ export default defineAdapter({
|
|
|
1573
1575
|
channel: "voice",
|
|
1574
1576
|
supportedProtocolVersions: [OMNI_PROTOCOL_VERSION],
|
|
1575
1577
|
authenticationMethods: ["browser-sso"],
|
|
1576
|
-
|
|
1578
|
+
completionSettleMs: 5000,
|
|
1577
1579
|
idleCapabilities: {
|
|
1578
1580
|
dial: { destinations: "any-number" },
|
|
1579
1581
|
},
|
|
@@ -1611,7 +1613,7 @@ compile time.
|
|
|
1611
1613
|
| `timeCheck` | Optional `true`: implements `checkTime` for fresh provider-clock samples; host polling is independently opt-in. |
|
|
1612
1614
|
| `timestampAuthority` | Optional `"provider"`: provider timestamps are final; host instants are advisory. Omission makes no trust promise. |
|
|
1613
1615
|
| `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**. |
|
|
1614
|
-
| `
|
|
1616
|
+
| `completionSettleMs` | Required. How long after an applied completion -- `complete`, or a lead's `take-over` -- the provider's `task-ended` is owed, a positive whole number of milliseconds (`manifest.completionSettleMs`). A warm transfer's `complete` is not final task completion: the agent's wrap runs after it. Stated per provider, since platforms settle at different speeds. See **`task-ended`**. |
|
|
1615
1617
|
|
|
1616
1618
|
### Authentication methods
|
|
1617
1619
|
|
|
@@ -2021,7 +2023,7 @@ failure:
|
|
|
2021
2023
|
```
|
|
2022
2024
|
|
|
2023
2025
|
The `User` it carries is the **root of this provider's user namespace**. Every other person this
|
|
2024
|
-
provider names — a roster member, the manager on an imposed break, the agent on
|
|
2026
|
+
provider names — a roster member, the manager on an imposed break, the agent on an interaction step —
|
|
2025
2027
|
is identified from the same directory and carries the same `UserId` type.
|
|
2026
2028
|
|
|
2027
2029
|
| Field | Contract |
|
|
@@ -2073,7 +2075,7 @@ Creates one live provider connection for the signed-in agent.
|
|
|
2073
2075
|
| `autoAcceptTasks` | Agent provisioning policy relayed to the provider at login, stated by the host on every connection and never assumed from its absence. When `true`, a pending task states its `acceptance`; when `false`, every task requires agent acceptance. Fixed for this connection, like everything else here: the provider states or omits `acceptance` by the value it was sent, and Omni validates by that same value, not by a policy that has since moved — a change reaches the provider through a fresh `connect()`. |
|
|
2074
2076
|
| `timeZone` | The same value passed as `AuthenticationContext.timeZone`. The provider stores it on the agent and carries it on the identity. See **The agent's day**. |
|
|
2075
2077
|
| `phone` | The same value passed as `AuthenticationContext.phone`: how this login hears its calls. The type ties `host.mute` to it: a `softphone` login's host states what its Mute does, and a desk-phone or conversation login's host cannot, so the omission is a compile error rather than a live seat's discovery. See **The station is the host's**. |
|
|
2076
|
-
| `store` | The login's operational store, kept by the host for the life of the login, across a reload of the host, and cleared at sign-out: where an adapter that composes a record keeps what its platform cannot hold for it, such as the
|
|
2078
|
+
| `store` | The login's operational store, kept by the host for the life of the login, across a reload of the host, and cleared at sign-out: where an adapter that composes a record keeps what its platform cannot hold for it, such as the interaction legs a host reported. Three functions, by key. Never for anything sensitive, which is `AuthenticationContext.secrets`, a store a host may clear aggressively. **A task's keys carry the task id and go with the task**: a key written about a task names the task's id in the key, and is deleted before the task's end is published, because a platform retires a task id minutes after closing it and a requeue takes seconds, so a key that outlives its task is inherited by the next offer of the same id -- a record with legs the host never reported against it. A login-scoped key carries no task id and outlives any task. The harness requires the store of every connection (`store.shape`, `store.get`, `.set`, `.delete`), watches the one it hands over, and names a task's key still held after `task-ended` (`drive.store.retained`) or written about the task after its end -- a persist hung off a timer that saw the task as it was (`drive.store.late`). The ordinary late write is the host's, not the adapter's: a host reports a leg without waiting for the answer, so an unmute can follow `complete` by a tick, and the adapter answers a report about a task that has ended `failed` with `omni.task-not-found` and writes nothing -- the host ends its own open legs at the task's end, so such a report is the host's error to see, and a host reads every `recordStep` answer and awaits the one for the leg it closes at a task's end, the only moment a refusal is expected, since a report nobody waits for is an error nobody can see; an adapter that names no task in its keys gets no cleanup check, which is a gap rather than a pass, never an exemption: the obligation is that nothing of a closed task survives its ending, and an adapter that keeps every open task in one login-scoped value owes exactly that inside the value, where the harness cannot look. One key per task, named for it, is the shape the harness can hold, and the shape to reach for. The store lists nothing, so an adapter that needs to find its tasks keeps a login-scoped index of ids beside them; at the task's end it deletes the body first and reindexes after, since a crash between the two then leaves an index naming a task with no body, which a reader skips, where the other order leaves a body for a task that has ended, which is the hazard itself. A reader of the index tolerates an id with no body as an ending that was underway, not as corruption. |
|
|
2077
2079
|
| `host` | The host's report of the agent's station — devices, permissions, network — to consult before declaring the agent ready to the platform, and on every change. See **The host reports, the adapter decides**. |
|
|
2078
2080
|
| `signal` | Optional cancellation signal. Stop startup promptly when aborted and do not begin new work. |
|
|
2079
2081
|
| `log` | Optional structured logging callback. Never include credentials, tokens, or sensitive contact data. |
|
|
@@ -2151,7 +2153,7 @@ surface in one place, and what obliges an adapter to implement each one.
|
|
|
2151
2153
|
| `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**. |
|
|
2152
2154
|
| `setCapacity(capacity)` | Always. Nothing may be allocated until a capacity is stated, so there is no connection that does not receive it. |
|
|
2153
2155
|
| `execute(request)` | Always. Every channel has commands no capability gates — see **Which commands need a capability**. |
|
|
2154
|
-
| `describeUsers(ids)` | The adapter publishes any `UserId`: on `ImposedBreak.by`, a roster, or `
|
|
2156
|
+
| `describeUsers(ids)` | The adapter publishes any `UserId`: on `ImposedBreak.by`, a roster, or `interactionHistory[].by`. Each `User` carries its `timeZone`; a person whose zone the provider cannot name is omitted from the answer, as any unresolvable id is. |
|
|
2155
2157
|
| `dial(request)` | The manifest declares `idleCapabilities.dial`, and with it `dialOutcomes`. |
|
|
2156
2158
|
| `requestBreak(request)` | The login declares `capabilities.breaks`. |
|
|
2157
2159
|
| `commitBreak()` | The login declares `capabilities.breaks`. Commit and cancel are not optional halves of it. |
|
|
@@ -2210,13 +2212,13 @@ describeUsers(ids: UserId[]): Promise<User[]>
|
|
|
2210
2212
|
```
|
|
2211
2213
|
|
|
2212
2214
|
Required of any adapter that publishes a `UserId` — on `ImposedBreak.by`, a team roster, or
|
|
2213
|
-
`
|
|
2215
|
+
`interactionHistory[].by`. Publishing an identifier Omni cannot resolve puts a name on screen
|
|
2214
2216
|
that reads as a database key.
|
|
2215
2217
|
|
|
2216
2218
|
- **Omit an id you cannot resolve; do not invent a name for it.** A missing entry says *I do not
|
|
2217
2219
|
know this person*, which Omni renders as such. Ordering is not significant and the response may
|
|
2218
2220
|
be shorter than the request.
|
|
2219
|
-
- **Take the whole list in one call.** Omni resolves a roster or
|
|
2221
|
+
- **Take the whole list in one call.** Omni resolves a roster or an interaction history as a batch, and
|
|
2220
2222
|
a per-id round trip multiplies that by its length.
|
|
2221
2223
|
- **Omni caches a result for one hour, then resolves it again.** The identifier is stable across
|
|
2222
2224
|
logins but the name behind it is not, so the cache expires on a clock rather than living for the
|
|
@@ -2224,7 +2226,7 @@ that reads as a database key.
|
|
|
2224
2226
|
particular value, or on Omni asking again at any particular moment.
|
|
2225
2227
|
|
|
2226
2228
|
This is the only place a name comes from. Task data carries identifiers alone —
|
|
2227
|
-
`
|
|
2229
|
+
`interactionHistory[].by` is an id and nothing more — so a name is never copied into a task, never
|
|
2228
2230
|
duplicated across tasks, and never stale.
|
|
2229
2231
|
|
|
2230
2232
|
### `Connection.disconnect()`
|
|
@@ -2354,7 +2356,7 @@ The provider does not introduce new work as already `in-progress`.
|
|
|
2354
2356
|
|
|
2355
2357
|
A task appears already `in-progress` only in a snapshot taken after a reconnect or a resync,
|
|
2356
2358
|
reporting work that began earlier in this login and never stopped. A fresh login has none to
|
|
2357
|
-
report: nothing has been allocated yet, and work the agent was
|
|
2359
|
+
report: nothing has been allocated yet, and work the agent was working elsewhere is not carried
|
|
2358
2360
|
into a new session.
|
|
2359
2361
|
|
|
2360
2362
|
## Tasks
|
|
@@ -2366,15 +2368,15 @@ into a new session.
|
|
|
2366
2368
|
`task-offered` introduces a new task and does not imply acceptance.
|
|
2367
2369
|
|
|
2368
2370
|
`Task<C>` is channel-discriminated. For example, `Task<"email">` accepts
|
|
2369
|
-
`browsers` and `
|
|
2371
|
+
`browsers` and `outcomes`, but rejects voice-only controls such as `hold` and `endCall` at compile
|
|
2370
2372
|
time. Runtime conformance checks also require the task channel to match its provider manifest.
|
|
2371
2373
|
|
|
2372
2374
|
| Field | Contract |
|
|
2373
2375
|
| --- | --- |
|
|
2374
2376
|
| `id` | Required `TaskId`, the platform's own identity for the task. Unique among the tasks open at once; a platform retires an id minutes after closing it, so the same id comes back for another customer. Omni scopes it with the provider ID. |
|
|
2375
|
-
| `allocationId` | Required `AllocationId`: this life of the task, minted once per offer and never reused for the life of the login, whatever the id does. Every event, command and report that names a task names its allocation too, so a late dial outcome or a late
|
|
2377
|
+
| `allocationId` | Required `AllocationId`: this life of the task, minted once per offer and never reused for the life of the login, whatever the id does. Every event, command and report that names a task names its allocation too, so a late dial outcome or a late interaction report for the first customer never lands on the next under the same id. The stream refuses an offer reusing one (`stream.taskOffered.allocation`), an update restating another life of the id (`stream.taskUpdated.allocation`), and a media or ending event naming a life that has ended or that nobody has seen (`stream.allocation.ended`, `.unknown`); a `dial-outcome` may name an ended life, since a dial placed late routinely outlives its call, and the host routes it there. See **A task's life on the wire**. |
|
|
2376
2378
|
| `title` | Agent-facing task title. |
|
|
2377
|
-
| `channel` | Channel
|
|
2379
|
+
| `channel` | Channel used for this task. It must equal the source provider's manifest channel. |
|
|
2378
2380
|
| `taskType` | Required provider-defined source or category of work, such as a voice `Queue Name`, `Mailbox Folder`, `Chat Source`, `Support`, `Billing`, or `Returns`. |
|
|
2379
2381
|
| `capabilities` | Controls and workspace features available for this specific task. |
|
|
2380
2382
|
| `capabilitySource` | Required. Who chose the capabilities: `queue` when somebody configured these terms, `ungoverned` when nothing handed the work over -- an agent's own outbound -- and `undetermined` when a queue was named and its terms could not be read, in which case `capabilities` is what the provider will honour, not what the platform permits. A host shows `undetermined` where the agent works. See **Task capabilities**. |
|
|
@@ -2387,9 +2389,9 @@ time. Runtime conformance checks also require the task channel to match its prov
|
|
|
2387
2389
|
| `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. |
|
|
2388
2390
|
| `reference` | Optional agent-facing reference such as a case, call, conversation, ticket, or message number. It is distinct from the protocol `id`. |
|
|
2389
2391
|
| `completionMode` | `agent-command` waits for the channel's `complete` command; `provider-automatic` completes without one. |
|
|
2390
|
-
| `wrapAllowance` | Fixed time allowed to complete the task after primary
|
|
2392
|
+
| `wrapAllowance` | Fixed time allowed to complete the task after primary interaction 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. |
|
|
2391
2393
|
| `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. |
|
|
2392
|
-
| `
|
|
2394
|
+
| `interactionHistory` | The call record: `steps` — the ordered interaction history of this open task, one entry per occurrence, oldest first — and what they add up to before this agent, `interactionSeconds`, `holdSeconds`, `queueSeconds`, `transfers`, each present when the provider knows it. Live task data restated with the task, not a permanent archive. See **Interaction history**. |
|
|
2393
2395
|
| `onCall` | Voice only. Who is on the call, or being brought onto it, as the provider states it, replaced whole with the task: `party` is the customer -- carrying a `stage` while being dialled again on the same task, a connect-back with the host's `dialId` or a platform's callback without, ringing from the moment the dial is placed and joined on its answered outcome --, `agent` a person by user id, `consulted` and `conferenced` somebody a dial is bringing in, listed from the moment the dial is placed -- with the `destinationId` dialled, the `dialId` where a host placed it, the `stage` reached (`ringing` until answered, `joined` after), and `held: true` on anyone joined and parked. A `consulted` entry is what makes `transfer` `complete` and `cancel` issuable. `label` names a destination -- a person, a queue -- not a phrase; the host supplies the verb. Present when the provider knows the room, absent when it does not. See **Every dial has an outcome**. |
|
|
2394
2396
|
| `leadAssist` | Voice only. Present from the agent's request for a lead until the lead leaves or the request ends: `requested` while nobody has joined, `joined` with the lead's `leadId` once somebody has. See **Lead assist**. |
|
|
2395
2397
|
| `assisting` | Voice only, on the lead's own task for a call they joined: which member asked, with their note. Its presence is what makes `lead-assist` `take-over` and `leave` issuable. See **Lead assist**. |
|
|
@@ -2435,7 +2437,7 @@ The canonical task transitions are:
|
|
|
2435
2437
|
| `in-progress` | Agent starts a warm transfer (`transfer` `warm`); the customer is parked | `paused` |
|
|
2436
2438
|
| `paused` | Provider or agent resumes the task | `in-progress` |
|
|
2437
2439
|
| `paused` | Agent cancels a warm transfer (`transfer` `cancel`) | `in-progress` |
|
|
2438
|
-
| `in-progress` or `paused` | Contact
|
|
2440
|
+
| `in-progress` or `paused` | Contact interaction ends and follow-up work remains | `completing` |
|
|
2439
2441
|
| `completing` | Agent connects back to the party (`connect-back`) | `in-progress` |
|
|
2440
2442
|
| Any phase | Provider emits `task-ended` | Removed |
|
|
2441
2443
|
|
|
@@ -2444,7 +2446,7 @@ The canonical task transitions are:
|
|
|
2444
2446
|
A task id is the platform's, and platforms reuse them: a closed id is retired minutes later, a
|
|
2445
2447
|
requeue takes seconds, and on one platform a call was offered twice, thirteen seconds apart, under
|
|
2446
2448
|
one id, through a close and a re-offer. Everything that names a task after the fact -- a dial
|
|
2447
|
-
outcome, a media event, an ending,
|
|
2449
|
+
outcome, a media event, an ending, an interaction report, a command -- would land on whichever life of
|
|
2448
2450
|
the id is open when it arrives. So every offer mints an `allocationId`, unique for the life of the
|
|
2449
2451
|
login, and every one of those names it beside the `taskId`. The host keys its state on the pair, a
|
|
2450
2452
|
late event finds the life it belongs to or is refused, and a task-scoped browser session
|
|
@@ -2491,7 +2493,7 @@ ring-back is audio the agent hears -- so `task-media-started` arrives on the `pr
|
|
|
2491
2493
|
its party ringing. On `answered` the task is `in-progress`; on any other outcome the media ends,
|
|
2492
2494
|
`task-media-ended` starts the wrap clock as on every voice task, and the task goes to
|
|
2493
2495
|
`completing`, so the agent records the
|
|
2494
|
-
no-answer as the
|
|
2496
|
+
no-answer as the outcome it is -- in a campaign that is the commonest outcome there is, and it
|
|
2495
2497
|
is work, not a cancellation. A `preview` task therefore needs a manifest that says how a dial ends
|
|
2496
2498
|
(`task.preview.dialOutcomes.required`).
|
|
2497
2499
|
|
|
@@ -2508,7 +2510,7 @@ carries the provider's authoritative preparation-end instant and exactly one tri
|
|
|
2508
2510
|
The agent may press Call early in either fixed-preparation dialing mode, or at any time in
|
|
2509
2511
|
`waits` mode. With `waits`, the countdown is a preparation target, not an expiry: when it reaches
|
|
2510
2512
|
zero the UI says "Waiting for agent" and keeps Call available. There is no automatic task-ended,
|
|
2511
|
-
|
|
2513
|
+
completion, dialing or phase change. No repeating timer action occurs on later snapshots. This differs
|
|
2512
2514
|
from unlimited preparation only by displaying a preparation target. The former `expires` deadline
|
|
2513
2515
|
value is not supported; elapsed preparation must not withdraw the task. For `calls` and `host-calls`, the deadline ends
|
|
2514
2516
|
preparation and triggers initiation; it does not promise ringing, playable audio or customer
|
|
@@ -2549,33 +2551,33 @@ is named on its side. The task past preview has no deadline to wait for, so it c
|
|
|
2549
2551
|
field.
|
|
2550
2552
|
|
|
2551
2553
|
**Provider execution and host controls are distinct.** Answer, hold/resume, transfer (including
|
|
2552
|
-
IVR routing), conference and
|
|
2554
|
+
IVR routing), conference and interaction completion are executed by the provider
|
|
2553
2555
|
through the adapter. The task's applicable capability, phase, completion mode and command-specific
|
|
2554
2556
|
prerequisites decide which controls are offered. Caller disconnect also executes at the provider. Microphone mute is host-owned, never a provider
|
|
2555
2557
|
TaskCommand or task capability. The separately declared host recording facility keeps its existing
|
|
2556
2558
|
recording contract; this distinction does not remove it or move provider call operations to the host.
|
|
2557
2559
|
|
|
2558
|
-
A caller journey may revisit the same agent while an earlier
|
|
2559
|
-
IDs must remain distinct for concurrently open
|
|
2560
|
+
A caller journey may revisit the same agent while an earlier interaction still wraps. Public task
|
|
2561
|
+
IDs must remain distinct for concurrently open interactions. For source-owned interaction IDs allocated
|
|
2560
2562
|
before offer and never reused per assignment, both public task ID and allocation ID may equal that
|
|
2561
|
-
|
|
2562
|
-
retain the exact pair; never retarget an old command to the newest
|
|
2563
|
+
interaction ID. Restore them unchanged on reconnect. Commands, media, endings and interaction reports
|
|
2564
|
+
retain the exact pair; never retarget an old command to the newest interaction of a journey.
|
|
2563
2565
|
`validateTaskCommandRequest` checks the request's pair against the supplied published task and
|
|
2564
2566
|
then its command prerequisites. The caller must select that task within the correct provider/login
|
|
2565
2567
|
and recheck at the provider; the validator cannot prove source freshness or authorization.
|
|
2566
|
-
`task-ended` retires only that
|
|
2568
|
+
`task-ended` retires only that interaction's resources. Another interaction, caller channel or IVR/queue
|
|
2567
2569
|
portion must not be cleared because this task ended. Snapshot counts include all still-owned wrap
|
|
2568
|
-
and active
|
|
2570
|
+
and active interactions; task completion is not caller hangup.
|
|
2569
2571
|
|
|
2570
|
-
**Voice describes the agent's
|
|
2572
|
+
**Voice describes the agent's interaction within the wider call.** The task defines that agent's
|
|
2571
2573
|
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
|
|
2573
|
-
|
|
2574
|
+
continue through IVR, queues, other agents, holds or conferences while this interaction ends or wraps.
|
|
2575
|
+
Completing this task ends this interaction responsibility; it is not evidence that the caller's channel
|
|
2574
2576
|
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
|
|
2577
|
+
connection. Commands retain their explicit targets and effects; task completion must not silently
|
|
2576
2578
|
become a caller-disconnect operation.
|
|
2577
2579
|
|
|
2578
|
-
**A task is never its audio.** A voice task
|
|
2580
|
+
**A task is never its audio.** A voice task represents the agent’s interaction: the call is offered when it is
|
|
2579
2581
|
routed to the agent and accepted as its `acceptance` dictates, and its presence and phase follow
|
|
2580
2582
|
the provider's reports about the work — never the audio. Wherever audio moves — an offer, a hold, a
|
|
2581
2583
|
consult, a conference leg joining or leaving, a transfer, a connect-back — the media follows
|
|
@@ -2586,7 +2588,7 @@ are the adapter's and the platform's, transient, and decide neither when a task
|
|
|
2586
2588
|
phase it is in.
|
|
2587
2589
|
|
|
2588
2590
|
The line runs between the provider's word and Omni's own senses. `task-media-ended` is the
|
|
2589
|
-
provider's report that primary
|
|
2591
|
+
provider's report that primary interaction ended — a fact about the work, which is why the completion
|
|
2590
2592
|
allowance starts on it and the Connect back control appears on it — and Omni follows that report as it
|
|
2591
2593
|
follows any other. What Omni never does is derive a task's state from its own media session: a
|
|
2592
2594
|
stream that drops, a track that ends, a transport that disconnects, a microphone that fails, an
|
|
@@ -2612,7 +2614,7 @@ whether the channel carries real-time media:
|
|
|
2612
2614
|
| Email | The `task-updated` that moves the task to `completing`: the provider's word that the platform has accepted the outgoing message, the allowance running from that publication's `occurredAt` |
|
|
2613
2615
|
| Other non-media channels | The `task-updated` that moves the task to `completing` |
|
|
2614
2616
|
|
|
2615
|
-
**Off voice, `completing` is the provider's word that
|
|
2617
|
+
**Off voice, `completing` is the provider's word that interaction ended.** There is no media event to
|
|
2616
2618
|
carry it, so the phase does; a host starts the wrap clock at that publication and nowhere else. It
|
|
2617
2619
|
is optional: a conversation with nothing to wrap moves from `in-progress` to `task-ended` and
|
|
2618
2620
|
`completing` is never published. Under `provider-automatic` with a non-zero `wrapAllowance` it is
|
|
@@ -2629,7 +2631,7 @@ const emailCompletion = {
|
|
|
2629
2631
|
```
|
|
2630
2632
|
|
|
2631
2633
|
In this example, the agent has two minutes after sending the email to add notes, select a
|
|
2632
|
-
|
|
2634
|
+
outcome, and complete the task.
|
|
2633
2635
|
|
|
2634
2636
|
`0` means completion may happen immediately. With `provider-automatic`, the provider may complete
|
|
2635
2637
|
without waiting for a command; with `agent-command`, it still waits for `complete`.
|
|
@@ -2685,14 +2687,14 @@ tell a task coming back from one going backwards (`stream.taskUpdated.phase`).
|
|
|
2685
2687
|
**The control exists only while there is a window to use it in.** Under `agent-command` the task
|
|
2686
2688
|
stays `completing` until the agent completes it, so the window is open for as long as they need.
|
|
2687
2689
|
Under `provider-automatic` the window is the allowance -- and with `wrapAllowance: 0` there
|
|
2688
|
-
is none: the provider
|
|
2690
|
+
is none: the provider completes the task at provider end, and Omni does not offer Connect back,
|
|
2689
2691
|
whatever the task declares. A capability names a control that can be used; on a task with no `completing`
|
|
2690
2692
|
window it cannot, and declaring it there changes nothing.
|
|
2691
2693
|
|
|
2692
2694
|
```ts
|
|
2693
2695
|
const connectBackCapable = {
|
|
2694
2696
|
channel: "voice",
|
|
2695
|
-
capabilities: { hold: true, connectBack: true,
|
|
2697
|
+
capabilities: { hold: true, connectBack: true, outcomes: true },
|
|
2696
2698
|
phase: "completing",
|
|
2697
2699
|
completionMode: "provider-automatic",
|
|
2698
2700
|
wrapAllowance: 30,
|
|
@@ -2712,13 +2714,37 @@ const immediateProviderCompletion = {
|
|
|
2712
2714
|
} satisfies Pick<Task, "completionMode" | "wrapAllowance">;
|
|
2713
2715
|
```
|
|
2714
2716
|
|
|
2715
|
-
###
|
|
2717
|
+
### Interaction history
|
|
2716
2718
|
|
|
2717
|
-
|
|
2719
|
+
The API uses the same interaction terminology: `interactionHistory`, `InteractionReport`,
|
|
2720
|
+
`InteractionReportResult`, and the corresponding step types and validators. Hosts and providers
|
|
2721
|
+
must adopt these names together; no legacy aliases are provided. The manifest's
|
|
2722
|
+
`completionSettleMs` bounds final task completion, not entry into the `completing` wrap-up phase.
|
|
2723
|
+
The `recordStep` method and `complete` command keep their existing names and behavior.
|
|
2724
|
+
|
|
2725
|
+
Migration from the earlier spelling:
|
|
2726
|
+
|
|
2727
|
+
| Former API | Current API |
|
|
2728
|
+
| --- | --- |
|
|
2729
|
+
| Task.handlingHistory | `Task.interactionHistory` |
|
|
2730
|
+
| TaskHandlingHistory, TaskHandlingStep, HandlingStep | `TaskInteractionHistory`, `TaskInteractionStep`, `InteractionStep` |
|
|
2731
|
+
| HandlingReport, HandlingReportResult, validateHandlingReport | `InteractionReport`, `InteractionReportResult`, `validateInteractionReport` |
|
|
2732
|
+
| HANDLING_STEPS_WITH_A_PERSON, HANDLING_STEPS_THAT_DIAL | `INTERACTION_STEPS_WITH_A_PERSON`, `INTERACTION_STEPS_THAT_DIAL` |
|
|
2733
|
+
| handlingStepExpectsAPerson, handlingStepDials | `interactionStepExpectsAPerson`, `interactionStepDials` |
|
|
2734
|
+
| handleSeconds | `interactionSeconds` |
|
|
2735
|
+
| Manifest.disposalSettleMs | `Manifest.completionSettleMs` |
|
|
2736
|
+
|
|
2737
|
+
Update producers, consumers, saved task snapshots, and validation-rule assertions together.
|
|
2738
|
+
History/report rule names now use `interactionHistory`/`interactionReport`; phase and conformance
|
|
2739
|
+
rules use `interaction` and final-task rules use `completion`. Legacy fields are rejected even
|
|
2740
|
+
when the new field is also supplied.
|
|
2741
|
+
|
|
2742
|
+
|
|
2743
|
+
`Task.interactionHistory` is the call record: the steps that brought the task to the agent, oldest
|
|
2718
2744
|
first, and what they add up to before this agent:
|
|
2719
2745
|
|
|
2720
2746
|
```ts
|
|
2721
|
-
|
|
2747
|
+
interactionHistory: {
|
|
2722
2748
|
steps: [
|
|
2723
2749
|
{ step: "queued", at: "2026-08-21T00:59:00Z", seconds: 41 },
|
|
2724
2750
|
{ step: "answered", at: "2026-08-21T00:59:41Z", seconds: 312, by: "a-17" },
|
|
@@ -2726,7 +2752,7 @@ handlingHistory: {
|
|
|
2726
2752
|
{ step: "transferred", at: "2026-08-21T01:04:53Z", by: "a-17" },
|
|
2727
2753
|
{ step: "answered", at: "2026-08-21T01:05:02Z", by: "a-23" },
|
|
2728
2754
|
],
|
|
2729
|
-
|
|
2755
|
+
interactionSeconds: 312, // handled by others before this agent
|
|
2730
2756
|
holdSeconds: 35, // holds others put the caller on
|
|
2731
2757
|
queueSeconds: 41, // waiting before anyone answered
|
|
2732
2758
|
transfers: 1, // hands the call has changed
|
|
@@ -2741,7 +2767,7 @@ something else, which will arrive under its own name and must not be folded in h
|
|
|
2741
2767
|
It rides in the snapshot and is replaced whole like everything else there.
|
|
2742
2768
|
|
|
2743
2769
|
Steps are `queued`, `offered`, `answered`, `held`, `muted`, `transferred`, `conferenced`,
|
|
2744
|
-
`unanswered`, and each is defined on `
|
|
2770
|
+
`unanswered`, and each is defined on `TaskInteractionStep`.
|
|
2745
2771
|
|
|
2746
2772
|
**The record is one entry per occurrence, oldest first.** Each hold is its own `held` entry — `at`
|
|
2747
2773
|
when it began, `seconds` once it ended and omitted while it runs — and a second hold is a second
|
|
@@ -2749,7 +2775,7 @@ entry after the first, never a revision of it. A leg that has ended states its d
|
|
|
2749
2775
|
task says whether a leg can still be running: a hold runs only while the task is `paused`, a mute
|
|
2750
2776
|
only while its media is up, so a `held` entry without `seconds` on a task that is not paused, or a
|
|
2751
2777
|
`muted` one on a call that is over, is a leg nobody closed and reads exactly like a leg running now
|
|
2752
|
-
(`task.
|
|
2778
|
+
(`task.interactionHistory.held.open`, `.muted.open`). Whoever performs the leg closes it — the
|
|
2753
2779
|
provider whose platform parks the caller, the host whose microphone it is — by restating the entry
|
|
2754
2780
|
with its duration, and **an entry that cannot be closed is not written**: open for ever is a
|
|
2755
2781
|
plausible nought one level down from a total. **A leg still open when the media ends is closed by
|
|
@@ -2762,7 +2788,7 @@ provider closed it. One publisher of the closed leg, and no order between them t
|
|
|
2762
2788
|
hold, at the entry's `at` plus its `seconds`, and nothing is lost by not naming it twice. The same goes for every step: two mutes are two
|
|
2763
2789
|
`muted` entries, and a call that joined two queues -- a menu's, then this one -- has two `queued`
|
|
2764
2790
|
entries, one per join. Order is enforced: an entry earlier than the one before it is refused
|
|
2765
|
-
(`task.
|
|
2791
|
+
(`task.interactionHistory.order`). **Intervals between steps are the host's to subtract**, never a
|
|
2766
2792
|
total the provider adds: *time to offer*, from the call's arrival to the agent's screen lighting
|
|
2767
2793
|
up, is the `offered` entry's `at` minus the last `queued` entry's, and the ring is outside it.
|
|
2768
2794
|
`queueSeconds` keeps the industry's meaning -- the whole wait until somebody answered -- and is not
|
|
@@ -2776,7 +2802,7 @@ on a resync snapshot -- carries every entry the host has already read, by step a
|
|
|
2776
2802
|
may add to them; it never has fewer, and never drops the record while the task is open. A fact
|
|
2777
2803
|
stated to the host and then withdrawn without an event is a record contradicting itself, exactly
|
|
2778
2804
|
as a task that lost the terms it had read would be, and the stream refuses it the same way
|
|
2779
|
-
(`stream.taskUpdated.
|
|
2805
|
+
(`stream.taskUpdated.interactionHistory`, `stream.snapshot.interactionHistory`). A provider whose
|
|
2780
2806
|
platform cannot hold a leg the host reported keeps it in the login's `store` for the life of the
|
|
2781
2807
|
task, so a reload of the host restates the same record; the entry is keyed by `step` and `at`, so
|
|
2782
2808
|
a running hold restated with its final `seconds` is the same entry.
|
|
@@ -2811,12 +2837,12 @@ counting the wrong thing.
|
|
|
2811
2837
|
the host's** — so without a step the provider, which alone keeps the task's record, would have no
|
|
2812
2838
|
account of a period the agent could not be heard. Each `muted` entry carries `mutedBy`, as the host
|
|
2813
2839
|
reported it: `host` for the agent's own Mute, `station` for a headset or system mute the host
|
|
2814
|
-
observed. No other step has it (`task.
|
|
2840
|
+
observed. No other step has it (`task.interactionHistory.mutedBy`, `.mutedBy.unexpected`).
|
|
2815
2841
|
|
|
2816
2842
|
**A step that dialled says which dial and where.** `transferred`, `conferenced` and `unanswered`
|
|
2817
2843
|
each end a dial, so the entry carries the `destinationId` it went to and, where a host placed it, the
|
|
2818
2844
|
`dialId` the host minted; no other step dialled, and either field on one is refused
|
|
2819
|
-
(`task.
|
|
2845
|
+
(`task.interactionHistory.dialId.unexpected`). This is how a dial made before a transfer is placeable
|
|
2820
2846
|
by whoever holds the task now: a `dial-outcome` naming a `dialId` the host never minted is looked
|
|
2821
2847
|
up in the record, not discarded.
|
|
2822
2848
|
|
|
@@ -2829,7 +2855,7 @@ Four rules a provider has to keep:
|
|
|
2829
2855
|
- **Omit `seconds` while it is unknown. Never send `0`.** A leg still talking is not a zero-second
|
|
2830
2856
|
conversation, and on live data that is the ordinary case rather than an edge. A zero is rejected.
|
|
2831
2857
|
- **`by` is a bare `UserId`, and not necessarily an agent.** A lead or a manager takes part
|
|
2832
|
-
|
|
2858
|
+
during an interaction too — a transfer accepted, a call conferenced in — so the field names whoever it was,
|
|
2833
2859
|
the same way `ImposedBreak.by` does. It comes from this provider's own directory, the same
|
|
2834
2860
|
namespace as `AuthenticationState.identity.id` and the team roster, so entries pair
|
|
2835
2861
|
within a provider and never across one.
|
|
@@ -2839,10 +2865,10 @@ Four rules a provider has to keep:
|
|
|
2839
2865
|
|
|
2840
2866
|
**An absent `by` means different things on different steps, and both are legitimate.** On
|
|
2841
2867
|
`queued` nobody takes part, so there is nothing to name. On every other step somebody did — see
|
|
2842
|
-
`
|
|
2868
|
+
`INTERACTION_STEPS_WITH_A_PERSON` and `interactionStepExpectsAPerson()` — so an absent `by` there says
|
|
2843
2869
|
*this was handled and the provider cannot say by whom*. The one step that is never unattributed is
|
|
2844
2870
|
`muted`: the host has exactly one agent and the provider knows who, so `by` is required there
|
|
2845
|
-
(`task.
|
|
2871
|
+
(`task.interactionHistory.muted.by`).
|
|
2846
2872
|
|
|
2847
2873
|
That case is ordinary rather than theoretical: a leg answered on a shared phone, a manager's
|
|
2848
2874
|
handset, or a device the provider cannot resolve to a person. **Report the step without `by`
|
|
@@ -2854,7 +2880,7 @@ A host must render the two differently. Showing an unattributed `answered` the s
|
|
|
2854
2880
|
`queued` tells the agent nobody was involved, which is not what was said. Omni renders it as
|
|
2855
2881
|
*"not recorded"* in the place the name would go.
|
|
2856
2882
|
|
|
2857
|
-
Omit `
|
|
2883
|
+
Omit `interactionHistory` entirely when the provider cannot observe the steps. Empty `steps` is a
|
|
2858
2884
|
different claim — it says the task has had none.
|
|
2859
2885
|
|
|
2860
2886
|
**What the record adds up to is stated, not summed.** A call that has changed hands arrives
|
|
@@ -2891,13 +2917,13 @@ elapsed while the leg runs and final once it has ended — and **the end is stat
|
|
|
2891
2917
|
inferred**: `ended: true` marks the last report, and it carries the final duration. **What a
|
|
2892
2918
|
provider never asked for never crosses.** The running report is sent only to a provider whose
|
|
2893
2919
|
manifest declares `runningStepReports`; every other provider receives exactly two reports per
|
|
2894
|
-
leg, when it began and when it ended, and `
|
|
2895
|
-
a running one it was never asked for (`
|
|
2920
|
+
leg, when it began and when it ended, and `validateInteractionReport(report, path, manifest)` refuses
|
|
2921
|
+
a running one it was never asked for (`interactionReport.running.unexpected`). What a provider that
|
|
2896
2922
|
did ask for them forwards upstream, and how often, is its own business. The step appears in
|
|
2897
|
-
`
|
|
2923
|
+
`interactionHistory` when the *provider* publishes it: Omni never writes the record itself.
|
|
2898
2924
|
`recordStep` is required of every softphone login's connection, since every call on a softphone
|
|
2899
2925
|
can be muted by the host, and answers `recorded` with the canonical history `at`. A `muted` report says whose the silence was,
|
|
2900
|
-
`mutedBy: "host"` or `"station"`, and no other report has the word (`
|
|
2926
|
+
`mutedBy: "host"` or `"station"`, and no other report has the word (`interactionReport.mutedBy`,
|
|
2901
2927
|
`.mutedBy.unexpected`). On a desk phone the microphone is the phone's:
|
|
2902
2928
|
the host mutes nothing and records nothing.
|
|
2903
2929
|
|
|
@@ -2920,7 +2946,7 @@ At a provider-confirmed task/media end, the host also stops the associated local
|
|
|
2920
2946
|
its observed ending where the report is still accepted. A host closing report repeats its original
|
|
2921
2947
|
key and may include its measured duration, but cannot reverse an already confirmed provider end.
|
|
2922
2948
|
The provider acknowledges a known closed leg without rewriting its final record; after the task
|
|
2923
|
-
has been
|
|
2949
|
+
has been completed, it may refuse the report as task-not-found. The host reads that response rather
|
|
2924
2950
|
than retrying or recreating the task. An observed host end before a provider closure is still
|
|
2925
2951
|
reported normally; the provider decides the final timestamp and publishes the record.
|
|
2926
2952
|
|
|
@@ -3052,9 +3078,9 @@ source is one more published fact about them, and the one that lets a host tell
|
|
|
3052
3078
|
|
|
3053
3079
|
**Completing the task is not a capability, and no capability set withholds it.** `complete` is
|
|
3054
3080
|
governed by `completionMode` alone: under `agent-command` it is always available, whatever the set
|
|
3055
|
-
says, and the `
|
|
3081
|
+
says, and the `outcomes` capability decides only whether a code travels with it. Likewise
|
|
3056
3082
|
Answer on a pending task and Call on a preview are the phase's controls, not the set's. The
|
|
3057
|
-
capability set governs what the agent may do *with* the task;
|
|
3083
|
+
capability set governs what the agent may do *with* the task; completing it is never on the
|
|
3058
3084
|
list.
|
|
3059
3085
|
|
|
3060
3086
|
**A permission that changes while the task is open is republished on the task at the moment it
|
|
@@ -3100,7 +3126,7 @@ Terms once read stay read: a re-read that fails mid-task is not a new fact about
|
|
|
3100
3126
|
last statement stands and the failure is a `diagnostic`, and a task that was published under
|
|
3101
3127
|
`queue` or `ungoverned` never returns to `undetermined`, on an update (`stream.taskUpdated.capabilitySource`)
|
|
3102
3128
|
or on a resync snapshot (`stream.snapshot.capabilitySource`); a record once read never loses an
|
|
3103
|
-
entry the same two ways (`stream.taskUpdated.
|
|
3129
|
+
entry the same two ways (`stream.taskUpdated.interactionHistory`, `stream.snapshot.interactionHistory`);
|
|
3104
3130
|
a snapshot carrying a task still at work does not forget the audio the stream held up, since media
|
|
3105
3131
|
ends on `task-media-ended` and the call moves on (`stream.snapshot.media`); a voice task ends after
|
|
3106
3132
|
its audio ends, never around it, whatever the outcome (`stream.taskEnded.mediaOpen`); and a task does not go
|
|
@@ -3146,7 +3172,7 @@ const taskCapabilities = {
|
|
|
3146
3172
|
channel: "voice",
|
|
3147
3173
|
capabilities: {
|
|
3148
3174
|
hold: true,
|
|
3149
|
-
|
|
3175
|
+
outcomes: true,
|
|
3150
3176
|
},
|
|
3151
3177
|
browsers: [],
|
|
3152
3178
|
} satisfies Pick<Task<"voice">, "channel" | "capabilities" | "browsers">;
|
|
@@ -3154,7 +3180,7 @@ const taskCapabilities = {
|
|
|
3154
3180
|
|
|
3155
3181
|
A capability says the control may be offered; the task's phase says whether there is anything to use
|
|
3156
3182
|
it on. Every control below that acts on the call or the conversation -- everything but `decline`,
|
|
3157
|
-
`connectBack` and `
|
|
3183
|
+
`connectBack` and `outcomes` -- is offered only while the task is `in-progress` or `paused`.
|
|
3158
3184
|
See **Which commands need a capability**.
|
|
3159
3185
|
|
|
3160
3186
|
### Voice capabilities
|
|
@@ -3163,27 +3189,47 @@ See **Which commands need a capability**.
|
|
|
3163
3189
|
| --- | --- | --- |
|
|
3164
3190
|
| `decline` | Pending-task button: Decline | The provider can decline a pending voice offer. Omni shows it only when provisioning also permits declining. |
|
|
3165
3191
|
| `hold` | Primary toggle: Hold | Omni may issue voice-task `hold` and `resume` commands. |
|
|
3166
|
-
| `endCall` | Primary button: End call | The provider ends the caller connection and all owned/inherited agent-added channels; wrap/
|
|
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
|
|
3192
|
+
| `endCall` | Primary button: End call | The provider ends the caller connection and all owned/inherited agent-added channels; wrap/completion remain separate. See **Ending a call, and removing one person from it**. |
|
|
3193
|
+
| `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 completes the task at provider end. See **Connecting back during completion**. |
|
|
3168
3194
|
| `coldTransfer` | Secondary menu item: Cold transfer | Omni may hand the customer straight to a destination, with nobody spoken to first. |
|
|
3169
3195
|
| `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**. |
|
|
3170
3196
|
| `leadAssist` | Secondary menu item: Lead assist | Omni may ask a lead to join this call, with a note. The lead's decision reaches the agent on `Task.leadAssist`. See **Lead assist**. |
|
|
3171
3197
|
| `conference` | Secondary button: Conference | Omni may dial a destination into the active call, and remove one person from it -- a conferenced entry, one still ringing included, which calls the dial off, or the party, leaving the agent with the colleague. See **Ending a call, and removing one person from it**. |
|
|
3172
3198
|
| `recording` | Overflow menu item: Recording | Per-task provider and host policies expose only their permitted recording actions; each has independent state and routing. |
|
|
3173
|
-
| `
|
|
3199
|
+
| `outcomes` | Primary button: Complete | Omni may request task completion with a provider outcome and notes. |
|
|
3174
3200
|
|
|
3175
3201
|
### Publishing codes and destinations
|
|
3176
3202
|
|
|
3177
3203
|
Four capabilities accept an object when the provider wants Omni to render real choices. For
|
|
3178
|
-
`
|
|
3204
|
+
`outcomes` alone, `true` remains valid and means "offer the control with nothing published";
|
|
3179
3205
|
the three directory controls -- `coldTransfer`, `warmTransfer`, `conference` -- carry their
|
|
3180
3206
|
directory or are refused, since once nothing is typed the directory is the control.
|
|
3181
3207
|
|
|
3182
|
-
#### `
|
|
3208
|
+
#### `outcomes`
|
|
3209
|
+
|
|
3210
|
+
An outcome describes what happened; completion finishes the task. Outcome codes are selected
|
|
3211
|
+
from the task's own published list. They are distinct from `DialOutcome`, which reports the
|
|
3212
|
+
result of a dial attempt.
|
|
3213
|
+
|
|
3214
|
+
Migration requires providers and hosts to adopt the renamed types and fields together:
|
|
3215
|
+
|
|
3216
|
+
| Former API | Current API |
|
|
3217
|
+
| --- | --- |
|
|
3218
|
+
| DispositionCode | `OutcomeCode` |
|
|
3219
|
+
| DispositionRules | `OutcomeRules` |
|
|
3220
|
+
| DispositionPayload | `OutcomePayload` |
|
|
3221
|
+
| Task.capabilities.dispositions | `Task.capabilities.outcomes` |
|
|
3222
|
+
| complete.disposition | `complete.outcome` |
|
|
3223
|
+
|
|
3224
|
+
Validation and conformance diagnostics use `task.outcomes`, `task.outcome`, and
|
|
3225
|
+
`command.complete.outcome` in place of the former names. Update retained task snapshots and
|
|
3226
|
+
command producers as well as imports. Old capability and command fields are rejected, including
|
|
3227
|
+
payloads containing both names; there are no compatibility aliases. Code IDs, notes, required
|
|
3228
|
+
selection rules, and completion behavior are unchanged.
|
|
3183
3229
|
|
|
3184
3230
|
```ts
|
|
3185
3231
|
capabilities: {
|
|
3186
|
-
|
|
3232
|
+
outcomes: {
|
|
3187
3233
|
required: true,
|
|
3188
3234
|
notes: "optional",
|
|
3189
3235
|
codes: [
|
|
@@ -3198,9 +3244,9 @@ capabilities: {
|
|
|
3198
3244
|
| --- | --- |
|
|
3199
3245
|
| `required` | When `true`, Omni must collect a code before issuing `complete`. A required policy must publish at least one code. |
|
|
3200
3246
|
| `notes` | `required`, `optional`, or `none`; controls the free-text field beside the code. |
|
|
3201
|
-
| `codes` | Codes Omni offers. `id` values are non-empty and unique; Omni sends the chosen `id` as `TaskCommand.complete.
|
|
3247
|
+
| `codes` | Codes Omni offers. `id` values are non-empty and unique; Omni sends the chosen `id` as `TaskCommand.complete.outcome`. |
|
|
3202
3248
|
|
|
3203
|
-
With `
|
|
3249
|
+
With `outcomes: true` Omni shows a Complete control and sends `complete` with no code, because
|
|
3204
3250
|
the provider published none.
|
|
3205
3251
|
|
|
3206
3252
|
#### `coldTransfer`, `warmTransfer` and `conference`
|
|
@@ -3234,7 +3280,7 @@ queue, named agent or another configured destination. No destination kind is inf
|
|
|
3234
3280
|
label. The host offers only the directory attached to this task's specific transfer capability,
|
|
3235
3281
|
and sends the exact destination ID; it does not invent an agent picker or arbitrary dial target.
|
|
3236
3282
|
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
|
|
3283
|
+
end the caller journey; a later assignment to the same agent is a new interaction.
|
|
3238
3284
|
|
|
3239
3285
|
#### Ending a call, and removing one person from it
|
|
3240
3286
|
|
|
@@ -3242,7 +3288,7 @@ A call has everyone on `Task.onCall`, and two commands take people off it, both
|
|
|
3242
3288
|
provider.
|
|
3243
3289
|
|
|
3244
3290
|
```ts
|
|
3245
|
-
{ type: "end-call" } // provider ends caller and owned/inherited agent-added channels; wrap/
|
|
3291
|
+
{ type: "end-call" } // provider ends caller and owned/inherited agent-added channels; wrap/completion are separate
|
|
3246
3292
|
{ type: "conference", action: "remove", party: true } // the customer leaves; the agent stays with the colleague
|
|
3247
3293
|
{ type: "conference", action: "remove", destinationId: "tier2" } // the conferenced person leaves; ringing, this calls the dial off
|
|
3248
3294
|
```
|
|
@@ -3250,20 +3296,20 @@ provider.
|
|
|
3250
3296
|
The host sends the ordinary task-scoped `execute` request with `command: { type: "end-call" }`.
|
|
3251
3297
|
The provider knows the caller and agent-added channel bindings; no channel list or owner details
|
|
3252
3298
|
are sent by the host. The task's `endCall` capability is permission, not an executor selection.
|
|
3253
|
-
`validateTaskCommandRequest` checks the exact
|
|
3299
|
+
`validateTaskCommandRequest` checks the exact interaction/allocation and delegates the command's
|
|
3254
3300
|
policy/phase checks. The provider rechecks current ownership atomically; no client validator can
|
|
3255
3301
|
prove that its view is fresh. The host does not implement agent end-call; low-level removal of individual channels is not
|
|
3256
3302
|
this agent action. There is no host end-call execution API or fallback route.
|
|
3257
3303
|
|
|
3258
|
-
`end-call` is gated by `endCall` on the current
|
|
3259
|
-
all agent-added channels owned by that
|
|
3304
|
+
`end-call` is gated by `endCall` on the current interaction. It ends the caller connection and
|
|
3305
|
+
all agent-added channels owned by that interaction, including channels originally added by previous
|
|
3260
3306
|
agents and inherited through transfer or takeover. Ownership is not inferred from who originally
|
|
3261
3307
|
dialed, the current UI selection, or a person's label. The provider maintains that authority
|
|
3262
|
-
and checks the exact
|
|
3308
|
+
and checks the exact interaction/allocation when acting. Agent-added channels are not left behind
|
|
3263
3309
|
merely because another agent added them.
|
|
3264
3310
|
|
|
3265
3311
|
On a successful transfer or takeover, ownership of those channels passes to the receiving
|
|
3266
|
-
|
|
3312
|
+
interaction (lead or another agent), not just ownership of the caller leg. The outgoing interaction may
|
|
3267
3313
|
still have its allowed wrap work, but cannot disconnect channels it no longer owns. A pending or
|
|
3268
3314
|
unknown handover is not a confirmed ownership change: retain source reservations and reconcile.
|
|
3269
3315
|
The provider must serialize handover and end-call so a delayed outgoing command cannot tear down
|
|
@@ -3271,12 +3317,12 @@ the recipient's call. No public channel-owner field or guessed identity is intro
|
|
|
3271
3317
|
the source must provide authoritative ownership and command fencing before declaring permission.
|
|
3272
3318
|
|
|
3273
3319
|
The resulting end of this agent's media is published as `task-media-ended`. Any wrap remains
|
|
3274
|
-
until its own completion
|
|
3320
|
+
until its own completion; `end-call` is not `task-ended`, and `complete` is not an implicit
|
|
3275
3321
|
caller/channel disconnect. Other agents' independent tasks end or wrap only on their own source
|
|
3276
|
-
transitions. Neither
|
|
3322
|
+
transitions. Neither completion nor transfer itself ends the continuing caller journey.
|
|
3277
3323
|
|
|
3278
3324
|
`conference` `remove` remains a person-specific provider operation, gated by `conference`.
|
|
3279
|
-
It does not implicitly
|
|
3325
|
+
It does not implicitly complete any other interaction. Its person/consultation prerequisites
|
|
3280
3326
|
remain separate from the permission to disconnect the caller. A remove that would leave the
|
|
3281
3327
|
agent alone is not a remove but an `end-call`, and the provider answers it `failed`.
|
|
3282
3328
|
The consulted destination of a warm transfer is not removed this way:
|
|
@@ -3317,8 +3363,8 @@ that offers `warmTransfer` implements all three.
|
|
|
3317
3363
|
`applied` on `complete` says the provider is bridging the customer to the destination and
|
|
3318
3364
|
dropping the agent's leg. What follows is what follows any call the agent leaves: the agent's
|
|
3319
3365
|
media ends and the provider reports `task-media-ended`, the task moves to `completing`, any wrap
|
|
3320
|
-
allowance runs, and the task ends `completed` as any call does. A warm complete is not a
|
|
3321
|
-
the agent has a wrap to do, so nothing is owed within `
|
|
3366
|
+
allowance runs, and the task ends `completed` as any call does. A warm complete is not a completion:
|
|
3367
|
+
the agent has a wrap to do, so nothing is owed within `completionSettleMs`. `transferred` names a
|
|
3322
3368
|
cold transfer alone, where the agent had no wrap. `applied` on `cancel` says the destination is
|
|
3323
3369
|
dropped; the task returns to `in-progress` with the `consulted` entry gone. Omni waits for the
|
|
3324
3370
|
provider's report of both, as it does for every command.
|
|
@@ -3332,15 +3378,15 @@ customer.
|
|
|
3332
3378
|
| Capability | Omni UI | Contract |
|
|
3333
3379
|
| --- | --- | --- |
|
|
3334
3380
|
| `decline` | Pending-task button: Decline | The provider can decline a pending chat offer. Omni shows it only when provisioning also permits declining. |
|
|
3335
|
-
| `hold` | Primary toggle: Hold | Omni may pause and resume agent
|
|
3336
|
-
| `
|
|
3381
|
+
| `hold` | Primary toggle: Hold | Omni may pause and resume the agent’s interaction in the chat. |
|
|
3382
|
+
| `outcomes` | Primary button: Complete | Omni may request task completion with a provider outcome and notes. |
|
|
3337
3383
|
|
|
3338
3384
|
### Email capabilities
|
|
3339
3385
|
|
|
3340
3386
|
| Capability | Omni UI | Contract |
|
|
3341
3387
|
| --- | --- | --- |
|
|
3342
3388
|
| `decline` | Pending-task button: Decline | The provider can decline a pending email offer. Omni shows it only when provisioning also permits declining. |
|
|
3343
|
-
| `
|
|
3389
|
+
| `outcomes` | Primary button: Complete | Omni may request task completion with a provider outcome and notes. |
|
|
3344
3390
|
|
|
3345
3391
|
### Custom capabilities
|
|
3346
3392
|
|
|
@@ -3426,13 +3472,13 @@ expect(result).toEqual({ status: "dialling", dialId: "dial-7f2" });
|
|
|
3426
3472
|
const outcome: ProviderEvent<"voice"> = { type: "dial-outcome", dialId: "dial-7f2", taskId, allocationId, destinationId: "tier2", outcome: "no-answer", reason: "No route to destination" };
|
|
3427
3473
|
|
|
3428
3474
|
// 4. In the record, so a dial made before a transfer is placeable by whoever holds the task now.
|
|
3429
|
-
const step:
|
|
3475
|
+
const step: TaskInteractionStep = { step: "unanswered", at: "2026-08-21T09:15:30Z", by: "A-12", dialId: "dial-7f2", destinationId: "tier2" };
|
|
3430
3476
|
```
|
|
3431
3477
|
|
|
3432
3478
|
The identity is a correlation handle between host and provider and **is never shown to the
|
|
3433
3479
|
agent**. A host holding several tasks places an outcome by the `dialId` it minted; one it did not
|
|
3434
3480
|
mint -- a dial made by the previous agent before a transfer -- it finds on the task's `onCall` or in
|
|
3435
|
-
its `
|
|
3481
|
+
its `interactionHistory`, which is why the steps that dial carry it. The `dialId` is not a retry key:
|
|
3436
3482
|
Omni never repeats a dial, and a command still carries no key for that purpose.
|
|
3437
3483
|
|
|
3438
3484
|
**`dialling` is the answer to a dial, and `applied` is not.** A command that dials answers
|
|
@@ -3462,7 +3508,7 @@ and the harness places the outcome all the same (`TaskStream`, `stream.dialOutco
|
|
|
3462
3508
|
call it belonged to and never against the next one.
|
|
3463
3509
|
|
|
3464
3510
|
The stream knows a dial from three places: `TaskStream.dialled(dialId)`, which a host calls when it
|
|
3465
|
-
places one; an entry on a task's `onCall`; and a step in its `
|
|
3511
|
+
places one; an entry on a task's `onCall`; and a step in its `interactionHistory`. Since a dialled entry
|
|
3466
3512
|
is listed from placement, `dialled()` is load-bearing only for a dial that never has an entry -- a
|
|
3467
3513
|
cold transfer, a connect-back, an idle dialpad call -- or whose entry has already left the room; an
|
|
3468
3514
|
outcome for a dial nobody placed and no task mentions is what `stream.dialOutcome.unknown` refuses.
|
|
@@ -3507,12 +3553,12 @@ the platform added itself carries none. `held: true` is presence as claim. A sna
|
|
|
3507
3553
|
after a transfer reads the room from here rather than inferring it from a sequence of outcomes it
|
|
3508
3554
|
never saw.
|
|
3509
3555
|
|
|
3510
|
-
**`onCall` describes this agent's current
|
|
3511
|
-
and added channels remain connected, the remaining room is still valid. When this
|
|
3556
|
+
**`onCall` describes this agent's current interaction.** When the caller disconnects but the agent
|
|
3557
|
+
and added channels remain connected, the remaining room is still valid. When this interaction's
|
|
3512
3558
|
media ends or the task enters `completing`, clear its `onCall` view; absence is used only by a
|
|
3513
3559
|
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
|
|
3515
|
-
task may remain open for wrap.
|
|
3560
|
+
agents' channels ended. A published room receives a final empty view for this interaction, and the
|
|
3561
|
+
task may remain open for wrap. Completion is a separate task action, not the caller's disconnect.
|
|
3516
3562
|
|
|
3517
3563
|
**A field that describes the present is cleared by the transition that ends it.** `onCall`,
|
|
3518
3564
|
`previewEndsAt` and `atDeadline` are three instances of one shape, and there will be more: each
|
|
@@ -3793,7 +3839,7 @@ supports mapping them onto, and every member means something specific:
|
|
|
3793
3839
|
| `meal` | A meal: lunch, dinner, whatever the shift calls it. |
|
|
3794
3840
|
| `rest` | A rest period the agent is entitled to and a busy hour cannot cancel. Usually the reason also marked `alwaysAvailable`, though the two are separate: this says what the break *is*, that says whether policy can withdraw it. |
|
|
3795
3841
|
| `training` | Learning something the agent is expected to know afterwards: a course, e-learning, a product walkthrough. However it is delivered, and whoever attends. |
|
|
3796
|
-
| `coaching` | Reviewing this agent's own work with somebody accountable for it: a call listened back, quality feedback, a one-to-one about their
|
|
3842
|
+
| `coaching` | Reviewing this agent's own work with somebody accountable for it: a call listened back, quality feedback, a one-to-one about their interactions. Even where the outcome is that they learn something. |
|
|
3797
3843
|
| `meeting` | A scheduled gathering that is neither — a team huddle, a project call, a town hall. The agent attends and contributes; nobody is assessing their work and there is nothing they must know by the end. |
|
|
3798
3844
|
| `administrative` | Paperwork and follow-up not attached to a particular contact. |
|
|
3799
3845
|
| `technical` | Equipment or system trouble stopping the agent taking work: a dead headset, a phone that never registered, a tool that will not load. The one member that is not an activity — it says why the agent *cannot* work rather than what they are doing, which makes it the right home for a not-ready state raised about the agent's equipment. |
|
|
@@ -4000,7 +4046,7 @@ Each availability value means one thing:
|
|
|
4000
4046
|
| Value | Meaning |
|
|
4001
4047
|
| --- | --- |
|
|
4002
4048
|
| `ready` | Signed in, able to take work, none assigned. |
|
|
4003
|
-
| `on-task` |
|
|
4049
|
+
| `on-task` | Working on at least one task. It says nothing about how many, and nothing about whether more will fit. |
|
|
4004
4050
|
| `on-break` | Stopped and not taking work, whether they asked or somebody stopped them. The reason lives on their own `BreakState`, not here. |
|
|
4005
4051
|
| `reserved` | Signed in here, and the host holds this agent's capacity for another provider (`count: 0`, host-stopped): not receiving this provider's work, and not on a break. See **Capacity**. |
|
|
4006
4052
|
| `signed-out` | Known to this team but not signed in to this provider. |
|
|
@@ -4111,7 +4157,7 @@ choice that is no command at all:
|
|
|
4111
4157
|
| --- | --- | --- |
|
|
4112
4158
|
| `{ type: "lead-assist", action: "take-over" }` | `task-media-ended`, then `task-ended` with `{ type: "taken-over", leadId }`: **no `completing` window**, the agent is idle at once. The audio ends first, as before every voice ending (`stream.taskEnded.mediaOpen`), and the lead is named by user id, since a lead is not a directory item | Continues alone, and ends as any call does |
|
|
4113
4159
|
| `{ type: "lead-assist", action: "leave" }` | Continues; `leadAssist` is cleared | `task-media-ended`, then `task-ended` with `{ type: "left" }` -- the call goes on without them |
|
|
4114
|
-
| Stays until the customer hangs up | `task-media-ended`, `completing`, its own
|
|
4160
|
+
| Stays until the customer hangs up | `task-media-ended`, `completing`, its own outcome | The same, independently: **both have the completion window** |
|
|
4115
4161
|
|
|
4116
4162
|
`left` is the one outcome that ends a task without ending the call: this agent left a call that
|
|
4117
4163
|
continues without them. It reads as neither a completion nor a cancellation, because it is
|
|
@@ -4120,7 +4166,7 @@ neither.
|
|
|
4120
4166
|
```ts
|
|
4121
4167
|
const leadAssistCapable = {
|
|
4122
4168
|
channel: "voice",
|
|
4123
|
-
capabilities: { hold: true, leadAssist: true,
|
|
4169
|
+
capabilities: { hold: true, leadAssist: true, outcomes: true },
|
|
4124
4170
|
phase: "in-progress",
|
|
4125
4171
|
leadAssist: { stage: "joined", leadId: "L-9", note: "Refund dispute, needs approval", since: "2026-08-21T09:04:00Z" },
|
|
4126
4172
|
} satisfies Pick<Task<"voice">, "channel" | "capabilities" | "phase" | "leadAssist">;
|
|
@@ -4171,7 +4217,7 @@ the state, restated on every change, and a `whisper` or `barge` the login's list
|
|
|
4171
4217
|
is answered `failed`. **A lead listens to one call at a time** (`snapshot.monitoring.single`), and
|
|
4172
4218
|
a task is a joined call or a monitored one, never both (`task.monitoring.assisting`). The member's
|
|
4173
4219
|
call ending ends the lead's task as it ends the member's, with `left`, since the lead was never
|
|
4174
|
-
|
|
4220
|
+
working on it.
|
|
4175
4221
|
|
|
4176
4222
|
**A lead listens only while holding no work of their own.** A monitoring task is the lead's only
|
|
4177
4223
|
task, and a snapshot carrying one beside any other is refused (`snapshot.monitoring.alone`); Omni
|
|
@@ -4441,8 +4487,8 @@ with a stale view: a flip against a state the provider has already changed turns
|
|
|
4441
4487
|
then off again. A custom `toggle` control therefore carries its own boolean. `hold` and `resume`,
|
|
4442
4488
|
`pause` and `resume` need no flag, being pairs rather than toggles.
|
|
4443
4489
|
|
|
4444
|
-
**`complete` sends
|
|
4445
|
-
`
|
|
4490
|
+
**`complete` sends an outcome only where one was published.** `outcome` is a
|
|
4491
|
+
`OutcomeCode.id` from the task's own `outcomes` capability, and `notes` obeys that
|
|
4446
4492
|
capability's `notes` setting. A task publishing no codes still receives `complete`, with neither.
|
|
4447
4493
|
|
|
4448
4494
|
### Where a command executes
|
|
@@ -4492,14 +4538,14 @@ and an adapter would take the agent out of ready in the middle of a call. A brow
|
|
|
4492
4538
|
mute through the capture track's muted state, read-only, and cannot lift it; a native host reads
|
|
4493
4539
|
the endpoint and can clear an operating-system mute. A hardware slider is nobody's to clear.
|
|
4494
4540
|
|
|
4495
|
-
**The record carries the same word.** A `muted`
|
|
4541
|
+
**The record carries the same word.** A `muted` interaction leg is a period the agent could not be
|
|
4496
4542
|
heard, and the record exists so that period is not a hole. The host reports every such period
|
|
4497
4543
|
through `recordStep` -- its own Mute, and a station mute it observed during a call -- with
|
|
4498
4544
|
`mutedBy` saying whose the silence was, and the provider writes the word into the entry
|
|
4499
|
-
(`task.
|
|
4545
|
+
(`task.interactionHistory.mutedBy`). The report names no agent because the host has exactly one, and
|
|
4500
4546
|
the provider knows who that is: it attributes the leg to the login's agent in `by`, a fact it
|
|
4501
4547
|
holds and not an inference, for a station mute as much as for the host's own. An unattributed
|
|
4502
|
-
`muted` entry is refused (`task.
|
|
4548
|
+
`muted` entry is refused (`task.interactionHistory.muted.by`); a shared handset's unattributed hold
|
|
4503
4549
|
is a different claim, and stands. A supervisor reading the record then sees "the agent muted for
|
|
4504
4550
|
forty seconds" and "the agent's headset was muted for forty seconds" as the different things they
|
|
4505
4551
|
are. See **The host records what it performs**.
|
|
@@ -4547,7 +4593,7 @@ declared:
|
|
|
4547
4593
|
| `conference` with `action: "remove"` | The `conference` capability, and somebody else on the call: a remove that would leave the agent alone is `end-call`, and a provider answers it `failed`. |
|
|
4548
4594
|
| `decline` | The `decline` capability on any channel, **and** Omni provisioning permitting it. One word for refusing an offer, whatever the channel. |
|
|
4549
4595
|
| `call` | The `preview` phase. A record put in front of an agent is there to be called, so the phase is the gate and there is no capability. It is a dial, with a `dialId` and a `dial-outcome`. |
|
|
4550
|
-
| `complete` | `completionMode: "agent-command"`. The `
|
|
4596
|
+
| `complete` | `completionMode: "agent-command"`. The `outcomes` capability decides whether a code travels with the command, never whether the command exists — a task Omni cannot complete never ends. What travels is what the capability published: a code from its list where it has one (`command.complete.outcome.unknown`), a code at all where it requires one (`.outcome.required`), notes as it said (`.notes.required`, `.notes.unexpected`), and neither where the task declares no outcomes (`.outcome.unexpected`). |
|
|
4551
4597
|
| `connect-back` | The `connectBack` 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. |
|
|
4552
4598
|
| `transfer` with `action: "warm"` | The `warmTransfer` capability. `action: "cold"` is gated by `coldTransfer`; the two are declared and offered separately. |
|
|
4553
4599
|
| `transfer` with `action: "complete"` or `"cancel"` | A consultation in progress -- a `consulted` entry on `Task.onCall`. Without one there is nothing to complete or cancel, and a provider that receives either answers `failed`. |
|
|
@@ -4557,15 +4603,15 @@ declared:
|
|
|
4557
4603
|
| `custom` | A control the task published under `capabilities.custom`, by its `id` (`command.capability.custom`), carrying a non-empty string for each `required` prompt field and strings for any optional fields supplied (`command.custom.prompt`). A toggle carries its target `on` boolean (`command.custom.on`). |
|
|
4558
4604
|
| Everything else | Its own named capability. |
|
|
4559
4605
|
|
|
4560
|
-
**A control on the contact belongs to the
|
|
4606
|
+
**A control on the contact belongs to the interaction phases**, `in-progress` and `paused`:
|
|
4561
4607
|
`hold`, `resume` and `pause`, `end-call`, every `transfer` and `conference` action, and
|
|
4562
|
-
every `lead-assist` action. These controls act within this agent's current
|
|
4563
|
-
In `completing`, that
|
|
4564
|
-
an IVR, queue or another agent's
|
|
4565
|
-
of this
|
|
4566
|
-
declared while the task remains open; the phase prevents this task from controlling
|
|
4567
|
-
it no longer owns. Omni shows these controls only in the two
|
|
4568
|
-
`validateTaskCommand` refuses them outside those phases (`command.phase.
|
|
4608
|
+
every `lead-assist` action. These controls act within this agent's current interaction.
|
|
4609
|
+
In `completing`, that interaction has ended and its wrap work remains. The caller may still be in
|
|
4610
|
+
an IVR, queue or another agent's interaction, and other channels may remain connected. Completion
|
|
4611
|
+
of this interaction does not establish that the caller or bridge ended. The capability stays
|
|
4612
|
+
declared while the task remains open; the phase prevents this task from controlling an interaction
|
|
4613
|
+
it no longer owns. Omni shows these controls only in the two interaction phases, and
|
|
4614
|
+
`validateTaskCommand` refuses them outside those phases (`command.phase.interaction`).
|
|
4569
4615
|
The commands with a phase of their own -- `answer`, `accept` and `decline` in `pending`,
|
|
4570
4616
|
`call` in `preview`, `connect-back` in `completing`, `complete` in any -- are not among them.
|
|
4571
4617
|
|
|
@@ -4955,14 +5001,14 @@ A `left` outcome ends the task for this agent alone: the call continues without
|
|
|
4955
5001
|
when a lead who joined it leaves -- see **Lead assist**.
|
|
4956
5002
|
|
|
4957
5003
|
A successful `complete` or `transfer` command does not clear the task. Omni waits for `task-ended`,
|
|
4958
|
-
and not for ever: `applied` to a
|
|
4959
|
-
provider has
|
|
4960
|
-
manifest's `
|
|
5004
|
+
and not for ever: `applied` to a completion -- `complete`, or a lead's `take-over` -- says the
|
|
5005
|
+
provider has completed the task, and its `task-ended` follows within the
|
|
5006
|
+
manifest's `completionSettleMs`. A provider never answers `applied` for a completion it has not yet
|
|
4961
5007
|
performed. Past the bound the host calls `snapshot()`: a snapshot still carrying the task is a task
|
|
4962
5008
|
held open by a provider that said it was done, and the desk shows it as unsettled -- "Completing...
|
|
4963
5009
|
the provider has not confirmed" -- naming the command; a snapshot no longer carrying it clears the
|
|
4964
5010
|
task, since the ending was owed and lost. The drive holds a provider to the same bound
|
|
4965
|
-
(`drive.
|
|
5011
|
+
(`drive.completion.unsettled`). The `task-media-ended` event and the `completing` phase are likewise
|
|
4966
5012
|
non-terminal. A replacement
|
|
4967
5013
|
snapshot that no longer contains the task also clears it. Repeated `task-ended` delivery with the
|
|
4968
5014
|
same envelope ID is harmless, and a `task-ended` naming an allocation that has already ended is
|
|
@@ -5048,7 +5094,7 @@ The same treatment for a `UserId`, and needed for the same reason: user identifi
|
|
|
5048
5094
|
issued by each provider independently, so two providers will eventually issue the same string for
|
|
5049
5095
|
different people. Encode and join before storing or comparing.
|
|
5050
5096
|
|
|
5051
|
-
Use it for every `UserId` — `
|
|
5097
|
+
Use it for every `UserId` — `interactionHistory[].by`, roster members, `memberId` on a
|
|
5052
5098
|
lead command, `ImposedBreak.by`. A bare one is only ever compared against another from the **same** provider; anything
|
|
5053
5099
|
wider goes through this key.
|
|
5054
5100
|
|
|
@@ -5077,13 +5123,13 @@ same exported checks are used by Omni and adapter tests so their interpretations
|
|
|
5077
5123
|
| `validateProviderTimeCheckRequest(request)` | Fresh-request shape; the host enforces actual uniqueness and outstanding-request lifetime. |
|
|
5078
5124
|
| `validateProviderTimeCheckResult(result, request, loginId)` | ISO timestamp, clock identity, exact request/login correlation; timing and source accuracy remain runtime checks. |
|
|
5079
5125
|
| `validateProviderTimeEstimate(estimate, scope)` | Optional host estimate shape and provider/login scope; no accuracy guarantee. |
|
|
5080
|
-
| `
|
|
5126
|
+
| `validateInteractionReport(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`. |
|
|
5081
5127
|
| `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. |
|
|
5082
5128
|
| `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. |
|
|
5083
5129
|
| `validateLoginStore(store)` | The login's store the host hands every connection: an object with `get`, `set` and `delete` (`store.shape`, `store.get`, `.set`, `.delete`). The harness holds `ConnectContext.store` to it. |
|
|
5084
5130
|
| `validateCapacity(capacity)` | What the host states as capacity: a whole number of zero or more (`capacity.count`), zero being host-stopped. The harness states one on connect and two, one and zero after the drive, each answered `applied`, and holds any offer to the count in force (`stream.taskOffered.overCapacity`). |
|
|
5085
5131
|
| `validateAuthenticationResult(result, method)` | What `start()` or `complete()` answered: a challenge or a rejection, a login or a rejection. A rejection's failure is held to its rules -- an `omni.` code the contract lists, and `omni.phone-not-permitted` never retryable, since the agent's station is configuration. `validateAuthenticationFailure(failure)` is the same check on a failure alone. |
|
|
5086
|
-
| `validateTaskCommand(command, task?)` | What a command needs to be issuable, against the task it names: its own shape -- a dial's `dialId`, a transfer's item, a remove naming exactly one person -- and, with the task, the capability the table above gates it on (`command.capability.<name>`, `.locked`), the phase it belongs to (`command.phase.*`, `command.phase.
|
|
5132
|
+
| `validateTaskCommand(command, task?)` | What a command needs to be issuable, against the task it names: its own shape -- a dial's `dialId`, a transfer's item, a remove naming exactly one person -- and, with the task, the capability the table above gates it on (`command.capability.<name>`, `.locked`), the phase it belongs to (`command.phase.*`, `command.phase.interaction` for every control on the call or the conversation), and the state that has to stand: a consulted entry, a lead requested, somebody else still on the call (`command.conference.remove.alone`). A host validates before sending and an adapter before acting. |
|
|
5087
5133
|
| `validateResult(result, method)` | What a connection method answered: the status it gives, a failure where the status says so and nowhere else, the failure's shape, and that an `omni.` code is one this contract names. |
|
|
5088
5134
|
| `validateAuthenticationState(state)` | The identity each state must carry, the capabilities a usable login declares, and the expiry that only `authenticated` may. Omni applies it to every state a session publishes — the republished as much as the first. |
|
|
5089
5135
|
|
|
@@ -5159,7 +5205,7 @@ latest the session published during the run, which differs only when the adapter
|
|
|
5159
5205
|
A capability granted by a later login requires its methods just as one declared at sign-in does.
|
|
5160
5206
|
|
|
5161
5207
|
`result.notExercised` lists what the run never reached — one subject per family of rules: each
|
|
5162
|
-
optional part of a task (`task.browsers`, `task.
|
|
5208
|
+
optional part of a task (`task.browsers`, `task.interactionHistory`, `task.leadAssist`, …), the break's
|
|
5163
5209
|
`reasons` and `imposed`, the roster's `members` and `requests`, each declared contribution, and
|
|
5164
5210
|
each event type (`event.task-ended`, …) — and so what a clean `violations` says nothing about.
|
|
5165
5211
|
Nothing there is a violation: an adapter with no team has nothing to exercise. But a fixture with
|
|
@@ -5171,7 +5217,7 @@ outcome -- stays in `notExercised` for every adapter, and a rule about a live ca
|
|
|
5171
5217
|
in each adopter's own tests. `{ drive: true }` closes that: the exercise takes the first task the
|
|
5172
5218
|
provider offers through one ordinary lifecycle -- accept it, wait for its media and open it on a
|
|
5173
5219
|
softphone, hold and resume where the task offers `hold`, end the call where it offers `endCall`,
|
|
5174
|
-
complete it with
|
|
5220
|
+
complete it with an outcome where the agent completes -- and holds every step to the rules a
|
|
5175
5221
|
host holds a provider to. Each command is validated against the task as published
|
|
5176
5222
|
(`drive.command.*`), each answer for its method, a refusal of a control the task offered is a
|
|
5177
5223
|
violation (`drive.command.failed`), and an event the provider owes and never sends is one too
|
|
@@ -5193,7 +5239,7 @@ gap and not a pass. With the audio open on a softphone,
|
|
|
5193
5239
|
the drive mutes it for one second and reports the leg through `recordStep`, begun and then ended,
|
|
5194
5240
|
expecting each report `recorded` with the provider-selected history `at` (`drive.recordStep.failed`, `.rejected`, `result.recordStep.at`); then it mutes again and
|
|
5195
5241
|
ends the call muted, as agents do, so the leg is open when the media ends, the provider closes it
|
|
5196
|
-
in the completing publication or the open entry is refused (`task.
|
|
5242
|
+
in the completing publication or the open entry is refused (`task.interactionHistory.muted.open`),
|
|
5197
5243
|
and the drive's closing report after the media ended is expected `recorded` and to change nothing:
|
|
5198
5244
|
a record restated afterwards with that leg's duration altered is named (`drive.recordStep.overwritten`).
|
|
5199
5245
|
Where the provider restates the task's record afterwards, each leg is in it or the hole is named
|
|
@@ -5206,7 +5252,7 @@ connection to the end. A platform that holds the record hands it back, and an ad
|
|
|
5206
5252
|
the record in memory has nothing and is named (`drive.reload.snapshot`, `drive.reload.history`,
|
|
5207
5253
|
`.rejected`). The second adapter's snapshot is taken as any resync is: held to what the stream knew
|
|
5208
5254
|
before it replaces it, so a phase gone backwards, a record that shrank or audio forgotten on a task
|
|
5209
|
-
still at work is named by the stream's own rules (`stream.snapshot.phase`, `.
|
|
5255
|
+
still at work is named by the stream's own rules (`stream.snapshot.phase`, `.interactionHistory`,
|
|
5210
5256
|
`.media`) and a reload is a place those rules keep working, not one where they stop. The second
|
|
5211
5257
|
adapter is held to what the first was: the same provider
|
|
5212
5258
|
(`drive.reload.manifest`), a snapshot that stands as any snapshot must, and a record that lost none
|
|
@@ -5223,11 +5269,11 @@ handed the adapter holds no key naming the task's id, delimited, never as a run
|
|
|
5223
5269
|
another id (`drive.store.retained`); an adapter whose keys never named the task leaves the rule
|
|
5224
5270
|
unevaluated, and the result says so rather than passing it: a task's keys go
|
|
5225
5271
|
with the task, or the next offer of the same id inherits them. Outside the
|
|
5226
|
-
|
|
5227
|
-
`confirmed`, where the provider publishes it, and in `completing` once this agent's
|
|
5272
|
+
interaction phases with `hold` still declared -- in
|
|
5273
|
+
`confirmed`, where the provider publishes it, and in `completing` once this agent's interaction has ended -- the
|
|
5228
5274
|
drive sends `hold` past the validator that would hold it back, and expects `failed`: the adapter
|
|
5229
5275
|
is the second gate on a control on the contact, and one that applies it outside this task's
|
|
5230
|
-
current
|
|
5276
|
+
current interaction is named (`drive.command.interaction`). The drive cannot put a task into a phase the provider
|
|
5231
5277
|
never publishes, so a provider that goes straight from `pending` to `in-progress` is checked in
|
|
5232
5278
|
`completing` alone. A task the agent completes is
|
|
5233
5279
|
completed from wherever it stands once the drive has nothing left to do on it -- from `completing`
|
|
@@ -5332,10 +5378,10 @@ This package declares that contract; it supplies no recorder, media mixing, stor
|
|
|
5332
5378
|
|
|
5333
5379
|
Stop finishes the recording and retains captured audio. Cancel abandons the recording and discards
|
|
5334
5380
|
its captured audio; the task policy offers it with `cancel: true`. There is no configurable cancel
|
|
5335
|
-
effect. A recorder that cannot confirm
|
|
5381
|
+
effect. A recorder that cannot confirm completion must not offer Cancel; it can offer Stop instead.
|
|
5336
5382
|
Cancel never means cancelling an in-flight start request or deleting arbitrary past recordings.
|
|
5337
5383
|
Stop can be applied only after finalization/retention succeeds; Cancel only after both cessation
|
|
5338
|
-
and
|
|
5384
|
+
and completion of this recording's audio succeed under the recorder's storage contract.
|
|
5339
5385
|
Partial success (capture stopped but storage outcome unknown) cannot return failed with a claim
|
|
5340
5386
|
of no effect. It rejects with unknown outcome, reports the failure visibly and publishes whatever
|
|
5341
5387
|
current capture state is actually known. Retention is not a promise of sample-perfect audio.
|
|
@@ -5353,7 +5399,7 @@ host until its executor reconciles/finishes them, with visible unresolved cleanu
|
|
|
5353
5399
|
|
|
5354
5400
|
Only start/resume require an in-progress or paused task with started media. Pause/stop/cancel may
|
|
5355
5401
|
also finish an independently observed recorder while the task is completing. A pending task may
|
|
5356
|
-
show active recording, but agent controls wait until
|
|
5402
|
+
show active recording, but agent controls wait until interaction begins. The two recording paths may
|
|
5357
5403
|
both be active. A command to either path has no implied effect on the other.
|
|
5358
5404
|
|
|
5359
5405
|
Each confirmed observation has a fresh opaque observation identity, a canonical UTC millisecond
|
|
@@ -5378,11 +5424,11 @@ same operation; reuse with different contents is refused. It never silently retr
|
|
|
5378
5424
|
operation. Implementations without reliable request correlation must refuse controls.
|
|
5379
5425
|
|
|
5380
5426
|
Keep command progress in host UI separately from authoritative state. Applied means the requested
|
|
5381
|
-
transition and
|
|
5427
|
+
recording transition and any retain/discard effect actually completed; publish the confirming task or host report before
|
|
5382
5428
|
resolving applied. Failed means confirmed no effect. Rejected promise means outcome unknown:
|
|
5383
5429
|
show the failure, reconcile that path and do not automatically retry or pretend inactive. Authoritative
|
|
5384
5430
|
updates remain full current task/host views, not replayed provider events. These current-state fields
|
|
5385
|
-
create no
|
|
5431
|
+
create no interaction-history entries and infer no actors, durations or historical capture boundaries.
|
|
5386
5432
|
|
|
5387
5433
|
Migration is explicit: replace the old true recording capability with per-path action policies,
|
|
5388
5434
|
replace untargeted commands with scoped commands, and retain protocol version 1. During pre-release
|
|
@@ -5392,7 +5438,7 @@ This change does not publish a package or enable recording in any existing host/
|
|
|
5392
5438
|
|
|
5393
5439
|
`validateRecordingOutcome` checks confirming observations against recording-specific applied/failed
|
|
5394
5440
|
semantics, including pause/resume identity and rejection of a dialling result. It cannot prove audio
|
|
5395
|
-
retention/
|
|
5441
|
+
retention/completion or source truth from a status flag; those remain executor obligations.
|
|
5396
5442
|
|
|
5397
5443
|
Host destination selection binds storage when start actually creates a recording. Existing recordings
|
|
5398
5444
|
keep that binding through pause/resume/stop/cancel; a later policy cannot redirect their stored audio.
|