@xema/omni-protocol 0.1.116 → 0.1.118
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 +73 -30
- package/dist/index.js +10 -1
- package/dist/testing.d.ts +15 -1
- package/dist/testing.js +75 -1
- package/dist/validation.d.ts +19 -9
- package/dist/validation.js +126 -88
- package/guide/agent.md +19 -1
- package/guide/phone.md +1 -1
- package/guide/voice.md +115 -109
- package/guide.md +72 -35
- package/package.json +1 -1
package/guide/voice.md
CHANGED
|
@@ -262,7 +262,7 @@ provider decides: another IVR, a queue, a survey, or the end of the call. Gated
|
|
|
262
262
|
with nobody coming back to them, and the provider's path for a caller who continues never begins.
|
|
263
263
|
So the caller is taken off hold first: the agent computer holds the command back while the task is `paused`
|
|
264
264
|
(`command.endCall.held`) and shows End call disabled on hold, and a provider that receives it
|
|
265
|
-
anyway answers `failed
|
|
265
|
+
anyway answers `failed` with `omni.on-hold`, so the agent application can say resume first. Resume, then end. `terminate-call` needs no such gate: it ends the
|
|
266
266
|
caller's channel with the rest, held or not, and nobody is left waiting.
|
|
267
267
|
|
|
268
268
|
**`terminate-call` ends the whole call.** Every channel on it ends at the provider: the agent's,
|
|
@@ -463,114 +463,120 @@ agent's.
|
|
|
463
463
|
## Independent task recording
|
|
464
464
|
|
|
465
465
|
Recording is voice-only. A task can arrive already recording, including while pending, and offer
|
|
466
|
-
no recording controls.
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
an unknown storage place or an unsupported action is refused visibly, never redirected to provider
|
|
480
|
-
recording or a default upload location. Initial agent application support requires voice softphone audio and
|
|
481
|
-
capture of both local and remote audio; an agent application unable to capture either must refuse start/resume.
|
|
482
|
-
This package declares that contract; it supplies no recorder, audio mixing, storage or upload.
|
|
483
|
-
|
|
484
|
-
| Action | Required current recording state | Confirmed outcome |
|
|
485
|
-
| --- | --- | --- |
|
|
486
|
-
| start | inactive | active with a new recording identity |
|
|
487
|
-
| pause | active | paused, preserving recording identity and captured audio |
|
|
488
|
-
| resume | paused | active with the same recording identity |
|
|
489
|
-
| stop | active or paused | inactive; finish and retain captured audio |
|
|
490
|
-
| cancel | active or paused | inactive; abandon and discard captured audio |
|
|
491
|
-
|
|
492
|
-
Stop finishes the recording and retains captured audio. Cancel abandons the recording and discards
|
|
493
|
-
its captured audio; the task policy offers it with `cancel: true`. There is no configurable cancel
|
|
494
|
-
effect. A recorder that cannot confirm completion must not offer Cancel; it can offer Stop instead.
|
|
495
|
-
Cancel never means cancelling an in-flight start request or deleting arbitrary past recordings.
|
|
496
|
-
Stop can be applied only after finalization/retention succeeds; Cancel only after both cessation
|
|
497
|
-
and completion of this recording's audio succeed under the recorder's storage contract.
|
|
498
|
-
Partial success -- capture stopped but the storage outcome unknown, a start the recorder cannot
|
|
499
|
-
vouch for either way -- is a settled `failed`, under the code `omni.recording-unsettled`: the
|
|
500
|
-
command did not do what it promised, and the state the provider publishes with it is whatever
|
|
501
|
-
capture state is actually known. It is never `applied`, and never an unsettled promise the agent
|
|
502
|
-
application would be left to resync, since the provider knows exactly what it could not settle.
|
|
503
|
-
There is no request identity on a recording command: the outcome is the state, and what the
|
|
504
|
-
agent sees is the state. Retention is not a promise of sample-perfect audio.
|
|
505
|
-
|
|
506
|
-
Provider commands go exclusively to `Connection.execute`; agent application commands go exclusively to
|
|
507
|
-
`HostRecording.execute`. Both include the assignment and observation identities; all
|
|
508
|
-
non-start commands identify the particular recording. IDs are opaque and scoped by provider login,
|
|
509
|
-
task and recorder owner. They are never inferred from filenames or current agent identity. A
|
|
510
|
-
recording ID survives pause/resume and reassignment only where the same recorder confirms continuity;
|
|
511
|
-
commands always name the current assignment. A later start gets a different ID. Reconnect does not
|
|
512
|
-
create a new recording or fresh evidence. After lost continuity, use unknown until reconciled.
|
|
513
|
-
There is no automatic restart, hand-over to another recorder, or stop on task hold/disconnect.
|
|
514
|
-
Task removal does not prove recording stopped: outstanding agent application recorders remain tracked by the
|
|
515
|
-
agent application until its executor reconciles/finishes them, with visible unresolved cleanup failures.
|
|
516
|
-
|
|
517
|
-
Only start/resume require an in-progress or paused task with started audio. Pause/stop/cancel may
|
|
518
|
-
also finish an independently observed recorder while the task is completing. A pending task may
|
|
519
|
-
show active recording, but agent controls wait until interaction begins. The two recording paths may
|
|
520
|
-
both be active. A command to either path has no implied effect on the other.
|
|
521
|
-
|
|
522
|
-
Each confirmed observation has a fresh opaque observation identity, a canonical UTC millisecond
|
|
523
|
-
observation instant and an exclusive expiry. These times describe current evidence, never historical
|
|
524
|
-
capture boundaries. A trusted observer-domain current time must satisfy observedAt <= now < validUntil.
|
|
525
|
-
Use `effectiveRecordingState` with that explicitly trusted time; an unavailable clock yields unknown.
|
|
526
|
-
Receipt, replay and task publication never extend freshness. Clock discontinuity invalidates evidence;
|
|
527
|
-
consumers using the explicit recording-expiry clock-estimation exception must invalidate that
|
|
528
|
-
estimate and use monotonic aging so clock rollback cannot
|
|
529
|
-
revive expired evidence. The executor compares observation identity and recording identity against
|
|
530
|
-
its latest state atomically before I/O. An intervening observation or assignment change refuses the
|
|
531
|
-
stale command; it never acts on a replacement recorder. Providers lacking trustworthy current-state
|
|
532
|
-
identity or observation time publish unknown and offer no issuable controls.
|
|
533
|
-
|
|
534
|
-
Use `validateTask` and `validateTaskCommand` for published shape and static permissions.
|
|
535
|
-
Immediately before dispatch, additionally use `validateRecordingRequest` against the full current
|
|
536
|
-
task and explicit observer-domain time. For agent application commands also supply the current agent application declaration,
|
|
537
|
-
full agent application report and softphone context. The same checks must run at the executing boundary;
|
|
538
|
-
client validation alone is not authorization. The executor serializes operations on each recorder
|
|
539
|
-
and compares `observationId` and `recordingId` against its latest evidence before acting: a
|
|
540
|
-
command against superseded evidence is refused, and nothing is retried on the executor's own
|
|
541
|
-
account.
|
|
542
|
-
|
|
543
|
-
Keep command progress in agent application UI separately from authoritative state. Applied means the requested
|
|
544
|
-
recording transition and any retain/discard effect actually completed; publish the confirming task or agent application report before
|
|
545
|
-
resolving applied. Failed means confirmed no effect. Rejected promise means outcome unknown:
|
|
546
|
-
show the failure, reconcile that path and do not automatically retry or pretend inactive. Authoritative
|
|
547
|
-
updates remain full current task/agent application views, not replayed provider events. These current-state fields
|
|
548
|
-
create no interaction-history entries and infer no actors, durations or historical capture boundaries.
|
|
549
|
-
|
|
550
|
-
Migration is explicit: replace the old true recording capability with per-path action policies,
|
|
551
|
-
replace untargeted commands with scoped commands, and retain protocol version 1. During pre-release
|
|
552
|
-
development, hosts and adapters must align their recording contracts; negotiation does not detect
|
|
553
|
-
differences between package revisions that share that protocol number. No legacy-shape fallback is provided.
|
|
554
|
-
This change does not publish a package or enable recording in any existing agent application/provider by itself.
|
|
555
|
-
|
|
556
|
-
`validateRecordingOutcome` checks confirming observations against recording-specific applied/failed
|
|
557
|
-
semantics, including pause/resume identity and rejection of a dialling result. It cannot prove audio
|
|
558
|
-
retention/completion or source truth from a status flag; those remain executor obligations.
|
|
559
|
-
|
|
560
|
-
The storage place named on the task is bound when start actually creates a recording. Existing recordings
|
|
561
|
-
keep that binding through pause/resume/stop/cancel; a later policy cannot redirect their stored audio.
|
|
562
|
-
The executor rejects a mismatched storage place rather than moving or discarding another binding.
|
|
563
|
-
Action permission does not authorize unattended invocation: agent application controls require the agent's explicit
|
|
564
|
-
act, and provider authorization remains enforced at its authenticated command boundary.
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
Recording dispatch must receive the same known task-validation context as the published task.
|
|
568
|
-
Pass organisation levels and other known restrictions through the optional fourth argument of
|
|
569
|
-
`validateTaskCommand`, and through `taskContext` in `validateRecordingRequest`. This keeps a valid
|
|
570
|
-
custom organisation lock from being rejected against the default ladder, and prevents known
|
|
571
|
-
capability restrictions from disappearing at dispatch. The standalone `validateRecordingCommandState`
|
|
572
|
-
requires an affirmative permission; false, malformed permission declarations and unknown actions grant nothing.
|
|
466
|
+
no recording controls. **Each recorder publishes its own recordings, as a list, whenever one
|
|
467
|
+
changes.** The provider's are the task's `recordings`, restated whole on every publication of the task
|
|
468
|
+
and on every snapshot. The agent application's are on its `HostReport.recordings`, one entry per
|
|
469
|
+
assignment, never on a provider's task. A provider task update cannot replace the agent
|
|
470
|
+
application's recordings, and a command to one recorder has no effect on the other.
|
|
471
|
+
|
|
472
|
+
```json
|
|
473
|
+
"recordings": [
|
|
474
|
+
{ "id": "rec-7", "follows": "party", "status": "recording" },
|
|
475
|
+
{ "id": "rec-8", "follows": "agent", "status": "paused", "reason": "sensitive-details" },
|
|
476
|
+
{ "id": "rec-3", "follows": "party", "status": "not-recording" }
|
|
477
|
+
]
|
|
478
|
+
```
|
|
573
479
|
|
|
480
|
+
**A recording follows one side of the call.** `follows` says whose channel it captures: the
|
|
481
|
+
`party`'s, or the `agent`'s own. The two are separate captures on most platforms, and the agent
|
|
482
|
+
computer needs to know which: a recording that follows the party keeps capturing hold music
|
|
483
|
+
while the agent talks to a colleague, and only one that follows the agent says whether the
|
|
484
|
+
agent's own voice is on it. Each side has at most one recording that is, or may be, capturing at
|
|
485
|
+
once -- `starting`, `recording`, `paused` or `unknown` (`recording.follows.live`) -- and any number
|
|
486
|
+
that have finished.
|
|
487
|
+
|
|
488
|
+
**Nothing expires.** A recording stands as its recorder last stated it until the recorder says
|
|
489
|
+
otherwise. There is no observation time, no validity window and no clock to compare: everything
|
|
490
|
+
the agent computer shows comes from the wire, and a recording mark that went dark when a recorder
|
|
491
|
+
check ran late, then came back, was a fault reported from the floor. The recorder publishes on
|
|
492
|
+
change and restates the whole list on connect and resume.
|
|
493
|
+
|
|
494
|
+
| Status | Meaning |
|
|
495
|
+
| --- | --- |
|
|
496
|
+
| `starting` | Asked for and not yet confirmed by the recorder. |
|
|
497
|
+
| `recording` | Capturing. A pause or a stop still in flight stays `recording` until the recorder confirms it: telling the agent they are not recorded when they may be is the one thing this must never say. |
|
|
498
|
+
| `paused` | Open but not capturing, with a `reason`: `requested`, someone asked for it, or `sensitive-details`, the platform paused it while card or account details are taken. |
|
|
499
|
+
| `not-recording` | Finished, or never started on that side; captured audio is kept or discarded as its stop or cancel said. |
|
|
500
|
+
| `unknown` | The recorder cannot say, with a `reason`: `recorder-unreachable`, or `unsettled`, a command whose effect it could not establish either way. Never shown as stopped. |
|
|
501
|
+
|
|
502
|
+
A mark that is not lit says why: `paused` and `unknown` carry a reason from those closed lists, and
|
|
503
|
+
nothing else carries one (`recording.reason`, `.reason.unexpected`). Task hold, task pause and
|
|
504
|
+
recording pause are independent.
|
|
505
|
+
|
|
506
|
+
**Permission is per task.** The policy is `Task.capabilities.recording`, with independently
|
|
507
|
+
optional provider and agent application action sets. The outer capability may be locked. Absence
|
|
508
|
+
grants no permission, and a recording may exist without any permission. A provider may offer only
|
|
509
|
+
stop for a recording started automatically, or withdraw a control on a later task update. There is
|
|
510
|
+
no global recording mode and no automatic start from a capability declaration. Agent application
|
|
511
|
+
support is declared on `ConnectContext.host.recording`, not the provider-owned manifest. The task
|
|
512
|
+
names one of the storage places the agent application provisioned, by `storageId`; an unknown
|
|
513
|
+
storage place or an unsupported action is refused visibly, never redirected to provider recording
|
|
514
|
+
or a default upload location. Initial agent application support requires voice softphone audio and
|
|
515
|
+
capture of both local and remote audio; an agent application unable to capture either must refuse
|
|
516
|
+
start/resume. This package declares that contract; it supplies no recorder, audio mixing, storage
|
|
517
|
+
or upload.
|
|
518
|
+
|
|
519
|
+
**A command names what it acts on.** A start names the side, `follows`; every other action names
|
|
520
|
+
the recording by its `recordingId`. The recorder mints the id when it accepts the start, before
|
|
521
|
+
the recorder has answered, publishes the recording `starting` under it, and keeps it through pause
|
|
522
|
+
and resume; a later start is a new recording with a new id. The recorder serializes the operations
|
|
523
|
+
on each recording and refuses an action the recording no longer stands for: a pause on a recording
|
|
524
|
+
that is not `recording`, a stop on one already `not-recording`, a start on a side already
|
|
525
|
+
capturing, a recording the task does not carry (`recording.command.state`,
|
|
526
|
+
`.command.recordingId`). Nothing is retried on the recorder's own account.
|
|
527
|
+
|
|
528
|
+
| Action | The recording before | Confirmed outcome |
|
|
529
|
+
| --- | --- | --- |
|
|
530
|
+
| start | none live on that side | a new recording on that side, `recording` |
|
|
531
|
+
| pause | `recording` | the same recording, `paused`, captured audio kept |
|
|
532
|
+
| resume | `paused` | the same recording, `recording` |
|
|
533
|
+
| stop | `recording` or `paused` | the same recording, `not-recording`; captured audio finished and kept |
|
|
534
|
+
| cancel | `recording` or `paused` | the same recording, `not-recording`; captured audio discarded |
|
|
535
|
+
|
|
536
|
+
Stop finishes the recording and retains captured audio. Cancel abandons it and discards captured
|
|
537
|
+
audio; the task policy offers it with `cancel: true`, and there is no configurable cancel effect.
|
|
538
|
+
A recorder that cannot confirm completion must not offer Cancel; it can offer Stop instead. Cancel
|
|
539
|
+
never means cancelling an in-flight start or deleting past recordings. Stop is applied only after
|
|
540
|
+
finalization and retention succeed; Cancel only after both cessation and discarding succeed.
|
|
541
|
+
**Applied means confirmed**: the recorder publishes the confirming list before it answers
|
|
542
|
+
`applied`. Failed means confirmed no effect. Partial success -- capture stopped but the storage
|
|
543
|
+
outcome unknown, a start the recorder cannot vouch for either way -- is a settled `failed` under
|
|
544
|
+
`omni.recording-unsettled`, and the recording is published `unknown` with the reason `unsettled`.
|
|
545
|
+
A rejected promise means the outcome is unknown: show the failure, reconcile, and do not retry or
|
|
546
|
+
pretend `not-recording`. There is no request identity on a recording command: the outcome is the
|
|
547
|
+
recording, and what the agent sees is the recording. Retention is not a promise of sample-perfect
|
|
548
|
+
audio.
|
|
549
|
+
|
|
550
|
+
Provider commands go only to `Connection.execute`; agent application commands go only to
|
|
551
|
+
`HostRecording.execute`. Both name the assignment. Ids are opaque and scoped by provider login,
|
|
552
|
+
task and recorder; they are never inferred from filenames or the current agent. Reconnect creates
|
|
553
|
+
no recording; the recorder restates its list. There is no automatic restart, hand-over to another
|
|
554
|
+
recorder, or stop on task hold or disconnect. Task removal does not prove recording stopped:
|
|
555
|
+
outstanding agent application recordings stay tracked by the agent application until its recorder
|
|
556
|
+
finishes them, with unresolved cleanup failures visible.
|
|
557
|
+
|
|
558
|
+
Only start and resume need an `in-progress` or `paused` task with started audio. Pause, stop and
|
|
559
|
+
cancel may also finish a recording while the task is `completing`. A pending task may show a
|
|
560
|
+
recording, but agent controls wait until interaction begins.
|
|
561
|
+
|
|
562
|
+
Use `validateTask` and `validateTaskCommand` for the published shape and static permissions.
|
|
563
|
+
Immediately before dispatch, also use `validateRecordingRequest` against the full current task;
|
|
564
|
+
for agent application commands supply the current declaration, full report and softphone context.
|
|
565
|
+
The same checks run at the recorder; client validation alone is not authorization.
|
|
566
|
+
`validateRecordings` checks one recorder's list, and `validateRecordingOutcome` checks an answer
|
|
567
|
+
against the lists published before and after it: an applied start shows a new recording on its side,
|
|
568
|
+
anything else the same recording in its new status, and a failed one nothing changed there. It
|
|
569
|
+
cannot prove retention or source truth from a status; those remain the recorder's obligations. The
|
|
570
|
+
storage place named on the task is bound when start creates a recording, and later policy cannot
|
|
571
|
+
redirect it.
|
|
572
|
+
|
|
573
|
+
Recording dispatch receives the same known task-validation context as the published task: pass
|
|
574
|
+
organisation levels and other known restrictions through the optional fourth argument of
|
|
575
|
+
`validateTaskCommand`, and through `taskContext` in `validateRecordingRequest`, so a valid custom
|
|
576
|
+
organisation lock is not rejected against the default ladder. Migration is explicit: the former
|
|
577
|
+
`recording` state with its observation and validity window is refused on a task
|
|
578
|
+
(`recording.task.renamed`), and a command carries no observation id. Protocol version 1 stays;
|
|
579
|
+
there is no legacy-shape fallback.
|
|
574
580
|
|
|
575
581
|
### Agent application recording announcements to the caller
|
|
576
582
|
|
|
@@ -590,7 +596,7 @@ cancel, they hear that recording stopped. If pause/resume is supported, say paus
|
|
|
590
596
|
pause is not presented as a finished recording. Announcements follow actual capture transitions,
|
|
591
597
|
not button clicks, accepted requests or task-policy changes. When attaching to an already-active
|
|
592
598
|
agent application recording, announce that recording is active, without inventing its original start time.
|
|
593
|
-
|
|
599
|
+
Restating the same status does not repeat an announcement. A recording that is `unknown`
|
|
594
600
|
must never produce a stopped announcement.
|
|
595
601
|
|
|
596
602
|
A local sound, UI indicator, screen-reader message to the agent, or sound leaking through the
|
package/guide.md
CHANGED
|
@@ -70,7 +70,7 @@ state use `status`; name the object when discussing them to avoid ambiguity.
|
|
|
70
70
|
| `phase` | A task | `pending`, `confirmed`, `preview`, `in-progress`, `paused`, `completing` |
|
|
71
71
|
| `audio` | A task's audio | `started`, `ended` |
|
|
72
72
|
| `transport` | A connection | `connecting`, `active`, `error` |
|
|
73
|
-
| `status` | An authentication session | `signed-out`, `authenticating`, `authenticated`, `refreshing`, `expired` |
|
|
73
|
+
| `status` | An authentication session | `signed-out`, `authenticating`, `authenticated`, `refreshing`, `expired`, `terminated` |
|
|
74
74
|
| `status` | The agent’s break lifecycle | `not-requested`, `awaiting-approval`, `granted`, `starting-after-task`, `on-break` |
|
|
75
75
|
| `availability` | A team member | `ready`, `on-task`, `on-break`, `elsewhere`, `signed-out` |
|
|
76
76
|
|
|
@@ -83,7 +83,7 @@ user IDs, returned user details and directory requirements are unchanged.
|
|
|
83
83
|
|
|
84
84
|
### `OMNI_PROTOCOL_VERSION`
|
|
85
85
|
|
|
86
|
-
The exact protocol version implemented by this package. The current value remains `1` during pre-release development. Recording contract changes do not introduce a new protocol version. Hosts and adapters adopt the current declarations together;
|
|
86
|
+
The exact protocol version implemented by this package. The current value remains `1` during pre-release development. Recording contract changes do not introduce a new protocol version. Hosts and adapters adopt the current declarations together; there is no compatibility mapping for older recording shapes.
|
|
87
87
|
|
|
88
88
|
### `Manifest.supportedProtocolVersions`
|
|
89
89
|
|
|
@@ -301,7 +301,8 @@ type AuthenticationState =
|
|
|
301
301
|
| { status: "authenticating" }
|
|
302
302
|
| { status: "authenticated"; identity: User; capabilities: UserCapabilities; expiresAt?: IsoTimestamp }
|
|
303
303
|
| { status: "refreshing"; identity: User; capabilities: UserCapabilities }
|
|
304
|
-
| { status: "expired"; identity?: User; failure?: AuthenticationFailure }
|
|
304
|
+
| { status: "expired"; identity?: User; failure?: AuthenticationFailure }
|
|
305
|
+
| { status: "terminated"; by: UserId | "provider"; identity?: User; failure: AuthenticationFailure & { retryable: false } };
|
|
305
306
|
|
|
306
307
|
type AuthenticationFailure = {
|
|
307
308
|
code: string;
|
|
@@ -342,23 +343,22 @@ interface TaskRecordingPolicy {
|
|
|
342
343
|
provider?: RecordingActions;
|
|
343
344
|
host?: RecordingActions & { storageId: string };
|
|
344
345
|
}
|
|
345
|
-
type
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
| { action: "
|
|
357
|
-
| { action: "pause" | "resume" | "stop" | "cancel"; recordingId: string }
|
|
346
|
+
type RecordingSide = "party" | "agent";
|
|
347
|
+
type RecordingStatus = "starting" | "recording" | "paused" | "not-recording" | "unknown";
|
|
348
|
+
type RecordingPausedReason = "requested" | "sensitive-details";
|
|
349
|
+
type RecordingUnknownReason = "recorder-unreachable" | "unsettled";
|
|
350
|
+
type Recording = { id: string; follows: RecordingSide } & (
|
|
351
|
+
| { status: "starting" | "recording" | "not-recording"; reason?: never }
|
|
352
|
+
| { status: "paused"; reason: RecordingPausedReason }
|
|
353
|
+
| { status: "unknown"; reason: RecordingUnknownReason }
|
|
354
|
+
);
|
|
355
|
+
type RecordingCommand = { type: "recording"; source: RecordingSource } & (
|
|
356
|
+
| { action: "start"; follows: RecordingSide; recordingId?: never }
|
|
357
|
+
| { action: "pause" | "resume" | "stop" | "cancel"; recordingId: string; follows?: never }
|
|
358
358
|
);
|
|
359
359
|
interface HostRecordingReport {
|
|
360
360
|
assignmentId: AssignmentId;
|
|
361
|
-
|
|
361
|
+
recordings: Recording[];
|
|
362
362
|
}
|
|
363
363
|
interface HostRecording {
|
|
364
364
|
announcesToCaller?: true;
|
|
@@ -693,7 +693,8 @@ type HistoryStep =
|
|
|
693
693
|
| "muted"
|
|
694
694
|
| "transferred"
|
|
695
695
|
| "conferenced"
|
|
696
|
-
| "unanswered"
|
|
696
|
+
| "unanswered"
|
|
697
|
+
| "callback-requested";
|
|
697
698
|
|
|
698
699
|
type TaskHistory = {
|
|
699
700
|
steps: TaskHistoryStep[];
|
|
@@ -776,8 +777,8 @@ type Task<C extends Channel = Channel> = {
|
|
|
776
777
|
history?: TaskHistory;
|
|
777
778
|
} & TaskCompletion & (
|
|
778
779
|
C extends "voice"
|
|
779
|
-
? {
|
|
780
|
-
: {
|
|
780
|
+
? { recordings?: Recording[]; onCall?: OnCall[]; leadAssist?: TaskLeadAssist; takenOver?: TaskTakenOver; audio?: TaskAudioState }
|
|
781
|
+
: { recordings?: never; onCall?: never; leadAssist?: never; takenOver?: never; audio?: never }
|
|
781
782
|
);
|
|
782
783
|
|
|
783
784
|
type PreviewDeadline = "provider-dials" | "host-dials" | "waits";
|
|
@@ -1344,6 +1345,7 @@ const OMNI_FAILURE_CODES = [
|
|
|
1344
1345
|
"omni.unavailable",
|
|
1345
1346
|
"omni.break-already-committed",
|
|
1346
1347
|
"omni.recording-unsettled",
|
|
1348
|
+
"omni.on-hold",
|
|
1347
1349
|
"omni.break-forced-by-provider",
|
|
1348
1350
|
] as const;
|
|
1349
1351
|
type OmniFailureCode = (typeof OMNI_FAILURE_CODES)[number];
|
|
@@ -1991,11 +1993,12 @@ reports later changes.
|
|
|
1991
1993
|
|
|
1992
1994
|
| Status | Contract |
|
|
1993
1995
|
| --- | --- |
|
|
1994
|
-
| `signed-out` | No usable provider session exists. |
|
|
1996
|
+
| `signed-out` | No usable provider session exists, because the agent signed out on this agent computer, or never signed in. Only the agent's own Sign out here leads to it. |
|
|
1995
1997
|
| `authenticating` | An interactive `browser-sso` or `credentials` flow is active. |
|
|
1996
1998
|
| `authenticated` | A usable session exists. Includes the provider identity, the login's `capabilities`, and optional token expiry time. |
|
|
1997
1999
|
| `refreshing` | The adapter is refreshing its session. Existing provider identity and capabilities remain available, unchanged: a change to either is published as `authenticated`. |
|
|
1998
|
-
| `expired` | The session
|
|
2000
|
+
| `expired` | The session's time ran out, or its refresh is failing. It may include an identity and typed failure. The agent signs in again on the same login, and the workspace stays. |
|
|
2001
|
+
| `terminated` | The session was ended from outside this agent computer: by a supervisor, by the agent on another screen, or by the platform itself. `by` says who, a person by `UserId` or `provider` where no person was behind it (`authentication.terminated.by`), and `failure` says why and is never retryable (`authentication.terminated.failure`, `.retryable`). It is never reported as `expired`, which would hide who ended it, nor as `signed-out`, which is the agent's own act. |
|
|
1999
2002
|
|
|
2000
2003
|
Omni calls `connect()` only after authentication reaches `authenticated`. Token refresh remains
|
|
2001
2004
|
adapter-owned; the adapter publishes `refreshing`, followed by `authenticated` or `expired`.
|
|
@@ -2137,16 +2140,50 @@ authorization codes, tokens, or provider responses containing secrets.
|
|
|
2137
2140
|
|
|
2138
2141
|
### Sign-out
|
|
2139
2142
|
|
|
2140
|
-
`signOut()`
|
|
2141
|
-
session
|
|
2142
|
-
|
|
2143
|
+
`signOut()` is the agent's own Sign out on this agent computer. It tells the provider where
|
|
2144
|
+
supported, revoking or invalidating the session, then **deletes the stored session secrets
|
|
2145
|
+
whatever happened**, and moves state to `signed-out`. Telling the provider may be retried within a
|
|
2146
|
+
short bound the adapter states, and then given up: a provider that cannot be reached never keeps a
|
|
2147
|
+
token on the agent's machine. If it was not told, `signOut()` still deletes and still reaches
|
|
2148
|
+
`signed-out`, and answers `failed`, so the agent application can show that a session may remain at
|
|
2149
|
+
the provider. `close()` stops authentication-state observation but does not sign the agent out.
|
|
2150
|
+
|
|
2151
|
+
**The final moments are the agent application's to finish.** Its own steps before signing out --
|
|
2152
|
+
stopping capacity, putting tasks down -- may be retried within a short bound it states, and then
|
|
2153
|
+
given up; a step that failed never skips `signOut()`. A desk that skipped the adapter's sign-out
|
|
2154
|
+
because its own retirement failed left the token behind, and the next reload restored the session.
|
|
2155
|
+
|
|
2156
|
+
### How a session's secrets end
|
|
2157
|
+
|
|
2158
|
+
A token that can no longer start a session does not outlive it. The adapter deletes the stored
|
|
2159
|
+
secrets **before** it publishes the ending, so that nothing is left for a reload to restore:
|
|
2160
|
+
|
|
2161
|
+
| Ending | Secrets | The provider |
|
|
2162
|
+
| --- | --- | --- |
|
|
2163
|
+
| `signed-out` | Deleted, whether or not the provider was told. | Told where supported, as above. |
|
|
2164
|
+
| `terminated` | Deleted. | Never told: the session is already over, and a sign-out sent now could end the agent's newer session somewhere else. |
|
|
2165
|
+
| `expired` with a failure that says `retryable: true` | Kept, for the retry: a network outage does not sign out a floor. | Retried by the adapter. |
|
|
2166
|
+
| `expired` otherwise | Deleted: the provider refused the token, or its time ran out. | Not told. |
|
|
2167
|
+
|
|
2168
|
+
**The agent application makes sure after the adapter.** Once a session is `signed-out`,
|
|
2169
|
+
`terminated`, or `expired` with anything but a retryable failure, the agent application clears that
|
|
2170
|
+
provider's partition of the store, and a sign-in after it always starts a fresh flow and never
|
|
2171
|
+
resumes a stored token. The adapter deletes; the agent application makes sure. `testAdapter` holds
|
|
2172
|
+
the adapter's half: when a published state ends the session, the store it handed the adapter must
|
|
2173
|
+
already be empty (`authentication.secrets.retained`).
|
|
2143
2174
|
|
|
2144
2175
|
### Secure-storage boundary
|
|
2145
2176
|
|
|
2146
|
-
|
|
2147
|
-
`get`, `set`, and `delete`; adapters cannot enumerate another provider's secrets.
|
|
2148
|
-
|
|
2149
|
-
|
|
2177
|
+
The agent application provides the `SecretStore`, scoped to the provider manifest ID. It exposes
|
|
2178
|
+
only `get`, `set`, and `delete`; adapters cannot enumerate another provider's secrets. **What backs
|
|
2179
|
+
it is the agent application's, and depends on what it is**: a native agent application uses the
|
|
2180
|
+
operating system's keychain; a browser agent application has none, and keeps the store in the
|
|
2181
|
+
tab's session storage, which survives a reload and ends with the tab. A browser store is readable
|
|
2182
|
+
by any script running on its page, so a browser agent application is only as safe as its own page.
|
|
2183
|
+
|
|
2184
|
+
Adapters may store refresh tokens or equivalent session material, but never raw submitted
|
|
2185
|
+
credentials, and they put a secret nowhere but the `SecretStore`: never in manifests, logs,
|
|
2186
|
+
events, snapshots, task attributes, errors, the login's `store`, or browser storage of their own.
|
|
2150
2187
|
|
|
2151
2188
|
## Connecting to a provider
|
|
2152
2189
|
|
|
@@ -2379,8 +2416,6 @@ The current exceptions and their reasons are:
|
|
|
2379
2416
|
| --- | --- |
|
|
2380
2417
|
| Agent application display of provider deadlines (`expiresInSeconds`, `previewEndsInSeconds`, `wrapEndsInSeconds`) | Each is seconds from the publication that carried it, counted down on the agent computer from receipt: the provider's own arithmetic, so no clock is compared and the transport delay is the only error. The countdown estimates the display only; it does not establish that the provider acted. Without usable time, show timing uncertainty. |
|
|
2381
2418
|
| Agent application-triggered preview end | The agent computer counts `previewEndsInSeconds` down from receipt and issues `dial` when it reaches zero: the provider owns the deadline and the agent computer owns the trigger. It does not dial before the countdown has run out, and a reload restarts it from the next publication. |
|
|
2382
|
-
| Recording evidence expiry | A bounded observer-domain clock estimate with monotonic aging may assess freshness because observation and expiry belong to the recorder's clock. If time cannot be trusted, recording state is unknown; never renew evidence from receipt or replay. |
|
|
2383
|
-
| Unknown recording state | `observedAt` and `validUntil` are absent because there is no confirmed observation. The containing provider event still has its own `occurredAt`; that publication is not a recorder observation. |
|
|
2384
2419
|
| Direct snapshot reads and method requests/results | These are reads/operations, not event envelopes, and their current types have no general event timestamp. A snapshot event still carries `occurredAt`; embedded source instants remain unchanged. Do not treat a method result or read completion time as an occurrence boundary. |
|
|
2385
2420
|
| Agent application-provided provider-clock estimate | Best-effort ISO time may be supplied by the agent application for optional comparisons; it is not a source observation or accuracy guarantee. The reason is that the agent application does not own the provider clock. |
|
|
2386
2421
|
| Provider receipt timestamps | The provider may use its own receipt instant for final records of receipt/processing because agent application timestamps are untrusted advisory input. Receipt must not be presented as an earlier action or capture boundary. |
|
|
@@ -2488,14 +2523,13 @@ A provider need not trust or adopt any agent application-supplied timestamp. It
|
|
|
2488
2523
|
message using its own clock at receipt and use that for its own processing/accounting. The reason
|
|
2489
2524
|
for this exception is that agent application clocks and agent application estimates are not authoritative at the provider.
|
|
2490
2525
|
Receipt time must be described as receipt/observation time, not relabeled as the original agent application
|
|
2491
|
-
action,
|
|
2526
|
+
action, a recording's start or stop, or proof an operation applied. An existing field with a
|
|
2492
2527
|
specific occurrence meaning still requires that evidence; this option does not silently redefine
|
|
2493
2528
|
it. Agent application-only instants remain advisory input; the provider owns its authoritative publication.
|
|
2494
2529
|
|
|
2495
2530
|
This agent application estimate is another explicit timestamp exception: it helps optional displays and
|
|
2496
2531
|
provider-clock comparisons when the agent application lacks that clock directly. It is not a source occurrence,
|
|
2497
|
-
may not replace event/history timestamps, and alone cannot authorize deadline actions
|
|
2498
|
-
recording freshness. Uses requiring a trustworthy bound still need independently established
|
|
2532
|
+
may not replace event/history timestamps, and alone cannot authorize deadline actions. Uses requiring a trustworthy bound still need independently established
|
|
2499
2533
|
clock accuracy/drift assumptions. It neither synchronizes the operating-system clock nor changes
|
|
2500
2534
|
ISO timestamps already published by the provider.
|
|
2501
2535
|
|
|
@@ -2677,7 +2711,9 @@ snapshots until it ends.
|
|
|
2677
2711
|
with `task-ended` and an outcome, whatever became of the call: answered and completed, declined,
|
|
2678
2712
|
withdrawn, abandoned in the ring, lapsed, taken over. An offer that is simply never mentioned
|
|
2679
2713
|
again leaves the agent computer holding a task nobody will close, and the test names it
|
|
2680
|
-
(`stream.taskOffered.unended`).
|
|
2714
|
+
(`stream.taskOffered.unended`). **Every dial is owed its outcome** the same way: a dial the test
|
|
2715
|
+
placed and the provider answered `dialling` with no `dial-outcome` by the end of the run is named
|
|
2716
|
+
(`stream.dialOutcome.missing`).
|
|
2681
2717
|
|
|
2682
2718
|
### `task-updated`
|
|
2683
2719
|
|
|
@@ -3108,6 +3144,7 @@ cannot be established from TypeScript structure alone.
|
|
|
3108
3144
|
| `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. |
|
|
3109
3145
|
| `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. |
|
|
3110
3146
|
| `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. |
|
|
3147
|
+
| `assertHistoryAgreesAcrossTasks(earlier, later)` | Two tasks of one call -- a hand-over, a take-over, a callback: the later restates every earlier step in order, with the same step, instant, person, dial and destination, and any duration already stated unchanged. Throws when either carries no history. |
|
|
3111
3148
|
| `assertBreakAttemptProviders(candidates, asked)` | A break attempt asks every usable provider holding capacity, `refreshing` included, and nothing of a provider whose login is `expired`. |
|
|
3112
3149
|
| `assertForcedBreakStopsTheRest(forcedOn, candidates, stopped)` | A forced break on one provider stops the agent everywhere else: capacity zero stated on every other usable provider holding capacity, nothing on the forcing one, nothing on a dead login. See **Forced breaks** in `guide/breaks.md`. |
|
|
3113
3150
|
| `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. |
|