@runtypelabs/persona 4.13.0 → 4.15.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 (106) hide show
  1. package/README.md +4 -4
  2. package/dist/animations/glyph-cycle.d.cts +1 -1
  3. package/dist/animations/glyph-cycle.d.ts +1 -1
  4. package/dist/animations/{types-DveIaNx6.d.cts → types-BpWGm7_6.d.cts} +17 -0
  5. package/dist/animations/{types-DveIaNx6.d.ts → types-BpWGm7_6.d.ts} +17 -0
  6. package/dist/animations/wipe.d.cts +1 -1
  7. package/dist/animations/wipe.d.ts +1 -1
  8. package/dist/chunk-QJ4LV2KW.js +1 -0
  9. package/dist/codegen.cjs +12 -11
  10. package/dist/codegen.js +14 -13
  11. package/dist/{context-mentions-ONG7A7M6.js → context-mentions-BTSTIBFC.js} +1 -1
  12. package/dist/context-mentions.cjs +11 -8
  13. package/dist/context-mentions.d.cts +339 -22
  14. package/dist/context-mentions.d.ts +339 -22
  15. package/dist/context-mentions.js +11 -8
  16. package/dist/index.cjs +68 -67
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +483 -48
  19. package/dist/index.d.ts +483 -48
  20. package/dist/index.global.js +61 -58
  21. package/dist/index.global.js.map +1 -1
  22. package/dist/index.js +70 -69
  23. package/dist/index.js.map +1 -1
  24. package/dist/launcher.global.js +5 -2
  25. package/dist/launcher.global.js.map +1 -1
  26. package/dist/smart-dom-reader.d.cts +339 -22
  27. package/dist/smart-dom-reader.d.ts +339 -22
  28. package/dist/theme-editor-preview.cjs +67 -67
  29. package/dist/theme-editor-preview.d.cts +355 -23
  30. package/dist/theme-editor-preview.d.ts +355 -23
  31. package/dist/theme-editor-preview.js +75 -75
  32. package/dist/theme-editor.cjs +10 -10
  33. package/dist/theme-editor.d.cts +376 -24
  34. package/dist/theme-editor.d.ts +376 -24
  35. package/dist/theme-editor.js +10 -10
  36. package/dist/theme-reference.cjs +1 -1
  37. package/dist/theme-reference.d.cts +38 -0
  38. package/dist/theme-reference.d.ts +38 -0
  39. package/dist/theme-reference.js +1 -1
  40. package/dist/widget.css +1 -1
  41. package/package.json +1 -1
  42. package/src/artifacts-session.test.ts +106 -0
  43. package/src/ask-user-question-tool.ts +10 -7
  44. package/src/client.test.ts +2 -2
  45. package/src/client.ts +4 -1
  46. package/src/components/artifact-card.ts +13 -2
  47. package/src/components/artifact-inline.ts +11 -2
  48. package/src/components/artifact-preview.ts +1 -1
  49. package/src/components/composer-builder.test.ts +2 -0
  50. package/src/components/composer-builder.ts +1 -1
  51. package/src/components/composer-parts.test.ts +105 -4
  52. package/src/components/composer-parts.ts +54 -16
  53. package/src/components/message-bubble.test.ts +7 -1
  54. package/src/components/message-bubble.ts +11 -8
  55. package/src/components/messages.ts +5 -2
  56. package/src/components/panel.ts +31 -5
  57. package/src/components/pill-composer-builder.test.ts +18 -0
  58. package/src/components/suggestions.test.ts +233 -0
  59. package/src/components/suggestions.ts +463 -81
  60. package/src/components/widget-view.ts +4 -0
  61. package/src/defaults.ts +5 -3
  62. package/src/generated/runtype-openapi-contract.ts +17 -0
  63. package/src/index-core.ts +52 -2
  64. package/src/launcher-global.ts +1 -1
  65. package/src/plugins/types.ts +71 -3
  66. package/src/presets.ts +3 -3
  67. package/src/session.ts +55 -12
  68. package/src/styles/widget-styles.test.ts +162 -0
  69. package/src/styles/widget.css +440 -1
  70. package/src/suggest-replies-tool.test.ts +233 -2
  71. package/src/suggest-replies-tool.ts +115 -39
  72. package/src/theme-editor/index.ts +5 -0
  73. package/src/theme-editor/presets.test.ts +43 -0
  74. package/src/theme-editor/presets.ts +101 -87
  75. package/src/theme-editor/preview-utils.test.ts +34 -0
  76. package/src/theme-editor/preview-utils.ts +77 -2
  77. package/src/theme-editor/sections.test.ts +72 -7
  78. package/src/theme-editor/sections.ts +113 -13
  79. package/src/theme-editor/state-preferences.test.ts +15 -0
  80. package/src/theme-editor/state.ts +46 -0
  81. package/src/theme-editor/types.ts +15 -1
  82. package/src/theme-editor/webmcp/tools.test.ts +13 -0
  83. package/src/theme-editor/webmcp/tools.ts +5 -0
  84. package/src/theme-reference.ts +9 -2
  85. package/src/types/theme.ts +32 -0
  86. package/src/types.ts +291 -25
  87. package/src/ui.artifact-pane-gating.test.ts +5 -5
  88. package/src/ui.config-preferences.test.ts +166 -0
  89. package/src/ui.detached-panel.test.ts +1 -1
  90. package/src/ui.suggest-replies.test.ts +1066 -1
  91. package/src/ui.ts +311 -45
  92. package/src/ui.typing-indicator.test.ts +135 -0
  93. package/src/utils/artifact-display.test.ts +180 -16
  94. package/src/utils/artifact-display.ts +155 -14
  95. package/src/utils/artifact-file.test.ts +1 -1
  96. package/src/utils/code-generators.test.ts +40 -0
  97. package/src/utils/code-generators.ts +22 -0
  98. package/src/utils/config-merge.test.ts +84 -0
  99. package/src/utils/config-merge.ts +6 -1
  100. package/src/utils/feature-preferences.test.ts +326 -0
  101. package/src/utils/feature-preferences.ts +498 -0
  102. package/src/utils/icons.test.ts +26 -0
  103. package/src/utils/icons.ts +8 -0
  104. package/src/utils/theme.test.ts +171 -0
  105. package/src/utils/tokens.ts +71 -7
  106. package/dist/chunk-MMUPR2JW.js +0 -1
