@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,326 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import {
3
+ applyFeaturePreferences,
4
+ mergeFeaturePreferences,
5
+ parseWidgetPreferenceSlice,
6
+ resolveConfigPreferences,
7
+ } from "./feature-preferences";
8
+ import type { AgentWidgetConfig, WidgetPreferenceSlice } from "../types";
9
+
10
+ describe("feature preferences", () => {
11
+ test("merges object display rules by selector key across layers", () => {
12
+ expect(
13
+ mergeFeaturePreferences([
14
+ {
15
+ showToolCalls: false,
16
+ artifacts: {
17
+ display: {
18
+ default: "collapsed",
19
+ byKind: { markdown: "panel" },
20
+ files: { byMediaType: { "text/html": "inline" } },
21
+ },
22
+ },
23
+ },
24
+ {
25
+ showToolCalls: undefined,
26
+ artifacts: {
27
+ display: {
28
+ files: { default: "inline" },
29
+ byKind: { component: "collapsed" },
30
+ },
31
+ },
32
+ },
33
+ ])
34
+ ).toEqual({
35
+ showToolCalls: false,
36
+ artifacts: {
37
+ display: {
38
+ default: "collapsed",
39
+ byKind: { markdown: "panel", component: "collapsed" },
40
+ files: {
41
+ default: "inline",
42
+ byMediaType: { "text/html": "inline" },
43
+ },
44
+ },
45
+ },
46
+ });
47
+ });
48
+
49
+ test("a later-layer string replaces the whole subtree it names", () => {
50
+ expect(
51
+ mergeFeaturePreferences([
52
+ {
53
+ artifacts: {
54
+ display: {
55
+ default: "collapsed",
56
+ files: { byMediaType: { "text/csv": "panel" } },
57
+ },
58
+ },
59
+ },
60
+ { artifacts: { display: { files: "inline" } } },
61
+ ])
62
+ ).toEqual({
63
+ artifacts: {
64
+ display: { default: "collapsed", files: "inline" },
65
+ },
66
+ });
67
+ expect(
68
+ mergeFeaturePreferences([
69
+ { artifacts: { display: { default: "collapsed", files: "panel" } } },
70
+ { artifacts: { display: "inline" } },
71
+ ])
72
+ ).toEqual({
73
+ artifacts: { display: "inline" },
74
+ });
75
+ });
76
+
77
+ test("canonicalizes the deprecated 'card' mode alias in stored preferences", () => {
78
+ expect(
79
+ parseWidgetPreferenceSlice({
80
+ artifacts: {
81
+ display: {
82
+ default: "card",
83
+ files: { byMediaType: { "text/csv": "card" } },
84
+ byKind: { component: "card" },
85
+ },
86
+ },
87
+ }).preferences
88
+ ).toEqual({
89
+ artifacts: {
90
+ display: {
91
+ default: "collapsed",
92
+ files: { byMediaType: { "text/csv": "collapsed" } },
93
+ byKind: { component: "collapsed" },
94
+ },
95
+ },
96
+ });
97
+ });
98
+
99
+ test("normalizes the deprecated byType selector to byKind", () => {
100
+ expect(
101
+ parseWidgetPreferenceSlice({
102
+ artifacts: {
103
+ display: {
104
+ byType: { markdown: "collapsed", component: "panel" },
105
+ byKind: { component: "inline" },
106
+ },
107
+ },
108
+ }).preferences
109
+ ).toEqual({
110
+ artifacts: {
111
+ display: {
112
+ byKind: { markdown: "collapsed", component: "inline" },
113
+ },
114
+ },
115
+ });
116
+ });
117
+
118
+ test("does not mutate inputs and ignores prototype-pollution keys", () => {
119
+ const base = {
120
+ artifacts: {
121
+ display: {
122
+ byKind: { markdown: "collapsed" as const },
123
+ },
124
+ },
125
+ };
126
+ const unsafe = JSON.parse(
127
+ '{"artifacts":{"display":{"byKind":{"component":"inline"},"__proto__":{"polluted":true}}},"constructor":{"prototype":{"polluted":true}}}'
128
+ );
129
+
130
+ const merged = mergeFeaturePreferences([base, unsafe]);
131
+
132
+ expect(merged).toEqual({
133
+ artifacts: {
134
+ display: {
135
+ byKind: {
136
+ markdown: "collapsed",
137
+ component: "inline",
138
+ },
139
+ },
140
+ },
141
+ });
142
+ expect(base).toEqual({
143
+ artifacts: {
144
+ display: {
145
+ byKind: { markdown: "collapsed" },
146
+ },
147
+ },
148
+ });
149
+ expect(({} as { polluted?: boolean }).polluted).toBeUndefined();
150
+ });
151
+
152
+ test("allows only curated persisted fields at the runtime boundary", () => {
153
+ const storedJson: unknown = {
154
+ artifacts: {
155
+ enabled: false,
156
+ allowedTypes: [],
157
+ display: {
158
+ default: "sideways",
159
+ files: "inline",
160
+ unknownSelector: "panel",
161
+ },
162
+ filePreview: {
163
+ enabled: false,
164
+ iframeSandbox: "allow-same-origin allow-scripts",
165
+ dangerouslyAllowSameOrigin: true,
166
+ },
167
+ },
168
+ unknownRoot: true,
169
+ };
170
+ const parsed = parseWidgetPreferenceSlice(storedJson);
171
+ expect(parsed.preferences).toEqual({
172
+ artifacts: {
173
+ display: { files: "inline" },
174
+ filePreview: { enabled: false },
175
+ },
176
+ });
177
+ expect(parsed.issues.map((entry) => entry.path)).toEqual(
178
+ expect.arrayContaining([
179
+ "artifacts.enabled",
180
+ "artifacts.allowedTypes",
181
+ "artifacts.display.default",
182
+ "artifacts.display.unknownSelector",
183
+ "artifacts.filePreview.iframeSandbox",
184
+ "artifacts.filePreview.dangerouslyAllowSameOrigin",
185
+ "unknownRoot",
186
+ ])
187
+ );
188
+
189
+ const result = applyFeaturePreferences(
190
+ {
191
+ artifacts: {
192
+ enabled: true,
193
+ allowedTypes: ["markdown"],
194
+ filePreview: {
195
+ iframeSandbox: "allow-scripts",
196
+ dangerouslyAllowSameOrigin: false,
197
+ },
198
+ },
199
+ },
200
+ [storedJson as WidgetPreferenceSlice]
201
+ );
202
+ expect(result.artifacts).toMatchObject({
203
+ enabled: true,
204
+ allowedTypes: ["markdown"],
205
+ display: { files: "inline" },
206
+ filePreview: {
207
+ enabled: false,
208
+ iframeSandbox: "allow-scripts",
209
+ dangerouslyAllowSameOrigin: false,
210
+ },
211
+ });
212
+ });
213
+
214
+ test("preserves base callbacks and the default from a string display config", () => {
215
+ const onArtifactAction = () => true;
216
+ const renderInline = () => document.createElement("div");
217
+ const toolbarAction = {
218
+ id: "export",
219
+ label: "Export",
220
+ onClick: () => undefined,
221
+ };
222
+ const result = applyFeaturePreferences(
223
+ {
224
+ artifacts: {
225
+ display: "collapsed",
226
+ onArtifactAction,
227
+ renderInline,
228
+ toolbarActions: [toolbarAction],
229
+ },
230
+ },
231
+ [{ artifacts: { display: { files: "inline" } } }]
232
+ );
233
+ expect(result.artifacts).toMatchObject({
234
+ display: { default: "collapsed", files: "inline" },
235
+ onArtifactAction,
236
+ renderInline,
237
+ toolbarActions: [toolbarAction],
238
+ });
239
+ });
240
+
241
+ test("rejects invalid media-type selectors but accepts wildcards", () => {
242
+ const parsed = parseWidgetPreferenceSlice({
243
+ artifacts: {
244
+ display: {
245
+ files: {
246
+ byMediaType: {
247
+ "image/*": "inline",
248
+ "*/*": "panel",
249
+ "not a mime": "collapsed",
250
+ },
251
+ },
252
+ },
253
+ },
254
+ });
255
+ expect(parsed.preferences).toEqual({
256
+ artifacts: {
257
+ display: { files: { byMediaType: { "image/*": "inline" } } },
258
+ },
259
+ });
260
+ expect(parsed.issues.map((entry) => entry.path)).toEqual([
261
+ "artifacts.display.files.byMediaType.*/*",
262
+ "artifacts.display.files.byMediaType.not a mime",
263
+ ]);
264
+ });
265
+ });
266
+
267
+ describe("resolveConfigPreferences", () => {
268
+ test("returns the config unchanged when preferences are absent or empty", () => {
269
+ const config: AgentWidgetConfig = {
270
+ apiUrl: "https://api.example.com/chat",
271
+ features: { artifacts: { enabled: true, display: "panel" } },
272
+ };
273
+ expect(resolveConfigPreferences(config)).toBe(config);
274
+ const withEmpty = { ...config, preferences: {} };
275
+ expect(resolveConfigPreferences(withEmpty)).toBe(withEmpty);
276
+ });
277
+
278
+ test("bakes preferences into features and keeps other config keys", () => {
279
+ const resolved = resolveConfigPreferences({
280
+ apiUrl: "https://api.example.com/chat",
281
+ features: {
282
+ artifacts: {
283
+ enabled: true,
284
+ display: { default: "panel", byKind: { component: "collapsed" } },
285
+ },
286
+ },
287
+ preferences: { artifacts: { display: { default: "inline" } } },
288
+ });
289
+ expect(resolved.apiUrl).toBe("https://api.example.com/chat");
290
+ expect(resolved.features?.artifacts?.enabled).toBe(true);
291
+ expect(resolved.features?.artifacts?.display).toEqual({
292
+ default: "inline",
293
+ byKind: { component: "collapsed" },
294
+ });
295
+ });
296
+
297
+ test("a string display preference replaces the base rule subtree", () => {
298
+ const resolved = resolveConfigPreferences({
299
+ features: {
300
+ artifacts: {
301
+ display: { default: "panel", byKind: { markdown: "collapsed" } },
302
+ },
303
+ },
304
+ preferences: { artifacts: { display: "inline" } },
305
+ });
306
+ expect(resolved.features?.artifacts?.display).toBe("inline");
307
+ });
308
+
309
+ test("preferences cannot carry keys outside the allowlist", () => {
310
+ const resolved = resolveConfigPreferences({
311
+ features: {
312
+ artifacts: { enabled: true, filePreview: { iframeSandbox: "allow-scripts" } },
313
+ },
314
+ preferences: {
315
+ artifacts: {
316
+ display: "inline",
317
+ filePreview: { iframeSandbox: "allow-same-origin" },
318
+ },
319
+ } as unknown as WidgetPreferenceSlice,
320
+ });
321
+ expect(resolved.features?.artifacts?.filePreview?.iframeSandbox).toBe(
322
+ "allow-scripts"
323
+ );
324
+ expect(resolved.features?.artifacts?.display).toBe("inline");
325
+ });
326
+ });