@runtypelabs/persona 4.33.0 → 4.35.1

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 (220) hide show
  1. package/dist/activity-ui.d.cts +3 -15
  2. package/dist/activity-ui.d.ts +3 -15
  3. package/dist/animations/glyph-cycle.d.cts +1 -3
  4. package/dist/animations/glyph-cycle.d.ts +1 -3
  5. package/dist/animations/wipe.d.cts +1 -3
  6. package/dist/animations/wipe.d.ts +1 -3
  7. package/dist/approval-ui.cjs +2 -2
  8. package/dist/approval-ui.d.cts +3 -15
  9. package/dist/approval-ui.d.ts +3 -15
  10. package/dist/approval-ui.js +1 -1
  11. package/dist/artifacts-ui.cjs +1 -1
  12. package/dist/artifacts-ui.d.cts +3 -21
  13. package/dist/artifacts-ui.d.ts +3 -21
  14. package/dist/background-runtime.d.cts +2 -3
  15. package/dist/background-runtime.d.ts +2 -3
  16. package/dist/{chunk-DW7Vg_l2.js → chunk-B1P8w9_X.js} +1 -1
  17. package/dist/{chunk-D6U0E82p.js → chunk-B30Jdgg6.js} +1 -1
  18. package/dist/{chunk-gu34lr-P.js → chunk-Brdx4NCz.js} +1 -1
  19. package/dist/{chunk-6CUrH_jm.js → chunk-Bzo9GaAi.js} +1 -1
  20. package/dist/{chunk-Ds_twaZn.js → chunk-C4v-HNhS.js} +1 -1
  21. package/dist/{chunk-Bkma5hIU.js → chunk-CE4seJxF.js} +1 -1
  22. package/dist/chunk-CNBMBx-Y.js +2 -0
  23. package/dist/{chunk-ql5N2I_t.js → chunk-CWzHNuVK.js} +1 -1
  24. package/dist/{chunk-DbGLjU2P.js → chunk-CYpt7PsO.js} +1 -1
  25. package/dist/chunk-Cd8mp8Sv.js +1 -0
  26. package/dist/chunk-CsfE8WRG.js +1 -0
  27. package/dist/{chunk-DOZmcve8.js → chunk-Csr59G5V.js} +1 -1
  28. package/dist/{chunk-Co3VVwGe.js → chunk-D2pHRNEq.js} +0 -0
  29. package/dist/chunk-D3kZO8u-.js +1 -0
  30. package/dist/chunk-DAthf5H3.js +1 -0
  31. package/dist/{chunk-CXQJG7pb.js → chunk-DZ_aillY.js} +2 -2
  32. package/dist/{chunk-6C23QQdP.js → chunk-DgJV9zbK.js} +1 -1
  33. package/dist/chunk-Dv6KtIT1.js +1 -0
  34. package/dist/chunk-Dws8IrdR.js +1 -0
  35. package/dist/chunk-ThxxD8oK.js +1 -0
  36. package/dist/{chunk-C-EHtpbB.js → chunk-h5aOI_Pa.js} +1 -1
  37. package/dist/chunk-kizhspe5.js +1 -0
  38. package/dist/client-dispatch.js +1 -1
  39. package/dist/client-history.js +1 -1
  40. package/dist/client-stream.js +1 -1
  41. package/dist/{code-generators-DqauYyfX.d.cts → code-generators-DPAqTcAz.d.cts} +0 -2
  42. package/dist/{code-generators-DqauYyfX.d.ts → code-generators-DPAqTcAz.d.ts} +0 -2
  43. package/dist/codegen.cjs +1 -1
  44. package/dist/codegen.d.cts +1 -1
  45. package/dist/codegen.d.ts +1 -1
  46. package/dist/codegen.js +1 -1
  47. package/dist/composer-actions-runtime.cjs +1 -1
  48. package/dist/composer-actions-runtime.d.cts +4 -20
  49. package/dist/composer-actions-runtime.d.ts +4 -20
  50. package/dist/composer-actions-runtime.js +1 -1
  51. package/dist/{composer-input-DnqGCXZz.d.ts → composer-input-BSDNPOqV.d.cts} +1 -7
  52. package/dist/{composer-input-DnqGCXZz.d.cts → composer-input-BSDNPOqV.d.ts} +1 -7
  53. package/dist/{composer-store-6Iwy6WLI.d.ts → composer-store-DC-gAjtN.d.cts} +1 -3
  54. package/dist/{composer-store-6Iwy6WLI.d.cts → composer-store-DC-gAjtN.d.ts} +1 -3
  55. package/dist/config-diagnostics.d.cts +0 -2
  56. package/dist/config-diagnostics.d.ts +0 -2
  57. package/dist/{context-mention-manager-B-rHF98n.d.ts → context-mention-manager-CCpeoGL2.d.cts} +2 -6
  58. package/dist/{context-mention-manager-B-rHF98n.d.cts → context-mention-manager-CCpeoGL2.d.ts} +2 -6
  59. package/dist/context-mentions-inline.cjs +1 -1
  60. package/dist/context-mentions-inline.d.cts +2 -4
  61. package/dist/context-mentions-inline.d.ts +2 -4
  62. package/dist/context-mentions-inline.js +1 -1
  63. package/dist/context-mentions.cjs +2 -2
  64. package/dist/context-mentions.d.cts +3 -9
  65. package/dist/context-mentions.d.ts +3 -9
  66. package/dist/context-mentions.js +1 -1
  67. package/dist/{defaults-BuOjqUAG.d.ts → defaults-tj5RcN8r.d.cts} +1 -5
  68. package/dist/{defaults-BuOjqUAG.d.cts → defaults-tj5RcN8r.d.ts} +1 -5
  69. package/dist/dictation-runtime.cjs +1 -1
  70. package/dist/dictation-runtime.d.cts +4 -13
  71. package/dist/dictation-runtime.d.ts +4 -13
  72. package/dist/dictation-runtime.js +1 -1
  73. package/dist/{dom-context-DHTabIn7.d.cts → dom-context-Bw-_9Evh.d.cts} +0 -2
  74. package/dist/{dom-context-DHTabIn7.d.ts → dom-context-Bw-_9Evh.d.ts} +0 -2
  75. package/dist/event-stream-view.cjs +1 -1
  76. package/dist/event-stream-view.d.cts +3 -16
  77. package/dist/event-stream-view.d.ts +3 -16
  78. package/dist/{fallback-speech-engine-SqEfkGda.d.ts → fallback-speech-engine-gkhbjLiZ.d.cts} +1 -3
  79. package/dist/{fallback-speech-engine-SqEfkGda.d.cts → fallback-speech-engine-gkhbjLiZ.d.ts} +1 -3
  80. package/dist/{feature-preferences-DqAmje-2.d.ts → feature-preferences-CerDWGZ7.d.cts} +1 -5
  81. package/dist/{feature-preferences-DqAmje-2.d.cts → feature-preferences-CerDWGZ7.d.ts} +1 -5
  82. package/dist/forms-ui.d.cts +2 -4
  83. package/dist/forms-ui.d.ts +2 -4
  84. package/dist/{history-confirm-dialog-B3L7QNcO.d.cts → history-confirm-dialog-CrkSB3Ra.d.cts} +2 -14
  85. package/dist/{history-confirm-dialog-B3L7QNcO.d.ts → history-confirm-dialog-CrkSB3Ra.d.ts} +2 -14
  86. package/dist/history-shell.cjs +1 -1
  87. package/dist/history-shell.d.cts +8 -27
  88. package/dist/history-shell.d.ts +8 -27
  89. package/dist/history-shell.js +1 -1
  90. package/dist/history-view.d.cts +1 -1
  91. package/dist/history-view.d.ts +1 -1
  92. package/dist/icons-extra.cjs +1 -1
  93. package/dist/icons-extra.d.cts +0 -2
  94. package/dist/icons-extra.d.ts +0 -2
  95. package/dist/index.cjs +44 -44
  96. package/dist/index.cjs.map +1 -1
  97. package/dist/index.d.cts +10 -10
  98. package/dist/index.d.ts +10 -10
  99. package/dist/index.global.js +1 -1
  100. package/dist/index.global.js.map +1 -1
  101. package/dist/index.js +50 -50
  102. package/dist/index.js.map +1 -1
  103. package/dist/install.global.js +1 -1
  104. package/dist/install.global.js.map +1 -1
  105. package/dist/launcher.global.js +2 -2
  106. package/dist/launcher.global.js.map +1 -1
  107. package/dist/{loading-label-runtime-loader-JecjOfJS.d.ts → loading-label-runtime-loader-fWIRrhUF.d.cts} +1 -3
  108. package/dist/{loading-label-runtime-loader-JecjOfJS.d.cts → loading-label-runtime-loader-fWIRrhUF.d.ts} +1 -3
  109. package/dist/loading-label-runtime.cjs +1 -1
  110. package/dist/loading-label-runtime.d.cts +1 -1
  111. package/dist/loading-label-runtime.d.ts +1 -1
  112. package/dist/loading-label-runtime.js +1 -1
  113. package/dist/markdown-parsers.js +1 -1
  114. package/dist/{message-bubble-CBYtpDsT.d.ts → message-bubble-Czc0lOHr.d.cts} +20 -3
  115. package/dist/{message-bubble-CBYtpDsT.d.cts → message-bubble-Czc0lOHr.d.ts} +20 -3
  116. package/dist/{plugin-kit-C1mRjLgW.d.cts → plugin-kit-C30jE-cw.d.cts} +3 -3
  117. package/dist/{plugin-kit-C1mRjLgW.d.ts → plugin-kit-C30jE-cw.d.ts} +3 -3
  118. package/dist/plugin-kit.cjs +1 -1
  119. package/dist/plugin-kit.d.cts +1 -1
  120. package/dist/plugin-kit.d.ts +1 -1
  121. package/dist/plugin-kit.js +1 -1
  122. package/dist/preferences-runtime.cjs +1 -1
  123. package/dist/preferences-runtime.d.cts +1 -1
  124. package/dist/preferences-runtime.d.ts +1 -1
  125. package/dist/preferences-runtime.js +1 -1
  126. package/dist/{preview-_UJwwt2L.d.cts → preview-D-osV3FU.d.cts} +2 -6
  127. package/dist/{preview-_UJwwt2L.d.ts → preview-D-osV3FU.d.ts} +2 -6
  128. package/dist/{registry-B5ZV3K-h.d.ts → registry-B6w91tx0.d.cts} +1 -8
  129. package/dist/{registry-B5ZV3K-h.d.cts → registry-B6w91tx0.d.ts} +1 -8
  130. package/dist/{session-Bys8_XuB.d.cts → session-BUQrs3Of.d.cts} +10 -93
  131. package/dist/{session-Bys8_XuB.d.ts → session-BUQrs3Of.d.ts} +10 -93
  132. package/dist/session-reconnect.d.cts +2 -4
  133. package/dist/session-reconnect.d.ts +2 -4
  134. package/dist/smart-dom-reader.d.cts +2 -8
  135. package/dist/smart-dom-reader.d.ts +2 -8
  136. package/dist/testing.d.cts +0 -2
  137. package/dist/testing.d.ts +0 -2
  138. package/dist/theme-editor-preview.cjs +50 -50
  139. package/dist/theme-editor-preview.d.cts +1 -1
  140. package/dist/theme-editor-preview.d.ts +1 -1
  141. package/dist/theme-editor-preview.js +25 -25
  142. package/dist/theme-editor.cjs +25 -5
  143. package/dist/theme-editor.d.cts +2 -22
  144. package/dist/theme-editor.d.ts +2 -22
  145. package/dist/theme-editor.js +1 -1
  146. package/dist/theme-reference.cjs +1 -1
  147. package/dist/theme-reference.d.cts +2 -4
  148. package/dist/theme-reference.d.ts +2 -4
  149. package/dist/theme-reference.js +1 -1
  150. package/dist/{tool-loading-animation-d5qAQcwv.d.ts → tool-loading-animation-B7XUf8TN.d.cts} +1 -7
  151. package/dist/{tool-loading-animation-d5qAQcwv.d.cts → tool-loading-animation-B7XUf8TN.d.ts} +1 -7
  152. package/dist/{tooltip-BkW-8MnD.d.cts → tooltip-DbXkAuJL.d.cts} +0 -2
  153. package/dist/{tooltip-BkW-8MnD.d.ts → tooltip-DbXkAuJL.d.ts} +0 -2
  154. package/dist/{turn-activity-ui-q6fHVnx-.d.cts → turn-activity-ui-CohuNMHT.d.cts} +2 -12
  155. package/dist/{turn-activity-ui-q6fHVnx-.d.ts → turn-activity-ui-CohuNMHT.d.ts} +2 -12
  156. package/dist/turn-activity-ui.d.cts +1 -1
  157. package/dist/turn-activity-ui.d.ts +1 -1
  158. package/dist/{types-B_mj21jg.d.cts → types-9i0xcAMT.d.cts} +128 -29
  159. package/dist/{types-B_mj21jg.d.ts → types-9i0xcAMT.d.ts} +128 -29
  160. package/dist/ui-extras.js +3 -3
  161. package/dist/voice-client-tools.d.cts +1 -3
  162. package/dist/voice-client-tools.d.ts +1 -3
  163. package/dist/voice-runtime.cjs +1 -1
  164. package/dist/voice-runtime.d.cts +2 -12
  165. package/dist/voice-runtime.d.ts +2 -12
  166. package/dist/voice-runtime.js +2 -2
  167. package/dist/voice-worklet-player.d.cts +2 -6
  168. package/dist/voice-worklet-player.d.ts +2 -6
  169. package/dist/{webmcp-runtime-entry-ClEO3qdI.d.ts → webmcp-runtime-entry-XNaXa-9h.d.cts} +9 -5
  170. package/dist/{webmcp-runtime-entry-ClEO3qdI.d.cts → webmcp-runtime-entry-XNaXa-9h.d.ts} +9 -5
  171. package/dist/webmcp-runtime.cjs +1 -1
  172. package/dist/webmcp-runtime.d.cts +1 -1
  173. package/dist/webmcp-runtime.d.ts +1 -1
  174. package/dist/webmcp-runtime.js +1 -1
  175. package/dist/widget.css +1 -1
  176. package/package.json +5 -5
  177. package/src/client-dispatch.ts +13 -0
  178. package/src/client.ts +37 -2
  179. package/src/client.webmcp-require-confirm.test.ts +212 -0
  180. package/src/components/composer-model-picker.ts +0 -14
  181. package/src/components/composer-overflow-menu.ts +0 -15
  182. package/src/components/composer-tokens.ts +0 -20
  183. package/src/components/event-stream-view.test.ts +5 -0
  184. package/src/components/message-bubble.ts +26 -6
  185. package/src/components/scroll-to-bottom.test.ts +135 -0
  186. package/src/components/scroll-to-bottom.ts +50 -13
  187. package/src/components/user-message-collapse.ts +117 -0
  188. package/src/defaults.ts +6 -0
  189. package/src/generated/runtype-openapi-contract.ts +13 -0
  190. package/src/plugin-kit.test.ts +101 -0
  191. package/src/plugin-kit.ts +29 -1
  192. package/src/session.ts +6 -2
  193. package/src/session.webmcp.test.ts +32 -0
  194. package/src/styles/widget.css +56 -0
  195. package/src/theme-reference.ts +2 -2
  196. package/src/types/theme.ts +29 -0
  197. package/src/types.ts +84 -5
  198. package/src/ui.composer-models.test.ts +5 -7
  199. package/src/ui.composer-overflow.test.ts +14 -21
  200. package/src/ui.scroll-additive.test.ts +219 -117
  201. package/src/ui.scroll.test.ts +176 -13
  202. package/src/ui.ts +230 -107
  203. package/src/ui.turn-activity.test.ts +7 -7
  204. package/src/ui.user-message-collapse.test.ts +540 -0
  205. package/src/utils/auto-follow.test.ts +0 -38
  206. package/src/utils/auto-follow.ts +0 -35
  207. package/src/utils/context-mention-controller.test.ts +56 -0
  208. package/src/utils/theme.test.ts +23 -0
  209. package/src/utils/tokens.ts +16 -0
  210. package/src/webmcp-bridge.test.ts +102 -0
  211. package/src/webmcp-runtime-entry.ts +29 -1
  212. package/dist/chunk-BAkaTZpd.js +0 -1
  213. package/dist/chunk-BEDTp-dm.js +0 -1
  214. package/dist/chunk-C7-m0EmI.js +0 -1
  215. package/dist/chunk-CNhThrLX.js +0 -1
  216. package/dist/chunk-CVMIncEY.js +0 -1
  217. package/dist/chunk-CzLEhoB_.js +0 -2
  218. package/dist/chunk-DVgzoVCB.js +0 -1
  219. package/dist/chunk-d54BJdKd.js +0 -1
  220. package/dist/chunk-dLJWKfOb.js +0 -1
