@runtypelabs/persona 4.23.2 → 4.24.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 (156) hide show
  1. package/dist/animations/glyph-cycle.d.cts +1 -1
  2. package/dist/animations/glyph-cycle.d.ts +1 -1
  3. package/dist/animations/{types-CI7OckWI.d.cts → types-Db61aI9J.d.cts} +2 -0
  4. package/dist/animations/{types-CI7OckWI.d.ts → types-Db61aI9J.d.ts} +2 -0
  5. package/dist/animations/wipe.d.cts +1 -1
  6. package/dist/animations/wipe.d.ts +1 -1
  7. package/dist/animations-extra.d.cts +2 -0
  8. package/dist/animations-extra.d.ts +2 -0
  9. package/dist/approval-ui-3NMYNXBD.js +1 -0
  10. package/dist/approval-ui.cjs +2 -2
  11. package/dist/approval-ui.d.cts +215 -37
  12. package/dist/approval-ui.d.ts +215 -37
  13. package/dist/approval-ui.js +2 -2
  14. package/dist/{artifacts-ui-F4N6VMKF.js → artifacts-ui-IGB5YKLO.js} +4 -4
  15. package/dist/artifacts-ui.d.cts +210 -25
  16. package/dist/artifacts-ui.d.ts +210 -25
  17. package/dist/{chunk-NIKYKSNZ.js → chunk-7CO4T5KB.js} +1 -1
  18. package/dist/codegen.cjs +11 -11
  19. package/dist/codegen.js +13 -13
  20. package/dist/context-mentions.d.cts +210 -25
  21. package/dist/context-mentions.d.ts +210 -25
  22. package/dist/event-stream-view.d.cts +210 -25
  23. package/dist/event-stream-view.d.ts +210 -25
  24. package/dist/forms-ui.d.cts +245 -27
  25. package/dist/forms-ui.d.ts +245 -27
  26. package/dist/history-view.d.cts +2 -0
  27. package/dist/history-view.d.ts +2 -0
  28. package/dist/index.cjs +70 -69
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.d.cts +270 -39
  31. package/dist/index.d.ts +270 -39
  32. package/dist/index.global.js +57 -54
  33. package/dist/index.global.js.map +1 -1
  34. package/dist/index.js +70 -69
  35. package/dist/index.js.map +1 -1
  36. package/dist/install.global.js +1 -1
  37. package/dist/install.global.js.map +1 -1
  38. package/dist/launcher.global.js +2 -2
  39. package/dist/launcher.global.js.map +1 -1
  40. package/dist/session-reconnect.d.cts +210 -25
  41. package/dist/session-reconnect.d.ts +210 -25
  42. package/dist/smart-dom-reader.d.cts +210 -25
  43. package/dist/smart-dom-reader.d.ts +210 -25
  44. package/dist/theme-editor-preview.cjs +74 -73
  45. package/dist/theme-editor-preview.d.cts +217 -25
  46. package/dist/theme-editor-preview.d.ts +217 -25
  47. package/dist/theme-editor-preview.js +51 -50
  48. package/dist/theme-editor.cjs +10 -10
  49. package/dist/theme-editor.d.cts +230 -27
  50. package/dist/theme-editor.d.ts +230 -27
  51. package/dist/theme-editor.js +10 -10
  52. package/dist/theme-reference.cjs +1 -1
  53. package/dist/theme-reference.d.cts +1522 -11
  54. package/dist/theme-reference.d.ts +1522 -11
  55. package/dist/theme-reference.js +1 -1
  56. package/dist/widget.css +1 -1
  57. package/package.json +3 -2
  58. package/src/__fixtures__/defaults-parity.v4.json +776 -0
  59. package/src/approval-ui-entry.ts +0 -1
  60. package/src/approval-ui-loader.ts +5 -4
  61. package/src/approval-ui.ts +0 -1
  62. package/src/client-identity-proof.test.ts +193 -0
  63. package/src/client-session-warm.test.ts +216 -0
  64. package/src/client-visitor-history.test.ts +6 -0
  65. package/src/client.ts +122 -30
  66. package/src/codegen.test.ts +24 -0
  67. package/src/components/activity-row.test.ts +100 -0
  68. package/src/components/activity-row.ts +136 -0
  69. package/src/components/approval-actions.test.ts +8 -5
  70. package/src/components/approval-actions.ts +19 -6
  71. package/src/components/approval-bubble.test.ts +56 -9
  72. package/src/components/approval-bubble.ts +36 -41
  73. package/src/components/composer-builder.ts +5 -2
  74. package/src/components/composer-parts.ts +10 -2
  75. package/src/components/header-builder.test.ts +32 -0
  76. package/src/components/header-builder.ts +7 -4
  77. package/src/components/header-layouts.test.ts +12 -0
  78. package/src/components/header-layouts.ts +3 -2
  79. package/src/components/launcher.ts +11 -2
  80. package/src/components/message-bubble.test.ts +22 -0
  81. package/src/components/message-bubble.ts +7 -3
  82. package/src/components/panel.test.ts +52 -1
  83. package/src/components/panel.ts +47 -8
  84. package/src/components/pill-composer-builder.test.ts +2 -0
  85. package/src/components/pill-composer-builder.ts +0 -1
  86. package/src/components/reasoning-bubble.ts +29 -17
  87. package/src/components/suggestions.test.ts +1 -0
  88. package/src/components/suggestions.ts +0 -16
  89. package/src/components/tool-bubble.ts +131 -114
  90. package/src/components/tool-details.test.ts +87 -0
  91. package/src/components/tool-details.ts +102 -0
  92. package/src/components/welcome.test.ts +8 -0
  93. package/src/components/welcome.ts +4 -2
  94. package/src/defaults-parity.test.ts +158 -0
  95. package/src/defaults.test.ts +69 -0
  96. package/src/defaults.ts +128 -49
  97. package/src/generated/runtype-openapi-contract.ts +2 -2
  98. package/src/index-core.ts +2 -0
  99. package/src/install.test.ts +24 -0
  100. package/src/install.ts +3 -1
  101. package/src/launcher-global.ts +2 -1
  102. package/src/launcher-variants.test.ts +52 -0
  103. package/src/runtime/init-update-reset.test.ts +21 -0
  104. package/src/session.ts +29 -6
  105. package/src/styles/widget-styles.test.ts +19 -0
  106. package/src/styles/widget.css +295 -23
  107. package/src/theme-editor/index.ts +2 -0
  108. package/src/theme-editor/preview-utils.test.ts +8 -0
  109. package/src/theme-editor/preview-utils.ts +7 -3
  110. package/src/theme-editor/sections.test.ts +31 -0
  111. package/src/theme-editor/sections.ts +55 -1
  112. package/src/theme-editor/state-preferences.test.ts +49 -0
  113. package/src/theme-editor/state.ts +73 -15
  114. package/src/theme-reference.test.ts +20 -0
  115. package/src/theme-reference.ts +11 -5
  116. package/src/tool-call-display-defaults.test.ts +33 -1
  117. package/src/types/theme.ts +80 -3
  118. package/src/types.ts +136 -22
  119. package/src/ui.activity-row.test.ts +91 -0
  120. package/src/ui.approval-plugin.test.ts +63 -0
  121. package/src/ui.composer-compact.test.ts +24 -2
  122. package/src/ui.composer-placement.test.ts +23 -0
  123. package/src/ui.detached-panel.test.ts +33 -6
  124. package/src/ui.docked.test.ts +19 -0
  125. package/src/ui.header-update-stability.test.ts +25 -0
  126. package/src/ui.identity-proof.test.ts +58 -0
  127. package/src/ui.launcher-teaser.test.ts +25 -0
  128. package/src/ui.message-width.test.ts +17 -2
  129. package/src/ui.reasoning-display.test.ts +92 -0
  130. package/src/ui.rebuild-composer.test.ts +20 -3
  131. package/src/ui.scroll.test.ts +32 -0
  132. package/src/ui.session-init.test.ts +312 -0
  133. package/src/ui.status-indicator.test.ts +82 -0
  134. package/src/ui.suggest-replies.test.ts +11 -0
  135. package/src/ui.ts +363 -74
  136. package/src/ui.v5-defaults.test.ts +144 -0
  137. package/src/ui.welcome.test.ts +92 -3
  138. package/src/utils/__snapshots__/tokens-parity.test.ts.snap +811 -140
  139. package/src/utils/code-generators.ts +14 -0
  140. package/src/utils/composer-compact.test.ts +0 -2
  141. package/src/utils/composer-compact.ts +2 -4
  142. package/src/utils/composer-placement.ts +2 -3
  143. package/src/utils/config-merge.test.ts +81 -5
  144. package/src/utils/config-merge.ts +13 -3
  145. package/src/utils/defaults-provenance.ts +67 -0
  146. package/src/utils/dock.ts +7 -1
  147. package/src/utils/launcher-variant.ts +16 -0
  148. package/src/utils/panel-config.ts +97 -0
  149. package/src/utils/status-indicator.ts +39 -0
  150. package/src/utils/theme.test.ts +182 -0
  151. package/src/utils/theme.ts +131 -18
  152. package/src/utils/tokens.test.ts +122 -1
  153. package/src/utils/tokens.ts +177 -17
  154. package/src/welcome.test.ts +23 -0
  155. package/src/welcome.ts +16 -9
  156. package/dist/approval-ui-K7R5Q5OC.js +0 -1
