@xema/omni-protocol 0.1.77 → 0.1.79
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 +8 -1
- package/dist/index.d.ts +49 -30
- package/dist/index.js +8 -3
- package/dist/testing.d.ts +6 -2
- package/dist/testing.js +235 -112
- package/dist/validation.js +192 -77
- package/guide.md +128 -86
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -126,11 +126,18 @@ const request = { reasonId, requestedAt } satisfies BreakRequest; // error: requ
|
|
|
126
126
|
## Building
|
|
127
127
|
|
|
128
128
|
```
|
|
129
|
-
pnpm install
|
|
129
|
+
pnpm install --frozen-lockfile
|
|
130
|
+
pnpm check # clean build, type checks, tests, and packed-package checks
|
|
130
131
|
pnpm build # emits dist/
|
|
131
132
|
pnpm test # type-checks the tests, then runs them
|
|
133
|
+
pnpm check:package # verifies the current dist/ as a consumer would
|
|
132
134
|
```
|
|
133
135
|
|
|
136
|
+
CI runs the complete check on Node 20, 22, and 24 for pull requests (including drafts)
|
|
137
|
+
and pushes to main. Package checks create a temporary npm tarball, verify its contents,
|
|
138
|
+
and check JavaScript imports and TypeScript declarations for every exported entry point
|
|
139
|
+
from an isolated consumer. Publishing also runs `pnpm check` through `prepublishOnly`.
|
|
140
|
+
|
|
134
141
|
## The guide is authoritative
|
|
135
142
|
|
|
136
143
|
Where `guide.md` and any code here disagree, the guide is right and the code is a defect. It has
|
package/dist/index.d.ts
CHANGED
|
@@ -88,7 +88,8 @@ export interface BrowserAccess {
|
|
|
88
88
|
}
|
|
89
89
|
export interface PersonalBrowserCapability {
|
|
90
90
|
access: BrowserAccess;
|
|
91
|
-
|
|
91
|
+
/** Which navigations the access policy is checked against: every redirect and navigation, or the starting URL alone. Stated, never defaulted. */
|
|
92
|
+
accessAppliesTo: "initial-url" | "all-navigation";
|
|
92
93
|
}
|
|
93
94
|
export type DialDestinations = "contacts-only" | "any-number";
|
|
94
95
|
export interface DialCapability {
|
|
@@ -175,8 +176,9 @@ export interface Manifest<C extends Channel = Channel> {
|
|
|
175
176
|
*/
|
|
176
177
|
runningStepReports?: true;
|
|
177
178
|
/**
|
|
178
|
-
* How long after an applied disposal -- `complete`,
|
|
179
|
-
* `
|
|
179
|
+
* How long after an applied disposal -- `complete`, or a lead's `take-over` -- the provider's
|
|
180
|
+
* `task-ended` is owed, in milliseconds. A warm transfer's `complete` is not one: the agent's
|
|
181
|
+
* wrap runs after it as after any call. `applied` says the provider
|
|
180
182
|
* has disposed of the task; the ending follows within this, or the host resyncs and shows the
|
|
181
183
|
* task as unsettled. Stated per provider, since platforms settle at different speeds.
|
|
182
184
|
*/
|
|
@@ -548,8 +550,8 @@ export interface CustomCapability {
|
|
|
548
550
|
control: "button" | "toggle" | "menu-item";
|
|
549
551
|
label: string;
|
|
550
552
|
placement: "primary" | "secondary" | "overflow";
|
|
551
|
-
/** Where the control's work renders: inline in the workspace, or as a page of its own.
|
|
552
|
-
render
|
|
553
|
+
/** Where the control's work renders: inline in the workspace, or as a page of its own. Stated, never defaulted. */
|
|
554
|
+
render: "inline" | "page";
|
|
553
555
|
};
|
|
554
556
|
/** What the agent supplies before the action runs; the values travel on the custom command. */
|
|
555
557
|
prompt?: {
|
|
@@ -729,11 +731,12 @@ export declare const ON_CALL_ROLES: readonly ["party", "agent", "consulted", "co
|
|
|
729
731
|
* stated so a snapshot says who is present without anyone having seen the outcome. Nobody ringing is
|
|
730
732
|
* held. `label` names a destination -- a person, a queue -- not a phrase. At most one `party` and
|
|
731
733
|
* one `consulted`: the consult commands name neither because there is exactly one. The `party`
|
|
732
|
-
* carries a `stage`
|
|
733
|
-
*
|
|
734
|
-
* `ringing` from the moment the dial is placed
|
|
735
|
-
*
|
|
736
|
-
*
|
|
734
|
+
* carries a `stage` while being dialled -- by any host dial, a dialpad call, a preview Call or a
|
|
735
|
+
* connect-back, carrying its `dialId`, or by a callback the platform places itself, carrying none --
|
|
736
|
+
* `ringing` from the moment the dial is placed, `joined` on the publication that follows its
|
|
737
|
+
* answered outcome, and no stage from the next publication on, so a host can show a call being
|
|
738
|
+
* placed rather than assert one that is still ringing, and a stale copy carrying `joined` cannot
|
|
739
|
+
* pass as a connect-back. A party with no stage is on the call.
|
|
737
740
|
*/
|
|
738
741
|
export type OnCall = {
|
|
739
742
|
since: IsoTimestamp;
|
|
@@ -760,12 +763,16 @@ export type OnCall = {
|
|
|
760
763
|
* The agent's request for a lead, from asking until the lead leaves or the request ends.
|
|
761
764
|
* `requested` while nobody has joined; `joined`, with `leadId`, once somebody has.
|
|
762
765
|
*/
|
|
763
|
-
export
|
|
764
|
-
stage: "requested" | "joined";
|
|
765
|
-
leadId?: UserId;
|
|
766
|
+
export type TaskLeadAssist = {
|
|
766
767
|
note?: string;
|
|
767
768
|
since: IsoTimestamp;
|
|
768
|
-
}
|
|
769
|
+
} & ({
|
|
770
|
+
stage: "requested";
|
|
771
|
+
leadId?: never;
|
|
772
|
+
} | {
|
|
773
|
+
stage: "joined";
|
|
774
|
+
leadId: UserId;
|
|
775
|
+
});
|
|
769
776
|
/**
|
|
770
777
|
* On the lead's own task for a call they joined: which member asked, with their note. Its
|
|
771
778
|
* presence is what makes `lead-assist` `take-over` and `leave` issuable.
|
|
@@ -864,7 +871,9 @@ export type Task<C extends Channel = Channel> = {
|
|
|
864
871
|
* How this offer is accepted, stated on the pending task rather than the offer so a reconnect
|
|
865
872
|
* snapshot says it too: an offer the host never received is not accepted on the person's behalf
|
|
866
873
|
* for want of a word. Required while `pending` when Omni said it may auto-accept
|
|
867
|
-
* (`autoAcceptTasks: true`)
|
|
874
|
+
* (`autoAcceptTasks: true`) or the work is the agent's own -- a dial, a connect-back, a join, a
|
|
875
|
+
* monitor, which then say `automatic` under either provisioning -- forbidden on routed work when
|
|
876
|
+
* Omni said not, and absent past `pending`.
|
|
868
877
|
*/
|
|
869
878
|
acceptance?: AcceptanceMode;
|
|
870
879
|
/**
|
|
@@ -892,19 +901,26 @@ export type Task<C extends Channel = Channel> = {
|
|
|
892
901
|
media?: never;
|
|
893
902
|
});
|
|
894
903
|
/**
|
|
895
|
-
* What the provider wants of Omni's acceptance policy for one offer.
|
|
896
|
-
* willing to accept for the agent (`autoAcceptTasks: true`): `consent` is therefore
|
|
897
|
-
* provider's requirement of an explicit acceptance, never Omni's own policy, which
|
|
898
|
-
* absent field.
|
|
904
|
+
* What the provider wants of Omni's acceptance policy for one offer. On routed work, present only
|
|
905
|
+
* where Omni was willing to accept for the agent (`autoAcceptTasks: true`): `consent` is therefore
|
|
906
|
+
* always the provider's requirement of an explicit acceptance, never Omni's own policy, which
|
|
907
|
+
* travels as an absent field. Work the agent originated says `automatic` whatever the provisioning.
|
|
899
908
|
*/
|
|
900
909
|
export type AcceptanceMode = "no-preference" | "consent" | "automatic";
|
|
901
910
|
export type TaskOutcome = {
|
|
902
911
|
type: "completed";
|
|
903
912
|
by: "agent" | "provider";
|
|
904
|
-
}
|
|
913
|
+
}
|
|
914
|
+
/** A cold transfer to a destination the agent chose: a directory item's id. */
|
|
915
|
+
| {
|
|
905
916
|
type: "transferred";
|
|
906
917
|
destinationId?: string;
|
|
907
918
|
}
|
|
919
|
+
/** A lead took the call over: the agent's task ends and the lead's continues. Named by the lead, since a lead is not a directory item. */
|
|
920
|
+
| {
|
|
921
|
+
type: "taken-over";
|
|
922
|
+
leadId: UserId;
|
|
923
|
+
}
|
|
908
924
|
/** Who called the work off: the agent declining, the provider withdrawing or re-routing, the party abandoning. */
|
|
909
925
|
| {
|
|
910
926
|
type: "cancelled";
|
|
@@ -1155,15 +1171,13 @@ export interface BreakState {
|
|
|
1155
1171
|
retryAfterMs?: number;
|
|
1156
1172
|
/** Not-ready codes this provider offers. Omitted when it defines none. */
|
|
1157
1173
|
reasons?: BreakReason[];
|
|
1158
|
-
/** Which reason the current break is on. Omitted when there is no break. */
|
|
1174
|
+
/** Which reason the current break is on, a published `BreakReason.id`. Omitted when there is no break; required on a break in effect or starting after the task where the provider publishes `reasons`, an imposed one included. */
|
|
1159
1175
|
activeReasonId?: string;
|
|
1160
1176
|
imposed?: ImposedBreak;
|
|
1161
1177
|
}
|
|
1178
|
+
/** Capacity is a statement, not a request: it is taken, never refused. A provider that cannot carry the count allocates within what it can and says so on a `diagnostic`. */
|
|
1162
1179
|
export type CapacityResult = {
|
|
1163
1180
|
status: "applied";
|
|
1164
|
-
} | {
|
|
1165
|
-
status: "failed";
|
|
1166
|
-
failure: ProtocolFailure;
|
|
1167
1181
|
};
|
|
1168
1182
|
/** Succeeding is not the outcome: `requested` says the provider holds it, not that it was granted. */
|
|
1169
1183
|
export type BreakRequestResult = {
|
|
@@ -1191,13 +1205,15 @@ export type BreakEndResult = {
|
|
|
1191
1205
|
status: "failed";
|
|
1192
1206
|
failure: ProtocolFailure;
|
|
1193
1207
|
};
|
|
1194
|
-
|
|
1208
|
+
/** `reserved`: signed in here, and the host holds this agent's capacity for another provider (`count: 0`, host-stopped); not receiving this provider's work. */
|
|
1209
|
+
export type TeamMemberAvailability = "ready" | "on-task" | "on-break" | "reserved" | "signed-out";
|
|
1195
1210
|
export interface TeamMember {
|
|
1196
1211
|
id: UserId;
|
|
1197
1212
|
availability: TeamMemberAvailability;
|
|
1198
1213
|
/** Omitted rather than invented: Omni renders it as a duration. */
|
|
1199
1214
|
since?: IsoTimestamp;
|
|
1200
|
-
break
|
|
1215
|
+
/** A request in flight or a grant not yet in effect. `not-requested` is absence, and `in-effect` is `availability: "on-break"`. */
|
|
1216
|
+
break?: Extract<BreakApproval, "awaiting-decision" | "granted" | "starting-after-task">;
|
|
1201
1217
|
}
|
|
1202
1218
|
/** A member asking this lead to join their call. */
|
|
1203
1219
|
export interface LeadRequest {
|
|
@@ -1273,9 +1289,12 @@ export type TeamBreakCommand = {
|
|
|
1273
1289
|
} | {
|
|
1274
1290
|
type: "policy";
|
|
1275
1291
|
policy: "ask" | "auto-approve" | "suspended";
|
|
1276
|
-
}
|
|
1292
|
+
}
|
|
1293
|
+
/** `reasonId` names a published `BreakReason.id`, required whenever the provider publishes reasons: the member's imposed break carries it as `activeReasonId`. */
|
|
1294
|
+
| {
|
|
1277
1295
|
type: "place";
|
|
1278
1296
|
memberId: UserId;
|
|
1297
|
+
reasonId?: string;
|
|
1279
1298
|
reason?: string;
|
|
1280
1299
|
} | {
|
|
1281
1300
|
type: "release";
|
|
@@ -1291,7 +1310,7 @@ export interface TeamBreakCommandRequest {
|
|
|
1291
1310
|
command: TeamBreakCommand;
|
|
1292
1311
|
}
|
|
1293
1312
|
/**
|
|
1294
|
-
* The task's real-time audio as the provider holds it: `
|
|
1313
|
+
* The task's real-time audio as the provider holds it: `started` while audio should be attached,
|
|
1295
1314
|
* `ended` once primary handling's audio ended, and the field omitted while none should be. The
|
|
1296
1315
|
* provider's word -- a desk attaches and renders audio from it, never from its own senses.
|
|
1297
1316
|
*/
|
|
@@ -1323,7 +1342,7 @@ export type OpenMediaResult = {
|
|
|
1323
1342
|
*/
|
|
1324
1343
|
export type PreferenceId = "hold" | `skill:${string}`;
|
|
1325
1344
|
/**
|
|
1326
|
-
* Who stated a value as it stands: a level -- `person` among them -- or `
|
|
1345
|
+
* Who stated a value as it stands: a level -- `person` among them -- or `provider`, the
|
|
1327
1346
|
* protocol's own word for "no level has said anything and the provider's default applies".
|
|
1328
1347
|
* Nothing is hidden for want of a row.
|
|
1329
1348
|
*/
|
|
@@ -1411,7 +1430,7 @@ export interface Snapshot<C extends Channel = Channel> {
|
|
|
1411
1430
|
* tasks against it and needs no new signal when one ends. The agent is one person on several
|
|
1412
1431
|
* providers, and the host divides their capacity among them: `0` is host-stopped -- the agent's
|
|
1413
1432
|
* capacity is elsewhere for now, this provider allocates nothing and shows the member as
|
|
1414
|
-
* `
|
|
1433
|
+
* `reserved` -- and is not a break, which is the agent not working at all. The host restates a
|
|
1415
1434
|
* count of one or more when this provider has the agent's capacity again.
|
|
1416
1435
|
*/
|
|
1417
1436
|
export interface AgentCapacity {
|
package/dist/index.js
CHANGED
|
@@ -180,11 +180,16 @@ export function handlingStepExpectsAPerson(step) {
|
|
|
180
180
|
*/
|
|
181
181
|
export function sameCapabilities(a, b) {
|
|
182
182
|
const modes = (team) => team?.monitorControl === undefined ? undefined : [...team.monitorControl].sort().join(",");
|
|
183
|
+
// Every field, so a capability added later cannot be missed here: `satisfies` pins the field
|
|
184
|
+
// lists to the types, and a new key is a compile error until it is compared.
|
|
185
|
+
const teamKeys = { breakControl: true, leadAssistControl: true, monitorControl: true, policyControl: true };
|
|
186
|
+
void { breaks: true, preferences: true, team: true };
|
|
187
|
+
const preferences = (list) => list === undefined ? undefined : list.map(p => [p.id, p.label, p.enabled, p.setBy, p.lockedBy ?? "", p.reason ?? ""].join("\u0000")).sort().join("\u0001");
|
|
183
188
|
return a.breaks === b.breaks &&
|
|
184
189
|
(a.team === undefined) === (b.team === undefined) &&
|
|
185
|
-
a.team?.
|
|
186
|
-
a.team
|
|
187
|
-
|
|
190
|
+
Object.keys(teamKeys).every(key => key === "monitorControl" || a.team?.[key] === b.team?.[key]) &&
|
|
191
|
+
modes(a.team) === modes(b.team) &&
|
|
192
|
+
preferences(a.preferences) === preferences(b.preferences);
|
|
188
193
|
}
|
|
189
194
|
/**
|
|
190
195
|
* The storage-profile key a reusing browser shares, or `undefined` where it shares nothing.
|
package/dist/testing.d.ts
CHANGED
|
@@ -163,8 +163,12 @@ export declare class TaskStream {
|
|
|
163
163
|
openCount(): number;
|
|
164
164
|
/** Whether an offered task is a dial arriving -- the host's own call, a connect-back -- which counts against no capacity: an entry on its call carrying a dial. */
|
|
165
165
|
static carriesDial(task: unknown): boolean;
|
|
166
|
-
/**
|
|
167
|
-
|
|
166
|
+
/**
|
|
167
|
+
* Whether the update shows the party being dialled again -- a connect-back, a platform's callback --
|
|
168
|
+
* the one thing that brings a completing task back: the party ringing, or joined by a dial whose
|
|
169
|
+
* answered outcome this stream saw. A stale copy carrying `joined` from a dial long over is not that.
|
|
170
|
+
*/
|
|
171
|
+
private partyDialled;
|
|
168
172
|
/** The entries of a task's record, each by step and instant, or undefined where the task carries no record. */
|
|
169
173
|
private static record;
|
|
170
174
|
/** What a restated record lost of the one read before it: nothing, or the entries by step and instant. */
|