@runtypelabs/persona 4.7.0 → 4.9.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 (185) hide show
  1. package/README.md +1 -0
  2. package/dist/animations/glyph-cycle.cjs +2 -2
  3. package/dist/animations/glyph-cycle.d.cts +1 -1
  4. package/dist/animations/glyph-cycle.d.ts +1 -1
  5. package/dist/animations/glyph-cycle.js +2 -2
  6. package/dist/animations/{types-CSmiKRVa.d.cts → types-4ROVJ1gA.d.cts} +85 -3
  7. package/dist/animations/{types-CSmiKRVa.d.ts → types-4ROVJ1gA.d.ts} +85 -3
  8. package/dist/animations/wipe.cjs +2 -2
  9. package/dist/animations/wipe.d.cts +1 -1
  10. package/dist/animations/wipe.d.ts +1 -1
  11. package/dist/chunk-5EIIHQLQ.js +1 -0
  12. package/dist/chunk-IO5VVUKP.js +3 -0
  13. package/dist/chunk-IPVK3KOM.js +1 -0
  14. package/dist/chunk-UPO4GUFC.js +1 -0
  15. package/dist/codegen.cjs +13 -13
  16. package/dist/codegen.js +14 -14
  17. package/dist/context-mentions-7S5KVUTG.js +169 -0
  18. package/dist/context-mentions-inline-TTCN7ZM2.js +4 -0
  19. package/dist/context-mentions-inline.cjs +4 -0
  20. package/dist/context-mentions-inline.d.cts +203 -0
  21. package/dist/context-mentions-inline.d.ts +203 -0
  22. package/dist/context-mentions-inline.js +4 -0
  23. package/dist/context-mentions.cjs +295 -0
  24. package/dist/context-mentions.d.cts +7025 -0
  25. package/dist/context-mentions.d.ts +7025 -0
  26. package/dist/context-mentions.js +295 -0
  27. package/dist/index.cjs +99 -68
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.cts +1507 -182
  30. package/dist/index.d.ts +1507 -182
  31. package/dist/index.global.js +88 -56
  32. package/dist/index.global.js.map +1 -1
  33. package/dist/index.js +99 -68
  34. package/dist/index.js.map +1 -1
  35. package/dist/install.global.js +1 -1
  36. package/dist/install.global.js.map +1 -1
  37. package/dist/launcher.global.js +3 -2
  38. package/dist/launcher.global.js.map +1 -1
  39. package/dist/markdown-parsers.js +24 -24
  40. package/dist/plugin-kit.cjs +1 -1
  41. package/dist/plugin-kit.d.cts +17 -0
  42. package/dist/plugin-kit.d.ts +17 -0
  43. package/dist/plugin-kit.js +1 -1
  44. package/dist/runtype-tts-entry-UJAEF7NZ.js +1 -0
  45. package/dist/runtype-tts.js +1 -1
  46. package/dist/session-reconnect-JKIJBHS5.js +1 -0
  47. package/dist/smart-dom-reader.cjs +18 -17
  48. package/dist/smart-dom-reader.d.cts +1260 -16
  49. package/dist/smart-dom-reader.d.ts +1260 -16
  50. package/dist/smart-dom-reader.js +18 -17
  51. package/dist/testing.cjs +3 -3
  52. package/dist/testing.js +3 -3
  53. package/dist/theme-editor-preview.cjs +260 -58
  54. package/dist/theme-editor-preview.d.cts +1234 -15
  55. package/dist/theme-editor-preview.d.ts +1234 -15
  56. package/dist/theme-editor-preview.js +87 -58
  57. package/dist/theme-editor.cjs +10 -10
  58. package/dist/theme-editor.d.cts +1226 -15
  59. package/dist/theme-editor.d.ts +1226 -15
  60. package/dist/theme-editor.js +10 -10
  61. package/dist/theme-reference.cjs +1 -1
  62. package/dist/theme-reference.d.cts +76 -0
  63. package/dist/theme-reference.d.ts +76 -0
  64. package/dist/theme-reference.js +1 -1
  65. package/dist/voice-worklet-player.cjs +2 -2
  66. package/dist/voice-worklet-player.js +2 -2
  67. package/dist/webmcp-polyfill.js +2 -2
  68. package/dist/widget.css +1 -1
  69. package/package.json +16 -5
  70. package/src/artifacts-session.test.ts +178 -0
  71. package/src/client.test.ts +255 -1
  72. package/src/client.ts +157 -60
  73. package/src/components/artifact-card.test.ts +333 -0
  74. package/src/components/artifact-card.ts +75 -28
  75. package/src/components/artifact-inline.test.ts +1328 -0
  76. package/src/components/artifact-inline.ts +920 -0
  77. package/src/components/artifact-pane.test.ts +1042 -0
  78. package/src/components/artifact-pane.ts +440 -131
  79. package/src/components/artifact-preview.test.ts +1155 -0
  80. package/src/components/artifact-preview.ts +994 -0
  81. package/src/components/composer-parts.ts +3 -12
  82. package/src/components/context-mention-button.test.ts +70 -0
  83. package/src/components/context-mention-button.ts +82 -0
  84. package/src/components/context-mention-chip.ts +134 -0
  85. package/src/components/context-mention-menu.test.ts +508 -0
  86. package/src/components/context-mention-menu.ts +0 -0
  87. package/src/components/message-bubble.test.ts +175 -0
  88. package/src/components/message-bubble.ts +177 -19
  89. package/src/components/panel.ts +7 -10
  90. package/src/components/pill-composer-builder.test.ts +6 -2
  91. package/src/components/pill-composer-builder.ts +6 -6
  92. package/src/components/reasoning-bubble.ts +1 -13
  93. package/src/components/registry.ts +38 -3
  94. package/src/components/tool-bubble.ts +1 -13
  95. package/src/context-mentions-bundle.test.ts +163 -0
  96. package/src/context-mentions-entry.ts +185 -0
  97. package/src/context-mentions-inline-entry.test.ts +136 -0
  98. package/src/context-mentions-inline-entry.ts +226 -0
  99. package/src/context-mentions-inline-loader.test.ts +30 -0
  100. package/src/context-mentions-inline-loader.ts +36 -0
  101. package/src/context-mentions-inline.ts +15 -0
  102. package/src/context-mentions-loader.ts +32 -0
  103. package/src/context-mentions.ts +16 -0
  104. package/src/defaults.ts +1 -0
  105. package/src/generated/runtype-openapi-contract.ts +55 -3
  106. package/src/index-core.ts +46 -1
  107. package/src/index-global.ts +51 -0
  108. package/src/index.ts +8 -0
  109. package/src/markdown-parsers-loader.test.ts +158 -0
  110. package/src/markdown-parsers-loader.ts +83 -27
  111. package/src/plugin-kit.test.ts +40 -0
  112. package/src/plugin-kit.ts +39 -5
  113. package/src/runtime/host-layout.test.ts +163 -0
  114. package/src/runtime/host-layout.ts +110 -7
  115. package/src/runtime/init.ts +18 -61
  116. package/src/session.mentions.test.ts +175 -0
  117. package/src/session.test.ts +52 -4
  118. package/src/session.ts +197 -27
  119. package/src/smart-dom-reader.test.ts +129 -2
  120. package/src/smart-dom-reader.ts +127 -1
  121. package/src/styles/context-mention-menu-css.ts +176 -0
  122. package/src/styles/widget.css +1002 -152
  123. package/src/theme-editor/preview.ts +2 -0
  124. package/src/theme-editor/sections.test.ts +26 -1
  125. package/src/theme-editor/sections.ts +13 -5
  126. package/src/theme-reference.ts +2 -2
  127. package/src/tool-call-display-defaults.test.ts +1 -0
  128. package/src/types/theme.ts +79 -0
  129. package/src/types.ts +1041 -17
  130. package/src/ui.artifact-pane-gating.test.ts +636 -0
  131. package/src/ui.component-directive.test.ts +104 -0
  132. package/src/ui.composer-bar.test.ts +60 -2
  133. package/src/ui.detached-panel.test.ts +1049 -0
  134. package/src/ui.mention-submit.test.ts +235 -0
  135. package/src/ui.tool-display.test.ts +51 -0
  136. package/src/ui.ts +998 -159
  137. package/src/utils/artifact-custom-actions.ts +128 -0
  138. package/src/utils/artifact-display.test.ts +42 -0
  139. package/src/utils/artifact-display.ts +84 -0
  140. package/src/utils/artifact-file.test.ts +116 -0
  141. package/src/utils/artifact-file.ts +117 -0
  142. package/src/utils/artifact-gate.test.ts +112 -5
  143. package/src/utils/artifact-gate.ts +39 -14
  144. package/src/utils/artifact-loading-status.ts +55 -0
  145. package/src/utils/artifact-status-label.ts +190 -0
  146. package/src/utils/buttons.ts +7 -1
  147. package/src/utils/chunk-loader.test.ts +97 -0
  148. package/src/utils/chunk-loader.ts +88 -0
  149. package/src/utils/code-highlight.test.ts +186 -0
  150. package/src/utils/code-highlight.ts +400 -0
  151. package/src/utils/composer-contenteditable.test.ts +507 -0
  152. package/src/utils/composer-contenteditable.ts +626 -0
  153. package/src/utils/composer-document.test.ts +280 -0
  154. package/src/utils/composer-document.ts +293 -0
  155. package/src/utils/composer-history.test.ts +35 -7
  156. package/src/utils/composer-history.ts +30 -20
  157. package/src/utils/composer-input.ts +159 -0
  158. package/src/utils/context-mention-controller.test.ts +1215 -0
  159. package/src/utils/context-mention-controller.ts +1186 -0
  160. package/src/utils/context-mention-manager.test.ts +422 -0
  161. package/src/utils/context-mention-manager.ts +410 -0
  162. package/src/utils/context-mention-orchestrator.test.ts +538 -0
  163. package/src/utils/context-mention-orchestrator.ts +348 -0
  164. package/src/utils/icons.ts +2 -0
  165. package/src/utils/live-region.test.ts +108 -0
  166. package/src/utils/live-region.ts +94 -0
  167. package/src/utils/mention-channels.ts +63 -0
  168. package/src/utils/mention-llm-format.test.ts +91 -0
  169. package/src/utils/mention-llm-format.ts +79 -0
  170. package/src/utils/mention-matcher.test.ts +86 -0
  171. package/src/utils/mention-matcher.ts +221 -0
  172. package/src/utils/mention-token.ts +72 -0
  173. package/src/utils/mention-trigger.test.ts +155 -0
  174. package/src/utils/mention-trigger.ts +156 -0
  175. package/src/utils/roving-tablist.test.ts +152 -0
  176. package/src/utils/roving-tablist.ts +111 -0
  177. package/src/utils/spinner.ts +45 -0
  178. package/src/utils/theme.test.ts +76 -4
  179. package/src/utils/theme.ts +7 -0
  180. package/src/utils/tokens.ts +118 -11
  181. package/src/utils/tool-loading-animation.test.ts +32 -0
  182. package/src/utils/tool-loading-animation.ts +24 -0
  183. package/dist/chunk-DFBSCFYN.js +0 -1
  184. package/dist/runtype-tts-entry-HFUV2UF7.js +0 -1
  185. package/dist/session-reconnect-U77QFUR7.js +0 -1
