@xema/omni-protocol 0.1.91 → 0.1.92
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 -2
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +2 -2
- package/dist/validation.d.ts +1 -1
- package/dist/validation.js +7 -7
- package/guide.md +19 -17
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1254,7 +1254,7 @@ export interface BreakRequest {
|
|
|
1254
1254
|
reasonId?: string;
|
|
1255
1255
|
}
|
|
1256
1256
|
/**
|
|
1257
|
-
* A break
|
|
1257
|
+
* A break forced on the agent rather than requested by them.
|
|
1258
1258
|
*
|
|
1259
1259
|
* `by` is required in both arms. Who put somebody off the floor survives whether or not the
|
|
1260
1260
|
* break ends on a clock -- a forced break with no origin is a state the agent cannot reason
|
|
@@ -1400,7 +1400,7 @@ export type TeamBreakCommand = {
|
|
|
1400
1400
|
}
|
|
1401
1401
|
/** `reasonId` names a published `BreakReason.id`, required whenever the provider publishes reasons: the member's forced break carries it as `activeReasonId`. */
|
|
1402
1402
|
| {
|
|
1403
|
-
type: "
|
|
1403
|
+
type: "force";
|
|
1404
1404
|
memberId: UserId;
|
|
1405
1405
|
reasonId?: string;
|
|
1406
1406
|
reason?: string;
|
package/dist/testing.d.ts
CHANGED
|
@@ -202,7 +202,7 @@ export declare class BreakStream {
|
|
|
202
202
|
/**
|
|
203
203
|
* A break follows its requests. Given a provider's stream -- seeded with its initial snapshot,
|
|
204
204
|
* either supplied separately or carried by a snapshot event -- every `break-state` moves the way the guide describes: a commit's states only
|
|
205
|
-
* after a grant, never backwards, and a break
|
|
205
|
+
* after a grant, never backwards, and a break forced on the agent arriving in effect with `forced`.
|
|
206
206
|
*/
|
|
207
207
|
export declare function assertBreakFollowsItsRequests(envelopes: readonly ProviderEventEnvelope[], snapshot?: Snapshot): void;
|
|
208
208
|
/**
|
package/dist/testing.js
CHANGED
|
@@ -1867,7 +1867,7 @@ async function driveOneCall(drive) {
|
|
|
1867
1867
|
}
|
|
1868
1868
|
// A request goes not-requested -> awaiting-decision | granted; a commit goes granted ->
|
|
1869
1869
|
// starting-after-task | in-effect; work ending goes starting-after-task -> in-effect; a denial,
|
|
1870
|
-
// a cancel, an end or a release goes back to not-requested; a
|
|
1870
|
+
// a cancel, an end or a release goes back to not-requested; a forced break arrives in-effect
|
|
1871
1871
|
// with `forced`. Nothing else is a move the guide describes.
|
|
1872
1872
|
/** What a stream has said about the agent's break, and the moves it may not make. */
|
|
1873
1873
|
export class BreakStream {
|
|
@@ -1914,7 +1914,7 @@ export class BreakStream {
|
|
|
1914
1914
|
/**
|
|
1915
1915
|
* A break follows its requests. Given a provider's stream -- seeded with its initial snapshot,
|
|
1916
1916
|
* either supplied separately or carried by a snapshot event -- every `break-state` moves the way the guide describes: a commit's states only
|
|
1917
|
-
* after a grant, never backwards, and a break
|
|
1917
|
+
* after a grant, never backwards, and a break forced on the agent arriving in effect with `forced`.
|
|
1918
1918
|
*/
|
|
1919
1919
|
export function assertBreakFollowsItsRequests(envelopes, snapshot) {
|
|
1920
1920
|
const stream = new BreakStream();
|
package/dist/validation.d.ts
CHANGED
|
@@ -51,7 +51,7 @@ export declare function validateTask(task: unknown, context: TaskValidationConte
|
|
|
51
51
|
export declare function validateBreakStatus(state: unknown, tasks: unknown, path?: string): ProtocolViolation[];
|
|
52
52
|
/**
|
|
53
53
|
* Checks lead break dispatch using current authentication, transport, team member list (`team`) and,
|
|
54
|
-
* for
|
|
54
|
+
* for force/release, the target's full `memberBreak`. The provider must authorize the target
|
|
55
55
|
* and recheck the decision atomically; a team member list is not authority to act after it has changed.
|
|
56
56
|
*/
|
|
57
57
|
export declare function validateTeamBreakCommand(request: unknown, context: unknown, path?: string): ProtocolViolation[];
|
package/dist/validation.js
CHANGED
|
@@ -1301,7 +1301,7 @@ function validateForcedBreak(value, path, into) {
|
|
|
1301
1301
|
// `by` is required either way. Who put somebody off the floor survives whether or not the
|
|
1302
1302
|
// break ends on a clock -- a forced break with no origin is a state the agent cannot
|
|
1303
1303
|
// reason about.
|
|
1304
|
-
into.require(isUserId(value.by), "break.forced.by", `${path}.by`, "a forced break must say who
|
|
1304
|
+
into.require(isUserId(value.by), "break.forced.by", `${path}.by`, "a forced break must say who forced it");
|
|
1305
1305
|
if (value.endsAutomatically === true) {
|
|
1306
1306
|
into.timestamp(value.endsAt, "break.forced.endsAt", `${path}.endsAt`);
|
|
1307
1307
|
}
|
|
@@ -1350,7 +1350,7 @@ export function validateBreakStatus(state, tasks, path = "snapshot") {
|
|
|
1350
1350
|
}
|
|
1351
1351
|
/**
|
|
1352
1352
|
* Checks lead break dispatch using current authentication, transport, team member list (`team`) and,
|
|
1353
|
-
* for
|
|
1353
|
+
* for force/release, the target's full `memberBreak`. The provider must authorize the target
|
|
1354
1354
|
* and recheck the decision atomically; a team member list is not authority to act after it has changed.
|
|
1355
1355
|
*/
|
|
1356
1356
|
export function validateTeamBreakCommand(request, context, path = "teamBreakCommand") {
|
|
@@ -1367,7 +1367,7 @@ export function validateTeamBreakCommand(request, context, path = "teamBreakComm
|
|
|
1367
1367
|
const command = request.command;
|
|
1368
1368
|
const allowed = {
|
|
1369
1369
|
decide: ["type", "memberId", "decision", "reason"], policy: ["type", "policy"],
|
|
1370
|
-
|
|
1370
|
+
force: ["type", "memberId", "reasonId", "reason"], release: ["type", "memberId"],
|
|
1371
1371
|
};
|
|
1372
1372
|
const fields = typeof command.type === "string" && Object.hasOwn(allowed, command.type) ? allowed[command.type] : undefined;
|
|
1373
1373
|
if (!fields) {
|
|
@@ -1399,11 +1399,11 @@ export function validateTeamBreakCommand(request, context, path = "teamBreakComm
|
|
|
1399
1399
|
const state = isPlainObject(context.memberBreak) ? context.memberBreak : {};
|
|
1400
1400
|
if (command.type === "release")
|
|
1401
1401
|
into.require(state.forced !== undefined && (state.approval === "in-effect" || state.approval === "starting-after-task"), "team.break.command.release", path, "release a currently forced break");
|
|
1402
|
-
if (command.type === "
|
|
1402
|
+
if (command.type === "force") {
|
|
1403
1403
|
if (command.reasonId !== undefined)
|
|
1404
1404
|
into.filled(command.reasonId, "team.break.command.reasonId", path, "reasonId must not be empty");
|
|
1405
1405
|
const reasons = Array.isArray(state.reasons) ? state.reasons : [];
|
|
1406
|
-
into.require(state.reasons === undefined ? command.reasonId === undefined : reasons.some((r) => isPlainObject(r) && r.id === command.reasonId), "team.break.command.reasonId", path, "
|
|
1406
|
+
into.require(state.reasons === undefined ? command.reasonId === undefined : reasons.some((r) => isPlainObject(r) && r.id === command.reasonId), "team.break.command.reasonId", path, "force uses the target's currently published reason codes");
|
|
1407
1407
|
}
|
|
1408
1408
|
}
|
|
1409
1409
|
return into.violations;
|
|
@@ -1518,7 +1518,7 @@ function validateBreakState(value, path, into) {
|
|
|
1518
1518
|
}
|
|
1519
1519
|
if (value.forced !== undefined) {
|
|
1520
1520
|
validateForcedBreak(value.forced, `${path}.forced`, into);
|
|
1521
|
-
// A forced break is a break somebody
|
|
1521
|
+
// A forced break is a break somebody forced; beside `not-requested` there is no break.
|
|
1522
1522
|
// A forced break is a break in progress or about to be: it travels with in-effect or
|
|
1523
1523
|
// starting-after-task and nothing else. Beside granted, the host would commit a break nobody asked for.
|
|
1524
1524
|
into.require(value.approval === "in-effect" || value.approval === "starting-after-task", "break.forced.approval", `${path}.forced`, `a forced break is in effect or starting after the task; ${describeValue(value.approval)} says the agent asked, or that there is none`);
|
|
@@ -1557,7 +1557,7 @@ function validateBreakState(value, path, into) {
|
|
|
1557
1557
|
into.require(seen.has(value.activeReasonId), "break.activeReasonId.known", `${path}.activeReasonId`, `activeReasonId names a reason the provider did not publish: ${value.activeReasonId}`);
|
|
1558
1558
|
}
|
|
1559
1559
|
// A break in effect, or about to be, on a provider that publishes reasons is on one of them: an
|
|
1560
|
-
// forced one included, since the lead's
|
|
1560
|
+
// forced one included, since the lead's force command named it. A break of no kind is a break whose
|
|
1561
1561
|
// rules -- who may listen through it, whether it counts -- nobody can apply.
|
|
1562
1562
|
ruleEvaluated("break.activeReasonId.required");
|
|
1563
1563
|
if (seen.size > 0 && (value.approval === "in-effect" || value.approval === "starting-after-task")) {
|
package/guide.md
CHANGED
|
@@ -1036,7 +1036,7 @@ type TeamLeadAssistCommand =
|
|
|
1036
1036
|
type TeamBreakCommand =
|
|
1037
1037
|
| { type: "decide"; memberId: UserId; decision: "granted" | "denied"; reason?: string }
|
|
1038
1038
|
| { type: "policy"; policy: "ask" | "auto-approve" | "suspended" }
|
|
1039
|
-
| { type: "
|
|
1039
|
+
| { type: "force"; memberId: UserId; reasonId?: string; reason?: string }
|
|
1040
1040
|
| { type: "release"; memberId: UserId };
|
|
1041
1041
|
|
|
1042
1042
|
type TeamBreakCommandRequest = {
|
|
@@ -1924,7 +1924,7 @@ them from what arrives later.
|
|
|
1924
1924
|
| --- | --- |
|
|
1925
1925
|
| `breaks` | This login may request a break. Requires the four break methods on the connection. |
|
|
1926
1926
|
| `team` | This login leads a team. The provider publishes a `TeamMembers` object to it on every snapshot — `members: []` when nobody is in it — and to nobody else. |
|
|
1927
|
-
| `team.breakControl` | This lead may act on their team's breaks through `executeTeamBreak` —
|
|
1927
|
+
| `team.breakControl` | This lead may act on their team's breaks through `executeTeamBreak` — force, release, decide, set policy — as far as the provider supports; a command it lacks answers `omni.capability-not-enabled`. Omni asks for a decision only against a member whose `break` is `awaiting-decision`, so a provider that grants on request is never asked to decide. Requires `executeTeamBreak`. |
|
|
1928
1928
|
| `team.leadAssistControl` | This lead may join a member's call on request. Requires `executeTeamLeadAssist`. |
|
|
1929
1929
|
| `team.listeningControl` | This lead may listen to a member's call unasked, in the listed modes and no others: `listen`, `coach`, `join-call`. The list always includes `listen`, since the other two begin from one. Requires `executeTeamListen`. See **Listening to a call**. |
|
|
1930
1930
|
| `team.policyControl` | This lead sets the team's policy per capability — on, off, or the person's — within what the queue allows. Requires `executeTeamPolicy`; the team member list carries `policies`. |
|
|
@@ -3614,7 +3614,7 @@ nothing while none are being accepted — so they are not published separately.
|
|
|
3614
3614
|
| `retryAfterMs` | How long until the agent may retry, when the provider can say. |
|
|
3615
3615
|
| `reasons` | Not-ready codes this provider offers. Omitted when it defines none; an empty list is refused, being a second spelling of the same fact. |
|
|
3616
3616
|
| `activeReasonId` | The `BreakReason.id` the current break is on. Omitted when there is no break. Required on a break `in-effect` or `starting-after-task` where the provider publishes `reasons`, a forced break included (`break.activeReasonId.required`): a break with a kind the provider cannot name is a break whose rules nobody can apply. |
|
|
3617
|
-
| `forced` | Set when the break was
|
|
3617
|
+
| `forced` | Set when the break was forced on the agent rather than requested. |
|
|
3618
3618
|
|
|
3619
3619
|
A request can be waiting for two unrelated things, and they are separate values because
|
|
3620
3620
|
rendering one as the other tells an agent to wait for somebody who is never coming:
|
|
@@ -3646,12 +3646,14 @@ Migration: ImposedBreak is now `ForcedBreak`, and the former imposed field is no
|
|
|
3646
3646
|
`BreakState.forced`. Diagnostic and harness coverage names use `break.forced`; the agent-end
|
|
3647
3647
|
diagnostic is `break.command.end.forced`. Update hosts and providers together. The old field
|
|
3648
3648
|
is rejected, including when both spellings are sent; no compatibility alias is provided.
|
|
3649
|
-
The
|
|
3649
|
+
The team break command formerly named place is now `force`. Hosts and providers must
|
|
3650
|
+
adopt the new command together; the former spelling is rejected without an alias.
|
|
3651
|
+
The `release` command and break ordering rules are unchanged.
|
|
3650
3652
|
|
|
3651
|
-
`ForcedBreak` says who
|
|
3653
|
+
`ForcedBreak` says who forced the break, whether automatic ending is enabled, and, when enabled,
|
|
3652
3654
|
when the provider will end it. A break the agent did not choose is not manually resumable by them.
|
|
3653
3655
|
|
|
3654
|
-
**Every forced break has a person behind it.** A lead or a manager
|
|
3656
|
+
**Every forced break has a person behind it.** A lead or a manager forced it; there is no such
|
|
3655
3657
|
thing as a break the platform forced on its own. Where a platform applies one automatically, it is
|
|
3656
3658
|
executing a preference somebody configured, and that person is the owner of the action — `by` names
|
|
3657
3659
|
them, not the machinery that carried it out.
|
|
@@ -3662,7 +3664,7 @@ a display name.
|
|
|
3662
3664
|
**A forced break travels with `in-effect` or `starting-after-task`, and nothing else.** It is a
|
|
3663
3665
|
break in progress or about to be; beside `granted` or `awaiting-decision` the host would read a
|
|
3664
3666
|
request the agent never made and commit it (`break.forced.approval`). Where the provider publishes
|
|
3665
|
-
`reasons`, the lead's `
|
|
3667
|
+
`reasons`, the lead's `force` named one, and the member's state carries it as `activeReasonId`.
|
|
3666
3668
|
|
|
3667
3669
|
For example:
|
|
3668
3670
|
|
|
@@ -3677,7 +3679,7 @@ forced: {
|
|
|
3677
3679
|
The presence of `forced` means the agent cannot end the break manually, so Omni withdraws its
|
|
3678
3680
|
Resume control from that agent. With `endsAutomatically: true`, the provider ends the break at
|
|
3679
3681
|
`endsAt`; with `endsAutomatically: false`, it does not end the break on a timer. An authorized lead
|
|
3680
|
-
may end either form with **Resume**, not only whoever
|
|
3682
|
+
may end either form with **Resume**, not only whoever forced it. Omni shows **Stopped by <who>**,
|
|
3681
3683
|
resolving the name with `describeUsers()`, and shows when the break will end where automatic ending
|
|
3682
3684
|
is enabled.
|
|
3683
3685
|
|
|
@@ -3768,10 +3770,10 @@ request object only to the request method, and undefined to the other three.
|
|
|
3768
3770
|
| `cancelBreak` | `awaiting-decision` or `granted`. A concurrent commit winning still answers `omni.break-already-committed` and requires recovery. |
|
|
3769
3771
|
| `endBreak` | `in-effect` or `starting-after-task` during reconciliation; an agent cannot end a forced break. |
|
|
3770
3772
|
|
|
3771
|
-
Use `validateTeamBreakCommand(request, context)` for lead decisions,
|
|
3773
|
+
Use `validateTeamBreakCommand(request, context)` for lead decisions, forcing a break, release and
|
|
3772
3774
|
policy commands. It requires the live lead capability and active transport, a current target
|
|
3773
|
-
team member list for member commands, and the target's complete break state for
|
|
3774
|
-
Approve/deny requires an awaiting decision;
|
|
3775
|
+
team member list for member commands, and the target's complete break state for forcing/releasing a break.
|
|
3776
|
+
Approve/deny requires an awaiting decision; forcing a break uses the target's reason codes; release
|
|
3775
3777
|
requires a forced committed break. The context's target state must belong to the named member;
|
|
3776
3778
|
that association and backend authorization are provider responsibilities.
|
|
3777
3779
|
|
|
@@ -3860,7 +3862,7 @@ and the other scheduled maintenance, match on it, and nothing could tell the dif
|
|
|
3860
3862
|
specificity: about this agent's own work is `coaching`, else something they must know afterwards is
|
|
3861
3863
|
`training`, else `meeting`.
|
|
3862
3864
|
|
|
3863
|
-
**A break somebody
|
|
3865
|
+
**A break somebody forced on the agent is not automatically one of these.** Where the agent was
|
|
3864
3866
|
stopped rather than choosing to stop — see **Forced breaks** — set `BreakState.forced` and prefer
|
|
3865
3867
|
omitting `kind` to reaching for `other`. None of the ten describes "something was done to this
|
|
3866
3868
|
agent", and `other` claims a classification that was never made.
|
|
@@ -4100,8 +4102,8 @@ One method, `executeTeamBreak`, taking a discriminated command exactly as `execu
|
|
|
4100
4102
|
| --- | --- |
|
|
4101
4103
|
| `{ type: "decide", memberId: UserId, 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`. |
|
|
4102
4104
|
| `{ type: "policy", policy }` | `ask`, `auto-approve`, or `suspended`. |
|
|
4103
|
-
| `{ type: "
|
|
4104
|
-
| `{ type: "release", memberId: UserId }` | Ends a forced break on that member, whoever
|
|
4105
|
+
| `{ type: "force", memberId: UserId, reasonId?, reason? }` | 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. |
|
|
4106
|
+
| `{ type: "release", memberId: UserId }` | Ends a forced break on that member, whoever forced it. |
|
|
4105
4107
|
|
|
4106
4108
|
`memberId` is this provider's own identifier for the member, as published on its team member list. It is
|
|
4107
4109
|
never an identifier from another provider, and Omni does not translate between them; names come
|
|
@@ -4109,7 +4111,7 @@ from `describeUsers()`.
|
|
|
4109
4111
|
|
|
4110
4112
|
`suspended` means requests are **rejected outright** rather than left pending — nobody is coming to
|
|
4111
4113
|
approve them. A provider that suspends breaks must also publish `mayAsk: false` to the team's
|
|
4112
|
-
agents so they see it before asking. A `
|
|
4114
|
+
agents so they see it before asking. A `force` must likewise reach that member as a `forced` break
|
|
4113
4115
|
on their own `BreakState`, or they are stopped from working with no way to see why.
|
|
4114
4116
|
|
|
4115
4117
|
What happens when no lead is online — auto-approving, for instance — is the provider's decision and is
|
|
@@ -4947,7 +4949,7 @@ Replaces this provider's complete `break` object. Its `approval` uses the canoni
|
|
|
4947
4949
|
reasons, retry details, and any forced break.
|
|
4948
4950
|
|
|
4949
4951
|
Each state is also held to the one before it. A commit's states, `starting-after-task` and
|
|
4950
|
-
`in-effect`, follow a grant — the one arrival in a committed state nobody asked for is a
|
|
4952
|
+
`in-effect`, follow a grant — the one arrival in a committed state nobody asked for is a forced
|
|
4951
4953
|
break, which says so with `forced`: in effect at once, or `starting-after-task` while the member
|
|
4952
4954
|
finishes the call they are on (`stream.breakState.commitBeforeGrant`); and a break never moves backwards —
|
|
4953
4955
|
from `in-effect` or `starting-after-task` to a grant or a request, or from `granted` to
|
|
@@ -5380,7 +5382,7 @@ cannot be established from TypeScript structure alone.
|
|
|
5380
5382
|
| `assertReconnectWithMissedAssignments(before, reconnect, ids)` | A reconnect snapshot restores assignments received while offline. |
|
|
5381
5383
|
| `stillHost(report?, guarantees?, mute?)` | A host 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. |
|
|
5382
5384
|
| `TaskStream`, `BreakStream` | The cross-event models the harness applies after the connect snapshot, exported for a host that wants the same rules at its boundary: `seed(snapshot)`, then `apply(envelope)` returns the violations. |
|
|
5383
|
-
| `assertBreakFollowsItsRequests(envelopes, snapshot?)` | A break follows its requests: a commit's states only after a grant, never backwards, and a
|
|
5385
|
+
| `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. |
|
|
5384
5386
|
| `assertMediaFollowsTheTask(envelopes, snapshot?)` | The media follows the task and never decides it: every task is introduced once, `task-media-started` and `task-media-ended` alternate on work that has begun, media ends only where it arrived, and what follows the media ending is `completing` or `task-ended`. The harness applies the same rules to every event after the connect snapshot (`stream.*`). A sequence with no media satisfies it by never testing it — pair it with the assertion that the media end is present. |
|
|
5385
5387
|
| `assertBreakAttemptProviders(candidates, asked)` | A break attempt asks every usable provider holding capacity, `refreshing` included, and nothing of a provider whose login is `expired`. |
|
|
5386
5388
|
| `assertBreakBeginsAfterTask(steps)` | A break asked for on a task is committed as `starting-after-task` while work remains and reaches `in-effect` only once nothing is outstanding — never beside a task, never later than the step that has none. |
|