@burdenoff/fe-libs 2026.1024.1 → 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.
@@ -115,7 +115,11 @@ var f = "inline-flex h-8 items-center gap-1.5 rounded-full border border-border-
115
115
  }), /* @__PURE__ */ u("button", {
116
116
  type: "button",
117
117
  onClick: () => {
118
- M || (w.current?.focus(), h.confirm());
118
+ if (M) {
119
+ h.recheckRetry();
120
+ return;
121
+ }
122
+ w.current?.focus(), h.confirm();
119
123
  },
120
124
  "aria-describedby": M ? `${b} ${x}` : b,
121
125
  "aria-disabled": M,
@@ -29,31 +29,34 @@ var y = "text-xs font-medium text-text-secondary", b = "text-xs text-text-muted"
29
29
  }, [r]);
30
30
  let { phase: I } = P, L = I === "starting" || I === "connecting" || I === "ready" || I === "ending", [R, z] = c(!1);
31
31
  R && (I === "off" || I === "choosing" || I === "ready" || I === "ending") && z(!1);
32
- let B = E(), ge = P.retryAt !== null && P.retryAt > B, V = ne(P, D, B), H = P.selectedAvatarId, U = (e) => {
32
+ let B = E(), V = P.retryAt !== null && P.retryAt > B, H = ne(P, D, B), U = P.selectedAvatarId, W = (e) => {
33
33
  let t = P.avatars.indexOf(e);
34
34
  return de?.[e] ?? (t === -1 ? D.chooserNone : `${D.chooserLabel} ${t + 1}`);
35
- }, W = I === "ready" ? { kind: "turn-off" } : I === "ending" ? { kind: "ending" } : R || I === "starting" || I === "connecting" ? { kind: "starting" } : P.offered ? {
35
+ }, G = I === "ready" ? { kind: "turn-off" } : I === "ending" ? { kind: "ending" } : R || I === "starting" || I === "connecting" ? { kind: "starting" } : P.offered ? {
36
36
  kind: "turn-on",
37
- ready: !ge && H !== null && (I === "choosing" || I === "off" && P.avatars.includes(H))
38
- } : null, G = s(null), K = W?.kind === "turn-off";
37
+ ready: !V && U !== null && (I === "choosing" || I === "off" && P.avatars.includes(U))
38
+ } : null, K = s(null), ge = G?.kind === "turn-off";
39
39
  ie(() => {
40
- G.current = K ? E() : null;
41
- }, [K, E]);
40
+ K.current = ge ? E() : null;
41
+ }, [ge, E]);
42
42
  let _e = i((e) => {
43
43
  if (e !== null) return () => {
44
44
  e.ownerDocument.activeElement === e && N.current?.focus();
45
45
  };
46
46
  }, []), ve = () => {
47
- if (W !== null) switch (W.kind) {
47
+ if (G !== null) switch (G.kind) {
48
48
  case "turn-on":
49
- if (!W.ready || H === null) return;
49
+ if (!G.ready || U === null) {
50
+ V && r.recheckRetry();
51
+ return;
52
+ }
50
53
  z(!0), I === "choosing" ? r.confirm() : r.requestOn({
51
54
  start: !0,
52
- avatarId: H
55
+ avatarId: U
53
56
  });
54
57
  return;
55
58
  case "turn-off": {
56
- let e = G.current;
59
+ let e = K.current;
57
60
  if (e !== null && E() - e < 1e3) return;
58
61
  r.switchOff();
59
62
  return;
@@ -61,7 +64,7 @@ var y = "text-xs font-medium text-text-secondary", b = "text-xs text-text-muted"
61
64
  case "starting":
62
65
  case "ending": return;
63
66
  }
64
- }, ye = te(F, me), q = H === null ? null : pe.get(H) ?? null, J = ye === "speaker" && F.avatarSpeaker !== null ? `${h}${F.avatarSpeaker}` : ye === "paired" && q !== null ? m : p, be = J === m ? q : J.startsWith(h) ? F.avatarSpeaker : null, Y = J === p ? O.activeVoiceName : be === null ? null : v(be, O.voiceChoices)?.name ?? null, xe = O.speakingMessageId !== null || O.spokenChunks !== null, Se = Y !== null && !C.live && !xe, Ce = O.activeVoiceName ? D.voiceModeMineNamed(_(O.activeVoiceName)) : D.voiceModeMine, we = (e) => {
67
+ }, ye = te(F, me), q = U === null ? null : pe.get(U) ?? null, J = ye === "speaker" && F.avatarSpeaker !== null ? `${h}${F.avatarSpeaker}` : ye === "paired" && q !== null ? m : p, be = J === m ? q : J.startsWith(h) ? F.avatarSpeaker : null, Y = J === p ? O.activeVoiceName : be === null ? null : v(be, O.voiceChoices)?.name ?? null, xe = O.speakingMessageId !== null || O.spokenChunks !== null, Se = Y !== null && !C.live && !xe, Ce = O.activeVoiceName ? D.voiceModeMineNamed(_(O.activeVoiceName)) : D.voiceModeMine, we = (e) => {
65
68
  if (e === p || e === m) {
66
69
  T.setVoiceMode(e);
67
70
  return;
@@ -73,7 +76,7 @@ var y = "text-xs font-medium text-text-secondary", b = "text-xs text-text-muted"
73
76
  }, []), ke = (e) => {
74
77
  let t = fe?.get(e);
75
78
  return t === void 0 || Ee.has(t) ? null : t;
76
- }, X = !L && H !== null ? ke(H) : null, Z = L ? null : w === void 0 ? C.impossible ? D.askNeedsVoice : null : w === "by-typing" ? D.askByTyping : w === "needs-voice" ? D.askNeedsVoice : w === "nothing" ? D.askNothing : null, Ae = I === "off" && P.avatars.length === 0 && P.offered, [Q, je] = c(!1), $ = Ae && (Q || P.problem !== null);
79
+ }, X = !L && U !== null ? ke(U) : null, Z = L ? null : w === void 0 ? C.impossible ? D.askNeedsVoice : null : w === "by-typing" ? D.askByTyping : w === "needs-voice" ? D.askNeedsVoice : w === "nothing" ? D.askNothing : null, Ae = I === "off" && P.avatars.length === 0 && P.offered, [Q, je] = c(!1), $ = Ae && (Q || P.problem !== null);
77
80
  return $ !== Q && je($), /* @__PURE__ */ f("div", {
78
81
  ref: N,
79
82
  tabIndex: -1,
@@ -96,9 +99,9 @@ var y = "text-xs font-medium text-text-secondary", b = "text-xs text-text-muted"
96
99
  playsInline: !0,
97
100
  "data-testid": "assistant-avatar-tab-video"
98
101
  }) : null,
99
- X !== null && H !== null ? /* @__PURE__ */ d(re, {
102
+ X !== null && U !== null ? /* @__PURE__ */ d(re, {
100
103
  size: "frame",
101
- name: U(H),
104
+ name: W(U),
102
105
  src: X,
103
106
  onError: Oe
104
107
  }) : null,
@@ -121,10 +124,10 @@ var y = "text-xs font-medium text-text-secondary", b = "text-xs text-text-muted"
121
124
  })
122
125
  }),
