@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.
- package/dist/animations/glyph-cycle.d.cts +1 -1
- package/dist/animations/glyph-cycle.d.ts +1 -1
- package/dist/animations/{types-CI7OckWI.d.cts → types-Db61aI9J.d.cts} +2 -0
- package/dist/animations/{types-CI7OckWI.d.ts → types-Db61aI9J.d.ts} +2 -0
- package/dist/animations/wipe.d.cts +1 -1
- package/dist/animations/wipe.d.ts +1 -1
- package/dist/animations-extra.d.cts +2 -0
- package/dist/animations-extra.d.ts +2 -0
- package/dist/approval-ui-3NMYNXBD.js +1 -0
- package/dist/approval-ui.cjs +2 -2
- package/dist/approval-ui.d.cts +215 -37
- package/dist/approval-ui.d.ts +215 -37
- package/dist/approval-ui.js +2 -2
- package/dist/{artifacts-ui-F4N6VMKF.js → artifacts-ui-IGB5YKLO.js} +4 -4
- package/dist/artifacts-ui.d.cts +210 -25
- package/dist/artifacts-ui.d.ts +210 -25
- package/dist/{chunk-NIKYKSNZ.js → chunk-7CO4T5KB.js} +1 -1
- package/dist/codegen.cjs +11 -11
- package/dist/codegen.js +13 -13
- package/dist/context-mentions.d.cts +210 -25
- package/dist/context-mentions.d.ts +210 -25
- package/dist/event-stream-view.d.cts +210 -25
- package/dist/event-stream-view.d.ts +210 -25
- package/dist/forms-ui.d.cts +245 -27
- package/dist/forms-ui.d.ts +245 -27
- package/dist/history-view.d.cts +2 -0
- package/dist/history-view.d.ts +2 -0
- package/dist/index.cjs +70 -69
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +270 -39
- package/dist/index.d.ts +270 -39
- package/dist/index.global.js +57 -54
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +70 -69
- package/dist/index.js.map +1 -1
- package/dist/install.global.js +1 -1
- package/dist/install.global.js.map +1 -1
- package/dist/launcher.global.js +2 -2
- package/dist/launcher.global.js.map +1 -1
- package/dist/session-reconnect.d.cts +210 -25
- package/dist/session-reconnect.d.ts +210 -25
- package/dist/smart-dom-reader.d.cts +210 -25
- package/dist/smart-dom-reader.d.ts +210 -25
- package/dist/theme-editor-preview.cjs +74 -73
- package/dist/theme-editor-preview.d.cts +217 -25
- package/dist/theme-editor-preview.d.ts +217 -25
- package/dist/theme-editor-preview.js +51 -50
- package/dist/theme-editor.cjs +10 -10
- package/dist/theme-editor.d.cts +230 -27
- package/dist/theme-editor.d.ts +230 -27
- package/dist/theme-editor.js +10 -10
- package/dist/theme-reference.cjs +1 -1
- package/dist/theme-reference.d.cts +1522 -11
- package/dist/theme-reference.d.ts +1522 -11
- package/dist/theme-reference.js +1 -1
- package/dist/widget.css +1 -1
- package/package.json +3 -2
- package/src/__fixtures__/defaults-parity.v4.json +776 -0
- package/src/approval-ui-entry.ts +0 -1
- package/src/approval-ui-loader.ts +5 -4
- package/src/approval-ui.ts +0 -1
- package/src/client-identity-proof.test.ts +193 -0
- package/src/client-session-warm.test.ts +216 -0
- package/src/client-visitor-history.test.ts +6 -0
- package/src/client.ts +122 -30
- package/src/codegen.test.ts +24 -0
- package/src/components/activity-row.test.ts +100 -0
- package/src/components/activity-row.ts +136 -0
- package/src/components/approval-actions.test.ts +8 -5
- package/src/components/approval-actions.ts +19 -6
- package/src/components/approval-bubble.test.ts +56 -9
- package/src/components/approval-bubble.ts +36 -41
- package/src/components/composer-builder.ts +5 -2
- package/src/components/composer-parts.ts +10 -2
- package/src/components/header-builder.test.ts +32 -0
- package/src/components/header-builder.ts +7 -4
- package/src/components/header-layouts.test.ts +12 -0
- package/src/components/header-layouts.ts +3 -2
- package/src/components/launcher.ts +11 -2
- package/src/components/message-bubble.test.ts +22 -0
- package/src/components/message-bubble.ts +7 -3
- package/src/components/panel.test.ts +52 -1
- package/src/components/panel.ts +47 -8
- package/src/components/pill-composer-builder.test.ts +2 -0
- package/src/components/pill-composer-builder.ts +0 -1
- package/src/components/reasoning-bubble.ts +29 -17
- package/src/components/suggestions.test.ts +1 -0
- package/src/components/suggestions.ts +0 -16
- package/src/components/tool-bubble.ts +131 -114
- package/src/components/tool-details.test.ts +87 -0
- package/src/components/tool-details.ts +102 -0
- package/src/components/welcome.test.ts +8 -0
- package/src/components/welcome.ts +4 -2
- package/src/defaults-parity.test.ts +158 -0
- package/src/defaults.test.ts +69 -0
- package/src/defaults.ts +128 -49
- package/src/generated/runtype-openapi-contract.ts +2 -2
- package/src/index-core.ts +2 -0
- package/src/install.test.ts +24 -0
- package/src/install.ts +3 -1
- package/src/launcher-global.ts +2 -1
- package/src/launcher-variants.test.ts +52 -0
- package/src/runtime/init-update-reset.test.ts +21 -0
- package/src/session.ts +29 -6
- package/src/styles/widget-styles.test.ts +19 -0
- package/src/styles/widget.css +295 -23
- package/src/theme-editor/index.ts +2 -0
- package/src/theme-editor/preview-utils.test.ts +8 -0
- package/src/theme-editor/preview-utils.ts +7 -3
- package/src/theme-editor/sections.test.ts +31 -0
- package/src/theme-editor/sections.ts +55 -1
- package/src/theme-editor/state-preferences.test.ts +49 -0
- package/src/theme-editor/state.ts +73 -15
- package/src/theme-reference.test.ts +20 -0
- package/src/theme-reference.ts +11 -5
- package/src/tool-call-display-defaults.test.ts +33 -1
- package/src/types/theme.ts +80 -3
- package/src/types.ts +136 -22
- package/src/ui.activity-row.test.ts +91 -0
- package/src/ui.approval-plugin.test.ts +63 -0
- package/src/ui.composer-compact.test.ts +24 -2
- package/src/ui.composer-placement.test.ts +23 -0
- package/src/ui.detached-panel.test.ts +33 -6
- package/src/ui.docked.test.ts +19 -0
- package/src/ui.header-update-stability.test.ts +25 -0
- package/src/ui.identity-proof.test.ts +58 -0
- package/src/ui.launcher-teaser.test.ts +25 -0
- package/src/ui.message-width.test.ts +17 -2
- package/src/ui.reasoning-display.test.ts +92 -0
- package/src/ui.rebuild-composer.test.ts +20 -3
- package/src/ui.scroll.test.ts +32 -0
- package/src/ui.session-init.test.ts +312 -0
- package/src/ui.status-indicator.test.ts +82 -0
- package/src/ui.suggest-replies.test.ts +11 -0
- package/src/ui.ts +363 -74
- package/src/ui.v5-defaults.test.ts +144 -0
- package/src/ui.welcome.test.ts +92 -3
- package/src/utils/__snapshots__/tokens-parity.test.ts.snap +811 -140
- package/src/utils/code-generators.ts +14 -0
- package/src/utils/composer-compact.test.ts +0 -2
- package/src/utils/composer-compact.ts +2 -4
- package/src/utils/composer-placement.ts +2 -3
- package/src/utils/config-merge.test.ts +81 -5
- package/src/utils/config-merge.ts +13 -3
- package/src/utils/defaults-provenance.ts +67 -0
- package/src/utils/dock.ts +7 -1
- package/src/utils/launcher-variant.ts +16 -0
- package/src/utils/panel-config.ts +97 -0
- package/src/utils/status-indicator.ts +39 -0
- package/src/utils/theme.test.ts +182 -0
- package/src/utils/theme.ts +131 -18
- package/src/utils/tokens.test.ts +122 -1
- package/src/utils/tokens.ts +177 -17
- package/src/welcome.test.ts +23 -0
- package/src/welcome.ts +16 -9
- package/dist/approval-ui-K7R5Q5OC.js +0 -1
package/src/types/theme.ts
CHANGED
|
@@ -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
|
|
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`.
|
|
3082
|
-
*
|
|
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"
|
|
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
|
|
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.
|
|
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
|
|
5522
|
-
*
|
|
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
|
-
/**
|
|
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.
|
|
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
|
-
/**
|
|
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"
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
7144
|
-
*
|
|
7145
|
-
* until resetHistoryIdentity()
|
|
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
|
+
});
|