vanilla-agent 1.5.0 → 1.6.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.
package/src/ui.ts CHANGED
@@ -1,6 +1,14 @@
1
1
  import { escapeHtml } from "./postprocessors";
2
2
  import { AgentWidgetSession, AgentWidgetSessionStatus } from "./session";
3
- import { AgentWidgetConfig, AgentWidgetMessage, AgentWidgetEvent } from "./types";
3
+ import {
4
+ AgentWidgetConfig,
5
+ AgentWidgetMessage,
6
+ AgentWidgetEvent,
7
+ AgentWidgetStorageAdapter,
8
+ AgentWidgetStoredState,
9
+ AgentWidgetControllerEventMap,
10
+ AgentWidgetVoiceStateEvent
11
+ } from "./types";
4
12
  import { applyThemeVariables } from "./utils/theme";
5
13
  import { renderLucideIcon } from "./utils/icons";
6
14
  import { createElement } from "./utils/dom";
@@ -15,9 +23,29 @@ import { createSuggestions } from "./components/suggestions";
15
23
  import { enhanceWithForms } from "./components/forms";
16
24
  import { pluginRegistry } from "./plugins/registry";
17
25
  import { mergeWithDefaults } from "./defaults";
26
+ import { createEventBus } from "./utils/events";
27
+ import {
28
+ createActionManager,
29
+ defaultActionHandlers,
30
+ defaultJsonActionParser
31
+ } from "./utils/actions";
18
32
 
19
33
  // Default localStorage key for chat history (automatically cleared on clear chat)
20
34
  const DEFAULT_CHAT_HISTORY_STORAGE_KEY = "vanilla-agent-chat-history";
35
+ const VOICE_STATE_RESTORE_WINDOW = 30 * 1000;
36
+
37
+ const ensureRecord = (value: unknown): Record<string, unknown> => {
38
+ if (!value || typeof value !== "object") {
39
+ return {};
40
+ }
41
+ return { ...(value as Record<string, unknown>) };
42
+ };
43
+
44
+ const stripStreamingFromMessages = (messages: AgentWidgetMessage[]) =>
45
+ messages.map((message) => ({
46
+ ...message,
47
+ streaming: false
48
+ }));
21
49
 
