@runtypelabs/persona 4.23.2 → 4.25.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 (166) 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 +278 -37
  12. package/dist/approval-ui.d.ts +278 -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 +273 -25
  16. package/dist/artifacts-ui.d.ts +273 -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 +273 -25
  21. package/dist/context-mentions.d.ts +273 -25
  22. package/dist/event-stream-view.d.cts +273 -25
  23. package/dist/event-stream-view.d.ts +273 -25
  24. package/dist/forms-ui.d.cts +319 -27
  25. package/dist/forms-ui.d.ts +319 -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 +344 -39
  31. package/dist/index.d.ts +344 -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 +273 -25
  41. package/dist/session-reconnect.d.ts +273 -25
  42. package/dist/smart-dom-reader.d.cts +273 -25
  43. package/dist/smart-dom-reader.d.ts +273 -25
  44. package/dist/theme-editor-preview.cjs +74 -73
  45. package/dist/theme-editor-preview.d.cts +280 -25
  46. package/dist/theme-editor-preview.d.ts +280 -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 +293 -27
  50. package/dist/theme-editor.d.ts +293 -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/voice-runtime-7DA7MJH4.js +1 -0
  57. package/dist/voice-runtime.cjs +1 -1
  58. package/dist/voice-runtime.d.cts +33 -0
  59. package/dist/voice-runtime.d.ts +33 -0
  60. package/dist/voice-runtime.js +1 -1
  61. package/dist/widget.css +1 -1
  62. package/package.json +3 -2
  63. package/src/__fixtures__/defaults-parity.v4.json +776 -0
  64. package/src/approval-ui-entry.ts +0 -1
  65. package/src/approval-ui-loader.ts +5 -4
  66. package/src/approval-ui.ts +0 -1
  67. package/src/client-identity-proof.test.ts +193 -0
  68. package/src/client-session-warm.test.ts +216 -0
  69. package/src/client-visitor-history.test.ts +6 -0
  70. package/src/client.ts +122 -30
  71. package/src/codegen.test.ts +24 -0
  72. package/src/components/activity-row.test.ts +100 -0
  73. package/src/components/activity-row.ts +136 -0
  74. package/src/components/approval-actions.test.ts +8 -5
  75. package/src/components/approval-actions.ts +19 -6
  76. package/src/components/approval-bubble.test.ts +56 -9
  77. package/src/components/approval-bubble.ts +36 -41
  78. package/src/components/composer-builder.ts +5 -2
  79. package/src/components/composer-parts.ts +10 -2
  80. package/src/components/header-builder.test.ts +32 -0
  81. package/src/components/header-builder.ts +7 -4
  82. package/src/components/header-layouts.test.ts +12 -0
  83. package/src/components/header-layouts.ts +3 -2
  84. package/src/components/launcher.ts +11 -2
  85. package/src/components/message-bubble.test.ts +22 -0
  86. package/src/components/message-bubble.ts +7 -3
  87. package/src/components/panel.test.ts +52 -1
  88. package/src/components/panel.ts +47 -8
  89. package/src/components/pill-composer-builder.test.ts +2 -0
  90. package/src/components/pill-composer-builder.ts +0 -1
  91. package/src/components/reasoning-bubble.ts +29 -17
  92. package/src/components/suggestions.test.ts +1 -0
  93. package/src/components/suggestions.ts +0 -16
  94. package/src/components/tool-bubble.ts +131 -114
  95. package/src/components/tool-details.test.ts +87 -0
  96. package/src/components/tool-details.ts +102 -0
  97. package/src/components/welcome.test.ts +8 -0
  98. package/src/components/welcome.ts +4 -2
  99. package/src/defaults-parity.test.ts +158 -0
  100. package/src/defaults.test.ts +69 -0
  101. package/src/defaults.ts +128 -49
  102. package/src/generated/runtype-openapi-contract.ts +2 -2
  103. package/src/index-core.ts +4 -0
  104. package/src/install.test.ts +24 -0
  105. package/src/install.ts +3 -1
  106. package/src/launcher-global.ts +2 -1
  107. package/src/launcher-variants.test.ts +52 -0
  108. package/src/runtime/init-update-reset.test.ts +21 -0
  109. package/src/session.ts +59 -7
  110. package/src/session.voice.test.ts +72 -1
  111. package/src/styles/widget-styles.test.ts +19 -0
  112. package/src/styles/widget.css +295 -23
  113. package/src/theme-editor/index.ts +2 -0
  114. package/src/theme-editor/preview-utils.test.ts +8 -0
  115. package/src/theme-editor/preview-utils.ts +7 -3
  116. package/src/theme-editor/sections.test.ts +31 -0
  117. package/src/theme-editor/sections.ts +55 -1
  118. package/src/theme-editor/state-preferences.test.ts +49 -0
  119. package/src/theme-editor/state.ts +73 -15
  120. package/src/theme-reference.test.ts +20 -0
  121. package/src/theme-reference.ts +11 -5
  122. package/src/tool-call-display-defaults.test.ts +33 -1
  123. package/src/types/theme.ts +80 -3
  124. package/src/types.ts +207 -22
  125. package/src/ui.activity-row.test.ts +91 -0
  126. package/src/ui.approval-plugin.test.ts +63 -0
  127. package/src/ui.composer-compact.test.ts +24 -2
  128. package/src/ui.composer-placement.test.ts +23 -0
  129. package/src/ui.detached-panel.test.ts +33 -6
  130. package/src/ui.docked.test.ts +19 -0
  131. package/src/ui.header-update-stability.test.ts +25 -0
  132. package/src/ui.identity-proof.test.ts +58 -0
  133. package/src/ui.launcher-teaser.test.ts +25 -0
  134. package/src/ui.message-width.test.ts +17 -2
  135. package/src/ui.reasoning-display.test.ts +92 -0
  136. package/src/ui.rebuild-composer.test.ts +20 -3
  137. package/src/ui.scroll.test.ts +32 -0
  138. package/src/ui.session-init.test.ts +312 -0
  139. package/src/ui.status-indicator.test.ts +82 -0
  140. package/src/ui.suggest-replies.test.ts +11 -0
  141. package/src/ui.ts +422 -74
  142. package/src/ui.v5-defaults.test.ts +144 -0
  143. package/src/ui.voice-prewarm.test.ts +222 -0
  144. package/src/ui.welcome.test.ts +92 -3
  145. package/src/utils/__snapshots__/tokens-parity.test.ts.snap +811 -140
  146. package/src/utils/code-generators.ts +14 -0
  147. package/src/utils/composer-compact.test.ts +0 -2
  148. package/src/utils/composer-compact.ts +2 -4
  149. package/src/utils/composer-placement.ts +2 -3
  150. package/src/utils/config-merge.test.ts +81 -5
  151. package/src/utils/config-merge.ts +13 -3
  152. package/src/utils/defaults-provenance.ts +67 -0
  153. package/src/utils/dock.ts +7 -1
  154. package/src/utils/launcher-variant.ts +16 -0
  155. package/src/utils/panel-config.ts +97 -0
  156. package/src/utils/status-indicator.ts +39 -0
  157. package/src/utils/theme.test.ts +182 -0
  158. package/src/utils/theme.ts +131 -18
  159. package/src/utils/tokens.test.ts +122 -1
  160. package/src/utils/tokens.ts +177 -17
  161. package/src/voice/runtype-voice-provider.ts +238 -27
  162. package/src/voice/voice.test.ts +272 -2
  163. package/src/welcome.test.ts +23 -0
  164. package/src/welcome.ts +16 -9
  165. package/dist/approval-ui-K7R5Q5OC.js +0 -1
  166. package/dist/voice-runtime-EFR3R5ES.js +0 -1
