@runtypelabs/persona 3.34.0 → 3.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/README.md +11 -11
  2. package/dist/animations/glyph-cycle.d.cts +1 -1
  3. package/dist/animations/glyph-cycle.d.ts +1 -1
  4. package/dist/animations/{types-CthJFfNx.d.cts → types-DgYsuwXL.d.cts} +15 -14
  5. package/dist/animations/{types-CthJFfNx.d.ts → types-DgYsuwXL.d.ts} +15 -14
  6. package/dist/animations/wipe.d.cts +1 -1
  7. package/dist/animations/wipe.d.ts +1 -1
  8. package/dist/codegen.cjs +11 -11
  9. package/dist/codegen.js +10 -10
  10. package/dist/index.cjs +46 -44
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.cts +514 -158
  13. package/dist/index.d.ts +514 -158
  14. package/dist/index.global.js +41 -89
  15. package/dist/index.global.js.map +1 -1
  16. package/dist/index.js +46 -44
  17. package/dist/index.js.map +1 -1
  18. package/dist/install.global.js +1 -1
  19. package/dist/install.global.js.map +1 -1
  20. package/dist/launcher.global.js +2 -2
  21. package/dist/launcher.global.js.map +1 -1
  22. package/dist/markdown-parsers.js +53 -0
  23. package/dist/plugin-kit.d.cts +4 -4
  24. package/dist/plugin-kit.d.ts +4 -4
  25. package/dist/runtype-tts.js +1 -0
  26. package/dist/smart-dom-reader.cjs +1 -1
  27. package/dist/smart-dom-reader.d.cts +352 -103
  28. package/dist/smart-dom-reader.d.ts +352 -103
  29. package/dist/smart-dom-reader.js +1 -1
  30. package/dist/theme-editor-preview.cjs +203 -0
  31. package/dist/theme-editor-preview.d.cts +5748 -0
  32. package/dist/theme-editor-preview.d.ts +5748 -0
  33. package/dist/theme-editor-preview.js +203 -0
  34. package/dist/theme-editor.cjs +14 -131
  35. package/dist/theme-editor.d.cts +362 -574
  36. package/dist/theme-editor.d.ts +362 -574
  37. package/dist/theme-editor.js +14 -131
  38. package/dist/theme-reference.cjs +1 -1
  39. package/dist/theme-reference.d.cts +1 -1
  40. package/dist/theme-reference.d.ts +1 -1
  41. package/dist/theme-reference.js +1 -1
  42. package/dist/voice-worklet-player.cjs +80 -0
  43. package/dist/voice-worklet-player.d.cts +215 -0
  44. package/dist/voice-worklet-player.d.ts +215 -0
  45. package/dist/voice-worklet-player.js +80 -0
  46. package/dist/widget.css +54 -36
  47. package/package.json +16 -2
  48. package/src/animations/glyph-cycle.ts +11 -14
  49. package/src/animations/wipe.ts +1 -1
  50. package/src/ask-user-question-tool.test.ts +1 -1
  51. package/src/ask-user-question-tool.ts +6 -8
  52. package/src/client.test.ts +67 -12
  53. package/src/client.ts +40 -32
  54. package/src/codegen.test.ts +1 -1
  55. package/src/codegen.ts +1 -1
  56. package/src/components/approval-bubble.test.ts +1 -1
  57. package/src/components/approval-bubble.ts +3 -3
  58. package/src/components/artifact-pane.ts +3 -3
  59. package/src/components/ask-user-question-bubble.test.ts +14 -14
  60. package/src/components/ask-user-question-bubble.ts +10 -10
  61. package/src/components/composer-builder.ts +17 -16
  62. package/src/components/composer-parts.test.ts +54 -0
  63. package/src/components/composer-parts.ts +95 -126
  64. package/src/components/demo-carousel.ts +1 -1
  65. package/src/components/event-stream-view.test.ts +7 -7
  66. package/src/components/event-stream-view.ts +5 -5
  67. package/src/components/header-builder.ts +33 -27
  68. package/src/components/header-layouts.ts +1 -1
  69. package/src/components/header-parts.test.ts +90 -0
  70. package/src/components/header-parts.ts +62 -93
  71. package/src/components/launcher.test.ts +11 -0
  72. package/src/components/launcher.ts +1 -1
  73. package/src/components/message-bubble.test.ts +74 -2
  74. package/src/components/message-bubble.ts +25 -11
  75. package/src/components/messages.ts +2 -2
  76. package/src/components/panel.test.ts +1 -1
  77. package/src/components/panel.ts +92 -97
  78. package/src/components/pill-composer-builder.ts +28 -24
  79. package/src/components/reasoning-bubble.ts +2 -2
  80. package/src/components/suggestions.ts +1 -1
  81. package/src/components/tool-bubble.ts +1 -1
  82. package/src/components/widget-view.test.ts +181 -0
  83. package/src/components/widget-view.ts +234 -0
  84. package/src/defaults.ts +54 -51
  85. package/src/generated/runtype-openapi-contract.ts +5 -0
  86. package/src/index-core.ts +30 -4
  87. package/src/index-global.ts +73 -6
  88. package/src/index.ts +7 -1
  89. package/src/install.test.ts +11 -11
  90. package/src/install.ts +28 -18
  91. package/src/launcher-global.ts +25 -14
  92. package/src/markdown-parsers-eager.ts +20 -0
  93. package/src/markdown-parsers-entry.ts +4 -0
  94. package/src/markdown-parsers-loader.ts +49 -0
  95. package/src/plugin-kit.test.ts +1 -1
  96. package/src/plugin-kit.ts +7 -7
  97. package/src/plugins/types.ts +5 -5
  98. package/src/postprocessors.ts +27 -14
  99. package/src/runtime/host-layout.test.ts +1 -1
  100. package/src/runtime/host-layout.ts +4 -5
  101. package/src/runtime/init.ts +2 -2
  102. package/src/session.test.ts +5 -6
  103. package/src/session.ts +333 -227
  104. package/src/session.voice.test.ts +146 -0
  105. package/src/session.webmcp.test.ts +24 -25
  106. package/src/smart-dom-reader.ts +3 -4
  107. package/src/styles/widget.css +54 -36
  108. package/src/suggest-replies-tool.test.ts +3 -3
  109. package/src/suggest-replies-tool.ts +5 -5
  110. package/src/testing/index.ts +1 -1
  111. package/src/theme-editor/color-utils.ts +2 -2
  112. package/src/theme-editor/index.ts +4 -13
  113. package/src/theme-editor/presets.ts +1 -1
  114. package/src/theme-editor/preview-utils.ts +22 -20
  115. package/src/theme-editor/preview.ts +4 -1
  116. package/src/theme-editor/role-mappings.ts +3 -3
  117. package/src/theme-editor/sections.ts +21 -20
  118. package/src/theme-editor/types.ts +1 -1
  119. package/src/theme-editor/webmcp/index.ts +1 -1
  120. package/src/theme-editor/webmcp/summary.ts +1 -2
  121. package/src/theme-editor/webmcp/tools.ts +30 -30
  122. package/src/theme-editor/webmcp/types.ts +2 -2
  123. package/src/theme-editor-preview.ts +10 -0
  124. package/src/theme-reference.ts +26 -26
  125. package/src/types.ts +368 -99
  126. package/src/ui.approval-plugin.test.ts +3 -3
  127. package/src/ui.ask-user-question-plugin.test.ts +9 -9
  128. package/src/ui.component-directive.test.ts +4 -4
  129. package/src/ui.composer-bar.test.ts +10 -11
  130. package/src/ui.composer-keyboard.test.ts +5 -5
  131. package/src/ui.header-icon-color.test.ts +59 -0
  132. package/src/ui.scroll.test.ts +5 -6
  133. package/src/ui.stop-button.test.ts +3 -4
  134. package/src/ui.suggest-replies.test.ts +2 -2
  135. package/src/ui.ts +292 -131
  136. package/src/utils/buttons.ts +1 -1
  137. package/src/utils/code-generators.ts +1 -1
  138. package/src/utils/composer-history.ts +3 -3
  139. package/src/utils/dom-context.test.ts +2 -2
  140. package/src/utils/dom-context.ts +7 -7
  141. package/src/utils/dom.test.ts +67 -0
  142. package/src/utils/dom.ts +75 -0
  143. package/src/utils/dropdown.ts +2 -2
  144. package/src/utils/event-stream-buffer.test.ts +2 -2
  145. package/src/utils/message-fingerprint.test.ts +9 -0
  146. package/src/utils/message-fingerprint.ts +5 -0
  147. package/src/utils/morph.ts +1 -1
  148. package/src/utils/sanitize.ts +32 -16
  149. package/src/utils/sequence-buffer.test.ts +8 -8
  150. package/src/utils/sequence-buffer.ts +4 -4
  151. package/src/utils/smart-dom-adapter.test.ts +1 -1
  152. package/src/utils/smart-dom-adapter.ts +4 -5
  153. package/src/utils/speech-text.test.ts +122 -0
  154. package/src/utils/speech-text.ts +101 -0
  155. package/src/utils/stream-animation.test.ts +3 -3
  156. package/src/utils/stream-animation.ts +6 -6
  157. package/src/utils/theme.test.ts +1 -1
  158. package/src/utils/throughput-tracker.test.ts +11 -11
  159. package/src/utils/throughput-tracker.ts +9 -9
  160. package/src/utils/tokens.ts +13 -13
  161. package/src/utils/virtual-scroller.ts +1 -1
  162. package/src/vendor/smart-dom-reader/README.md +4 -4
  163. package/src/vendor/smart-dom-reader/index.d.ts +1 -1
  164. package/src/vendor/smart-dom-reader/index.js +2 -2
  165. package/src/version.ts +1 -1
  166. package/src/voice/audio-playback-manager.test.ts +159 -0
  167. package/src/voice/audio-playback-manager.ts +145 -30
  168. package/src/voice/browser-speech-engine.ts +130 -0
  169. package/src/voice/fallback-speech-engine.ts +89 -0
  170. package/src/voice/index.ts +21 -0
  171. package/src/voice/read-aloud-controller.test.ts +143 -0
  172. package/src/voice/read-aloud-controller.ts +136 -0
  173. package/src/voice/runtype-speech-engine.test.ts +280 -0
  174. package/src/voice/runtype-speech-engine.ts +211 -0
  175. package/src/voice/runtype-tts-entry.ts +10 -0
  176. package/src/voice/runtype-tts-loader.test.ts +47 -0
  177. package/src/voice/runtype-tts-loader.ts +42 -0
  178. package/src/voice/runtype-voice-provider.ts +332 -537
  179. package/src/voice/voice-factory.ts +26 -4
  180. package/src/voice/voice.test.ts +321 -7
  181. package/src/voice/worklet-playback-engine.ts +272 -0
  182. package/src/voice-worklet-player.ts +39 -0
  183. package/src/webmcp-bridge.test.ts +6 -6
  184. package/src/webmcp-bridge.ts +24 -25
  185. package/src/webmcp-polyfill.ts +1 -1
  186. package/src/voice/voice-activity-detector.ts +0 -90
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Optional PCM playback engine entry (`@runtypelabs/persona/voice-worklet-player`).
3
+ *
4
+ * A jitter-buffered AudioWorklet player for raw PCM16 / 24 kHz / mono streams,
5
+ * kept out of the main bundle. Two ways in:
6
+ * - `createPcmStreamPlayer({ prebufferMs })` — general primitive with
7
+ * pause/resume; reuse it inside a hosted `SpeechEngine` (server/ElevenLabs/
8
+ * OpenAI TTS) for gapless "Read aloud".
9
+ * - `createWorkletPlaybackEngine` — realtime-named alias; pass it to
10
+ * `voiceRecognition.provider.runtype.createPlaybackEngine`.
11
+ */
12
+ export {
13
+ createPcmStreamPlayer,
14
+ createWorkletPlaybackEngine,
15
+ } from "./voice/worklet-playback-engine";
16
+ export type { PcmStreamPlayerOptions } from "./voice/worklet-playback-engine";
17
+ export type { VoicePlaybackEngine, PcmStreamPlayer } from "./types";
18
+
19
+ // Runtype-hosted "Read aloud" engine. It streams PCM from Runtype's
20
+ // `/v1/agents/:id/speak` into a PcmStreamPlayer. By default that's the in-bundle
21
+ // AudioPlaybackManager, so the engine no longer depends on the worklet — but it
22
+ // pairs naturally with `createPcmStreamPlayer` above (pass it via
23
+ // `createPlaybackEngine`) for jitter-buffered playback. Most integrations don't
24
+ // import it directly — `textToSpeech: { provider: 'runtype' }` wires it
25
+ // automatically; this export is for manual `textToSpeech.createEngine` control.
26
+ export {
27
+ RuntypeSpeechEngine,
28
+ type RuntypeSpeechEngineOptions,
29
+ } from "./voice/runtype-speech-engine";
30
+
31
+ // Browser-fallback wrapper for a hosted SpeechEngine: tries the primary, and on
32
+ // a pre-playback failure silently switches to a secondary (browser) voice.
33
+ // `textToSpeech: { provider: 'runtype' }` wires it automatically; exported here
34
+ // for manual `textToSpeech.createEngine` control. Kept off the main entry so the
35
+ // IIFE/CDN build can defer the whole read-aloud path to `runtype-tts.js`.
36
+ export {
37
+ FallbackSpeechEngine,
38
+ type FallbackSpeechEngineOptions,
39
+ } from "./voice/fallback-speech-engine";
@@ -154,7 +154,7 @@ describe("WebMcpBridge.snapshotForDispatch", () => {
154
154
  });
