@burdenoff/fe-libs 2026.1013.9 → 2026.1013.11

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.
@@ -17,13 +17,117 @@ export interface SpeechSpeakOptions {
17
17
  * heard (BOFF-7283). Omitted means fallback is allowed.
18
18
  */
19
19
  fallback?: boolean;
20
+ /**
21
+ * Play this reply's hosted audio HERE instead of on the speakers: the
22
+ * optional live avatar (BOFF-8822). Attached per reply by the panel that
23
+ * shows the avatar, so no other caller of the shared engine can reach it.
24
+ * An engine with no audio data to hand over (the device voice) ignores it.
25
+ *
26
+ * It belongs to the whole reply: one output, one piece count and one hard
27
+ * stop, whether the reply is one `speak()` or a queue that grows by appends.
28
+ */
29
+ audioOutput?: SpeechAudioOutput;
30
+ }
31
+ /**
32
+ * Somewhere other than the speakers to play a reply's hosted audio
33
+ * (BOFF-8822).
34
+ *
35
+ * ★★ The hosted engine keeps everything else: the request (voice, language,
36
+ * rate), the retries, the one-piece prefetch, the refusal latch, `cancel()`.
37
+ * Each piece is synthesised ONCE. If the output fails, the pieces it never
38
+ * began play on the speakers from the bytes already fetched, so falling back
39
+ * never synthesises anything twice, and an output's failure never latches the
40
+ * engine off.
41
+ *
42
+ * ★ Leaving is not stopping. When the output stops being the reply's
43
+ * destination while the reply goes on (the user switched the avatar off), it
44
+ * rejects the pending `write` or `drain` (or the next one, if none is pending)
45
+ * and every later one with an error whose `reason` is `SPEECH_OUTPUT_LEFT`. The
46
+ * rest of the reply then plays on the speakers, exactly as after a failure, and
47
+ * still ends with `onEnd`. Switching it off never goes through the engine's
48
+ * `cancel()`, which silences the reply, nor through `retract()`.
49
+ *
50
+ * ★★ Refusing is neither leaving nor failing (operator decision D7 b). When the
51
+ * output cannot show that what it played BEFORE this reply has stopped (its
52
+ * drain bound ran out while that audio may still be heard), it must not start
53
+ * this reply anywhere: it writes nothing, unmutes nothing, and rejects the
54
+ * pending `write` or `drain` (or the next one, if none is pending) and every
55
+ * later one with a `SpeechOutputRefusal`. The engine then silences it, plays
56
+ * NOTHING on the speakers, and ends the reply with
57
+ * `onError('output-not-cleared')`, which no fallback re-speaks. A `drain()`
58
+ * with nothing written yet is no exception: the engine drains before it
59
+ * reports a failure of its own, and a drain that resolves lets the device
60
+ * voice speak the reply.
61
+ *
62
+ * One object serves exactly one reply: attach a fresh one to each, so neither
63
+ * a leave, a refusal nor the hard stop carries over to the next reply.
64
+ */
65
+ export interface SpeechAudioOutput {
66
+ /**
67
+ * Take piece `index` (the bytes the proxy returned). `index` counts the
68
+ * reply's pieces from 0 with ONE counter for the whole reply: for a queue it
69
+ * continues across appends, and a retracted number is never given again.
70
+ *
71
+ * Resolve when the next piece may follow: once this one is queued, pieces
72
+ * play back to back; resolving when it BEGINS keeps synthesis one piece
73
+ * ahead, as on the speakers. Call `began()` when its sound begins, and,
74
+ * before rejecting, for any piece that MAY have been heard: the speakers
75
+ * resume after the last piece that began, so nothing is heard twice. A piece
76
+ * retracted before it began resolves its `write` without `began()`. Writing
77
+ * may go on after a `drain()`, until `cancel()` or the reply's end.
78
+ */
79
+ write(bytes: Uint8Array, index: number, began: () => void): Promise<void>;
80
+ /**
81
+ * Resolve once everything written SO FAR has finished playing; reject as
82
+ * `write` does. Callable more than once: a queue may drain when its speech
83
+ * pauses and write again afterwards, even before that drain settles (a piece
84
+ * written meanwhile is not part of it). The reply's last drain follows its
85
+ * end and its last write.
86
+ */
87
+ drain(): Promise<void>;
88
+ /**
89
+ * Drop every piece with `index >= fromIndex` that has not begun; the piece
90
+ * playing, if any, finishes. Call `began()` first for any of them that MAY
91
+ * have been heard: it is then the one playing, and stays. Queued speech is
92
+ * withdrawn this way; `cancel()` remains the hard stop.
93
+ */
94
+ retract(fromIndex: number): void;
95
+ /**
96
+ * The hard stop (a stop, a new reply, a failure): silence at once and drop
97
+ * everything queued. Pending `write`/`drain` calls still settle, either way.
98
+ */
99
+ cancel(): void;
100
+ }
101
+ /**
102
+ * The `reason` a `SpeechAudioOutput` rejects with once it has LEFT the reply
103
+ * (switched off while the reply goes on). Any other rejection is a failure,
104
+ * except a refusal (`SPEECH_OUTPUT_REFUSE_AUDIBLE`).
105
+ */
106
+ export declare const SPEECH_OUTPUT_LEFT = "left";
107
+ /**
108
+ * The `reason` a `SpeechAudioOutput` rejects with to REFUSE the reply
109
+ * (operator decision D7 b): what it played before could not be shown to have
110
+ * stopped, so nothing of this reply may be heard, on the output, on the
111
+ * speakers or on any other voice. Exported so that an output cannot misspell it
112
+ * and silently turn a refusal into a failure the speakers take over.
113
+ */
114
+ export declare const SPEECH_OUTPUT_REFUSE_AUDIBLE = "refuse-audible";
115
+ /** What a `SpeechAudioOutput` rejects with to refuse the reply (`SPEECH_OUTPUT_REFUSE_AUDIBLE`). */
116
+ export interface SpeechOutputRefusal {
117
+ readonly reason: typeof SPEECH_OUTPUT_REFUSE_AUDIBLE;
20
118
  }