@@ -0,0 +1,158 @@
1
+ // @vitest-environment jsdom
2
+
3
+ /**
4
+ * Locked 4.22.0 defaults contract. The fixture was captured before the v5
5
+ * defaults work began and must not be regenerated as part of later changes.
6
+ *
7
+ * JSDOM does not perform browser layout or resolve inherited custom
8
+ * properties. This test therefore records the DOM-facing computed declarations
9
+ * it can reliably expose, alongside the resolved config and emitted token map.
10
+ * The Playwright visual harness covers physical layout separately.
11
+ */
12
+
13
+ import { readFileSync } from "node:fs";
14
+ import { resolve } from "node:path";
15
+ import { afterEach, describe, expect, it } from "vitest";
16
+
17
+ import { mergeWithDefaults } from "./defaults";
18
+ import { createAgentExperience } from "./ui";
19
+ import { createTheme, themeToCssVariables } from "./utils/tokens";
20
+
21
+ const fixturePath = resolve(process.cwd(), "src/__fixtures__/defaults-parity.v4.json");
22
+ const stylesheetPath = resolve(process.cwd(), "src/styles/widget.css");
23
+
24
+ const styles = (element: Element | null) => {
25
+ if (!element) throw new Error("Expected baseline element to be rendered");
26
+ const computed = getComputedStyle(element);
27
+ return Object.fromEntries(
28
+ ["display", "position", "background-color", "color", "border-radius", "padding", "margin", "font-size", "line-height", "box-shadow", "width", "height"].map(
29
+ (property) => [property, computed.getPropertyValue(property)]
30
+ )
31
+ );
32
+ };
33
+
34
+ const snapshot = (v5Defaults = false) => {
35
+ const stylesheet = document.createElement("style");
36
+ stylesheet.textContent = readFileSync(stylesheetPath, "utf8");
37
+ document.head.appendChild(stylesheet);
38
+ const mount = document.createElement("div");
39
+ document.body.appendChild(mount);
40
+ const controller = createAgentExperience(mount, {
41
+ apiUrl: "https://api.example.com/chat",
42
+ launcher: { autoExpand: true },
43
+ persistState: false,
44
+ future: { v5Defaults },
45
+ });
46
+ controller.injectUserMessage({ content: "Baseline user message" });
47
+ controller.injectAssistantMessage({ content: "Baseline assistant message" });
48
+ controller.injectTestMessage({
49
+ type: "message",
50
+ message: {
51
+ id: "baseline-tool",
52
+ role: "assistant",
53
+ content: "",
54
+ createdAt: "2026-01-01T00:00:00.000Z",
55
+ sequence: 3,
56
+ streaming: false,
57
+ variant: "tool",
58
+ toolCall: { id: "baseline-tool", name: "baseline_tool", status: "complete", chunks: ["done"] },
59
+ },
60
+ });
61
+
62
+ const result = {
63
+ themeCssVariables: themeToCssVariables(createTheme(undefined, { future: { v5Defaults } })),
64
+ mergedDefaultConfig: mergeWithDefaults(),
65
+ computedStyles: {
66
+ header: styles(mount.querySelector(".persona-widget-header")),
67
+ userBubble: styles(mount.querySelector(".persona-message-row-user .persona-message-bubble")),
68
+ assistantBubble: styles(mount.querySelector(".persona-message-row-assistant .persona-message-bubble")),
69
+ composer: styles(mount.querySelector("[data-persona-composer-form]")),
70
+ launcher: styles(mount.querySelector(".persona-launcher-surface button")),
71
+ toolBubble: styles(mount.querySelector(".persona-tool-bubble")),
72
+ },
73
+ };
74
+ controller.destroy();
75
+ mount.remove();
76
+ stylesheet.remove();
77
+ return result;
78
+ };
79
+
80
+ afterEach(() => document.body.replaceChildren());
81
+
82
+ type DefaultsSnapshot = ReturnType<typeof snapshot>;
83
+
84
+ // JSDOM leaves some computed declarations as var(...) strings. Resolve a
85
+ // whole-value reference against that snapshot's own emitted tokens before
86
+ // comparing, so deleting an unreachable fallback is not a visual regression.
87
+ // Unknown references stay verbatim; changes to actual tokens still fail the
88
+ // independent, complete themeCssVariables comparison. The fixture stays intact.
89
+ const resolveComputedTokens = (value: DefaultsSnapshot): DefaultsSnapshot => ({
90
+ ...value,
91
+ computedStyles: Object.fromEntries(
92
+ Object.entries(value.computedStyles).map(([name, declarations]) => [name,
93
+ Object.fromEntries(Object.entries(declarations).map(([property, declaration]) => {
94
+ const seen = new Set<string>();
95
+ let resolved = declaration;
96
+ while (!seen.has(resolved)) {
97
+ seen.add(resolved);
98
+ const reference = /^var\((--[\w-]+)(?:,[\s\S]*)?\)$/.exec(resolved);
99
+ const token = reference && value.themeCssVariables[reference[1]];
100
+ if (typeof token !== "string") break;
101
+ resolved = token;
102
+ }
103
+ return [property, resolved.replace(/([\d.]+)rem\b/g, (_, amount) => `${Number(amount) * 16}px`)];
104
+ })),
105
+ ])
106
+ ) as DefaultsSnapshot["computedStyles"],
107
+ });
108
+
109
+ describe("4.22.0 defaults parity (flag off)", () => {
110
+ it("matches the committed baseline", () => {
111
+ // JSON is deliberately the fixture format: it makes omitted/undefined
112
+ // config and token values deterministic across Node versions.
113
+ const actual = JSON.parse(JSON.stringify(snapshot(false)));
114
+ const baseline = JSON.parse(readFileSync(fixturePath, "utf8"));
115
+ // Phase 3 activates formerly unused tokens. Lock their old and new values
116
+ // explicitly instead of rewriting the historical fixture or ignoring keys.
117
+ const activatedTokens: Record<string, [string, string]> = {
118
+ "--persona-components-header-padding": ["1rem", "20px 24px"],
119
+ "--persona-components-panel-height": ["600px", "min(640px, max(200px, calc(100vh - 64px)))"],
120
+ "--persona-components-panel-maxHeight": ["calc(100vh - 80px)", "none"],
121
+ "--persona-components-panel-maxWidth": ["440px", "none"],
122
+ };
123
+ const addedTokens: Record<string, string> = {
124
+ "--persona-components-header-minimalPadding": "16px 24px",
125
+ "--persona-components-message-gap": "12px",
126
+ "--persona-cw-container": "#f9fafb",
127
+ "--persona-cw-surface": "#f9fafb",
128
+ "--persona-cw-border": "#e5e7eb",
129
+ };
130
+ for (const [mode, border, shadow, radius] of [
131
+ ["floating", "1px solid var(--persona-border)", baseline.themeCssVariables["--persona-components-panel-shadow"], "0.75rem"],
132
+ ["inline", "1px solid var(--persona-border)", "none", "0.75rem"],
133
+ ["docked", "none", "none", "0.75rem"],
134
+ ["sidebar", "none", undefined, "0"],
135
+ ["mobile", "none", "none", "0"],
136
+ ]) {
137
+ addedTokens[`--persona-components-panel-modes-${mode}-border`] = border!;
138
+ addedTokens[`--persona-components-panel-modes-${mode}-borderRadius`] = radius!;
139
+ if (shadow !== undefined) addedTokens[`--persona-components-panel-modes-${mode}-shadow`] = shadow;
140
+ }
141
+ // Resolve computed declarations before adapting the token map for comparison.
142
+ // The baseline stylesheet uses rem; this JSDOM harness assumes a 16px root.
143
+ const resolvedActual = resolveComputedTokens(actual);
144
+ const resolvedBaseline = resolveComputedTokens(baseline);
145
+ resolvedActual.themeCssVariables = { ...actual.themeCssVariables };
146
+ for (const [key, [oldValue, newValue]] of Object.entries(activatedTokens)) {
147
+ expect(baseline.themeCssVariables[key], key).toBe(oldValue);
148
+ expect(actual.themeCssVariables[key], key).toBe(newValue);
149
+ resolvedActual.themeCssVariables[key] = oldValue;
150
+ }
151
+ for (const [key, value] of Object.entries(addedTokens)) {
152
+ expect(baseline.themeCssVariables).not.toHaveProperty(key);
153
+ expect(actual.themeCssVariables[key], key).toBe(value);
154
+ delete resolvedActual.themeCssVariables[key];
155
+ }
156
+ expect(resolvedActual).toEqual(resolvedBaseline);
157
+ });
158
+ });
@@ -0,0 +1,69 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ DEFAULT_WIDGET_CONFIG,
4
+ mergeWithDefaults,
5
+ resolveDefaults,
6
+ resolveDefaultsVersion,
7
+ } from "./defaults";
8
+
9
+ describe.each([false, true])("defaults resolver (v5Defaults: %s)", (v5Defaults) => {
10
+ const future = { v5Defaults };
11
+
12
+ it("selects the requested version and its documented defaults", () => {
13
+ expect(resolveDefaultsVersion({ future })).toBe(v5Defaults ? "v5" : "v4");
14
+ const defaults = resolveDefaults({ future });
15
+ if (!v5Defaults) expect(defaults).toEqual(DEFAULT_WIDGET_CONFIG);
16
+ expect(defaults.layout?.header?.showSubtitle).toBe(!v5Defaults);
17
+ expect(defaults.composer?.layout).toBe(v5Defaults ? "single-row" : undefined);
18
+ expect(defaults.launcher?.variant).toBe(v5Defaults ? "circle" : undefined);
19
+ expect(defaults.composer?.placement).toBe(v5Defaults ? "overlay" : undefined);
20
+ expect(defaults.launcher?.width).toBe(v5Defaults
21
+ ? "min(400px, calc(100vw - 24px))"
22
+ : "min(440px, calc(100vw - 24px))");
23
+ });
24
+
25
+ it("preserves explicit composer and header options above the version defaults", () => {
26
+ const result = mergeWithDefaults({
27
+ future, composer: { layout: "stacked", placement: "block" },
28
+ layout: { header: { showSubtitle: true } },
29
+ });
30
+ expect(result.composer?.layout).toBe("stacked");
31
+ expect(result.composer?.placement).toBe("block");
32
+ expect(result.layout?.header?.showSubtitle).toBe(true);
33
+ const partial = mergeWithDefaults({ future, composer: { placement: "overlay" } });
34
+ expect(partial.composer?.layout).toBe(v5Defaults ? "single-row" : undefined);
35
+ });
36
+
37
+ it("merges partial role sizing above the selected default width", () => {
38
+ const result = mergeWithDefaults({ future, layout: { messages: { assistant: { maxWidth: "65ch" } } } });
39
+ expect(result.layout?.messages?.assistant?.maxWidth).toBe("65ch");
40
+ expect(result.layout?.messages?.assistant?.width).toBe(v5Defaults ? "full" : undefined);
41
+ expect(result.layout?.messages?.user).toBeUndefined();
42
+ });
43
+
44
+ it("merges explicit options and nested theme overrides above the selected defaults", () => {
45
+ const config = {
46
+ future,
47
+ launcher: { headerIconSize: "31px", title: "Custom title" },
48
+ features: { reasoningDisplay: { expandable: false } },
49
+ theme: { components: { header: { padding: "7px", title: { fontSize: "19px" } } } },
50
+ };
51
+ const result = mergeWithDefaults(config);
52
+ expect(result.future).toEqual(future);
53
+ expect(result.launcher?.headerIconSize).toBe("31px");
54
+ expect(result.launcher?.title).toBe("Custom title");
55
+ expect(result.features?.reasoningDisplay).toEqual({
56
+ ...DEFAULT_WIDGET_CONFIG.features?.reasoningDisplay,
57
+ ...(v5Defaults ? { variant: "row", iconVisibility: "active", autoExpand: false, loadingAnimation: "shimmer" } : {}),
58
+ expandable: false,
59
+ });
60
+ expect(result.theme?.components?.header).toEqual(config.theme.components.header);
61
+ expect(resolveDefaults({ future }).launcher?.headerIconSize).toBe(v5Defaults ? "20px" : "40px");
62
+ });
63
+ });
64
+
65
+ it("keeps the legacy defaults when the opt-in is missing", () => {
66
+ expect(resolveDefaultsVersion()).toBe("v4");
67
+ expect(resolveDefaultsVersion({ future: {} })).toBe("v4");
68
+ expect(JSON.stringify(mergeWithDefaults())).toBe(JSON.stringify(DEFAULT_WIDGET_CONFIG));
69
+ });
package/src/defaults.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import type { AgentWidgetConfig, AgentWidgetLauncherConfig } from "./types";
2
2
  import type { DeepPartial, PersonaTheme } from "./types/theme";