@@ -0,0 +1,15 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { ThemeEditorState } from "./state";
3
+
4
+ describe("ThemeEditorState preference reset", () => {
5
+ it("unsets a path and prunes empty parent objects", () => {
6
+ const state = new ThemeEditorState(undefined, {}, { mergeDefaults: false });
7
+ state.set("features.artifacts.display.files", "inline");
8
+ expect(state.get("features.artifacts.display.files")).toBe("inline");
9
+
10
+ state.unset("features.artifacts.display.files");
11
+
12
+ expect(state.get("features.artifacts.display.files")).toBeUndefined();
13
+ expect(state.getConfig()).not.toHaveProperty("features");
14
+ });
15
+ });
@@ -32,6 +32,30 @@ function setByPath(obj: unknown, path: string, value: unknown): unknown {
32
32
  };
33
33
  }
34
34
 
35
+ function unsetByPath(obj: unknown, path: string): unknown {
36
+ const parts = path.split('.');
37
+ if (parts.length === 1) {
38
+ const result = { ...(obj as Record<string, unknown>) };
39
+ delete result[parts[0]];
40
+ return result;
41
+ }
42
+ const [first, ...rest] = parts;
43
+ const current = obj as Record<string, unknown>;
44
+ const child = unsetByPath(current?.[first] ?? {}, rest.join('.'));
45
+ const result = { ...current };
46
+ if (
47
+ typeof child === 'object' &&
48
+ child !== null &&
49
+ !Array.isArray(child) &&
50
+ Object.keys(child as Record<string, unknown>).length === 0
51
+ ) {
52
+ delete result[first];
53
+ } else {
54
+ result[first] = child;
55
+ }
56
+ return result;
57
+ }
58
+
35
59
  // ─── ThemeEditorState ───────────────────────────────────────────
36
60
 
