@xema/omni-protocol 0.1.123 → 0.1.124

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -2095,6 +2095,20 @@ export declare const HISTORY_STEPS_WITH_A_PERSON: readonly ["offered", "answered
2095
2095
  /** The steps a dial writes, and so the only ones that carry a `dialId` and a `destinationId`. */
2096
2096
  export declare const HISTORY_STEPS_THAT_DIAL: readonly ["transferred", "conferenced", "unanswered"];
2097
2097
  export declare const historyStepDials: (step: HistoryStep) => boolean;
2098
+ /**
2099
+ * Consecutive entries of one step, shown as one span. The record keeps an entry per occurrence --
2100
+ * two holds are counted as two -- and this is how the agent sees them: once, from the first
2101
+ * entry's `at`, lasting the sum of their `seconds` when every entry states one, and with no
2102
+ * duration when any does not, never a sum that silently leaves a running leg out.
2103
+ */
2104
+ export interface HistorySpan {
2105
+ step: HistoryStep;
2106
+ at: IsoTimestamp;
2107
+ seconds?: DurationSeconds;
2108
+ entries: TaskHistoryStep[];
2109
+ }
2110
+ /** Groups a record's consecutive same-step entries into spans, oldest first. */
2111
+ export declare function historySpans(steps: readonly TaskHistoryStep[]): HistorySpan[];
2098
2112
  /** Whether an absent `by` means "could not attribute" rather than "nobody was involved". */
2099
2113
  export declare function historyStepExpectsAPerson(step: HistoryStep): boolean;
2100
2114
  export interface BrowserSessionKeyInput {
package/dist/index.js CHANGED
@@ -201,6 +201,23 @@ export const historyStepDials = (step) => HISTORY_STEPS_THAT_DIAL.includes(step)
201
201
  // Pinned both ways: a step added to `HistoryStep` has to be placed here, and a step listed here
202
202
  // has to exist there. `satisfies` on the list covers the second; this statement covers the first.
203
203
  true;
204
+ /** Groups a record's consecutive same-step entries into spans, oldest first. */
205
+ export function historySpans(steps) {
206
+ const spans = [];
207
+ for (const entry of steps) {
208
+ const last = spans[spans.length - 1];
209
+ if (last !== undefined && last.step === entry.step)
210
+ last.entries.push(entry);
211
+ else
212
+ spans.push({ step: entry.step, at: entry.at, entries: [entry] });
213
+ }
214
+ for (const span of spans) {
215
+ if (span.entries.every(entry => entry.seconds !== undefined)) {
216
+ span.seconds = span.entries.reduce((total, entry) => total + entry.seconds, 0);
217
+ }
218
+ }
219
+ return spans;
220
+ }
204
221
  /** Whether an absent `by` means "could not attribute" rather than "nobody was involved". */
205
222
  export function historyStepExpectsAPerson(step) {
206
223
  return HISTORY_STEPS_WITH_A_PERSON.includes(step);
package/guide/agent.md CHANGED
@@ -443,13 +443,16 @@ arrive omits it rather than guessing an instant. Omni shows it as *Incoming call
443
443
  Amazon Connect keeps an initiation time, Genesys Cloud a conversation start and NICE CXone a contact
444
444
  start, separate from the queue.
445
445
 
446
- **Time with the IVR or a bot is `self-service`.** The caller pressing through a menu, talking to a
447
- voice bot, or a chat customer with a bot before a person takes over: one step for all of them, on
448
- every channel, the way Genesys Cloud and NICE CXone report IVR and bot time together as
449
- self-service. Nobody from the provider takes part, so it has no `by`. `at` is when the customer
450
- entered it and `seconds`, once it ended, how long they spent there; a customer sent back to a menu
451
- after a transfer has a second entry, one per visit, as a second queue is a second `queued`. A
452
- customer that words it *IVR* for its agents does so in `labels.historySteps`.
446
+ **Time the platform handles the customer before a queue is `self-service`.** The caller pressing
447
+ through a menu, talking to a voice bot, hearing an announcement, or waiting while the platform
448
+ looks them up; a chat customer with a bot before a person takes over: one step for all of them, on
449
+ every channel. Genesys Cloud and NICE CXone report IVR and bot time as self-service; here it also
450
+ takes in the routing time Genesys Cloud keeps as a separate system segment, so a provider that
451
+ measures the gap before each queue without knowing what played in it reports it here rather than
452
+ nowhere. It is therefore not evidence that the customer used a menu. Nobody from the provider takes
453
+ part, so it has no `by`. `at` is when it began and `seconds`, once it ended, how long it lasted; a
454
+ customer routed again after a transfer has a second entry, one per visit, as a second queue is a
455
+ second `queued`. A customer that words it *IVR* for its agents does so in `labels.historySteps`.
453
456
 
454
457
  **A callback carries the call it comes back to.** When a caller asks to be called back, or an agent
455
458
  arranges it, the provider writes a `callback-requested` step: `by` names the agent who arranged it, and
@@ -483,7 +486,13 @@ provider closed it. One publisher of the closed leg, and no order between them t
483
486
  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
484
487
  `muted` entries, and a call that joined two queues -- a menu's, then this one -- has two `queued`
485
488
  entries, one per join. Order is enforced: an entry earlier than the one before it is refused
486
- (`task.history.order`). **Intervals between steps are the agent application's to subtract**, never a
489
+ (`task.history.order`).
490
+
491
+ **Shown as spans.** The record keeps one entry per occurrence, because leads count them — two holds
492
+ are two holds, the way Amazon Connect counts holds per contact, and two queues say which queue the
493
+ call waited in. The agent sees consecutive entries of one step as one span: `historySpans(steps)`
494
+ groups them, as Genesys Cloud keeps segments apart in its record and joins them in the agent's
495
+ timeline. A provider never merges entries to get that look. **Intervals between steps are the agent application's to subtract**, never a
487
496
  total the provider adds: *time to offer*, from the call joining its queue to the agent's screen lighting
488
497
  up, is the `offered` entry's `at` minus the last `queued` entry's, and the ring is outside it.
489
498
  `queueSeconds` keeps the industry's meaning -- the whole wait until somebody answered -- and is not
package/guide.md CHANGED
@@ -725,6 +725,13 @@ type TaskHistoryStep = {
725
725
  mutedBy?: MutedBy;
726
726
  };
727
727
 
728
+ type HistorySpan = {
729
+ step: HistoryStep;
730
+ at: IsoTimestamp;
731
+ seconds?: DurationSeconds;
732
+ entries: TaskHistoryStep[];
733
+ };
734
+
728
735
  type TaskCompletion =
729
736
  | { completionMode: "agent-command"; wrapAllowance?: DurationSeconds }
730
737
  | { completionMode: "provider-automatic"; wrapAllowance: DurationSeconds };
@@ -2990,6 +2997,14 @@ Whether two logins declare the same capabilities, field by field — key order a
2990
2997
  `team: {}` distinct from `team` absent. It is the comparison an adapter makes before republishing
2991
2998
  `authenticated`, and the one `testAdapter` holds `refreshing` to.
2992
2999
 
3000
+ ### `historySpans(steps)`
3001
+
3002
+ Groups a record's consecutive entries of one step into a `HistorySpan` each — `step`, the first
3003
+ entry's `at`, `seconds` summed when every entry states one and absent when any does not, and the
3004
+ `entries` themselves — so every agent application shows *Self-service · 30s* once where the
3005
+ record has two self-service entries in a row. Only neighbours are grouped: a hold, an answer and
3006
+ another hold are three spans. See **Shown as spans** in `guide/agent.md`.
3007
+
2993
3008
  ## Runtime validation
2994
3009
 
2995
3010
  Structural rules in this document are executable through the runtime validators Omni applies to
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xema/omni-protocol",
3
- "version": "0.1.123",
3
+ "version": "0.1.124",
4
4
  "description": "The Omni protocol: the contract every provider adapter implements",
5
5
  "type": "module",
6
6
  "license": "MIT",