@@ -0,0 +1,235 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
4
+
5
+ import {
6
+ createAgentExperience,
7
+ mergeMentionContext,
8
+ mergeFinalizedMentions,
9
+ } from "./ui";
10
+ import { createStaticMentionSource } from "./utils/mention-matcher";
11
+ import { loadContextMentions } from "./context-mentions-loader";
12
+ import type { MentionSubmitBundle } from "./utils/context-mention-manager";
13
+
14
+ const createMount = () => {
15
+ const mount = document.createElement("div");
16
+ document.body.appendChild(mount);
17
+ return mount;
18
+ };
19
+
20
+ const flush = async (times = 8) => {
21
+ for (let i = 0; i < times; i += 1) {
22
+ // eslint-disable-next-line no-await-in-loop
23
+ await Promise.resolve();
24
+ }
25
+ };
26
+
27
+ // Deterministically flush the lazy mention chunk load → mount → open chain by
28
+ // awaiting the same memoized loader promise the orchestrator uses, then pumping
29
+ // a few macrotasks for the debounced menu search.
30
+ const flushMentions = async () => {
31
+ await loadContextMentions().catch(() => {});
32
+ for (let i = 0; i < 8; i += 1) {
33
+ // eslint-disable-next-line no-await-in-loop
34
+ await new Promise((r) => setTimeout(r, 0));
35
+ }
36
+ };
37
+
38
+ const getTextarea = (mount: HTMLElement) =>
39
+ mount.querySelector<HTMLTextAreaElement>("[data-persona-composer-input]")!;
40
+
41
+ const getSubmit = (mount: HTMLElement) =>
42
+ mount.querySelector<HTMLButtonElement>("[data-persona-composer-submit]")!;
43
+
44
+ const press = (el: Element, key: string) =>
45
+ el.dispatchEvent(
46
+ new KeyboardEvent("keydown", { key, bubbles: true, cancelable: true })
47
+ );
48
+
49
+ // --- Bug 3: deep cross-bundle context merge (pure helpers) -----------------
50
+ describe("mergeMentionContext / mergeFinalizedMentions", () => {
51
+ it("deep-merges per-item maps for a source present in both bundles", () => {
52
+ const a = { files: { app: "a-body" }, docs: { r: 1 } };
53
+ const b = { files: { readme: "b-body" }, links: { x: 2 } };
54
+ const merged = mergeMentionContext(a, b);
55
+ // The shared `files` source keeps BOTH items (shallow spread would drop `app`).
56
+ expect(merged.files).toEqual({ app: "a-body", readme: "b-body" });
57
+ expect(merged.docs).toEqual({ r: 1 });
58
+ expect(merged.links).toEqual({ x: 2 });
59
+ });
60
+
61
+ it("b wins on a genuine per-item collision", () => {
62
+ const merged = mergeMentionContext(
63
+ { files: { app: "old" } },
64
+ { files: { app: "new" } }
65
+ );
66
+ expect(merged.files).toEqual({ app: "new" });
67
+ });
68
+
69
+ it("concatenates blocks/contentParts and deep-merges context", () => {
70
+ const ra: MentionSubmitBundle = {
71
+ blocks: ["A"],
72
+ contentParts: [],
73
+ context: { files: { app: "a" } },
74
+ };
75
+ const rb: MentionSubmitBundle = {
76
+ blocks: ["B"],
77
+ contentParts: [],
78
+ context: { files: { readme: "b" } },
79
+ };
80
+ const out = mergeFinalizedMentions([ra, rb]);
81
+ expect(out.blocks).toEqual(["A", "B"]);
82
+ expect(out.context.files).toEqual({ app: "a", readme: "b" });
83
+ });
84
+
85
+ it("returns an empty bundle when nothing is fulfilled", () => {
86
+ expect(mergeFinalizedMentions([])).toEqual({
87
+ blocks: [],
88
+ contentParts: [],
89
+ context: {},
90
+ });
91
+ });
92
+ });
93
+
94
+ // --- Bug 1: double-submit re-entrancy guard --------------------------------
95
+ describe("createAgentExperience: double-submit guard", () => {
96
+ const originalFetch = global.fetch;
97
+ let fetchCalls = 0;
98
+
99
+ beforeEach(() => {
100
+ fetchCalls = 0;
101
+ window.scrollTo = vi.fn();
102
+ vi.stubGlobal("requestAnimationFrame", (cb: (t: number) => void) => {
103
+ cb(0);
104
+ return 1;
105
+ });
106
+ vi.stubGlobal("cancelAnimationFrame", () => {});
107
+ // Fetch hangs (models an in-flight SSE stream) and counts invocations.
108
+ global.fetch = vi.fn().mockImplementation((_url: string, options: any) => {
109
+ fetchCalls += 1;
110
+ const signal = options.signal as AbortSignal;
111
+ return new Promise((_resolve, reject) => {
112
+ signal.addEventListener("abort", () => {
113
+ const err = new Error("aborted");
114
+ err.name = "AbortError";
115
+ reject(err);
116
+ });
117
+ });
118
+ }) as unknown as typeof fetch;
119
+ });
120
+
121
+ afterEach(() => {
122
+ document.body.innerHTML = "";
123
+ global.fetch = originalFetch;
124
+ vi.restoreAllMocks();
125
+ });
126
+
127
+ it("two synchronous submits dispatch the message only once", async () => {
128
+ const mount = createMount();
129
+ const controller = createAgentExperience(mount, {
130
+ apiUrl: "https://api.example.com/chat",
131
+ launcher: { enabled: false },
132
+ });
133
+
134
+ const textarea = getTextarea(mount);
135
+ textarea.value = "hello";
136
+
137
+ // Both clicks land inside the async pre-send window (isStreaming() is still
138
+ // false); the guard must swallow the second.
139
+ getSubmit(mount).click();
140
+ getSubmit(mount).click();
141
+ await flush();
142
+
143
+ expect(fetchCalls).toBe(1);
144
+ controller.destroy();
145
+ });
146
+ });
147
+
148
+ // --- Bug 4: history recall must not open the mention menu ------------------
149
+ describe("createAgentExperience: history recall does not open the mention menu", () => {
150
+ beforeEach(() => {
151
+ window.scrollTo = vi.fn();
152
+ try {
153
+ window.localStorage.clear();
154
+ } catch {
155
+ /* jsdom edge cases */
156
+ }
157
+ });
158
+
159
+ afterEach(() => {
160
+ document.body.innerHTML = "";
161
+ vi.restoreAllMocks();
162
+ });
163
+
164
+ const menuOpen = () =>
165
+ document.querySelector('[role="listbox"]') !== null ||
166
+ !!document
167
+ .querySelector("[aria-haspopup='listbox']")
168
+ ?.getAttribute("aria-expanded")
169
+ ?.includes("true");
170
+
171
+ it("recalling a message ending in '@word' keeps the menu closed and ArrowUp still navigates", async () => {
172
+ const mount = createMount();
173
+ const controller = createAgentExperience(mount, {
174
+ apiUrl: "https://api.example.com/chat",
175
+ launcher: { enabled: false },
176
+ contextMentions: {
177
+ enabled: true,
178
+ sources: [
179
+ createStaticMentionSource({
180
+ id: "files",
181
+ label: "Files",
182
+ items: [{ id: "app", label: "app" }],
183
+ resolve: (i: { label: string }) => ({ llmAppend: `body of ${i.label}` }),
184
+ }),
185
+ ],
186
+ },
187
+ initialMessages: [
188
+ {
189
+ id: "u1",
190
+ role: "user",
191
+ content: "first note",
192
+ createdAt: "2026-01-01T00:00:00.000Z",
193
+ },
194
+ {
195
+ id: "u2",
196
+ role: "user",
197
+ content: "second @app",
198
+ createdAt: "2026-01-01T00:00:02.000Z",
199
+ },
200
+ ],
201
+ });
202
+
203
+ const textarea = getTextarea(mount);
204
+
205
+ // Warm the lazy mention engine by opening the picker from its affordance
206
+ // button, so `engine` is live for the recall step below (a loaded engine is
207
+ // exactly the state in which the bug reopened the menu on synthetic input).
208
+ mount.querySelector<HTMLButtonElement>("[aria-haspopup='listbox']")!.click();
209
+ await flushMentions();
210
+ // If the harness can't drive the async menu open, this scenario can't be
211
+ // expressed; bail loudly rather than asserting a vacuous pass.
212
+ expect(menuOpen()).toBe(true);
213
+
214
+ // Close the menu, back to an empty composer.
215
+ press(textarea, "Escape");
216
+ await flush();
217
+ expect(menuOpen()).toBe(false);
218
+
219
+ // Recall the newest user message (ends in '@app'): its synthetic input must
220
+ // NOT reopen the menu (engine.handleInput would otherwise fire synchronously).
221
+ textarea.setSelectionRange(0, 0);
222
+ press(textarea, "ArrowUp");
223
+ await flush();
224
+ expect(textarea.value).toBe("second @app");
225
+ expect(menuOpen()).toBe(false);
226
+
227
+ // Because the menu stayed closed, ArrowUp is still owned by history nav.
228
+ textarea.setSelectionRange(0, 0);
229
+ press(textarea, "ArrowUp");
230
+ await flush();
231
+ expect(textarea.value).toBe("first note");
232
+
233
+ controller.destroy();
234
+ });
235
+ });
@@ -201,4 +201,55 @@ describe("createAgentExperience tool call display modes", () => {
201
201
 
202
202
  controller.destroy();
203
203
  });