37
61
  export class ThemeEditorState {
@@ -111,6 +135,28 @@ export class ThemeEditorState {
111
135
  this.notifyListeners();
112
136
  }
113
137
 
138
+ /** Delete a dot-path and prune empty parents so sparse preferences inherit. */
139
+ unset(path: string): void {
140
+ if (path.startsWith('theme.')) {
141
+ const themePath = path.replace('theme.', '');
142
+ this.theme = unsetByPath(this.theme, themePath) as PersonaTheme;
143
+ this.syncThemeIntoConfig();
144
+ } else if (path.startsWith('darkTheme.')) {
145
+ const themePath = path.replace('darkTheme.', '');
146
+ this.config = {
147
+ ...this.config,
148
+ darkTheme: unsetByPath(
149
+ this.config.darkTheme ?? {},
150
+ themePath
151
+ ) as AgentWidgetConfig['darkTheme'],
152
+ };
153
+ } else {
154
+ this.config = unsetByPath(this.config, path) as AgentWidgetConfig;
155
+ }
156
+ this.recordHistory();
157
+ this.notifyListeners();
158
+ }
159
+
114
160
  /** Batch-set multiple paths at once */
115
161
  setBatch(updates: Record<string, unknown>): void {
116
162
  let themeChanged = false;
@@ -1,7 +1,11 @@
1
1
  /** Field definition types for the declarative configurator system (headless: no DOM) */
2
2
 
3
3
  import type { DeepPartial, PersonaTheme } from '../types/theme';
4
- import type { AgentWidgetConfig } from '../types';
4
+ import type {
5
+ AgentWidgetConfig,
6
+ WidgetPreferenceCapabilities,
7
+ WidgetPreferenceSlice,
8
+ } from '../types';
5
9
 
6
10
  // ─── Field System ────────────────────────────────────────────────
7
11
 
@@ -101,6 +105,12 @@ export interface FieldDef {
101
105
  formatValue?: (value: unknown) => unknown;
102
106
  /** Convert control input back into the stored value shape */
103
107
  parseValue?: (value: unknown) => unknown;
108
+ /** Capability required for hosts to render this field. */
109
+ requiresCapability?: keyof WidgetPreferenceCapabilities;
110
+ /** The host may offer an inherit/reset action which deletes this path. */
111
+ unsettable?: boolean;
112
+ /** Label for the inherit/reset action. Default: "Use default". */
113
+ unsetLabel?: string;
104
114
  }
105
115
 
106
116
  export interface SectionDef {
@@ -112,6 +122,8 @@ export interface SectionDef {
112
122
  collapsed?: boolean;
113
123
  /** Preset buttons for this section */
114
124
  presets?: SectionPreset[];
125
+ /** Capability required for hosts to render this section. */
126
+ requiresCapability?: keyof WidgetPreferenceCapabilities;
115
127
  }
116
128
 
117
129
  export interface SectionPreset {
@@ -149,6 +161,8 @@ export interface ThemeEditorPreset {
149
161
  toolCall?: AgentWidgetToolCallConfig;
150
162
  /** Tool call styling for dark mode (falls back to toolCall if not set) */
151
163
  darkToolCall?: AgentWidgetToolCallConfig;
164
+ /** Sparse, persistable widget behavior layered separately from theme tokens. */
165
+ behavior?: WidgetPreferenceSlice;
152
166
  preview: {
153
167
  primary: string;
154
168
  surface: string;
@@ -123,6 +123,19 @@ describe('createThemeEditorTools', () => {
123
123
  );
124
124
  });
125
125
 
126
+ it('apply_preset installs both Persona default slots with automatic scheme selection', async () => {
127
+ const out = await call(tools.get('apply_preset')!, { presetId: 'persona-default' });
128
+
129
+ expect(out.applied.appliedPreset.id).toBe('persona-default');
130
+ expect(state.get('colorScheme')).toBe('auto');
131
+ expect(state.get('theme.components.artifact.pane.background')).toBe(
132
+ 'semantic.colors.container'
133
+ );
134
+ expect(state.get('darkTheme.semantic.colors.surface')).toBe(
135
+ 'palette.colors.gray.800'
136
+ );
137
+ });
138
+
126
139
  it('configure_widget writes config paths', async () => {
127
140
  await call(tools.get('configure_widget')!, {
128
141
  launcherPosition: 'bottom-left',
@@ -10,6 +10,7 @@
10
10
  */
11
11
 
12
12
  import { createTheme } from '../../utils/theme';
13
+ import { applyFeaturePreferences } from '../../utils/feature-preferences';
13
14
  import type { AgentWidgetConfig } from '../../types';
14
15
  import type { PersonaTheme } from '../../types/theme';
15
16
  import { generateColorScale, SHADE_KEYS } from '../color-utils';
@@ -469,8 +470,12 @@ export function createThemeEditorTools(
469
470
  const config: AgentWidgetConfig = { ...state.getConfig() };
470
471
  if (preset.darkTheme) {
471
472
  config.darkTheme = createTheme(preset.darkTheme, { validate: false }) as PersonaTheme;
473
+ config.colorScheme = 'auto';
472
474
  }
473
475
  if (preset.toolCall) config.toolCall = preset.toolCall;
476
+ if (preset.behavior) {
477
+ config.features = applyFeaturePreferences(config.features, [preset.behavior]);
478
+ }
474
479
  state.setFullConfig(config, theme);
475
480
  const warnings = quickContrastWarnings(state, ['body', 'assistant-message'], 'light');
476
481
  return result({ appliedPreset: { id: preset.id, name: preset.name } }, warnings);
@@ -142,7 +142,8 @@ export const THEME_TOKEN_DOCS = {
142
142
  'Floating scroll-to-bottom affordance shared by transcript and event stream: background, foreground, border, size, borderRadius, shadow, padding, gap, fontSize, iconSize.',
143
143
  toolBubble: 'shadow: tool call row box-shadow.',
144
144
  reasoningBubble: 'shadow: reasoning/thinking row box-shadow.',
145
- composer: 'shadow: message input form box-shadow.',
145
+ composer:
146
+ 'Message input form: shadow (box-shadow), padding (form inset), gap (textarea row to actions row), fontSize and lineHeight (textarea type).',
146
147
  artifact:
147
148
  'toolbar (icon styling, copy menu), tab (background, active states), pane (background, toolbarBackground).',
148
149
  },
@@ -245,8 +246,14 @@ export const THEME_TOKEN_DOCS = {
245
246
  properties:
246
247
  'enabled, provider ("browser" | "runtype"), browserFallback, voice, rate, pitch.',
247
248
  },
249
+ suggestions: {
250
+ description:
251
+ 'Rich starter and follow-up suggestion surfaces. Strings remain valid item shorthand.',
252
+ properties:
253
+ 'starters.items, starters.variant ("card" | "chip" | "list"), starters.placement ("auto" | "welcome" | "composer"), starters.behavior ("send" | "fill"), starters.overflow ("scroll" | "wrap"), starters.maxItems, followUps.enabled, followUps.expose, followUps.variant, followUps.placement ("auto" | "after-message" | "composer"), followUps.behavior, followUps.overflow ("scroll" | "wrap"), followUps.maxItems.',
254
+ },
248
255
  suggestionChips:
249
- 'string[]: Suggested prompts shown to the user.',
256
+ 'Deprecated string[] shorthand for starter prompts. Prefer suggestions.starters.items.',
250
257
  messageActions: {
251
258
  description: 'Message action buttons (copy, upvote, downvote).',
252
259
  properties:
@@ -365,6 +365,14 @@ export interface ReasoningBubbleTokens {
365
365
  export interface ComposerChromeTokens {
366
366
  /** Box-shadow on the composer form (raw CSS, e.g. `none`). */
367
367
  shadow: string;
368
+ /** Inner padding of the composer form (raw CSS shorthand). @default "0.75rem 1rem" */
369
+ padding?: string;
370
+ /** Gap between the textarea row and the actions row. @default "0.5rem" */
371
+ gap?: string;
372
+ /** Composer textarea font-size. @default "0.875rem" */
373
+ fontSize?: string;
374
+ /** Composer textarea line-height. @default "1.25rem" */
375
+ lineHeight?: string;
368
376
  }
369
377
 
370
378
  /** Artifact toolbar chrome. */
@@ -524,6 +532,28 @@ export interface ScrollToBottomTokens extends ComponentTokenSet {
524
532
  iconSize?: string;
525
533
  }
526
534
 
535
+ /** Visual tokens shared by one suggestion presentation variant. */
536
+ export interface SuggestionVariantTokens extends ComponentTokenSet {
537
+ gap?: string;
538
+ minHeight?: string;
539
+ fontSize?: string;
540
+ lineHeight?: string;
541
+ iconSize?: string;
542
+ hoverBackground?: TokenReference<'color'>;
543
+ hoverForeground?: TokenReference<'color'>;
544
+ hoverBorder?: TokenReference<'color'>;
545
+ pressedBackground?: TokenReference<'color'>;
546
+ focusRing?: TokenReference<'color'>;
547
+ disabledOpacity?: string;
548
+ }
549
+
550
+ /** Starter and follow-up suggestion chrome. */
551
+ export interface SuggestionTokens {
552
+ chip: SuggestionVariantTokens;
553
+ card: SuggestionVariantTokens;
554
+ list: SuggestionVariantTokens;
555
+ }
556
+
527
557
  /** Toggle group chrome (used by createToggleGroup). */
528
558
  export interface ToggleGroupTokens {
529
559
  /** Gap between toggle buttons. Default: 0 (connected). */
@@ -555,6 +585,8 @@ export interface ComponentTokens {
555
585
  labelButton?: LabelButtonTokens;
556
586
  /** Scroll-to-bottom indicator styling tokens. */
557
587
  scrollToBottom?: ScrollToBottomTokens;
588
+ /** Starter prompt and follow-up suggestion styling tokens. */
589
+ suggestion?: SuggestionTokens;
558
590
  /** Toggle group styling tokens. */
559
591
  toggleGroup?: ToggleGroupTokens;
560
592
  /** Artifact toolbar, tab strip, and pane chrome. */
package/src/types.ts CHANGED
@@ -685,6 +685,15 @@ export type AgentToolsConfig = {
685
685
  /** Artifact kinds for the Persona sidebar and dispatch payload */
686
686
  export type PersonaArtifactKind = "markdown" | "component";
687
687
 
688
+ export type PersonaArtifactPresentation = {
689
+ /**
690
+ * Producer hint for where this artifact renders (content-disposition
691
+ * model). Beats configured defaults; loses to explicit `files`/`byKind`
692
+ * rules. Persona never infers it; the producer or host supplies it.
693
+ */
694
+ preferredMode?: PersonaArtifactDisplayModeInput;
695
+ };
696
+
688
697
  /**
689
698
  * Agent configuration for agent execution mode.
690
699
  * When provided in the widget config, enables agent loop execution instead of flow dispatch.
@@ -1375,10 +1384,60 @@ export type AgentWidgetArtifactsLayoutConfig = {
1375
1384
  }) => void | Promise<void>;
1376
1385
  };
1377
1386
 
1387
+ /**
1388
+ * Where the artifact body renders by default. Every mode shows the compact
1389
+ * reference in the transcript; the mode names where the body goes:
1390
+ * nowhere yet (`collapsed`), the side panel (`panel`), or the conversation
1391
+ * itself (`inline`).
1392
+ */
1378
1393
  export type PersonaArtifactDisplayMode =
1379
- | "card" // reference card in transcript; pane opens on click
1380
- | "panel" // reference card in transcript; pane also auto-opens on artifact_start (current default behavior)
1381
- | "inline"; // artifact preview renders directly in the transcript; no pane involvement
1394
+ | "collapsed" // body renders nowhere until the user selects the reference; pane opens on click
1395
+ | "panel" // body renders in the side panel, which auto-opens on artifact_start (default behavior)
1396
+ | "inline"; // body renders directly in the transcript; no pane involvement
1397
+
1398
+ /**
1399
+ * Accepted wherever a display mode is written. `"card"` is a deprecated
1400
+ * alias of `"collapsed"` (the old name described the reference card, which
1401
+ * every mode shows, instead of where the body renders). Resolution and
1402
+ * parsing always canonicalize to `"collapsed"`.
1403
+ */
1404
+ export type PersonaArtifactDisplayModeInput =
1405
+ | PersonaArtifactDisplayMode
1406
+ | "card";
1407
+
1408
+ /**
1409
+ * Display rules for file-backed artifacts. `byMediaType` keys are exact MIME
1410
+ * types (`"text/html"`) or major-type wildcards (`"image/*"`), matched
1411
+ * case-insensitively without parameters; exact beats wildcard.
1412
+ */
1413
+ export type PersonaArtifactFilesDisplayRules = {
1414
+ default?: PersonaArtifactDisplayModeInput;
1415
+ byMediaType?: Record<string, PersonaArtifactDisplayModeInput>;
1416
+ };
1417
+
1418
+ /**
1419
+ * Scoped artifact display rules. Matching resolves from the most specific
1420
+ * selector to `default`; see `resolveArtifactDisplay`.
1421
+ *
1422
+ * Across preference layers, one merge rule applies everywhere: a string value
1423
+ * replaces the whole subtree it names; an object refines it per key. So
1424
+ * `files: "inline"` overrides every lower-layer file rule, while
1425
+ * `files: { default: "inline" }` keeps lower-layer MIME exceptions.
1426
+ */
1427
+ export type PersonaArtifactDisplayRules = {
1428
+ default?: PersonaArtifactDisplayModeInput;
1429
+ byKind?: Partial<
1430
+ Record<PersonaArtifactKind, PersonaArtifactDisplayModeInput>
1431
+ >;
1432
+ /**
1433
+ * @deprecated Use `byKind`. When both are present, `byKind` wins.
1434
+ */
1435
+ byType?: Partial<
1436
+ Record<PersonaArtifactKind, PersonaArtifactDisplayModeInput>
1437
+ >;
1438
+ /** Rules for any artifact carrying file metadata. */
1439
+ files?: PersonaArtifactDisplayModeInput | PersonaArtifactFilesDisplayRules;
1440
+ };
1382
1441
 
1383
1442
  /** Context handed to custom artifact actions. Content fields are best-effort: resolved from live session state or, after a refresh, from the persisted reference-card payload. */
1384
1443
  export type PersonaArtifactActionContext = {
@@ -1439,15 +1498,12 @@ export type AgentWidgetArtifactsFeature = {
1439
1498
  allowedTypes?: PersonaArtifactKind[];
1440
1499
  /**
1441
1500
  * Where artifact bodies render. A single mode applies to all artifacts;
1442
- * the object form sets a default plus per-type overrides.
1501
+ * the object form sets a default plus scoped overrides.
1443
1502
  * Defaults to "panel" (current behavior).
1444
1503
  */
1445
1504
  display?:
1446
- | PersonaArtifactDisplayMode
1447
- | {
1448
- default?: PersonaArtifactDisplayMode;
1449
- byType?: Partial<Record<PersonaArtifactKind, PersonaArtifactDisplayMode>>;
1450
- };
1505
+ | PersonaArtifactDisplayModeInput
1506
+ | PersonaArtifactDisplayRules;
1451
1507
  /** Split / drawer dimensions and launcher widen behavior */
1452
1508
  layout?: AgentWidgetArtifactsLayoutConfig;
1453
1509
  /**
@@ -2201,20 +2257,85 @@ export type AgentWidgetFeatureFlags = {
2201
2257
  */
2202
2258
  askUserQuestion?: AgentWidgetAskUserQuestionFeature;
2203
2259
  /**
2204
- * Built-in `suggest_replies` quick-reply chips. When the assistant invokes
2205
- * the tool, the widget shows the suggestions as tappable chips above the
2206
- * composer (reusing the suggestion-chips surface) and immediately resumes
2207
- * the execution: fire-and-forget, no user input awaited.
2260
+ * Built-in `suggest_replies` follow-ups. When the assistant invokes the
2261
+ * tool, the widget shows the suggestions using `suggestions.followUps` and
2262
+ * immediately resumes the execution: fire-and-forget, no user input awaited.
2263
+ *
2264
+ * @deprecated Both keys moved to `suggestions.followUps`: use
2265
+ * `suggestions.followUps.enabled` and `suggestions.followUps.expose`. This
2266
+ * alias keeps working per key, with `suggestions.followUps` winning.
2208
2267
  */
2209
2268
  suggestReplies?: AgentWidgetSuggestRepliesFeature;
2210
2269
  };
2211
2270
 
2271
+ export type WidgetPreferenceCapabilities = {
2272
+ artifacts?: boolean;
2273
+ tools?: boolean;
2274
+ reasoning?: boolean;
2275
+ };
2276
+
2277
+ /**
2278
+ * Persistable artifact-layout choices. The full layout config deliberately is
2279
+ * not exposed because it also contains callbacks and embedder-owned chrome.
2280
+ */
2281
+ export type WidgetArtifactLayoutPreference = Pick<
2282
+ AgentWidgetArtifactsLayoutConfig,
2283
+ | "paneWidth"
2284
+ | "paneMaxWidth"
2285
+ | "paneMinWidth"
2286
+ | "expandLauncherPanelWhenOpen"
2287
+ | "expandedPanelWidth"
2288
+ | "resizable"
2289
+ | "resizableMinWidth"
2290
+ | "resizableMaxWidth"
2291
+ >;
2292
+
2293
+ /**
2294
+ * Curated, JSON-serializable display preferences in native `features` shape.
2295
+ *
2296
+ * Absence means inherit the next preference layer and ultimately Persona's
2297
+ * current default. Persist only explicit choices; resetting a choice deletes
2298
+ * its key.
2299
+ *
2300
+ * Capability facts (`artifacts.enabled`, `allowedTypes`), trusted iframe
2301
+ * security, callbacks, actions, renderers, labels, and theme styling are
2302
+ * intentionally excluded because they remain embedder/code-owned.
2303
+ */
2304
+ export type WidgetPreferenceSlice = {
2305
+ showToolCalls?: boolean;
2306
+ showReasoning?: boolean;
2307
+ toolCallDisplay?: Pick<
2308
+ AgentWidgetToolCallDisplayFeature,
2309
+ | "collapsedMode"
2310
+ | "expandable"
2311
+ | "grouped"
2312
+ | "groupedMode"
2313
+ | "loadingAnimation"
2314
+ >;
2315
+ reasoningDisplay?: Pick<
2316
+ AgentWidgetReasoningDisplayFeature,
2317
+ "expandable" | "activePreview"
2318
+ >;
2319
+ artifacts?: {
2320
+ /**
2321
+ * String and object forms are both persistable and mean different things
2322
+ * when layers merge: a string replaces every lower-layer display rule; an
2323
+ * object refines lower layers per selector key (same rule applies to
2324
+ * `display.files`).
2325
+ */
2326
+ display?: PersonaArtifactDisplayModeInput | PersonaArtifactDisplayRules;
2327
+ filePreview?: { enabled?: boolean };
2328
+ layout?: WidgetArtifactLayoutPreference;
2329
+ };
2330
+ };
2331
+
2212
2332
  /**
2213
- * Feature config for the built-in `suggest_replies` quick-reply chips.
2214
- * Chips render in the existing suggestions slot above the composer and are
2215
- * styled by the widget-level `suggestionChipsConfig`. A tapped chip is sent
2216
- * verbatim as the user's next message; chips clear once any user message
2217
- * follows them.
2333
+ * Feature config for the built-in `suggest_replies` follow-ups.
2334
+ *
2335
+ * @deprecated Both keys moved to `suggestions.followUps`
2336
+ * (`suggestions.followUps.enabled` / `suggestions.followUps.expose`), which
2337
+ * also owns presentation, placement, and send/fill behavior. Resolution is
2338
+ * per key with `suggestions.followUps` winning; removal waits for 5.0.
2218
2339
  */
2219
2340
  export type AgentWidgetSuggestRepliesFeature = {
2220
2341
  /**
@@ -2222,14 +2343,18 @@ export type AgentWidgetSuggestRepliesFeature = {
2222
2343
  * renders as a regular tool bubble and is NOT auto-resumed: only set this
2223
2344
  * with no server-side `suggest_replies` declaration, or the execution
2224
2345
  * parks awaiting a resume that never comes.
2346
+ * @deprecated Use `suggestions.followUps.enabled`.
2225
2347
  */
2226
2348
  enabled?: boolean;
2227
2349
  /**
2228
2350
  * Advertise the built-in `suggest_replies` tool to the agent on every
2229
2351
  * dispatch via `clientTools[]`: no server-side `runtimeTools` declaration
2230
- * needed. Defaults to `false`: flows that already declare the tool via
2231
- * `runtimeTools` would otherwise present it to the model twice. Ignored
2232
- * when `enabled` is `false`.
2352
+ * needed. Defaults to `false`. Declare the tool in one place: the API
2353
+ * dedupes a double declaration by name with `clientTools[]` winning, so the
2354
+ * model still sees exactly one tool, but the widget's description and schema
2355
+ * win and the effective tool type becomes `local` (the execution pauses and
2356
+ * the widget auto-resumes it). Ignored when `enabled` is `false`.
2357
+ * @deprecated Use `suggestions.followUps.expose`.
2233
2358
  */
2234
2359
  expose?: boolean;
2235
2360
  };
@@ -2304,10 +2429,13 @@ export type AgentWidgetAskUserQuestionFeature = {
2304
2429
  * matching {@link AskUserQuestionPayload}; when the model calls it, the
2305
2430
  * existing answer-pill sheet renders and the answer resumes the execution.
2306
2431
  *
2307
- * Defaults to `false`: flows that already declare `ask_user_question` via
2308
- * `runtimeTools` would otherwise present the tool to the model twice.
2309
- * Ignored when `enabled` is `false`: never offer the agent a question
2310
- * tool the widget can't render an answer UI for.
2432
+ * Defaults to `false`. Declare the tool in one place: the API dedupes a
2433
+ * double declaration by name with `clientTools[]` winning, so the model
2434
+ * still sees exactly one tool, but the widget's description and schema win
2435
+ * and the effective tool type becomes `local` (the execution pauses and the
2436
+ * widget resumes it with the user's answer). Ignored when `enabled` is
2437
+ * `false`: never offer the agent a question tool the widget can't render an
2438
+ * answer UI for.
2311
2439
  */
2312
2440
  expose?: boolean;
2313
2441
  /** Slide-in animation duration in ms. Defaults to 180. */
@@ -2796,6 +2924,10 @@ export type AgentWidgetSendButtonConfig = {
2796
2924
  paddingY?: string;
2797
2925
  iconText?: string;
2798
2926
  iconName?: string;
2927
+ /** Icon glyph box in icon mode, e.g. "20px". Defaults to half of `size`. */
2928
+ iconSize?: string;
2929
+ /** Stroke width for the icon glyph, in viewBox units. Default 2. */
2930
+ iconStrokeWidth?: number;
2799
2931
  useIcon?: boolean;
2800
2932
  tooltipText?: string;
2801
2933
  showTooltip?: boolean;
@@ -3611,6 +3743,123 @@ export type AgentWidgetSuggestionChipsConfig = {
3611
3743
  paddingY?: string;
3612
3744
  };
3613
3745
 
3746
+ /** A suggested next action shown to the user. Strings remain supported as shorthand. */
3747
+ export type AgentWidgetSuggestion =
3748
+ | string
3749
+ | {
3750
+ /** Stable analytics / DOM identity. Falls back to the prompt or label. */
3751
+ id?: string;
3752
+ /** Concise visible action label. */
3753
+ label: string;
3754
+ /** Text placed in the composer or sent. Defaults to `label`. */
3755
+ prompt?: string;
3756
+ /** Optional supporting copy, most useful with the `card` variant. */
3757
+ description?: string;
3758
+ /** Optional icon from Persona's bundled Lucide registry. */
3759
+ icon?: IconName;
3760
+ /**
3761
+ * CSS color for this item's glyph. Tints the icon only: label, border,
3762
+ * and background stay neutral. Ignored without an `icon`.
3763
+ */
3764
+ iconColor?: string;
3765
+ /** Override the surface's click behavior for this item. */
3766
+ behavior?: AgentWidgetSuggestionBehavior;
3767
+ /** Visually promote one high-confidence action. */
3768
+ emphasis?: "default" | "primary";
3769
+ };
3770
+
3771
+ export type AgentWidgetSuggestionBehavior = "send" | "fill";
3772
+ export type AgentWidgetSuggestionVariant = "chip" | "card" | "list";
3773
+ export type AgentWidgetSuggestionSurface = "starter" | "followUp";
3774
+ /**
3775
+ * Who produced the rendered suggestions: static config, the agent's
3776
+ * `suggest_replies` tool, or `controller.setFollowUpSuggestions`.
3777
+ */
3778
+ export type AgentWidgetSuggestionSource = "config" | "agent" | "host";
3779
+
3780
+ /**
3781
+ * A suggestion after Persona has normalized shorthand strings and resolved the
3782
+ * effective click behavior. Plugin hooks receive this stable shape.
3783
+ */
3784
+ export type AgentWidgetResolvedSuggestion = {
3785
+ id: string;
3786
+ label: string;
3787
+ prompt: string;
3788
+ description?: string;
3789
+ icon?: IconName;
3790
+ /** CSS color for the glyph only; never the label, border, or background. */
3791
+ iconColor?: string;
3792
+ behavior: AgentWidgetSuggestionBehavior;
3793
+ emphasis: "default" | "primary";
3794
+ };
3795
+
3796
+ /** Presentation keys shared by every suggestion surface. */
3797
+ export type AgentWidgetSuggestionSurfaceConfig = {
3798
+ /** Item density. Defaults to `card` for starters and `chip` for follow-ups. */
3799
+ variant?: AgentWidgetSuggestionVariant;
3800
+ /** Default click behavior, overridable per item. Defaults to `send`. */
3801
+ behavior?: AgentWidgetSuggestionBehavior;
3802
+ /**
3803
+ * Layout for items past the surface width. Defaults to `wrap` for starters
3804
+ * and `scroll` for follow-ups. Applies to the `chip` variant only; the
3805
+ * `card` and `list` layouts manage their own stacking.
3806
+ */
3807
+ overflow?: "scroll" | "wrap";
3808
+ /** Maximum visible items. Defaults to 4. */
3809
+ maxItems?: number;
3810
+ };
3811
+
3812
+ export type AgentWidgetStarterSuggestionsConfig =
3813
+ AgentWidgetSuggestionSurfaceConfig & {
3814
+ /** Suggestions shown before the first user message. */
3815
+ items?: AgentWidgetSuggestion[];
3816
+ /**
3817
+ * `auto` uses the welcome surface when the welcome card renders and the
3818
+ * composer otherwise. Explicit values are literal: `welcome` renders
3819
+ * nothing when the welcome card is hidden. Defaults to `auto`.
3820
+ */
3821
+ placement?: "auto" | "welcome" | "composer";
3822
+ };
3823
+
3824
+ export type AgentWidgetFollowUpSuggestionsConfig =
3825
+ AgentWidgetSuggestionSurfaceConfig & {
3826
+ /**
3827
+ * Enable the built-in `suggest_replies` follow-ups. Defaults to true. When
3828
+ * false, `suggest_replies` renders as a regular tool bubble and is NOT
3829
+ * auto-resumed: only set this with no server-side `suggest_replies`
3830
+ * declaration, or the execution parks awaiting a resume that never comes.
3831
+ */
3832
+ enabled?: boolean;
3833
+ /**
3834
+ * Advertise the built-in `suggest_replies` tool to the agent on every
3835
+ * dispatch via `clientTools[]`: no server-side `runtimeTools` declaration
3836
+ * needed. Defaults to `false`. Declare the tool in one place: the API
3837
+ * dedupes a double declaration by name with `clientTools[]` winning, so
3838
+ * the model still sees exactly one tool, but the widget's description and
3839
+ * schema win and the effective tool type becomes `local` (the execution
3840
+ * pauses and the widget auto-resumes it). Ignored when `enabled` is
3841
+ * `false`.
3842
+ */
3843
+ expose?: boolean;
3844
+ /**
3845
+ * `auto` places follow-ups after the transcript in regular panels and above
3846
+ * the composer in composer-bar mode. Defaults to `auto`.
3847
+ */
3848
+ placement?: "auto" | "after-message" | "composer";
3849
+ // No `items`: follow-up items are contextual and come from the agent's
3850
+ // `suggest_replies` tool or `controller.setFollowUpSuggestions`, not static
3851
+ // config. Static lists are `starters`.
3852
+ };
3853
+
3854
+ /**
3855
+ * Structured suggestions configuration. The legacy `suggestionChips` and
3856
+ * `suggestionChipsConfig` fields remain supported for backwards compatibility.
3857
+ */
3858
+ export type AgentWidgetSuggestionsConfig = {
3859
+ starters?: AgentWidgetStarterSuggestionsConfig;
3860
+ followUps?: AgentWidgetFollowUpSuggestionsConfig;
3861
+ };
3862
+
3614
3863
  /**
3615
3864
  * Interface for pluggable stream parsers that extract text from streaming responses.
3616
3865
  * Parsers handle incremental parsing to extract text values from structured formats (JSON, XML, etc.).
@@ -4902,6 +5151,15 @@ export type AgentWidgetConfig = {
4902
5151
  */
4903
5152
  colorScheme?: 'auto' | 'light' | 'dark';
4904
5153
  features?: AgentWidgetFeatureFlags;
5154
+ /**
5155
+ * Per-instance display overrides, applied over `features` through the
5156
+ * preference allowlist (security/capability keys cannot pass). String
5157
+ * `display` values replace the base rule subtree; objects refine it per
5158
+ * key. Each `controller.update({ preferences })` replaces the previous
5159
+ * slice wholesale (dropped keys revert to the base `features`); an
5160
+ * explicit-undefined patch clears it entirely.
5161
+ */
5162
+ preferences?: WidgetPreferenceSlice;
4905
5163
  /**
4906
5164
  * When true, focus the chat input after the panel opens and the open animation completes.
4907
5165
  * Applies to launcher mode (user click, controller.open(), autoExpand) and inline mode (on init).
@@ -4922,7 +5180,11 @@ export type AgentWidgetConfig = {
4922
5180
  * with `initialArtifacts`.
4923
5181
  */
4924
5182
  initialSelectedArtifactId?: string | null;
5183
+ /** Rich starter and follow-up suggestion surfaces. */
5184
+ suggestions?: AgentWidgetSuggestionsConfig;
5185
+ /** @deprecated Prefer `suggestions.starters.items`. */
4925
5186
  suggestionChips?: string[];
5187
+ /** @deprecated Prefer semantic tokens under `theme.components.suggestion`. */
4926
5188
  suggestionChipsConfig?: AgentWidgetSuggestionChipsConfig;
4927
5189
  debug?: boolean;
4928
5190
  formEndpoint?: string;
@@ -5864,6 +6126,8 @@ export type PersonaArtifactRecord = {
5864
6126
  props?: Record<string, unknown>;
5865
6127
  /** Present when this markdown artifact is a previewable file (see PersonaArtifactFileMeta). */
5866
6128
  file?: PersonaArtifactFileMeta;
6129
+ /** Optional presentation metadata such as the producer's display-mode hint. */
6130
+ presentation?: PersonaArtifactPresentation;
5867
6131
  };
5868
6132
 
5869
6133
  /** Programmatic artifact upsert (controller / window API) */
@@ -5875,6 +6139,7 @@ export type PersonaArtifactManualUpsert =
5875
6139
  content: string;
5876
6140
  /** Optional file metadata for previewable file artifacts. */
5877
6141
  file?: PersonaArtifactFileMeta;
6142
+ presentation?: PersonaArtifactPresentation;
5878
6143
  /** Set false to update the registry/pane without a transcript block (pre-4.x behavior). */
5879
6144
  transcript?: boolean;
5880
6145
  }
@@ -5884,6 +6149,7 @@ export type PersonaArtifactManualUpsert =
5884
6149
  title?: string;
5885
6150
  component: string;
5886
6151
  props?: Record<string, unknown>;
6152
+ presentation?: PersonaArtifactPresentation;
5887
6153
  /** Set false to update the registry/pane without a transcript block (pre-4.x behavior). */
5888
6154
  transcript?: boolean;
5889
6155
  };