@xema/omni-protocol 0.1.66 → 0.1.68
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 +36 -3
- package/dist/index.js +2 -2
- package/dist/testing.d.ts +20 -1
- package/dist/testing.js +233 -22
- package/dist/validation.d.ts +16 -2
- package/dist/validation.js +149 -22
- package/guide.md +103 -36
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -26,6 +26,14 @@ export type IsoTimestamp = string;
|
|
|
26
26
|
export type UserId = string;
|
|
27
27
|
/** A non-empty, opaque task identifier unique within one provider. Scope it with `taskKey()`. */
|
|
28
28
|
export type TaskId = string;
|
|
29
|
+
/**
|
|
30
|
+
* One life of a task on the wire. A platform retires a task id minutes after closing it and a
|
|
31
|
+
* requeue takes seconds, so the same `TaskId` comes back for another customer while a late dial
|
|
32
|
+
* outcome or a late handling report for the first is still in flight. The allocation id is minted
|
|
33
|
+
* once per offer and never reused for the life of the login, and every event, command and report
|
|
34
|
+
* that names a task names its allocation too, so nothing lands on the next life of the id.
|
|
35
|
+
*/
|
|
36
|
+
export type AllocationId = string;
|
|
29
37
|
/**
|
|
30
38
|
* The host's identity for one dial, minted before the command leaves and unique across the
|
|
31
39
|
* installation. A dial is the one command whose outcome arrives later and apart, so the provider
|
|
@@ -442,8 +450,8 @@ export type ConnectContext = {
|
|
|
442
450
|
protocolVersion: number;
|
|
443
451
|
/** The session that authenticated this connection. */
|
|
444
452
|
loginId: string;
|
|
445
|
-
/** Omni-side policy: whether the agent's tasks are accepted without asking them. */
|
|
446
|
-
autoAcceptTasks
|
|
453
|
+
/** Omni-side policy: whether the agent's tasks are accepted without asking them. Stated, never assumed: a host says which. */
|
|
454
|
+
autoAcceptTasks: boolean;
|
|
447
455
|
/**
|
|
448
456
|
* The zone the agent's day is reckoned in, the same value passed as
|
|
449
457
|
* `AuthenticationContext.timeZone`. The provider stores it on the agent and carries it on the
|
|
@@ -713,13 +721,24 @@ export declare const ON_CALL_ROLES: readonly ["party", "agent", "consulted", "co
|
|
|
713
721
|
* either way, and the `stage` it has reached: `ringing` until the dial is answered, `joined` after,
|
|
714
722
|
* stated so a snapshot says who is present without anyone having seen the outcome. Nobody ringing is
|
|
715
723
|
* held. `label` names a destination -- a person, a queue -- not a phrase. At most one `party` and
|
|
716
|
-
* one `consulted`: the consult commands name neither because there is exactly one.
|
|
724
|
+
* one `consulted`: the consult commands name neither because there is exactly one. The `party`
|
|
725
|
+
* carries a `stage` only while being dialled again on the same task -- a connect-back the host
|
|
726
|
+
* placed, carrying its `dialId`, or a callback the platform places itself, carrying none --
|
|
727
|
+
* `ringing` from the moment the dial is placed and `joined` on its answered outcome, so a host can
|
|
728
|
+
* show a call being placed rather than assert one that is still ringing. A party with no stage is
|
|
729
|
+
* on the call.
|
|
717
730
|
*/
|
|
718
731
|
export type OnCall = {
|
|
719
732
|
since: IsoTimestamp;
|
|
720
733
|
held?: true;
|
|
721
734
|
} & ({
|
|
722
735
|
role: "party";
|
|
736
|
+
dialId?: never;
|
|
737
|
+
stage?: never;
|
|
738
|
+
} | {
|
|
739
|
+
role: "party";
|
|
740
|
+
stage: OnCallStage;
|
|
741
|
+
dialId?: DialId;
|
|
723
742
|
} | {
|
|
724
743
|
role: "agent";
|
|
725
744
|
userId: UserId;
|
|
@@ -759,6 +778,7 @@ export interface TaskAssisting {
|
|
|
759
778
|
export interface TaskMonitoring {
|
|
760
779
|
memberId: UserId;
|
|
761
780
|
taskId: TaskId;
|
|
781
|
+
allocationId: AllocationId;
|
|
762
782
|
mode: MonitorMode;
|
|
763
783
|
since: IsoTimestamp;
|
|
764
784
|
}
|
|
@@ -815,6 +835,8 @@ export type CapabilitySource = "queue" | "ungoverned" | "undetermined";
|
|
|
815
835
|
export declare const CAPABILITY_SOURCES: readonly ["queue", "ungoverned", "undetermined"];
|
|
816
836
|
export type Task<C extends Channel = Channel> = {
|
|
817
837
|
id: TaskId;
|
|
838
|
+
/** This life of the task: minted once per offer, never reused for the life of the login. See `AllocationId`. */
|
|
839
|
+
allocationId: AllocationId;
|
|
818
840
|
title: string;
|
|
819
841
|
channel: C;
|
|
820
842
|
/** The provider's own name for a category of work. Finer-grained than a channel. */
|
|
@@ -1024,6 +1046,7 @@ export interface CustomTaskCommand {
|
|
|
1024
1046
|
export type TaskCommand<C extends Channel = Channel> = (C extends "voice" ? VoiceTaskCommand : C extends "chat" ? ChatTaskCommand : EmailTaskCommand) | CustomTaskCommand;
|
|
1025
1047
|
export interface TaskCommandRequest<C extends Channel = Channel> {
|
|
1026
1048
|
taskId: TaskId;
|
|
1049
|
+
allocationId: AllocationId;
|
|
1027
1050
|
command: TaskCommand<C>;
|
|
1028
1051
|
}
|
|
1029
1052
|
/**
|
|
@@ -1171,6 +1194,7 @@ export interface LeadRequest {
|
|
|
1171
1194
|
id: string;
|
|
1172
1195
|
memberId: UserId;
|
|
1173
1196
|
taskId: TaskId;
|
|
1197
|
+
allocationId: AllocationId;
|
|
1174
1198
|
note?: string;
|
|
1175
1199
|
since: IsoTimestamp;
|
|
1176
1200
|
}
|
|
@@ -1269,6 +1293,7 @@ export interface VoiceMediaSession {
|
|
|
1269
1293
|
}
|
|
1270
1294
|
export interface OpenMediaRequest {
|
|
1271
1295
|
taskId: TaskId;
|
|
1296
|
+
allocationId: AllocationId;
|
|
1272
1297
|
/** The agent's microphone as Omni captured it, `HostReport.audio.input.localAudio`; absent while that input is `unavailable`. */
|
|
1273
1298
|
localAudio?: MediaStream;
|
|
1274
1299
|
}
|
|
@@ -1328,6 +1353,7 @@ export type SetPreferenceRequest = {
|
|
|
1328
1353
|
*/
|
|
1329
1354
|
export type HandlingReport = {
|
|
1330
1355
|
taskId: TaskId;
|
|
1356
|
+
allocationId: AllocationId;
|
|
1331
1357
|
at: IsoTimestamp;
|
|
1332
1358
|
} & (
|
|
1333
1359
|
/** A muted leg says whose the silence was: the host's own Mute, or the station's slider or system. */
|
|
@@ -1416,12 +1442,15 @@ export type ProviderEvent<C extends Channel = Channel> = {
|
|
|
1416
1442
|
} | {
|
|
1417
1443
|
type: "task-media-started";
|
|
1418
1444
|
taskId: TaskId;
|
|
1445
|
+
allocationId: AllocationId;
|
|
1419
1446
|
} | {
|
|
1420
1447
|
type: "task-media-ended";
|
|
1421
1448
|
taskId: TaskId;
|
|
1449
|
+
allocationId: AllocationId;
|
|
1422
1450
|
} | {
|
|
1423
1451
|
type: "task-ended";
|
|
1424
1452
|
taskId: TaskId;
|
|
1453
|
+
allocationId: AllocationId;
|
|
1425
1454
|
outcome: TaskOutcome;
|
|
1426
1455
|
}
|
|
1427
1456
|
/**
|
|
@@ -1436,6 +1465,7 @@ export type ProviderEvent<C extends Channel = Channel> = {
|
|
|
1436
1465
|
dialId: DialId;
|
|
1437
1466
|
outcome: DialOutcome;
|
|
1438
1467
|
taskId?: TaskId;
|
|
1468
|
+
allocationId?: AllocationId;
|
|
1439
1469
|
destinationId?: string;
|
|
1440
1470
|
reason?: string;
|
|
1441
1471
|
} | {
|
|
@@ -1452,6 +1482,7 @@ export type ProviderEvent<C extends Channel = Channel> = {
|
|
|
1452
1482
|
expected: string;
|
|
1453
1483
|
observed: string;
|
|
1454
1484
|
taskId?: TaskId;
|
|
1485
|
+
allocationId?: AllocationId;
|
|
1455
1486
|
} | {
|
|
1456
1487
|
type: "team-updated";
|
|
1457
1488
|
team: TeamRoster;
|
|
@@ -1592,6 +1623,8 @@ export interface BrowserSessionKeyInput {
|
|
|
1592
1623
|
/** `Manifest.id`, never `displayName`: only the id is unique across an installation and stable. */
|
|
1593
1624
|
providerId: string;
|
|
1594
1625
|
taskId: TaskId;
|
|
1626
|
+
/** The task's allocation: a task-scoped session lives one life of the task, never the next customer's under a reused id. */
|
|
1627
|
+
allocationId: AllocationId;
|
|
1595
1628
|
/** `Task.taskType`. */
|
|
1596
1629
|
taskType: string;
|
|
1597
1630
|
browser: TaskBrowser;
|
package/dist/index.js
CHANGED
|
@@ -196,7 +196,7 @@ export function sameCapabilities(a, b) {
|
|
|
196
196
|
* cannot collide with a provider called `a` and a tab called `b`.
|
|
197
197
|
*/
|
|
198
198
|
export function browserSessionKey(input) {
|
|
199
|
-
const { providerId,
|
|
199
|
+
const { providerId, allocationId, taskType, browser } = input;
|
|
200
200
|
if (browser.sharedSession !== true)
|
|
201
201
|
return undefined;
|
|
202
202
|
// `encodeURIComponent` leaves `.` untouched, and `.` is the separator: a raw join would let
|
|
@@ -204,7 +204,7 @@ export function browserSessionKey(input) {
|
|
|
204
204
|
const part = (value) => encodeURIComponent(value).replaceAll(".", "%2E");
|
|
205
205
|
switch (browser.isolationScheme) {
|
|
206
206
|
case BROWSER_ISOLATION_SCHEMES.PROVIDER_NAME__TASK_ID__TAB_NAME:
|
|
207
|
-
return `${part(providerId)}.${part(
|
|
207
|
+
return `${part(providerId)}.${part(allocationId)}.${part(browser.name)}`;
|
|
208
208
|
case BROWSER_ISOLATION_SCHEMES.TAB_NAME:
|
|
209
209
|
return part(browser.name);
|
|
210
210
|
case BROWSER_ISOLATION_SCHEMES.PROVIDER_NAME__TASK_TYPE_NAME__TAB_NAME:
|
package/dist/testing.d.ts
CHANGED
|
@@ -29,6 +29,12 @@ export interface AdapterContractResult {
|
|
|
29
29
|
notExercised: readonly ContractSubject[];
|
|
30
30
|
/** Every violation observed. Non-empty only when `collectOnly` suppressed the throw. */
|
|
31
31
|
violations: readonly ProtocolViolation[];
|
|
32
|
+
/**
|
|
33
|
+
* Every rule the run evaluated, pass or fail: the validators' as each was applied, the stream's
|
|
34
|
+
* and the drive's as each case was considered. A rule absent here was never looked at, which a
|
|
35
|
+
* clean `violations` says nothing about; a test that needs a rule to have run asserts it here.
|
|
36
|
+
*/
|
|
37
|
+
rulesEvaluated: ReadonlySet<string>;
|
|
32
38
|
}
|
|
33
39
|
export interface ExerciseAdapterOptions {
|
|
34
40
|
/** Return violations in the result instead of throwing. Defaults to `false`. */
|
|
@@ -127,13 +133,26 @@ export declare function assertReconnectWithMissedAssignments<C extends Channel>(
|
|
|
127
133
|
* both.
|
|
128
134
|
*/
|
|
129
135
|
export declare function assertDeniedAndRetriedBreak(approvals: readonly BreakApproval[]): void;
|
|
130
|
-
/** What a stream has said about the tasks it carries, and the rules across events. */
|
|
131
136
|
export declare class TaskStream {
|
|
132
137
|
private readonly tasks;
|
|
138
|
+
private readonly allocations;
|
|
139
|
+
private readonly endedAllocations;
|
|
133
140
|
private readonly dials;
|
|
134
141
|
/** The host placed a dial: its outcome, whenever it arrives, is one the stream expects. */
|
|
135
142
|
dialled(dialId: string): void;
|
|
136
143
|
private noteDials;
|
|
144
|
+
/**
|
|
145
|
+
* Whether an event about a task names the life that is open under its id. One naming a life that
|
|
146
|
+
* has ended is the late event the allocation exists to catch; one naming a life nobody has seen is
|
|
147
|
+
* a different fault. Either way it is not applied to the open task.
|
|
148
|
+
*/
|
|
149
|
+
private namesTheOpenLife;
|
|
150
|
+
/** How many tasks the stream currently holds open. */
|
|
151
|
+
openCount(): number;
|
|
152
|
+
/** 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. */
|
|
153
|
+
static carriesDial(task: unknown): boolean;
|
|
154
|
+
/** Whether the update shows the party being dialled again -- a connect-back, a platform's callback -- the one thing that brings a completing task back. */
|
|
155
|
+
private static partyDialled;
|
|
137
156
|
/** The entries of a task's record, each by step and instant, or undefined where the task carries no record. */
|
|
138
157
|
private static record;
|
|
139
158
|
/** What a restated record lost of the one read before it: nothing, or the entries by step and instant. */
|