@runtypelabs/persona 3.34.0 → 3.35.0

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 (186) hide show
  1. package/README.md +11 -11
  2. package/dist/animations/glyph-cycle.d.cts +1 -1
  3. package/dist/animations/glyph-cycle.d.ts +1 -1
  4. package/dist/animations/{types-CthJFfNx.d.cts → types-DgYsuwXL.d.cts} +15 -14
  5. package/dist/animations/{types-CthJFfNx.d.ts → types-DgYsuwXL.d.ts} +15 -14
  6. package/dist/animations/wipe.d.cts +1 -1
  7. package/dist/animations/wipe.d.ts +1 -1
  8. package/dist/codegen.cjs +11 -11
  9. package/dist/codegen.js +10 -10
  10. package/dist/index.cjs +46 -44
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.cts +514 -158
  13. package/dist/index.d.ts +514 -158
  14. package/dist/index.global.js +41 -89
  15. package/dist/index.global.js.map +1 -1
  16. package/dist/index.js +46 -44
  17. package/dist/index.js.map +1 -1
  18. package/dist/install.global.js +1 -1
  19. package/dist/install.global.js.map +1 -1
  20. package/dist/launcher.global.js +2 -2
  21. package/dist/launcher.global.js.map +1 -1
  22. package/dist/markdown-parsers.js +53 -0
  23. package/dist/plugin-kit.d.cts +4 -4
  24. package/dist/plugin-kit.d.ts +4 -4
  25. package/dist/runtype-tts.js +1 -0
  26. package/dist/smart-dom-reader.cjs +1 -1
  27. package/dist/smart-dom-reader.d.cts +352 -103
  28. package/dist/smart-dom-reader.d.ts +352 -103
  29. package/dist/smart-dom-reader.js +1 -1
  30. package/dist/theme-editor-preview.cjs +203 -0
  31. package/dist/theme-editor-preview.d.cts +5748 -0
  32. package/dist/theme-editor-preview.d.ts +5748 -0
  33. package/dist/theme-editor-preview.js +203 -0
  34. package/dist/theme-editor.cjs +14 -131
  35. package/dist/theme-editor.d.cts +362 -574
  36. package/dist/theme-editor.d.ts +362 -574
  37. package/dist/theme-editor.js +14 -131
  38. package/dist/theme-reference.cjs +1 -1
  39. package/dist/theme-reference.d.cts +1 -1
  40. package/dist/theme-reference.d.ts +1 -1
  41. package/dist/theme-reference.js +1 -1
  42. package/dist/voice-worklet-player.cjs +80 -0
  43. package/dist/voice-worklet-player.d.cts +215 -0
  44. package/dist/voice-worklet-player.d.ts +215 -0
  45. package/dist/voice-worklet-player.js +80 -0
  46. package/dist/widget.css +54 -36
  47. package/package.json +16 -2
  48. package/src/animations/glyph-cycle.ts +11 -14
  49. package/src/animations/wipe.ts +1 -1
  50. package/src/ask-user-question-tool.test.ts +1 -1
  51. package/src/ask-user-question-tool.ts +6 -8
  52. package/src/client.test.ts +67 -12
  53. package/src/client.ts +40 -32
  54. package/src/codegen.test.ts +1 -1
  55. package/src/codegen.ts +1 -1
  56. package/src/components/approval-bubble.test.ts +1 -1
  57. package/src/components/approval-bubble.ts +3 -3
  58. package/src/components/artifact-pane.ts +3 -3
  59. package/src/components/ask-user-question-bubble.test.ts +14 -14
  60. package/src/components/ask-user-question-bubble.ts +10 -10
  61. package/src/components/composer-builder.ts +17 -16
  62. package/src/components/composer-parts.test.ts +54 -0
  63. package/src/components/composer-parts.ts +95 -126
  64. package/src/components/demo-carousel.ts +1 -1
  65. package/src/components/event-stream-view.test.ts +7 -7
  66. package/src/components/event-stream-view.ts +5 -5
  67. package/src/components/header-builder.ts +33 -27
  68. package/src/components/header-layouts.ts +1 -1
  69. package/src/components/header-parts.test.ts +90 -0
  70. package/src/components/header-parts.ts +62 -93
  71. package/src/components/launcher.test.ts +11 -0
  72. package/src/components/launcher.ts +1 -1
  73. package/src/components/message-bubble.test.ts +74 -2
  74. package/src/components/message-bubble.ts +25 -11
  75. package/src/components/messages.ts +2 -2
  76. package/src/components/panel.test.ts +1 -1
  77. package/src/components/panel.ts +92 -97
  78. package/src/components/pill-composer-builder.ts +28 -24
  79. package/src/components/reasoning-bubble.ts +2 -2
  80. package/src/components/suggestions.ts +1 -1
  81. package/src/components/tool-bubble.ts +1 -1
  82. package/src/components/widget-view.test.ts +181 -0
  83. package/src/components/widget-view.ts +234 -0
  84. package/src/defaults.ts +54 -51
  85. package/src/generated/runtype-openapi-contract.ts +5 -0
  86. package/src/index-core.ts +30 -4
  87. package/src/index-global.ts +73 -6
  88. package/src/index.ts +7 -1
  89. package/src/install.test.ts +11 -11
  90. package/src/install.ts +28 -18
  91. package/src/launcher-global.ts +25 -14
  92. package/src/markdown-parsers-eager.ts +20 -0
  93. package/src/markdown-parsers-entry.ts +4 -0
  94. package/src/markdown-parsers-loader.ts +49 -0
  95. package/src/plugin-kit.test.ts +1 -1
  96. package/src/plugin-kit.ts +7 -7
  97. package/src/plugins/types.ts +5 -5
  98. package/src/postprocessors.ts +27 -14
  99. package/src/runtime/host-layout.test.ts +1 -1
  100. package/src/runtime/host-layout.ts +4 -5
  101. package/src/runtime/init.ts +2 -2
  102. package/src/session.test.ts +5 -6
  103. package/src/session.ts +333 -227
  104. package/src/session.voice.test.ts +146 -0
  105. package/src/session.webmcp.test.ts +24 -25
  106. package/src/smart-dom-reader.ts +3 -4
  107. package/src/styles/widget.css +54 -36
  108. package/src/suggest-replies-tool.test.ts +3 -3
  109. package/src/suggest-replies-tool.ts +5 -5
  110. package/src/testing/index.ts +1 -1
  111. package/src/theme-editor/color-utils.ts +2 -2
  112. package/src/theme-editor/index.ts +4 -13
  113. package/src/theme-editor/presets.ts +1 -1
  114. package/src/theme-editor/preview-utils.ts +22 -20
  115. package/src/theme-editor/preview.ts +4 -1
  116. package/src/theme-editor/role-mappings.ts +3 -3
  117. package/src/theme-editor/sections.ts +21 -20
  118. package/src/theme-editor/types.ts +1 -1
  119. package/src/theme-editor/webmcp/index.ts +1 -1
  120. package/src/theme-editor/webmcp/summary.ts +1 -2
  121. package/src/theme-editor/webmcp/tools.ts +30 -30
  122. package/src/theme-editor/webmcp/types.ts +2 -2
  123. package/src/theme-editor-preview.ts +10 -0
  124. package/src/theme-reference.ts +26 -26
  125. package/src/types.ts +368 -99
  126. package/src/ui.approval-plugin.test.ts +3 -3
  127. package/src/ui.ask-user-question-plugin.test.ts +9 -9
  128. package/src/ui.component-directive.test.ts +4 -4
  129. package/src/ui.composer-bar.test.ts +10 -11
  130. package/src/ui.composer-keyboard.test.ts +5 -5
  131. package/src/ui.header-icon-color.test.ts +59 -0
  132. package/src/ui.scroll.test.ts +5 -6
  133. package/src/ui.stop-button.test.ts +3 -4
  134. package/src/ui.suggest-replies.test.ts +2 -2
  135. package/src/ui.ts +292 -131
  136. package/src/utils/buttons.ts +1 -1
  137. package/src/utils/code-generators.ts +1 -1
  138. package/src/utils/composer-history.ts +3 -3
  139. package/src/utils/dom-context.test.ts +2 -2
  140. package/src/utils/dom-context.ts +7 -7
  141. package/src/utils/dom.test.ts +67 -0
  142. package/src/utils/dom.ts +75 -0
  143. package/src/utils/dropdown.ts +2 -2
  144. package/src/utils/event-stream-buffer.test.ts +2 -2
  145. package/src/utils/message-fingerprint.test.ts +9 -0
  146. package/src/utils/message-fingerprint.ts +5 -0
  147. package/src/utils/morph.ts +1 -1
  148. package/src/utils/sanitize.ts +32 -16
  149. package/src/utils/sequence-buffer.test.ts +8 -8
  150. package/src/utils/sequence-buffer.ts +4 -4
  151. package/src/utils/smart-dom-adapter.test.ts +1 -1
  152. package/src/utils/smart-dom-adapter.ts +4 -5
  153. package/src/utils/speech-text.test.ts +122 -0
  154. package/src/utils/speech-text.ts +101 -0
  155. package/src/utils/stream-animation.test.ts +3 -3
  156. package/src/utils/stream-animation.ts +6 -6
  157. package/src/utils/theme.test.ts +1 -1
  158. package/src/utils/throughput-tracker.test.ts +11 -11
  159. package/src/utils/throughput-tracker.ts +9 -9
  160. package/src/utils/tokens.ts +13 -13
  161. package/src/utils/virtual-scroller.ts +1 -1
  162. package/src/vendor/smart-dom-reader/README.md +4 -4
  163. package/src/vendor/smart-dom-reader/index.d.ts +1 -1
  164. package/src/vendor/smart-dom-reader/index.js +2 -2
  165. package/src/version.ts +1 -1
  166. package/src/voice/audio-playback-manager.test.ts +159 -0
  167. package/src/voice/audio-playback-manager.ts +145 -30
  168. package/src/voice/browser-speech-engine.ts +130 -0
  169. package/src/voice/fallback-speech-engine.ts +89 -0
  170. package/src/voice/index.ts +21 -0
  171. package/src/voice/read-aloud-controller.test.ts +143 -0
  172. package/src/voice/read-aloud-controller.ts +136 -0
  173. package/src/voice/runtype-speech-engine.test.ts +280 -0
  174. package/src/voice/runtype-speech-engine.ts +211 -0
  175. package/src/voice/runtype-tts-entry.ts +10 -0
  176. package/src/voice/runtype-tts-loader.test.ts +47 -0
  177. package/src/voice/runtype-tts-loader.ts +42 -0
  178. package/src/voice/runtype-voice-provider.ts +332 -537
  179. package/src/voice/voice-factory.ts +26 -4
  180. package/src/voice/voice.test.ts +321 -7
  181. package/src/voice/worklet-playback-engine.ts +272 -0
  182. package/src/voice-worklet-player.ts +39 -0
  183. package/src/webmcp-bridge.test.ts +6 -6
  184. package/src/webmcp-bridge.ts +24 -25
  185. package/src/webmcp-polyfill.ts +1 -1
  186. package/src/voice/voice-activity-detector.ts +0 -90
