@xema/omni-protocol 0.1.36 → 0.1.37

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/testing.js CHANGED
@@ -619,7 +619,7 @@ export class TaskStream {
619
619
  tasks = new Map();
620
620
  // Every dial the stream can place an outcome against: one the host said it placed, or one a
621
621
  // task carried on `onCall` or in its record -- which is how a dial made before a transfer is known
622
- // to whoever holds the task now. `ended` once its outcome arrived, since an outcome comes once.
622
+ // to whoever holds the task now. `answered` or `ended` once its outcome arrived, since it comes once.
623
623
  dials = new Map();
624
624
  /** The host placed a dial: its outcome, whenever it arrives, is one the stream expects. */
625
625
  dialled(dialId) {
@@ -640,7 +640,16 @@ export class TaskStream {
640
640
  }
641
641
  static stated(task) {
642
642
  const media = isRecord(task) && (task.media === "started" || task.media === "ended") ? task.media : "none";
643
- return { phase: String(isRecord(task) ? task.phase : undefined), media };
643
+ // The stage of every dialled entry the room names by its dial, so an update can be held to the
644
+ // outcome that moves it.
645
+ const stages = new Map();
646
+ if (isRecord(task) && Array.isArray(task.onCall)) {
647
+ for (const entry of task.onCall) {
648
+ if (isRecord(entry) && typeof entry.dialId === "string" && typeof entry.stage === "string")
649
+ stages.set(entry.dialId, entry.stage);
650
+ }
651
+ }
652
+ return { phase: String(isRecord(task) ? task.phase : undefined), media, stages };
644
653
  }
645
654
  /** Replaces what is known with a snapshot's tasks, as a snapshot replaces Omni's state. */
646
655
  seed(snapshot) {
@@ -696,6 +705,14 @@ export class TaskStream {
696
705
  if (known.media !== next.media && !(known.media === "ended" && next.media === "none")) {
697
706
  refuse("stream.taskUpdated.media", `${at}.task.media`, `a task-updated re-states media, it does not move it: ${id} held ${known.media} and the update says ${next.media}; audio arrives on task-media-started and ends on task-media-ended`);
698
707
  }
708
+ // The same pairing for a dialled entry: the dial-outcome is the transition, the stage is
709
+ // the state, and the outcome comes first. An update that joins an entry on its own is
710
+ // moving what only an answered outcome moves.
711
+ for (const [dialId, stage] of next.stages) {
712
+ if (stage === "joined" && known.stages.get(dialId) === "ringing" && this.dials.get(dialId) !== "answered") {
713
+ refuse("stream.taskUpdated.stage", `${at}.task.onCall`, `a task-updated re-states a stage, it does not move it: ${dialId} was ringing and the update says joined before any answered dial-outcome for it`);
714
+ }
715
+ }
699
716
  this.tasks.set(id, next);
700
717
  }
701
718
  this.noteDials(event.task);
@@ -746,10 +763,10 @@ export class TaskStream {
746
763
  if (dial === undefined) {
747
764
  refuse("stream.dialOutcome.unknown", `${at}.dialId`, `${event.dialId} is not a dial the host placed or a task carried: an outcome ends a dial somebody made`);
748
765
  }
749
- else if (dial === "ended") {
766
+ else if (dial !== "placed") {
750
767
  refuse("stream.dialOutcome.duplicate", `${at}.dialId`, `${event.dialId} already has its outcome; a dial ends once`);
751
768
  }
752
- this.dials.set(event.dialId, "ended");
769
+ this.dials.set(event.dialId, event.outcome === "answered" ? "answered" : "ended");
753
770
  break;
754
771
  }
755
772
  default:
package/guide.md CHANGED
@@ -2859,7 +2859,9 @@ still-ringing `destination` calls that dial off, with `cancelled` as its outcome
2859
2859
  **The entry says where it stands.** A dialled entry carries `stage`: `ringing` until its dial is
2860
2860
  answered, `joined` after. The `dial-outcome` is the transition and the stage is the state, the same
2861
2861
  pairing as `task-media-started` and `media`: an `answered` outcome and the task restated with the
2862
- entry `joined` say one thing twice, and any other outcome removes the entry. It is stated rather
2862
+ entry `joined` say one thing twice, and any other outcome removes the entry. The outcome comes
2863
+ first: a `task-updated` that itself moves an entry from `ringing` to `joined` before an `answered`
2864
+ outcome for its dial is refused (`stream.taskUpdated.stage`), as an update that moves `media` is. It is stated rather
2863
2865
  than left to whoever placed the dial because that knowledge lives in one process: after a reload,
2864
2866
  on a second session, or on a supervisor's screen the snapshot is the only source, and a room that
2865
2867
  cannot tell ringing from joined says it contains someone who may never arrive. Nobody ringing is
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xema/omni-protocol",
3
- "version": "0.1.36",
3
+ "version": "0.1.37",
4
4
  "description": "The Omni protocol: the contract every provider adapter implements",
5
5
  "type": "module",
6
6
  "license": "MIT",