21
119
  /**
22
120
  * Why playback stopped. `cancelled` is not a failure: it is what every queued
23
121
  * utterance reports when we deliberately cancel (new playback, panel close,
24
122
  * conversation switch), and the UI must stay silent about it.
123
+ *
124
+ * `output-not-cleared` is a failure that must NEVER be heard (BOFF-8822,
125
+ * operator decision D7 b): the reply's audio output refused it because what it
126
+ * played before could not be shown to have stopped. Nothing re-speaks it, the
127
+ * device voice included, and its text stays where it is. Only a reply with an
128
+ * `audioOutput` can end this way.
25
129
  */
26
- export type SpeechErrorReason = 'cancelled' | 'not-allowed' | 'unsupported' | 'unknown';
130
+ export type SpeechErrorReason = 'cancelled' | 'not-allowed' | 'unsupported' | 'unknown' | 'output-not-cleared';
27
131
  export interface SpeechSpeakHandlers {
28
132
  /**
29
133
  * Audio has actually begun playing — not merely been requested.
@@ -0,0 +1,4 @@
1
+ //#region src/shared/assistant/speech/types.ts
2
+ var e = "left", t = "refuse-audible";
3
+ //#endregion
4
+ export { e as SPEECH_OUTPUT_LEFT, t as SPEECH_OUTPUT_REFUSE_AUDIBLE };
@@ -54,6 +54,17 @@ export interface AssistantSpeechContextValue {
54
54
  setVoice: (voiceName: string | null) => void;
55
55
  /** The message currently being spoken, if any. */
56
56
  speakingMessageId: string | null;
57
+ /**
58
+ * ★★ The reply an audio output REFUSED (BOFF-8822, operator decision D7 b;
59
+ * reason `output-not-cleared`): what it played before could not be shown to
60
+ * have stopped, so the reply was never heard, on any voice. It ended as a
61
+ * failure like any other (playback is idle and its text is untouched); this
62
+ * says WHICH reply, so the voice session can explain the silence. A new
63
+ * object per refusal; null until one happens, which needs an audio output.
64
+ */
65
+ outputNotCleared: {
66
+ readonly messageId: string;
67
+ } | null;
57
68
  isSpeaking: (messageId: string) => boolean;
58
69
  /**
59
70
  * Speak this message, or stop if it is already the one speaking.