@xema/omni-protocol 0.1.71 → 0.1.72
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 +19 -3
- package/dist/validation.js +6 -0
- package/guide.md +6 -1
- package/package.json +1 -1
package/dist/testing.js
CHANGED
|
@@ -1055,7 +1055,7 @@ export class TaskStream {
|
|
|
1055
1055
|
case "task-updated":
|
|
1056
1056
|
if (id === undefined)
|
|
1057
1057
|
break;
|
|
1058
|
-
ruleEvaluated("stream.taskUpdated.unknown", "stream.taskUpdated.capabilitySource", "stream.taskUpdated.phase", "stream.taskUpdated.allocation", "stream.taskUpdated.handlingHistory", "stream.taskMediaEnded.follow", "stream.taskUpdated.media", "stream.taskUpdated.stage");
|
|
1058
|
+
ruleEvaluated("stream.taskUpdated.unknown", "stream.taskUpdated.capabilitySource", "stream.taskUpdated.phase", "stream.taskUpdated.allocation", "stream.taskUpdated.mediaOpen", "stream.taskUpdated.handlingHistory", "stream.taskMediaEnded.follow", "stream.taskUpdated.media", "stream.taskUpdated.stage");
|
|
1059
1059
|
if (known === undefined) {
|
|
1060
1060
|
refuse("stream.taskUpdated.unknown", `${at}.task.id`, `${id} was never offered or carried on a snapshot`);
|
|
1061
1061
|
break;
|
|
@@ -1094,6 +1094,15 @@ export class TaskStream {
|
|
|
1094
1094
|
refuse("stream.taskUpdated.handlingHistory", `${at}.task.handlingHistory`, `${id}'s record lost ${lost.join(", ")} on the update: an entry read by the host stays in the record until the task ends`);
|
|
1095
1095
|
}
|
|
1096
1096
|
}
|
|
1097
|
+
// A task completes after its media ends, never around it: an update that moves a task to
|
|
1098
|
+
// completing while the stream holds its audio as started is a call whose audio never ended,
|
|
1099
|
+
// whoever caused the ending -- the drive's end-call, a transfer, the provider's own hand.
|
|
1100
|
+
{
|
|
1101
|
+
const to = isRecord(event.task) ? String(event.task.phase) : "";
|
|
1102
|
+
if (to === "completing" && known.media === "started" && known.phase !== "completing") {
|
|
1103
|
+
refuse("stream.taskUpdated.mediaOpen", `${at}.task.phase`, `${id} moves to completing with its media still started: the audio ends first, on task-media-ended, and a wrap-up with the call still up is audio that never ended`);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1097
1106
|
if (known.media === "ended") {
|
|
1098
1107
|
const phase = isRecord(event.task) ? String(event.task.phase) : "";
|
|
1099
1108
|
if (phase !== "completing") {
|
|
@@ -1519,11 +1528,18 @@ async function driveOneCall(drive) {
|
|
|
1519
1528
|
// 5. End the call, where the agent may.
|
|
1520
1529
|
if (drive.channel === "voice" && latestTask().phase === "in-progress" && offers("endCall")) {
|
|
1521
1530
|
if (await send({ type: "end-call" }) !== undefined) {
|
|
1531
|
+
// A task that completes with its audio still up is refused by the stream as it passes
|
|
1532
|
+
// (stream.taskUpdated.mediaOpen); the drive does not also wait out the clock for an ending
|
|
1533
|
+
// that is not coming, so the run reads by the rule and not by its timeout.
|
|
1522
1534
|
const mediaEnded = await waitFor("task-media-ended after end-call", envelope => {
|
|
1523
1535
|
const event = envelope.event;
|
|
1524
|
-
|
|
1536
|
+
if (event.type === "task-media-ended" && event.taskId === taskId)
|
|
1537
|
+
return "ended";
|
|
1538
|
+
if (event.type === "task-updated" && isTask(event.task) && event.task.id === taskId && event.task.phase === "completing")
|
|
1539
|
+
return "completing";
|
|
1540
|
+
return undefined;
|
|
1525
1541
|
}, cursor);
|
|
1526
|
-
if (mediaEnded
|
|
1542
|
+
if (mediaEnded?.found === "ended")
|
|
1527
1543
|
cursor = mediaEnded.at;
|
|
1528
1544
|
if (await updated(t => t.phase === "completing", "the task completing after its media ended") !== undefined && offers("hold")) {
|
|
1529
1545
|
await holdRefusedOutsideHandling();
|
package/dist/validation.js
CHANGED
|
@@ -1021,6 +1021,12 @@ function validateTaskInto(task, context, path, into) {
|
|
|
1021
1021
|
// The room is who is on the call now, and a task outlives its call by the whole of wrap-up: a
|
|
1022
1022
|
// task whose call has ended -- completing, or media ended -- carries nobody, or the last thing the
|
|
1023
1023
|
// wire said about the call stays true for ever. Empty and absent both say nobody.
|
|
1024
|
+
// A completing task's call is over: its media has ended, or it never started. Audio still stated
|
|
1025
|
+
// as started on a task in wrap-up is a call whose audio never ended -- the customer heard through
|
|
1026
|
+
// the agent's notes -- and a task stating it contradicts itself.
|
|
1027
|
+
if (task.phase === "completing" && task.media === "started") {
|
|
1028
|
+
into.add("task.media.completing", `${path}.media`, "a completing task's media has ended or never started: the call is over, and media still started is audio that never ended");
|
|
1029
|
+
}
|
|
1024
1030
|
if (Array.isArray(task.onCall) && task.onCall.length > 0 && (task.phase === "completing" || task.media === "ended")) {
|
|
1025
1031
|
into.add("task.onCall.ended", `${path}.onCall`, `the call has ended (${task.phase === "completing" ? "the task is completing" : "its media ended"}) and onCall still names people on it: the room is who is on the call now, and now nobody is`);
|
|
1026
1032
|
}
|
package/guide.md
CHANGED
|
@@ -2854,7 +2854,12 @@ which the update itself shows: the party on the call carrying a `stage`. A compl
|
|
|
2854
2854
|
republished as `in-progress` from a stale copy carries no such stage, and that is the ending the
|
|
2855
2855
|
agent never saw. Audio arrives only on a task at work: `task-media-started` on a `completing` task
|
|
2856
2856
|
is refused as it is on a pending one (`stream.taskMediaStarted.beforeWork`), since a connect-back
|
|
2857
|
-
returns the task to `in-progress` before any media.
|
|
2857
|
+
returns the task to `in-progress` before any media. And a task completes after its audio ends,
|
|
2858
|
+
never around it: an update moving a task to `completing` while the stream holds its media as
|
|
2859
|
+
started is refused (`stream.taskUpdated.mediaOpen`), whoever caused the ending, and a task stating
|
|
2860
|
+
`completing` with `media: "started"` contradicts itself on any snapshot or update
|
|
2861
|
+
(`task.media.completing`). The consequence the rule exists for is concrete: the customer's audio
|
|
2862
|
+
keeps playing through the agent's wrap-up.
|
|
2858
2863
|
|
|
2859
2864
|
What the agent is told differs by source, and only one source tells them anything. Under `queue`
|
|
2860
2865
|
and `ungoverned` the agent sees controls and nothing about where they came from: both are facts,
|