@xema/omni-protocol 0.1.105 → 0.1.106
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/testing.d.ts +1 -10
- package/dist/testing.js +0 -27
- package/guide.md +70 -64
- package/package.json +1 -1
package/dist/testing.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Adapter, type AuthenticationState, type ProviderEventEnvelope, type Snapshot, type Task, type
|
|
1
|
+
import { type Adapter, type AuthenticationState, type ProviderEventEnvelope, type Snapshot, type Task, type BreakStatus, type BrowserSessionKeyInput, type Channel, type ConnectContext, type Host, type HostGuarantees, type HostMute, type LoginStore, type HostReport, type Manifest, type ProviderEvent } from "./index.js";
|
|
2
2
|
import { type ProtocolViolation } from "./validation.js";
|
|
3
3
|
export { ProtocolConformanceError, assertNoViolations, type ProtocolViolation } from "./validation.js";
|
|
4
4
|
/**
|
|
@@ -276,15 +276,6 @@ export interface BreakOnTaskStep {
|
|
|
276
276
|
* outstanding -- never beside a task, and never later than the step that has none.
|
|
277
277
|
*/
|
|
278
278
|
export declare function assertBreakBeginsAfterTask(steps: readonly BreakOnTaskStep[]): void;
|
|
279
|
-
/**
|
|
280
|
-
* Validates the deadline derived from audio end and the task's fixed wrap allowance.
|
|
281
|
-
*
|
|
282
|
-
* A task with no allowance has no deadline, so `observedDeadline` must then be `undefined`: a
|
|
283
|
-
* host counting down what the provider left open is the violation, and so is a host counting
|
|
284
|
-
* nothing down when the provider set a clock. `toleranceMs` absorbs scheduler jitter in a real
|
|
285
|
-
* implementation; pass 0 to demand an exact match.
|
|
286
|
-
*/
|
|
287
|
-
export declare function assertWrapTimeout(task: Pick<TaskCompletion, "completionMode" | "wrapAllowance">, audioEndedAt: string, observedDeadline: string | undefined, toleranceMs?: number): void;
|
|
288
279
|
/** One browser in one task of one provider. `providerId` is `Manifest.id`, never `displayName`. */
|
|
289
280
|
export type BrowserIsolationScenario = BrowserSessionKeyInput;
|
|
290
281
|
/** Validates whether two task-browser definitions should share one browser session. */
|
package/dist/testing.js
CHANGED
|
@@ -2207,33 +2207,6 @@ export function assertBreakBeginsAfterTask(steps) {
|
|
|
2207
2207
|
throw new Error(`Break-on-task scenario must end in effect, ended ${String(steps.at(-1)?.approval)}`);
|
|
2208
2208
|
}
|
|
2209
2209
|
}
|
|
2210
|
-
/**
|
|
2211
|
-
* Validates the deadline derived from audio end and the task's fixed wrap allowance.
|
|
2212
|
-
*
|
|
2213
|
-
* A task with no allowance has no deadline, so `observedDeadline` must then be `undefined`: a
|
|
2214
|
-
* host counting down what the provider left open is the violation, and so is a host counting
|
|
2215
|
-
* nothing down when the provider set a clock. `toleranceMs` absorbs scheduler jitter in a real
|
|
2216
|
-
* implementation; pass 0 to demand an exact match.
|
|
2217
|
-
*/
|
|
2218
|
-
export function assertWrapTimeout(task, audioEndedAt, observedDeadline, toleranceMs = 1_000) {
|
|
2219
|
-
if (task.wrapAllowance === undefined) {
|
|
2220
|
-
if (observedDeadline !== undefined) {
|
|
2221
|
-
throw new Error(`Wrap deadline mismatch: the task states no allowance, so there is no deadline, received ${observedDeadline}`);
|
|
2222
|
-
}
|
|
2223
|
-
return;
|
|
2224
|
-
}
|
|
2225
|
-
if (observedDeadline === undefined) {
|
|
2226
|
-
throw new Error(`Wrap deadline mismatch: the task allows ${task.wrapAllowance}s, but no deadline was observed`);
|
|
2227
|
-
}
|
|
2228
|
-
const ended = Date.parse(audioEndedAt);
|
|
2229
|
-
const deadline = Date.parse(observedDeadline);
|
|
2230
|
-
if (Number.isNaN(ended) || Number.isNaN(deadline))
|
|
2231
|
-
throw new Error("Wrap scenario requires valid ISO-8601 times");
|
|
2232
|
-
const expected = ended + task.wrapAllowance * 1_000;
|
|
2233
|
-
if (Math.abs(deadline - expected) > toleranceMs) {
|
|
2234
|
-
throw new Error(`Wrap deadline mismatch: expected ${new Date(expected).toISOString()} within ${toleranceMs}ms, received ${observedDeadline}`);
|
|
2235
|
-
}
|
|
2236
|
-
}
|
|
2237
2210
|
/** The session key one scenario derives, or `undefined` where the browser shares nothing. */
|
|
2238
2211
|
const sessionKeyFor = (scenario) => browserSessionKey(scenario);
|
|
2239
2212
|
/** Validates whether two task-browser definitions should share one browser session. */
|
package/guide.md
CHANGED
|
@@ -23,7 +23,7 @@ are used precisely throughout and mean nothing looser here.
|
|
|
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
25
|
| **Agent** | The person signed in and taking work. On `onCall`, `agent` is a person on the call by user id; a conference directory may include an agent when the provider publishes that directory item. |
|
|
26
|
-
| **Lead** | An agent whose login declares `capabilities.lead`. The flag
|
|
26
|
+
| **Lead** | An agent whose login declares `capabilities.lead`. The flag lets the agent application offer the team feature; the application switches it on with `lead-features` on every connect, and only then does the provider publish the team -- whole on `team-updated`, then one member at a time -- to that login and to nobody else: **the login is the permission**. The lead sees their members with their tasks and stats, and acts on them with the authority the flag carries. |
|
|
27
27
|
| **Local policy** | Rules configured by the agent application about this agent, outside the provider 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 local policy disagree, the stricter wins. |
|
|
28
28
|
| **Call** | The caller’s complete phone call, which may continue through IVRs, queues and several agents. The protocol never describes the IVR or the queue; it carries the call's history and details to whichever agent holds it now. |
|
|
29
29
|
| **Assignment** | The call routed to this agent, from the offer until the task ends. It is the one thing that crosses: the provider assigns, names the assignment with `assignmentId`, and that is the only name the desk ever uses back. A call that comes back is a new assignment on the same call, and a declined or lapsed offer is an assignment that became nothing more. What the platform calls its own record, and whether it reuses that name, is the adapter's business and unknown to the desk. |
|
|
@@ -41,7 +41,7 @@ are used precisely throughout and mean nothing looser here.
|
|
|
41
41
|
| **Break** | A reported, supervised state in which the agent is not working — one with a reason, a decision behind it and a return. It covers what a platform may call *not-ready*, including equipment trouble. An agent who is merely at capacity is not on a break. |
|
|
42
42
|
| **Workspace** | What Omni shows the agent. The **task workspace** holds the selected task, its controls and its browsers; the **idle workspace** holds what a provider contributes when no task is selected — dialpad, contacts, calendar, team member list. |
|
|
43
43
|
| **Dial** | One outbound call the agent application asks a provider to place — from the idle dialpad, a preview's Call, a conference add, or a connect-back. Identified by the agent application's `dialId`, accepted as `dialling`, and ended by exactly one `dial-outcome`. See **Every dial has an outcome**. |
|
|
44
|
-
| **Listen** | A lead's channel in a member's call, unasked, from the team member list: `listen` in silence, `coach` heard by the agent alone, `join-call` heard by everyone. It is a team act naming the member, never a task on the lead's desk;
|
|
44
|
+
| **Listen** | A lead's channel in a member's call, unasked, from the team member list: `listen` in silence, `coach` heard by the agent alone, `join-call` heard by everyone. It is a team act naming the member, never a task on the lead's desk; `listen` and `coach` leave no trace on the member's task, while a lead heard by everyone is on its `onCall` as an `agent`. Every lead sees every lead on the call. A lead who wants the call takes it over. See **Listening to a call**. |
|
|
45
45
|
| **On the call** | Who a voice task's audio joins, or is bringing in, as the provider states it on `Task.onCall`: the party, the agents, and anyone conferenced in from the moment their dial is placed. |
|
|
46
46
|
|
|
47
47
|
These fields describe state within their containing objects. Both authentication and break
|
|
@@ -54,7 +54,7 @@ state use `status`; name the object when discussing them to avoid ambiguity.
|
|
|
54
54
|
| `transport` | A connection | `connecting`, `active`, `error` |
|
|
55
55
|
| `status` | An authentication session | `signed-out`, `authenticating`, `authenticated`, `refreshing`, `expired` |
|
|
56
56
|
| `status` | The agent’s break lifecycle | `not-requested`, `awaiting-approval`, `granted`, `starting-after-task`, `on-break` |
|
|
57
|
-
| `availability` | A team member | `ready`, `on-task`, `on-break`, `signed-out` |
|
|
57
|
+
| `availability` | A team member | `ready`, `on-task`, `on-break`, `elsewhere`, `signed-out` |
|
|
58
58
|
|
|
59
59
|
Migration: describeUsers is now `getUserDetails`, and validateDescribedUsers is now
|
|
60
60
|
`validateUserDetails`. Related diagnostics use getUserDetails. Hosts and providers must
|
|
@@ -124,7 +124,7 @@ identifier of its own for an agent or a manager, and none crosses this boundary
|
|
|
124
124
|
operating-system account, not a directory identity, not a licence.
|
|
125
125
|
|
|
126
126
|
So a `UserId` means nothing outside the provider that issued it. Provider A's
|
|
127
|
-
`history[].by` and provider B's team member
|
|
127
|
+
`history[].by` and provider B's team member `id` are unrelated strings that will
|
|
128
128
|
eventually collide, and one person on several providers has several identities that nothing here
|
|
129
129
|
pairs. Scope every user identifier with its provider ID before storing or comparing it, exactly as
|
|
130
130
|
`assignmentKey()` already does for tasks — see `userKey()` under **Utilities**.
|
|
@@ -835,7 +835,7 @@ type TaskCommandResult =
|
|
|
835
835
|
### Who decides what an agent may do
|
|
836
836
|
|
|
837
837
|
An agent desk has two managers, not one. **The queue** — a process, a work type — is owned by a
|
|
838
|
-
process manager and **allows** a set of capabilities: hold, connect back,
|
|
838
|
+
process manager and **allows** a set of capabilities: hold, connect back, dial, conference,
|
|
839
839
|
whether the number is visible, the actions it offers, the skills it needs. **The people** are
|
|
840
840
|
managed through the organisation's structure — a team, a location, the organisation itself, in
|
|
841
841
|
whatever combination the structure defines for a person — and **decide** per capability within
|
|
@@ -897,7 +897,7 @@ controls — is content, and is never locked.
|
|
|
897
897
|
**A lead sets the team's policy from their team member list.** A login that declares
|
|
898
898
|
`capabilities.lead` may `executeTeam({ command: { type: "set-policy", capability, setting } })`, where the provider offers policy control and says so by publishing `policies`,
|
|
899
899
|
with `on`, `off`, or `person`, for any task control, `dial`, or a skill — and only `hold`
|
|
900
|
-
and skills may be `person`; connect back and
|
|
900
|
+
and skills may be `person`; connect back and dial are the team's, on or off, within what the queue
|
|
901
901
|
allows. The team member list carries `policies` for such a login: every policy as it stands, who set it, and
|
|
902
902
|
`lockedBy` where a level above the team made it theirs to keep, which the lead sees and cannot
|
|
903
903
|
change — `set-policy` on it answers `failed` with `omni.capability-not-enabled`.
|
|
@@ -1399,7 +1399,7 @@ The map key is the exact `Task.taskType`. When no entry exists, Omni uses the pr
|
|
|
1399
1399
|
channel's entry in `DEFAULT_TASK_TYPE_PRESENTATION`. A mixed-channel list falls back to the words
|
|
1400
1400
|
"Task" and "Tasks" when its items do not share one label.
|
|
1401
1401
|
|
|
1402
|
-
`referenceLabel` labels `Task.reference`; it does not label
|
|
1402
|
+
`referenceLabel` labels `Task.reference`; it does not label `assignmentId`. Omni shows a
|
|
1403
1403
|
reference only when both values are present.
|
|
1404
1404
|
|
|
1405
1405
|
**So supply it whenever the task type has references.** Because an entry replaces the channel
|
|
@@ -1441,7 +1441,8 @@ only where the corresponding capability is declared; an absent capability means
|
|
|
1441
1441
|
there is no separate permission flag.
|
|
1442
1442
|
|
|
1443
1443
|
The commands every task has are authorized by other fields the provider declared — a task that was
|
|
1444
|
-
offered can be accepted, one in `preview` can be called,
|
|
1444
|
+
offered can be accepted, one in `preview` can be called, any task under `agent-command` and one
|
|
1445
|
+
with a wrap under `provider-automatic` can be completed.
|
|
1445
1446
|
Nothing is issuable that the provider did not publish; only
|
|
1446
1447
|
which field says so varies. See **Which commands need a capability**.
|
|
1447
1448
|
|
|
@@ -1484,7 +1485,7 @@ is never the provider's answer: an answer travels only as a resolved `{ status:
|
|
|
1484
1485
|
and every rejection is transport loss, whatever the last `transport-status` said and whatever the
|
|
1485
1486
|
rejection carries.
|
|
1486
1487
|
|
|
1487
|
-
A command therefore carries no key. The provider names the assignment, the
|
|
1488
|
+
A command therefore carries no key. The provider names the assignment, the member's request and the
|
|
1488
1489
|
member, and Omni refers to them by those names; **Omni never asks a provider to remember a name
|
|
1489
1490
|
Omni made up.**
|
|
1490
1491
|
|
|
@@ -1637,7 +1638,7 @@ compile time.
|
|
|
1637
1638
|
| `orgLevels` | The organisation's whole ladder as the provider calls it, each level with the label a desk shows for "who decided". Stated outright, `person` included: what it leaves out does not exist. Omitted for the typical four, `DEFAULT_LEVELS`. See **Who decides what an agent may do**. |
|
|
1638
1639
|
| `phones` | Voice only, and required there: the phones this platform can put an agent on, `softphone` (the call's audio lands in the agent application) and/or `deskPhone` (a handset the platform rings; the agent application shows the call and opens nothing). The agent application picks one per login. See **How the agent hears the call**. |
|
|
1639
1640
|
| `dialOutcomes` | Voice only. How a dial can end on this platform, as it distinguishes them: `answered` and at least one way of not reaching the destination. Required of a provider that dials at all — an idle dialpad, or tasks that conference or call back — and a `dial-outcome` carries only a declared member. See **Every dial has an outcome**. |
|
|
1640
|
-
| `timeCheck` | `true`: implements `checkTime` and states `providerTime` on every snapshot. Required of a provider that publishes an instant the desk renders as a running duration -- a member's `since`, a
|
|
1641
|
+
| `timeCheck` | `true`: implements `checkTime` and states `providerTime` on every snapshot. Required of a provider that publishes an instant the desk renders as a running duration -- a member's `since`, a `listening[].since`, an `onCall.since`, a shift's `signedInAt` -- so every screen counts from the provider's clock (`manifest.timeCheck.required`); agent application polling is independently opt-in. See **Every screen counts from the provider's clock**. |
|
|
1641
1642
|
| `timestampAuthority` | Optional `"provider"`: provider timestamps are final; agent application instants are advisory. Omission makes no trust promise. |
|
|
1642
1643
|
| `runningStepReports` | The provider takes running reports of an agent application-performed step — `recordStep` with `seconds` so far and no `ended`. Omitted, the agent application sends exactly two reports per leg, when it began and when it ended, and a running one is refused. See **The agent application records what it performs**. |
|
|
1643
1644
|
| `settleMs` | Required. How long after `applied` the wire shows what the provider did: the `task-ended` after a `complete`, the `calendar-updated` after a `schedule`. A positive whole number of milliseconds (`manifest.settleMs`). Stated per provider, since platforms settle at different speeds. See **`task-ended`**. |
|
|
@@ -1771,8 +1772,8 @@ in the agent's view would show that it had happened.
|
|
|
1771
1772
|
|
|
1772
1773
|
`calendar` is available to voice, chat, and email providers. It contributes a calendar to the idle
|
|
1773
1774
|
dashboard for callbacks and other scheduled activities associated with that provider. Calendar is
|
|
1774
|
-
|
|
1775
|
-
complete them.
|
|
1775
|
+
the provider's: Omni displays activities and, where a task offers `schedule`, asks the provider to
|
|
1776
|
+
create one -- see **Scheduling a follow-up** -- but cannot reschedule, cancel, or complete them.
|
|
1776
1777
|
|
|
1777
1778
|
```ts
|
|
1778
1779
|
idleCapabilities: { calendar: true }
|
|
@@ -1800,9 +1801,9 @@ matched by nobody and defined by nobody, and calendars merge across providers ex
|
|
|
1800
1801
|
do, so one provider's `Callback` and another's `Call back` fragment a list that looks organised.
|
|
1801
1802
|
Send the category as an attribute and Omni displays it.
|
|
1802
1803
|
|
|
1803
|
-
Nothing is lost by that here, because there is nothing for a category to drive.
|
|
1804
|
-
calendar
|
|
1805
|
-
a callback has a `
|
|
1804
|
+
Nothing is lost by that here, because there is nothing for a category to drive. Nothing on the
|
|
1805
|
+
calendar varies by kind -- `schedule` takes no category -- and what an activity *is* already shows
|
|
1806
|
+
in what it carries: a callback has a `party`, a training does not.
|
|
1806
1807
|
|
|
1807
1808
|
#### Contacts (`contacts`)
|
|
1808
1809
|
|
|
@@ -2207,6 +2208,7 @@ Returns the provider's complete authoritative state at one point in time.
|
|
|
2207
2208
|
snapshot is read is held until it lands, and what happens to it then depends on what it is. A
|
|
2208
2209
|
snapshot restates state, so an event of a state-replacing kind -- `task-offered`, `task-updated`,
|
|
2209
2210
|
`task-ended`, `task-audio-started`, `task-audio-ended`, `break-state`, `team-updated`,
|
|
2211
|
+
`team-member-updated`, `team-member-removed`, `team-policies-updated`, `shift-updated`,
|
|
2210
2212
|
`contacts-updated`, `calendar-updated`, `transport-status`, `snapshot` -- is dropped, because the
|
|
2211
2213
|
snapshot accounts for it. An event that reports a transaction no snapshot carries --
|
|
2212
2214
|
`dial-outcome`, `diagnostic`, `announcement`, `queue-summary` -- is applied after the snapshot,
|
|
@@ -2275,9 +2277,11 @@ capacity is stated, and needs no other word for it.
|
|
|
2275
2277
|
|
|
2276
2278
|
**2. The agent becomes ready.** The agent application states the capacity when its local policy says: at once,
|
|
2277
2279
|
or when the agent presses Ready. A successful connection, a healthy provider, or the absence of a
|
|
2278
|
-
break does not imply readiness. After that,
|
|
2279
|
-
under `capabilities.breaks
|
|
2280
|
-
|
|
2280
|
+
break does not imply readiness. After that, an agent stops taking work on this provider only by a
|
|
2281
|
+
break under `capabilities.breaks`, or by the agent application restating `count: 0` -- which is how
|
|
2282
|
+
a forced break on another provider reaches this one; a login that declares no breaks has no
|
|
2283
|
+
not-ready of its own, and `count: 0` is the agent application's division of capacity, not the
|
|
2284
|
+
agent's choice.
|
|
2281
2285
|
|
|
2282
2286
|
**3. Omni states their concurrent capacity.** Only now does Omni ask providers for work, saying how
|
|
2283
2287
|
much the agent can take at once. It is a standing declaration rather than a poll: Omni restates the
|
|
@@ -2557,12 +2561,10 @@ answer at that exact instant. Scheduling/dispatch delay must remain visible, and
|
|
|
2557
2561
|
prevented initiation must be reported rather than leaving a silently expired countdown.
|
|
2558
2562
|
A source eligibility threshold that may never trigger an attempt is not this promise.
|
|
2559
2563
|
|
|
2560
|
-
The two deadline fields travel together and only in preview. The
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
receipt-time or local-default deadline is inferred. Neither elapsed time nor submission changes
|
|
2565
|
-
the task phase: subsequent authoritative events state ringing, answer, audio and completion.
|
|
2564
|
+
The two deadline fields travel together and only in preview. The countdown is the seconds the
|
|
2565
|
+
publication carried, counted from its receipt: the agent application infers no deadline where none
|
|
2566
|
+
was stated and reads no clock against another. Neither elapsed time nor submission changes the
|
|
2567
|
+
task phase: subsequent authoritative events state ringing, answer, audio and completion.
|
|
2566
2568
|
|
|
2567
2569
|
An agent application must serialize manual clicks and its timer under the exact provider/login/task/assignment
|
|
2568
2570
|
scope, allowing at most one unresolved submission. Recheck the current phase, deadline owner,
|
|
@@ -2678,8 +2680,8 @@ audio:
|
|
|
2678
2680
|
| Channel | Wrap allowance starts at |
|
|
2679
2681
|
| --- | --- |
|
|
2680
2682
|
| Voice and any channel with real-time audio | The `task-audio-ended` event |
|
|
2681
|
-
| Chat | The `task-updated` that moves the task to `completing`: the provider's word that the conversation is closed on its side
|
|
2682
|
-
| Email | The `task-updated` that moves the task to `completing`: the provider's word that the platform has accepted the outgoing message
|
|
2683
|
+
| Chat | The `task-updated` that moves the task to `completing`: the provider's word that the conversation is closed on its side; what is left arrives on that publication as `wrapEndsInSeconds` and is counted down from receipt |
|
|
2684
|
+
| Email | The `task-updated` that moves the task to `completing`: the provider's word that the platform has accepted the outgoing message; what is left arrives on that publication as `wrapEndsInSeconds` and is counted down from receipt |
|
|
2683
2685
|
| Other non-audio channels | The `task-updated` that moves the task to `completing` |
|
|
2684
2686
|
|
|
2685
2687
|
**Off voice, `completing` is the provider's word that interaction ended.** There is no audio event to
|
|
@@ -2793,7 +2795,7 @@ The record is the call's, not the task's: most of what it holds happened before
|
|
|
2793
2795
|
the total of earlier agents' time is `interactionSeconds` and the phase rules are
|
|
2794
2796
|
`command.phase.interaction`. Hosts and providers adopt these names together; no legacy aliases
|
|
2795
2797
|
are provided. The manifest's `settleMs` bounds final task completion, not entry into
|
|
2796
|
-
the `completing` wrap-up phase. The `recordStep` method and `complete` command keep their names.
|
|
2798
|
+
the `completing` wrap-up phase, and bounds an applied `schedule` the same way. The `recordStep` method and `complete` command keep their names.
|
|
2797
2799
|
|
|
2798
2800
|
Migration from the earlier spellings:
|
|
2799
2801
|
|
|
@@ -2933,7 +2935,7 @@ correlation/conformance only; it is not an application-side timestamp reconcilia
|
|
|
2933
2935
|
If two distinct entries would collide under the history's `(step, at)` key, do not invent a time
|
|
2934
2936
|
or combine them: report the representation conflict. No ambiguous event is silently accepted.
|
|
2935
2937
|
|
|
2936
|
-
**
|
|
2938
|
+
**Interaction time is anchored, not restarted.** It runs from the
|
|
2937
2939
|
`answered` step's `at` — from the task's first `in-progress` where the provider reports no
|
|
2938
2940
|
history — until the task's audio ends, and a hold neither pauses nor resets it: the hold's own
|
|
2939
2941
|
duration is the `held` entry's `seconds`, and a desk that restarts its counter on resume is
|
|
@@ -2945,9 +2947,9 @@ account of a period the agent could not be heard. Each `muted` entry carries `mu
|
|
|
2945
2947
|
reported it: `host` for the agent's own Mute, `station` for a headset or system mute the agent application
|
|
2946
2948
|
observed. No other step has it (`task.history.mutedBy`, `.mutedBy.unexpected`).
|
|
2947
2949
|
|
|
2948
|
-
**A step that dialled says which dial and where.** `
|
|
2949
|
-
|
|
2950
|
-
`dialId` the agent application minted; no other step dialled, and either field on one is refused
|
|
2950
|
+
**A step that dialled says which dial and where.** `conferenced` and `unanswered` each end a dial,
|
|
2951
|
+
and `transferred` a hand-over on the platform's own routing, so the entry carries the `destinationId`
|
|
2952
|
+
it went to and, where an agent application placed the dial, the `dialId` the agent application minted; no other step dialled, and either field on one is refused
|
|
2951
2953
|
(`task.history.dialId.unexpected`). This is how a dial made before a take-over is placeable
|
|
2952
2954
|
by whoever holds the task now: a `dial-outcome` naming a `dialId` the agent application never minted is looked
|
|
2953
2955
|
up in the record, not discarded.
|
|
@@ -2963,7 +2965,7 @@ Four rules a provider has to keep:
|
|
|
2963
2965
|
conversation, and on live data that is the ordinary case rather than an edge. A zero is rejected.
|
|
2964
2966
|
- **`by` is a bare `UserId`, and not necessarily an agent.** A lead or a manager takes part
|
|
2965
2967
|
during an interaction too — a call taken over, a call conferenced in — so the field names whoever it was,
|
|
2966
|
-
the same
|
|
2968
|
+
from the same directory `ForcedBreak.by` names a lead from. It comes from this provider's own directory, the same
|
|
2967
2969
|
namespace as `AuthenticationState.identity.id` and the team member list, so entries pair
|
|
2968
2970
|
within a provider and never across one.
|
|
2969
2971
|
- **A task carries no names.** Omni resolves what to display with `getUserDetails()`. Two people
|
|
@@ -3287,7 +3289,7 @@ const taskCapabilities = {
|
|
|
3287
3289
|
|
|
3288
3290
|
A capability says the control may be offered; the task's phase says whether there is anything to use
|
|
3289
3291
|
it on. Every control below that acts on the call or the conversation -- everything but `decline`,
|
|
3290
|
-
`connectBack` and `outcomes` -- is offered only while the task is `in-progress` or `paused`.
|
|
3292
|
+
`connectBack`, `schedule` and `outcomes` -- is offered only while the task is `in-progress` or `paused`.
|
|
3291
3293
|
See **Which commands need a capability**.
|
|
3292
3294
|
|
|
3293
3295
|
### Voice capabilities
|
|
@@ -3582,7 +3584,7 @@ there was one, the directory item it went to where there was one, and how the di
|
|
|
3582
3584
|
set: `answered`, `busy`, `no-answer`, `unreachable`, `rejected`, `cancelled`, `unexplained`.
|
|
3583
3585
|
`answered` is stated rather than read off somebody appearing on the call, because an agent application that infers
|
|
3584
3586
|
success cannot tell "reached" from "still ringing". `cancelled` is the dialler calling the dial off
|
|
3585
|
-
before anyone answered -- the agent hanging up while a
|
|
3587
|
+
before anyone answered -- the agent hanging up while a conferenced colleague still rings -- which is not a
|
|
3586
3588
|
`no-answer` the destination never gave. `unexplained` is the switch dropping the call with no cause among
|
|
3587
3589
|
those named here: a statement about the switch, sent instead of the nearest cause it did not give.
|
|
3588
3590
|
`reason` is the switch's own words, optional, and shown to the agent attributed to the switch rather
|
|
@@ -3599,8 +3601,8 @@ call it belonged to and never against the next one.
|
|
|
3599
3601
|
|
|
3600
3602
|
The stream knows a dial from three places: `TaskStream.dialled(dialId)`, which an agent application calls when it
|
|
3601
3603
|
places one; an entry on a task's `onCall`; and a step in its `history`. Since a dialled entry
|
|
3602
|
-
is listed from placement, `dialled()` is load-bearing only for a dial that never has an entry --
|
|
3603
|
-
|
|
3604
|
+
is listed from placement, `dialled()` is load-bearing only for a dial that never has an entry -- an
|
|
3605
|
+
idle dialpad call, which has no task yet -- or whose entry has already left the room; an
|
|
3604
3606
|
outcome for a dial nobody placed and no task mentions is what `stream.dialOutcome.unknown` refuses.
|
|
3605
3607
|
|
|
3606
3608
|
**`answered` says what happened to the dial; `onCall` says who is in the room.** They are two
|
|
@@ -3711,7 +3713,7 @@ rendering one as the other tells an agent to wait for somebody who is never comi
|
|
|
3711
3713
|
| --- | --- |
|
|
3712
3714
|
| `not-requested` | No request outstanding. |
|
|
3713
3715
|
| `awaiting-approval` | A person has to decide. The agent is waiting on somebody. |
|
|
3714
|
-
| `granted` |
|
|
3716
|
+
| `granted` | The request was granted, by a person or by an `automatically-approved` policy. Omni may now tell this provider to stop the agent; until it does, work continues normally, and this says nothing about why Omni has not. |
|
|
3715
3717
|
| `starting-after-task` | Omni has told the provider to stop; the break begins when the current task ends. No new work arrives meanwhile, and nobody needs to act. It waits on a task, so beside no task it is refused (`break.starting-after-task.tasks`): a committed break with nothing outstanding is `on-break`. |
|
|
3716
3718
|
| `on-break` | The agent is on the break now. It holds no task: a break begins when the work ends, so a snapshot reporting `on-break` beside a task is refused as `break.on-break.tasks`. The one exception is a call a lead took over, which the provider assigns whatever break the lead is on -- see **Lead assist**. |
|
|
3717
3719
|
|
|
@@ -3749,7 +3751,7 @@ The separate authentication and general failure retryAfterMs fields are unchange
|
|
|
3749
3751
|
|
|
3750
3752
|
Migration: the former in-effect break approval value is now `on-break`. It means the
|
|
3751
3753
|
agent is on the break, not merely granted permission. The old value is rejected without an
|
|
3752
|
-
alias. The task prerequisite,
|
|
3754
|
+
alias. The task prerequisite, the take-over exception and ordering rules are unchanged;
|
|
3753
3755
|
the task-conflict diagnostic is `break.on-break.tasks`. Team member availability already uses
|
|
3754
3756
|
`on-break` and is unchanged.
|
|
3755
3757
|
|
|
@@ -3783,7 +3785,7 @@ prerequisite are unchanged.
|
|
|
3783
3785
|
The team break command formerly named place is now `force-break`. Hosts and providers must
|
|
3784
3786
|
adopt the new command together; place and the interim force spelling are rejected without aliases.
|
|
3785
3787
|
The team break command formerly named release is now `end-forced-break`, and its diagnostic is
|
|
3786
|
-
`team.
|
|
3788
|
+
`team.command.endForcedBreak`. The former release command and the interim end spelling are rejected without aliases.
|
|
3787
3789
|
The agent’s `endBreak` now applies to forced breaks as well as requested breaks.
|
|
3788
3790
|
|
|
3789
3791
|
`ForcedBreak` names who forced the break and may say how long it is expected to run yet, as
|
|
@@ -3960,15 +3962,16 @@ association and backend authorization are provider responsibilities.
|
|
|
3960
3962
|
|
|
3961
3963
|
Use `validateBreakStatus(state, tasks)` on the complete retained task view after each transaction,
|
|
3962
3964
|
as well as `validateBreakTransition` for event ordering. Snapshot validation shares the status
|
|
3963
|
-
checks. Pending and completing tasks still count as work. Only
|
|
3964
|
-
|
|
3965
|
+
checks. Pending and completing tasks still count as work. Only a call a lead took over
|
|
3966
|
+
(`takenOver`) sits beside a break in effect. Validate full task shapes and the envelope separately.
|
|
3965
3967
|
|
|
3966
3968
|
These validators report violations without dispatching, rewriting state or proving freshness.
|
|
3967
3969
|
Recheck prerequisites atomically at the provider; client validation cannot prevent a race.
|
|
3968
3970
|
Continue to use `validateResult` for each method's response vocabulary. A request acknowledgment
|
|
3969
3971
|
is not a grant, and no method response overwrites a newer state emission. Rejected promises
|
|
3970
3972
|
remain unknown and recover by snapshot, not automatic retry. The agent application's frozen provider set,
|
|
3971
|
-
durable mutually exclusive commit/cancel decision and reconciliation obligations
|
|
3973
|
+
durable mutually exclusive commit/cancel decision and reconciliation obligations under
|
|
3974
|
+
**Coordinating a multi-provider break** remain
|
|
3972
3975
|
required; per-provider validation does not implement the multi-provider coordinator.
|
|
3973
3976
|
|
|
3974
3977
|
### `requestBreak(request)`
|
|
@@ -4184,9 +4187,9 @@ won, cancel returns `omni.break-already-committed`, and Omni resumes commit reco
|
|
|
4184
4187
|
returning the agent to `working`.
|
|
4185
4188
|
|
|
4186
4189
|
A forced break is not rolled back by this algorithm. If one appears during either the request or
|
|
4187
|
-
cancellation,
|
|
4188
|
-
|
|
4189
|
-
stopped
|
|
4190
|
+
cancellation, the attempt is abandoned: Omni cancels every grant still outstanding and states
|
|
4191
|
+
`setCapacity({ count: 0 })` on every other usable provider, as **Forced breaks** sets out; no grant
|
|
4192
|
+
is committed, because the agent has already stopped everywhere.
|
|
4190
4193
|
|
|
4191
4194
|
This is two-phase coordination across vendor systems: the approval phase keeps the agent working;
|
|
4192
4195
|
the durable commit decision and snapshot reconciliation provide convergence after partial delivery.
|
|
@@ -4343,7 +4346,7 @@ assignment from the member alone.
|
|
|
4343
4346
|
| `{ type: "lead-features", enabled }` | The team feature on or off. See **The lead switches the feature on and off** above. |
|
|
4344
4347
|
| `{ type: "decide-break-request", memberId, decision, reason? }` | Settles one pending request. `decision` is `granted` or `denied`. A grant moves the member to `granted`; a denial ends the request and moves it directly to `not-requested`. |
|
|
4345
4348
|
| `{ type: "set-break-policy", policy }` | `approval-required`, `automatically-approved`, or `requests-suspended`. |
|
|
4346
|
-
| `{ type: "force-break", memberId, reasonId?, reason?, expectedDurationMs? }` | Puts a member on a break they did not ask for. `reasonId` names a published `BreakReason.id` and is required whenever the provider publishes `reasons`; the member's forced break carries it as `activeReasonId`, so its kind is known. Optional `expectedDurationMs` is advisory
|
|
4349
|
+
| `{ type: "force-break", memberId, reasonId?, reason?, expectedDurationMs? }` | Puts a member on a break they did not ask for. `reasonId` names a published `BreakReason.id` and is required whenever the provider publishes `reasons`; the member's forced break carries it as `activeReasonId`, so its kind is known. Optional `expectedDurationMs` is advisory; the resulting forced break publishes what is left of it as `expectedEndsInSeconds`, restated on every publication. |
|
|
4347
4350
|
| `{ type: "end-forced-break", memberId }` | Asks the provider to lift a forced-break restriction a lead asked for, by clearing `BreakState.forced`; one the platform imposed is its own to lift (`team.command.endForcedBreak.provider`, `omni.break-forced-by-provider`). The committed break continues; only the agent resumes work. |
|
|
4348
4351
|
| `{ type: "join", memberId, assignmentId? }` | Answers the member's request: the provider bridges the lead's channel into the call. See **Lead assist**. |
|
|
4349
4352
|
| `{ type: "decline", memberId, assignmentId?, reason? }` | Refuses the member's request. |
|
|
@@ -4405,7 +4408,7 @@ provider clears `leadAssist` from the agent's task** and drops the request from
|
|
|
4405
4408
|
else changes; the agent is still on the call.
|
|
4406
4409
|
|
|
4407
4410
|
**On `leave`, the lead's channel drops and the call goes on**: the member's task loses its
|
|
4408
|
-
`leadAssist`, the member loses `listening`, and the lead is free. Nothing ended for the member.
|
|
4411
|
+
`leadAssist`, the member loses this lead's entry in `listening`, and the lead is free. Nothing ended for the member.
|
|
4409
4412
|
|
|
4410
4413
|
**On `take-over-call`, the provider moves the call from the member to the lead**, and the lead is
|
|
4411
4414
|
its new agent. There are two paths to it -- the agent asked and the lead joined, or the lead was
|
|
@@ -4413,7 +4416,7 @@ listening unasked -- and one act at the end of either. What follows is the same
|
|
|
4413
4416
|
|
|
4414
4417
|
| | The member's task | The lead's desk |
|
|
4415
4418
|
| --- | --- | --- |
|
|
4416
|
-
| Take-over | `task-audio-ended`, then `completing`: the member's interaction is over and their wrap runs, exactly as after `end-call`. The audio ends first, as before every voice ending (`stream.taskEnded.audioOpen`). The member loses `listening` on the team member list no later than the offer, and the lead's listening audio closes. | An ordinary assignment arrives: `task-offered` with `acceptance: "automatic"`, carrying the call's history and `takenOver: { memberId, since }`, within the lead's capacity. |
|
|
4419
|
+
| Take-over | `task-audio-ended`, then `completing`: the member's interaction is over and their wrap runs, exactly as after `end-call`. The audio ends first, as before every voice ending (`stream.taskEnded.audioOpen`). The member loses this lead's `listening` entry on the team member list no later than the offer, and the lead's listening audio closes. | An ordinary assignment arrives: `task-offered` with `acceptance: "automatic"`, carrying the call's history and `takenOver: { memberId, since }`, within the lead's capacity. |
|
|
4417
4420
|
| The member completes | `task-ended` with `{ type: "taken-over", leadId }`, at the member's own completion. The lead is named by user id, since a lead is not a directory item. | The lead works the call as any agent would, and ends it as any call ends. |
|
|
4418
4421
|
|
|
4419
4422
|
**The taken-over call is offered whatever break the lead is on, and counted like any other.** A
|
|
@@ -4486,7 +4489,7 @@ executeTeam({ command: { type: "join-call", memberId: "A-1" } })
|
|
|
4486
4489
|
// 5. The lead leaves: the provider publishes the member without L-9's entry, and the task without L-9 in the room; the application closes the audio.
|
|
4487
4490
|
executeTeam({ command: { type: "leave", memberId: "A-1" } })
|
|
4488
4491
|
// 6. The member's call ends: the same as leave, from the provider's side.
|
|
4489
|
-
// 7. The lead takes the call: the member loses listening no later than the offer, the application
|
|
4492
|
+
// 7. The lead takes the call: the member loses this lead's listening entry no later than the offer, the application
|
|
4490
4493
|
// closes the listening audio, and the lead's own call arrives as an ordinary assignment marked
|
|
4491
4494
|
// takenOver, counted against capacity, with its audio following task-audio-started as any call's does.
|
|
4492
4495
|
// At capacity the take-over is refused and the lead stays listening.
|
|
@@ -4647,7 +4650,7 @@ anything to become available.
|
|
|
4647
4650
|
|
|
4648
4651
|
**A break is not a capacity of zero, and neither is the reverse.** Capacity says how much of this
|
|
4649
4652
|
agent this provider may carry; a break says they are not working at all. A provider told
|
|
4650
|
-
`count: 0` shows an agent whose capacity is elsewhere, `
|
|
4653
|
+
`count: 0` shows an agent whose capacity is elsewhere, `elsewhere`; a provider told a break shows
|
|
4651
4654
|
`on-break`. Collapsing the two would leave a provider unable to tell an agent working on another
|
|
4652
4655
|
provider from an agent who has gone to lunch, and only one of those needs a reason, a decision and
|
|
4653
4656
|
a return.
|
|
@@ -4873,7 +4876,7 @@ declared:
|
|
|
4873
4876
|
| `complete` | A wrap to cut short: any task but a `provider-automatic` one with `wrapAllowance: 0`, under either completion mode (`command.complete.wrapAllowance`). 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`). |
|
|
4874
4877
|
| `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. |
|
|
4875
4878
|
| `schedule` | The `schedule` capability, on any channel, in `in-progress`, `paused` or `completing`: a follow-up is promised on the call and written up in wrap alike. The manifest declares `calendar` for it to land on. |
|
|
4876
|
-
| `lead-assist` with `action: "request"` or `"cancel"` | The `leadAssist` capability. `cancel` needs a request standing -- `Task.leadAssist` with
|
|
4879
|
+
| `lead-assist` with `action: "request"` or `"cancel"` | The `leadAssist` capability. `cancel` needs a request standing -- `Task.leadAssist` with `stage: "requested"`. |
|
|
4877
4880
|
| `conference` with `action: "add"` | Its capability, and a `destinationId` the directory offered: the id Omni sends is the id the provider published (`command.destination.unknown`). |
|
|
4878
4881
|
| `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`). |
|
|
4879
4882
|
| Everything else | Its own named capability. |
|
|
@@ -4952,6 +4955,8 @@ react rather than only display the message:
|
|
|
4952
4955
|
| `omni.rate-limited` | The action was throttled. Pair with `retryAfterMs`. |
|
|
4953
4956
|
| `omni.unavailable` | The provider is temporarily unable to serve the action, including any command sent while `transport-status` is not `active`. |
|
|
4954
4957
|
| `omni.break-already-committed` | Cancellation lost the commit/cancel race; Omni must finish commit recovery. |
|
|
4958
|
+
| `omni.recording-unsettled` | A recording command the provider could not settle either way: a settled `failed`, never a promise left unresolved. See **Independent task recording**. |
|
|
4959
|
+
| `omni.break-forced-by-provider` | A lead asked to lift a break the platform imposed; the platform lifts its own. See **Forced breaks**. |
|
|
4955
4960
|
|
|
4956
4961
|
They are published as `OMNI_FAILURE_CODES`.
|
|
4957
4962
|
|
|
@@ -4985,7 +4990,7 @@ The current exceptions and their reasons are:
|
|
|
4985
4990
|
| Case | Exception and reason |
|
|
4986
4991
|
| --- | --- |
|
|
4987
4992
|
| Agent application display of provider deadlines (`expiresInSeconds`, `previewEndsInSeconds`, `wrapEndsInSeconds`) | Each is seconds from the publication that carried it, counted down on the desk from receipt: the provider's own arithmetic, so no clock is compared and the transport delay is the only error. The countdown estimates the display only; it does not establish that the provider acted. Without usable time, show timing uncertainty. |
|
|
4988
|
-
| Agent application-triggered preview
|
|
4993
|
+
| Agent application-triggered preview end | The desk counts `previewEndsInSeconds` down from receipt and issues `dial` when it reaches zero: the provider owns the deadline and the desk owns the trigger. It does not dial before the countdown has run out, and a reload restarts it from the next publication. |
|
|
4989
4994
|
| Recording evidence expiry | A bounded observer-domain clock estimate with monotonic aging may assess freshness because observation and expiry belong to the recorder's clock. If time cannot be trusted, recording state is unknown; never renew evidence from receipt or replay. |
|
|
4990
4995
|
| Unknown recording state | `observedAt` and `validUntil` are absent because there is no confirmed observation. The containing provider event still has its own `occurredAt`; that publication is not a recorder observation. |
|
|
4991
4996
|
| Direct snapshot reads and method requests/results | These are reads/operations, not event envelopes, and their current types have no general event timestamp. A snapshot event still carries `occurredAt`; embedded source instants remain unchanged. Do not treat a method result or read completion time as an occurrence boundary. |
|
|
@@ -5128,9 +5133,9 @@ direction — the provider asking Omni to reconcile — and neither replaces the
|
|
|
5128
5133
|
|
|
5129
5134
|
Carries a complete `Snapshot` after reconnect or when the provider explicitly requests
|
|
5130
5135
|
reconciliation. `reason` is `reconnected` or `provider-requested`. Omni replaces the provider's
|
|
5131
|
-
current status, break state, tasks, contacts, scheduled activities
|
|
5132
|
-
snapshot. It carries what the login
|
|
5133
|
-
snapshot — and nothing they do not; a capability is withdrawn by a republished `authenticated`,
|
|
5136
|
+
current status, break state, tasks, contacts, scheduled activities, the agent's day and the team
|
|
5137
|
+
member list with this snapshot. It carries what the login and its switches call for — the whole
|
|
5138
|
+
team for a lead with the feature on, on every snapshot — and nothing they do not; a capability is withdrawn by a republished `authenticated`,
|
|
5134
5139
|
never by an omission from a snapshot.
|
|
5135
5140
|
|
|
5136
5141
|
### `transport-status`
|
|
@@ -5293,8 +5298,10 @@ wrapped audio has nothing left to end.
|
|
|
5293
5298
|
|
|
5294
5299
|
### `task-audio-ended`
|
|
5295
5300
|
|
|
5296
|
-
Signals that a task's real-time audio ended. For voice and similar channels,
|
|
5297
|
-
|
|
5301
|
+
Signals that a task's real-time audio ended. For voice and similar channels, the `completing`
|
|
5302
|
+
publication that follows carries `wrapEndsInSeconds` where a wrap allowance is stated: under
|
|
5303
|
+
`provider-automatic` the provider ends the task when it runs out, under `agent-command` it is shown
|
|
5304
|
+
and nothing more. It does not remove the task, and it ends only audio that `task-audio-started` — or
|
|
5298
5305
|
a task carried with `audio: "started"` — attached.
|
|
5299
5306
|
|
|
5300
5307
|
### `task-ended`
|
|
@@ -5434,7 +5441,7 @@ issued by each provider independently, so two providers will eventually issue th
|
|
|
5434
5441
|
different people. Encode and join before storing or comparing.
|
|
5435
5442
|
|
|
5436
5443
|
Use it for every `UserId` — `history[].by`, team members, `memberId` on a
|
|
5437
|
-
lead command, `ForcedBreak.by`. A bare one is only ever compared against another from the **same** provider; anything
|
|
5444
|
+
lead command, `ForcedBreak.by` where it names a person rather than `"provider"`. A bare one is only ever compared against another from the **same** provider; anything
|
|
5438
5445
|
wider goes through this key.
|
|
5439
5446
|
|
|
5440
5447
|
### `sameCapabilities(a, b)`
|
|
@@ -5477,7 +5484,7 @@ once. A violation carries a stable `rule` id such as `task.browser.url.scheme`,
|
|
|
5477
5484
|
found at such as `snapshot.tasks[0].browsers[1].url`, and a `message`.
|
|
5478
5485
|
|
|
5479
5486
|
Some rules need to know who is reading. `validateTeamMembers`, `validateSnapshot`, and
|
|
5480
|
-
`validateEventEnvelope` take an optional final `{ self, capabilities }` — the signed-in agent's
|
|
5487
|
+
`validateEventEnvelope` take an optional final `{ self, capabilities, leadFeatures }` — the signed-in agent's
|
|
5481
5488
|
`AuthenticationState.identity.id` and their login's `capabilities`. Given `self`, a team member list that
|
|
5482
5489
|
carries that agent reports `team.member.self`, on a `team-member-updated` and a `team-member-removed` as on the list. Given `capabilities`, a team member list published to a login that does not lead
|
|
5483
5490
|
reports `team.unentitled`, and a team event to such a login reports `event.team.capability`; given
|
|
@@ -5670,7 +5677,6 @@ cannot be established from TypeScript structure alone.
|
|
|
5670
5677
|
| `assertForcedBreakStopsTheRest(forcedOn, candidates, stopped)` | A forced break on one provider stops the agent everywhere else: capacity zero stated on every other usable provider holding capacity, nothing on the forcing one, nothing on a dead login. See **Forced breaks**. |
|
|
5671
5678
|
| `assertBreakBeginsAfterTask(steps)` | A break asked for on a task is committed as `starting-after-task` while work remains and reaches `on-break` only once nothing is outstanding — never beside a task, never later than the step that has none. |
|
|
5672
5679
|
| `assertDeniedAndRetriedBreak(states)` | A denial transitions directly to `not-requested`; a later request can still be granted. |
|
|
5673
|
-
| `assertWrapTimeout(task, audioEndedAt, deadline, toleranceMs?)` | The wrap deadline equals audio end plus the task allowance, within a tolerance that defaults to 1000ms; a task with no allowance has no deadline, and one observed is the violation. |
|
|
5674
5680
|
| `assertBrowserSessionIsolation(left, right, expected)` | Browser reuse follows only the declared isolation scheme. |
|
|
5675
5681
|
| `assertNoBrowserSessionKeyCollisions(scenarios)` | No two distinct scenarios derive the same session key. Feed it adversarial names. |
|
|
5676
5682
|
|
|
@@ -5764,9 +5770,9 @@ Immediately before dispatch, additionally use `validateRecordingRequest` against
|
|
|
5764
5770
|
task and explicit observer-domain time. For agent application commands also supply the current agent application declaration,
|
|
5765
5771
|
full agent application report and softphone context. The same checks must run at the executing boundary;
|
|
5766
5772
|
client validation alone is not authorization. The executor serializes operations on each recorder
|
|
5767
|
-
and
|
|
5768
|
-
|
|
5769
|
-
|
|
5773
|
+
and compares `observationId` and `recordingId` against its latest evidence before acting: a
|
|
5774
|
+
command against superseded evidence is refused, and nothing is retried on the executor's own
|
|
5775
|
+
account.
|
|
5770
5776
|
|
|
5771
5777
|
Keep command progress in agent application UI separately from authoritative state. Applied means the requested
|
|
5772
5778
|
recording transition and any retain/discard effect actually completed; publish the confirming task or agent application report before
|
|
@@ -5830,7 +5836,7 @@ never claim delivery or replay the notice into a different call.
|
|
|
5830
5836
|
|
|
5831
5837
|
For a command under this guarantee, applied requires both the recording action and its announcement
|
|
5832
5838
|
to succeed. If capture changes but announcement delivery fails, publish the actual recording state,
|
|
5833
|
-
report the announcement failure visibly and
|
|
5839
|
+
report the announcement failure visibly and answer `failed` under `omni.recording-unsettled`. Do not claim no
|
|
5834
5840
|
effect, automatically repeat the recording action or silently switch to an agent-only notice.
|
|
5835
5841
|
|
|
5836
5842
|
`validateHostRecording` checks the declaration, including its true-or-absent guarantee. Actual audio
|