@@ -0,0 +1,234 @@
1
+ import { AgentWidgetConfig } from "../types";
2
+ import { AgentWidgetPlugin } from "../plugins/types";
3
+ import { createWrapper, buildPanel, PanelElements } from "./panel";
4
+ import { HeaderElements } from "./header-builder";
5
+ import { createLauncherButton, LauncherButton } from "./launcher";
6
+
7
+ /**
8
+ * Widget view assembly layer.
9
+ *
10
+ * `ui.ts` owns behavior (session dispatch, streaming, scroll, voice, events);
11
+ * this module owns the one-time structural assembly of the widget's DOM and
12
+ * groups the resulting element references into named regions so callers read
13
+ * `view.composer.textarea` instead of destructuring a ~30-field flat object.
14
+ *
15
+ * The grouped refs are a view over the same nodes returned by `buildPanel`;
16
+ * the raw flat `PanelElements` is still exposed as `view.panelElements` for
17
+ * incremental migration. No framework, no virtual DOM: these are the real
18
+ * elements built by the existing `panel.ts` / `composer-*` / `header-*`
19
+ * builders.
20
+ */
21
+
22
+ export interface WidgetShellRefs {
23
+ wrapper: HTMLElement;
24
+ panel: HTMLElement;
25
+ /** Composer-bar mode only: viewport-fixed sibling that owns the pill. */
26
+ pillRoot?: HTMLElement;
27
+ }
28
+
29
+ export interface WidgetTranscriptRefs {
30
+ container: HTMLElement;
31
+ body: HTMLElement;
32
+ messagesWrapper: HTMLElement;
33
+ /** Absolute slot above the composer where interactive sheets mount. */
34
+ composerOverlay: HTMLElement;
35
+ introTitle: HTMLElement;
36
+ introSubtitle: HTMLElement;
37
+ }
38
+
39
+ export interface WidgetHeaderRefs {
40
+ /** The header root element currently mounted in the container. */
41
+ element: HTMLElement;
42
+ iconHolder: HTMLElement;
43
+ headerTitle: HTMLElement;
44
+ headerSubtitle: HTMLElement;
45
+ closeButton: HTMLButtonElement;
46
+ closeButtonWrapper: HTMLElement | null;
47
+ clearChatButton: HTMLButtonElement | null;
48
+ clearChatButtonWrapper: HTMLElement | null;
49
+ }
50
+
51
+ export interface WidgetComposerRefs {
52
+ /** The footer root element currently mounted in the panel. */
53
+ footer: HTMLElement;
54
+ form: HTMLFormElement;
55
+ textarea: HTMLTextAreaElement;
56
+ sendButton: HTMLButtonElement;
57
+ sendButtonWrapper: HTMLElement;
58
+ micButton: HTMLButtonElement | null;
59
+ micButtonWrapper: HTMLElement | null;
60
+ statusText: HTMLElement;
61
+ suggestions: HTMLElement;
62
+ attachmentButton: HTMLButtonElement | null;
63
+ attachmentButtonWrapper: HTMLElement | null;
64
+ attachmentInput: HTMLInputElement | null;
65
+ attachmentPreviewsContainer: HTMLElement | null;
66
+ actionsRow: HTMLElement;
67
+ leftActions: HTMLElement;
68
+ rightActions: HTMLElement;
69
+ setSendButtonMode: (mode: "send" | "stop") => void;
70
+ /** Composer-bar peek banner (undefined for non-composer-bar modes). */
71
+ peekBanner?: HTMLButtonElement;
72
+ peekTextNode?: HTMLElement;
73
+ }
74
+
75
+ export interface WidgetView {
76
+ shell: WidgetShellRefs;
77
+ /** Raw flat refs from `buildPanel`, retained for incremental migration. */
78
+ panelElements: PanelElements;
79
+ transcript: WidgetTranscriptRefs;
80
+ header: WidgetHeaderRefs;
81
+ composer: WidgetComposerRefs;
82
+ /**
83
+ * Swap the mounted header element for `next.header` and mirror the header
84
+ * sub-refs (icon/title/subtitle/close) onto `view.header`. Used by the
85
+ * header-layout rebuild path. Returns the new `HeaderElements` for the
86
+ * caller to mirror into its own locals during incremental migration.
87
+ */
88
+ replaceHeader: (next: HeaderElements) => HeaderElements;
89
+ /**
90
+ * Swap the mounted footer element for `nextFooter` and update
91
+ * `view.composer.footer`. The remaining composer sub-refs become stale
92
+ * after a plugin-provided composer replaces the footer; callers re-derive
93
+ * them from the new footer (see `bindComposerRefsFromFooter` in ui.ts).
94
+ */
95
+ replaceComposer: (nextFooter: HTMLElement) => void;
96
+ }
97
+
98
+ export interface CreateWidgetViewOptions {
99
+ config: AgentWidgetConfig;
100
+ /** Whether the panel renders a close affordance (panel is toggleable). */
101
+ showClose: boolean;
102
+ }
103
+
104
+ /**
105
+ * Build the widget shell + panel once and group the element references into
106
+ * named regions. Equivalent to the previous `createWrapper(config)` +
107
+ * `buildPanel(config, showClose)` pair, plus the grouping/replacement helpers.
108
+ */
109
+ export const createWidgetView = ({
110
+ config,
111
+ showClose,
112
+ }: CreateWidgetViewOptions): WidgetView => {
113
+ const { wrapper, panel, pillRoot } = createWrapper(config);
114
+ const panelElements = buildPanel(config, showClose);
115
+
116
+ const shell: WidgetShellRefs = { wrapper, panel, pillRoot };
117
+
118
+ const transcript: WidgetTranscriptRefs = {
119
+ container: panelElements.container,
120
+ body: panelElements.body,
121
+ messagesWrapper: panelElements.messagesWrapper,
122
+ composerOverlay: panelElements.composerOverlay,
123
+ introTitle: panelElements.introTitle,
124
+ introSubtitle: panelElements.introSubtitle,
125
+ };
126
+
127
+ const header: WidgetHeaderRefs = {
128
+ element: panelElements.header,
129
+ iconHolder: panelElements.iconHolder,
130
+ headerTitle: panelElements.headerTitle,
131
+ headerSubtitle: panelElements.headerSubtitle,
132
+ closeButton: panelElements.closeButton,
133
+ closeButtonWrapper: panelElements.closeButtonWrapper,
134
+ clearChatButton: panelElements.clearChatButton,
135
+ clearChatButtonWrapper: panelElements.clearChatButtonWrapper,
136
+ };
137
+
138
+ const composer: WidgetComposerRefs = {
139
+ footer: panelElements.footer,
140
+ form: panelElements.composerForm,
141
+ textarea: panelElements.textarea,
142
+ sendButton: panelElements.sendButton,
143
+ sendButtonWrapper: panelElements.sendButtonWrapper,
144
+ micButton: panelElements.micButton,
145
+ micButtonWrapper: panelElements.micButtonWrapper,
146
+ statusText: panelElements.statusText,
147
+ suggestions: panelElements.suggestions,
148
+ attachmentButton: panelElements.attachmentButton,
149
+ attachmentButtonWrapper: panelElements.attachmentButtonWrapper,
150
+ attachmentInput: panelElements.attachmentInput,
151
+ attachmentPreviewsContainer: panelElements.attachmentPreviewsContainer,
152
+ actionsRow: panelElements.actionsRow,
153
+ leftActions: panelElements.leftActions,
154
+ rightActions: panelElements.rightActions,
155
+ setSendButtonMode: panelElements.setSendButtonMode,
156
+ peekBanner: panelElements.peekBanner,
157
+ peekTextNode: panelElements.peekTextNode,
158
+ };
159
+
160
+ const replaceHeader = (next: HeaderElements): HeaderElements => {
161
+ header.element.replaceWith(next.header);
162
+ header.element = next.header;
163
+ header.iconHolder = next.iconHolder;
164
+ header.headerTitle = next.headerTitle;
165
+ header.headerSubtitle = next.headerSubtitle;
166
+ header.closeButton = next.closeButton;
167
+ header.closeButtonWrapper = next.closeButtonWrapper;
168
+ header.clearChatButton = next.clearChatButton;
169
+ header.clearChatButtonWrapper = next.clearChatButtonWrapper;
170
+ return next;
171
+ };
172
+
173
+ const replaceComposer = (nextFooter: HTMLElement): void => {
174
+ composer.footer.replaceWith(nextFooter);
175
+ composer.footer = nextFooter;
176
+ };
177
+
178
+ return {
179
+ shell,
180
+ panelElements,
181
+ transcript,
182
+ header,
183
+ composer,
184
+ replaceHeader,
185
+ replaceComposer,
186
+ };
187
+ };
188
+
189
+ export interface ResolveLauncherOptions {
190
+ config: AgentWidgetConfig;
191
+ plugins: AgentWidgetPlugin[];
192
+ /** Toggles the panel open/closed; wired to the launcher's click. */
193
+ onToggle: () => void;
194
+ }
195
+
196
+ export interface ResolvedLauncher {
197
+ /**
198
+ * The default launcher controller (with `update`/`destroy`), or null when a
199
+ * plugin supplied a custom launcher element.
200
+ */
201
+ instance: LauncherButton | null;
202
+ /** The element to mount: either the default button or the plugin's element. */
203
+ element: HTMLElement;
204
+ }
205
+
206
+ /**
207
+ * Resolve the launcher element, honoring a `renderLauncher` plugin if present.
208
+ * De-duplicates the identical default-or-plugin logic that previously lived at
209
+ * both the initial-build and re-enable call sites in ui.ts.
210
+ *
211
+ * When a plugin returns a custom element, `instance` is null (the plugin owns
212
+ * its own updates); otherwise the default `LauncherButton` controller is
213
+ * returned so the caller can drive `update()`/`destroy()`.
214
+ */
215
+ export const resolveLauncher = ({
216
+ config,
217
+ plugins,
218
+ onToggle,
219
+ }: ResolveLauncherOptions): ResolvedLauncher => {
220
+ const launcherPlugin = plugins.find((p) => p.renderLauncher);
221
+ if (launcherPlugin?.renderLauncher) {
222
+ const customLauncher = launcherPlugin.renderLauncher({
223
+ config,
224
+ defaultRenderer: () => createLauncherButton(config, onToggle).element,
225
+ onToggle,
226
+ });
227
+ if (customLauncher) {
228
+ return { instance: null, element: customLauncher };
229
+ }
230
+ }
231
+
232
+ const instance = createLauncherButton(config, onToggle);
233
+ return { instance, element: instance.element };
234
+ };
package/src/defaults.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { AgentWidgetConfig } from "./types";
1
+ import type { AgentWidgetConfig, AgentWidgetLauncherConfig } from "./types";
2
2
  import type { DeepPartial, PersonaTheme } from "./types/theme";
