@xema/omni-protocol 0.1.98 → 0.1.100
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 +155 -133
- package/dist/index.js +7 -17
- package/dist/testing.d.ts +12 -12
- package/dist/testing.js +147 -133
- package/dist/validation.d.ts +20 -6
- package/dist/validation.js +238 -265
- package/guide.md +406 -449
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -106,7 +106,7 @@ export type DialOutcome = "answered" | "busy" | "no-answer" | "unreachable" | "r
|
|
|
106
106
|
export declare const DIAL_OUTCOMES: readonly ["answered", "busy", "no-answer", "unreachable", "rejected", "cancelled", "unexplained"];
|
|
107
107
|
/**
|
|
108
108
|
* How the agent hears the call: on a `softphone`, where the host owns the audio and opens it
|
|
109
|
-
* through `
|
|
109
|
+
* through `openAudio`, or on a `deskPhone`, a handset the platform rings, where the host opens
|
|
110
110
|
* nothing and only shows the call. A voice manifest lists the phones its platform supports, and
|
|
111
111
|
* the host picks one for the login at authentication.
|
|
112
112
|
*/
|
|
@@ -232,25 +232,6 @@ export interface AuthenticationFailure {
|
|
|
232
232
|
/** Names a declared credentials field when the failure belongs to one. */
|
|
233
233
|
field?: string;
|
|
234
234
|
}
|
|
235
|
-
/** What a lead may do with their team. Declared by presence. */
|
|
236
|
-
export interface TeamCapabilities {
|
|
237
|
-
/**
|
|
238
|
-
* This lead may act on their team's breaks through `executeTeamBreak` -- force-break, end-forced-break, decide-break-request,
|
|
239
|
-
* set-break-policy -- as far as the provider supports; a command it lacks answers
|
|
240
|
-
* `omni.capability-not-enabled`. Requires `executeTeamBreak`.
|
|
241
|
-
*/
|
|
242
|
-
breakControl?: true;
|
|
243
|
-
/** This lead may join a member's call on request. Requires `executeTeamLeadAssist`. */
|
|
244
|
-
leadAssistControl?: true;
|
|
245
|
-
/**
|
|
246
|
-
* This lead may listen to a member's call unasked, in these modes and no others. Coach and
|
|
247
|
-
* join-call begin with silent listening, so the list always includes `listen`. Nothing of it reaches the
|
|
248
|
-
* member's task. Requires `executeTeamListen`.
|
|
249
|
-
*/
|
|
250
|
-
listeningControl?: ListeningMode[];
|
|
251
|
-
/** This lead sets the team's policy per capability -- on, off, or the agent's -- within what the queue allows. Requires `executeTeamPolicy`. */
|
|
252
|
-
policyControl?: true;
|
|
253
|
-
}
|
|
254
235
|
/**
|
|
255
236
|
* How a lead listening to a member's call is heard: `listen` hears both sides in silence,
|
|
256
237
|
* `coach` is heard by the agent alone, `join-call` by everyone on the call.
|
|
@@ -267,8 +248,14 @@ export interface UserCapabilities {
|
|
|
267
248
|
breaks?: true;
|
|
268
249
|
/** The choices the team left to this person, with where each stands. Omitted when there are none. Requires `setPreference`. */
|
|
269
250
|
preferences?: AgentPreference[];
|
|
270
|
-
/**
|
|
271
|
-
|
|
251
|
+
/**
|
|
252
|
+
* This login leads a team. The flag alone turns on the team feature in the agent application:
|
|
253
|
+
* the lead sees their members with their tasks and stats, and acts on them through `executeTeam`
|
|
254
|
+
* with the authority the flag carries. No per-action permission rides beside it. While the lead
|
|
255
|
+
* has the feature on, a `TeamMembers` object is published to them on every snapshot, `members: []`
|
|
256
|
+
* included; to nobody else, ever.
|
|
257
|
+
*/
|
|
258
|
+
lead?: true;
|
|
272
259
|
}
|
|
273
260
|
/**
|
|
274
261
|
* Only `authenticated` and `refreshing` know who the agent is and what they may do, and only
|
|
@@ -446,7 +433,7 @@ export interface RecordingActions {
|
|
|
446
433
|
export interface TaskRecordingPolicy {
|
|
447
434
|
provider?: RecordingActions;
|
|
448
435
|
host?: RecordingActions & {
|
|
449
|
-
|
|
436
|
+
storageId: string;
|
|
450
437
|
};
|
|
451
438
|
}
|
|
452
439
|
/** Current evidence only. Expiry/disconnect means unknown, never a historical stop. */
|
|
@@ -491,7 +478,7 @@ export interface HostRecording {
|
|
|
491
478
|
announcesToCaller?: true;
|
|
492
479
|
actions: RecordingAction[];
|
|
493
480
|
/** Explicitly provisioned destinations. A task chooses one; no upload/storage fallback. */
|
|
494
|
-
|
|
481
|
+
storageIds: string[];
|
|
495
482
|
execute(request: HostRecordingRequest): Promise<RecordingCommandResult>;
|
|
496
483
|
}
|
|
497
484
|
/** Recording applies or fails; it never dials. Rejection without result means unknown. */
|
|
@@ -668,6 +655,7 @@ export type TaskCapabilities<C extends Channel = Channel> = C extends "voice" ?
|
|
|
668
655
|
hold?: Lockable<true>;
|
|
669
656
|
/** The provider ends the caller connection and agent-added channels owned by this interaction, including inherited channels after transfer/takeover. Interaction completion remains separate. */
|
|
670
657
|
endCall?: Lockable<true>;
|
|
658
|
+
terminateCall?: Lockable<true>;
|
|
671
659
|
/** Connect back to the party while `completing`, whoever placed the call; the task returns to `in-progress`. */
|
|
672
660
|
connectBack?: Lockable<true>;
|
|
673
661
|
coldTransfer?: Lockable<DestinationDirectory>;
|
|
@@ -741,9 +729,9 @@ export type TaskPhase =
|
|
|
741
729
|
/** What the system does when a preview's deadline passes with no Call pressed. */
|
|
742
730
|
export type PreviewDeadline =
|
|
743
731
|
/** Provider initiates dialing when preparation ends; never a host timer command. */
|
|
744
|
-
"
|
|
732
|
+
"provider-dials"
|
|
745
733
|
/** Host initiates the ordinary call command when preparation ends. */
|
|
746
|
-
| "host-
|
|
734
|
+
| "host-dials"
|
|
747
735
|
/** Preparation target has elapsed; remain in preview until the agent presses Call. */
|
|
748
736
|
| "waits";
|
|
749
737
|
/** Who ends the task: the agent issuing `complete`, or the provider deciding it is over. */
|
|
@@ -875,27 +863,13 @@ export type TaskLeadAssist = {
|
|
|
875
863
|
leadId: UserId;
|
|
876
864
|
});
|
|
877
865
|
/**
|
|
878
|
-
* On
|
|
879
|
-
*
|
|
866
|
+
* On a task that reached this agent by a lead's take-over: which member the call was taken from,
|
|
867
|
+
* and when. The provider moves the call from one agent to another, and the lead is the new agent,
|
|
868
|
+
* so the task is an ordinary assignment with the call's history -- offered whatever break the lead
|
|
869
|
+
* is on, counted against no capacity, provided their voice channel is free.
|
|
880
870
|
*/
|
|
881
|
-
export interface
|
|
871
|
+
export interface TaskTakenOver {
|
|
882
872
|
memberId: UserId;
|
|
883
|
-
/** The member's assignment the lead joined: the lead's task has an assignment of its own. */
|
|
884
|
-
assignmentId: AssignmentId;
|
|
885
|
-
note?: string;
|
|
886
|
-
since: IsoTimestamp;
|
|
887
|
-
}
|
|
888
|
-
/**
|
|
889
|
-
* On the lead's own task while they listen to a member's call: whose call, which call, and how the
|
|
890
|
-
* lead is heard, restated on every change of mode. Nothing of it reaches the member's task. There
|
|
891
|
-
* is no take-over here; a lead who wants the call uses lead assist. It is the lead's only task:
|
|
892
|
-
* a lead listens while holding no work of their own, idle or on a break of a kind in
|
|
893
|
-
* `LISTENING_BREAK_KINDS`.
|
|
894
|
-
*/
|
|
895
|
-
export interface TaskListening {
|
|
896
|
-
memberId: UserId;
|
|
897
|
-
assignmentId: AssignmentId;
|
|
898
|
-
mode: ListeningMode;
|
|
899
873
|
since: IsoTimestamp;
|
|
900
874
|
}
|
|
901
875
|
/**
|
|
@@ -943,12 +917,12 @@ export interface Locked {
|
|
|
943
917
|
*/
|
|
944
918
|
export type Lockable<T> = T | Locked;
|
|
945
919
|
/**
|
|
946
|
-
* Who chose a task's capabilities. `
|
|
920
|
+
* Who chose a task's capabilities. `not-yet-read` is a fault the provider states on the task rather
|
|
947
921
|
* than hides: the platform's terms could not be read, and the set published is what the provider
|
|
948
922
|
* will honour, not what the platform permits.
|
|
949
923
|
*/
|
|
950
|
-
export type CapabilitySource = "queue" | "
|
|
951
|
-
export declare const CAPABILITY_SOURCES: readonly ["queue", "
|
|
924
|
+
export type CapabilitySource = "queue" | "nobody" | "not-yet-read";
|
|
925
|
+
export declare const CAPABILITY_SOURCES: readonly ["queue", "nobody", "not-yet-read"];
|
|
952
926
|
export type Task<C extends Channel = Channel> = {
|
|
953
927
|
/** The assignment this task is the record of, and its one identity. See `AssignmentId`. */
|
|
954
928
|
assignmentId: AssignmentId;
|
|
@@ -959,8 +933,8 @@ export type Task<C extends Channel = Channel> = {
|
|
|
959
933
|
capabilities: TaskCapabilities<C>;
|
|
960
934
|
/**
|
|
961
935
|
* Where the capabilities came from before the provider put them on the task, so a host can tell
|
|
962
|
-
* a fact from a fault: `queue` when somebody chose these terms, `
|
|
963
|
-
* the work over, `
|
|
936
|
+
* a fact from a fault: `queue` when somebody chose these terms, `nobody` when nothing handed
|
|
937
|
+
* the work over, `not-yet-read` when a queue was named and its terms could not be read -- the
|
|
964
938
|
* provider then publishes what it will honour and says so here, where the controls are drawn.
|
|
965
939
|
*/
|
|
966
940
|
capabilitySource: CapabilitySource;
|
|
@@ -979,7 +953,7 @@ export type Task<C extends Channel = Channel> = {
|
|
|
979
953
|
acceptance?: AcceptanceMode;
|
|
980
954
|
/**
|
|
981
955
|
* In `preview` only, and together: the preparation target and behavior when it elapses,
|
|
982
|
-
* and who acts then -- `
|
|
956
|
+
* and who acts then -- `provider-dials` makes the provider initiate dialing, `host-dials` makes the
|
|
983
957
|
* host issue Call, and `waits` keeps waiting for the agent without dialing or ending the task.
|
|
984
958
|
* Absent, the agent has as long as they need, without a preparation countdown.
|
|
985
959
|
*/
|
|
@@ -995,16 +969,14 @@ export type Task<C extends Channel = Channel> = {
|
|
|
995
969
|
};
|
|
996
970
|
onCall?: OnCall[];
|
|
997
971
|
leadAssist?: TaskLeadAssist;
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
media?: TaskMediaState;
|
|
972
|
+
takenOver?: TaskTakenOver;
|
|
973
|
+
audio?: TaskAudioState;
|
|
1001
974
|
} : {
|
|
1002
975
|
recording?: never;
|
|
1003
976
|
onCall?: never;
|
|
1004
977
|
leadAssist?: never;
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
media?: never;
|
|
978
|
+
takenOver?: never;
|
|
979
|
+
audio?: never;
|
|
1008
980
|
});
|
|
1009
981
|
/**
|
|
1010
982
|
* What the provider wants of Omni's acceptance policy for one offer. On routed work, present only
|
|
@@ -1022,7 +994,7 @@ export type TaskOutcome = {
|
|
|
1022
994
|
type: "transferred";
|
|
1023
995
|
destinationId?: string;
|
|
1024
996
|
}
|
|
1025
|
-
/** A lead took the call over
|
|
997
|
+
/** A lead took the call over and the agent has wrapped it: the outcome at the agent's own completion. Named by the lead, since a lead is not a directory item. */
|
|
1026
998
|
| {
|
|
1027
999
|
type: "taken-over";
|
|
1028
1000
|
leadId: UserId;
|
|
@@ -1037,16 +1009,12 @@ export type TaskOutcome = {
|
|
|
1037
1009
|
| {
|
|
1038
1010
|
type: "expired";
|
|
1039
1011
|
phase: "pending" | "confirmed" | "preview";
|
|
1040
|
-
}
|
|
1041
|
-
/** This agent left a call that continues without them: a lead who joined and dropped. */
|
|
1042
|
-
| {
|
|
1043
|
-
type: "left";
|
|
1044
1012
|
} | {
|
|
1045
1013
|
type: "failed";
|
|
1046
1014
|
failure: ProtocolFailure;
|
|
1047
1015
|
};
|
|
1048
1016
|
export declare const TASK_COMMAND_NAMES: {
|
|
1049
|
-
readonly voice: readonly ["answer", "decline", "
|
|
1017
|
+
readonly voice: readonly ["answer", "decline", "dial", "hold", "resume", "end-call", "terminate-call", "connect-back", "transfer", "lead-assist", "conference", "recording", "complete"];
|
|
1050
1018
|
readonly chat: readonly ["accept", "decline", "pause", "resume", "complete"];
|
|
1051
1019
|
readonly email: readonly ["accept", "decline", "complete"];
|
|
1052
1020
|
};
|
|
@@ -1062,17 +1030,21 @@ export type VoiceTaskCommand = {
|
|
|
1062
1030
|
}
|
|
1063
1031
|
/** In `preview`: place the call to the party whose record the agent has read. A dial, gated by the phase alone. */
|
|
1064
1032
|
| {
|
|
1065
|
-
type: "
|
|
1033
|
+
type: "dial";
|
|
1066
1034
|
dialId: DialId;
|
|
1067
1035
|
} | {
|
|
1068
1036
|
type: "hold";
|
|
1069
1037
|
} | {
|
|
1070
1038
|
type: "resume";
|
|
1071
1039
|
}
|
|
1072
|
-
/** End the
|
|
1040
|
+
/** End the agent's channel and every channel the agent added; the caller continues on the provider's path. Gated by `endCall`. */
|
|
1073
1041
|
| {
|
|
1074
1042
|
type: "end-call";
|
|
1075
1043
|
}
|
|
1044
|
+
/** End the caller channel. Gated by `terminateCall`. */
|
|
1045
|
+
| {
|
|
1046
|
+
type: "terminate-call";
|
|
1047
|
+
}
|
|
1076
1048
|
/** Issuable only in `completing`, under the `connectBack` capability. Dials the party's own number, so it names none. */
|
|
1077
1049
|
| {
|
|
1078
1050
|
type: "connect-back";
|
|
@@ -1113,16 +1085,6 @@ export type VoiceTaskCommand = {
|
|
|
1113
1085
|
type: "lead-assist";
|
|
1114
1086
|
action: "cancel";
|
|
1115
1087
|
}
|
|
1116
|
-
/** The lead keeps the customer; the agent's task ends `transferred`. Needs `Task.assisting`. */
|
|
1117
|
-
| {
|
|
1118
|
-
type: "lead-assist";
|
|
1119
|
-
action: "take-over-call";
|
|
1120
|
-
}
|
|
1121
|
-
/** The lead drops; the agent continues. The lead's task ends `left`. Needs `Task.assisting`. */
|
|
1122
|
-
| {
|
|
1123
|
-
type: "lead-assist";
|
|
1124
|
-
action: "leave";
|
|
1125
|
-
}
|
|
1126
1088
|
/** Dial the directory item `destinationId` into the call. Gated by `conference`. */
|
|
1127
1089
|
| {
|
|
1128
1090
|
type: "conference";
|
|
@@ -1230,12 +1192,6 @@ export declare const BREAK_KINDS: readonly ["short-break", "meal", "rest", "trai
|
|
|
1230
1192
|
* problem one level up. `other` matches nothing, including another provider's `other`.
|
|
1231
1193
|
*/
|
|
1232
1194
|
export type BreakKind = (typeof BREAK_KINDS)[number];
|
|
1233
|
-
/**
|
|
1234
|
-
* The breaks during which a lead may listen to a member's call: work of another sort, not rest.
|
|
1235
|
-
* A listening task is the one task a break in effect may hold, and only on one of these.
|
|
1236
|
-
*/
|
|
1237
|
-
export declare const LISTENING_BREAK_KINDS: readonly ["coaching", "administrative", "training"];
|
|
1238
|
-
export declare const breakKindAllowsListening: (kind: BreakKind | undefined) => boolean;
|
|
1239
1195
|
export interface BreakReason {
|
|
1240
1196
|
id: string;
|
|
1241
1197
|
label: string;
|
|
@@ -1300,8 +1256,8 @@ export type BreakEndResult = {
|
|
|
1300
1256
|
status: "failed";
|
|
1301
1257
|
failure: ProtocolFailure;
|
|
1302
1258
|
};
|
|
1303
|
-
/** `
|
|
1304
|
-
export type TeamMemberAvailability = "ready" | "on-task" | "on-break" | "
|
|
1259
|
+
/** `elsewhere`: signed in here, and the host holds this agent's capacity for another provider (`count: 0`, host-stopped); not receiving this provider's work. */
|
|
1260
|
+
export type TeamMemberAvailability = "ready" | "on-task" | "on-break" | "elsewhere" | "signed-out";
|
|
1305
1261
|
export interface TeamMember {
|
|
1306
1262
|
id: UserId;
|
|
1307
1263
|
availability: TeamMemberAvailability;
|
|
@@ -1309,24 +1265,65 @@ export interface TeamMember {
|
|
|
1309
1265
|
since?: IsoTimestamp;
|
|
1310
1266
|
/** A request in flight or a grant not yet in effect. `not-requested` is absence, and `on-break` is `availability: "on-break"`. */
|
|
1311
1267
|
break?: Extract<BreakStatus, "awaiting-approval" | "granted" | "starting-after-task">;
|
|
1268
|
+
/** The member's open tasks, trimmed, kept current by the provider; `[]` when they hold none, omitted only where the provider cannot see them. */
|
|
1269
|
+
tasks?: MemberTask[];
|
|
1270
|
+
/** Present while this lead is on the member's call, listening, coaching or joined; absent otherwise. */
|
|
1271
|
+
listening?: MemberListening;
|
|
1312
1272
|
}
|
|
1313
|
-
/** A member asking this lead to join their call. */
|
|
1273
|
+
/** A member asking this lead to join their call, named by the member and the assignment. */
|
|
1314
1274
|
export interface LeadRequest {
|
|
1315
|
-
id: string;
|
|
1316
1275
|
memberId: UserId;
|
|
1317
1276
|
assignmentId: AssignmentId;
|
|
1318
1277
|
note?: string;
|
|
1319
1278
|
since: IsoTimestamp;
|
|
1320
1279
|
}
|
|
1321
1280
|
/**
|
|
1322
|
-
*
|
|
1323
|
-
*
|
|
1281
|
+
* A member's task as the lead sees it: the same task the member's desk holds, trimmed by the
|
|
1282
|
+
* provider. The assignment, what the call is and where it stands, the room and the full history
|
|
1283
|
+
* are always here; the workspace -- browsers, controls, completion terms -- is the member's and
|
|
1284
|
+
* travels only if the provider chooses to send it.
|
|
1285
|
+
*/
|
|
1286
|
+
export type MemberTask<C extends Channel = Channel> = {
|
|
1287
|
+
assignmentId: AssignmentId;
|
|
1288
|
+
title: string;
|
|
1289
|
+
channel: C;
|
|
1290
|
+
taskType: string;
|
|
1291
|
+
phase: TaskPhase;
|
|
1292
|
+
party?: Contact;
|
|
1293
|
+
reference?: string;
|
|
1294
|
+
attributes?: TaskAttribute[];
|
|
1295
|
+
history?: TaskHistory;
|
|
1296
|
+
capabilities?: TaskCapabilities<C>;
|
|
1297
|
+
capabilitySource?: CapabilitySource;
|
|
1298
|
+
browsers?: TaskBrowser[];
|
|
1299
|
+
completionMode?: CompletionMode;
|
|
1300
|
+
wrapAllowance?: DurationSeconds;
|
|
1301
|
+
} & (C extends "voice" ? {
|
|
1302
|
+
onCall?: OnCall[];
|
|
1303
|
+
leadAssist?: TaskLeadAssist;
|
|
1304
|
+
takenOver?: TaskTakenOver;
|
|
1305
|
+
audio?: TaskAudioState;
|
|
1306
|
+
} : {
|
|
1307
|
+
onCall?: never;
|
|
1308
|
+
leadAssist?: never;
|
|
1309
|
+
takenOver?: never;
|
|
1310
|
+
audio?: never;
|
|
1311
|
+
});
|
|
1312
|
+
/** The lead on this member's call, in the mode they are heard, since when. */
|
|
1313
|
+
export interface MemberListening {
|
|
1314
|
+
mode: ListeningMode;
|
|
1315
|
+
since: IsoTimestamp;
|
|
1316
|
+
}
|
|
1317
|
+
/**
|
|
1318
|
+
* The lead's view of their team: published to a login that declares `capabilities.lead` while the
|
|
1319
|
+
* lead has the team feature on, on every snapshot, and to nobody else. The provider keeps it
|
|
1320
|
+
* current: when a member holds, mutes or is transferred, the list is republished whole.
|
|
1324
1321
|
*/
|
|
1325
1322
|
export interface TeamMembers {
|
|
1326
1323
|
members: TeamMember[];
|
|
1327
|
-
/**
|
|
1328
|
-
requests
|
|
1329
|
-
/** The team's policy per capability, as it stands. Present
|
|
1324
|
+
/** Members asking a lead to join their call; `[]` when nobody is asking. */
|
|
1325
|
+
requests: LeadRequest[];
|
|
1326
|
+
/** The team's policy per capability, as it stands. Present where the provider offers policy control; absent where it does not. */
|
|
1330
1327
|
policies?: TeamPolicies;
|
|
1331
1328
|
}
|
|
1332
1329
|
/** A capability a team policy can name: any task control, new call, or a skill by its provider id. */
|
|
@@ -1339,42 +1336,57 @@ export interface TeamPolicy extends Resolved {
|
|
|
1339
1336
|
}
|
|
1340
1337
|
export type TeamPolicies = Partial<Record<PolicyKey, TeamPolicy>>;
|
|
1341
1338
|
export type TeamPolicyCommand = {
|
|
1342
|
-
type: "set";
|
|
1339
|
+
type: "set-policy";
|
|
1343
1340
|
capability: PolicyKey;
|
|
1344
1341
|
setting: TeamPolicySetting;
|
|
1345
1342
|
};
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1343
|
+
/**
|
|
1344
|
+
* The team feature on or off. The lead flag enables it; the lead switches it on the fly, and the
|
|
1345
|
+
* provider needs to know: while it is on, team events scoped to the lead's team arrive and the
|
|
1346
|
+
* lead may act; while it is off, the lead is an ordinary agent and nothing of the team reaches them.
|
|
1347
|
+
*/
|
|
1348
|
+
export type TeamFeatureCommand = {
|
|
1349
|
+
type: "lead-features";
|
|
1350
|
+
enabled: boolean;
|
|
1351
|
+
};
|
|
1352
|
+
/**
|
|
1353
|
+
* The lead's acts on a member's call, each naming the member and, where the lead has it, the
|
|
1354
|
+
* member's assignment; the provider resolves the member's current assignment from the member alone.
|
|
1355
|
+
* `join` answers a member's request and `decline` refuses it; `listen` starts unasked, silent,
|
|
1356
|
+
* `coach` is heard by the member alone and `join-call` by everyone; `leave` takes the lead's channel
|
|
1357
|
+
* off the call however it got there; `take-over-call` moves the call to the lead as its new agent,
|
|
1358
|
+
* from a joined call or a listened one alike. One member's call at a time.
|
|
1359
|
+
*/
|
|
1360
|
+
export type TeamCallCommand = {
|
|
1350
1361
|
type: "join";
|
|
1351
|
-
|
|
1362
|
+
memberId: UserId;
|
|
1363
|
+
assignmentId?: AssignmentId;
|
|
1352
1364
|
} | {
|
|
1353
1365
|
type: "decline";
|
|
1354
|
-
|
|
1366
|
+
memberId: UserId;
|
|
1367
|
+
assignmentId?: AssignmentId;
|
|
1355
1368
|
reason?: string;
|
|
1356
|
-
}
|
|
1357
|
-
export interface TeamLeadAssistCommandRequest {
|
|
1358
|
-
command: TeamLeadAssistCommand;
|
|
1359
|
-
}
|
|
1360
|
-
/**
|
|
1361
|
-
* A lead listening to a member's call. `listen` starts one, silent, on a member from the team member list;
|
|
1362
|
-
* `coach` and `join-call` change how the standing one is heard; `leave` ends it. One member at a
|
|
1363
|
-
* time, and only the modes the login's `listeningControl` lists.
|
|
1364
|
-
*/
|
|
1365
|
-
export type TeamListenCommand = {
|
|
1369
|
+
} | {
|
|
1366
1370
|
type: "listen";
|
|
1367
1371
|
memberId: UserId;
|
|
1372
|
+
assignmentId?: AssignmentId;
|
|
1368
1373
|
} | {
|
|
1369
1374
|
type: "coach";
|
|
1375
|
+
memberId: UserId;
|
|
1376
|
+
assignmentId?: AssignmentId;
|
|
1370
1377
|
} | {
|
|
1371
1378
|
type: "join-call";
|
|
1379
|
+
memberId: UserId;
|
|
1380
|
+
assignmentId?: AssignmentId;
|
|
1372
1381
|
} | {
|
|
1373
1382
|
type: "leave";
|
|
1383
|
+
memberId: UserId;
|
|
1384
|
+
assignmentId?: AssignmentId;
|
|
1385
|
+
} | {
|
|
1386
|
+
type: "take-over-call";
|
|
1387
|
+
memberId: UserId;
|
|
1388
|
+
assignmentId?: AssignmentId;
|
|
1374
1389
|
};
|
|
1375
|
-
export interface TeamListenCommandRequest {
|
|
1376
|
-
command: TeamListenCommand;
|
|
1377
|
-
}
|
|
1378
1390
|
export type TeamBreakCommand = {
|
|
1379
1391
|
type: "decide-break-request";
|
|
1380
1392
|
memberId: UserId;
|
|
@@ -1397,34 +1409,36 @@ export type TeamBreakCommand = {
|
|
|
1397
1409
|
type: "end-forced-break";
|
|
1398
1410
|
memberId: UserId;
|
|
1399
1411
|
};
|
|
1412
|
+
/** Every lead act, as `execute` takes a `TaskCommand`: discriminated by `type`. */
|
|
1413
|
+
export type TeamCommand = TeamFeatureCommand | TeamBreakCommand | TeamCallCommand | TeamPolicyCommand;
|
|
1414
|
+
export interface TeamCommandRequest {
|
|
1415
|
+
command: TeamCommand;
|
|
1416
|
+
}
|
|
1400
1417
|
export type TeamCommandResult = {
|
|
1401
1418
|
status: "applied";
|
|
1402
1419
|
} | {
|
|
1403
1420
|
status: "failed";
|
|
1404
1421
|
failure: ProtocolFailure;
|
|
1405
1422
|
};
|
|
1406
|
-
export interface TeamBreakCommandRequest {
|
|
1407
|
-
command: TeamBreakCommand;
|
|
1408
|
-
}
|
|
1409
1423
|
/**
|
|
1410
1424
|
* The task's real-time audio as the provider holds it: `started` while audio should be attached,
|
|
1411
1425
|
* `ended` once primary interaction's audio ended, and the field omitted while none should be. The
|
|
1412
1426
|
* provider's word -- a desk attaches and renders audio from it, never from its own senses.
|
|
1413
1427
|
*/
|
|
1414
|
-
export type
|
|
1415
|
-
export interface
|
|
1428
|
+
export type TaskAudioState = "started" | "ended";
|
|
1429
|
+
export interface CallAudio {
|
|
1416
1430
|
remoteAudio: MediaStream;
|
|
1417
1431
|
setMuted(muted: boolean): void;
|
|
1418
1432
|
close(): void;
|
|
1419
1433
|
}
|
|
1420
|
-
export interface
|
|
1434
|
+
export interface OpenAudioRequest {
|
|
1421
1435
|
assignmentId: AssignmentId;
|
|
1422
1436
|
/** The agent's microphone as Omni captured it, `HostReport.audio.input.localAudio`; absent while that input is `unavailable`. */
|
|
1423
1437
|
localAudio?: MediaStream;
|
|
1424
1438
|
}
|
|
1425
|
-
export type
|
|
1439
|
+
export type OpenAudioResult = {
|
|
1426
1440
|
status: "opened";
|
|
1427
|
-
|
|
1441
|
+
audio: CallAudio;
|
|
1428
1442
|
} | {
|
|
1429
1443
|
status: "unavailable";
|
|
1430
1444
|
failure: ProtocolFailure;
|
|
@@ -1516,7 +1530,7 @@ export interface Snapshot<C extends Channel = Channel> {
|
|
|
1516
1530
|
/** 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. */
|
|
1517
1531
|
taskCount: number;
|
|
1518
1532
|
contacts?: Contact[];
|
|
1519
|
-
|
|
1533
|
+
calendar?: ScheduledActivity[];
|
|
1520
1534
|
team?: TeamMembers;
|
|
1521
1535
|
}
|
|
1522
1536
|
/**
|
|
@@ -1526,7 +1540,7 @@ export interface Snapshot<C extends Channel = Channel> {
|
|
|
1526
1540
|
* tasks against it and needs no new signal when one ends. The agent is one person on several
|
|
1527
1541
|
* providers, and the host divides their capacity among them: `0` is host-stopped -- the agent's
|
|
1528
1542
|
* capacity is elsewhere for now, this provider assigns nothing and shows the member as
|
|
1529
|
-
* `
|
|
1543
|
+
* `elsewhere` -- and is not a break, which is the agent not working at all. The host restates a
|
|
1530
1544
|
* count of one or more when this provider has the agent's capacity again.
|
|
1531
1545
|
*/
|
|
1532
1546
|
export interface AgentCapacity {
|
|
@@ -1569,10 +1583,24 @@ export type ProviderEvent<C extends Channel = Channel> = {
|
|
|
1569
1583
|
type: "task-updated";
|
|
1570
1584
|
task: Task<C>;
|
|
1571
1585
|
} | {
|
|
1572
|
-
type: "task-
|
|
1586
|
+
type: "task-audio-started";
|
|
1573
1587
|
assignmentId: AssignmentId;
|
|
1574
1588
|
} | {
|
|
1575
|
-
type: "task-
|
|
1589
|
+
type: "task-audio-ended";
|
|
1590
|
+
assignmentId: AssignmentId;
|
|
1591
|
+
}
|
|
1592
|
+
/**
|
|
1593
|
+
* The lead's channel is in a member's call -- listening, coaching or joined -- and its audio should
|
|
1594
|
+
* attach: on a softphone the application opens audio on the member's assignment as it does for a
|
|
1595
|
+
* task. Voice, to a login that declares `lead`, never to the member.
|
|
1596
|
+
*/
|
|
1597
|
+
| {
|
|
1598
|
+
type: "team-audio-started";
|
|
1599
|
+
memberId: UserId;
|
|
1600
|
+
assignmentId: AssignmentId;
|
|
1601
|
+
} | {
|
|
1602
|
+
type: "team-audio-ended";
|
|
1603
|
+
memberId: UserId;
|
|
1576
1604
|
assignmentId: AssignmentId;
|
|
1577
1605
|
} | {
|
|
1578
1606
|
type: "task-ended";
|
|
@@ -1615,7 +1643,7 @@ export type ProviderEvent<C extends Channel = Channel> = {
|
|
|
1615
1643
|
contacts: Contact[];
|
|
1616
1644
|
} | {
|
|
1617
1645
|
type: "calendar-updated";
|
|
1618
|
-
|
|
1646
|
+
calendar: ScheduledActivity[];
|
|
1619
1647
|
};
|
|
1620
1648
|
/**
|
|
1621
1649
|
* The `Provider` prefix survives here for a mechanical reason rather than a naming one: `Event`
|
|
@@ -1687,16 +1715,10 @@ export interface Connection<C extends Channel = Channel> {
|
|
|
1687
1715
|
commitBreak?(): Promise<BreakCommitResult>;
|
|
1688
1716
|
cancelBreak?(): Promise<BreakCancelResult>;
|
|
1689
1717
|
endBreak?(): Promise<BreakEndResult>;
|
|
1690
|
-
/** Required when the login declares `capabilities.team.
|
|
1691
|
-
|
|
1692
|
-
/** Required when the login declares `capabilities.team.leadAssistControl`. */
|
|
1693
|
-
executeTeamLeadAssist?(request: TeamLeadAssistCommandRequest): Promise<TeamCommandResult>;
|
|
1694
|
-
/** Required when the login declares `capabilities.team.listeningControl`. */
|
|
1695
|
-
executeTeamListen?(request: TeamListenCommandRequest): Promise<TeamCommandResult>;
|
|
1696
|
-
/** Required when the login declares `capabilities.team.policyControl`. */
|
|
1697
|
-
executeTeamPolicy?(request: TeamPolicyCommandRequest): Promise<TeamCommandResult>;
|
|
1718
|
+
/** Required when the login declares `capabilities.lead`: every lead act, on the team surface and nowhere else. */
|
|
1719
|
+
executeTeam?(request: TeamCommandRequest): Promise<TeamCommandResult>;
|
|
1698
1720
|
/** Required of a voice adapter whose manifest lists `softphone`: on one, the call's audio lands in Omni. */
|
|
1699
|
-
|
|
1721
|
+
openAudio?(request: OpenAudioRequest): Promise<OpenAudioResult>;
|
|
1700
1722
|
/** Required when the login declares `capabilities.preferences`: the person's own choice, kept by the provider and republished as `authenticated`. */
|
|
1701
1723
|
setPreference?(request: SetPreferenceRequest): Promise<PreferenceResult>;
|
|
1702
1724
|
/** Records an interaction leg the host performed. Required of a softphone login's connection: the host mutes its microphone on any call, and the record is the provider's. */
|
|
@@ -1791,7 +1813,7 @@ export interface BrowserSessionKeyInput {
|
|
|
1791
1813
|
* Whether two logins declare the same capabilities, field by field. The comparison an adapter
|
|
1792
1814
|
* makes before republishing `authenticated`: capabilities are current, not fixed, and the natural
|
|
1793
1815
|
* guard -- comparing the identity -- never fires on a demotion, because the thing that changed is
|
|
1794
|
-
* not the thing being compared. Key order does not matter, and
|
|
1816
|
+
* not the thing being compared. Key order does not matter, and a withdrawn `lead` is a change.
|
|
1795
1817
|
*/
|
|
1796
1818
|
export declare function sameCapabilities(a: UserCapabilities, b: UserCapabilities): boolean;
|
|
1797
1819
|
/**
|
package/dist/index.js
CHANGED
|
@@ -71,19 +71,19 @@ export const DEFAULT_LEVELS = [
|
|
|
71
71
|
export function effectiveLevels(declared) {
|
|
72
72
|
return [...(declared ?? DEFAULT_LEVELS)];
|
|
73
73
|
}
|
|
74
|
-
export const CAPABILITY_SOURCES = ["queue", "
|
|
74
|
+
export const CAPABILITY_SOURCES = ["queue", "nobody", "not-yet-read"];
|
|
75
75
|
// ---------------------------------------------------------------------------
|
|
76
76
|
// Task commands.
|
|
77
77
|
// ---------------------------------------------------------------------------
|
|
78
78
|
export const TASK_COMMAND_NAMES = {
|
|
79
|
-
voice: ["answer", "decline", "
|
|
79
|
+
voice: ["answer", "decline", "dial", "hold", "resume", "end-call", "terminate-call",
|
|
80
80
|
"connect-back", "transfer", "lead-assist", "conference", "recording", "complete"],
|
|
81
81
|
chat: ["accept", "decline", "pause", "resume", "complete"],
|
|
82
82
|
email: ["accept", "decline", "complete"],
|
|
83
83
|
};
|
|
84
84
|
/** The dial a command places, by the host's identity for it; `undefined` for a command that dials nothing. */
|
|
85
85
|
export function commandDialId(command) {
|
|
86
|
-
if (command.type === "connect-back" || command.type === "
|
|
86
|
+
if (command.type === "connect-back" || command.type === "dial")
|
|
87
87
|
return command.dialId;
|
|
88
88
|
if (command.type === "transfer" && (command.action === "cold" || command.action === "warm"))
|
|
89
89
|
return command.dialId;
|
|
@@ -95,12 +95,6 @@ export const BREAK_KINDS = [
|
|
|
95
95
|
"short-break", "meal", "rest", "training", "coaching",
|
|
96
96
|
"meeting", "administrative", "technical", "personal", "other",
|
|
97
97
|
];
|
|
98
|
-
/**
|
|
99
|
-
* The breaks during which a lead may listen to a member's call: work of another sort, not rest.
|
|
100
|
-
* A listening task is the one task a break in effect may hold, and only on one of these.
|
|
101
|
-
*/
|
|
102
|
-
export const LISTENING_BREAK_KINDS = ["coaching", "administrative", "training"];
|
|
103
|
-
export const breakKindAllowsListening = (kind) => kind !== undefined && LISTENING_BREAK_KINDS.includes(kind);
|
|
104
98
|
export const OMNI_FAILURE_CODES = [
|
|
105
99
|
"omni.not-authenticated",
|
|
106
100
|
"omni.capability-not-enabled",
|
|
@@ -177,19 +171,15 @@ export function historyStepExpectsAPerson(step) {
|
|
|
177
171
|
* Whether two logins declare the same capabilities, field by field. The comparison an adapter
|
|
178
172
|
* makes before republishing `authenticated`: capabilities are current, not fixed, and the natural
|
|
179
173
|
* guard -- comparing the identity -- never fires on a demotion, because the thing that changed is
|
|
180
|
-
* not the thing being compared. Key order does not matter, and
|
|
174
|
+
* not the thing being compared. Key order does not matter, and a withdrawn `lead` is a change.
|
|
181
175
|
*/
|
|
182
176
|
export function sameCapabilities(a, b) {
|
|
183
|
-
const modes = (team) => team?.listeningControl === undefined ? undefined : [...team.listeningControl].sort().join(",");
|
|
184
177
|
// Every field, so a capability added later cannot be missed here: `satisfies` pins the field
|
|
185
|
-
//
|
|
186
|
-
|
|
187
|
-
void { breaks: true, preferences: true, team: true };
|
|
178
|
+
// list to the type, and a new key is a compile error until it is compared.
|
|
179
|
+
void { breaks: true, preferences: true, lead: true };
|
|
188
180
|
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");
|
|
189
181
|
return a.breaks === b.breaks &&
|
|
190
|
-
|
|
191
|
-
Object.keys(teamKeys).every(key => key === "listeningControl" || a.team?.[key] === b.team?.[key]) &&
|
|
192
|
-
modes(a.team) === modes(b.team) &&
|
|
182
|
+
a.lead === b.lead &&
|
|
193
183
|
preferences(a.preferences) === preferences(b.preferences);
|
|
194
184
|
}
|
|
195
185
|
/**
|