@runtypelabs/persona 3.34.1 → 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 +12 -12
  53. package/src/client.ts +30 -31
  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,181 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { describe, expect, it, vi } from "vitest";
4
+ import { createWidgetView, resolveLauncher } from "./widget-view";
5
+ import type { HeaderElements } from "./header-builder";
6
+ import type { AgentWidgetConfig } from "../types";
7
+ import type { AgentWidgetPlugin } from "../plugins/types";
8
+
9
+ describe("createWidgetView: default assembly", () => {
10
+ const config: AgentWidgetConfig = {
11
+ apiUrl: "/api",
12
+ voiceRecognition: { enabled: true, provider: { type: "runtype" } },
13
+ attachments: { enabled: true },
14
+ };
15
+
16
+ it("groups shell, transcript, header, and composer refs over the real nodes", () => {
17
+ const view = createWidgetView({ config, showClose: true });
18
+
19
+ // Shell. The empty `panel` is wired to the `container` later by ui.ts
20
+ // (panel.appendChild(container)); createWidgetView preserves that split.
21
+ expect(view.shell.wrapper.contains(view.shell.panel)).toBe(true);
22
+ expect(view.shell.pillRoot).toBeUndefined();
23
+
24
+ // Transcript subtree is wired by buildPanel
25
+ expect(view.transcript.container.contains(view.transcript.body)).toBe(true);
26
+ expect(view.transcript.body.contains(view.transcript.messagesWrapper)).toBe(true);
27
+
28
+ // Header + composer are mounted in the container
29
+ expect(view.transcript.container.contains(view.header.element)).toBe(true);
30
+ expect(view.transcript.container.contains(view.composer.footer)).toBe(true);
31
+
32
+ // Grouped refs are the same nodes as the raw flat panelElements
33
+ expect(view.composer.textarea).toBe(view.panelElements.textarea);
34
+ expect(view.composer.footer).toBe(view.panelElements.footer);
35
+ expect(view.header.element).toBe(view.panelElements.header);
36
+ });
37
+
38
+ it("exposes the stable composer ref attributes", () => {
39
+ const view = createWidgetView({ config, showClose: true });
40
+
41
+ expect(view.composer.textarea.getAttribute("data-persona-composer-input")).toBe("");
42
+ expect(view.composer.sendButton.getAttribute("data-persona-composer-submit")).toBe("");
43
+ expect(view.composer.statusText.getAttribute("data-persona-composer-status")).toBe("");
44
+ expect(view.composer.suggestions.getAttribute("data-persona-composer-suggestions")).toBe("");
45
+ expect(view.composer.actionsRow.getAttribute("data-persona-composer-actions")).toBe("");
46
+ expect(view.composer.micButton).not.toBeNull();
47
+ expect(
48
+ view.composer.attachmentButton?.getAttribute("data-persona-composer-attachment-button")
49
+ ).toBe("");
50
+ expect(
51
+ view.composer.attachmentInput?.getAttribute("data-persona-composer-attachment-input")
52
+ ).toBe("");
53
+ expect(
54
+ view.composer.attachmentPreviewsContainer?.getAttribute(
55
+ "data-persona-composer-attachment-previews"
56
+ )
57
+ ).toBe("");
58
+ });
59
+
60
+ it("hides header and footer when layout disables them", () => {
61
+ const view = createWidgetView({
62
+ config: { apiUrl: "/api", layout: { showHeader: false, showFooter: false } },
63
+ showClose: true,
64
+ });
65
+
66
+ expect(view.header.element.style.display).toBe("none");
67
+ expect(view.composer.footer.style.display).toBe("none");
68
+ });
69
+ });
70
+
71
+ describe("createWidgetView: composer-bar assembly", () => {
72
+ const config: AgentWidgetConfig = {
73
+ apiUrl: "/api",
74
+ launcher: { mountMode: "composer-bar" },
75
+ };
76
+
77
+ it("exposes the pill root and peek banner", () => {
78
+ const view = createWidgetView({ config, showClose: true });
79
+
80
+ expect(view.shell.pillRoot).toBeInstanceOf(HTMLElement);
81
+ expect(view.composer.peekBanner).toBeInstanceOf(HTMLElement);
82
+ expect(view.composer.peekTextNode).toBeInstanceOf(HTMLElement);
83
+ // Composer-bar uses a hidden placeholder header (close button is the chrome).
84
+ expect(view.header.element.style.display).toBe("none");
85
+ });
86
+ });
87
+
88
+ describe("createWidgetView: replacement helpers", () => {
89
+ it("replaceHeader swaps the mounted element and mirrors sub-refs", () => {
90
+ const view = createWidgetView({ config: { apiUrl: "/api" }, showClose: true });
91
+ const original = view.header.element;
92
+ const parent = original.parentElement;
93
+
94
+ const next: HeaderElements = {
95
+ header: document.createElement("header"),
96
+ iconHolder: document.createElement("span"),
97
+ headerTitle: document.createElement("span"),
98
+ headerSubtitle: document.createElement("span"),
99
+ closeButton: document.createElement("button"),
100
+ closeButtonWrapper: document.createElement("div"),
101
+ clearChatButton: null,
102
+ clearChatButtonWrapper: null,
103
+ };
104
+
105
+ const returned = view.replaceHeader(next);
106
+
107
+ expect(returned).toBe(next);
108
+ expect(view.header.element).toBe(next.header);
109
+ expect(view.header.iconHolder).toBe(next.iconHolder);
110
+ expect(view.header.closeButton).toBe(next.closeButton);
111
+ expect(parent?.contains(next.header)).toBe(true);
112
+ expect(parent?.contains(original)).toBe(false);
113
+ });
114
+
115
+ it("replaceComposer swaps the mounted footer and updates the ref", () => {
116
+ const view = createWidgetView({ config: { apiUrl: "/api" }, showClose: true });
117
+ const original = view.composer.footer;
118
+ const parent = original.parentElement;
119
+ const nextFooter = document.createElement("div");
120
+
121
+ view.replaceComposer(nextFooter);
122
+
123
+ expect(view.composer.footer).toBe(nextFooter);
124
+ expect(parent?.contains(nextFooter)).toBe(true);
125
+ expect(parent?.contains(original)).toBe(false);
126
+ });
127
+ });
128
+
129
+ describe("resolveLauncher", () => {
130
+ const config: AgentWidgetConfig = { apiUrl: "/api" };
131
+
132
+ it("builds the default launcher controller and wires onToggle", () => {
133
+ const onToggle = vi.fn();
134
+ const { instance, element } = resolveLauncher({ config, plugins: [], onToggle });
135
+
136
+ expect(instance).not.toBeNull();
137
+ expect(element).toBe(instance?.element);
138
+ expect(element.tagName).toBe("BUTTON");
139
+
140
+ element.dispatchEvent(new Event("click"));
141
+ expect(onToggle).toHaveBeenCalledTimes(1);
142
+ });
143
+
144
+ it("uses a plugin-provided launcher element and returns no controller", () => {
145
+ const custom = document.createElement("div");
146
+ custom.id = "custom-launcher";
147
+ const plugin: AgentWidgetPlugin = {
148
+ id: "custom",
149
+ renderLauncher: ({ defaultRenderer }) => {
150
+ // The default renderer is still callable; the plugin opts to ignore it.
151
+ expect(defaultRenderer().tagName).toBe("BUTTON");
152
+ return custom;
153
+ },
154
+ };
155
+
156
+ const { instance, element } = resolveLauncher({
157
+ config,
158
+ plugins: [plugin],
159
+ onToggle: () => {},
160
+ });
161
+
162
+ expect(instance).toBeNull();
163
+ expect(element).toBe(custom);
164
+ });
165
+
166
+ it("falls back to the default launcher when the plugin returns null", () => {
167
+ const plugin: AgentWidgetPlugin = {
168
+ id: "passthrough",
169
+ renderLauncher: () => null,
170
+ };
171
+
172
+ const { instance, element } = resolveLauncher({
173
+ config,
174
+ plugins: [plugin],
175
+ onToggle: () => {},
176
+ });
177
+
178
+ expect(instance).not.toBeNull();
179
+ expect(element).toBe(instance?.element);
180
+ });
181
+ });
@@ -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;