@xema/omni-protocol 0.1.43 → 0.1.44

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
@@ -512,10 +512,15 @@ export type TaskPhase =
512
512
  "pending"
513
513
  /** Accepted, and not yet started. */
514
514
  | "confirmed"
515
- /** Being made ready -- a preview the agent reads before the work begins. */
516
- | "preparing" | "in-progress" | "paused"
515
+ /**
516
+ * Voice only: the customer's record is on the agent's screen and no call has gone out. The agent
517
+ * presses Call, or `previewEndsAt` arrives and `atDeadline` says what the system does instead.
518
+ */
519
+ | "preview" | "in-progress" | "paused"
517
520
  /** The work is over and the agent is finishing up. */
518
521
  | "completing";
522
+ /** What the system does when a preview's deadline passes with no Call pressed. */
523
+ export type PreviewDeadline = "calls" | "expires";
519
524
  /** Who ends the task: the agent issuing `complete`, or the provider deciding it is over. */
520
525
  export type CompletionMode = "agent-command" | "provider-automatic";
521
526
  export interface TaskAttributeBase {
@@ -710,6 +715,13 @@ export type Task<C extends Channel = Channel> = {
710
715
  * (`autoAcceptTasks: true`), forbidden when it said not, and absent past `pending`.
711
716
  */
712
717
  acceptance?: AcceptanceMode;
718
+ /**
719
+ * In `preview` only, and together: when the system stops waiting for the agent to press Call,
720
+ * and what it does then -- `calls` places the call itself, `expires` takes the record back and
721
+ * the task ends `expired`. Absent, the agent has as long as they need.
722
+ */
723
+ previewEndsAt?: IsoTimestamp;
724
+ atDeadline?: PreviewDeadline;
713
725
  /** The identifier an agent reads back to a customer, where the provider has one. */
714
726
  reference?: string;
715
727
  attributes?: TaskAttribute[];
@@ -747,7 +759,7 @@ export type TaskOutcome = {
747
759
  /** Only the phases in which somebody is still being waited on can expire. */
748
760
  | {
749
761
  type: "expired";
750
- phase: "pending" | "confirmed" | "preparing";
762
+ phase: "pending" | "confirmed" | "preview";
751
763
  }
752
764
  /** This agent left a call that continues without them: a lead who joined and dropped. */
753
765
  | {
@@ -757,7 +769,7 @@ export type TaskOutcome = {
757
769
  failure: ProtocolFailure;
758
770
  };
759
771
  export declare const TASK_COMMAND_NAMES: {
760
- readonly voice: readonly ["answer", "decline", "start-call", "mute", "hold", "resume", "disconnect", "connect-back", "transfer", "lead-assist", "conference", "recording", "complete"];
772
+ readonly voice: readonly ["answer", "decline", "call", "mute", "hold", "resume", "disconnect", "connect-back", "transfer", "lead-assist", "conference", "recording", "complete"];
761
773
  readonly chat: readonly ["accept", "decline", "pause", "resume", "complete"];
762
774
  readonly email: readonly ["accept", "decline", "complete"];
763
775
  };
@@ -770,8 +782,11 @@ export type VoiceTaskCommand = {
770
782
  type: "answer";
771
783
  } | {
772
784
  type: "decline";
773
- } | {
774
- type: "start-call";
785
+ }
786
+ /** In `preview`: place the call to the party whose record the agent has read. A dial, gated by the phase alone. */
787
+ | {
788
+ type: "call";
789
+ dialId: DialId;
775
790
  } | {
776
791
  type: "mute";
777
792
  muted: boolean;
@@ -1255,7 +1270,6 @@ export type ProviderEvent<C extends Channel = Channel> = {
1255
1270
  type: "task-offered";
1256
1271
  task: Task<C>;
1257
1272
  allocationExpiresAt?: IsoTimestamp;
1258
- preparationEndsAt?: IsoTimestamp;
1259
1273
  } | {
1260
1274
  type: "task-updated";
1261
1275
  task: Task<C>;
@@ -1372,7 +1386,7 @@ export declare const DEFAULT_TASK_PHASE_LABELS: {
1372
1386
  readonly voice: {
1373
1387
  readonly pending: "Offered";
1374
1388
  readonly confirmed: "Accepted";
1375
- readonly preparing: "Preview";
1389
+ readonly preview: "Preview";
1376
1390
  readonly "in-progress": "On Call";
1377
1391
  readonly paused: "On Hold";
1378
1392
  readonly completing: "After Call Work";
@@ -1380,7 +1394,7 @@ export declare const DEFAULT_TASK_PHASE_LABELS: {
1380
1394
  readonly chat: {
1381
1395
  readonly pending: "Incoming Chat";
1382
1396
  readonly confirmed: "Accepted";
1383
- readonly preparing: "Preparing";
1397
+ readonly preview: "Preview";
1384
1398
  readonly "in-progress": "In Chat";
1385
1399
  readonly paused: "Paused";
1386
1400
  readonly completing: "Wrap-up";
@@ -1388,7 +1402,7 @@ export declare const DEFAULT_TASK_PHASE_LABELS: {
1388
1402
  readonly email: {
1389
1403
  readonly pending: "Assigned";
1390
1404
  readonly confirmed: "Accepted";
1391
- readonly preparing: "Reviewing";
1405
+ readonly preview: "Preview";
1392
1406
  readonly "in-progress": "Working";
1393
1407
  readonly paused: "Paused";
1394
1408
  readonly completing: "Completing";
package/dist/index.js CHANGED
@@ -71,14 +71,14 @@ export function effectiveLevels(declared) {
71
71
  // Task commands.
72
72
  // ---------------------------------------------------------------------------
73
73
  export const TASK_COMMAND_NAMES = {
74
- voice: ["answer", "decline", "start-call", "mute", "hold", "resume", "disconnect",
74
+ voice: ["answer", "decline", "call", "mute", "hold", "resume", "disconnect",
75
75
  "connect-back", "transfer", "lead-assist", "conference", "recording", "complete"],
76
76
  chat: ["accept", "decline", "pause", "resume", "complete"],
77
77
  email: ["accept", "decline", "complete"],
78
78
  };
79
79
  /** The dial a command places, by the host's identity for it; `undefined` for a command that dials nothing. */
80
80
  export function commandDialId(command) {
81
- if (command.type === "connect-back")
81
+ if (command.type === "connect-back" || command.type === "call")
82
82
  return command.dialId;
83
83
  if (command.type === "transfer" && (command.action === "cold" || command.action === "warm"))
84
84
  return command.dialId;
@@ -117,15 +117,15 @@ export function defineAdapter(adapter) {
117
117
  // ---------------------------------------------------------------------------
118
118
  export const DEFAULT_TASK_PHASE_LABELS = {
119
119
  voice: {
120
- pending: "Offered", confirmed: "Accepted", preparing: "Preview",
120
+ pending: "Offered", confirmed: "Accepted", preview: "Preview",
121
121
  "in-progress": "On Call", paused: "On Hold", completing: "After Call Work",
122
122
  },
123
123
  chat: {
124
- pending: "Incoming Chat", confirmed: "Accepted", preparing: "Preparing",
124
+ pending: "Incoming Chat", confirmed: "Accepted", preview: "Preview",
125
125
  "in-progress": "In Chat", paused: "Paused", completing: "Wrap-up",
126
126
  },
127
127
  email: {
128
- pending: "Assigned", confirmed: "Accepted", preparing: "Reviewing",
128
+ pending: "Assigned", confirmed: "Accepted", preview: "Preview",
129
129
  "in-progress": "Working", paused: "Paused", completing: "Completing",
130
130
  },
131
131
  };
@@ -38,7 +38,7 @@ const membersOf = (members) => Object.keys(members);
38
38
  const CHANNELS = membersOf({ voice: true, chat: true, email: true });
39
39
  const TRANSPORT_RECOVERIES = membersOf({ reconnect: true, reauthenticate: true });
40
40
  const TASK_PHASES = membersOf({
41
- pending: true, confirmed: true, preparing: true, "in-progress": true, paused: true, completing: true,
41
+ pending: true, confirmed: true, preview: true, "in-progress": true, paused: true, completing: true,
42
42
  });
43
43
  const COMPLETION_MODES = membersOf({ "agent-command": true, "provider-automatic": true });
44
44
  const ACCEPTANCE_MODES = membersOf({
@@ -82,13 +82,14 @@ const MEMBER_BREAKS = membersOf({
82
82
  "awaiting-decision": true, granted: true, "starting-after-task": true,
83
83
  });
84
84
  const OFFERABLE_PHASES = membersOf({
85
- pending: true, confirmed: true, preparing: true,
85
+ pending: true, confirmed: true, preview: true,
86
86
  });
87
+ const PREVIEW_DEADLINES = membersOf({ calls: true, expires: true });
87
88
  const TEAM_CAPABILITIES = membersOf({ breakControl: true, leadAssistControl: true, policyControl: true, monitorControl: true });
88
89
  const MONITOR_MODES = membersOf({ monitor: true, whisper: true, barge: true });
89
90
  const COMPLETED_BY = membersOf({ agent: true, provider: true });
90
91
  const EXPIRABLE_PHASES = membersOf({
91
- pending: true, confirmed: true, preparing: true,
92
+ pending: true, confirmed: true, preview: true,
92
93
  });
93
94
  const ISOLATION_SCHEME_VALUES = Object.values(BROWSER_ISOLATION_SCHEMES);
94
95
  /** The capabilities each channel arm of `TaskCapabilities` declares, keyed off the type itself. */
@@ -864,7 +865,28 @@ function validateTaskInto(task, context, path, into) {
864
865
  into.require(isTaskId(task.id), "task.id", `${path}.id`, "a task needs a non-empty id");
865
866
  into.filled(task.title, "task.title", `${path}.title`, "a task needs a title");
866
867
  into.filled(task.taskType, "task.taskType", `${path}.taskType`, "a task needs a task type");
867
- into.oneOf(task.phase, TASK_PHASES, "task.phase", `${path}.phase`);
868
+ if (into.oneOf(task.phase, TASK_PHASES, "task.phase", `${path}.phase`) && task.phase === "preview") {
869
+ // A preview is a record waiting for the agent to press Call: only voice has a call to place,
870
+ // and pressing Call is a dial, so the manifest has to have said how a dial ends.
871
+ into.require(context.channel === "voice", "task.phase.channel", `${path}.phase`, `a ${context.channel} task has no call to preview`);
872
+ into.require(context.dialOutcomesDeclared !== false, "task.preview.dialOutcomes.required", `${path}.phase`, "a preview ends in the agent pressing Call, which dials, and the manifest declares no dialOutcomes to say how a dial ends");
873
+ }
874
+ // The deadline and what happens at it travel together, and only while the record is being previewed.
875
+ if (task.previewEndsAt !== undefined || task.atDeadline !== undefined) {
876
+ into.require(task.phase === "preview", "task.preview.deadline.unexpected", `${path}.previewEndsAt`, "previewEndsAt and atDeadline belong to a task in preview; past it there is nothing to wait for");
877
+ if (task.previewEndsAt === undefined) {
878
+ into.add("task.preview.previewEndsAt.required", `${path}.previewEndsAt`, "atDeadline says what happens at a deadline, so there has to be one");
879
+ }
880
+ else {
881
+ into.timestamp(task.previewEndsAt, "task.preview.previewEndsAt", `${path}.previewEndsAt`);
882
+ }
883
+ if (task.atDeadline === undefined) {
884
+ into.add("task.preview.atDeadline.required", `${path}.atDeadline`, "a preview with a deadline says what the system does at it: calls, or expires -- an agent counting down has to know which");
885
+ }
886
+ else {
887
+ into.oneOf(task.atDeadline, PREVIEW_DEADLINES, "task.preview.atDeadline", `${path}.atDeadline`);
888
+ }
889
+ }
868
890
  // Acceptance is the offer's word, carried on the pending task so a snapshot can say it: it
869
891
  // travels exactly when Omni said tasks may be auto-accepted, and only while the task is pending.
870
892
  if (task.acceptance !== undefined) {
@@ -1386,10 +1408,8 @@ export function validateEventEnvelope(envelope, manifest, path = "event", contex
1386
1408
  if (isPlainObject(event.task) && typeof event.task.phase === "string") {
1387
1409
  into.require(OFFERABLE_PHASES.includes(event.task.phase), "event.taskOffered.phase", `${at}.task.phase`, `task-offered introduces a task as ${OFFERABLE_PHASES.join(", ")}, never as ${event.task.phase}`);
1388
1410
  }
1389
- for (const field of ["allocationExpiresAt", "preparationEndsAt"]) {
1390
- if (event[field] !== undefined)
1391
- into.timestamp(event[field], `event.taskOffered.${field}`, `${at}.${field}`);
1392
- }
1411
+ if (event.allocationExpiresAt !== undefined)
1412
+ into.timestamp(event.allocationExpiresAt, "event.taskOffered.allocationExpiresAt", `${at}.allocationExpiresAt`);
1393
1413
  break;
1394
1414
  case "task-updated":
1395
1415
  validateTaskInto(event.task, { channel, levels, autoAcceptTasks: context.autoAcceptTasks, dialOutcomesDeclared: manifestDials(manifest) }, `${at}.task`, into);
package/guide.md CHANGED
@@ -45,7 +45,7 @@ transport, a task, a break and a call each have a word of their own:
45
45
 
46
46
  | Word | Belongs to | Values |
47
47
  | --- | --- | --- |
48
- | `phase` | A task | `pending`, `confirmed`, `preparing`, `in-progress`, `paused`, `completing` |
48
+ | `phase` | A task | `pending`, `confirmed`, `preview`, `in-progress`, `paused`, `completing` |
49
49
  | `media` | A task's audio | `started`, `ended` |
50
50
  | `transport` | A connection | `connecting`, `active`, `error` |
51
51
  | `status` | An authentication session | `signed-out`, `authenticating`, `authenticated`, `refreshing`, `expired` |
@@ -542,7 +542,7 @@ default — see **Choosing an isolation scheme**.
542
542
  type TaskPhase =
543
543
  | "pending"
544
544
  | "confirmed"
545
- | "preparing"
545
+ | "preview"
546
546
  | "in-progress"
547
547
  | "paused"
548
548
  | "completing";
@@ -647,6 +647,8 @@ type Task<C extends Channel = Channel> = {
647
647
  party?: Contact;
648
648
  phase: TaskPhase;
649
649
  acceptance?: AcceptanceMode;
650
+ previewEndsAt?: IsoTimestamp;
651
+ atDeadline?: PreviewDeadline;
650
652
  reference?: string;
651
653
  attributes?: TaskAttribute[];
652
654
  handlingHistory?: TaskHandlingHistory;
@@ -656,6 +658,8 @@ type Task<C extends Channel = Channel> = {
656
658
  : { onCall?: never; leadAssist?: never; assisting?: never; monitoring?: never; media?: never }
657
659
  );
658
660
 
661
+ type PreviewDeadline = "calls" | "expires";
662
+
659
663
  type AcceptanceMode =
660
664
  | "no-preference"
661
665
  | "consent"
@@ -665,7 +669,7 @@ type TaskOutcome =
665
669
  | { type: "completed"; by: "agent" | "provider" }
666
670
  | { type: "transferred"; destination?: string }
667
671
  | { type: "cancelled"; reason?: string }
668
- | { type: "expired"; phase: "pending" | "confirmed" | "preparing" }
672
+ | { type: "expired"; phase: "pending" | "confirmed" | "preview" }
669
673
  | { type: "left" }
670
674
  | { type: "failed"; failure: ProtocolFailure };
671
675
  ```
@@ -677,7 +681,7 @@ const TASK_COMMAND_NAMES = {
677
681
  voice: [
678
682
  "answer",
679
683
  "decline",
680
- "start-call",
684
+ "call",
681
685
  "mute",
682
686
  "hold",
683
687
  "resume",
@@ -701,7 +705,7 @@ type DispositionPayload = { disposition?: string; notes?: string };
701
705
  type VoiceTaskCommand =
702
706
  | { type: "answer" }
703
707
  | { type: "decline" }
704
- | { type: "start-call" }
708
+ | { type: "call"; dialId: DialId }
705
709
  | { type: "mute"; muted: boolean }
706
710
  | { type: "hold" }
707
711
  | { type: "resume" }
@@ -997,7 +1001,6 @@ type ProviderEvent =
997
1001
  type: "task-offered";
998
1002
  task: Task;
999
1003
  allocationExpiresAt?: IsoTimestamp;
1000
- preparationEndsAt?: IsoTimestamp;
1001
1004
  }
1002
1005
  | { type: "task-updated"; task: Task }
1003
1006
  | { type: "task-media-started"; taskId: TaskId }
@@ -1163,7 +1166,7 @@ const DEFAULT_TASK_PHASE_LABELS = {
1163
1166
  voice: {
1164
1167
  pending: "Offered",
1165
1168
  confirmed: "Accepted",
1166
- preparing: "Preview",
1169
+ preview: "Preview",
1167
1170
  "in-progress": "On Call",
1168
1171
  paused: "On Hold",
1169
1172
  completing: "After Call Work",
@@ -1171,7 +1174,7 @@ const DEFAULT_TASK_PHASE_LABELS = {
1171
1174
  chat: {
1172
1175
  pending: "Incoming Chat",
1173
1176
  confirmed: "Accepted",
1174
- preparing: "Preparing",
1177
+ preview: "Preview",
1175
1178
  "in-progress": "In Chat",
1176
1179
  paused: "Paused",
1177
1180
  completing: "Wrap-up",
@@ -1179,7 +1182,7 @@ const DEFAULT_TASK_PHASE_LABELS = {
1179
1182
  email: {
1180
1183
  pending: "Assigned",
1181
1184
  confirmed: "Accepted",
1182
- preparing: "Reviewing",
1185
+ preview: "Preview",
1183
1186
  "in-progress": "Working",
1184
1187
  paused: "Paused",
1185
1188
  completing: "Completing",
@@ -1283,7 +1286,7 @@ only where the corresponding capability is declared; an absent capability means
1283
1286
  there is no separate permission flag.
1284
1287
 
1285
1288
  The commands every task has are authorized by other fields the provider declared — a task that was
1286
- offered can be accepted, one in `preparing` can be started, one whose `completionMode` is
1289
+ offered can be accepted, one in `preview` can be called, one whose `completionMode` is
1287
1290
  `agent-command` can be completed. Nothing is issuable that the provider did not publish; only
1288
1291
  which field says so varies. See **Which commands need a capability**.
1289
1292
 
@@ -2092,7 +2095,6 @@ const allocation = {
2092
2095
  type: "task-offered",
2093
2096
  task: { ...task, phase: "pending", acceptance: "consent" },
2094
2097
  allocationExpiresAt: "2026-08-25T10:41:07.000Z",
2095
- preparationEndsAt: "2026-08-25T10:40:37.000Z",
2096
2098
  } satisfies Extract<ProviderEvent, { type: "task-offered" }>;
2097
2099
  ```
2098
2100
 
@@ -2114,10 +2116,8 @@ down and stops offering **Accept** once it passes. **Omit it unless the provider
2114
2116
  A provider that reports only elapsed ring time after the fact cannot say when an offer is due
2115
2117
  to end, and a computed value would have Omni withdraw **Accept** from a task still pending.
2116
2118
 
2117
- `preparationEndsAt` is the time available for the agent to review the task context before acting.
2118
- Omni presents the deadline with the allocation so a preview-based dialer can show how long remains
2119
- before the agent must start the contact. Reaching the timestamp does not imply a transition; the
2120
- provider reports what happens next through an event.
2119
+ A preview's deadline is not on the offer. It travels on the task, as `previewEndsAt` with
2120
+ `atDeadline`, so a snapshot carries it too -- see **Preview: the agent presses Call**.
2121
2121
 
2122
2122
  A provider may withdraw a pending task by emitting `task-ended` with a `cancelled` outcome.
2123
2123
 
@@ -2151,9 +2151,11 @@ time. Runtime conformance checks also require the task channel to match its prov
2151
2151
  | `capabilities` | Controls and workspace features available for this specific task. |
2152
2152
  | `browsers` | Named browser definitions for the task workspace: at least one when the task declares the `browsers` capability, empty when it does not. |
2153
2153
  | `party` | The person or entity on the other end of this task, as a `Contact`: often a name and one address; a withheld caller ID may leave nothing to send at all. Optional. The party is who the task is *with*; `contacts` is the directory. |
2154
- | `phase` | Current canonical task phase: `pending`, `confirmed`, `preparing`, `in-progress`, `paused`, or `completing`. |
2154
+ | `phase` | Current canonical task phase: `pending`, `confirmed`, `preview`, `in-progress`, `paused`, or `completing`. `preview` is voice only. |
2155
2155
  | `media` | Voice only. The task's real-time audio as the provider holds it: `started` while audio is attached, `ended` once it ended, omitted while none is. The provider's word — see **`task-media-started`**. |
2156
2156
  | `acceptance` | How this offer is accepted — `no-preference`, `consent`, or `automatic` — stated on the pending task so a reconnect snapshot says it too. Required while `pending` when `autoAcceptTasks` was `true`, forbidden when it was `false`, and absent past `pending`. See **Acceptance modes**. |
2157
+ | `previewEndsAt` | Voice only, in `preview`: when the system stops waiting for the agent to press Call. Absent, the agent has as long as they need. Always with `atDeadline`. See **Preview: the agent presses Call**. |
2158
+ | `atDeadline` | Voice only, in `preview`, with `previewEndsAt`: what the system does at the deadline -- `calls` places the call itself, `expires` takes the record back and the task ends `expired`. |
2157
2159
  | `reference` | Optional agent-facing reference such as a case, call, conversation, ticket, or message number. It is distinct from the protocol `id`. |
2158
2160
  | `completionMode` | `agent-command` waits for the channel's `complete` command; `provider-automatic` completes without one. |
2159
2161
  | `wrapAllowance` | Fixed time allowed to complete the task after primary handling ends. For real-time media, it begins after `task-media-ended`. Required under `provider-automatic`, where the provider acts on it. Optional under `agent-command`: omitted says the provider imposes no deadline, and Omni counts nothing down. |
@@ -2192,9 +2194,11 @@ The canonical task transitions are:
2192
2194
  | --- | --- | --- |
2193
2195
  | No task | Provider allocates a task | `pending` |
2194
2196
  | `pending` | Task is accepted | `confirmed` |
2195
- | `confirmed` | Preparation begins | `preparing` |
2196
- | `confirmed` | Work begins without preparation | `in-progress` |
2197
- | `preparing` | Agent starts the contact | `in-progress` |
2197
+ | `confirmed` | The customer's record is put in front of the agent before any call goes out (voice) | `preview` |
2198
+ | `confirmed` | Work begins | `in-progress` |
2199
+ | `preview` | Agent presses Call (`call`) and the customer answers, or the deadline `calls` and they answer | `in-progress` |
2200
+ | `preview` | The call goes out and nobody answers | `completing` |
2201
+ | `preview` | The deadline `expires` | Removed by `task-ended` with `expired` outcome |
2198
2202
  | `pending` | Provider withdraws the allocation | Removed by `task-ended` with `cancelled` outcome |
2199
2203
  | No task | Snapshot reports work already underway | `in-progress` |
2200
2204
  | `in-progress` | Provider or agent pauses the task | `paused` |
@@ -2207,9 +2211,47 @@ The canonical task transitions are:
2207
2211
 
2208
2212
  Allocation, acceptance, and progress are distinct. Acceptance follows `autoAcceptTasks` and the
2209
2213
  task's `acceptance`, moving the task from `pending` to `confirmed`. The provider reports
2210
- subsequent transitions to `preparing` or `in-progress`; Omni does not infer them from the acceptance
2214
+ subsequent transitions to `preview` or `in-progress`; Omni does not infer them from the acceptance
2211
2215
  command.
2212
2216
 
2217
+ #### Preview: the agent presses Call
2218
+
2219
+ An outbound campaign that lets the agent see who they are about to call is a **preview**: the
2220
+ record arrives as a task, the task sits in `preview` with the party on it, and no call has gone
2221
+ out. The agent reads and presses Call. That is the whole phase, and it exists on voice alone --
2222
+ chat and email have no call to place, and their reading is ordinary work in `in-progress`.
2223
+
2224
+ ```ts
2225
+ const previewed = {
2226
+ channel: "voice",
2227
+ capabilities: {},
2228
+ phase: "preview",
2229
+ party: { name: "Maya Rao", number: "+919876543210" },
2230
+ previewEndsAt: "2026-08-25T10:40:37.000Z",
2231
+ atDeadline: "calls",
2232
+ } satisfies Pick<Task<"voice">, "channel" | "capabilities" | "phase" | "party" | "previewEndsAt" | "atDeadline">;
2233
+
2234
+ // The agent presses Call. It is a dial like any other.
2235
+ const pressed: TaskCommand<"voice"> = { type: "call", dialId: "dial-7f2" };
2236
+ ```
2237
+
2238
+ **Call is a dial.** It carries the host's `dialId`, is answered `dialling`, and ends in exactly one
2239
+ `dial-outcome`; the phase is its gate and there is no capability, since a record put in front of an
2240
+ agent is there to be called. On `answered` the task is `in-progress` and its media follows on
2241
+ `task-media-started`. On any other outcome the task goes to `completing`, so the agent records the
2242
+ no-answer as the disposition it is -- in a campaign that is the commonest outcome there is, and it
2243
+ is work, not a cancellation. A `preview` task therefore needs a manifest that says how a dial ends
2244
+ (`task.preview.dialOutcomes.required`).
2245
+
2246
+ **The deadline says what the system will do.** A provider that will not wait forever states
2247
+ `previewEndsAt`, and with it `atDeadline`: `calls` means the system places the call itself when the
2248
+ time runs out, which is progressive dialling; `expires` means the record is taken back and the task
2249
+ ends with an `expired` outcome naming `preview`. The two travel together
2250
+ (`task.preview.atDeadline.required`, `task.preview.previewEndsAt.required`), only while the task is in
2251
+ `preview` (`task.preview.deadline.unexpected`), and Omni's countdown reads "calling in 12s" or
2252
+ "expires in 12s" from the provider's word. With neither, the agent has as long as they need.
2253
+ Reaching the instant is not itself a transition: the provider reports what it did, as an event.
2254
+
2213
2255
  **A task is never its audio.** A voice task is the allocation: the call is offered when it is
2214
2256
  routed to the agent and accepted as its `acceptance` dictates, and its presence and phase follow
2215
2257
  the provider's reports about the work — never the audio. Wherever audio moves — an offer, a hold, a
@@ -3707,7 +3749,7 @@ declared:
3707
3749
  | --- | --- |
3708
3750
  | `answer`, `accept` | Nothing. A task that was offered can be accepted, or offering it meant nothing. |
3709
3751
  | `decline` | The `decline` capability on any channel, **and** Omni provisioning permitting it. One word for refusing an offer, whatever the channel. |
3710
- | `start-call` | The `preparing` phase. It starts the contact a preview gave the agent time to read, so the phase is the gate and there is no capability. |
3752
+ | `call` | The `preview` phase. A record put in front of an agent is there to be called, so the phase is the gate and there is no capability. It is a dial, with a `dialId` and a `dial-outcome`. |
3711
3753
  | `complete` | `completionMode: "agent-command"`. The `dispositions` capability decides whether a code travels with the command, never whether the command exists — a task Omni cannot complete never ends. |
3712
3754
  | `connect-back` | The `connectBack` capability **and** the `completing` phase. It exists to reach the party again after the call, so it has no meaning while the call is up. |
3713
3755
  | `transfer` with `action: "warm"` | The `warmTransfer` capability. `action: "cold"` is gated by `coldTransfer`; the two are declared and offered separately. |
@@ -3785,7 +3827,7 @@ They are published as `OMNI_FAILURE_CODES`.
3785
3827
  #### Provider instants are read against a provider clock
3786
3828
 
3787
3829
  Every deadline in this contract is a provider instant that Omni counts down: `allocationExpiresAt`,
3788
- `preparationEndsAt`, and the wrap deadline of `task-media-ended` plus `wrapAllowance` where
3830
+ `previewEndsAt`, and the wrap deadline of `task-media-ended` plus `wrapAllowance` where
3789
3831
  one is stated.
3790
3832
  Comparing those against the host clock is wrong by whatever the two machines disagree by, and the
3791
3833
  damaging direction is early — **Accept** withdrawn from an offer still ringing, a wrap timer
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xema/omni-protocol",
3
- "version": "0.1.43",
3
+ "version": "0.1.44",
4
4
  "description": "The Omni protocol: the contract every provider adapter implements",
5
5
  "type": "module",
6
6
  "license": "MIT",