@xema/omni-protocol 0.1.99 → 0.1.101

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
@@ -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 `openMedia`, or on a `deskPhone`, a handset the platform rings, where the host opens
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
  */
@@ -433,7 +433,7 @@ export interface RecordingActions {
433
433
  export interface TaskRecordingPolicy {
434
434
  provider?: RecordingActions;
435
435
  host?: RecordingActions & {
436
- destinationId: string;
436
+ storageId: string;
437
437
  };
438
438
  }
439
439
  /** Current evidence only. Expiry/disconnect means unknown, never a historical stop. */
@@ -478,7 +478,7 @@ export interface HostRecording {
478
478
  announcesToCaller?: true;
479
479
  actions: RecordingAction[];
480
480
  /** Explicitly provisioned destinations. A task chooses one; no upload/storage fallback. */
481
- destinationIds: string[];
481
+ storageIds: string[];
482
482
  execute(request: HostRecordingRequest): Promise<RecordingCommandResult>;
483
483
  }
484
484
  /** Recording applies or fails; it never dials. Rejection without result means unknown. */
@@ -729,9 +729,9 @@ export type TaskPhase =
729
729
  /** What the system does when a preview's deadline passes with no Call pressed. */
730
730
  export type PreviewDeadline =
731
731
  /** Provider initiates dialing when preparation ends; never a host timer command. */
732
- "calls"
732
+ "provider-dials"
733
733
  /** Host initiates the ordinary call command when preparation ends. */
734
- | "host-calls"
734
+ | "host-dials"
735
735
  /** Preparation target has elapsed; remain in preview until the agent presses Call. */
736
736
  | "waits";
737
737
  /** Who ends the task: the agent issuing `complete`, or the provider deciding it is over. */
@@ -866,7 +866,7 @@ export type TaskLeadAssist = {
866
866
  * On a task that reached this agent by a lead's take-over: which member the call was taken from,
867
867
  * and when. The provider moves the call from one agent to another, and the lead is the new agent,
868
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.
869
+ * is on, counted against their capacity like any other call, provided they are under it and their voice channel is free.
870
870
  */
871
871
  export interface TaskTakenOver {
872
872
  memberId: UserId;
@@ -917,12 +917,12 @@ export interface Locked {
917
917
  */
918
918
  export type Lockable<T> = T | Locked;
919
919
  /**
920
- * Who chose a task's capabilities. `undetermined` is a fault the provider states on the task rather
920
+ * Who chose a task's capabilities. `not-yet-read` is a fault the provider states on the task rather
921
921
  * than hides: the platform's terms could not be read, and the set published is what the provider
922
922
  * will honour, not what the platform permits.
923
923
  */
924
- export type CapabilitySource = "queue" | "ungoverned" | "undetermined";
925
- export declare const CAPABILITY_SOURCES: readonly ["queue", "ungoverned", "undetermined"];
924
+ export type CapabilitySource = "queue" | "nobody" | "not-yet-read";
925
+ export declare const CAPABILITY_SOURCES: readonly ["queue", "nobody", "not-yet-read"];
926
926
  export type Task<C extends Channel = Channel> = {
927
927
  /** The assignment this task is the record of, and its one identity. See `AssignmentId`. */
928
928
  assignmentId: AssignmentId;
@@ -933,8 +933,8 @@ export type Task<C extends Channel = Channel> = {
933
933
  capabilities: TaskCapabilities<C>;
934
934
  /**
935
935
  * Where the capabilities came from before the provider put them on the task, so a host can tell
936
- * a fact from a fault: `queue` when somebody chose these terms, `ungoverned` when nothing handed
937
- * the work over, `undetermined` when a queue was named and its terms could not be read -- the
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
938
938
  * provider then publishes what it will honour and says so here, where the controls are drawn.
939
939
  */
940
940
  capabilitySource: CapabilitySource;
@@ -953,7 +953,7 @@ export type Task<C extends Channel = Channel> = {
953
953
  acceptance?: AcceptanceMode;
954
954
  /**
955
955
  * In `preview` only, and together: the preparation target and behavior when it elapses,
956
- * and who acts then -- `calls` makes the provider initiate dialing, `host-calls` makes the
956
+ * and who acts then -- `provider-dials` makes the provider initiate dialing, `host-dials` makes the
957
957
  * host issue Call, and `waits` keeps waiting for the agent without dialing or ending the task.
958
958
  * Absent, the agent has as long as they need, without a preparation countdown.
959
959
  */
@@ -970,13 +970,13 @@ export type Task<C extends Channel = Channel> = {
970
970
  onCall?: OnCall[];
971
971
  leadAssist?: TaskLeadAssist;
972
972
  takenOver?: TaskTakenOver;
973
- media?: TaskMediaState;
973
+ audio?: TaskAudioState;
974
974
  } : {
975
975
  recording?: never;
976
976
  onCall?: never;
977
977
  leadAssist?: never;
978
978
  takenOver?: never;
979
- media?: never;
979
+ audio?: never;
980
980
  });
981
981
  /**
982
982
  * What the provider wants of Omni's acceptance policy for one offer. On routed work, present only
@@ -1014,7 +1014,7 @@ export type TaskOutcome = {
1014
1014
  failure: ProtocolFailure;
1015
1015
  };
1016
1016
  export declare const TASK_COMMAND_NAMES: {
1017
- readonly voice: readonly ["answer", "decline", "call", "hold", "resume", "end-call", "terminate-call", "connect-back", "transfer", "lead-assist", "conference", "recording", "complete"];
1017
+ readonly voice: readonly ["answer", "decline", "dial", "hold", "resume", "end-call", "terminate-call", "connect-back", "transfer", "lead-assist", "conference", "recording", "complete"];
1018
1018
  readonly chat: readonly ["accept", "decline", "pause", "resume", "complete"];
1019
1019
  readonly email: readonly ["accept", "decline", "complete"];
1020
1020
  };
@@ -1030,7 +1030,7 @@ export type VoiceTaskCommand = {
1030
1030
  }
1031
1031
  /** In `preview`: place the call to the party whose record the agent has read. A dial, gated by the phase alone. */
1032
1032
  | {
1033
- type: "call";
1033
+ type: "dial";
1034
1034
  dialId: DialId;
1035
1035
  } | {
1036
1036
  type: "hold";
@@ -1041,7 +1041,7 @@ export type VoiceTaskCommand = {
1041
1041
  | {
1042
1042
  type: "end-call";
1043
1043
  }
1044
- /** End the caller channel. Gated by `terminateCall`. */
1044
+ /** End the whole call at the provider: every channel on it, the agent's, the caller's and anyone else's. Gated by `terminateCall`. */
1045
1045
  | {
1046
1046
  type: "terminate-call";
1047
1047
  }
@@ -1256,8 +1256,8 @@ export type BreakEndResult = {
1256
1256
  status: "failed";
1257
1257
  failure: ProtocolFailure;
1258
1258
  };
1259
- /** `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. */
1260
- export type TeamMemberAvailability = "ready" | "on-task" | "on-break" | "reserved" | "signed-out";
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";
1261
1261
  export interface TeamMember {
1262
1262
  id: UserId;
1263
1263
  availability: TeamMemberAvailability;
@@ -1269,6 +1269,8 @@ export interface TeamMember {
1269
1269
  tasks?: MemberTask[];
1270
1270
  /** Present while this lead is on the member's call, listening, coaching or joined; absent otherwise. */
1271
1271
  listening?: MemberListening;
1272
+ /** The member's own history for the day: sign-in, sign-out, breaks and productivity. Omitted where the provider cannot say. */
1273
+ shift?: MemberShift;
1272
1274
  }
1273
1275
  /** A member asking this lead to join their call, named by the member and the assignment. */
1274
1276
  export interface LeadRequest {
@@ -1302,18 +1304,43 @@ export type MemberTask<C extends Channel = Channel> = {
1302
1304
  onCall?: OnCall[];
1303
1305
  leadAssist?: TaskLeadAssist;
1304
1306
  takenOver?: TaskTakenOver;
1305
- media?: TaskMediaState;
1307
+ audio?: TaskAudioState;
1306
1308
  } : {
1307
1309
  onCall?: never;
1308
1310
  leadAssist?: never;
1309
1311
  takenOver?: never;
1310
- media?: never;
1312
+ audio?: never;
1311
1313
  });
1312
- /** The lead on this member's call, in the mode they are heard, since when. */
1314
+ /**
1315
+ * The lead on this member's call: which of the member's calls, in the mode they are heard, since
1316
+ * when. Its appearance on the team member list is the provider's word that the lead's audio is up,
1317
+ * and the application opens `openAudio` on the assignment named; its disappearance closes it.
1318
+ */
1313
1319
  export interface MemberListening {
1320
+ assignmentId: AssignmentId;
1314
1321
  mode: ListeningMode;
1315
1322
  since: IsoTimestamp;
1316
1323
  }
1324
+ export type ShiftEventKind = "signed-in" | "signed-out" | "break-started" | "break-ended";
1325
+ export declare const SHIFT_EVENT_KINDS: readonly ["signed-in", "signed-out", "break-started", "break-ended"];
1326
+ export interface ShiftEvent {
1327
+ at: IsoTimestamp;
1328
+ kind: ShiftEventKind;
1329
+ }
1330
+ /**
1331
+ * The member's own history for the day, about the person rather than any one call: when they
1332
+ * signed in and out, today's totals as the provider counts them, each present only when the
1333
+ * provider knows it, and the day's sign-in, sign-out and break events, oldest first.
1334
+ */
1335
+ export interface MemberShift {
1336
+ signedInAt: IsoTimestamp;
1337
+ signedOutAt?: IsoTimestamp;
1338
+ talkSeconds?: DurationSeconds;
1339
+ holdSeconds?: DurationSeconds;
1340
+ breakSeconds?: DurationSeconds;
1341
+ tasksHandled?: number;
1342
+ events?: ShiftEvent[];
1343
+ }
1317
1344
  /**
1318
1345
  * The lead's view of their team: published to a login that declares `capabilities.lead` while the
1319
1346
  * lead has the team feature on, on every snapshot, and to nobody else. The provider keeps it
@@ -1425,20 +1452,20 @@ export type TeamCommandResult = {
1425
1452
  * `ended` once primary interaction's audio ended, and the field omitted while none should be. The
1426
1453
  * provider's word -- a desk attaches and renders audio from it, never from its own senses.
1427
1454
  */
1428
- export type TaskMediaState = "started" | "ended";
1429
- export interface VoiceMediaSession {
1455
+ export type TaskAudioState = "started" | "ended";
1456
+ export interface CallAudio {
1430
1457
  remoteAudio: MediaStream;
1431
1458
  setMuted(muted: boolean): void;
1432
1459
  close(): void;
1433
1460
  }
1434
- export interface OpenMediaRequest {
1461
+ export interface OpenAudioRequest {
1435
1462
  assignmentId: AssignmentId;
1436
1463
  /** The agent's microphone as Omni captured it, `HostReport.audio.input.localAudio`; absent while that input is `unavailable`. */
1437
1464
  localAudio?: MediaStream;
1438
1465
  }
1439
- export type OpenMediaResult = {
1466
+ export type OpenAudioResult = {
1440
1467
  status: "opened";
1441
- session: VoiceMediaSession;
1468
+ audio: CallAudio;
1442
1469
  } | {
1443
1470
  status: "unavailable";
1444
1471
  failure: ProtocolFailure;
@@ -1530,7 +1557,7 @@ export interface Snapshot<C extends Channel = Channel> {
1530
1557
  /** 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. */
1531
1558
  taskCount: number;
1532
1559
  contacts?: Contact[];
1533
- scheduledActivities?: ScheduledActivity[];
1560
+ calendar?: ScheduledActivity[];
1534
1561
  team?: TeamMembers;
1535
1562
  }
1536
1563
  /**
@@ -1540,7 +1567,7 @@ export interface Snapshot<C extends Channel = Channel> {
1540
1567
  * tasks against it and needs no new signal when one ends. The agent is one person on several
1541
1568
  * providers, and the host divides their capacity among them: `0` is host-stopped -- the agent's
1542
1569
  * capacity is elsewhere for now, this provider assigns nothing and shows the member as
1543
- * `reserved` -- and is not a break, which is the agent not working at all. The host restates a
1570
+ * `elsewhere` -- and is not a break, which is the agent not working at all. The host restates a
1544
1571
  * count of one or more when this provider has the agent's capacity again.
1545
1572
  */
1546
1573
  export interface AgentCapacity {
@@ -1583,24 +1610,10 @@ export type ProviderEvent<C extends Channel = Channel> = {
1583
1610
  type: "task-updated";
1584
1611
  task: Task<C>;
1585
1612
  } | {
1586
- type: "task-media-started";
1613
+ type: "task-audio-started";
1587
1614
  assignmentId: AssignmentId;
1588
1615
  } | {
1589
- type: "task-media-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 media 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-media-started";
1599
- memberId: UserId;
1600
- assignmentId: AssignmentId;
1601
- } | {
1602
- type: "team-media-ended";
1603
- memberId: UserId;
1616
+ type: "task-audio-ended";
1604
1617
  assignmentId: AssignmentId;
1605
1618
  } | {
1606
1619
  type: "task-ended";
@@ -1643,7 +1656,7 @@ export type ProviderEvent<C extends Channel = Channel> = {
1643
1656
  contacts: Contact[];
1644
1657
  } | {
1645
1658
  type: "calendar-updated";
1646
- scheduledActivities: ScheduledActivity[];
1659
+ calendar: ScheduledActivity[];
1647
1660
  };
1648
1661
  /**
1649
1662
  * The `Provider` prefix survives here for a mechanical reason rather than a naming one: `Event`
@@ -1718,7 +1731,7 @@ export interface Connection<C extends Channel = Channel> {
1718
1731
  /** Required when the login declares `capabilities.lead`: every lead act, on the team surface and nowhere else. */
1719
1732
  executeTeam?(request: TeamCommandRequest): Promise<TeamCommandResult>;
1720
1733
  /** Required of a voice adapter whose manifest lists `softphone`: on one, the call's audio lands in Omni. */
1721
- openMedia?(request: OpenMediaRequest): Promise<OpenMediaResult>;
1734
+ openAudio?(request: OpenAudioRequest): Promise<OpenAudioResult>;
1722
1735
  /** Required when the login declares `capabilities.preferences`: the person's own choice, kept by the provider and republished as `authenticated`. */
1723
1736
  setPreference?(request: SetPreferenceRequest): Promise<PreferenceResult>;
1724
1737
  /** 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. */
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", "ungoverned", "undetermined"];
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", "call", "hold", "resume", "end-call", "terminate-call",
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 === "call")
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,6 +95,7 @@ export const BREAK_KINDS = [
95
95
  "short-break", "meal", "rest", "training", "coaching",
96
96
  "meeting", "administrative", "technical", "personal", "other",
97
97
  ];
98
+ export const SHIFT_EVENT_KINDS = ["signed-in", "signed-out", "break-started", "break-ended"];
98
99
  export const OMNI_FAILURE_CODES = [
99
100
  "omni.not-authenticated",
100
101
  "omni.capability-not-enabled",
package/dist/testing.d.ts CHANGED
@@ -7,7 +7,7 @@ export { ProtocolConformanceError, assertNoViolations, type ProtocolViolation }
7
7
  * rules -- each optional part of a task, each optional part of the break state and team member list, each
8
8
  * declared contribution, and each event type.
9
9
  */
10
- declare const STATE_SUBJECTS: readonly ["tasks", "task.browsers", "task.attributes", "task.history", "task.onCall", "task.leadAssist", "task.takenOver", "task.media", "task.acceptance", "task.outcomes", "task.destinations", "task.custom", "task.locked", "break.reasons", "break.forced", "team.members", "team.requests", "team.tasks", "team.listening", "contacts", "scheduledActivities", "team.policies"];
10
+ declare const STATE_SUBJECTS: readonly ["tasks", "task.browsers", "task.attributes", "task.history", "task.onCall", "task.leadAssist", "task.takenOver", "task.audio", "task.acceptance", "task.outcomes", "task.destinations", "task.custom", "task.locked", "break.reasons", "break.forced", "team.members", "team.requests", "team.tasks", "team.listening", "team.shift", "contacts", "calendar", "team.policies"];
11
11
  export type ContractSubject = (typeof STATE_SUBJECTS)[number] | `event.${ProviderEvent["type"]}`;
12
12
  export interface AdapterContractResult {
13
13
  events: ProviderEventEnvelope[];
@@ -41,7 +41,7 @@ export interface ExerciseAdapterOptions {
41
41
  collectOnly?: boolean;
42
42
  /**
43
43
  * Drive one ordinary lifecycle on the first task the provider offers -- accept it, open its
44
- * media on a softphone, hold and resume where offered, end the call where offered, complete it
44
+ * audio on a softphone, hold and resume where offered, end the call where offered, complete it
45
45
  * where the agent completes -- so the rules about a live call are reached rather than listed
46
46
  * under `notExercised`. Off by default, since it issues commands against whatever platform the
47
47
  * adapter is connected to: turn it on against a test backend.
@@ -177,9 +177,9 @@ export declare class TaskStream {
177
177
  * Takes a resync snapshot -- a `snapshot` event, or the read a reloaded client makes -- as the
178
178
  * state now, and first holds it to what the stream knew. A snapshot replaces state; it does not
179
179
  * get to forget it. A task it carries that the stream already held may not go backwards, lose an
180
- * entry of its record, return to undetermined terms, or be at work without the audio the stream
181
- * held up: media ends on task-media-ended and the call moves on, so a task still in-progress or
182
- * paused without its media is a snapshot that lost state, not a call that ended.
180
+ * entry of its record, return to not-yet-read terms, or be at work without the audio the stream
181
+ * held up: audio ends on task-audio-ended and the call moves on, so a task still in-progress or
182
+ * paused without its audio is a snapshot that lost state, not a call that ended.
183
183
  */
184
184
  resync(snapshot: unknown, at: string): ProtocolViolation[];
185
185
  /** Replaces what is known with a snapshot's tasks, as a snapshot replaces Omni's state. */
@@ -205,12 +205,12 @@ export declare class BreakStream {
205
205
  */
206
206
  export declare function assertBreakFollowsItsRequests(envelopes: readonly ProviderEventEnvelope[], snapshot?: Snapshot): void;
207
207
  /**
208
- * The media follows the task and never decides it. Given a provider's stream -- optionally seeded
209
- * with the snapshot it began from -- every task is introduced once, `task-media-started` and
210
- * `task-media-ended` alternate on work that has begun, media ends only where it arrived, and what
211
- * follows the media ending is `completing` or `task-ended`.
208
+ * The audio follows the task and never decides it. Given a provider's stream -- optionally seeded
209
+ * with the snapshot it began from -- every task is introduced once, `task-audio-started` and
210
+ * `task-audio-ended` alternate on work that has begun, audio ends only where it arrived, and what
211
+ * follows the audio ending is `completing` or `task-ended`.
212
212
  */
213
- export declare function assertMediaFollowsTheTask(envelopes: readonly ProviderEventEnvelope[], snapshot?: Snapshot): void;
213
+ export declare function assertAudioFollowsTheTask(envelopes: readonly ProviderEventEnvelope[], snapshot?: Snapshot): void;
214
214
  /** A login store that lives in memory for the test: what most adapter tests hand `exerciseAdapter` as `context.store`. */
215
215
  export declare function memoryStore(): LoginStore;
216
216
  /**
@@ -252,14 +252,14 @@ export interface BreakOnTaskStep {
252
252
  */
253
253
  export declare function assertBreakBeginsAfterTask(steps: readonly BreakOnTaskStep[]): void;
254
254
  /**
255
- * Validates the deadline derived from media end and the task's fixed wrap allowance.
255
+ * Validates the deadline derived from audio end and the task's fixed wrap allowance.
256
256
  *
257
257
  * A task with no allowance has no deadline, so `observedDeadline` must then be `undefined`: a
258
258
  * host counting down what the provider left open is the violation, and so is a host counting
259
259
  * nothing down when the provider set a clock. `toleranceMs` absorbs scheduler jitter in a real
260
260
  * implementation; pass 0 to demand an exact match.
261
261
  */
262
- export declare function assertWrapTimeout(task: Pick<TaskCompletion, "completionMode" | "wrapAllowance">, mediaEndedAt: string, observedDeadline: string | undefined, toleranceMs?: number): void;
262
+ export declare function assertWrapTimeout(task: Pick<TaskCompletion, "completionMode" | "wrapAllowance">, audioEndedAt: string, observedDeadline: string | undefined, toleranceMs?: number): void;
263
263
  /** One browser in one task of one provider. `providerId` is `Manifest.id`, never `displayName`. */
264
264
  export type BrowserIsolationScenario = BrowserSessionKeyInput;
265
265
  /** Validates whether two task-browser definitions should share one browser session. */