@runtypelabs/persona 4.30.0 → 4.32.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 (139) hide show
  1. package/README.md +22 -1
  2. package/dist/activity-ui.cjs +2 -2
  3. package/dist/activity-ui.d.cts +11 -3
  4. package/dist/activity-ui.d.ts +11 -3
  5. package/dist/activity-ui.js +1 -1
  6. package/dist/animations/glyph-cycle.d.cts +1 -1
  7. package/dist/animations/glyph-cycle.d.ts +1 -1
  8. package/dist/animations/wipe.d.cts +1 -1
  9. package/dist/animations/wipe.d.ts +1 -1
  10. package/dist/approval-ui.d.cts +2 -2
  11. package/dist/approval-ui.d.ts +2 -2
  12. package/dist/artifacts-ui.d.cts +3 -3
  13. package/dist/artifacts-ui.d.ts +3 -3
  14. package/dist/background-runtime.d.cts +1 -1
  15. package/dist/background-runtime.d.ts +1 -1
  16. package/dist/chunk-C2IbR39s.js +1 -0
  17. package/dist/chunk-CkE90FtA.js +2 -0
  18. package/dist/{chunk-n96BB0YH.js → chunk-CzZBmTLd.js} +1 -1
  19. package/dist/chunk-DABqu7C3.js +2 -0
  20. package/dist/chunk-DDAwu89-.js +1 -0
  21. package/dist/{chunk-CqEWCkJt.js → chunk-DZg9bFK6.js} +1 -1
  22. package/dist/chunk-DbD5qyBw.js +3 -0
  23. package/dist/{chunk-gayfbmEU.js → chunk-FEzOcZof.js} +1 -1
  24. package/dist/{chunk-UyZbbbz3.js → chunk-MdlQetfC.js} +2 -2
  25. package/dist/client-dispatch.js +1 -1
  26. package/dist/client-history.js +1 -1
  27. package/dist/codegen.cjs +1 -1
  28. package/dist/codegen.js +1 -1
  29. package/dist/composer-actions-runtime.d.cts +2 -2
  30. package/dist/composer-actions-runtime.d.ts +2 -2
  31. package/dist/{composer-input-C76gfIoK.d.cts → composer-input-dwg_o9Z-.d.cts} +1 -1
  32. package/dist/{composer-input-C76gfIoK.d.ts → composer-input-dwg_o9Z-.d.ts} +1 -1
  33. package/dist/{composer-store-C7tvaz90.d.cts → composer-store-Dw7Zbqxr.d.cts} +1 -1
  34. package/dist/{composer-store-C7tvaz90.d.ts → composer-store-Dw7Zbqxr.d.ts} +1 -1
  35. package/dist/{context-mention-manager-IfT4JHma.d.cts → context-mention-manager-D2p0-JH-.d.cts} +2 -2
  36. package/dist/{context-mention-manager-IfT4JHma.d.ts → context-mention-manager-D2p0-JH-.d.ts} +2 -2
  37. package/dist/context-mentions-inline.d.cts +2 -2
  38. package/dist/context-mentions-inline.d.ts +2 -2
  39. package/dist/context-mentions.d.cts +3 -3
  40. package/dist/context-mentions.d.ts +3 -3
  41. package/dist/{defaults-Cc_p0PGM.d.cts → defaults-CjX0kKzb.d.cts} +1 -1
  42. package/dist/{defaults-Cc_p0PGM.d.ts → defaults-CjX0kKzb.d.ts} +1 -1
  43. package/dist/dictation-runtime.d.cts +2 -2
  44. package/dist/dictation-runtime.d.ts +2 -2
  45. package/dist/event-stream-view.d.cts +1 -1
  46. package/dist/event-stream-view.d.ts +1 -1
  47. package/dist/{fallback-speech-engine-CSxWpG0A.d.cts → fallback-speech-engine-DmF7MczI.d.cts} +1 -1
  48. package/dist/{fallback-speech-engine-CSxWpG0A.d.ts → fallback-speech-engine-DmF7MczI.d.ts} +1 -1
  49. package/dist/{feature-preferences-eeSOmDxg.d.cts → feature-preferences-CVFDt9Xf.d.cts} +1 -1
  50. package/dist/{feature-preferences-eeSOmDxg.d.ts → feature-preferences-CVFDt9Xf.d.ts} +1 -1
  51. package/dist/forms-ui.d.cts +2 -2
  52. package/dist/forms-ui.d.ts +2 -2
  53. package/dist/{history-confirm-dialog-DXkuAgh-.d.cts → history-confirm-dialog-DJEXsTyE.d.cts} +1 -1
  54. package/dist/{history-confirm-dialog-DXkuAgh-.d.ts → history-confirm-dialog-DJEXsTyE.d.ts} +1 -1
  55. package/dist/history-shell.d.cts +6 -6
  56. package/dist/history-shell.d.ts +6 -6
  57. package/dist/history-view.d.cts +1 -1
  58. package/dist/history-view.d.ts +1 -1
  59. package/dist/index.cjs +49 -47
  60. package/dist/index.cjs.map +1 -1
  61. package/dist/index.d.cts +9 -9
  62. package/dist/index.d.ts +9 -9
  63. package/dist/index.global.js +1 -1
  64. package/dist/index.global.js.map +1 -1
  65. package/dist/index.js +43 -41
  66. package/dist/index.js.map +1 -1
  67. package/dist/{loading-label-runtime-loader-C53YyPJ-.d.cts → loading-label-runtime-loader-CPxnoaoi.d.cts} +1 -1
  68. package/dist/{loading-label-runtime-loader-C53YyPJ-.d.ts → loading-label-runtime-loader-CPxnoaoi.d.ts} +1 -1
  69. package/dist/loading-label-runtime.d.cts +1 -1
  70. package/dist/loading-label-runtime.d.ts +1 -1
  71. package/dist/markdown-parsers.js +18 -15
  72. package/dist/{message-bubble-Cg_sTXUN.d.cts → message-bubble-CkZGlCLC.d.cts} +1 -1
  73. package/dist/{message-bubble-Cg_sTXUN.d.ts → message-bubble-CkZGlCLC.d.ts} +1 -1
  74. package/dist/preferences-runtime.d.cts +1 -1
  75. package/dist/preferences-runtime.d.ts +1 -1
  76. package/dist/{preview-BeMKALry.d.cts → preview-DdUvoLb4.d.cts} +2 -2
  77. package/dist/{preview-BeMKALry.d.ts → preview-DdUvoLb4.d.ts} +2 -2
  78. package/dist/{registry-BBe-Lesw.d.ts → registry-Dz4iHNST.d.cts} +3 -1
  79. package/dist/{registry-BBe-Lesw.d.cts → registry-Dz4iHNST.d.ts} +3 -1
  80. package/dist/{session-C0fSteA4.d.cts → session-Baxp9iRK.d.cts} +24 -9
  81. package/dist/{session-C0fSteA4.d.ts → session-Baxp9iRK.d.ts} +24 -9
  82. package/dist/session-reconnect.d.cts +2 -2
  83. package/dist/session-reconnect.d.ts +2 -2
  84. package/dist/smart-dom-reader.d.cts +1 -1
  85. package/dist/smart-dom-reader.d.ts +1 -1
  86. package/dist/theme-editor-preview.cjs +47 -45
  87. package/dist/theme-editor-preview.d.cts +1 -1
  88. package/dist/theme-editor-preview.d.ts +1 -1
  89. package/dist/theme-editor-preview.js +23 -24
  90. package/dist/theme-editor.d.cts +2 -2
  91. package/dist/theme-editor.d.ts +2 -2
  92. package/dist/theme-reference.d.cts +2 -2
  93. package/dist/theme-reference.d.ts +2 -2
  94. package/dist/{tool-loading-animation-BvdaZfwh.d.cts → tool-loading-animation-un3kA5Ur.d.cts} +1 -1
  95. package/dist/{tool-loading-animation-BvdaZfwh.d.ts → tool-loading-animation-un3kA5Ur.d.ts} +1 -1
  96. package/dist/{turn-activity-ui-BYBlInZQ.d.cts → turn-activity-ui-BGNhtMcH.d.cts} +2 -2
  97. package/dist/{turn-activity-ui-BYBlInZQ.d.ts → turn-activity-ui-BGNhtMcH.d.ts} +2 -2
  98. package/dist/turn-activity-ui.d.cts +1 -1
  99. package/dist/turn-activity-ui.d.ts +1 -1
  100. package/dist/{types-CiXi4ICM.d.cts → types-BOZHQIk0.d.cts} +13 -1
  101. package/dist/{types-CiXi4ICM.d.ts → types-BOZHQIk0.d.ts} +13 -1
  102. package/dist/voice-client-tools.d.cts +1 -1
  103. package/dist/voice-client-tools.d.ts +1 -1
  104. package/dist/voice-runtime.cjs +1 -1
  105. package/dist/voice-runtime.d.cts +2 -2
  106. package/dist/voice-runtime.d.ts +2 -2
  107. package/dist/voice-runtime.js +1 -1
  108. package/dist/voice-worklet-player.d.cts +2 -2
  109. package/dist/voice-worklet-player.d.ts +2 -2
  110. package/dist/{webmcp-runtime-entry-BTbOg3Au.d.cts → webmcp-runtime-entry-DuAG1_yF.d.cts} +1 -1
  111. package/dist/{webmcp-runtime-entry-BTbOg3Au.d.ts → webmcp-runtime-entry-DuAG1_yF.d.ts} +1 -1
  112. package/dist/webmcp-runtime.d.cts +1 -1
  113. package/dist/webmcp-runtime.d.ts +1 -1
  114. package/dist/widget.css +1 -1
  115. package/package.json +2 -2
  116. package/src/activity-ui.ts +23 -0
  117. package/src/components/disclosure-animation.ts +5 -0
  118. package/src/components/tool-bubble.ts +1 -0
  119. package/src/index-core.ts +2 -0
  120. package/src/markdown-parsers-eager.ts +2 -1
  121. package/src/markdown-parsers-entry.ts +1 -0
  122. package/src/markdown-parsers-loader.ts +2 -0
  123. package/src/postprocessors.ts +14 -0
  124. package/src/session.ts +3 -0
  125. package/src/styles/widget.css +6 -0
  126. package/src/types.ts +13 -0
  127. package/src/ui.background-conversations.test.ts +6 -0
  128. package/src/ui.footer-visibility.test.ts +54 -0
  129. package/src/ui.page-cache.test.ts +268 -0
  130. package/src/ui.postprocess.test.ts +80 -1
  131. package/src/ui.reveal-tool-call.test.ts +258 -0
  132. package/src/ui.ts +222 -32
  133. package/src/utils/actions.ts +3 -0
  134. package/src/utils/streaming-markdown.test.ts +103 -0
  135. package/src/utils/streaming-markdown.ts +186 -0
  136. package/dist/chunk-18tPmsiJ.js +0 -1
  137. package/dist/chunk-B3OrH9Ng.js +0 -2
  138. package/dist/chunk-Dp2vWE9V.js +0 -1
  139. package/dist/chunk-VloQ2dnh.js +0 -1
