@runtypelabs/persona 3.34.1 → 3.36.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 +56 -12
  53. package/src/client.ts +35 -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
@@ -1,5 +1,5 @@
1
1
  /**
2
- * VENDORED type declarations @mcp-b/smart-dom-reader v2.3.1 (MIT).
2
+ * VENDORED type declarations: @mcp-b/smart-dom-reader v2.3.1 (MIT).
3
3
  * Copied verbatim from upstream dist/index.d.mts (only the trailing sourceMappingURL
4
4
  * comment removed). See ./index.js and ./README.md for why this is vendored.
5
5
  */
@@ -188,14 +188,14 @@ declare global {
188
188
  * Enriched DOM context collection for providing richer page information to AI.
189
189
  *
190
190
  * Captures interactive elements, stable CSS selectors, ARIA roles, data attributes,
191
- * and visibility state giving the LLM much better context than basic className/innerText.
191
+ * and visibility state: giving the LLM much better context than basic className/innerText.
192
192
  *
193
193
  * ## Modes
194
194
  *
195
195
  * - **structured** (default): collects candidates, scores them with optional {@link ParseRule}
196
196
  * hooks, then applies `maxElements`. Rich containers (e.g. product cards) can surface
197
197
  * before unrelated static noise.
198
- * - **simple**: legacy behavior cap during traversal, interactive-first ordering, no rule
198
+ * - **simple**: legacy behavior: cap during traversal, interactive-first ordering, no rule
199
199
  * scoring or {@link EnrichedPageElement.formattedSummary}.
200
200
  */
201
201
  interface EnrichedPageElement {
@@ -222,8 +222,8 @@ interface EnrichedPageElement {
222
222
  * Pure mapper: `@mcp-b/smart-dom-reader` output → Persona's {@link EnrichedPageElement}[].
223
223
  *
224
224
  * This module imports the smart-dom-reader types with `import type` ONLY, so the
225
- * library's runtime value is never pulled in here. That keeps the mapper and its
226
- * unit test free of any DOM and free of the (vendored) library at runtime; the
225
+ * library's runtime value is never pulled in here. That keeps the mapper, and its
226
+ * unit test: free of any DOM and free of the (vendored) library at runtime; the
227
227
  * types are erased during compilation.
228
228
  *
229
229
  * The smart-dom-reader returns a structured {@link SmartDOMResult} JSON object with
@@ -315,7 +315,7 @@ interface AgentWidgetPlugin {
315
315
  defaultRenderer: () => HTMLElement;
316
316
  onSubmit: (text: string) => void;
317
317
  /**
318
- * When true, the assistant stream is active same moment `session.isStreaming()` becomes true.
318
+ * When true, the assistant stream is active: same moment `session.isStreaming()` becomes true.
319
319
  * Prefer wiring controls to `data-persona-composer-disable-when-streaming` plus `setComposerDisabled`
320
320
  * in the host, or react to `footer.dataset.personaComposerStreaming === "true"`.
321
321
  */
@@ -364,14 +364,14 @@ interface AgentWidgetPlugin {
364
364
  * Custom renderer for `ask_user_question` tool calls.
365
365
  *
366
366
  * When a plugin returns an `HTMLElement`, it is inserted into the transcript
367
- * in place of the default (which is no transcript bubble the built-in
367
+ * in place of the default (which is no transcript bubble: the built-in
368
368
  * renders a sheet over the composer). The built-in composer-overlay sheet
369
369
  * is suppressed so the plugin's UI fully owns the interaction.
370
370
  *
371
371
  * Return `null` to fall through to the built-in overlay sheet.
372
372
  *
373
373
  * The context gives you a pre-parsed `payload` (may be partial while the
374
- * tool call is still streaming check `complete`) and two callbacks:
374
+ * tool call is still streaming: check `complete`) and two callbacks:
375
375
  * `resolve(answer)` resumes the paused LOCAL tool with the user's answer,
376
376
  * and `dismiss()` cancels with the sentinel `"(dismissed)"`.
377
377
  *
@@ -420,8 +420,8 @@ interface AgentWidgetPlugin {
420
420
  *
421
421
  * Return an `HTMLElement` to fully own the approval UI, `defaultRenderer()`
422
422
  * to render (or wrap) the built-in bubble, or `null` to fall through to the
423
- * default. Unlike the built-in bubble whose Approve/Deny buttons are wired
424
- * via delegation a fully custom element resolves the approval by calling
423
+ * default. Unlike the built-in bubble: whose Approve/Deny buttons are wired
424
+ * via delegation: a fully custom element resolves the approval by calling
425
425
  * the `approve`/`deny` callbacks. Both route through the same path the
426
426
  * built-in buttons use (optimistic update, `onDecision`, in-place anchoring).
427
427
  *
@@ -1295,6 +1295,7 @@ type RuntypeFlowSSEEvent = {
1295
1295
  totalSteps?: number;
1296
1296
  type: "flow_start";
1297
1297
  } | {
1298
+ claudeManagedAgentId?: string;
1298
1299
  completedAt?: string;
1299
1300
  completedSteps?: number;
1300
1301
  duration?: number;
@@ -1409,6 +1410,7 @@ type RuntypeFlowSSEEvent = {
1409
1410
  toolId: string;
1410
1411
  };
1411
1412
  type: "step_complete";
1413
+ unresolvedVariables?: Array<string>;
1412
1414
  }) | {
1413
1415
  error: string;
1414
1416
  executionId?: string;
@@ -1742,7 +1744,7 @@ type AgentConfig = {
1742
1744
  temperature?: number;
1743
1745
  /** Tool configuration for the agent */
1744
1746
  tools?: AgentToolsConfig;
1745
- /** Persona artifacts sibling of tools (virtual agent / API parity) */
1747
+ /** Persona artifacts: sibling of tools (virtual agent / API parity) */
1746
1748
  artifacts?: ArtifactConfigPayload;
1747
1749
  /** Loop configuration for multi-turn execution */
1748
1750
  loopConfig?: AgentLoopConfig;
@@ -1764,7 +1766,7 @@ type AgentRequestOptions = {
1764
1766
  * Wire shape for a single client-discovered tool sent on `dispatch.clientTools[]`.
1765
1767
  *
1766
1768
  * Mirrors the SDK's `ClientToolDefinition` in `@runtypelabs/sdk`. Only the
1767
- * JSON-serializable surface of a WebMCP tool the `execute` function stays
1769
+ * JSON-serializable surface of a WebMCP tool: the `execute` function stays
1768
1770
  * client-side; the server merges these into the agent's tool catalog under
1769
1771
  * the `webmcp:` namespace.
1770
1772
  */
@@ -1772,16 +1774,16 @@ type ClientToolDefinition = {
1772
1774
  /** Bare tool name; the server prepends `webmcp:` on the wire. */
1773
1775
  name: string;
1774
1776
  description: string;
1775
- /** JSON Schema (per WebMCP spec) passed through as-is. */
1777
+ /** JSON Schema (per WebMCP spec): passed through as-is. */
1776
1778
  parametersSchema?: object;
1777
1779
  /**
1778
1780
  * `'webmcp'` for tools discovered via the polyfill (server prepends the
1779
1781
  * `webmcp:` wire prefix); `'sdk'` for widget/SDK-provided tools (name stays
1780
- * bare on the wire). Matches the server's accepted enum any other value
1782
+ * bare on the wire). Matches the server's accepted enum: any other value
1781
1783
  * fails dispatch validation.
1782
1784
  */
1783
1785
  origin?: 'webmcp' | 'sdk';
1784
- /** Origin of the page that registered the tool for server-side audit. */
1786
+ /** Origin of the page that registered the tool: for server-side audit. */
1785
1787
  pageOrigin?: string;
1786
1788
  /**
1787
1789
  * WebMCP `Tool.annotations` (spec). Not used for gating server-side; the
@@ -1813,7 +1815,7 @@ type WebMcpConfirmInfo = {
1813
1815
  untrustedContentHint?: boolean;
1814
1816
  };
1815
1817
  /**
1816
- * Why the confirm was requested. Currently always `'gate'` the default
1818
+ * Why the confirm was requested. Currently always `'gate'`: the default
1817
1819
  * confirm-by-default gate that fires before every `webmcp:*` call. (The
1818
1820
  * `@mcp-b/webmcp-polyfill` owns the spec's `requestUserInteraction` callback
1819
1821
  * internally, so Persona no longer surfaces a nested in-tool confirm.)
@@ -1827,7 +1829,7 @@ type WebMcpConfirmHandler = (info: WebMcpConfirmInfo) => Promise<boolean>;
1827
1829
  /**
1828
1830
  * Widget-level WebMCP configuration. Set `enabled: true` to opt in. The
1829
1831
  * surface's server-side `webmcp` policy is the source of truth for which
1830
- * tools are accepted these client-side options are convenience filters.
1832
+ * tools are accepted: these client-side options are convenience filters.
1831
1833
  */
1832
1834
  type AgentWidgetWebMcpConfig = {
1833
1835
  /** Master switch. Default: `false` (widget never installs the polyfill). */
@@ -1843,7 +1845,7 @@ type AgentWidgetWebMcpConfig = {
1843
1845
  * `webmcp:*` call; return `true` to approve immediately and skip the
1844
1846
  * confirmation UI entirely. Use this to auto-allow read-only tools (e.g.
1845
1847
  * a catalog search) while still gating mutating ones. Only consulted on
1846
- * the default-UI path a custom `onConfirm` takes full control instead.
1848
+ * the default-UI path: a custom `onConfirm` takes full control instead.
1847
1849
  */
1848
1850
  autoApprove?: (info: WebMcpConfirmInfo) => boolean;
1849
1851
  /**
@@ -1886,7 +1888,7 @@ type AgentMessageMetadata = {
1886
1888
  * `flow_await` events for a LOCAL tool (core#3878). Present only when the
1887
1889
  * server emits it. Two PARALLEL calls to the same tool in one turn share a
1888
1890
  * `toolName` (and a collapsed `toolId`) but get DISTINCT `webMcpToolCallId`s,
1889
- * so this is the key the widget batches a single `/resume` on preferred
1891
+ * so this is the key the widget batches a single `/resume` on: preferred
1890
1892
  * over tool name, which collides for same-tool parallel calls. Absent →
1891
1893
  * fall back to the legacy name-keyed resume contract.
1892
1894
  */
@@ -2022,6 +2024,14 @@ type AgentWidgetMessageActionsConfig = {
2022
2024
  * @default false
2023
2025
  */
2024
2026
  showDownvote?: boolean;
2027
+ /**
2028
+ * Show a "Read aloud" button that speaks the assistant message via
2029
+ * text-to-speech (play / pause / resume). Uses the browser Web Speech API by
2030
+ * default, or a hosted engine supplied via `textToSpeech.createEngine`.
2031
+ * Voice, rate and pitch are read from the `textToSpeech` config.
2032
+ * @default false
2033
+ */
2034
+ showReadAloud?: boolean;
2025
2035
  /**
2026
2036
  * Visibility mode: 'always' shows buttons always, 'hover' shows on hover only
2027
2037
  * @default 'hover'
@@ -2096,8 +2106,8 @@ type AgentWidgetArtifactsLayoutConfig = {
2096
2106
  resizableMaxWidth?: string;
2097
2107
  /**
2098
2108
  * Visual treatment for the artifact column in split mode.
2099
- * - `'panel'` bordered sidebar with left border, gap, and shadow (default).
2100
- * - `'seamless'` flush with chat: no border or shadow, container background, zero gap.
2109
+ * - `'panel'`: bordered sidebar with left border, gap, and shadow (default).
2110
+ * - `'seamless'`: flush with chat: no border or shadow, container background, zero gap.
2101
2111
  * @default 'panel'
2102
2112
  */
2103
2113
  paneAppearance?: "panel" | "seamless";
@@ -2111,7 +2121,7 @@ type AgentWidgetArtifactsLayoutConfig = {
2111
2121
  */
2112
2122
  paneBorder?: string;
2113
2123
  /**
2114
- * `border-left` shorthand only typical for split view next to chat (with or without resizer).
2124
+ * `border-left` shorthand only: typical for split view next to chat (with or without resizer).
2115
2125
  * Ignored if `paneBorder` is set. Example: `"1px solid #cccccc"`.
2116
2126
  */
2117
2127
  paneBorderLeft?: string;
@@ -2138,8 +2148,8 @@ type AgentWidgetArtifactsLayoutConfig = {
2138
2148
  panePadding?: string;
2139
2149
  /**
2140
2150
  * Toolbar layout preset.
2141
- * - `default` "Artifacts" title, horizontal tabs, text close.
2142
- * - `document` view/source toggle, document title, copy / refresh / close; tab strip hidden when only one artifact.
2151
+ * - `default`: "Artifacts" title, horizontal tabs, text close.
2152
+ * - `document`: view/source toggle, document title, copy / refresh / close; tab strip hidden when only one artifact.
2143
2153
  * @default 'default'
2144
2154
  */
2145
2155
  toolbarPreset?: "default" | "document";
@@ -2151,7 +2161,7 @@ type AgentWidgetArtifactsLayoutConfig = {
2151
2161
  * When `toolbarPreset` is `document`, show a small chevron after the copy control (e.g. menu affordance).
2152
2162
  */
2153
2163
  documentToolbarShowCopyChevron?: boolean;
2154
- /** Document toolbar icon buttons (view, code, copy, refresh, close) CSS color. Sets `--persona-artifact-doc-toolbar-icon-color` on the widget root. */
2164
+ /** Document toolbar icon buttons (view, code, copy, refresh, close): CSS color. Sets `--persona-artifact-doc-toolbar-icon-color` on the widget root. */
2155
2165
  documentToolbarIconColor?: string;
2156
2166
  /** Active view/source toggle background. Sets `--persona-artifact-doc-toggle-active-bg`. */
2157
2167
  documentToolbarToggleActiveBackground?: string;
@@ -2295,11 +2305,11 @@ type AgentWidgetToolCallDisplayFeature = {
2295
2305
  expandable?: boolean;
2296
2306
  /**
2297
2307
  * Animation mode applied to the tool call header text while the tool is active.
2298
- * - "none" static text, no animation
2299
- * - "pulse" opacity pulse on the entire header text
2300
- * - "shimmer" monochrome opacity sweep per character
2301
- * - "shimmer-color" color gradient sweep per character
2302
- * - "rainbow" rainbow color cycle per character
2308
+ * - "none": static text, no animation
2309
+ * - "pulse": opacity pulse on the entire header text
2310
+ * - "shimmer": monochrome opacity sweep per character
2311
+ * - "shimmer-color": color gradient sweep per character
2312
+ * - "rainbow": rainbow color cycle per character
2303
2313
  * @default "none"
2304
2314
  */
2305
2315
  loadingAnimation?: AgentWidgetToolCallLoadingAnimation;
@@ -2328,11 +2338,11 @@ type AgentWidgetReasoningDisplayFeature = {
2328
2338
  /**
2329
2339
  * Animation mode applied to the reasoning header text while reasoning is active.
2330
2340
  * Reuses the same modes as tool call animations.
2331
- * - "none" static text, no animation
2332
- * - "pulse" opacity pulse on the entire header text
2333
- * - "shimmer" monochrome opacity sweep per character
2334
- * - "shimmer-color" color gradient sweep per character
2335
- * - "rainbow" rainbow color cycle per character
2341
+ * - "none": static text, no animation
2342
+ * - "pulse": opacity pulse on the entire header text
2343
+ * - "shimmer": monochrome opacity sweep per character
2344
+ * - "shimmer-color": color gradient sweep per character
2345
+ * - "rainbow": rainbow color cycle per character
2336
2346
  * @default "none"
2337
2347
  */
2338
2348
  loadingAnimation?: AgentWidgetToolCallLoadingAnimation;
@@ -2341,34 +2351,34 @@ type AgentWidgetReasoningDisplayFeature = {
2341
2351
  * Reveal animation applied to assistant message text while it is streaming.
2342
2352
  *
2343
2353
  * Built-in types always available:
2344
- * - `none` text appears as tokens arrive (default).
2345
- * - `typewriter` characters fade in with a blinking caret.
2346
- * - `pop-bubble` the bubble scales in; text streams normally afterward.
2347
- * - `letter-rise` per-char translateY + fade reveal.
2348
- * - `word-fade` per-word blur + translateY fade-in.
2354
+ * - `none`: text appears as tokens arrive (default).
2355
+ * - `typewriter`: characters fade in with a blinking caret.
2356
+ * - `pop-bubble`: the bubble scales in; text streams normally afterward.
2357
+ * - `letter-rise`: per-char translateY + fade reveal.
2358
+ * - `word-fade`: per-word blur + translateY fade-in.
2349
2359
  *
2350
2360
  * Subpath plugins (import from `@runtypelabs/persona/animations/*` to register):
2351
2361
  * - `wipe`, `glyph-cycle`.
2352
2362
  *
2353
- * Custom types are allowed register a plugin with any string name and
2363
+ * Custom types are allowed: register a plugin with any string name and
2354
2364
  * reference it by that name in `type`.
2355
2365
  */
2356
2366
  type AgentWidgetStreamAnimationBuiltinType = "none" | "typewriter" | "word-fade" | "letter-rise" | "glyph-cycle" | "wipe" | "pop-bubble";
2357
2367
  type AgentWidgetStreamAnimationType = AgentWidgetStreamAnimationBuiltinType | (string & {});
2358
2368
  /**
2359
2369
  * Placeholder shown inside a streaming assistant bubble before the first token arrives.
2360
- * - `none` use the default typing-dots indicator (existing behavior).
2361
- * - `skeleton` shimmer bars, replaced by streaming content once it starts.
2370
+ * - `none`: use the default typing-dots indicator (existing behavior).
2371
+ * - `skeleton`: shimmer bars, replaced by streaming content once it starts.
2362
2372
  */
2363
2373
  type AgentWidgetStreamAnimationPlaceholder = "none" | "skeleton";
2364
2374
  /**
2365
2375
  * How much of the accumulated streaming content to display while tokens are
2366
2376
  * still arriving. Trimming to a boundary means in-progress words or lines
2367
- * stay hidden until they complete useful for animations that benefit from
2377
+ * stay hidden until they complete: useful for animations that benefit from
2368
2378
  * unit-complete reveals (e.g. wipe, glyph-cycle).
2369
- * - `none` show every character as it arrives (default).
2370
- * - `word` trim to the last whitespace boundary.
2371
- * - `line` trim to the last newline boundary.
2379
+ * - `none`: show every character as it arrives (default).
2380
+ * - `word`: trim to the last whitespace boundary.
2381
+ * - `line`: trim to the last newline boundary.
2372
2382
  */
2373
2383
  type AgentWidgetStreamAnimationBuffer = "none" | "word" | "line";
2374
2384
  /**
@@ -2399,7 +2409,7 @@ type StreamAnimationContext = {
2399
2409
  * widget's style host.
2400
2410
  * - For each streaming assistant message whose `type` matches `name`, the
2401
2411
  * widget applies `containerClass` / `bubbleClass`, wraps text per `wrap`,
2402
- * and if `useCaret` is true appends a blinking caret.
2412
+ * and, if `useCaret` is true, appends a blinking caret.
2403
2413
  * - Hooks fire after the live DOM is morphed; plugins use stable element IDs
2404
2414
  * and `data-preserve-animation` to safely mutate per-char or per-word spans
2405
2415
  * without idiomorph clobbering in-flight work.
@@ -2415,7 +2425,7 @@ type StreamAnimationPlugin = {
2415
2425
  wrap?: "none" | "char" | "word";
2416
2426
  /**
2417
2427
  * HTML tags whose descendant text is skipped during wrapping. Defaults to
2418
- * `["pre", "code", "a", "script", "style"]` useful for keeping code
2428
+ * `["pre", "code", "a", "script", "style"]`: useful for keeping code
2419
2429
  * blocks legible and link click-targets intact. Plugins that want to
2420
2430
  * animate characters inside inline code (e.g. `glyph-cycle`) can narrow
2421
2431
  * the list.
@@ -2444,8 +2454,7 @@ type StreamAnimationPlugin = {
2444
2454
  /**
2445
2455
  * Report whether the plugin still has in-flight animation work for a
2446
2456
  * message. When `true`, the widget keeps rendering the message in its
2447
- * "streaming-animated" mode even after `message.streaming` flips false
2448
- * preventing the final non-animated render from yanking the rug out from
2457
+ * "streaming-animated" mode even after `message.streaming` flips false: * preventing the final non-animated render from yanking the rug out from
2449
2458
  * under unfinished per-char cycles or reveals.
2450
2459
  */
2451
2460
  isAnimating?: (message: AgentWidgetMessage) => boolean;
@@ -2458,7 +2467,7 @@ type AgentWidgetStreamAnimationFeature = {
2458
2467
  /**
2459
2468
  * Per-unit animation duration (ms) for `typewriter`, `letter-rise`, `word-fade`,
2460
2469
  * and per-unit plugin animations. Each arriving character/word animates from
2461
- * invisible to visible over this duration, independent of its position the
2470
+ * invisible to visible over this duration, independent of its position: the
2462
2471
  * streaming cadence itself provides the visible stagger.
2463
2472
  * @default 120
2464
2473
  */
@@ -2518,7 +2527,7 @@ type AgentWidgetFeatureFlags = {
2518
2527
  * Built-in `suggest_replies` quick-reply chips. When the assistant invokes
2519
2528
  * the tool, the widget shows the suggestions as tappable chips above the
2520
2529
  * composer (reusing the suggestion-chips surface) and immediately resumes
2521
- * the execution fire-and-forget, no user input awaited.
2530
+ * the execution: fire-and-forget, no user input awaited.
2522
2531
  */
2523
2532
  suggestReplies?: AgentWidgetSuggestRepliesFeature;
2524
2533
  };
@@ -2532,14 +2541,14 @@ type AgentWidgetFeatureFlags = {
2532
2541
  type AgentWidgetSuggestRepliesFeature = {
2533
2542
  /**
2534
2543
  * Enable the feature. Defaults to true. When false, `suggest_replies`
2535
- * renders as a regular tool bubble and is NOT auto-resumed only set this
2544
+ * renders as a regular tool bubble and is NOT auto-resumed: only set this
2536
2545
  * with no server-side `suggest_replies` declaration, or the execution
2537
2546
  * parks awaiting a resume that never comes.
2538
2547
  */
2539
2548
  enabled?: boolean;
2540
2549
  /**
2541
2550
  * Advertise the built-in `suggest_replies` tool to the agent on every
2542
- * dispatch via `clientTools[]` no server-side `runtimeTools` declaration
2551
+ * dispatch via `clientTools[]`: no server-side `runtimeTools` declaration
2543
2552
  * needed. Defaults to `false`: flows that already declare the tool via
2544
2553
  * `runtimeTools` would otherwise present it to the model twice. Ignored
2545
2554
  * when `enabled` is `false`.
@@ -2555,7 +2564,7 @@ type AskUserQuestionOption = {
2555
2564
  label: string;
2556
2565
  /** Optional long-form description (shown as a subtitle on tap-hover). */
2557
2566
  description?: string;
2558
- /** Optional rich preview reserved for future rendering; ignored in v1. */
2567
+ /** Optional rich preview: reserved for future rendering; ignored in v1. */
2559
2568
  preview?: string;
2560
2569
  };
2561
2570
  /**
@@ -2607,14 +2616,14 @@ type AgentWidgetAskUserQuestionFeature = {
2607
2616
  enabled?: boolean;
2608
2617
  /**
2609
2618
  * Advertise the built-in `ask_user_question` tool to the agent on every
2610
- * dispatch via `clientTools[]` no server-side `runtimeTools` declaration
2619
+ * dispatch via `clientTools[]`: no server-side `runtimeTools` declaration
2611
2620
  * needed. The tool ships with a model-facing description and JSON schema
2612
2621
  * matching {@link AskUserQuestionPayload}; when the model calls it, the
2613
2622
  * existing answer-pill sheet renders and the answer resumes the execution.
2614
2623
  *
2615
2624
  * Defaults to `false`: flows that already declare `ask_user_question` via
2616
2625
  * `runtimeTools` would otherwise present the tool to the model twice.
2617
- * Ignored when `enabled` is `false` never offer the agent a question
2626
+ * Ignored when `enabled` is `false`: never offer the agent a question
2618
2627
  * tool the widget can't render an answer UI for.
2619
2628
  */
2620
2629
  expose?: boolean;
@@ -2635,17 +2644,17 @@ type AgentWidgetAskUserQuestionFeature = {
2635
2644
  /**
2636
2645
  * In grouped (multi-question) mode, auto-advance to the next page after a
2637
2646
  * single-select pill pick or free-text submit on intermediate pages.
2638
- * Defaults to `true`. The final page never auto-submits users always
2647
+ * Defaults to `true`. The final page never auto-submits: users always
2639
2648
  * confirm with an explicit "Submit all" click. Multi-select pages always
2640
2649
  * require an explicit Next regardless of this setting.
2641
2650
  */
2642
2651
  groupedAutoAdvance?: boolean;
2643
2652
  /**
2644
2653
  * Visual layout for the option list.
2645
- * - `"rows"` (default) full-width stacked rows with always-visible
2654
+ * - `"rows"` (default): full-width stacked rows with always-visible
2646
2655
  * descriptions, right-edge number badges (single-select) or checkboxes
2647
2656
  * (multi-select), and an always-visible inline "Other" input.
2648
- * - `"pills"` legacy compact pill list with horizontal wrap; description
2657
+ * - `"pills"`: legacy compact pill list with horizontal wrap; description
2649
2658
  * surfaces as a tooltip and the "Other…" pill expands on click.
2650
2659
  */
2651
2660
  layout?: "rows" | "pills";
@@ -2813,7 +2822,7 @@ type AgentWidgetDockConfig = {
2813
2822
  *
2814
2823
  * - Set a CSS length (e.g. `"600px"`, `"80vh"`) to override the cap.
2815
2824
  * - Set `false` to disable the guard entirely (the panel then sizes purely
2816
- * from the surrounding layout make sure your page provides a definite
2825
+ * from the surrounding layout: make sure your page provides a definite
2817
2826
  * height all the way down to the dock target's parent).
2818
2827
  *
2819
2828
  * @default "100dvh"
@@ -2887,7 +2896,7 @@ type AgentWidgetComposerBarConfig = {
2887
2896
  */
2888
2897
  modalMaxHeight?: string;
2889
2898
  /**
2890
- * Configuration for the "peek" banner the chrome-less row above the
2899
+ * Configuration for the "peek" banner: the chrome-less row above the
2891
2900
  * collapsed pill that previews the most recent assistant message.
2892
2901
  */
2893
2902
  peek?: AgentWidgetComposerBarPeekConfig;
@@ -2902,7 +2911,7 @@ type AgentWidgetComposerBarConfig = {
2902
2911
  * - Otherwise the peek inherits from `features.streamAnimation`.
2903
2912
  *
2904
2913
  * Per-surface carve-outs:
2905
- * - `bubbleClass` from a plugin (used by `pop-bubble`) is ignored the peek
2914
+ * - `bubbleClass` from a plugin (used by `pop-bubble`) is ignored: the peek
2906
2915
  * has no bubble analog.
2907
2916
  * - `containerClass`, `wrap` ("char" | "word"), `useCaret`, `placeholder`
2908
2917
  * ("skeleton"), `buffer` ("word" | "line"), `speed`, `duration`, and
@@ -3148,6 +3157,11 @@ type AgentWidgetVoiceRecognitionConfig = {
3148
3157
  /** Button border color while speaking. Inherits idle borderColor if not set */
3149
3158
  speakingBorderColor?: string;
3150
3159
  autoResume?: boolean | "assistant";
3160
+ /**
3161
+ * Called with per-turn latency metrics on the realtime (`runtype`) voice path.
3162
+ * Fires once per turn after the reply completes.
3163
+ */
3164
+ onMetrics?: (metrics: VoiceMetrics) => void;
3151
3165
  provider?: {
3152
3166
  type: 'browser' | 'runtype' | 'custom';
3153
3167
  browser?: {
@@ -3156,15 +3170,31 @@ type AgentWidgetVoiceRecognitionConfig = {
3156
3170
  };
3157
3171
  runtype?: {
3158
3172
  agentId: string;
3159
- clientToken: string;
3173
+ /** Defaults to the widget's `clientToken` when omitted. */
3174
+ clientToken?: string;
3175
+ /** Defaults to the widget's `apiUrl` when omitted. */
3160
3176
  host?: string;
3161
3177
  voiceId?: string;
3162
- /** Duration of silence (ms) before auto-stopping recording. Default: 2000 */
3178
+ /**
3179
+ * Optional custom streaming playback engine. Defaults to the built-in
3180
+ * `AudioPlaybackManager`. Import `createWorkletPlaybackEngine` from
3181
+ * `@runtypelabs/persona/voice-worklet-player` for a jitter-buffered engine.
3182
+ */
3183
+ createPlaybackEngine?: () => VoicePlaybackEngine | Promise<VoicePlaybackEngine>;
3184
+ /** @deprecated No-op on the realtime path: the server's STT owns turn-taking. */
3163
3185
  pauseDuration?: number;
3164
- /** RMS volume threshold below which counts as silence. Default: 0.01 */
3186
+ /** @deprecated No-op on the realtime path: the server's STT owns turn-taking. */
3165
3187
  silenceThreshold?: number;
3166
3188
  };
3167
- custom?: any;
3189
+ /**
3190
+ * Bring-your-own voice provider. Set `type: 'custom'` and pass either a
3191
+ * ready {@link VoiceProvider} instance or a `() => VoiceProvider` factory
3192
+ * (the factory defers construction until voice is set up). STT-style
3193
+ * providers deliver a final transcript via `onResult` (sent as a user
3194
+ * message); full-duplex providers can drive `onTranscript`/`onMetrics` like
3195
+ * the realtime `runtype` provider.
3196
+ */
3197
+ custom?: VoiceProvider | (() => VoiceProvider);
3168
3198
  };
3169
3199
  };
3170
3200
  /**
@@ -3187,18 +3217,68 @@ type TextToSpeechConfig = {
3187
3217
  enabled: boolean;
3188
3218
  /**
3189
3219
  * TTS provider.
3190
- * - `'browser'` Use the Web Speech API for all assistant messages (default).
3191
- * - `'runtype'` Server handles TTS for voice interactions.
3192
- * Set `browserFallback: true` to also speak text-typed responses via the browser.
3220
+ * - `'browser'`: Use the Web Speech API for all assistant messages (default).
3221
+ * - `'runtype'`: Use Runtype-hosted TTS. The realtime voice path speaks
3222
+ * replies during a voice call, and the per-message "Read aloud" button (and
3223
+ * auto-speak of text-typed replies) stream from Runtype's
3224
+ * `POST {host}/v1/agents/:agentId/speak` endpoint via the built-in
3225
+ * {@link SpeechEngine}. `host`/`agentId`/`clientToken` are derived from the
3226
+ * widget config (see `agentId`/`host` below); unless `browserFallback` is
3227
+ * `false`, a missing endpoint or transient failure falls back to the browser
3228
+ * voice so the button is never broken.
3193
3229
  */
3194
3230
  provider?: 'browser' | 'runtype';
3195
3231
  /**
3196
- * When `provider` is `'runtype'`, fall back to browser TTS for assistant
3197
- * messages that the server didn't already speak (e.g. text-typed messages).
3232
+ * When `provider` is `'runtype'`:
3233
+ * - Read-aloud / auto-speak: acts as a safety net — if Runtype TTS is
3234
+ * unreachable (no endpoint yet, network/auth failure) or the Runtype config
3235
+ * is incomplete, speech falls back to the browser Web Speech API instead of
3236
+ * erroring. Set to `false` to surface Runtype errors instead. **Defaults to
3237
+ * on for this path.**
3238
+ * - Auto-speak of text-typed replies also requires this to be truthy (the
3239
+ * realtime voice path already speaks voice-call replies).
3240
+ *
3198
3241
  * Has no effect when provider is `'browser'` (browser TTS is always used).
3199
- * @default false
3200
3242
  */
3201
3243
  browserFallback?: boolean;
3244
+ /**
3245
+ * When `provider` is `'runtype'`, the agent whose configured voice synthesizes
3246
+ * read-aloud / auto-speak audio. Falls back to
3247
+ * `voiceRecognition.provider.runtype.agentId` when omitted. Required (here or
3248
+ * there) for Runtype TTS to activate; without it the browser voice is used.
3249
+ */
3250
+ agentId?: string;
3251
+ /**
3252
+ * When `provider` is `'runtype'`, the Runtype API host (e.g.
3253
+ * `https://api.runtype.com`). Defaults to the widget's `apiUrl`.
3254
+ */
3255
+ host?: string;
3256
+ /**
3257
+ * When `provider` is `'runtype'`, audio (ms) the streaming player buffers
3258
+ * before the first sample and after an underrun. Lower = snappier start;
3259
+ * higher = rides out delivery hiccups. Default 200. Applies to the default
3260
+ * in-bundle player; a custom `createPlaybackEngine` manages its own prebuffer.
3261
+ */
3262
+ prebufferMs?: number;
3263
+ /**
3264
+ * When `provider` is `'runtype'`, an optional factory for the streaming PCM
3265
+ * player that synthesizes read-aloud / auto-speak audio. Defaults to the
3266
+ * in-bundle, main-thread `AudioPlaybackManager` (smooth for steady Runtype
3267
+ * streams, no AudioWorklet — keeps the widget bundle lean).
3268
+ *
3269
+ * For the higher-quality, jitter-buffered AudioWorklet player, import
3270
+ * `createPcmStreamPlayer` from `@runtypelabs/persona/voice-worklet-player` and
3271
+ * pass it here — it then ships in your bundle, not Persona's:
3272
+ *
3273
+ * @example
3274
+ * import { createPcmStreamPlayer } from '@runtypelabs/persona/voice-worklet-player'
3275
+ * textToSpeech: {
3276
+ * enabled: true,
3277
+ * provider: 'runtype',
3278
+ * createPlaybackEngine: () => createPcmStreamPlayer({ prebufferMs: 400 }),
3279
+ * }
3280
+ */
3281
+ createPlaybackEngine?: () => PcmStreamPlayer | Promise<PcmStreamPlayer>;
3202
3282
  /** Voice name to use for browser TTS (e.g., 'Google US English'). If not found, uses auto-detect. */
3203
3283
  voice?: string;
3204
3284
  /**
@@ -3217,7 +3297,177 @@ type TextToSpeechConfig = {
3217
3297
  rate?: number;
3218
3298
  /** Speech pitch (0 - 2). Default: 1 */
3219
3299
  pitch?: number;
3300
+ /**
3301
+ * Factory for a custom {@link SpeechEngine}, used by both the auto-speak path
3302
+ * and the per-message "Read aloud" action. When omitted, the browser Web
3303
+ * Speech API engine is used. Return a hosted engine here to use server-side
3304
+ * TTS (e.g. Runtype) — such an engine can stream audio through the realtime
3305
+ * voice {@link VoicePlaybackEngine}. May be async (it is resolved on first
3306
+ * playback, inside the user gesture).
3307
+ */
3308
+ createEngine?: () => SpeechEngine | Promise<SpeechEngine>;
3220
3309
  };
3310
+ /** A unit of speech handed to a {@link SpeechEngine}. */
3311
+ interface SpeechRequest {
3312
+ /** Plain text to speak. The widget strips Markdown before calling the engine. */
3313
+ text: string;
3314
+ /** Preferred voice identifier (engine-specific; browser = voice name). */
3315
+ voice?: string;
3316
+ /** Speech rate (engine-specific range; browser: 0.1–10, default 1). */
3317
+ rate?: number;
3318
+ /** Speech pitch (engine-specific range; browser: 0–2, default 1). */
3319
+ pitch?: number;
3320
+ }
3321
+ /** Lifecycle callbacks a {@link SpeechEngine} invokes during playback. */
3322
+ interface SpeechCallbacks {
3323
+ /** Audio has started playing. */
3324
+ onStart?: () => void;
3325
+ /** Playback finished naturally (or was canceled). */
3326
+ onEnd?: () => void;
3327
+ /** Playback failed. */
3328
+ onError?: (error: Error) => void;
3329
+ }
3330
+ /**
3331
+ * Pluggable text-to-speech engine behind the "Read aloud" message action.
3332
+ *
3333
+ * The widget ships a browser Web Speech API engine by default. Provide a hosted
3334
+ * engine (Runtype TTS, ElevenLabs, a server proxy, …) by implementing this
3335
+ * interface and returning it from {@link TextToSpeechConfig.createEngine}; a
3336
+ * server engine can stream audio into the realtime voice
3337
+ * {@link VoicePlaybackEngine}.
3338
+ */
3339
+ interface SpeechEngine {
3340
+ /** Stable identifier, e.g. `"browser"`, `"runtype"`, or a custom id. */
3341
+ readonly id: string;
3342
+ /**
3343
+ * Whether {@link pause}/{@link resume} are supported. When `false`, the UI
3344
+ * offers play/stop only (tapping a playing message stops it).
3345
+ */
3346
+ readonly supportsPause: boolean;
3347
+ /** Begin speaking. Drives the lifecycle through `callbacks`. */
3348
+ speak(request: SpeechRequest, callbacks: SpeechCallbacks): void;
3349
+ /** Pause playback (no-op if unsupported). */
3350
+ pause(): void;
3351
+ /** Resume paused playback (no-op if unsupported). */
3352
+ resume(): void;
3353
+ /** Stop playback and discard any queued audio. */
3354
+ stop(): void;
3355
+ /** Optional teardown of engine resources. */
3356
+ destroy?(): void;
3357
+ }
3358
+ /**
3359
+ * Voice recognition result with transcript and optional audio
3360
+ */
3361
+ type VoiceResult = {
3362
+ text: string;
3363
+ transcript?: string;
3364
+ audio?: {
3365
+ base64: string;
3366
+ format: 'wav' | 'mp3' | 'ogg' | 'webm';
3367
+ sampleRate: number;
3368
+ duration: number;
3369
+ };
3370
+ confidence?: number;
3371
+ provider: 'runtype' | 'browser' | 'custom';
3372
+ };
3373
+ /**
3374
+ * Voice provider status states
3375
+ */
3376
+ type VoiceStatus = 'disconnected' | 'connected' | 'listening' | 'processing' | 'speaking' | 'error' | 'idle';
3377
+ /**
3378
+ * Per-turn latency metrics emitted by the realtime voice path.
3379
+ * The wire frame is snake_case (`metrics`); decoded to camelCase at the
3380
+ * provider boundary before being surfaced through `onMetrics`.
3381
+ */
3382
+ type VoiceMetrics = {
3383
+ /** LLM generation time (ms). */
3384
+ llmMs?: number;
3385
+ /** Text-to-speech synthesis time (ms). */
3386
+ ttsMs?: number;
3387
+ /** Time to first audio frame (ms). */
3388
+ firstAudioMs?: number;
3389
+ /** Total turn time (ms). */
3390
+ totalMs?: number;
3391
+ };
3392
+ /**
3393
+ * Streaming PCM playback engine used by the realtime voice provider.
3394
+ *
3395
+ * The provider feeds raw PCM16 LE mono @ 24kHz (WAV header already stripped)
3396
+ * via `enqueue`, signals end-of-reply with `markStreamEnd`, and learns when
3397
+ * playback has fully drained via `onFinished`. The default implementation is
3398
+ * `AudioPlaybackManager`; an optional jitter-buffered AudioWorklet engine ships
3399
+ * from `@runtypelabs/persona/voice-worklet-player` and can be injected via the
3400
+ * `runtype.createPlaybackEngine` config hook.
3401
+ */
3402
+ interface VoicePlaybackEngine {
3403
+ /** Enqueue a raw PCM16 LE mono @ 24kHz chunk (no WAV header). */
3404
+ enqueue(pcm: Uint8Array): void;
3405
+ /** Signal that no more chunks will arrive for the current reply. */
3406
+ markStreamEnd(): void;
3407
+ /** Immediately stop playback and discard queued audio. */
3408
+ flush(): void;
3409
+ /** Register a callback fired once all queued audio has finished playing. */
3410
+ onFinished(callback: () => void): void;
3411
+ /** Release all audio resources. */
3412
+ destroy(): Promise<void> | void;
3413
+ }
3414
+ /**
3415
+ * A {@link VoicePlaybackEngine} that also supports pause/resume. Returned by
3416
+ * `createPcmStreamPlayer` (`@runtypelabs/persona/voice-worklet-player`): a
3417
+ * jitter-buffered AudioWorklet player for raw PCM16 / 24 kHz / mono streams.
3418
+ * Reuse it inside a hosted {@link SpeechEngine} to get gapless playback with a
3419
+ * configurable prebuffer and graceful underrun handling — feed each streamed
3420
+ * chunk to {@link VoicePlaybackEngine.enqueue} and the worklet does the rest.
3421
+ */
3422
+ interface PcmStreamPlayer extends VoicePlaybackEngine {
3423
+ /** Pause playback; the audio clock suspends and {@link resume} continues in place. */
3424
+ pause(): void;
3425
+ /** Resume playback after {@link pause}. */
3426
+ resume(): void;
3427
+ /**
3428
+ * Register a callback fired once audible playback actually begins — i.e. the
3429
+ * prebuffer waterline filled and the first sample reached the output. Use this
3430
+ * (rather than "first chunk enqueued") to flip a UI from loading to playing, so
3431
+ * the spinner holds through the prebuffer. Fires once per playback session
3432
+ * (cleared by {@link VoicePlaybackEngine.flush}); a mid-reply underrun re-buffer
3433
+ * does not re-fire it.
3434
+ */
3435
+ onStarted(callback: () => void): void;
3436
+ }
3437
+ /**
3438
+ * Voice provider interface
3439
+ * Abstract interface for all voice providers in the Persona SDK
3440
+ */
3441
+ interface VoiceProvider {
3442
+ type: 'browser' | 'runtype' | 'custom';
3443
+ connect(): Promise<void>;
3444
+ disconnect(): Promise<void>;
3445
+ startListening(): Promise<void>;
3446
+ stopListening(): Promise<void>;
3447
+ onResult(callback: (result: VoiceResult) => void): void;
3448
+ onError(callback: (error: Error) => void): void;
3449
+ onStatusChange(callback: (status: VoiceStatus) => void): void;
3450
+ /** Register a callback fired when recording stops and audio is about to be sent */
3451
+ onProcessingStart?(callback: () => void): void;
3452
+ /**
3453
+ * Register a callback for incremental transcript updates during a voice turn.
3454
+ * `isFinal=false` is a live interim update (user partials, or assistant deltas
3455
+ * on providers that stream them); `isFinal=true` finalizes that role's text.
3456
+ * On the realtime `runtype` path, interim updates fire for the `user` only and
3457
+ * the `assistant` arrives as a single final.
3458
+ */
3459
+ onTranscript?(callback: (role: 'user' | 'assistant', text: string, isFinal: boolean) => void): void;
3460
+ /** Register a callback for per-turn latency metrics (realtime path). */
3461
+ onMetrics?(callback: (metrics: VoiceMetrics) => void): void;
3462
+ /** Returns the current interruption mode (only meaningful for Runtype provider) */
3463
+ getInterruptionMode?(): "none" | "cancel" | "barge-in";
3464
+ /** Returns true if the barge-in mic stream is alive (hot mic between turns) */
3465
+ isBargeInActive?(): boolean;
3466
+ /** Tear down the barge-in mic pipeline: "hang up" the always-on mic */
3467
+ deactivateBargeIn?(): Promise<void>;
3468
+ /** Stop playback / cancel in-flight request without starting recording */
3469
+ stopPlayback?(): void;
3470
+ }
3221
3471
  /**
3222
3472
  * Extra context for an approval decision, surfaced to `onDecision` and to the
3223
3473
  * `approve`/`deny` callbacks passed to the `renderApproval` plugin hook.
@@ -3226,7 +3476,7 @@ type AgentWidgetApprovalDecisionOptions = {
3226
3476
  /**
3227
3477
  * The user chose a "remember this" affordance (e.g. an "Always allow"
3228
3478
  * button) rather than a one-time decision. The widget resolves the *current*
3229
- * approval identically whether or not this is set an approval bubble is a
3479
+ * approval identically whether or not this is set: an approval bubble is a
3230
3480
  * single binary gate (`approved`/`denied`). Persisting a don't-ask-again
3231
3481
  * policy for *future* approvals (auto-resolving them, or not surfacing them)
3232
3482
  * is the integrator's responsibility, typically inside `onDecision`.
@@ -3280,9 +3530,9 @@ type AgentWidgetApprovalConfig = {
3280
3530
  /**
3281
3531
  * How the technical details (the tool's agent-facing description and the
3282
3532
  * raw parameters JSON) are presented:
3283
- * - `"collapsed"` (default) hidden behind a "Show details" toggle
3284
- * - `"expanded"` visible, with the toggle available to hide them
3285
- * - `"hidden"` never rendered
3533
+ * - `"collapsed"` (default): hidden behind a "Show details" toggle
3534
+ * - `"expanded"`: visible, with the toggle available to hide them
3535
+ * - `"hidden"`: never rendered
3286
3536
  */
3287
3537
  detailsDisplay?: "collapsed" | "expanded" | "hidden";
3288
3538
  /** Label for the toggle that reveals the technical details */
@@ -3304,7 +3554,7 @@ type AgentWidgetApprovalConfig = {
3304
3554
  displayTitle?: string;
3305
3555
  /**
3306
3556
  * Agent-authored justification for this specific call, when the agent
3307
- * provided one. It is the agent's own claim if you fold it into the
3557
+ * provided one. It is the agent's own claim: if you fold it into the
3308
3558
  * summary, keep it attributed to the agent.
3309
3559
  */
3310
3560
  reason?: string;
@@ -3410,7 +3660,7 @@ type AgentWidgetToolCallConfig = {
3410
3660
  *
3411
3661
  * **Placeholders:** `{toolName}` (tool name), `{duration}` (live-updating elapsed time).
3412
3662
  *
3413
- * **Inline formatting:** `~dim~`, `*italic*`, `**bold**` parsed at render time and
3663
+ * **Inline formatting:** `~dim~`, `*italic*`, `**bold**`: parsed at render time and
3414
3664
  * applied as styled `<span>` elements. Works with all animation modes.
3415
3665
  *
3416
3666
  * When not set, falls back to the current `collapsedMode` behavior.
@@ -3423,7 +3673,7 @@ type AgentWidgetToolCallConfig = {
3423
3673
  *
3424
3674
  * **Placeholders:** `{toolName}` (tool name), `{duration}` (final elapsed time).
3425
3675
  *
3426
- * **Inline formatting:** `~dim~`, `*italic*`, `**bold**` same syntax as `activeTextTemplate`.
3676
+ * **Inline formatting:** `~dim~`, `*italic*`, `**bold**`: same syntax as `activeTextTemplate`.
3427
3677
  *
3428
3678
  * When not set, falls back to the existing "Used tool for X seconds" text.
3429
3679
  * @example "Finished {toolName} ~{duration}~"
@@ -3484,7 +3734,7 @@ type AgentWidgetReasoningConfig = {
3484
3734
  *
3485
3735
  * **Placeholders:** `{duration}` (live-updating elapsed time).
3486
3736
  *
3487
- * **Inline formatting:** `~dim~`, `*italic*`, `**bold**` parsed at render time.
3737
+ * **Inline formatting:** `~dim~`, `*italic*`, `**bold**`: parsed at render time.
3488
3738
  *
3489
3739
  * When not set, falls back to the default "Thinking..." text.
3490
3740
  * @example "Thinking... ~{duration}~"
@@ -3495,7 +3745,7 @@ type AgentWidgetReasoningConfig = {
3495
3745
  *
3496
3746
  * **Placeholders:** `{duration}` (final elapsed time).
3497
3747
  *
3498
- * **Inline formatting:** `~dim~`, `*italic*`, `**bold**` same syntax as `activeTextTemplate`.
3748
+ * **Inline formatting:** `~dim~`, `*italic*`, `**bold**`: same syntax as `activeTextTemplate`.
3499
3749
  *
3500
3750
  * When not set, falls back to the default "Thought for X seconds" text.
3501
3751
  * @example "Thought for ~{duration}~"
@@ -3594,7 +3844,7 @@ type AgentWidgetSSEEventResult = {
3594
3844
  done?: boolean;
3595
3845
  /** Error message if an error occurred */
3596
3846
  error?: string;
3597
- /** Text segment identity when this changes, a new assistant message bubble is created */
3847
+ /** Text segment identity: when this changes, a new assistant message bubble is created */
3598
3848
  partId?: string;
3599
3849
  } | null;
3600
3850
  /**
@@ -4574,7 +4824,7 @@ type AgentWidgetConfig = {
4574
4824
  * Per-stop-reason copy for the inline notice rendered on assistant
4575
4825
  * bubbles when the runtime reports a non-natural stop (e.g. the agent
4576
4826
  * loop hit `max_tool_calls` and was cut off mid-loop). Each key is
4577
- * optional keys you omit fall back to the built-in defaults. Set a
4827
+ * optional: keys you omit fall back to the built-in defaults. Set a
4578
4828
  * key to an empty string to suppress the notice for that reason.
4579
4829
  */
4580
4830
  stopReasonNotice?: Partial<Record<StopReasonKind, string>>;
@@ -4667,12 +4917,12 @@ type AgentWidgetConfig = {
4667
4917
  */
4668
4918
  approval?: AgentWidgetApprovalConfig | false;
4669
4919
  /**
4670
- * WebMCP consume page-registered tools (`document.modelContext.registerTool`).
4920
+ * WebMCP: consume page-registered tools (`document.modelContext.registerTool`).
4671
4921
  * When `enabled`, the widget installs `@mcp-b/webmcp-polyfill`, snapshots the
4672
4922
  * registry on every dispatch, ships it as `clientTools[]`, and executes
4673
4923
  * returned `webmcp:*` tool calls with confirm-by-default gating.
4674
4924
  *
4675
- * Server-side policy on the chat surface is the source of truth these
4925
+ * Server-side policy on the chat surface is the source of truth: these
4676
4926
  * fields layer on top.
4677
4927
  *
4678
4928
  * @example
@@ -4706,7 +4956,7 @@ type AgentWidgetConfig = {
4706
4956
  * This hook runs synchronously and must return the (potentially modified) state.
4707
4957
  *
4708
4958
  * Returning `{ state, open: true }` also signals that the widget panel should
4709
- * open after initialization useful when injecting a post-navigation message
4959
+ * open after initialization: useful when injecting a post-navigation message
4710
4960
  * that the user should immediately see.
4711
4961
  *
4712
4962
  * @example
@@ -5025,7 +5275,7 @@ type AgentWidgetConfig = {
5025
5275
  * Setting this to `false` is the explicit kill-switch: it disables UI-state
5026
5276
  * persistence **and** message-history persistence. When `false`, any
5027
5277
  * `storageAdapter` you configure is ignored and the default localStorage
5028
- * adapter is not created no chat history is read or written. Pass `true`
5278
+ * adapter is not created: no chat history is read or written. Pass `true`
5029
5279
  * (or omit) to keep the default behavior of persisting messages via the
5030
5280
  * configured `storageAdapter` (or the built-in localStorage adapter).
5031
5281
  *
@@ -5054,7 +5304,7 @@ type AgentWidgetConfig = {
5054
5304
  *
5055
5305
  * @example
5056
5306
  * ```typescript
5057
- * // Ephemeral widget no message history written anywhere
5307
+ * // Ephemeral widget: no message history written anywhere
5058
5308
  * config: {
5059
5309
  * persistState: false
5060
5310
  * }
@@ -5121,7 +5371,7 @@ type AgentWidgetApproval = {
5121
5371
  * Agent-authored justification for this specific call (the agent's own
5122
5372
  * claim about its intent, extracted server-side from the reserved
5123
5373
  * `_approvalReason` parameter). Render it attributed to the agent and as
5124
- * plain text it is approver context, not a system statement.
5374
+ * plain text: it is approver context, not a system statement.
5125
5375
  */
5126
5376
  reason?: string;
5127
5377
  parameters?: unknown;
@@ -5131,16 +5381,16 @@ type AgentWidgetMessageVariant = "assistant" | "reasoning" | "tool" | "approval"
5131
5381
  /**
5132
5382
  * Per-turn / per-step stop reason emitted by the runtime on
5133
5383
  * `agent_turn_complete` and `step_complete` SSE events. The vocabulary is
5134
- * owned by the upstream Runtype API do not extend without coordination.
5384
+ * owned by the upstream Runtype API: do not extend without coordination.
5135
5385
  *
5136
- * - `end_turn` natural completion (no affordance needed)
5137
- * - `max_tool_calls` agent loop tripped the configured tool-call ceiling
5138
- * - `length` provider hit max output tokens
5139
- * - `content_filter` provider content filter intervened
5140
- * - `error` provider/runtime error (prefer existing error rendering)
5141
- * - `unknown` explicitly reported but uninformative
5386
+ * - `end_turn`: natural completion (no affordance needed)
5387
+ * - `max_tool_calls`: agent loop tripped the configured tool-call ceiling
5388
+ * - `length`: provider hit max output tokens
5389
+ * - `content_filter`: provider content filter intervened
5390
+ * - `error`: provider/runtime error (prefer existing error rendering)
5391
+ * - `unknown`: explicitly reported but uninformative
5142
5392
  *
5143
- * Absent (`undefined`) means "not reported" distinct from `'unknown'`.
5393
+ * Absent (`undefined`) means "not reported": distinct from `'unknown'`.
5144
5394
  */
5145
5395
  type StopReasonKind = RuntypeStopReasonKind;
5146
5396
  /**
@@ -5257,11 +5507,10 @@ type PersonaArtifactRecord = {
5257
5507
  * Adapts `@mcp-b/smart-dom-reader` into Persona's enriched page-context pipeline and
5258
5508
  * exposes a ready-made {@link AgentWidgetContextProvider} you can drop into
5259
5509
  * `config.contextProviders`. This is the ONLY module that imports the smart-dom-reader
5260
- * runtime value, so the library never reaches the main bundle or the IIFE/CDN build
5261
- * importing this subpath is opt-in.
5510
+ * runtime value, so the library never reaches the main bundle or the IIFE/CDN build: * importing this subpath is opt-in.
5262
5511
  *
5263
5512
  * The library is **vendored** under `src/vendor/smart-dom-reader/` (it is mis-published
5264
- * on npm and cannot be imported by name see that directory's README). Vendoring it
5513
+ * on npm and cannot be imported by name: see that directory's README). Vendoring it
5265
5514
  * here means consumers need no extra install: the code is bundled into this entry, and
5266
5515
  * consumers who never import this subpath pay nothing.
5267
5516
  *
@@ -5306,7 +5555,7 @@ interface SmartDomContextOptions extends SmartDomAdapterOptions {
5306
5555
  /** Document to extract from. Default: the global `document`. Ignored when `root` is set. */
5307
5556
  document?: Document;
5308
5557
  /**
5309
- * Scope extraction to this element's subtree instead of the whole document parity
5558
+ * Scope extraction to this element's subtree instead of the whole document: parity
5310
5559
  * with `collectEnrichedPageContext`'s `root`. Useful to read only a main-content region
5311
5560
  * and skip site chrome (nav, sidebars). Shadow DOM inside the subtree is still pierced.
5312
5561
  * When set, `document` is ignored.