@burdenoff/fe-libs 2026.1024.3 → 2026.1026.1

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 (33) hide show
  1. package/dist/shared/assistant/avatar/live/LiveAvatarMinutes.d.ts +24 -0
  2. package/dist/shared/assistant/avatar/live/LiveAvatarMinutes.js +161 -0
  3. package/dist/shared/assistant/avatar/live/LiveAvatarStage.js +95 -74
  4. package/dist/shared/assistant/avatar/live/LiveAvatarTabView.d.ts +6 -0
  5. package/dist/shared/assistant/avatar/live/LiveAvatarTabView.js +195 -172
  6. package/dist/shared/assistant/avatar/live/avatarPorts.d.ts +9 -0
  7. package/dist/shared/assistant/avatar/live/avatarSessionClient.d.ts +77 -1
  8. package/dist/shared/assistant/avatar/live/avatarSessionClient.js +193 -72
  9. package/dist/shared/assistant/avatar/live/avatarTransport.d.ts +25 -3
  10. package/dist/shared/assistant/avatar/live/avatarTransport.js +29 -2
  11. package/dist/shared/assistant/avatar/live/createLiveAvatarSeam.d.ts +35 -4
  12. package/dist/shared/assistant/avatar/live/createLiveAvatarSeam.js +136 -79
  13. package/dist/shared/assistant/avatar/live/liveAvatarController.d.ts +79 -3
  14. package/dist/shared/assistant/avatar/live/liveAvatarController.js +424 -245
  15. package/dist/shared/assistant/avatar/live/liveAvatarMinutesView.d.ts +127 -0
  16. package/dist/shared/assistant/avatar/live/liveAvatarMinutesView.js +168 -0
  17. package/dist/shared/assistant/avatar/live/liveAvatarStatus.d.ts +3 -0
  18. package/dist/shared/assistant/avatar/live/liveAvatarStatus.js +11 -7
  19. package/dist/shared/assistant/avatar/live/liveKitAvatarRoom.js +6 -1
  20. package/dist/shared/assistant/avatar/useAvatarCopy.d.ts +127 -2
  21. package/dist/shared/assistant/avatar/useAvatarCopy.js +116 -7
  22. package/dist/shared/assistant/i18nManifest.d.ts +32 -1
  23. package/dist/shared/assistant/i18nManifest.js +55 -24
  24. package/dist/shared/assistant/index.d.ts +2 -0
  25. package/dist/shared/assistant/ui/AssistantChatArea.js +17 -17
  26. package/dist/shared/assistant/ui/AssistantMarkdownRenderer.d.ts +5 -2
  27. package/dist/shared/assistant/ui/assistantLiveAvatarSeam.d.ts +11 -3
  28. package/dist/shared/assistant/ui/avatar/AssistantAvatarTab.js +78 -76
  29. package/dist/shared/assistant/ui/avatar/avatarAskGate.d.ts +2 -0
  30. package/dist/shared/assistant/ui/avatar/avatarAskGate.js +1 -0
  31. package/dist/shared/events/index.d.ts +2 -1
  32. package/dist/shared-assistant.js +227 -225
  33. package/package.json +1 -1
@@ -4,23 +4,121 @@ import { VOICE_THINKING_LABEL as n } from "../voice/voiceStatus.js";
4
4
  import { useMemo as r } from "react";
5
5
  //#region src/shared/assistant/avatar/useAvatarCopy.ts
6
6
  var i = {
7
+ title: {
8
+ key: "avatar.minutes.howCounted.title",
9
+ fallback: "How avatar credits are counted"
10
+ },
11
+ unit: {
12
+ key: "avatar.minutes.howCounted.unit",
13
+ fallback: "1 credit = 1 minute of avatar time."
14
+ },
15
+ start: {
16
+ key: "avatar.minutes.howCounted.start",
17
+ fallback: "Counting starts once you and the avatar are both connected. Waiting for the avatar and starts that fail are free."
18
+ },
19
+ rounding: {
20
+ key: "avatar.minutes.howCounted.rounding",
21
+ fallback: "Each started minute uses one avatar credit. For example, a 2-minute-10-second session uses 3 credits."
22
+ },
23
+ drops: {
24
+ key: "avatar.minutes.howCounted.drops",
25
+ fallback: "Short connection drops that don't end the session count as part of it, like a phone call."
26
+ },
27
+ lost: {
28
+ key: "avatar.minutes.howCounted.lost",
29
+ fallback: "If the connection is lost, counting stops at the last moment we saw you both connected, minus a {{seconds}}-second safety margin."
30
+ },
31
+ lostNoFigure: {
32
+ key: "avatar.minutes.howCounted.lostNoFigure",
33
+ fallback: "If the connection is lost, counting stops at the last moment we saw you both connected, minus a safety margin."
34
+ },
35
+ ourEnd: {
36
+ key: "avatar.minutes.howCounted.ourEnd",
37
+ fallback: "If we stop the session because of a problem or maintenance on our side, or the avatar fails, the unfinished minute is free."
38
+ },
39
+ support: {
40
+ key: "avatar.minutes.howCounted.support",
41
+ fallback: "If something looks wrong, contact support: we credit the whole session back."
42
+ }
43
+ }, a = {
7
44
  hour: "numeric",
8
45
  minute: "2-digit"
9
- }, a = {
46
+ }, o = {
10
47
  month: "short",
11
48
  day: "numeric",
12
49
  hour: "numeric",
13
50
  minute: "2-digit"
14
51
  };