@@ -1230,3 +1230,35 @@ describe("AgentWidgetSession: WebMCP parallel batched resume (core#3878)", () =>
1230
1230
  expect(s.webMcpApprovalResolvers.size).toBe(0);
1231
1231
  });
1232
1232
  });
1233
+
1234
+ describe("WebMCP requireConfirmFor on the default approval UI", () => {
1235
+ const pendingBubbles = (session: AgentWidgetSession) =>
1236
+ (session as unknown as { messages: AgentWidgetMessage[] }).messages.filter(
1237
+ (m) => m.variant === "approval" && m.approval?.status === "pending",
1238
+ );
1239
+
1240
+ it("prompts for a matched tool even when autoApprove returns true", async () => {
1241
+ const autoApprove = vi.fn(() => true);
1242
+ const { session } = makeSession({ webmcp: { autoApprove } });
1243
+ const pending = session.requestWebMcpApproval({
1244
+ toolName: "checkout_submit",
1245
+ args: {},
1246
+ reason: "gate",
1247
+ confirmRequired: true,
1248
+ });
1249
+ expect(autoApprove).not.toHaveBeenCalled();
1250
+ expect(pendingBubbles(session)).toHaveLength(1);
1251
+ session.cancel();
1252
+ await expect(pending).resolves.toBe(false);
1253
+ });
1254
+
1255
+ it("still auto-approves an unmatched tool", async () => {
1256
+ const autoApprove = vi.fn(() => true);
1257
+ const { session } = makeSession({ webmcp: { autoApprove } });
1258
+ await expect(
1259
+ session.requestWebMcpApproval({ toolName: "search", args: {}, reason: "gate" }),
1260
+ ).resolves.toBe(true);
1261
+ expect(autoApprove).toHaveBeenCalledTimes(1);
1262
+ expect(pendingBubbles(session)).toHaveLength(0);
1263
+ });
1264
+ });
@@ -3109,6 +3109,52 @@
3109
3109
  box-shadow: var(--persona-message-user-shadow);
