@runtypelabs/persona 4.21.1 → 4.22.1

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 (129) 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-B_JdVK9o.d.cts → types-9Vxm-PPz.d.cts} +136 -6
  4. package/dist/animations/{types-B_JdVK9o.d.ts → types-9Vxm-PPz.d.ts} +136 -6
  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 +136 -6
  8. package/dist/animations-extra.d.ts +136 -6
  9. package/dist/{approval-ui-RNNBR5R3.js → approval-ui-K7R5Q5OC.js} +1 -1
  10. package/dist/approval-ui.cjs +2 -2
  11. package/dist/approval-ui.d.cts +221 -16
  12. package/dist/approval-ui.d.ts +221 -16
  13. package/dist/approval-ui.js +2 -2
  14. package/dist/artifacts-ui-F4N6VMKF.js +7 -0
  15. package/dist/artifacts-ui.cjs +1 -1
  16. package/dist/artifacts-ui.d.cts +221 -16
  17. package/dist/artifacts-ui.d.ts +221 -16
  18. package/dist/artifacts-ui.js +1 -1
  19. package/dist/chunk-NQLDCJAP.js +4 -0
  20. package/dist/codegen.cjs +1 -1
  21. package/dist/codegen.js +1 -1
  22. package/dist/context-mentions.d.cts +221 -16
  23. package/dist/context-mentions.d.ts +221 -16
  24. package/dist/event-stream-view-GL3ADRY2.js +1 -0
  25. package/dist/event-stream-view.cjs +1 -1
  26. package/dist/event-stream-view.d.cts +221 -16
  27. package/dist/event-stream-view.d.ts +221 -16
  28. package/dist/event-stream-view.js +1 -1
  29. package/dist/forms-ui.d.cts +234 -25
  30. package/dist/forms-ui.d.ts +234 -25
  31. package/dist/history-view.d.cts +136 -6
  32. package/dist/history-view.d.ts +136 -6
  33. package/dist/index.cjs +70 -70
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.d.cts +302 -32
  36. package/dist/index.d.ts +302 -32
  37. package/dist/index.global.js +54 -54
  38. package/dist/index.global.js.map +1 -1
  39. package/dist/index.js +69 -69
  40. package/dist/index.js.map +1 -1
  41. package/dist/launcher.global.js +2 -2
  42. package/dist/launcher.global.js.map +1 -1
  43. package/dist/session-reconnect.d.cts +221 -16
  44. package/dist/session-reconnect.d.ts +221 -16
  45. package/dist/smart-dom-reader.d.cts +221 -16
  46. package/dist/smart-dom-reader.d.ts +221 -16
  47. package/dist/testing.cjs +3 -3
  48. package/dist/testing.d.cts +3 -4
  49. package/dist/testing.d.ts +3 -4
  50. package/dist/testing.js +3 -3
  51. package/dist/theme-editor-preview.cjs +73 -73
  52. package/dist/theme-editor-preview.d.cts +221 -16
  53. package/dist/theme-editor-preview.d.ts +221 -16
  54. package/dist/theme-editor-preview.js +51 -51
  55. package/dist/theme-editor.cjs +4 -4
  56. package/dist/theme-editor.d.cts +221 -16
  57. package/dist/theme-editor.d.ts +221 -16
  58. package/dist/theme-editor.js +2 -2
  59. package/dist/theme-reference.cjs +1 -1
  60. package/dist/theme-reference.d.cts +65 -1
  61. package/dist/theme-reference.d.ts +65 -1
  62. package/dist/theme-reference.js +1 -1
  63. package/dist/voice-runtime.d.cts +6 -2
  64. package/dist/voice-runtime.d.ts +6 -2
  65. package/dist/webmcp-polyfill.js +1 -4
  66. package/dist/webmcp-runtime-JI7T4QYF.js +6 -0
  67. package/dist/webmcp-runtime.cjs +2 -2
  68. package/dist/webmcp-runtime.d.cts +34 -2
  69. package/dist/webmcp-runtime.d.ts +34 -2
  70. package/dist/webmcp-runtime.js +2 -2
  71. package/package.json +2 -2
  72. package/src/ask-user-question-tool.ts +1 -1
  73. package/src/client-history-rest.test.ts +2 -1
  74. package/src/client-visitor-history.test.ts +20 -4
  75. package/src/client.composer-options.test.ts +1 -1
  76. package/src/client.interrupt.test.ts +2 -2
  77. package/src/client.test.ts +477 -915
  78. package/src/client.ts +134 -494
  79. package/src/client.unified-tools.test.ts +76 -0
  80. package/src/components/artifact-preview.ts +5 -0
  81. package/src/components/event-stream-badge-theming.test.ts +162 -0
  82. package/src/components/event-stream-view.test.ts +76 -76
  83. package/src/components/event-stream-view.ts +55 -20
  84. package/src/components/tool-bubble.test.ts +12 -0
  85. package/src/components/tool-bubble.ts +13 -2
  86. package/src/event-stream-view-bundle.test.ts +2 -2
  87. package/src/generated/runtype-openapi-contract.ts +137 -1
  88. package/src/index-core.ts +2 -0
  89. package/src/internal/runtype-history-provider.test.ts +2 -2
  90. package/src/runtime/init.registry.test.ts +56 -0
  91. package/src/runtime/init.ts +17 -0
  92. package/src/session-history.test.ts +1 -1
  93. package/src/session.quote.test.ts +1 -1
  94. package/src/session.resubmit-from.test.ts +1 -1
  95. package/src/session.test.ts +5 -5
  96. package/src/session.ts +95 -36
  97. package/src/suggest-replies-tool.ts +1 -1
  98. package/src/testing/mock-stream.test.ts +37 -16
  99. package/src/testing/mock-stream.ts +14 -9
  100. package/src/types/theme.ts +41 -1
  101. package/src/types.ts +57 -15
  102. package/src/ui.ask-user-question-plugin.test.ts +7 -7
  103. package/src/ui.composer-defer-one.test.ts +6 -34
  104. package/src/ui.composer-draft-persistence.test.ts +1 -1
  105. package/src/ui.composer-quote.test.ts +1 -1
  106. package/src/ui.message-history-actions.test.ts +1 -1
  107. package/src/ui.reasoning-display.test.ts +136 -2
  108. package/src/ui.streaming-coalescing.test.ts +10 -13
  109. package/src/ui.ts +101 -35
  110. package/src/ui.typing-indicator.test.ts +216 -0
  111. package/src/ui.voice-custom-provider.test.ts +211 -0
  112. package/src/utils/__snapshots__/tokens-parity.test.ts.snap +1740 -0
  113. package/src/utils/event-stream-buffer.test.ts +13 -13
  114. package/src/utils/event-stream-capture.test.ts +20 -20
  115. package/src/utils/formatting.ts +22 -0
  116. package/src/utils/theme.ts +16 -0
  117. package/src/utils/throughput-tracker.test.ts +100 -142
  118. package/src/utils/throughput-tracker.ts +18 -132
  119. package/src/utils/tokens.ts +59 -0
  120. package/src/utils/voice-support.ts +18 -1
  121. package/src/voice/voice-turn-tracker.ts +18 -0
  122. package/src/webmcp-bridge.test.ts +132 -23
  123. package/src/webmcp-bridge.ts +27 -3
  124. package/src/webmcp-runtime-entry.ts +71 -11
  125. package/dist/artifacts-ui-J6AKUGTS.js +0 -7
  126. package/dist/chunk-HBE7LFNT.js +0 -4
  127. package/dist/event-stream-view-73NWKWVQ.js +0 -1
  128. package/dist/webmcp-runtime-P3CHZRHF.js +0 -6
  129. package/src/utils/__fixtures__/unified-translator.oracle.ts +0 -717