3
3
  import { deepMerge } from "./utils/deep-merge";
4
4
 
@@ -12,6 +12,58 @@ export const DEFAULT_FLOATING_LAUNCHER_WIDTH = "min(440px, calc(100vw - 24px))";
12
12
  /** Max width cap paired with {@link DEFAULT_FLOATING_LAUNCHER_WIDTH} for theme defaults. */
13
13
  export const DEFAULT_FLOATING_LAUNCHER_MAX_WIDTH = "440px";
14
14
 
15
+ export const DEFAULT_LAUNCHER_CONFIG: AgentWidgetLauncherConfig = {
16
+ enabled: true,
17
+ mountMode: "floating",
18
+ dock: {
19
+ side: "right",
20
+ width: "420px",
21
+ },
22
+ title: "Chat Assistant",
23
+ subtitle: "Here to help you get answers fast",
24
+ agentIconText: "💬",
25
+ agentIconName: "bot",
26
+ headerIconName: "bot",
27
+ position: "bottom-right",
28
+ width: DEFAULT_FLOATING_LAUNCHER_WIDTH,
29
+ heightOffset: 0,
30
+ autoExpand: false,
31
+ callToActionIconHidden: false,
32
+ agentIconSize: "40px",
33
+ headerIconSize: "40px",
34
+ closeButtonSize: "32px",
35
+ // Zero out browser-default <button> padding so the icon gets the full
36
+ // 32x32 content box, matching clearChat.paddingX/Y below. Without this,
37
+ // UA stylesheets add ~1-2px vertical and ~6px horizontal padding that
38
+ // eats into the border-box width and shrinks the rendered icon.
39
+ closeButtonPaddingX: "0px",
40
+ closeButtonPaddingY: "0px",
41
+ callToActionIconName: "arrow-up-right",
42
+ callToActionIconText: "",
43
+ callToActionIconSize: "32px",
44
+ callToActionIconPadding: "5px",
45
+ callToActionIconColor: undefined,
46
+ callToActionIconBackgroundColor: undefined,
47
+ // closeButtonColor / clearChat.iconColor omitted so theme.components.header.actionIconForeground applies.
48
+ closeButtonBackgroundColor: "transparent",
49
+ clearChat: {
50
+ backgroundColor: "transparent",
51
+ borderColor: "transparent",
52
+ enabled: true,
53
+ placement: "inline",
54
+ iconName: "refresh-cw",
55
+ size: "32px",
56
+ showTooltip: true,
57
+ tooltipText: "Clear chat",
58
+ paddingX: "0px",
59
+ paddingY: "0px",
60
+ },
61
+ headerIconHidden: false,
62
+ border: undefined,
63
+ shadow:
64
+ "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)",
65
+ };
66
+
15
67
  /**
16
68
  * Default widget configuration
17
69
  * Single source of truth for all default values
@@ -23,56 +75,7 @@ export const DEFAULT_WIDGET_CONFIG: Partial<AgentWidgetConfig> = {
23
75
  theme: undefined,
24
76
  darkTheme: undefined,
25
77
  colorScheme: "light",
26
- launcher: {
27
- enabled: true,
28
- mountMode: "floating",
29
- dock: {
30
- side: "right",
31
- width: "420px",
32
- },
33
- title: "Chat Assistant",
34
- subtitle: "Here to help you get answers fast",
35
- agentIconText: "💬",
36
- agentIconName: "bot",
37
- headerIconName: "bot",
38
- position: "bottom-right",
39
- width: DEFAULT_FLOATING_LAUNCHER_WIDTH,
40
- heightOffset: 0,
41
- autoExpand: false,
42
- callToActionIconHidden: false,
43
- agentIconSize: "40px",
44
- headerIconSize: "40px",
45
- closeButtonSize: "32px",
46
- // Zero out browser-default <button> padding so the icon gets the full
47
- // 32x32 content box, matching clearChat.paddingX/Y below. Without this,
48
- // UA stylesheets add ~1-2px vertical and ~6px horizontal padding that
49
- // eats into the border-box width and shrinks the rendered icon.
50
- closeButtonPaddingX: "0px",
51
- closeButtonPaddingY: "0px",
52
- callToActionIconName: "arrow-up-right",
53
- callToActionIconText: "",
54
- callToActionIconSize: "32px",
55
- callToActionIconPadding: "5px",
56
- callToActionIconColor: undefined,
57
- callToActionIconBackgroundColor: undefined,
58
- // closeButtonColor / clearChat.iconColor omitted so theme.components.header.actionIconForeground applies.
59
- closeButtonBackgroundColor: "transparent",
60
- clearChat: {
61
- backgroundColor: "transparent",
62
- borderColor: "transparent",
63
- enabled: true,
64
- placement: "inline",
65
- iconName: "refresh-cw",
66
- size: "32px",
67
- showTooltip: true,
68
- tooltipText: "Clear chat",
69
- paddingX: "0px",
70
- paddingY: "0px",
71
- },
72
- headerIconHidden: false,
73
- border: undefined,
74
- shadow: "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)",
75
- },
78
+ launcher: DEFAULT_LAUNCHER_CONFIG,
76
79
  copy: {
77
80
  welcomeTitle: "Hello 👋",
78
81
  welcomeSubtitle: "Ask anything about your account or products.",
@@ -285,6 +285,7 @@ export type RuntypeFlowSSEEvent = {
285
285
  totalSteps?: number;
286
286
  type: "flow_start";
287
287
  } | {
288
+ claudeManagedAgentId?: string;
288
289
  completedAt?: string;
289
290
  completedSteps?: number;
290
291
  duration?: number;
@@ -399,6 +400,7 @@ export type RuntypeFlowSSEEvent = {
399
400
  toolId: string;
400
401
  };
401
402
  type: "step_complete";
403
+ unresolvedVariables?: Array<string>;
402
404
  }) | {
403
405
  error: string;
404
406
  executionId?: string;
@@ -836,6 +838,7 @@ export type RuntypeDispatchSSEEvent = {
836
838
  totalSteps?: number;
837
839
  type: "flow_start";
838
840
  } | {
841
+ claudeManagedAgentId?: string;
839
842
  completedAt?: string;
840
843
  completedSteps?: number;
841
844
  duration?: number;
@@ -950,6 +953,7 @@ export type RuntypeDispatchSSEEvent = {
950
953
  toolId: string;
951
954
  };
952
955
  type: "step_complete";
956
+ unresolvedVariables?: Array<string>;
953
957
  }) | {
954
958
  error: string;
955
959
  executionId?: string;
@@ -1253,6 +1257,7 @@ export type RuntypeClientFeedbackRequest = {
1253
1257
  metadata?: Record<string, unknown>;
1254
1258
  rating?: number;
1255
1259
  sessionId: string;
1260
+ token: string;
1256
1261
  type: "upvote" | "downvote" | "copy" | "csat" | "nps";
1257
1262
  };
1258
1263
 
package/src/index-core.ts CHANGED
@@ -82,7 +82,7 @@ export type {
82
82
  AgentWidgetApproval,
83
83
  AgentWidgetApprovalConfig,
84
84
  AgentWidgetApprovalDecisionOptions,
85
- // WebMCP page-discovered tool consumption
85
+ // WebMCP: page-discovered tool consumption
86
86
  AgentWidgetWebMcpConfig,
87
87
  ClientToolDefinition,
88
88
  WebMcpConfirmHandler,
@@ -98,6 +98,7 @@ export type {
98
98
  EventStreamPayloadRenderContext,
99
99
  // Controller event map
100
100
  AgentWidgetControllerEventMap,
101
+ AgentWidgetReadAloudEvent,
101
102
  // Ask-user-question (built-in answer-pill sheet) types
102
103
  AskUserQuestionPayload,
103
104
  AskUserQuestionPrompt,
@@ -245,7 +246,7 @@ export type { AgentWidgetInitHandle };
245
246
  export type { AgentWidgetPlugin } from "./plugins/types";
246
247
  export { pluginRegistry } from "./plugins/registry";
247
248
 
248
- // Stream animation plugin API lets consumers register custom animations
249
+ // Stream animation plugin API: lets consumers register custom animations
249
250
  // that match the built-in surface (typewriter, pop-bubble) and subpath
250
251
  // modules (letter-rise, word-fade, wipe, glyph-cycle).
251
252
  export {
@@ -263,7 +264,7 @@ export type {
263
264
  AgentWidgetStreamAnimationPlaceholder,
264
265
  } from "./types";
265
266
 
266
- // Action system types needed to type the `actionHandlers` / `actionParsers`
267
+ // Action system types: needed to type the `actionHandlers` / `actionParsers`
267
268
  // config options and to author custom handlers/parsers.
268
269
  export type {
269
270
  AgentWidgetActionHandler,
@@ -435,7 +436,32 @@ export type {
435
436
  VoiceProvider,
436
437
  VoiceResult,
437
438
  VoiceStatus,
438
- VoiceConfig
439
+ VoiceConfig,
440
+ VoiceMetrics,
441
+ VoicePlaybackEngine,
442
+ PcmStreamPlayer,
443
+ AgentWidgetVoiceStatusEvent,
444
+ ReadAloudState,
445
+ SpeechEngine,
446
+ SpeechRequest,
447
+ SpeechCallbacks
439
448
  } from "./types";
440
449
 
450
+ export {
451
+ BrowserSpeechEngine,
452
+ pickBestVoice,
453
+ ReadAloudController
454
+ } from "./voice";
455
+ export type {
456
+ BrowserSpeechEngineOptions,
457
+ ReadAloudListener,
458
+ FallbackSpeechEngineOptions
459
+ } from "./voice";
460
+ // RuntypeSpeechEngine + FallbackSpeechEngine ship from the
461
+ // `@runtypelabs/persona/voice-worklet-player` subpath, not here: keeping the
462
+ // hosted read-aloud engine out of the main bundle lets the IIFE/CDN build defer
463
+ // it to the `runtype-tts.js` chunk (loaded only for `provider: 'runtype'`). The
464
+ // blessed way to use it is `textToSpeech: { provider: 'runtype' }` — no import
465
+ // needed.
466
+
441
467
  export default initAgentWidgetFn;
@@ -1,18 +1,18 @@
1
1
  /**
2
- * IIFE entry point bundled for `<script>` tag consumers.
2
+ * IIFE entry point: bundled for `<script>` tag consumers.
3
3
  *
4
4
  * This file re-exports everything from the main entry AND side-imports all
5
5
  * built-in subpath animations so they register automatically. Script-tag
6
6
  * users who include the global build don't need extra script tags or
7
- * registration calls setting `features.streamAnimation.type` to any
7
+ * registration calls: setting `features.streamAnimation.type` to any
8
8
  * built-in name just works.
9
9
  *
10
10
  * npm consumers continue to import from the main entry (`import ... from
11
- * "@runtypelabs/persona"`) those animations stay in their subpath
11
+ * "@runtypelabs/persona"`): those animations stay in their subpath
12
12
  * modules so bundlers can tree-shake them.
13
13
  */
