@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runtypelabs/persona",
3
- "version": "3.34.1",
3
+ "version": "3.35.0",
4
4
  "description": "Themeable, pluggable streaming agent widget for websites, in plain JS with support for voice input and reasoning / tool output.",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -27,6 +27,11 @@
27
27
  "import": "./dist/theme-editor.js",
28
28
  "require": "./dist/theme-editor.cjs"
29
29
  },
30
+ "./theme-editor/preview": {
31
+ "types": "./dist/theme-editor-preview.d.ts",
32
+ "import": "./dist/theme-editor-preview.js",
33
+ "require": "./dist/theme-editor-preview.cjs"
34
+ },
30
35
  "./testing": {
31
36
  "types": "./dist/testing.d.ts",
32
37
  "import": "./dist/testing.js",
@@ -37,6 +42,11 @@
37
42
  "import": "./dist/smart-dom-reader.js",
38
43
  "require": "./dist/smart-dom-reader.cjs"
39
44
  },
45
+ "./voice-worklet-player": {
46
+ "types": "./dist/voice-worklet-player.d.ts",
47
+ "import": "./dist/voice-worklet-player.js",
48
+ "require": "./dist/voice-worklet-player.cjs"
49
+ },
40
50
  "./plugin-kit": {
41
51
  "types": "./dist/plugin-kit.d.ts",
42
52
  "import": "./dist/plugin-kit.js",
@@ -109,17 +119,21 @@
109
119
  "access": "public"
110
120
  },
