@burdenoff/fe-libs 2026.1023.16 → 2026.1024.2

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.
@@ -98,8 +98,18 @@ function j() {
98
98
  function M(e) {
99
99
  if (!(typeof window > "u")) return (t) => (window.addEventListener(e, t), () => window.removeEventListener(e, t));
100
100
  }
101
- function N(e = {}) {
102
- let t = e.preferences ?? O, n = e.controller ?? {}, r = n.subscribeWorkspaceChange ?? M("burdenoff:workspace-changed"), i = n.subscribePageHide ?? M("pagehide"), a = p({
101
+ function N() {
102
+ if (!(typeof window > "u" || typeof document > "u")) return (e) => {
103
+ let t = window, n = document, r = () => {
104
+ n.visibilityState === "visible" && e();
105
+ };
106
+ return n.addEventListener("visibilitychange", r), t.addEventListener("pageshow", e), () => {
107
+ n.removeEventListener("visibilitychange", r), t.removeEventListener("pageshow", e);
108
+ };
109
+ };
110
+ }
111
+ function P(e = {}) {
112
+ let t = e.preferences ?? O, n = e.controller ?? {}, r = n.subscribeWorkspaceChange ?? M("burdenoff:workspace-changed"), i = n.subscribePageHide ?? M("pagehide"), a = n.subscribePageShow ?? N(), o = n.now ?? (() => Date.now()), u = p({
103
113
  client: e.client ?? D(),
104
114
  openRoom: e.openRoom ?? m,
105
115
  scheduler: e.scheduler ?? s,
@@ -111,10 +121,11 @@ function N(e = {}) {
111
121
  }),
112
122
  startMeter: n.startMeter ?? ((e, t) => typeof MediaStream > "u" ? null : c(new MediaStream([e]), t)),
113
123
  browserSupported: e.browserSupported ?? (() => w()),
114
- now: n.now ?? (() => Date.now()),
124
+ now: o,
115
125
  newIdempotencyKey: n.newIdempotencyKey ?? (() => d()),
116
126
  ...r ? { subscribeWorkspaceChange: r } : {},
117
127
  ...i ? { subscribePageHide: i } : {},
128
+ ...a ? { subscribePageShow: a } : {},
118
129
  ...n.idleMs === void 0 ? {} : { idleMs: n.idleMs },
119
130
  ...n.endBeforeDeadlineMs === void 0 ? {} : { endBeforeDeadlineMs: n.endBeforeDeadlineMs },
120
131
  ...n.connectTimeoutMs === void 0 ? {} : { connectTimeoutMs: n.connectTimeoutMs },
@@ -123,53 +134,53 @@ function N(e = {}) {
123
134
  ...n.stallMs === void 0 ? {} : { stallMs: n.stallMs },
124
135
  ...n.readinessProbe === void 0 ? {} : { readinessProbe: n.readinessProbe },
125
136
  ...e.onLog ? { onLog: e.onLog } : {}
126
- }), o = b(e.avatarVoices), u = e.avatarImages === void 0 ? null : _(e.avatarImages), f = e.avatarVoiceStore ?? k, T = () => f.getState(), E = (n, r) => {
127
- let i = v(T(), {
128
- avatarVoices: o,
137
+ }), f = b(e.avatarVoices), T = e.avatarImages === void 0 ? null : _(e.avatarImages), E = e.avatarVoiceStore ?? k, P = () => E.getState(), F = (n, r) => {
138
+ let i = v(P(), {
139
+ avatarVoices: f,
129
140
  avatarId: t.getState().avatarId,
130
141
  ...e.defaultAvatarVoiceMode ? { hostDefault: e.defaultAvatarVoiceMode } : {}
131
142
  });
132
143
  return i === null ? null : y(i, n, r.listVoices());
133
- }, N = (e, t) => {
144
+ }, I = (e, t) => {
134
145
  let n = {
135
146
  ...e,
136
147
  voice: t
137
148
  };
138
- return n.audioOutput = x(a.createOutput(), () => {
149
+ return n.audioOutput = x(u.createOutput(), () => {
139
150
  n.voice = e.voice;
140
- }, (e) => a.subscribe(() => {
141
- a.getSnapshot().phase !== "ready" && e();
151
+ }, (e) => u.subscribe(() => {
152
+ u.getSnapshot().phase !== "ready" && e();
142
153
  })), n;
143
154
  };
144
155
  return {
145
156
  decorateSpeechEngine: (e) => {
146
157
  let t = (t) => {
147
- let n = E(t.lang, e), r = n === null ? t : {
158
+ let n = F(t.lang, e), r = n === null ? t : {
148
159
  ...t,
149
160
  voice: n
150
161
  };
151
162
  return {
152
163
  voice: n,
153
- target: a.route(r, e)
164
+ target: u.route(r, e)
154
165
  };
155
166
  }, n = null, r = () => {
156
167
  let e = n;
157
168
  n = null, e?.();
158
169
  }, i = (t, r, i) => {
159
- let o = !0, s = !1, c = () => void 0, l = () => {
160
- o && (o = !1, n === l && (n = null), c());
161
- }, u = () => s ? !1 : (s = !0, l(), !0), d = () => {
162
- !o || !u() || (e.cancel(), i?.onError?.("output-left"));
170
+ let a = !0, o = !1, s = () => void 0, c = () => {
171
+ a && (a = !1, n === c && (n = null), s());
172
+ }, l = () => o ? !1 : (o = !0, c(), !0), d = () => {
173
+ !a || !l() || (e.cancel(), i?.onError?.("output-left"));
163
174
  };
164
- c = a.subscribe(() => {
165
- a.getSnapshot().phase !== "ready" && d();
166
- }), n = l, r.audioOutput &&= A(r.audioOutput, d), r.fallback = !1, e.speak(t, r, {
175
+ s = u.subscribe(() => {
176
+ u.getSnapshot().phase !== "ready" && d();
177
+ }), n = c, r.audioOutput &&= A(r.audioOutput, d), r.fallback = !1, e.speak(t, r, {
167
178
  ...i,
168
179
  onEnd: () => {
169
- u() && i?.onEnd?.();
180
+ l() && i?.onEnd?.();
170
181
  },
171
182
  onError: (e) => {
172
- u() && i?.onError?.(e);
183
+ l() && i?.onError?.(e);
173
184
  }
174
185
  });
175
186
  };
@@ -179,26 +190,26 @@ function N(e = {}) {
179
190
  listVoices: () => e.listVoices(),
180
191
  subscribeVoices: (t) => e.subscribeVoices(t),
181
192
  wouldSpeakOnAvatar: (e) => t(e).target === "avatar",
182
- speak: (n, o, s) => {
193
+ speak: (n, a, o) => {
183
194
  r();
184
- let { voice: c, target: l } = t(o);
185
- if (l !== "avatar") {
186
- if (o.avatarOnly === !0) {
187
- e.cancel(), s?.onError?.("output-left");
195
+ let { voice: s, target: c } = t(a);
196
+ if (c !== "avatar") {
197
+ if (a.avatarOnly === !0) {
198
+ e.cancel(), o?.onError?.("output-left");
188
199
  return;
189
200
  }
190
- e.speak(n, o, s);
201
+ e.speak(n, a, o);
191
202
  return;
192
203
  }
193
- let u = c === null ? {
194
- ...o,
195
- audioOutput: a.createOutput()
196
- } : N(o, c);
197
- if (o.avatarOnly === !0) {
198
- i(n, u, s);
204
+ let l = s === null ? {
205
+ ...a,
206
+ audioOutput: u.createOutput()
207
+ } : I(a, s);
208
+ if (a.avatarOnly === !0) {
209
+ i(n, l, o);
199
210
  return;
200
211
  }
201
- e.speak(n, u, s);
212
+ e.speak(n, l, o);
202
213
  },
203
214
  cancel: () => {
204
215
  r(), e.cancel();
@@ -206,7 +217,7 @@ function N(e = {}) {
206
217
  };
207
218
  },
208
219
  Stage: ({ expanded: t, fallback: n }) => /* @__PURE__ */ C(g, {
209
- controller: a,
220
+ controller: u,
210
221
  expanded: t,
211
222
  fallback: n,
212
223
  ...e.avatarNames ? { avatarNames: e.avatarNames } : {}
@@ -214,33 +225,33 @@ function N(e = {}) {
214
225
  enabled: {
215
226
  subscribe: (e) => t.subscribe(e),
216
227
  getSnapshot: () => t.getState().avatarEnabled,
217
- switchOff: () => a.switchOff(),
218
- voiceLive: (e) => a.setVoiceLive(e)
228
+ switchOff: () => u.switchOff(),
229
+ voiceLive: (e) => u.setVoiceLive(e)
219
230
  },
220
231
  tab: {
221
232
  offered: {
222
- subscribe: (e) => a.subscribe(e),
223
- getSnapshot: () => a.getSnapshot().tabOffered
233
+ subscribe: (e) => u.subscribe(e),
234
+ getSnapshot: () => u.getSnapshot().tabOffered
224
235
  },
225
236
  ask: {
226
- subscribe: (e) => a.subscribe(e),
227
- getSnapshot: () => h(a.getSnapshot())
237
+ subscribe: (e) => u.subscribe(e),
238
+ getSnapshot: () => h(u.getSnapshot(), o())
228
239
  },
229
- setOpen: (e) => a.setTabOpen(e),
230
- heldByVoice: () => a.getSnapshot().heldByVoice,
240
+ setOpen: (e) => u.setTabOpen(e),
241
+ heldByVoice: () => u.getSnapshot().heldByVoice,
231
242
  View: (t) => /* @__PURE__ */ C(S, {
232
243
  ...t,
233
- controller: a,
234
- avatarVoices: o,
235
- voiceStore: f,
244
+ controller: u,
245
+ avatarVoices: f,
246
+ voiceStore: E,
236
247
  ...e.avatarNames ? { avatarNames: e.avatarNames } : {},
237
- ...u ? { avatarImages: u } : {},
248
+ ...T ? { avatarImages: T } : {},
238
249
  ...e.defaultAvatarVoiceMode ? { defaultAvatarVoiceMode: e.defaultAvatarVoiceMode } : {}
239
250
  })
240
251
  },
241
- controller: a,
242
- dispose: () => a.dispose()
252
+ controller: u,
253
+ dispose: () => u.dispose()
243
254
  };
244
255
  }
245
256
  //#endregion
246
- export { N as createLiveAvatarSeam, w as isLiveAvatarBrowserSupported, T as liveAvatarAuthorization, E as liveAvatarWorkspaceAuthorization };
257
+ export { P as createLiveAvatarSeam, w as isLiveAvatarBrowserSupported, T as liveAvatarAuthorization, E as liveAvatarWorkspaceAuthorization };
@@ -4,7 +4,7 @@ import { AvatarMeterHandle } from './avatarAudioMeter';
4
4
  import { AvatarRoom, AvatarScheduler } from './avatarPorts';
5
5
  import { AvatarTrackerConfig } from './avatarPlaybackTracker';
6
6
  import { AvatarDecodedPiece, AvatarNotice, AvatarReplyOutputs } from './avatarReplyOutputs';
7
- import { AvatarClientEndReason, AvatarSessionClient } from './avatarSessionClient';
7
+ import { AvatarBudgetScope, AvatarClientEndReason, AvatarSessionClient } from './avatarSessionClient';
8
8
  import { AvatarReadinessProbeOptions } from './avatarTransport';
9
9
  /** End after this long without a reply on the avatar while it is on (OD8, BACKEND-DESIGN §8.7). */
10
10
  export declare const LIVE_AVATAR_IDLE_MS = 120000;
@@ -46,9 +46,9 @@ export interface LiveAvatarSnapshot {
46
46
  readonly tabOffered: boolean;
47
47
  /**
48
48
  * After a "not now" refusal that said when (the server's Retry-After): the wall-clock ms a start may be tried again. Null otherwise.
49
- * It goes with `problem`: cleared together, and both clear by themselves when the time comes. A newer refusal (including a question
50
- * that failed or was answered "not offered"), a workspace switch and a voice conversation starting outside the Avatar tab clear them
51
- * earlier.
49
+ * It goes with `problem`: cleared together, and both clear by themselves when the time comes (its timer, or `recheckRetry` when the
50
+ * page is shown again after a system sleep made that timer late). A newer refusal (including a question that failed or was answered
51
+ * "not offered"), a workspace switch and a voice conversation starting outside the Avatar tab clear them earlier.
52
52
  */
53
53
  readonly retryAt: number | null;
54
54
  /** The avatar was turned on outside the tab during the voice conversation that is still live: closing the tab hands it back to Voice. */
@@ -66,6 +66,11 @@ export interface LiveAvatarSnapshot {
66
66
  /** What the person must read before the avatar starts; null until the server said which version. */
67
67
  readonly consentVersion: string | null;
68
68
  readonly problem: AvatarProblem | null;
69
+ /**
70
+ * With the problem `budget-exhausted`, whose budget is used up when the server said (BOFF-9333): the person's own, their workspace's,
71
+ * or everyone's. Null otherwise: no scope sent (today's server), or another problem. It goes with `problem`, as `retryAt` does.
72
+ */
73
+ readonly budgetScope: AvatarBudgetScope | null;
69
74
  readonly notice: LiveAvatarNotice | null;
70
75
  /** The browser blocks the avatar's sound; a tap on the stage (`resumeAudio`) lifts it. */
71
76
  readonly audioBlocked: boolean;
@@ -112,11 +117,19 @@ export interface LiveAvatarControllerDeps {
112
117
  readonly startMeter: (track: MediaStreamTrack, onChange: (audible: boolean) => void) => AvatarMeterHandle | null;
113
118
  /** The browser can show the avatar at all (desktop Chromium first: GATE1B §10). */
114
119
  readonly browserSupported: () => boolean;
115
- /** Wall-clock milliseconds: only the fallback for `endsAt` when the server announced no session length (the server's clock may differ). */
120
+ /**
121
+ * Wall-clock milliseconds: a refusal's retry time (`retryAt`), its lock and its re-check (`recheckRetry`); also the fallback for
122
+ * `endsAt` when the server announced no session length (the server's clock may differ).
123
+ */
116
124
  readonly now: () => number;
117
125
  readonly newIdempotencyKey: () => string;
118
126
  readonly subscribeWorkspaceChange?: (listener: () => void) => () => void;
119
127
  readonly subscribePageHide?: (listener: () => void) => () => void;
128
+ /**
129
+ * The page is shown again (its document visible again, or shown from the back/forward cache): the listener is `recheckRetry`. The
130
+ * scheduler's monotonic clock does not count a system sleep, so after one a refusal's timer fires late; the wall clock (`now`) does.
131
+ */
132
+ readonly subscribePageShow?: (listener: () => void) => () => void;
120
133
  readonly idleMs?: number;
121
134
  readonly endBeforeDeadlineMs?: number;
122
135
  readonly connectTimeoutMs?: number;
@@ -152,6 +165,13 @@ export interface LiveAvatarController {
152
165
  * time (`retryAt`) that has not come.
153
166
  */
154
167
  confirm(): void;
168
+ /**
169
+ * BOFF-9333 UI-11: once the wall clock has reached a refusal's retry time (`retryAt`), clear the refusal now, as its timer does: the
170
+ * problem, whose budget it was, the time, and the timer with them. That timer runs on the scheduler's monotonic clock, which a system
171
+ * sleep stops (macOS, Linux), so after one it fires late. Runs when the page is shown again (`subscribePageShow`); a host with its own
172
+ * wake signal may call it too. With no time, or before it, it does nothing: nothing is cleared early, and the timer stays as it is.
173
+ */
174
+ recheckRetry(): void;
155
175
  /**
156
176
  * The Avatar tab opened or closed (BOFF-9341). Open: the avatar may be asked for with no voice conversation, and an avatar already on
157
177
  * is adopted; a question already in flight is asked again, choose only. Close: any question, choice or session ends as the person's