14
14
 
15
- // Re-export the full public API from `index-core` (NOT `index`) so the
15
+ // Re-export the full public API: from `index-core` (NOT `index`) so the
16
16
  // dev-only helpers (`generateCodeSnippet`, `createDemoCarousel`) stay out of the
17
17
  // CDN/IIFE bundle. npm consumers still get them via the `index.ts` barrel.
18
18
  export * from "./index-core";
@@ -35,7 +35,7 @@ export type { StreamAnimationPlugin, StreamAnimationContext } from "./types";
35
35
  // ---------------------------------------------------------------------------
36
36
  // Deferred WebMCP polyfill loading.
37
37
  //
38
- // This bundle is built with `@mcp-b/webmcp-polyfill` external the bridge's
38
+ // This bundle is built with `@mcp-b/webmcp-polyfill` external: the bridge's
39
39
  // default `import("@mcp-b/webmcp-polyfill")` is a bare specifier no browser
40
40
  // can resolve, so register a loader that imports the self-contained
41
41
  // `webmcp-polyfill.js` chunk from a sibling URL instead. Mirrors how
@@ -44,7 +44,7 @@ export type { StreamAnimationPlugin, StreamAnimationContext } from "./types";
44
44
 
45
45
  import { setWebMcpPolyfillLoader } from "./webmcp-bridge";
