@burtson-labs/bandit-engine 2.0.54 → 2.0.57

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/dist/index.js CHANGED
@@ -3260,7 +3260,7 @@ var init_knowledgeStore = __esm({
3260
3260
  });
3261
3261
 
3262
3262
  // src/store/mcpToolsStore.ts
3263
- var import_zustand11, healthCheckTool, newsTool, weatherTool, docsTool, sportsTool, imageGenerationTool, defaultTools, useMCPToolsStore;
3263
+ var import_zustand11, healthCheckTool, webSearchTool, webFetchTool, imageGenerationTool, defaultTools, useMCPToolsStore;
3264
3264
  var init_mcpToolsStore = __esm({
3265
3265
  "src/store/mcpToolsStore.ts"() {
3266
3266
  "use strict";
@@ -3286,94 +3286,47 @@ var init_mcpToolsStore = __esm({
3286
3286
  method: "GET",
3287
3287
  isBuiltIn: true
3288
3288
  };
3289
- newsTool = {
3290
- id: "news",
3291
- name: "news",
3292
- description: "Get the latest news headlines and articles from various sources",
3289
+ webSearchTool = {
3290
+ id: "web-search",
3291
+ name: "web_search",
3292
+ description: "Search the web for current information, documentation, and facts",
3293
3293
  enabled: true,
3294
3294
  type: "function",
3295
3295
  function: {
3296
- name: "news",
3297
- description: "Get news articles and headlines",
3296
+ name: "web_search",
3297
+ description: "Search the web and return ranked results with snippets (and an optional summarized answer). Use for current events, documentation, libraries, error messages, and factual lookups.",
3298
3298
  parameters: {
3299
3299
  type: "object",
3300
3300
  properties: {
3301
- topic: { type: "string", description: "Filter news by topic" },
3302
- count: { type: "number", description: "Number of articles to return (1-100)" },
3303
- headlines: { type: "boolean", description: "Return top headlines instead of searching" }
3304
- },
3305
- required: []
3306
- }
3307
- },
3308
- endpoint: "/mcp/news",
3309
- method: "GET",
3310
- isBuiltIn: true
3311
- };
3312
- weatherTool = {
3313
- id: "weather",
3314
- name: "weather",
3315
- description: "Get current weather conditions and forecasts by location",
3316
- enabled: true,
3317
- type: "function",
3318
- function: {
3319
- name: "weather",
3320
- description: "Get weather information by location",
3321
- parameters: {
3322
- type: "object",
3323
- properties: {
3324
- zip: { type: "string", description: "US zip code" },
3325
- latitude: { type: "number", description: "Latitude" },
3326
- longitude: { type: "number", description: "Longitude" }
3327
- },
3328
- required: []
3329
- }
3330
- },
3331
- endpoint: "/mcp/weather",
3332
- method: "GET",
3333
- isBuiltIn: true
3334
- };
3335
- docsTool = {
3336
- id: "docs",
3337
- name: "docs",
3338
- description: "Search framework documentation",
3339
- enabled: true,
3340
- type: "function",
3341
- function: {
3342
- name: "docs",
3343
- description: "Search framework documentation",
3344
- parameters: {
3345
- type: "object",
3346
- properties: {
3347
- query: { type: "string", description: "Search query" },
3348
- framework: { type: "string", description: "Specific framework to search" },
3349
- count: { type: "number", description: "Number of results (1-50)" }
3301
+ query: { type: "string", description: "The search query \u2014 natural language or keywords" },
3302
+ count: { type: "number", description: "Number of results to return (1-10, default 5)" },
3303
+ include_answer: { type: "boolean", description: "Include a short summarized answer when available" }
3350
3304
  },
3351
3305
  required: ["query"]
3352
3306
  }
3353
3307
  },
3354
- endpoint: "/mcp/docs",
3308
+ endpoint: "/mcp/web-search",
3355
3309
  method: "GET",
3356
3310
  isBuiltIn: true
3357
3311
  };
3358
- sportsTool = {
3359
- id: "sports",
3360
- name: "sports",
3361
- description: "Get sports scores and game information",
3312
+ webFetchTool = {
3313
+ id: "web-fetch",
3314
+ name: "web_fetch",
3315
+ description: "Fetch the text content of a specific URL",
3362
3316
  enabled: true,
3363
3317
  type: "function",
3364
3318
  function: {
3365
- name: "sports",
3366
- description: "Get sports scores and game information",
3319
+ name: "web_fetch",
3320
+ description: "Fetch a single public URL and return its trimmed text content. Use when you already have a specific link you need to read.",
3367
3321
  parameters: {
3368
3322
  type: "object",
3369
3323
  properties: {
3370
- league: { type: "string", description: "Filter by specific league (e.g., nfl, nba)" },
3371
- date: { type: "string", description: "Date in YYYY-MM-DD format" }
3324
+ url: { type: "string", description: "Absolute http(s) URL to fetch" }
3372
3325
  },
3373
- required: []
3326
+ required: ["url"]
3374
3327
  }
3375
3328
  },
3376
- endpoint: "/mcp/sports",
3329
+ endpoint: "/mcp/web-fetch",
3377
3330
  method: "GET",
3378
3331
  isBuiltIn: true
3379
3332
  };
@@ -3401,7 +3354,7 @@ var init_mcpToolsStore = __esm({
3401
3354
  method: "POST",
3402
3355
  isBuiltIn: true
3403
3356
  };
3404
- defaultTools = [healthCheckTool, newsTool, weatherTool, docsTool, sportsTool, imageGenerationTool];
3357
+ defaultTools = [healthCheckTool, webSearchTool, webFetchTool, imageGenerationTool];
3405
3358
  useMCPToolsStore = (0, import_zustand11.create)((set, get) => ({
3406
3359
  tools: defaultTools,
3407
3360
  isLoaded: false,
@@ -15483,7 +15436,7 @@ var init_stt_client = __esm({
15483
15436
  normalizedBlob = new Blob([blob], { type: "audio/webm" });
15484
15437
  }
15485
15438
  const body = new FormData();
15486
- const filename = normalizedBlob.type.includes("ogg") ? "audio.ogg" : normalizedBlob.type.includes("wav") ? "audio.wav" : normalizedBlob.type.includes("mp3") ? "audio.mp3" : "audio.webm";
15439
+ const filename = normalizedBlob.type.includes("ogg") ? "audio.ogg" : normalizedBlob.type.includes("wav") ? "audio.wav" : normalizedBlob.type.includes("mp4") || normalizedBlob.type.includes("m4a") || normalizedBlob.type.includes("aac") ? "audio.mp4" : normalizedBlob.type.includes("mp3") ? "audio.mp3" : "audio.webm";
15487
15440
  body.append("audio", normalizedBlob, filename);
15488
15441
  body.append("file", normalizedBlob, filename);
15489
15442
  body.append("audioFile", normalizedBlob, filename);
@@ -20558,6 +20511,7 @@ USE THE ABOVE CONTENT to answer the user's question. Reference specific informat
20558
20511
  - Only use tools for live data (news, weather, sports scores) when specifically requested
20559
20512
  - Trust and utilize the provided context without hesitation`;
20560
20513
  enhancedSystemPrompt += ragGuidance;
20514
+ const systemPromptForSummary = enhancedSystemPrompt;
20561
20515
  const mcpToolsAvailable = isMCPAvailable();
20562
20516
  if (mcpToolsAvailable) {
20563
20517
  const enabledTools = getEnabledMCPToolsForAI();
@@ -20573,10 +20527,10 @@ USE THE ABOVE CONTENT to answer the user's question. Reference specific informat
20573
20527
  TOOL USAGE PROTOCOL (conservative approach)
20574
20528
  - PRIORITIZE your built-in knowledge and the provided context ABOVE to answer questions first.
20575
20529
  - Use your training data and general knowledge confidently for common topics, concepts, and questions.
20576
- - Only call tools for SPECIFIC, CURRENT information that requires real-time data:
20577
- * news() - ONLY when explicitly asked about recent/breaking news, current events, or "what's happening now"
20578
- * sports() - ONLY when asked about live scores, current games, recent sports results, or league standings
20579
- * search/documentation tools - ONLY when asked about very specific technical documentation or when you need to supplement your knowledge with current docs
20530
+ - Only call tools for SPECIFIC, CURRENT information that requires real-time data or a source you don't already have:
20531
+ * web_search() - when asked about recent/current events, breaking news, live information (weather, prices, sports scores), or when you need to look up documentation, libraries, APIs, error messages, or verify a specific fact
20532
+ * web_fetch() - ONLY when you already have a specific URL whose contents you need to read
20533
+ * image_generation() - ONLY when explicitly asked to create or generate an image
20580
20534
  - For general questions about concepts, definitions, explanations, or how-to topics, use your built-in knowledge WITHOUT calling tools.
20581
20535
  - Examples of what NOT to use tools for: "who are you?", "what is React?", "explain machine learning", "how does X work?", general programming questions.
20582
20536
  - When a tool is truly needed, call exactly ONE tool that best matches the request.
@@ -20588,11 +20542,11 @@ functionName({"param": "value"})
20588
20542
  Examples of appropriate tool usage:
20589
20543
 
20590
20544
  \`\`\`tool_code
20591
- news({"topic": "latest AI developments", "count": 5})
20545
+ web_search({"query": "latest AI developments 2026", "count": 5})
20592
20546
  \`\`\`
20593
20547
 
20594
20548
  \`\`\`tool_code
20595
- sports({"league": "nfl", "type": "scores"})
20549
+ web_fetch({"url": "https://example.com/changelog"})
20596
20550
  \`\`\`
20597
20551
  `;
20598
20552
  enhancedSystemPrompt += `
@@ -20635,6 +20589,7 @@ ${protocol}`;
20635
20589
  if (openIdx !== -1) result = result.slice(0, openIdx);
20636
20590
  return result.trimStart();
20637
20591
  };
20592
+ const stripToolBlocks = (text) => text.replace(/```(?:tool_code|TOOL_CODE)\s*\n[\s\S]*?\n```/gi, "").trim();
20638
20593
  const flushNow = () => {
20639
20594
  clearFlushTimer();
20640
20595
  if (!sawToolBlock) {
@@ -20708,6 +20663,8 @@ ${protocol}`;
20708
20663
  }
20709
20664
  const toolCallMatches = fullMessage.match(/```(?:tool_code|TOOL_CODE)\s*\n([^`]+)\n```/gi);
20710
20665
  let enhancedMessage = fullMessage;
20666
+ const summarizableResults = [];
20667
+ const inlineImageBlocks = [];
20711
20668
  if (toolCallMatches && toolCallMatches.length > 0 && mcpToolsAvailable) {
20712
20669
  debugLogger.info("Detected tool calls in AI response", {
20713
20670
  toolCallCount: toolCallMatches.length,
@@ -20748,47 +20705,43 @@ ${protocol}`;
20748
20705
  });
20749
20706
  let resultText = "";
20750
20707
  if (result.success) {
20751
- if (functionName === "sports" && Array.isArray(result.data)) {
20752
- const games = result.data.filter(Boolean);
20753
- const q = (question || "").toLowerCase();
20754
- const normalize2 = (value) => typeof value === "string" ? value.toLowerCase().replace(/[^a-z0-9 ]/g, "") : "";
20755
- const tokens = (value) => normalize2(value).split(" ").filter((w) => w.length > 3);
20756
- const formatTeamScore = (team, score) => {
20757
- const teamName = typeof team === "string" && team.trim().length > 0 ? team : "Team";
20758
- const teamScore = typeof score === "number" || typeof score === "string" ? String(score) : "\u2014";
20759
- return `${teamName} ${teamScore}`;
20760
- };
20761
- const scoreLine = (game) => {
20762
- const status = typeof game.status === "string" ? game.status : "";
20763
- const final = status.toLowerCase().includes("final");
20764
- const score = `${formatTeamScore(game.homeTeam, game.homeScore)} \u2014 ${formatTeamScore(
20765
- game.awayTeam,
20766
- game.awayScore
20767
- )}`;
20768
- return final ? `Final: ${score}` : `${status || "In progress"} \u2014 ${score}`;
20769
- };
20770
- let best = null;
20771
- let bestScore = -1;
20772
- for (const game of games) {
20773
- const homeTokens = tokens(game.homeTeam);
20774
- const awayTokens = tokens(game.awayTeam);
20775
- const hits = [...homeTokens, ...awayTokens].reduce(
20776
- (acc, token) => acc + (q.includes(token) ? 1 : 0),
20777
- 0
20708
+ if (functionName === "web_search" || functionName === "web-search") {
20709
+ const search = result.data ?? {};
20710
+ const items = Array.isArray(search.results) ? search.results : [];
20711
+ const blocks = [];
20712
+ if (typeof search.answer === "string" && search.answer.trim().length > 0) {
20713
+ blocks.push(search.answer.trim());
20714
+ }
20715
+ if (items.length > 0) {
20716
+ blocks.push(
20717
+ items.slice(0, 6).map((item, index) => {
20718
+ const title = item.title?.trim() || "Untitled";
20719
+ const url = item.url?.trim();
20720
+ const snippet = item.content?.trim();
20721
+ let line = `${index + 1}. **${title}**`;
20722
+ if (url) line += `
20723
+ ${url}`;
20724
+ if (snippet) {
20725
+ const truncated = snippet.length > 300 ? `${snippet.slice(0, 300)}\u2026` : snippet;
20726
+ line += `
20727
+ ${truncated}`;
20728
+ }
20729
+ return line;
20730
+ }).join("\n\n")
20778
20731
  );
20779
- if (hits > bestScore) {
20780
- bestScore = hits;
20781
- best = game;
20782
- }
20783
20732
  }
20784
- if (best && bestScore > 0) {
20785
- resultText = scoreLine(best);
20786
- } else if (games.length > 0) {
20787
- const topGames = games.slice(0, Math.min(5, games.length));
20788
- resultText = `Here are some current scores:
20789
- - ${topGames.map(scoreLine).join("\n- ")}`;
20733
+ resultText = blocks.length ? blocks.join("\n\n") : `No results found${search.query ? ` for "${search.query}"` : ""}.`;
20734
+ } else if (functionName === "web_fetch" || functionName === "web-fetch") {
20735
+ const fetched = result.data ?? {};
20736
+ if (fetched.blocked || fetched.error && !fetched.content) {
20737
+ resultText = fetched.error || "Unable to fetch that URL.";
20790
20738
  } else {
20791
- resultText = "No games available right now.";
20739
+ const body = (fetched.content ?? "").trim();
20740
+ const preview = body.length > 2e3 ? `${body.slice(0, 2e3)}\u2026` : body;
20741
+ const source = fetched.url ? `**Source:** ${fetched.url}
20742
+
20743
+ ` : "";
20744
+ resultText = preview ? `${source}${preview}` : `${source}No readable content found.`;
20792
20745
  }
20793
20746
  } else if (functionName === "image_generation" || functionName === "image-generation") {
20794
20747
  const imageData = result.data ?? {};
@@ -20799,63 +20752,6 @@ ${protocol}`;
20799
20752
 
20800
20753
  ${revisedPrompt ? `Prompt refinement: \u201C${revisedPrompt}\u201D
20801
20754
  ` : ""}Note: the image link may expire in ~2 hours.` : "Image generated successfully.";
20802
- } else if (functionName === "news" && Array.isArray(result.data)) {
20803
- const items = result.data.slice(0, 3);
20804
- resultText = items.length ? `## Top News Results
20805
-
20806
- ${items.map((item, index) => {
20807
- const title = item.title ?? "Untitled";
20808
- const source = item.source ?? "Unknown";
20809
- const url = item.url ?? item.link;
20810
- const description = item.description ?? item.summary;
20811
- const publishedAt = item.publishedAt ?? item.published;
20812
- let newsItem = `### ${index + 1}. ${title}
20813
- `;
20814
- if (typeof description === "string" && description.length > 0) {
20815
- const truncated = description.length > 150 ? `${description.slice(0, 150)}...` : description;
20816
- newsItem += `${truncated}
20817
-
20818
- `;
20819
- }
20820
- newsItem += `**Source:** ${source}
20821
- `;
20822
- if (publishedAt) {
20823
- const date = new Date(publishedAt);
20824
- if (!Number.isNaN(date.getTime())) {
20825
- newsItem += `**Published:** ${date.toLocaleDateString()} at ${date.toLocaleTimeString(
20826
- [],
20827
- { hour: "2-digit", minute: "2-digit" }
20828
- )}
20829
- `;
20830
- }
20831
- }
20832
- if (typeof url === "string" && url.length > 0) {
20833
- newsItem += `
20834
- **[\u{1F4F0} Read Full Article](${url})**
20835
- `;
20836
- }
20837
- return newsItem;
20838
- }).join("\n---\n\n")}` : "No news results found.";
20839
- } else if (functionName === "docs" && Array.isArray(result.data)) {
20840
- const items = result.data.slice(0, 3);
20841
- resultText = items.length ? `Relevant docs:
20842
- - ${items.map((item) => {
20843
- const title = item.title ?? "Untitled doc";
20844
- const url = item.url ?? item.link;
20845
- return url ? `${title} \u2014 ${url}` : title;
20846
- }).join("\n- ")}` : "No documentation results found.";
20847
- } else if (functionName === "weather") {
20848
- const weatherData = result.data ?? {};
20849
- const informative = [weatherData.message, weatherData.details, weatherData.error].map((value) => typeof value === "string" ? value.trim() : void 0).filter((value) => Boolean(value) && value.toLowerCase() !== "n/a");
20850
- if (informative.length > 0) {
20851
- const locationLabel = typeof weatherData.location === "string" && weatherData.location.length > 0 ? ` for ${weatherData.location}` : "";
20852
- resultText = `Weather service notice${locationLabel}: ${informative.join(" \u2014 ")}`;
20853
- } else {
20854
- const parts = [weatherData.location, weatherData.description, weatherData.temperature].map(
20855
- (value) => typeof value === "number" || typeof value === "string" ? String(value).trim() : void 0
20856
- ).filter((value) => Boolean(value));
20857
- resultText = parts.length ? parts.join(" \u2014 ") : "No weather data found.";
20858
- }
20859
20755
  } else if (typeof result.data === "string") {
20860
20756
  resultText = result.data;
20861
20757
  } else if (result.data) {
@@ -20866,16 +20762,16 @@ ${items.map((item, index) => {
20866
20762
  } else {
20867
20763
  const data = result.data ?? {};
20868
20764
  const informative = [data.message, data.details, data.error, result.error].map((value) => typeof value === "string" ? value.trim() : void 0).filter((value) => Boolean(value) && value.toLowerCase() !== "n/a");
20869
- if (functionName === "weather" && informative.length) {
20870
- const locationLabel = typeof data.location === "string" && data.location.length > 0 ? ` for ${data.location}` : "";
20871
- resultText = `Weather service issue${locationLabel}: ${informative.join(" \u2014 ")}`;
20872
- } else if (informative.length) {
20873
- resultText = informative.join(" \u2014 ");
20874
- } else {
20875
- resultText = `I couldn't complete that request: ${result.error || "Unknown error"}.`;
20876
- }
20765
+ resultText = informative.length ? informative.join(" \u2014 ") : `I couldn't complete that request: ${result.error || "Unknown error"}.`;
20877
20766
  }
20878
20767
  enhancedMessage = enhancedMessage.replace(placeholderToken, resultText);
20768
+ if (result.success) {
20769
+ if (functionName === "image_generation" || functionName === "image-generation") {
20770
+ inlineImageBlocks.push(resultText);
20771
+ } else if (functionName !== "check_gateway_health") {
20772
+ summarizableResults.push({ name: functionName, output: resultText });
20773
+ }
20774
+ }
20879
20775
  debugLogger.info("Tool execution completed", {
20880
20776
  functionName,
20881
20777
  success: result.success,
@@ -20898,6 +20794,65 @@ ${items.map((item, index) => {
20898
20794
  }
20899
20795
  }
20900
20796
  }
20797
+ if (summarizableResults.length > 0) {
20798
+ try {
20799
+ const toolResultsText = summarizableResults.map((r) => `## ${r.name}
20800
+ ${r.output}`).join("\n\n");
20801
+ const summaryMessages = [
20802
+ { role: "system", content: systemPromptForSummary },
20803
+ ...contextMessages,
20804
+ { role: "user", content: question },
20805
+ { role: "assistant", content: stripToolBlocks(fullMessage) || "Let me look that up." },
20806
+ {
20807
+ role: "user",
20808
+ content: `I ran the tool(s) you requested. Here are the raw results:
20809
+
20810
+ ${toolResultsText}
20811
+
20812
+ Using these results together with your own knowledge, answer my original question concisely and in a natural, well-formatted way. Cite source URLs inline when you rely on them. Do NOT output tool_code or call any tools again.`
20813
+ }
20814
+ ];
20815
+ const summaryRequest = {
20816
+ model: modelName,
20817
+ messages: summaryMessages,
20818
+ stream: true,
20819
+ options: { num_predict: tokenLimit + 250 }
20820
+ };
20821
+ clearFlushTimer();
20822
+ setStreamBuffer("");
20823
+ const summaryText = await new Promise((resolve) => {
20824
+ let acc = "";
20825
+ const summarySub = provider.chat(summaryRequest).subscribe({
20826
+ next: (data) => {
20827
+ if (data?.message?.content) {
20828
+ acc += data.message.content;
20829
+ const visible = stripThinking(acc);
20830
+ latestDisplayMessage = visible;
20831
+ lastPartialRef.current.text = visible;
20832
+ setStreamBuffer(visible);
20833
+ }
20834
+ },
20835
+ error: (summaryErr) => {
20836
+ debugLogger.error("Summarization pass failed", {
20837
+ error: summaryErr instanceof Error ? summaryErr.message : String(summaryErr)
20838
+ });
20839
+ resolve("");
20840
+ },
20841
+ complete: () => resolve(stripThinking(acc).trim())
20842
+ });
20843
+ currentSubRef.current = summarySub;
20844
+ });
20845
+ if (summaryText.trim()) {
20846
+ enhancedMessage = summaryText + (inlineImageBlocks.length ? `
20847
+
20848
+ ${inlineImageBlocks.join("\n\n")}` : "");
20849
+ }
20850
+ } catch (summaryError) {
20851
+ debugLogger.error("Summarization pass threw", {
20852
+ error: summaryError instanceof Error ? summaryError.message : String(summaryError)
20853
+ });
20854
+ }
20855
+ }
20901
20856
  }
20902
20857
  overrideComponentStatus("Idle");
20903
20858
  setIsSubmitting(false);
@@ -28245,7 +28200,7 @@ var init_ConnectionStatus = __esm({
28245
28200
  });
28246
28201
 
28247
28202
  // src/hooks/useVoiceMode.ts
28248
- var import_react35, RMS_BASELINE, RMS_NORMALIZER, computeRms, useVoiceMode;
28203
+ var import_react35, RMS_BASELINE, RMS_NORMALIZER, computeRms, pickSupportedMimeType, useVoiceMode;
28249
28204
  var init_useVoiceMode = __esm({
28250
28205
  "src/hooks/useVoiceMode.ts"() {
28251
28206
  "use strict";
@@ -28263,6 +28218,19 @@ var init_useVoiceMode = __esm({
28263
28218
  }
28264
28219
  return Math.sqrt(sumSquares / data.length);
28265
28220
  };
28221
+ pickSupportedMimeType = () => {
28222
+ if (typeof MediaRecorder === "undefined" || typeof MediaRecorder.isTypeSupported !== "function") {
28223
+ return void 0;
28224
+ }
28225
+ const candidates = [
28226
+ "audio/webm;codecs=opus",
28227
+ "audio/webm",
28228
+ "audio/mp4;codecs=mp4a.40.2",
28229
+ "audio/mp4",
28230
+ "audio/aac"
28231
+ ];
28232
+ return candidates.find((type) => MediaRecorder.isTypeSupported(type));
28233
+ };
28266
28234
  useVoiceMode = (config) => {
28267
28235
  const enabled = useVoiceModeStore((state) => state.enabled);
28268
28236
  const setStatus = useVoiceModeStore((state) => state.setStatus);
@@ -28286,10 +28254,15 @@ var init_useVoiceMode = __esm({
28286
28254
  let silenceStartAt = 0;
28287
28255
  const isRecordingRef = { current: false };
28288
28256
  const isProcessingRef = { current: false };
28257
+ let removeResumeListeners = null;
28289
28258
  const amplitudeThreshold = configRef.current.amplitudeThreshold ?? 0.025;
28290
28259
  const minSpeechMs = configRef.current.minSpeechMs ?? 180;
28291
28260
  const minSilenceMs = configRef.current.minSilenceMs ?? 720;
28292
28261
  const clearAudioSession = async () => {
28262
+ if (removeResumeListeners) {
28263
+ removeResumeListeners();
28264
+ removeResumeListeners = null;
28265
+ }
28293
28266
  if (rafId) {
28294
28267
  cancelAnimationFrame(rafId);
28295
28268
  rafId = null;
@@ -28381,7 +28354,8 @@ var init_useVoiceMode = __esm({
28381
28354
  const startRecording = (stream) => {
28382
28355
  chunks = [];
28383
28356
  try {
28384
- recorder = new MediaRecorder(stream);
28357
+ const mimeType = pickSupportedMimeType();
28358
+ recorder = mimeType ? new MediaRecorder(stream, { mimeType }) : new MediaRecorder(stream);
28385
28359
  recorder.addEventListener("dataavailable", (event) => {
28386
28360
  if (event.data && event.data.size > 0) {
28387
28361
  chunks.push(event.data);
@@ -28475,6 +28449,28 @@ var init_useVoiceMode = __esm({
28475
28449
  dataArray
28476
28450
  };
28477
28451
  resetTransientState();
28452
+ if (audioContext.state === "suspended") {
28453
+ try {
28454
+ await audioContext.resume();
28455
+ } catch (resumeError) {
28456
+ debugLogger.warn("Voice mode audio context resume failed", { error: resumeError });
28457
+ }
28458
+ }
28459
+ if (!cancelled && audioContext.state === "suspended") {
28460
+ const resumeOnGesture = () => {
28461
+ audioContext.resume().catch(() => void 0);
28462
+ };
28463
+ const gestureEvents = ["touchend", "pointerdown", "click"];
28464
+ gestureEvents.forEach(
28465
+ (evt) => window.addEventListener(evt, resumeOnGesture, { once: true, passive: true })
28466
+ );
28467
+ removeResumeListeners = () => {
28468
+ gestureEvents.forEach((evt) => window.removeEventListener(evt, resumeOnGesture));
28469
+ };
28470
+ }
28471
+ if (cancelled) {
28472
+ return;
28473
+ }
28478
28474
  monitorAudio();
28479
28475
  } catch (error) {
28480
28476
  debugLogger.error("Voice mode failed to initialize microphone", {