@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 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?: boolean;
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, taskId, taskType, browser } = input;
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(taskId)}.${part(browser.name)}`;
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. */