46
46
 
47
- // Capture at module-evaluation time `document.currentScript` is null once
47
+ // Capture at module-evaluation time: `document.currentScript` is null once
48
48
  // execution leaves the script's initial synchronous run.
49
49
  const widgetScriptSrc: string | null =
50
50
  typeof document !== "undefined"
@@ -70,3 +70,70 @@ setWebMcpPolyfillLoader(() => {
70
70
  // leaves it untouched (and must not try to bundle it).
71
71
  return import(/* @vite-ignore */ chunkUrl);
72
72
  });
73
+
74
+ // ---------------------------------------------------------------------------
75
+ // Deferred Markdown Parsers (marked + dompurify) loading.
76
+ //
77
+ // This bundle is built with `./markdown-parsers-entry` external: the Markdown
78
+ // and HTML sanitization libraries are kept out of the CDN payload. Register a
79
+ // loader that imports the self-contained `markdown-parsers.js` chunk from a sibling
80
+ // URL. The session prefetches it at init so it's warm before the first message.
81
+ // ---------------------------------------------------------------------------
82
+
83
+ import { setMarkdownParsersLoader, loadMarkdownParsers } from "./markdown-parsers-loader";
84
+
85
+ setMarkdownParsersLoader(() => {
86
+ const chunkUrl = widgetScriptSrc?.replace(
87
+ /index\.global\.js($|\?)/,
88
+ "markdown-parsers.js$1",
89
+ );
90
+ if (!chunkUrl || chunkUrl === widgetScriptSrc) {
91
+ return Promise.reject(
92
+ new Error(
93
+ "Could not derive the markdown-parsers.js URL from the widget script URL " +
94
+ `(${widgetScriptSrc ?? "unavailable"}). Self-hosted deployments that ` +
95
+ "rename index.global.js should host markdown-parsers.js alongside it.",
96
+ ),
97
+ );
98
+ }
99
+ return import(/* @vite-ignore */ chunkUrl);
100
+ });
101
+
102
+ // Kick off the load immediately since it will likely be needed.
103
+ loadMarkdownParsers().catch(err => {
104
+ // It's okay if this fails (e.g. ad blocker), it'll just fall back to plain text.
105
+ if (typeof console !== "undefined") {
106
+ console.warn("[Persona] Failed to pre-load markdown parsers", err);
107
+ }
108
+ });
109
+ // ---------------------------------------------------------------------------
110
+ // Deferred Runtype TTS engine loading.
111
+ //
112
+ // This bundle is built with `./runtype-speech-engine` external (see
113
+ // `tsup.global.config.ts`): the hosted read-aloud engine + the
114
+ // AudioPlaybackManager it bundles are kept out of the CDN payload. Register a
115
+ // loader that imports the self-contained `runtype-tts.js` chunk from a sibling
116
+ // URL; the session prefetches it at init when `textToSpeech.provider:'runtype'`
117
+ // is set, so it's warm before the first click. Same pattern as the WebMCP
118
+ // polyfill above.
119
+ // ---------------------------------------------------------------------------
120
+
121
+ import { setRuntypeTtsLoader } from "./voice/runtype-tts-loader";
122
+
123
+ setRuntypeTtsLoader(() => {
124
+ const chunkUrl = widgetScriptSrc?.replace(
125
+ /index\.global\.js($|\?)/,
126
+ "runtype-tts.js$1",
127
+ );
128
+ if (!chunkUrl || chunkUrl === widgetScriptSrc) {
129
+ return Promise.reject(
130
+ new Error(
131
+ "Could not derive the runtype-tts.js URL from the widget script URL " +
132
+ `(${widgetScriptSrc ?? "unavailable"}). Self-hosted deployments that ` +
133
+ "rename index.global.js should host runtype-tts.js alongside it, or set " +
134
+ "textToSpeech.createEngine to supply a speech engine directly.",
135
+ ),
136
+ );
137
+ }
138
+ return import(/* @vite-ignore */ chunkUrl);
139
+ });
package/src/index.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * npm package entry (`@runtypelabs/persona`).
3
3
  *
