@xema/omni-protocol 0.1.114 → 0.1.116

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/guide.md CHANGED
@@ -20,9 +20,9 @@ every cross-reference names the file it points into.
20
20
 
21
21
  | File | |
22
22
  | --- | --- |
23
- | `guide/agent.md` | **The agent's work.** What an agent does on any channel: the assignment from offer to wrap, the task and its record, the controls a task offers, the commands the desk sends. |
23
+ | `guide/agent.md` | **The agent's work.** What an agent does on any channel: the assignment from offer to wrap, the task and its record, the controls a task offers, the commands the agent computer sends. |
24
24
  | `guide/voice.md` | **Voice.** What is true of a call and of nothing else: preview, connecting back, ending a call, conference, every dial and its outcome, recording. |
25
- | `guide/phone.md` | **The phone.** How the agent hears the call: softphone and desk phone, the station and its headset, opening the audio, the microphone and the record. |
25
+ | `guide/phone.md` | **The phone.** How the agent hears the call: softphone and hardphone, the station and its headset, opening the audio, the microphone and the record. |
26
26
  | `guide/chat.md` | **Chat.** What is true of a chat and of nothing else. |
27
27
  | `guide/email.md` | **Email.** What is true of an email and of nothing else. |
28
28
  | `guide/queue.md` | **The agent's own queue.** The next call, lined up for this agent while they finish the one they are on: the ask, the lined-up call, and the release. |
@@ -44,9 +44,9 @@ are used precisely throughout and mean nothing looser here.
44
44
  | **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. |
45
45
  | **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. |
46
46
  | **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. |
47
- | **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. |
47
+ | **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 agent computer 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 agent computer. |
48
48
  | **Interaction** | The agent's time on the call, from answer until their audio ends: the `in-progress` and `paused` phases. An assignment produces at most one interaction, and a call that returns produces a new assignment and so a new interaction. |
49
- | **Task** in `guide/agent.md` | The desk's record of one assignment, from offer through wrap: the workspace, controls and completion work the agent has for it, described by the provider and held by the desk. A task is identified by the assignment it is the record of. `pending`, `confirmed` and `preview` are assigned and not yet interacting; `in-progress` and `paused` are the interaction; `completing` is the assignment outliving its interaction. A voice task is one assignment, never the caller's whole call. |
49
+ | **Task** in `guide/agent.md` | The agent computer's record of one assignment, from offer through wrap: the workspace, controls and completion work the agent has for it, described by the provider and held by the agent computer. A task is identified by the assignment it is the record of. `pending`, `confirmed` and `preview` are assigned and not yet interacting; `in-progress` and `paused` are the interaction; `completing` is the assignment outliving its interaction. A voice task is one assignment, never the caller's whole call. **Task is the word on every channel**: voice, chat and email each hand an agent the same unit, offered, worked and wrapped under one assignment, and chat and email have no call. So a channel platform's own name for one agent's part of a contact -- an agent call on a phone platform -- is a task here, and the platform's record of the whole contact across agents and callbacks is the backend's, outside this protocol. |
50
50
  | **Channel** | The kind of work a provider carries: `voice`, `chat`, or `email`. Fixed per provider by its manifest. |
51
51
  | **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. |
52
52
  | **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 agent application only offers it — see **Where a command executes** in `guide/agent.md`. |
@@ -59,7 +59,7 @@ are used precisely throughout and mean nothing looser here.
59
59
  | **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. |
60
60
  | **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. |
61
61
  | **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** in `guide/voice.md`. |