3110
3110
  }
3111
3111
 
3112
+ /* Long user message collapse (features.userMessageCollapse). The region clips
3113
+ without becoming a scroller (overflow: clip), so the full text stays in the
3114
+ DOM for find, copy and screen readers with no nested scroll area. The fade
3115
+ is a mask rather than a gradient overlay, so it works on any bubble fill in
3116
+ light and dark schemes; the toggle inherits the bubble's text color. */
3117
+ .persona-user-collapse-region[data-persona-user-collapse="collapsed"] {
3118
+ max-height: var(--persona-user-collapse-max-height, 240px);
3119
+ overflow: clip;
3120
+ -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - var(--persona-user-collapse-fade-height, 48px)), transparent);
3121
+ mask-image: linear-gradient(to bottom, #000 calc(100% - var(--persona-user-collapse-fade-height, 48px)), transparent);
3122
+ }
3123
+
3124
+ .persona-user-collapse-toggle {
3125
+ display: inline-flex;
3126
+ align-items: center;
3127
+ margin-top: 0.25rem;
3128
+ padding: 0.125rem 0;
3129
+ border: 0;
3130
+ background: none;
3131
+ color: var(--persona-user-collapse-toggle-fg, inherit);
3132
+ font-family: inherit;
3133
+ font-size: var(--persona-user-collapse-toggle-font-size, 0.8125rem);
3134
+ font-weight: var(--persona-user-collapse-toggle-font-weight, 600);
3135
+ line-height: 1.25rem;
3136
+ cursor: pointer;
3137
+ text-decoration: underline;
3138
+ text-decoration-color: transparent;
3139
+ text-underline-offset: 2px;
3140
+ transition: text-decoration-color 150ms ease;
3141
+ }
3142
+
3143
+ .persona-user-collapse-toggle:hover {
3144
+ text-decoration-color: currentColor;
3145
+ }
3146
+
3147
+ .persona-user-collapse-toggle:focus-visible {
3148
+ outline: 2px solid currentColor;
3149
+ outline-offset: 2px;
3150
+ }
3151
+
3152
+ @media (prefers-reduced-motion: reduce) {
3153
+ .persona-user-collapse-toggle {
3154
+ transition: none;
3155
+ }
3156
+ }
3157
+
3112
3158
  [data-persona-root] .persona-tool-bubble.persona-shadow-sm {
3113
3159
  box-shadow: var(--persona-tool-bubble-shadow);
3114
3160
  }