@@ -0,0 +1,211 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { afterEach, beforeEach, expect, it, vi } from "vitest";
4
+ import { createAgentExperience } from "./ui";
5
+ import type { VoiceProvider, VoiceStatus } from "./types";
6
+
7
+ const controllers: ReturnType<typeof createAgentExperience>[] = [];
8
+ beforeEach(() => { window.scrollTo = vi.fn(); });
9
+ afterEach(() => {
10
+ for (const controller of controllers.splice(0)) controller.destroy();
11
+ document.body.innerHTML = "";
12
+ window.localStorage.clear();
13
+ vi.unstubAllGlobals();
14
+ });
15
+
16
+ function fixture(mode: "none" | "cancel" | "barge-in" = "cancel") {
17
+ let statusCallback: (status: VoiceStatus) => void = () => {};
18
+ let transcriptCallback: NonNullable<Parameters<NonNullable<VoiceProvider["onTranscript"]>>[0]> = () => {};
19
+ let active = false;
20
+ const provider: VoiceProvider = {
21
+ type: "runtype",
22
+ connect: vi.fn(async () => {}),
23
+ disconnect: vi.fn(async () => { active = false; }),
24
+ startListening: vi.fn(async () => { active = true; statusCallback("listening"); }),
25
+ stopListening: vi.fn(async () => { active = false; statusCallback("idle"); }),
26
+ onResult: vi.fn(),
27
+ onError: vi.fn(),
28
+ onStatusChange: callback => { statusCallback = callback; },
29
+ onTranscript: callback => { transcriptCallback = callback; },
30
+ getInterruptionMode: () => mode,
31
+ isBargeInActive: () => active,
32
+ stopPlayback: vi.fn(() => { statusCallback("listening"); }),
33
+ deactivateBargeIn: vi.fn(async () => { active = false; statusCallback("idle"); }),
34
+ };
35
+ const recognition = vi.fn();
36
+ vi.stubGlobal("SpeechRecognition", recognition);
37
+ const mount = document.createElement("div");
38
+ document.body.appendChild(mount);
39
+ const controller = createAgentExperience(mount, {
40
+ apiUrl: "https://api.example.test/chat",
41
+ launcher: { enabled: false }, persistState: false, suggestionChips: [],
42
+ voiceRecognition: { enabled: true, provider: { type: "custom", custom: () => provider } },
43
+ });
44
+ controllers.push(controller);
45
+ const mic = () => mount.querySelector<HTMLButtonElement>("[data-persona-composer-mic]")!;
46
+ return { provider, recognition, mic, controller, emit: (status: VoiceStatus) => statusCallback(status),
47
+ transcript: (role: "user" | "assistant", text: string, final = true, turnId?: string) => transcriptCallback(role, text, final, { turnId }) };
48
+ }
49
+
50
+ it("starts the custom provider from the microphone and cancels playback without hanging up", async () => {
51
+ const { provider, recognition, mic, emit } = fixture();
52
+ mic().click();
53
+ await vi.waitFor(() => expect(provider.startListening).toHaveBeenCalledOnce());
54
+ expect(recognition).not.toHaveBeenCalled();
55
+ emit("speaking");
56
+ expect(mic().getAttribute("aria-label")).toBe("Stop playback and re-record");
57
+ mic().click();
58
+ expect(provider.stopPlayback).toHaveBeenCalledOnce();
59
+ expect(provider.stopListening).not.toHaveBeenCalled();
60
+ expect(provider.deactivateBargeIn).not.toHaveBeenCalled();
61
+ });
62
+
63
+ it("respects none mode and cleans up the custom provider on destroy", async () => {
64
+ const { provider, mic, emit, controller } = fixture("none");
65
+ mic().click();
66
+ await vi.waitFor(() => expect(provider.startListening).toHaveBeenCalledOnce());
67
+ emit("speaking");
68
+ mic().click();
69
+ expect(provider.stopPlayback).not.toHaveBeenCalled();
70
+ expect(provider.stopListening).not.toHaveBeenCalled();
71
+ controller.destroy();
72
+ controllers.splice(controllers.indexOf(controller), 1);
73
+ expect(provider.disconnect).toHaveBeenCalled();
74
+ });
75
+
76
+ it("hangs up a barge-in custom call through the microphone control", async () => {
77
+ const { provider, mic, emit } = fixture("barge-in");
78
+ mic().click();
79
+ await vi.waitFor(() => expect(provider.startListening).toHaveBeenCalledOnce());
80
+ emit("speaking");
81
+ mic().click();
82
+ await vi.waitFor(() => expect(provider.deactivateBargeIn).toHaveBeenCalledOnce());
83
+ });
84
+
85
+ it("routes controller start and stop to the custom provider after a composer update", async () => {
86
+ const { provider, recognition, controller, mic, emit } = fixture();
87
+ controller.update({ voiceRecognition: { enabled: false } });
88
+ controller.update({ voiceRecognition: { enabled: true } });
89
+ expect(mic()).not.toBeNull();
90
+ await controller.startVoiceRecognition();
91
+ await vi.waitFor(() => expect(provider.startListening).toHaveBeenCalledOnce());
92
+ emit("speaking");
93
+ await controller.stopVoiceRecognition();
94
+ expect(provider.deactivateBargeIn).toHaveBeenCalledOnce();
95
+ expect(recognition).not.toHaveBeenCalled();
96
+ });
97
+
98
+ it.each(["cancel", "barge-in"] as const)("settles a pending assistant placeholder after %s during thinking", async (mode) => {
99
+ const { provider, mic, emit, transcript, controller } = fixture(mode);
100
+ mic().click();
101
+ await vi.waitFor(() => expect(provider.startListening).toHaveBeenCalledOnce());
102
+ emit("processing");
103
+ transcript("user", "Tell me a story");
104
+ expect(controller.getMessages().some(message => message.streaming)).toBe(true);
105
+ if (mode === "cancel") mic().click();
106
+ else emit("listening");
107
+ expect(controller.getMessages().map(message => message.content)).toEqual(["Tell me a story"]);
108
+ expect(controller.getMessages().some(message => message.streaming || message.voiceProcessing)).toBe(false);
109
+ transcript("user", "Next question");
110
+ transcript("assistant", "Next answer");
111
+ expect(controller.getMessages().map(message => message.content)).toEqual(["Tell me a story", "Next question", "Next answer"]);
112
+ });
113
+
114
+ it("preserves partial voice content while clearing its streaming flags on hangup", async () => {
115
+ const { provider, mic, emit, transcript, controller } = fixture("barge-in");
116
+ mic().click();
117
+ await vi.waitFor(() => expect(provider.startListening).toHaveBeenCalledOnce());
118
+ transcript("user", "Explain");
119
+ transcript("assistant", "A partial answer", false);
120
+ emit("speaking");
121
+ mic().click();
122
+ expect(controller.getMessages().at(-1)).toMatchObject({ content: "A partial answer", streaming: false, voiceProcessing: false });
123
+ });
124
+
125
+ it("keeps the current user partial in one bubble when playback returns to listening", async () => {
126
+ const { provider, mic, emit, transcript, controller } = fixture();
127
+ mic().click();
128
+ await vi.waitFor(() => expect(provider.startListening).toHaveBeenCalledOnce());
129
+ transcript("user", "Hello");
130
+ transcript("assistant", "Hi");
131
+ transcript("user", "Next", false);
132
+ emit("listening");
133
+ transcript("user", "Next question");
134
+ transcript("assistant", "Next answer");
135
+ expect(controller.getMessages().filter(message => message.role === "user").map(message => message.content)).toEqual(["Hello", "Next question"]);
136
+ });
137
+
138
+ it("drops a cancelled reply arriving after a newer identified turn", async () => {
139
+ const { provider, mic, emit, transcript, controller } = fixture();
140
+ mic().click();
141
+ await vi.waitFor(() => expect(provider.startListening).toHaveBeenCalledOnce());
142
+ transcript("user", "Old question", true, "old");
143
+ emit("processing");
144
+ mic().click();
145
+ transcript("assistant", "Old partial", false, "old");
146
+ transcript("user", "New question", true, "new");
147
+ transcript("assistant", "Cancelled old answer", true, "old");
148
+ transcript("assistant", "New answer", true, "new");
149
+ expect(controller.getMessages().map(message => message.content)).toEqual(["Old question", "New question", "New answer"]);
150
+ });
151
+
152
+ it("ignores untagged cancelled output until the next user final without losing that next answer", async () => {
153
+ const { provider, mic, emit, transcript, controller } = fixture();
154
+ mic().click();
155
+ await vi.waitFor(() => expect(provider.startListening).toHaveBeenCalledOnce());
156
+ transcript("user", "Old question");
157
+ emit("processing");
158
+ mic().click();
159
+ transcript("assistant", "Cancelled answer");
160
+ transcript("user", "New question");
161
+ transcript("assistant", "New answer");
162
+ expect(controller.getMessages().map(message => message.content)).toEqual(["Old question", "New question", "New answer"]);
163
+ });
164
+
165
+ it("disconnects the old provider before constructing its replacement and ignores its stale callbacks", async () => {
166
+ const { provider, mic, controller, transcript, emit } = fixture();
167
+ mic().click();
168
+ await vi.waitFor(() => expect(provider.startListening).toHaveBeenCalledOnce());
169
+ let finishDisconnect!: () => void;
170
+ vi.mocked(provider.disconnect).mockImplementation(() => new Promise<void>(resolve => { finishDisconnect = resolve; }));
171
+ const replacement = { ...provider, connect: vi.fn(async () => {}), startListening: vi.fn(async () => {}), disconnect: vi.fn(async () => {}) };
172
+ const factory = vi.fn(() => replacement);
173
+ controller.update({ voiceRecognition: { enabled: true, provider: { type: "custom", custom: factory } } });
174
+ expect(provider.disconnect).toHaveBeenCalledOnce();
175
+ expect(controller.isVoiceActive()).toBe(false);
176
+ expect(factory).not.toHaveBeenCalled();
177
+ transcript("assistant", "Stale reply");
178
+ emit("speaking");
179
+ expect(controller.getMessages()).toEqual([]);
180
+ finishDisconnect();
181
+ await controller.startVoiceRecognition();
182
+ await vi.waitFor(() => expect(replacement.startListening).toHaveBeenCalledOnce());
183
+ expect(factory).toHaveBeenCalledOnce();
184
+ });
185
+
186
+ it("disconnects a live call when disabled and reinstalls the provider on reenable", async () => {
187
+ const { provider, mic, controller } = fixture();
188
+ mic().click();
189
+ await vi.waitFor(() => expect(provider.startListening).toHaveBeenCalledOnce());
190
+ controller.update({ voiceRecognition: { enabled: false } });
191
+ expect(provider.disconnect).toHaveBeenCalledOnce();
192
+ expect(controller.isVoiceActive()).toBe(false);
193
+ controller.update({ voiceRecognition: { enabled: true } });
194
+ await controller.startVoiceRecognition();
195
+ await vi.waitFor(() => expect(provider.startListening).toHaveBeenCalledTimes(2));
196
+ });
197
+
198
+ it("sets up custom voice after starting with browser dictation and preserves it for cosmetic updates", async () => {
199
+ const { provider, controller } = fixture();
200
+ await controller.startVoiceRecognition();
201
+ await vi.waitFor(() => expect(provider.startListening).toHaveBeenCalledOnce());
202
+ controller.update({ voiceRecognition: { provider: { type: "browser" } } });
203
+ expect(provider.disconnect).toHaveBeenCalledOnce();
204
+ expect(controller.isVoiceActive()).toBe(false);
205
+ vi.mocked(provider.startListening).mockClear();
206
+ controller.update({ voiceRecognition: { provider: { type: "custom", custom: () => provider } } });
207
+ await controller.startVoiceRecognition();
208
+ await vi.waitFor(() => expect(provider.startListening).toHaveBeenCalledOnce());
209
+ controller.update({ voiceRecognition: { iconName: "mic" }, colorScheme: "dark" });
210
+ expect(provider.disconnect).toHaveBeenCalledOnce();
211
+ });