@@ -212,6 +212,8 @@ export interface InputTokens extends ComponentTokenSet {
212
212
  }
213
213
 
214
214
  export interface LauncherTokens extends ComponentTokenSet {
215
+ /** Distance from the viewport edges. Circle default: 20px; pill: 24px. */
216
+ offset?: string;
215
217
  size: string;
216
218
  iconSize: string;
217
219
  shadow: TokenReference<'shadow'>;
@@ -235,6 +237,15 @@ export interface PanelTokens extends ComponentTokenSet {
235
237
  inset?: string;
236
238
  /** Background of the region revealed behind a detached panel. */
237
239
  canvasBackground?: string;
240
+ /** Chrome defaults by rendered panel mode. */
241
+ modes?: Partial<Record<'floating' | 'inline' | 'sidebar' | 'docked' | 'mobile', {
242
+ /** Full border shorthand (for example, `"1px solid var(--persona-border)"`). */
243
+ border?: string;
244
+ /** Box-shadow token reference or raw CSS shadow. */
245
+ shadow?: string;
246
+ /** Corner radius token reference or raw CSS radius. */
247
+ borderRadius?: TokenReference<'radius'>;
248
+ }>>;
238
249
  }
239
250
 
240
251
  /** Per-element text styling shared by themable text surfaces. */
@@ -263,10 +274,16 @@ export interface HeaderTokens extends ComponentTokenSet {
263
274
  */
264
275
  border?: TokenReference<'color'>;
265
276
  borderRadius: TokenReference<'radius'>;
277
+ /** Standard-header padding. @default "20px 24px" */
278
+ padding?: string;
279
+ /** Minimal-header padding. Explicitly wins over `padding`. @default "16px 24px" */
280
+ minimalPadding?: string;
266
281
  /** Background of the rounded avatar tile next to the title (Lucide / emoji / image). */
267
282
  iconBackground: TokenReference<'color'>;
268
283
  /** Foreground (glyph stroke or emoji text) on the header avatar tile. */
269
284
  iconForeground: TokenReference<'color'>;
285
+ /** Header identity glyph size as a fraction of its icon box. @default "0.6" */
286
+ iconScale?: string;
270
287
  /**
271
288
  * Legacy alias of `title.color`; `title.color` wins when both are set.
272
289
  * Unset, the title takes `foreground`, then the primary color.
@@ -305,6 +322,14 @@ export interface HeaderTokens extends ComponentTokenSet {
305
322
  * sibling. @default "1.5"
306
323
  */
307
324
  controlStrokeWidth?: string;
325
+ /** Header control corner radius. Per-control radius overrides win. */
326
+ controlBorderRadius?: string;
327
+ /** Default control hover/focus fill. Explicit button backgrounds win. */
328
+ controlHoverBackground?: TokenReference<'color'>;
329
+ /** Default control hover/focus icon color. Explicit button colors win. */
330
+ controlHoverForeground?: TokenReference<'color'>;
331
+ /** Keyboard focus outline; unset preserves the browser default. */
332
+ controlFocusOutline?: string;
308
333
  /** Box-shadow on the header (e.g., a fade shadow to replace the default border). */
309
334
  shadow?: string;
310
335
  /** Override the header bottom border (e.g., `none`). */
@@ -484,6 +509,12 @@ export interface MessageGeometryTokens {
484
509
  }
485
510
 
486
511
  export interface MessageTokens {
512
+ /** Height of the optional transcript top fade. Default 18px. */
513
+ topFadeHeight?: string;
514
+ /** Vertical gap between transcript turns. @default "12px" */
515
+ gap?: string;
516
+ /** Gap between turns in fullscreen mode; falls back to `gap`. */
517
+ fullscreenGap?: string;
487
518
  user: MessageGeometryTokens & {
488
519
  background: TokenReference<'color'>;
489
520
  text: TokenReference<'color'>;
@@ -497,6 +528,8 @@ export interface MessageTokens {
497
528
  borderRadius: TokenReference<'radius'>;
498
529
  /** Assistant bubble border color (CSS color). */
499
530
  border?: TokenReference<'color'>;
531
+ /** Border width; set to 0px for a flat assistant response. @default "1px" */
532
+ borderWidth?: string;
500
533
  /** Assistant bubble box-shadow (token ref or raw CSS, e.g. `none`). */
501
534
  shadow?: string;
502
535
  };
@@ -622,13 +655,42 @@ export interface AttachmentTokens {
622
655
  }
623
656
 
624
657
  /** Tool-call row chrome (collapsible tool bubbles). */
625
- export interface ToolBubbleTokens {
658
+ export interface ActivityTokens {
659
+ /** Minimum activity row height. */
660
+ rowHeight?: string;
661
+ /** Group child indentation; falls back to indent when unset. V5: 0px. */
662
+ groupIndent?: string;
663
+ /** Extra vertical gap between grouped rows. V5: 0px. */
664
+ groupGap?: string;
665
+ /** Padding around expanded group children. V5: 0px. */
666
+ groupPadding?: string;
667
+ /** Gap between consecutive activity entries. V5: 0px. */
668
+ transcriptGap?: string;
669
+ /** Gap from activity to an assistant response. V5: 12px. */
670
+ responseGap?: string;
671
+ /** Expanded body padding. V5: 4px 0. */
672
+ bodyPadding?: string;
673
+ /** Expanded reasoning text size. V5: 13px. */
674
+ bodySize?: string;
675
+ /** Expanded reasoning line height. V5: 1.5. */
676
+ bodyLineHeight?: string;
677
+ /** Expanded reasoning font weight. V5: 400. */
678
+ bodyFontWeight?: string;
679
+ /** Gap between expanded detail sections. V5: 8px. */
680
+ sectionGap?: string;
681
+ labelSize?: string;
682
+ iconSize?: string;
683
+ indent?: string;
684
+ bodySurface?: TokenReference<'color'>;
685
+ }
686
+
687
+ export interface ToolBubbleTokens extends ActivityTokens {
626
688
  /** Box-shadow for tool bubbles (token ref or raw CSS, e.g. `none`). */
627
689
  shadow: string;
628
690
  }
629
691
 
630
692
  /** Reasoning / “thinking” row chrome. */
631
- export interface ReasoningBubbleTokens {
693
+ export interface ReasoningBubbleTokens extends ActivityTokens {
632
694
  shadow: string;
633
695
  }
634
696
 
@@ -769,6 +831,10 @@ export interface ComposerChromeTokens {
769
831
  shadow: string;
770
832
  /** Border color of the composer form. @default semantic.colors.border */
771
833
  borderColor?: TokenReference<'color'>;
834
+ /** Focused form border color; falls back to borderColor. V5: a stronger neutral border. */
835
+ focusBorderColor?: TokenReference<'color'>;
836
+ /** Outer focus ring (CSS outline shorthand). V4: none; V5: a thin neutral ring. */
837
+ focusRing?: string;
772
838
  /** Inner padding of the composer form (raw CSS shorthand). @default "0.75rem 1rem" */
773
839
  padding?: string;
774
840
  /** Gap between the textarea row and the actions row. @default "0.5rem" */
@@ -787,12 +853,18 @@ export interface ComposerChromeTokens {
787
853
  controlSize?: string;
788
854
  /** Glyph box inside those controls. @default "24px" */
789
855
  controlIconSize?: string;
856
+ /** Send/stop glyph size. Explicit sendButton.iconSize wins. V5: 18px. */
857
+ sendIconSize?: string;
858
+ /** Icon-mode submit button radius. V4 inherits the primary button; V5: 9999px. */
859
+ sendButtonRadius?: string;
860
+ /** Footer top border (CSS shorthand). V4: 1px solid divider; V5: none. */
861
+ footerBorder?: string;
790
862
  /**
791
863
  * Background painted behind an overlaid composer footer
792
864
  * (`composer.placement: "overlay"`). Any CSS `background` value, gradients
793
865
  * included — same convention as the shadow tokens. Inert under
794
866
  * `placement: "block"`.
795
- * @default "transparent"
867
+ * @default "transparent" (V4); a theme-aware 24px fade (V5)
796
868
  */
797
869
  overlayBand?: string;
798
870
  /** Segmented mode-group track. */
@@ -979,6 +1051,8 @@ export interface ScrollToBottomTokens extends ComponentTokenSet {
979
1051
 
980
1052
  /** Visual tokens shared by one suggestion presentation variant. */
981
1053
  export interface SuggestionVariantTokens extends ComponentTokenSet {
1054
+ /** Label weight. V4: 500; V5: 400. */
1055
+ fontWeight?: string;
982
1056
  /** Space inside one item, between its icon and its copy. */
983
1057
  gap?: string;
984
1058
  /** Space between suggestion items in the container. */
@@ -1030,6 +1104,7 @@ export interface ComponentTokens {
1030
1104
  voice: VoiceTokens;
1031
1105
  approval: ApprovalTokens;
1032
1106
  attachment: AttachmentTokens;
1107
+ activity?: ActivityTokens;
1033
1108
  toolBubble: ToolBubbleTokens;
1034
1109
  reasoningBubble: ReasoningBubbleTokens;
1035
1110
  /** Event-stream inspector badge chips. */
@@ -1124,4 +1199,6 @@ export interface CreateThemeOptions {
1124
1199
  plugins?: PersonaThemePlugin[];
1125
1200
  validate?: boolean;
1126
1201
  extend?: PersonaTheme;
1202
+ /** Selects the defaults overlay; explicit theme tokens always take precedence. */
1203
+ future?: { v5Defaults?: boolean };
1127
1204
  }
package/src/types.ts CHANGED
@@ -2254,6 +2254,14 @@ export type AgentWidgetToolCallLoadingAnimation =
2254
2254
  | "rainbow";
2255
2255
 
2256
2256
  export type AgentWidgetToolCallDisplayFeature = {
2257
+ /** Activity chrome. Defaults to card in V4 and row with future.v5Defaults. */
2258
+ variant?: "card" | "row";
2259
+ /** Row status icons: always, active/attention-needed only, or never. V5 defaults to active; V4 to always. */
2260
+ iconVisibility?: "always" | "active" | "never";
2261
+ /** Row variant: open when the first content chunk arrives. Default false in V5; true in V4. */
2262
+ autoExpand?: boolean;
2263
+ /** Row variant: collapse once after completion, in ms; false disables. Default 1000. */
2264
+ autoCollapseDelay?: number | false;
2257
2265
  /**
2258
2266
  * Controls what collapsed tool call rows show in their header/summary area.
2259
2267
  * @default "tool-call"
@@ -2284,9 +2292,10 @@ export type AgentWidgetToolCallDisplayFeature = {
2284
2292
  * Controls how a grouped tool sequence is rendered.
2285
2293
  * - `"stack"`: show the group summary and each child tool row.
2286
2294
  * - `"summary"`: show one consolidated summary row only.
2295
+ * - `"collapsible"`: expandable activity group (V5 default).
2287
2296
  * @default "stack"
2288
2297
  */
2289
- groupedMode?: "stack" | "summary";
2298
+ groupedMode?: "stack" | "summary" | "collapsible";
2290
2299
  /**
2291
2300
  * What happens to a tool call row once it completes.
2292
2301
  *
@@ -2319,6 +2328,14 @@ export type AgentWidgetToolCallDisplayFeature = {
2319
2328
  export type AgentWidgetToolCallCompletedVisibility = "kept" | "removed";
2320
2329
 
2321
2330
  export type AgentWidgetReasoningDisplayFeature = {
2331
+ /** Activity chrome. Defaults to card in V4 and row with future.v5Defaults. */
2332
+ variant?: "card" | "row";
2333
+ /** Row status icons: always, active/attention-needed only, or never. V5 defaults to active; V4 to always. */
2334
+ iconVisibility?: "always" | "active" | "never";
2335
+ /** Row variant: open when the first content chunk arrives. Default false in V5; true in V4. */
2336
+ autoExpand?: boolean;
2337
+ /** Row variant: collapse once after completion, in ms; false disables. Default 1000. */
2338
+ autoCollapseDelay?: number | false;
2322
2339
  /**
2323
2340
  * When true, active collapsed reasoning rows can render a lightweight preview block.
2324
2341
  * @default false
@@ -3078,8 +3095,8 @@ export interface AgentWidgetHistoryFeature {
3078
3095
  * otherwise `"browser"`) and narrows it to what the provider advertises, so a
3079
3096
  * browser-only provider is never asked for a scope it cannot serve. An
3080
3097
  * explicit `features.history.scope` is a hard request and still fails closed
3081
- * with `unsupported_scope`. `getIdentityProof` is never called for a supplied
3082
- * provider, and `HistoryOperationContext` carries only the resolved scope;
3098
+ * with `unsupported_scope`. History operations on a supplied provider never
3099
+ * call `getIdentityProof`, and `HistoryOperationContext` carries only the resolved scope;
3083
3100
  * report your own identity through `getIdentityStatus()` /
3084
3101
  * `subscribeIdentityStatus()`, which is what the scope banner and its retry
3085
3102
  * affordance read.
@@ -3483,7 +3500,7 @@ export type EventStreamConfig = {
3483
3500
  /**
3484
3501
  * Fields to extract from event payloads for description text.
3485
3502
  * The first matching field value is displayed after the badge.
3486
- * @default ["flowName", "stepName", "name", "tool", "toolName"]
3503
+ * @default ["flowName", "stepName", "reasoningText", "text", "name", "tool", "toolName"]
3487
3504
  */
3488
3505
  descriptionFields?: string[];
3489
3506
  /**
@@ -3702,6 +3719,8 @@ export type AgentWidgetComposerBarPeekConfig = {
3702
3719
  };
3703
3720
 
3704
3721
  export type AgentWidgetLauncherConfig = {
3722
+ /** Collapsed launcher shape. V4 defaults to pill; V5 defaults to circle. */
3723
+ variant?: "pill" | "circle";
3705
3724
  enabled?: boolean;
3706
3725
  title?: string;
3707
3726
  subtitle?: string;
@@ -4496,9 +4515,9 @@ export type AgentWidgetComposerConfig = {
4496
4515
  /**
4497
4516
  * Where the composer footer sits relative to the transcript.
4498
4517
  *
4499
- * - `"block"` (default): the footer is a flex sibling below the scroll body.
4518
+ * - `"block"` (V4 default): the footer is a flex sibling below the scroll body.
4500
4519
  * Nothing scrolls behind it.
4501
- * - `"overlay"`: the footer is absolutely overlaid on the scroll body, so the
4520
+ * - `"overlay"` (V5 default): the footer is absolutely overlaid on the scroll body, so the
4502
4521
  * transcript scrolls behind it (the chatgpt.com / claude.ai model, where the
4503
4522
  * composer is sticky inside the scroller). The widget reserves the footer's
4504
4523
  * live height as bottom padding on the scroll body and on a plugin welcome
@@ -4510,7 +4529,7 @@ export type AgentWidgetComposerConfig = {
4510
4529
  * included) and `theme.components.input.backdropFilter`.
4511
4530
  *
4512
4531
  * Ignored in composer-bar mount mode, which owns its own geometry.
4513
- * @default "block"
4532
+ * @default "block" (V4), "overlay" (V5)
4514
4533
  */
4515
4534
  placement?: ComposerPlacement;
4516
4535
  /**
@@ -4524,8 +4543,9 @@ export type AgentWidgetComposerConfig = {
4524
4543
  *
4525
4544
  * `"single-row"` describes the IDLE composer only. It composes with the
4526
4545
  * compact state, so a wrapped draft, chips, attachment previews, a quote, a
4527
- * pending card, or live dictation all fall back to the stacked card, exactly
4528
- * as `data-persona-composer-compact` does today.
4546
+ * pending card all fall back to the stacked card, exactly
4547
+ * as `data-persona-composer-compact` does today. Recording alone does not
4548
+ * expand the composer; dictated text expands it when it wraps.
4529
4549
  *
4530
4550
  * Ignored in composer-bar mount mode, whose pill is already one row.
4531
4551
  * @default "stacked"
@@ -4572,7 +4592,14 @@ export type AgentWidgetClearChatConfig = {
4572
4592
  };
4573
4593
 
4574
4594
  export type AgentWidgetStatusIndicatorConfig = {
4595
+ /** Show the indicator. Explicit false also hides transient connection states. */
4575
4596
  visible?: boolean;
4597
+ /**
4598
+ * "always" shows status beneath the composer; "transient" shows connecting,
4599
+ * reconnecting, and error status above it, hiding normal idle/streaming status.
4600
+ * Available in both defaults states. Default: "always" in v4, "transient" in v5.
4601
+ */
4602
+ mode?: "always" | "transient";
4576
4603
  /** Text alignment. Default: 'right'. */
4577
4604
  align?: 'left' | 'center' | 'right';
4578
4605
  idleText?: string;
@@ -5443,7 +5470,8 @@ export type AgentWidgetResolvedSuggestion = {
5443
5470
 
5444
5471
  /** Presentation keys shared by every suggestion surface. */
5445
5472
  export type AgentWidgetSuggestionSurfaceConfig = {
5446
- /** Item density. Defaults to `card` for starters and `chip` for follow-ups. */
5473
+ /** Item density. V4 starters: card. V5 starters: chip, or card in fullscreen.
5474
+ * Follow-ups default to chip. */
5447
5475
  variant?: AgentWidgetSuggestionVariant;
5448
5476
  /** Default click behavior, overridable per item. Defaults to `send`. */
5449
5477
  behavior?: AgentWidgetSuggestionBehavior;
@@ -5454,7 +5482,7 @@ export type AgentWidgetSuggestionSurfaceConfig = {
5454
5482
  * own stacking.
5455
5483
  */
5456
5484
  overflow?: "scroll" | "wrap";
5457
- /** Maximum visible items. Defaults to 4. */
5485
+ /** Maximum visible items. Defaults to 4, or 3 for V5 non-fullscreen starters. */
5458
5486
  maxItems?: number;
5459
5487
  };
5460
5488
 
@@ -5518,8 +5546,8 @@ export type AgentWidgetWelcomeVariant = "card" | "hero" | "none";
5518
5546
 
5519
5547
  /**
5520
5548
  * When the welcome surface goes away. `"never"` is the default for `"card"`;
5521
- * `"on-first-message"` is the default for `"hero"` and cannot be overridden
5522
- * there.
5549
+ * `"on-first-message"` is forced for legacy V4 `"hero"`. V5 defaults every
5550
+ * variant to `"on-first-message"`. Explicit layout opts into overridable dismissal.
5523
5551
  */
5524
5552
  export type AgentWidgetWelcomeDismiss = "never" | "on-first-message";
5525
5553
 
@@ -5571,7 +5599,10 @@ export type AgentWidgetWelcomeAlign = "start" | "center";
5571
5599
  * host-set value from a filled-in default.
5572
5600
  */
5573
5601
  export interface AgentWidgetWelcomeConfig {
5574
- /** Card or hero title. Defaults to "Hello 👋". */
5602
+ /** Vertical placement: V4 defaults to top; V5 to centered. Fullscreen centered
5603
+ * welcomes also raise the empty composer unless `anchor` is explicitly set. */
5604
+ layout?: "top" | "centered";
5605
+ /** Card or hero title. V4: "Hello 👋". V5: "What can I help with?". */
5575
5606
  title?: string;
5576
5607
  /**
5577
5608
  * Small muted line above the title, e.g. a section name. Omitted when unset
@@ -5584,19 +5615,22 @@ export interface AgentWidgetWelcomeConfig {
5584
5615
  icon?: AgentWidgetWelcomeIcon;
5585
5616
  /**
5586
5617
  * Horizontal alignment of the kicker, title, subtitle, and starter
5587
- * suggestions. Unset follows the variant: `card` starts, `hero` centers.
5618
+ * suggestions. Centered layouts default to center. Otherwise unset follows
5619
+ * the variant: `card` starts, `hero` centers.
5588
5620
  */
5589
5621
  align?: AgentWidgetWelcomeAlign;
5590
5622
  /** @default "card" */
5591
5623
  variant?: AgentWidgetWelcomeVariant;
5592
- /** @default "never" for `card`, always `"on-first-message"` for `hero`. */
5624
+ /** V4: never for card, forced on-first-message for hero. V5: on-first-message
5625
+ * for all variants; explicit values win. Explicit layout also allows overriding
5626
+ * the legacy hero dismissal in V4. */
5593
5627
  dismiss?: AgentWidgetWelcomeDismiss;
5594
5628
  /**
5595
5629
  * Vertical anchoring of the greeting-plus-composer pair in the EMPTY
5596
5630
  * conversation only. On the first message the pair drops to the bottom
5597
5631
  * (animated; see `data-persona-conversation-state`).
5598
5632
  *
5599
- * - `"bottom"` (default): the composer stays pinned at the bottom.
5633
+ * - `"bottom"`: the composer stays pinned at the bottom.
5600
5634
  * - `"center"`: greeting and composer float together so the composer's top
5601
5635
  * edge lands at `anchorComposerTop` of the panel column, and the welcome
5602
5636
  * surface is end-anchored `composerGap` above it.
@@ -5604,7 +5638,8 @@ export interface AgentWidgetWelcomeConfig {
5604
5638
  * Composes with either `composer.placement`. Ignored in composer-bar mount
5605
5639
  * mode. `renderWelcome` plugin content inherits the anchor: the widget
5606
5640
  * positions the overlay host and reserves the composer zone.
5607
- * @default "bottom"
5641
+ * Defaults to bottom, except centered fullscreen layouts, which center the
5642
+ * measured greeting/composer group when no anchor override is supplied.
5608
5643
  */
5609
5644
  anchor?: AgentWidgetWelcomeAnchor;
5610
5645
  /**
@@ -5941,6 +5976,8 @@ export interface WidgetHistoryInternals {
5941
5976
  */
5942
5977
  export type ClientChatRequest = {
5943
5978
  sessionId: string;
5979
+ /** Fresh identity proof for this execution, independent of visitor history. */
5980
+ identityProof?: RuntypeClientChatRequest['identityProof'];
5944
5981
  messages: Array<{
5945
5982
  id?: string;
5946
5983
  role: 'user' | 'assistant' | 'system';
@@ -6323,6 +6360,9 @@ export type AgentWidgetLayoutConfig = {
6323
6360
  * `launcher.composerBar.contentMaxWidth`)
6324
6361
  */
6325
6362
  contentMaxWidth?: string;
6363
+ /** Fade the transcript's top edge when content is scrolled above it.
6364
+ * Defaults to false in V4 and true with future.v5Defaults. */
6365
+ topFade?: boolean;
6326
6366
  };
6327
6367
 
6328
6368
  // ============================================================================
@@ -6951,7 +6991,32 @@ export type AgentWidgetLoadingIndicatorConfig = {
6951
6991
  renderIdle?: (context: IdleIndicatorRenderContext) => HTMLElement | null;
6952
6992
  };
6953
6993
 
6994
+ /** Built-in `sessionInit` triggers. See {@link AgentWidgetConfig.sessionInit}. */
6995
+ export type AgentWidgetSessionInitTrigger = 'input' | 'focus' | 'mount' | 'send';
6996
+
6997
+ /**
6998
+ * Custom `sessionInit` trigger. Runs when the widget mounts in client token mode,
6999
+ * and again if `update()` swaps in a different function. Call `warm()` whenever
7000
+ * the visitor shows intent (it is safe to call repeatedly). Return a cleanup to
7001
+ * run when the widget is destroyed or the hook is replaced.
7002
+ */
7003
+ export type AgentWidgetSessionInitHook = (context: {
7004
+ /** Fire-and-forget early session init; latched and error-swallowing. */
7005
+ warm: () => void;
7006
+ /** The widget's mount element. */
7007
+ mount: HTMLElement;
7008
+ }) => void | (() => void);
7009
+
6954
7010
  export type AgentWidgetConfig = {
7011
+ /** Opt in to upcoming major-version defaults without changing explicit settings. */
7012
+ future?: {
7013
+ /**
7014
+ * Use the v5 default styling as it rolls out in 4.x. Every affected setting
7015
+ * remains independently configurable. Explicit options and theme tokens win.
7016
+ * @default false
7017
+ */
7018
+ v5Defaults?: boolean;
7019
+ };
6955
7020
  apiUrl?: string;
6956
7021
  flowId?: string;
6957
7022
  /**
@@ -7106,6 +7171,46 @@ export type AgentWidgetConfig = {
7106
7171
  * ```
7107
7172
  */
7108
7173
  onSessionExpired?: () => void;
7174
+ /**
7175
+ * When the widget calls `/v1/client/init` in client token mode, ahead of the
7176
+ * first send. Ignored outside client token mode.
7177
+ *
7178
+ * - `'input'` (default): on the composer's first user keystroke that leaves
7179
+ * non-empty text. Programmatic value changes (restored drafts,
7180
+ * `setMessage()`, history recall) do not count.
7181
+ * - `'focus'`: when the visitor focuses the composer by pointer or Tab (and
7182
+ * on first input, as a fallback). Programmatic focus, such as
7183
+ * `autoFocusInput` on open or mount, does not count.
7184
+ * - `'mount'`: as soon as the widget mounts (the behavior before this option
7185
+ * existed). Use it when a server-configured welcome message, which arrives
7186
+ * with the init response, must show before the visitor types.
7187
+ * - `'send'`: no early init; the first send initializes the session.
7188
+ * - A function: custom trigger. Called on mount (and when `update()` swaps
7189
+ * in a different function) with a `warm` callback to call whenever you
7190
+ * decide (e.g. on hover); may return a cleanup that runs on destroy or
7191
+ * replacement. Replaces the built-in triggers.
7192
+ *
7193
+ * The early init is fire-and-forget: it runs at most once per session
7194
+ * lifetime (re-armed after expiry or a new conversation), sends exactly the
7195
+ * same request the send would, and swallows errors. A failure resurfaces
7196
+ * when the send retries init. A send during an in-flight early init reuses
7197
+ * it, so only one `/v1/client/init` is issued. When visitor history is
7198
+ * enabled the widget still initializes on mount to restore the conversation.
7199
+ * Hosts can also trigger it imperatively with `controller.warmSession()`.
7200
+ *
7201
+ * @default 'input'
7202
+ *
7203
+ * @example
7204
+ * ```typescript
7205
+ * config: {
7206
+ * clientToken: 'ct_live_...',
7207
+ * sessionInit: ({ warm, mount }) => {
7208
+ * mount.addEventListener('pointerenter', warm, { once: true });
7209
+ * }
7210
+ * }
7211
+ * ```
7212
+ */
7213
+ sessionInit?: AgentWidgetSessionInitTrigger | AgentWidgetSessionInitHook;
7109
7214
  /**
7110
7215
  * Get stored session ID for session resumption (client token mode only).
7111
7216
  * Called when initializing a new session to check if there's a previous session_id
@@ -7138,13 +7243,20 @@ export type AgentWidgetConfig = {
7138
7243
  */
7139
7244
  setStoredSessionId?: (sessionId: string) => void;
7140
7245
  /**
7141
- * Called immediately before any cross-device history request and before a
7246
+ * With identityProvider, called before every client-token chat POST, including retries, independently of history.
7247
+ * Also called immediately before any cross-device history request and before a
7142
7248
  * conversationId resume of a conversation discovered on another device.
7143
- * Return a fresh end-user access token (rt_eu_… / JWT). Null uses exact-browser
7144
- * scope only for a never-bound visitor; a previously bound visitor fails closed
7145
- * until resetHistoryIdentity() runs. Never cached by the widget.
7249
+ * Return a fresh end-user access token (rt_eu_… / JWT). Null stops identity-enabled chat.
7250
+ * For history only, null uses exact-browser scope for a never-bound visitor;
7251
+ * a bound visitor fails closed until resetHistoryIdentity(). Never cached.
7146
7252
  */
7147
7253
  getIdentityProof?: () => string | null | Promise<string | null>;
7254
+ /**
7255
+ * Provider name registered with Runtype, for example "clerk". Enables per-request
7256
+ * chat identity through getIdentityProof without enabling history. A missing or
7257
+ * failed proof stops the turn; omitting this preserves history-only callbacks.
7258
+ */
7259
+ identityProvider?: string;
7148
7260
  /**
7149
7261
  * Read the persisted active conversation id (client token mode only).
7150
7262
  * Called only after the internal stored-state bootstrap gate resolves.
@@ -7840,6 +7952,8 @@ export type AgentWidgetReasoning = {
7840
7952
  };
7841
7953
 
7842
7954
  export type AgentWidgetToolCall = {
7955
+ /** Optional approval state for activity-row status chrome. */
7956
+ approvalStatus?: "pending" | "approved" | "denied" | "timeout";
7843
7957
  id: string;
7844
7958
  name?: string;
7845
7959
  status: "pending" | "running" | "complete";
@@ -0,0 +1,91 @@
1
+ // @vitest-environment jsdom
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
+ import { createAgentExperience, type AgentWidgetController } from './ui';
4
+ import type { AgentWidgetConfig, AgentWidgetMessage } from './types';
5
+ let controller: AgentWidgetController;
6
+ let mount: HTMLElement;
7
+ beforeEach(() => { window.localStorage.clear(); window.scrollTo = vi.fn(); mount = document.createElement('div'); document.body.appendChild(mount); });
8
+ afterEach(() => { controller?.destroy(); document.body.replaceChildren(); vi.useRealTimers(); });
9
+ const inject = (id: string, status: 'running' | 'complete', chunks: string[] = []) => controller.injectTestMessage({ type: 'message', message: { id, role: 'assistant', content: '', createdAt: '2026-01-01T00:00:00Z', sequence: Number(id.slice(-1)), variant: 'tool', toolCall: { id, name: 'Search', status, chunks } } });
10
+ const header = (id: string) => mount.querySelector<HTMLButtonElement>(`[data-message-id="${id}"] > button`)!;
11
+ const start = (v5Defaults: boolean, toolCallDisplay: NonNullable<AgentWidgetConfig['features']>['toolCallDisplay']) => { controller = createAgentExperience(mount, { future: { v5Defaults }, apiUrl: '/test', persistState: false, launcher: { enabled: false }, features: { toolCallDisplay } }); };
12
+ describe.each([false, true])('activity UI (V5 %s)', v5Defaults => {
13
+ it('uses version defaults for streaming tool and reasoning disclosure', () => {
14
+ start(v5Defaults, { variant: 'row', grouped: false });
15
+ inject('tool1', 'running', ['one']);
16
+ expect(header('tool1').getAttribute('aria-expanded')).toBe(String(!v5Defaults));
17
+ if (v5Defaults) {
18
+ inject('tool1', 'running', ['one', 'two']);
19
+ expect(header('tool1').getAttribute('aria-expanded')).toBe('false');
20
+ header('tool1').click();
21
+ expect(header('tool1').getAttribute('aria-expanded')).toBe('true');
22
+ }
23
+ controller.destroy();
24
+ controller = createAgentExperience(mount, { future: { v5Defaults }, apiUrl: '/test', persistState: false, launcher: { enabled: false }, features: { reasoningDisplay: { variant: 'row' } } });
25
+ controller.injectTestMessage({ type: 'message', message: { id: 'r1', role: 'assistant', content: '', createdAt: '2026-01-01', variant: 'reasoning', reasoning: { id: 'r1', status: 'streaming', chunks: ['Thinking'] } } });
26
+ expect(header('r1').getAttribute('aria-expanded')).toBe(String(!v5Defaults));
27
+ });
28
+ it('opens on first token, collapses once, and preserves a manual decision', () => {
29
+ start(v5Defaults, { variant: 'row', grouped: false, autoExpand: true }); vi.useFakeTimers();
30
+ inject('tool1', 'running'); expect(header('tool1').getAttribute('aria-expanded')).toBe('false');
31
+ inject('tool1', 'running', ['one']); expect(header('tool1').getAttribute('aria-expanded')).toBe('true');
32
+ inject('tool1', 'complete', ['one']); vi.advanceTimersByTime(1000); expect(header('tool1').getAttribute('aria-expanded')).toBe('false');
33
+ header('tool1').click(); expect(header('tool1').getAttribute('aria-expanded')).toBe('true');
34
+ inject('tool1', 'complete', ['one']); vi.advanceTimersByTime(2000); expect(header('tool1').getAttribute('aria-expanded')).toBe('true');
35
+ });
36
+ it('cancels pending collapse on clear and supports reused message IDs', () => {
37
+ start(v5Defaults, { variant: 'row', grouped: false, autoExpand: true }); vi.useFakeTimers();
38
+ inject('tool1', 'running', ['one']); inject('tool1', 'complete', ['one']); controller.clearChat();
39
+ inject('tool1', 'running', ['new']); vi.advanceTimersByTime(1100); expect(header('tool1').getAttribute('aria-expanded')).toBe('true');
40
+ controller.destroy(); vi.advanceTimersByTime(1100);
41
+ });
42
+ it('groups consecutive calls with a collapsible label and staggered children', () => {
43
+ start(v5Defaults, { variant: 'row', grouped: true, groupedMode: 'collapsible' });
44
+ inject('tool1', 'complete', ['one']); inject('tool2', 'complete', ['two']);
45
+ const group = mount.querySelector('[data-persona-tool-group]')!;
46
+ expect(group.querySelector('button')?.textContent).toContain('Used 2 tools');
47
+ const children = group.querySelectorAll<HTMLElement>('.persona-activity-group-child'); expect(children).toHaveLength(2);
48
+ expect(children[1].style.getPropertyValue('--persona-activity-stagger')).toBe('40ms');
49
+ const chevron = () => header('tool-group-tool1').querySelector('.persona-activity-chevron svg')!.outerHTML;
50
+ const collapsedChevron = chevron();
51
+ expect(header('tool-group-tool1').querySelector('svg[stroke="currentColor"]')).not.toBeNull();
52
+ header('tool-group-tool1').click(); expect(header('tool-group-tool1').getAttribute('aria-expanded')).toBe('true');
53
+ expect(chevron()).not.toBe(collapsedChevron);
54
+ header('tool-group-tool1').click();
55
+ expect(header('tool-group-tool1').getAttribute('aria-expanded')).toBe('false');
56
+ expect(chevron()).toBe(collapsedChevron);
57
+ controller.injectTestMessage({ type: 'message', message: { id: 'text3', sequence: 3, role: 'assistant', content: 'Break', createdAt: '2026-01-01T00:00:00Z' } });
58
+ inject('tool4', 'complete', ['four']); expect(mount.querySelectorAll('[data-persona-tool-group]')).toHaveLength(1);
59
+ });
60
+ it('honors explicit static display and content renderers', () => {
61
+ start(v5Defaults, { variant: 'row', grouped: false, expandable: false });
62
+ inject('tool1', 'running', ['one']); expect(header('tool1').hasAttribute('data-expand-header')).toBe(false);
63
+ });
64
+ it('uses the same first-token lifecycle for reasoning', () => {
65
+ controller = createAgentExperience(mount, { future: { v5Defaults }, apiUrl: '/test', persistState: false, launcher: { enabled: false }, features: { reasoningDisplay: { variant: 'row', autoExpand: true, autoCollapseDelay: 50 } } });
66
+ vi.useFakeTimers();
67
+ const m: AgentWidgetMessage = { id: 'r1', role: 'assistant', content: '', createdAt: '2026-01-01', variant: 'reasoning', reasoning: { id: 'r1', status: 'streaming', chunks: ['Thinking'] } };
68
+ controller.injectTestMessage({ type: 'message', message: m }); expect(header('r1').getAttribute('aria-expanded')).toBe('true');
69
+ controller.injectTestMessage({ type: 'message', message: { ...m, reasoning: { ...m.reasoning!, status: 'complete' } } });
70
+ vi.advanceTimersByTime(50); expect(header('r1').getAttribute('aria-expanded')).toBe('false');
71
+ });
72
+ });
73
+
74
+ it('copies current tool output after a streaming DOM update', async () => {
75
+ start(true, { grouped: false });
76
+ const writeText = vi.fn().mockResolvedValue(undefined);
77
+ const original = Object.getOwnPropertyDescriptor(navigator, 'clipboard');
78
+ Object.defineProperty(navigator, 'clipboard', { configurable: true, value: { writeText } });
79
+ try {
80
+ inject('tool1', 'running', ['first']);
81
+ header('tool1').click();
82
+ inject('tool1', 'complete', ['updated output']);
83
+ mount.querySelector<HTMLButtonElement>('[data-persona-copy-tool-detail]')!.click();
84
+ await Promise.resolve();
85
+ expect(writeText).toHaveBeenCalledWith('updated output');
86
+ expect(header('tool1').getAttribute('aria-expanded')).toBe('true');
87
+ } finally {
88
+ if (original) Object.defineProperty(navigator, 'clipboard', original);
89
+ else Reflect.deleteProperty(navigator, 'clipboard');
90
+ }
91
+ });