155
155
 
156
156
  it("returns empty when initializeWebMCPPolyfill() throws", async () => {
157
- // No pre-existing modelContext otherwise install() short-circuits
157
+ // No pre-existing modelContext: otherwise install() short-circuits
158
158
  // before importing the polyfill and the throwing init never runs.
159
159
  vi.stubGlobal("document", {});
160
160
  polyfillMock.initThrows = true;
@@ -249,7 +249,7 @@ describe("setWebMcpPolyfillLoader", () => {
249
249
  });
250
250
 
251
251
  it("never invokes the loader when a compatible modelContext is already present", async () => {
252
- // beforeEach stubs a compatible document.modelContext the install
252
+ // beforeEach stubs a compatible document.modelContext: the install
253
253
  // short-circuit must win, so a failing loader is irrelevant.
254
254
  registry.tools = [fakeTool({ name: "search" })];
255
255
  const loader = vi.fn(() => Promise.reject(new Error("should not load")));
@@ -439,7 +439,7 @@ describe("WebMcpBridge.executeToolCall", () => {
439
439
  ];
440
440
  const bridge = new WebMcpBridge({ enabled: true, onConfirm: confirmSpy });
441
441
  const r = await bridge.executeToolCall("webmcp:sensitive", {});
442
- // Only the single outer gate fires the polyfill owns the in-tool callback.
442
+ // Only the single outer gate fires: the polyfill owns the in-tool callback.
443
443
  expect(confirmSpy).toHaveBeenCalledTimes(1);
444
444
  expect(r.isError).toBeUndefined();
445
445
  expect(r.content[0]).toEqual({
@@ -532,13 +532,13 @@ describe("WebMcpBridge.executeToolCall", () => {
532
532
  const r = await pending;
533
533
  expect(r.isError).toBe(true);
534
534
  expect((r.content[0] as { text: string }).text).toMatch(/abort/i);
535
- // Late resolve from the page side must not poison anything.
535
+ // Late resolve from the page side: must not poison anything.
536
536
  resolveStuck?.("late");
537
537
  });
538
538
 
539
539
  it("rejects a webmcp call for a tool excluded by the client allowlist", async () => {
540
540
  // snapshotForDispatch filters by allowlist for the wire surface, but
541
- // executeToolCall must also re-check it defense-in-depth alongside the
541
+ // executeToolCall must also re-check it: defense-in-depth alongside the
542
542
  // server-side check.
543
543
  const executeSpy = vi.fn(() => "should not run");
544
544
  registry.tools = [
@@ -556,7 +556,7 @@ describe("WebMcpBridge.executeToolCall", () => {
556
556
  });
557
557
  });
558
558
 
559
- describe("computeClientToolsFingerprint diff-only / send-once", () => {
559
+ describe("computeClientToolsFingerprint: diff-only / send-once", () => {
560
560
  const tool = (over: Partial<ClientToolDefinition> = {}): ClientToolDefinition => ({
561
561
  name: "search",
562
562
  description: "Search the catalog",
@@ -19,15 +19,15 @@
19
19
  * with no MCP-B-only extensions. The spec standardizes the *producer* side;
20
20
  * Persona is an in-page *consumer*, so it reads the registry via the
21
21
  * producer-facing preview API:
22
- * - `getTools()` async; returns `{ name, description, inputSchema }` where
22
+ * - `getTools()`: async; returns `{ name, description, inputSchema }` where
23
23
  * `inputSchema` is a JSON *string*. Annotations are not exposed here.
24
- * - `executeTool(toolInfo, inputArgsJson, { signal })` async; validates args
24
+ * - `executeTool(toolInfo, inputArgsJson, { signal })`: async; validates args
25
25
  * against the tool's schema, runs `execute()`, and returns the raw result as
26
26
  * a JSON *string* (or `null` for `undefined`). Honors `signal` for abort.
27
27
  *
28
28
  * The polyfill auto-installs `document.modelContext` at module-evaluation time,
29
29
  * so it is imported *dynamically* and only when `config.webmcp.enabled === true`
30
- * a static import would install the global for every widget consumer,
30
+ *: a static import would install the global for every widget consumer,
31
31
  * including those that never opted into WebMCP.
32
32
  *
33
33
  * Confirm model: every `webmcp:*` call goes through one confirm gate before
@@ -49,7 +49,7 @@ import type {
49
49
  * Persona waits before telling the agent the tool failed, keeping a misbehaving
50
50
  * tool from pinning the agent indefinitely. The timeout aborts the polyfill's
51
51
  * `executeTool` via an `AbortSignal`, so the page's work is asked to stop too
52
- * (cooperatively a tool that ignores the signal may still complete).
52
+ * (cooperatively: a tool that ignores the signal may still complete).
53
53
  */
54
54
  const DEFAULT_TOOL_TIMEOUT_MS = 30_000;
55
55
 
@@ -70,7 +70,7 @@ interface ModelContextToolInfo {
70
70
  * Display title declared on the tool (`ToolDescriptor.title` in the WebMCP
71
71
  * spec). The polyfill returns `""` when the tool didn't declare one. Note:
72
72
  * `annotations` (incl. the legacy `annotations.title`) are NOT exposed on
73
- * this strict consumer surface top-level `title` is the only display-name
73
+ * this strict consumer surface: top-level `title` is the only display-name
74
74
  * channel available to us.
75
75
  */
76
76
  title?: string;
@@ -97,7 +97,7 @@ const webMcpToolDisplayTitles = new Map<string, string>();
97
97
 
98
98
  /**
99
99
  * Record declared display titles from a fresh `getTools()` read. The map is
100
- * rebuilt from scratch callers always pass the FULL registry snapshot so
100
+ * rebuilt from scratch, callers always pass the FULL registry snapshot, so
101
101
  * a tool that unregistered or dropped its title can't leave a stale label
102
102
  * behind. Exported for tests; production callers are the bridge's registry
103
103
  * reads.
@@ -160,16 +160,15 @@ export const setWebMcpPolyfillLoader = (
160
160
  * The widget caches "the fingerprint of the tool set last sent in full" for the
161
161
  * current session; an unchanged set on a follow-up turn lets it ship only the
162
162
  * fingerprint instead of the whole array. Per-tool strings are sorted so tool
163
- * ordering does not affect the result. `pageOrigin` is deliberately excluded
164
- * it is audit metadata, not part of the tool contract.
163
+ * ordering does not affect the result. `pageOrigin` is deliberately excluded: * it is audit metadata, not part of the tool contract.
165
164
  *
166
165
  * This is a fast, non-cryptographic content key. The canonical per-tool content
167
166
  * is hashed down to a short, fixed-length digest so the result fits the server's
168
167
  * `clientToolsFingerprint` wire field (`z.string().max(128)`) regardless of how
169
- * many tools the page registers sending the raw concatenated content would
168
+ * many tools the page registers: sending the raw concatenated content would
170
169
  * overflow that bound and be rejected with a 400. The server stores and compares
171
170
  * the widget's fingerprint verbatim, so cross-implementation byte-equality is NOT
172
- * required only self-consistency across this widget's turns.
171
+ * required: only self-consistency across this widget's turns.
173
172
  */
174
173
  export function computeClientToolsFingerprint(
175
174
  tools: ClientToolDefinition[],
@@ -190,7 +189,7 @@ export function computeClientToolsFingerprint(
190
189
  }
191
190
 
192
191
  /**
193
- * cyrb53 a fast, well-distributed non-cryptographic string hash. Returns a
192
+ * cyrb53: a fast, well-distributed non-cryptographic string hash. Returns a
194
193
  * 53-bit value (safe-integer range). Two independent seeds are combined by the
195
194
  * caller for a ~106-bit digest, which makes accidental collisions across a
196
195
  * single conversation's handful of tool-set variants infeasible.
@@ -252,7 +251,7 @@ export class WebMcpBridge {
252
251
 
253
252
  /**
254
253
  * `true` when the bridge can both snapshot the registry AND execute returned
255
- * tool calls i.e. the polyfill is installed and `document.modelContext`
254
+ * tool calls: i.e. the polyfill is installed and `document.modelContext`
256
255
  * exposes the consumer surface (`getTools` / `executeTool`). Native browsers
257
256
  * that ship `document.modelContext` satisfy this too.
258
257
  *
@@ -268,7 +267,7 @@ export class WebMcpBridge {
268
267
 
269
268
  /**
270
269
  * Per-turn snapshot for `dispatch.clientTools[]`. Returns the JSON-only
271
- * surface `execute` stays client-side, reached later via `executeToolCall`.
270
+ * surface: `execute` stays client-side, reached later via `executeToolCall`.
272
271
  *
273
272
  * Async because the strict polyfill's `getTools()` is async. Both payload
274
273
  * builders in `client.ts` already `await`, so this adds no new ceremony.
@@ -284,7 +283,7 @@ export class WebMcpBridge {
284
283
  try {
285
284
  infos = await mc.getTools();
286
285
  } catch (err) {
287
- log.warn("getTools() threw shipping an empty WebMCP snapshot.", err);
286
+ log.warn("getTools() threw: shipping an empty WebMCP snapshot.", err);
288
287
  return [];
289
288
  }
290
289
  recordWebMcpToolDisplayTitles(infos);
@@ -310,7 +309,7 @@ export class WebMcpBridge {
310
309
  * Execute a `webmcp:<name>` tool call returned by the agent and return the
311
310
  * normalized MCP-shaped result for `/resume`.
312
311
  *
313
- * Failure modes all return `{ isError: true, content: [...] }` rather than
312
+ * Failure modes: all return `{ isError: true, content: [...] }` rather than
314
313
  * throwing, so the dispatch can resume cleanly:
315
314
  * - bridge not operational
316
315
  * - tool not in registry (e.g. unmounted between snapshot and call)
@@ -374,7 +373,7 @@ export class WebMcpBridge {
374
373
 
375
374
  // Re-apply the client-side allowlist at execute time. `snapshotForDispatch`
376
375
  // already filters it for `clientTools[]`, but the agent could request a
377
- // tool that the integrator excluded e.g. a `webmcp:` call replayed from
376
+ // tool that the integrator excluded: e.g. a `webmcp:` call replayed from
378
377
  // history, a server bug, or a page that re-registered a previously-hidden
379
378
  // tool. The server is the trust boundary; this is a defense-in-depth
380
379
  // convenience check to keep us symmetric with the snapshot.
@@ -384,7 +383,7 @@ export class WebMcpBridge {
384
383
  );
385
384
  }
386
385
 
387
- // Bail before the confirm renders a late approval after cancel() would
386
+ // Bail before the confirm renders: a late approval after cancel() would
388
387
  // otherwise fire a host-page side effect with no matching /resume.
389
388
  if (signal?.aborted) {
390
389
  return errorResult("Aborted by cancel()");
@@ -413,7 +412,7 @@ export class WebMcpBridge {
413
412
 
414
413
  // Drive both the 30s timeout and the caller's `signal` through a single
415
414
  // AbortController passed to `executeTool`. The polyfill races the page's
416
- // `execute()` against this signal, so abort is cooperative a tool that
415
+ // `execute()` against this signal, so abort is cooperative: a tool that
417
416
  // ignores the signal may still complete on the page after the agent gets
418
417
  // an `isError` result. Side-effectful tools should bound their own work.
419
418
  const controller = new AbortController();
@@ -457,7 +456,7 @@ export class WebMcpBridge {
457
456
  * widget consumers that never enable WebMCP.
458
457
  *
459
458
  * Producer pages should still install the polyfill themselves (or import it)
460
- * before registering tools Persona's install is a fallback, and a page that
459
+ * before registering tools: Persona's install is a fallback, and a page that
461
460
  * registers tools at load before Persona's first dispatch needs the global to
462
461
  * already exist.
463
462
  */
@@ -472,7 +471,7 @@ export class WebMcpBridge {
472
471
  private async install(): Promise<void> {
473
472
  try {
474
473
  // A compatible registry is already on the page (the host installed the
475
- // polyfill, or a native impl) initialize would no-op against it, so
474
+ // polyfill, or a native impl): initialize would no-op against it, so
476
475
  // skip loading the module entirely. Pages that register tools before
477
476
  // Persona's first dispatch always land here, because registering
478
477
  // requires `document.modelContext` to exist.
@@ -489,7 +488,7 @@ export class WebMcpBridge {
489
488
  this.installed = true;
490
489
  } catch (err) {
491
490
  log.warn(
492
- "Failed to load @mcp-b/webmcp-polyfill WebMCP consumption disabled.",
491
+ "Failed to load @mcp-b/webmcp-polyfill: WebMCP consumption disabled.",
493
492
  err,
494
493
  );
495
494
  this.installed = false;
@@ -504,7 +503,7 @@ export class WebMcpBridge {
504
503
  if (typeof document === "undefined") return null;
505
504
  const mc = (document as Document & { modelContext?: unknown }).modelContext;
506
505
  if (!mc || typeof mc !== "object") {
507
- // Absent (not yet installed, or no WebMCP on this page) not an error,
506
+ // Absent (not yet installed, or no WebMCP on this page): not an error,
508
507
  // and not worth warning about; the snapshot/execute paths fall back to a
509
508
  // clean "not operational" result.
510
509
  return null;
@@ -517,13 +516,13 @@ export class WebMcpBridge {
517
516
  // A `document.modelContext` IS present but doesn't expose the strict-core
518
517
  // surface we consume (`getTools` / `executeTool`). This usually means a
519
518
  // different or older WebMCP polyfill (or a native impl on a divergent
520
- // draft) installed the global first which `@mcp-b/webmcp-polyfill`
519
+ // draft) installed the global first: which `@mcp-b/webmcp-polyfill`
521
520
  // correctly declines to overwrite. Warn once so integrators understand
522
521
  // why WebMCP is inert instead of seeing a silent no-op.
523
522
  if (!this.incompatibleContextWarned) {
524
523
  this.incompatibleContextWarned = true;
525
524
  log.warn(
526
- "document.modelContext is present but does not expose getTools()/executeTool() " +
525
+ "document.modelContext is present but does not expose getTools()/executeTool(): " +
527
526
  "WebMCP consumption is disabled. Another (incompatible or older) WebMCP polyfill " +
528
527
  "likely installed document.modelContext before Persona. Remove it, or use a polyfill " +
529
528
  "implementing the strict standard surface (e.g. @mcp-b/webmcp-polyfill).",
@@ -618,7 +617,7 @@ const normalizeSerializedResult = (raw: string | null): WebMcpToolResult => {
618
617
  try {
619
618
  parsed = JSON.parse(raw);
620
619
  } catch {
621
- // Not valid JSON (shouldn't happen the polyfill stringifies) surface
620
+ // Not valid JSON (shouldn't happen, the polyfill stringifies), surface
622
621
  // the raw string as text rather than dropping it.
623
622
  return { content: [{ type: "text", text: raw }] };
624
623
  }
@@ -8,7 +8,7 @@
8
8
  * imports this chunk from a URL derived from the widget script's own `src`
9
9
  * (see the loader registered in `index-global.ts`).
10
10
  *
11
- * Built self-contained (`--no-external`) it must work standalone on a CDN
11
+ * Built self-contained (`--no-external`): it must work standalone on a CDN
12
12
  * with no module resolution. npm/bundler consumers never load this file;
13
13
  * their bundlers resolve the bare `import("@mcp-b/webmcp-polyfill")` in
14
14
  * `webmcp-bridge.ts` directly.
@@ -1,90 +0,0 @@
1
- /**
2
- * Voice Activity Detector (VAD)
3
- *
4
- * Reusable RMS-based voice activity detection that monitors a mic stream
5
- * and fires a callback when a condition is sustained for a given duration.
6
- *
7
- * - mode 'silence': fires when volume stays below threshold (user stopped talking)
8
- * - mode 'speech': fires when volume stays above threshold (user started talking)
9
- *
10
- * Fires callback exactly once per start() call, then stops checking.
11
- * Calling start() again implicitly calls stop() first.
12
- */
13
- export class VoiceActivityDetector {
14
- private sourceNode: MediaStreamAudioSourceNode | null = null;
15
- private analyserNode: AnalyserNode | null = null;
16
- private interval: ReturnType<typeof setInterval> | null = null;
17
- private conditionStart: number | null = null;
18
- private fired = false;
19
-
20
- /**
21
- * Begin monitoring the given stream for voice activity.
22
- *
23
- * @param audioContext Active AudioContext
24
- * @param stream MediaStream from getUserMedia
25
- * @param mode 'silence' fires when quiet for duration, 'speech' fires when loud for duration
26
- * @param config threshold (RMS level) and duration (ms)
27
- * @param callback Fires exactly once when the condition is met
28
- */
29
- start(
30
- audioContext: AudioContext,
31
- stream: MediaStream,
32
- mode: "silence" | "speech",
33
- config: { threshold: number; duration: number },
34
- callback: () => void,
35
- ): void {
36
- this.stop();
37
-
38
- this.fired = false;
39
- this.conditionStart = null;
40
-
41
- this.sourceNode = audioContext.createMediaStreamSource(stream);
42
- this.analyserNode = audioContext.createAnalyser();
43
- this.analyserNode.fftSize = 2048;
44
- this.sourceNode.connect(this.analyserNode);
45
-
46
- const dataArray = new Float32Array(this.analyserNode.fftSize);
47
-
48
- this.interval = setInterval(() => {
49
- if (!this.analyserNode || this.fired) return;
50
- this.analyserNode.getFloatTimeDomainData(dataArray);
51
-
52
- // Compute RMS volume
53
- let sum = 0;
54
- for (let i = 0; i < dataArray.length; i++) {
55
- sum += dataArray[i] * dataArray[i];
56
- }
57
- const rms = Math.sqrt(sum / dataArray.length);
58
-
59
- const conditionMet =
60
- mode === "silence"
61
- ? rms < config.threshold
62
- : rms >= config.threshold;
63
-
64
- if (conditionMet) {
65
- if (this.conditionStart === null) {
66
- this.conditionStart = Date.now();
67
- } else if (Date.now() - this.conditionStart >= config.duration) {
68
- this.fired = true;
69
- callback();
70
- }
71
- } else {
72
- this.conditionStart = null;
73
- }
74
- }, 100);
75
- }
76
-
77
- stop(): void {
78
- if (this.interval) {
79
- clearInterval(this.interval);
80
- this.interval = null;
81
- }
82
- if (this.sourceNode) {
83
- this.sourceNode.disconnect();
84
- this.sourceNode = null;
85
- }
86
- this.analyserNode = null;
87
- this.conditionStart = null;
88
- this.fired = false;
89
- }
90
- }