123
126
  /* @__PURE__ */ d("p", {
124
- className: V === null ? "sr-only" : "text-center text-sm text-text-secondary",
127
+ className: H === null ? "sr-only" : "text-center text-sm text-text-secondary",
125
128
  role: "status",
126
129
  "data-testid": "assistant-avatar-tab-status",
127
- children: V ?? ""
130
+ children: H ?? ""
128
131
  }),
129
132
  P.avatars.length > 1 && !L ? /* @__PURE__ */ f("fieldset", {
130
133
  className: "flex min-w-0 flex-col gap-1.5",
@@ -135,7 +138,7 @@ var y = "text-xs font-medium text-text-secondary", b = "text-xs text-text-muted"
135
138
  }), /* @__PURE__ */ d("div", {
136
139
  className: "flex flex-wrap gap-2",
137
140
  children: P.avatars.map((e) => {
138
- let t = e === H, n = U(e);
141
+ let t = e === U, n = W(e);
139
142
  return /* @__PURE__ */ f("label", {
140
143
  className: `${oe} ${t ? "border-transparent bg-action-primary-bg text-action-primary-text" : "border-border-default bg-bg-elevated text-text-secondary hover:bg-bg-raised hover:text-text-primary"}`,
141
144
  title: t ? D.chooserSelected(n) : D.chooserOption(n),
@@ -164,7 +167,7 @@ var y = "text-xs font-medium text-text-secondary", b = "text-xs text-text-muted"
164
167
  }, e);
165
168
  })
166
169
  })]
167
- }) : P.avatars.length > 0 && H !== null ? /* @__PURE__ */ f("div", {
170
+ }) : P.avatars.length > 0 && U !== null ? /* @__PURE__ */ f("div", {
168
171
  className: "flex flex-col gap-0.5",
169
172
  "data-testid": "assistant-avatar-tab-chosen",
170
173
  children: [/* @__PURE__ */ f("p", {
@@ -174,7 +177,7 @@ var y = "text-xs font-medium text-text-secondary", b = "text-xs text-text-muted"
174
177
  children: D.chooserLabel
175
178
  }), /* @__PURE__ */ d("span", {
176
179
  className: "truncate text-text-primary",
177
- children: U(H)
180
+ children: W(U)
178
181
  })]
179
182
  }), L && P.avatars.length > 1 ? /* @__PURE__ */ d("p", {
180
183
  className: b,
@@ -245,16 +248,16 @@ var y = "text-xs font-medium text-text-secondary", b = "text-xs text-text-muted"
245
248
  "data-testid": "assistant-avatar-tab-disclosure",
246
249
  children: D.consent
247
250
  }),