62
- | **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** in `guide/lead.md`. |
62
+ | **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 computer; `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** in `guide/lead.md`. |
63
63
  | **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. |
64
64
 
65
65
  These fields describe state within their containing objects. Both authentication and break
@@ -198,7 +198,7 @@ type DialCapability = { destinations: DialDestinations };
198
198
 
199
199
  type DialOutcome = "answered" | "busy" | "no-answer" | "unreachable" | "rejected" | "cancelled" | "unexplained";
200
200
 
201
- type Phone = "softphone" | "deskPhone";
201
+ type Phone = "softphone" | "hardphone";
202
202
 
203
203
  type PhoneStatus = "ready" | "unregistered" | "do-not-disturb" | "off-hook";
204
204
 
@@ -384,6 +384,7 @@ type HostReport = {
384
384
  type HostGuarantees = {
385
385
  browserUrlVisibility?: true;
386
386
  personConsent?: true;
387
+ muteUnavailableOnHold?: true;
387
388
  };
388
389
 
389
390
  type ProviderTimeScope = { providerId: string; loginId: string };
@@ -420,7 +421,7 @@ type ConnectContext = {
420
421
  log?: (entry: unknown) => void;
421
422
  } & (
422
423
  | { phone: "softphone"; host: { mute: HostMute } }
423
- | { phone?: "deskPhone"; host: { mute?: never; recording?: never } }
424
+ | { phone?: "hardphone"; host: { mute?: never; recording?: never } }
424
425
  );
425
426
 
426
427
  type TransportStatus = "connecting" | "active" | "error";
@@ -875,7 +876,7 @@ type TaskCommandResult =
875
876
 
876
877
  ### Who decides what an agent may do
877
878
 
878
- An agent desk has two managers, not one. **The queue** — a process, a work type — is owned by a
879
+ An agent agent computer has two managers, not one. **The queue** — a process, a work type — is owned by a
879
880
  process manager and **allows** a set of capabilities: hold, connect back, dial, conference,
880
881
  whether the number is visible, the actions it offers, the skills it needs. **The people** are
881
882
  managed through the organisation's structure — a team, a location, the organisation itself, in
@@ -890,7 +891,7 @@ the person settles the value for everyone below it, and where nothing enforces t
890
891
  level that says anything wins. The protocol names a level by the id the manifest declares for it and
891
892
  never describes the chain between them: which levels a person passes through is the structure's to
892
893
  know. A typical organisation has four, and they are the defaults — `DEFAULT_LEVELS`: `org`, `site`,
893
- `team`, `person`, each with the label a desk shows. A structure that differs states its whole
894
+ `team`, `person`, each with the label an agent computer shows. A structure that differs states its whole
894
895
  ladder in `Manifest.orgLevels`, `person` included: what the list carries is in force, and what it
895
896
  leaves out does not exist — a structure with no site level declares `org`, `team`, `person`, and
896
897
  `site` is refused on its wire. A declared level is one the provider's own store actually resolves
@@ -916,10 +917,10 @@ to ask their lead or their site. `lockedBy` is the discriminant: a value that ca
916
917
  lock, so nothing that can be locked — a directory, a number — may carry that key itself. A
917
918
  contact's number and email are the same, since each identifies a person: where the queue says
918
919
  the agent may not see it, the provider sends `{ lockedBy }` in its place — the last digits or
919
- nothing — and a CRM link carries a token, never the value with a flag the desk is asked to honour.
920
+ nothing — and a CRM link carries a token, never the value with a flag the agent computer is asked to honour.
920
921
  **What the queue locks is locked on the whole task.** A number the agent may not see appears
921
922
  nowhere else they read — not in the title, the reference, an attribute, a custom control's label,
922
- or a browser URL the desk shows; a browser whose URL must carry it says `urlVisibility: "hidden"`.
923
+ or a browser URL the agent computer shows; a browser whose URL must carry it says `urlVisibility: "hidden"`.
923
924
  The adapter is the one that knows the value, so it is the one held to it: given the values the
924
925
  queue locked, the validator refuses any other field carrying one, digits compared as digits so no
925
926
  formatting hides them (`task.locked.leak`), and a conformance run whose tasks lock a party's number
@@ -927,7 +928,7 @@ or email states those values in `lockedValues`, since a run that cannot ask the
927
928
  pass. An agent application never has the value and never asks. A name is not locked.
928
929
 
929
930
  **The lock is per audience.** Who sees the number is the provider's choice for each copy of the
930
- task: the member's, on their desk, and the leads', on the team member list. Hidden from the agent
931
+ task: the member's, on their agent computer, and the leads', on the team member list. Hidden from the agent
931
932
  and shown to the lead, shown to the agent and hidden from the lead, hidden from both, shown to
932
933
  both -- any of the four, as the platform's policy says, and each copy is the provider's statement
933
934
  to that audience. Each copy keeps its own lock whole: where the lead's copy locks the number,
@@ -1709,11 +1710,11 @@ compile time.
1709
1710
  | `idleCapabilities` | Declares actions Omni may offer while the agent has no active task, such as voice dialing. Task controls do not belong here. |
1710
1711
  | `phaseLabels` | Optional static adapter-defined display names for canonical `TaskPhase` values. They cannot vary at runtime. |
1711
1712
  | `taskTypePresentation` | Optional static adapter-defined presentation keyed by exact `taskType`. It names the item and its optional agent-facing reference. |
1712
- | `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**. |
1713
- | `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** in `guide/phone.md`. |
1713
+ | `orgLevels` | The organisation's whole ladder as the provider calls it, each level with the label an agent computer 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**. |
1714
+ | `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 `hardphone` (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** in `guide/phone.md`. |
1714
1715
  | `phoneStatus` | Voice only, declared by presence as `true`: the platform sees the phone itself -- its registration, its do-not-disturb, its hook, its calls -- and publishes it as `Snapshot.phone` and `phone-updated`. A platform that cannot see the phone omits it, and then publishes no phone (`manifest.phoneStatus`). See **The phone's own view** in `guide/phone.md`. |
1715
1716
  | `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** in `guide/voice.md`. |
1716
- | `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**. |
1717
+ | `timeCheck` | `true`: implements `checkTime` and states `providerTime` on every snapshot. Required of a provider that publishes an instant the agent computer 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**. |
1717
1718
  | `timestampAuthority` | Optional `"provider"`: provider timestamps are final; agent application instants are advisory. Omission makes no trust promise. |
1718
1719
  | `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** in `guide/agent.md`. |
1719
1720
  | `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`**. |
@@ -2158,9 +2159,9 @@ Creates one live provider connection for the signed-in agent.
2158
2159
  - May reject for authentication, configuration, or startup failure.
2159
2160
  - Must not create a second agent session merely because the underlying transport reconnects.
2160
2161
  - **One client at a time per login, and the later one wins.** A second `connect()` on a login
2161
- that already has a live client is the agent moving desks -- a desk left open at work, signed in
2162
+ that already has a live client is the agent moving agent computers -- an agent computer left open at work, signed in
2162
2163
  from home. The provider takes the new connection, and ends the first with `transport-status`
2163
- `error` and `recovery: "displaced"`, so the first desk stops stating capacity and reporting legs
2164
+ `error` and `recovery: "displaced"`, so the first agent computer stops stating capacity and reporting legs
2164
2165
  and shows the agent they are signed in elsewhere. Two live clients on one login would be two
2165
2166
  sources for one agent, and the lead's list would follow whichever spoke last.
2166
2167
  - The returned connection owns reconnect until Omni calls `disconnect()` or aborts `context.signal`.
@@ -2179,7 +2180,7 @@ Creates one live provider connection for the signed-in agent.
2179
2180
  | `loginId` | Omni-generated identity for this login. It is the same value passed as `AuthenticationContext.loginId`, so an adapter can correlate this connection with the session that authenticated it. Stable across transport reconnects and changed only by a new login. |
2180
2181
  | `autoAcceptTasks` | Agent local policy policy relayed to the provider at login, stated by the agent application 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()`. |
2181
2182
  | `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**. |
2182
- | `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 agent application states what its Mute does, and a desk-phone or conversation login's agent application cannot, so the omission is a compile error rather than a live seat's discovery. See **The station is the agent application's** in `guide/phone.md`. |
2183
+ | `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 agent application states what its Mute does, and a hardphone or conversation login's agent application cannot, so the omission is a compile error rather than a live seat's discovery. See **The station is the agent application's** in `guide/phone.md`. |
2183
2184
  | `store` | The login's operational store, kept by the agent application for the life of the login, across a reload of the agent application, 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 an agent application reported. Three functions, by key. Never for anything sensitive, which is `AuthenticationContext.secrets`, a store an agent application may clear aggressively. **A task's keys carry its assignment id and go with the task**: a key written about a task names the assignment id in the key, and is deleted before the task's end is published, so that nothing of a closed task survives its ending and nothing is left for whatever comes next -- a record with legs the agent application never reported against it. A login-scoped key carries no assignment 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` (`test.store.retained`) or written about the task after its end -- a persist hung off a timer that saw the task as it was (`test.store.late`). The ordinary late write is the agent application's, not the adapter's: an agent application 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.assignment-not-found` and writes nothing -- the agent application ends its own open legs at the task's end, so such a report is the agent application's error to see, and an agent application 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. |
2184
2185
  | `host` | The agent application'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 agent application reports, the adapter decides** in `guide/phone.md`. |
2185
2186
  | `signal` | Optional cancellation signal. Stop startup promptly when aborted and do not begin new work. |
@@ -2188,7 +2189,7 @@ Creates one live provider connection for the signed-in agent.
2188
2189
  ### The agent's day
2189
2190
 
2190
2191
  Every instant on this wire carries an explicit offset, so a moment is unambiguous everywhere and
2191
- a desk renders it in the viewer's clock without help. A **day** is different: hours toward
2192
+ an agent computer renders it in the viewer's clock without help. A **day** is different: hours toward
2192
2193
  target, an answer streak, a per-queue count for today -- anything bucketed by day -- is bucketed
2193
2194
  by somebody's day, and a platform that was never told whose uses its own. An agent in Chennai
2194
2195
  then finds their day rolling at 05:30, and a night shift in Chicago lands in two buckets.
@@ -2236,7 +2237,7 @@ a capability it agrees with the login: a lead's snapshot carries `team`, nobody
2236
2237
  | --- | --- |
2237
2238
  | `transport` | Current `TransportStatus` — whether this provider's transport can serve the login. Defined under **`transport-status`**. |
2238
2239
  | `loginId` | Identity of this login. It must match the connection context. |
2239
- | `providerTime` | The provider's own instant of this read. Required of a provider that declares `timeCheck` (`snapshot.providerTime`), forbidden of one that does not (`snapshot.providerTime.unexpected`): a clock sample the desk counts durations from, saving it a `checkTime`. |
2240
+ | `providerTime` | The provider's own instant of this read. Required of a provider that declares `timeCheck` (`snapshot.providerTime`), forbidden of one that does not (`snapshot.providerTime.unexpected`): a clock sample the agent computer counts durations from, saving it a `checkTime`. |
2240
2241
  | `break` | Complete break state, including status, whether the agent may ask, reasons, retry details, and any forced break. |
2241
2242
  | `tasks` | Complete set of tasks currently offered to or owned by this agent. |
2242
2243
  | `taskCount` | The provider's own count of those tasks, stated rather than inferred, and it must equal `tasks.length`. A snapshot with no work says `taskCount: 0` in so many words — a blank or unanswered state lacks the count and cannot pass as a confirmed empty. |
@@ -2272,8 +2273,8 @@ surface in one place, and what obliges an adapter to implement each one.
2272
2273
  | `requestNextCall()`, `cancelNextCall()`, `releaseLinedUp()` | The login declares `capabilities.nextCall`. The three stand together: a lined-up call the agent could neither withdraw nor let go is a promise with no way out. See **The agent's own queue** in `guide/queue.md`. |
2273
2274
  | `executeTeam(request)` | The login declares `capabilities.lead`: every lead act, on the team surface and nowhere else. See **Lead commands** in `guide/lead.md`. |
2274
2275
  | `setPreference(request)` | The login declares `capabilities.preferences`: the person's choice has to have somewhere to go. |
2275
- | `recordStep(report)` | The manifest lists `softphone` among its `phones`. On a softphone the agent application mutes its own microphone on any call, and the provider's record has to have somewhere to take that leg; a desk phone's microphone is the phone's. See **The agent application records what it performs** in `guide/agent.md`. |
2276
- | `openAudio(request)` | The manifest lists `softphone` among its `phones`. On a softphone the call's audio lands in Omni, so the adapter has to open it; a platform of desk phones alone never does. |
2276
+ | `recordStep(report)` | The manifest lists `softphone` among its `phones`. On a softphone the agent application mutes its own microphone on any call, and the provider's record has to have somewhere to take that leg; a hardphone's microphone is the phone's. See **The agent application records what it performs** in `guide/agent.md`. |
2277
+ | `openAudio(request)` | The manifest lists `softphone` among its `phones`. On a softphone the call's audio lands in Omni, so the adapter has to open it; a platform of hardphones alone never does. |
2277
2278
 
2278
2279
  **The four break methods stand or fall together.** Declaring `capabilities.breaks` at login and then
2279
2280
  implementing `requestBreak` without `commitBreak` leaves an agent granted a break that can never
@@ -2376,8 +2377,8 @@ The current exceptions and their reasons are:
2376
2377
 
2377
2378
  | Case | Exception and reason |
2378
2379
  | --- | --- |
2379
- | 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. |
2380
- | 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. |
2380
+ | Agent application display of provider deadlines (`expiresInSeconds`, `previewEndsInSeconds`, `wrapEndsInSeconds`) | Each is seconds from the publication that carried it, counted down on the agent computer 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. |
2381
+ | Agent application-triggered preview end | The agent computer counts `previewEndsInSeconds` down from receipt and issues `dial` when it reaches zero: the provider owns the deadline and the agent computer owns the trigger. It does not dial before the countdown has run out, and a reload restarts it from the next publication. |
2381
2382
  | 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. |
2382
2383
  | 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. |
2383
2384
  | 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. |
@@ -2392,24 +2393,24 @@ source-measured durations where required; timestamp subtraction is not a substit
2392
2393
 
2393
2394
  #### Every screen counts from the provider's clock
2394
2395
 
2395
- Two screens watching the same agent -- their own desk, their lead's, a manager's on another floor
2396
+ Two screens watching the same agent -- their own computer, their lead's, a manager's on another floor
2396
2397
  -- must show the same numbers. Delay between them is fine; arithmetic that differs is not. So
2397
- nothing the desk shows is its own count, with one exception: the running duration of the active
2398
+ nothing the agent computer shows is its own count, with one exception: the running duration of the active
2398
2399
  task or call, which a screen times itself from the moment it saw the call begin -- the
2399
- `task-audio-started` on a softphone, the answer it sent on a desk phone -- and which two such
2400
+ `task-audio-started` on a softphone, the answer it sent on a hardphone -- and which two such
2400
2401
  screens may therefore disagree on, since their clocks differ. That is the whole exception. A call
2401
2402
  a screen found already up -- on a connect or reconnect snapshot, on a lead's list, after a reload
2402
2403
  -- is counted from the provider's `answered` step's `at` against the provider's clock, like every
2403
- other duration, so a reloaded desk shows 12:40 beside the lead's 12:40 and not 0:05. And a leg
2404
+ other duration, so a reloaded agent computer shows 12:40 beside the lead's 12:40 and not 0:05. And a leg
2404
2405
  that has closed shows the provider's `seconds` on every screen, never the count a screen kept.
2405
2406
  Everything else comes from the wire.
2406
2407
 
2407
2408
  Durations off the active call -- how long a member has been `on-break` from their `since`, how
2408
2409
  long a lead has been listening, how long since `signedInAt` -- are counted from the provider's
2409
- clock, never the screen's. The desk keeps the latest provider instant it has received -- a
2410
+ clock, never the screen's. The agent computer keeps the latest provider instant it has received -- a
2410
2411
  snapshot's `providerTime`, an envelope's `occurredAt`, a `checkTime` result -- with the monotonic
2411
2412
  elapsed time since receipt, and renders every such duration from that; a sample older than
2412
- `maxSampleAgeMs` is refreshed with `checkTime`. Without a usable sample the desk shows the instant
2413
+ `maxSampleAgeMs` is refreshed with `checkTime`. Without a usable sample the agent computer shows the instant
2413
2414
  and no count. A provider that publishes any of these instants therefore declares `timeCheck`
2414
2415
  (`manifest.timeCheck.required`) and states `providerTime` on every snapshot
2415
2416
  (`snapshot.providerTime`). Two screens then differ by their delay and nothing else.
@@ -2417,7 +2418,7 @@ and no count. A provider that publishes any of these instants therefore declares
2417
2418
  **Seconds left are worked out at each publication.** `expiresInSeconds`, `previewEndsInSeconds`,
2418
2419
  `wrapEndsInSeconds` and `expectedEndsInSeconds` are each the provider's arithmetic at the instant
2419
2420
  of the publication that carries them, never a value copied from an earlier one. A provider that
2420
- builds the next publication by spreading the last one forward hands a reloaded desk a countdown
2421
+ builds the next publication by spreading the last one forward hands a reloaded agent computer a countdown
2421
2422
  already spent -- sixty seconds shown beside a lead's screen showing twenty -- and the stream
2422
2423
  names it: between two publications of one countdown the value loses at least the seconds the
2423
2424
  provider's own clock says passed, within a second of rounding (`stream.countdown.copied`, on an
@@ -2431,7 +2432,7 @@ overruns; that is the true value, not a copy.
2431
2432
  the `expired` ending within the seconds plus `settleMs`, then tests the next offer
2432
2433
  (`test.offer.expired`). A preview's deadline is held to its `atDeadline` the same way: under
2433
2434
  `provider-dials` the provider dials when it runs out, under `host-dials` and `waits` the preview
2434
- stands until the desk dials (`test.preview.deadline`).
2435
+ stands until the agent computer dials (`test.preview.deadline`).
2435
2436
 
2436
2437
  #### Optional periodic provider time checks and agent application estimates
2437
2438
 
@@ -2570,9 +2571,9 @@ died; the agent application knows how to run a login. `recovery` joins the two:
2570
2571
  disposes the errored connection with `disconnect()` and calls `connect()` afresh, as for
2571
2572
  `reconnect`, and the fresh snapshot re-establishes state. The adapter tears nothing down itself
2572
2573
  and expects nothing to carry on: the connection that reported `error` is finished.
2573
- - **`displaced`** — another client connected on this login and took it: the agent moved desks.
2574
+ - **`displaced`** — another client connected on this login and took it: the agent moved agent computers.
2574
2575
  Nothing revives this connection on its own -- Omni does not reconnect, or it would take the
2575
- login straight back from the desk the agent just sat down at -- and the desk shows the agent
2576
+ login straight back from the agent computer the agent just sat down at -- and the agent computer shows the agent
2576
2577
  they are signed in elsewhere, with a way to take the login back, which is a `connect()` that
2577
2578
  displaces the other. See **`Adapter.connect(context)`**.
2578
2579
 
@@ -2594,8 +2595,8 @@ come — see **Liveness**.
2594
2595
 
2595
2596
  **The status is seen, not merely known.** An agent application renders the transport's status where the agent
2596
2597
  works — `connecting` from the moment it is reported, `error` with what revives it — and never a
2597
- healthy workspace over a transport that cannot serve it: an agent talking on a desk whose transport
2598
- died was an incident, and it happened because the desk knew and did not say. The same obligation
2598
+ healthy workspace over a transport that cannot serve it: an agent talking on an agent computer whose transport
2599
+ died was an incident, and it happened because the agent computer knew and did not say. The same obligation
2599
2600
  covers a `diagnostic`: shown where the agent works, and counted.
2600
2601
 
2601
2602
  ### `break-state`
@@ -2675,7 +2676,7 @@ snapshots until it ends.
2675
2676
  **Every offer is owed an ending.** An assignment the provider introduced is an assignment it ends,
2676
2677
  with `task-ended` and an outcome, whatever became of the call: answered and completed, declined,
2677
2678
  withdrawn, abandoned in the ring, lapsed, taken over. An offer that is simply never mentioned
2678
- again leaves the desk holding a task nobody will close, and the test names it
2679
+ again leaves the agent computer holding a task nobody will close, and the test names it
2679
2680
  (`stream.taskOffered.unended`).
2680
2681
 
2681
2682
  ### `task-updated`
@@ -2693,7 +2694,7 @@ platform, a station going in use the moment a call is answered, sends it then, b
2693
2694
  opened anything, and `openAudio` has its own answer for what the agent application did. It
2694
2695
  names a task whose work has begun, and it alternates with `task-audio-ended`: audio that never
2695
2696
  started cannot end, so a live call whose provider says nothing about its audio is a provider in
2696
- breach, not a state a desk fills in from its own devices.
2697
+ breach, not a state an agent computer fills in from its own devices.
2697
2698
 
2698
2699
  The event is the transition and the task's `audio` field is the state. A `task-updated` re-states
2699
2700
  the audio its task already holds — republishing `started` on a hold is a statement, not a second
@@ -2728,7 +2729,7 @@ and not for ever: `applied` to a `complete` says the
2728
2729
  provider has completed the task, and its `task-ended` follows within the
2729
2730
  manifest's `settleMs`. A provider never answers `applied` for a completion it has not yet
2730
2731
  performed. Past the bound the agent application calls `snapshot()`: a snapshot still carrying the task is a task
2731
- held open by a provider that said it was done, and the desk shows it as unsettled -- "Completing...
2732
+ held open by a provider that said it was done, and the agent computer shows it as unsettled -- "Completing...
2732
2733
  the provider has not confirmed" -- naming the command; a snapshot no longer carrying it clears the
2733
2734
  task, since the ending was owed and lost. The test holds a provider to the same bound
2734
2735
  (`test.completion.unsettled`). The `task-audio-ended` event and the `completing` phase are likewise
@@ -2841,7 +2842,7 @@ agent's own queue** in `guide/queue.md`.
2841
2842
  ### `phone-updated`
2842
2843
 
2843
2844
  The phone as the platform now sees it, whole, replacing `Snapshot.phone`: a registration lost or
2844
- back, do-not-disturb pressed or cleared, the handset lifted with no call or replaced, the desk
2845
+ back, do-not-disturb pressed or cleared, the handset lifted with no call or replaced, the agent computer
2845
2846
  phone's own mute. Emitted only by a provider whose manifest declares `phoneStatus`
2846
2847
  (`event.phone.capability`). The lead's member carries the same `PhoneState`, republished with the
2847
2848
  member. See **The phone's own view** in `guide/phone.md`.
@@ -2891,12 +2892,12 @@ same exported checks are used by Omni and adapter tests so their interpretations
2891
2892
  | `validateEventEnvelope(envelope, manifest)` | Envelope identity, timestamp, and the payload for each event type. |
2892
2893
  | `validateContact(contact)` | Contact field shapes and attribute keys. Every field is optional, so this checks what is present rather than what is missing. |
2893
2894
  | `validateScheduledActivity(activity)` | Required activity fields and start/end ordering. |
2894
- | `validateHostGuarantees(guarantees)` | What an agent application promises: only the guarantees this contract names, each declared by presence and never `false`. The harness validates the guarantees of whatever agent application a test hands the adapter. |
2895
+ | `validateHostGuarantees(guarantees, path?, phone?)` | What an agent application promises: only the guarantees this contract names, each declared by presence and never `false`. Given the login's phone, the mute promise is held to it: `muteUnavailableOnHold` required of a softphone login's agent application and refused of any other (`host.guarantee.muteUnavailableOnHold.required` / `.unexpected`). The harness validates the guarantees of whatever agent application a test hands the adapter, against the phone it chose. |
2895
2896
  | `validateProviderTimeCheckPolicy(policy)` | Explicit optional polling settings, positive safe-integer durations and round-trip/timeout/interval ordering. |
2896
2897
  | `validateProviderTimeCheckRequest(request)` | Fresh-request shape; the agent application enforces actual uniqueness and outstanding-request lifetime. |
2897
2898
  | `validateProviderTimeCheckResult(result, request, loginId)` | ISO timestamp, clock identity, exact request/login correlation; timing and source accuracy remain runtime checks. |
2898
2899
  | `validateProviderTimeEstimate(estimate, scope)` | Optional agent application estimate shape and provider/login scope; no accuracy guarantee. |
2899
- | `validateHistoryReport(report, path?, manifest?)` | What the agent application 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`. |
2900
+ | `validateHistoryReport(report, path?, manifest?, task?)` | What the agent application 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`. Given the task, the agent application's own mute beginning on a `paused` task is refused (`historyReport.muted.held`): the Mute waits for resume. |
2900
2901
  | `validateHostReport(report)` | The agent application'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 agent application a test hands the adapter; `stillHost(report)` builds one that never changes. |
2901
2902
  | `validateHostMute(mute, softphone)` | What the agent application's Mute does, stated on a softphone login and nowhere else: `stream` or `station` (`host.mute`), required where the agent application holds a microphone (`host.mute.required`) and refused where it does not (`host.mute.unexpected`). The harness holds `ConnectContext.host.mute` to it. |
2902
2903
  | `validateLoginStore(store)` | The login's store the agent application hands every connection: an object with `get`, `set` and `delete` (`store.shape`, `store.get`, `.set`, `.delete`). The harness holds `ConnectContext.store` to it. |
@@ -2918,7 +2919,7 @@ reports `team.unentitled`, and a team event to such a login reports `event.team.
2918
2919
  `leadFeatures: true`, a snapshot without a team reports `team.required`, and given `leadFeatures: false`,
2919
2920
  anything of the team reaching the lead reports `team.unexpected` or `event.team.features`. Without them those rules are not checked, because they cannot be.
2920
2921
  `testAdapter` passes all three, holding the switch off until it has sent it. Given the manifest, a
2921
- snapshot or event that carries an instant the desk renders as a running duration under a manifest
2922
+ snapshot or event that carries an instant the agent computer renders as a running duration under a manifest
2922
2923
  without `timeCheck` reports `manifest.timeCheck.required`.
2923
2924
 
2924
2925
  `assertNoViolations(violations)` throws `ProtocolConformanceError` — which carries the full
@@ -2931,22 +2932,22 @@ reaching the workspace.
2931
2932
 
2932
2933
  ### What the agent application does with what it refuses
2933
2934
 
2934
- A refusal has an aftermath on the desk and a report to the provider, and both are stated.
2935
+ A refusal has an aftermath on the agent computer and a report to the provider, and both are stated.
2935
2936
 
2936
2937
  - **A refused snapshot replaces nothing.** The agent application keeps the last state it took from this
2937
2938
  provider whole, and shows the provider as faulted -- the transport as it stands, and the words
2938
2939
  "last update refused" with the rule -- so the agent knows the view is standing still rather than
2939
2940
  believing it current. It does not adopt the good half of a snapshot: a task the agent is on would
2940
- vanish from the desk while the call is up.
2941
+ vanish from the agent computer while the call is up.
2941
2942
  - **A refused event is dropped and counted.** The state the agent application holds does not move for it. The
2942
2943
  provider is told, and may republish a corrected state or raise a `diagnostic`; the agent application retries
2943
- nothing. The desk shows nothing for it: the view kept moving for every other event, so "last
2944
+ nothing. The agent computer shows nothing for it: the view kept moving for every other event, so "last
2944
2945
  update refused" would be false of it, and a chip that said nothing would be right about the
2945
2946
  transport. Counted, and the provider told, is all an agent application can truthfully do for one event.
2946
2947
  - **The provider is told, every time.** `refused(report)` carries the artefact, the envelope id for
2947
2948
  an event, and every violation with its rule and path. An adapter logs it at error and treats it as
2948
2949
  its own defect until shown otherwise: what the agent application refused never reached the agent, and a
2949
- provider that hears nothing runs a whole shift beside a frozen desk with nothing wrong on its
2950
+ provider that hears nothing runs a whole shift beside a frozen agent computer with nothing wrong on its
2950
2951
  side. The harness tells an adapter under test exactly as an agent application does
2951
2952
  (`connection.refused.required`, `connection.refused.rejected`).
2952
2953
 
@@ -2988,7 +2989,7 @@ no tasks exercises no task rule, and a pass over it reads as coverage it is not.
2988
2989
  **A static run never answers a call.** It states a capacity and disconnects, so everything
2989
2990
  downstream of `answer` -- the room, the stage, audio, every phase past `pending`, every dial
2990
2991
  outcome -- stays in `notTested` for every adapter, and a rule about a live call is enforced only
2991
- in each adopter's own tests. `{ withCall: true }` closes that: the exercise takes the first task the
2992
+ in each adopter's own tests. `{ withCall: true }` closes that: the test takes the first task the
2992
2993
  provider offers through one ordinary lifecycle -- accept it, wait for its audio and open it on a
2993
2994
  softphone, hold and resume where the task offers `hold`, end the call where it offers `endCall`,
2994
2995
  complete it with an outcome where the agent completes -- and holds every step to the rules a
@@ -3014,6 +3015,8 @@ as each case was considered, so a test that needs a rule to have run asserts it
3014
3015
  inferring it from an empty `violations`, and a rule absent from it was never looked at, which is a
3015
3016
  gap and not a pass. With the audio open on a softphone,
3016
3017
  the test mutes it for one second and reports the leg through `recordStep`, begun and then ended,
3018
+ and with the task on hold sends a mute begun there past the validator and expects `failed`, naming an
3019
+ adapter that records it (`test.recordStep.held`);
3017
3020
  expecting each report `recorded` with the provider-selected history `at` (`test.recordStep.failed`, `.rejected`, `result.recordStep.at`); then it mutes again and
3018
3021
  ends the call muted, as agents do, so the leg is open when the audio ends, the provider closes it
3019
3022
  in the completing publication or the open entry is refused (`task.history.muted.open`),
@@ -3101,7 +3104,7 @@ cannot be established from TypeScript structure alone.
3101
3104
  | `assertReached(result, subjects)` | The exercise met every subject named; throws listing those it did not. Pair it with a clean `testAdapter` result. |
3102
3105
  | `assertAuthenticationRestoreAndExpiry(states)` | A restored authenticated session can refresh and ends in expiry. Every state is validated. |
3103
3106
  | `assertReconnectWithMissedAssignments(before, reconnect, ids)` | A reconnect snapshot restores assignments received while offline. |
3104
- | `stillHost(report?, guarantees?, mute?)` | An agent application that reports one thing and never changes, for a test context: `{ online: true }` by default, a report with audio for a softphone voice adapter, and never for a desk phone. |
3107
+ | `stillHost(report?, guarantees?, mute?)` | An agent application that reports one thing and never changes, for a test context: `{ online: true }` by default, a report with audio for a softphone voice adapter, and never for a hardphone. |
3105
3108
  | `TaskStream`, `BreakStream` | The cross-event models the harness applies after the connect snapshot, exported for an agent application that wants the same rules at its boundary: `seed(snapshot)`, then `apply(envelope)` returns the violations. |
3106
3109
  | `assertBreakFollowsItsRequests(envelopes, snapshot?)` | A break follows its requests: a commit's states only after a grant, never backwards, and a forced break arriving in effect with `forced`. The harness applies the same rules after the connect snapshot. |
3107
3110
  | `assertAudioFollowsTheTask(envelopes, snapshot?)` | The audio follows the task and never decides it: every task is introduced once, `task-audio-started` and `task-audio-ended` alternate on work that has begun, audio ends only where it arrived, and what follows the audio ending is `completing` or `task-ended`. The harness applies the same rules to every event after the connect snapshot (`stream.*`). A sequence with no audio satisfies it by never testing it — pair it with the assertion that the audio end is present. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xema/omni-protocol",
3
- "version": "0.1.114",
3
+ "version": "0.1.116",
4
4
  "description": "The Omni protocol: the contract every provider adapter implements",
5
5
  "type": "module",
6
6
  "license": "MIT",