@xema/omni-protocol 0.1.7 → 0.1.9
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/README.md +17 -2
- package/guide.md +65 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -59,9 +59,11 @@ assignments, break denial and retry, wrap timeout, browser isolation.
|
|
|
59
59
|
> conforming one. A suite that only asserts "this conforming case does not throw" passes unchanged
|
|
60
60
|
> if the helper is gutted, so pair every positive case with the violating twin.
|
|
61
61
|
|
|
62
|
-
##
|
|
62
|
+
## Three things TypeScript will not catch for you
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
All found by adapters against this contract, and all produce a green build over a wrong shape.
|
|
65
|
+
Two share one cause: TypeScript checks extra keys only on a literal that is the thing directly
|
|
66
|
+
assigned. Move the literal anywhere else and the check is gone.
|
|
65
67
|
|
|
66
68
|
**Conditional spreads are the blind spot on a task literal.** A key inside
|
|
67
69
|
`...(cond ? { … } : {})` is never checked against the task type, and `satisfies Task<C>` on the
|
|
@@ -88,6 +90,19 @@ switch (command.action) {
|
|
|
88
90
|
}
|
|
89
91
|
```
|
|
90
92
|
|
|
93
|
+
**A `const` fixture escapes excess-property checking.** Park a literal in a variable and it is no
|
|
94
|
+
longer the thing directly assigned — the same reason the conditional spread escapes — so a shared
|
|
95
|
+
test fixture keeps a field the contract has dropped: `tsc` says nothing and the suite is
|
|
96
|
+
confidently green over a shape that no longer exists. Annotate the `const` or `satisfies` it where
|
|
97
|
+
it is declared; either names the field on the next build.
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
take({ reasonId, requestedAt }); // error: requestedAt
|
|
101
|
+
const request = { reasonId, requestedAt }; take(request); // no error — the hole
|
|
102
|
+
const request: BreakRequest = { reasonId, requestedAt }; // error: requestedAt
|
|
103
|
+
const request = { reasonId, requestedAt } satisfies BreakRequest; // error: requestedAt
|
|
104
|
+
```
|
|
105
|
+
|
|
91
106
|
## Building
|
|
92
107
|
|
|
93
108
|
```
|
package/guide.md
CHANGED
|
@@ -914,10 +914,22 @@ is applied.
|
|
|
914
914
|
|
|
915
915
|
### 6. An unsettled result is unknown, and unknown is not retried
|
|
916
916
|
|
|
917
|
-
A settled result is a fact. A promise that rejects with no result
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
917
|
+
A settled result is a fact. A promise that rejects with no result means *unknown*: the provider
|
|
918
|
+
may have done it or not, and neither Omni nor an adapter on the agent's PC can find out in time to
|
|
919
|
+
make a repeat safe. Omni does not retry; if the agent acts again it is a new command.
|
|
920
|
+
|
|
921
|
+
On a persistent ordered transport there is only one way a command goes unsettled: the connection
|
|
922
|
+
went away underneath it. **An adapter that cannot settle a command has lost its transport, and
|
|
923
|
+
says so** — `provider-status` `connecting`, reconnect, snapshot — whichever channel the command
|
|
924
|
+
actually travelled on. An unsettled promise is therefore always followed by a snapshot, and that
|
|
925
|
+
snapshot is the answer; Omni waits for it rather than calling `snapshot()` itself. While the
|
|
926
|
+
transport is up, a result says the provider accepted the command, and the event that follows —
|
|
927
|
+
`task-updated`, `break-state` — says what it did.
|
|
928
|
+
|
|
929
|
+
**Classify on the rejection, never on a connection status published separately from it.** The
|
|
930
|
+
status is a report about the wire and races the rejection; the rejection is the event. A rejection
|
|
931
|
+
is the provider's answer only when it carries the provider's answer — a failure the provider
|
|
932
|
+
named. Every other rejection is transport loss, whatever the last `provider-status` said.
|
|
921
933
|
|
|
922
934
|
A command therefore carries no key. The provider names its own records — a task, a lead request, a
|
|
923
935
|
member — and Omni refers to them by those names; **Omni never asks a provider to remember a name
|
|
@@ -934,6 +946,11 @@ and the agent is on it; had nothing been placed, nothing arrives and the agent d
|
|
|
934
946
|
Omni must not do is dial again on the agent's behalf — the one outcome worse than a lost answer is
|
|
935
947
|
two phones ringing at the customer.
|
|
936
948
|
|
|
949
|
+
An agent presses Hold while the provider is reconnecting. Nothing is queued at either end: the
|
|
950
|
+
adapter answers `failed` with `omni.unavailable`, Omni shows the refusal, and the agent presses
|
|
951
|
+
again once the provider is `active` — against the state as it is then, rather than a held press
|
|
952
|
+
fired into a state that has moved on.
|
|
953
|
+
|
|
937
954
|
### 7. Work is pulled, never pushed
|
|
938
955
|
|
|
939
956
|
Allocate only within the concurrent capacity Omni has stated for this agent.
|
|
@@ -2207,8 +2224,8 @@ answer and ask again when they want to. `decisionReason` may carry the words att
|
|
|
2207
2224
|
decision, but `approval` does not remain denied.
|
|
2208
2225
|
|
|
2209
2226
|
A provider reports `starting-after-task` only after Omni commits a `granted` request while
|
|
2210
|
-
work is still active. Omni does not
|
|
2211
|
-
it; it
|
|
2227
|
+
work is still active. Omni does not send the request again, because asking again would not move
|
|
2228
|
+
it; it sends the commit again only from a reconnect snapshot that shows the grant still standing.
|
|
2212
2229
|
|
|
2213
2230
|
`accepting: false` is what lets Omni withdraw the control rather than let an agent ask and be
|
|
2214
2231
|
refused. A `BreakReason` marked `alwaysAvailable` survives it: a mandatory rest period is not
|
|
@@ -2425,10 +2442,13 @@ Omni coordinates one attempt as follows:
|
|
|
2425
2442
|
causes Omni to take the cancel path.
|
|
2426
2443
|
4. If every participant reports `granted`, durably choose commit, enter `committing-break`,
|
|
2427
2444
|
and send `commitBreak()` to every participant. A provider then stops offering new work
|
|
2428
|
-
and reports `starting-after-task` or `in-effect`.
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2445
|
+
and reports `starting-after-task` or `in-effect`. The **commit bound** — ten seconds from the
|
|
2446
|
+
decision, tunable per deployment — decides who is kept: a participant that has not applied the
|
|
2447
|
+
commit by then, still `granted` or unreachable, is set aside as unreconciled and the break
|
|
2448
|
+
begins without it. `in-effect` decides `on-break`: Omni enters it once every kept participant
|
|
2449
|
+
reports `in-effect`. A kept participant reporting `starting-after-task` has applied the commit
|
|
2450
|
+
and is finishing a task; the bound is on delivery, not on that task. Omni shows the break as
|
|
2451
|
+
settled and beginning when the task ends, and offers no cancel, because the commit is durable.
|
|
2432
2452
|
5. If any participant fails or denies the request, cannot be reconciled within the bounded
|
|
2433
2453
|
decision timeout, or the agent cancels before commit, durably choose cancel and enter
|
|
2434
2454
|
`cancelling-break`. Send `cancelBreak()` to every participant still reporting
|
|
@@ -2467,7 +2487,13 @@ snapshot before anything else, and the snapshot decides:
|
|
|
2467
2487
|
against an agent who is already on break elsewhere. **A new login is this case too**: the grant
|
|
2468
2488
|
belonged to the old session.
|
|
2469
2489
|
|
|
2470
|
-
The provider must not offer work in the meantime, and the commit is what stops it
|
|
2490
|
+
The provider must not offer work in the meantime, and the commit is what stops it; Omni gives it
|
|
2491
|
+
no capacity until it is reconciled.
|
|
2492
|
+
|
|
2493
|
+
**If the agent has already ended the break elsewhere, the attempt is over** and the returning
|
|
2494
|
+
provider is reconciled to that instead: still `granted` gets `cancelBreak()`, because committing
|
|
2495
|
+
would stop an agent who is working again; `starting-after-task` or `in-effect` gets `endBreak()`.
|
|
2496
|
+
Never rolling back is about a break that is still on, not one the agent has finished.
|
|
2471
2497
|
|
|
2472
2498
|
Omni may tell the agent which platforms the break has not yet reached, as it already does when a
|
|
2473
2499
|
break cannot be paired across every provider.
|
|
@@ -2564,6 +2590,15 @@ decides who leads a team: no roster means nothing is shown, which is the correct
|
|
|
2564
2590
|
agent who leads nobody. The same rule governs `TeamRoster.breakControl` — present when this lead
|
|
2565
2591
|
decides their team's breaks, absent when they do not.
|
|
2566
2592
|
|
|
2593
|
+
**The roster never carries the agent it is published to — not in `members`, and not in
|
|
2594
|
+
`requests`.** A lead does not report to themself: their own break request and their own ask for a
|
|
2595
|
+
lead go up to whoever leads them and appear on *that* person's roster, while the requester sees
|
|
2596
|
+
only their own `BreakState` and their task's `lead` move. An adapter whose platform lists the lead
|
|
2597
|
+
among their own members filters the signed-in identity out before publishing. **Entitlement is a
|
|
2598
|
+
role the provider knows, never inferred from who is listed:** a lead with nobody in their team
|
|
2599
|
+
publishes `[]`, an agent with no such role publishes nothing, and no member count can tell those
|
|
2600
|
+
two apart.
|
|
2601
|
+
|
|
2567
2602
|
### Lead commands
|
|
2568
2603
|
|
|
2569
2604
|
One method, `executeTeamBreak`, taking a discriminated command exactly as `execute` takes a
|
|
@@ -2833,10 +2868,18 @@ Applies a `TaskCommandRequest` to one provider-local task.
|
|
|
2833
2868
|
- `applied` confirms the side effect completed. `failed` confirms it did **not**, with a typed
|
|
2834
2869
|
`ProtocolFailure`; a provider that will not and one that cannot report the same shape, and `code`
|
|
2835
2870
|
says which.
|
|
2871
|
+
- A command sent while `provider-status` is not `active` answers `failed` with `omni.unavailable`.
|
|
2872
|
+
Neither Omni nor the adapter queues it.
|
|
2873
|
+
- **A command that asks for a state answers `applied` when that state holds, whoever brought it
|
|
2874
|
+
about; a command that acts answers `failed` when it cannot act.** Declining a lead request
|
|
2875
|
+
already gone is `applied`; joining one already gone is `failed`, since nobody joined. A lead
|
|
2876
|
+
deciding a member's break that another lead has already decided is `applied` when the decisions
|
|
2877
|
+
agree and `failed`, saying so in `message`, when they differ. `commitBreak()` on a break already
|
|
2878
|
+
in effect is `committed` for the same reason.
|
|
2836
2879
|
- **A settled result is a fact; an unsettled promise is not.** Transport uncertainty may reject the
|
|
2837
|
-
promise with no result at all, and that means *unknown*, not *failed
|
|
2838
|
-
|
|
2839
|
-
happen.
|
|
2880
|
+
promise with no result at all, and that means *unknown*, not *failed*, and a snapshot follows —
|
|
2881
|
+
see **An unsettled result is unknown**. `failed` must never be returned for something the
|
|
2882
|
+
provider is unsure of, because Omni will show the agent it did not happen.
|
|
2840
2883
|
|
|
2841
2884
|
### `ProtocolFailure`
|
|
2842
2885
|
|
|
@@ -2858,7 +2901,7 @@ react rather than only display the message:
|
|
|
2858
2901
|
| `omni.task-not-found` | The provider-local task id is unknown, typically after the task already ended. |
|
|
2859
2902
|
| `omni.destination-not-permitted` | The dial or transfer destination violates the provider's policy. |
|
|
2860
2903
|
| `omni.rate-limited` | The action was throttled. Pair with `retryAfterMs`. |
|
|
2861
|
-
| `omni.unavailable` | The provider is temporarily unable to serve the action
|
|
2904
|
+
| `omni.unavailable` | The provider is temporarily unable to serve the action, including any command sent while `provider-status` is not `active`. |
|
|
2862
2905
|
| `omni.break-already-committed` | Cancellation lost the commit/cancel race; Omni must finish commit recovery. |
|
|
2863
2906
|
|
|
2864
2907
|
They are published as `OMNI_FAILURE_CODES`.
|
|
@@ -2915,9 +2958,10 @@ healthy provider from a dead one.
|
|
|
2915
2958
|
|
|
2916
2959
|
#### Requesting a resync
|
|
2917
2960
|
|
|
2918
|
-
Omni
|
|
2919
|
-
|
|
2920
|
-
|
|
2961
|
+
Omni calls `snapshot()` at connect; after that, snapshots come to it — on reconnect, and when the
|
|
2962
|
+
provider asks. It may still call `snapshot()` at any time, but never to learn a command's fate:
|
|
2963
|
+
the reconnect snapshot already carries it. `reason: "provider-requested"` covers the opposite
|
|
2964
|
+
direction — the provider asking Omni to reconcile — and neither replaces the other.
|
|
2921
2965
|
|
|
2922
2966
|
### `snapshot`
|
|
2923
2967
|
|
|
@@ -2959,9 +3003,9 @@ reasons, retry details, and any imposed break.
|
|
|
2959
3003
|
For a multi-provider attempt, "every provider" is the participant set frozen when the attempt
|
|
2960
3004
|
entered `requesting-break`. Omni commits only after every participant reports `granted` —
|
|
2961
3005
|
that one is unconditional, because nothing has stopped yet and waiting costs only time. It enters
|
|
2962
|
-
`on-break` once every participant
|
|
2963
|
-
|
|
2964
|
-
|
|
3006
|
+
`on-break` once every kept participant reports `in-effect`; the commit bound decides who is kept,
|
|
3007
|
+
setting aside a participant that has not applied the commit rather than holding a break that has
|
|
3008
|
+
already begun elsewhere. Otherwise it follows the two-phase rules under **Coordinating a
|
|
2965
3009
|
multi-provider break**.
|
|
2966
3010
|
|
|
2967
3011
|
### `task-offered`
|