@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 +60 -47
- package/dist/index.js +4 -3
- package/dist/testing.d.ts +12 -12
- package/dist/testing.js +110 -107
- package/dist/validation.d.ts +1 -1
- package/dist/validation.js +108 -72
- package/guide.md +250 -210
- package/package.json +1 -1
package/guide.md
CHANGED
|
@@ -50,7 +50,7 @@ state use `status`; name the object when discussing them to avoid ambiguity.
|
|
|
50
50
|
| Word | Belongs to | Values |
|
|
51
51
|
| --- | --- | --- |
|
|
52
52
|
| `phase` | A task | `pending`, `confirmed`, `preview`, `in-progress`, `paused`, `completing` |
|
|
53
|
-
| `
|
|
53
|
+
| `audio` | A task's audio | `started`, `ended` |
|
|
54
54
|
| `transport` | A connection | `connecting`, `active`, `error` |
|
|
55
55
|
| `status` | An authentication session | `signed-out`, `authenticating`, `authenticated`, `refreshing`, `expired` |
|
|
56
56
|
| `status` | The agent’s break lifecycle | `not-requested`, `awaiting-approval`, `granted`, `starting-after-task`, `on-break` |
|
|
@@ -302,7 +302,7 @@ interface RecordingActions {
|
|
|
302
302
|
}
|
|
303
303
|
interface TaskRecordingPolicy {
|
|
304
304
|
provider?: RecordingActions;
|
|
305
|
-
host?: RecordingActions & {
|
|
305
|
+
host?: RecordingActions & { storageId: string };
|
|
306
306
|
}
|
|
307
307
|
type RecordingState =
|
|
308
308
|
| { status: "unknown"; observationId?: never; observedAt?: never; validUntil?: never; recordingId?: never }
|
|
@@ -326,7 +326,7 @@ interface HostRecordingReport {
|
|
|
326
326
|
interface HostRecording {
|
|
327
327
|
announcesToCaller?: true;
|
|
328
328
|
actions: RecordingAction[];
|
|
329
|
-
|
|
329
|
+
storageIds: string[];
|
|
330
330
|
execute(request: HostRecordingRequest): Promise<RecordingCommandResult>;
|
|
331
331
|
}
|
|
332
332
|
type RecordingCommandResult = Exclude<TaskCommandResult, { status: "dialling" }>;
|
|
@@ -479,7 +479,7 @@ type Snapshot = {
|
|
|
479
479
|
tasks: Task[];
|
|
480
480
|
taskCount: number;
|
|
481
481
|
contacts?: Contact[];
|
|
482
|
-
|
|
482
|
+
calendar?: ScheduledActivity[];
|
|
483
483
|
team?: TeamMembers;
|
|
484
484
|
};
|
|
485
485
|
|
|
@@ -710,9 +710,9 @@ type Locked = {
|
|
|
710
710
|
|
|
711
711
|
type Lockable<T> = T | Locked;
|
|
712
712
|
|
|
713
|
-
type
|
|
713
|
+
type TaskAudioState = "started" | "ended";
|
|
714
714
|
|
|
715
|
-
type CapabilitySource = "queue" | "
|
|
715
|
+
type CapabilitySource = "queue" | "nobody" | "not-yet-read";
|
|
716
716
|
|
|
717
717
|
type Task<C extends Channel = Channel> = {
|
|
718
718
|
assignmentId: AssignmentId;
|
|
@@ -732,11 +732,11 @@ type Task<C extends Channel = Channel> = {
|
|
|
732
732
|
history?: TaskHistory;
|
|
733
733
|
} & TaskCompletion & (
|
|
734
734
|
C extends "voice"
|
|
735
|
-
? { recording?: { provider?: RecordingState }; onCall?: OnCall[]; leadAssist?: TaskLeadAssist; takenOver?: TaskTakenOver;
|
|
736
|
-
: { recording?: never; onCall?: never; leadAssist?: never; takenOver?: never;
|
|
735
|
+
? { recording?: { provider?: RecordingState }; onCall?: OnCall[]; leadAssist?: TaskLeadAssist; takenOver?: TaskTakenOver; audio?: TaskAudioState }
|
|
736
|
+
: { recording?: never; onCall?: never; leadAssist?: never; takenOver?: never; audio?: never }
|
|
737
737
|
);
|
|
738
738
|
|
|
739
|
-
type PreviewDeadline = "
|
|
739
|
+
type PreviewDeadline = "provider-dials" | "host-dials" | "waits";
|
|
740
740
|
|
|
741
741
|
type AcceptanceMode =
|
|
742
742
|
| "no-preference"
|
|
@@ -759,7 +759,7 @@ const TASK_COMMAND_NAMES = {
|
|
|
759
759
|
voice: [
|
|
760
760
|
"answer",
|
|
761
761
|
"decline",
|
|
762
|
-
"
|
|
762
|
+
"dial",
|
|
763
763
|
"hold",
|
|
764
764
|
"resume",
|
|
765
765
|
"end-call",
|
|
@@ -783,7 +783,7 @@ type OutcomePayload = { outcome?: string; notes?: string };
|
|
|
783
783
|
type VoiceTaskCommand =
|
|
784
784
|
| { type: "answer" }
|
|
785
785
|
| { type: "decline" }
|
|
786
|
-
| { type: "
|
|
786
|
+
| { type: "dial"; dialId: DialId }
|
|
787
787
|
| { type: "hold" }
|
|
788
788
|
| { type: "resume" }
|
|
789
789
|
| { type: "end-call" }
|
|
@@ -968,7 +968,7 @@ type BreakEndResult =
|
|
|
968
968
|
### Team
|
|
969
969
|
|
|
970
970
|
```ts
|
|
971
|
-
type TeamMemberAvailability = "ready" | "on-task" | "on-break" | "
|
|
971
|
+
type TeamMemberAvailability = "ready" | "on-task" | "on-break" | "elsewhere" | "signed-out";
|
|
972
972
|
|
|
973
973
|
type TeamMember = {
|
|
974
974
|
id: UserId;
|
|
@@ -977,6 +977,7 @@ type TeamMember = {
|
|
|
977
977
|
break?: Extract<BreakStatus, "awaiting-approval" | "granted" | "starting-after-task">;
|
|
978
978
|
tasks?: MemberTask[];
|
|
979
979
|
listening?: MemberListening;
|
|
980
|
+
shift?: MemberShift;
|
|
980
981
|
};
|
|
981
982
|
|
|
982
983
|
type LeadRequest = {
|
|
@@ -1002,14 +1003,32 @@ type MemberTask<C extends Channel = Channel> = {
|
|
|
1002
1003
|
completionMode?: CompletionMode;
|
|
1003
1004
|
wrapAllowance?: DurationSeconds;
|
|
1004
1005
|
} & (C extends "voice"
|
|
1005
|
-
? { onCall?: OnCall[]; leadAssist?: TaskLeadAssist; takenOver?: TaskTakenOver;
|
|
1006
|
-
: { onCall?: never; leadAssist?: never; takenOver?: never;
|
|
1006
|
+
? { onCall?: OnCall[]; leadAssist?: TaskLeadAssist; takenOver?: TaskTakenOver; audio?: TaskAudioState }
|
|
1007
|
+
: { onCall?: never; leadAssist?: never; takenOver?: never; audio?: never });
|
|
1007
1008
|
|
|
1008
1009
|
type MemberListening = {
|
|
1010
|
+
assignmentId: AssignmentId;
|
|
1009
1011
|
mode: ListeningMode;
|
|
1010
1012
|
since: IsoTimestamp;
|
|
1011
1013
|
};
|
|
1012
1014
|
|
|
1015
|
+
type ShiftEventKind = "signed-in" | "signed-out" | "break-started" | "break-ended";
|
|
1016
|
+
|
|
1017
|
+
type ShiftEvent = {
|
|
1018
|
+
at: IsoTimestamp;
|
|
1019
|
+
kind: ShiftEventKind;
|
|
1020
|
+
};
|
|
1021
|
+
|
|
1022
|
+
type MemberShift = {
|
|
1023
|
+
signedInAt: IsoTimestamp;
|
|
1024
|
+
signedOutAt?: IsoTimestamp;
|
|
1025
|
+
talkSeconds?: DurationSeconds;
|
|
1026
|
+
holdSeconds?: DurationSeconds;
|
|
1027
|
+
breakSeconds?: DurationSeconds;
|
|
1028
|
+
tasksHandled?: number;
|
|
1029
|
+
events?: ShiftEvent[];
|
|
1030
|
+
};
|
|
1031
|
+
|
|
1013
1032
|
type TeamMembers = {
|
|
1014
1033
|
members: TeamMember[];
|
|
1015
1034
|
requests: LeadRequest[];
|
|
@@ -1061,20 +1080,20 @@ type TeamCommandResult =
|
|
|
1061
1080
|
| { status: "failed"; failure: ProtocolFailure };
|
|
1062
1081
|
```
|
|
1063
1082
|
|
|
1064
|
-
###
|
|
1083
|
+
### Audio
|
|
1065
1084
|
|
|
1066
1085
|
```ts
|
|
1067
|
-
type
|
|
1086
|
+
type CallAudio = {
|
|
1068
1087
|
remoteAudio: MediaStream;
|
|
1069
1088
|
setMuted(muted: boolean): void;
|
|
1070
1089
|
close(): void;
|
|
1071
1090
|
};
|
|
1072
1091
|
|
|
1073
|
-
type
|
|
1074
|
-
| { status: "opened";
|
|
1092
|
+
type OpenAudioResult =
|
|
1093
|
+
| { status: "opened"; audio: CallAudio }
|
|
1075
1094
|
| { status: "unavailable"; failure: ProtocolFailure };
|
|
1076
1095
|
|
|
1077
|
-
type
|
|
1096
|
+
type OpenAudioRequest = {
|
|
1078
1097
|
assignmentId: AssignmentId;
|
|
1079
1098
|
localAudio?: MediaStream;
|
|
1080
1099
|
};
|
|
@@ -1106,10 +1125,8 @@ type ProviderEvent =
|
|
|
1106
1125
|
assignmentExpiresAt?: IsoTimestamp;
|
|
1107
1126
|
}
|
|
1108
1127
|
| { type: "task-updated"; task: Task }
|
|
1109
|
-
| { type: "task-
|
|
1110
|
-
| { type: "task-
|
|
1111
|
-
| { type: "team-media-started"; memberId: UserId; assignmentId: AssignmentId }
|
|
1112
|
-
| { type: "team-media-ended"; memberId: UserId; assignmentId: AssignmentId }
|
|
1128
|
+
| { type: "task-audio-started"; assignmentId: AssignmentId }
|
|
1129
|
+
| { type: "task-audio-ended"; assignmentId: AssignmentId }
|
|
1113
1130
|
| { type: "task-ended"; assignmentId: AssignmentId; outcome: TaskOutcome }
|
|
1114
1131
|
| { type: "dial-outcome"; dialId: DialId; outcome: DialOutcome; assignmentId?: AssignmentId; destinationId?: string; reason?: string }
|
|
1115
1132
|
| { type: "announcement"; text: string; html?: string; announcedAt: IsoTimestamp; expiresAt?: IsoTimestamp }
|
|
@@ -1117,7 +1134,7 @@ type ProviderEvent =
|
|
|
1117
1134
|
| { type: "diagnostic"; expected: string; observed: string; assignmentId?: AssignmentId }
|
|
1118
1135
|
| { type: "team-updated"; team: TeamMembers }
|
|
1119
1136
|
| { type: "contacts-updated"; contacts: Contact[] }
|
|
1120
|
-
| { type: "calendar-updated";
|
|
1137
|
+
| { type: "calendar-updated"; calendar: ScheduledActivity[] };
|
|
1121
1138
|
|
|
1122
1139
|
type ProviderEventEnvelope = {
|
|
1123
1140
|
id: string;
|
|
@@ -1172,7 +1189,7 @@ type Connection<C extends Channel = Channel> = {
|
|
|
1172
1189
|
endBreak?(): Promise<BreakEndResult>;
|
|
1173
1190
|
|
|
1174
1191
|
executeTeam?(request: TeamCommandRequest): Promise<TeamCommandResult>;
|
|
1175
|
-
|
|
1192
|
+
openAudio?(request: OpenAudioRequest): Promise<OpenAudioResult>;
|
|
1176
1193
|
setPreference?(request: SetPreferenceRequest): Promise<PreferenceResult>;
|
|
1177
1194
|
recordStep?(report: HistoryReport): Promise<HistoryReportResult>;
|
|
1178
1195
|
};
|
|
@@ -1531,7 +1548,7 @@ Request/response polling does not have those properties and is not a transport f
|
|
|
1531
1548
|
|
|
1532
1549
|
Everything an adapter publishes about a task is derived from what the platform states, or kept in
|
|
1533
1550
|
the login's store where the platform cannot hold it. Per offer, the `assignmentId` comes from what
|
|
1534
|
-
the offer itself states; per call, the
|
|
1551
|
+
the offer itself states; per call, the audio follows `task-audio-started` and `task-audio-ended`,
|
|
1535
1552
|
never a flag set when the audio was opened; per session, the phone the agent holds is what the
|
|
1536
1553
|
platform restates on activation, not a mapping read once over HTTP. What the platform cannot hold
|
|
1537
1554
|
-- an agent application's muted leg -- goes in `ConnectContext.store`, keyed by the task and gone with it.
|
|
@@ -1539,11 +1556,11 @@ platform restates on activation, not a mapping read once over HTTP. What the pla
|
|
|
1539
1556
|
A fact read once over a connection and kept in the adapter object dies with the client. An agent application
|
|
1540
1557
|
reload is the first client dying and a second coming up for the same login, and the second
|
|
1541
1558
|
publishes the same truth about the same task as the first did, or the first was publishing
|
|
1542
|
-
something it made up: an assignment minted for the connection,
|
|
1559
|
+
something it made up: an assignment minted for the connection, audio remembered as a boolean, a
|
|
1543
1560
|
phone the second never read. Three pieces of exactly that state were found in one adapter in an
|
|
1544
1561
|
afternoon, and none of it was visible until the adapter was built twice. The drive's `rebuild` is the test of this
|
|
1545
1562
|
principle: given a way to build the adapter again, the run hands the login over and holds the
|
|
1546
|
-
second to what the first published (`drive.reload.snapshot`, `.history`, `.
|
|
1563
|
+
second to what the first published (`drive.reload.snapshot`, `.history`, `.openAudio`, and the
|
|
1547
1564
|
stream's own rules across the resync). An adapter that cannot be built twice against its platform
|
|
1548
1565
|
and publish the same task is not a conformant adapter yet, whatever a single run says.
|
|
1549
1566
|
|
|
@@ -1753,7 +1770,7 @@ contributions from active providers into one agent-facing calendar while retaini
|
|
|
1753
1770
|
provider identity for each activity.
|
|
1754
1771
|
|
|
1755
1772
|
When declared, the provider publishes its authoritative list through
|
|
1756
|
-
`Snapshot.
|
|
1773
|
+
`Snapshot.calendar` and replaces it with a `calendar-updated` event when it
|
|
1757
1774
|
changes.
|
|
1758
1775
|
|
|
1759
1776
|
| Field | Contract |
|
|
@@ -2127,13 +2144,13 @@ a capability it agrees with the login: a lead's snapshot carries `team`, nobody
|
|
|
2127
2144
|
| `tasks` | Complete set of tasks currently offered to or owned by this agent. |
|
|
2128
2145
|
| `taskCount` | The provider's own count of those tasks, stated rather than inferred, and it must equal `tasks.length`. A snapshot with no work says `taskCount: 0` in so many words — a blank or unanswered state lacks the count and cannot pass as a confirmed empty. |
|
|
2129
2146
|
| `contacts` | Required complete contact contribution when the manifest declares `contacts`; `[]` clears it. Omitted only when it does not. |
|
|
2130
|
-
| `
|
|
2147
|
+
| `calendar` | Required complete calendar contribution when the manifest declares `calendar`; `[]` clears it. Omitted only when it does not. |
|
|
2131
2148
|
| `team` | Required `TeamMembers` when the login declares `capabilities.lead` and the lead has the team feature on, `members: []` when nobody is in it. Forbidden otherwise — the login is the permission, and a lead who turned the feature off gets nothing of the team. |
|
|
2132
2149
|
|
|
2133
2150
|
## Live connection
|
|
2134
2151
|
|
|
2135
2152
|
`Connection` is what `connect()` returns. Its methods are documented in the sections that follow
|
|
2136
|
-
and under **Breaks**, **Team leads**, **Real-time
|
|
2153
|
+
and under **Breaks**, **Team leads**, **Real-time audio** and **Task commands**; this is the whole
|
|
2137
2154
|
surface in one place, and what obliges an adapter to implement each one.
|
|
2138
2155
|
|
|
2139
2156
|
| Method | Implement it when |
|
|
@@ -2154,7 +2171,7 @@ surface in one place, and what obliges an adapter to implement each one.
|
|
|
2154
2171
|
| `executeTeam(request)` | The login declares `capabilities.lead`: every lead act, on the team surface and nowhere else. See **Lead commands**. |
|
|
2155
2172
|
| `setPreference(request)` | The login declares `capabilities.preferences`: the person's choice has to have somewhere to go. |
|
|
2156
2173
|
| `recordStep(report)` | The manifest lists `softphone` among its `phones`. On a softphone the agent application mutes its own microphone on any call, and the provider's record has to have somewhere to take that leg; a desk phone's microphone is the phone's. See **The agent application records what it performs**. |
|
|
2157
|
-
| `
|
|
2174
|
+
| `openAudio(request)` | The manifest lists `softphone` among its `phones`. On a softphone the call's audio lands in Omni, so the adapter has to open it; a platform of desk phones alone never does. |
|
|
2158
2175
|
|
|
2159
2176
|
**The four break methods stand or fall together.** Declaring `capabilities.breaks` at login and then
|
|
2160
2177
|
implementing `requestBreak` without `commitBreak` leaves an agent granted a break that can never
|
|
@@ -2168,7 +2185,7 @@ Returns the provider's complete authoritative state at one point in time.
|
|
|
2168
2185
|
- Omni registers `subscribe()` before awaiting the initial snapshot. An event delivered while the
|
|
2169
2186
|
snapshot is read is held until it lands, and what happens to it then depends on what it is. A
|
|
2170
2187
|
snapshot restates state, so an event of a state-replacing kind -- `task-offered`, `task-updated`,
|
|
2171
|
-
`task-ended`, `task-
|
|
2188
|
+
`task-ended`, `task-audio-started`, `task-audio-ended`, `break-state`, `team-updated`,
|
|
2172
2189
|
`contacts-updated`, `calendar-updated`, `transport-status`, `snapshot` -- is dropped, because the
|
|
2173
2190
|
snapshot accounts for it. An event that reports a transaction no snapshot carries --
|
|
2174
2191
|
`dial-outcome`, `diagnostic`, `announcement`, `queue-summary` -- is applied after the snapshot,
|
|
@@ -2224,7 +2241,7 @@ Stops the connection and releases adapter-owned resources.
|
|
|
2224
2241
|
|
|
2225
2242
|
- Must be safe after partial startup and safe to call once during normal shutdown.
|
|
2226
2243
|
- Must stop automatic reconnect.
|
|
2227
|
-
- Must remove event handlers and release
|
|
2244
|
+
- Must remove event handlers and release audio resources owned by the adapter.
|
|
2228
2245
|
- Does not imply that active tasks were completed or removed.
|
|
2229
2246
|
|
|
2230
2247
|
## Task assignment lifecycle
|
|
@@ -2310,7 +2327,7 @@ const assignment = {
|
|
|
2310
2327
|
```
|
|
2311
2328
|
|
|
2312
2329
|
The rule the phase exists to express: **nothing is acquired on the agent's behalf while a task
|
|
2313
|
-
is pending.** An agent application that carries
|
|
2330
|
+
is pending.** An agent application that carries audio must not open the microphone until the task is
|
|
2314
2331
|
accepted. Omni does not open the task's browsers either — a task that rings out costs nothing.
|
|
2315
2332
|
|
|
2316
2333
|
When consent is required, Omni offers the agent an **Accept** control. The call is the
|
|
@@ -2362,27 +2379,27 @@ time. Runtime conformance checks also require the task channel to match its prov
|
|
|
2362
2379
|
|
|
2363
2380
|
| Field | Contract |
|
|
2364
2381
|
| --- | --- |
|
|
2365
|
-
| `assignmentId` | Required `AssignmentId`: the assignment this task is the record of, and its one identity. Issued by the provider, unique within the provider, never reused. Every event, command and report that names a task names it by this, so a late dial outcome or a late history report for one customer never lands on the next. The stream refuses an assignment introduced twice (`stream.taskOffered.duplicate`) and
|
|
2382
|
+
| `assignmentId` | Required `AssignmentId`: the assignment this task is the record of, and its one identity. Issued by the provider, unique within the provider, never reused. Every event, command and report that names a task names it by this, so a late dial outcome or a late history report for one customer never lands on the next. The stream refuses an assignment introduced twice (`stream.taskOffered.duplicate`) and an audio or ending event naming one that has ended or that nobody has seen (`stream.assignment.ended`, `.unknown`); a `dial-outcome` may name an ended assignment, since a dial placed late routinely outlives its call, and the agent application routes it there. See **A task's life on the wire**. |
|
|
2366
2383
|
| `title` | Agent-facing task title. |
|
|
2367
2384
|
| `channel` | Channel used for this task. It must equal the source provider's manifest channel. |
|
|
2368
2385
|
| `taskType` | Required provider-defined source or category of work, such as a voice `Queue Name`, `Mailbox Folder`, `Chat Source`, `Support`, `Billing`, or `Returns`. |
|
|
2369
2386
|
| `capabilities` | Controls and workspace features available for this specific task. |
|
|
2370
|
-
| `capabilitySource` | Required. Who chose the capabilities: `queue` when somebody configured these terms, `
|
|
2387
|
+
| `capabilitySource` | Required. Who chose the capabilities: `queue` when somebody configured these terms, `nobody` when nothing handed the work over -- an agent's own outbound -- and `not-yet-read` when a queue was named and its terms could not be read, in which case `capabilities` is what the provider will honour, not what the platform permits. An agent application shows `not-yet-read` where the agent works. See **Task capabilities**. |
|
|
2371
2388
|
| `browsers` | Named browser definitions for the task workspace: at least one when the task declares the `browsers` capability, empty when it does not. |
|
|
2372
2389
|
| `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. |
|
|
2373
2390
|
| `phase` | Current canonical task phase: `pending`, `confirmed`, `preview`, `in-progress`, `paused`, or `completing`. `preview` is voice only. |
|
|
2374
|
-
| `
|
|
2391
|
+
| `audio` | 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-audio-started`**. Audio names a task whose work has begun, or whose party the agent application is dialling: on a `pending`, `confirmed` or `preview` task with nobody ringing it is refused (`task.audio.beforeWork`), on a snapshot as on the event, since an agent application opens the microphone on it; with the party ringing by an agent application dial or provider-triggered preview dial, actual ring-back audio may precede answer. |
|
|
2375
2392
|
| `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**. |
|
|
2376
2393
|
| `previewEndsAt` | Voice only, in `preview`: the preparation target instant. Absent, the agent has as long as they need without a preparation countdown. Always with `atDeadline`. See **Preview: the agent presses Call**. |
|
|
2377
|
-
| `atDeadline` | Voice only, in `preview`, with `previewEndsAt`: what the system does at the deadline -- `
|
|
2394
|
+
| `atDeadline` | Voice only, in `preview`, with `previewEndsAt`: what the system does at the deadline -- `provider-dials` makes the provider initiate dialing, `host-dials` makes the agent application issue Call, `waits` keeps the task in preview awaiting the agent. |
|
|
2378
2395
|
| `reference` | Optional agent-facing reference such as a case, call, conversation, ticket, or message number. It is distinct from the protocol `id`. |
|
|
2379
2396
|
| `completionMode` | `agent-command` waits for the channel's `complete` command; `provider-automatic` completes without one. |
|
|
2380
|
-
| `wrapAllowance` | Fixed time allowed to complete the task after primary interaction ends. For real-time
|
|
2397
|
+
| `wrapAllowance` | Fixed time allowed to complete the task after primary interaction ends. For real-time audio, it begins after `task-audio-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. |
|
|
2381
2398
|
| `attributes` | Optional ordered, typed `TaskAttribute` entries with keys unique within the task. Each contact or timestamp is a separate array item; new attribute shapes require new union members. |
|
|
2382
2399
|
| `history` | The call record: `steps` — the ordered interaction history of this open task, one entry per occurrence, oldest first — and what they add up to before this agent, `interactionSeconds`, `holdSeconds`, `queueSeconds`, `transfers`, each present when the provider knows it. Live task data restated with the task, not a permanent archive. See **Interaction history**. |
|
|
2383
2400
|
| `onCall` | Voice only. Who is on the call, or being brought onto it, as the provider states it, replaced whole with the task: `party` is the customer -- carrying a `stage` while being dialled again on the same task, a connect-back with the agent application's `dialId` or a platform's callback without, ringing from the moment the dial is placed and joined on its answered outcome --, `agent` a person by user id, `consulted` and `conferenced` somebody a dial is bringing in, listed from the moment the dial is placed -- with the `destinationId` dialled, the `dialId` where an agent application placed it, the `stage` reached (`ringing` until answered, `joined` after), and `held: true` on anyone joined and parked. A `consulted` entry is what makes `transfer` `complete` and `cancel` issuable. `label` names a destination -- a person, a queue -- not a phrase; the agent application supplies the verb. Present when the provider knows the room, absent when it does not. See **Every dial has an outcome**. |
|
|
2384
2401
|
| `leadAssist` | Voice only. Present from the agent's request for a lead until the lead leaves or the request ends: `requested` while nobody has joined, `joined` with the lead's `leadId` once somebody has. See **Lead assist**. |
|
|
2385
|
-
| `takenOver` | Voice only, on a task that reached this agent by a lead's take-over: which member the call was taken from, and when. It is an ordinary assignment with the call's history, offered whatever break the lead is on and counted against
|
|
2402
|
+
| `takenOver` | Voice only, on a task that reached this agent by a lead's take-over: which member the call was taken from, and when. It is an ordinary assignment with the call's history, offered whatever break the lead is on and counted against their capacity like any other call. See **Lead assist**. |
|
|
2386
2403
|
|
|
2387
2404
|
`TaskAttribute` entries carry typed detail alongside the task:
|
|
2388
2405
|
|
|
@@ -2414,7 +2431,7 @@ The canonical task transitions are:
|
|
|
2414
2431
|
| `pending` | Task is accepted | `confirmed` |
|
|
2415
2432
|
| `confirmed` | The customer's record is put in front of the agent before any call goes out (voice) | `preview` |
|
|
2416
2433
|
| `confirmed` | Work begins | `in-progress` |
|
|
2417
|
-
| `preview` | Agent presses Call (`
|
|
2434
|
+
| `preview` | Agent presses Call (`dial`) and the customer answers, or the deadline `provider-dials` and they answer | `in-progress` |
|
|
2418
2435
|
| `preview` | The call goes out and nobody answers | `completing` |
|
|
2419
2436
|
| `preview` | The preparation target passes with `atDeadline: "waits"` | Remains `preview`, waiting for the agent to press Call |
|
|
2420
2437
|
| `pending` | Provider withdraws the assignment, the agent declines, or the party abandons the ring | Removed by `task-ended` with `cancelled` outcome, `by` saying which |
|
|
@@ -2433,7 +2450,7 @@ The canonical task transitions are:
|
|
|
2433
2450
|
A task has one name, the assignment it is the record of, and the provider issues it. Platforms
|
|
2434
2451
|
reuse their own handles: a closed one is retired minutes later, a requeue takes seconds, and on
|
|
2435
2452
|
one platform a call was offered twice, thirteen seconds apart, under one handle, through a close
|
|
2436
|
-
and a re-offer. Everything that names a task after the fact -- a dial outcome,
|
|
2453
|
+
and a re-offer. Everything that names a task after the fact -- a dial outcome, an audio event, an
|
|
2437
2454
|
ending, a history report, a command -- would land on whichever assignment is open when it arrives.
|
|
2438
2455
|
So the assignment id is unique within the provider and never reused: where the platform's handle
|
|
2439
2456
|
never comes back, the adapter passes it through; where it does, the adapter mints the assignment id
|
|
@@ -2442,7 +2459,7 @@ belongs to or is refused, and a task-scoped browser session (`PROVIDER_NAME__ASS
|
|
|
2442
2459
|
lives one assignment, never the next customer's cookies. A dial the agent application placed was
|
|
2443
2460
|
already safe, since its outcome is placed by the agent application's own `dialId`; where the
|
|
2444
2461
|
assignment earns its place is everything the agent application does not mint --
|
|
2445
|
-
`task-
|
|
2462
|
+
`task-audio-started`, `task-audio-ended`, `task-ended`, a `recordStep` naming a task -- any of which
|
|
2446
2463
|
would otherwise find the next customer under a reused handle and act on them. A minted id is part
|
|
2447
2464
|
of the task the adapter keeps, not a field held in memory beside it: it lives in the login's
|
|
2448
2465
|
`store` with the task, or is derived from a platform handle that itself never reuses, so a rebuilt
|
|
@@ -2469,19 +2486,19 @@ const previewed = {
|
|
|
2469
2486
|
phase: "preview",
|
|
2470
2487
|
party: { name: "Maya Rao", number: "+919876543210" },
|
|
2471
2488
|
previewEndsAt: "2026-08-25T10:40:37.000Z",
|
|
2472
|
-
atDeadline: "
|
|
2489
|
+
atDeadline: "provider-dials",
|
|
2473
2490
|
} satisfies Pick<Task<"voice">, "channel" | "capabilities" | "phase" | "party" | "previewEndsAt" | "atDeadline">;
|
|
2474
2491
|
|
|
2475
2492
|
// The agent presses Call. It is a dial like any other.
|
|
2476
|
-
const pressed: TaskCommand<"voice"> = { type: "
|
|
2493
|
+
const pressed: TaskCommand<"voice"> = { type: "dial", dialId: "dial-7f2" };
|
|
2477
2494
|
```
|
|
2478
2495
|
|
|
2479
2496
|
**Call is a dial.** It carries the agent application's `dialId`, is answered `dialling`, and ends in exactly one
|
|
2480
2497
|
`dial-outcome`; the phase is its gate and there is no capability, since a record put in front of an
|
|
2481
|
-
agent is there to be called. Its
|
|
2482
|
-
ring-back is audio the agent hears -- so `task-
|
|
2483
|
-
its party ringing. On `answered` the task is `in-progress`; on any other outcome the
|
|
2484
|
-
`task-
|
|
2498
|
+
agent is there to be called. Its audio starts on `dialling`, as every agent application-placed dial's does --
|
|
2499
|
+
ring-back is audio the agent hears -- so `task-audio-started` arrives on the `preview` task with
|
|
2500
|
+
its party ringing. On `answered` the task is `in-progress`; on any other outcome the audio ends,
|
|
2501
|
+
`task-audio-ended` starts the wrap clock as on every voice task, and the task goes to
|
|
2485
2502
|
`completing`, so the agent records the
|
|
2486
2503
|
no-answer as the outcome it is -- in a campaign that is the commonest outcome there is, and it
|
|
2487
2504
|
is work, not a cancellation. A `preview` task therefore needs a manifest that says how a dial ends
|
|
@@ -2493,8 +2510,8 @@ carries the provider's authoritative preparation-end instant and exactly one tri
|
|
|
2493
2510
|
|
|
2494
2511
|
| Declaration | At preparation end |
|
|
2495
2512
|
| --- | --- |
|
|
2496
|
-
| `atDeadline: "
|
|
2497
|
-
| `atDeadline: "host-
|
|
2513
|
+
| `atDeadline: "provider-dials"` | The provider initiates dialing. The agent application never sends a timer-triggered Call. |
|
|
2514
|
+
| `atDeadline: "host-dials"` | The agent application sends the ordinary `dial` command with a fresh agent application `dialId`. The provider does not independently auto-dial this task. |
|
|
2498
2515
|
| `atDeadline: "waits"` | Neither side auto-dials. The task remains in preview until the agent presses Call. |
|
|
2499
2516
|
|
|
2500
2517
|
The agent may press Call early in either fixed-preparation dialing mode, or at any time in
|
|
@@ -2502,7 +2519,7 @@ The agent may press Call early in either fixed-preparation dialing mode, or at a
|
|
|
2502
2519
|
zero the UI says "Waiting for agent" and keeps Call available. There is no automatic task-ended,
|
|
2503
2520
|
completion, dialing or phase change. No repeating timer action occurs on later snapshots. This differs
|
|
2504
2521
|
from unlimited preparation only by displaying a preparation target. The former `expires` deadline
|
|
2505
|
-
value is not supported; elapsed preparation must not withdraw the task. For `
|
|
2522
|
+
value is not supported; elapsed preparation must not withdraw the task. For `provider-dials` and `host-dials`, the deadline ends
|
|
2506
2523
|
preparation and triggers initiation; it does not promise ringing, playable audio or customer
|
|
2507
2524
|
answer at that exact instant. Scheduling/dispatch delay must remain visible, and a failed or
|
|
2508
2525
|
prevented initiation must be reported rather than leaving a silently expired countdown.
|
|
@@ -2513,7 +2530,7 @@ time; clock estimation is allowed only under the explicit agent application-trig
|
|
|
2513
2530
|
Without usable provider-domain time
|
|
2514
2531
|
it cannot safely auto-trigger and must expose the uncertainty and reconcile. No zero-duration,
|
|
2515
2532
|
receipt-time or local-default deadline is inferred. Neither elapsed time nor submission changes
|
|
2516
|
-
the task phase: subsequent authoritative events state ringing, answer,
|
|
2533
|
+
the task phase: subsequent authoritative events state ringing, answer, audio and completion.
|
|
2517
2534
|
|
|
2518
2535
|
An agent application must serialize manual clicks and its timer under the exact provider/login/task/assignment
|
|
2519
2536
|
scope, allowing at most one unresolved submission. Recheck the current phase, deadline owner,
|
|
@@ -2527,11 +2544,11 @@ command as ownership of an already-running independent attempt.
|
|
|
2527
2544
|
Agent application-triggered automatic dialing is an ordinary agent application dial: its result is `dialling` with the same
|
|
2528
2545
|
agent application `dialId`, followed by exactly one correlated terminal `dial-outcome`. A provider-triggered
|
|
2529
2546
|
dial has no invented agent application ID or agent application dial outcome. It reports source-evidenced party ringing,
|
|
2530
|
-
actual
|
|
2531
|
-
party without an agent application ID may carry actual pre-answer
|
|
2532
|
-
|
|
2547
|
+
actual audio and customer answer separately. In preview with `atDeadline: "provider-dials"`, a ringing
|
|
2548
|
+
party without an agent application ID may carry actual pre-answer audio; the deadline alone permits no audio.
|
|
2549
|
+
Audio is still introduced/ended by the corresponding events and must not imply customer answer.
|
|
2533
2550
|
Submission acceptance is never an answered outcome or permission to publish `in-progress` early.
|
|
2534
|
-
Unsupported source correlation/outcome/
|
|
2551
|
+
Unsupported source correlation/outcome/audio evidence remains an integration blocker.
|
|
2535
2552
|
|
|
2536
2553
|
**Drop both fields when the phase moves.** A provider that builds the `in-progress` task by
|
|
2537
2554
|
spreading the `preview` one carries `previewEndsAt` and `atDeadline` with it, and the agent application refuses
|
|
@@ -2549,7 +2566,7 @@ recording contract; this distinction does not remove it or move provider call op
|
|
|
2549
2566
|
|
|
2550
2567
|
A caller journey may revisit the same agent while an earlier interaction still wraps. Each is its
|
|
2551
2568
|
own assignment, and two open at once carry two assignment ids. Restore them unchanged on
|
|
2552
|
-
reconnect. Commands,
|
|
2569
|
+
reconnect. Commands, audio, endings and history reports name the exact assignment; never retarget
|
|
2553
2570
|
an old command to the newest interaction of a journey. `validateTaskCommandRequest` checks the
|
|
2554
2571
|
request's assignment against the supplied published task and then its command prerequisites. The
|
|
2555
2572
|
caller must select that task within the correct provider/login and recheck at the provider; the
|
|
@@ -2559,33 +2576,33 @@ portion must not be cleared because this task ended. Snapshot counts include all
|
|
|
2559
2576
|
and active interactions; task completion is not caller hangup.
|
|
2560
2577
|
|
|
2561
2578
|
**Voice describes the agent's interaction within the wider call.** The task defines that agent's
|
|
2562
|
-
workspace, tools, permissions,
|
|
2579
|
+
workspace, tools, permissions, audio participation and completion work. The caller's channel may
|
|
2563
2580
|
continue through IVR, queues, other agents, holds or conferences while this interaction ends or wraps.
|
|
2564
2581
|
Completing this task ends this interaction responsibility; it is not evidence that the caller's channel
|
|
2565
|
-
or journey ended. Task
|
|
2582
|
+
or journey ended. Task audio describes this agent's attachment, not the lifetime of every party's
|
|
2566
2583
|
connection. Commands retain their explicit targets and effects; task completion must not silently
|
|
2567
2584
|
become a caller-disconnect operation.
|
|
2568
2585
|
|
|
2569
2586
|
**A task is never its audio.** A voice task represents the agent’s interaction: the call is offered when it is
|
|
2570
2587
|
routed to the agent and accepted as its `acceptance` dictates, and its presence and phase follow
|
|
2571
2588
|
the provider's reports about the work — never the audio. Wherever audio moves — an offer, a hold, a
|
|
2572
|
-
consult, a conference leg joining or leaving, a transfer, a connect-back — the
|
|
2573
|
-
separately, arriving on `task-
|
|
2574
|
-
`task-
|
|
2589
|
+
consult, a conference leg joining or leaving, a transfer, a connect-back — the audio follows
|
|
2590
|
+
separately, arriving on `task-audio-started`, attaching through `openAudio` and ending with
|
|
2591
|
+
`task-audio-ended`. Omni does not ring,
|
|
2575
2592
|
bridge, or hold a line. How the phone rings, whether it rings at all, and where legs join and leave
|
|
2576
2593
|
are the adapter's and the platform's, transient, and decide neither when a task exists nor what
|
|
2577
2594
|
phase it is in.
|
|
2578
2595
|
|
|
2579
|
-
The line runs between the provider's word and Omni's own senses. `task-
|
|
2596
|
+
The line runs between the provider's word and Omni's own senses. `task-audio-ended` is the
|
|
2580
2597
|
provider's report that primary interaction ended — a fact about the work, which is why the completion
|
|
2581
2598
|
allowance starts on it and the Connect back control appears on it — and Omni follows that report as it
|
|
2582
|
-
follows any other. What Omni never does is derive a task's state from its own
|
|
2599
|
+
follows any other. What Omni never does is derive a task's state from its own audio session: a
|
|
2583
2600
|
stream that drops, a track that ends, a transport that disconnects, a microphone that fails, an
|
|
2584
2601
|
endpoint re-registering change nothing about the task until the provider says so. Structurally:
|
|
2585
|
-
`task-
|
|
2586
|
-
only where it arrived, what follows the
|
|
2602
|
+
`task-audio-started` and `task-audio-ended` alternate on a task whose work has begun, audio ends
|
|
2603
|
+
only where it arrived, what follows the audio ending is `completing` or `task-ended`, and every
|
|
2587
2604
|
task is introduced once — `exerciseAdapter` holds the stream to that from the connect snapshot on,
|
|
2588
|
-
and `
|
|
2605
|
+
and `assertAudioFollowsTheTask` holds any sequence.
|
|
2589
2606
|
|
|
2590
2607
|
#### Completion timing
|
|
2591
2608
|
|
|
@@ -2594,16 +2611,16 @@ the task open until Omni sends the channel's `complete` command. With `provider-
|
|
|
2594
2611
|
provider may complete the task without receiving that command.
|
|
2595
2612
|
|
|
2596
2613
|
`wrapAllowance` is independent of that decision. It is fixed, and when it starts depends on
|
|
2597
|
-
whether the channel carries real-time
|
|
2614
|
+
whether the channel carries real-time audio:
|
|
2598
2615
|
|
|
2599
2616
|
| Channel | Wrap allowance starts at |
|
|
2600
2617
|
| --- | --- |
|
|
2601
|
-
| Voice and any channel with real-time
|
|
2618
|
+
| Voice and any channel with real-time audio | The `task-audio-ended` event |
|
|
2602
2619
|
| Chat | The `task-updated` that moves the task to `completing`: the provider's word that the conversation is closed on its side, the allowance running from that publication's `occurredAt` |
|
|
2603
2620
|
| Email | The `task-updated` that moves the task to `completing`: the provider's word that the platform has accepted the outgoing message, the allowance running from that publication's `occurredAt` |
|
|
2604
|
-
| Other non-
|
|
2621
|
+
| Other non-audio channels | The `task-updated` that moves the task to `completing` |
|
|
2605
2622
|
|
|
2606
|
-
**Off voice, `completing` is the provider's word that interaction ended.** There is no
|
|
2623
|
+
**Off voice, `completing` is the provider's word that interaction ended.** There is no audio event to
|
|
2607
2624
|
carry it, so the phase does; an agent application starts the wrap clock at that publication and nowhere else. It
|
|
2608
2625
|
is optional: a conversation with nothing to wrap moves from `in-progress` to `task-ended` and
|
|
2609
2626
|
`completing` is never published. Under `provider-automatic` with a non-zero `wrapAllowance` it is
|
|
@@ -2638,9 +2655,9 @@ const untimedWrap = {
|
|
|
2638
2655
|
} satisfies Pick<Task<"voice">, "completionMode" | "wrapAllowance">;
|
|
2639
2656
|
```
|
|
2640
2657
|
|
|
2641
|
-
Here the customer has hung up, `task-
|
|
2658
|
+
Here the customer has hung up, `task-audio-ended` has been sent on time, the task is `completing`,
|
|
2642
2659
|
and the agent takes as long as the work needs. Moving the task to `completing` late to avoid a
|
|
2643
|
-
deadline is not an alternative on
|
|
2660
|
+
deadline is not an alternative on an audio channel: the clock starts at a real event, and delaying
|
|
2644
2661
|
that event would falsify the phase and everything timed from it.
|
|
2645
2662
|
|
|
2646
2663
|
#### Connecting back during completion
|
|
@@ -2655,9 +2672,9 @@ is; the command carries no destination, and it is a dial like any other, so it c
|
|
|
2655
2672
|
|
|
2656
2673
|
On `dialling` the provider is placing the call and the task returns to `in-progress`: the agent is
|
|
2657
2674
|
working again, and the wrap allowance is **discarded, not paused**. From there the call is
|
|
2658
|
-
reported as any call is -- `paused`, `in-progress`, and when its
|
|
2675
|
+
reported as any call is -- `paused`, `in-progress`, and when its audio ends, `task-audio-ended`
|
|
2659
2676
|
again, which starts a fresh allowance from that instant. A party who does not answer is a dial
|
|
2660
|
-
whose outcome says so and a call whose
|
|
2677
|
+
whose outcome says so and a call whose audio ended: the task returns to `completing` through the
|
|
2661
2678
|
same event and the clock starts again from there. At no point is an agent dialling against a
|
|
2662
2679
|
deadline.
|
|
2663
2680
|
|
|
@@ -2693,7 +2710,7 @@ const connectBackCapable = {
|
|
|
2693
2710
|
With ten seconds of the thirty left, the agent presses Connect back: `execute({ command: { type:
|
|
2694
2711
|
"connect-back", dialId } })` returns `dialling`, the task is `in-progress`, and the thirty seconds
|
|
2695
2712
|
are gone.
|
|
2696
|
-
The second call ends: `task-
|
|
2713
|
+
The second call ends: `task-audio-ended`, the task is `completing`, and a new thirty seconds runs
|
|
2697
2714
|
from that instant.
|
|
2698
2715
|
|
|
2699
2716
|
```ts
|
|
@@ -2729,6 +2746,12 @@ Migration from the earlier spellings:
|
|
|
2729
2746
|
| Task.id, TaskId, and `taskId` on every event, command, report and lead request | gone: a task is named by `assignmentId` alone, and `assignmentKey(providerId, assignmentId)` scopes it |
|
|
2730
2747
|
| taskKey, omni.task-not-found, PROVIDER_NAME__TASK_ID__TAB_NAME (ProviderName.TaskId.TabName) | `assignmentKey`, `omni.assignment-not-found`, `PROVIDER_NAME__ASSIGNMENT_ID__TAB_NAME` (`ProviderName.AssignmentId.TabName`) |
|
|
2731
2748
|
| Manifest.disposalSettleMs | `Manifest.completionSettleMs` |
|
|
2749
|
+
| the call command, atDeadline calls and host-calls | `dial`, `"provider-dials"`, `"host-dials"` |
|
|
2750
|
+
| media: task-media-started/-ended, team-media-*, Task.media, TaskMediaState, openMedia, OpenMediaRequest/Result, VoiceMediaSession and its session field | audio: `task-audio-started`, `task-audio-ended`, `audio` on the task, `TaskAudioState`, `openAudio`, `OpenAudioRequest`, `OpenAudioResult`, `CallAudio` on the result's `audio`; there is no team audio, the lead's follows `listening` on the member |
|
|
2751
|
+
| capabilitySource values ungoverned and undetermined | `nobody`, `not-yet-read` (`capabilitySource.notYetRead`) |
|
|
2752
|
+
| Snapshot.scheduledActivities, and scheduledActivities on calendar-updated | `calendar` in both |
|
|
2753
|
+
| availability reserved | `elsewhere` |
|
|
2754
|
+
| recording destinationId, HostRecording.destinationIds | `storageId`, `storageIds` (`recording.storage`, `recording.host.storage`) |
|
|
2732
2755
|
|
|
2733
2756
|
Update producers, consumers, saved task snapshots, and validation-rule assertions together.
|
|
2734
2757
|
History and report rule names use `history` and `historyReport`; assignment rules use
|
|
@@ -2772,16 +2795,16 @@ Steps are `queued`, `offered`, `answered`, `held`, `muted`, `transferred`, `conf
|
|
|
2772
2795
|
when it began, `seconds` once it ended and omitted while it runs — and a second hold is a second
|
|
2773
2796
|
entry after the first, never a revision of it. A leg that has ended states its duration, and the
|
|
2774
2797
|
task says whether a leg can still be running: a hold runs only while the task is `paused`, a mute
|
|
2775
|
-
only while its
|
|
2798
|
+
only while its audio is up, so a `held` entry without `seconds` on a task that is not paused, or a
|
|
2776
2799
|
`muted` one on a call that is over, is a leg nobody closed and reads exactly like a leg running now
|
|
2777
2800
|
(`task.history.held.open`, `.muted.open`). Whoever performs the leg closes it — the
|
|
2778
2801
|
provider whose platform parks the caller, the agent application whose microphone it is — by restating the entry
|
|
2779
2802
|
with its duration, and **an entry that cannot be closed is not written**: open for ever is a
|
|
2780
|
-
plausible nought one level down from a total. **A leg still open when the
|
|
2781
|
-
the provider**, at that instant, in the same publication that says the
|
|
2803
|
+
plausible nought one level down from a total. **A leg still open when the audio ends is closed by
|
|
2804
|
+
the provider**, at that instant, in the same publication that says the audio ended: the provider
|
|
2782
2805
|
is the one that knows the instant, and the agent application can only learn of it afterwards. Agents end calls
|
|
2783
|
-
muted, so the agent application's leg is routinely open at
|
|
2784
|
-
from the leg's `at` to the
|
|
2806
|
+
muted, so the agent application's leg is routinely open at audio end; the provider closes it with the duration
|
|
2807
|
+
from the leg's `at` to the audio's end, and the agent application's own closing report for that leg, which
|
|
2785
2808
|
follows what it hears, is answered `recorded` and changes nothing, the entry standing as the
|
|
2786
2809
|
provider closed it. One publisher of the closed leg, and no order between them to get right. There is no resumed step; a resume is the end of a
|
|
2787
2810
|
hold, at the entry's `at` plus its `seconds`, and nothing is lost by not naming it twice. The same goes for every step: two mutes are two
|
|
@@ -2828,7 +2851,7 @@ or combine them: report the representation conflict. No ambiguous event is silen
|
|
|
2828
2851
|
|
|
2829
2852
|
**Handle time is anchored, not restarted.** It runs from the
|
|
2830
2853
|
`answered` step's `at` — from the task's first `in-progress` where the provider reports no
|
|
2831
|
-
history — until the task's
|
|
2854
|
+
history — until the task's audio ends, and a hold neither pauses nor resets it: the hold's own
|
|
2832
2855
|
duration is the `held` entry's `seconds`, and a desk that restarts its counter on resume is
|
|
2833
2856
|
counting the wrong thing.
|
|
2834
2857
|
|
|
@@ -2928,7 +2951,7 @@ the agent application mutes nothing and records nothing.
|
|
|
2928
2951
|
**The provider's confirmed end is decisive.** The agent application keeps local state for interaction and
|
|
2929
2952
|
reporting, but follows the provider's authoritative state. When the provider publishes the end
|
|
2930
2953
|
of the current mute leg, the agent application ends that local mute and releases its agent application-controlled mute
|
|
2931
|
-
on the matching task's
|
|
2954
|
+
on the matching task's audio. It does not wait for its own timer, reopen the leg, replace the
|
|
2932
2955
|
provider timestamp, or overwrite the provider's final duration with a later agent application report.
|
|
2933
2956
|
A later agent mute is a new leg, never a reopening of the ended one.
|
|
2934
2957
|
|
|
@@ -2940,7 +2963,7 @@ retain the current provider view and apply the matching closure once correlation
|
|
|
2940
2963
|
never guess a match from arrival order or nearest timestamp. Failure to release an agent application-controlled
|
|
2941
2964
|
mute is reported visibly; local device reports still describe the actual device state.
|
|
2942
2965
|
|
|
2943
|
-
At a provider-confirmed task/
|
|
2966
|
+
At a provider-confirmed task/audio end, the agent application also stops the associated local mute and reports
|
|
2944
2967
|
its observed ending where the report is still accepted. An agent application closing report repeats its original
|
|
2945
2968
|
key and may include its measured duration, but cannot reverse an already confirmed provider end.
|
|
2946
2969
|
The provider acknowledges a known closed leg without rewriting its final record; after the task
|
|
@@ -3068,7 +3091,7 @@ from no other source -- not the queue the task came from, not the login, not its
|
|
|
3068
3091
|
about this task, and a client with two sources and a rule for choosing between them is the shape
|
|
3069
3092
|
that produces two consumers disagreeing about one fact. The task is the one source, and the
|
|
3070
3093
|
provider puts on the task what the platform permits for it -- and says, in `capabilitySource`, who
|
|
3071
|
-
chose those terms. `queue` says somebody configured them. `
|
|
3094
|
+
chose those terms. `queue` says somebody configured them. `nobody` says nothing handed the work
|
|
3072
3095
|
over: an agent's own outbound call has no queue behind it, and the provider states what it permits
|
|
3073
3096
|
for such a call. Neither changes where an agent application reads the capabilities from, which is the task; the
|
|
3074
3097
|
source is one more published fact about them, and the one that lets an agent application tell a fact from a fault
|
|
@@ -3106,15 +3129,15 @@ direction: the task as offered and as republished, and one command that was issu
|
|
|
3106
3129
|
first and is refused under the last for want of the capability withdrawn -- and nothing else.
|
|
3107
3130
|
|
|
3108
3131
|
**An empty capability set is a statement, not a shrug.** `capabilities: {}` under `queue` or
|
|
3109
|
-
`
|
|
3132
|
+
`nobody` says the platform permits nothing capability-gated on this task, and an agent application draws
|
|
3110
3133
|
nothing beyond what the phase and `completionMode` require. A provider that has not yet learned
|
|
3111
3134
|
what the platform permits -- a queue's configuration that has not reached it -- knows nothing of
|
|
3112
3135
|
the kind, and must not publish the task as if it did, neither as `{}` nor as every control it has.
|
|
3113
|
-
It publishes the task under `capabilitySource: "
|
|
3136
|
+
It publishes the task under `capabilitySource: "not-yet-read"`, with the capabilities it will
|
|
3114
3137
|
honour until it knows, and an agent application shows that where the agent works, beside the controls it draws
|
|
3115
3138
|
from them: "no queue governs this call" is a fact, "the configuration has not arrived" is a fault,
|
|
3116
3139
|
and the set alone cannot tell them apart, so the provider says which. What a provider honours
|
|
3117
|
-
under
|
|
3140
|
+
under not-yet-read terms is its own call -- a floor that would rather an agent briefly hold a
|
|
3118
3141
|
control the platform might not have granted than lose hold or hang-up mid-call over a slow
|
|
3119
3142
|
configuration read publishes those -- and the protocol chooses no default set for it. The fault is
|
|
3120
3143
|
also reported as a `diagnostic` naming the task, one per occurrence, so an operator counts it. When
|
|
@@ -3122,12 +3145,12 @@ the terms arrive, the task is republished under `queue` with the set as it now s
|
|
|
3122
3145
|
republish as any other permission that changed while the task was open. The move goes one way.
|
|
3123
3146
|
Terms once read stay read: a re-read that fails mid-task is not a new fact about the task, so the
|
|
3124
3147
|
last statement stands and the failure is a `diagnostic`, and a task that was published under
|
|
3125
|
-
`queue` or `
|
|
3148
|
+
`queue` or `nobody` never returns to `not-yet-read`, on an update (`stream.taskUpdated.capabilitySource`)
|
|
3126
3149
|
or on a resync snapshot (`stream.snapshot.capabilitySource`); a record once read never loses an
|
|
3127
3150
|
entry the same two ways (`stream.taskUpdated.history`, `stream.snapshot.history`);
|
|
3128
|
-
a snapshot carrying a task still at work does not forget the audio the stream held up, since
|
|
3129
|
-
ends on `task-
|
|
3130
|
-
its audio ends, never around it, whatever the outcome (`stream.taskEnded.
|
|
3151
|
+
a snapshot carrying a task still at work does not forget the audio the stream held up, since audio
|
|
3152
|
+
ends on `task-audio-ended` and the call moves on (`stream.snapshot.audio`); a voice task ends after
|
|
3153
|
+
its audio ends, never around it, whatever the outcome (`stream.taskEnded.audioOpen`); and a task does not go
|
|
3131
3154
|
backwards, on an update or on a resync (`stream.taskUpdated.phase`, `stream.snapshot.phase`): the
|
|
3132
3155
|
stream sees publications, not
|
|
3133
3156
|
transitions, and a task may pass through a phase between two, so `pending` to `in-progress` stands
|
|
@@ -3136,32 +3159,32 @@ the transition table -- back to `pending`, back to `confirmed` or `preview` once
|
|
|
3136
3159
|
`completing` except by the party being dialled again, a connect-back or a platform's callback,
|
|
3137
3160
|
which the update itself shows: the party ringing, or joined by a dial whose answered outcome the
|
|
3138
3161
|
stream saw in this life. A completing task republished as `in-progress` from a stale copy carries
|
|
3139
|
-
no such stage, and that is the ending the agent never saw. Audio arrives only on a task at work: `task-
|
|
3140
|
-
is refused as it is on a pending one (`stream.
|
|
3141
|
-
returns the task to `in-progress` before any
|
|
3142
|
-
never around it: an update moving a task to `completing` while the stream holds its
|
|
3143
|
-
started is refused (`stream.taskUpdated.
|
|
3144
|
-
`completing` with `
|
|
3145
|
-
(`task.
|
|
3162
|
+
no such stage, and that is the ending the agent never saw. Audio arrives only on a task at work: `task-audio-started` on a `completing` task
|
|
3163
|
+
is refused as it is on a pending one (`stream.taskAudioStarted.beforeWork`), since a connect-back
|
|
3164
|
+
returns the task to `in-progress` before any audio. And a task completes after its audio ends,
|
|
3165
|
+
never around it: an update moving a task to `completing` while the stream holds its audio as
|
|
3166
|
+
started is refused (`stream.taskUpdated.audioOpen`), whoever caused the ending, and a task stating
|
|
3167
|
+
`completing` with `audio: "started"` contradicts itself on any snapshot or update
|
|
3168
|
+
(`task.audio.completing`). The consequence the rule exists for is concrete: the customer's audio
|
|
3146
3169
|
keeps playing through the agent's wrap-up.
|
|
3147
3170
|
|
|
3148
3171
|
What the agent is told differs by source, and only one source tells them anything. Under `queue`
|
|
3149
|
-
and `
|
|
3172
|
+
and `nobody` the agent sees controls and nothing about where they came from: both are facts,
|
|
3150
3173
|
and an agent working a call has no use for the name of the rule behind its buttons. Under
|
|
3151
|
-
`
|
|
3174
|
+
`not-yet-read` the agent is told, beside the controls, that these are what the provider will honour
|
|
3152
3175
|
until the queue's terms arrive, and that the controls may change when they do -- a statement about
|
|
3153
3176
|
the buttons in front of them now, not about the provider, because that is what changes when the
|
|
3154
3177
|
republish lands. It stays for as long as the set is provisional, beside the controls it qualifies;
|
|
3155
3178
|
a message that shows and clears has said nothing about the buttons still on the screen. The two
|
|
3156
|
-
words are close in English and far apart on the desk: `
|
|
3179
|
+
words are close in English and far apart on the desk: `nobody` is silence, `not-yet-read` is a
|
|
3157
3180
|
standing notice.
|
|
3158
3181
|
|
|
3159
|
-
One consequence for whoever builds the agent application: because a conformance run fails on `
|
|
3160
|
-
conformant adapter never shows an agent application `
|
|
3182
|
+
One consequence for whoever builds the agent application: because a conformance run fails on `not-yet-read`, a
|
|
3183
|
+
conformant adapter never shows an agent application `not-yet-read` under test, and a clean `exerciseAdapter`
|
|
3161
3184
|
result says nothing about how the agent application renders it. That rendering is tested against a fixture --
|
|
3162
|
-
a task published under `
|
|
3185
|
+
a task published under `not-yet-read` shows the notice, the same task under `queue` shows none --
|
|
3163
3186
|
and never against an adapter, even in principle. `exerciseAdapter` treats a
|
|
3164
|
-
task published under `
|
|
3187
|
+
task published under `not-yet-read` as a violation (`capabilitySource.notYetRead`), as it treats
|
|
3165
3188
|
a diagnostic: a conformance run against a platform that cannot say what it permits fails loudly
|
|
3166
3189
|
rather than passing with a note.
|
|
3167
3190
|
|
|
@@ -3188,7 +3211,7 @@ See **Which commands need a capability**.
|
|
|
3188
3211
|
| `decline` | Pending-task button: Decline | The provider can decline a pending voice offer. Omni shows it only when local policy also permits declining. |
|
|
3189
3212
|
| `hold` | Primary toggle: Hold | Omni may issue voice-task `hold` and `resume` commands. |
|
|
3190
3213
|
| `endCall` | Primary button: End call | The provider ends the agent's channel and every channel the agent added; the caller continues on the provider's path. The task goes to its wrap. See **Ending a call, and removing one person from it**. |
|
|
3191
|
-
| `terminateCall` | Primary button: Terminate call | The provider ends the caller
|
|
3214
|
+
| `terminateCall` | Primary button: Terminate call | The provider ends the whole call: every channel on it, the agent's, the caller's, any colleague's and anyone else's. The task goes to its wrap. See **Ending a call, and removing one person from it**. |
|
|
3192
3215
|
| `connectBack` | Completing-task button: Connect back | Omni may have the provider connect the agent back to the task's party while the task is `completing`, returning it to `in-progress` on the same task. Not offered where there is no `completing` window: `provider-automatic` with a zero allowance completes the task at provider end. See **Connecting back during completion**. |
|
|
3193
3216
|
| `coldTransfer` | Secondary menu item: Cold transfer | Omni may hand the customer straight to a destination, with nobody spoken to first. |
|
|
3194
3217
|
| `warmTransfer` | Secondary menu item: Warm transfer | Omni may park the customer and call a destination first, then hand the customer over or cancel back. See **Warm transfer**. |
|
|
@@ -3288,7 +3311,7 @@ provider. Two of them end the agent's part; they differ in what happens to the c
|
|
|
3288
3311
|
|
|
3289
3312
|
```ts
|
|
3290
3313
|
{ type: "end-call" } // the agent's channel and the channels the agent added end; the caller continues
|
|
3291
|
-
{ type: "terminate-call" } // the
|
|
3314
|
+
{ type: "terminate-call" } // every channel on the call ends
|
|
3292
3315
|
{ type: "conference", action: "remove", party: true } // the customer leaves; the agent stays with the colleague
|
|
3293
3316
|
{ type: "conference", action: "remove", destinationId: "tier2" } // the conferenced person leaves; ringing, this calls the dial off
|
|
3294
3317
|
```
|
|
@@ -3297,12 +3320,13 @@ provider. Two of them end the agent's part; they differ in what happens to the c
|
|
|
3297
3320
|
conferenced colleague, a consulted destination -- end, and the caller continues on the path the
|
|
3298
3321
|
provider decides: another IVR, a queue, a survey, or the end of the call. Gated by `endCall`.
|
|
3299
3322
|
|
|
3300
|
-
**`terminate-call` ends the
|
|
3301
|
-
|
|
3302
|
-
|
|
3323
|
+
**`terminate-call` ends the whole call.** Every channel on it ends at the provider: the agent's,
|
|
3324
|
+
the caller's, any colleague the agent added, and anyone else on the call. Nobody is left on the
|
|
3325
|
+
line. Gated by `terminateCall`, a separate permission, since ending a customer's call is a
|
|
3326
|
+
different thing from putting my own side down.
|
|
3303
3327
|
|
|
3304
3328
|
Both are commands to the provider through `execute`, and nothing happens on the desk until the
|
|
3305
|
-
provider reports it: the agent's
|
|
3329
|
+
provider reports it: the agent's audio ends on `task-audio-ended`, the task moves to `completing`,
|
|
3306
3330
|
any wrap allowance runs, and the agent completes. Neither is `task-ended`, and `complete` is
|
|
3307
3331
|
neither of them. Both capabilities are the queue's to grant and a level's to lock, and both may
|
|
3308
3332
|
change while the task is open: the provider republishes the task at the moment a permission
|
|
@@ -3352,7 +3376,7 @@ that offers `warmTransfer` implements all three.
|
|
|
3352
3376
|
|
|
3353
3377
|
`applied` on `complete` says the provider is bridging the customer to the destination and
|
|
3354
3378
|
dropping the agent's leg. What follows is what follows any call the agent leaves: the agent's
|
|
3355
|
-
|
|
3379
|
+
audio ends and the provider reports `task-audio-ended`, the task moves to `completing`, any wrap
|
|
3356
3380
|
allowance runs, and the task ends `completed` as any call does. A warm complete is not a completion:
|
|
3357
3381
|
the agent has a wrap to do, so nothing is owed within `completionSettleMs`. `transferred` names a
|
|
3358
3382
|
cold transfer alone, where the agent had no wrap. `applied` on `cancel` says the destination is
|
|
@@ -3438,7 +3462,7 @@ inside Omni.
|
|
|
3438
3462
|
|
|
3439
3463
|
## Every dial has an outcome
|
|
3440
3464
|
|
|
3441
|
-
Five commands place a call: `dial` from the idle dialpad, `
|
|
3465
|
+
Five commands place a call: `dial()` from the idle dialpad, the `dial` command from preview, a cold or warm `transfer`, a
|
|
3442
3466
|
`conference` `add`, and `connect-back`. Each is accepted or refused at once, and each then ends later
|
|
3443
3467
|
and apart from its answer -- the destination picks up, is busy, or never does -- and a dial placed
|
|
3444
3468
|
late in a call routinely outlives the call. Nothing in between is reported: the wire says
|
|
@@ -3544,7 +3568,7 @@ never saw.
|
|
|
3544
3568
|
|
|
3545
3569
|
**`onCall` describes this agent's current interaction.** When the caller disconnects but the agent
|
|
3546
3570
|
and added channels remain connected, the remaining room is still valid. When this interaction's
|
|
3547
|
-
|
|
3571
|
+
audio ends or the task enters `completing`, clear its `onCall` view; absence is used only by a
|
|
3548
3572
|
provider that never publishes the room. This does not assert that the caller, bridge or other
|
|
3549
3573
|
agents' channels ended. A published room receives a final empty view for this interaction, and the
|
|
3550
3574
|
task may remain open for wrap. Completion is a separate task action, not the caller's disconnect.
|
|
@@ -3569,10 +3593,10 @@ still-ringing `destinationId` calls that dial off, with `cancelled` as its outco
|
|
|
3569
3593
|
|
|
3570
3594
|
**The entry says where it stands.** A dialled entry carries `stage`: `ringing` until its dial is
|
|
3571
3595
|
answered, `joined` after. The `dial-outcome` is the transition and the stage is the state, the same
|
|
3572
|
-
pairing as `task-
|
|
3596
|
+
pairing as `task-audio-started` and `audio`: an `answered` outcome and the task restated with the
|
|
3573
3597
|
entry `joined` say one thing twice, and any other outcome removes the entry. The outcome comes
|
|
3574
3598
|
first: a `task-updated` that itself moves an entry from `ringing` to `joined` before an `answered`
|
|
3575
|
-
outcome for its dial is refused (`stream.taskUpdated.stage`), as an update that moves `
|
|
3599
|
+
outcome for its dial is refused (`stream.taskUpdated.stage`), as an update that moves `audio` is.
|
|
3576
3600
|
The rule keys on the entry's `dialId`, so an entry the platform added itself, with none, is one
|
|
3577
3601
|
whose move the stream cannot hold to an outcome: a clean stream proves nothing about ordering there. It is stated rather
|
|
3578
3602
|
than left to whoever placed the dial because that knowledge lives in one process: after a reload,
|
|
@@ -3789,7 +3813,7 @@ Your own tasks are the only ones you count. What the agent holds at other provid
|
|
|
3789
3813
|
concern — Omni set `count` knowing it, and this is how: the agent is one person on several
|
|
3790
3814
|
providers, and the agent application divides their capacity among them rather than telling each the whole. A
|
|
3791
3815
|
provider that has none of it for now is told **`count: 0`, agent application-stopped**: assign nothing, show
|
|
3792
|
-
the member as `
|
|
3816
|
+
the member as `elsewhere` on the team member list -- signed in here, capacity held by the agent application for elsewhere,
|
|
3793
3817
|
a fact this provider holds, where `on-task` would assert work it cannot see -- and take the next
|
|
3794
3818
|
count as any other when the agent application has capacity for this provider again. Zero is the one restatement
|
|
3795
3819
|
that follows work rather than local policy.
|
|
@@ -4125,8 +4149,9 @@ for nothing and computes nothing.
|
|
|
4125
4149
|
| `availability` | Required. What the member is doing now. |
|
|
4126
4150
|
| `since` | Optional. When the current `availability` began — not when they signed in, and not when the team member list was read. |
|
|
4127
4151
|
| `break` | Present only while the member has an outstanding break request. See **A member waiting for a break**. |
|
|
4128
|
-
| `tasks` | The member's open tasks as `MemberTask`s: the same task the member's desk holds, trimmed by the provider. The assignment, title, task type, phase, party, room
|
|
4129
|
-
| `listening` | Present while this lead's channel is in this member's call — listening, coaching or joined —
|
|
4152
|
+
| `tasks` | The member's open tasks as `MemberTask`s: the same task the member's desk holds, trimmed by the provider. The assignment, title, task type, phase, party, room and audio are what the lead reads; the task's history is carried in the same shape as on the member's desk, as much of it as the provider chooses to send; the workspace — controls, their source, browsers, completion terms — is the member's and travels only if the provider sends it. `[]` when the member holds none; omitted only where the provider cannot see them. |
|
|
4153
|
+
| `listening` | Present while this lead's channel is in this member's call — listening, coaching or joined — naming which of the member's calls by `assignmentId`, the `mode` they are heard in, and since when. Its appearance is what the lead's audio opens on. One member's call at a time (`team.listening.single`). |
|
|
4154
|
+
| `shift` | The member's own history for the day, about the person rather than any one call: `signedInAt`, `signedOutAt` once it has happened, today's totals as the provider counts them — `talkSeconds`, `holdSeconds`, `breakSeconds`, `tasksHandled`, each present only when the provider knows it — and the day's sign-in, sign-out and break `events`, oldest first. Omitted where the provider cannot say. |
|
|
4130
4155
|
|
|
4131
4156
|
Each availability value means one thing:
|
|
4132
4157
|
|
|
@@ -4135,7 +4160,7 @@ Each availability value means one thing:
|
|
|
4135
4160
|
| `ready` | Signed in, able to take work, none assigned. |
|
|
4136
4161
|
| `on-task` | Working on at least one task. It says nothing about how many, and nothing about whether more will fit. |
|
|
4137
4162
|
| `on-break` | Stopped and not taking work, whether they asked or somebody stopped them. The reason lives on their own `BreakState`, not here. |
|
|
4138
|
-
| `
|
|
4163
|
+
| `elsewhere` | Signed in here, and the agent application holds this agent's capacity for another provider (`count: 0`, agent application-stopped): not receiving this provider's work, and not on a break. See **Capacity**. |
|
|
4139
4164
|
| `signed-out` | Known to this team but not signed in to this provider. |
|
|
4140
4165
|
|
|
4141
4166
|
**Always publish the complete team member list, never a change to it.** Team presence typically reaches an
|
|
@@ -4233,8 +4258,8 @@ executeTeam({ command: { type: "decline", memberId: "A-1", reason: "In a call" }
|
|
|
4233
4258
|
|
|
4234
4259
|
**On `join` the provider bridges the lead's channel into the call.** The member's task moves to
|
|
4235
4260
|
`leadAssist: { stage: "joined", leadId }`, the member on the team member list carries
|
|
4236
|
-
`listening: { mode: "join-call", since }`, and the lead's audio
|
|
4237
|
-
describes. Nothing is a task on the lead's desk: a join is the lead's act on the team surface, not
|
|
4261
|
+
`listening: { assignmentId, mode: "join-call", since }`, and the lead's audio opens on it as
|
|
4262
|
+
**Listening to a call** describes. Nothing is a task on the lead's desk: a join is the lead's act on the team surface, not
|
|
4238
4263
|
work assigned to them.
|
|
4239
4264
|
|
|
4240
4265
|
**A lead is on one member's call at a time.** A `join` from a lead already on a call -- their own
|
|
@@ -4255,15 +4280,17 @@ listening unasked -- and one act at the end of either. What follows is the same
|
|
|
4255
4280
|
|
|
4256
4281
|
| | The member's task | The lead's desk |
|
|
4257
4282
|
| --- | --- | --- |
|
|
4258
|
-
| Take-over | `task-
|
|
4283
|
+
| Take-over | `task-audio-ended`, then `completing`: the member's interaction is over and their wrap runs, exactly as after `end-call`. The audio ends first, as before every voice ending (`stream.taskEnded.audioOpen`). The member loses `listening` on the team member list no later than the offer, and the lead's listening audio closes. | An ordinary assignment arrives: `task-offered` with `acceptance: "automatic"`, carrying the call's history and `takenOver: { memberId, since }`, within the lead's capacity. |
|
|
4259
4284
|
| The member completes | `task-ended` with `{ type: "taken-over", leadId }`, at the member's own completion. The lead is named by user id, since a lead is not a directory item. | The lead works the call as any agent would, and ends it as any call ends. |
|
|
4260
4285
|
|
|
4261
|
-
**The taken-over call is offered whatever break the lead is on
|
|
4262
|
-
on a break of a working kind, and the provider assigns the call
|
|
4263
|
-
break in effect may hold (`break.on-break.tasks` allows it, and
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4286
|
+
**The taken-over call is offered whatever break the lead is on, and counted like any other.** A
|
|
4287
|
+
lead working as lead alone is on a break of a working kind, and the provider assigns the call
|
|
4288
|
+
regardless: it is the one task a break in effect may hold (`break.on-break.tasks` allows it, and
|
|
4289
|
+
nothing else). It is a call landing on the lead's desk, so it counts against their capacity as
|
|
4290
|
+
every call does, with no exemption: a lead at capacity cannot take over, and the provider answers
|
|
4291
|
+
`failed`. The other conditions are that the lead is not already on another call and their voice
|
|
4292
|
+
channel is free. A lead with the team feature off cannot take over, since they have no member's
|
|
4293
|
+
assignment to name.
|
|
4267
4294
|
|
|
4268
4295
|
```ts
|
|
4269
4296
|
const leadAssistCapable = {
|
|
@@ -4297,34 +4324,47 @@ history. The three modes determine who hears the lead:
|
|
|
4297
4324
|
| `coach` | The agent alone. The customer hears nothing. |
|
|
4298
4325
|
| `join-call` | Everyone on the call. |
|
|
4299
4326
|
|
|
4300
|
-
All three come with the `lead` flag; a centre reserves none of them per lead.
|
|
4327
|
+
All three come with the `lead` flag; a centre reserves none of them per lead.
|
|
4301
4328
|
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4329
|
+
**There is no team audio.** Audio is the agent's or the lead's, and the lead's audio while
|
|
4330
|
+
listening follows the provider's published state exactly as a task's does: `listening` on the
|
|
4331
|
+
member is the provider's word that the lead's channel is in the call. When it appears, the
|
|
4332
|
+
application opens `openAudio({ assignmentId })` on the lead's connection for the call it names,
|
|
4333
|
+
and plays the `CallAudio` returned; when it goes, the application closes it. Nothing else says
|
|
4334
|
+
when the lead's audio is up, and a reload reopens it from the snapshot's team member list as a task
|
|
4335
|
+
carried with `audio: "started"` is reopened.
|
|
4336
|
+
|
|
4337
|
+
The flow, end to end:
|
|
4307
4338
|
|
|
4308
|
-
|
|
4339
|
+
```ts
|
|
4340
|
+
// 1. The lead, with the feature on, sees a member on-task with their tasks, and picks a call.
|
|
4341
|
+
// 2. The lead's channel joins it in silence. Conditions: the lead's voice channel is free --
|
|
4342
|
+
// not on a call, not listening elsewhere. Capacity is not involved: nothing is assigned.
|
|
4343
|
+
executeTeam({ command: { type: "listen", memberId: "A-1", assignmentId: "alloc-42" } })
|
|
4344
|
+
// 3. The provider republishes the team member list; the member carries
|
|
4345
|
+
// listening: { assignmentId: "alloc-42", mode: "listen", since }
|
|
4346
|
+
// -- the application opens openAudio({ assignmentId: "alloc-42" }) on the lead's connection.
|
|
4347
|
+
// 4. The lead changes how they are heard; the provider restates listening.mode. The audio stays open.
|
|
4309
4348
|
executeTeam({ command: { type: "coach", memberId: "A-1" } })
|
|
4310
4349
|
executeTeam({ command: { type: "join-call", memberId: "A-1" } })
|
|
4311
|
-
|
|
4312
|
-
// 3. The lead leaves, or takes the call.
|
|
4350
|
+
// 5. The lead leaves: the provider republishes the member without listening; the application closes the audio.
|
|
4313
4351
|
executeTeam({ command: { type: "leave", memberId: "A-1" } })
|
|
4352
|
+
// 6. The member's call ends: the same as leave, from the provider's side.
|
|
4353
|
+
// 7. The lead takes the call: the member loses listening no later than the offer, the application
|
|
4354
|
+
// closes the listening audio, and the lead's own call arrives as an ordinary assignment marked
|
|
4355
|
+
// takenOver, counted against capacity, with its audio following task-audio-started as any call's does.
|
|
4356
|
+
// At capacity the take-over is refused and the lead stays listening.
|
|
4314
4357
|
executeTeam({ command: { type: "take-over-call", memberId: "A-1" } })
|
|
4358
|
+
// 8. A join on the member's request is the same flow entered at step 3 with mode: "join-call",
|
|
4359
|
+
// and the member's task shows leadAssist: joined.
|
|
4360
|
+
// 9. A reload of the lead's application: the snapshot's team member list still carries listening,
|
|
4361
|
+
// so the audio is reopened.
|
|
4315
4362
|
```
|
|
4316
4363
|
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
to a login that declares `lead` (`event.teamMedia.*`). The member's own media events are the
|
|
4322
|
-
member's and never reach the lead.
|
|
4323
|
-
|
|
4324
|
-
**`listening` on the member is the state**, restated on every change of mode, and a lead is on
|
|
4325
|
-
one member's call at a time (`team.listening.single`). The member's call ending ends the lead's
|
|
4326
|
-
listening with it: the provider republishes the member without `listening` and sends
|
|
4327
|
-
`team-media-ended`.
|
|
4364
|
+
**`listening` on the member is the state**, restated on every change of mode, naming the call so
|
|
4365
|
+
the application knows which audio to open (`team.member.listening.assignmentId`, and one the
|
|
4366
|
+
member's tasks carry, `team.member.listening.assignment`), and a lead is on one member's call at a
|
|
4367
|
+
time (`team.listening.single`).
|
|
4328
4368
|
|
|
4329
4369
|
**A lead listens while holding no call of their own.** Omni offers Listen to a lead whose voice
|
|
4330
4370
|
channel is free, and a provider answers a `listen` from one whose channel is not `failed`. That
|
|
@@ -4369,20 +4409,20 @@ A request needing *more than one* approval is not something this contract descri
|
|
|
4369
4409
|
no partial state to report and no progress to display: a request is either still owed a
|
|
4370
4410
|
decision or it is not.
|
|
4371
4411
|
|
|
4372
|
-
## Real-time
|
|
4412
|
+
## Real-time audio
|
|
4373
4413
|
|
|
4374
|
-
Every voice provider has
|
|
4414
|
+
Every voice provider has audio: `channel: "voice"` says audio exists. Where the agent hears it
|
|
4375
4415
|
depends on the `phone` the agent application selected at authentication and connect from the manifest's
|
|
4376
4416
|
`phones`. On a softphone, audio lands in Omni; on a desk phone, it lands on the handset and Omni
|
|
4377
|
-
opens no audio. See **How the agent hears the call**.
|
|
4378
|
-
chat and email publish none (`event.
|
|
4417
|
+
opens no audio. See **How the agent hears the call**. Audio transitions are voice-only;
|
|
4418
|
+
chat and email publish none (`event.audio.channel`, `stream.taskAudio.channel`).
|
|
4379
4419
|
|
|
4380
4420
|
The provider owns signalling and the platform's endpoint configuration. The agent application selects the
|
|
4381
4421
|
declared phone mode; it does not enumerate or reconfigure the platform's devices. The selected
|
|
4382
4422
|
mode stays with the login rather than being inferred from a snapshot or a device failure.
|
|
4383
4423
|
|
|
4384
4424
|
Nor does the audio ever stand in for the task: a task's presence and
|
|
4385
|
-
phase follow the provider's reports about the work, and the
|
|
4425
|
+
phase follow the provider's reports about the work, and the audio — attaching, moving through a
|
|
4386
4426
|
hold, a consult, a conference or a transfer, and ending — is transient beside it. See **A task is
|
|
4387
4427
|
never its audio** under **Task assignment lifecycle**.
|
|
4388
4428
|
|
|
@@ -4419,7 +4459,7 @@ refuses a false one, as it refuses a name this contract does not list.
|
|
|
4419
4459
|
| --- | --- |
|
|
4420
4460
|
| `online` | Whether the agent application has a network interface up. Not a claim that anything is reachable — the adapter knows whether it can reach its own platform far better than the agent application does — so `false` is a reason not to go ready and `true` is not a reason to. |
|
|
4421
4461
|
| `audio` | Present on a voice connection, absent where there is no audio. |
|
|
4422
|
-
| `audio.input` | `available` with `localAudio` — the microphone as captured, the same stream `
|
|
4462
|
+
| `audio.input` | `available` with `localAudio` — the microphone as captured, the same stream `openAudio` receives — and `flowing`, false while no audio moves through it, when `mutedBy` says who stopped it: `host`, the agent application's own Mute on an agent application that mutes the station, or `station`, a slider on the headset or the operating system, which the agent application observed and did not do. An adapter treats `host` as the agent's act on a call and `station` as a condition of the station. `unavailable` with `reason`, since each wants a different fix from the agent: `no-device`; `denied`; `not-asked`, which an agent application that asks at connect never publishes; `in-use`, a device present and permitted that another application holds — on an agent desktop the commonest of all; `lost`, a capture that ended. An agent application decides the reason from the devices before the error name: a browser can report a permission error on a machine with no microphone at all, and "grant permission" is the wrong instruction for an agent who needs to plug one in. `failure` carries the words Omni showed them. |
|
|
4423
4463
|
| `audio.output` | `available`, with `flowing` where the agent application can know whether audio reaches the speaker — a browser mostly cannot, and omits it; a native agent application reads the endpoint — false while it is silenced, when `mutedBy` says who did it, as on input. Or `unavailable` with `reason` — `no-device`, or `lost` for one removed — and `failure`: an agent who cannot hear is as unable to take a call as one who cannot speak. |
|
|
4424
4464
|
|
|
4425
4465
|
Omni republishes the report whenever it changes — a permission granted late, a headset unplugged,
|
|
@@ -4488,10 +4528,10 @@ A voice manifest lists its `phones` and any other channel lists none (`manifest.
|
|
|
4488
4528
|
`manifest.phones.channel`). The agent application's `phone` is required on a voice login, absent on any other,
|
|
4489
4529
|
and one the manifest listed (`context.phone.required`, `.unexpected`, `.unsupported`). Everything
|
|
4490
4530
|
about audio then follows the phone rather than the channel: on a softphone the agent application reports its
|
|
4491
|
-
audio, the adapter implements `
|
|
4492
|
-
desk phone the agent application reports no audio, opens nothing, and `task-
|
|
4531
|
+
audio, the adapter implements `openAudio`, and `task-audio-started` is the word to open it; on a
|
|
4532
|
+
desk phone the agent application reports no audio, opens nothing, and `task-audio-started` still marks the
|
|
4493
4533
|
moment the call is live so the desk shows it, with the sound on the handset. A platform that lists
|
|
4494
|
-
`deskPhone` alone never implements `
|
|
4534
|
+
`deskPhone` alone never implements `openAudio`; one that lists `softphone` always does.
|
|
4495
4535
|
|
|
4496
4536
|
**The mode comes from the agent application; the status comes from the provider.** The agent application knows which kind
|
|
4497
4537
|
of station the agent signed in at, because the person chose it, and nothing else can know that.
|
|
@@ -4503,10 +4543,10 @@ the handset is momentarily unregistered, and demanding a microphone of it, is th
|
|
|
4503
4543
|
sentence exists to refuse. What the provider does when the handset is not registered is what it
|
|
4504
4544
|
does for any station that cannot take a call -- hold the agent not-ready and say why.
|
|
4505
4545
|
|
|
4506
|
-
**On a desk-phone login the agent application never calls `
|
|
4546
|
+
**On a desk-phone login the agent application never calls `openAudio`.** There is no stream to hand over and
|
|
4507
4547
|
no audio to attach; an agent application that calls it anyway is in error, and an adapter that receives the call
|
|
4508
4548
|
answers `unavailable` with a non-retryable failure, since waiting changes nothing about a station
|
|
4509
|
-
that is a telephone. The harness requires no `
|
|
4549
|
+
that is a telephone. The harness requires no `openAudio` of such an adapter and never calls it.
|
|
4510
4550
|
|
|
4511
4551
|
**Which handset a desk-phone login rings is the platform's configuration for that agent**, and
|
|
4512
4552
|
this wire never asks the agent for it: an agent application declares `phone` and nothing more. What a platform
|
|
@@ -4529,11 +4569,11 @@ is the administrator's, and a login is not a request to reconfigure an agent.
|
|
|
4529
4569
|
|
|
4530
4570
|
### Opening the audio
|
|
4531
4571
|
|
|
4532
|
-
`
|
|
4572
|
+
`openAudio` hands Omni the remote audio for one task. Every adapter whose manifest lists
|
|
4533
4573
|
`softphone` implements it, because on a softphone the call's audio lands in Omni:
|
|
4534
4574
|
|
|
4535
4575
|
```ts
|
|
4536
|
-
|
|
4576
|
+
openAudio({ assignmentId, localAudio }): Promise<OpenAudioResult>
|
|
4537
4577
|
// { status: "opened", session } | { status: "unavailable", failure }
|
|
4538
4578
|
```
|
|
4539
4579
|
|
|
@@ -4556,8 +4596,8 @@ carries as `audio.input.localAudio`, and absent while that input is `unavailable
|
|
|
4556
4596
|
bridges audio without an application-side input may ignore it; one that needs it and finds it absent
|
|
4557
4597
|
answers `unavailable` with a failure Omni shows the agent.
|
|
4558
4598
|
|
|
4559
|
-
**When to ask is the provider's word, not Omni's guess.** On a softphone login, Omni opens
|
|
4560
|
-
and on a task arriving with `
|
|
4599
|
+
**When to ask is the provider's word, not Omni's guess.** On a softphone login, Omni opens audio on `task-audio-started`,
|
|
4600
|
+
and on a task arriving with `audio: "started"` on a snapshot; it closes on `task-audio-ended` and
|
|
4561
4601
|
when the task ends. Between those words, nothing Omni's own senses report — a stream that drops, a
|
|
4562
4602
|
track that ends — moves the task or its audio.
|
|
4563
4603
|
|
|
@@ -4607,8 +4647,8 @@ agent application's. So mute is not a capability a provider declares, not a cont
|
|
|
4607
4647
|
locks, not a preference the person keeps with the provider, and not a command: nothing about the
|
|
4608
4648
|
station crosses to the provider except two things -- the station's condition, in the agent application report,
|
|
4609
4649
|
and the record of when the agent could not be heard, through `recordStep`. An agent application offers Mute on
|
|
4610
|
-
every voice task with
|
|
4611
|
-
alone, and performs it through `
|
|
4650
|
+
every voice task with audio open, under its own local policy, in `in-progress` and `paused`
|
|
4651
|
+
alone, and performs it through `CallAudio.setMuted()`. A task carrying
|
|
4612
4652
|
`capabilities.mute` is refused (`task.capability.unknown`), and so is a `mute` command
|
|
4613
4653
|
(`command.type`), a `mute` preference (`preference.id`) and a `mute` policy (`team.policy.key`).
|
|
4614
4654
|
|
|
@@ -4644,9 +4684,9 @@ forty seconds" and "the agent's headset was muted for forty seconds" as the diff
|
|
|
4644
4684
|
are. See **The agent application records what it performs**.
|
|
4645
4685
|
|
|
4646
4686
|
**Mute has a lifecycle, and none of it is inferred.** A call that starts while the station is
|
|
4647
|
-
already muted begins a `muted` leg the moment its
|
|
4687
|
+
already muted begins a `muted` leg the moment its audio starts, `mutedBy: "station"`. Audio that
|
|
4648
4688
|
ends while any leg is open ends the leg at that instant, as every agent application-performed leg ends. And the
|
|
4649
|
-
agent application's own Mute starts off on every call, which is every `task-
|
|
4689
|
+
agent application's own Mute starts off on every call, which is every `task-audio-started` -- a connect-back
|
|
4650
4690
|
opens a second call on the same task with no offer -- so an agent is never muted by the call before.
|
|
4651
4691
|
|
|
4652
4692
|
**Every press on a headset is the agent's own press**, performed the agent application's way, and the lights
|
|
@@ -4683,10 +4723,10 @@ declared:
|
|
|
4683
4723
|
| --- | --- |
|
|
4684
4724
|
| `answer`, `accept` | Nothing. A task that was offered can be accepted, or offering it meant nothing. |
|
|
4685
4725
|
| `end-call` | The `endCall` capability: ends my part, the caller continues. |
|
|
4686
|
-
| `terminate-call` | The `terminateCall` capability: ends the
|
|
4726
|
+
| `terminate-call` | The `terminateCall` capability: ends the whole call, every channel on it. |
|
|
4687
4727
|
| `conference` with `action: "remove"` | The `conference` capability, and somebody else on the call: a remove that would leave the agent alone is `end-call`, and a provider answers it `failed`. |
|
|
4688
4728
|
| `decline` | The `decline` capability on any channel, **and** Omni local policy permitting it. One word for refusing an offer, whatever the channel. |
|
|
4689
|
-
| `
|
|
4729
|
+
| `dial` | 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`. |
|
|
4690
4730
|
| `complete` | `completionMode: "agent-command"`. The `outcomes` capability decides whether a code travels with the command, never whether the command exists — a task Omni cannot complete never ends. What travels is what the capability published: a code from its list where it has one (`command.complete.outcome.unknown`), a code at all where it requires one (`.outcome.required`), notes as it said (`.notes.required`, `.notes.unexpected`), and neither where the task declares no outcomes (`.outcome.unexpected`). |
|
|
4691
4731
|
| `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. |
|
|
4692
4732
|
| `transfer` with `action: "warm"` | The `warmTransfer` capability. `action: "cold"` is gated by `coldTransfer`; the two are declared and offered separately. |
|
|
@@ -4706,7 +4746,7 @@ declared while the task remains open; the phase prevents this task from controll
|
|
|
4706
4746
|
it no longer owns. Omni shows these controls only in the two interaction phases, and
|
|
4707
4747
|
`validateTaskCommand` refuses them outside those phases (`command.phase.interaction`).
|
|
4708
4748
|
The commands with a phase of their own -- `answer`, `accept` and `decline` in `pending`,
|
|
4709
|
-
`
|
|
4749
|
+
`dial` in `preview`, `connect-back` in `completing`, `complete` in any -- are not among them.
|
|
4710
4750
|
|
|
4711
4751
|
`validateTaskCommand(command, task)` holds a command to this table at runtime, both ways: the
|
|
4712
4752
|
capability it needs, the phase it belongs to, and the state that has to stand. The task it wants
|
|
@@ -5055,30 +5095,30 @@ snapshots until it ends.
|
|
|
5055
5095
|
Replaces the current representation of one provider-local task. It is a full task value, not a
|
|
5056
5096
|
partial patch.
|
|
5057
5097
|
|
|
5058
|
-
### `task-
|
|
5098
|
+
### `task-audio-started`
|
|
5059
5099
|
|
|
5060
|
-
The provider's word that the task's audio should now attach. Omni calls `
|
|
5061
|
-
a task carried with `
|
|
5100
|
+
The provider's word that the task's audio should now attach. Omni calls `openAudio` on it — and on
|
|
5101
|
+
a task carried with `audio: "started"`, which is how a reconnect snapshot reattaches audio an
|
|
5062
5102
|
earlier event brought — and renders the call as live from that word, never from its own senses. It
|
|
5063
|
-
precedes `
|
|
5103
|
+
precedes `openAudio` and is never a reply to it: a provider whose audio state comes from the
|
|
5064
5104
|
platform, a station going in use the moment a call is answered, sends it then, before any agent application has
|
|
5065
|
-
opened anything, and `
|
|
5066
|
-
names a task whose work has begun, and it alternates with `task-
|
|
5105
|
+
opened anything, and `openAudio` has its own answer for what the agent application did. It
|
|
5106
|
+
names a task whose work has begun, and it alternates with `task-audio-ended`: audio that never
|
|
5067
5107
|
started cannot end, so a live call whose provider says nothing about its audio is a provider in
|
|
5068
5108
|
breach, not a state a desk fills in from its own devices.
|
|
5069
5109
|
|
|
5070
|
-
The event is the transition and the task's `
|
|
5071
|
-
the
|
|
5110
|
+
The event is the transition and the task's `audio` field is the state. A `task-updated` re-states
|
|
5111
|
+
the audio its task already holds — republishing `started` on a hold is a statement, not a second
|
|
5072
5112
|
arrival — but it does not move it: an update that itself flips the field is refused
|
|
5073
|
-
(`stream.taskUpdated.
|
|
5113
|
+
(`stream.taskUpdated.audio`), and the pairing at the moment audio arrives is the phase change
|
|
5074
5114
|
without the field, then the event. Releasing `ended` is the one move an update may make, since
|
|
5075
5115
|
wrapped audio has nothing left to end.
|
|
5076
5116
|
|
|
5077
|
-
### `task-
|
|
5117
|
+
### `task-audio-ended`
|
|
5078
5118
|
|
|
5079
|
-
Signals that a task's real-time
|
|
5080
|
-
completion timer. It does not remove the task, and it ends only audio that `task-
|
|
5081
|
-
a task carried with `
|
|
5119
|
+
Signals that a task's real-time audio ended. For voice and similar channels, this starts the fixed
|
|
5120
|
+
completion timer. It does not remove the task, and it ends only audio that `task-audio-started` — or
|
|
5121
|
+
a task carried with `audio: "started"` — attached.
|
|
5082
5122
|
|
|
5083
5123
|
### `task-ended`
|
|
5084
5124
|
|
|
@@ -5086,7 +5126,7 @@ Every outcome ends the task for this agent. On `task-ended`, Omni:
|
|
|
5086
5126
|
|
|
5087
5127
|
- removes the task from its current provider view;
|
|
5088
5128
|
- clears the task workspace when it is selected;
|
|
5089
|
-
- stops task timers and
|
|
5129
|
+
- stops task timers and audio;
|
|
5090
5130
|
- releases task-scoped resources; and
|
|
5091
5131
|
- selects another task or returns to the idle workspace.
|
|
5092
5132
|
|
|
@@ -5101,7 +5141,7 @@ performed. Past the bound the agent application calls `snapshot()`: a snapshot s
|
|
|
5101
5141
|
held open by a provider that said it was done, and the desk shows it as unsettled -- "Completing...
|
|
5102
5142
|
the provider has not confirmed" -- naming the command; a snapshot no longer carrying it clears the
|
|
5103
5143
|
task, since the ending was owed and lost. The drive holds a provider to the same bound
|
|
5104
|
-
(`drive.completion.unsettled`). The `task-
|
|
5144
|
+
(`drive.completion.unsettled`). The `task-audio-ended` event and the `completing` phase are likewise
|
|
5105
5145
|
non-terminal. A replacement
|
|
5106
5146
|
snapshot that no longer contains the task also clears it. Repeated `task-ended` delivery with the
|
|
5107
5147
|
same envelope ID is harmless, and a `task-ended` naming an assignment that has already ended is
|
|
@@ -5135,8 +5175,8 @@ console is the silence problem one layer up. One event per occurrence; the agent
|
|
|
5135
5175
|
provider does not batch. `exerciseAdapter` treats a diagnostic delivered during a run as a
|
|
5136
5176
|
violation (`diagnostic.raised`): a conformance run against a platform that is breaking its rules
|
|
5137
5177
|
fails loudly rather than passing with a note. A task published under `capabilitySource:
|
|
5138
|
-
"
|
|
5139
|
-
(`capabilitySource.
|
|
5178
|
+
"not-yet-read"` is the same kind of fact, stated on the task instead, and fails a run the same way
|
|
5179
|
+
(`capabilitySource.notYetRead`); see **Task capabilities**.
|
|
5140
5180
|
|
|
5141
5181
|
| Field | Contract |
|
|
5142
5182
|
| --- | --- |
|
|
@@ -5306,10 +5346,10 @@ Nothing there is a violation: an adapter with no team has nothing to exercise. B
|
|
|
5306
5346
|
no tasks exercises no task rule, and a pass over it reads as coverage it is not.
|
|
5307
5347
|
|
|
5308
5348
|
**A static run never answers a call.** It states a capacity and disconnects, so everything
|
|
5309
|
-
downstream of `answer` -- the room, the stage,
|
|
5349
|
+
downstream of `answer` -- the room, the stage, audio, every phase past `pending`, every dial
|
|
5310
5350
|
outcome -- stays in `notExercised` for every adapter, and a rule about a live call is enforced only
|
|
5311
5351
|
in each adopter's own tests. `{ drive: true }` closes that: the exercise takes the first task the
|
|
5312
|
-
provider offers through one ordinary lifecycle -- accept it, wait for its
|
|
5352
|
+
provider offers through one ordinary lifecycle -- accept it, wait for its audio and open it on a
|
|
5313
5353
|
softphone, hold and resume where the task offers `hold`, end the call where it offers `endCall`,
|
|
5314
5354
|
complete it with an outcome where the agent completes -- and holds every step to the rules a
|
|
5315
5355
|
agent application holds a provider to. Each command is validated against the task as published
|
|
@@ -5324,17 +5364,17 @@ nothing (`getUserDetails.user.*`, `getUserDetails.unasked`, `connection.getUserD
|
|
|
5324
5364
|
the second adapter a `rebuild` gives comes up signed in as the same login from the secrets alone
|
|
5325
5365
|
before it reads anything (`drive.reload.login`), and is then held to everything the first was on
|
|
5326
5366
|
connect: the methods its declarations call for, the agent application's report, a capacity stated to it, its
|
|
5327
|
-
snapshot read as the connect snapshot was, and its
|
|
5328
|
-
`
|
|
5367
|
+
snapshot read as the connect snapshot was, and its audio opened afresh on a task carried with
|
|
5368
|
+
`audio: "started"`, since the first client's session died with it. The result also says which rules the
|
|
5329
5369
|
run evaluated, pass or fail, in `rulesEvaluated`: the validators' as each was applied, the stream's
|
|
5330
5370
|
as each case was considered, so a test that needs a rule to have run asserts it there rather than
|
|
5331
5371
|
inferring it from an empty `violations`, and a rule absent from it was never looked at, which is a
|
|
5332
5372
|
gap and not a pass. With the audio open on a softphone,
|
|
5333
5373
|
the drive mutes it for one second and reports the leg through `recordStep`, begun and then ended,
|
|
5334
5374
|
expecting each report `recorded` with the provider-selected history `at` (`drive.recordStep.failed`, `.rejected`, `result.recordStep.at`); then it mutes again and
|
|
5335
|
-
ends the call muted, as agents do, so the leg is open when the
|
|
5375
|
+
ends the call muted, as agents do, so the leg is open when the audio ends, the provider closes it
|
|
5336
5376
|
in the completing publication or the open entry is refused (`task.history.muted.open`),
|
|
5337
|
-
and the drive's closing report after the
|
|
5377
|
+
and the drive's closing report after the audio ended is expected `recorded` and to change nothing:
|
|
5338
5378
|
a record restated afterwards with that leg's duration altered is named (`drive.recordStep.overwritten`).
|
|
5339
5379
|
Where the provider restates the task's record afterwards, each leg is in it or the hole is named
|
|
5340
5380
|
(`drive.recordStep.history`). Given `rebuild`, a way to build the adapter again as an agent application reload
|
|
@@ -5347,7 +5387,7 @@ the record in memory has nothing and is named (`drive.reload.snapshot`, `drive.r
|
|
|
5347
5387
|
`.rejected`). The second adapter's snapshot is taken as any resync is: held to what the stream knew
|
|
5348
5388
|
before it replaces it, so a phase gone backwards, a record that shrank or audio forgotten on a task
|
|
5349
5389
|
still at work is named by the stream's own rules (`stream.snapshot.phase`, `.history`,
|
|
5350
|
-
`.
|
|
5390
|
+
`.audio`) and a reload is a place those rules keep working, not one where they stop. The second
|
|
5351
5391
|
adapter is held to what the first was: the same provider
|
|
5352
5392
|
(`drive.reload.manifest`), a snapshot that stands as any snapshot must, and a record that lost none
|
|
5353
5393
|
of the entries the first had published -- a record once read is not unread across a reload either.
|
|
@@ -5374,15 +5414,15 @@ completed from wherever it stands once the drive has nothing left to do on it --
|
|
|
5374
5414
|
after an `end-call`, or from `in-progress` where there is no call to end, which is every chat and
|
|
5375
5415
|
email and a voice task offering no `endCall` -- so a conversation reaches its end as a call does.
|
|
5376
5416
|
The drive stops where the task offers no way on and says nothing about what it could not reach. A
|
|
5377
|
-
softphone adapter written for a browser needs its
|
|
5378
|
-
harness: the drive opens
|
|
5417
|
+
softphone adapter written for a browser needs its audio APIs supplied by whatever runs the
|
|
5418
|
+
harness: the drive opens audio outside a browser, and a provider that quietly stopped carrying audio
|
|
5379
5419
|
where `AudioContext` was missing would be lying about the one thing the channel is for. It is off by default because it issues
|
|
5380
5420
|
commands against whatever platform the adapter is connected to: turn it on against a test backend.
|
|
5381
5421
|
|
|
5382
5422
|
```ts
|
|
5383
5423
|
const driven = await exerciseAdapter(adapter, context, { collectOnly: true, drive: true });
|
|
5384
5424
|
expect(driven.violations).toEqual([]);
|
|
5385
|
-
assertReached(driven, ["task.onCall", "task.
|
|
5425
|
+
assertReached(driven, ["task.onCall", "task.audio", "event.task-ended"]);
|
|
5386
5426
|
```
|
|
5387
5427
|
`assertReached(result, subjects)` is the paired assertion: it throws naming every subject the run
|
|
5388
5428
|
never met, so a test that meant to check a team member list cannot pass on a fixture that never produced one.
|
|
@@ -5419,11 +5459,11 @@ cannot be established from TypeScript structure alone.
|
|
|
5419
5459
|
| `stillHost(report?, guarantees?, mute?)` | An agent application that reports one thing and never changes, for a test context: `{ online: true }` by default, a report with audio for a softphone voice adapter, and never for a desk phone. |
|
|
5420
5460
|
| `TaskStream`, `BreakStream` | The cross-event models the harness applies after the connect snapshot, exported for an agent application that wants the same rules at its boundary: `seed(snapshot)`, then `apply(envelope)` returns the violations. |
|
|
5421
5461
|
| `assertBreakFollowsItsRequests(envelopes, snapshot?)` | A break follows its requests: a commit's states only after a grant, never backwards, and a forced break arriving in effect with `forced`. The harness applies the same rules after the connect snapshot. |
|
|
5422
|
-
| `
|
|
5462
|
+
| `assertAudioFollowsTheTask(envelopes, snapshot?)` | The audio follows the task and never decides it: every task is introduced once, `task-audio-started` and `task-audio-ended` alternate on work that has begun, audio ends only where it arrived, and what follows the audio ending is `completing` or `task-ended`. The harness applies the same rules to every event after the connect snapshot (`stream.*`). A sequence with no audio satisfies it by never testing it — pair it with the assertion that the audio end is present. |
|
|
5423
5463
|
| `assertBreakAttemptProviders(candidates, asked)` | A break attempt asks every usable provider holding capacity, `refreshing` included, and nothing of a provider whose login is `expired`. |
|
|
5424
5464
|
| `assertBreakBeginsAfterTask(steps)` | A break asked for on a task is committed as `starting-after-task` while work remains and reaches `on-break` only once nothing is outstanding — never beside a task, never later than the step that has none. |
|
|
5425
5465
|
| `assertDeniedAndRetriedBreak(states)` | A denial transitions directly to `not-requested`; a later request can still be granted. |
|
|
5426
|
-
| `assertWrapTimeout(task,
|
|
5466
|
+
| `assertWrapTimeout(task, audioEndedAt, deadline, toleranceMs?)` | The wrap deadline equals audio end plus the task allowance, within a tolerance that defaults to 1000ms; a task with no allowance has no deadline, and one observed is the violation. |
|
|
5427
5467
|
| `assertBrowserSessionIsolation(left, right, expected)` | Browser reuse follows only the declared isolation scheme. |
|
|
5428
5468
|
| `assertNoBrowserSessionKeyCollisions(scenarios)` | No two distinct scenarios derive the same session key. Feed it adversarial names. |
|
|
5429
5469
|
|
|
@@ -5456,11 +5496,11 @@ action sets. The outer capability may be locked. Absence grants no permission, a
|
|
|
5456
5496
|
exist without any permission. A provider may offer only stop for a recording started automatically,
|
|
5457
5497
|
or withdraw a control on a later task update. There is no global recording mode and no automatic
|
|
5458
5498
|
start from a capability declaration. Agent application support is declared on `ConnectContext.host.recording`,
|
|
5459
|
-
not the provider-owned manifest. The task
|
|
5460
|
-
unknown
|
|
5461
|
-
recording or a default upload location. Initial agent application support requires voice softphone
|
|
5499
|
+
not the provider-owned manifest. The task names one of the storage places the agent application provisioned, by `storageId`;
|
|
5500
|
+
an unknown storage place or an unsupported action is refused visibly, never redirected to provider
|
|
5501
|
+
recording or a default upload location. Initial agent application support requires voice softphone audio and
|
|
5462
5502
|
capture of both local and remote audio; an agent application unable to capture either must refuse start/resume.
|
|
5463
|
-
This package declares that contract; it supplies no recorder,
|
|
5503
|
+
This package declares that contract; it supplies no recorder, audio mixing, storage or upload.
|
|
5464
5504
|
|
|
5465
5505
|
| Action | Required current recording state | Confirmed outcome |
|
|
5466
5506
|
| --- | --- | --- |
|
|
@@ -5491,7 +5531,7 @@ There is no automatic restart, transfer to another recorder, or stop on task hol
|
|
|
5491
5531
|
Task removal does not prove recording stopped: outstanding agent application recorders remain tracked by the
|
|
5492
5532
|
agent application until its executor reconciles/finishes them, with visible unresolved cleanup failures.
|
|
5493
5533
|
|
|
5494
|
-
Only start/resume require an in-progress or paused task with started
|
|
5534
|
+
Only start/resume require an in-progress or paused task with started audio. Pause/stop/cancel may
|
|
5495
5535
|
also finish an independently observed recorder while the task is completing. A pending task may
|
|
5496
5536
|
show active recording, but agent controls wait until interaction begins. The two recording paths may
|
|
5497
5537
|
both be active. A command to either path has no implied effect on the other.
|
|
@@ -5534,9 +5574,9 @@ This change does not publish a package or enable recording in any existing agent
|
|
|
5534
5574
|
semantics, including pause/resume identity and rejection of a dialling result. It cannot prove audio
|
|
5535
5575
|
retention/completion or source truth from a status flag; those remain executor obligations.
|
|
5536
5576
|
|
|
5537
|
-
|
|
5577
|
+
The storage place named on the task is bound when start actually creates a recording. Existing recordings
|
|
5538
5578
|
keep that binding through pause/resume/stop/cancel; a later policy cannot redirect their stored audio.
|
|
5539
|
-
The executor rejects a mismatched
|
|
5579
|
+
The executor rejects a mismatched storage place rather than moving or discarding another binding.
|
|
5540
5580
|
Action permission does not authorize unattended invocation: agent application controls require the agent's explicit
|
|
5541
5581
|
act, and provider authorization remains enforced at its authenticated command boundary.
|
|
5542
5582
|
|