22
50
  type Controller = {
23
51
  update: (config: AgentWidgetConfig) => void;
@@ -31,23 +59,58 @@ type Controller = {
31
59
  startVoiceRecognition: () => boolean;
32
60
  stopVoiceRecognition: () => boolean;
33
61
  injectTestMessage: (event: AgentWidgetEvent) => void;
62
+ getMessages: () => AgentWidgetMessage[];
63
+ getStatus: () => AgentWidgetSessionStatus;
64
+ getPersistentMetadata: () => Record<string, unknown>;
65
+ updatePersistentMetadata: (
66
+ updater: (prev: Record<string, unknown>) => Record<string, unknown>
67
+ ) => void;
68
+ on: <K extends keyof AgentWidgetControllerEventMap>(
69
+ event: K,
70
+ handler: (payload: AgentWidgetControllerEventMap[K]) => void
71
+ ) => () => void;
72
+ off: <K extends keyof AgentWidgetControllerEventMap>(
73
+ event: K,
74
+ handler: (payload: AgentWidgetControllerEventMap[K]) => void
75
+ ) => void;
34
76
  };
35
77
 
36
- const buildPostprocessor = (cfg?: AgentWidgetConfig): MessageTransform => {
37
- if (cfg?.postprocessMessage) {
38
- return (context) =>
39
- cfg.postprocessMessage!({
40
- text: context.text,
78
+ const buildPostprocessor = (
79
+ cfg: AgentWidgetConfig | undefined,
80
+ actionManager?: ReturnType<typeof createActionManager>
81
+ ): MessageTransform => {
82
+ return (context) => {
83
+ let nextText = context.text ?? "";
84
+ const rawPayload = context.message.rawContent ?? null;
85
+
86
+ if (actionManager) {
87
+ const actionOverride = actionManager.process({
88
+ text: nextText,
89
+ raw: rawPayload ?? nextText,
41
90
  message: context.message,
42
91
  streaming: context.streaming
43
92
  });
44
- }
45
- return ({ text }) => escapeHtml(text);
93
+ if (actionOverride !== null) {
94
+ nextText = actionOverride;
95
+ }
96
+ }
97
+
98
+ if (cfg?.postprocessMessage) {
99
+ return cfg.postprocessMessage({
100
+ ...context,
101
+ text: nextText,
102
+ raw: rawPayload ?? context.text ?? ""
103
+ });
104
+ }
105
+
106
+ return escapeHtml(nextText);
107
+ };
46
108
  };
47
109
 
48
110
  export const createAgentExperience = (
49
111
  mount: HTMLElement,
50
- initialConfig?: AgentWidgetConfig
112
+ initialConfig?: AgentWidgetConfig,
113
+ runtimeOptions?: { debugTools?: boolean }
51
114
  ): Controller => {
52
115
  // Tailwind config uses important: "#vanilla-agent-root", so ensure mount has this ID
53
116
  if (!mount.id || mount.id !== "vanilla-agent-root") {
@@ -59,13 +122,70 @@ export const createAgentExperience = (
59
122
 
60
123
  // Get plugins for this instance
61
124
  const plugins = pluginRegistry.getForInstance(config.plugins);
125
+ const eventBus = createEventBus<AgentWidgetControllerEventMap>();
126
+
127
+ const storageAdapter: AgentWidgetStorageAdapter | undefined =
128
+ config.storageAdapter;
129
+ let persistentMetadata: Record<string, unknown> = {};
130
+ let pendingStoredState: Promise<AgentWidgetStoredState | null> | null = null;
131
+
132
+ if (storageAdapter?.load) {
133
+ try {
134
+ const storedState = storageAdapter.load();
135
+ if (storedState && typeof (storedState as Promise<any>).then === "function") {
136
+ pendingStoredState = storedState as Promise<AgentWidgetStoredState | null>;
137
+ } else if (storedState) {
138
+ const immediateState = storedState as AgentWidgetStoredState;
139
+ if (immediateState.metadata) {
140
+ persistentMetadata = ensureRecord(immediateState.metadata);
141
+ }
142
+ if (immediateState.messages?.length) {
143
+ config = { ...config, initialMessages: immediateState.messages };
144
+ }
145
+ }
146
+ } catch (error) {
147
+ if (typeof console !== "undefined") {
148
+ // eslint-disable-next-line no-console
149
+ console.error("[AgentWidget] Failed to load stored state:", error);
150
+ }
151
+ }
152
+ }
153
+
154
+ const getMetadata = () => persistentMetadata;
155
+ const updateMetadata = (
156
+ updater: (prev: Record<string, unknown>) => Record<string, unknown>
157
+ ) => {
158
+ const next = updater({ ...persistentMetadata }) ?? {};
159
+ persistentMetadata = next;
160
+ persistState();
161
+ };
162
+
163
+ const resolvedActionParsers =
164
+ config.actionParsers && config.actionParsers.length
165
+ ? config.actionParsers
166
+ : [defaultJsonActionParser];
167
+
168
+ const resolvedActionHandlers =
169
+ config.actionHandlers && config.actionHandlers.length
170
+ ? config.actionHandlers
171
+ : [defaultActionHandlers.message, defaultActionHandlers.messageAndClick];
172
+
173
+ let actionManager = createActionManager({
174
+ parsers: resolvedActionParsers,
175
+ handlers: resolvedActionHandlers,
176
+ getMetadata,
177
+ updateMetadata,
178
+ emit: eventBus.emit,
179
+ documentRef: typeof document !== "undefined" ? document : null
180
+ });
181
+ actionManager.syncFromMetadata();
62
182
 
63
183
  let launcherEnabled = config.launcher?.enabled ?? true;
64
184
  let autoExpand = config.launcher?.autoExpand ?? false;
65
185
  let prevAutoExpand = autoExpand;
66
186
  let prevLauncherEnabled = launcherEnabled;
67
187
  let open = launcherEnabled ? autoExpand : true;
68
- let postprocess = buildPostprocessor(config);
188
+ let postprocess = buildPostprocessor(config, actionManager);
69
189
  let showReasoning = config.features?.showReasoning ?? true;
70
190
  let showToolCalls = config.features?.showToolCalls ?? true;
71
191
 
@@ -121,6 +241,77 @@ export const createAgentExperience = (
121
241
  const AUTO_SCROLL_BLOCK_TIME = 2000;
122
242
  const USER_SCROLL_THRESHOLD = 5;
123
243
  const BOTTOM_THRESHOLD = 50;
244
+ const messageState = new Map<
245
+ string,
246
+ { streaming?: boolean; role: AgentWidgetMessage["role"] }
247
+ >();
248
+ const voiceState = {
249
+ active: false,
250
+ manuallyDeactivated: false,
251
+ lastUserMessageWasVoice: false
252
+ };
253
+ const voiceAutoResumeMode = config.voiceRecognition?.autoResume ?? false;
254
+ const emitVoiceState = (source: AgentWidgetVoiceStateEvent["source"]) => {
255
+ eventBus.emit("voice:state", {
256
+ active: voiceState.active,
257
+ source,
258
+ timestamp: Date.now()
259
+ });
260
+ };
261
+ const persistVoiceMetadata = () => {
262
+ updateMetadata((prev) => ({
263
+ ...prev,
264
+ voiceState: {
265
+ active: voiceState.active,
266
+ timestamp: Date.now(),
267
+ manuallyDeactivated: voiceState.manuallyDeactivated
268
+ }
269
+ }));
270
+ };
271
+ const maybeRestoreVoiceFromMetadata = () => {
272
+ if (config.voiceRecognition?.enabled === false) return;
273
+ const rawVoiceState = ensureRecord((persistentMetadata as any).voiceState);
274
+ const wasActive = Boolean(rawVoiceState.active);
275
+ const timestamp = Number(rawVoiceState.timestamp ?? 0);
276
+ voiceState.manuallyDeactivated = Boolean(rawVoiceState.manuallyDeactivated);
277
+ if (wasActive && Date.now() - timestamp < VOICE_STATE_RESTORE_WINDOW) {
278
+ setTimeout(() => {
279
+ if (!voiceState.active) {
280
+ voiceState.manuallyDeactivated = false;
281
+ startVoiceRecognition("restore");
282
+ }
283
+ }, 1000);
284
+ }
285
+ };
286
+
287
+ const getMessagesForPersistence = () =>
288
+ session ? stripStreamingFromMessages(session.getMessages()) : [];
289
+
290
+ function persistState(messagesOverride?: AgentWidgetMessage[]) {
291
+ if (!storageAdapter?.save || !session) return;
292
+ const payload = {
293
+ messages: messagesOverride
294
+ ? stripStreamingFromMessages(messagesOverride)
295
+ : getMessagesForPersistence(),
296
+ metadata: persistentMetadata
297
+ };
298
+ try {
299
+ const result = storageAdapter.save(payload);
300
+ if (result instanceof Promise) {
301
+ result.catch((error) => {
302
+ if (typeof console !== "undefined") {
303
+ // eslint-disable-next-line no-console
304
+ console.error("[AgentWidget] Failed to persist state:", error);
305
+ }
306
+ });
307
+ }
308
+ } catch (error) {
309
+ if (typeof console !== "undefined") {
310
+ // eslint-disable-next-line no-console
311
+ console.error("[AgentWidget] Failed to persist state:", error);
312
+ }
313
+ }
314
+ }
124
315
 
125
316
  const scheduleAutoScroll = (force = false) => {
126
317
  if (!shouldAutoScroll) return;
@@ -224,6 +415,38 @@ export const createAgentExperience = (
224
415
  smoothScrollRAF = requestAnimationFrame(animate);
225
416
  };
226
417
 
418
+ const trackMessages = (messages: AgentWidgetMessage[]) => {
419
+ const nextState = new Map<
420
+ string,
421
+ { streaming?: boolean; role: AgentWidgetMessage["role"] }
422
+ >();
423
+
424
+ messages.forEach((message) => {
425
+ const previous = messageState.get(message.id);
426
+ nextState.set(message.id, {
427
+ streaming: message.streaming,
428
+ role: message.role
429
+ });
430
+
431
+ if (!previous && message.role === "assistant") {
432
+ eventBus.emit("assistant:message", message);
433
+ }
434
+
435
+ if (
436
+ message.role === "assistant" &&
437
+ previous?.streaming &&
438
+ message.streaming === false
439
+ ) {
440
+ eventBus.emit("assistant:complete", message);
441
+ }
442
+ });
443
+
444
+ messageState.clear();
445
+ nextState.forEach((value, key) => {
446
+ messageState.set(key, value);
447
+ });
448
+ };
449
+
227
450
 
228
451
  // Message rendering with plugin support
229
452
  const renderMessagesWithPlugins = (
@@ -445,6 +668,13 @@ export const createAgentExperience = (
445
668
  }
446
669
  }
447
670
  scheduleAutoScroll(!isStreaming);
671
+ trackMessages(messages);
672
+
673
+ const lastUserMessage = [...messages]
674
+ .reverse()
675
+ .find((msg) => msg.role === "user");
676
+ voiceState.lastUserMessageWasVoice = Boolean(lastUserMessage?.viaVoice);
677
+ persistState(messages);
448
678
  },
449
679
  onStatusChanged(status) {
450
680
  const currentStatusConfig = config.statusIndicator ?? {};
@@ -470,6 +700,26 @@ export const createAgentExperience = (
470
700
  }
471
701
  });
472
702
 
703
+ if (pendingStoredState) {
704
+ pendingStoredState
705
+ .then((state) => {
706
+ if (!state) return;
707
+ if (state.metadata) {
708
+ persistentMetadata = ensureRecord(state.metadata);
709
+ actionManager.syncFromMetadata();
710
+ }
711
+ if (state.messages?.length) {
712
+ session.hydrateMessages(state.messages);
713
+ }
714
+ })
715
+ .catch((error) => {
716
+ if (typeof console !== "undefined") {
717
+ // eslint-disable-next-line no-console
718
+ console.error("[AgentWidget] Failed to hydrate stored state:", error);
719
+ }
720
+ });
721
+ }
722
+
473
723
  const handleSubmit = (event: Event) => {
474
724
  event.preventDefault();
475
725
  const value = textarea.value.trim();
@@ -500,7 +750,9 @@ export const createAgentExperience = (
500
750
  return (window as any).webkitSpeechRecognition || (window as any).SpeechRecognition || null;
501
751
  };
502
752
 
503
- const startVoiceRecognition = () => {
753
+ const startVoiceRecognition = (
754
+ source: AgentWidgetVoiceStateEvent["source"] = "user"
755
+ ) => {
504
756
  if (isRecording || session.isStreaming()) return;
505
757
 
506
758
  const SpeechRecognitionClass = getSpeechRecognitionClass();
@@ -579,6 +831,12 @@ export const createAgentExperience = (
579
831
  try {
580
832
  speechRecognition.start();
581
833
  isRecording = true;
834
+ voiceState.active = true;
835
+ if (source !== "system") {
836
+ voiceState.manuallyDeactivated = false;
837
+ }
838
+ emitVoiceState(source);
839
+ persistVoiceMetadata();
582
840
  if (micButton) {
583
841
  // Store original styles
584
842
  originalMicStyles = {
@@ -612,11 +870,13 @@ export const createAgentExperience = (
612
870
  micButton.setAttribute("aria-label", "Stop voice recognition");
613
871
  }
614
872
  } catch (error) {
615
- stopVoiceRecognition();
873
+ stopVoiceRecognition("system");
616
874
  }
617
875
  };
618
876
 
619
- const stopVoiceRecognition = () => {
877
+ const stopVoiceRecognition = (
878
+ source: AgentWidgetVoiceStateEvent["source"] = "user"
879
+ ) => {
620
880
  if (!isRecording) return;
621
881
 
622
882
  isRecording = false;
@@ -634,6 +894,10 @@ export const createAgentExperience = (
634
894
  speechRecognition = null;
635
895
  }
636
896
 
897
+ voiceState.active = false;
898
+ emitVoiceState(source);
899
+ persistVoiceMetadata();
900
+
637
901
  if (micButton) {
638
902
  micButton.classList.remove("tvw-voice-recording");
639
903
 
@@ -754,14 +1018,18 @@ export const createAgentExperience = (
754
1018
  if (isRecording) {
755
1019
  // Stop recording and submit
756
1020
  const finalValue = textarea.value.trim();
757
- stopVoiceRecognition();
1021
+ voiceState.manuallyDeactivated = true;
1022
+ persistVoiceMetadata();
1023
+ stopVoiceRecognition("user");
758
1024
  if (finalValue) {
759
1025
  textarea.value = "";
760
1026
  session.sendMessage(finalValue);
761
1027
  }
762
1028
  } else {
763
1029
  // Start recording
764
- startVoiceRecognition();
1030
+ voiceState.manuallyDeactivated = false;
1031
+ persistVoiceMetadata();
1032
+ startVoiceRecognition("user");
765
1033
  }
766
1034
  };
767
1035
 
@@ -769,13 +1037,27 @@ export const createAgentExperience = (
769
1037
  micButton.addEventListener("click", handleMicButtonClick);
770
1038
 
771
1039
  destroyCallbacks.push(() => {
772
- stopVoiceRecognition();
1040
+ stopVoiceRecognition("system");
773
1041
  if (micButton) {
774
1042
  micButton.removeEventListener("click", handleMicButtonClick);
775
1043
  }
776
1044
  });
777
1045
  }
778
1046
 
1047
+ const autoResumeUnsub = eventBus.on("assistant:complete", () => {
1048
+ if (!voiceAutoResumeMode) return;
1049
+ if (voiceState.active || voiceState.manuallyDeactivated) return;
1050
+ if (voiceAutoResumeMode === "assistant" && !voiceState.lastUserMessageWasVoice) {
1051
+ return;
1052
+ }
1053
+ setTimeout(() => {
1054
+ if (!voiceState.active && !voiceState.manuallyDeactivated) {
1055
+ startVoiceRecognition("auto");
1056
+ }
1057
+ }, 600);
1058
+ });
1059
+ destroyCallbacks.push(autoResumeUnsub);
1060
+
779
1061
  const toggleOpen = () => {
780
1062
  setOpenState(!open);
781
1063
  };
@@ -792,6 +1074,7 @@ export const createAgentExperience = (
792
1074
  updateCopy();
793
1075
  setComposerDisabled(session.isStreaming());
794
1076
  scheduleAutoScroll(true);
1077
+ maybeRestoreVoiceFromMetadata();
795
1078
 
796
1079
  const recalcPanelHeight = () => {
797
1080
  if (!launcherEnabled) {
@@ -902,6 +1185,27 @@ export const createAgentExperience = (
902
1185
  detail: { timestamp: new Date().toISOString() }
903
1186
  });
904
1187
  window.dispatchEvent(clearEvent);
1188
+
1189
+ if (storageAdapter?.clear) {
1190
+ try {
1191
+ const result = storageAdapter.clear();
1192
+ if (result instanceof Promise) {
1193
+ result.catch((error) => {
1194
+ if (typeof console !== "undefined") {
1195
+ // eslint-disable-next-line no-console
1196
+ console.error("[AgentWidget] Failed to clear storage adapter:", error);
1197
+ }
1198
+ });
1199
+ }
1200
+ } catch (error) {
1201
+ if (typeof console !== "undefined") {
1202
+ // eslint-disable-next-line no-console
1203
+ console.error("[AgentWidget] Failed to clear storage adapter:", error);
1204
+ }
1205
+ }
1206
+ }
1207
+ persistentMetadata = {};
1208
+ actionManager.syncFromMetadata();
905
1209
  });
906
1210
  };
907
1211
 
@@ -925,7 +1229,7 @@ export const createAgentExperience = (
925
1229
  });
926
1230
  }
927
1231
 
928
- return {
1232
+ const controller: Controller = {
929
1233
  update(nextConfig: AgentWidgetConfig) {
930
1234
  const previousToolCallConfig = config.toolCall;
931
1235
  config = { ...config, ...nextConfig };
@@ -1387,7 +1691,25 @@ export const createAgentExperience = (
1387
1691
  }
1388
1692
  }
1389
1693
 
1390
- postprocess = buildPostprocessor(config);
1694
+ const nextParsers =
1695
+ config.actionParsers && config.actionParsers.length
1696
+ ? config.actionParsers
1697
+ : [defaultJsonActionParser];
1698
+ const nextHandlers =
1699
+ config.actionHandlers && config.actionHandlers.length
1700
+ ? config.actionHandlers
1701
+ : [defaultActionHandlers.message, defaultActionHandlers.messageAndClick];
1702
+
1703
+ actionManager = createActionManager({
1704
+ parsers: nextParsers,
1705
+ handlers: nextHandlers,
1706
+ getMetadata,
1707
+ updateMetadata,
1708
+ emit: eventBus.emit,
1709
+ documentRef: typeof document !== "undefined" ? document : null
1710
+ });
1711
+
1712
+ postprocess = buildPostprocessor(config, actionManager);
1391
1713
  session.updateConfig(config);
1392
1714
  renderMessagesWithPlugins(
1393
1715
  messagesWrapper,
@@ -1732,6 +2054,27 @@ export const createAgentExperience = (
1732
2054
  detail: { timestamp: new Date().toISOString() }
1733
2055
  });
1734
2056
  window.dispatchEvent(clearEvent);
2057
+
2058
+ if (storageAdapter?.clear) {
2059
+ try {
2060
+ const result = storageAdapter.clear();
2061
+ if (result instanceof Promise) {
2062
+ result.catch((error) => {
2063
+ if (typeof console !== "undefined") {
2064
+ // eslint-disable-next-line no-console
2065
+ console.error("[AgentWidget] Failed to clear storage adapter:", error);
2066
+ }
2067
+ });
2068
+ }
2069
+ } catch (error) {
2070
+ if (typeof console !== "undefined") {
2071
+ // eslint-disable-next-line no-console
2072
+ console.error("[AgentWidget] Failed to clear storage adapter:", error);
2073
+ }
2074
+ }
2075
+ }
2076
+ persistentMetadata = {};
2077
+ actionManager.syncFromMetadata();
1735
2078
  },
1736
2079
  setMessage(message: string): boolean {
1737
2080
  if (!textarea) return false;
@@ -1773,13 +2116,17 @@ export const createAgentExperience = (
1773
2116
  setOpenState(true);
1774
2117
  }
1775
2118
 
1776
- startVoiceRecognition();
2119
+ voiceState.manuallyDeactivated = false;
2120
+ persistVoiceMetadata();
2121
+ startVoiceRecognition("user");
1777
2122
  return true;
1778
2123
  },
1779
2124
  stopVoiceRecognition(): boolean {
1780
2125
  if (!isRecording) return false;
1781
2126
 
1782
- stopVoiceRecognition();
2127
+ voiceState.manuallyDeactivated = true;
2128
+ persistVoiceMetadata();
2129
+ stopVoiceRecognition("user");
1783
2130
  return true;
1784
2131
  },
1785
2132
  injectTestMessage(event: AgentWidgetEvent) {
@@ -1789,6 +2136,26 @@ export const createAgentExperience = (
1789
2136
  }
1790
2137
  session.injectTestEvent(event);
1791
2138
  },
2139
+ getMessages() {
2140
+ return session.getMessages();
2141
+ },
2142
+ getStatus() {
2143
+ return session.getStatus();
2144
+ },
2145
+ getPersistentMetadata() {
2146
+ return { ...persistentMetadata };
2147
+ },
2148
+ updatePersistentMetadata(
2149
+ updater: (prev: Record<string, unknown>) => Record<string, unknown>
2150
+ ) {
2151
+ updateMetadata(updater);
2152
+ },
2153
+ on(event, handler) {
2154
+ return eventBus.on(event, handler);
2155
+ },
2156
+ off(event, handler) {
2157
+ eventBus.off(event, handler);
2158
+ },
1792
2159
  destroy() {
1793
2160
  destroyCallbacks.forEach((cb) => cb());
1794
2161
  wrapper.remove();
@@ -1798,6 +2165,33 @@ export const createAgentExperience = (
1798
2165
  }
1799
2166
  }
1800
2167
  };
2168
+
2169
+ const shouldExposeDebugApi =
2170
+ (runtimeOptions?.debugTools ?? false) || Boolean(config.debug);
2171
+
2172
+ if (shouldExposeDebugApi && typeof window !== "undefined") {
2173
+ const previousDebug = (window as any).AgentWidgetBrowser;
2174
+ const debugApi = {
2175
+ controller,
2176
+ getMessages: controller.getMessages,
2177
+ getStatus: controller.getStatus,
2178
+ getMetadata: controller.getPersistentMetadata,
2179
+ updateMetadata: controller.updatePersistentMetadata,
2180
+ clearHistory: () => controller.clearChat(),
2181
+ setVoiceActive: (active: boolean) =>
2182
+ active
2183
+ ? controller.startVoiceRecognition()
2184
+ : controller.stopVoiceRecognition()
2185
+ };
2186
+ (window as any).AgentWidgetBrowser = debugApi;
2187
+ destroyCallbacks.push(() => {
2188
+ if ((window as any).AgentWidgetBrowser === debugApi) {
2189
+ (window as any).AgentWidgetBrowser = previousDebug;
2190
+ }
2191
+ });
2192
+ }
2193
+
2194
+ return controller;
1801
2195
  };
1802
2196
 
1803
2197
  export type AgentWidgetController = Controller;