204
+
205
+ it("keeps tool calls grouped across reasoning that is hidden", () => {
206
+ const mount = createMount();
207
+ const controller = createAgentExperience(mount, {
208
+ apiUrl: "https://api.example.com/chat",
209
+ launcher: { enabled: false },
210
+ features: {
211
+ showReasoning: false,
212
+ toolCallDisplay: {
213
+ grouped: true,
214
+ },
215
+ },
216
+ } as any);
217
+
218
+ injectToolMessage(controller, { id: "tool-1", name: "Inspect data" });
219
+ injectReasoningMessage(controller, { id: "reason-1", chunks: ["Choosing a query"] });
220
+ injectToolMessage(controller, { id: "tool-2", name: "Run analysis" });
221
+
222
+ const group = mount.querySelector("[data-persona-tool-group='true']");
223
+ expect(group).not.toBeNull();
224
+ expect(group?.querySelectorAll("[data-persona-tool-group-item]")).toHaveLength(2);
225
+
226
+ controller.destroy();
227
+ });
228
+
229
+ it("can render a grouped tool sequence as one summary item", () => {
230
+ const mount = createMount();
231
+ const controller = createAgentExperience(mount, {
232
+ apiUrl: "https://api.example.com/chat",
233
+ launcher: { enabled: false },
234
+ features: {
235
+ toolCallDisplay: {
236
+ grouped: true,
237
+ groupedMode: "summary",
238
+ },
239
+ },
240
+ toolCall: {
241
+ renderGroupedSummary: () => "Analysis ready",
242
+ },
243
+ } as any);
244
+
245
+ injectToolMessage(controller, { id: "tool-1", name: "Inspect data", status: "complete" });
246
+ injectToolMessage(controller, { id: "tool-2", name: "Run analysis", status: "complete" });
247
+
248
+ const group = mount.querySelector("[data-persona-tool-group='true']");
249
+ expect(group?.textContent).toContain("Analysis ready");
250
+ expect(group?.querySelector("[data-persona-tool-group-item]")).toBeNull();
251
+ expect(mount.querySelector(".persona-tool-bubble")).toBeNull();
252
+
253
+ controller.destroy();
254
+ });
204
255
  });