@xema/omni-protocol 0.1.24 → 0.1.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -0
- package/dist/testing.js +10 -2
- package/dist/validation.js +6 -0
- package/guide.md +25 -2
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1006,6 +1006,8 @@ export interface Snapshot<C extends Channel = Channel> {
|
|
|
1006
1006
|
break: BreakState;
|
|
1007
1007
|
/** Every task currently owned by this agent for this provider. */
|
|
1008
1008
|
tasks: Task<C>[];
|
|
1009
|
+
/** The provider's own count of those tasks, stated rather than inferred: it must equal `tasks.length`, so a blank or unanswered state can never pass as a confirmed empty. */
|
|
1010
|
+
taskCount: number;
|
|
1009
1011
|
contacts?: Contact[];
|
|
1010
1012
|
scheduledActivities?: ScheduledActivity[];
|
|
1011
1013
|
team?: TeamRoster;
|
package/dist/testing.js
CHANGED
|
@@ -637,8 +637,16 @@ export class TaskStream {
|
|
|
637
637
|
refuse("stream.taskMediaEnded.follow", `${at}.task.phase`, `after its media ended, ${id} completes or ends; ${phase} is a phase the audio does not decide`);
|
|
638
638
|
}
|
|
639
639
|
}
|
|
640
|
-
// A task replaces the task
|
|
641
|
-
|
|
640
|
+
// A task replaces the task, and an update re-states media without moving it: the
|
|
641
|
+
// transitions belong to task-media-ready and task-media-ended. Releasing ended is the
|
|
642
|
+
// one move an update may make, since wrapped audio has nothing left to end.
|
|
643
|
+
{
|
|
644
|
+
const next = TaskStream.stated(event.task);
|
|
645
|
+
if (known.media !== next.media && !(known.media === "ended" && next.media === "none")) {
|
|
646
|
+
refuse("stream.taskUpdated.media", `${at}.task.media`, `a task-updated re-states media, it does not move it: ${id} held ${known.media} and the update says ${next.media}; audio arrives on task-media-ready and ends on task-media-ended`);
|
|
647
|
+
}
|
|
648
|
+
this.tasks.set(id, next);
|
|
649
|
+
}
|
|
642
650
|
break;
|
|
643
651
|
case "task-media-ready":
|
|
644
652
|
if (id === undefined)
|
package/dist/validation.js
CHANGED
|
@@ -993,6 +993,12 @@ export function validateSnapshot(snapshot, manifest, path = "snapshot", context
|
|
|
993
993
|
into.require(snapshot.sessionId === context.sessionId, "snapshot.sessionId.mismatch", `${path}.sessionId`, `a snapshot for session ${String(snapshot.sessionId)} on a login whose session is ${context.sessionId}`);
|
|
994
994
|
}
|
|
995
995
|
validateBreakState(snapshot.break, `${path}.break`, into);
|
|
996
|
+
// The count is the provider's confirmation of how much work it answered with. Stated, never
|
|
997
|
+
// inferred: an unanswered or blank state lacks it, and cannot pass as a confirmed empty.
|
|
998
|
+
if (into.require(typeof snapshot.taskCount === "number" && Number.isInteger(snapshot.taskCount) && snapshot.taskCount >= 0, "snapshot.taskCount", `${path}.taskCount`, "a snapshot states its task count: a whole number, zero or more")
|
|
999
|
+
&& Array.isArray(snapshot.tasks)) {
|
|
1000
|
+
into.require(snapshot.taskCount === snapshot.tasks.length, "snapshot.taskCount.mismatch", `${path}.taskCount`, `taskCount says ${snapshot.taskCount} and tasks carries ${snapshot.tasks.length}: a count that does not reconcile is an answer nobody gave`);
|
|
1001
|
+
}
|
|
996
1002
|
if (!Array.isArray(snapshot.tasks)) {
|
|
997
1003
|
into.add("snapshot.tasks.shape", `${path}.tasks`, "a snapshot must carry a tasks array");
|
|
998
1004
|
}
|
package/guide.md
CHANGED
|
@@ -363,6 +363,7 @@ type Snapshot = {
|
|
|
363
363
|
sessionId: string;
|
|
364
364
|
break: BreakState;
|
|
365
365
|
tasks: Task[];
|
|
366
|
+
taskCount: number;
|
|
366
367
|
contacts?: Contact[];
|
|
367
368
|
scheduledActivities?: ScheduledActivity[];
|
|
368
369
|
team?: TeamRoster;
|
|
@@ -1223,6 +1224,17 @@ which field says so varies. See **Which commands need a capability**.
|
|
|
1223
1224
|
|
|
1224
1225
|
Snapshots establish and replace provider state when an agent signs in, reconnects, or resynchronises.
|
|
1225
1226
|
|
|
1227
|
+
**A snapshot is the provider's answer, and an adapter that got no answer publishes nothing.** A
|
|
1228
|
+
state read that answers unknown — a session not yet associated, a backend mid-failover — is not an
|
|
1229
|
+
empty state: the adapter keeps what it holds, stays `connecting`, and publishes a snapshot only
|
|
1230
|
+
once the provider has actually answered for this login, exactly as `connect()` may not resolve
|
|
1231
|
+
before it can provide a meaningful one. And emptiness is stated, never inferred: every snapshot
|
|
1232
|
+
carries `taskCount`, the provider's own count reconciled against `tasks.length`, so a snapshot
|
|
1233
|
+
with no work says `taskCount: 0` in so many words and a blank or half-built state — which lacks
|
|
1234
|
+
the count — can never pass as a confirmed empty. Absence of knowledge is never evidence of
|
|
1235
|
+
absence, and every place "empty" is allowed to carry both meanings will eventually clear
|
|
1236
|
+
somebody's live call.
|
|
1237
|
+
|
|
1226
1238
|
Events report completed transactions after that baseline. Nothing is missed while the connection
|
|
1227
1239
|
holds; when it drops, the reconnect snapshot re-establishes the baseline before any further event
|
|
1228
1240
|
is applied.
|
|
@@ -1837,6 +1849,7 @@ a capability it agrees with the login: a lead's snapshot carries `team`, nobody
|
|
|
1837
1849
|
| `sessionId` | Identity of this login session. It must match the connection context. |
|
|
1838
1850
|
| `break` | Complete break state, including approval, accepting state, reasons, retry details, and any imposed break. |
|
|
1839
1851
|
| `tasks` | Complete set of tasks currently offered to or owned by this agent. |
|
|
1852
|
+
| `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. |
|
|
1840
1853
|
| `contacts` | Required complete contact contribution when the manifest declares `contacts`; `[]` clears it. Omitted only when it does not. |
|
|
1841
1854
|
| `scheduledActivities` | Required complete calendar contribution when the manifest declares `calendar`; `[]` clears it. Omitted only when it does not. |
|
|
1842
1855
|
| `team` | Required `TeamRoster` when the login declares `capabilities.team`, `[]` when nobody is in it. Forbidden otherwise — the login is the permission. |
|
|
@@ -3402,7 +3415,10 @@ silently lose a message, so while the connection is up Omni has seen everything
|
|
|
3402
3415
|
Loss has exactly one shape: the connection went away. The adapter reports `connecting` or `error`,
|
|
3403
3416
|
reconnects, and emits a `snapshot` event carrying complete state. That snapshot is the repair —
|
|
3404
3417
|
whatever was missed while the connection was down is in it, and Omni replaces its provider view
|
|
3405
|
-
rather than reasoning about what it did not receive.
|
|
3418
|
+
rather than reasoning about what it did not receive. A repair is an answer like any other: a
|
|
3419
|
+
platform that has not yet answered for this login after a reconnect — a state read served empty by
|
|
3420
|
+
a backend that does not know the session yet — yields no snapshot, and the adapter stays
|
|
3421
|
+
`connecting` holding what it holds. See **Snapshots establish state; events report transactions**.
|
|
3406
3422
|
|
|
3407
3423
|
A snapshot must account for **everything the adapter has emitted before it resolves**, not merely
|
|
3408
3424
|
everything emitted when it was requested. Omni discards events buffered during the read on that
|
|
@@ -3517,6 +3533,13 @@ names a task whose work has begun, and it alternates with `task-media-ended`: me
|
|
|
3517
3533
|
made ready cannot end, so a live call whose provider says nothing about its audio is a provider in
|
|
3518
3534
|
breach, not a state a desk fills in from its own devices.
|
|
3519
3535
|
|
|
3536
|
+
The event is the transition and the task's `media` field is the state. A `task-updated` re-states
|
|
3537
|
+
the media its task already holds — republishing `ready` on a hold is a statement, not a second
|
|
3538
|
+
arrival — but it does not move it: an update that itself flips the field is refused
|
|
3539
|
+
(`stream.taskUpdated.media`), and the pairing at the moment audio arrives is the phase change
|
|
3540
|
+
without the field, then the event. Releasing `ended` is the one move an update may make, since
|
|
3541
|
+
wrapped audio has nothing left to end.
|
|
3542
|
+
|
|
3520
3543
|
### `task-media-ended`
|
|
3521
3544
|
|
|
3522
3545
|
Signals that a task's real-time media ended. For voice and similar channels, this starts the fixed
|
|
@@ -3610,7 +3633,7 @@ same exported checks are used by Omni and adapter tests so their interpretations
|
|
|
3610
3633
|
| --- | --- |
|
|
3611
3634
|
| `validateManifest(manifest)` | Identity, protocol-version interoperability, authentication methods, and idle-capability shapes. |
|
|
3612
3635
|
| `validateTask(task, { channel })` | Identity, channel agreement, phase, completion allowance, capability shapes, custom controls, and browsers. |
|
|
3613
|
-
| `validateSnapshot(snapshot, manifest)` | Status, break state, break reasons, team roster, and every task, contact, and activity, including idle-capability gating both ways: a contribution the manifest never declared is refused, and one it declares is required, `[]` included. |
|
|
3636
|
+
| `validateSnapshot(snapshot, manifest)` | Status, break state, break reasons, team roster, the stated `taskCount` reconciled against the tasks carried, and every task, contact, and activity, including idle-capability gating both ways: a contribution the manifest never declared is refused, and one it declares is required, `[]` included. |
|
|
3614
3637
|
| `validateEventEnvelope(envelope, manifest)` | Envelope identity, timestamp, and the payload for each event type. |
|
|
3615
3638
|
| `validateContact(contact)` | Contact field shapes and attribute keys. Every field is optional, so this checks what is present rather than what is missing. |
|
|
3616
3639
|
| `validateScheduledActivity(activity)` | Required activity fields and start/end ordering. |
|