111
121
  "scripts": {
112
- "build": "rimraf dist && pnpm run build:styles && pnpm run build:client && pnpm run build:installer && pnpm run build:launcher && pnpm run build:webmcp-polyfill && pnpm run build:theme-ref && pnpm run build:codegen && pnpm run build:theme-editor && pnpm run build:testing && pnpm run build:smart-dom-reader && pnpm run build:plugin-kit && pnpm run build:animations",
122
+ "build": "rimraf dist && pnpm run build:styles && pnpm run build:markdown-parsers && pnpm run build:client && pnpm run build:installer && pnpm run build:launcher && pnpm run build:webmcp-polyfill && pnpm run build:runtype-tts && pnpm run build:theme-ref && pnpm run build:codegen && pnpm run build:theme-editor && pnpm run build:theme-editor-preview && pnpm run build:testing && pnpm run build:smart-dom-reader && pnpm run build:voice-worklet-player && pnpm run build:plugin-kit && pnpm run build:animations",
123
+ "build:markdown-parsers": "tsup --config tsup.markdown-parsers.config.ts",
113
124
  "build:plugin-kit": "tsup src/plugin-kit.ts --format esm,cjs --minify --dts --out-dir dist --no-splitting",
114
125
  "build:theme-editor": "tsup src/theme-editor.ts --format esm,cjs --minify --dts --out-dir dist --no-splitting",
126
+ "build:theme-editor-preview": "tsup src/theme-editor-preview.ts --format esm,cjs --minify --dts --out-dir dist --no-splitting",
115
127
  "build:testing": "tsup src/testing.ts --format esm,cjs --minify --dts --out-dir dist --no-splitting",
116
128
  "build:smart-dom-reader": "tsup src/smart-dom-reader.ts --format esm,cjs --minify --dts --out-dir dist --no-splitting",
129
+ "build:voice-worklet-player": "tsup src/voice-worklet-player.ts --format esm,cjs --minify --dts --out-dir dist --no-splitting",
117
130
  "build:animations": "tsup src/animations/glyph-cycle.ts src/animations/wipe.ts --format esm,cjs --minify --dts --out-dir dist/animations --no-splitting",
118
131
  "build:theme-ref": "tsup src/theme-reference.ts --format esm,cjs --minify --dts",
119
132
  "build:codegen": "tsup src/codegen.ts --format esm,cjs --minify --dts",
120
133
  "build:styles": "node -e \"const fs=require('fs');fs.mkdirSync('dist',{recursive:true});fs.copyFileSync('src/styles/widget.css','dist/widget.css');\"",
121
134
  "build:client": "tsup src/index.ts --format esm,cjs --minify --sourcemap --splitting false --dts --loader \".css=text\" && tsup --config tsup.global.config.ts && node -e \"const fs=require('fs');for(const ext of ['.global.js','.global.js.map']){const from='dist/index-global'+ext;if(fs.existsSync(from))fs.renameSync(from,'dist/index'+ext);}\"",
122
135
  "build:webmcp-polyfill": "tsup --config tsup.webmcp-polyfill.config.ts",
136
+ "build:runtype-tts": "tsup --config tsup.runtype-tts.config.ts",
123
137
  "build:installer": "tsup src/install.ts --format iife --global-name SiteAgentInstaller --out-dir dist --minify --sourcemap --no-splitting",
124
138
  "build:launcher": "tsup src/launcher-global.ts --format iife --global-name AgentWidgetLauncher --minify --sourcemap --splitting false --out-dir dist && node -e \"const fs=require('fs');for(const ext of ['.global.js','.global.js.map']){const from='dist/launcher-global'+ext;if(fs.existsSync(from))fs.renameSync(from,'dist/launcher'+ext);}\"",
125
139
  "lint": "eslint . --ext .ts",
@@ -2,14 +2,13 @@ import type { StreamAnimationPlugin } from "../types";
2
2
  import { registerStreamAnimationPlugin } from "../utils/stream-animation";
3
3
 
4
4
  /**
5
- * Glyph-cycle animation "hacker-settling" reveal.
5
+ * Glyph-cycle animation: "hacker-settling" reveal.
6
6
  *
7
7
  * Each arriving char briefly cycles through a sequence of random glyphs
8
8
  * before locking in to its final character. Unlike a pure CSS effect, this
9
9
  * needs JS because it mutates `textContent` per tick. To keep idiomorph from
10
10
  * clobbering mid-cycle work, each cycling span carries
11
- * `data-preserve-runtime="stream-glyph-cycle"` until its cycle completes
12
- * morph honors that attribute as an absolute skip marker.
11
+ * `data-preserve-runtime="stream-glyph-cycle"` until its cycle completes: * morph honors that attribute as an absolute skip marker.
13
12
  *
14
13
  * A short buffer (`BUFFER_THRESHOLD`) holds the bubble empty (with the
15
14
  * typing indicator) until enough text has arrived to run a visible reveal.
@@ -19,7 +18,7 @@ import { registerStreamAnimationPlugin } from "../utils/stream-animation";
19
18
  * settling wave flows through the full message.
20
19
  *
21
20
  * Ships as a subpath module so consumers who don't want it pay zero cost.
22
- * Importing this module auto-registers the plugin globally just add the
21
+ * Importing this module auto-registers the plugin globally: just add the
23
22
  * import and set `features.streamAnimation.type = "glyph-cycle"`.
24
23
  *
25
24
  * ```ts
@@ -54,8 +53,7 @@ const STYLES = `
54
53
  const GLYPHS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789#$%&@";
55
54
  const TICK_COUNT = 10;
56
55
  // Base interval between glyph swaps (ms). Scaled by the widget's configured
57
- // `speed` so the cycle feels coherent with the rest of the stream animation
58
- // faster speed → snappier cycling, slower speed → more deliberate flicker.
56
+ // `speed` so the cycle feels coherent with the rest of the stream animation:// faster speed → snappier cycling, slower speed → more deliberate flicker.
59
57
  const BASE_TICK_MS = 120;
60
58
  const DEFAULT_STEP_MS = 120;
61
59
  // Probability that each later still-cycling sibling gets re-randomized on
@@ -63,7 +61,7 @@ const DEFAULT_STEP_MS = 120;
63
61
  const CROSS_FLICKER_PROBABILITY = 0.4;
64
62
  // Hold the stream back until at least this many chars have arrived. Once
65
63
  // released, every char is rendered as a random glyph and settles in order
66
- // from the start so the visible animation carries through the full text.
64
+ // from the start, so the visible animation carries through the full text.
67
65
  const BUFFER_THRESHOLD = 50;
68
66
 
69
67
  const getStepMs = (container: HTMLElement | null): number => {
@@ -96,7 +94,7 @@ const flickerLaterSiblings = (span: HTMLElement): void => {
96
94
  const container = span.closest(".persona-stream-glyph-cycle") as HTMLElement | null;
97
95
  if (!container) return;
98
96
  // Any scheduled char that still has its final char stashed is "pending
99
- // or cycling" both kinds are valid flicker targets. Settled chars have
97
+ // or cycling": both kinds are valid flicker targets. Settled chars have
100
98
  // `data-glyph-cycle-final` deleted and are excluded.
101
99
  const unsettled = container.querySelectorAll<HTMLElement>(
102
100
  ".persona-stream-char[data-glyph-cycle-final]"
@@ -115,8 +113,8 @@ const flickerLaterSiblings = (span: HTMLElement): void => {
115
113
  };
116
114
 
117
115
  // Per-container timestamp tracking: when the next char's cycle should settle.
118
- // Each char starts cycling immediately on schedule no static placeholder
119
- // wait and finishes when `Date.now() >= settleAt`. This slot advances by
116
+ // Each char starts cycling immediately on schedule: no static placeholder
117
+ // wait, and finishes when `Date.now() >= settleAt`. This slot advances by
120
118
  // one stagger step per schedule, so the burst settles left-to-right while
121
119
  // every char is visibly flickering from the moment it enters the DOM.
122
120
  const containerNextSettleAt = new WeakMap<Element, number>();
@@ -139,8 +137,7 @@ const observeStagger = (container: Element | null, now: number): number => {
139
137
  let observed = containerArrivalMs.get(container) ?? DEFAULT_ARRIVAL_MS;
140
138
  if (last !== undefined) {
141
139
  const interval = now - last;
142
- // Skip near-zero intervals (burst within one MutationObserver batch)
143
- // they don't reflect the real stream cadence.
140
+ // Skip near-zero intervals (burst within one MutationObserver batch): // they don't reflect the real stream cadence.
144
141
  if (interval > 1) {
145
142
  observed = observed * (1 - EMA_ALPHA) + interval * EMA_ALPHA;
146
143
  containerArrivalMs.set(container, observed);
@@ -180,7 +177,7 @@ const scheduleCycle = (span: HTMLElement): void => {
180
177
  span.dataset.glyphCycleScheduled = "true";
181
178
  // Stash the target char and immediately paint a random glyph so the span
182
179
  // reads as "cycling" even before its staggered kickoff. Also opt out of
183
- // morph without this, streamed token re-renders would overwrite our
180
+ // morph: without this, streamed token re-renders would overwrite our
184
181
  // placeholder glyph with the final char.
185
182
  span.dataset.glyphCycleFinal = finalChar;
186
183
  span.setAttribute("data-preserve-runtime", "stream-glyph-cycle");
@@ -275,7 +272,7 @@ export const glyphCycle: StreamAnimationPlugin = {
275
272
  // `**template literal**`) would trigger a render of the partial
276
273
  // `**template`, which markdown emits as literal asterisks, gets
277
274
  // wrapped + marked `data-preserve-runtime`, and leaks through later
278
- // morphs the final structure can't reconcile.
275
+ // morphs: the final structure can't reconcile.
279
276
  let boldPairs = 0;
280
277
  let lastSafe = -1;
281
278
  let i = 0;
@@ -2,7 +2,7 @@ import type { StreamAnimationPlugin } from "../types";
2
2
  import { registerStreamAnimationPlugin } from "../utils/stream-animation";
3
3
 
4
4
  /**
5
- * Wipe animation per-word left-to-right mask reveal.
5
+ * Wipe animation: per-word left-to-right mask reveal.
6
6
  *
7
7
  * Each arriving word is revealed via a soft feathered mask that sweeps from
8
8
  * right to left. Uses `mask-image` (not `background-clip: text`) so the
@@ -137,7 +137,7 @@ describe("AgentWidgetClient - built-in ask_user_question exposure", () => {
137
137
 
138
138
  it("changes the clientTools fingerprint when toggled (diff-only resend)", () => {
139
139
  // In client-token mode the widget only resends the full clientTools[]
140
- // when the fingerprint changes toggling expose must change it so the
140
+ // when the fingerprint changes: toggling expose must change it so the
141
141
  // server actually learns about (or forgets) the built-in tool.
142
142
  const webMcpOnly = [{ name: "search", description: "s" }];
143
143
  const withBuiltIn = [ASK_USER_QUESTION_CLIENT_TOOL, ...webMcpOnly];
@@ -2,7 +2,7 @@
2
2
  * Built-in `ask_user_question` client tool.
3
3
  *
4
4
  * The widget can advertise this tool to the agent on every dispatch via
5
- * `clientTools[]` (set `features.askUserQuestion.expose: true`) the same
5
+ * `clientTools[]` (set `features.askUserQuestion.expose: true`): the same
6
6
  * wire surface WebMCP page tools ride. The server registers it as a LOCAL
7
7
  * tool under its bare name (`origin: 'sdk'` tools are not `webmcp:`-prefixed),
8
8
  * so when the model calls it the execution pauses with a `step_await`
@@ -13,7 +13,7 @@
13
13
  * This replaces the previous integrator burden of hand-declaring the tool in
14
14
  * a flow's `runtimeTools` and keeping that schema in sync with the renderer.
15
15
  * Flows that already declare `ask_user_question` server-side should leave
16
- * `expose` off the model would otherwise see the tool twice.
16
+ * `expose` off: the model would otherwise see the tool twice.
17
17
  */
18
18
 
19
19
  import {
@@ -28,8 +28,7 @@ import type { AgentWidgetConfig, ClientToolDefinition } from "./types";
28
28
 
29
29
  /**
30
30
  * JSON Schema for the tool's parameters. Mirrors {@link AskUserQuestionPayload}
31
- * the shape `parseAskUserQuestionPayload` hydrates the answer sheet from
32
- * so the schema the model is held to and the schema the renderer expects can
31
+ *, the shape `parseAskUserQuestionPayload` hydrates the answer sheet from, * so the schema the model is held to and the schema the renderer expects can
33
32
  * never drift.
34
33
  */
35
34
  export const ASK_USER_QUESTION_PARAMETERS_SCHEMA = {
@@ -57,7 +56,7 @@ export const ASK_USER_QUESTION_PARAMETERS_SCHEMA = {
57
56
  minItems: 2,
58
57
  maxItems: 4,
59
58
  description:
60
- '2-4 distinct choices. Do NOT add an "Other" option free text is automatic.',
59
+ '2-4 distinct choices. Do NOT add an "Other" option: free text is automatic.',
61
60
  items: {
62
61
  type: "object",
63
62
  properties: {
@@ -102,7 +101,7 @@ export const ASK_USER_QUESTION_CLIENT_TOOL: ClientToolDefinition = {
102
101
  name: ASK_USER_QUESTION_TOOL_NAME,
103
102
  description:
104
103
  "Ask the user multiple-choice questions and wait for their answers. Use " +
105
- "only when blocked on a decision that is the user's to make a " +
104
+ "only when blocked on a decision that is the user's to make: a " +
106
105
  "preference, a choice between valid approaches, or information you " +
107
106
  "cannot infer. Each question offers 2-4 options plus an automatic " +
108
107
  "free-text input. The result maps each question to its answer (an array " +
@@ -119,8 +118,7 @@ export const ASK_USER_QUESTION_CLIENT_TOOL: ClientToolDefinition = {
119
118
  *
120
119
  * Each tool is gated on BOTH of its feature flags: `expose` opts the tool
121
120
  * into the agent's catalog, and `enabled !== false` guarantees the widget can
122
- * actually render UI (and, for fire-and-forget tools, auto-resume) for it
123
- * exposing a tool with its feature disabled would park the execution on a
121
+ * actually render UI (and, for fire-and-forget tools, auto-resume) for it: * exposing a tool with its feature disabled would park the execution on a
124
122
  * generic tool bubble with no way to advance.
125
123
  */
126
124
  export const builtInClientToolsForDispatch = (
@@ -2348,7 +2348,7 @@ describe('AgentWidgetClient - Out-of-Order Sequence Reordering', () => {
2348
2348
  };
2349
2349
  e({ type: 'flow_start', flowId: 'f1', flowName: 'Test', totalSteps: 1 });
2350
2350
  e({ type: 'step_start', id: 's1', name: 'Prompt', stepType: 'prompt', index: 1, totalSteps: 1 });
2351
- // No seq field should append in arrival order
2351
+ // No seq field: should append in arrival order
2352
2352
  e({ type: 'step_delta', id: 's1', text: 'Hello ' });
2353
2353
  e({ type: 'step_delta', id: 's1', text: 'world' });
2354
2354
  e({ type: 'step_delta', id: 's1', text: '!' });
@@ -2386,7 +2386,7 @@ describe('AgentWidgetClient - Out-of-Order Sequence Reordering', () => {
2386
2386
  };
2387
2387
  e({ type: 'flow_start', flowId: 'f1', flowName: 'Test', totalSteps: 1 });
2388
2388
  e({ type: 'step_start', id: 's1', name: 'Prompt', stepType: 'prompt', index: 1, totalSteps: 1 });
2389
- // seq=3 arrives first (leading gap seq 1 and 2 arrive later)
2389
+ // seq=3 arrives first (leading gap: seq 1 and 2 arrive later)
2390
2390
  e({ type: 'step_delta', id: 's1', text: 'c', partId: 'text_0', seq: 3 });
2391
2391
  e({ type: 'step_delta', id: 's1', text: 'a', partId: 'text_0', seq: 1 });
2392
2392
  e({ type: 'step_delta', id: 's1', text: 'b', partId: 'text_0', seq: 2 });
@@ -2425,7 +2425,7 @@ describe('AgentWidgetClient - Out-of-Order Sequence Reordering', () => {
2425
2425
  };
2426
2426
  e({ type: 'flow_start', flowId: 'f1', flowName: 'Test', totalSteps: 1 });
2427
2427
  e({ type: 'step_start', id: 's1', name: 'Prompt', stepType: 'prompt', index: 1, totalSteps: 1 });
2428
- // reason_delta uses sequenceIndex, step_delta uses seq same counter
2428
+ // reason_delta uses sequenceIndex, step_delta uses seq: same counter
2429
2429
  e({ type: 'reason_start', reasoningId: 'r1', hidden: false, done: false });
2430
2430
  e({ type: 'reason_delta', reasoningId: 'r1', reasoningText: 'thinking', hidden: false, done: false, sequenceIndex: 1 });
2431
2431
  e({ type: 'reason_complete', reasoningId: 'r1', hidden: false, done: true });
@@ -2518,7 +2518,7 @@ describe('AgentWidgetClient - Out-of-Order Sequence Reordering', () => {
2518
2518
  };
2519
2519
  e({ type: 'flow_start', flowId: 'f1', flowName: 'Test', totalSteps: 1 });
2520
2520
  e({ type: 'step_start', id: 's1', name: 'Prompt', stepType: 'prompt', index: 1, totalSteps: 1 });
2521
- // Only a seq=3 event arrives seq=1 and seq=2 are never delivered.
2521
+ // Only a seq=3 event arrives: seq=1 and seq=2 are never delivered.
2522
2522
  // Without the end-of-stream flush, this event would be stranded in
2523
2523
  // the reorder buffer and never emitted.
2524
2524
  e({ type: 'step_delta', id: 's1', text: 'tail', partId: 'text_0', seq: 3 });
@@ -2607,7 +2607,7 @@ describe('AgentWidgetClient - Out-of-Order Sequence Reordering', () => {
2607
2607
  controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`));
2608
2608
  };
2609
2609
  e({ type: 'flow_start', flowId: 'f1', flowName: 'Test', totalSteps: 1 });
2610
- // Only sequenced event but seq > 1 so it would be buffered.
2610
+ // Only sequenced event, but seq > 1 so it would be buffered.
2611
2611
  e({ type: 'error', error: 'boom', seq: 2 });
2612
2612
  controller.close();
2613
2613
  },
@@ -2767,7 +2767,7 @@ describe('AgentWidgetClient - stopReason propagation', () => {
2767
2767
  });
2768
2768
 
2769
2769
  // ============================================================================
2770
- // Within-turn text/tool interleaving assistant text bubbles must seal at
2770
+ // Within-turn text/tool interleaving: assistant text bubbles must seal at
2771
2771
  // each agent_tool_start so the chronological text→tool→text→tool sequence
2772
2772
  // renders as distinct timeline entries instead of one merged bubble that
2773
2773
  // appears below all the tool cards.
@@ -3004,7 +3004,7 @@ describe('AgentWidgetClient - agent_turn text/tool interleaving', () => {
3004
3004
  // step_await (LOCAL tool pause) + resumeFlow
3005
3005
  // ============================================================================
3006
3006
 
3007
- describe('AgentWidgetClient step_await parsing', () => {
3007
+ describe('AgentWidgetClient: step_await parsing', () => {
3008
3008
  const buildStepAwaitStream = (payload: Record<string, unknown>): ReadableStream<Uint8Array> => {
3009
3009
  const encoder = new TextEncoder();
3010
3010
  const body = `event: step_await\ndata: ${JSON.stringify({ type: 'step_await', ...payload })}\n\n`;
@@ -3196,7 +3196,7 @@ describe('AgentWidgetClient.resumeFlow', () => {
3196
3196
 
3197
3197
  await client.resumeFlow('exec_xyz', { toolu_A: { ok: true } });
3198
3198
 
3199
- // Session-authed sibling of /v1/client/chat no Bearer key, sessionId in body.
3199
+ // Session-authed sibling of /v1/client/chat: no Bearer key, sessionId in body.
3200
3200
  expect(capturedUrl).toBe('https://api.runtype.com/v1/client/resume');
3201
3201
  expect(capturedBody).toEqual({
3202
3202
  executionId: 'exec_xyz',
@@ -3239,7 +3239,7 @@ describe('AgentWidgetClient.resumeFlow', () => {
3239
3239
  clientToken: 'ct_live_demo',
3240
3240
  apiUrl: 'https://api.runtype.com',
3241
3241
  });
3242
- // A stale session that has already expired a long WebMCP approval wait can
3242
+ // A stale session that has already expired: a long WebMCP approval wait can
3243
3243
  // outlive it, so resumeFlow must not trust this.clientSession directly.
3244
3244
  (client as unknown as { clientSession: { sessionId: string; expiresAt: Date } }).clientSession = {
3245
3245
  sessionId: 'cs_stale',
@@ -3647,7 +3647,7 @@ describe('AgentWidgetClient - agent_media events', () => {
3647
3647
  sseEvent('agent_iteration_start', { executionId: execId, iteration: 1, maxTurns: 1, startedAt: new Date().toISOString(), seq: 2 }),
3648
3648
  sseEvent('agent_turn_start', { executionId: execId, iteration: 1, turnIndex: 0, role: 'assistant', turnId: 'turn_1', seq: 3 }),
3649
3649
  sseEvent('agent_turn_delta', { executionId: execId, iteration: 1, delta: 'Streaming...', contentType: 'text', turnId: 'turn_1', seq: 4 }),
3650
- // Media arrives mid-stream earlier text bubble is still streaming.
3650
+ // Media arrives mid-stream: earlier text bubble is still streaming.
3651
3651
  sseEvent('agent_media', {
3652
3652
  executionId: execId, iteration: 1, toolCallId: 'tc_seal', toolName: 'shot',
3653
3653
  media: [{ type: 'media', data: 'PNG', mediaType: 'image/png' }],
@@ -3755,7 +3755,7 @@ describe('AgentWidgetClient - agent_media events', () => {
3755
3755
  sseEvent('agent_iteration_start', { executionId: execId, iteration: 1, maxTurns: 1, startedAt: new Date().toISOString(), seq: 2 }),
3756
3756
  sseEvent('agent_media', {
3757
3757
  executionId: execId, iteration: 1, toolCallId: 'tc_blob', toolName: 'opaque',
3758
- // mediaType is empty should not produce a malformed `data:;base64,...` URI.
3758
+ // mediaType is empty: should not produce a malformed `data:;base64,...` URI.
3759
3759
  media: [{ type: 'media', data: 'AAAA', mediaType: '' }],
3760
3760
  seq: 3,
3761
3761
  }),
@@ -4004,7 +4004,7 @@ describe('AgentWidgetClient - diff-only clientTools (client-token)', () => {
4004
4004
  return sse();
4005
4005
  });
4006
4006
  const client = makeClient(TOOLS);
4007
- // Pre-seed the cache so the first attempt would be fingerprint-only the
4007
+ // Pre-seed the cache so the first attempt would be fingerprint-only: the
4008
4008
  // server then forces a resend.
4009
4009
  (client as unknown as { lastSentClientToolsFingerprint: string | null; clientToolsFingerprintSessionId: string | null }).lastSentClientToolsFingerprint =
4010
4010
  'stale';
package/src/client.ts CHANGED
@@ -180,7 +180,7 @@ export class AgentWidgetClient {
180
180
  private lastSentClientToolsFingerprint: string | null = null;
181
181
  private clientToolsFingerprintSessionId: string | null = null;
182
182
 
183
- // WebMCP page-discovered tool consumption (see ./webmcp-bridge).
183
+ // WebMCP: page-discovered tool consumption (see ./webmcp-bridge).
184
184
  // Constructed lazily: null when `config.webmcp?.enabled !== true`.
185
185
  private readonly webMcpBridge: WebMcpBridge | null;
186
186
 
@@ -206,8 +206,8 @@ export class AgentWidgetClient {
206
206
  * Refresh config in place WITHOUT tearing down the live connection or the
207
207
  * WebMCP bridge. `AgentWidgetSession.updateConfig` calls this when only
208
208
  * connection-irrelevant fields changed (theme, copy, layout, suggestions, …),
209
- * so a UI update that lands mid-turn e.g. a `webmcp:*` tool restyling the
210
- * widget while the agent's turn is still streaming doesn't abandon the
209
+ * so a UI update that lands mid-turn: e.g. a `webmcp:*` tool restyling the
210
+ * widget while the agent's turn is still streaming: doesn't abandon the
211
211
  * in-flight stream/resume. Connection or request-shaping changes (apiUrl,
212
212
  * clientToken, webmcp, headers, parser, …) take the full client rebuild path
213
213
  * in the session instead, which is the only place the bridge is recreated.
@@ -215,7 +215,7 @@ export class AgentWidgetClient {
215
215
  * Only the live-read `config` is refreshed (e.g. `iterationDisplay`); the
216
216
  * constructor-derived request-shaping fields (apiUrl, headers, parser,
217
217
  * contextProviders, middleware, …) are left untouched because the session
218
- * routes any change to those down the full-rebuild path instead so they are
218
+ * routes any change to those down the full-rebuild path instead, so they are
219
219
  * guaranteed unchanged here. The `webMcpBridge` instance and its
220
220
  * installed-polyfill memo are deliberately preserved, which keeps any
221
221
  * in-flight resolve alive.
@@ -254,7 +254,7 @@ export class AgentWidgetClient {
254
254
  * WebMCP: execute a returned `webmcp:<name>` tool call against the page's
255
255
  * registry and return the normalized MCP-shaped result for `/resume`. The
256
256
  * bridge handles confirm-bubble gating, the 30s timeout, error
257
- * normalization, and `signal`-driven abort callers never see throws.
257
+ * normalization, and `signal`-driven abort: callers never see throws.
258
258
  *
259
259
  * Returns `null` when WebMCP is not enabled on this client (signal to the
260
260
  * session that it should fall back to the legacy local-tool resume path,
@@ -701,7 +701,7 @@ export class AgentWidgetClient {
701
701
  this.lastSentClientToolsFingerprint = null;
702
702
  continue;
703
703
  }
704
- // Some other 409 keep the parsed body for the handler below.
704
+ // Some other 409: keep the parsed body for the handler below.
705
705
  errorData = body ?? { error: 'Chat request failed' };
706
706
  }
707
707
  break;
@@ -971,10 +971,10 @@ export class AgentWidgetClient {
971
971
  * Routes by mode:
972
972
  * - **client-token mode**: POST `${apiBase}/v1/client/resume` (the
973
973
  * session-authenticated sibling of `/v1/client/chat`; runtypelabs/core#3889),
974
- * with the active `sessionId` in the body and no Bearer key a browser
974
+ * with the active `sessionId` in the body and no Bearer key: a browser
975
975
  * client-token page holds no secret. `clientTools` are already persisted
976
976
  * server-side from the dispatch turn, so only `toolOutputs` is re-sent.
977
- * - **dispatch / proxy mode**: POST `${apiUrl}/resume` Runtype mounts
977
+ * - **dispatch / proxy mode**: POST `${apiUrl}/resume`: Runtype mounts
978
978
  * resume as a child of `/v1/dispatch`, so the URL is `${apiUrl}/resume`,
979
979
  * and proxies follow the same shape (`/api/chat/dispatch/resume`).
980
980
  *
@@ -998,8 +998,8 @@ export class AgentWidgetClient {
998
998
  // The client-token resume route authenticates the session, not a Bearer
999
999
  // key. A WebMCP approval can sit awaiting user input for a long time, so by
1000
1000
  // the time we resume the original session may have expired. Re-validate (and
1001
- // silently re-init if needed) via initSession() which returns the live
1002
- // session when `new Date() < expiresAt`, else mints a fresh one instead of
1001
+ // silently re-init if needed) via initSession(): which returns the live
1002
+ // session when `new Date() < expiresAt`, else mints a fresh one: instead of
1003
1003
  // trusting the possibly-stale `this.clientSession`. (core#3889; BugBot
1004
1004
  // PR #214 r3367875360.)
1005
1005
  let resumeSessionId: string | undefined;
@@ -1069,7 +1069,7 @@ export class AgentWidgetClient {
1069
1069
 
1070
1070
  // Client tools: built-in widget tools (ask_user_question, when exposed)
1071
1071
  // plus the per-turn WebMCP page-registry snapshot. Name collisions are
1072
- // impossible WebMCP entries are `webmcp:`-prefixed server-side while
1072
+ // impossible: WebMCP entries are `webmcp:`-prefixed server-side while
1073
1073
  // `sdk`-origin built-ins keep bare names. Both kinds ride the same
1074
1074
  // diff-only fingerprint path in client-token mode. Kept to a single await
1075
1075
  // so dispatch microtask timing is unchanged.
@@ -1185,7 +1185,7 @@ export class AgentWidgetClient {
1185
1185
  // dropping `clientTools` accidentally; the WebMCP wire surface
1186
1186
  // is invisible to them. The integrator can still set
1187
1187
  // `clientTools: []` or `clientTools: undefined` explicitly to
1188
- // strip them on purpose we only fall back when the field is
1188
+ // strip them on purpose: we only fall back when the field is
1189
1189
  // entirely absent from the returned object.
1190
1190
  if (
1191
1191
  payload.clientTools !== undefined &&
@@ -1258,7 +1258,7 @@ export class AgentWidgetClient {
1258
1258
  createNewAssistant(result.partId);
1259
1259
  }
1260
1260
 
1261
- // Update partId tracking (only when partId is provided backward compatible)
1261
+ // Update partId tracking (only when partId is provided: backward compatible)
1262
1262
  if (result.partId !== undefined) {
1263
1263
  partIdState.current = result.partId;
1264
1264
  }
@@ -1381,7 +1381,7 @@ export class AgentWidgetClient {
1381
1381
  // Messages produced by steps inside a nested flow executed as a tool.
1382
1382
  // Keyed by `${parentToolId}::${nestedStepId}::${partId}` so each nested
1383
1383
  // step (send-stream, prompt) gets its own assistant message, and prompts
1384
- // with inner tool calls split into one message per text segment still
1384
+ // with inner tool calls split into one message per text segment: still
1385
1385
  // attributable to the parent tool call.
1386
1386
  const nestedStepMessages = new Map<string, AgentWidgetMessage>();
1387
1387
  // Most-recent partId seen for a given `${toolId}::${stepId}` scope, used
@@ -1406,7 +1406,7 @@ export class AgentWidgetClient {
1406
1406
  ) => `${toolId}::${stepId}::${partId}`;
1407
1407
 
1408
1408
  // Prefix used to sweep every nested message belonging to a single
1409
- // (toolId, stepId) scope needed on step_complete to seal any segments
1409
+ // (toolId, stepId) scope: needed on step_complete to seal any segments
1410
1410
  // that are still streaming.
1411
1411
  const getNestedStepPrefix = (toolId: string, stepId: string) =>
1412
1412
  `${toolId}::${stepId}::`;
@@ -1728,7 +1728,7 @@ export class AgentWidgetClient {
1728
1728
  const cur = msg.content ?? "";
1729
1729
  if (mergedDisplay.trim() === "") return;
1730
1730
  // Only replace when empty, or when the stream left a strict prefix of the
1731
- // authoritative final (truncation). Do not use length alone multi-segment
1731
+ // authoritative final (truncation). Do not use length alone: multi-segment
1732
1732
  // flows can have a short last bubble whose content is not a prefix of the
1733
1733
  // full step response.
1734
1734
  if (
@@ -1810,7 +1810,7 @@ export class AgentWidgetClient {
1810
1810
  // Declared here so scheduleReadyQueueDrain can reference it; assigned
1811
1811
  // after all handler-scoped variables are initialised (before the SSE loop).
1812
1812
  let drainReadyQueue: () => void;
1813
- // Two drain paths both are intentional, do not remove either:
1813
+ // Two drain paths: both are intentional, do not remove either:
1814
1814
  // 1. Microtask drain (scheduleReadyQueueDrain): required when the
1815
1815
  // buffer's emitter fires from the gap-timeout setTimeout callback,
1816
1816
  // because there is no surrounding synchronous drain site there.
@@ -1931,7 +1931,7 @@ export class AgentWidgetClient {
1931
1931
  const toolId =
1932
1932
  resolveToolId(payload, true) ?? `tool-${nextSequence()}`;
1933
1933
  const toolName = payload.toolName ?? payload.name;
1934
- // Suppress tool UI for artifact emit tools artifacts are handled via artifact_* events
1934
+ // Suppress tool UI for artifact emit tools: artifacts are handled via artifact_* events
1935
1935
  if (isArtifactEmitToolName(toolName)) {
1936
1936
  artifactToolCallIds.add(toolId);
1937
1937
  continue;
@@ -2056,7 +2056,7 @@ export class AgentWidgetClient {
2056
2056
  // Key the message by the per-call `toolCallId` (provider `toolu_…`;
2057
2057
  // core#3878) when present. Two PARALLEL calls to the SAME tool in one
2058
2058
  // turn collapse to an identical `toolId` (`runtime_webmcp:<name>_<ms>`)
2059
- // and `index: 0` only `toolCallId` distinguishes them. Keying on it
2059
+ // and `index: 0`: only `toolCallId` distinguishes them. Keying on it
2060
2060
  // (a) keeps the two awaits as DISTINCT messages with their own args
2061
2061
  // instead of the second clobbering the first, and (b) merges each
2062
2062
  // await into the matching `tool_start` bubble (also keyed by
@@ -2105,7 +2105,7 @@ export class AgentWidgetClient {
2105
2105
  emitMessage(toolMessage);
2106
2106
  } else if (payloadType === "text_start") {
2107
2107
  // Lifecycle event: a new text segment is beginning (emitted at tool boundaries).
2108
- // When toolContext is present this fired inside a nested flow it must not
2108
+ // When toolContext is present this fired inside a nested flow: it must not
2109
2109
  // seal or rotate the outer assistant message. Nested prompt segmentation is
2110
2110
  // handled via nestedStepMessages keyed by (toolId, stepId).
2111
2111
  if ((payload as any).toolContext?.toolId) {
@@ -2127,7 +2127,7 @@ export class AgentWidgetClient {
2127
2127
  }
2128
2128
  } else if (payloadType === "text_end") {
2129
2129
  // Lifecycle event: current text segment ended (tool call about to start).
2130
- // When toolContext is present the boundary belongs to a nested flow leave
2130
+ // When toolContext is present the boundary belongs to a nested flow: leave
2131
2131
  // outer assistant state alone so the outer stream is never interrupted by
2132
2132
  // nested activity.
2133
2133
  if ((payload as any).toolContext?.toolId) {
@@ -2155,7 +2155,7 @@ export class AgentWidgetClient {
2155
2155
  // originated inside a nested flow executed as a tool. Surface it as
2156
2156
  // its own assistant message keyed by the nested step id, so authors
2157
2157
  // who add send-stream / prompt steps inside their flow see them as
2158
- // real messages in the timeline, in order rather than merging
2158
+ // real messages in the timeline, in order: rather than merging
2159
2159
  // into the outer assistant bubble or getting buried in the tool
2160
2160
  // card. Each nested step id gets its own message; the parent tool
2161
2161
  // bubble continues to represent the invocation via tool_* events.
@@ -2304,7 +2304,7 @@ export class AgentWidgetClient {
2304
2304
  const text = typeof result === 'string' ? result : result?.text ?? null;
2305
2305
 
2306
2306
  if (text !== null && text.trim() !== "") {
2307
- // Parser successfully extracted text update the chunk's assistant
2307
+ // Parser successfully extracted text: update the chunk's assistant
2308
2308
  // (not assistantMessage; text_end may have cleared that ref before microtasks run)
2309
2309
  assistant.content = text;
2310
2310
  emitMessage(assistant);
@@ -2478,14 +2478,14 @@ export class AgentWidgetClient {
2478
2478
 
2479
2479
  // Capture optional per-step stopReason emitted by the runtime
2480
2480
  // (e.g. `'max_tool_calls'`, `'length'`). This is the dispatch-mode
2481
- // fallback `agent_turn_complete` will overwrite it later in
2481
+ // fallback: `agent_turn_complete` will overwrite it later in
2482
2482
  // agent-loop streams.
2483
2483
  const stepStopReason = (payload as any).stopReason as
2484
2484
  | StopReasonKind
2485
2485
  | undefined;
2486
2486
 
2487
2487
  if (didSplitByPartId) {
2488
- // Sealed segment(s) do not create a second bubble from step_complete.
2488
+ // Sealed segment(s): do not create a second bubble from step_complete.
2489
2489
  // Merge authoritative final response into the last sealed segment (fixes async lag).
2490
2490
  if (assistantMessage !== null) {
2491
2491
  const msg: AgentWidgetMessage = assistantMessage;
@@ -2635,7 +2635,7 @@ export class AgentWidgetClient {
2635
2635
  } else if (payloadType === "flow_complete") {
2636
2636
  const finalContent = payload.result?.response;
2637
2637
  if (didSplitByPartId) {
2638
- // Content was split into multiple assistant messages the full response
2638
+ // Content was split into multiple assistant messages: the full response
2639
2639
  // in flow_complete would overwrite the last segment. Just finalize streaming.
2640
2640
  if (assistantMessage !== null) {
2641
2641
  const msg: AgentWidgetMessage = assistantMessage;
@@ -2808,8 +2808,7 @@ export class AgentWidgetClient {
2808
2808
  }
2809
2809
 
2810
2810
  // Attach the turn-level stopReason to the assistant message
2811
- // produced by this turn. Only overwrite the current message
2812
- // prior turns already sealed their own stopReason via step_complete.
2811
+ // produced by this turn. Only overwrite the current message: // prior turns already sealed their own stopReason via step_complete.
2813
2812
  // Falls back to lastAssistantInTurn when the current bubble was
2814
2813
  // sealed at a tool-call boundary mid-turn, so the notice still
2815
2814
  // attaches to the final visible text segment.
@@ -2895,7 +2894,7 @@ export class AgentWidgetClient {
2895
2894
  } else if (payloadType === "agent_media") {
2896
2895
  // A tool produced media (image / audio / video / file). Render it
2897
2896
  // as a synthetic assistant message inserted at the point the tool
2898
- // completed between the tool bubble and the next text turn.
2897
+ // completed: between the tool bubble and the next text turn.
2899
2898
  //
2900
2899
  // Wire format is the AI SDK–aligned `MediaContentPart` from
2901
2900
  // @runtypelabs/shared:
@@ -2922,7 +2921,7 @@ export class AgentWidgetClient {
2922
2921
  const data = typeof rec.data === "string" ? rec.data : undefined;
2923
2922
  if (!data) continue;
2924
2923
  // Empty/missing mediaType yields `data:;base64,...` which RFC 2397
2925
- // resolves to `text/plain` stamp a default so the data URI is
2924
+ // resolves to `text/plain`: stamp a default so the data URI is
2926
2925
  // well-formed and the part lands in the file bucket.
2927
2926
  mediaType = rawMediaType.length > 0 ? rawMediaType : "application/octet-stream";
2928
2927
  src = `data:${mediaType};base64,${data}`;
@@ -2999,7 +2998,7 @@ export class AgentWidgetClient {
2999
2998
 
3000
2999
  // Seal any in-flight assistant text bubble before splitting the
3001
3000
  // stream. Without this, an orphan bubble retains `streaming: true`
3002
- // forever `agent_complete` only finalizes the latest
3001
+ // forever: `agent_complete` only finalizes the latest
3003
3002
  // `assistantMessage`, so the typing/caret indicator would stay on
3004
3003
  // the prior bubble even though no more deltas will arrive.
3005
3004
  const prevAssistant = assistantMessage as AgentWidgetMessage | null;
@@ -5,7 +5,7 @@ import { VERSION } from "./version";
5
5
 
6
6
  // The `@runtypelabs/persona/codegen` subpath is the server/Worker-safe entry for
7
7
  // snippet generation. It must expose the exact same generator as the internal
8
- // module (and, transitively, the main barrel) no fork, no drift.
8
+ // module (and, transitively, the main barrel): no fork, no drift.
9
9
  describe("codegen subpath", () => {
10
10
  const config = {
11
11
  apiUrl: "https://api.example.com/chat",
package/src/codegen.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Pure code-snippet generation entry (`@runtypelabs/persona/codegen`).
3
3
  *
4
- * `generateCodeSnippet` is pure string-templating it builds install snippets
4
+ * `generateCodeSnippet` is pure string-templating: it builds install snippets
5
5
  * from a widget config and depends only on a type import + the `VERSION`
6
6
  * constant. The main `index.ts` barrel re-exports it too, but that barrel pulls
7
7
  * in the full widget runtime (`index-core` → idiomorph / marked / DOM), which is
@@ -280,7 +280,7 @@ describe("createApprovalBubble agent reason", () => {
280
280
  expect(reasonLine.textContent).toContain(
281
281
  "Adds the <b>large</b> blue tee to your cart as you asked."
282
282
  );
283
- // Plain text rendering markup in the agent-authored string must not
283
+ // Plain text rendering: markup in the agent-authored string must not
284
284
  // become elements (prompt-injection surface).
285
285
  expect(reasonLine.querySelector("b")).toBeNull();
286
286
  });
@@ -173,7 +173,7 @@ export const createApprovalBubble = (
173
173
  bubble.id = `bubble-${message.id}`;
174
174
  bubble.setAttribute("data-message-id", message.id);
175
175
 
176
- // Apply styling use semantic tokens with config overrides
176
+ // Apply styling: use semantic tokens with config overrides
177
177
  bubble.style.backgroundColor = approvalConfig?.backgroundColor ?? "var(--persona-approval-bg, #fefce8)";
178
178
  bubble.style.borderColor = approvalConfig?.borderColor ?? "var(--persona-approval-border, #fef08a)";
179
179
  bubble.style.boxShadow =
@@ -242,7 +242,7 @@ export const createApprovalBubble = (
242
242
 
243
243
  // User-facing summary line. The wire `description` is the tool's
244
244
  // agent-facing description (prompt prose, usage rules), so it is not shown
245
- // here it lives in the collapsible details section below. Label priority:
245
+ // here: it lives in the collapsible details section below. Label priority:
246
246
  // formatDescription → declared WebMCP `title` → humanized tool name →
247
247
  // raw description (no tool name at all).
248
248
  const isWebMcpTool =
@@ -276,7 +276,7 @@ export const createApprovalBubble = (
276
276
 
277
277
  // Agent-authored justification for this specific call. It is the agent's
278
278
  // own claim about its intent (attacker-writable under prompt injection), so
279
- // it is rendered as plain text via textContent never markdown/HTML and
279
+ // it is rendered as plain text via textContent, never markdown/HTML, and
280
280
  // explicitly attributed to the agent rather than spoken in system voice.
281
281
  if (approval.reason) {
282
282
  const reasonLine = createElement("p", "persona-text-sm persona-mt-1 persona-text-persona-muted");