@xema/omni-protocol 0.1.60 → 0.1.61

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.
Files changed (3) hide show
  1. package/dist/testing.js +19 -13
  2. package/guide.md +14 -6
  3. package/package.json +1 -1
package/dist/testing.js CHANGED
@@ -1000,7 +1000,25 @@ async function driveOneCall(drive) {
1000
1000
  if (await updated(t => t.phase === "in-progress" || t.phase === "completing", "the task leaving preview after Call") === undefined)
1001
1001
  return found;
1002
1002
  }
1003
+ // The other direction of step 4, wherever the task stands outside the handling phases with the
1004
+ // control still declared: a host holds it back (command.phase.handling), and an adapter that
1005
+ // receives it anyway must refuse it -- so the drive sends it past the validator and expects failed.
1006
+ const holdRefusedOutsideHandling = async () => {
1007
+ const phase = String(latestTask().phase);
1008
+ let answer;
1009
+ try {
1010
+ answer = await drive.connection.execute({ taskId, command: { type: "hold" } });
1011
+ }
1012
+ catch (error) {
1013
+ refuse("drive.command.rejected", "drive.command.hold", `execute rejected rather than answered: ${String(error)}`);
1014
+ }
1015
+ if (isRecord(answer) && answer.status !== "failed") {
1016
+ refuse("drive.command.handling", "drive.command.hold", `the provider applied hold on a ${phase} task: a control on the contact belongs to in-progress or paused, and the adapter is the second gate`);
1017
+ }
1018
+ };
1003
1019
  if (latestTask().phase === "confirmed") {
1020
+ if (offers("hold"))
1021
+ await holdRefusedOutsideHandling();
1004
1022
  if (await updated(t => t.phase !== "confirmed", "the task leaving confirmed") === undefined)
1005
1023
  return found;
1006
1024
  }
@@ -1086,19 +1104,7 @@ async function driveOneCall(drive) {
1086
1104
  if (mediaEnded !== undefined)
1087
1105
  cursor = mediaEnded.at;
1088
1106
  if (await updated(t => t.phase === "completing", "the task completing after its media ended") !== undefined && offers("hold")) {
1089
- // The other direction of step 4: the same control, still declared, on a call that is over.
1090
- // A host holds it back (command.phase.handling), and an adapter that receives it anyway
1091
- // must refuse it -- so the drive sends it past the validator and expects failed.
1092
- let answer;
1093
- try {
1094
- answer = await drive.connection.execute({ taskId, command: { type: "hold" } });
1095
- }
1096
- catch (error) {
1097
- refuse("drive.command.rejected", "drive.command.hold", `execute rejected rather than answered: ${String(error)}`);
1098
- }
1099
- if (isRecord(answer) && answer.status !== "failed") {
1100
- refuse("drive.command.handling", "drive.command.hold", `the provider applied hold on a completing task: a control on the contact belongs to in-progress or paused, and the adapter is the second gate`);
1101
- }
1107
+ await holdRefusedOutsideHandling();
1102
1108
  }
1103
1109
  }
1104
1110
  }
package/guide.md CHANGED
@@ -2465,8 +2465,13 @@ Steps are `queued`, `offered`, `answered`, `held`, `muted`, `transferred`, `conf
2465
2465
  **The record is one entry per occurrence, oldest first.** Each hold is its own `held` entry — `at`
2466
2466
  when it began, `seconds` once it ended and omitted while it runs — and a second hold is a second
2467
2467
  entry after the first, never a revision of it. The same goes for every step: two mutes are two
2468
- `muted` entries. Order is enforced: an entry earlier than the one before it is refused
2469
- (`task.handlingHistory.order`). **Handle time is anchored, not restarted.** It runs from the
2468
+ `muted` entries, and a call that joined two queues -- a menu's, then this one -- has two `queued`
2469
+ entries, one per join. Order is enforced: an entry earlier than the one before it is refused
2470
+ (`task.handlingHistory.order`). **Intervals between steps are the host's to subtract**, never a
2471
+ total the provider adds: *time to offer*, from the call's arrival to the agent's screen lighting
2472
+ up, is the `offered` entry's `at` minus the last `queued` entry's, and the ring is outside it.
2473
+ `queueSeconds` keeps the industry's meaning -- the whole wait until somebody answered -- and is not
2474
+ that interval. **Handle time is anchored, not restarted.** It runs from the
2470
2475
  `answered` step's `at` — from the task's first `in-progress` where the provider reports no
2471
2476
  history — until the task's media ends, and a hold neither pauses nor resets it: the hold's own
2472
2477
  duration is the `held` entry's `seconds`, and a desk that restarts its counter on resume is
@@ -4540,10 +4545,13 @@ violation (`drive.command.failed`), and an event the provider owes and never sen
4540
4545
  the drive mutes it for one second and reports the leg through `recordStep`, begun and then ended,
4541
4546
  expecting each report `recorded` (`drive.recordStep.failed`, `.rejected`); and where the provider
4542
4547
  restates the task's record afterwards, the leg is in it or the hole is named
4543
- (`drive.recordStep.history`). Once the call has ended and the task is
4544
- `completing`, the drive sends `hold` once more, past the validator that would hold it back, and
4545
- expects `failed`: the adapter is the second gate on a control on the contact, and one that applies
4546
- it on a call that is over is named (`drive.command.handling`). A task the agent completes is
4548
+ (`drive.recordStep.history`). Outside the handling phases with `hold` still declared -- in
4549
+ `confirmed`, where the provider publishes it, and in `completing` once the call has ended -- the
4550
+ drive sends `hold` past the validator that would hold it back, and expects `failed`: the adapter
4551
+ is the second gate on a control on the contact, and one that applies it where there is nothing to
4552
+ hold is named (`drive.command.handling`). The drive cannot put a task into a phase the provider
4553
+ never publishes, so a provider that goes straight from `pending` to `in-progress` is checked in
4554
+ `completing` alone. A task the agent completes is
4547
4555
  completed from wherever it stands once the drive has nothing left to do on it -- from `completing`
4548
4556
  after an `end-call`, or from `in-progress` where there is no call to end, which is every chat and
4549
4557
  email and a voice task offering no `endCall` -- so a conversation reaches its end as a call does.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xema/omni-protocol",
3
- "version": "0.1.60",
3
+ "version": "0.1.61",
4
4
  "description": "The Omni protocol: the contract every provider adapter implements",
5
5
  "type": "module",
6
6
  "license": "MIT",