248
- W === null ? null : /* @__PURE__ */ d("button", {
251
+ G === null ? null : /* @__PURE__ */ d("button", {
249
252
  ref: _e,
250
253
  type: "button",
251
254
  onClick: ve,
252
- "aria-disabled": W.kind === "turn-on" ? !W.ready : W.kind !== "turn-off",
253
- "aria-describedby": W.kind === "turn-on" ? k : void 0,
254
- className: W.kind === "turn-off" ? S : ae,
255
+ "aria-disabled": G.kind === "turn-on" ? !G.ready : G.kind !== "turn-off",
256
+ "aria-describedby": G.kind === "turn-on" ? k : void 0,
257
+ className: G.kind === "turn-off" ? S : ae,
255
258
  "data-testid": "assistant-avatar-tab-switch",
256
- "data-slot": W.kind,
257
- children: W.kind === "turn-off" ? D.stop : W.kind === "starting" ? D.turnOnStarting : W.kind === "ending" ? D.stateEnding : D.consentConfirm
259
+ "data-slot": G.kind,
260
+ children: G.kind === "turn-off" ? D.stop : G.kind === "starting" ? D.turnOnStarting : G.kind === "ending" ? D.stateEnding : D.consentConfirm
258
261
  }),
259
262
  $ ? /* @__PURE__ */ d("button", {
260
263
  type: "button",
@@ -45,7 +45,7 @@ export interface LiveAvatarSeamOptions {
45
45
  readonly avatarVoiceStore?: LiveAvatarVoiceStore;
46
46
  readonly scheduler?: AvatarScheduler;
47
47
  readonly browserSupported?: () => boolean;
48
- readonly controller?: Partial<Pick<LiveAvatarControllerDeps, 'createAudioElement' | 'releaseAudioElement' | 'startMeter' | 'decode' | 'now' | 'newIdempotencyKey' | 'subscribeWorkspaceChange' | 'subscribePageHide' | 'idleMs' | 'endBeforeDeadlineMs' | 'connectTimeoutMs' | 'trackerConfig' | 'maxDrainMs' | 'stallMs' | 'readinessProbe'>>;
48
+ readonly controller?: Partial<Pick<LiveAvatarControllerDeps, 'createAudioElement' | 'releaseAudioElement' | 'startMeter' | 'decode' | 'now' | 'newIdempotencyKey' | 'subscribeWorkspaceChange' | 'subscribePageHide' | 'subscribePageShow' | 'idleMs' | 'endBeforeDeadlineMs' | 'connectTimeoutMs' | 'trackerConfig' | 'maxDrainMs' | 'stallMs' | 'readinessProbe'>>;
49
49
  }
50
50
  /** The seam, plus what a host or a test may need of it. */
51
51
  export interface LiveAvatarSeamHandle extends AssistantLiveAvatarSeam {
@@ -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 };
@@ -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. */
@@ -117,11 +117,19 @@ export interface LiveAvatarControllerDeps {
117
117
  readonly startMeter: (track: MediaStreamTrack, onChange: (audible: boolean) => void) => AvatarMeterHandle | null;
118
118
  /** The browser can show the avatar at all (desktop Chromium first: GATE1B §10). */
119
119
  readonly browserSupported: () => boolean;
120
- /** 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
+ */
121
124
  readonly now: () => number;
122
125
  readonly newIdempotencyKey: () => string;
123
126
  readonly subscribeWorkspaceChange?: (listener: () => void) => () => void;
124
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;
125
133
  readonly idleMs?: number;
126
134
  readonly endBeforeDeadlineMs?: number;
127
135
  readonly connectTimeoutMs?: number;
@@ -157,6 +165,13 @@ export interface LiveAvatarController {
157
165
  * time (`retryAt`) that has not come.
158
166
  */
159
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;
160
175
  /**
161
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
162
177
  * is adopted; a question already in flight is asked again, choose only. Close: any question, choice or session ends as the person's
@@ -3,7 +3,7 @@ import { createAvatarTransportPort as n } from "./avatarTransportPort.js";
3
3
  import { createAvatarReplyOutputs as r } from "./avatarReplyOutputs.js";
4
4
  import { AvatarApiError as i, avatarAvailabilityRefusal as a, avatarRefusalFor as o } from "./avatarSessionClient.js";
5
5
  //#region src/shared/assistant/avatar/live/liveAvatarController.ts
6
- var s = 12e4, c = 1e4, l = 8e3, ee = 5e3, u = 2e4, d = 1e3, f = {
6
+ var s = 12e4, c = 1e4, l = 8e3, ee = 5e3, u = 2e4, te = 1e3, d = {
7
7
  voiceLive: !1,
8
8
  offered: !1,
9
9
  tabOpen: !1,
@@ -21,7 +21,7 @@ var s = 12e4, c = 1e4, l = 8e3, ee = 5e3, u = 2e4, d = 1e3, f = {
21
21
  audioBlocked: !1,
22
22
  videoReady: !1
23
23
  };
24
- function p(e) {
24
+ function f(e) {
25
25
  switch (e) {
26
26
  case "permission_revoked": return {
27
27
  problem: "unavailable",
@@ -57,53 +57,53 @@ function p(e) {
57
57
  };
58
58
  }
59
59
  }
60
- var m = new Set(["playback-blocked", "needs-hosted-voice"]), h = {
60
+ var p = new Set(["playback-blocked", "needs-hosted-voice"]), m = {
61
61
  problem: "unavailable",
62
62
  hide: !0
63
63
  };
64
- function g(s) {
65
- let { scheduler: c, preferences: l } = s, u = s.idleMs ?? 12e4, d = s.endBeforeDeadlineMs ?? 1e4, g = s.connectTimeoutMs ?? 2e4, _ = s.requestTimeoutMs ?? 8e3, v = {
66
- ...f,
64
+ function h(s) {
65
+ let { scheduler: c, preferences: l } = s, u = s.idleMs ?? 12e4, te = s.endBeforeDeadlineMs ?? 1e4, h = s.connectTimeoutMs ?? 2e4, g = s.requestTimeoutMs ?? 8e3, _ = {
66
+ ...d,
67
67
  tabOffered: s.browserSupported()
68
- }, y = /* @__PURE__ */ new Set(), b = !1, x = null, S = 0, C = !1, w = !1, T = 0, E = null, D = null, O = [], k = 0, A = !1, te = "user", j = !1, M = !1, N = null, P = null, F = null, I = null, L = !1;
68
+ }, v = /* @__PURE__ */ new Set(), y = !1, b = null, x = 0, S = !1, C = !1, w = 0, T = null, E = null, D = [], O = 0, k = !1, A = "user", j = !1, M = !1, N = null, P = null, F = null, I = null, L = !1;
69
69
  function R(e) {
70
70
  try {
71
71
  s.onLog?.(e);
72
72
  } catch {}
73
73
  }
74
74
  function z() {
75
- return !b && s.browserSupported();
75
+ return !y && s.browserSupported();
76
76
  }
77
77
  function ne(e, t) {
78
78
  return (e || t) && z();
79
79
  }
80
80
  function B() {
81
- return v.retryAt !== null && s.now() < v.retryAt;
81
+ return _.retryAt !== null && s.now() < _.retryAt;
82
82
  }
83
83
  function V(e) {
84
84
  let t = {
85
- ...v,
85
+ ..._,
86
86
  ...e
87
87
  };
88
- e.problem !== void 0 && e.problem !== v.problem && (e.retryAt === void 0 && (t.retryAt = null), e.budgetScope === void 0 && (t.budgetScope = null));
88
+ e.problem !== void 0 && e.problem !== _.problem && (e.retryAt === void 0 && (t.retryAt = null), e.budgetScope === void 0 && (t.budgetScope = null));
89
89
  let n = {
90
90
  ...t,
91
91
  offered: ne(t.voiceLive, t.tabOpen),
92
92
  tabOffered: z(),
93
93
  heldByVoice: M && t.voiceLive
94
94
  };
95
- if (!Object.keys(n).every((e) => n[e] === v[e])) {
96
- n.retryAt !== v.retryAt && (I?.(), I = null), v = n;
97
- for (let e of [...y]) try {
95
+ if (!Object.keys(n).every((e) => n[e] === _[e])) {
96
+ n.retryAt !== _.retryAt && (I?.(), I = null), _ = n;
97
+ for (let e of [...v]) try {
98
98
  e();
99
99
  } catch {}
100
100
  }
101
101
  }
102
102
  function H() {
103
- return A && !j ? "voice_end" : "user";
103
+ return k && !j ? "voice_end" : "user";
104
104
  }
105
105
  function U() {
106
- l.getState().avatarEnabled !== C && l.setAvatarEnabled(C);
106
+ l.getState().avatarEnabled !== S && l.setAvatarEnabled(S);
107
107
  }
108
108
  let re = {
109
109
  positionSec: null,
@@ -111,15 +111,15 @@ function g(s) {
111
111
  rpc: "none"
112
112
  }, W = r({
113
113
  port: {
114
- isActive: () => D !== null && D.port.isActive(),
115
- isAudioBlocked: () => D?.port.isAudioBlocked() ?? !1,
116
- connectionId: () => D?.port.connectionId() ?? 0,
117
- isMeterHealthy: () => D?.port.isMeterHealthy() ?? !1,
118
- isMeterAudible: () => D?.port.isMeterAudible() ?? !1,
119
- openSegment: () => D ? D.port.openSegment() : Promise.reject(new e("connection")),
120
- clearBuffer: (e) => D ? D.port.clearBuffer(e) : Promise.resolve(re),
121
- setOutputMuted: (e) => D?.port.setOutputMuted(e),
122
- subscribe: (e) => D ? D.port.subscribe(e) : () => void 0
114
+ isActive: () => E !== null && E.port.isActive(),
115
+ isAudioBlocked: () => E?.port.isAudioBlocked() ?? !1,
116
+ connectionId: () => E?.port.connectionId() ?? 0,
117
+ isMeterHealthy: () => E?.port.isMeterHealthy() ?? !1,
118
+ isMeterAudible: () => E?.port.isMeterAudible() ?? !1,
119
+ openSegment: () => E ? E.port.openSegment() : Promise.reject(new e("connection")),
120
+ clearBuffer: (e) => E ? E.port.clearBuffer(e) : Promise.resolve(re),
121
+ setOutputMuted: (e) => E?.port.setOutputMuted(e),
122
+ subscribe: (e) => E ? E.port.subscribe(e) : () => void 0
123
123
  },
124
124
  scheduler: c,
125
125
  decode: s.decode,
@@ -135,7 +135,7 @@ function g(s) {
135
135
  V({ notice: "interrupted" });
136
136
  return;
137
137
  case "voice-fallback":
138
- C && V({ notice: "voice-only" });
138
+ S && V({ notice: "voice-only" });
139
139
  return;
140
140
  case "audio-blocked":
141
141
  V({
@@ -151,7 +151,7 @@ function g(s) {
151
151
  P?.(), P = null;
152
152
  return;
153
153
  }
154
- if (w) {
154
+ if (C) {
155
155
  K("user", { notice: "time-limit" });
156
156
  return;
157
157
  }
@@ -159,8 +159,8 @@ function g(s) {
159
159
  }
160
160
  });
161
161
  function G() {
162
- P?.(), P = null, v.phase === "ready" && (P = c.setTimeout(() => {
163
- P = null, v.phase === "ready" && !W.isBusy() && K("idle", {});
162
+ P?.(), P = null, _.phase === "ready" && (P = c.setTimeout(() => {
163
+ P = null, _.phase === "ready" && !W.isBusy() && K("idle", {});
164
164
  }, u));
165
165
  }
166
166
  function ie() {
@@ -168,8 +168,8 @@ function g(s) {
168
168
  }
169
169
  function ae() {
170
170
  W.leave();
171
- let e = D;
172
- if (D = null, e !== null) {
171
+ let e = E;
172
+ if (E = null, e !== null) {
173
173
  if (e.unsubscribe?.(), e.meter !== null) {
174
174
  if (e.meterAudible) {
175
175
  e.meterAudible = !1;
@@ -181,9 +181,9 @@ function g(s) {
181
181
  }
182
182
  }
183
183
  function K(e, t) {
184
- T += 1, te = e, C = !1, w = !1, M = !1, N = null, ae(), ie();
185
- let n = E;
186
- E = null, U(), n !== null && (R({
184
+ w += 1, A = e, S = !1, C = !1, M = !1, N = null, ae(), ie();
185
+ let n = T;
186
+ T = null, U(), n !== null && (R({
187
187
  event: "session-ended",
188
188
  reason: e
189
189
  }), s.client()?.end(n, {
@@ -194,32 +194,39 @@ function g(s) {
194
194
  closing: !1,
195
195
  audioBlocked: !1,
196
196
  videoReady: !1,
197
- ...t.problem === void 0 ? v.problem !== null && m.has(v.problem) ? { problem: null } : {} : { problem: t.problem },
197
+ ...t.problem === void 0 ? _.problem !== null && p.has(_.problem) ? { problem: null } : {} : { problem: t.problem },
198
198
  ...t.notice === void 0 ? {} : { notice: t.notice }
199
199
  });
200
200
  }
201
201
  function q(e) {
202
202
  let t = o(e);
203
- t.hide && (b = !0), e instanceof i && R({
203
+ t.hide && (y = !0), e instanceof i && R({
204
204
  event: "refused",
205
205
  status: e.status,
206
206
  code: e.code
207
- }), C = !1, M = !1, N = null, U();
207
+ }), S = !1, M = !1, N = null, U();
208
208
  let n = t.retryAfterSeconds !== void 0 && t.retryAfterSeconds > 0 ? t.retryAfterSeconds * 1e3 : 0, r = n > 0 ? s.now() + n : null;
209
209
  return V({
210
210
  phase: "off",
211
211
  problem: t.problem,
212
212
  retryAt: r,
213
213
  budgetScope: t.scope ?? null
214
- }), r !== null && v.retryAt === r && (I = c.setTimeout(() => {
215
- I = null, v.retryAt === r && V({
216
- problem: null,
217
- retryAt: null
218
- });
214
+ }), r !== null && _.retryAt === r && (I = c.setTimeout(() => {
215
+ I = null, J(r);
219
216
  }, n)), t;
220
217
  }
221
- function J(e = h) {
222
- e.hide && (b = !0), C = !1, M = !1, N = null, U(), V({
218
+ function J(e) {
219
+ _.retryAt === e && V({
220
+ problem: null,
221
+ retryAt: null
222
+ });
223
+ }
224
+ function Y() {
225
+ let e = _.retryAt;
226
+ L || e === null || B() || J(e);
227
+ }
228
+ function X(e = m) {
229
+ e.hide && (y = !0), S = !1, M = !1, N = null, U(), V({
223
230
  phase: "off",
224
231
  problem: e.problem,
225
232
  retryAt: null,
@@ -229,7 +236,7 @@ function g(s) {
229
236
  function oe(e) {
230
237
  switch (e.type) {
231
238
  case "audioPlayback":
232
- e.canPlay ? v.problem === "playback-blocked" ? V({
239
+ e.canPlay ? _.problem === "playback-blocked" ? V({
233
240
  audioBlocked: !1,
234
241
  problem: null
235
242
  }) : V({ audioBlocked: !1 }) : V({
@@ -245,22 +252,22 @@ function g(s) {
245
252
  }
246
253
  }
247
254
  async function se() {
248
- let e = E;
249
- if (e === null || v.phase === "ending" || v.phase === "off") return;
250
- let t = T;
255
+ let e = T;
256
+ if (e === null || _.phase === "ending" || _.phase === "off") return;
257
+ let t = w;
251
258
  V({ phase: "ending" });
252
259
  let n = null, r = !1;
253
260
  try {
254
- let t = s.client(), i = t === null ? void 0 : await Y((n) => t.status(e, n));
261
+ let t = s.client(), i = t === null ? void 0 : await le((n) => t.status(e, n));
255
262
  n = i?.endedReason ?? null, r = i !== void 0 && [
256
263
  "ended",
257
264
  "failed",
258
265
  "canceled"
259
266
  ].includes(i.state);
260
267
  } catch {}
261
- if (T !== t) return;
262
- let i = p(n);
263
- i.hide && (b = !0), r && (E = null, R({
268
+ if (w !== t) return;
269
+ let i = f(n);
270
+ i.hide && (y = !0), r && (T = null, R({
264
271
  event: "session-ended",
265
272
  reason: "server"
266
273
  })), K("client_error", i);
@@ -268,10 +275,10 @@ function g(s) {
268
275
  async function ce(e, r) {
269
276
  let i = s.client();
270
277
  if (i === null) {
271
- J();
278
+ X();
272
279
  return;
273
280
  }
274
- let a = ++T;
281
+ let a = ++w;
275
282
  V({
276
283
  phase: "starting",
277
284
  problem: null,
@@ -281,103 +288,103 @@ function g(s) {
281
288
  try {
282
289
  o = await s.openRoom();
283
290
  } catch {
284
- T === a && J();
291
+ w === a && X();
285
292
  return;
286
293
  }
287
294
  let l = () => {
288
295
  o.disconnect().catch(() => void 0);
289
296
  };
290
- if (T !== a) {
297
+ if (w !== a) {
291
298
  l();
292
299
  return;
293
300
  }
294
- let u = c.now(), f;
301
+ let u = c.now(), d;
295
302
  try {
296
- f = await i.create({
303
+ d = await i.create({
297
304
  avatarId: e,
298
305
  consentVersion: r,
299
306
  idempotencyKey: s.newIdempotencyKey()
300
307
  });
301
308
  } catch (e) {
302
- if (l(), T !== a) return;
303
- q(e).showDisclosure && (x = null, $.requestOn({ start: !1 }));
309
+ if (l(), w !== a) return;
310
+ q(e).showDisclosure && (b = null, $.requestOn({ start: !1 }));
304
311
  return;
305
312
  }
306
- if (T !== a) {
307
- l(), i.end(f.sessionId, { reason: te === "voice_end" ? "voice_end" : "user" }).catch(() => void 0);
313
+ if (w !== a) {
314
+ l(), i.end(d.sessionId, { reason: A === "voice_end" ? "voice_end" : "user" }).catch(() => void 0);
308
315
  return;
309
316
  }
310
- E = f.sessionId, V({ phase: "connecting" });
311
- let p = new t({
317
+ T = d.sessionId, V({ phase: "connecting" });
318
+ let f = new t({
312
319
  room: o,
313
- avatarIdentity: f.avatarIdentity,
320
+ avatarIdentity: d.avatarIdentity,
314
321
  scheduler: c,
315
322
  ...s.readinessProbe === void 0 ? {} : { readinessProbe: s.readinessProbe }
316
- }), m = /* @__PURE__ */ new Set(), h = {
317
- session: f,
318
- transport: p,
323
+ }), p = /* @__PURE__ */ new Set(), m = {
324
+ session: d,
325
+ transport: f,
319
326
  audioElement: s.createAudioElement(),
320
327
  meter: null,
321
328
  meterAudible: !1,
322
- meterListeners: m,
329
+ meterListeners: p,
323
330
  videoTrack: null,
324
331
  unsubscribe: null,
325
332
  port: n({
326
- transport: p,
327
- isEnabled: () => C && !w,
328
- subscribeMeter: (e) => (m.add(e), () => {
329
- m.delete(e);
333
+ transport: f,
334
+ isEnabled: () => S && !C,
335
+ subscribeMeter: (e) => (p.add(e), () => {
336
+ p.delete(e);
330
337
  }),
331
- isMeterHealthy: () => h.meter?.isRunning() ?? !1
338
+ isMeterHealthy: () => m.meter?.isRunning() ?? !1
332
339
  })
333
340
  };
334
- D = h, h.unsubscribe = p.subscribe(oe);
341
+ E = m, m.unsubscribe = f.subscribe(oe);
335
342
  try {
336
- await le(p.connect({
337
- url: f.livekitUrl,
338
- token: f.participantToken,
339
- audioElement: h.audioElement,
343
+ await ue(f.connect({
344
+ url: d.livekitUrl,
345
+ token: d.participantToken,
346
+ audioElement: m.audioElement,
340
347
  onAvatarAudio: (e) => {
341
- if (!(e.mediaStreamTrack === null || h.meter !== null)) try {
342
- h.meter = s.startMeter(e.mediaStreamTrack, (e) => {
343
- h.meterAudible = e;
344
- for (let t of [...m]) t(e);
348
+ if (!(e.mediaStreamTrack === null || m.meter !== null)) try {
349
+ m.meter = s.startMeter(e.mediaStreamTrack, (e) => {
350
+ m.meterAudible = e;
351
+ for (let t of [...p]) t(e);
345
352
  });
346
353
  } catch {
347
- h.meter = null;
354
+ m.meter = null;
348
355
  }
349
356
  },
350
357
  onAvatarVideo: (e) => {
351
- h.videoTrack = e;
352
- let t = X();
353
- t !== null && e.attach(t), D === h && V({ videoReady: !0 });
358
+ m.videoTrack = e;
359
+ let t = Z();
360
+ t !== null && e.attach(t), E === m && V({ videoReady: !0 });
354
361
  }
355
- }), g);
362
+ }), h);
356
363
  } catch (e) {
357
- if (T !== a) return;
364
+ if (w !== a) return;
358
365
  R({
359
366
  event: "connect-failed",
360
367
  error: e instanceof Error ? e.name : "error"
361
368
  }), K("client_error", { problem: "connection" });
362
369
  return;
363
370
  }
364
- if (T !== a || D !== h) return;
371
+ if (w !== a || E !== m) return;
365
372
  V({
366
373
  phase: "ready",
367
- audioBlocked: p.audioBlocked,
368
- ...p.audioBlocked ? { problem: "playback-blocked" } : {}
374
+ audioBlocked: f.audioBlocked,
375
+ ...f.audioBlocked ? { problem: "playback-blocked" } : {}
369
376
  }), G();
370
- let _ = S > 0 ? u + S - ee - c.now() : f.endsAt - s.now(), v = Math.max(0, _ - d);
377
+ let g = x > 0 ? u + x - ee - c.now() : d.endsAt - s.now(), _ = Math.max(0, g - te);
371
378
  F = c.setTimeout(() => {
372
- F = null, T === a && (w = !0, V({ closing: !0 }), W.isBusy() || K("user", { notice: "time-limit" }));
373
- }, v);
379
+ F = null, w === a && (C = !0, V({ closing: !0 }), W.isBusy() || K("user", { notice: "time-limit" }));
380
+ }, _);
374
381
  }
375
- function Y(t) {
382
+ function le(t) {
376
383
  let n = new AbortController();
377
384
  return new Promise((r, i) => {
378
385
  let a = c.setTimeout(() => {
379
386
  n.abort(), i(new e("connection"));
380
- }, _);
387
+ }, g);
381
388
  t(n.signal).then((e) => {
382
389
  a(), r(e);
383
390
  }, (e) => {
@@ -385,7 +392,7 @@ function g(s) {
385
392
  });
386
393
  });
387
394
  }
388
- function le(t, n) {
395
+ function ue(t, n) {
389
396
  return new Promise((r, i) => {
390
397
  let a = c.setTimeout(() => i(new e("connection")), n);
391
398
  t.then((e) => {
@@ -395,32 +402,32 @@ function g(s) {
395
402
  });
396
403
  });
397
404
  }
398
- function X() {
399
- return O[O.length - 1] ?? null;
405
+ function Z() {
406
+ return D[D.length - 1] ?? null;
400
407
  }
401
- function Z(e) {
402
- let t = O.indexOf(e);
408
+ function de(e) {
409
+ let t = D.indexOf(e);
403
410
  if (t === -1) return;
404
- let n = t === O.length - 1;
405
- O.splice(t, 1);
406
- let r = D?.videoTrack ?? null;
411
+ let n = t === D.length - 1;
412
+ D.splice(t, 1);
413
+ let r = E?.videoTrack ?? null;
407
414
  if (!n || r === null) return;
408
415
  r.detach();
409
- let i = X();
416
+ let i = Z();
410
417
  i !== null && r.attach(i);
411
418
  }
412
419
  let Q = [];
413
420
  Q.push(l.subscribe(() => {
414
421
  let e = l.getState().avatarEnabled;
415
- if (e !== C) {
422
+ if (e !== S) {
416
423
  if (e) {
417
424
  l.setAvatarEnabled(!1);
418
425
  return;
419
426
  }
420
- v.phase !== "off" && v.phase !== "checking" && v.phase !== "choosing" ? K(H(), {}) : C = !1;
427
+ _.phase !== "off" && _.phase !== "checking" && _.phase !== "choosing" ? K(H(), {}) : S = !1;
421
428
  }
422
429
  })), s.subscribeWorkspaceChange && Q.push(s.subscribeWorkspaceChange(() => {
423
- if (b = !1, v.phase === "off" ? V({
430
+ if (y = !1, _.phase === "off" ? V({
424
431
  problem: null,
425
432
  avatars: [],
426
433
  consentVersion: null
@@ -428,26 +435,26 @@ function g(s) {
428
435
  avatars: [],
429
436
  consentVersion: null
430
437
  })), j) {
431
- let e = T;
438
+ let e = w;
432
439
  c.setTimeout(() => {
433
- j && T === e && $.requestOn({ start: !1 });
440
+ j && w === e && $.requestOn({ start: !1 });
434
441
  }, 0);
435
442
  }
436
443
  })), s.subscribePageHide && Q.push(s.subscribePageHide(() => {
437
- (E !== null || v.phase !== "off") && K("page_hide", { keepalive: !0 });
438
- }));
444
+ (T !== null || _.phase !== "off") && K("page_hide", { keepalive: !0 });
445
+ })), s.subscribePageShow && Q.push(s.subscribePageShow(Y));
439
446
  let $ = {
440
447
  subscribe(e) {
441
- return y.add(e), () => {
442
- y.delete(e);
448
+ return v.add(e), () => {
449
+ v.delete(e);
443
450
  };
444
451
  },
445
- getSnapshot: () => v,
452
+ getSnapshot: () => _,
446
453
  setVoiceLive(e) {
447
- if (A = !e, e) {
454
+ if (k = !e, e) {
448
455
  V({
449
456
  voiceLive: !0,
450
- problem: b || j && B() ? v.problem : null,
457
+ problem: y || j && B() ? _.problem : null,
451
458
  notice: null
452
459
  });
453
460
  return;
@@ -456,33 +463,33 @@ function g(s) {
456
463
  V({ voiceLive: !1 });
457
464
  return;
458
465
  }
459
- (E !== null || v.phase !== "off") && (v.phase === "checking" || v.phase === "choosing" ? (T += 1, V({ phase: "off" })) : K("voice_end", {})), V({
466
+ (T !== null || _.phase !== "off") && (_.phase === "checking" || _.phase === "choosing" ? (w += 1, V({ phase: "off" })) : K("voice_end", {})), V({
460
467
  voiceLive: !1,
461
468
  notice: null
462
469
  });
463
470
  },
464
471
  requestOn(e = {}) {
465
- if (L || !v.offered || v.phase !== "off") return;
472
+ if (L || !_.offered || _.phase !== "off") return;
466
473
  let t = e.start === void 0 ? "auto" : e.start ? "start" : "choose";
467
474
  if (t === "start" && B()) return;
468
- let n = e.avatarId ?? v.selectedAvatarId, r = v.consentVersion, i = s.client();
475
+ let n = e.avatarId ?? _.selectedAvatarId, r = _.consentVersion, i = s.client();
469
476
  if (i === null) {
470
- J();
477
+ X();
471
478
  return;
472
479
  }
473
480
  t === "start" && (N = c.now());
474
- let o = ++T;
481
+ let o = ++w;
475
482
  V({
476
483
  phase: "checking",
477
- problem: B() ? v.problem : null,
484
+ problem: B() ? _.problem : null,
478
485
  notice: null
479
- }), Y((e) => i.availability(e)).then((e) => {
480
- if (T !== o) return;
486
+ }), le((e) => i.availability(e)).then((e) => {
487
+ if (w !== o) return;
481
488
  if (!e.available) {
482
- J(a(e.reason));
489
+ X(a(e.reason));
483
490
  return;
484
491
  }
485
- S = e.maxSessionSeconds * 1e3;
492
+ x = e.maxSessionSeconds * 1e3;
486
493
  let i = t === "start" && n !== null && e.avatars.includes(n), s = l.getState().avatarId, c = i ? n : s !== null && e.avatars.includes(s) ? s : e.avatars[0] ?? null;
487
494
  switch (V({
488
495
  phase: "choosing",
@@ -491,7 +498,7 @@ function g(s) {
491
498
  consentVersion: e.consentVersion
492
499
  }), t) {
493
500
  case "auto":
494
- x === e.consentVersion && e.avatars.length === 1 && $.confirm();
501
+ b === e.consentVersion && e.avatars.length === 1 && $.confirm();
495
502
  return;
496
503
  case "start":
497
504
  i && r === e.consentVersion ? $.confirm() : N = null;
@@ -499,45 +506,46 @@ function g(s) {
499
506
  case "choose": return;
500
507
  }
501
508
  }, (e) => {
502
- T === o && q(e);
509
+ w === o && q(e);
503
510
  });
504
511
  },
505
512
  confirm() {
506
- if (v.phase !== "choosing" || !(v.voiceLive || j) || B()) return;
507
- let e = v.selectedAvatarId, t = v.consentVersion;
508
- e === null || t === null || (x = t, l.setAvatarId(e), C = !0, M = !j, j && (N ??= c.now()), U(), ce(e, t));
513
+ if (_.phase !== "choosing" || !(_.voiceLive || j) || B()) return;
514
+ let e = _.selectedAvatarId, t = _.consentVersion;
515
+ e === null || t === null || (b = t, l.setAvatarId(e), S = !0, M = !j, j && (N ??= c.now()), U(), ce(e, t));
509
516
  },
517
+ recheckRetry: Y,
510
518
  cancelChoice() {
511
- v.phase === "choosing" && (T += 1, N = null, V({ phase: "off" }));
519
+ _.phase === "choosing" && (w += 1, N = null, V({ phase: "off" }));
512
520
  },
513
521
  setTabOpen(e) {
514
522
  if (L || e === j) return;
515
523
  if (j = e, e) {
516
- V({ tabOpen: !0 }), v.phase === "checking" && (T += 1, V({ phase: "off" }), $.requestOn({ start: !1 }));
524
+ V({ tabOpen: !0 }), _.phase === "checking" && (w += 1, V({ phase: "off" }), $.requestOn({ start: !1 }));
517
525
  return;
518
526
  }
519
527
  N = null;
520
- let t = v.phase;
528
+ let t = _.phase;
521
529
  if (t === "checking" || t === "choosing") {
522
- T += 1, V({
530
+ w += 1, V({
523
531
  tabOpen: !1,
524
532
  phase: "off"
525
533
  });
526
534
  return;
527
535
  }
528
- (E !== null || t !== "off") && !v.heldByVoice && K("user", {}), V({ tabOpen: !1 });
536
+ (T !== null || t !== "off") && !_.heldByVoice && K("user", {}), V({ tabOpen: !1 });
529
537
  },
530
538
  selectAvatar(e) {
531
- v.avatars.includes(e) && V({ selectedAvatarId: e });
539
+ _.avatars.includes(e) && V({ selectedAvatarId: e });
532
540
  },
533
541
  switchOff() {
534
- let { phase: e } = v;
542
+ let { phase: e } = _;
535
543
  if (!(j && N !== null && (e === "checking" || e === "starting" || e === "connecting") && c.now() - N < 1e3)) {
536
544
  if (e === "checking" || e === "choosing") {
537
- T += 1, N = null, V({ phase: "off" });
545
+ w += 1, N = null, V({ phase: "off" });
538
546
  return;
539
547
  }
540
- if (e === "off" && !C) {
548
+ if (e === "off" && !S) {
541
549
  U();
542
550
  return;
543
551
  }
@@ -545,41 +553,41 @@ function g(s) {
545
553
  }
546
554
  },
547
555
  resumeAudio() {
548
- let e = D;
556
+ let e = E;
549
557
  e !== null && (e.transport.resumeAudio().catch(() => void 0), e.meter?.resume().catch(() => void 0));
550
558
  },
551
559
  setStageMounted(e) {
552
- k = Math.max(0, k + (e ? 1 : -1));
560
+ O = Math.max(0, O + (e ? 1 : -1));
553
561
  },
554
562
  attachVideo(e) {
555
563
  if (e === null) {
556
- let e = X();
557
- e !== null && Z(e);
564
+ let e = Z();
565
+ e !== null && de(e);
558
566
  return;
559
567
  }
560
- let t = O.indexOf(e);
561
- t !== -1 && O.splice(t, 1), O.push(e);
562
- let n = D?.videoTrack ?? null;
563
- return n !== null && (n.detach(), n.attach(e)), () => Z(e);
568
+ let t = D.indexOf(e);
569
+ t !== -1 && D.splice(t, 1), D.push(e);
570
+ let n = E?.videoTrack ?? null;
571
+ return n !== null && (n.detach(), n.attach(e)), () => de(e);
564
572
  },
565
573
  route(e, t) {
566
- if (v.phase !== "ready" || w || !C || k === 0 || D === null || D.port.isAudioBlocked() || e.fallback === !1) return "speakers";
574
+ if (_.phase !== "ready" || C || !S || O === 0 || E === null || E.port.isAudioBlocked() || e.fallback === !1) return "speakers";
567
575
  let n = e.voice;
568
576
  return n !== null && n.source !== "hosted" || !t.listVoices().some((e) => e.source === "hosted") ? (R({
569
577
  event: "notice",
570
578
  notice: { kind: "needs-hosted-voice" }
571
- }), V({ problem: "needs-hosted-voice" }), "speakers") : (v.problem === "needs-hosted-voice" && V({ problem: null }), "avatar");
579
+ }), V({ problem: "needs-hosted-voice" }), "speakers") : (_.problem === "needs-hosted-voice" && V({ problem: null }), "avatar");
572
580
  },
573
581
  createOutput: () => W.createOutput(),
574
582
  dispose() {
575
583
  if (!L) {
576
- L = !0, (E !== null || v.phase !== "off") && K("user", {}), I?.(), I = null;
584
+ L = !0, (T !== null || _.phase !== "off") && K("user", {}), I?.(), I = null;
577
585
  for (let e of Q.splice(0)) e();
578
- y.clear();
586
+ v.clear();
579
587
  }
580
588
  }
581
589
  };
582
590
  return $;
583
591
  }
584
592
  //#endregion
585
- export { u as LIVE_AVATAR_CONNECT_TIMEOUT_MS, c as LIVE_AVATAR_END_BEFORE_DEADLINE_MS, s as LIVE_AVATAR_IDLE_MS, l as LIVE_AVATAR_REQUEST_TIMEOUT_MS, d as LIVE_AVATAR_TAB_OFF_GUARD_MS, g as createLiveAvatarController };
593
+ export { u as LIVE_AVATAR_CONNECT_TIMEOUT_MS, c as LIVE_AVATAR_END_BEFORE_DEADLINE_MS, s as LIVE_AVATAR_IDLE_MS, l as LIVE_AVATAR_REQUEST_TIMEOUT_MS, te as LIVE_AVATAR_TAB_OFF_GUARD_MS, h as createLiveAvatarController };
@@ -3,16 +3,18 @@ import { AvatarCopy } from '../useAvatarCopy';
3
3
  import { LiveAvatarSnapshot } from './liveAvatarController';
4
4
  /**
5
5
  * ★ BOFF-9360: whether the avatar can take a typed question's answer now, as the Avatar tab's ask box reads it
6
- * (`LiveAvatarTabSeam.ask`). Pure, from the snapshot alone: it never asks `route()`, which has side effects.
6
+ * (`LiveAvatarTabSeam.ask`). From the snapshot and the time (`now`) alone: it never asks `route()`, which has side effects.
7
7
  * - `ready`: on, and nothing in the way;
8
8
  * - `sound-blocked`: on, but the browser blocks its sound until a tap on the frame lifts it (critic m1);
9
9
  * - `ending`: stopping, or near its time limit (`closing`), so no new reply goes to it (critic m1);
10
10
  * - `starting`: being started or joined;
11
- * - `locked`: off after a "not now" refusal that said when (`retryAt`): Turn on waits for that time (critic m11);
11
+ * - `locked`: off after a "not now" refusal that said when (`retryAt`), until that time (`now`, wall-clock ms): Turn on waits for it
12
+ * (critic m11). From that time on it is `off`, even while the snapshot still carries the time: the timer that clears it runs late
13
+ * after a system sleep (BOFF-9333 UI-11);
12
14
  * - `off`: off, or being set up (checking, choosing).
13
15
  * A device voice (no hosted voice to animate) is not predicted here: the route says so per reply, and the tab's
14
16
  * status line already explains it (`needs-hosted-voice`).
15
17
  */
16
- export declare function liveAvatarAskState(snapshot: LiveAvatarSnapshot): LiveAvatarAskState;
18
+ export declare function liveAvatarAskState(snapshot: LiveAvatarSnapshot, now?: number): LiveAvatarAskState;
17
19
  /** The words for the avatar's state line: what went wrong, what it is doing, or null. `now`: wall-clock ms, for the retry time. */
18
20
  export declare function liveAvatarStatusText(snapshot: LiveAvatarSnapshot, copy: AvatarCopy, now?: number): string | null;
@@ -1,11 +1,11 @@
1
1
  //#region src/shared/assistant/avatar/live/liveAvatarStatus.ts
2
- function e(e) {
2
+ function e(e, t = Date.now()) {
3
3
  switch (e.phase) {
4
4
  case "ready": return e.closing ? "ending" : e.audioBlocked ? "sound-blocked" : "ready";
5
5
  case "starting":
6
6
  case "connecting": return "starting";
7
7
  case "ending": return "ending";
8
- default: return e.retryAt === null ? "off" : "locked";
8
+ default: return e.retryAt !== null && e.retryAt > t ? "locked" : "off";
9
9
  }
10
10
  }
11
11
  function t(e, t, n = Date.now()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/fe-libs",
3
- "version": "2026.1024.1",
3
+ "version": "2026.1024.2",
4
4
  "description": "Burdenoff frontend primitives and domain libraries",
5
5
  "type": "module",
6
6
  "bin": {