15
- function o(e, t, n) {
16
- let r = new Date(e).toDateString() === new Date(t).toDateString() ? i : a;
52
+ function s(e, t, n) {
53
+ let r = new Date(e).toDateString() === new Date(t).toDateString() ? a : o;
17
54
  try {
18
55
  return new Intl.DateTimeFormat(n, r).format(e);
19
56
  } catch {
20
57
  return new Intl.DateTimeFormat("en", r).format(e);
21
58
  }
22
59
  }
23
- function s() {
60
+ var c = {
61
+ timeZone: "UTC",
62
+ day: "numeric",
63
+ month: "short"
64
+ }, l = /* @__PURE__ */ new Map(), u = /* @__PURE__ */ new Map();
65
+ function d(e, t) {
66
+ let n = t ?? "", r = l.get(n);
67
+ if (r === void 0) {
68
+ try {
69
+ r = new Intl.DateTimeFormat(t, c);
70
+ } catch {
71
+ r = new Intl.DateTimeFormat("en", c);
72
+ }
73
+ l.set(n, r);
74
+ }
75
+ return r.format(e);
76
+ }
77
+ function f(e, t) {
78
+ let n = t ?? "", r = u.get(n);
79
+ if (r === void 0) {
80
+ try {
81
+ r = new Intl.NumberFormat(t, { maximumFractionDigits: 0 });
82
+ } catch {
83
+ r = new Intl.NumberFormat("en", { maximumFractionDigits: 0 });
84
+ }
85
+ u.set(n, r);
86
+ }
87
+ return r.format(e);
88
+ }
89
+ function p(e, t) {
90
+ let n = (e) => f(e, t), r = (e) => d(e, t), a = (t, n) => e(t.key, t.fallback, n), o = i;
91
+ return {
92
+ left: (t) => e("avatar.minutes.left", "Avatar credits left: {{count}}", { count: n(t) }),
93
+ shared: e("avatar.minutes.shared", "Everyone in this workspace shares these credits."),
94
+ freePerMonth: (t) => e("avatar.minutes.freePerMonth", "Free avatar credits each month: {{count}}", { count: n(t) }),
95
+ noFree: e("avatar.minutes.noFree", "No free avatar credits are included."),
96
+ planPerMonth: (t) => e("avatar.minutes.planPerMonth", "Avatar credits from paid plans each month: {{count}}", { count: n(t) }),
97
+ resetsOn: (t) => e("avatar.minutes.resetsOn", "Avatar credits reset on {{date}} (UTC).", { date: r(t) }),
98
+ freePerPerson: (t) => e("avatar.minutes.freePerPerson", "Free avatar credits per person each month: {{count}}", { count: n(t) }),
99
+ low: e("avatar.minutes.low", "Avatar credits are running low. The avatar stops when they run out."),
100
+ sessionLeft: (t) => e("avatar.minutes.sessionLeft", "Minutes left in this session: about {{count}}", { count: n(t) }),
101
+ seePlans: e("avatar.minutes.seePlans", "See plans"),
102
+ askAdmin: e("avatar.minutes.askAdmin", "Ask your billing administrator about a plan with more avatar credits."),
103
+ checkAgain: e("avatar.minutes.checkAgain", "Check again"),
104
+ checking: e("avatar.minutes.checking", "Checking…"),
105
+ checkingPlan: e("avatar.minutes.checkingPlan", "Checking your plan…"),
106
+ assignPlan: e("avatar.minutes.assignPlan", "Assign your new plan to this workspace in Billing to use its credits."),
107
+ ended: e("avatar.minutes.ended", "The avatar stopped because this workspace’s avatar credits ran out. Continuing with voice only."),
108
+ endedNoVoice: e("avatar.minutes.endedNoVoice", "The avatar stopped because this workspace’s avatar credits ran out."),
109
+ howCountedTitle: a(o.title),
110
+ howCounted: (e) => [
111
+ a(o.unit),
112
+ a(o.start),
113
+ a(o.rounding),
114
+ a(o.drops),
115
+ e === null ? a(o.lostNoFigure) : a(o.lost, { seconds: n(e) }),
116
+ a(o.ourEnd),
117
+ a(o.support)
118
+ ]
119
+ };
120
+ }
121
+ function m() {
24
122
  let i = t(), { locale: a } = e();
25
123
  return r(() => ({
26
124
  toggle: i("avatar.toggle", "Live avatar"),
@@ -58,7 +156,7 @@ function s() {
58
156
  chooserLocked: i("avatar.chooserLocked", "Turn off the avatar to change it."),
59
157
  tabLeaveHint: i("avatar.tabLeaveHint", "Leaving the Avatar tab turns the avatar off."),
60
158
  availableAgain: (e) => i("avatar.availableAgain", "You can start it again at {{time}}.", { time: e }),
61
- retryTime: (e, t) => o(e, t, a),
159
+ retryTime: (e, t) => s(e, t, a),
62
160
  stateTimeLimitNoVoice: i("avatar.stateTimeLimitNoVoice", "The avatar reached its time limit."),
63
161
  stateVoiceOnlyNoVoice: i("avatar.stateVoiceOnlyNoVoice", "The avatar is unavailable right now."),
64
162
  turnOnStarting: i("avatar.turnOnStarting", "Starting the avatar…"),
@@ -89,6 +187,7 @@ function s() {
89
187
  case "mode-escalated": return i("avatar.askMode", "This conversation can make changes, so typed questions to the avatar aren't available here. Start a new conversation to ask one.");
90
188
  case "avatar-off": return i("avatar.askNeedsAvatar", "Turn on the avatar to send your question. You can type it now.");
91
189
  case "avatar-again": return i("avatar.askAgain", "You can send your question when the avatar is on again.");
190
+ case "avatar-minutes": return i("avatar.askNoMinutes", "You can send your question when avatar credits are available.");
92
191
  case "sound": return i("avatar.askSound", "Turn on the avatar's sound first.");
93
192
  case "starting": return i("avatar.askStarting", "You can send your question when the avatar is ready.");
94
193
  case "busy": return i("avatar.askBusy", "You can send your question when the assistant is ready.");
@@ -135,9 +234,19 @@ function s() {
135
234
  case "needs-hosted-voice": return i("avatar.problemNeedsHostedVoice", "The avatar needs one of the assistant’s own voices. Choose one in the voice settings.");
136
235
  case "unsupported-browser": return i("avatar.problemUnsupportedBrowser", "This browser can’t show the avatar.");
137
236
  case "playback-blocked": return i("avatar.problemPlaybackBlocked", "The browser blocked the avatar’s sound. Tap the avatar to turn it on.");
237
+ case "minutes-exhausted": switch (t.minutesReason) {
238
+ case "FREE_CEILING": return i("avatar.problemMinutesFreeCeiling", "You’ve used your free avatar credits for this month.");
239
+ case "NOT_INCLUDED": return i("avatar.problemMinutesNotIncluded", "Avatar credits aren’t included for this workspace.");
240
+ case "ZERO_SESSION_LIMIT": return i("avatar.problemMinutesZeroSessionLimit", "You’ve started too many short avatar sessions today.");
241
+ default: return t.minutesResetsAt === void 0 || t.minutesResetsAt === null ? i("avatar.problemMinutesExhausted", "No avatar credits left in this workspace.") : i("avatar.problemMinutesExhaustedUntil", "No avatar credits left in this workspace. They come back on {{date}} (UTC).", { date: d(t.minutesResetsAt, a) });
242
+ }
243
+ case "minutes-in-use": return r ? i("avatar.problemMinutesInUseTimed", "Avatar credits are in use in another session.") : i("avatar.problemMinutesInUse", "Avatar credits are in use in another session. Try again when it ends.");
244
+ case "minutes-in-use-self": return r ? i("avatar.problemMinutesInUseSelfTimed", "Your avatar is still running on another device. End it there.") : i("avatar.problemMinutesInUseSelf", "Your avatar is still running on another device. End it there, or try again when it ends.");
245
+ case "minutes-unavailable": return r ? i("avatar.problemMinutesUnavailableTimed", "Avatar credits can’t be checked right now.") : i("avatar.problemMinutesUnavailable", "Avatar credits can’t be checked right now. Try again in a moment.");
138
246
  }
139
- }
247
+ },
248
+ minutes: p(i, a)
140
249
  }), [i, a]);
141
250
  }
142
251
  //#endregion
143
- export { o as formatAvatarRetryTime, s as useAvatarCopy };
252
+ export { i as AVATAR_MINUTES_HOW_COUNTED, f as formatAvatarCount, d as formatAvatarMinutesDate, s as formatAvatarRetryTime, m as useAvatarCopy };
@@ -100,7 +100,9 @@ import { AssistantModeConfig } from './conversation';
100
100
  * runtime graph is a handful of modules: data tables and the pure mode-id and
101
101
  * mode-capability checks, plus `i18n.tsx` for
102
102
  * `assistantTranslationKey` and the I18n and product providers that module
103
- * brings. Its one package is `react`, which every app already has, and it
103
+ * brings, and `avatar/useAvatarCopy.ts` for the one table of "How avatar
104
+ * credits are counted" (BOFF-9446), which needs nothing more. Its one package
105
+ * is `react`, which every app already has, and it
104
106
  * loads under plain `bun`; the test walks the graph so no UI module or other
105
107
  * package joins it unnoticed.
106
108
  *
@@ -235,6 +237,7 @@ export declare const ASSISTANT_I18N_LITERAL_COPY: {
235
237
  readonly 'avatar.askNeedsAvatar': "Turn on the avatar to send your question. You can type it now.";
236
238
  readonly 'avatar.askNeedsVoice': "You can still turn the avatar on, but you ask it questions in a voice conversation.";
237
239
  readonly 'avatar.askNoFiles': "To attach files, use Chat.";
240
+ readonly 'avatar.askNoMinutes': "You can send your question when avatar credits are available.";
238
241
  readonly 'avatar.askNotHeard': "This answer wasn't read aloud: the avatar may still have been finishing the last answer.";
239
242
  readonly 'avatar.askNotSpoken': "The avatar wasn't available, so this answer wasn't read aloud.";
240
243
  readonly 'avatar.askNothing': "You can't ask the avatar anything in this conversation.";
@@ -259,6 +262,23 @@ export declare const ASSISTANT_I18N_LITERAL_COPY: {
259
262
  readonly 'avatar.consentCancel': "Not now";
260
263
  readonly 'avatar.consentConfirm': "Turn on the avatar";
261
264
  readonly 'avatar.handsFreeOff': "Hands-free interrupt is off, so the avatar's voice can't interrupt itself. It stays off after you turn the avatar off; turn it back on in More options. You can still tap to interrupt.";
265
+ readonly 'avatar.minutes.askAdmin': "Ask your billing administrator about a plan with more avatar credits.";
266
+ readonly 'avatar.minutes.assignPlan': "Assign your new plan to this workspace in Billing to use its credits.";
267
+ readonly 'avatar.minutes.checkAgain': "Check again";
268
+ readonly 'avatar.minutes.checking': "Checking…";
269
+ readonly 'avatar.minutes.checkingPlan': "Checking your plan…";
270
+ readonly 'avatar.minutes.ended': "The avatar stopped because this workspace’s avatar credits ran out. Continuing with voice only.";
271
+ readonly 'avatar.minutes.endedNoVoice': "The avatar stopped because this workspace’s avatar credits ran out.";
272
+ readonly 'avatar.minutes.freePerMonth': "Free avatar credits each month: {{count}}";
273
+ readonly 'avatar.minutes.freePerPerson': "Free avatar credits per person each month: {{count}}";
274
+ readonly 'avatar.minutes.left': "Avatar credits left: {{count}}";
275
+ readonly 'avatar.minutes.low': "Avatar credits are running low. The avatar stops when they run out.";
276
+ readonly 'avatar.minutes.noFree': "No free avatar credits are included.";
277
+ readonly 'avatar.minutes.planPerMonth': "Avatar credits from paid plans each month: {{count}}";
278
+ readonly 'avatar.minutes.resetsOn': "Avatar credits reset on {{date}} (UTC).";
279
+ readonly 'avatar.minutes.seePlans': "See plans";
280
+ readonly 'avatar.minutes.sessionLeft': "Minutes left in this session: about {{count}}";
281
+ readonly 'avatar.minutes.shared': "Everyone in this workspace shares these credits.";
262
282
  readonly 'avatar.problemAlreadyRunning': "An avatar is already running for you.";
263
283
  readonly 'avatar.problemBudgetExhausted': "Avatar time is used up for now.";
264
284
  readonly 'avatar.problemBudgetExhaustedEveryone': "Avatar time is used up for everyone for now.";
@@ -267,6 +287,17 @@ export declare const ASSISTANT_I18N_LITERAL_COPY: {
267
287
  readonly 'avatar.problemCapacity': "Avatars are busy right now. Try again in a moment.";
268
288
  readonly 'avatar.problemCapacityTimed': "Avatars are busy right now.";
269
289
  readonly 'avatar.problemConnection': "The connection to the avatar was lost.";
290
+ readonly 'avatar.problemMinutesExhausted': "No avatar credits left in this workspace.";
291
+ readonly 'avatar.problemMinutesExhaustedUntil': "No avatar credits left in this workspace. They come back on {{date}} (UTC).";
292
+ readonly 'avatar.problemMinutesFreeCeiling': "You’ve used your free avatar credits for this month.";
293
+ readonly 'avatar.problemMinutesInUse': "Avatar credits are in use in another session. Try again when it ends.";
294
+ readonly 'avatar.problemMinutesInUseSelf': "Your avatar is still running on another device. End it there, or try again when it ends.";
295
+ readonly 'avatar.problemMinutesInUseSelfTimed': "Your avatar is still running on another device. End it there.";
296
+ readonly 'avatar.problemMinutesInUseTimed': "Avatar credits are in use in another session.";
297
+ readonly 'avatar.problemMinutesNotIncluded': "Avatar credits aren’t included for this workspace.";
298
+ readonly 'avatar.problemMinutesUnavailable': "Avatar credits can’t be checked right now. Try again in a moment.";
299
+ readonly 'avatar.problemMinutesUnavailableTimed': "Avatar credits can’t be checked right now.";
300
+ readonly 'avatar.problemMinutesZeroSessionLimit': "You’ve started too many short avatar sessions today.";
270
301
  readonly 'avatar.problemNeedsHostedVoice': "The avatar needs one of the assistant’s own voices. Choose one in the voice settings.";
271
302
  readonly 'avatar.problemNotReady': "This avatar isn’t ready yet. Try another one.";
272
303
  readonly 'avatar.problemPaused': "Avatars are paused for now.";
@@ -7,11 +7,12 @@ import { INCOMPLETE_TURN_NOTICES as a } from "./streamBudget.js";
7
7
  import { STAGE_COPY as o, THINKING_PHRASES as s } from "./stageCopy.js";
8
8
  import { VOICE_MUTED_LABEL as c, VOICE_THINKING_LABEL as l } from "./voice/voiceStatus.js";
9
9
  import { ATTACHMENT_SOURCES as u } from "./attachmentSources.js";
10
- import { assistantProductCopyEntries as d } from "./productCopy.js";
11
- import { WRITE_PROPOSAL_ERROR_COPY as f } from "./writeProposalErrorCopy.js";
12
- import { WRITE_PROPOSAL_VOICE_COPY as p } from "./writeProposalVoice.js";
10
+ import { AVATAR_MINUTES_HOW_COUNTED as d } from "./avatar/useAvatarCopy.js";
11
+ import { assistantProductCopyEntries as f } from "./productCopy.js";
12
+ import { WRITE_PROPOSAL_ERROR_COPY as p } from "./writeProposalErrorCopy.js";
13
+ import { WRITE_PROPOSAL_VOICE_COPY as m } from "./writeProposalVoice.js";
13
14
  //#region src/shared/assistant/i18nManifest.ts
14
- var m = {
15
+ var h = {
15
16
  "apiCalls.consentBody": "In Take action mode the assistant can read AND change your data — creating records, logging activity, updating details, and more, on your behalf.",
16
17
  "apiCalls.consentBody2": "It will describe what it is about to do and ask you to confirm before making a change. Only continue if you want the assistant to take actions for you.",
17
18
  "apiCalls.consentCancel": "Stay in Ask",
@@ -129,6 +130,7 @@ var m = {
129
130
  "avatar.askNeedsAvatar": "Turn on the avatar to send your question. You can type it now.",
130
131
  "avatar.askNeedsVoice": "You can still turn the avatar on, but you ask it questions in a voice conversation.",
131
132
  "avatar.askNoFiles": "To attach files, use Chat.",
133
+ "avatar.askNoMinutes": "You can send your question when avatar credits are available.",
132
134
  "avatar.askNotHeard": "This answer wasn't read aloud: the avatar may still have been finishing the last answer.",
133
135
  "avatar.askNotSpoken": "The avatar wasn't available, so this answer wasn't read aloud.",
134
136
  "avatar.askNothing": "You can't ask the avatar anything in this conversation.",
@@ -153,6 +155,23 @@ var m = {
153
155
  "avatar.consentCancel": "Not now",
154
156
  "avatar.consentConfirm": "Turn on the avatar",
155
157
  "avatar.handsFreeOff": "Hands-free interrupt is off, so the avatar's voice can't interrupt itself. It stays off after you turn the avatar off; turn it back on in More options. You can still tap to interrupt.",
158
+ "avatar.minutes.askAdmin": "Ask your billing administrator about a plan with more avatar credits.",
159
+ "avatar.minutes.assignPlan": "Assign your new plan to this workspace in Billing to use its credits.",
160
+ "avatar.minutes.checkAgain": "Check again",
161
+ "avatar.minutes.checking": "Checking…",
162
+ "avatar.minutes.checkingPlan": "Checking your plan…",
163
+ "avatar.minutes.ended": "The avatar stopped because this workspace’s avatar credits ran out. Continuing with voice only.",
164
+ "avatar.minutes.endedNoVoice": "The avatar stopped because this workspace’s avatar credits ran out.",
165
+ "avatar.minutes.freePerMonth": "Free avatar credits each month: {{count}}",
166
+ "avatar.minutes.freePerPerson": "Free avatar credits per person each month: {{count}}",
167
+ "avatar.minutes.left": "Avatar credits left: {{count}}",
168
+ "avatar.minutes.low": "Avatar credits are running low. The avatar stops when they run out.",
169
+ "avatar.minutes.noFree": "No free avatar credits are included.",
170
+ "avatar.minutes.planPerMonth": "Avatar credits from paid plans each month: {{count}}",
171
+ "avatar.minutes.resetsOn": "Avatar credits reset on {{date}} (UTC).",
172
+ "avatar.minutes.seePlans": "See plans",
173
+ "avatar.minutes.sessionLeft": "Minutes left in this session: about {{count}}",
174
+ "avatar.minutes.shared": "Everyone in this workspace shares these credits.",
156
175
  "avatar.problemAlreadyRunning": "An avatar is already running for you.",
157
176
  "avatar.problemBudgetExhausted": "Avatar time is used up for now.",
158
177
  "avatar.problemBudgetExhaustedEveryone": "Avatar time is used up for everyone for now.",
@@ -161,6 +180,17 @@ var m = {
161
180
  "avatar.problemCapacity": "Avatars are busy right now. Try again in a moment.",
162
181
  "avatar.problemCapacityTimed": "Avatars are busy right now.",
163
182
  "avatar.problemConnection": "The connection to the avatar was lost.",
183
+ "avatar.problemMinutesExhausted": "No avatar credits left in this workspace.",
184
+ "avatar.problemMinutesExhaustedUntil": "No avatar credits left in this workspace. They come back on {{date}} (UTC).",
185
+ "avatar.problemMinutesFreeCeiling": "You’ve used your free avatar credits for this month.",
186
+ "avatar.problemMinutesInUse": "Avatar credits are in use in another session. Try again when it ends.",
187
+ "avatar.problemMinutesInUseSelf": "Your avatar is still running on another device. End it there, or try again when it ends.",
188
+ "avatar.problemMinutesInUseSelfTimed": "Your avatar is still running on another device. End it there.",
189
+ "avatar.problemMinutesInUseTimed": "Avatar credits are in use in another session.",
190
+ "avatar.problemMinutesNotIncluded": "Avatar credits aren’t included for this workspace.",
191
+ "avatar.problemMinutesUnavailable": "Avatar credits can’t be checked right now. Try again in a moment.",
192
+ "avatar.problemMinutesUnavailableTimed": "Avatar credits can’t be checked right now.",
193
+ "avatar.problemMinutesZeroSessionLimit": "You’ve started too many short avatar sessions today.",
164
194
  "avatar.problemNeedsHostedVoice": "The avatar needs one of the assistant’s own voices. Choose one in the voice settings.",
165
195
  "avatar.problemNotReady": "This avatar isn’t ready yet. Try another one.",
166
196
  "avatar.problemPaused": "Avatars are paused for now.",
@@ -542,34 +572,35 @@ var m = {
542
572
  "writeProposal.values": "Values",
543
573
  "writeProposal.variablesOmitted": "{{count}} more values not shown",
544
574
  "writeProposal.waitForTurn": "You can confirm once the assistant finishes replying."
545
- }, h = (e) => Object.fromEntries(e.map((e) => [e.key, e.fallback])), g = Object.freeze({
546
- ...h(Object.values(o)),
547
- ...h(s),
548
- ...h([l, c]),
549
- ...h(a),
550
- ...h(Object.values(i)),
551
- ...h(Object.values(r)),
552
- ...h(Object.values(f)),
553
- ...h(Object.values(p)),
575
+ }, g = (e) => Object.fromEntries(e.map((e) => [e.key, e.fallback])), _ = Object.freeze({
576
+ ...g(Object.values(o)),
577
+ ...g(s),
578
+ ...g([l, c]),
579
+ ...g(a),
580
+ ...g(Object.values(i)),
581
+ ...g(Object.values(r)),
582
+ ...g(Object.values(p)),
583
+ ...g(Object.values(m)),
584
+ ...g(Object.values(d)),
554
585
  ...Object.fromEntries(u.map((e) => [e.labelKey, e.labelFallback]))
555
- }), _ = Object.freeze({
586
+ }), v = Object.freeze({
556
587
  "attachments.retrying": "Retrying {{attempt}} of {{max}}",
557
588
  "attachments.waking": "File service is starting up — retry {{attempt}} of {{max}}",
558
589
  "outcome.usageDailyLimitReachedNoTime": "You've reached today's AI assistant limit. Try again tomorrow."
559
- }), v = Object.freeze({
560
- ...m,
561
- ...g,
562
- ..._
590
+ }), y = Object.freeze({
591
+ ...h,
592
+ ..._,
593
+ ...v
563
594
  });
564
- function y(e) {
595
+ function b(e) {
565
596
  let r = e.map((e) => e.id).filter((e) => !n(e));
566
597
  if (r.length > 0) throw Error(`assistantModeI18nCopy: malformed mode id ${r.map((e) => JSON.stringify(e)).join(", ")}. Declare product modes with declareAssistantMode().`);
567
598
  return Object.fromEntries(e.map((e) => [`mode.${e.id}`, e.label || t(e)]));
568
599
  }
569
- function b(t) {
600
+ function x(t) {
570
601
  let n = {
571
- ...v,
572
- ...y(t.modes)
602
+ ...y,
603
+ ...b(t.modes)
573
604
  }, r = Object.entries(t.overrides ?? {}), i = r.filter(([e]) => !Object.hasOwn(n, e)).map(([e]) => e);
574
605
  if (i.length > 0) throw Error(`assistantI18nSeedCopy: no shared assistant key ${i.join(", ")} to override. Check the spelling, or that the key is a mode this product offers; a key fe-libs no longer requests belongs with the product's own keys.`);
575
606
  let a = r.filter(([, e]) => e.trim() === "").map(([e]) => e);
@@ -577,7 +608,7 @@ function b(t) {
577
608
  let o = {
578
609
  ...n,
579
610
  ...Object.fromEntries(r),
580
- ...d({
611
+ ...f({
581
612
  modes: t.modes,
582
613
  pageLabels: t.pageLabels
583
614
  })
@@ -585,4 +616,4 @@ function b(t) {
585
616
  return Object.fromEntries(Object.entries(o).map(([n, r]) => [e(t.namespace, n), r]));
586
617
  }
587
618
  //#endregion
588
- export { g as ASSISTANT_I18N_COMPUTED_COPY, m as ASSISTANT_I18N_LITERAL_COPY, v as ASSISTANT_I18N_MANIFEST, _ as ASSISTANT_I18N_SEED_COMPAT_COPY, b as assistantI18nSeedCopy, y as assistantModeI18nCopy };
619
+ export { _ as ASSISTANT_I18N_COMPUTED_COPY, h as ASSISTANT_I18N_LITERAL_COPY, y as ASSISTANT_I18N_MANIFEST, v as ASSISTANT_I18N_SEED_COMPAT_COPY, x as assistantI18nSeedCopy, b as assistantModeI18nCopy };
@@ -345,6 +345,8 @@ export * from './avatar/live/LiveAvatarTabView';
345
345
  export * from './avatar/live/avatarFaces';
346
346
  export * from './avatar/live/AvatarFace';
347
347
  export * from './avatar/live/liveAvatarStatus';
348
+ export * from './avatar/live/liveAvatarMinutesView';
349
+ export * from './avatar/live/LiveAvatarMinutes';
348
350
  export * from './avatar/live/createLiveAvatarSeam';
349
351
  export * from './avatar/live/avatarVoice';
350
352
  export * from './promptDelivery';
@@ -31,10 +31,10 @@ import { resolveAssistantLanguage as $e } from "../voice/languages.js";
31
31
  import { resolveLanguagePair as et } from "../voice/languagePair.js";
32
32
  import { INITIAL_TRANSCRIPT_AUTO_OPEN as tt, isAwaitingOwnConversation as nt, maySendSpokenTurn as C, planTranscriptAutoOpen as rt, planVoiceSessionBinding as it, planVoiceSessionStart as at, planVoiceSpokenEntry as ot, voiceBindingAtStart as st, voiceBindingConversationId as ct } from "../voice/voiceReopen.js";
33
33
  import { countInlineImageCandidates as lt, countInlinedImageParts as ut, largestInlineCharsPerFile as dt, resolveAssistantInlineLimits as ft, toMessageAttachment as pt } from "../attachments.js";
34
- import { WRITE_PROPOSAL_VOICE_COPY as mt, isVoiceProposalBound as ht, voiceProposalChip as gt, voiceProposalChipText as _t, voiceReadbacksDue as vt, voiceWaitingProposals as yt, writeProposalVoiceLine as bt, writeProposalVoiceReadback as xt } from "../writeProposalVoice.js";
35
- import { createAdminModeAccessGate as St, resolveAssistantSendModeAfterAccess as Ct } from "../adminModeSendGate.js";
36
- import { buildBoundedAttachmentTurn as wt } from "../attachmentTurn.js";
37
- import { useAvatarCopy as Tt } from "../avatar/useAvatarCopy.js";
34
+ import { useAvatarCopy as mt } from "../avatar/useAvatarCopy.js";
35
+ import { WRITE_PROPOSAL_VOICE_COPY as ht, isVoiceProposalBound as gt, voiceProposalChip as _t, voiceProposalChipText as vt, voiceReadbacksDue as yt, voiceWaitingProposals as bt, writeProposalVoiceLine as xt, writeProposalVoiceReadback as St } from "../writeProposalVoice.js";
36
+ import { createAdminModeAccessGate as Ct, resolveAssistantSendModeAfterAccess as wt } from "../adminModeSendGate.js";
37
+ import { buildBoundedAttachmentTurn as Tt } from "../attachmentTurn.js";
38
38
  import { attachmentUrlIsStale as Et, refreshStaleAttachmentUrls as Dt } from "../attachmentCommit.js";
39
39
  import { VOICE_COMMIT_BUDGET_MS as Ot, settleAndCommitSpokenTurn as kt } from "../voiceCommit.js";
40
40
  import { getOrganizationId as At, getWorkspaceId as w } from "../context.js";
@@ -126,7 +126,7 @@ function M() {
126
126
  var Ei = () => () => {}, Di = () => "off", Oi = ({ visible: Oi = !0, writeAccess: ki = "unknown" }) => {
127
127
  let N = a(), { product: P, modes: F, gatherPageContext: Ai, buildPrompt: ji, reportTurnTimings: Mi, reportTokenUsage: Ni, localUiCommands: Pi } = i(), I = De(i().writeApproval), Fi = E((e, t) => {
128
128
  let n = _.getState(), r = t ?? n.sandboxesByMode[e]?.sandboxId ?? null;
129
- return ht({
129
+ return gt({
130
130
  mode: e,
131
131
  productWriteApproval: I,
132
132
  capabilities: r ? n.sandboxWriteCapabilities[r] : null
@@ -158,7 +158,7 @@ var Ei = () => () => {}, Di = () => "off", Oi = ({ visible: Oi = !0, writeAccess
158
158
  let e = window.setTimeout(() => Ma(Na), Ci);
159
159
  return () => window.clearTimeout(e);
160
160
  }, [Na]);
161
- let { canUseApiCalls: Pa, loading: Fa } = _r(w(), R), Ia = k(St({
161
+ let { canUseApiCalls: Pa, loading: Fa } = _r(w(), R), Ia = k(Ct({
162
162
  loading: Fa,
163
163
  canUseAdminModes: Pa
164
164
  })).current, La = k(F);
@@ -223,7 +223,7 @@ var Ei = () => () => {}, Di = () => "off", Oi = ({ visible: Oi = !0, writeAccess
223
223
  }, [W]), fo = k({}), q = k(Sn()), J = k(new wr()), po = k(/* @__PURE__ */ new Set()), mo = O(() => F.find((e) => e.id === V), [F, V]), ho = _((e) => {
224
224
  let t = e.sandboxesByMode[V]?.sandboxId;
225
225
  return t ? e.sandboxWriteCapabilities[t] : void 0;
226
- }), go = mo !== void 0 && ht({
226
+ }), go = mo !== void 0 && gt({
227
227
  mode: V,
228
228
  productWriteApproval: I,
229
229
  capabilities: ho
@@ -1601,7 +1601,7 @@ var Ei = () => () => {}, Di = () => "off", Oi = ({ visible: Oi = !0, writeAccess
1601
1601
  conversationId: v,
1602
1602
  writeReply: ce === "proposal-v1" ? n.writeReplyBound?.writeReply ?? null : null,
1603
1603
  ...ue ? { uiCatalogue: ue } : {}
1604
- }, { prompt: me, parts: he } = wt({
1604
+ }, { prompt: me, parts: he } = Tt({
1605
1605
  content: ie ? `${ie}${ae}` : ae,
1606
1606
  attachments: t,
1607
1607
  limits: ft(le),
@@ -1756,7 +1756,7 @@ var Ei = () => () => {}, Di = () => "off", Oi = ({ visible: Oi = !0, writeAccess
1756
1756
  code: "UNAUTHENTICATED",
1757
1757
  authFailure: "WORKSPACE_CONTEXT_MISSING"
1758
1758
  } });
1759
- let e = await Ct(g, () => La.current.find((e) => e.id === g), Ia);
1759
+ let e = await wt(g, () => La.current.find((e) => e.id === g), Ia);
1760
1760
  le = e.config, fe = e.capabilities;
1761
1761
  let t = le, n = Ae();
1762
1762
  if (n.abandon) {
@@ -2052,7 +2052,7 @@ var Ei = () => () => {}, Di = () => "off", Oi = ({ visible: Oi = !0, writeAccess
2052
2052
  D(() => {
2053
2053
  ws.current = rc;
2054
2054
  }, [rc]);
2055
- let ic = $.active, ac = O(() => yt(Object.values(xa), B, Sa), [
2055
+ let ic = $.active, ac = O(() => bt(Object.values(xa), B, Sa), [
2056
2056
  xa,
2057
2057
  B,
2058
2058
  Sa
@@ -2073,7 +2073,7 @@ var Ei = () => () => {}, Di = () => "off", Oi = ({ visible: Oi = !0, writeAccess
2073
2073
  ac,
2074
2074
  ba,
2075
2075
  cc
2076
- ]), uc = ic ? gt(ac) : null, dc = oc ? xa[oc] : void 0, fc = ic && dc && dc.session.conversationId === B ? {
2076
+ ]), uc = ic ? _t(ac) : null, dc = oc ? xa[oc] : void 0, fc = ic && dc && dc.session.conversationId === B ? {
2077
2077
  content: /* @__PURE__ */ j(kn, {
2078
2078
  refs: [{
2079
2079
  proposalId: dc.view.proposalId,
@@ -2089,7 +2089,7 @@ var Ei = () => () => {}, Di = () => "off", Oi = ({ visible: Oi = !0, writeAccess
2089
2089
  onReview: cc,
2090
2090
  retiredSandboxes: Sa
2091
2091
  }),
2092
- closeLabel: bt(N, mt.trayClose),
2092
+ closeLabel: xt(N, ht.trayClose),
2093
2093
  onClose: () => sc(null)
2094
2094
  } : null, pc = Ca.asking, mc = xs?.asking === !0, hc = Ss?.asking === !0, gc = er(), _c = $.holdListening;
2095
2095
  r("assistant.transcript", O(() => ic || K ? null : { run: () => to ? { status: "already" } : (ao(!0), { status: "done" }) }, [
@@ -2121,15 +2121,15 @@ var Ei = () => () => {}, Di = () => "off", Oi = ({ visible: Oi = !0, writeAccess
2121
2121
  vc.current = new Set(Object.keys(xa));
2122
2122
  return;
2123
2123
  }
2124
- for (let { view: t, session: n } of vt(ac, e)) {
2124
+ for (let { view: t, session: n } of yt(ac, e)) {
2125
2125
  e.add(t.proposalId);
2126
2126
  let r = ba[n.sandboxId];
2127
- if (!ht({
2127
+ if (!gt({
2128
2128
  mode: "api-calls",
2129
2129
  productWriteApproval: I,
2130
2130
  capabilities: r
2131
2131
  })) continue;
2132
- let i = xt(t, Tn(t, r).offerConfirm, N), a = t.proposalId;
2132
+ let i = St(t, Tn(t, r).offerConfirm, N), a = t.proposalId;
2133
2133
  yc({
2134
2134
  id: `write-readback:${a}`,
2135
2135
  text: i.text,
@@ -2221,7 +2221,7 @@ var Ei = () => () => {}, Di = () => "off", Oi = ({ visible: Oi = !0, writeAccess
2221
2221
  busy: _a || Co !== null || Oa || Qo,
2222
2222
  committing: kc.committing,
2223
2223
  dictating: kc.dictating
2224
- }), Mc = Tt(), [Nc, Pc] = A(null);
2224
+ }), Mc = mt(), [Nc, Pc] = A(null);
2225
2225
  D(() => {
2226
2226
  Nc !== null && (!K || Nc.conversationId !== B) && Pc(null);
2227
2227
  }, [
@@ -2587,7 +2587,7 @@ var Ei = () => () => {}, Di = () => "off", Oi = ({ visible: Oi = !0, writeAccess
2587
2587
  onToggleMute: $.toggleMute,
2588
2588
  onEnd: Oc,
2589
2589
  proposalChip: uc ? {
2590
- label: _t(uc, N),
2590
+ label: vt(uc, N),
2591
2591
  onOpen: lc
2592
2592
  } : null,
2593
2593
  proposalTray: fc,
@@ -46,8 +46,11 @@ export type AssistantNavigateResult =
46
46
  * navigator that does not say is treated as the assistant's.
47
47
  */
48
48
  export type AssistantNavigateInitiator = NavigationInitiator;
49
- /** Which assistant surface the person tapped; only ever used for counting. */
50
- export type AssistantNavigateSurface = 'offer' | 'reply-link' | 'connect-source';
49
+ /**
50
+ * Which assistant surface the person tapped; only ever used for counting. `avatar-minutes` (★ BOFF-9446): the Avatar tab's "See plans",
51
+ * for avatar credits.
52
+ */
53
+ export type AssistantNavigateSurface = 'offer' | 'reply-link' | 'connect-source' | 'avatar-minutes';
51
54
  export interface AssistantNavigateContext {
52
55
  surface?: AssistantNavigateSurface;
53
56
  }
@@ -1,5 +1,6 @@
1
1
  import { ComponentType, FC, ReactNode, RefObject } from 'react';
2
2
  import { SpeechEngine } from '../speech/types';
3
+ import { AssistantNavigate } from './AssistantMarkdownRenderer';
3
4
  import { VoiceView } from './voice/AssistantVoiceSession';
4
5
  export interface LiveAvatarStageProps {
5
6
  /** Full-screen (true) or the in-panel stage (false). */
@@ -33,15 +34,16 @@ export interface AssistantLiveAvatarSeam {
33
34
  /** Whether the Avatar tab may be offered, as a store read with `useSyncExternalStore`. STABLE. */
34
35
  export interface LiveAvatarTabOfferedStore {
35
36
  subscribe: (onChange: () => void) => () => void;
36
- /** This browser can show the avatar (desktop Chrome first), and it was not refused on this page. */
37
+ /** This browser can show the avatar (any browser or phone with what it needs), and it was not refused on this page. */
37
38
  getSnapshot: () => boolean;
38
39
  }
39
40
  /**
40
41
  * ★ BOFF-9360: whether the avatar can take the answer to a typed question now (`liveAvatarAskState` maps the
41
42
  * controller's snapshot): `ready`; `sound-blocked` (on, its sound blocked until a tap); `ending` (stopping, or near its
42
- * time limit); `starting`; `locked` (off until the time a refusal named); `off`.
43
+ * time limit); `starting`; `locked` (off until the time a refusal named); `no-minutes` (★ BOFF-9446: off, and Turn on
44
+ * cannot start it for avatar credits: none left for this person); `off`.
43
45
  */
44
- export type LiveAvatarAskState = 'off' | 'locked' | 'starting' | 'ready' | 'sound-blocked' | 'ending';
46
+ export type LiveAvatarAskState = 'off' | 'locked' | 'no-minutes' | 'starting' | 'ready' | 'sound-blocked' | 'ending';
45
47
  /** `LiveAvatarAskState` as a store read with `useSyncExternalStore`. STABLE. */
46
48
  export interface LiveAvatarAskStore {
47
49
  subscribe: (onChange: () => void) => () => void;
@@ -118,6 +120,12 @@ export interface LiveAvatarTabProps {
118
120
  * `voice.impossible` alone ("…you ask it questions in a voice conversation").
119
121
  */
120
122
  readonly askLine?: LiveAvatarTabAskLine;
123
+ /**
124
+ * ★ BOFF-9446: the panel's navigator (the host's `onNavigate`, behind the leave-page guard): the tab's "See plans" opens the app's
125
+ * plans page with it, as the person's own navigation, and never changes the page's location itself. Absent or null: no navigation,
126
+ * so nothing offers to open billing.
127
+ */
128
+ readonly navigate?: AssistantNavigate | null;
121
129
  }
122
130
  /** The ids the tab row and the two tab panels share (`AIAssistantPanel` makes them). */
123
131
  export interface LiveAvatarTabIds {