@@ -4869,6 +4915,16 @@
4869
4915
  text-align: center;
4870
4916
  }
4871
4917
 
4918
+ /* A pending approval below the fold: the control widens to a labelled pill.
4919
+ Unset approval tokens keep the pill's own colors. */
4920
+ [data-persona-root] .persona-scroll-to-bottom-indicator[data-persona-scroll-to-bottom-approval] {
4921
+ border-color: var(--persona-scroll-to-bottom-approval-border, var(--persona-scroll-to-bottom-border, var(--persona-primary, #111827)));
4922
+ background: var(--persona-scroll-to-bottom-approval-bg, var(--persona-scroll-to-bottom-bg, var(--persona-button-primary-bg, var(--persona-accent, #0f0f0f))));
4923
+ color: var(--persona-scroll-to-bottom-approval-fg, var(--persona-scroll-to-bottom-fg, var(--persona-button-primary-fg, var(--persona-text-inverse, #ffffff))));
4924
+ font-weight: var(--persona-scroll-to-bottom-approval-font-weight, 500);
4925
+ white-space: nowrap;
4926
+ }
4927
+
4872
4928
  [data-persona-root] .persona-scroll-to-bottom-indicator:hover {
4873
4929
  opacity: 0.92;
4874
4930
  }
@@ -171,7 +171,7 @@ export const THEME_TOKEN_DOCS = {
171
171
  askUserQuestion: 'sheet (background, border, shadow), pill (background, foreground, borderRadius, selectedBackground, selectedForeground), customInput.background. Local features.askUserQuestion.styles still wins; navigation, labels and slide timing remain feature config.',
172
172
  attachment: 'image (background, border).',
173
173
  scrollToBottom:
174
- 'Floating scroll-to-bottom affordance shared by transcript and event stream: background, foreground, border, size, borderRadius, shadow, padding, gap, fontSize, iconSize.',
174
+ 'Floating scroll-to-bottom affordance shared by transcript and event stream: background, foreground, border, size, borderRadius, shadow, padding, gap, fontSize, iconSize; approval (background, foreground, border, fontWeight) for the labelled "Approval needed" state, falling back to the control colors.',
175
175
  toolBubble: 'shadow, background, borderColor, borderWidth, borderRadius; header (background, foreground, padding), content (background, foreground, padding), codeBlock (background, foreground, borderColor), toggleForeground, labelForeground, loading (primary, secondary, duration). Applies to card and activity-row variants without changing their distinct defaults. CSS duration uses ms/s; legacy numeric milliseconds must convert explicitly. Theme content.padding precedes activity.bodyPadding/toolBubble.bodyPadding fallbacks; explicit legacy axes override only their axis.',
176
176
  scrollbar:
177
177
  'Shared scrollbar appearance for every scroller: thumb (default semantic.colors.border), track (default transparent).',
@@ -250,7 +250,7 @@ export const THEME_TOKEN_DOCS = {
250
250
  description:
251
251
  'Shared transcript + event-stream jump-to-latest affordance.',
252
252
  properties:
253
- 'features.scrollToBottom.enabled, features.scrollToBottom.iconName, features.scrollToBottom.label (empty string renders icon-only). Defaults: enabled=true, iconName="arrow-down", label="".',
253
+ 'features.scrollToBottom.enabled, features.scrollToBottom.iconName, features.scrollToBottom.label (empty string renders icon-only), features.scrollToBottom.approvalLabel (shown while a pending approval is below the fold; empty string keeps the regular control). Defaults: enabled=true, iconName="arrow-down", label="", approvalLabel="Approval needed".',
254
254
  },
255
255
  toolCall: {
256
256
  description:
@@ -530,6 +530,22 @@ export interface MessageActionsTokens {
530
530
  borderRadius?: TokenReference<'radius'>;
531
531
  }
532
532
 
533
+ /**
534
+ * Long user message collapse (`features.userMessageCollapse`). Every key is
535
+ * optional; unset, the fade and the toggle follow the user bubble's own text
536
+ * color and type, so they already work on any fill in light and dark schemes.
537
+ */
538
+ export interface UserMessageCollapseTokens {
539
+ /** Height of the bottom fade on a collapsed bubble. @default "48px" */
540
+ fadeHeight?: string;
541
+ /** "Show more" / "Show less" text color. Defaults to the user bubble text color. */
542
+ toggleForeground?: TokenReference<'color'>;
543
+ /** Toggle label type size. @default "0.8125rem" */
544
+ toggleFontSize?: string;
545
+ /** Toggle label weight. @default "600" */
546
+ toggleFontWeight?: string;
547
+ }
548
+
533
549
  /**
534
550
  * Bubble geometry and type, per role. Every key is unset by default and each
535
551
  * one falls back to the value the message layout preset already produced, so
@@ -1307,6 +1323,17 @@ export interface ScrollToBottomTokens extends ComponentTokenSet {
1307
1323
  fontSize?: string;
1308
1324
  fontWeight?: string;
1309
1325
  };
1326
+ /**
1327
+ * The labelled "Approval needed" state shown while a pending approval is
1328
+ * below the fold. Omitted values follow the control's own colors, so it
1329
+ * matches the theme until you give it an accent.
1330
+ */
1331
+ approval?: {
1332
+ background?: TokenReference<'color'>;
1333
+ foreground?: TokenReference<'color'>;
1334
+ border?: TokenReference<'color'>;
1335
+ fontWeight?: string;
1336
+ };
1310
1337
  }
1311
1338
 
1312
1339
  /** Visual tokens shared by one suggestion presentation variant. */
@@ -1357,6 +1384,8 @@ export interface ComponentTokens {
1357
1384
  message: MessageTokens;
1358
1385
  /** Hover-revealed per-message action row (copy, vote, read aloud). */
1359
1386
  messageActions?: MessageActionsTokens;
1387
+ /** Fade and "Show more" toggle on collapsed long user messages. */
1388
+ userMessageCollapse?: UserMessageCollapseTokens;
1360
1389
  /** Welcome / intro card shown above the message list. */
1361
1390
  introCard?: IntroCardTokens;
1362
1391
  /** Markdown surfaces (chat + artifact pane). */
package/src/types.ts CHANGED
@@ -886,6 +886,16 @@ export type WebMcpConfirmInfo = {
886
886
  * callbacks on v6 receive an AbortSignal, not a nested confirmation API.
887
887
  */
888
888
  reason: 'gate';
889
+ /**
890
+ * `true` when the tool matches a `requireConfirmFor` pattern (the widget's
891
+ * `webmcp.requireConfirmFor`, or the bound Runtype surface's
892
+ * `behavior.webmcp.requireConfirmFor` from client-token init). The call
893
+ * must be confirmed by the user: Persona never runs `autoApprove` for it,
894
+ * and a custom `onConfirm` should not auto-approve it either. In
895
+ * client-token mode it is also set on every call until the surface policy
896
+ * has loaded (fail closed).
897
+ */
898
+ confirmRequired?: true;
889
899
  /**
890
900
  * Aborts when the call no longer needs an answer (its deadline passed, or it
891
901
  * was cancelled). A confirm UI should close and resolve `false`; the tool
@@ -936,8 +946,23 @@ export type AgentWidgetWebMcpConfig = {
936
946
  * confirmation UI entirely. Use this to auto-allow read-only tools (e.g.
937
947
  * a catalog search) while still gating mutating ones. Only consulted on
938
948
  * the default-UI path: a custom `onConfirm` takes full control instead.
949
+ * Never consulted for a tool matching `requireConfirmFor`.
939
950
  */
940
951
  autoApprove?: (info: WebMcpConfirmInfo) => boolean;
952
+ /**
953
+ * Tool-name patterns that must always be confirmed, even when `autoApprove`
954
+ * would approve them (and even on the voice path's read-only fast tier).
955
+ * Matched against the bare tool name (no `webmcp:` prefix); `*` matches any
956
+ * run of characters, e.g. `["checkout_*", "delete_account"]`. A matched
957
+ * call shows the default approval bubble, or reaches your `onConfirm` with
958
+ * `info.confirmRequired === true`.
959
+ *
960
+ * Merged with the bound surface's `behavior.webmcp.requireConfirmFor`,
961
+ * which Runtype returns from `/v1/client/init` in client-token mode. This
962
+ * is client-side UX, not a security boundary: the server enforces only
963
+ * `webmcp.enabled` and its `allowlist`.
964
+ */
965
+ requireConfirmFor?: string[];
941
966
  /**
942
967
  * Confirm gate handler. When omitted, Persona renders its native in-panel
943
968
  * approval bubble (the same chrome used for server-driven tool approvals)
@@ -2228,10 +2253,13 @@ export type AgentWidgetArtifactsFeature = {
2228
2253
  *
2229
2254
  * - `"anchor-top"` (default): on send, scroll the user's message near the top
2230
2255
  * of the viewport and hold it there while the response streams in beneath it
2231
- * (ChatGPT-style). When a turn has no user send to anchor to (a proactive
2232
- * greeting, an injected assistant message, a resubmit, or first-load
2233
- * streaming), it falls back to `"follow"` for that turn so content never
2234
- * streams in off-screen.
2256
+ * (ChatGPT-style). The anchor holds for the whole reply, even when the
2257
+ * message is taller than the viewport or tool calls push the answer below
2258
+ * the fold, until the reader scrolls away, jumps to the latest content with
2259
+ * the scroll-to-bottom control, or sends again. When a turn has no user send
2260
+ * to anchor to (a proactive greeting, an injected assistant message, a
2261
+ * resubmit, or first-load streaming), it falls back to `"follow"` for that
2262
+ * turn so content never streams in off-screen.
2235
2263
  * - `"follow"`: keep the newest content pinned to the bottom of the viewport,
2236
2264
  * pausing when the user scrolls up and resuming when they return to the
2237
2265
  * bottom. This was the default before 4.x.
@@ -2320,7 +2348,8 @@ export type AgentWidgetScrollBehaviorFeature = {
2320
2348
 
2321
2349
  /** Decorative glyph state. Hidden glyphs should pause host-owned animation. */
2322
2350
  export type AgentWidgetScrollToBottomIconState = {
2323
- activity: "idle" | "generating-below";
2351
+ /** `"approval-below"`: a pending approval is below the fold and the turn waits on it. */
2352
+ activity: "idle" | "generating-below" | "approval-below";
2324
2353
  visible: boolean;
2325
2354
  hovered: boolean;
2326
2355
  focused: boolean;
@@ -2360,6 +2389,15 @@ export type AgentWidgetScrollToBottomFeature = {
2360
2389
  * @default ""
2361
2390
  */
2362
2391
  label?: string;
2392
+ /**
2393
+ * Label shown while a pending approval sits below the fold. The control
2394
+ * widens to a labelled pill with the approval's shield glyph, and the turn
2395
+ * stays put until the reader jumps (anchor-top never scrolls them to it).
2396
+ * Styled by `theme.components.scrollToBottom.approval`. Set to an empty
2397
+ * string to keep the regular control.
2398
+ * @default "Approval needed"
2399
+ */
2400
+ approvalLabel?: string;
2363
2401
  /** Glyph while a live response is arriving below. V4 defaults to arrow; V5 to dots. */
2364
2402
  streamingIndicator?: "dots" | "arrow";
2365
2403
  /** Visibility transition. V4 defaults to none; V5 to fade. Honors reduced motion. */
@@ -2788,6 +2826,31 @@ export type AgentWidgetStreamAnimationFeature = {
2788
2826
  plugins?: Record<string, StreamAnimationPlugin>;
2789
2827
  };
2790
2828
 
2829
+ /**
2830
+ * Collapse long user messages behind a "Show more" / "Show less" toggle.
2831
+ * Only the built-in user bubble collapses; `layout.messages.renderUserMessage`
2832
+ * and plugin `renderMessage` output is left untouched.
2833
+ */
2834
+ export type AgentWidgetUserMessageCollapseFeature = {
2835
+ /**
2836
+ * Clip a user bubble whose rendered content (text, markdown, code,
2837
+ * attachments) is taller than `maxHeight`, with a bottom fade and a real
2838
+ * toggle button. The full message stays in the DOM, so find-in-page, copy,
2839
+ * selection and screen readers still reach it. Interim voice transcripts
2840
+ * stay expanded and collapse once final. A reader's toggle is remembered
2841
+ * per message for the life of the widget.
2842
+ * @default false (`true` under `future.v5Defaults`)
2843
+ */
2844
+ enabled?: boolean;
2845
+ /**
2846
+ * Collapsed height in px. Content within about two lines (48px) of the
2847
+ * limit is left expanded, so the toggle never hides just a line or two.
2848
+ * The default shows about ten lines of bubble text.
2849
+ * @default 240
2850
+ */
2851
+ maxHeight?: number;
2852
+ };
2853
+
2791
2854
  export type AgentWidgetFeatureFlags = {
2792
2855
  showReasoning?: boolean;
2793
2856
  showToolCalls?: boolean;
@@ -2804,6 +2867,8 @@ export type AgentWidgetFeatureFlags = {
2804
2867
  scrollToBottom?: AgentWidgetScrollToBottomFeature;
2805
2868
  /** Transcript scroll behavior during streamed responses. */
2806
2869
  scrollBehavior?: AgentWidgetScrollBehaviorFeature;
2870
+ /** "Show more" collapse for long user messages. Off by default; on under `future.v5Defaults`. */
2871
+ userMessageCollapse?: AgentWidgetUserMessageCollapseFeature;
2807
2872
  /** Collapsed transcript behavior for tool call rows. */
2808
2873
  toolCallDisplay?: AgentWidgetToolCallDisplayFeature;
2809
2874
  /** Collapsed transcript behavior for reasoning rows. */
@@ -6782,6 +6847,11 @@ export type ClientSession = {
6782
6847
  welcomeMessage: string | null;
6783
6848
  placeholder: string;
6784
6849
  theme: Record<string, unknown> | null;
6850
+ /**
6851
+ * The bound chat surface's WebMCP confirm policy. Present only when the
6852
+ * surface enables WebMCP and lists `requireConfirmFor` patterns.
6853
+ */
6854
+ webmcp?: { requireConfirmFor: string[] };
6785
6855
  };
6786
6856
  };
6787
6857
 
@@ -6822,6 +6892,11 @@ export type ClientInitResponse = {
6822
6892
  welcomeMessage: string | null;
6823
6893
  placeholder: string;
6824
6894
  theme: Record<string, unknown> | null;
6895
+ /**
6896
+ * The bound chat surface's WebMCP confirm policy. Present only when the
6897
+ * surface enables WebMCP and lists `requireConfirmFor` patterns.
6898
+ */
6899
+ webmcp?: { requireConfirmFor: string[] };
6825
6900
  };
6826
6901
  };
6827
6902
 
@@ -8433,6 +8508,10 @@ export type AgentWidgetConfig = {
8433
8508
  sendButtonLabel?: string;
8434
8509
  /** Button label shown in text mode while a response is streaming. Default: "Stop". */
8435
8510
  stopButtonLabel?: string;
8511
+ /** Toggle label on a collapsed long user message (`features.userMessageCollapse`). @default "Show more" */
8512
+ showMoreLabel?: string;
8513
+ /** Toggle label on an expanded long user message (`features.userMessageCollapse`). @default "Show less" */
8514
+ showLessLabel?: string;
8436
8515
  /**
8437
8516
  * When false, the welcome / intro card is not shown above the message list.
8438
8517
  * @deprecated Use `welcome.variant: "none"`.
@@ -344,29 +344,27 @@ describe("composer model picker", () => {
344
344
  },
345
345
  });
346
346
  expect(mount.style.getPropertyValue(token)).toBe("#1e1f20");
347
- // The panel is portaled to document.body, so it never inherits the
348
- // mount's theme vars. Stamped on the trigger here because jsdom's
349
- // computed style does not inherit custom properties.
347
+ // The panel is portaled to document.body, outside the mount, so it
348
+ // carries a copy of the mount's theme vars.
350
349
  const button = trigger(mount)!;
351
- button.style.setProperty(token, "#1e1f20");
352
350
 
353
351
  button.click();
354
352
  const panel = menu()!;
353
+ expect(mount.contains(panel)).toBe(false);
355
354
  expect(panel.style.getPropertyValue(token)).toBe("#1e1f20");
356
355
  button.click();
357
356
 
358
357
  // Explicit undefined is the documented reset, so the mount stops
359
- // carrying the var; the trigger's stand-in follows it.
358
+ // carrying the var.
360
359
  controller.update({
361
360
  theme: {
362
361
  components: { composer: { modelPicker: { menuBackground: undefined } } },
363
362
  },
364
363
  });
365
364
  expect(mount.style.getPropertyValue(token)).toBe("");
366
- button.style.removeProperty(token);
367
365
 
368
366
  // The menu outlives the update, so the next open has to clear the value
369
- // the first one stamped; an empty inline value is the fallback state.
367
+ // the first one copied; an empty inline value is the fallback state.
370
368
  trigger(mount)!.click();
371
369
  expect(menu()).toBe(panel);
372
370
  expect(panel.style.getPropertyValue(token)).toBe("");
@@ -103,27 +103,25 @@ describe("composer overflow menu in the live widget", () => {
103
103
  vi.restoreAllMocks();
104
104
  });
105
105
 
106
- it("forwards the panel surface tokens onto the portaled menu on open", () => {
106
+ it("carries the panel surface tokens onto the portaled menu on open", () => {
107
107
  const { mount } = makeController({
108
108
  composer: {
109
109
  actions: [action("folded", { presentation: "overflow" })],
110
110
  actionOverflow: { enabled: true },
111
111
  },
112
+ theme: {
113
+ components: {
114
+ composer: {
115
+ overflowMenu: { background: "#353535", borderColor: "rgba(255, 255, 255, 0.08)" },
116
+ },
117
+ },
118
+ },
112
119
  });
113
- // The panel is portaled to document.body, so it never inherits the mount's
114
- // theme vars. Stamped on the trigger here because jsdom's computed style
115
- // does not inherit custom properties.
116
- const trigger = triggerOf(mount)!;
117
- trigger.style.setProperty(
118
- "--persona-components-composer-overflowMenu-background",
119
- "#353535"
120
- );
121
- trigger.style.setProperty(
122
- "--persona-components-composer-overflowMenu-borderColor",
123
- "rgba(255, 255, 255, 0.08)"
124
- );
125
120
 
121
+ // The panel is portaled to document.body, outside the mount, so it
122
+ // carries a copy of the mount's theme vars.
126
123
  const panel = openPanel(mount)!;
124
+ expect(mount.contains(panel)).toBe(false);
127
125
 
128
126
  expect(
129
127
  panel.style.getPropertyValue(
@@ -135,7 +133,7 @@ describe("composer overflow menu in the live widget", () => {
135
133
  "--persona-components-composer-overflowMenu-borderColor"
136
134
  )
137
135
  ).toBe("rgba(255, 255, 255, 0.08)");
138
- // Unset keys stamp nothing, so the stylesheet's fallback still stands.
136
+ // Unset keys copy nothing, so the stylesheet's fallback still stands.
139
137
  expect(
140
138
  panel.style.getPropertyValue(
141
139
  "--persona-components-composer-overflowMenu-shadow"
@@ -155,27 +153,22 @@ describe("composer overflow menu in the live widget", () => {
155
153
  },
156
154
  });
157
155
  expect(mount.style.getPropertyValue(token)).toBe("#353535");
158
- // Stamped on the trigger for the same reason as the test above: jsdom's
159
- // computed style does not inherit custom properties.
160
- const trigger = triggerOf(mount)!;
161
- trigger.style.setProperty(token, "#353535");
162
156
 
163
157
  const panel = openPanel(mount)!;
164
158
  expect(panel.style.getPropertyValue(token)).toBe("#353535");
165
159
  triggerOf(mount)!.click();
166
160
 
167
161
  // Explicit undefined is the documented reset, so the mount stops carrying
168
- // the var; the trigger's stand-in follows it.
162
+ // the var.
169
163
  controller.update({
170
164
  theme: {
171
165
  components: { composer: { overflowMenu: { background: undefined } } },
172
166
  },
173
167
  });
174
168
  expect(mount.style.getPropertyValue(token)).toBe("");
175
- trigger.style.removeProperty(token);
176
169
 
177
170
  // The panel outlives the update, so the next open has to clear the value
178
- // the first one stamped; an empty inline value is the fallback state.
171
+ // the first one copied; an empty inline value is the fallback state.
179
172
  const reopened = openPanel(mount)!;
180
173
  expect(reopened).toBe(panel);
181
174
  expect(reopened.style.getPropertyValue(token)).toBe("");