4
4
  * This is a thin barrel over `index-core.ts` (the shared public API) that adds
5
- * back the **dev/config-tool-only** helpers `generateCodeSnippet` and
5
+ * back the **dev/config-tool-only** helpers: `generateCodeSnippet` and
6
6
  * `createDemoCarousel`. Those are kept out of `index-core.ts` so the IIFE/CDN
7
7
  * build (`index-global.ts`, which re-exports from `index-core.ts`) doesn't ship
8
8
  * them: a running widget never needs them, only build-time/demo tooling does.
@@ -12,6 +12,12 @@
12
12
  * `createDemoCarousel`.
13
13
  */
14
14
 
15
+ // Register `marked` + `dompurify` synchronously for the bundled npm build so
16
+ // the synchronous markdown/sanitize API renders on first paint. The IIFE/CDN
17
+ // entry (`index-global.ts`) does NOT import this; it lazy-loads the parsers
18
+ // from the `markdown-parsers.js` chunk instead. Must run before any render.
19
+ import "./markdown-parsers-eager";
20
+
15
21
  // Full public API (everything except the two dev-only helpers below).
16
22
  export * from "./index-core";
17
23
  export { default } from "./index-core";
@@ -70,7 +70,7 @@ function provideLauncherBundle() {
70
70
  /**
71
71
  * Force the next dynamically-inserted <script> to error. jsdom never fires
72
72
  * load/error on injected scripts, so we trigger `onerror` on a fake timer that
73
- * `runAllTimersAsync()` flushes deterministic under fake timers.
73
+ * `runAllTimersAsync()` flushes: deterministic under fake timers.
74
74
  */
75
75
  function failNextScriptLoad() {
76
76
  const head = document.head;
@@ -137,15 +137,15 @@ afterEach(() => {
137
137
  vi.restoreAllMocks();
138
138
  });
139
139
 
140
- describe("install.ts onScriptLoad beacon", () => {
140
+ describe("install.ts: onScriptLoad beacon", () => {
141
141
  it("fires synchronously on script execution, before any loading or gating", async () => {
142
142
  const events = recordEvents();
143
143
  const onScriptLoad = vi.fn();
144
144
 
145
- // No config at all the beacon must still fire (diagnostics: "did my embed run").
145
+ // No config at all: the beacon must still fire (diagnostics: "did my embed run").
146
146
  await install({ onScriptLoad });
147
147
 
148
- // Fired during module evaluation no timer flush needed.
148
+ // Fired during module evaluation: no timer flush needed.
149
149
  expect(onScriptLoad).toHaveBeenCalledTimes(1);
150
150
  expect(onScriptLoad).toHaveBeenCalledWith({ version: "latest" });
151
151
  expect(events.types()[0]).toBe("persona:script-load");
@@ -160,7 +160,7 @@ describe("install.ts — onScriptLoad beacon", () => {
160
160
  });
161
161
  });
162
162
 
163
- describe("install.ts deferred launcher path", () => {
163
+ describe("install.ts: deferred launcher path", () => {
164
164
  it("mounts the critical launcher, fires onLauncherShown, and defers onChatReady until open", async () => {
165
165
  markCssLoaded();
166
166
  provideFullBundle(); // so loadJS resolves instantly when the user opens
@@ -186,7 +186,7 @@ describe("install.ts — deferred launcher path", () => {
186
186
  expect(launcherMount).toHaveBeenCalledTimes(1);
187
187
  expect(onLauncherShown).toHaveBeenCalledTimes(1);
188
188
  expect(onLauncherShown).toHaveBeenCalledWith({ deferred: true, element: launcherElement });
189
- // The full widget has NOT been initialized yet that waits for first open.
189
+ // The full widget has NOT been initialized yet: that waits for first open.
190
190
  expect(initAgentWidget).not.toHaveBeenCalled();
191
191
  expect(onChatReady).not.toHaveBeenCalled();
192
192
 
@@ -230,7 +230,7 @@ describe("install.ts — deferred launcher path", () => {
230
230
  });
231
231
  });
232
232
 
233
- describe("install.ts eager path", () => {
233
+ describe("install.ts: eager path", () => {
234
234
  it("non-floating (launcher disabled) eager-loads, fires onChatReady, and does NOT fire onLauncherShown", async () => {
235
235
  markCssLoaded();
236
236
  provideFullBundle();
@@ -272,7 +272,7 @@ describe("install.ts — eager path", () => {
272
272
  });
273
273
  });
274
274
 
275
- describe("install.ts deferral gate", () => {
275
+ describe("install.ts: deferral gate", () => {
276
276
  async function expectDecision(config: any, { defers }: { defers: boolean }) {
277
277
  markCssLoaded();
278
278
  provideFullBundle();
@@ -345,7 +345,7 @@ describe("install.ts — deferral gate", () => {
345
345
  });
346
346
  });
347
347
 
348
- describe("install.ts onReady deprecation alias", () => {
348
+ describe("install.ts: onReady deprecation alias", () => {
349
349
  it("still calls the deprecated onReady and warns once", async () => {
350
350
  markCssLoaded();
351
351
  provideFullBundle();
@@ -376,7 +376,7 @@ describe("install.ts — onReady deprecation alias", () => {
376
376
  });
377
377
  });
378
378
 
379
- describe("install.ts onError", () => {
379
+ describe("install.ts: onError", () => {
380
380
  it("fires onError with phase 'init' and dispatches persona:error when initialization throws", async () => {
381
381
  markCssLoaded();
382
382
  provideFullBundle();
@@ -420,7 +420,7 @@ describe("install.ts — onError", () => {
420
420
  });
421
421
  });
422
422
 
423
- describe("install.ts lifecycle callback safety", () => {
423
+ describe("install.ts: lifecycle callback safety", () => {
424
424
  it("a throwing onScriptLoad callback does not break the rest of installation", async () => {
425
425
  markCssLoaded();
426
426
  provideFullBundle();