@@ -0,0 +1,258 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
4
+ import { createAgentExperience, type AgentWidgetController } from "./ui";
5
+
6
+ let controller: AgentWidgetController;
7
+ let mount: HTMLElement;
8
+
9
+ const injectTool = (id: string, sequence: number, status: "running" | "complete" = "complete", chunks = ["Tool details"]) =>
10
+ controller.injectTestMessage({
11
+ type: "message",
12
+ message: {
13
+ id: `msg-${id}`,
14
+ role: "assistant",
15
+ content: "",
16
+ createdAt: "2026-01-01T00:00:00Z",
17
+ sequence,
18
+ streaming: status === "running",
19
+ variant: "tool",
20
+ toolCall: { id, name: "Search", status, chunks },
21
+ },
22
+ });
23
+
24
+ const setup = (grouped: boolean, v5Defaults = true) => {
25
+ controller = createAgentExperience(mount, {
26
+ apiUrl: "/test",
27
+ persistState: false,
28
+ future: { v5Defaults },
29
+ launcher: { enabled: false },
30
+ features: {
31
+ toolCallDisplay: { grouped, groupedMode: "collapsible", autoExpand: false, autoCollapseDelay: false },
32
+ },
33
+ });
34
+ injectTool("call-1", 1);
35
+ injectTool("call-2", 2);
36
+ };
37
+
38
+ const toolBubble = (toolCallId: string) =>
39
+ mount.querySelector<HTMLElement>(`.persona-tool-bubble[data-tool-call-id="${toolCallId}"]`);
40
+ const ownHeader = (bubble: HTMLElement) =>
41
+ Array.from(bubble.querySelectorAll<HTMLElement>('button[data-expand-header="true"]'))
42
+ .find((header) => header.closest(".persona-tool-bubble") === bubble)!;
43
+
44
+ const installScrollMetrics = () => {
45
+ const body = mount.querySelector<HTMLElement>(".persona-widget-body")!;
46
+ let height = 1000;
47
+ Object.defineProperties(body, {
48
+ clientHeight: { configurable: true, get: () => 400 },
49
+ scrollHeight: { configurable: true, get: () => height },
50
+ });
51
+ body.scrollTo = (options?: ScrollToOptions | number) => {
52
+ if (options && typeof options === "object") body.scrollTop = options.top ?? 0;
53
+ };
54
+ for (const [id, top] of [["call-1", 100], ["call-2", 900]] as const) {
55
+ Object.defineProperties(ownHeader(toolBubble(id)!), {
56
+ offsetTop: { configurable: true, get: () => top },
57
+ offsetParent: { configurable: true, get: () => body },
58
+ });
59
+ }
60
+ return { body, grow: () => { height += 400; } };
61
+ };
62
+
63
+ beforeEach(() => {
64
+ vi.useFakeTimers();
65
+ window.localStorage.clear();
66
+ window.scrollTo = vi.fn();
67
+ mount = document.createElement("div");
68
+ document.body.appendChild(mount);
69
+ });
70
+
71
+ afterEach(() => {
72
+ controller?.destroy();
73
+ document.body.replaceChildren();
74
+ vi.unstubAllGlobals();
75
+ vi.useRealTimers();
76
+ });
77
+
78
+ describe("revealToolCall", () => {
79
+ it("stamps each tool bubble with its tool call id", () => {
80
+ setup(false);
81
+ expect(toolBubble("call-1")?.dataset.messageId).toBe("msg-call-1");
82
+ expect(toolBubble("call-2")?.dataset.messageId).toBe("msg-call-2");
83
+ });
84
+
85
+ it("expands a standalone tool and marks it until the highlight expires", () => {
86
+ setup(false);
87
+ const bubble = toolBubble("call-2")!;
88
+ expect(ownHeader(bubble).getAttribute("aria-expanded")).toBe("false");
89
+
90
+ expect(controller.revealToolCall("call-2")).toBe(true);
91
+ expect(ownHeader(toolBubble("call-2")!).getAttribute("aria-expanded")).toBe("true");
92
+ expect(toolBubble("call-2")!.hasAttribute("data-persona-revealed")).toBe(true);
93
+ expect(ownHeader(toolBubble("call-1")!).getAttribute("aria-expanded")).toBe("false");
94
+
95
+ vi.advanceTimersByTime(2000);
96
+ expect(toolBubble("call-2")!.hasAttribute("data-persona-revealed")).toBe(false);
97
+ });
98
+
99
+ it("opens the collapsed group that holds the tool before expanding it", () => {
100
+ setup(true);
101
+ const group = mount.querySelector<HTMLElement>('[data-message-id="tool-group-msg-call-1"]')!;
102
+ expect(ownHeader(group).getAttribute("aria-expanded")).toBe("false");
103
+
104
+ expect(controller.revealToolCall("call-2")).toBe(true);
105
+ expect(ownHeader(group).getAttribute("aria-expanded")).toBe("true");
106
+ expect(ownHeader(toolBubble("call-2")!).getAttribute("aria-expanded")).toBe("true");
107
+ });
108
+
109
+ it.each([[false, false], [false, true], [true, false], [true, true]])(
110
+ "keeps the highlight through streaming renders (V5: %s, grouped: %s)",
111
+ (v5Defaults, grouped) => {
112
+ setup(grouped, v5Defaults);
113
+ injectTool("call-2", 2, "running", ["Searching"]);
114
+ controller.revealToolCall("call-2");
115
+
116
+ vi.advanceTimersByTime(1000);
117
+ injectTool("call-2", 2, "running", ["Searching", "Found a page"]);
118
+ expect(toolBubble("call-2")!.textContent).toContain("Found a page");
119
+ expect(toolBubble("call-2")!.hasAttribute("data-persona-revealed")).toBe(true);
120
+
121
+ injectTool("call-2", 2, "complete", ["Search complete"]);
122
+ expect(toolBubble("call-2")!.hasAttribute("data-persona-revealed")).toBe(true);
123
+ vi.advanceTimersByTime(999);
124
+ expect(toolBubble("call-2")!.hasAttribute("data-persona-revealed")).toBe(true);
125
+ vi.advanceTimersByTime(1);
126
+ expect(toolBubble("call-2")!.hasAttribute("data-persona-revealed")).toBe(false);
127
+ injectTool("call-2", 2, "complete", ["Updated result"]);
128
+ expect(toolBubble("call-2")!.hasAttribute("data-persona-revealed")).toBe(false);
129
+ },
130
+ );
131
+
132
+ it("restarts the highlight duration when revealing the same tool again", () => {
133
+ setup(false);
134
+ controller.revealToolCall("call-2");
135
+ vi.advanceTimersByTime(1000);
136
+ controller.revealToolCall("call-2");
137
+ vi.advanceTimersByTime(1000);
138
+ expect(toolBubble("call-2")!.hasAttribute("data-persona-revealed")).toBe(true);
139
+ vi.advanceTimersByTime(1000);
140
+ expect(toolBubble("call-2")!.hasAttribute("data-persona-revealed")).toBe(false);
141
+ });
142
+
143
+ it("keeps the highlight when regrouping the transcript", () => {
144
+ setup(false);
145
+ controller.revealToolCall("call-2");
146
+ controller.update({ features: { toolCallDisplay: { grouped: true } } });
147
+ expect(toolBubble("call-2")!.parentElement?.closest("[data-persona-tool-group]")).not.toBeNull();
148
+ expect(toolBubble("call-2")!.hasAttribute("data-persona-revealed")).toBe(true);
149
+ vi.advanceTimersByTime(2000);
150
+ expect(toolBubble("call-2")!.hasAttribute("data-persona-revealed")).toBe(false);
151
+ });
152
+
153
+ it("does not carry a highlight into a cleared conversation with the same tool ids", () => {
154
+ setup(false);
155
+ controller.revealToolCall("call-2");
156
+ controller.clearChat();
157
+ injectTool("call-2", 2);
158
+ expect(toolBubble("call-2")!.hasAttribute("data-persona-revealed")).toBe(false);
159
+ });
160
+
161
+ it.each([false, true])("keeps following paused when centering reaches the bottom (reduced motion: %s)", (reduced) => {
162
+ vi.stubGlobal("matchMedia", vi.fn(() => ({ matches: reduced, addEventListener: vi.fn(), removeEventListener: vi.fn() })));
163
+ setup(false);
164
+ controller.update({ features: { scrollBehavior: { mode: "follow" } } });
165
+ const { body, grow } = installScrollMetrics();
166
+ controller.revealToolCall("call-2", { expand: false, highlightMs: 0 });
167
+ vi.advanceTimersByTime(500);
168
+ expect(body.scrollTop).toBe(600);
169
+ body.dispatchEvent(new Event("scroll"));
170
+ vi.advanceTimersByTime(200);
171
+ body.dispatchEvent(new Event("scroll"));
172
+ grow();
173
+ injectTool("call-2", 2, "running", ["More streamed output"]);
174
+ vi.advanceTimersByTime(500);
175
+ expect(body.scrollTop).toBe(600);
176
+ });
177
+
178
+ it("cancels an earlier jump to latest and keeps following paused", () => {
179
+ setup(false);
180
+ controller.update({ features: {
181
+ scrollBehavior: { mode: "follow" },
182
+ scrollToBottom: { scrollBehavior: "smooth", scrollDurationMs: 800 },
183
+ } });
184
+ vi.advanceTimersByTime(1000);
185
+ const { body, grow } = installScrollMetrics();
186
+ body.scrollTop = 0;
187
+ body.dispatchEvent(new Event("scroll"));
188
+ mount.querySelector<HTMLElement>("[data-persona-scroll-to-bottom]")!.dispatchEvent(new MouseEvent("click"));
189
+ vi.advanceTimersByTime(80);
190
+ expect(body.scrollTop).toBeGreaterThan(0);
191
+ controller.revealToolCall("call-1", { expand: false });
192
+ vi.advanceTimersByTime(1000);
193
+ expect(body.scrollTop).toBe(0);
194
+ grow();
195
+ injectTool("call-2", 2, "running", ["More streamed output"]);
196
+ vi.advanceTimersByTime(500);
197
+ expect(body.scrollTop).toBe(0);
198
+ });
199
+
200
+ it.each([
201
+ ["wheel", 80, false],
202
+ ["wheel", 250, false],
203
+ ["keydown", 80, false],
204
+ ["touchmove", 80, false],
205
+ ["pointerdown", 80, false],
206
+ ["wheel", 80, true],
207
+ ] as const)("yields to %s at %sms (reduced motion: %s)", (input, elapsed, reduced) => {
208
+ vi.stubGlobal("matchMedia", vi.fn(() => ({ matches: reduced, addEventListener: vi.fn(), removeEventListener: vi.fn() })));
209
+ setup(false);
210
+ controller.update({ features: { scrollBehavior: { mode: "follow" } } });
211
+ const { body } = installScrollMetrics();
212
+ controller.revealToolCall("call-2", { expand: false });
213
+ if (elapsed > 200) {
214
+ vi.advanceTimersByTime(180);
215
+ // A disclosure transition moves the target before the settle pass.
216
+ Object.defineProperty(ownHeader(toolBubble("call-2")!), "offsetTop", { configurable: true, get: () => 750 });
217
+ vi.advanceTimersByTime(elapsed - 180);
218
+ } else vi.advanceTimersByTime(elapsed);
219
+ body.dispatchEvent(input === "wheel" ? new WheelEvent("wheel", { deltaY: -100 })
220
+ : input === "keydown" ? new KeyboardEvent("keydown", { key: "ArrowUp" }) : new Event(input));
221
+ body.scrollTop = 123;
222
+ body.dispatchEvent(new Event("scroll"));
223
+ vi.advanceTimersByTime(20);
224
+ expect(body.scrollTop).toBe(123);
225
+ vi.advanceTimersByTime(480);
226
+ expect(body.scrollTop).toBe(123);
227
+ expect(toolBubble("call-2")!.hasAttribute("data-persona-revealed")).toBe(true);
228
+ controller.revealToolCall("call-2", { expand: false });
229
+ vi.advanceTimersByTime(500);
230
+ expect(body.scrollTop).toBe(elapsed > 200 ? 550 : 600);
231
+ });
232
+
233
+ it("cancels the previous reveal's delayed centering", () => {
234
+ vi.stubGlobal("matchMedia", vi.fn(() => ({ matches: true, addEventListener: vi.fn(), removeEventListener: vi.fn() })));
235
+ setup(false);
236
+ const { body } = installScrollMetrics();
237
+ controller.revealToolCall("call-1", { expand: false, highlightMs: 0 });
238
+ vi.advanceTimersByTime(100);
239
+ controller.revealToolCall("call-2", { expand: false, highlightMs: 0 });
240
+ expect(body.scrollTop).toBe(600);
241
+ vi.advanceTimersByTime(150);
242
+ expect(body.scrollTop).toBe(600);
243
+ vi.advanceTimersByTime(50);
244
+ expect(body.scrollTop).toBe(600);
245
+ });
246
+
247
+ it("leaves details closed and skips the mark when asked", () => {
248
+ setup(false);
249
+ expect(controller.revealToolCall("call-1", { expand: false, highlightMs: 0 })).toBe(true);
250
+ expect(ownHeader(toolBubble("call-1")!).getAttribute("aria-expanded")).toBe("false");
251
+ expect(toolBubble("call-1")!.hasAttribute("data-persona-revealed")).toBe(false);
252
+ });
253
+
254
+ it("returns false for an unknown tool call id", () => {
255
+ setup(false);
256
+ expect(controller.revealToolCall("missing")).toBe(false);
257
+ });
258
+ });