3
3
  import { deepMerge } from "./utils/deep-merge";
4
+ import { inheritDefaultProvenance } from "./utils/defaults-provenance";
5
+ import { inheritPanelAliasProvenance } from "./utils/panel-config";
4
6
  import {
5
7
  DEFAULT_TOOLTIP_DELAY_MS,
6
8
  DEFAULT_TOOLTIP_SKIP_DELAY_MS,
@@ -16,12 +18,33 @@ export const DEFAULT_FLOATING_LAUNCHER_WIDTH = "min(440px, calc(100vw - 24px))";
16
18
  /** Max width cap paired with {@link DEFAULT_FLOATING_LAUNCHER_WIDTH} for theme defaults. */
17
19
  export const DEFAULT_FLOATING_LAUNCHER_MAX_WIDTH = "440px";
18
20
 
21
+ /** Canonical header avatar box size, shared by the initial render and updates. */
22
+ export const DEFAULT_HEADER_ICON_SIZE = "40px";
23
+
19
24
  /**
20
25
  * Canonical composer placeholder. The low-level builders fall back to this so
21
26
  * they cannot drift from `DEFAULT_WIDGET_CONFIG.copy.inputPlaceholder`.
22
27
  */
23
28
  export const DEFAULT_INPUT_PLACEHOLDER = "How can I help...";
24
29
 
30
+ /** Shared by config resolution and standalone transcript component builders. */
31
+ export const DEFAULT_TOOL_CALL_DISPLAY = {
32
+ collapsedMode: "tool-call",
33
+ activePreview: false,
34
+ grouped: false,
35
+ groupedMode: "stack",
36
+ previewMaxLines: 3,
37
+ expandable: true,
38
+ loadingAnimation: "none",
39
+ } satisfies NonNullable<NonNullable<AgentWidgetConfig["features"]>["toolCallDisplay"]>;
40
+
41
+ export const DEFAULT_REASONING_DISPLAY = {
42
+ activePreview: false,
43
+ previewMaxLines: 3,
44
+ expandable: true,
45
+ loadingAnimation: "none",
46
+ } satisfies NonNullable<NonNullable<AgentWidgetConfig["features"]>["reasoningDisplay"]>;
47
+
25
48
  export const DEFAULT_LAUNCHER_CONFIG: AgentWidgetLauncherConfig = {
26
49
  enabled: true,
27
50
  mountMode: "floating",
@@ -40,7 +63,7 @@ export const DEFAULT_LAUNCHER_CONFIG: AgentWidgetLauncherConfig = {
40
63
  autoExpand: false,
41
64
  callToActionIconHidden: false,
42
65
  agentIconSize: "40px",
43
- headerIconSize: "40px",
66
+ headerIconSize: DEFAULT_HEADER_ICON_SIZE,
44
67
  // closeButtonSize / clearChat.size omitted so theme.components.header.controlSize
45
68
  // sizes the header controls; setting either here would pin them past the token.
46
69
  // Zero out browser-default <button> padding so the icon gets the full
@@ -78,7 +101,7 @@ export const DEFAULT_LAUNCHER_CONFIG: AgentWidgetLauncherConfig = {
78
101
  * Default widget configuration
79
102
  * Single source of truth for all default values
80
103
  */
81
- export const DEFAULT_WIDGET_CONFIG: Partial<AgentWidgetConfig> = {
104
+ export const DEFAULTS_BASE: Partial<AgentWidgetConfig> = {
82
105
  apiUrl: "https://api.runtype.com/api/chat/dispatch",
83
106
  // Client token mode defaults (optional, only used when clientToken is set)
84
107
  clientToken: undefined,
@@ -155,21 +178,8 @@ export const DEFAULT_WIDGET_CONFIG: Partial<AgentWidgetConfig> = {
155
178
  // default so the default UX keeps the affordance.)
156
179
  showActivityWhilePinned: true,
157
180
  },
158
- toolCallDisplay: {
159
- collapsedMode: "tool-call",
160
- activePreview: false,
161
- grouped: false,
162
- groupedMode: "stack",
163
- previewMaxLines: 3,
164
- expandable: true,
165
- loadingAnimation: "none",
166
- },
167
- reasoningDisplay: {
168
- activePreview: false,
169
- previewMaxLines: 3,
170
- expandable: true,
171
- loadingAnimation: "none",
172
- },
181
+ toolCallDisplay: DEFAULT_TOOL_CALL_DISPLAY,
182
+ reasoningDisplay: DEFAULT_REASONING_DISPLAY,
173
183
  streamAnimation: {
174
184
  type: "none",
175
185
  placeholder: "none",
@@ -246,6 +256,49 @@ export const DEFAULT_WIDGET_CONFIG: Partial<AgentWidgetConfig> = {
246
256
  debug: false,
247
257
  };
248
258
 
259
+ /** Version-specific defaults, selected by the staged V5 opt-in. */
260
+ export const DEFAULTS_V4: Partial<AgentWidgetConfig> = {};
261
+ export const DEFAULT_LAUNCHER_V5: Partial<AgentWidgetLauncherConfig> = {
262
+ variant: "circle",
263
+ width: "min(400px, calc(100vw - 24px))",
264
+ headerIconSize: "20px",
265
+ };
266
+ export const DEFAULTS_V5: Partial<AgentWidgetConfig> = {
267
+ launcher: DEFAULT_LAUNCHER_V5,
268
+ features: {
269
+ toolCallDisplay: { variant: "row", iconVisibility: "active", autoExpand: false, grouped: true, groupedMode: "collapsible", loadingAnimation: "shimmer" },
270
+ reasoningDisplay: { variant: "row", iconVisibility: "active", autoExpand: false, loadingAnimation: "shimmer" },
271
+ },
272
+ composer: { layout: "single-row", placement: "overlay" },
273
+ sendButton: { iconName: "arrow-up" },
274
+ statusIndicator: { mode: "transient" },
275
+ layout: {
276
+ contentMaxWidth: "768px",
277
+ topFade: true,
278
+ header: { showSubtitle: false },
279
+ messages: { assistant: { width: "full" } },
280
+ },
281
+ };
282
+
283
+ export type DefaultsVersion = "v4" | "v5";
284
+
285
+ /** Shared by config, token, theme, and editor resolution. Only true opts in. */
286
+ export function resolveDefaultsVersion(
287
+ config?: Pick<AgentWidgetConfig, "future">
288
+ ): DefaultsVersion {
289
+ return config?.future?.v5Defaults === true ? "v5" : "v4";
290
+ }
291
+
292
+ export function resolveDefaults(
293
+ config?: Pick<AgentWidgetConfig, "future">
294
+ ): Partial<AgentWidgetConfig> {
295
+ const overlay = resolveDefaultsVersion(config) === "v5" ? DEFAULTS_V5 : DEFAULTS_V4;
296
+ return deepMerge(DEFAULTS_BASE, overlay) as Partial<AgentWidgetConfig>;
297
+ }
298
+
299
+ /** Backward-compatible export of the legacy widget defaults. */
300
+ export const DEFAULT_WIDGET_CONFIG = resolveDefaults();
301
+
249
302
  function mergeThemePartials(
250
303
  base: DeepPartial<PersonaTheme> | undefined,
251
304
  override: DeepPartial<PersonaTheme> | undefined
@@ -266,57 +319,62 @@ function mergeThemePartials(
266
319
  export function mergeWithDefaults(
267
320
  config?: Partial<AgentWidgetConfig>
268
321
  ): Partial<AgentWidgetConfig> {
269
- if (!config) return DEFAULT_WIDGET_CONFIG;
322
+ const defaults = resolveDefaults(config);
323
+ if (!config) return inheritDefaultProvenance(inheritPanelAliasProvenance(defaults), undefined, DEFAULTS_V5);
270
324
 
271
- return {
272
- ...DEFAULT_WIDGET_CONFIG,
325
+ return inheritDefaultProvenance(inheritPanelAliasProvenance({
326
+ ...defaults,
273
327
  ...config,
274
- theme: mergeThemePartials(DEFAULT_WIDGET_CONFIG.theme, config.theme),
275
- darkTheme: mergeThemePartials(DEFAULT_WIDGET_CONFIG.darkTheme, config.darkTheme),
328
+ theme: mergeThemePartials(defaults.theme, config.theme),
329
+ darkTheme: mergeThemePartials(defaults.darkTheme, config.darkTheme),
276
330
  launcher: {
277
- ...DEFAULT_WIDGET_CONFIG.launcher,
331
+ ...defaults.launcher,
278
332
  ...config.launcher,
279
333
  dock: {
280
- ...DEFAULT_WIDGET_CONFIG.launcher?.dock,
334
+ ...defaults.launcher?.dock,
281
335
  ...config.launcher?.dock,
282
336
  },
283
337
  clearChat: {
284
- ...DEFAULT_WIDGET_CONFIG.launcher?.clearChat,
338
+ ...defaults.launcher?.clearChat,
285
339
  ...config.launcher?.clearChat,
286
340
  },
287
341
  },
342
+ ...((defaults.composer || config.composer) ? { composer: {
343
+ ...defaults.composer,
344
+ ...config.composer,
345
+ } } : {}),
288
346
  tooltip: {
289
- ...DEFAULT_WIDGET_CONFIG.tooltip,
347
+ ...defaults.tooltip,
290
348
  ...config.tooltip,
291
349
  },
292
350
  copy: {
293
- ...DEFAULT_WIDGET_CONFIG.copy,
351
+ ...defaults.copy,
294
352
  ...config.copy,
295
353
  },
296
354
  sendButton: {
297
- ...DEFAULT_WIDGET_CONFIG.sendButton,
355
+ ...defaults.sendButton,
298
356
  ...config.sendButton,
299
357
  },
300
358
  statusIndicator: {
301
- ...DEFAULT_WIDGET_CONFIG.statusIndicator,
359
+ ...defaults.statusIndicator,
302
360
  ...config.statusIndicator,
303
361
  },
304
362
  voiceRecognition: {
305
- ...DEFAULT_WIDGET_CONFIG.voiceRecognition,
363
+ ...defaults.voiceRecognition,
306
364
  ...config.voiceRecognition,
307
365
  },
308
366
  features: (() => {
309
- const da = DEFAULT_WIDGET_CONFIG.features?.artifacts;
367
+ const da = defaults.features?.artifacts;
310
368
  const ca = config.features?.artifacts;
311
- const dsb = DEFAULT_WIDGET_CONFIG.features?.scrollToBottom;
369
+ const dsb = defaults.features?.scrollToBottom;
312
370
  const csb = config.features?.scrollToBottom;
313
- const dsc = DEFAULT_WIDGET_CONFIG.features?.scrollBehavior;
371
+ const dsc = defaults.features?.scrollBehavior;
314
372
  const csc = config.features?.scrollBehavior;
315
- const dsa = DEFAULT_WIDGET_CONFIG.features?.streamAnimation;
373
+ const dsa = defaults.features?.streamAnimation;
316
374
  const csa = config.features?.streamAnimation;
317
- const dau = DEFAULT_WIDGET_CONFIG.features?.askUserQuestion;
375
+ const dau = defaults.features?.askUserQuestion;
318
376
  const cau = config.features?.askUserQuestion;
319
- const dh = DEFAULT_WIDGET_CONFIG.features?.history;
377
+ const dh = defaults.features?.history;
320
378
  const ch = config.features?.history;
321
379
  const mergedArtifacts =
322
380
  da === undefined && ca === undefined
@@ -373,8 +431,16 @@ export function mergeWithDefaults(
373
431
  },
374
432
  };
375
433
  return {
376
- ...DEFAULT_WIDGET_CONFIG.features,
434
+ ...defaults.features,
377
435
  ...config.features,
436
+ toolCallDisplay: {
437
+ ...defaults.features?.toolCallDisplay,
438
+ ...config.features?.toolCallDisplay,
439
+ },
440
+ reasoningDisplay: {
441
+ ...defaults.features?.reasoningDisplay,
442
+ ...config.features?.reasoningDisplay,
443
+ },
378
444
  ...(mergedScrollToBottom !== undefined ? { scrollToBottom: mergedScrollToBottom } : {}),
379
445
  ...(mergedScrollBehavior !== undefined ? { scrollBehavior: mergedScrollBehavior } : {}),
380
446
  ...(mergedArtifacts !== undefined ? { artifacts: mergedArtifacts } : {}),
@@ -383,46 +449,59 @@ export function mergeWithDefaults(
383
449
  ...(mergedHistory !== undefined ? { history: mergedHistory } : {}),
384
450
  };
385
451
  })(),
386
- suggestionChips: config.suggestionChips ?? DEFAULT_WIDGET_CONFIG.suggestionChips,
452
+ suggestionChips: config.suggestionChips ?? defaults.suggestionChips,
387
453
  suggestionChipsConfig: {
388
- ...DEFAULT_WIDGET_CONFIG.suggestionChipsConfig,
454
+ ...defaults.suggestionChipsConfig,
389
455
  ...config.suggestionChipsConfig,
390
456
  },
391
457
  layout: {
392
- ...DEFAULT_WIDGET_CONFIG.layout,
458
+ ...defaults.layout,
393
459
  ...config.layout,
394
460
  header: {
395
- ...DEFAULT_WIDGET_CONFIG.layout?.header,
461
+ ...defaults.layout?.header,
396
462
  ...config.layout?.header,
397
463
  },
398
464
  messages: {
399
- ...DEFAULT_WIDGET_CONFIG.layout?.messages,
465
+ ...defaults.layout?.messages,
400
466
  ...config.layout?.messages,
467
+ ...((defaults.layout?.messages?.user || config.layout?.messages?.user) ? {
468
+ user: {
469
+ ...defaults.layout?.messages?.user,
470
+ ...config.layout?.messages?.user,
471
+ },
472
+ } : {}),
473
+ ...((defaults.layout?.messages?.assistant || config.layout?.messages?.assistant) ? {
474
+ assistant: {
475
+ ...defaults.layout?.messages?.assistant,
476
+ ...config.layout?.messages?.assistant,
477
+ },
478
+ } : {}),
401
479
  avatar: {
402
- ...DEFAULT_WIDGET_CONFIG.layout?.messages?.avatar,
480
+ ...defaults.layout?.messages?.avatar,
403
481
  ...config.layout?.messages?.avatar,
404
482
  },
405
483
  timestamp: {
406
- ...DEFAULT_WIDGET_CONFIG.layout?.messages?.timestamp,
484
+ ...defaults.layout?.messages?.timestamp,
407
485
  ...config.layout?.messages?.timestamp,
408
486
  },
409
487
  },
410
488
  slots: {
411
- ...DEFAULT_WIDGET_CONFIG.layout?.slots,
489
+ ...defaults.layout?.slots,
412
490
  ...config.layout?.slots,
413
491
  },
414
492
  },
415
493
  markdown: {
416
- ...DEFAULT_WIDGET_CONFIG.markdown,
494
+ ...defaults.markdown,
417
495
  ...config.markdown,
418
496
  options: {
419
- ...DEFAULT_WIDGET_CONFIG.markdown?.options,
497
+ ...defaults.markdown?.options,
420
498
  ...config.markdown?.options,
421
499
  },
422
500
  },
423
501
  messageActions: {
424
- ...DEFAULT_WIDGET_CONFIG.messageActions,
502
+ ...defaults.messageActions,
425
503
  ...config.messageActions,
426
504
  },
427
- };
505
+ }, config), config, DEFAULTS_V5);
506
+
428
507
  }
@@ -619,7 +619,7 @@ export type RuntypeClientInitResponse = {
619
619
  conversationRevision: string;
620
620
  durableRecovery?: {
621
621
  enabled: boolean;
622
- join?: boolean;
622
+ steer?: boolean;
623
623
  };
624
624
  expiresAt: string;
625
625
  flow?: {
@@ -681,7 +681,7 @@ export type RuntypeClientChatRequest = {
681
681
  }>;
682
682
  metadata?: Record<string, unknown>;
683
683
  sessionId: string;
684
- submitMode?: "normal" | "interrupt" | "join";
684
+ submitMode?: "normal" | "interrupt" | "steer";
685
685
  turnId?: string;
686
686
  };
687
687
 
package/src/index-core.ts CHANGED
@@ -9,6 +9,10 @@ export type {
9
9
  AgentWidgetStorageAdapter,
10
10
  AgentWidgetStoredState,
11
11
  AgentWidgetConfigPatch,
12
+ AgentWidgetSessionInitTrigger,
13
+ AgentWidgetSessionInitHook,
14
+ AgentWidgetVoicePrewarmTrigger,
15
+ AgentWidgetVoicePrewarmHook,
12
16
  TargetResolver,
13
17
  ResolvedTarget,
14
18
  AgentWidgetFeatureFlags,
@@ -226,6 +226,30 @@ describe("install.ts: deferred launcher path", () => {
226
226
 
227
227
  expect(initAgentWidget).toHaveBeenCalledTimes(1);
228
228
  });
229
+
230
+ it.each([false, true])("passes future.v5Defaults=%s through the deferred launcher handoff", async (v5Defaults) => {
231
+ markCssLoaded();
232
+ provideFullBundle();
233
+ provideLauncherBundle();
234
+
235
+ const variant = v5Defaults ? "pill" : "circle";
236
+ await install({
237
+ config: { apiUrl: "/api", launcher: { variant } },
238
+ future: { v5Defaults },
239
+ });
240
+ await flush();
241
+
242
+ expect(launcherMount).toHaveBeenCalledWith(expect.objectContaining({
243
+ config: expect.objectContaining({ future: { v5Defaults }, launcher: expect.objectContaining({ variant }) }),
244
+ }));
245
+
246
+ capturedOnOpen!();
247
+ await flush();
248
+
249
+ expect(initAgentWidget).toHaveBeenCalledWith(expect.objectContaining({
250
+ config: expect.objectContaining({ future: { v5Defaults }, launcher: expect.objectContaining({ variant }) }),
251
+ }));
252
+ });
229
253
  });
230
254
 
231
255
  describe("install.ts: eager path", () => {
package/src/install.ts CHANGED
@@ -19,6 +19,8 @@ interface SiteAgentInstallConfig {
19
19
  jsUrl?: string;
20
20
  target?: string | HTMLElement;
21
21
  config?: any;
22
+ /** Opt in to the staged v5 defaults when using top-level installer options. */
23
+ future?: { v5Defaults?: boolean };
22
24
  autoInit?: boolean;
23
25
  // Client token mode options (can also be set via data attributes)
24
26
  clientToken?: string;
@@ -447,6 +449,7 @@ declare global {
447
449
  if (config.clientToken && !widgetConfig.clientToken) widgetConfig.clientToken = config.clientToken;
448
450
  if (config.flowId && !widgetConfig.flowId) widgetConfig.flowId = config.flowId;
449
451
  if (config.agentId && !widgetConfig.agentId) widgetConfig.agentId = config.agentId;
452
+ if (config.future && !widgetConfig.future) widgetConfig.future = config.future;
450
453
 
451
454
  const hasApiConfig = !!(widgetConfig.apiUrl || widgetConfig.clientToken);
452
455
  return { target, widgetConfig, hasApiConfig };
@@ -631,4 +634,3 @@ declare global {
631
634
  // This prevents Next.js/Nuxt/etc. from removing dynamically added CSS
632
635
  waitForHydration(install);
633
636
  })();
634
-
@@ -22,7 +22,7 @@
22
22
  */
23
23
  import { createLauncherSurface } from "./components/launcher";
24
24
  import { applyThemeVariables } from "./utils/theme";
25
- import { DEFAULT_LAUNCHER_CONFIG } from "./defaults";
25
+ import { DEFAULT_LAUNCHER_CONFIG, DEFAULT_LAUNCHER_V5 } from "./defaults";
26
26
  import { setIconsExtraLoader } from "./icons-extra-loader";
27
27
  import type { AgentWidgetConfig } from "./types";
28
28
 
@@ -94,6 +94,7 @@ const mergeCriticalLauncherConfig = (
94
94
  ...config,
95
95
  launcher: {
96
96
  ...DEFAULT_LAUNCHER_CONFIG,
97
+ ...(config?.future?.v5Defaults === true ? DEFAULT_LAUNCHER_V5 : {}),
97
98
  ...config?.launcher,
98
99
  dock: {
99
100
  ...DEFAULT_LAUNCHER_CONFIG.dock,