{"version":3,"sources":["../src/lib/workers/canvas-processor.worker.bundle.ts","../src/lib/workers/canvas.worker.bundle.ts","../src/lib/audio/audio-stutter-analyzer.ts","../src/config/agent-config.ts","../src/hooks/use-context-updater.ts","../src/hooks/use-guides.ts","../src/utils/case-conversion.ts","../src/lib/sammy-api/conversation-service.ts","../src/lib/sammy-api/core-service.ts","../src/lib/sammy-api/memory-service.ts","../src/types/services.ts","../src/lib/sammy-api/sammy-api-client.ts","../src/lib/sammy-api/guides-service.ts","../src/hooks/use-guides-manager.ts","../src/types/agent-types.ts","../src/hooks/use-guides-query-params.ts","../src/hooks/use-render-capture.ts","../src/lib/workers/canvas-processor-manager.ts","../src/types/render-capture-types.ts","../src/utils/canvas-utils.ts","../src/utils/modern-screenshot/change-png-dpi.ts","../src/utils/modern-screenshot/utils.ts","../src/utils/modern-screenshot/create-logger.ts","../src/utils/modern-screenshot/get-default-request-init.ts","../src/utils/modern-screenshot/create-context.ts","../src/utils/modern-screenshot/image-to-canvas.ts","../src/utils/modern-screenshot/clone-canvas.ts","../src/utils/modern-screenshot/clone-iframe.ts","../src/utils/modern-screenshot/clone-image.ts","../src/utils/modern-screenshot/clone-video.ts","../src/utils/modern-screenshot/clone-element.ts","../src/utils/modern-screenshot/sandbox.ts","../src/utils/modern-screenshot/get-default-style.ts","../src/utils/modern-screenshot/get-diff-style.ts","../src/utils/modern-screenshot/copy-css-styles.ts","../src/utils/modern-screenshot/copy-input-value.ts","../src/utils/modern-screenshot/copy-pseudo-class.ts","../src/utils/modern-screenshot/clone-node.ts","../src/utils/modern-screenshot/destroy-context.ts","../src/utils/modern-screenshot/fetch.ts","../src/utils/modern-screenshot/css-url.ts","../src/utils/modern-screenshot/embed-css-style-image.ts","../src/utils/modern-screenshot/embed-image-element.ts","../src/utils/modern-screenshot/embed-svg-use.ts","../src/utils/modern-screenshot/embed-node.ts","../src/utils/modern-screenshot/embed-web-font.ts","../src/utils/modern-screenshot/converts/dom-to-foreign-object-svg.ts","../src/utils/modern-screenshot/converts/dom-to-canvas.ts","../src/utils/capture-frame.ts","../src/utils/screenshot-context.ts","../src/hooks/use-audio-adaptation.ts","../src/hooks/use-conversation-render-events.ts","../src/hooks/use-dom-change-detector.ts","../src/hooks/use-periodic-capture.ts","../src/hooks/use-screen-capture.ts","../src/hooks/use-video-capture.ts","../src/lib/audio/audio-recorder.ts","../src/lib/audio/audioworklet-registry.ts","../src/lib/audio/noise-config.ts","../src/lib/audio/noise-gate-processor.ts","../src/lib/audio/noise-suppression-processor.ts","../src/utils/audio-utils.ts","../src/worklets/audio-processing.ts","../src/worklets/vol-meter.ts","../src/lib/audio/audio-streamer.ts","../src/lib/core/context-memory-manager.ts","../src/lib/core/context-state-manager.ts","../src/lib/core/observability-manager.ts","../src/lib/core/sammy-agent-core.ts","../src/lib/core/screen-capture-manager.ts","../src/lib/genai-live-client.ts","../src/lib/audio/audio-aggregator.ts","../src/lib/workers/observability.worker.bundle.ts","../src/lib/workers/observability-worker-manager.ts","../src/lib/wrappers/wrap-genai-client.ts","../src/lib/tools/definitions/get-context-tool.ts","../src/lib/tools/types.ts","../src/utils/tool-utils.ts","../src/lib/tools/definitions/end-session.ts","../src/lib/tools/definitions/escalate.ts","../src/lib/tools/definitions/index.ts","../src/lib/tools/tool-manager.ts","../src/providers/sammy-agent-context.tsx","../src/hooks/use-agent-state.ts","../src/lib/tools/use-tool-events.ts","../src/types/memory-types.ts","../src/utils/prompt-helpers.ts"],"names":["canvas_processor_worker_bundle_exports","__export","canvasProcessorWorkerCode","init_canvas_processor_worker_bundle","__esmMin","canvas_worker_bundle_exports","canvasWorkerCode","init_canvas_worker_bundle","_AudioStutterAnalyzer","enabled","type","subtype","message","data","event","underruns","e","longCaptures","correlations","underrun","nearbyRenders","r","corr","i","render","audioActiveRenders","times","avg","a","b","max","min","bufferEvents","bufferAheads","avgBuffer","minBuffer","time","icon","warning","active","underrunCount","longCaptureCount","AudioStutterAnalyzer","__publicField","audioStutterAnalyzer","ActivityHandling","EndSensitivity","HarmBlockThreshold","HarmCategory","Modality","StartSensitivity","TurnCoverage","getAgentConfig","functionDeclarations","systemPrompt","useCallback","useEffect","useRef","useContextUpdater","options","updateInterval","includeTitle","includeDomain","includePath","lastUrlRef","lastTitleRef","intervalRef","agentRef","updateContext","currentUrl","currentTitle","metadata","url","startMonitoring","agent","stopMonitoring","originalPushState","originalReplaceState","args","updateUserContext","userData","setCustomContext","key","value","useMemo","useState","convertCamelToSnakeCase","obj","v","acc","snakeKey","camelToSnakeCase","str","letter","markConversationReady","apiCaller","conversationId","response","error","sendMessages","payload","apiPayload","escalateConversation","request","createConversationServices","generateEphemeralToken","constraints","getSystemPrompt","query","params","convertedParams","instantiateSession","body","createCoreServices","getAllMemories","sammyThreeOrganisationFeatureId","retryOptions","path","searchMemories","createMemoryServices","ServiceError","code","status","details","TokenExpiredError","SammyApiClient","authConfig","method","headers","timeout","retries","skipValidation","isFormData","requestHeaders","requestConfig","maxRetries","attempt","controller","timeoutId","errorDetails","currentError","ms","resolve","fetchGuide","guideId","getUserGuides","guide","createGuidesServices","useGuides","walkthroughId","currentGuide","setCurrentGuide","userGuides","setUserGuides","isLoadingGuide","setIsLoadingGuide","isLoadingUserGuides","setIsLoadingUserGuides","guideError","setGuideError","userGuidesError","setUserGuidesError","sammyApiClient","guidesService","fetchGuideInternal","err","fetchUserGuidesInternal","guides","refreshGuide","refreshUserGuides","markGuideAsCompleted","prev","AgentMode","useGuidesQueryParams","checkQueryOnMount","onGuideDetected","queryParamName","debug","log","cleanupGuideParam","currentParams","cleanUrl","checkQueryParameters","cleanupQueryParams","urlParams","updated","param","newUrl","timer","handlePopState","useGuidesManager","autoStartFromURL","onStartAgent","onWalkthroughStart","detectedWalkthroughId","setDetectedWalkthroughId","startWalkthrough","success","handleGuideDetected","CanvasProcessorManager","debugLogs","workerModule","blob","workerUrl","id","hash","shouldSend","pending","_id","reject","canvas","targetWidth","targetHeight","scale","jpegQuality","useHashing","lastHash","minInterval","maxInterval","timeSinceLastSent","hashResolution","promise","bitmap","DEFAULT_CONFIG","AUDIO_AWARE_CONFIG","createSimpleHash","imageData","step","decideShouldSend","currentHash","config","downloadScreenshot","link","renderToTemporaryCanvas","sourceCanvas","tempCanvas","tempCtx","renderToUserCanvas","ctx","renderToCanvas","_P","_H","_Y","_S","PREFIX","IN_BROWSER","SUPPORT_WEB_WORKER","SUPPORT_ATOB","SUPPORT_BTOA","USER_AGENT","IN_CHROME","IN_SAFARI","IN_FIREFOX","isContext","isCssFontFaceRule","rule","isCSSImportRule","isElementNode","node","isSVGElementNode","isSVGImageElementNode","isSVGUseElementNode","isHTMLElementNode","isCommentNode","isTextNode","isImageElement","isVideoElement","isCanvasElement","isTextareaElement","isInputElement","isStyleElement","isScriptElement","isSelectElement","isSlotElement","isIFrameElement","consoleWarn","supportWebp","ownerDocument","isDataUrl","resolveUrl","baseUrl","doc","getDocument","base","target","XMLNS","createSvg","width","height","svg","svgToDataUrl","removeControlCharacter","xhtml","readBlob","reader","blobToDataUrl","createImage","img","loadMedia","media","userOnError","onWarn","removeEventListeners","onResolve","currentSrc","onLoadeddata","onError","onLoad","waitUntilLoad","selectors","el","uuid","counter","random","splitFontFamily","fontFamily","val","uid","createLogger","prefix","label","getDefaultRequestInit","bypassingCache","orCreateContext","createContext","workerNumber","features","ownerWindow","requests","context","createStyleElement","worker","result","resolveBoundingBox","style","cssText","box","imageToCanvas","image","drawImageCount","drawImageInterval","loaded","context2d","createCanvas","drawImage","backgroundColor","cloneCanvas","dataURL","cloned","clonedCtx","cloneIframe","iframe","cloneNode","cloneImage","cloneVideo","video","canPlay","cloneElement","getSandBox","sandbox","ignoredStyles","includedAttributes","getDefaultStyle","pseudoElement","defaultComputedStyles","nodeName","isSvgNode","attributes","name","sandboxWindow","sandboxDocument","root","computedStyle","styles","len","getDiffStyle","defaultStyle","includeStyleProperties","diffStyle","prefixs","prefixTree","applyTo","priority","subIndex","map","copyCssStyles","isRoot","currentParentNodeStyle","clonedStyle","_","copyInputValue","pseudoClasses","scrollbarPseudoClasses","copyPseudoClass","copyScrollbar","addWordToFontFamilies","svgStyleElement","svgStyles","currentNodeStyle","copyBy","pseudoClass","content","klasses","cloneStyle","allClasses","excludeParentNodes","appendChildNode","child","childCloned","restoreScrollPosition","cloneChildNodes","firstChild","nodes","chlidCloned","scrollTop","scrollLeft","transform","matrix","c","applyCssStyleWithOptions","NORMAL_ATTRIBUTE_RE","fontFamilies","onCloneEachNode","names","overflow","textTransform","families","word","family","text","destroyContext","baseFetch","responseType","requestInit","contextFetch","rawUrl","requestType","imageDom","acceptOfImage","fetchFn","placeholderImage","font","workers","canFontMinify","fontTexts","needFontMinify","baseFetchOptions","replaceCssUrlToDataUrl","isImage","hasCssUrl","parseCssUrls","dataUrl","toRE","URL_RE","raw","quotation","escaped","properties","embedCssStyleImage","property","newValue","embedImageElement","originalSrc","embedSvgUse","svgDefsElement","href","svgUrl","definition","res","svgData","embedNode","tasks","embedWebFont","clone","fontCssTexts","filterPreferredFormat","styleSheets","styleSheet","cssRule","index","importIndex","replacedCssText","parseCss","COMMENTS_RE","KEYFRAMES_RE","source","matches","IMPORT_RE","UNIFIED_RE","URL_WITH_FORMAT_RE","FONT_SRC_RE","preferredFormat","match","src","format","domToForeignObjectSvg","progress","autoDestruct","onCloneNode","onEmbedNode","onCreateForeignObjectSvg","allCssText","count","current","runTask","task","createForeignObjectSvg","foreignObject","domToCanvas","captureFrame","refs","canvasRef","clientRef","screenshotContextRef","canvasProcessorRef","lastFrameHashRef","lastSentTimeRef","lastCaptureTimeRef","concurrentCaptureRef","hasPendingCaptureRef","lastCaptureDurationRef","audioActiveRef","capturesPausedRef","slowCaptureCountRef","captureElement","mergedConfig","getCurrentSettings","criticalRender","client","processor","now","timeSinceLastCapture","perfStart","captureStartTime","captureEndTime","renderStartTime","renderEndTime","processStartTime","processEndTime","captureStartData","captureCanvas","fallbackCanvas","captureDuration","scaleRatio","renderedCanvas","totalTime","timingData","longCaptureData","sendFrame","sendStart","sendTime","sendData","observableClient","isOffScreen","createScreenshotContext","htmlEl","useAudioAdaptation","isCapturing","audioActivityTimeoutRef","audioActive","setAudioActive","shouldSkipCapture","lastCaptureDuration","handleAudio","useConversationRenderEvents","onCriticalRender","userSpeakingRef","agentSpeakingRef","lastInputTranscriptionRef","lastOutputTranscriptionRef","handleInputTranscription","transcription","isStarting","isStopping","handleOutputTranscription","handleTurnComplete","handleInterrupted","DEFAULT_OBSERVER_CONFIG","useDomChangeDetector","onDomChange","debounceMs","observerRef","debounceTimerRef","lastChangeTimeRef","handleMutations","mutations","mutation","usePeriodicCapture","getTimeSinceLastChange","periodicCheckTimerRef","startPeriodicCapture","setupInterval","checkInterval","checkAudioState","currentInterval","useRenderCapture","contextElementRef","captureTriggered","setCaptureTriggered","criticalRenderTriggered","setCriticalRenderTriggered","setIsCapturing","captureFrameCallback","triggerCriticalRender","handleDomChange","startRenderCapture","stopRenderCapture","useVideoCapture","videoRef","videoStream","captureTimeoutRef","videoStreamRef","tryAttach","intervalId","base64","startVideoCapture","scheduleNextCapture","stopVideoCapture","useScreenCapture","stream","setStream","isStreaming","setIsStreaming","setVideoStream","activeClientRef","isCapturingRender","isCapturingVideo","handleStreamEnded","track","startStreaming","mediaStream","stopStreaming","EventEmitter","registeredWorklets","createWorketFromSrc","workletName","workletSrc","script","DEFAULT_NOISE_GATE_CONFIG","DEFAULT_NOISE_SUPPRESSION_CONFIG","ENVIRONMENT_PRESETS","NoiseGateProcessor","audioContext","state","cb","newState","currentTime","callback","volume","isAboveThreshold","timeSinceLastSignal","KoalaWorker","WebVoiceProcessor","BasicAudioProcessor","KoalaProcessor","koalaModel","processCallback","enhancedPcm","NoiseSuppressionProcessor","newConfig","koalaProcessor","basicProcessor","fallbackError","didInteract","base64ToArrayBuffer","binaryString","bytes","arrayBufferToBase64","buffer","binary","concatenateArrayBuffers","buffers","totalLength","sum","buf","offset","setupAudioFlushOnUnload","flushCallback","handleBeforeUnload","_event","handleVisibilityChange","AudioRecordingWorklet","audio_processing_default","VolMeterWorket","vol_meter_default","AudioRecorder","oneMinuteAgo","recentGateHistory","h","openTime","clearedBytes","combinedBuffer","combinedView","view","audioConstraints","settings","audioChain","ev","arrayBuffer","arrayBufferString","vuWorkletName","handleStop","AudioStreamer","debugAudioPerformance","chunk","float32Array","dataView","int16","audioData","audioBuffer","scheduleData","worklets","graph","handlers","handler","startTime","underrunData","completeData","nextCheckTime","chunkData","processingBuffer","workletsRecord","worklet","ContextMemoryManager","memoryService","contextStateManager","observabilityManager","session","threshold","searchOptions","memories","memory","preview","relevance","ContextStateManager","m","urlChanged","preferences","includeMemories","includePageMetadata","includeUserPreferences","includeCustomContext","memoryLimit","sections","memoriesToInclude","includeTimestamp","customContext","additionalInfo","prefs","lines","l","dates","d","uuidv4","ObservabilityManager","sessionId","sequenceNum","uniqueTime","currentSequenceNumber","fullEvent","eventType","reason","endTime","totalDuration","events","summary","stats","ScreenCaptureManager","screenCaptureCallbacks","GoogleGenAI","GenAILiveClient","serverContent","parts","audioParts","p","base64s","otherParts","difference","b64","model","callbacks","turnComplete","chunks","hasAudio","hasVideo","ch","toolResponse","array","values","item","AudioAggregator","speaker","pcmData","sampleRate","turnNumber","timestamp","timestamps","promises","userBytesBuffered","agentBytesBuffered","pcmLength","wavBuffer","writeString","string","dataSize","conversationStartTime","sortedChunks","outputChunks","firstChunkTime","silenceBytes","currentTimeMs","chunkDurationMs","expectedTime","gapMs","gapBytes","conversationStart","userAudio","agentAudio","resampledAgent","fromRate","toRate","ratio","inputView","outputLength","outputBuffer","outputView","sourceIndex","fraction","leftChannel","rightChannel","leftView","rightView","maxLength","stereoLength","stereoData","observabilityWorkerCode","ObservabilityWorkerManager","pcmBuffer","typedArray","intervalMs","transfer","workerManagerCache","wrapGenAIClient","observabilityConfig","useWorker","workerManager","cacheKey","workerConfig","audioAggregator","manager","managerConfig","inputTranscriptBuffer","outputTranscriptBuffer","originalConnect","promptText","tool","functionDecl","originalSend","partsArray","characterCount","messageType","part","originalSendRealtimeInput","sizeBytes","originalSendToolResponse","hasText","toolCall","toolName","parameters","serializeEvent","serializeEvents","FunctionResponseScheduling","Type","createTypedEventEmitter","emitter","listener","ToolCategories","createGetContextTool","fc","queryLower","formattedContext","missingToolNameError","missingToolHandlerError","toolError","Behavior","endSessionTool","escalateTool","userContext","escalationResponse","DefaultToolDefinitions","ToolManager","tools","services","agentState","responses","functionResponses","category","SammyAgentCore","contextTool","tokenResult","baseClient","agentConnectConfig","finalTrace","muted","audioCtx","errorEvent","userMessage","agentMessage","base64Data","messages","useContext","useAgentState","setAgentState","updateAgentState","variable","prevState","useToolEvents","agentCore","toolCallbacks","toolEventEmitter","cleanupFunctions","cleanup","jsx","SammyAgentContext","SammyAgentProvider","children","guidesQueryParam","guidesDebug","onTokenExpired","onTurnComplete","onMemoryUpdate","onToolCall","onConnectionStateChange","agentCoreRef","activeSession","agentVolume","userVolume","isConnected","isConnecting","guidesStateRef","screenCapture","finalConfig","handleError","startAgent","connected","guidesState","stopAgent","sendMessage","toggleMuted","agentStatus","contextValue","useSammyAgentContext","MemorySourceType","AgentType","MemoryCategory","saveTextToFile","filename"],"mappings":"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","sourcesContent":["// Auto-generated file - do not edit\n// Generated from src/lib/workers/canvas-processor.worker.ts\nexport const canvasProcessorWorkerCode = \"\\\"use strict\\\";(()=>{function R(n){let s=n.data,i=Math.max(1,Math.floor(s.length/1024)),e=0;for(let a=0;a<s.length;a+=i)e=(e<<5)-e+s[a],e=e&e;return e.toString(36)}async function y(n){try{let{id:s,bitmap:i,targetWidth:e,targetHeight:a,scale:b,jpegQuality:w,useHashing:C,lastHash:u,hashResolution:m}=n,c=new OffscreenCanvas(e,a),h=c.getContext(\\\"2d\\\");if(!h)throw new Error(\\\"Failed to get 2d context\\\");b<1&&(h.imageSmoothingEnabled=!0,h.imageSmoothingQuality=\\\"high\\\"),h.drawImage(i,0,0,e,a),i.close();let g,l=!0;if(C){let r=m?.width||Math.min(320,e),t=m?.height||Math.min(180,a),o=new OffscreenCanvas(r,t).getContext(\\\"2d\\\");if(o){o.drawImage(c,0,0,r,t);let d=o.getImageData(0,0,r,t);g=R(d),l=!u||g!==u}}let f;if(l){let r=await c.convertToBlob({type:\\\"image/jpeg\\\",quality:w}),t=new FileReader,p=new Promise((o,d)=>{t.onloadend=()=>{let v=t.result.split(\\\",\\\")[1];o(v)},t.onerror=d});t.readAsDataURL(r),f=await p}return{id:s,success:!0,data:f,hash:g,shouldSend:l}}catch(s){return{id:n.id,success:!1,error:s instanceof Error?s.message:\\\"Unknown error\\\"}}}self.onmessage=async n=>{let s=await y(n.data);self.postMessage(s)};})();\\n\";\n","// Auto-generated file - do not edit\n// Generated from src/lib/workers/canvas.worker.ts\nexport const canvasWorkerCode = \"\\\"use strict\\\";(()=>{var s=typeof window<\\\"u\\\",y=s&&\\\"Worker\\\"in window,T=s&&\\\"atob\\\"in window,h=s&&\\\"btoa\\\"in window,i=s?window.navigator?.userAgent:\\\"\\\",f=i.includes(\\\"Chrome\\\"),g=i.includes(\\\"AppleWebKit\\\")&&!f,E=i.includes(\\\"Firefox\\\");var m=(...e)=>{};function x(e,t){return new Promise((r,o)=>{let n=new FileReader;n.onload=()=>r(n.result),n.onerror=()=>o(n.error),n.onabort=()=>o(new Error(`Failed read blob to ${t}`)),t===\\\"dataUrl\\\"?n.readAsDataURL(e):t===\\\"arrayBuffer\\\"&&n.readAsArrayBuffer(e)})}var d=e=>x(e,\\\"dataUrl\\\");function u(e){let{url:t,timeout:r,responseType:o,...n}=e,c=new AbortController,p=r?setTimeout(()=>c.abort(),r):void 0;return fetch(t,{signal:c.signal,...n}).then(a=>{if(!a.ok)throw new Error(\\\"Failed fetch, not 2xx response\\\",{cause:a});switch(o){case\\\"arrayBuffer\\\":return a.arrayBuffer();case\\\"dataUrl\\\":return a.blob().then(d);case\\\"text\\\":default:return a.text()}}).finally(()=>clearTimeout(p))}var l=self;l.onmessage=async e=>{let t=e.data,r=t.rawUrl||t.url;try{let o=await u(t);l.postMessage({url:r,result:o})}catch(o){m(o),l.postMessage({url:r})}};})();\\n\";\n","/**\n * Built-in Audio Stutter Analyzer\n * Automatically collects events from audio and render systems to help debug stuttering\n */\n\ninterface AnalysisEvent {\n  type: 'audio' | 'render';\n  subtype: string;\n  message: string;\n  data: Record<string, any>;\n  timestamp: number;\n}\n\ninterface StutterCorrelation {\n  underrun: AnalysisEvent;\n  nearbyRenders: AnalysisEvent[];\n  timeDiff: number;\n}\n\nclass AudioStutterAnalyzer {\n  private static instance: AudioStutterAnalyzer;\n  private events: AnalysisEvent[] = [];\n  private isActive: boolean = true;\n  private debugMode: boolean = false; // New flag for conditional debugging\n  private maxEvents: number = 1000;\n\n  private constructor() {\n    // Expose to window for easy access\n    if (typeof window !== 'undefined') {\n      (window as any).audioStutterAnalyzer = this;\n    }\n  }\n\n  static getInstance(): AudioStutterAnalyzer {\n    if (!AudioStutterAnalyzer.instance) {\n      AudioStutterAnalyzer.instance = new AudioStutterAnalyzer();\n    }\n    return AudioStutterAnalyzer.instance;\n  }\n\n  setDebugMode(enabled: boolean) {\n    this.debugMode = enabled;\n    if (enabled) {\n      console.log(\n        '🎯 [AUDIO-STUTTER-ANALYZER] Debug mode ENABLED - Performance tracking active'\n      );\n    } else {\n      console.log(\n        '🎯 [AUDIO-STUTTER-ANALYZER] Debug mode DISABLED - Performance tracking inactive'\n      );\n      this.clear(); // Clear events when disabling\n    }\n  }\n\n  addEvent(\n    type: 'audio' | 'render',\n    subtype: string,\n    message: string,\n    data: Record<string, any> = {}\n  ) {\n    if (!this.isActive || !this.debugMode) return; // Check debugMode flag\n\n    const event: AnalysisEvent = {\n      type,\n      subtype,\n      message,\n      data,\n      timestamp: Date.now(),\n    };\n\n    this.events.push(event);\n\n    // Keep events under max limit\n    if (this.events.length > this.maxEvents) {\n      this.events.shift();\n    }\n\n    // Auto-log warnings only in debug mode\n    if (\n      this.debugMode &&\n      (subtype === 'underrun' || subtype === 'long-capture')\n    ) {\n      console.warn(`⚠️ [STUTTER-ANALYZER] ${message}`, data);\n    }\n  }\n\n  getAnalysis() {\n    console.log('\\n🔍 === AUDIO STUTTER ANALYSIS ===\\n');\n\n    if (this.events.length === 0) {\n      console.log(\n        'No events recorded yet. The analyzer is running automatically.'\n      );\n      return;\n    }\n\n    // Find underruns\n    const underruns = this.events.filter((e) => e.subtype === 'underrun');\n    console.log(`⚠️ Found ${underruns.length} potential audio underruns`);\n\n    // Find long captures during audio\n    const longCaptures = this.events.filter(\n      (e) => e.subtype === 'long-capture'\n    );\n    console.log(\n      `⚠️ Found ${longCaptures.length} long captures during audio playback`\n    );\n\n    // Analyze correlations\n    const correlations: StutterCorrelation[] = [];\n\n    underruns.forEach((underrun) => {\n      // Find renders within 100ms of this underrun\n      const nearbyRenders = this.events.filter(\n        (e) =>\n          e.type === 'render' &&\n          Math.abs(e.timestamp - underrun.timestamp) < 100\n      );\n\n      if (nearbyRenders.length > 0) {\n        correlations.push({\n          underrun,\n          nearbyRenders,\n          timeDiff: Math.min(\n            ...nearbyRenders.map((r) =>\n              Math.abs(r.timestamp - underrun.timestamp)\n            )\n          ),\n        });\n      }\n    });\n\n    // Print correlations\n    if (correlations.length > 0) {\n      console.log(\n        `\\n🔴 Found ${correlations.length} potential stutter correlations:\\n`\n      );\n\n      correlations.forEach((corr, i) => {\n        console.log(`Correlation ${i + 1}:`);\n        console.log(\n          `  Underrun at: ${new Date(corr.underrun.timestamp).toISOString()}`\n        );\n        console.log(`  Lateness: ${corr.underrun.data.lateness}s`);\n        console.log(`  Nearby renders (${corr.nearbyRenders.length}):`);\n\n        corr.nearbyRenders.forEach((render) => {\n          if (render.data.totalMs) {\n            console.log(\n              `    - Render took ${render.data.totalMs}ms (${render.data.captureMs}ms capture, ${render.data.renderMs}ms render, ${render.data.processMs}ms process)`\n            );\n          }\n        });\n        console.log('');\n      });\n    }\n\n    // Calculate render stats during audio\n    const audioActiveRenders = this.events.filter(\n      (e) =>\n        e.type === 'render' &&\n        e.subtype === 'complete' &&\n        e.data.audioActive === true &&\n        e.data.totalMs\n    );\n\n    if (audioActiveRenders.length > 0) {\n      const times = audioActiveRenders.map((e) => parseFloat(e.data.totalMs));\n      const avg = times.reduce((a, b) => a + b, 0) / times.length;\n      const max = Math.max(...times);\n      const min = Math.min(...times);\n\n      console.log('\\n📊 Render performance during audio playback:');\n      console.log(`  Samples: ${times.length}`);\n      console.log(`  Average: ${avg.toFixed(2)}ms`);\n      console.log(`  Min: ${min.toFixed(2)}ms`);\n      console.log(`  Max: ${max.toFixed(2)}ms`);\n      console.log(\n        `  Over 50ms: ${times.filter((t) => t > 50).length} (${((times.filter((t) => t > 50).length / times.length) * 100).toFixed(1)}%)`\n      );\n    }\n\n    // Audio buffer stats\n    const bufferEvents = this.events.filter(\n      (e) => e.type === 'audio' && e.subtype === 'schedule-complete'\n    );\n    if (bufferEvents.length > 0) {\n      const bufferAheads = bufferEvents\n        .map((e) => parseFloat(e.data.bufferAhead))\n        .filter((b) => !isNaN(b));\n      if (bufferAheads.length > 0) {\n        const avgBuffer =\n          bufferAheads.reduce((a, b) => a + b, 0) / bufferAheads.length;\n        const minBuffer = Math.min(...bufferAheads);\n\n        console.log('\\n📊 Audio buffer statistics:');\n        console.log(`  Average buffer ahead: ${avgBuffer.toFixed(3)}s`);\n        console.log(`  Minimum buffer ahead: ${minBuffer.toFixed(3)}s`);\n        console.log(\n          `  Buffer drops below 0.1s: ${bufferAheads.filter((b) => b < 0.1).length} times`\n        );\n      }\n    }\n\n    // Show recent timeline\n    console.log('\\n📅 Recent event timeline (last 30 events):');\n    this.events.slice(-30).forEach((e) => {\n      const time = new Date(e.timestamp).toISOString().substr(11, 12);\n      const icon = e.type === 'audio' ? '🎵' : '📸';\n      const warning =\n        e.subtype === 'underrun' || e.subtype === 'long-capture' ? '⚠️ ' : '';\n      console.log(\n        `${warning}${icon} ${time} [${e.subtype}] ${e.message.substring(0, 60)}...`\n      );\n    });\n\n    console.log('\\n=== END ANALYSIS ===\\n');\n    console.log(\n      '💡 Tip: Call audioStutterAnalyzer.getAnalysis() anytime to see updated results'\n    );\n    console.log(\n      '💡 Tip: Call audioStutterAnalyzer.clear() to reset the event buffer'\n    );\n  }\n\n  clear() {\n    this.events = [];\n    console.log('🧹 Audio stutter analyzer buffer cleared');\n  }\n\n  setActive(active: boolean) {\n    this.isActive = active;\n    console.log(\n      `📊 Audio stutter analyzer ${active ? 'activated' : 'deactivated'}`\n    );\n  }\n\n  getEvents() {\n    return [...this.events];\n  }\n\n  // Get a summary for quick checking\n  getSummary() {\n    const underrunCount = this.events.filter(\n      (e) => e.subtype === 'underrun'\n    ).length;\n    const longCaptureCount = this.events.filter(\n      (e) => e.subtype === 'long-capture'\n    ).length;\n\n    return {\n      totalEvents: this.events.length,\n      underruns: underrunCount,\n      longCaptures: longCaptureCount,\n      isActive: this.isActive,\n      debugMode: this.debugMode,\n    };\n  }\n}\n\n// Export singleton instance\nexport const audioStutterAnalyzer = AudioStutterAnalyzer.getInstance();\n\n// Log that analyzer is ready\nif (typeof window !== 'undefined') {\n  console.log(\n    '🎯 [AUDIO-STUTTER-ANALYZER] Ready! Enable debug mode with audioStutterAnalyzer.setDebugMode(true)'\n  );\n}\n","/**\n * Configuration settings for the Sammy Agent\n *\n * This file contains the base configuration for the Gemini Live API connection,\n * including audio settings, activity detection, safety settings, and tool declarations.\n * The configuration is optimized for natural conversation flow with proper turn-taking\n * and reduced interruptions.\n */\n\nimport {\n  ActivityHandling,\n  EndSensitivity,\n  HarmBlockThreshold,\n  HarmCategory,\n  Modality,\n  StartSensitivity,\n  TurnCoverage,\n} from '@google/genai';\n\nimport { TypedFunctionDeclaration } from '@/lib/tools/types';\n\n/**\n * Base configuration for customer success agent sessions\n * Optimized for natural conversation flow with proper turn-taking\n */\nexport const getAgentConfig = (\n  functionDeclarations: TypedFunctionDeclaration[],\n  systemPrompt: string\n) => {\n  return {\n    // Core generation settings - adjusted for more natural responses\n    responseModalities: [Modality.AUDIO],\n    temperature: 0.6, // Balanced for natural but focused responses\n    topP: 0.85, // Slightly reduced for consistency\n    topK: 40,\n    maxOutputTokens: 1000,\n    seed: 12345,\n\n    // Audio configuration (enhanced)\n    speechConfig: {\n      voiceConfig: {\n        prebuiltVoiceConfig: { voiceName: 'aoede' },\n      },\n      languageCode: 'en-US',\n    },\n\n    // Adjusted activity detection for immediate response\n    realtimeInputConfig: {\n      automaticActivityDetection: {\n        disabled: false,\n        startOfSpeechSensitivity: StartSensitivity.START_SENSITIVITY_HIGH,\n        endOfSpeechSensitivity: EndSensitivity.END_SENSITIVITY_HIGH,\n        prefixPaddingMs: 40,\n        silenceDurationMs: 300,\n      },\n      activityHandling: ActivityHandling.START_OF_ACTIVITY_INTERRUPTS, // Allow interruption\n      turnCoverage: TurnCoverage.TURN_INCLUDES_ONLY_ACTIVITY,\n    },\n\n    // Reduced proactive behavior to prevent over-talking\n    // proactivity: {\n    //   proactiveAudio: false,\n    // },\n\n    // Context window compression for long conversations\n    // contextWindowCompression: {\n    //   triggerTokens: '24000', // Very high threshold\n    //   slidingWindow: {\n    //     targetTokens: '12000', // Keep substantial context\n    //   },\n    // },\n\n    // Emotional intelligence for customer success\n    // enableAffectiveDialog: true, // Helps agent detect and respond to customer emotions\n\n    // Media resolution for screen sharing\n    // mediaResolution: MediaResolution.MEDIA_RESOLUTION_HIGH, // Better quality for screen content\n\n    // Tools and function calling - now using tool manager for dynamic control\n    tools: [\n      {\n        functionDeclarations,\n      },\n    ],\n\n    systemInstruction: {\n      parts: [{ text: systemPrompt }],\n    },\n\n    // Transcription for logging\n    inputAudioTranscription: true,\n    outputAudioTranscription: true,\n\n    // Enhanced safety settings\n    safetySettings: [\n      {\n        category: HarmCategory.HARM_CATEGORY_HARASSMENT,\n        threshold: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE,\n      },\n      {\n        category: HarmCategory.HARM_CATEGORY_HATE_SPEECH,\n        threshold: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE,\n      },\n      {\n        category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,\n        threshold: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE,\n      },\n      {\n        category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,\n        threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,\n      },\n    ],\n  };\n};\n","/**\n * Hook for updating context state based on page changes\n *\n * This hook returns a function that should be called from within\n * the SammyAgentProvider where agentCoreRef is available.\n *\n * Usage:\n * const contextUpdater = useContextUpdater();\n * // Inside provider: contextUpdater(agentCoreRef.current);\n */\n\nimport { useCallback, useEffect, useRef } from 'react';\n\nimport { SammyAgentCore } from '../lib/core/sammy-agent-core';\n\nexport interface UseContextUpdaterOptions {\n  includeDomain?: boolean;\n  includePath?: boolean;\n  includeTitle?: boolean;\n  updateInterval?: number; // How often to check for changes (ms)\n}\n\nexport const useContextUpdater = (options: UseContextUpdaterOptions = {}) => {\n  const {\n    updateInterval = 1000,\n    includeTitle = true,\n    includeDomain = true,\n    includePath = true,\n  } = options;\n\n  const lastUrlRef = useRef<string>('');\n  const lastTitleRef = useRef<string>('');\n  const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\n  const agentRef = useRef<SammyAgentCore | null>(null);\n\n  // Function to update context\n  const updateContext = useCallback(() => {\n    const currentUrl = window.location.href;\n    const currentTitle = document.title;\n\n    // Check if URL or title changed\n    if (\n      currentUrl !== lastUrlRef.current ||\n      currentTitle !== lastTitleRef.current\n    ) {\n      lastUrlRef.current = currentUrl;\n      lastTitleRef.current = currentTitle;\n\n      // Only update if agent is available\n      if (agentRef.current) {\n        const metadata: any = {\n          url: currentUrl,\n        };\n\n        if (includeTitle) {\n          metadata.title = currentTitle;\n        }\n\n        if (includeDomain) {\n          try {\n            const url = new URL(currentUrl);\n            metadata.domain = url.hostname;\n          } catch (e) {\n            // Invalid URL, skip domain\n          }\n        }\n\n        if (includePath) {\n          try {\n            const url = new URL(currentUrl);\n            metadata.path = url.pathname;\n          } catch (e) {\n            // Invalid URL, skip path\n          }\n        }\n\n        // Update the context\n        agentRef.current.updatePageContext(metadata);\n        console.log('🔄 [USE-CONTEXT-UPDATER] Updated page context:', metadata);\n      }\n    }\n  }, [includeTitle, includeDomain, includePath]);\n\n  // Start monitoring\n  const startMonitoring = useCallback(\n    (agent: SammyAgentCore | null) => {\n      agentRef.current = agent;\n\n      if (!agent) return;\n\n      // Initial update\n      updateContext();\n\n      // Clear any existing interval\n      if (intervalRef.current) {\n        clearInterval(intervalRef.current);\n      }\n\n      // Set up interval to check for changes\n      intervalRef.current = setInterval(updateContext, updateInterval);\n    },\n    [updateContext, updateInterval]\n  );\n\n  // Stop monitoring\n  const stopMonitoring = useCallback(() => {\n    if (intervalRef.current) {\n      clearInterval(intervalRef.current);\n      intervalRef.current = null;\n    }\n    agentRef.current = null;\n  }, []);\n\n  // Set up global event listeners\n  useEffect(() => {\n    // Listen for popstate events (back/forward navigation)\n    window.addEventListener('popstate', updateContext);\n\n    // Listen for pushstate/replacestate\n    const originalPushState = window.history.pushState;\n    const originalReplaceState = window.history.replaceState;\n\n    window.history.pushState = function (...args) {\n      originalPushState.apply(window.history, args);\n      setTimeout(updateContext, 100); // Small delay to ensure title updates\n    };\n\n    window.history.replaceState = function (...args) {\n      originalReplaceState.apply(window.history, args);\n      setTimeout(updateContext, 100);\n    };\n\n    // Cleanup\n    return () => {\n      stopMonitoring();\n      window.removeEventListener('popstate', updateContext);\n      window.history.pushState = originalPushState;\n      window.history.replaceState = originalReplaceState;\n    };\n  }, [updateContext, stopMonitoring]);\n\n  // Manual update functions\n  const updateUserContext = useCallback(\n    (userData: {\n      name?: string;\n      preferences?: Record<string, any>;\n      settings?: Record<string, any>;\n    }) => {\n      if (agentRef.current) {\n        agentRef.current.updateUserContext(userData);\n      }\n    },\n    []\n  );\n\n  const setCustomContext = useCallback((key: string, value: any) => {\n    if (agentRef.current) {\n      const contextManager = agentRef.current.getContextStateManager();\n      contextManager.setCustomContext(key, value);\n    }\n  }, []);\n\n  return {\n    startMonitoring,\n    stopMonitoring,\n    updateUserContext,\n    setCustomContext,\n  };\n};\n","/**\n * Hook to provide always-on guide data with automatic fetching\n * Provides guide data and state management for walkthrough functionality\n */\n\nimport { useCallback, useEffect, useMemo, useState } from 'react';\n\nimport {\n  createGuidesServices,\n  SammyApiClient,\n  SammyGuide,\n} from '@/lib/sammy-api';\nimport { AuthConfig } from '@/types/agent-types';\n\ninterface UseGuidesProps {\n  authConfig?: AuthConfig;\n  walkthroughId?: string; // For auto-fetching specific guide\n}\n\ninterface UseGuidesReturn {\n  // Always-on data\n  currentGuide: SammyGuide | null;\n  // Error states\n  guideError: Error | null;\n\n  // Loading states\n  isLoadingGuide: boolean;\n  isLoadingUserGuides: boolean;\n\n  // Update completion status (called when conversation starts)\n  markGuideAsCompleted: (guideId: string) => void;\n  // Manual refresh functions\n  refreshGuide: () => void;\n\n  refreshUserGuides: () => void;\n  userGuides: SammyGuide[];\n\n  userGuidesError: Error | null;\n}\n\nexport function useGuides({\n  authConfig,\n  walkthroughId,\n}: UseGuidesProps): UseGuidesReturn {\n  // Guide data state\n  const [currentGuide, setCurrentGuide] = useState<SammyGuide | null>(null);\n  const [userGuides, setUserGuides] = useState<SammyGuide[]>([]);\n\n  // Loading states\n  const [isLoadingGuide, setIsLoadingGuide] = useState(false);\n  const [isLoadingUserGuides, setIsLoadingUserGuides] = useState(false);\n\n  // Error states\n  const [guideError, setGuideError] = useState<Error | null>(null);\n  const [userGuidesError, setUserGuidesError] = useState<Error | null>(null);\n\n  // Create API client and guides service\n  const sammyApiClient = useMemo(\n    () => (authConfig ? new SammyApiClient(authConfig) : null),\n    [authConfig]\n  );\n\n  const guidesService = useMemo(\n    () => (sammyApiClient ? createGuidesServices(sammyApiClient) : null),\n    [sammyApiClient]\n  );\n\n  // Internal fetch functions\n  const fetchGuideInternal = useCallback(\n    async (guideId: string) => {\n      if (!guidesService) return null;\n\n      setIsLoadingGuide(true);\n      setGuideError(null);\n\n      try {\n        const guide = await guidesService.fetchGuide(guideId);\n        setCurrentGuide(guide);\n        return guide;\n      } catch (error) {\n        const err =\n          error instanceof Error ? error : new Error('Failed to fetch guide');\n        setGuideError(err);\n        setCurrentGuide(null);\n        return null;\n      } finally {\n        setIsLoadingGuide(false);\n      }\n    },\n    [guidesService]\n  );\n\n  const fetchUserGuidesInternal = useCallback(async () => {\n    if (!guidesService) return [];\n\n    setIsLoadingUserGuides(true);\n    setUserGuidesError(null);\n\n    try {\n      const guides = await guidesService.getUserGuides();\n      setUserGuides(guides);\n      return guides;\n    } catch (error) {\n      const err =\n        error instanceof Error\n          ? error\n          : new Error('Failed to fetch user guides');\n      setUserGuidesError(err);\n      setUserGuides([]);\n      return [];\n    } finally {\n      setIsLoadingUserGuides(false);\n    }\n  }, [guidesService]);\n\n  // Auto-fetch current guide when walkthroughId changes\n  useEffect(() => {\n    if (walkthroughId) {\n      fetchGuideInternal(walkthroughId);\n    } else {\n      setCurrentGuide(null);\n    }\n  }, [walkthroughId, fetchGuideInternal]);\n\n  // Auto-fetch user guides when service is available\n  useEffect(() => {\n    if (guidesService) {\n      fetchUserGuidesInternal();\n    }\n  }, [guidesService, fetchUserGuidesInternal]);\n\n  // Manual refresh functions\n  const refreshGuide = useCallback(() => {\n    if (walkthroughId) {\n      fetchGuideInternal(walkthroughId);\n    }\n  }, [walkthroughId, fetchGuideInternal]);\n\n  const refreshUserGuides = useCallback(() => {\n    fetchUserGuidesInternal();\n  }, [fetchUserGuidesInternal]);\n\n  // Mark guide as completed (called when conversation starts)\n  const markGuideAsCompleted = useCallback((guideId: string) => {\n    // Update current guide if it matches - using functional update to access current value\n    setCurrentGuide((prev) =>\n      prev?.guideId === guideId ? { ...prev, isCompleted: true } : prev\n    );\n\n    // Update user guides list\n    setUserGuides((prev) =>\n      prev.map((guide) =>\n        guide.guideId === guideId ? { ...guide, isCompleted: true } : guide\n      )\n    );\n  }, []); // No dependencies needed - using functional updates\n\n  return {\n    currentGuide,\n    userGuides,\n    isLoadingGuide,\n    isLoadingUserGuides,\n    guideError,\n    userGuidesError,\n    refreshGuide,\n    refreshUserGuides,\n    markGuideAsCompleted,\n  };\n}\n","/* eslint-disable @typescript-eslint/no-explicit-any */\n/**\n * Case conversion utilities for Memory feature\n *   Runtime conversion functions: snakeToCamelCase, camelToSnakeCase for recursively transforming memory form data between SDK and frontend formats\n */\n\n// Utility type to convert camelCase to snake_case\nexport type CamelToSnakeCase<S extends string> =\n  S extends `${infer T}${infer U}`\n    ? U extends Uncapitalize<U>\n      ? `${T}${CamelToSnakeCase<U>}`\n      : `${T}_${Uncapitalize<CamelToSnakeCase<U>>}`\n    : S;\n\n// Utility type to recursively convert all keys in an object from snake_case to camelCase\nexport type KeysToCamelCase<T> = T extends (infer U)[]\n  ? KeysToCamelCase<U>[]\n  : T extends Date\n    ? T\n    : T extends object\n      ? {\n          [K in keyof T as SnakeToCamelCase<string & K>]: KeysToCamelCase<T[K]>;\n        }\n      : T;\n\n// Utility type to recursively convert all keys in an object from camelCase to snake_case\nexport type KeysToSnakeCase<T> = T extends (infer U)[]\n  ? KeysToSnakeCase<U>[]\n  : T extends Date\n    ? T\n    : T extends object\n      ? {\n          [K in keyof T as CamelToSnakeCase<string & K>]: KeysToSnakeCase<T[K]>;\n        }\n      : T;\n\n// Utility type to convert snake_case to camelCase\nexport type SnakeToCamelCase<S extends string> =\n  S extends `${infer T}_${infer U}`\n    ? `${T}${Capitalize<SnakeToCamelCase<U>>}`\n    : S;\n\n// Convert object keys from camelCase to snake_case recursively\nexport function convertCamelToSnakeCase(obj: any): any {\n  if (Array.isArray(obj)) {\n    return obj.map((v) => convertCamelToSnakeCase(v));\n  }\n  if (obj !== null && typeof obj === 'object' && !(obj instanceof Date)) {\n    return Object.keys(obj).reduce((acc, key) => {\n      const snakeKey = camelToSnakeCase(key);\n      (acc as Record<string, unknown>)[snakeKey] = convertCamelToSnakeCase(\n        (obj as Record<string, unknown>)[key]\n      );\n      return acc;\n    }, {});\n  }\n  return obj;\n}\n\n// Convert object keys from snake_case to camelCase recursively\nexport function convertSnakeToCamelCase(obj: any): any {\n  if (Array.isArray(obj)) {\n    return obj.map((v) => convertSnakeToCamelCase(v));\n  }\n  if (obj !== null && typeof obj === 'object' && !(obj instanceof Date)) {\n    return Object.keys(obj).reduce((acc, key) => {\n      const camelKey = snakeToCamelCase(key);\n      (acc as Record<string, unknown>)[camelKey] = convertSnakeToCamelCase(\n        (obj as Record<string, unknown>)[key]\n      );\n      return acc;\n    }, {});\n  }\n  return obj;\n}\n\n// Convert camelCase string to snake_case\nfunction camelToSnakeCase(str: string): string {\n  return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);\n}\n\n// Convert snake_case string to camelCase\nfunction snakeToCamelCase(str: string): string {\n  return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());\n}\n","/**\n * Conversation Service\n * Business logic service for conversation lifecycle operations using SammyApiClient.\n * Handles conversation status updates and lifecycle management.\n *\n * API endpoints:\n * - markReady: PATCH /{conversation_id}/mark-ready - Updates conversation status to trigger memory processing\n */\n\nimport { AgentMode } from '@/types/agent-types';\nimport { convertCamelToSnakeCase } from '@/utils/case-conversion';\n\nimport { SammyApiClient } from './sammy-api-client';\n\n// ============================================================================\n// CONVERSATION SERVICE IMPLEMENTATION\n// ============================================================================\n\n// Service interface for conversation operations\nexport interface ConversationService {\n  markConversationReadyToProcess: (\n    conversationId: string\n  ) => Promise<MarkConversationReadyResponse>;\n  sendMessages: (payload: MessagePayload) => Promise<void>;\n  escalateConversation: (\n    request: EscalateConversationRequest\n  ) => Promise<EscalateConversationResponse>;\n}\n\n// Response interface for mark-ready operation\nexport interface MarkConversationReadyResponse {\n  success: boolean;\n  conversationId?: string;\n  message?: string;\n}\n\nexport interface MessagePayload {\n  messages: Array<{\n    sender: 'agent' | 'user';\n    text: string;\n    timestamp: string;\n  }>;\n  agentMode: AgentMode;\n  conversationId: string;\n  sammyThreeOrganisationFeatureId?: string;\n  guideId?: string;\n}\n\n// Escalation request/response interfaces\nexport interface EscalateConversationRequest {\n  conversation_id: string;\n}\n\nexport interface EscalateConversationResponse {\n  success: boolean;\n  message: string;\n  conversation_id: string;\n}\n\n/**\n * Creates the mark conversation ready service\n * Updates conversation status which triggers processing to update memories for next session\n */\nexport const markConversationReady =\n  (apiCaller: SammyApiClient) =>\n  async (conversationId: string): Promise<MarkConversationReadyResponse> => {\n    try {\n      const response = await apiCaller.patch<{\n        success: boolean;\n        message?: string;\n      }>(`/api/v1/conversations/${conversationId}/mark-ready`, {});\n\n      return {\n        success: true,\n        conversationId,\n        message:\n          response.message || 'Conversation marked as ready for processing',\n      };\n    } catch (error) {\n      console.error(\n        '[ConversationService] Failed to mark conversation ready:',\n        error\n      );\n      return {\n        success: false,\n        conversationId,\n        message:\n          error instanceof Error ? error.message : 'Unknown error occurred',\n      };\n    }\n  };\n\nexport const sendMessages =\n  (apiCaller: SammyApiClient) =>\n  async (payload: MessagePayload): Promise<void> => {\n    try {\n      const apiPayload = convertCamelToSnakeCase(payload);\n\n      await apiCaller.post('/api/v1/conversations/messages', apiPayload);\n    } catch (error) {\n      console.error('[ConversationService] Failed to send messages:', error);\n      throw error;\n    }\n  };\n\n/**\n * Escalates a conversation to human support\n */\nexport const escalateConversation =\n  (apiCaller: SammyApiClient) =>\n  async (\n    request: EscalateConversationRequest\n  ): Promise<EscalateConversationResponse> => {\n    try {\n      const response = await apiCaller.post<EscalateConversationResponse>(\n        '/api/v1/conversations/escalate',\n        request\n      );\n\n      console.log(\n        '[ConversationService] Conversation escalated successfully:',\n        response\n      );\n      return response;\n    } catch (error) {\n      console.error(\n        '[ConversationService] Failed to escalate conversation:',\n        error\n      );\n\n      // Return error response following the same pattern as other methods\n      return {\n        success: false,\n        message:\n          error instanceof Error\n            ? error.message\n            : 'Failed to escalate conversation',\n        conversation_id: request.conversation_id,\n      };\n    }\n  };\n\n/**\n * Factory function to create conversation service with all methods\n */\nexport const createConversationServices = (\n  apiCaller: SammyApiClient\n): ConversationService => ({\n  markConversationReadyToProcess: markConversationReady(apiCaller),\n  sendMessages: sendMessages(apiCaller),\n  escalateConversation: escalateConversation(apiCaller),\n});\n","/**\n * Core Services\n * Business logic services that use the SammyApiCaller for specific API operations.\n *\n * Services included:\n * - generateEphemeralToken: Generates ephemeral tokens for live API connections\n * - getSystemPrompt: Retrieves system prompts based on agent mode and context\n */\n\nimport {\n  EphemeralTokenConstraints,\n  InstantiateSessionRequest,\n  InstantiateSessionResponse,\n  SystemPromptQuery,\n} from '@/types/services';\nimport {\n  convertCamelToSnakeCase,\n  KeysToSnakeCase,\n} from '@/utils/case-conversion';\n\nimport { SammyApiClient } from './sammy-api-client';\n\nexport interface CoreService {\n  generateEphemeralToken: (\n    constraints: EphemeralTokenConstraints\n  ) => Promise<EphemeralTokenResponse>;\n  getSystemPrompt: (\n    query: SystemPromptQuery\n  ) => Promise<{ systemPrompt: string }>;\n  instantiateSession: (\n    instantiateSessionRequest: InstantiateSessionRequest\n  ) => Promise<InstantiateSessionResponse>;\n}\n\n/**\n * Creates the ephemeral token service\n */\nexport interface EphemeralTokenResponse {\n  token: string;\n  ephemeral_token?: string;\n  expiresAt?: string;\n}\nconst generateEphemeralToken =\n  (apiCaller: SammyApiClient) =>\n  async (\n    constraints: EphemeralTokenConstraints\n  ): Promise<EphemeralTokenResponse> => {\n    try {\n      const response = await apiCaller.post<{ ephemeral_token: string }>(\n        '/api/v1/sammy-three/generate-ephemeral-token-using-jwt',\n        {\n          live_connect_constraints: {\n            model: constraints.model,\n            config: constraints.config,\n          },\n          http_options: {\n            api_version: 'v1alpha',\n          },\n        }\n      );\n\n      return {\n        token: response.ephemeral_token,\n      };\n    } catch (error) {\n      console.error('Ephemeral token generation error:', error);\n      throw error;\n    }\n  };\n\n/**\n * Creates the system prompt service\n */\nconst getSystemPrompt =\n  (apiCaller: SammyApiClient) =>\n  async (query: SystemPromptQuery): Promise<{ systemPrompt: string }> => {\n    try {\n      const params = new URLSearchParams();\n      const convertedParams = convertCamelToSnakeCase(query);\n      Object.entries(convertedParams).forEach(([key, value]) => {\n        if (value) params.append(key, value as string);\n      });\n      const response = await apiCaller.get<{ system_prompt: string }>(\n        `/api/v1/sammy-three/system-prompt?${params.toString()}`\n      );\n\n      return {\n        systemPrompt: response.system_prompt,\n      };\n    } catch (error) {\n      console.error('System prompt retrieval error:', error);\n      throw error;\n    }\n  };\n\nconst instantiateSession =\n  (apiCaller: SammyApiClient) =>\n  async (\n    request: InstantiateSessionRequest\n  ): Promise<InstantiateSessionResponse> => {\n    try {\n      const body = convertCamelToSnakeCase(request);\n\n      const response = await apiCaller.post<\n        KeysToSnakeCase<InstantiateSessionResponse>\n      >('/api/v1/sammy-three/instantiate-session', body);\n\n      return {\n        conversationId: response.conversation_id,\n      };\n    } catch (error) {\n      console.error('Instantiate session error:', error);\n      throw error;\n    }\n  };\n/**\n * Creates all core services using the provided API caller\n */\nexport const createCoreServices = (apiCaller: SammyApiClient): CoreService => {\n  return {\n    generateEphemeralToken: generateEphemeralToken(apiCaller),\n    getSystemPrompt: getSystemPrompt(apiCaller),\n    instantiateSession: instantiateSession(apiCaller),\n  };\n};\n","/**\n * Memory Service\n * Business logic service for memory search and retrieval operations using SammyApiClient.\n * Provides memory-specific functionality for search and getAllMemories operations.\n * Configured to work with the native Supabase backend API v2.0.\n */\n\nimport { MemoryEntry, SearchRequest } from '@/types/memory-types';\n\nimport { SammyApiClient } from './sammy-api-client';\n\n// ============================================================================\n// MEMORY SERVICE IMPLEMENTATION\n// ============================================================================\n\n// Service interface for memory operations\nexport interface MemoryService {\n  getAll: (\n    sammyThreeOrganisationFeatureId: string | null,\n    retryOptions?: { retryOnEmpty?: boolean }\n  ) => Promise<MemoryEntry[]>;\n  search: (\n    query: string,\n    options: Omit<SearchRequest, 'query'>\n  ) => Promise<MemoryEntry[]>;\n}\n\n/**\n * Creates the memory retrieval service\n */\nexport const getAllMemories =\n  (apiCaller: SammyApiClient) =>\n  async (\n    sammyThreeOrganisationFeatureId: string | null,\n    retryOptions?: { retryOnEmpty?: boolean }\n  ): Promise<MemoryEntry[]> => {\n    try {\n      const params = new URLSearchParams();\n      if (sammyThreeOrganisationFeatureId) {\n        params.set(\n          'sammy_three_organisation_feature_id',\n          sammyThreeOrganisationFeatureId\n        );\n      }\n\n      const path = params.toString()\n        ? `/api/v1/memories?${params}`\n        : '/api/v1/memories';\n\n      const response = await apiCaller.get<MemoryEntry[]>(path, {\n        retries: retryOptions?.retryOnEmpty ? 5 : 3,\n      });\n\n      if (retryOptions?.retryOnEmpty && response.length === 0) {\n        console.log(\n          '[MEMORY SERVICE] Got 0 memories, retries handled by API client'\n        );\n      }\n\n      return response;\n    } catch (error) {\n      console.error('Memory getAll operation error:', error);\n      throw error;\n    }\n  };\n\n/**\n * Creates the memory search service\n */\nexport const searchMemories =\n  (apiCaller: SammyApiClient) =>\n  async (\n    query: string,\n    options: Omit<SearchRequest, 'query'>\n  ): Promise<MemoryEntry[]> => {\n    if (!query) {\n      throw new Error('MemoryService.search: query is required');\n    }\n\n    try {\n      const payload: SearchRequest = {\n        query,\n        ...options,\n      };\n\n      const response = await apiCaller.post<MemoryEntry[]>(\n        '/api/v1/memories/search',\n        payload\n      );\n\n      return response;\n    } catch (error) {\n      console.error('Memory search operation error:', error);\n      throw error;\n    }\n  };\n\n/**\n * Creates all memory services using the provided API caller\n */\nexport const createMemoryServices = (\n  apiCaller: SammyApiClient\n): MemoryService => {\n  return {\n    getAll: getAllMemories(apiCaller),\n    search: searchMemories(apiCaller),\n  };\n};\n","/* eslint-disable @typescript-eslint/no-explicit-any */\n/**\n * Service interfaces for Sammy Agent\n * These define the contracts for external dependencies\n */\n\nimport { AgentMode } from './agent-types';\n\n// Types for service method parameters and responses\nexport interface EphemeralTokenConstraints {\n  config: {\n    response_modalities?: string[];\n    session_resumption?: Record<string, any>;\n  };\n  model: string;\n}\n\nexport interface EphemeralTokenResponse {\n  token: string;\n  ephemeral_token?: string; // Some APIs return it as ephemeral_token\n  expiresAt?: string;\n}\n\nexport interface MarkConversationReadyResponse {\n  success: boolean;\n  conversationId?: string;\n  message?: string;\n}\n\n/**\n * System prompt query\n * This is a loose type to allow users to provide any function structure they want\n */\nexport type SystemPromptQuery = Record<string, any>;\n\n/**\n * Schema for instantiating a session request\n */\nexport interface InstantiateSessionRequest {\n  /** The mode of the agent */\n  agentMode: AgentMode;\n  /** The ID of the conversation to instantiate */\n  conversationId: string;\n  /** Optional external user ID for the conversation */\n  externalUserId?: string;\n  /** Optional feature ID for filtering prompts */\n  sammyThreeOrganisationFeatureId?: string;\n}\n\n/**\n * Schema for instantiating a session response\n */\nexport interface InstantiateSessionResponse {\n  /** The ID of the conversation */\n  conversationId: string;\n}\n\n/**\n * Error types that services might throw\n */\nexport class ServiceError extends Error {\n  constructor(\n    message: string,\n    public code: string,\n    public status?: number,\n    public details?: any\n  ) {\n    super(message);\n    this.name = 'ServiceError';\n  }\n}\n\nexport class TokenExpiredError extends ServiceError {\n  constructor(message = 'Token expired', details?: any) {\n    super(message, 'TOKEN_EXPIRED', 401, details);\n  }\n}\n","/**\n * Sammy API Caller\n * A generic HTTP client configured for the Sammy API with built-in authentication,\n * error handling, and retry logic. This client is completely route-agnostic and\n * provides standard HTTP methods (GET, POST, PUT, PATCH, DELETE).\n */\n\nimport { AuthConfig } from '@/types/agent-types';\nimport { ServiceError, TokenExpiredError } from '@/types/services';\n\nexport interface ApiCallOptions {\n  path: string;\n  skipValidation?: boolean;\n  body?: unknown;\n  headers?: HeadersInit;\n  retries?: number;\n  timeout?: number;\n}\n\nexport interface ApiResponse<T = unknown> {\n  data: T;\n  headers: Headers;\n  status: number;\n}\n\nexport class SammyApiClient {\n  private token: string;\n  private baseUrl: string;\n  private onTokenExpired?: () => void;\n  private isTokenValid: boolean = true;\n\n  constructor(authConfig: AuthConfig) {\n    this.token = authConfig.token;\n    this.baseUrl = authConfig.baseUrl || 'https://api-dev.sammylabs.com';\n    this.onTokenExpired = authConfig.onTokenExpired;\n  }\n\n  /**\n   * Updates the authentication configuration\n   */\n  updateAuthConfig(authConfig: Partial<AuthConfig>): void {\n    if (authConfig.token) {\n      this.token = authConfig.token;\n      this.isTokenValid = true;\n    }\n    if (authConfig.baseUrl) {\n      this.baseUrl = authConfig.baseUrl;\n    }\n    if (authConfig.onTokenExpired !== undefined) {\n      this.onTokenExpired = authConfig.onTokenExpired;\n    }\n  }\n\n  /**\n   * Validates the JWT token\n   */\n  private async validateToken(): Promise<boolean> {\n    try {\n      const response = await fetch(`${this.baseUrl}/validate`, {\n        headers: {\n          Authorization: `Bearer ${this.token}`,\n        },\n      });\n\n      if (!response.ok) {\n        if (response.status === 401) {\n          this.isTokenValid = false;\n          if (this.onTokenExpired) {\n            this.onTokenExpired();\n          }\n          throw new TokenExpiredError('Token validation failed');\n        }\n        return false;\n      }\n\n      this.isTokenValid = true;\n      return true;\n    } catch (error) {\n      console.error('Token validation error:', error);\n      if (error instanceof TokenExpiredError) {\n        throw error;\n      }\n      return false;\n    }\n  }\n\n  /**\n   * Generic HTTP request method with retry logic\n   */\n  private async makeRequest<T = unknown>(\n    method: string,\n    options: ApiCallOptions\n  ): Promise<T> {\n    const {\n      path,\n      body,\n      headers = {},\n      timeout = 30000,\n      retries = 3,\n      skipValidation = false,\n    } = options;\n\n    // Validate token before making request (unless skipValidation is true)\n    if (!skipValidation && !(await this.validateToken())) {\n      throw new ServiceError(\n        'Invalid token - cannot make API call',\n        'AUTH_ERROR',\n        401\n      );\n    }\n\n    const url = new URL(path, this.baseUrl).toString();\n\n    // Check if body is FormData\n    const isFormData = body instanceof FormData;\n\n    // Build headers\n    const requestHeaders: Record<string, string> = {\n      Authorization: `Bearer ${this.token}`,\n    };\n\n    // Handle headers properly for FormData\n    const headersRecord = headers as Record<string, string>;\n    Object.entries(headersRecord).forEach(([key, value]) => {\n      if (key === 'Content-Type' && (isFormData || value === '')) {\n        // Skip Content-Type for FormData or when explicitly set to empty string\n        return;\n      }\n      requestHeaders[key] = value;\n    });\n\n    // Set default Content-Type if not FormData and not already set\n    if (!isFormData && !requestHeaders['Content-Type']) {\n      requestHeaders['Content-Type'] = 'application/json';\n    }\n\n    const requestConfig: RequestInit = {\n      method,\n      headers: requestHeaders,\n      body: isFormData\n        ? (body as FormData)\n        : body\n          ? JSON.stringify(body)\n          : undefined,\n    };\n\n    return this.attemptRequest<T>(\n      method,\n      path,\n      url,\n      requestConfig,\n      timeout,\n      retries,\n      0\n    );\n  }\n\n  /**\n   * Recursive helper method for request attempts with retry logic\n   */\n  private async attemptRequest<T = unknown>(\n    method: string,\n    path: string,\n    url: string,\n    requestConfig: RequestInit,\n    timeout: number,\n    maxRetries: number,\n    attempt: number\n  ): Promise<T> {\n    // Add timeout using AbortController\n    const controller = new AbortController();\n    const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n    try {\n      const response = await fetch(url, {\n        ...requestConfig,\n        signal: controller.signal,\n      });\n\n      clearTimeout(timeoutId);\n\n      if (!response.ok) {\n        const errorDetails = await this.getErrorDetails(response);\n\n        if (response.status === 401) {\n          this.isTokenValid = false;\n          if (this.onTokenExpired) {\n            this.onTokenExpired();\n          }\n          throw new TokenExpiredError(\n            'API request failed: Token expired',\n            errorDetails\n          );\n        }\n\n        // Don't retry on 4xx errors (except 429)\n        if (\n          response.status >= 400 &&\n          response.status < 500 &&\n          response.status !== 429\n        ) {\n          throw new ServiceError(\n            `Failed to ${method} ${path}`,\n            'CLIENT_ERROR',\n            response.status,\n            errorDetails\n          );\n        }\n\n        // Retry on 5xx errors or 429 (rate limiting)\n        if (attempt === maxRetries) {\n          throw new ServiceError(\n            `Failed to ${method} ${path}`,\n            'SERVER_ERROR',\n            response.status,\n            errorDetails\n          );\n        }\n\n        // Wait before retry (exponential backoff)\n        await this.delay(Math.pow(2, attempt) * 1000);\n        return this.attemptRequest<T>(\n          method,\n          path,\n          url,\n          requestConfig,\n          timeout,\n          maxRetries,\n          attempt + 1\n        );\n      }\n\n      // Handle empty responses\n      if (\n        response.status === 204 ||\n        response.headers.get('content-length') === '0'\n      ) {\n        return undefined as T;\n      }\n\n      const data = await response.json();\n      return data as T;\n    } catch (error) {\n      clearTimeout(timeoutId);\n\n      // Don't retry on auth errors or client errors\n      if (error instanceof TokenExpiredError || error instanceof ServiceError) {\n        throw error;\n      }\n\n      const currentError = error as Error;\n\n      // Don't retry on the last attempt\n      if (attempt === maxRetries) {\n        throw new ServiceError(\n          `Failed to ${method} ${path} after ${maxRetries + 1} attempts`,\n          'NETWORK_ERROR',\n          undefined,\n          { error: currentError, path, method }\n        );\n      }\n\n      // Wait before retry\n      await this.delay(Math.pow(2, attempt) * 1000);\n      return this.attemptRequest<T>(\n        method,\n        path,\n        url,\n        requestConfig,\n        timeout,\n        maxRetries,\n        attempt + 1\n      );\n    }\n  }\n\n  /**\n   * Helper method to extract error details from response\n   */\n  private async getErrorDetails(response: Response): Promise<unknown> {\n    try {\n      return await response.json();\n    } catch {\n      return { message: response.statusText };\n    }\n  }\n\n  /**\n   * Utility method for delays\n   */\n  private delay(ms: number): Promise<void> {\n    return new Promise((resolve) => setTimeout(resolve, ms));\n  }\n\n  /**\n   * GET request\n   */\n  async get<T = unknown>(\n    path: string,\n    options: Omit<ApiCallOptions, 'body' | 'path'> = {}\n  ): Promise<T> {\n    return this.makeRequest<T>('GET', { path, ...options });\n  }\n\n  /**\n   * POST request\n   */\n  async post<T = unknown>(\n    path: string,\n    body?: unknown,\n    options: Omit<ApiCallOptions, 'body' | 'path'> = {}\n  ): Promise<T> {\n    return this.makeRequest<T>('POST', { path, body, ...options });\n  }\n\n  /**\n   * PUT request\n   */\n  async put<T = unknown>(\n    path: string,\n    body?: unknown,\n    options: Omit<ApiCallOptions, 'body' | 'path'> = {}\n  ): Promise<T> {\n    return this.makeRequest<T>('PUT', { path, body, ...options });\n  }\n\n  /**\n   * PATCH request\n   */\n  async patch<T = unknown>(\n    path: string,\n    body?: unknown,\n    options: Omit<ApiCallOptions, 'body' | 'path'> = {}\n  ): Promise<T> {\n    return this.makeRequest<T>('PATCH', { path, body, ...options });\n  }\n\n  /**\n   * DELETE request\n   */\n  async delete<T = unknown>(\n    path: string,\n    options: Omit<ApiCallOptions, 'body' | 'path'> = {}\n  ): Promise<T> {\n    return this.makeRequest<T>('DELETE', { path, ...options });\n  }\n\n  /**\n   * Get current token validity status\n   */\n  getTokenValidityStatus(): boolean {\n    return this.isTokenValid;\n  }\n}\n","/**\n * Guides Service\n * Business logic service for walkthrough guide operations using SammyApiClient.\n * Handles guide validation and retrieval for walkthrough functionality.\n *\n * API endpoints:\n * - fetchGuide: GET /guides/{id} - Fetches and validates a single guide by ID\n * - getUserGuides: GET /guides - Lists user's guides with completion status\n */\n\nimport { KeysToSnakeCase } from '@/utils/case-conversion';\n\nimport { SammyApiClient } from './sammy-api-client';\n\n// ============================================================================\n// GUIDES SERVICE IMPLEMENTATION\n// ============================================================================\n\n// Service interface for guide operations\nexport interface GuidesService {\n  fetchGuide: (guideId: string) => Promise<SammyGuide | null>;\n  getUserGuides: () => Promise<SammyGuide[]>;\n}\n\n// Guide interface (camelCase, matching what API actually returns)\nexport interface SammyGuide {\n  guideId: string;\n  isCompleted: boolean;\n  organisationId: string;\n  prompt: string;\n  title: string;\n}\n\n// Response interface for list guides operation\nexport interface ListGuidesResponse {\n  guides: SammyGuide[];\n  total: number;\n}\n\n/**\n * Creates the guide validation service\n * Fetches and validates a guide by ID and returns guide data if valid for user's organization\n */\nexport const fetchGuide =\n  (apiCaller: SammyApiClient) =>\n  async (guideId: string): Promise<SammyGuide | null> => {\n    console.log('HERE fetchGuide', guideId);\n    try {\n      const response = await apiCaller.get<KeysToSnakeCase<SammyGuide>>(\n        `/api/v1/sammy-three/guides/${guideId}`\n      );\n\n      // Transform response from snake_case to camelCase (following core-service.ts pattern)\n      return {\n        guideId: response.guide_id,\n        title: response.title,\n        prompt: response.prompt,\n        organisationId: response.organisation_id,\n        isCompleted: response.is_completed,\n      };\n    } catch (error) {\n      console.error('[GuidesService] Failed to validate guide:', error);\n      return null;\n    }\n  };\n\n/**\n * Creates the user guides retrieval service\n * Fetches user's available guides with completion status\n */\nexport const getUserGuides =\n  (apiCaller: SammyApiClient) => async (): Promise<SammyGuide[]> => {\n    try {\n      const response = await apiCaller.get<KeysToSnakeCase<ListGuidesResponse>>(\n        '/api/v1/sammy-three/guides'\n      );\n\n      // Transform each guide from snake_case to camelCase (following core-service.ts pattern)\n      return response.guides.map((guide) => ({\n        guideId: guide.guide_id,\n        title: guide.title,\n        prompt: guide.prompt,\n        organisationId: guide.organisation_id,\n        isCompleted: guide.is_completed,\n      }));\n    } catch (error) {\n      console.error('[GuidesService] Failed to fetch user guides:', error);\n      return [];\n    }\n  };\n\n/**\n * Factory function to create guides service with all methods\n */\nexport const createGuidesServices = (\n  apiCaller: SammyApiClient\n): GuidesService => ({\n  fetchGuide: fetchGuide(apiCaller),\n  getUserGuides: getUserGuides(apiCaller),\n});\n","/**\n * Hook to manage guides functionality without requiring a separate context.\n * Combines guide data management, URL parameter detection, and walkthrough starting.\n */\n\nimport { useCallback, useState } from 'react';\n\nimport { SammyGuide } from '@/lib/sammy-api';\nimport { AgentMode, AuthConfig } from '@/types/agent-types';\n\nimport { useGuides } from './use-guides';\nimport { useGuidesQueryParams } from './use-guides-query-params';\n\nexport interface UseGuidesManagerOptions {\n  onStartAgent?: (options: {\n    guideId: string;\n    agentMode: AgentMode;\n  }) => Promise<boolean>;\n  authConfig?: AuthConfig;\n  autoStartFromURL?: boolean;\n  debug?: boolean;\n  enabled?: boolean; // Whether guides are enabled at all\n  onWalkthroughStart?: (guideId: string) => void;\n  queryParamName?: string;\n}\n\nexport interface GuidesState {\n  // Query parameter utilities\n  checkForGuideInUrl: () => Promise<boolean>;\n  // Guide data (always-on when enabled)\n  currentGuide: SammyGuide | null;\n  guideError: Error | null;\n  isLoadingGuide: boolean;\n  isLoadingUserGuides: boolean;\n  markGuideAsCompleted: (guideId: string) => void;\n\n  // Actions\n  refreshGuide: () => void;\n  refreshUserGuides: () => void;\n  userGuides: SammyGuide[];\n  userGuidesError: Error | null;\n  cleanupUrlParams: () => void;\n\n  // Detected walkthrough from URL\n  detectedWalkthroughId: string | null;\n  startWalkthrough: (guideId: string) => Promise<boolean>;\n}\n\nexport function useGuidesManager({\n  authConfig,\n  autoStartFromURL = false,\n  onStartAgent,\n  onWalkthroughStart,\n  queryParamName = 'walkthrough',\n  debug = false,\n  enabled = false,\n}: UseGuidesManagerOptions): GuidesState | null {\n  // State for detected walkthrough ID\n  const [detectedWalkthroughId, setDetectedWalkthroughId] = useState<\n    string | null\n  >(null);\n\n  // Use guides hook for guide data management\n  // Pass authConfig only when guides are enabled\n  const {\n    currentGuide,\n    userGuides,\n    isLoadingGuide,\n    isLoadingUserGuides,\n    guideError,\n    userGuidesError,\n    refreshGuide,\n    refreshUserGuides,\n    markGuideAsCompleted,\n  } = useGuides({\n    authConfig: enabled ? authConfig : undefined,\n    walkthroughId: enabled ? detectedWalkthroughId || undefined : undefined,\n  });\n\n  // Manual walkthrough start function\n  const startWalkthrough = useCallback(\n    async (guideId: string) => {\n      if (!enabled) return false;\n\n      console.log(`[useGuidesManager] Starting walkthrough: ${guideId}`);\n      setDetectedWalkthroughId(guideId);\n      onWalkthroughStart?.(guideId);\n\n      if (onStartAgent) {\n        try {\n          const success = await onStartAgent({\n            agentMode: AgentMode.USER,\n            guideId: guideId,\n          });\n\n          if (success) {\n            markGuideAsCompleted(guideId);\n          }\n          return success;\n        } catch (error) {\n          console.error(\n            '[useGuidesManager] Error starting walkthrough:',\n            error\n          );\n          return false;\n        }\n      }\n      return false;\n    },\n    [enabled, onWalkthroughStart, onStartAgent, markGuideAsCompleted]\n  );\n\n  // Callback for when a guide is detected in URL\n  const handleGuideDetected = useCallback(\n    async (guideId: string): Promise<boolean> => {\n      if (!enabled) return false;\n\n      console.log(`[useGuidesManager] Guide detected from URL: ${guideId}`);\n      setDetectedWalkthroughId(guideId);\n\n      // If autoStartFromURL is enabled, start the agent immediately\n      if (autoStartFromURL) {\n        console.log(\n          `[useGuidesManager] 🚀 Auto-starting walkthrough: ${guideId}`\n        );\n        onWalkthroughStart?.(guideId);\n\n        if (onStartAgent) {\n          try {\n            const success = await onStartAgent({\n              agentMode: AgentMode.USER,\n              guideId: guideId,\n            });\n\n            if (success) {\n              markGuideAsCompleted(guideId);\n              return true;\n            }\n          } catch (error) {\n            console.error('[useGuidesManager] Error starting agent:', error);\n          }\n        }\n      } else {\n        // Just set the detected ID, don't auto-start\n        console.log(\n          `[useGuidesManager] Guide detected but auto-start is disabled`\n        );\n      }\n\n      return false;\n    },\n    [\n      enabled,\n      autoStartFromURL,\n      onWalkthroughStart,\n      onStartAgent,\n      markGuideAsCompleted,\n    ]\n  );\n\n  // Use the query params hook for URL detection\n  const { checkQueryParameters, cleanupQueryParams } = useGuidesQueryParams({\n    checkQueryOnMount: enabled,\n    onGuideDetected: enabled ? handleGuideDetected : undefined,\n    queryParamName,\n    debug: enabled ? debug : false,\n  });\n\n  // If guides are not enabled, return null\n  if (!enabled) {\n    return null;\n  }\n\n  return {\n    currentGuide,\n    userGuides,\n    isLoadingGuide,\n    isLoadingUserGuides,\n    guideError,\n    userGuidesError,\n    detectedWalkthroughId,\n    refreshGuide,\n    refreshUserGuides,\n    markGuideAsCompleted,\n    startWalkthrough,\n    checkForGuideInUrl: checkQueryParameters,\n    cleanupUrlParams: cleanupQueryParams,\n  };\n}\n","/**\n * Core types for the Sammy Agent\n */\n\nimport { RefObject } from 'react';\n\nimport { LiveServerToolCall } from '@google/genai';\n\nimport { SammyAgentCore } from '@/lib/core/sammy-agent-core';\n\nimport { CaptureMethod, ScreenCaptureCallbacks } from './media-types';\nimport {\n  ObservabilityConfig,\n  SessionTrace,\n  TraceEvent,\n} from './observability-types';\n\nexport interface AgentState {\n  activeSession: AgentSession | null;\n  agentVolume: number;\n  error: Error | null;\n  isConnected: boolean;\n  isConnecting: boolean;\n  isStreaming: boolean;\n  userVolume: number;\n}\n\nexport interface UpdateAgentState {\n  (variable: keyof AgentState, value: AgentState[keyof AgentState]): void;\n}\n\nexport interface AgentStateContextType {\n  agentCoreRef: RefObject<SammyAgentCore | null>;\n  agentState: AgentState;\n  updateAgentState: UpdateAgentState;\n}\n\nexport enum AgentMode {\n  // Training mode\n  ADMIN = 'ADMIN',\n  // Sammy mode, equivalent to user mode internally\n  SAMMY = 'SAMMY',\n  // User mode\n  USER = 'USER',\n}\n\nexport interface AgentSession {\n  agentMode: AgentMode;\n  conversationId: string;\n  sessionId: string;\n  startedAt: Date;\n  sammyThreeOrganisationFeatureId?: string;\n  guideId?: string;\n}\n\nexport interface AuthConfig {\n  token: string;\n  baseUrl?: string;\n  onTokenExpired?: () => void;\n}\n\nexport interface SammyAgentCallbacks {\n  onConnectionStateChange?: (connected: boolean) => void;\n  onError?: (error: unknown) => void;\n  onMemoryUpdate?: (result: unknown) => void;\n  onObservabilityEvent?: (event: TraceEvent) => void;\n  onSessionTraceComplete?: (trace: SessionTrace) => void;\n  onTokenExpired?: () => void;\n  onToolCall?: (tool: LiveServerToolCall) => void;\n  onTurnComplete?: (summary: { agent: string; user: string }) => void;\n}\n\nexport interface SammyAgentConfigProps {\n  auth: AuthConfig;\n\n  captureConfig?: {\n    frameRate?: number;\n    quality?: number;\n  };\n\n  // Capture configuration\n  captureMethod?: CaptureMethod;\n\n  debugAudioPerformance?: boolean; // Enable audio performance debugging\n  // Agent behavior\n  debugLogs?: boolean;\n\n  // Voice settings\n  defaultVoice?: string;\n\n  // Model settings\n  model?: string;\n\n  // Observability configuration\n  observability?: ObservabilityConfig;\n}\n\nexport interface SammyAgentConfig extends SammyAgentConfigProps {\n  screenCaptureCallbacks: ScreenCaptureCallbacks;\n}\n","/**\n * Hook to handle guides-related query parameters\n * Detects ?walkthrough=guide-id in URL and triggers guide start\n */\n\nimport { useCallback, useEffect } from 'react';\n\ninterface UseGuidesQueryParamsOptions {\n  // Callback when a guide is detected in URL\n  onGuideDetected?: (guideId: string) => Promise<boolean>;\n  // Whether to check for query params on mount\n  checkQueryOnMount?: boolean;\n  // Enable debug logging\n  debug?: boolean;\n  // Query parameter name to look for (default: 'walkthrough')\n  queryParamName?: string;\n}\n\ninterface UseGuidesQueryParamsReturn {\n  // Manually check for query parameters\n  checkQueryParameters: () => Promise<boolean>;\n  // Clean up all guide-related query parameters\n  cleanupQueryParams: () => void;\n}\n\n/**\n * Hook to detect and handle guide walkthrough query parameters\n *\n * Example URLs:\n * - https://app.com?walkthrough=guide-123\n * - https://app.com/page?walkthrough=onboarding-guide\n */\nexport function useGuidesQueryParams({\n  checkQueryOnMount = true,\n  onGuideDetected,\n  queryParamName = 'walkthrough',\n  debug = false,\n}: UseGuidesQueryParamsOptions = {}): UseGuidesQueryParamsReturn {\n  // Helper to log debug messages\n  const log = useCallback(\n    (message: string, data?: any) => {\n      if (debug) {\n        console.log(`[useGuidesQueryParams] ${message}`, data || '');\n      }\n    },\n    [debug]\n  );\n\n  // Clean up the guide ID parameter from URL\n  const cleanupGuideParam = useCallback(() => {\n    if (typeof window === 'undefined' || !window.history?.replaceState) {\n      return;\n    }\n\n    try {\n      const currentParams = new URLSearchParams(window.location.search);\n\n      if (currentParams.has(queryParamName)) {\n        currentParams.delete(queryParamName);\n\n        const cleanUrl =\n          window.location.pathname +\n          (currentParams.toString() ? `?${currentParams.toString()}` : '') +\n          window.location.hash;\n\n        window.history.replaceState({}, '', cleanUrl);\n        log(`Cleaned up '${queryParamName}' parameter from URL`);\n      }\n    } catch (error) {\n      console.warn(\n        '[useGuidesQueryParams] Error cleaning up URL parameter:',\n        error\n      );\n    }\n  }, [queryParamName, log]);\n\n  // Check for guide ID in query parameters\n  const checkQueryParameters = useCallback(async (): Promise<boolean> => {\n    if (typeof window === 'undefined') {\n      return false;\n    }\n\n    try {\n      const urlParams = new URLSearchParams(window.location.search);\n      const guideId = urlParams.get(queryParamName);\n\n      if (!guideId) {\n        log('No guide ID found in query parameters');\n        return false;\n      }\n\n      log(`Guide detected in URL: ${guideId}`);\n\n      // If we have a callback, trigger the guide\n      if (onGuideDetected) {\n        log(`Starting guide: ${guideId}`);\n\n        try {\n          const success = await onGuideDetected(guideId);\n\n          if (success) {\n            log(`Guide started successfully: ${guideId}`);\n            // Clean up the parameter after successful start\n            cleanupGuideParam();\n            return true;\n          } else {\n            log(`Failed to start guide: ${guideId}`);\n            // Still clean up to prevent repeated attempts\n            cleanupGuideParam();\n            return false;\n          }\n        } catch (error) {\n          console.error('[useGuidesQueryParams] Error starting guide:', error);\n          // Clean up even on error to prevent loops\n          cleanupGuideParam();\n          return false;\n        }\n      } else {\n        log('No onGuideDetected callback provided');\n        return false;\n      }\n    } catch (error) {\n      console.error(\n        '[useGuidesQueryParams] Error checking query parameters:',\n        error\n      );\n      return false;\n    }\n  }, [queryParamName, onGuideDetected, cleanupGuideParam, log]);\n\n  // Clean up all guide-related query parameters\n  const cleanupQueryParams = useCallback(() => {\n    if (typeof window === 'undefined' || !window.history?.replaceState) {\n      return;\n    }\n\n    try {\n      const urlParams = new URLSearchParams(window.location.search);\n      let updated = false;\n\n      // List of parameters to clean up\n      const paramsToRemove = [\n        queryParamName,\n        'guide_id', // Alternative parameter names\n        'walkthrough_id',\n        'tutorial',\n        'tour',\n      ];\n\n      paramsToRemove.forEach((param) => {\n        if (urlParams.has(param)) {\n          urlParams.delete(param);\n          updated = true;\n          log(`Removing parameter: ${param}`);\n        }\n      });\n\n      if (updated) {\n        const newUrl =\n          window.location.pathname +\n          (urlParams.toString() ? `?${urlParams.toString()}` : '') +\n          window.location.hash;\n\n        window.history.replaceState({}, '', newUrl);\n        log('Cleaned up all guide-related query parameters');\n      }\n    } catch (error) {\n      console.warn(\n        '[useGuidesQueryParams] Error cleaning up query parameters:',\n        error\n      );\n    }\n  }, [queryParamName, log]);\n\n  // Check for guide in query parameters on mount\n  useEffect(() => {\n    if (checkQueryOnMount) {\n      // Use a small delay to ensure everything is initialized\n      const timer = setTimeout(() => {\n        checkQueryParameters();\n      }, 100);\n\n      return () => clearTimeout(timer);\n    }\n  }, [checkQueryOnMount, checkQueryParameters]);\n\n  // Also check when URL changes (for single-page apps)\n  useEffect(() => {\n    if (!checkQueryOnMount) return;\n\n    const handlePopState = () => {\n      log('URL changed, checking for guide parameters');\n      checkQueryParameters();\n    };\n\n    window.addEventListener('popstate', handlePopState);\n    return () => window.removeEventListener('popstate', handlePopState);\n  }, [checkQueryOnMount, checkQueryParameters, log]);\n\n  return {\n    checkQueryParameters,\n    cleanupQueryParams,\n  };\n}\n","import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\n\nimport { audioStutterAnalyzer } from '@/lib/audio/audio-stutter-analyzer';\nimport { GenAILiveClient } from '@/lib/genai-live-client';\nimport { CanvasProcessorManager } from '@/lib/workers/canvas-processor-manager';\nimport {\n  AUDIO_AWARE_CONFIG,\n  DEFAULT_CONFIG,\n  UseRenderCaptureProps,\n  UseRenderCaptureReturn,\n  CriticalRenderEvent,\n} from '@/types/render-capture-types';\nimport { captureFrame } from '@/utils/capture-frame';\nimport {\n  type Context,\n  createScreenshotContext,\n  destroyContext,\n} from '@/utils/screenshot-context';\n\nimport { useAudioAdaptation } from './use-audio-adaptation';\nimport { useConversationRenderEvents } from './use-conversation-render-events';\nimport { useDomChangeDetector } from './use-dom-change-detector';\nimport { usePeriodicCapture } from './use-periodic-capture';\n\nexport function useRenderCapture({\n  contextElementRef,\n  canvasRef,\n  config,\n}: UseRenderCaptureProps): UseRenderCaptureReturn {\n  const lastFrameHashRef = useRef<string>('');\n  const lastSentTimeRef = useRef<number>(0);\n  const lastCaptureTimeRef = useRef<number>(0);\n  const concurrentCaptureRef = useRef<boolean>(false);\n  const hasPendingCaptureRef = useRef<boolean>(false);\n  const [captureTriggered, setCaptureTriggered] = useState(0);\n  const [criticalRenderTriggered, setCriticalRenderTriggered] =\n    useState<CriticalRenderEvent | null>(null);\n  const [isCapturing, setIsCapturing] = useState(false);\n  const clientRef = useRef<GenAILiveClient | null>(null);\n  const screenshotContextRef = useRef<Context | null>(null);\n  const canvasProcessorRef = useRef<CanvasProcessorManager | null>(null);\n  const lastCaptureDurationRef = useRef<number>(0);\n  const capturesPausedRef = useRef<boolean>(false);\n  const slowCaptureCountRef = useRef<number>(0);\n\n  useEffect(() => {\n    console.log('🔧 [RENDER-CAPTURE] Hook initialized:', config);\n    audioStutterAnalyzer.setDebugMode(config.debugAudioPerformance ?? false);\n  }, [config.debugAudioPerformance]);\n\n  const mergedConfig = useMemo(\n    () => ({ ...DEFAULT_CONFIG, ...config }),\n    [config]\n  );\n\n  // Use the audio adaptation hook\n  const { audioActive, audioActiveRef, getCurrentSettings } =\n    useAudioAdaptation({\n      config: mergedConfig,\n      client: clientRef.current,\n      isCapturing,\n    });\n\n  const captureElement = useMemo(\n    () =>\n      config.scope === 'context' ? contextElementRef.current : document.body,\n    [config.scope, contextElementRef]\n  );\n\n  // Initialize canvas processor manager\n  useEffect(() => {\n    if (isCapturing && !canvasProcessorRef.current) {\n      canvasProcessorRef.current = new CanvasProcessorManager(\n        mergedConfig.debugLogs\n      );\n      console.log('🎨 [RENDER] Canvas processor manager created');\n    } else if (!isCapturing && canvasProcessorRef.current) {\n      canvasProcessorRef.current.destroy();\n      canvasProcessorRef.current = null;\n      console.log('🎨 [RENDER] Canvas processor manager destroyed');\n    }\n  }, [isCapturing, mergedConfig.debugLogs]);\n\n  // Create/destroy screenshot context when capturing starts/stops\n  useEffect(() => {\n    if (isCapturing && captureElement && !screenshotContextRef.current) {\n      createScreenshotContext({\n        captureElement,\n        canvasRef,\n        debugLogs: mergedConfig.debugLogs,\n      }).then((context) => {\n        if (context) {\n          screenshotContextRef.current = context;\n        }\n      });\n    } else if (!isCapturing && screenshotContextRef.current) {\n      if (mergedConfig.debugLogs) {\n        console.log('🎬 [RENDER] Destroying screenshot context');\n      }\n      const context = screenshotContextRef.current;\n      screenshotContextRef.current = null;\n      destroyContext(context);\n    }\n  }, [isCapturing, captureElement, canvasRef, mergedConfig.debugLogs]);\n\n  // Wrapped capture frame function\n  const captureFrameCallback = useCallback(\n    async (criticalRender?: CriticalRenderEvent) => {\n      await captureFrame(\n        {\n          canvasRef,\n          clientRef,\n          screenshotContextRef,\n          canvasProcessorRef,\n          lastFrameHashRef,\n          lastSentTimeRef,\n          lastCaptureTimeRef,\n          concurrentCaptureRef,\n          hasPendingCaptureRef,\n          lastCaptureDurationRef,\n          audioActiveRef,\n          capturesPausedRef,\n          slowCaptureCountRef,\n        },\n        {\n          captureElement,\n          mergedConfig,\n          getCurrentSettings,\n          criticalRender,\n        }\n      );\n    },\n    [captureElement, mergedConfig, getCurrentSettings, audioActiveRef]\n  );\n\n  // Critical render trigger callback\n  const triggerCriticalRender = useCallback(\n    (event: CriticalRenderEvent) => {\n      if (mergedConfig.debugLogs) {\n        console.log(\n          `🎯 [CRITICAL-RENDER] Critical render triggered: ${event.reason}`\n        );\n      }\n      setCriticalRenderTriggered(event);\n    },\n    [mergedConfig.debugLogs]\n  );\n\n  // DOM change handler\n  const handleDomChange = useCallback(() => {\n    if (!captureElement || !isCapturing) return;\n\n    // Skip ALL DOM-triggered captures during audio if nuclear option is enabled\n    if (\n      audioActiveRef.current &&\n      AUDIO_AWARE_CONFIG.skipDomCaptureDuringAudio &&\n      mergedConfig.enableAudioAdaptation\n    ) {\n      if (mergedConfig.debugLogs) {\n        console.log(\n          '🚫 [RENDER] DOM change ignored - audio playing with zero stutter mode enabled'\n        );\n      }\n      return;\n    }\n\n    // Check timing constraints BEFORE triggering\n    const now = Date.now();\n    const timeSinceLastCapture = now - lastCaptureTimeRef.current;\n    const { minInterval } = getCurrentSettings();\n\n    if (timeSinceLastCapture < minInterval) {\n      if (mergedConfig.debugLogs) {\n        console.log(\n          `🚫 [RENDER] DOM change ignored - too soon (${timeSinceLastCapture}ms < ${minInterval}ms)`\n        );\n      }\n      return;\n    }\n\n    // Only trigger if not already capturing or pending\n    if (!concurrentCaptureRef.current && !hasPendingCaptureRef.current) {\n      setCaptureTriggered((prev: number) => prev + 1);\n    }\n  }, [\n    mergedConfig.debugLogs,\n    mergedConfig.enableAudioAdaptation,\n    captureElement,\n    canvasRef,\n    isCapturing,\n    getCurrentSettings,\n    audioActiveRef,\n  ]);\n\n  // Use DOM change detector with dynamic debounce based on audio state\n  const { getTimeSinceLastChange } = useDomChangeDetector({\n    target: captureElement,\n    onDomChange: handleDomChange,\n    enabled:\n      isCapturing &&\n      !(\n        audioActive &&\n        AUDIO_AWARE_CONFIG.skipDomCaptureDuringAudio &&\n        mergedConfig.enableAudioAdaptation\n      ),\n    debounceMs:\n      audioActive && mergedConfig.enableAudioAdaptation\n        ? AUDIO_AWARE_CONFIG.audioActiveDomDebounce\n        : mergedConfig.domChangeDetection?.debounceMs,\n    config: mergedConfig.domChangeDetection?.observerConfig,\n  });\n\n  // Effect to capture when triggered by DOM changes\n  useEffect(() => {\n    if (mergedConfig.debugLogs) {\n      console.log('🔄 [RENDER] Capture trigger effect', {\n        captureTriggered,\n        isCapturing,\n        elementReady: !!captureElement,\n        canvasReady: !!canvasRef.current,\n        contextReady: !!screenshotContextRef.current,\n        audioActive: audioActiveRef.current,\n      });\n    }\n    if (captureTriggered > 0 && isCapturing && screenshotContextRef.current) {\n      if (mergedConfig.debugAudioPerformance) {\n        console.log('🔔 [RENDER] DOM-triggered capture initiated');\n      }\n      captureFrameCallback();\n    }\n  }, [\n    captureTriggered,\n    isCapturing,\n    captureFrameCallback,\n    mergedConfig.debugLogs,\n    mergedConfig.debugAudioPerformance,\n  ]);\n\n  // Handle critical render triggers\n  useEffect(() => {\n    if (\n      criticalRenderTriggered &&\n      isCapturing &&\n      screenshotContextRef.current\n    ) {\n      if (mergedConfig.debugAudioPerformance || mergedConfig.debugLogs) {\n        console.log(\n          `🎯 [CRITICAL-RENDER] Executing critical render for: ${criticalRenderTriggered.reason}`\n        );\n      }\n      captureFrameCallback(criticalRenderTriggered);\n      setCriticalRenderTriggered(null); // Reset after trigger\n    }\n  }, [\n    criticalRenderTriggered,\n    isCapturing,\n    captureFrameCallback,\n    mergedConfig.debugLogs,\n    mergedConfig.debugAudioPerformance,\n  ]);\n\n  // Set up conversation render events\n  useConversationRenderEvents({\n    client: clientRef.current,\n    onCriticalRender: triggerCriticalRender,\n    isCapturing,\n    debugLogs: mergedConfig.debugLogs,\n  });\n\n  // Use periodic capture hook\n  usePeriodicCapture({\n    isCapturing,\n    captureElement,\n    screenshotContextRef,\n    canvasProcessorRef,\n    audioActiveRef,\n    captureFrame: captureFrameCallback,\n    getCurrentSettings,\n    getTimeSinceLastChange,\n    lastSentTimeRef,\n    lastCaptureTimeRef,\n    mergedConfig,\n  });\n\n  // Start render capture callback\n  const startRenderCapture = useCallback((client: GenAILiveClient) => {\n    console.log('🔄 [RENDER] Starting render capture');\n    clientRef.current = client;\n    setIsCapturing(true);\n  }, []);\n\n  // Stop render capture callback\n  const stopRenderCapture = useCallback(() => {\n    console.log('🔄 [RENDER] Stopping render capture');\n    clientRef.current = null;\n    setIsCapturing(false);\n    lastCaptureDurationRef.current = 0;\n    capturesPausedRef.current = false;\n    slowCaptureCountRef.current = 0;\n    // Audio cleanup is handled by useAudioAdaptation\n  }, []);\n\n  return {\n    startRenderCapture,\n    stopRenderCapture,\n    isCapturing,\n    triggerCriticalRender,\n  };\n}\n","/**\n * Canvas Processor Manager\n * Manages offscreen canvas processing in a worker to prevent main thread blocking\n */\n\ninterface ProcessOptions {\n  targetWidth: number;\n  canvas: HTMLCanvasElement;\n  jpegQuality: number;\n  maxInterval: number;\n  minInterval: number;\n  scale: number;\n  targetHeight: number;\n  timeSinceLastSent: number;\n  useHashing: boolean;\n  hashResolution?: { width: number; height: number };\n  lastHash?: string;\n}\n\ninterface ProcessResult {\n  shouldSend: boolean;\n  data?: string;\n  hash?: string;\n}\n\nexport class CanvasProcessorManager {\n  private worker: Worker | null = null;\n  private pendingRequests = new Map<\n    string,\n    {\n      reject: (error: Error) => void;\n      resolve: (result: ProcessResult) => void;\n    }\n  >();\n  private requestCounter = 0;\n  private isInitialized = false;\n  private initPromise: Promise<void> | null = null;\n  private debugLogs = false;\n\n  constructor(debugLogs = false) {\n    this.debugLogs = debugLogs;\n  }\n\n  async initialize(): Promise<void> {\n    if (this.isInitialized) return;\n    if (this.initPromise) return this.initPromise;\n\n    this.initPromise = this.doInitialize();\n    return this.initPromise;\n  }\n\n  private async doInitialize(): Promise<void> {\n    try {\n      // Try to load the bundled worker code\n      // @ts-ignore - This file is generated during build\n      const workerModule = await import(\n        '@/lib/workers/canvas-processor.worker.bundle'\n      );\n\n      if (workerModule.canvasProcessorWorkerCode) {\n        // Create a blob URL from the worker code\n        const blob = new Blob([workerModule.canvasProcessorWorkerCode], {\n          type: 'application/javascript',\n        });\n        const workerUrl = URL.createObjectURL(blob);\n\n        this.worker = new Worker(workerUrl);\n        this.worker.onmessage = this.handleWorkerMessage.bind(this);\n        this.worker.onerror = this.handleWorkerError.bind(this);\n\n        this.isInitialized = true;\n\n        if (this.debugLogs) {\n          console.log('🎨 [CANVAS-PROCESSOR] Worker initialized successfully');\n        }\n\n        // Clean up blob URL after worker is created\n        URL.revokeObjectURL(workerUrl);\n      } else {\n        throw new Error('Canvas processor worker bundle not found');\n      }\n    } catch (error) {\n      console.error(\n        '❌ [CANVAS-PROCESSOR] Failed to initialize worker:',\n        error\n      );\n      throw error;\n    }\n  }\n\n  private handleWorkerMessage(event: MessageEvent): void {\n    const { id, success, data, hash, shouldSend, error } = event.data;\n    const pending = this.pendingRequests.get(id);\n\n    if (!pending) {\n      console.warn('Received response for unknown request:', id);\n      return;\n    }\n\n    this.pendingRequests.delete(id);\n\n    if (success) {\n      pending.resolve({ data, hash, shouldSend: shouldSend ?? false });\n    } else {\n      pending.reject(new Error(error || 'Unknown worker error'));\n    }\n  }\n\n  private handleWorkerError(error: ErrorEvent): void {\n    console.error('❌ [CANVAS-PROCESSOR] Worker error:', error);\n\n    // Reject all pending requests\n    for (const [_id, { reject }] of this.pendingRequests) {\n      reject(new Error('Worker crashed'));\n    }\n    this.pendingRequests.clear();\n\n    // Reset state\n    this.worker = null;\n    this.isInitialized = false;\n    this.initPromise = null;\n  }\n\n  async processCanvas(options: ProcessOptions): Promise<ProcessResult> {\n    // Ensure worker is initialized\n    await this.initialize();\n\n    if (!this.worker) {\n      throw new Error('Worker not available');\n    }\n\n    const {\n      canvas,\n      targetWidth,\n      targetHeight,\n      scale,\n      jpegQuality,\n      useHashing,\n      lastHash,\n      minInterval,\n      maxInterval,\n      timeSinceLastSent,\n      hashResolution,\n    } = options;\n\n    // Quick check on main thread for timing constraints\n    if (!useHashing) {\n      // Without hashing, just respect minimum interval\n      if (timeSinceLastSent < minInterval) {\n        return { shouldSend: false };\n      }\n    } else {\n      // Force send if max interval exceeded\n      if (timeSinceLastSent >= maxInterval) {\n        // Still process through worker for consistency\n      } else if (timeSinceLastSent < minInterval) {\n        // Don't process if too soon (unless content might have changed)\n        return { shouldSend: false };\n      }\n    }\n\n    // Create request ID\n    const id = `request-${++this.requestCounter}`;\n\n    // Create promise for the response\n    const promise = new Promise<ProcessResult>((resolve, reject) => {\n      this.pendingRequests.set(id, { resolve, reject });\n\n      // Set timeout to prevent hanging\n      setTimeout(() => {\n        if (this.pendingRequests.has(id)) {\n          this.pendingRequests.delete(id);\n          reject(new Error('Canvas processing timeout'));\n        }\n      }, 5000); // 5 second timeout\n    });\n\n    try {\n      // Create ImageBitmap from canvas for efficient transfer\n      const bitmap = await createImageBitmap(canvas);\n\n      // Send to worker with ImageBitmap (transferable)\n      this.worker.postMessage(\n        {\n          id,\n          operation: 'process',\n          bitmap, // Send ImageBitmap instead of canvas\n          targetWidth,\n          targetHeight,\n          scale,\n          jpegQuality,\n          useHashing,\n          lastHash,\n          hashResolution,\n        },\n        [bitmap]\n      ); // Transfer ownership of the bitmap\n\n      if (this.debugLogs) {\n        console.log('🎨 [CANVAS-PROCESSOR] Processing request sent:', id);\n      }\n\n      return await promise;\n    } catch (error) {\n      this.pendingRequests.delete(id);\n      throw error;\n    }\n  }\n\n  destroy(): void {\n    if (this.worker) {\n      this.worker.terminate();\n      this.worker = null;\n    }\n\n    // Reject any pending requests\n    for (const [, { reject }] of this.pendingRequests) {\n      reject(new Error('Manager destroyed'));\n    }\n    this.pendingRequests.clear();\n\n    this.isInitialized = false;\n    this.initPromise = null;\n  }\n}\n","import { RefObject } from 'react';\n\nimport { GenAILiveClient } from '@/lib/genai-live-client';\nimport { ScreenCaptureConfig } from '@/types/media-types';\n\nexport interface UseRenderCaptureProps {\n  canvasRef: RefObject<HTMLCanvasElement | null>;\n  config: ScreenCaptureConfig;\n  contextElementRef: RefObject<HTMLDivElement | null>;\n}\n\nexport interface CriticalRenderEvent {\n  reason:\n    | 'agent-started-speaking'\n    | 'agent-stopped-speaking'\n    | 'interrupted'\n    | 'turn-complete'\n    | 'user-started-speaking'\n    | 'user-stopped-speaking';\n  bypassAudioThrottling: true;\n  timestamp: number;\n}\n\nexport interface UseRenderCaptureReturn {\n  isCapturing: boolean;\n  startRenderCapture: (client: GenAILiveClient) => void;\n  stopRenderCapture: () => void;\n  triggerCriticalRender: (event: CriticalRenderEvent) => void;\n}\n\nexport const DEFAULT_CONFIG: Partial<ScreenCaptureConfig> = {\n  scope: 'document',\n  checkInterval: 1000,\n  jpegQuality: 0.9,\n  maxWidth: 1920,\n  maxHeight: 1080,\n  minInterval: 100,\n  maxInterval: 2000,\n  useHashing: true,\n  autoDownload: false,\n  domChangeDetection: {\n    debounceMs: 1000,\n  },\n  debugLogs: false,\n  enableAudioAdaptation: true,\n};\n\n// Enhanced audio-aware capture intervals with quality adjustments\nexport const AUDIO_AWARE_CONFIG = {\n  // When audio is playing, DRASTICALLY reduce captures to prevent ANY stuttering\n  audioActiveMinInterval: 2000, // Increase to 2s during audio (was 1s)\n  audioActiveMaxInterval: 15000, // Increase to 15s during audio (was 10s)\n  audioActiveDomDebounce: 5000, // Increase to 5s during audio (was 3s)\n  audioActiveScale: 0.4, // Even lower resolution during audio (was 0.5)\n  audioActiveJpegQuality: 0.5, // Even lower quality during audio (was 0.6)\n  // Capture skip thresholds\n  audioSkipThreshold: 150, // Skip next capture if previous took >150ms (was 200ms)\n  audioPauseThreshold: 300, // Pause all captures if any take >300ms during audio\n  // NUCLEAR OPTION: Set to true to completely skip DOM captures during audio\n  skipDomCaptureDuringAudio: true, // This guarantees ZERO underruns\n  // Normal settings when no audio\n  normalMinInterval: 100,\n  normalMaxInterval: 2000,\n  normalDomDebounce: 1000,\n  normalScale: 1,\n  normalJpegQuality: 0.9,\n};\n","/**\n * Canvas utilities for screen capture\n */\n\nimport { MutableRefObject } from 'react';\n\nimport { ScreenCaptureConfig } from '@/types/media-types';\n\nexport const createSimpleHash = (imageData: ImageData): string => {\n  let hash = 0;\n  const step = Math.max(1, Math.floor(imageData.data.length / 1000));\n\n  for (let i = 0; i < imageData.data.length; i += step) {\n    hash = ((hash << 5) - hash + imageData.data[i]) & 0xffffffff;\n  }\n  return hash.toString();\n};\n\nexport const processCanvas = (canvas: HTMLCanvasElement): string | null => {\n  const ctx = canvas.getContext('2d');\n  if (!ctx) {\n    console.error('Canvas context not found');\n    return null;\n  }\n  const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);\n  return createSimpleHash(imageData);\n};\n\nexport const decideShouldSend = (\n  currentHash: string,\n  lastHash: string,\n  timeSinceLastSent: number,\n  config: Required<ScreenCaptureConfig>\n): boolean => {\n  // Always send first frame\n  if (!lastHash) return true;\n\n  // Force send if max interval exceeded\n  if (timeSinceLastSent > config.maxInterval) return true;\n\n  // Don't send if minimum interval not met (unless content changed)\n  if (currentHash === lastHash && timeSinceLastSent < config.minInterval) {\n    return false;\n  }\n\n  // Send if content changed\n  if (currentHash !== lastHash) return true;\n\n  return false;\n};\n\n/**\n * Downloads a canvas as a JPEG image to local storage\n * USAGE: Only called when autoDownload is enabled in ScreenCaptureConfig\n * FOR S3 INTEGRATION: Replace this with uploadToS3(canvas, config) in the future\n */\nexport const downloadScreenshot = (\n  canvas: HTMLCanvasElement,\n  config: ScreenCaptureConfig\n) => {\n  const link = document.createElement('a');\n  link.download = `screenshot-${new Date().toISOString().replace(/[:.]/g, '-')}.jpg`;\n  link.href = canvas.toDataURL('image/jpeg', config.jpegQuality);\n  link.click();\n};\n\n/**\n * Downloads base64 image data to local storage\n * USAGE: Only called when autoDownload is enabled\n * FOR S3 INTEGRATION: Replace this with uploadToS3FromBase64(data) in the future\n */\nexport const downloadScreenshotData = (data: string) => {\n  const link = document.createElement('a');\n  link.download = `screenshot-${new Date().toISOString().replace(/[:.]/g, '-')}.jpg`;\n  link.href = data;\n  link.click();\n};\n\n/**\n * Renders the captured content to a temporary canvas when user canvas is not available\n */\nexport const renderToTemporaryCanvas = (\n  sourceCanvas: HTMLCanvasElement,\n  targetWidth: number,\n  targetHeight: number,\n  scale: number\n): HTMLCanvasElement => {\n  // If no scaling needed, return original canvas\n  if (scale >= 1) {\n    return sourceCanvas;\n  }\n\n  // Create and configure temporary canvas for scaling\n  const tempCanvas = document.createElement('canvas');\n  tempCanvas.width = targetWidth;\n  tempCanvas.height = targetHeight;\n\n  const tempCtx = tempCanvas.getContext('2d');\n  if (!tempCtx) {\n    console.warn('Could not create temporary canvas context, using original');\n    return sourceCanvas;\n  }\n\n  tempCtx.drawImage(sourceCanvas, 0, 0, targetWidth, targetHeight);\n  return tempCanvas;\n};\n\n/**\n * Renders the captured content to the user-provided canvas\n */\nexport const renderToUserCanvas = (\n  canvas: HTMLCanvasElement,\n  sourceCanvas: HTMLCanvasElement,\n  targetWidth: number,\n  targetHeight: number\n): HTMLCanvasElement => {\n  const ctx = canvas.getContext('2d');\n  if (!ctx) {\n    console.error('Canvas context not found');\n    throw new Error('Canvas context not available');\n  }\n\n  // Set canvas dimensions and draw scaled image\n  canvas.width = targetWidth;\n  canvas.height = targetHeight;\n  ctx.drawImage(sourceCanvas, 0, 0, targetWidth, targetHeight);\n\n  return canvas;\n};\n\n/**\n * Determines whether a frame should be sent based on hashing and timing constraints\n */\nexport const shouldSendFrame = (\n  renderedCanvas: HTMLCanvasElement,\n  targetWidth: number,\n  targetHeight: number,\n  timeSinceLastSent: number,\n  config: Required<ScreenCaptureConfig>,\n  lastFrameHashRef: MutableRefObject<string>\n): boolean => {\n  if (config.useHashing) {\n    const ctx = renderedCanvas.getContext('2d');\n    if (!ctx) {\n      console.error('Final canvas context not found');\n      return false;\n    }\n\n    const imageData = ctx.getImageData(0, 0, targetWidth, targetHeight);\n    const hash = createSimpleHash(imageData);\n\n    const shouldSend = decideShouldSend(\n      hash,\n      lastFrameHashRef.current,\n      timeSinceLastSent,\n      config\n    );\n\n    if (config.debugLogs) {\n      console.log('🎬 [RENDER] Hash comparison', {\n        currentHash: hash.substring(0, 8),\n        lastHash: lastFrameHashRef.current.substring(0, 8),\n        shouldSend,\n        timeSinceLastSent: `${timeSinceLastSent}ms`,\n      });\n    }\n\n    if (!shouldSend) return false;\n\n    // Update hash ref for next comparison\n    lastFrameHashRef.current = hash;\n    return true;\n  } else {\n    // Without hashing, just respect minimum interval\n    if (timeSinceLastSent < config.minInterval) {\n      return false;\n    }\n    return true;\n  }\n};\n\nexport const renderToCanvas = (\n  canvas: HTMLCanvasElement | null,\n  sourceCanvas: HTMLCanvasElement,\n  targetWidth: number,\n  targetHeight: number,\n  scale: number\n) => {\n  return canvas\n    ? renderToUserCanvas(canvas, sourceCanvas, targetWidth, targetHeight)\n    : renderToTemporaryCanvas(sourceCanvas, targetWidth, targetHeight, scale);\n};\n","const _P = 'p'.charCodeAt(0);\nconst _H = 'H'.charCodeAt(0);\nconst _Y = 'Y'.charCodeAt(0);\nconst _S = 's'.charCodeAt(0);\n\nlet pngDataTable: ReturnType<typeof createPngDataTable>;\n\nfunction createPngDataTable(): Int32Array {\n  /* Table of CRCs of all 8-bit messages. */\n  const crcTable = new Int32Array(256);\n  for (let n = 0; n < 256; n++) {\n    let c = n;\n    for (let k = 0; k < 8; k++) {\n      c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;\n    }\n    crcTable[n] = c;\n  }\n  return crcTable;\n}\n\nfunction calcCrc(uint8Array: Uint8Array): number {\n  let c = -1;\n  if (!pngDataTable) pngDataTable = createPngDataTable();\n  for (let n = 0; n < uint8Array.length; n++) {\n    c = pngDataTable[(c ^ uint8Array[n]) & 0xff] ^ (c >>> 8);\n  }\n  return c ^ -1;\n}\n\nfunction searchStartOfPhys(uint8Array: Uint8Array): number {\n  const length = uint8Array.length - 1;\n  // we check from the end since we cut the string in proximity of the header\n  // the header is within 21 bytes from the end.\n  for (let i = length; i >= 4; i--) {\n    if (\n      uint8Array[i - 4] === 9 &&\n      uint8Array[i - 3] === _P &&\n      uint8Array[i - 2] === _H &&\n      uint8Array[i - 1] === _Y &&\n      uint8Array[i] === _S\n    ) {\n      return i - 3;\n    }\n  }\n  return 0;\n}\n\nexport function changePngDpi(\n  uint8Array: Uint8Array,\n  dpi: number,\n  overwritepHYs = false\n): Uint8Array {\n  const physChunk = new Uint8Array(13);\n  // chunk header pHYs\n  // 9 bytes of data\n  // 4 bytes of crc\n  // this multiplication is because the standard is dpi per meter.\n  dpi *= 39.3701;\n  physChunk[0] = _P;\n  physChunk[1] = _H;\n  physChunk[2] = _Y;\n  physChunk[3] = _S;\n  physChunk[4] = dpi >>> 24; // dpiX highest byte\n  physChunk[5] = dpi >>> 16; // dpiX veryhigh byte\n  physChunk[6] = dpi >>> 8; // dpiX high byte\n  physChunk[7] = dpi & 0xff; // dpiX low byte\n  physChunk[8] = physChunk[4]; // dpiY highest byte\n  physChunk[9] = physChunk[5]; // dpiY veryhigh byte\n  physChunk[10] = physChunk[6]; // dpiY high byte\n  physChunk[11] = physChunk[7]; // dpiY low byte\n  physChunk[12] = 1; // dot per meter....\n\n  const crc = calcCrc(physChunk);\n\n  const crcChunk = new Uint8Array(4);\n  crcChunk[0] = crc >>> 24;\n  crcChunk[1] = crc >>> 16;\n  crcChunk[2] = crc >>> 8;\n  crcChunk[3] = crc & 0xff;\n\n  if (overwritepHYs) {\n    const startingIndex = searchStartOfPhys(uint8Array);\n    uint8Array.set(physChunk, startingIndex);\n    uint8Array.set(crcChunk, startingIndex + 13);\n    return uint8Array;\n  } else {\n    // i need to give back an array of data that is divisible by 3 so that\n    // dataurl encoding gives me integers, for luck this chunk is 17 + 4 = 21\n    // if it was we could add a text chunk contaning some info, untill desired\n    // length is met.\n\n    // chunk structur 4 bytes for length is 9\n    const chunkLength = new Uint8Array(4);\n    chunkLength[0] = 0;\n    chunkLength[1] = 0;\n    chunkLength[2] = 0;\n    chunkLength[3] = 9;\n\n    const finalHeader = new Uint8Array(54);\n    finalHeader.set(uint8Array, 0);\n    finalHeader.set(chunkLength, 33);\n    finalHeader.set(physChunk, 37);\n    finalHeader.set(crcChunk, 50);\n    return finalHeader;\n  }\n}\n\n// those are 3 possible signature of the physBlock in base64.\n// the pHYs signature block is preceed by the 4 bytes of lenght. The length of\n// the block is always 9 bytes. So a phys block has always this signature:\n// 0 0 0 9 p H Y s.\n// However the data64 encoding aligns we will always find one of those 3 strings.\n// this allow us to find this particular occurence of the pHYs block without\n// converting from b64 back to string\nconst b64PhysSignature1 = 'AAlwSFlz';\nconst b64PhysSignature2 = 'AAAJcEhZ';\nconst b64PhysSignature3 = 'AAAACXBI';\n\nexport function detectPhysChunkFromDataUrl(dataUrl: string): number {\n  let b64index = dataUrl.indexOf(b64PhysSignature1);\n  if (b64index === -1) {\n    b64index = dataUrl.indexOf(b64PhysSignature2);\n  }\n  if (b64index === -1) {\n    b64index = dataUrl.indexOf(b64PhysSignature3);\n  }\n  return b64index;\n}\n","import type { Context } from './context';\n\n// Constants\nexport const PREFIX = '[modern-screenshot]';\nexport const IN_BROWSER = typeof window !== 'undefined';\nexport const SUPPORT_WEB_WORKER = IN_BROWSER && 'Worker' in window;\nexport const SUPPORT_ATOB = IN_BROWSER && 'atob' in window;\nexport const SUPPORT_BTOA = IN_BROWSER && 'btoa' in window;\nexport const USER_AGENT = IN_BROWSER ? window.navigator?.userAgent : '';\nexport const IN_CHROME = USER_AGENT.includes('Chrome');\nexport const IN_SAFARI = USER_AGENT.includes('AppleWebKit') && !IN_CHROME;\nexport const IN_FIREFOX = USER_AGENT.includes('Firefox');\n\n// Context\nexport const isContext = <T extends Node>(value: any): value is Context<T> =>\n  value && '__CONTEXT__' in value;\n\n// CSS\nexport const isCssFontFaceRule = (rule: CSSRule): rule is CSSFontFaceRule =>\n  rule.constructor.name === 'CSSFontFaceRule';\nexport const isCSSImportRule = (rule: CSSRule): rule is CSSImportRule =>\n  rule.constructor.name === 'CSSImportRule';\n\n// Element\nexport const isElementNode = (node: Node): node is Element =>\n  node.nodeType === 1; // Node.ELEMENT_NODE\nexport const isSVGElementNode = (node: Element): node is SVGElement =>\n  typeof (node as SVGElement).className === 'object';\nexport const isSVGImageElementNode = (node: Element): node is SVGImageElement =>\n  node.tagName === 'image';\nexport const isSVGUseElementNode = (node: Element): node is SVGUseElement =>\n  node.tagName === 'use';\nexport const isHTMLElementNode = (node: Node): node is HTMLElement =>\n  isElementNode(node) &&\n  typeof (node as HTMLElement).style !== 'undefined' &&\n  !isSVGElementNode(node);\nexport const isCommentNode = (node: Node): node is Text => node.nodeType === 8; // Node.COMMENT_NODE\nexport const isTextNode = (node: Node): node is Text => node.nodeType === 3; // Node.TEXT_NODE\nexport const isImageElement = (node: Element): node is HTMLImageElement =>\n  node.tagName === 'IMG';\nexport const isVideoElement = (node: Element): node is HTMLVideoElement =>\n  node.tagName === 'VIDEO';\nexport const isCanvasElement = (node: Element): node is HTMLCanvasElement =>\n  node.tagName === 'CANVAS';\nexport const isTextareaElement = (node: Element): node is HTMLTextAreaElement =>\n  node.tagName === 'TEXTAREA';\nexport const isInputElement = (node: Element): node is HTMLInputElement =>\n  node.tagName === 'INPUT';\nexport const isStyleElement = (node: Element): node is HTMLStyleElement =>\n  node.tagName === 'STYLE';\nexport const isScriptElement = (node: Element): node is HTMLScriptElement =>\n  node.tagName === 'SCRIPT';\nexport const isSelectElement = (node: Element): node is HTMLSelectElement =>\n  node.tagName === 'SELECT';\nexport const isSlotElement = (node: Element): node is HTMLSlotElement =>\n  node.tagName === 'SLOT';\nexport const isIFrameElement = (node: Element): node is HTMLIFrameElement =>\n  node.tagName === 'IFRAME';\n\n// Console\nexport const consoleWarn = (...args: any[]): void =>\n  console.warn(PREFIX, ...args);\n\n// Supports\nexport function supportWebp(ownerDocument?: Document): boolean {\n  const canvas = ownerDocument?.createElement?.('canvas');\n  if (canvas) {\n    canvas.height = canvas.width = 1;\n  }\n  return (\n    Boolean(canvas) &&\n    'toDataURL' in canvas! &&\n    Boolean(canvas.toDataURL('image/webp').includes('image/webp'))\n  );\n}\n\nexport const isDataUrl = (url: string): boolean => url.startsWith('data:');\nexport function resolveUrl(url: string, baseUrl: string | null): string {\n  // url is absolute already\n  if (url.match(/^[a-z]+:\\/\\//i)) return url;\n\n  // url is absolute already, without protocol\n  if (IN_BROWSER && url.match(/^\\/\\//)) return window.location.protocol + url;\n\n  // dataURI, mailto:, tel:, etc.\n  if (url.match(/^[a-z]+:/i)) return url;\n\n  if (!IN_BROWSER) return url;\n\n  const doc = getDocument().implementation.createHTMLDocument();\n  const base = doc.createElement('base');\n  const a = doc.createElement('a');\n  doc.head.appendChild(base);\n  doc.body.appendChild(a);\n  if (baseUrl) base.href = baseUrl;\n  a.href = url;\n  return a.href;\n}\n\nexport function getDocument<T extends Node>(target?: T | null): Document {\n  return ((target && isElementNode(target as any)\n    ? target?.ownerDocument\n    : target) ?? window.document) as any;\n}\n\nexport const XMLNS = 'http://www.w3.org/2000/svg';\n\nexport function createSvg(\n  width: number,\n  height: number,\n  ownerDocument?: Document | null\n): SVGSVGElement {\n  const svg = getDocument(ownerDocument).createElementNS(XMLNS, 'svg');\n  svg.setAttributeNS(null, 'width', width.toString());\n  svg.setAttributeNS(null, 'height', height.toString());\n  svg.setAttributeNS(null, 'viewBox', `0 0 ${width} ${height}`);\n  return svg;\n}\n\nexport function svgToDataUrl(\n  svg: SVGElement,\n  removeControlCharacter: boolean\n): string {\n  let xhtml = new XMLSerializer().serializeToString(svg);\n\n  if (removeControlCharacter) {\n    xhtml = xhtml\n      // https://www.w3.org/TR/xml/#charsets\n      // eslint-disable-next-line no-control-regex\n      .replace(\n        // eslint-disable-next-line no-control-regex\n        /[\\u0000-\\u0008\\v\\f\\u000E-\\u001F\\uD800-\\uDFFF\\uFFFE\\uFFFF]/gu,\n        ''\n      );\n  }\n\n  return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(xhtml)}`;\n}\n\n// To Blob\nexport async function canvasToBlob(\n  canvas: HTMLCanvasElement,\n  type = 'image/png',\n  quality = 1\n): Promise<Blob> {\n  try {\n    return await new Promise((resolve, reject) => {\n      canvas.toBlob(\n        (blob) => {\n          if (blob) {\n            resolve(blob);\n          } else {\n            reject(new Error('Blob is null'));\n          }\n        },\n        type,\n        quality\n      );\n    });\n  } catch (error) {\n    if (SUPPORT_ATOB) {\n      return dataUrlToBlob(canvas.toDataURL(type, quality));\n    }\n    throw error;\n  }\n}\nexport function dataUrlToBlob(dataUrl: string): Blob {\n  const [header, base64] = dataUrl.split(',');\n  const type = header.match(/data:(.+);/)?.[1] ?? undefined;\n  const decoded = window.atob(base64);\n  const length = decoded.length;\n  const buffer = new Uint8Array(length);\n  for (let i = 0; i < length; i += 1) {\n    buffer[i] = decoded.charCodeAt(i);\n  }\n  return new Blob([buffer], { type });\n}\n\n// Blob to\n// eslint-disable-next-line no-redeclare\nexport function readBlob(blob: Blob, type: 'dataUrl'): Promise<string>;\n// eslint-disable-next-line no-redeclare\nexport function readBlob(blob: Blob, type: 'arrayBuffer'): Promise<ArrayBuffer>;\n// eslint-disable-next-line no-redeclare\nexport function readBlob(\n  blob: Blob,\n  type: 'dataUrl' | 'arrayBuffer'\n): Promise<string | ArrayBuffer | null> {\n  return new Promise((resolve, reject) => {\n    const reader = new FileReader();\n    reader.onload = () => resolve(reader.result);\n    reader.onerror = () => reject(reader.error);\n    reader.onabort = () => reject(new Error(`Failed read blob to ${type}`));\n    if (type === 'dataUrl') {\n      reader.readAsDataURL(blob);\n    } else if (type === 'arrayBuffer') {\n      reader.readAsArrayBuffer(blob);\n    }\n  });\n}\nexport const blobToDataUrl = (blob: Blob): Promise<string> =>\n  readBlob(blob, 'dataUrl');\nexport const blobToArrayBuffer = (blob: Blob): Promise<ArrayBuffer> =>\n  readBlob(blob, 'arrayBuffer');\n\nexport function createImage(\n  url: string,\n  ownerDocument?: Document | null\n): HTMLImageElement {\n  const img = getDocument(ownerDocument).createElement('img');\n  img.decoding = 'sync';\n  img.loading = 'eager';\n  img.src = url;\n  return img;\n}\n\ntype Media = HTMLVideoElement | HTMLImageElement | SVGImageElement;\n\ninterface LoadMediaOptions {\n  ownerDocument?: Document;\n  timeout?: number;\n  onError?: (error: Error) => void;\n  onWarn?: (...args: any[]) => void;\n}\n\n// eslint-disable-next-line no-redeclare\nexport function loadMedia<T extends Media>(\n  media: T,\n  options?: LoadMediaOptions\n): Promise<T>;\n// eslint-disable-next-line no-redeclare\nexport function loadMedia(\n  media: string,\n  options?: LoadMediaOptions\n): Promise<HTMLImageElement>;\n// eslint-disable-next-line no-redeclare\nexport function loadMedia(\n  media: any,\n  options?: LoadMediaOptions\n): Promise<any> {\n  return new Promise((resolve) => {\n    const {\n      timeout,\n      ownerDocument,\n      onError: userOnError,\n      onWarn,\n    } = options ?? {};\n    const node: Media =\n      typeof media === 'string'\n        ? createImage(media, getDocument(ownerDocument))\n        : media;\n    let timer: any = null;\n    let removeEventListeners: null | (() => void) = null;\n\n    function onResolve(): void {\n      resolve(node);\n      timer && clearTimeout(timer);\n      removeEventListeners?.();\n    }\n\n    if (timeout) {\n      timer = setTimeout(onResolve, timeout);\n    }\n\n    if (isVideoElement(node)) {\n      const currentSrc = node.currentSrc || node.src;\n      if (!currentSrc) {\n        if (node.poster) {\n          return loadMedia(node.poster, options).then(resolve);\n        }\n        return onResolve();\n      }\n      if (node.readyState >= 2) {\n        return onResolve();\n      }\n      const onLoadeddata = onResolve;\n      const onError = (error: any): void => {\n        onWarn?.('Failed video load', currentSrc, error);\n        userOnError?.(error);\n        onResolve();\n      };\n      removeEventListeners = () => {\n        node.removeEventListener('loadeddata', onLoadeddata);\n        node.removeEventListener('error', onError);\n      };\n      node.addEventListener('loadeddata', onLoadeddata, { once: true });\n      node.addEventListener('error', onError, { once: true });\n    } else {\n      const currentSrc = isSVGImageElementNode(node)\n        ? node.href.baseVal\n        : node.currentSrc || node.src;\n\n      if (!currentSrc) {\n        return onResolve();\n      }\n\n      const onLoad = async (): Promise<void> => {\n        if (isImageElement(node) && 'decode' in node) {\n          try {\n            await node.decode();\n          } catch (error) {\n            onWarn?.(\n              'Failed to decode image, trying to render anyway',\n              node.dataset.originalSrc || currentSrc,\n              error\n            );\n          }\n        }\n        onResolve();\n      };\n\n      const onError = (error: any): void => {\n        onWarn?.(\n          'Failed image load',\n          node.dataset.originalSrc || currentSrc,\n          error\n        );\n        onResolve();\n      };\n\n      if (isImageElement(node) && node.complete) {\n        return onLoad();\n      }\n\n      removeEventListeners = () => {\n        node.removeEventListener('load', onLoad);\n        node.removeEventListener('error', onError);\n      };\n\n      node.addEventListener('load', onLoad, { once: true });\n      node.addEventListener('error', onError, { once: true });\n    }\n  });\n}\n\nexport async function waitUntilLoad(\n  node: Node,\n  options?: LoadMediaOptions\n): Promise<void> {\n  if (isHTMLElementNode(node)) {\n    if (isImageElement(node) || isVideoElement(node)) {\n      await loadMedia(node, options);\n    } else {\n      await Promise.all(\n        ['img', 'video'].flatMap((selectors) => {\n          return Array.from(node.querySelectorAll(selectors)).map((el) =>\n            loadMedia(el as any, options)\n          );\n        })\n      );\n    }\n  }\n}\n\nexport const uuid = (function uuid() {\n  // generate uuid for className of pseudo elements.\n  // We should not use GUIDs, otherwise pseudo elements sometimes cannot be captured.\n  let counter = 0;\n\n  // ref: http://stackoverflow.com/a/6248722/2519373\n  const random = (): string =>\n    `0000${((Math.random() * 36 ** 4) << 0).toString(36)}`.slice(-4);\n\n  return () => {\n    counter += 1;\n    return `u${random()}${counter}`;\n  };\n})();\n\nexport function splitFontFamily(fontFamily?: string): undefined | string[] {\n  return (\n    fontFamily\n      ?.split(',')\n      // Chrome  '__Niconne_7b96fe, __Niconne_Fallback_7b96fe'\n      // Firefox '\"__Niconne_7b96fe\", \"__Niconne_Fallback_7b96fe\"'\n      .map((val) => val.trim().replace(/\"|'/g, '').toLowerCase())\n      .filter(Boolean)\n  );\n}\n","import { consoleWarn, PREFIX } from './utils';\n\nexport interface Logger {\n  time: (label: string) => void;\n  timeEnd: (label: string) => void;\n  warn: (...args: any[]) => void;\n}\n\nlet uid = 0;\nexport function createLogger(debug: boolean): Logger {\n  const prefix = `${PREFIX}[#${uid}]`;\n  uid++;\n  return {\n    // eslint-disable-next-line no-console\n    time: (label: string) => debug && console.time(`${prefix} ${label}`),\n    // eslint-disable-next-line no-console\n    timeEnd: (label: string) => debug && console.timeEnd(`${prefix} ${label}`),\n    warn: (...args: any[]) => debug && consoleWarn(...args),\n  };\n}\n","export function getDefaultRequestInit(\n  bypassingCache?: boolean | RegExp\n): RequestInit {\n  return {\n    cache: bypassingCache ? 'no-cache' : 'force-cache',\n  };\n}\n","import type { Context, Request } from './context';\nimport type { Options } from './options';\nimport { createLogger } from './create-logger';\nimport { getDefaultRequestInit } from './get-default-request-init';\nimport {\n  IN_BROWSER,\n  isContext,\n  isElementNode,\n  SUPPORT_WEB_WORKER,\n  supportWebp,\n  waitUntilLoad,\n  XMLNS,\n} from './utils';\n\n// eslint-disable-next-line no-redeclare\nexport async function orCreateContext<T extends Node>(\n  context: Context<T>\n): Promise<Context<T>>;\n// eslint-disable-next-line no-redeclare\nexport async function orCreateContext<T extends Node>(\n  node: T,\n  options?: Options\n): Promise<Context<T>>;\n// eslint-disable-next-line no-redeclare\nexport async function orCreateContext(\n  node: any,\n  options?: Options\n): Promise<Context> {\n  return isContext(node)\n    ? node\n    : createContext(node, { ...options, autoDestruct: true });\n}\n\nexport async function createContext<T extends Node>(\n  node: T,\n  options?: Options & { autoDestruct?: boolean }\n): Promise<Context<T>> {\n  const { scale = 1, workerUrl, workerNumber = 1 } = options || {};\n\n  const debug = Boolean(options?.debug);\n  const features = options?.features ?? true;\n\n  const ownerDocument =\n    node.ownerDocument ?? (IN_BROWSER ? window.document : undefined);\n  const ownerWindow =\n    node.ownerDocument?.defaultView ?? (IN_BROWSER ? window : undefined);\n  const requests = new Map<string, Request>();\n\n  const context: Context<T> = {\n    // Options\n    width: 0,\n    height: 0,\n    quality: 1,\n    type: 'image/png',\n    scale,\n    backgroundColor: null,\n    style: null,\n    filter: null,\n    maximumCanvasSize: 0,\n    timeout: 30000,\n    progress: null,\n    debug,\n    fetch: {\n      requestInit: getDefaultRequestInit(options?.fetch?.bypassingCache),\n      placeholderImage:\n        'data:image/png;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7',\n      bypassingCache: false,\n      ...options?.fetch,\n    },\n    fetchFn: null,\n    font: {},\n    drawImageInterval: 100,\n    workerUrl: null,\n    workerNumber,\n    onCloneEachNode: null,\n    onCloneNode: null,\n    onEmbedNode: null,\n    onCreateForeignObjectSvg: null,\n    includeStyleProperties: null,\n    autoDestruct: false,\n    ...options,\n\n    // InternalContext\n    __CONTEXT__: true,\n    log: createLogger(debug),\n    node,\n    ownerDocument,\n    ownerWindow,\n    dpi: scale === 1 ? null : 96 * scale,\n    svgStyleElement: createStyleElement(ownerDocument),\n    svgDefsElement: ownerDocument?.createElementNS(XMLNS, 'defs'),\n    svgStyles: new Map(),\n    defaultComputedStyles: new Map(),\n    workers: [\n      ...Array.from({\n        length:\n          SUPPORT_WEB_WORKER && workerUrl && workerNumber ? workerNumber : 0,\n      }),\n    ]\n      .map(() => {\n        try {\n          const worker = new Worker(workerUrl!);\n          worker.onmessage = async (event) => {\n            const { url, result } = event.data;\n            if (result) {\n              requests.get(url)?.resolve?.(result);\n            } else {\n              requests\n                .get(url)\n                ?.reject?.(\n                  new Error(`Error receiving message from worker: ${url}`)\n                );\n            }\n          };\n          worker.onmessageerror = (event) => {\n            const { url } = event.data;\n            requests\n              .get(url)\n              ?.reject?.(\n                new Error(`Error receiving message from worker: ${url}`)\n              );\n          };\n          return worker;\n        } catch (error) {\n          context.log.warn('Failed to new Worker', error);\n          return null;\n        }\n      })\n      .filter(Boolean) as any,\n    fontFamilies: new Map(),\n    fontCssTexts: new Map(),\n    acceptOfImage: `${[\n      supportWebp(ownerDocument) && 'image/webp',\n      'image/svg+xml',\n      'image/*',\n      '*/*',\n    ]\n      .filter(Boolean)\n      .join(',')};q=0.8`,\n    requests,\n    drawImageCount: 0,\n    tasks: [],\n\n    features,\n    isEnable: (key: string): boolean => {\n      if (key === 'restoreScrollPosition') {\n        return typeof features === 'boolean'\n          ? false\n          : ((features as any)[key] ?? false);\n      }\n      if (typeof features === 'boolean') {\n        return features;\n      }\n      return (features as any)[key] ?? true;\n    },\n    shadowRoots: [],\n  };\n\n  context.log.time('wait until load');\n  await waitUntilLoad(node, {\n    timeout: context.timeout,\n    onWarn: context.log.warn,\n  });\n  context.log.timeEnd('wait until load');\n\n  const { width, height } = resolveBoundingBox(node, context);\n  context.width = width;\n  context.height = height;\n\n  return context;\n}\n\nexport function createStyleElement(\n  ownerDocument?: Document\n): HTMLStyleElement | undefined {\n  if (!ownerDocument) return undefined;\n  const style = ownerDocument.createElement('style');\n  const cssText = style.ownerDocument.createTextNode(`\n.______background-clip--text {\n  background-clip: text;\n  -webkit-background-clip: text;\n}\n`);\n  style.appendChild(cssText);\n  return style;\n}\n\nfunction resolveBoundingBox(\n  node: Node,\n  context: Context\n): { width: number; height: number } {\n  let { width, height } = context;\n\n  if (isElementNode(node) && (!width || !height)) {\n    const box = node.getBoundingClientRect();\n\n    width = width || box.width || Number(node.getAttribute('width')) || 0;\n\n    height = height || box.height || Number(node.getAttribute('height')) || 0;\n  }\n\n  return { width, height };\n}\n","import type { Context } from './context';\nimport { loadMedia } from './utils';\n\nexport async function imageToCanvas<T extends HTMLImageElement>(\n  image: T,\n  context: Context\n): Promise<HTMLCanvasElement> {\n  const { log, timeout, drawImageCount, drawImageInterval } = context;\n\n  log.time('image to canvas');\n  const loaded = await loadMedia(image, { timeout, onWarn: context.log.warn });\n  const { canvas, context2d } = createCanvas(image.ownerDocument, context);\n  const drawImage = (): void => {\n    try {\n      context2d?.drawImage(loaded, 0, 0, canvas.width, canvas.height);\n    } catch (error) {\n      context.log.warn('Failed to drawImage', error);\n    }\n  };\n\n  drawImage();\n\n  if (context.isEnable('fixSvgXmlDecode')) {\n    for (let i = 0; i < drawImageCount; i++) {\n      await new Promise<void>((resolve) => {\n        setTimeout(() => {\n          drawImage();\n          resolve();\n        }, i + drawImageInterval);\n      });\n    }\n  }\n\n  context.drawImageCount = 0;\n\n  log.timeEnd('image to canvas');\n  return canvas;\n}\n\nfunction createCanvas(\n  ownerDocument: Document,\n  context: Context\n): { canvas: HTMLCanvasElement; context2d: CanvasRenderingContext2D | null } {\n  const {\n    width,\n    height,\n    scale,\n    backgroundColor,\n    maximumCanvasSize: max,\n  } = context;\n\n  const canvas = ownerDocument.createElement('canvas');\n\n  canvas.width = Math.floor(width * scale);\n  canvas.height = Math.floor(height * scale);\n  canvas.style.width = `${width}px`;\n  canvas.style.height = `${height}px`;\n\n  if (max) {\n    if (canvas.width > max || canvas.height > max) {\n      if (canvas.width > max && canvas.height > max) {\n        if (canvas.width > canvas.height) {\n          canvas.height *= max / canvas.width;\n          canvas.width = max;\n        } else {\n          canvas.width *= max / canvas.height;\n          canvas.height = max;\n        }\n      } else if (canvas.width > max) {\n        canvas.height *= max / canvas.width;\n        canvas.width = max;\n      } else {\n        canvas.width *= max / canvas.height;\n        canvas.height = max;\n      }\n    }\n  }\n\n  const context2d = canvas.getContext('2d');\n\n  if (context2d && backgroundColor) {\n    context2d.fillStyle = backgroundColor;\n    context2d.fillRect(0, 0, canvas.width, canvas.height);\n  }\n\n  return { canvas, context2d };\n}\n","import type { Context } from './context';\nimport { createImage } from './utils';\n\nexport function cloneCanvas<T extends HTMLCanvasElement>(\n  canvas: T,\n  context: Context\n): HTMLCanvasElement | HTMLImageElement {\n  if (canvas.ownerDocument) {\n    try {\n      const dataURL = canvas.toDataURL();\n      if (dataURL !== 'data:,') {\n        return createImage(dataURL, canvas.ownerDocument);\n      }\n    } catch (error) {\n      context.log.warn('Failed to clone canvas', error);\n    }\n  }\n\n  const cloned = canvas.cloneNode(false) as T;\n  const ctx = canvas.getContext('2d');\n  const clonedCtx = cloned.getContext('2d');\n\n  try {\n    if (ctx && clonedCtx) {\n      clonedCtx.putImageData(\n        ctx.getImageData(0, 0, canvas.width, canvas.height),\n        0,\n        0\n      );\n    }\n    return cloned;\n  } catch (error) {\n    context.log.warn('Failed to clone canvas', error);\n  }\n\n  return cloned;\n}\n","import type { Context } from './context';\nimport { cloneNode } from './clone-node';\n\nexport function cloneIframe<T extends HTMLIFrameElement>(\n  iframe: T,\n  context: Context\n): HTMLIFrameElement | Promise<HTMLBodyElement> {\n  try {\n    if (iframe?.contentDocument?.body) {\n      return cloneNode(\n        iframe.contentDocument.body,\n        context\n      ) as Promise<HTMLBodyElement>;\n    }\n  } catch (error) {\n    context.log.warn('Failed to clone iframe', error);\n  }\n\n  return iframe.cloneNode(false) as HTMLIFrameElement;\n}\n","export function cloneImage<T extends HTMLImageElement>(\n  image: T\n): HTMLImageElement {\n  const cloned = image.cloneNode(false) as T;\n\n  if (image.currentSrc && image.currentSrc !== image.src) {\n    cloned.src = image.currentSrc;\n    cloned.srcset = '';\n  }\n\n  if (cloned.loading === 'lazy') {\n    cloned.loading = 'eager';\n  }\n\n  return cloned;\n}\n","import type { Context } from './context';\nimport { cloneCanvas } from './clone-canvas';\nimport { createImage, loadMedia } from './utils';\n\nexport async function cloneVideo<T extends HTMLVideoElement>(\n  video: T,\n  context: Context\n): Promise<HTMLCanvasElement | HTMLImageElement | HTMLVideoElement> {\n  if (video.ownerDocument && !video.currentSrc && video.poster) {\n    return createImage(video.poster, video.ownerDocument);\n  }\n\n  const cloned = video.cloneNode(false) as T;\n  cloned.crossOrigin = 'anonymous';\n  if (video.currentSrc && video.currentSrc !== video.src) {\n    cloned.src = video.currentSrc;\n  }\n\n  // video to canvas\n  const ownerDocument = cloned.ownerDocument;\n  if (ownerDocument) {\n    let canPlay = true;\n    await loadMedia(cloned, {\n      onError: () => (canPlay = false),\n      onWarn: context.log.warn,\n    });\n    if (!canPlay) {\n      if (video.poster) {\n        return createImage(video.poster, video.ownerDocument);\n      }\n      return cloned;\n    }\n    cloned.currentTime = video.currentTime;\n    await new Promise((resolve) => {\n      cloned.addEventListener('seeked', resolve, { once: true });\n    });\n    const canvas = ownerDocument.createElement('canvas');\n    canvas.width = video.offsetWidth;\n    canvas.height = video.offsetHeight;\n    try {\n      const ctx = canvas.getContext('2d');\n      if (ctx) ctx.drawImage(cloned, 0, 0, canvas.width, canvas.height);\n    } catch (error) {\n      context.log.warn('Failed to clone video', error);\n      if (video.poster) {\n        return createImage(video.poster, video.ownerDocument);\n      }\n      return cloned;\n    }\n    return cloneCanvas(canvas, context);\n  }\n\n  return cloned;\n}\n","import type { Context } from './context';\nimport { cloneCanvas } from './clone-canvas';\nimport { cloneIframe } from './clone-iframe';\nimport { cloneImage } from './clone-image';\nimport { cloneVideo } from './clone-video';\nimport {\n  isCanvasElement,\n  isIFrameElement,\n  isImageElement,\n  isVideoElement,\n} from './utils';\n\nexport function cloneElement<T extends HTMLElement | SVGElement>(\n  node: T,\n  context: Context\n): (HTMLElement | SVGElement) | Promise<HTMLElement | SVGElement> {\n  if (isCanvasElement(node)) {\n    return cloneCanvas(node, context);\n  }\n\n  if (isIFrameElement(node)) {\n    return cloneIframe(node, context);\n  }\n\n  if (isImageElement(node)) {\n    return cloneImage(node);\n  }\n\n  if (isVideoElement(node)) {\n    return cloneVideo(node, context);\n  }\n\n  return node.cloneNode(false) as T;\n}\n","import type { Context } from './context';\nimport { uuid } from './utils';\n\nexport function getSandBox(context: Context): HTMLIFrameElement | undefined {\n  let sandbox = context.sandbox;\n  if (!sandbox) {\n    const { ownerDocument } = context;\n    try {\n      if (ownerDocument) {\n        sandbox = ownerDocument.createElement('iframe');\n        sandbox.id = `__SANDBOX__${uuid()}`;\n        sandbox.width = '0';\n        sandbox.height = '0';\n        sandbox.style.visibility = 'hidden';\n        sandbox.style.position = 'fixed';\n        ownerDocument.body.appendChild(sandbox);\n        sandbox.srcdoc =\n          '<!DOCTYPE html><meta charset=\"UTF-8\"><title></title><body>';\n        context.sandbox = sandbox;\n      }\n    } catch (error) {\n      context.log.warn('Failed to getSandBox', error);\n    }\n  }\n  return sandbox;\n}\n","import type { Context } from './context';\nimport { getSandBox } from './sandbox';\nimport { isSVGElementNode, XMLNS } from './utils';\n\nconst ignoredStyles = ['width', 'height', '-webkit-text-fill-color'];\n\nconst includedAttributes = ['stroke', 'fill'];\n\nexport function getDefaultStyle(\n  node: HTMLElement | SVGElement,\n  pseudoElement: string | null,\n  context: Context\n): Map<string, any> {\n  const { defaultComputedStyles } = context;\n\n  const nodeName = node.nodeName.toLowerCase();\n  const isSvgNode = isSVGElementNode(node) && nodeName !== 'svg';\n  const attributes = isSvgNode\n    ? includedAttributes\n        .map((name) => [name, node.getAttribute(name)])\n        .filter(([, value]) => value !== null)\n    : [];\n\n  const key = [\n    isSvgNode && 'svg',\n    nodeName,\n    attributes.map((name, value) => `${name}=${value}`).join(','),\n    pseudoElement,\n  ]\n    .filter(Boolean)\n    .join(':');\n\n  if (defaultComputedStyles.has(key)) return defaultComputedStyles.get(key)!;\n\n  const sandbox = getSandBox(context);\n  const sandboxWindow = sandbox?.contentWindow;\n  if (!sandboxWindow) return new Map();\n  const sandboxDocument = sandboxWindow?.document;\n\n  let root: HTMLElement | SVGSVGElement;\n  let el: Element;\n  if (isSvgNode) {\n    root = sandboxDocument.createElementNS(XMLNS, 'svg');\n    el = root.ownerDocument.createElementNS(root.namespaceURI, nodeName);\n    attributes.forEach(([name, value]) => {\n      el.setAttributeNS(null, name!, value!);\n    });\n    root.appendChild(el);\n  } else {\n    root = el = sandboxDocument.createElement(nodeName);\n  }\n  el.textContent = ' ';\n  sandboxDocument.body.appendChild(root);\n  const computedStyle = sandboxWindow.getComputedStyle(el, pseudoElement);\n  const styles = new Map<string, any>();\n  for (let len = computedStyle.length, i = 0; i < len; i++) {\n    const name = computedStyle.item(i);\n    if (ignoredStyles.includes(name)) continue;\n    styles.set(name, computedStyle.getPropertyValue(name));\n  }\n  sandboxDocument.body.removeChild(root);\n\n  defaultComputedStyles.set(key, styles);\n\n  return styles;\n}\n","export function getDiffStyle(\n  style: CSSStyleDeclaration,\n  defaultStyle: Map<string, string>,\n  includeStyleProperties?: string[] | null\n): Map<string, [string, string]> {\n  const diffStyle = new Map<string, [string, string]>();\n  const prefixs: string[] = [];\n  const prefixTree = new Map<string, Map<string, [string, string]>>();\n\n  if (includeStyleProperties) {\n    for (const name of includeStyleProperties) {\n      applyTo(name);\n    }\n  } else {\n    for (let len = style.length, i = 0; i < len; i++) {\n      const name = style.item(i);\n      applyTo(name);\n    }\n  }\n\n  for (let len = prefixs.length, i = 0; i < len; i++) {\n    prefixTree\n      .get(prefixs[i])\n      ?.forEach((value, name) => diffStyle.set(name, value));\n  }\n\n  function applyTo(name: string): void {\n    const value = style.getPropertyValue(name);\n    const priority = style.getPropertyPriority(name);\n\n    const subIndex = name.lastIndexOf('-');\n    const prefix = subIndex > -1 ? name.substring(0, subIndex) : undefined;\n    if (prefix) {\n      let map = prefixTree.get(prefix);\n      if (!map) {\n        map = new Map();\n        prefixTree.set(prefix, map);\n      }\n      map.set(name, [value, priority]);\n    }\n\n    if (defaultStyle.get(name) === value && !priority) return;\n\n    if (prefix) {\n      prefixs.push(prefix);\n    } else {\n      diffStyle.set(name, [value, priority]);\n    }\n  }\n\n  return diffStyle;\n}\n","import type { Context } from './context';\nimport { getDefaultStyle } from './get-default-style';\nimport { getDiffStyle } from './get-diff-style';\nimport { IN_CHROME } from './utils';\n\nexport function copyCssStyles<T extends HTMLElement | SVGElement>(\n  node: T,\n  cloned: T,\n  isRoot: boolean,\n  context: Context\n): Map<string, [string, string]> {\n  const { ownerWindow, includeStyleProperties, currentParentNodeStyle } =\n    context;\n  const clonedStyle = cloned.style;\n  const computedStyle = ownerWindow!.getComputedStyle(node);\n  const defaultStyle = getDefaultStyle(node, null, context);\n  currentParentNodeStyle?.forEach((_, key) => {\n    defaultStyle.delete(key);\n  });\n  const style = getDiffStyle(\n    computedStyle,\n    defaultStyle,\n    includeStyleProperties\n  );\n\n  // fix\n  style.delete('transition-property');\n  style.delete('all'); // svg: all\n  style.delete('d'); // svg: d\n  style.delete('content'); // Safari shows pseudoelements if content is set\n  if (isRoot) {\n    style.delete('margin-top');\n    style.delete('margin-right');\n    style.delete('margin-bottom');\n    style.delete('margin-left');\n    style.delete('margin-block-start');\n    style.delete('margin-block-end');\n    style.delete('margin-inline-start');\n    style.delete('margin-inline-end');\n    style.set('box-sizing', ['border-box', '']);\n  }\n  // fix background-clip: text\n  if (style.get('background-clip')?.[0] === 'text') {\n    cloned.classList.add('______background-clip--text');\n  }\n  // fix chromium\n  // https://github.com/RigoCorp/html-to-image/blob/master/src/cssFixes.ts\n  if (IN_CHROME) {\n    if (!style.has('font-kerning')) style.set('font-kerning', ['normal', '']);\n\n    if (\n      (style.get('overflow-x')?.[0] === 'hidden' ||\n        style.get('overflow-y')?.[0] === 'hidden') &&\n      style.get('text-overflow')?.[0] === 'ellipsis' &&\n      node.scrollWidth === node.clientWidth\n    ) {\n      style.set('text-overflow', ['clip', '']);\n    }\n  }\n\n  for (let len = clonedStyle.length, i = 0; i < len; i++) {\n    clonedStyle.removeProperty(clonedStyle.item(i));\n  }\n\n  style.forEach(([value, priority], name) => {\n    clonedStyle.setProperty(name, value, priority);\n  });\n\n  return style;\n}\n","import { isInputElement, isSelectElement, isTextareaElement } from './utils';\n\nexport function copyInputValue<T extends HTMLElement | SVGElement>(\n  node: T,\n  cloned: T\n): void {\n  if (\n    isTextareaElement(node) ||\n    isInputElement(node) ||\n    isSelectElement(node)\n  ) {\n    cloned.setAttribute('value', node.value);\n  }\n}\n","import type { Context } from './context';\nimport { getDefaultStyle } from './get-default-style';\nimport { getDiffStyle } from './get-diff-style';\nimport { uuid } from './utils';\n\nconst pseudoClasses = [\n  ':before',\n  ':after',\n  // ':placeholder', TODO\n];\n\nconst scrollbarPseudoClasses = [\n  ':-webkit-scrollbar',\n  ':-webkit-scrollbar-button',\n  // ':-webkit-scrollbar:horizontal', TODO\n  ':-webkit-scrollbar-thumb',\n  ':-webkit-scrollbar-track',\n  ':-webkit-scrollbar-track-piece',\n  // ':-webkit-scrollbar:vertical', TODO\n  ':-webkit-scrollbar-corner',\n  ':-webkit-resizer',\n];\n\nexport function copyPseudoClass<T extends HTMLElement | SVGElement>(\n  node: T,\n  cloned: T,\n  copyScrollbar: boolean,\n  context: Context,\n  addWordToFontFamilies?: (text: string) => void\n): void {\n  const { ownerWindow, svgStyleElement, svgStyles, currentNodeStyle } = context;\n\n  if (!svgStyleElement || !ownerWindow) return;\n\n  function copyBy(pseudoClass: string): void {\n    const computedStyle = ownerWindow!.getComputedStyle(node, pseudoClass);\n    let content = computedStyle.getPropertyValue('content');\n\n    if (!content || content === 'none') return;\n\n    addWordToFontFamilies?.(content);\n\n    content = content\n      // TODO support css.counter\n      .replace(/(')|(\")|(counter\\(.+\\))/g, '');\n\n    const klasses = [uuid()];\n    const defaultStyle = getDefaultStyle(node, pseudoClass, context);\n    currentNodeStyle?.forEach((_, key) => {\n      defaultStyle.delete(key);\n    });\n    const style = getDiffStyle(\n      computedStyle,\n      defaultStyle,\n      context.includeStyleProperties\n    );\n\n    // fix\n    style.delete('content');\n    style.delete('-webkit-locale');\n    // fix background-clip: text\n    if (style.get('background-clip')?.[0] === 'text') {\n      cloned.classList.add('______background-clip--text');\n    }\n\n    const cloneStyle = [`content: '${content}';`];\n\n    style.forEach(([value, priority], name) => {\n      cloneStyle.push(`${name}: ${value}${priority ? ' !important' : ''};`);\n    });\n\n    if (cloneStyle.length === 1) return;\n\n    try {\n      (cloned as any).className = [(cloned as any).className, ...klasses].join(\n        ' '\n      );\n    } catch (err) {\n      context.log.warn('Failed to copyPseudoClass', err);\n      return;\n    }\n\n    const cssText = cloneStyle.join('\\n  ');\n    let allClasses = svgStyles.get(cssText);\n    if (!allClasses) {\n      allClasses = [];\n      svgStyles.set(cssText, allClasses);\n    }\n    allClasses.push(`.${klasses[0]}:${pseudoClass}`);\n  }\n\n  pseudoClasses.forEach(copyBy);\n\n  if (copyScrollbar) scrollbarPseudoClasses.forEach(copyBy);\n}\n","import type { Context } from './context';\nimport { cloneElement } from './clone-element';\nimport { copyCssStyles } from './copy-css-styles';\nimport { copyInputValue } from './copy-input-value';\nimport { copyPseudoClass } from './copy-pseudo-class';\nimport {\n  isCommentNode,\n  isElementNode,\n  isHTMLElementNode,\n  isScriptElement,\n  isSlotElement,\n  isStyleElement,\n  isSVGElementNode,\n  isTextNode,\n  isVideoElement,\n  splitFontFamily,\n} from './utils';\n\nconst excludeParentNodes = new Set([\n  'symbol', // test/fixtures/svg.symbol.html\n]);\n\nasync function appendChildNode<T extends Node>(\n  node: T,\n  cloned: T,\n  child: ChildNode,\n  context: Context,\n  addWordToFontFamilies?: (text: string) => void\n): Promise<void> {\n  if (isElementNode(child) && (isStyleElement(child) || isScriptElement(child)))\n    return;\n\n  if (context.filter && !context.filter(child)) return;\n\n  if (\n    excludeParentNodes.has(cloned.nodeName) ||\n    excludeParentNodes.has(child.nodeName)\n  ) {\n    context.currentParentNodeStyle = undefined;\n  } else {\n    context.currentParentNodeStyle = context.currentNodeStyle;\n  }\n\n  const childCloned = await cloneNode(\n    child,\n    context,\n    false,\n    addWordToFontFamilies\n  );\n\n  if (context.isEnable('restoreScrollPosition')) {\n    restoreScrollPosition(node, childCloned);\n  }\n\n  cloned.appendChild(childCloned);\n}\n\nasync function cloneChildNodes<T extends Node>(\n  node: T,\n  cloned: T,\n  context: Context,\n  addWordToFontFamilies?: (text: string) => void\n): Promise<void> {\n  let firstChild = node.firstChild;\n  if (isElementNode(node)) {\n    if (node.shadowRoot) {\n      firstChild = node.shadowRoot?.firstChild;\n      context.shadowRoots.push(node.shadowRoot);\n    }\n  }\n\n  for (let child = firstChild; child; child = child.nextSibling) {\n    if (isCommentNode(child)) continue;\n    if (\n      isElementNode(child) &&\n      isSlotElement(child) &&\n      typeof child.assignedNodes === 'function'\n    ) {\n      const nodes = child.assignedNodes();\n      for (let i = 0; i < nodes.length; i++) {\n        await appendChildNode(\n          node,\n          cloned,\n          nodes[i] as ChildNode,\n          context,\n          addWordToFontFamilies\n        );\n      }\n    } else {\n      await appendChildNode(\n        node,\n        cloned,\n        child,\n        context,\n        addWordToFontFamilies\n      );\n    }\n  }\n}\n\nfunction restoreScrollPosition<T extends Node>(node: T, chlidCloned: T): void {\n  if (!isHTMLElementNode(node) || !isHTMLElementNode(chlidCloned)) return;\n\n  const { scrollTop, scrollLeft } = node;\n\n  if (!scrollTop && !scrollLeft) {\n    return;\n  }\n\n  const { transform } = chlidCloned.style;\n  const matrix = new DOMMatrix(transform);\n\n  const { a, b, c, d } = matrix;\n  matrix.a = 1;\n  matrix.b = 0;\n  matrix.c = 0;\n  matrix.d = 1;\n  matrix.translateSelf(-scrollLeft, -scrollTop);\n  matrix.a = a;\n  matrix.b = b;\n  matrix.c = c;\n  matrix.d = d;\n  chlidCloned.style.transform = matrix.toString();\n}\n\nfunction applyCssStyleWithOptions(\n  cloned: HTMLElement | SVGElement,\n  context: Context\n): void {\n  const { backgroundColor, width, height, style: styles } = context;\n  const clonedStyle = cloned.style;\n  if (backgroundColor)\n    clonedStyle.setProperty('background-color', backgroundColor, 'important');\n  if (width) clonedStyle.setProperty('width', `${width}px`, 'important');\n  if (height) clonedStyle.setProperty('height', `${height}px`, 'important');\n  if (styles) {\n    for (const name in styles) clonedStyle[name] = styles[name]!;\n  }\n}\n\n/** @example \"'{ */\nconst NORMAL_ATTRIBUTE_RE = /^[\\w-:]+$/;\n\nexport async function cloneNode<T extends Node>(\n  node: T,\n  context: Context,\n  isRoot = false,\n  addWordToFontFamilies?: (text: string) => void\n): Promise<Node> {\n  const { ownerDocument, ownerWindow, fontFamilies, onCloneEachNode } = context;\n\n  if (ownerDocument && isTextNode(node)) {\n    if (addWordToFontFamilies && /\\S/.test(node.data)) {\n      addWordToFontFamilies(node.data);\n    }\n    return ownerDocument.createTextNode(node.data);\n  }\n\n  if (\n    ownerDocument &&\n    ownerWindow &&\n    isElementNode(node) &&\n    (isHTMLElementNode(node) || isSVGElementNode(node))\n  ) {\n    const cloned = await cloneElement(node, context);\n\n    if (context.isEnable('removeAbnormalAttributes')) {\n      const names = cloned.getAttributeNames();\n      for (let len = names.length, i = 0; i < len; i++) {\n        const name = names[i];\n        if (!NORMAL_ATTRIBUTE_RE.test(name)) {\n          cloned.removeAttribute(name);\n        }\n      }\n    }\n\n    const style = (context.currentNodeStyle = copyCssStyles(\n      node,\n      cloned,\n      isRoot,\n      context\n    ));\n\n    if (isRoot) applyCssStyleWithOptions(cloned, context);\n\n    let copyScrollbar = false;\n    if (context.isEnable('copyScrollbar')) {\n      const overflow = [\n        style.get('overflow-x')?.[0],\n        style.get('overflow-y')?.[0],\n      ];\n      copyScrollbar =\n        overflow.includes('scroll') ||\n        ((overflow.includes('auto') || overflow.includes('overlay')) &&\n          (node.scrollHeight > node.clientHeight ||\n            node.scrollWidth > node.clientWidth));\n    }\n\n    const textTransform = style.get('text-transform')?.[0];\n    const families = splitFontFamily(style.get('font-family')?.[0]);\n    const addWordToFontFamilies = families\n      ? (word: string) => {\n          if (textTransform === 'uppercase') {\n            word = word.toUpperCase();\n          } else if (textTransform === 'lowercase') {\n            word = word.toLowerCase();\n          } else if (textTransform === 'capitalize') {\n            word = word[0].toUpperCase() + word.substring(1);\n          }\n          families!.forEach((family) => {\n            let fontFamily = fontFamilies.get(family);\n            if (!fontFamily) {\n              fontFamilies.set(family, (fontFamily = new Set()));\n            }\n            word.split('').forEach((text) => fontFamily!.add(text));\n          });\n        }\n      : undefined;\n\n    copyPseudoClass(\n      node,\n      cloned,\n      copyScrollbar,\n      context,\n      addWordToFontFamilies\n    );\n\n    copyInputValue(node, cloned);\n\n    if (!isVideoElement(node)) {\n      await cloneChildNodes(node, cloned, context, addWordToFontFamilies);\n    }\n\n    await onCloneEachNode?.(cloned);\n\n    return cloned;\n  }\n\n  const cloned = node.cloneNode(false);\n\n  await cloneChildNodes(node, cloned, context);\n\n  await onCloneEachNode?.(cloned);\n\n  return cloned;\n}\n","import type { Context } from './context';\n\nexport function destroyContext(context: Context): void {\n  context.ownerDocument = undefined;\n  context.ownerWindow = undefined;\n  context.svgStyleElement = undefined;\n  context.svgDefsElement = undefined;\n  context.svgStyles.clear();\n  context.defaultComputedStyles.clear();\n  if (context.sandbox) {\n    try {\n      context.sandbox.remove();\n    } catch (err) {\n      context.log.warn('Failed to destroyContext', err);\n    }\n    context.sandbox = undefined;\n  }\n  context.workers = [];\n  context.fontFamilies.clear();\n  context.fontCssTexts.clear();\n  context.requests.clear();\n  context.tasks = [];\n  context.shadowRoots = [];\n}\n","import type { Context } from './context';\nimport { blobToDataUrl, IN_FIREFOX, IN_SAFARI } from './utils';\n\nexport type BaseFetchOptions = RequestInit & {\n  url: string;\n  timeout?: number;\n  responseType?: 'text' | 'dataUrl' | 'arrayBuffer';\n};\n\nexport type ContextFetchOptions = BaseFetchOptions & {\n  requestType?: 'text' | 'image';\n  imageDom?: HTMLImageElement | SVGImageElement;\n};\n\nexport function baseFetch<T = any>(options: BaseFetchOptions): Promise<T> {\n  const { url, timeout, responseType, ...requestInit } = options;\n\n  const controller = new AbortController();\n\n  const timer = timeout\n    ? setTimeout(() => controller.abort(), timeout)\n    : undefined;\n\n  return fetch(url, { signal: controller.signal, ...requestInit })\n    .then((response) => {\n      if (!response.ok) {\n        throw new Error('Failed fetch, not 2xx response', { cause: response });\n      }\n      switch (responseType) {\n        case 'arrayBuffer':\n          return response.arrayBuffer() as any;\n        case 'dataUrl':\n          return response.blob().then(blobToDataUrl);\n        case 'text':\n        default:\n          return response.text();\n      }\n    })\n    .finally(() => clearTimeout(timer));\n}\n\nexport function contextFetch(\n  context: Context,\n  options: ContextFetchOptions\n): Promise<string> {\n  const {\n    url: rawUrl,\n    requestType = 'text',\n    responseType = 'text',\n    imageDom,\n  } = options;\n  let url = rawUrl;\n\n  const {\n    timeout,\n    acceptOfImage,\n    requests,\n    fetchFn,\n    fetch: { requestInit, bypassingCache, placeholderImage },\n    font,\n    workers,\n    fontFamilies,\n  } = context;\n\n  if (requestType === 'image' && (IN_SAFARI || IN_FIREFOX)) {\n    context.drawImageCount++;\n  }\n\n  let request = requests.get(rawUrl);\n  if (!request) {\n    // cache bypass so we dont have CORS issues with cached images\n    // ref: https://developer.mozilla.org/en/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest#Bypassing_the_cache\n    if (bypassingCache) {\n      if (bypassingCache instanceof RegExp && bypassingCache.test(url)) {\n        url += (/\\?/.test(url) ? '&' : '?') + new Date().getTime();\n      }\n    }\n\n    // Font minify\n    const canFontMinify = requestType.startsWith('font') && font && font.minify;\n    const fontTexts = new Set();\n    if (canFontMinify) {\n      const families = requestType.split(';')[1].split(',');\n      families.forEach((family) => {\n        if (!fontFamilies.has(family)) return;\n        fontFamilies.get(family)!.forEach((text) => fontTexts.add(text));\n      });\n    }\n    const needFontMinify = canFontMinify && fontTexts.size;\n\n    const baseFetchOptions: BaseFetchOptions = {\n      url,\n      timeout,\n      responseType: needFontMinify ? 'arrayBuffer' : responseType,\n      headers: requestType === 'image' ? { accept: acceptOfImage } : undefined,\n      ...requestInit,\n    };\n\n    request = {\n      type: requestType,\n      resolve: undefined,\n      reject: undefined,\n      response: null as any,\n    };\n\n    request.response = (async () => {\n      if (fetchFn && requestType === 'image') {\n        const result = await fetchFn(rawUrl);\n\n        if (result) return result;\n      }\n\n      if (!IN_SAFARI && rawUrl.startsWith('http') && workers.length) {\n        return new Promise<string>((resolve, reject) => {\n          const worker = workers[requests.size & (workers.length - 1)];\n          worker.postMessage({ rawUrl, ...baseFetchOptions });\n          request!.resolve = resolve;\n          request!.reject = reject;\n        });\n      }\n\n      return baseFetch(baseFetchOptions);\n    })().catch((error) => {\n      requests.delete(rawUrl);\n\n      if (requestType === 'image' && placeholderImage) {\n        context.log.warn(\n          'Failed to fetch image base64, trying to use placeholder image',\n          url\n        );\n        return typeof placeholderImage === 'string'\n          ? placeholderImage\n          : placeholderImage(imageDom!);\n      }\n\n      throw error;\n    }) as any;\n\n    requests.set(rawUrl, request);\n  }\n\n  return request.response;\n}\n","import type { Context } from './context';\nimport { contextFetch } from './fetch';\nimport { isDataUrl, resolveUrl } from './utils';\n\nexport async function replaceCssUrlToDataUrl(\n  cssText: string,\n  baseUrl: string | null,\n  context: Context,\n  isImage?: boolean\n): Promise<string> {\n  if (!hasCssUrl(cssText)) return cssText;\n\n  for (const [rawUrl, url] of parseCssUrls(cssText, baseUrl)) {\n    try {\n      const dataUrl = await contextFetch(context, {\n        url,\n        requestType: isImage ? 'image' : 'text',\n        responseType: 'dataUrl',\n      });\n      cssText = cssText.replace(toRE(rawUrl), `$1${dataUrl}$3`);\n    } catch (error) {\n      context.log.warn('Failed to fetch css data url', rawUrl, error);\n    }\n  }\n\n  return cssText;\n}\n\nexport function hasCssUrl(cssText: string): boolean {\n  // eslint-disable-next-line\n  return /url\\((['\"]?)([^'\"]+?)\\1\\)/.test(cssText);\n}\n\nexport const URL_RE = /url\\((['\"]?)([^'\"]+?)\\1\\)/g;\n\nfunction parseCssUrls(\n  cssText: string,\n  baseUrl: string | null\n): [string, string][] {\n  const result: [string, string][] = [];\n\n  cssText.replace(URL_RE, (raw, quotation, url) => {\n    result.push([url, resolveUrl(url, baseUrl)]);\n    return raw;\n  });\n\n  return result.filter(([url]) => !isDataUrl(url));\n}\n\nfunction toRE(url: string): RegExp {\n  // eslint-disable-next-line\n  const escaped = url.replace(/([.*+?^${}()|\\[\\]\\/\\\\])/g, '\\\\$1');\n  return new RegExp(`(url\\\\(['\"]?)(${escaped})(['\"]?\\\\))`, 'g');\n}\n","import type { Context } from './context';\nimport { replaceCssUrlToDataUrl } from './css-url';\nimport { IN_FIREFOX, IN_SAFARI } from './utils';\n\nconst properties = [\n  'background-image',\n  'border-image-source',\n  '-webkit-border-image',\n  '-webkit-mask-image',\n  'list-style-image',\n];\n\nexport function embedCssStyleImage(\n  style: CSSStyleDeclaration,\n  context: Context\n): Promise<void>[] {\n  return properties\n    .map((property) => {\n      const value = style.getPropertyValue(property);\n      if (!value || value === 'none') {\n        return null;\n      }\n      if (IN_SAFARI || IN_FIREFOX) {\n        context.drawImageCount++;\n      }\n      return replaceCssUrlToDataUrl(value, null, context, true).then(\n        (newValue) => {\n          if (!newValue || value === newValue) return;\n          style.setProperty(\n            property,\n            newValue,\n            style.getPropertyPriority(property)\n          );\n        }\n      );\n    })\n    .filter(Boolean) as Promise<void>[];\n}\n","import type { Context } from './context';\nimport { contextFetch } from './fetch';\nimport {\n  IN_FIREFOX,\n  IN_SAFARI,\n  isDataUrl,\n  isImageElement,\n  isSVGElementNode,\n} from './utils';\n\nexport function embedImageElement<T extends HTMLImageElement | SVGImageElement>(\n  cloned: T,\n  context: Context\n): Promise<void>[] {\n  if (isImageElement(cloned)) {\n    const originalSrc = cloned.currentSrc || cloned.src;\n\n    if (!isDataUrl(originalSrc)) {\n      return [\n        contextFetch(context, {\n          url: originalSrc,\n          imageDom: cloned,\n          requestType: 'image',\n          responseType: 'dataUrl',\n        }).then((url) => {\n          if (!url) return;\n          cloned.srcset = '';\n          cloned.dataset.originalSrc = originalSrc;\n          cloned.src = url || '';\n        }),\n      ];\n    }\n\n    if (IN_SAFARI || IN_FIREFOX) {\n      context.drawImageCount++;\n    }\n  } else if (isSVGElementNode(cloned) && !isDataUrl(cloned.href.baseVal)) {\n    const originalSrc = cloned.href.baseVal;\n    return [\n      contextFetch(context, {\n        url: originalSrc,\n        imageDom: cloned,\n        requestType: 'image',\n        responseType: 'dataUrl',\n      }).then((url) => {\n        if (!url) return;\n        cloned.dataset.originalSrc = originalSrc;\n        cloned.href.baseVal = url || '';\n      }),\n    ];\n  }\n  return [];\n}\n","import type { Context } from './context';\nimport { contextFetch } from './fetch';\n\nexport function embedSvgUse<T extends SVGUseElement>(\n  cloned: T,\n  context: Context\n): Promise<void>[] {\n  const { ownerDocument, svgDefsElement } = context;\n\n  const href =\n    cloned.getAttribute('href') ?? // check href first as this is preferred and will be used if both are set\n    cloned.getAttribute('xlink:href');\n\n  if (!href) return []; // skip blank hrefs\n\n  const [svgUrl, id] = href.split('#');\n\n  if (id) {\n    const query = `#${id}`;\n    const definition = context.shadowRoots.reduce(\n      (res, root) => {\n        return res ?? root.querySelector(`svg ${query}`);\n      },\n      ownerDocument?.querySelector(`svg ${query}`)\n    );\n\n    if (svgUrl) {\n      // change the href attribute to use a local symbol on this cloned use-node\n      cloned.setAttribute('href', query);\n      // No need to set xlink:href since this is ignored when href is set\n    }\n\n    if (svgDefsElement?.querySelector(query)) return []; // already exists in defs\n\n    if (definition) {\n      // found local embedded definition\n      // If custom cloneNode is used, the element's style will be defined inline, and the use tag cannot override the style.\n      // On balance, the probability that external styles will affect defs elements is small, so origin cloneNode is used.\n      svgDefsElement?.appendChild(definition.cloneNode(true));\n      return [];\n    } else if (svgUrl) {\n      // no local definition but found an url\n      // try to fetch the svg and append it to the svgDefsElement\n      return [\n        contextFetch(context, {\n          url: svgUrl,\n          responseType: 'text',\n        }).then((svgData) => {\n          svgDefsElement?.insertAdjacentHTML('beforeend', svgData);\n        }),\n      ];\n    }\n  }\n\n  return [];\n}\n","import type { Context } from './context';\nimport { embedCssStyleImage } from './embed-css-style-image';\nimport { embedImageElement } from './embed-image-element';\nimport { embedSvgUse } from './embed-svg-use';\nimport {\n  isElementNode,\n  isHTMLElementNode,\n  isImageElement,\n  isSVGImageElementNode,\n  isSVGUseElementNode,\n} from './utils';\n\nexport function embedNode<T extends Node>(cloned: T, context: Context): void {\n  const { tasks } = context;\n\n  if (isElementNode(cloned)) {\n    if (isImageElement(cloned) || isSVGImageElementNode(cloned)) {\n      tasks.push(...embedImageElement(cloned, context));\n    }\n\n    if (isSVGUseElementNode(cloned)) {\n      tasks.push(...embedSvgUse(cloned, context));\n    }\n  }\n\n  if (isHTMLElementNode(cloned)) {\n    tasks.push(...embedCssStyleImage(cloned.style, context));\n  }\n\n  cloned.childNodes.forEach((child) => {\n    embedNode(child, context);\n  });\n}\n","import type { Context } from './context';\nimport { hasCssUrl, replaceCssUrlToDataUrl, URL_RE } from './css-url';\nimport { contextFetch } from './fetch';\nimport {\n  isCssFontFaceRule,\n  isCSSImportRule,\n  resolveUrl,\n  splitFontFamily,\n} from './utils';\n\nexport async function embedWebFont<T extends Element>(\n  clone: T,\n  context: Context\n): Promise<void> {\n  const {\n    ownerDocument,\n    svgStyleElement,\n    fontFamilies,\n    fontCssTexts,\n    tasks,\n    font,\n  } = context;\n\n  if (!ownerDocument || !svgStyleElement || !fontFamilies.size) {\n    return;\n  }\n\n  if (font && font.cssText) {\n    const cssText = filterPreferredFormat(font.cssText, context);\n    svgStyleElement.appendChild(ownerDocument.createTextNode(`${cssText}\\n`));\n  } else {\n    const styleSheets = Array.from(ownerDocument.styleSheets).filter(\n      (styleSheet) => {\n        try {\n          return (\n            'cssRules' in styleSheet && Boolean(styleSheet.cssRules.length)\n          );\n        } catch (error) {\n          context.log.warn(\n            `Error while reading CSS rules from ${styleSheet.href}`,\n            error\n          );\n          return false;\n        }\n      }\n    );\n\n    await Promise.all(\n      styleSheets.flatMap((styleSheet) => {\n        return Array.from(styleSheet.cssRules).map(async (cssRule, index) => {\n          if (isCSSImportRule(cssRule)) {\n            let importIndex = index + 1;\n            const baseUrl = cssRule.href;\n            let cssText = '';\n            try {\n              cssText = await contextFetch(context, {\n                url: baseUrl,\n                requestType: 'text',\n                responseType: 'text',\n              });\n            } catch (error) {\n              context.log.warn(\n                `Error fetch remote css import from ${baseUrl}`,\n                error\n              );\n            }\n            const replacedCssText = cssText.replace(\n              URL_RE,\n              (raw, quotation, url) =>\n                raw.replace(url, resolveUrl(url, baseUrl))\n            );\n            for (const rule of parseCss(replacedCssText)) {\n              try {\n                styleSheet.insertRule(\n                  rule,\n                  rule.startsWith('@import')\n                    ? (importIndex += 1)\n                    : styleSheet.cssRules.length!\n                );\n              } catch (error) {\n                context.log.warn(\n                  'Error inserting rule from remote css import',\n                  { rule, error }\n                );\n              }\n            }\n          }\n        });\n      })\n    );\n\n    const cssRules = styleSheets.flatMap((styleSheet) =>\n      Array.from(styleSheet.cssRules)\n    );\n\n    cssRules\n      .filter(\n        (cssRule) =>\n          isCssFontFaceRule(cssRule) &&\n          hasCssUrl(cssRule.style.getPropertyValue('src')) &&\n          splitFontFamily(cssRule.style.getPropertyValue('font-family'))?.some(\n            (val) => fontFamilies.has(val)\n          )\n      )\n      .forEach((value) => {\n        const rule = value as CSSFontFaceRule;\n        const cssText = fontCssTexts.get(rule.cssText);\n        if (cssText) {\n          svgStyleElement.appendChild(\n            ownerDocument.createTextNode(`${cssText}\\n`)\n          );\n        } else {\n          tasks.push(\n            replaceCssUrlToDataUrl(\n              rule.cssText,\n              rule.parentStyleSheet ? rule.parentStyleSheet.href : null,\n              context\n            ).then((cssText) => {\n              cssText = filterPreferredFormat(cssText, context);\n              fontCssTexts.set(rule.cssText, cssText);\n              svgStyleElement.appendChild(\n                ownerDocument.createTextNode(`${cssText}\\n`)\n              );\n            })\n          );\n        }\n      });\n  }\n}\n\nconst COMMENTS_RE = /(\\/\\*[\\s\\S]*?\\*\\/)/g;\n// eslint-disable-next-line\nconst KEYFRAMES_RE = /((@.*?keyframes [\\s\\S]*?){([\\s\\S]*?}\\s*?)})/gi;\n\nfunction parseCss(source: string): string[] {\n  if (source == null) return [];\n  const result: string[] = [];\n  let cssText = source.replace(COMMENTS_RE, '');\n  while (true) {\n    const matches = KEYFRAMES_RE.exec(cssText);\n    if (!matches) break;\n    result.push(matches[0]);\n  }\n  cssText = cssText.replace(KEYFRAMES_RE, '');\n  // to match css & media queries together\n  const IMPORT_RE = /@import[\\s\\S]*?url\\([^)]*\\)[\\s\\S]*?;/gi;\n  const UNIFIED_RE = new RegExp(\n    // eslint-disable-next-line\n    '((\\\\s*?(?:\\\\/\\\\*[\\\\s\\\\S]*?\\\\*\\\\/)?\\\\s*?@media[\\\\s\\\\S]' +\n      // eslint-disable-next-line\n      '*?){([\\\\s\\\\S]*?)}\\\\s*?})|(([\\\\s\\\\S]*?){([\\\\s\\\\S]*?)})',\n    'gi'\n  );\n  while (true) {\n    let matches = IMPORT_RE.exec(cssText);\n    if (!matches) {\n      matches = UNIFIED_RE.exec(cssText);\n      if (!matches) {\n        break;\n      } else {\n        IMPORT_RE.lastIndex = UNIFIED_RE.lastIndex;\n      }\n    } else {\n      UNIFIED_RE.lastIndex = IMPORT_RE.lastIndex;\n    }\n    result.push(matches[0]);\n  }\n  return result;\n}\n\nconst URL_WITH_FORMAT_RE = /url\\([^)]+\\)\\s*format\\(([\"']?)([^\"']+)\\1\\)/g;\nconst FONT_SRC_RE = /src:\\s*(?:url\\([^)]+\\)\\s*format\\([^)]+\\)[,;]\\s*)+/g;\n\nfunction filterPreferredFormat(str: string, context: Context): string {\n  const { font } = context;\n\n  const preferredFormat = font ? font?.preferredFormat : undefined;\n\n  return preferredFormat\n    ? str.replace(FONT_SRC_RE, (match: string) => {\n        while (true) {\n          const [src, , format] = URL_WITH_FORMAT_RE.exec(match) || [];\n          if (!format) return '';\n          if (format === preferredFormat) return `src: ${src};`;\n        }\n      })\n    : str;\n}\n","import type { Context } from '../context';\nimport type { Options } from '../options';\nimport { cloneNode } from '../clone-node';\nimport { orCreateContext } from '../create-context';\nimport { destroyContext } from '../destroy-context';\nimport { embedNode } from '../embed-node';\nimport { embedWebFont } from '../embed-web-font';\nimport { createSvg, isElementNode, isSVGElementNode } from '../utils';\n\n// eslint-disable-next-line no-redeclare\nexport async function domToForeignObjectSvg<T extends Node>(\n  node: T,\n  options?: Options\n): Promise<SVGElement>;\n// eslint-disable-next-line no-redeclare\nexport async function domToForeignObjectSvg<T extends Node>(\n  context: Context<T>\n): Promise<SVGElement>;\n// eslint-disable-next-line no-redeclare\nexport async function domToForeignObjectSvg(\n  node: any,\n  options?: any\n): Promise<SVGElement> {\n  const context = await orCreateContext(node, options);\n\n  if (isElementNode(context.node) && isSVGElementNode(context.node))\n    return context.node;\n\n  const {\n    ownerDocument,\n    log,\n    tasks,\n    svgStyleElement,\n    svgDefsElement,\n    svgStyles,\n    font,\n    progress,\n    autoDestruct,\n    onCloneNode,\n    onEmbedNode,\n    onCreateForeignObjectSvg,\n  } = context;\n\n  log.time('clone node');\n  const clone = await cloneNode(context.node, context, true);\n  if (svgStyleElement && ownerDocument) {\n    let allCssText = '';\n    svgStyles.forEach((klasses, cssText) => {\n      allCssText += `${klasses.join(',\\n')} {\\n  ${cssText}\\n}\\n`;\n    });\n    svgStyleElement.appendChild(ownerDocument.createTextNode(allCssText));\n  }\n  log.timeEnd('clone node');\n\n  await onCloneNode?.(clone);\n\n  if (font !== false && isElementNode(clone)) {\n    log.time('embed web font');\n    await embedWebFont(clone, context);\n    log.timeEnd('embed web font');\n  }\n\n  log.time('embed node');\n  embedNode(clone, context);\n  const count = tasks.length;\n  let current = 0;\n  const runTask = async (): Promise<void> => {\n    while (true) {\n      const task = tasks.pop();\n      if (!task) break;\n      try {\n        await task;\n      } catch (error) {\n        context.log.warn('Failed to run task', error);\n      }\n      progress?.(++current, count);\n    }\n  };\n  progress?.(current, count);\n  await Promise.all([...Array.from({ length: 4 })].map(runTask));\n  log.timeEnd('embed node');\n\n  await onEmbedNode?.(clone);\n\n  const svg = createForeignObjectSvg(clone, context);\n  svgDefsElement && svg.insertBefore(svgDefsElement, svg.children[0]);\n  svgStyleElement && svg.insertBefore(svgStyleElement, svg.children[0]);\n\n  autoDestruct && destroyContext(context);\n\n  await onCreateForeignObjectSvg?.(svg);\n\n  return svg;\n}\n\nfunction createForeignObjectSvg(clone: Node, context: Context): SVGSVGElement {\n  const { width, height } = context;\n  const svg = createSvg(width, height, clone.ownerDocument);\n  const foreignObject = svg.ownerDocument.createElementNS(\n    svg.namespaceURI,\n    'foreignObject'\n  );\n  foreignObject.setAttributeNS(null, 'x', '0%');\n  foreignObject.setAttributeNS(null, 'y', '0%');\n  foreignObject.setAttributeNS(null, 'width', '100%');\n  foreignObject.setAttributeNS(null, 'height', '100%');\n  foreignObject.append(clone);\n  svg.appendChild(foreignObject);\n  return svg;\n}\n","import type { Context } from '../context';\nimport type { Options } from '../options';\nimport { createStyleElement, orCreateContext } from '../create-context';\nimport { imageToCanvas } from '../image-to-canvas';\n\nimport { createImage, svgToDataUrl, XMLNS } from '../utils';\nimport { domToForeignObjectSvg } from './dom-to-foreign-object-svg';\n\n// eslint-disable-next-line no-redeclare\nexport async function domToCanvas<T extends Node>(\n  node: T,\n  options?: Options\n): Promise<HTMLCanvasElement>;\n// eslint-disable-next-line no-redeclare\nexport async function domToCanvas<T extends Node>(\n  context: Context<T>\n): Promise<HTMLCanvasElement>;\n// eslint-disable-next-line no-redeclare\nexport async function domToCanvas(\n  node: any,\n  options?: any\n): Promise<HTMLCanvasElement> {\n  const context = await orCreateContext(node, options);\n  const svg = await domToForeignObjectSvg(context);\n  const dataUrl = svgToDataUrl(svg, context.isEnable('removeControlCharacter'));\n  if (!context.autoDestruct) {\n    context.svgStyleElement = createStyleElement(context.ownerDocument);\n    context.svgDefsElement = context.ownerDocument?.createElementNS(\n      XMLNS,\n      'defs'\n    );\n    context.svgStyles.clear();\n  }\n  const image = createImage(dataUrl, svg.ownerDocument);\n  return await imageToCanvas(image, context);\n}\n","import { MutableRefObject } from 'react';\n\nimport { audioStutterAnalyzer } from '@/lib/audio/audio-stutter-analyzer';\nimport { GenAILiveClient } from '@/lib/genai-live-client';\nimport { CanvasProcessorManager } from '@/lib/workers/canvas-processor-manager';\nimport { ObservableClient } from '@/lib/wrappers/wrap-genai-client';\nimport { ScreenCaptureConfig } from '@/types/media-types';\nimport {\n  AUDIO_AWARE_CONFIG,\n  CriticalRenderEvent,\n} from '@/types/render-capture-types';\nimport { downloadScreenshot, renderToCanvas } from '@/utils/canvas-utils';\nimport { domToCanvas } from '@/utils/modern-screenshot';\nimport { Context } from '@/utils/screenshot-context';\n\ninterface AudioAdaptationSettings {\n  domDebounce: number;\n  jpegQuality: number;\n  maxInterval: number;\n  minInterval: number;\n  scale: number;\n}\n\ninterface CaptureFrameRefs {\n  audioActiveRef: MutableRefObject<boolean>;\n  canvasProcessorRef: MutableRefObject<CanvasProcessorManager | null>;\n  canvasRef: MutableRefObject<HTMLCanvasElement | null>;\n  capturesPausedRef: MutableRefObject<boolean>;\n  clientRef: MutableRefObject<GenAILiveClient | null>;\n  concurrentCaptureRef: MutableRefObject<boolean>;\n  hasPendingCaptureRef: MutableRefObject<boolean>;\n  lastCaptureDurationRef: MutableRefObject<number>;\n  lastCaptureTimeRef: MutableRefObject<number>;\n  lastFrameHashRef: MutableRefObject<string>;\n  lastSentTimeRef: MutableRefObject<number>;\n  screenshotContextRef: MutableRefObject<Context | null>;\n  slowCaptureCountRef: MutableRefObject<number>;\n}\n\ninterface CaptureFrameConfig {\n  captureElement: Element | null;\n  getCurrentSettings: () => AudioAdaptationSettings;\n  mergedConfig: ScreenCaptureConfig;\n  criticalRender?: CriticalRenderEvent;\n}\n\nexport async function captureFrame(\n  refs: CaptureFrameRefs,\n  config: CaptureFrameConfig\n): Promise<void> {\n  const {\n    canvasRef,\n    clientRef,\n    screenshotContextRef,\n    canvasProcessorRef,\n    lastFrameHashRef,\n    lastSentTimeRef,\n    lastCaptureTimeRef,\n    concurrentCaptureRef,\n    hasPendingCaptureRef,\n    lastCaptureDurationRef,\n    audioActiveRef,\n    capturesPausedRef,\n    slowCaptureCountRef,\n  } = refs;\n\n  const { captureElement, mergedConfig, getCurrentSettings, criticalRender } =\n    config;\n\n  // Check element readiness first, before any state changes\n  const canvas = canvasRef.current;\n  const client = clientRef.current;\n  const context = screenshotContextRef.current;\n  const processor = canvasProcessorRef.current;\n\n  if (!captureElement || !client || !context || !processor) return;\n\n  const now = Date.now();\n  const timeSinceLastCapture = now - lastCaptureTimeRef.current;\n  const { minInterval } = getCurrentSettings();\n\n  // Critical renders bypass timing constraints and concurrent capture locks\n  if (!criticalRender) {\n    if (timeSinceLastCapture < minInterval || concurrentCaptureRef.current) {\n      // Set pending flag for later processing\n      hasPendingCaptureRef.current = true;\n      if (mergedConfig.debugLogs) {\n        console.log(\n          `⏳ [RENDER] Too soon - waiting ${minInterval - timeSinceLastCapture}ms more (min interval: ${minInterval}ms)`\n        );\n      }\n      return;\n    }\n  } else {\n    if (mergedConfig.debugLogs) {\n      console.log(\n        `🎯 [CRITICAL-RENDER] Bypassing timing constraints for ${criticalRender.reason} (${timeSinceLastCapture}ms since last)`\n      );\n    }\n  }\n\n  // CRITICAL: Update capture time IMMEDIATELY to prevent rapid re-captures\n  lastCaptureTimeRef.current = now;\n\n  // Performance timing\n  const perfStart = performance.now();\n  let captureStartTime = 0;\n  let captureEndTime = 0;\n  let renderStartTime = 0;\n  let renderEndTime = 0;\n  let processStartTime = 0;\n  let processEndTime = 0;\n\n  if (mergedConfig.debugLogs) {\n    console.log('🎬 [RENDER] Starting capture process', {\n      elementSize: `${captureElement.scrollWidth}x${captureElement.scrollHeight}`,\n      timeSinceLastCapture: `${timeSinceLastCapture}ms`,\n      audioActive: audioActiveRef.current,\n      minInterval: `${minInterval}ms`,\n    });\n  }\n\n  // Always log capture start for stutter correlation\n  const captureStartData = {\n    timestamp: now,\n    audioActive: audioActiveRef.current,\n  };\n  if (mergedConfig.debugAudioPerformance) {\n    console.log('📸 [RENDER-TIMING] Capture started', captureStartData);\n    audioStutterAnalyzer.addEvent(\n      'render',\n      'start',\n      'Capture started',\n      captureStartData\n    );\n  }\n\n  concurrentCaptureRef.current = true;\n  hasPendingCaptureRef.current = false;\n\n  try {\n    // Add marker to ignore canvas mutations during capture (only if canvas exists)\n    if (canvas) canvas.setAttribute('data-sammy-ignore', 'true');\n\n    // Get current settings based on audio state\n    const { scale, jpegQuality } = getCurrentSettings();\n\n    // Capture phase\n    captureStartTime = performance.now();\n\n    let captureCanvas: HTMLCanvasElement | undefined;\n\n    // AGGRESSIVE SKIP LOGIC: Multiple checks to prevent audio stuttering\n    // Critical renders bypass audio-aware throttling to ensure agent has fresh context\n    if (audioActiveRef.current && !criticalRender) {\n      // NUCLEAR OPTION: Skip all DOM operations during audio (unless critical)\n      if (AUDIO_AWARE_CONFIG.skipDomCaptureDuringAudio) {\n        captureStartTime = performance.now();\n\n        // Create a minimal placeholder - NO DOM OPERATIONS\n        captureCanvas = document.createElement('canvas');\n        captureCanvas.width = 400;\n        captureCanvas.height = 300;\n        const ctx = captureCanvas.getContext('2d');\n        if (ctx) {\n          ctx.fillStyle = '#f8f8f8';\n          ctx.fillRect(0, 0, 400, 300);\n          ctx.fillStyle = '#999';\n          ctx.font = '16px Arial';\n          ctx.textAlign = 'center';\n          ctx.fillText('🔊 Audio playing - visual paused', 200, 150);\n          ctx.font = '12px Arial';\n          ctx.fillText('(Zero stuttering mode)', 200, 170);\n        }\n\n        captureEndTime = performance.now();\n        lastCaptureDurationRef.current = captureEndTime - captureStartTime;\n\n        console.log(\n          `🔇 [RENDER] Using placeholder during audio (zero stutter mode) - next capture in ${getCurrentSettings().minInterval}ms`\n        );\n      } else {\n        // Original skip logic for less aggressive mode\n        // Check 1: Are captures paused due to consistently slow performance?\n        if (capturesPausedRef.current) {\n          console.log(\n            '⏸️ [RENDER] Captures paused during audio to prevent stuttering'\n          );\n          return;\n        }\n\n        // Check 2: Was the last capture too slow?\n        if (\n          lastCaptureDurationRef.current > AUDIO_AWARE_CONFIG.audioSkipThreshold\n        ) {\n          console.log(\n            `⏭️ [RENDER] Skipping capture - last one took ${lastCaptureDurationRef.current.toFixed(0)}ms during audio`\n          );\n          return;\n        }\n\n        // Check 3: Have we had multiple slow captures? If so, pause entirely\n        if (slowCaptureCountRef.current >= 2) {\n          capturesPausedRef.current = true;\n          console.log(\n            '⏸️ [RENDER] Pausing all captures during audio due to repeated slow captures'\n          );\n          return;\n        }\n      }\n    }\n\n    // Log critical render override of audio throttling\n    if (criticalRender && audioActiveRef.current) {\n      if (mergedConfig.debugLogs) {\n        console.log(\n          `🎯 [CRITICAL-RENDER] Performing DOM capture during audio for ${criticalRender.reason} - ensuring agent has fresh context`\n        );\n      }\n    }\n\n    // Only do DOM capture if we haven't already created a placeholder\n    if (!captureCanvas!) {\n      // Use modern-screenshot with the pre-created context for fast capture\n      captureCanvas = await domToCanvas(context).catch((error: Error) => {\n        // If modern-screenshot fails, create a fallback canvas\n        console.warn(\n          'modern-screenshot failed, creating fallback canvas:',\n          error\n        );\n        const fallbackCanvas = document.createElement('canvas');\n        fallbackCanvas.width = 800;\n        fallbackCanvas.height = 600;\n        const ctx = fallbackCanvas.getContext('2d');\n        if (ctx) {\n          ctx.fillStyle = '#f0f0f0';\n          ctx.fillRect(0, 0, 800, 600);\n          ctx.fillStyle = '#666';\n          ctx.font = '16px Arial';\n          ctx.textAlign = 'center';\n          ctx.fillText('Screen capture unavailable', 400, 300);\n        }\n        return fallbackCanvas;\n      });\n\n      captureEndTime = performance.now();\n\n      // Track capture duration for skip logic\n      const captureDuration = captureEndTime - captureStartTime;\n      lastCaptureDurationRef.current = captureDuration;\n\n      // Track slow captures during audio\n      if (audioActiveRef.current) {\n        if (captureDuration > AUDIO_AWARE_CONFIG.audioSkipThreshold) {\n          slowCaptureCountRef.current++;\n          console.log(\n            `⚠️ [RENDER] Slow capture during audio: ${captureDuration.toFixed(0)}ms (count: ${slowCaptureCountRef.current})`\n          );\n        } else {\n          // Reset counter if we get a fast capture\n          slowCaptureCountRef.current = Math.max(\n            0,\n            slowCaptureCountRef.current - 1\n          );\n        }\n\n        // Immediately pause if capture is extremely slow\n        if (captureDuration > AUDIO_AWARE_CONFIG.audioPauseThreshold) {\n          capturesPausedRef.current = true;\n          console.log(\n            `⏸️ [RENDER] Pausing captures - extremely slow capture (${captureDuration.toFixed(0)}ms) during audio`\n          );\n          return;\n        }\n      }\n    }\n\n    let targetWidth: number;\n    let targetHeight: number;\n\n    // Calculate scaled dimensions - use audio-aware scale\n    const scaleRatio = Math.min(\n      (mergedConfig.maxWidth! * scale) / captureCanvas.width,\n      (mergedConfig.maxHeight! * scale) / captureCanvas.height,\n      scale\n    );\n\n    targetWidth = Math.floor(captureCanvas.width * scaleRatio);\n    targetHeight = Math.floor(captureCanvas.height * scaleRatio);\n\n    // Render phase\n    renderStartTime = performance.now();\n    const renderedCanvas = renderToCanvas(\n      canvas,\n      captureCanvas,\n      targetWidth,\n      targetHeight,\n      scaleRatio\n    );\n    renderEndTime = performance.now();\n\n    // Process phase\n    processStartTime = performance.now();\n    // Send the canvas to the worker for JPEG encoding\n    const timeSinceLastSent = now - lastSentTimeRef.current;\n    const result = await processor.processCanvas({\n      canvas: renderedCanvas,\n      targetWidth,\n      targetHeight,\n      scale: scaleRatio,\n      jpegQuality: jpegQuality,\n      useHashing: mergedConfig.useHashing!,\n      lastHash: lastFrameHashRef.current,\n      minInterval: getCurrentSettings().minInterval,\n      maxInterval: getCurrentSettings().maxInterval,\n      timeSinceLastSent,\n      hashResolution: { width: 320, height: 180 },\n    });\n    processEndTime = performance.now();\n\n    // Log capture timing\n    const totalTime = performance.now() - perfStart;\n    const timingData = {\n      timestamp: Date.now(),\n      totalMs: totalTime.toFixed(2),\n      captureMs: (captureEndTime - captureStartTime).toFixed(2),\n      renderMs: (renderEndTime - renderStartTime).toFixed(2),\n      processMs: (processEndTime - processStartTime).toFixed(2),\n      shouldSend: result.shouldSend,\n      audioActive: audioActiveRef.current,\n    };\n    if (mergedConfig.debugAudioPerformance) {\n      console.log('📸 [RENDER-TIMING] Capture complete', timingData);\n      audioStutterAnalyzer.addEvent(\n        'render',\n        'complete',\n        'Capture complete',\n        timingData\n      );\n    }\n\n    // Log warning if capture took too long during audio\n    if (\n      audioActiveRef.current &&\n      totalTime > 50 &&\n      mergedConfig.debugAudioPerformance\n    ) {\n      const longCaptureData = {\n        totalMs: totalTime.toFixed(2),\n        threshold: '50ms',\n      };\n      console.warn(\n        '⚠️ [RENDER-TIMING] Long capture during audio playback!',\n        longCaptureData\n      );\n      audioStutterAnalyzer.addEvent(\n        'render',\n        'long-capture',\n        'Long capture during audio playback',\n        {\n          ...timingData,\n          ...longCaptureData,\n        }\n      );\n    }\n\n    if (!result.shouldSend || !result.data) {\n      if (mergedConfig.debugLogs) {\n        console.log('🎬 [RENDER] Frame skipped (duplicate or too soon)');\n      }\n      return;\n    }\n\n    // Update hash reference\n    if (result.hash) {\n      lastFrameHashRef.current = result.hash;\n    }\n\n    if (mergedConfig.debugLogs) {\n      console.log('📸 [RENDER] Sending frame:', {\n        dimensions: `${targetWidth}x${targetHeight}`,\n        timeSinceLastSent: `${timeSinceLastSent}ms`,\n        audioActive: audioActiveRef.current,\n      });\n    }\n\n    // Use requestIdleCallback for non-critical send operation if available\n    const sendFrame = () => {\n      const sendStart = performance.now();\n      client.sendRealtimeInput([\n        { mimeType: 'image/jpeg', data: result.data! },\n      ]);\n      const sendTime = performance.now() - sendStart;\n      const sendData = {\n        timestamp: Date.now(),\n        sendMs: sendTime.toFixed(2),\n        audioActive: audioActiveRef.current,\n      };\n      if (mergedConfig.debugAudioPerformance) {\n        console.log('📸 [RENDER-TIMING] Frame sent', sendData);\n        audioStutterAnalyzer.addEvent('render', 'sent', 'Frame sent', sendData);\n      }\n      lastSentTimeRef.current = now;\n\n      // Track critical render event if this was a critical render\n      if (criticalRender) {\n        // Check if client has observability\n        const observableClient = client as ObservableClient;\n        if (observableClient.observability?.manager) {\n          observableClient.observability.manager.trackEvent({\n            type: 'screen_capture.critical',\n            data: {\n              reason: criticalRender.reason,\n              audioActive: audioActiveRef.current,\n              bypassedThrottling: true,\n              timeSinceLastCapture: timeSinceLastCapture,\n              captureMethod: captureCanvas ? 'dom' : 'placeholder',\n              width: renderedCanvas.width,\n              height: renderedCanvas.height,\n              sizeBytes: result.data ? result.data.length : 0,\n            },\n          });\n\n          if (mergedConfig.debugLogs) {\n            console.log(\n              `🎯 [CRITICAL-RENDER] Tracked critical render event: ${criticalRender.reason}`\n            );\n          }\n        }\n      }\n\n      // Handle image persistence based on configuration\n      if (mergedConfig.autoDownload) {\n        // Download to local storage if enabled\n        downloadScreenshot(renderedCanvas, mergedConfig);\n        if (mergedConfig.debugLogs) {\n          console.log('💾 [RENDER] Image downloaded to local storage');\n        }\n      }\n    };\n\n    // Send with requestIdleCallback if available and not urgent\n    if ('requestIdleCallback' in window && !audioActiveRef.current) {\n      window.requestIdleCallback(sendFrame, { timeout: 50 });\n    } else {\n      // Send immediately if audio is active or requestIdleCallback not available\n      sendFrame();\n    }\n  } catch (error) {\n    console.error('❌ [RENDER] Capture error:', error);\n  } finally {\n    concurrentCaptureRef.current = false;\n    if (canvas) canvas.removeAttribute('data-sammy-ignore');\n  }\n}\n","import { RefObject } from 'react';\n\nimport {\n  type Context,\n  createContext,\n  destroyContext,\n} from '@/utils/modern-screenshot';\n\n// Export the Context type\nexport type { Context };\n\ninterface CreateScreenshotContextOptions {\n  canvasRef: RefObject<HTMLCanvasElement | null>;\n  captureElement: Element;\n  debugLogs?: boolean;\n}\n\n// Helper function for off-screen detection\nexport const isOffScreen = (el: Element): boolean => {\n  const r = el.getBoundingClientRect();\n  return (\n    r.bottom < 0 ||\n    r.top > window.innerHeight ||\n    r.right < 0 ||\n    r.left > window.innerWidth\n  );\n};\n\nexport async function createScreenshotContext({\n  captureElement,\n  canvasRef,\n  debugLogs = false,\n}: CreateScreenshotContextOptions): Promise<Context | null> {\n  if (debugLogs) {\n    console.log('🎬 [RENDER] Creating screenshot context');\n  }\n\n  let workerUrl: string | undefined;\n\n  try {\n    // Try to load the bundled worker code\n    // @ts-ignore - This file is generated during build\n    const workerModule = await import('@/lib/workers/canvas.worker.bundle');\n    if (workerModule.canvasWorkerCode) {\n      // Create a blob URL from the worker code\n      const blob = new Blob([workerModule.canvasWorkerCode], {\n        type: 'application/javascript',\n      });\n      workerUrl = URL.createObjectURL(blob);\n      if (debugLogs) {\n        console.log('🎬 [RENDER] Worker loaded successfully');\n      }\n    }\n  } catch (e) {\n    // Worker bundle not available - will fall back to main thread\n    if (debugLogs) {\n      console.log('🎬 [RENDER] Worker not available, using main thread mode');\n    }\n  }\n\n  try {\n    // Create context with or without worker\n    const context = await createContext(captureElement, {\n      // Use worker if available for optimal performance\n      workerUrl,\n      workerNumber: workerUrl ? 2 : undefined, // Use 2 parallel workers if worker is available\n      scale: 1,\n      timeout: 0, // No timeout for captures\n      // Enable CORS through fetch options\n      fetch: {\n        requestInit: {\n          mode: 'cors',\n          credentials: 'include',\n        },\n      },\n      // Apply element filtering\n      filter: (node: Node) => {\n        try {\n          // Check if it's an Element node\n          if (node.nodeType !== Node.ELEMENT_NODE) {\n            return true; // Include non-element nodes (text, etc.)\n          }\n\n          const el = node as Element;\n          const htmlEl = el as HTMLElement;\n          const canvas = canvasRef.current;\n\n          if (\n            // your own exclusions\n            el === canvas ||\n            htmlEl.dataset?.sammyIgnore === 'true' ||\n            // not on the screen right now\n            isOffScreen(el) ||\n            // zero-sized or totally transparent\n            (htmlEl.offsetWidth === 0 && htmlEl.offsetHeight === 0) ||\n            // media the AI doesn't need to \"read\"\n            ['CANVAS', 'IFRAME', 'IMG', 'PICTURE', 'SVG', 'VIDEO'].includes(\n              el.tagName\n            )\n          ) {\n            return false; // false means exclude\n          }\n          return true; // true means include\n        } catch (error) {\n          // If we can't check the element, exclude it to be safe\n          console.warn('Error checking element for screen capture:', error);\n          return false;\n        }\n      },\n    });\n\n    if (debugLogs) {\n      console.log('🎬 [RENDER] Screenshot context created', {\n        withWorker: !!workerUrl,\n        workerNumber: workerUrl ? 2 : 0,\n      });\n    }\n\n    // ALWAYS log performance mode for visibility\n    if (workerUrl) {\n      console.log(\n        '🚀 [RENDER] WORKER MODE ACTIVE - High performance capture (30-60 fps, zero UI jank)'\n      );\n    } else {\n      console.log(\n        '🚨🚨 [RENDER] MAIN THREAD FALLBACK - Reduced performance mode (5-10 fps) 🚨🚨'\n      );\n      console.log(\n        '🚨 [RENDER] Run \"pnpm build:workers\" to enable high-performance worker mode 🚨'\n      );\n    }\n\n    // Clean up worker URL after a delay to ensure it's loaded\n    if (workerUrl) {\n      setTimeout(() => URL.revokeObjectURL(workerUrl!), 1000);\n    }\n\n    return context;\n  } catch (error) {\n    console.error('❌ [RENDER] Failed to create screenshot context:', error);\n    return null;\n  }\n}\n\nexport { destroyContext };\n","import { useCallback, useEffect, useRef, useState } from 'react';\nimport { MutableRefObject } from 'react';\n\nimport { GenAILiveClient } from '@/lib/genai-live-client';\nimport { ScreenCaptureConfig } from '@/types/media-types';\n\n// Audio-aware capture configuration\n// NOTE: These values should be fine-tuned based on real-world testing\n// to find the optimal balance between visual updates and audio quality\nconst AUDIO_AWARE_CONFIG = {\n  // Intervals during audio playback (tune based on device performance)\n  audioActiveMinInterval: 1000, // 1 second minimum\n  audioActiveMaxInterval: 5000, // 5 seconds maximum\n  audioActiveDomDebounce: 2000, // 2 second debounce\n  audioActiveScale: 0.6, // Moderate quality reduction\n  audioActiveJpegQuality: 0.7, // Still decent quality\n  // Threshold for skipping captures (tune based on audio buffer sizes)\n  audioSkipThreshold: 200, // Skip if previous capture > 200ms\n  // Normal settings when no audio\n  normalMinInterval: 100,\n  normalMaxInterval: 2000,\n  normalDomDebounce: 1000,\n  normalScale: 1,\n  normalJpegQuality: 0.9,\n};\n\ninterface AudioAdaptationSettings {\n  domDebounce: number;\n  jpegQuality: number;\n  maxInterval: number;\n  minInterval: number;\n  scale: number;\n}\n\ninterface UseAudioAdaptationProps {\n  client: GenAILiveClient | null;\n  config: ScreenCaptureConfig;\n  isCapturing: boolean;\n}\n\ninterface UseAudioAdaptationReturn {\n  AUDIO_AWARE_CONFIG: typeof AUDIO_AWARE_CONFIG;\n  audioActive: boolean;\n  audioActiveRef: MutableRefObject<boolean>;\n  getCurrentSettings: () => AudioAdaptationSettings;\n  shouldSkipCapture: (lastCaptureDuration: number) => boolean;\n}\n\nexport function useAudioAdaptation({\n  config,\n  client,\n  isCapturing,\n}: UseAudioAdaptationProps): UseAudioAdaptationReturn {\n  const audioActiveRef = useRef<boolean>(false);\n  const audioActivityTimeoutRef = useRef<number>(-1);\n  const [audioActive, setAudioActive] = useState<boolean>(false);\n\n  // Get current intervals and quality settings based on audio state\n  const getCurrentSettings = useCallback((): AudioAdaptationSettings => {\n    // Only apply audio adaptations if enabled\n    if (audioActiveRef.current && config.enableAudioAdaptation) {\n      return {\n        minInterval: AUDIO_AWARE_CONFIG.audioActiveMinInterval,\n        maxInterval: AUDIO_AWARE_CONFIG.audioActiveMaxInterval,\n        domDebounce: AUDIO_AWARE_CONFIG.audioActiveDomDebounce,\n        scale: AUDIO_AWARE_CONFIG.audioActiveScale,\n        jpegQuality: AUDIO_AWARE_CONFIG.audioActiveJpegQuality,\n      };\n    }\n    return {\n      minInterval: config.minInterval ?? 100,\n      maxInterval: config.maxInterval ?? 2000,\n      domDebounce: config.domChangeDetection?.debounceMs ?? 1000,\n      scale: AUDIO_AWARE_CONFIG.normalScale,\n      jpegQuality: config.jpegQuality ?? 0.9,\n    };\n  }, [config]);\n\n  // Check if capture should be skipped based on last capture duration\n  const shouldSkipCapture = useCallback(\n    (lastCaptureDuration: number): boolean => {\n      // Only skip if audio adaptation is enabled and audio is active\n      if (!config.enableAudioAdaptation || !audioActiveRef.current) {\n        return false;\n      }\n\n      return lastCaptureDuration > AUDIO_AWARE_CONFIG.audioSkipThreshold;\n    },\n    [config.enableAudioAdaptation]\n  );\n\n  // Listen for audio events to detect when audio is playing\n  useEffect(() => {\n    if (!client || !isCapturing) return;\n\n    const handleAudio = () => {\n      audioActiveRef.current = true;\n      setAudioActive(true);\n\n      // Clear existing timeout\n      if (audioActivityTimeoutRef.current !== -1) {\n        clearTimeout(audioActivityTimeoutRef.current);\n      }\n\n      // Set audio as inactive after 1 second of no audio\n      audioActivityTimeoutRef.current = window.setTimeout(() => {\n        audioActiveRef.current = false;\n        setAudioActive(false);\n        if (config.debugLogs) {\n          console.log(\n            '🔇 [RENDER] Audio inactive - resuming normal capture rate'\n          );\n        }\n      }, 1000);\n\n      if (config.debugLogs) {\n        console.log(\n          '🔊 [RENDER] Audio active - reducing capture rate and quality'\n        );\n      }\n    };\n\n    client.on('audio', handleAudio);\n\n    return () => {\n      client.off('audio', handleAudio);\n      if (audioActivityTimeoutRef.current !== -1) {\n        clearTimeout(audioActivityTimeoutRef.current);\n      }\n    };\n  }, [client, isCapturing, config.debugLogs]);\n\n  // Clean up on unmount or when capturing stops\n  useEffect(() => {\n    return () => {\n      audioActiveRef.current = false;\n      setAudioActive(false);\n      if (audioActivityTimeoutRef.current !== -1) {\n        clearTimeout(audioActivityTimeoutRef.current);\n        audioActivityTimeoutRef.current = -1;\n      }\n    };\n  }, []);\n\n  return {\n    audioActive,\n    audioActiveRef,\n    getCurrentSettings,\n    shouldSkipCapture,\n    AUDIO_AWARE_CONFIG,\n  };\n}\n","import { useEffect, useRef } from 'react';\n\nimport { GenAILiveClient } from '@/lib/genai-live-client';\nimport { CriticalRenderEvent } from '@/types/render-capture-types';\n\ninterface UseConversationRenderEventsProps {\n  client: GenAILiveClient | null;\n  isCapturing: boolean;\n  onCriticalRender: (event: CriticalRenderEvent) => void;\n  debugLogs?: boolean;\n}\n\nexport function useConversationRenderEvents({\n  client,\n  onCriticalRender,\n  isCapturing,\n  debugLogs = false,\n}: UseConversationRenderEventsProps) {\n  const userSpeakingRef = useRef(false);\n  const agentSpeakingRef = useRef(false);\n  const lastInputTranscriptionRef = useRef('');\n  const lastOutputTranscriptionRef = useRef('');\n\n  useEffect(() => {\n    if (!client || !isCapturing) return;\n\n    // Track user input transcription to detect when user starts/stops speaking\n    const handleInputTranscription = (transcription: {\n      finished: boolean;\n      text: string;\n    }) => {\n      const wasEmpty = lastInputTranscriptionRef.current.length === 0;\n      const isStarting = wasEmpty && transcription.text.length > 0;\n      const isStopping =\n        transcription.finished && transcription.text.length > 0;\n\n      lastInputTranscriptionRef.current = transcription.finished\n        ? ''\n        : transcription.text;\n\n      if (isStarting && !userSpeakingRef.current) {\n        userSpeakingRef.current = true;\n        const event: CriticalRenderEvent = {\n          reason: 'user-started-speaking',\n          timestamp: Date.now(),\n          bypassAudioThrottling: true,\n        };\n\n        if (debugLogs) {\n          console.log(\n            '🎯 [CRITICAL-RENDER] User started speaking - triggering capture'\n          );\n        }\n        onCriticalRender(event);\n      }\n\n      if (isStopping && userSpeakingRef.current) {\n        userSpeakingRef.current = false;\n        const event: CriticalRenderEvent = {\n          reason: 'user-stopped-speaking',\n          timestamp: Date.now(),\n          bypassAudioThrottling: true,\n        };\n\n        if (debugLogs) {\n          console.log(\n            '🎯 [CRITICAL-RENDER] User stopped speaking - triggering capture'\n          );\n        }\n        onCriticalRender(event);\n      }\n    };\n\n    // Track agent output transcription to detect when agent starts/stops speaking\n    const handleOutputTranscription = (transcription: {\n      finished: boolean;\n      text: string;\n    }) => {\n      const wasEmpty = lastOutputTranscriptionRef.current.length === 0;\n      const isStarting = wasEmpty && transcription.text.length > 0;\n      const isStopping =\n        transcription.finished && transcription.text.length > 0;\n\n      lastOutputTranscriptionRef.current = transcription.finished\n        ? ''\n        : transcription.text;\n\n      if (isStarting && !agentSpeakingRef.current) {\n        agentSpeakingRef.current = true;\n        const event: CriticalRenderEvent = {\n          reason: 'agent-started-speaking',\n          timestamp: Date.now(),\n          bypassAudioThrottling: true,\n        };\n\n        if (debugLogs) {\n          console.log(\n            '🎯 [CRITICAL-RENDER] Agent started speaking - triggering capture'\n          );\n        }\n        onCriticalRender(event);\n      }\n\n      if (isStopping && agentSpeakingRef.current) {\n        agentSpeakingRef.current = false;\n        const event: CriticalRenderEvent = {\n          reason: 'agent-stopped-speaking',\n          timestamp: Date.now(),\n          bypassAudioThrottling: true,\n        };\n\n        if (debugLogs) {\n          console.log(\n            '🎯 [CRITICAL-RENDER] Agent stopped speaking - triggering capture'\n          );\n        }\n        onCriticalRender(event);\n      }\n    };\n\n    // Handle turn completion\n    const handleTurnComplete = () => {\n      // Reset state\n      userSpeakingRef.current = false;\n      agentSpeakingRef.current = false;\n      lastInputTranscriptionRef.current = '';\n      lastOutputTranscriptionRef.current = '';\n\n      const event: CriticalRenderEvent = {\n        reason: 'turn-complete',\n        timestamp: Date.now(),\n        bypassAudioThrottling: true,\n      };\n\n      if (debugLogs) {\n        console.log('🎯 [CRITICAL-RENDER] Turn complete - triggering capture');\n      }\n      onCriticalRender(event);\n    };\n\n    // Handle interruption\n    const handleInterrupted = () => {\n      // Reset state\n      agentSpeakingRef.current = false;\n      lastOutputTranscriptionRef.current = '';\n\n      const event: CriticalRenderEvent = {\n        reason: 'interrupted',\n        timestamp: Date.now(),\n        bypassAudioThrottling: true,\n      };\n\n      if (debugLogs) {\n        console.log(\n          '🎯 [CRITICAL-RENDER] Agent interrupted - triggering capture'\n        );\n      }\n      onCriticalRender(event);\n    };\n\n    // Set up event listeners\n    client.on('inputtranscription', handleInputTranscription);\n    client.on('outputtranscription', handleOutputTranscription);\n    client.on('turncomplete', handleTurnComplete);\n    client.on('interrupted', handleInterrupted);\n\n    if (debugLogs) {\n      console.log(\n        '🎯 [CRITICAL-RENDER] Conversation render events initialized'\n      );\n    }\n\n    // Cleanup\n    return () => {\n      client.off('inputtranscription', handleInputTranscription);\n      client.off('outputtranscription', handleOutputTranscription);\n      client.off('turncomplete', handleTurnComplete);\n      client.off('interrupted', handleInterrupted);\n\n      // Reset state\n      userSpeakingRef.current = false;\n      agentSpeakingRef.current = false;\n      lastInputTranscriptionRef.current = '';\n      lastOutputTranscriptionRef.current = '';\n    };\n  }, [client, isCapturing, onCriticalRender, debugLogs]);\n\n  return {\n    userSpeaking: userSpeakingRef.current,\n    agentSpeaking: agentSpeakingRef.current,\n  };\n}\n","/**\n * useDomChangeDetector Hook\n *   Detects DOM mutations and triggers callbacks when changes occur\n */\nimport { useCallback, useEffect, useRef } from 'react';\n\ninterface UseDomChangeDetectorProps {\n  enabled: boolean;\n  onDomChange: () => void;\n  target: HTMLElement | null;\n  config?: MutationObserverInit;\n  debounceMs?: number;\n}\n\nconst DEFAULT_OBSERVER_CONFIG: MutationObserverInit = {\n  childList: true,\n  subtree: true,\n  attributes: true,\n  characterData: true,\n  attributeOldValue: false,\n  characterDataOldValue: false,\n};\n\nexport function useDomChangeDetector({\n  target,\n  onDomChange,\n  enabled,\n  debounceMs,\n  config = DEFAULT_OBSERVER_CONFIG,\n}: UseDomChangeDetectorProps) {\n  const observerRef = useRef<MutationObserver | null>(null);\n  const debounceTimerRef = useRef<number>(-1);\n  const lastChangeTimeRef = useRef<number>(0);\n\n  const handleMutations = useCallback(\n    (mutations: MutationRecord[]) => {\n      // Filter out our own canvas mutations\n      const relevantMutations = mutations.filter((mutation) => {\n        const target = mutation.target;\n\n        // Only check Element-specific methods if target is an Element\n        if (target instanceof Element) {\n          // Skip mutations on canvas elements or their children\n          if (\n            target.tagName === 'CANVAS' ||\n            target.closest('canvas') ||\n            target.getAttribute?.('data-sammy-ignore') === 'true'\n          ) {\n            return false;\n          }\n        }\n\n        return true;\n      });\n\n      if (relevantMutations.length === 0) return;\n\n      lastChangeTimeRef.current = Date.now();\n\n      // Clear existing debounce timer\n      if (debounceTimerRef.current !== -1) {\n        clearTimeout(debounceTimerRef.current);\n      }\n\n      // Set new debounce timer\n      debounceTimerRef.current = window.setTimeout(() => {\n        onDomChange();\n        debounceTimerRef.current = -1;\n      }, debounceMs);\n    },\n    [onDomChange, debounceMs]\n  );\n\n  useEffect(() => {\n    if (!enabled || !target) {\n      // Clean up if disabled\n      if (observerRef.current) {\n        observerRef.current.disconnect();\n        observerRef.current = null;\n      }\n      if (debounceTimerRef.current !== -1) {\n        clearTimeout(debounceTimerRef.current);\n        debounceTimerRef.current = -1;\n      }\n      return;\n    }\n\n    // Create observer if it doesn't exist\n    if (!observerRef.current) {\n      observerRef.current = new MutationObserver(handleMutations);\n    }\n\n    // Start observing\n    observerRef.current.observe(target, config);\n\n    // Cleanup function\n    return () => {\n      if (observerRef.current) {\n        observerRef.current.disconnect();\n      }\n      if (debounceTimerRef.current !== -1) {\n        clearTimeout(debounceTimerRef.current);\n        debounceTimerRef.current = -1;\n      }\n    };\n  }, [enabled, target, config, handleMutations]);\n\n  // Expose method to get time since last change\n  const getTimeSinceLastChange = useCallback(() => {\n    return Date.now() - lastChangeTimeRef.current;\n  }, []);\n\n  return {\n    getTimeSinceLastChange,\n  };\n}\n","import { MutableRefObject, useEffect, useRef } from 'react';\n\nimport { ScreenCaptureConfig } from '@/types/media-types';\nimport { Context } from '@/utils/screenshot-context';\n\ninterface UsePeriodicCaptureProps {\n  audioActiveRef: MutableRefObject<boolean>;\n  canvasProcessorRef: MutableRefObject<any>;\n  captureElement: Element | null;\n  captureFrame: () => Promise<void>;\n  getCurrentSettings: () => any;\n  getTimeSinceLastChange: () => number;\n  isCapturing: boolean;\n  lastCaptureTimeRef: MutableRefObject<number>;\n  lastSentTimeRef: MutableRefObject<number>;\n  mergedConfig: ScreenCaptureConfig;\n  screenshotContextRef: MutableRefObject<Context | null>;\n}\n\nexport function usePeriodicCapture({\n  isCapturing,\n  captureElement,\n  screenshotContextRef,\n  canvasProcessorRef,\n  audioActiveRef,\n  captureFrame,\n  getCurrentSettings,\n  getTimeSinceLastChange,\n  lastSentTimeRef,\n  lastCaptureTimeRef,\n  mergedConfig,\n}: UsePeriodicCaptureProps): void {\n  const periodicCheckTimerRef = useRef<number>(-1);\n\n  useEffect(() => {\n    if (mergedConfig.debugLogs) {\n      console.log('🔄 [RENDER] Periodic capture setup', {\n        isCapturing,\n        elementReady: !!captureElement,\n        canvasReady: !!canvasProcessorRef.current,\n        contextReady: !!screenshotContextRef.current,\n      });\n    }\n\n    if (!isCapturing) {\n      if (periodicCheckTimerRef.current !== -1) {\n        clearInterval(periodicCheckTimerRef.current);\n        periodicCheckTimerRef.current = -1;\n      }\n      return;\n    }\n\n    // Wait for capture element and context to be ready before starting periodic capture\n    const startPeriodicCapture = () => {\n      if (\n        captureElement &&\n        screenshotContextRef.current &&\n        canvasProcessorRef.current\n      ) {\n        if (mergedConfig.debugLogs) {\n          console.log(\n            '🔄 [RENDER] Starting periodic capture - capture element and context ready'\n          );\n        }\n        // Don't do initial capture immediately during audio\n        if (!audioActiveRef.current) {\n          if (mergedConfig.debugAudioPerformance) {\n            console.log('🚀 [RENDER] Initial capture (no audio playing)');\n          }\n          captureFrame();\n        } else {\n          if (mergedConfig.debugAudioPerformance) {\n            console.log(\n              '🔇 [RENDER] Skipping initial capture - audio is playing'\n            );\n          }\n        }\n\n        // Set up periodic check with dynamic interval\n        const setupInterval = () => {\n          // Clear existing interval\n          if (periodicCheckTimerRef.current !== -1) {\n            clearInterval(periodicCheckTimerRef.current);\n          }\n\n          // Use audio-aware interval\n          const checkInterval = audioActiveRef.current\n            ? 1000 // Check every second during audio\n            : mergedConfig.checkInterval;\n\n          console.log(\n            `⏰ [RENDER] Setting periodic check interval to ${checkInterval}ms (audio: ${audioActiveRef.current})`\n          );\n\n          periodicCheckTimerRef.current = window.setInterval(() => {\n            // Check capture element and context are still ready\n            if (!captureElement || !screenshotContextRef.current) {\n              return;\n            }\n\n            const timeSinceLastSent = Date.now() - lastSentTimeRef.current;\n            const timeSinceLastCapture =\n              Date.now() - lastCaptureTimeRef.current;\n            const { maxInterval, minInterval } = getCurrentSettings();\n\n            // CRITICAL: Check both max interval AND min interval\n            if (\n              timeSinceLastSent >= maxInterval &&\n              timeSinceLastCapture >= minInterval\n            ) {\n              if (mergedConfig.debugLogs) {\n                console.log(\n                  `📸 [RENDER] Forcing periodic capture (${timeSinceLastSent}ms since last send, ${timeSinceLastCapture}ms since last capture, audio: ${audioActiveRef.current})`\n                );\n              }\n              if (mergedConfig.debugAudioPerformance) {\n                console.log('⏰ [RENDER] Periodic capture initiated');\n              }\n              // Don't await here to avoid blocking the interval\n              captureFrame().catch((error: any) => {\n                console.error('❌ [RENDER] Periodic capture error:', error);\n              });\n            } else if (\n              mergedConfig.debugLogs &&\n              timeSinceLastCapture < minInterval\n            ) {\n              console.log(\n                `⏳ [RENDER] Periodic check - too soon for capture (${timeSinceLastCapture}ms < ${minInterval}ms min interval)`\n              );\n            }\n          }, checkInterval);\n        };\n\n        setupInterval();\n\n        // Re-setup interval when audio state changes\n        const checkAudioState = setInterval(() => {\n          const currentInterval = audioActiveRef.current\n            ? 1000 // Check every second during audio\n            : mergedConfig.checkInterval;\n\n          // Only recreate if interval needs to change\n          if (periodicCheckTimerRef.current !== -1) {\n            const needsUpdate = audioActiveRef.current\n              ? currentInterval !== 1000\n              : currentInterval !== mergedConfig.checkInterval;\n\n            if (needsUpdate) {\n              console.log(\n                `🔄 [RENDER] Audio state changed - updating periodic interval`\n              );\n              setupInterval();\n            }\n          }\n        }, 1000); // Check every second\n\n        // Store the audio check interval so we can clean it up\n        (window as any).__audioCheckInterval = checkAudioState;\n      } else {\n        if (mergedConfig.debugLogs) {\n          console.log(\n            '🔄 [RENDER] Capture element or context not ready, retrying in 500ms',\n            {\n              captureElement: !!captureElement,\n              context: !!screenshotContextRef.current,\n              processor: !!canvasProcessorRef.current,\n            }\n          );\n        }\n        // Retry after a short delay if capture element or context isn't ready\n        setTimeout(startPeriodicCapture, 500);\n      }\n    };\n\n    startPeriodicCapture();\n\n    return () => {\n      if (periodicCheckTimerRef.current !== -1) {\n        clearInterval(periodicCheckTimerRef.current);\n        periodicCheckTimerRef.current = -1;\n      }\n      // Clean up the audio state check interval if it exists\n      if ((window as any).__audioCheckInterval) {\n        clearInterval((window as any).__audioCheckInterval);\n        (window as any).__audioCheckInterval = undefined;\n      }\n    };\n  }, [\n    isCapturing,\n    captureFrame,\n    mergedConfig.checkInterval,\n    mergedConfig.debugLogs,\n    getTimeSinceLastChange,\n    getCurrentSettings,\n    captureElement,\n  ]);\n}\n","/**\n * useScreenCapture Hook\n *   Unified hook for capturing screen content via either screen recording or DOM rendering\n *   Returns callbacks that take a GenAILiveClient to start/stop capture\n */\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\nimport { GenAILiveClient } from '@/lib/genai-live-client';\nimport { ScreenCapture, ScreenCaptureConfig } from '@/types/media-types';\n\nimport { useRenderCapture } from './use-render-capture';\nimport { useVideoCapture } from './use-video-capture';\n\ninterface UseScreenCaptureProps {\n  config: ScreenCaptureConfig;\n}\n\nexport function useScreenCapture({\n  config,\n}: UseScreenCaptureProps): ScreenCapture {\n  // Shared refs\n  const videoRef = useRef<HTMLVideoElement>(null);\n  const canvasRef = useRef<HTMLCanvasElement>(null);\n  const contextElementRef = useRef<HTMLDivElement>(null);\n\n  // State\n  const [stream, setStream] = useState<MediaStream | null>(null);\n  const [isStreaming, setIsStreaming] = useState(false);\n  const [videoStream, setVideoStream] = useState<MediaStream | null>(null);\n  const activeClientRef = useRef<GenAILiveClient | null>(null);\n\n  // Get render capture callbacks\n  const {\n    startRenderCapture,\n    stopRenderCapture,\n    isCapturing: isCapturingRender,\n  } = useRenderCapture({\n    contextElementRef,\n    canvasRef,\n    config,\n  });\n\n  // Get video capture callbacks\n  const {\n    startVideoCapture,\n    stopVideoCapture,\n    isCapturing: isCapturingVideo,\n  } = useVideoCapture({\n    videoRef,\n    videoStream,\n    canvasRef,\n    config,\n  });\n\n  // Handle stream ended\n  useEffect(() => {\n    if (!stream) return;\n\n    const handleStreamEnded = () => {\n      setIsStreaming(false);\n      setStream(null);\n      setVideoStream(null);\n      if (config.method === 'render') {\n        stopRenderCapture();\n      } else if (config.method === 'video') {\n        stopVideoCapture();\n      }\n      activeClientRef.current = null;\n    };\n\n    stream.getTracks().forEach((track) => {\n      track.addEventListener('ended', handleStreamEnded);\n    });\n\n    return () => {\n      stream.getTracks().forEach((track) => {\n        track.removeEventListener('ended', handleStreamEnded);\n      });\n    };\n  }, [stream, config.method, stopRenderCapture, stopVideoCapture]);\n\n  // Start capture based on method\n  const startStreaming = useCallback(\n    async (client: GenAILiveClient): Promise<MediaStream | void> => {\n      activeClientRef.current = client;\n\n      if (config.method === 'video') {\n        if (typeof window === 'undefined' || !navigator.mediaDevices) {\n          console.warn('Screen capture not available in this environment');\n          return;\n        }\n\n        try {\n          const mediaStream = await navigator.mediaDevices.getDisplayMedia({\n            video: true,\n          });\n          setStream(mediaStream);\n          setVideoStream(mediaStream);\n          setIsStreaming(true);\n          // Start video capture with the stream\n          startVideoCapture(client, mediaStream);\n          return mediaStream;\n        } catch (error) {\n          console.error('Failed to start screen capture:', error);\n          activeClientRef.current = null;\n          throw error;\n        }\n      } else if (config.method === 'render') {\n        // For render method, start the render capture\n        startRenderCapture(client);\n        setIsStreaming(true);\n      }\n    },\n    [config.method, startRenderCapture, startVideoCapture]\n  );\n\n  // Stop capture\n  const stopStreaming = useCallback(() => {\n    if (stream) {\n      stream.getTracks().forEach((track) => track.stop());\n      setStream(null);\n      setVideoStream(null);\n    }\n\n    if (config.method === 'render') {\n      stopRenderCapture();\n    } else if (config.method === 'video') {\n      stopVideoCapture();\n    }\n\n    setIsStreaming(false);\n    activeClientRef.current = null;\n  }, [stream, config.method, stopRenderCapture, stopVideoCapture]);\n\n  return {\n    // Refs\n    videoRef,\n    canvasRef,\n    contextElementRef,\n    // State\n    isStreaming: isStreaming || isCapturingRender || isCapturingVideo,\n    stream,\n    videoStream,\n    setVideoStream,\n    // Methods\n    startStreaming,\n    stopStreaming,\n  };\n}\n","/**\n * useVideoCapture Hook\n *   Captures video frames from screen recording and sends them to the AI client\n *   Returns callbacks to start/stop capture that take a GenAILiveClient\n */\nimport {\n  RefObject,\n  useCallback,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from 'react';\n\nimport { GenAILiveClient } from '@/lib/genai-live-client';\nimport { ScreenCaptureConfig } from '@/types/media-types';\nimport { createSimpleHash, decideShouldSend } from '@/utils/canvas-utils';\n\ninterface UseVideoCaptureProps {\n  videoRef: RefObject<HTMLVideoElement | null>;\n  videoStream: MediaStream | null;\n  canvasRef: RefObject<HTMLCanvasElement | null>;\n  config: ScreenCaptureConfig;\n}\n\ninterface UseVideoCaptureReturn {\n  startVideoCapture: (\n    client: GenAILiveClient,\n    videoStream: MediaStream\n  ) => void;\n  stopVideoCapture: () => void;\n  isCapturing: boolean;\n}\n\nconst DEFAULT_CONFIG: Partial<ScreenCaptureConfig> = {\n  checkInterval: 1000,\n  jpegQuality: 0.7,\n  maxWidth: 1920,\n  maxHeight: 1080,\n  minInterval: 3000,\n  maxInterval: 10000,\n  useHashing: true,\n  autoDownload: false, // Disable automatic downloads by default\n  debugLogs: true, // Default debug logging to false\n};\n\nexport function useVideoCapture({\n  videoRef,\n  videoStream,\n  canvasRef,\n  config,\n}: UseVideoCaptureProps): UseVideoCaptureReturn {\n  const lastFrameHashRef = useRef<string>('');\n  const lastSentTimeRef = useRef<number>(0);\n  const captureTimeoutRef = useRef<number>(-1);\n  const [isCapturing, setIsCapturing] = useState(false);\n  const clientRef = useRef<GenAILiveClient | null>(null);\n  const videoStreamRef = useRef<MediaStream | null>(null);\n\n  const mergedConfig = useMemo(\n    () => ({ ...DEFAULT_CONFIG, ...config }),\n    [config]\n  );\n\n  // Attach video stream to video element\n  useEffect(() => {\n    if (!videoStream) return;\n\n    const tryAttach = () => {\n      const node = videoRef.current;\n      if (node && node.srcObject !== videoStream) {\n        node.srcObject = videoStream;\n        node.play?.().catch((error) => {\n          console.error('Failed to play video:', error);\n        });\n        return true;\n      }\n      return false;\n    };\n\n    if (tryAttach()) return;\n\n    const intervalId = setInterval(() => {\n      if (tryAttach()) clearInterval(intervalId);\n    }, 100);\n\n    return () => clearInterval(intervalId);\n  }, [videoStream, videoRef]);\n\n  const captureFrame = useCallback(\n    (client: GenAILiveClient) => {\n      const video = videoRef.current;\n      const canvas = canvasRef.current;\n\n      if (!video || !canvas || video.readyState < 2) {\n        if (mergedConfig.debugLogs) {\n          console.log('🎬 [VIDEO] Skipping frame - not ready', {\n            hasVideo: !!video,\n            hasCanvas: !!canvas,\n            readyState: video?.readyState,\n          });\n        }\n        return;\n      }\n\n      try {\n        const ctx = canvas.getContext('2d');\n        if (!ctx) {\n          console.error('Canvas context not found');\n          return;\n        }\n\n        // Calculate target dimensions\n        const scale = Math.min(\n          mergedConfig.maxWidth! / video.videoWidth,\n          mergedConfig.maxHeight! / video.videoHeight,\n          1\n        );\n\n        const targetWidth = Math.floor(video.videoWidth * scale);\n        const targetHeight = Math.floor(video.videoHeight * scale);\n\n        canvas.width = targetWidth;\n        canvas.height = targetHeight;\n\n        // Draw video frame to canvas\n        ctx.drawImage(video, 0, 0, targetWidth, targetHeight);\n\n        // Check if we should send this frame\n        if (mergedConfig.useHashing) {\n          const imageData = ctx.getImageData(0, 0, targetWidth, targetHeight);\n          const hash = createSimpleHash(imageData);\n          const now = Date.now();\n          const timeSinceLastSent = now - lastSentTimeRef.current;\n\n          const shouldSend = decideShouldSend(\n            hash,\n            lastFrameHashRef.current,\n            timeSinceLastSent,\n            mergedConfig as Required<ScreenCaptureConfig>\n          );\n\n          if (!shouldSend) {\n            if (mergedConfig.debugLogs) {\n              console.log('🎬 [VIDEO] Skipping frame - no significant change');\n            }\n            return;\n          }\n\n          lastFrameHashRef.current = hash;\n          lastSentTimeRef.current = now;\n        }\n\n        // Convert to base64 and send\n        const base64 = canvas.toDataURL('image/jpeg', mergedConfig.jpegQuality);\n        const data = base64.slice(base64.indexOf(',') + 1);\n\n        if (mergedConfig.debugLogs) {\n          console.log('📹 [VIDEO] Sending frame:', {\n            dimensions: `${targetWidth}x${targetHeight}`,\n            dataLength: data.length,\n          });\n        }\n\n        client?.sendRealtimeInput([{ mimeType: 'image/jpeg', data }]);\n      } catch (error) {\n        console.error('❌ [VIDEO] Capture error:', error);\n      }\n    },\n    [videoRef, canvasRef, mergedConfig]\n  );\n\n  // Start video capture callback\n  const startVideoCapture = useCallback(\n    (client: GenAILiveClient, videoStream: MediaStream) => {\n      if (!videoStream) {\n        if (captureTimeoutRef.current !== -1) {\n          clearTimeout(captureTimeoutRef.current);\n          captureTimeoutRef.current = -1;\n        }\n        return;\n      }\n\n      const scheduleNextCapture = () => {\n        captureTimeoutRef.current = window.setTimeout(() => {\n          captureFrame(client);\n          scheduleNextCapture();\n        }, mergedConfig.checkInterval);\n      };\n\n      // Start with requestAnimationFrame for smooth initial capture\n      requestAnimationFrame(() => {\n        captureFrame(client);\n        scheduleNextCapture();\n      });\n\n      return () => {\n        if (captureTimeoutRef.current !== -1) {\n          clearTimeout(captureTimeoutRef.current);\n          captureTimeoutRef.current = -1;\n        }\n      };\n    },\n    [videoRef, mergedConfig.debugLogs, isCapturing]\n  );\n\n  // Stop video capture callback\n  const stopVideoCapture = useCallback(() => {\n    clientRef.current = null;\n    videoStreamRef.current = null;\n    setIsCapturing(false);\n\n    // Clear any pending timeout\n    if (captureTimeoutRef.current !== -1) {\n      clearTimeout(captureTimeoutRef.current);\n      captureTimeoutRef.current = -1;\n    }\n\n    // Clear video element\n    const video = videoRef.current;\n    if (video) {\n      video.srcObject = null;\n    }\n  }, [videoRef]);\n\n  return {\n    startVideoCapture,\n    stopVideoCapture,\n    isCapturing,\n  };\n}\n","import type {\n  AudioConfig,\n  AudioMetrics,\n  AudioProcessingType,\n  AudioRecorderEvents,\n  EnvironmentPreset,\n  NoiseGateConfig,\n  NoiseGateState,\n  NoiseSuppressionConfig,\n} from '@/types/audio-types';\n\nimport EventEmitter from 'eventemitter3';\n\nimport { createWorketFromSrc } from '@/lib/audio/audioworklet-registry';\nimport {\n  DEFAULT_NOISE_GATE_CONFIG,\n  DEFAULT_NOISE_SUPPRESSION_CONFIG,\n} from '@/lib/audio/noise-config';\nimport { NoiseGateProcessor } from '@/lib/audio/noise-gate-processor';\nimport { NoiseSuppressionProcessor } from '@/lib/audio/noise-suppression-processor';\nimport { audioContext } from '@/utils/audio-utils';\nimport AudioRecordingWorklet from '@/worklets/audio-processing';\nimport VolMeterWorket from '@/worklets/vol-meter';\n\n// Extended MediaTrackSettings type with audio processing properties\ninterface ExtendedMediaTrackSettings extends MediaTrackSettings {\n  autoGainControl?: boolean;\n  echoCancellation?: boolean;\n  noiseSuppression?: boolean;\n  sampleRate?: number;\n}\n\nexport class AudioRecorder extends EventEmitter<AudioRecorderEvents> {\n  private accumulatedBuffers: ArrayBuffer[] = [];\n  private config: {\n    sampleRate: number;\n    environmentPreset: EnvironmentPreset;\n    noiseGate: NoiseGateConfig;\n    noiseSuppression: NoiseSuppressionConfig;\n    onVolumeChange?: (volume: number) => void;\n  };\n  private gateStateHistory: Array<{\n    state: NoiseGateState;\n    timestamp: number;\n  }> = [];\n  private metrics: AudioMetrics = {\n    currentVolume: 0,\n    averageVolume: 0,\n    gateOpenCount: 0,\n    gateOpenPercentage: 0,\n    suppressionType: 'none',\n    errorCount: 0,\n  };\n  private noiseGate?: NoiseGateProcessor;\n  private noiseSuppression?: NoiseSuppressionProcessor;\n  private starting: Promise<void> | null = null;\n  private volumeHistory: number[] = [];\n\n  audioContext: AudioContext | undefined;\n  recording: boolean = false;\n  recordingWorklet: AudioWorkletNode | undefined;\n  source: MediaStreamAudioSourceNode | undefined;\n  stream: MediaStream | undefined;\n  vuWorklet: AudioWorkletNode | undefined;\n\n  constructor(config: AudioConfig = {}) {\n    super();\n\n    // Merge with defaults\n    this.config = {\n      sampleRate: config.sampleRate || 48000,\n      noiseGate: {\n        ...DEFAULT_NOISE_GATE_CONFIG,\n        ...config.noiseGate,\n      },\n      noiseSuppression: {\n        ...DEFAULT_NOISE_SUPPRESSION_CONFIG,\n        ...config.noiseSuppression,\n      },\n      environmentPreset: config.environmentPreset || 'home',\n      onVolumeChange: config.onVolumeChange,\n    };\n  }\n\n  private updateMetrics(volume: number): void {\n    this.metrics.currentVolume = volume;\n\n    // Update volume history\n    this.volumeHistory.push(volume);\n    if (this.volumeHistory.length > 60) {\n      // Keep last 60 samples (~1 second)\n      this.volumeHistory.shift();\n    }\n\n    // Calculate average volume\n    this.metrics.averageVolume =\n      this.volumeHistory.reduce((a, b) => a + b, 0) / this.volumeHistory.length;\n\n    // Calculate gate open percentage (last minute)\n    const oneMinuteAgo = Date.now() - 60000;\n    const recentGateHistory = this.gateStateHistory.filter(\n      (h) => h.timestamp > oneMinuteAgo\n    );\n\n    if (recentGateHistory.length > 1) {\n      let openTime = 0;\n      for (let i = 1; i < recentGateHistory.length; i++) {\n        if (recentGateHistory[i - 1].state === 'open') {\n          openTime +=\n            recentGateHistory[i].timestamp - recentGateHistory[i - 1].timestamp;\n        }\n      }\n      this.metrics.gateOpenPercentage = (openTime / 60000) * 100;\n    }\n\n    // Call volume change callback if provided\n    if (this.config.onVolumeChange) {\n      this.config.onVolumeChange(volume);\n    }\n  }\n\n  // Add new method to just clear the buffer without returning audio\n  clearBuffer(): number {\n    const clearedBytes = this.accumulatedBuffers.reduce(\n      (acc, buf) => acc + buf.byteLength,\n      0\n    );\n    this.accumulatedBuffers = [];\n    return clearedBytes;\n  }\n\n  getAccumulatedAudio(): ArrayBuffer {\n    // Combine all accumulated buffers\n    const totalLength = this.accumulatedBuffers.reduce(\n      (acc, buf) => acc + buf.byteLength,\n      0\n    );\n    const combinedBuffer = new ArrayBuffer(totalLength);\n    const combinedView = new Uint8Array(combinedBuffer);\n\n    let offset = 0;\n    this.accumulatedBuffers.forEach((buffer) => {\n      const view = new Uint8Array(buffer);\n      combinedView.set(view, offset);\n      offset += buffer.byteLength;\n    });\n\n    // Clear the accumulated buffers\n    this.accumulatedBuffers = [];\n\n    return combinedBuffer;\n  }\n\n  getMetrics(): AudioMetrics {\n    return { ...this.metrics };\n  }\n\n  getNoiseSuppressionType(): AudioProcessingType {\n    return this.metrics.suppressionType;\n  }\n\n  async start() {\n    if (typeof window === 'undefined') {\n      console.warn(\n        '[AudioRecorder] Audio recording not available in server environment'\n      );\n      return;\n    }\n\n    if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {\n      throw new Error('Could not request user media');\n    }\n\n    // Clear accumulated buffers when starting new recording\n    this.accumulatedBuffers = [];\n    this.volumeHistory = [];\n    this.gateStateHistory = [];\n\n    // eslint-disable-next-line no-async-promise-executor\n    this.starting = new Promise(async (resolve, reject) => {\n      try {\n        // Enhanced audio constraints with noise suppression\n        const audioConstraints: MediaStreamConstraints = {\n          audio: {\n            sampleRate: this.config.sampleRate,\n            channelCount: 1,\n            // Enable browser-level noise suppression\n            noiseSuppression: true,\n            echoCancellation: true,\n            autoGainControl: true,\n            // Additional quality settings\n            latency: 0.1,\n          } as MediaTrackConstraints,\n        };\n\n        this.stream =\n          await navigator.mediaDevices.getUserMedia(audioConstraints);\n\n        // Log actual settings applied\n        const audioTrack = this.stream.getAudioTracks()[0];\n        const settings = audioTrack.getSettings() as ExtendedMediaTrackSettings;\n        console.log('[AudioRecorder] Applied settings:', {\n          noiseSuppression: settings.noiseSuppression,\n          echoCancellation: settings.echoCancellation,\n          autoGainControl: settings.autoGainControl,\n          sampleRate: settings.sampleRate,\n        });\n\n        this.audioContext = await audioContext({\n          sampleRate: this.config.sampleRate,\n        });\n        this.source = this.audioContext.createMediaStreamSource(this.stream);\n\n        // Initialize processors\n        if (this.config.noiseSuppression.enabled) {\n          try {\n            this.noiseSuppression = new NoiseSuppressionProcessor(\n              this.config.noiseSuppression,\n              this.audioContext\n            );\n            await this.noiseSuppression.initialize();\n            this.metrics.suppressionType =\n              this.noiseSuppression.getProcessingType();\n          } catch (suppressionError) {\n            console.error(\n              '[AudioRecorder] Failed to initialize noise suppression:',\n              suppressionError\n            );\n            // Continue without noise suppression\n            this.noiseSuppression = undefined;\n          }\n        }\n\n        if (this.config.noiseGate.enabled) {\n          this.noiseGate = new NoiseGateProcessor(\n            this.audioContext,\n            this.config.noiseGate\n          );\n          this.noiseGate.onStateChange((state) => {\n            this.emit('gateStateChanged', state);\n            this.gateStateHistory.push({\n              state,\n              timestamp: this.audioContext!.currentTime * 1000, // Convert to milliseconds for consistency\n            });\n            if (state === 'open') {\n              this.metrics.gateOpenCount++;\n            }\n          });\n        }\n\n        // Build audio chain\n        let audioChain: AudioNode = this.source;\n\n        // Apply noise suppression\n        if (this.noiseSuppression) {\n          audioChain = this.noiseSuppression.process(audioChain);\n        }\n\n        // Apply noise gate\n        if (this.noiseGate) {\n          audioChain = this.noiseGate.connect(audioChain);\n        }\n\n        // Recording worklet\n        const workletName = 'audio-recorder-worklet';\n        const src = createWorketFromSrc(workletName, AudioRecordingWorklet);\n        await this.audioContext.audioWorklet.addModule(src);\n        this.recordingWorklet = new AudioWorkletNode(\n          this.audioContext,\n          workletName\n        );\n\n        this.recordingWorklet.port.onmessage = async (ev: MessageEvent) => {\n          try {\n            const arrayBuffer = ev.data?.data?.int16arrayBuffer;\n            if (arrayBuffer && arrayBuffer instanceof ArrayBuffer) {\n              const arrayBufferString = arrayBufferToBase64(arrayBuffer);\n              this.accumulatedBuffers.push(arrayBuffer);\n              this.emit('data', arrayBufferString);\n            }\n          } catch (error) {\n            this.metrics.errorCount++;\n            console.error(\n              '[AudioRecorder] Error processing audio data:',\n              error\n            );\n            this.emit('error', error as Error);\n            // Continue recording despite the error\n          }\n        };\n\n        audioChain.connect(this.recordingWorklet);\n\n        // VU meter worklet - connect to source for raw input monitoring\n        const vuWorkletName = 'vu-meter';\n        await this.audioContext.audioWorklet.addModule(\n          createWorketFromSrc(vuWorkletName, VolMeterWorket)\n        );\n        this.vuWorklet = new AudioWorkletNode(this.audioContext, vuWorkletName);\n        this.vuWorklet.port.onmessage = (ev: MessageEvent) => {\n          const volume = ev.data.volume;\n          this.updateMetrics(volume);\n\n          // Process noise gate\n          if (this.noiseGate) {\n            this.noiseGate.processVolume(volume);\n          }\n\n          this.emit('volume', volume);\n        };\n\n        this.source.connect(this.vuWorklet);\n        this.recording = true;\n\n        console.log('[AudioRecorder] Started with noise filtering:', {\n          gate: this.config.noiseGate.enabled,\n          suppression: this.metrics.suppressionType,\n        });\n\n        resolve();\n      } catch (error) {\n        this.metrics.errorCount++;\n        console.error('[AudioRecorder] Failed to start:', error);\n\n        // Clean up any partially initialized resources\n        if (this.noiseGate) {\n          this.noiseGate.cleanup();\n          this.noiseGate = undefined;\n        }\n\n        if (this.noiseSuppression) {\n          await this.noiseSuppression.cleanup();\n          this.noiseSuppression = undefined;\n        }\n\n        if (this.source) {\n          this.source.disconnect();\n          this.source = undefined;\n        }\n\n        if (this.stream) {\n          this.stream.getTracks().forEach((track) => track.stop());\n          this.stream = undefined;\n        }\n\n        this.emit('error', error as Error);\n        reject(error);\n      } finally {\n        this.starting = null;\n      }\n    });\n  }\n\n  async stop() {\n    // its plausible that stop would be called before start completes\n    // such as if the websocket immediately hangs up\n    const handleStop = async () => {\n      // Cleanup processors\n      if (this.noiseGate) {\n        this.noiseGate.cleanup();\n        this.noiseGate = undefined;\n      }\n\n      if (this.noiseSuppression) {\n        await this.noiseSuppression.cleanup();\n        this.noiseSuppression = undefined;\n      }\n\n      this.source?.disconnect();\n      this.stream?.getTracks().forEach((track) => track.stop());\n      this.stream = undefined;\n      this.recordingWorklet = undefined;\n      this.vuWorklet = undefined;\n      this.recording = false;\n\n      console.log('[AudioRecorder] Stopped and cleaned up');\n    };\n\n    if (this.starting) {\n      await this.starting;\n    }\n\n    await handleStop();\n  }\n\n  updateNoiseGateConfig(config: Partial<NoiseGateConfig>): void {\n    if (this.noiseGate) {\n      this.noiseGate.updateConfig(config);\n    }\n    this.config.noiseGate = { ...this.config.noiseGate, ...config };\n  }\n\n  updateNoiseSuppressionConfig(config: Partial<NoiseSuppressionConfig>): void {\n    if (this.noiseSuppression) {\n      this.noiseSuppression.updateConfig(config);\n    }\n    this.config.noiseSuppression = {\n      ...this.config.noiseSuppression,\n      ...config,\n    };\n  }\n}\n\nfunction arrayBufferToBase64(buffer: ArrayBuffer) {\n  var binary = '';\n  var bytes = new Uint8Array(buffer);\n  var len = bytes.byteLength;\n  for (var i = 0; i < len; i++) {\n    binary += String.fromCharCode(bytes[i]);\n  }\n  return window.btoa(binary);\n}\n","export type WorkletGraph = {\n  handlers: Array<(this: MessagePort, ev: MessageEvent) => any>;\n  node?: AudioWorkletNode;\n};\n\nexport const registeredWorklets: Map<\n  AudioContext,\n  Record<string, WorkletGraph>\n> = new Map();\n\nexport const createWorketFromSrc = (\n  workletName: string,\n  workletSrc: string\n) => {\n  const script = new Blob(\n    [`registerProcessor(\"${workletName}\", ${workletSrc})`],\n    {\n      type: 'application/javascript',\n    }\n  );\n\n  return URL.createObjectURL(script);\n};\n","/**\n * Noise Configuration: Centralized audio noise suppression and gate settings\n *\n * Environment-specific configurations for different noise conditions\n * with validation and helper functions\n */\n\nimport type {\n  AudioConfig,\n  AudioConfigValidationError,\n  EnvironmentConfig,\n  EnvironmentPreset,\n  NoiseGateConfig,\n  NoiseSuppressionConfig,\n} from '@/types/audio-types';\n\n// Default Noise Gate Configuration\nexport const DEFAULT_NOISE_GATE_CONFIG: NoiseGateConfig = {\n  enabled: true,\n  threshold: 0.04, // 4% - balanced threshold\n  attackTime: 30, // Fast response to speech\n  holdTime: 400, // Keep gate open during pauses\n  releaseTime: 150, // Quick close to cut noise\n};\n\n// Default Noise Suppression Configuration\nexport const DEFAULT_NOISE_SUPPRESSION_CONFIG: NoiseSuppressionConfig = {\n  enabled: true,\n  enhancementLevel: 'medium',\n  fallbackToBasic: true,\n  // API keys should come from environment variables\n  accessKey: process.env.NEXT_PUBLIC_PICOVOICE_ACCESS_KEY,\n  modelPath: process.env.NEXT_PUBLIC_KOALA_MODEL_PATH,\n};\n\n// Environment Presets\nexport const ENVIRONMENT_PRESETS: Record<EnvironmentPreset, EnvironmentConfig> =\n  {\n    office: {\n      description: 'Quiet office environment with minimal background noise',\n      noiseGate: {\n        ...DEFAULT_NOISE_GATE_CONFIG,\n        threshold: 0.025,\n        holdTime: 350,\n      },\n      noiseSuppression: {\n        ...DEFAULT_NOISE_SUPPRESSION_CONFIG,\n        enhancementLevel: 'light',\n      },\n    },\n\n    home: {\n      description: 'Home environment with moderate background noise',\n      noiseGate: {\n        ...DEFAULT_NOISE_GATE_CONFIG,\n        threshold: 0.05,\n        holdTime: 450,\n        attackTime: 25,\n      },\n      noiseSuppression: {\n        ...DEFAULT_NOISE_SUPPRESSION_CONFIG,\n        enhancementLevel: 'medium',\n      },\n    },\n\n    noisy: {\n      description:\n        'Noisy environment (cafe, public space) with significant background noise',\n      noiseGate: {\n        ...DEFAULT_NOISE_GATE_CONFIG,\n        threshold: 0.08,\n        holdTime: 600,\n        attackTime: 20,\n        releaseTime: 100,\n      },\n      noiseSuppression: {\n        ...DEFAULT_NOISE_SUPPRESSION_CONFIG,\n        enhancementLevel: 'aggressive',\n      },\n    },\n\n    studio: {\n      description: 'Professional recording environment with minimal noise',\n      noiseGate: {\n        ...DEFAULT_NOISE_GATE_CONFIG,\n        threshold: 0.015,\n        attackTime: 15,\n        releaseTime: 250,\n      },\n      noiseSuppression: {\n        ...DEFAULT_NOISE_SUPPRESSION_CONFIG,\n        enhancementLevel: 'light',\n      },\n    },\n\n    custom: {\n      description: 'Custom configuration',\n      noiseGate: DEFAULT_NOISE_GATE_CONFIG,\n      noiseSuppression: DEFAULT_NOISE_SUPPRESSION_CONFIG,\n    },\n  };\n\nexport function createAudioConfig(\n  preset: EnvironmentPreset = 'home',\n  overrides: Partial<AudioConfig> = {}\n): AudioConfig {\n  const envConfig = getEnvironmentConfig(preset);\n\n  return {\n    sampleRate: 48000,\n    noiseGate: {\n      ...envConfig.noiseGate,\n      ...overrides.noiseGate,\n    },\n    noiseSuppression: {\n      ...envConfig.noiseSuppression,\n      ...overrides.noiseSuppression,\n    },\n    environmentPreset: preset,\n  };\n}\n\n// Helper functions\nexport function getEnvironmentConfig(\n  preset: EnvironmentPreset = 'home'\n): EnvironmentConfig {\n  return ENVIRONMENT_PRESETS[preset] || ENVIRONMENT_PRESETS.home;\n}\n\n// Validation functions\nexport function validateNoiseGateConfig(\n  config: Partial<NoiseGateConfig>\n): AudioConfigValidationError[] {\n  const errors: AudioConfigValidationError[] = [];\n\n  if (config.threshold !== undefined) {\n    if (config.threshold < 0 || config.threshold > 1) {\n      errors.push({\n        field: 'noiseGate.threshold',\n        message: 'Threshold must be between 0 and 1',\n        value: config.threshold,\n      });\n    }\n  }\n\n  if (config.attackTime !== undefined && config.attackTime < 0) {\n    errors.push({\n      field: 'noiseGate.attackTime',\n      message: 'Attack time must be positive',\n      value: config.attackTime,\n    });\n  }\n\n  if (config.holdTime !== undefined && config.holdTime < 0) {\n    errors.push({\n      field: 'noiseGate.holdTime',\n      message: 'Hold time must be positive',\n      value: config.holdTime,\n    });\n  }\n\n  if (config.releaseTime !== undefined && config.releaseTime < 0) {\n    errors.push({\n      field: 'noiseGate.releaseTime',\n      message: 'Release time must be positive',\n      value: config.releaseTime,\n    });\n  }\n\n  return errors;\n}\n\nexport function validateNoiseSuppressionConfig(\n  config: Partial<NoiseSuppressionConfig>\n): AudioConfigValidationError[] {\n  const errors: AudioConfigValidationError[] = [];\n\n  if (config.enhancementLevel !== undefined) {\n    const validLevels = ['light', 'medium', 'aggressive'];\n    if (!validLevels.includes(config.enhancementLevel)) {\n      errors.push({\n        field: 'noiseSuppression.enhancementLevel',\n        message: `Enhancement level must be one of: ${validLevels.join(', ')}`,\n        value: config.enhancementLevel,\n      });\n    }\n  }\n\n  return errors;\n}\n\n// Quick preset helpers\nexport const getOfficeConfig = () => createAudioConfig('office');\nexport const getHomeConfig = () => createAudioConfig('home');\nexport const getNoisyConfig = () => createAudioConfig('noisy');\nexport const getStudioConfig = () => createAudioConfig('studio');\n\n// Debug helper\nexport function logAudioConfig(config: AudioConfig, label = 'Audio Config') {\n  if (process.env.NODE_ENV === 'development') {\n    console.group(`🎤 ${label}`);\n    console.log('Sample Rate:', config.sampleRate);\n    console.log('Environment:', config.environmentPreset);\n    console.log('Noise Gate:', config.noiseGate);\n    console.log('Noise Suppression:', config.noiseSuppression);\n    console.groupEnd();\n  }\n}\n","/**\n * Noise Gate Processor: Software noise gate implementation\n *\n * Implements a state machine-based noise gate that filters out\n * audio below a certain threshold to reduce background noise\n */\n\nimport type { NoiseGateConfig, NoiseGateState } from '@/types/audio-types';\n\nexport class NoiseGateProcessor {\n  private audioContext: AudioContext;\n  private config: NoiseGateConfig;\n  private gainNode: GainNode;\n  private lastVolumeAboveThreshold: number;\n  private pendingStateTransitionTime = 0;\n  private state: NoiseGateState = 'closed';\n  private stateChangeCallbacks: Array<(state: NoiseGateState) => void> = [];\n\n  constructor(audioContext: AudioContext, config: NoiseGateConfig) {\n    this.audioContext = audioContext;\n    this.config = config;\n    this.gainNode = audioContext.createGain();\n    this.gainNode.gain.value = config.enabled ? 0 : 1;\n\n    // Initialize to current audio context time to prevent immediate timeout\n    this.lastVolumeAboveThreshold = audioContext.currentTime;\n  }\n\n  /**\n   * Notify listeners of state change\n   */\n  private notifyStateChange(state: NoiseGateState): void {\n    this.stateChangeCallbacks.forEach((cb) => cb(state));\n  }\n\n  /**\n   * Transition to a new state using pure AudioParam automation\n   */\n  private transitionTo(newState: NoiseGateState): void {\n    if (this.state === newState) return;\n\n    const currentTime = this.audioContext.currentTime;\n    this.gainNode.gain.cancelScheduledValues(currentTime);\n\n    switch (newState) {\n      case 'closing':\n        this.state = 'closing';\n        // Schedule gain reduction to 0\n        this.gainNode.gain.setTargetAtTime(\n          0.0,\n          currentTime,\n          this.config.releaseTime / 1000 // Proper time constant\n        );\n        // Schedule state change at completion time\n        this.pendingStateTransitionTime =\n          currentTime + this.config.releaseTime / 1000;\n        break;\n\n      case 'opening':\n        this.state = 'opening';\n        // Schedule gain increase to 1\n        this.gainNode.gain.setTargetAtTime(\n          1.0,\n          currentTime,\n          this.config.attackTime / 1000 // Proper time constant\n        );\n        // Schedule state change at completion time\n        this.pendingStateTransitionTime =\n          currentTime + this.config.attackTime / 1000;\n        break;\n    }\n\n    this.notifyStateChange(newState);\n  }\n\n  /**\n   * Cleanup resources\n   */\n  cleanup(): void {\n    // Cancel any scheduled audio parameter changes\n    this.gainNode.gain.cancelScheduledValues(this.audioContext.currentTime);\n    this.gainNode.disconnect();\n    this.stateChangeCallbacks = [];\n    this.pendingStateTransitionTime = 0;\n  }\n\n  /**\n   * Connect audio source through the gate\n   */\n  connect(source: AudioNode): AudioNode {\n    source.connect(this.gainNode);\n    return this.gainNode;\n  }\n\n  /**\n   * Get current configuration\n   */\n  getConfig(): NoiseGateConfig {\n    return { ...this.config };\n  }\n\n  /**\n   * Get current state\n   */\n  getState(): NoiseGateState {\n    return this.state;\n  }\n\n  /**\n   * Remove state change listener\n   */\n  offStateChange(callback: (state: NoiseGateState) => void): void {\n    const index = this.stateChangeCallbacks.indexOf(callback);\n    if (index > -1) {\n      this.stateChangeCallbacks.splice(index, 1);\n    }\n  }\n\n  /**\n   * Add state change listener\n   */\n  onStateChange(callback: (state: NoiseGateState) => void): void {\n    this.stateChangeCallbacks.push(callback);\n  }\n\n  /**\n   * Process volume level and update gate state\n   */\n  processVolume(volume: number): void {\n    if (!this.config.enabled) return;\n\n    const now = this.audioContext.currentTime;\n    const isAboveThreshold = volume > this.config.threshold;\n\n    if (isAboveThreshold) {\n      this.lastVolumeAboveThreshold = now;\n    }\n\n    const timeSinceLastSignal = now - this.lastVolumeAboveThreshold;\n\n    // Check for completed transitions using audio context time\n    if (\n      this.pendingStateTransitionTime > 0 &&\n      now >= this.pendingStateTransitionTime\n    ) {\n      if (this.state === 'opening') {\n        this.state = 'open';\n        this.notifyStateChange('open');\n      } else if (this.state === 'closing') {\n        this.state = 'closed';\n        this.notifyStateChange('closed');\n      }\n      this.pendingStateTransitionTime = 0;\n    }\n\n    switch (this.state) {\n      case 'closed':\n        if (isAboveThreshold) {\n          this.transitionTo('opening');\n        }\n        break;\n\n      case 'closing':\n        if (isAboveThreshold) {\n          // Re-open if signal comes back\n          this.transitionTo('opening');\n        }\n        break;\n\n      case 'open':\n        if (\n          !isAboveThreshold &&\n          timeSinceLastSignal > this.config.holdTime / 1000\n        ) {\n          this.transitionTo('closing');\n        }\n        break;\n\n      case 'opening':\n        // Check if we should interrupt the opening\n        if (\n          !isAboveThreshold &&\n          timeSinceLastSignal > this.config.holdTime / 1000\n        ) {\n          this.transitionTo('closing');\n        }\n        break;\n    }\n  }\n\n  /**\n   * Update configuration\n   */\n  updateConfig(config: Partial<NoiseGateConfig>): void {\n    this.config = { ...this.config, ...config };\n\n    if (!this.config.enabled) {\n      // Cancel any pending transitions and open the gate immediately\n      this.gainNode.gain.cancelScheduledValues(this.audioContext.currentTime);\n      this.gainNode.gain.value = 1;\n      this.state = 'open';\n      this.pendingStateTransitionTime = 0;\n      this.notifyStateChange('open');\n    }\n    // Note: For other config changes during active transitions,\n    // the new timing values will be used on the next transition\n  }\n}\n","/**\n * Noise Suppression Processor: Advanced noise suppression with Picovoice Koala\n *\n * Implements noise suppression using Picovoice Koala SDK with\n * Web Audio API fallback for environments without Koala support\n */\n\nimport type {\n  AudioProcessingType,\n  AudioProcessor,\n  NoiseEnhancementLevel,\n  NoiseSuppressionConfig,\n} from '@/types/audio-types';\n\nimport { KoalaWorker } from '@picovoice/koala-web';\nimport { WebVoiceProcessor } from '@picovoice/web-voice-processor';\n\n// Basic Web Audio processor for fallback\nclass BasicAudioProcessor implements AudioProcessor {\n  private audioContext: AudioContext;\n  private compressor?: DynamicsCompressorNode;\n  private config: NoiseSuppressionConfig;\n  private highPassFilter?: BiquadFilterNode;\n  private lowPassFilter?: BiquadFilterNode;\n\n  constructor(audioContext: AudioContext, config: NoiseSuppressionConfig) {\n    this.audioContext = audioContext;\n    this.config = config;\n  }\n\n  private getCompressorRatio(): number {\n    const ratios: Record<NoiseEnhancementLevel, number> = {\n      light: 4,\n      medium: 8,\n      aggressive: 16,\n    };\n    return ratios[this.config.enhancementLevel];\n  }\n\n  private getCompressorThreshold(): number {\n    const thresholds: Record<NoiseEnhancementLevel, number> = {\n      light: -18,\n      medium: -14,\n      aggressive: -12,\n    };\n    return thresholds[this.config.enhancementLevel];\n  }\n\n  private getHighPassFrequency(): number {\n    const frequencies: Record<NoiseEnhancementLevel, number> = {\n      light: 70,\n      medium: 90,\n      aggressive: 120,\n    };\n    return frequencies[this.config.enhancementLevel];\n  }\n\n  private getLowPassFrequency(): number {\n    const frequencies: Record<NoiseEnhancementLevel, number> = {\n      light: 11000,\n      medium: 9000,\n      aggressive: 7000,\n    };\n    return frequencies[this.config.enhancementLevel];\n  }\n\n  async cleanup(): Promise<void> {\n    this.highPassFilter?.disconnect();\n    this.lowPassFilter?.disconnect();\n    this.compressor?.disconnect();\n  }\n\n  getType(): AudioProcessingType {\n    return 'basic';\n  }\n\n  async initialize(): Promise<void> {\n    // High-pass filter to remove low-frequency noise\n    this.highPassFilter = this.audioContext.createBiquadFilter();\n    this.highPassFilter.type = 'highpass';\n    this.highPassFilter.frequency.value = this.getHighPassFrequency();\n    this.highPassFilter.Q.value = 0.7;\n\n    // Low-pass filter to remove high-frequency noise\n    this.lowPassFilter = this.audioContext.createBiquadFilter();\n    this.lowPassFilter.type = 'lowpass';\n    this.lowPassFilter.frequency.value = this.getLowPassFrequency();\n    this.lowPassFilter.Q.value = 0.7;\n\n    // Compressor to even out volume levels\n    this.compressor = this.audioContext.createDynamicsCompressor();\n    this.compressor.threshold.value = this.getCompressorThreshold();\n    this.compressor.knee.value = 5;\n    this.compressor.ratio.value = this.getCompressorRatio();\n    this.compressor.attack.value = 0.01;\n    this.compressor.release.value = 0.1;\n  }\n\n  process(source: AudioNode): AudioNode {\n    if (!this.highPassFilter || !this.lowPassFilter || !this.compressor) {\n      return source;\n    }\n\n    source.connect(this.highPassFilter);\n    this.highPassFilter.connect(this.lowPassFilter);\n    this.lowPassFilter.connect(this.compressor);\n\n    return this.compressor;\n  }\n}\n\n// Koala processor for advanced noise suppression\nclass KoalaProcessor implements AudioProcessor {\n  private config: NoiseSuppressionConfig;\n  private enhancedAudioCallback?: (enhancedPcm: Int16Array) => void;\n  private koalaWorker: KoalaWorker | null = null;\n\n  constructor(config: NoiseSuppressionConfig) {\n    this.config = config;\n  }\n\n  async cleanup(): Promise<void> {\n    if (this.koalaWorker) {\n      await this.stopProcessing();\n      this.koalaWorker.terminate();\n      this.koalaWorker = null;\n    }\n  }\n\n  getType(): AudioProcessingType {\n    return 'koala';\n  }\n\n  async initialize(): Promise<void> {\n    if (!this.config.accessKey) {\n      throw new Error('Picovoice access key is required for Koala');\n    }\n\n    const koalaModel = this.config.modelPath\n      ? { publicPath: this.config.modelPath }\n      : this.config.modelBase64\n        ? { base64: this.config.modelBase64 }\n        : undefined;\n\n    if (!koalaModel) {\n      throw new Error('Koala model path or base64 data is required');\n    }\n\n    const processCallback = (enhancedPcm: Int16Array) => {\n      if (this.enhancedAudioCallback) {\n        this.enhancedAudioCallback(enhancedPcm);\n      }\n    };\n\n    const options = {\n      processErrorCallback: (error: Error | unknown) => {\n        console.error('[KoalaProcessor] Processing error:', error);\n      },\n    };\n\n    this.koalaWorker = await KoalaWorker.create(\n      this.config.accessKey,\n      processCallback,\n      koalaModel,\n      options\n    );\n  }\n\n  process(source: AudioNode): AudioNode {\n    // Koala processes audio separately via WebVoiceProcessor\n    // Return source as-is since Koala handles the processing\n    return source;\n  }\n\n  setEnhancedAudioCallback(callback: (enhancedPcm: Int16Array) => void): void {\n    this.enhancedAudioCallback = callback;\n  }\n\n  async startProcessing(): Promise<void> {\n    if (!this.koalaWorker) {\n      throw new Error('Koala worker not initialized');\n    }\n\n    await this.koalaWorker.reset();\n    await WebVoiceProcessor.subscribe(this.koalaWorker);\n  }\n\n  async stopProcessing(): Promise<void> {\n    if (this.koalaWorker) {\n      await WebVoiceProcessor.unsubscribe(this.koalaWorker);\n    }\n  }\n}\n\n// Main noise suppression processor\nexport class NoiseSuppressionProcessor {\n  private audioContext?: AudioContext;\n  private config: NoiseSuppressionConfig;\n  private initialized = false;\n  private processor: AudioProcessor | null = null;\n\n  constructor(config: NoiseSuppressionConfig, audioContext?: AudioContext) {\n    this.config = config;\n    this.audioContext = audioContext;\n  }\n\n  private shouldReinitialize(\n    newConfig: Partial<NoiseSuppressionConfig>\n  ): boolean {\n    // Reinitialize if key settings changed\n    return !!(\n      newConfig.enabled !== undefined ||\n      newConfig.accessKey !== undefined ||\n      newConfig.modelPath !== undefined ||\n      newConfig.enhancementLevel !== undefined\n    );\n  }\n\n  private shouldUseKoala(): boolean {\n    return !!(\n      this.config.accessKey &&\n      (this.config.modelPath || this.config.modelBase64)\n    );\n  }\n\n  async cleanup(): Promise<void> {\n    if (this.processor) {\n      await this.processor.cleanup();\n      this.processor = null;\n    }\n    this.initialized = false;\n  }\n\n  getProcessingType(): AudioProcessingType {\n    if (!this.config.enabled) return 'none';\n    return this.processor?.getType() || 'none';\n  }\n\n  async initialize(): Promise<void> {\n    if (!this.config.enabled) {\n      this.initialized = true;\n      return;\n    }\n\n    try {\n      // Try Koala first if configured\n      if (this.shouldUseKoala()) {\n        const koalaProcessor = new KoalaProcessor(this.config);\n        await koalaProcessor.initialize();\n        this.processor = koalaProcessor;\n        console.log(\n          '[NoiseSuppressionProcessor] Initialized with Picovoice Koala'\n        );\n      } else if (this.config.fallbackToBasic && this.audioContext) {\n        // Fallback to basic Web Audio filters\n        const basicProcessor = new BasicAudioProcessor(\n          this.audioContext,\n          this.config\n        );\n        await basicProcessor.initialize();\n        this.processor = basicProcessor;\n        console.log(\n          '[NoiseSuppressionProcessor] Initialized with Web Audio filters'\n        );\n      }\n\n      this.initialized = true;\n    } catch (error) {\n      console.error('[NoiseSuppressionProcessor] Initialization error:', error);\n\n      // Try fallback if Koala fails\n      if (this.config.fallbackToBasic && this.audioContext) {\n        try {\n          const basicProcessor = new BasicAudioProcessor(\n            this.audioContext,\n            this.config\n          );\n          await basicProcessor.initialize();\n          this.processor = basicProcessor;\n          this.initialized = true;\n          console.log(\n            '[NoiseSuppressionProcessor] Fell back to Web Audio filters'\n          );\n        } catch (fallbackError) {\n          console.error(\n            '[NoiseSuppressionProcessor] Fallback failed:',\n            fallbackError\n          );\n          throw fallbackError;\n        }\n      } else {\n        throw error;\n      }\n    }\n  }\n\n  isKoalaProcessor(): boolean {\n    return this.processor instanceof KoalaProcessor;\n  }\n\n  process(source: AudioNode): AudioNode {\n    if (!this.initialized || !this.processor || !this.config.enabled) {\n      return source;\n    }\n\n    return this.processor.process(source);\n  }\n\n  setKoalaCallback(callback: (enhancedPcm: Int16Array) => void): void {\n    if (this.processor instanceof KoalaProcessor) {\n      this.processor.setEnhancedAudioCallback(callback);\n    }\n  }\n\n  async startKoalaProcessing(): Promise<void> {\n    if (this.processor instanceof KoalaProcessor) {\n      await this.processor.startProcessing();\n    }\n  }\n\n  async stopKoalaProcessing(): Promise<void> {\n    if (this.processor instanceof KoalaProcessor) {\n      await this.processor.stopProcessing();\n    }\n  }\n\n  updateConfig(config: Partial<NoiseSuppressionConfig>): void {\n    this.config = { ...this.config, ...config };\n\n    // If configuration changed significantly, reinitialize\n    if (this.initialized && this.shouldReinitialize(config)) {\n      this.cleanup().then(() => this.initialize());\n    }\n  }\n}\n","export type GetAudioContextOptions = AudioContextOptions & {\n  id?: string;\n};\n\nconst map: Map<string, AudioContext> = new Map();\n\nexport const audioContext: (\n  options?: GetAudioContextOptions\n) => Promise<AudioContext> = (() => {\n  // Only create the promise if we're in the browser\n  const didInteract =\n    typeof window !== 'undefined'\n      ? new Promise((res) => {\n          window.addEventListener('pointerdown', res, { once: true });\n          window.addEventListener('keydown', res, { once: true });\n        })\n      : Promise.resolve();\n\n  return async (options?: GetAudioContextOptions) => {\n    try {\n      // Only try to create Audio if we're in the browser\n      if (typeof window !== 'undefined') {\n        const a = new Audio();\n        a.src =\n          'data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA';\n        await a.play();\n      }\n\n      if (options?.id && map.has(options.id)) {\n        const ctx = map.get(options.id);\n        if (ctx) {\n          return ctx;\n        }\n      }\n\n      // Only create AudioContext if we're in the browser\n      if (typeof window !== 'undefined') {\n        const ctx = new AudioContext(options);\n        if (options?.id) {\n          map.set(options.id, ctx);\n        }\n        return ctx;\n      }\n\n      // Return a mock AudioContext for SSR\n      return {} as AudioContext;\n    } catch (e) {\n      await didInteract;\n      if (options?.id && map.has(options.id)) {\n        const ctx = map.get(options.id);\n        if (ctx) {\n          return ctx;\n        }\n      }\n\n      // Only create AudioContext if we're in the browser\n      if (typeof window !== 'undefined') {\n        const ctx = new AudioContext(options);\n        if (options?.id) {\n          map.set(options.id, ctx);\n        }\n        return ctx;\n      }\n\n      // Return a mock AudioContext for SSR\n      return {} as AudioContext;\n    }\n  };\n})();\n\nexport const blobToJSON = (blob: Blob) =>\n  new Promise((resolve, reject) => {\n    const reader = new FileReader();\n    reader.onload = () => {\n      if (reader.result) {\n        const json = JSON.parse(reader.result as string);\n        resolve(json);\n      } else {\n        reject('oops');\n      }\n    };\n    reader.readAsText(blob);\n  });\n\nexport function base64ToArrayBuffer(base64: string) {\n  var binaryString = atob(base64);\n  var bytes = new Uint8Array(binaryString.length);\n  for (let i = 0; i < binaryString.length; i++) {\n    bytes[i] = binaryString.charCodeAt(i);\n  }\n  return bytes.buffer;\n}\n\n/**\n * Helper to convert ArrayBuffer to base64\n */\nexport function arrayBufferToBase64(buffer: ArrayBuffer): string {\n  const bytes = new Uint8Array(buffer);\n  let binary = '';\n  for (let i = 0; i < bytes.byteLength; i++) {\n    binary += String.fromCharCode(bytes[i]);\n  }\n  return btoa(binary);\n}\n\n/**\n * Helper to concatenate multiple ArrayBuffers\n */\nexport function concatenateArrayBuffers(buffers: ArrayBuffer[]): ArrayBuffer {\n  const totalLength = buffers.reduce((sum, buf) => sum + buf.byteLength, 0);\n  const result = new Uint8Array(totalLength);\n  let offset = 0;\n\n  for (const buffer of buffers) {\n    result.set(new Uint8Array(buffer), offset);\n    offset += buffer.byteLength;\n  }\n\n  return result.buffer;\n}\n\n/**\n * Saves a text string to a .txt file by triggering a browser download\n * @param content - The text content to save\n * @param filename - The filename for the downloaded file (without extension)\n */\nexport const saveTextToFile = (\n  content: string,\n  filename: string = 'system-prompt'\n) => {\n  try {\n    // Create a Blob with the text content\n    const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });\n\n    // Create a temporary URL for the blob\n    const url = URL.createObjectURL(blob);\n\n    // Create a temporary anchor element to trigger download\n    const link = document.createElement('a');\n    link.href = url;\n    link.download = `${filename}.txt`;\n\n    // Append to body, click, and remove\n    document.body.appendChild(link);\n    link.click();\n    document.body.removeChild(link);\n\n    // Clean up the URL object\n    URL.revokeObjectURL(url);\n\n    console.log(`📁 System prompt saved to ${filename}.txt`);\n  } catch (error) {\n    console.error('❌ Error saving system prompt to file:', error);\n  }\n};\n\n/**\n * Sets up beforeunload handler for audio flushing\n * @param flushCallback - Function to call on page unload\n * @returns Cleanup function to remove the handler\n */\nexport function setupAudioFlushOnUnload(\n  flushCallback: () => Promise<void> | void\n): () => void {\n  const handleBeforeUnload = (_event: BeforeUnloadEvent) => {\n    // Execute the flush synchronously if possible\n    const result = flushCallback();\n\n    // If it returns a promise, we can't guarantee it completes\n    // but we try anyway\n    if (result instanceof Promise) {\n      // Note: Most browsers won't wait for this promise\n      result.catch(console.error);\n    }\n  };\n\n  // Also handle visibility change for mobile browsers\n  const handleVisibilityChange = () => {\n    if (document.visibilityState === 'hidden') {\n      flushCallback();\n    }\n  };\n\n  window.addEventListener('beforeunload', handleBeforeUnload);\n  document.addEventListener('visibilitychange', handleVisibilityChange);\n\n  // Return cleanup function\n  return () => {\n    window.removeEventListener('beforeunload', handleBeforeUnload);\n    document.removeEventListener('visibilitychange', handleVisibilityChange);\n  };\n}\n","const AudioRecordingWorklet = `\nclass AudioProcessingWorklet extends AudioWorkletProcessor {\n\n  // send and clear buffer every 2048 samples, \n  // which at 16khz is about 8 times a second\n  buffer = new Int16Array(2048);\n\n  // current write index\n  bufferWriteIndex = 0;\n\n  constructor() {\n    super();\n    this.hasAudio = false;\n  }\n\n  /**\n   * @param inputs Float32Array[][] [input#][channel#][sample#] so to access first inputs 1st channel inputs[0][0]\n   * @param outputs Float32Array[][]\n   */\n  process(inputs) {\n    if (inputs[0].length) {\n      const channel0 = inputs[0][0];\n      this.processChunk(channel0);\n    }\n    return true;\n  }\n\n  sendAndClearBuffer(){\n    this.port.postMessage({\n      event: \"chunk\",\n      data: {\n        int16arrayBuffer: this.buffer.slice(0, this.bufferWriteIndex).buffer,\n      },\n    });\n    this.bufferWriteIndex = 0;\n  }\n\n  processChunk(float32Array) {\n    const l = float32Array.length;\n    \n    for (let i = 0; i < l; i++) {\n      // convert float32 -1 to 1 to int16 -32768 to 32767\n      const int16Value = float32Array[i] * 32768;\n      this.buffer[this.bufferWriteIndex++] = int16Value;\n      if(this.bufferWriteIndex >= this.buffer.length) {\n        this.sendAndClearBuffer();\n      }\n    }\n\n    if(this.bufferWriteIndex >= this.buffer.length) {\n      this.sendAndClearBuffer();\n    }\n  }\n}\n`;\n\nexport default AudioRecordingWorklet;\n","const VolMeterWorket = `\n  class VolMeter extends AudioWorkletProcessor {\n    volume\n    updateIntervalInMS\n    nextUpdateFrame\n\n    constructor() {\n      super()\n      this.volume = 0\n      this.updateIntervalInMS = 25\n      this.nextUpdateFrame = this.updateIntervalInMS\n      this.port.onmessage = event => {\n        if (event.data.updateIntervalInMS) {\n          this.updateIntervalInMS = event.data.updateIntervalInMS\n        }\n      }\n    }\n\n    get intervalInFrames() {\n      return (this.updateIntervalInMS / 1000) * sampleRate\n    }\n\n    process(inputs) {\n      const input = inputs[0]\n\n      if (input.length > 0) {\n        const samples = input[0]\n        let sum = 0\n        let rms = 0\n\n        for (let i = 0; i < samples.length; ++i) {\n          sum += samples[i] * samples[i]\n        }\n\n        rms = Math.sqrt(sum / samples.length)\n        this.volume = Math.max(rms, this.volume * 0.7)\n\n        this.nextUpdateFrame -= samples.length\n        if (this.nextUpdateFrame < 0) {\n          this.nextUpdateFrame += this.intervalInFrames\n          this.port.postMessage({volume: this.volume})\n        }\n      }\n\n      return true\n    }\n  }`;\n\nexport default VolMeterWorket;\n","import {\n  createWorketFromSrc,\n  registeredWorklets,\n} from './audioworklet-registry';\nimport { audioStutterAnalyzer } from './audio-stutter-analyzer';\n\nexport class AudioStreamer {\n  // A queue of audio buffers to be played. Each buffer is a Float32Array.\n  private audioQueue: Float32Array[] = [];\n  private bufferSize: number = 7680;\n  private checkInterval: number | null = null;\n  private endOfQueueAudioSource: AudioBufferSourceNode | null = null;\n  private initialBufferTime: number = 0.3; // Increase from 0.1 to 0.3 seconds\n  private isPlaying: boolean = false;\n  // Indicates if the stream has finished playing, e.g., interrupted.\n  private isStreamComplete: boolean = false;\n  private sampleRate: number = 24000;\n  private scheduledTime: number = 0;\n  private debugAudioPerformance: boolean = false; // Flag for performance debugging\n  // Web Audio API nodes. source => gain => destination\n  public gainNode: GainNode;\n  public onComplete = () => {};\n\n  public source: AudioBufferSourceNode;\n\n  constructor(\n    public context: AudioContext,\n    debugAudioPerformance: boolean = false\n  ) {\n    this.debugAudioPerformance = debugAudioPerformance;\n\n    if (this.debugAudioPerformance) {\n      console.log('🎵 [AUDIO-STREAMER] Constructor', {\n        contextState: context.state,\n        sampleRate: context.sampleRate,\n        baseLatency: context.baseLatency,\n        outputLatency: context.outputLatency,\n        timestamp: Date.now(),\n      });\n    }\n\n    this.gainNode = this.context.createGain();\n    this.source = this.context.createBufferSource();\n    this.gainNode.connect(this.context.destination);\n    this.addPCM16 = this.addPCM16.bind(this);\n\n    // Monitor context state changes\n    this.context.addEventListener('statechange', () => {\n      if (this.debugAudioPerformance) {\n        console.log('🎵 [AUDIO-STREAMER] Context state changed', {\n          newState: this.context.state,\n          currentTime: this.context.currentTime.toFixed(3),\n          timestamp: Date.now(),\n        });\n      }\n    });\n  }\n\n  /**\n   * Converts a Uint8Array of PCM16 audio data into a Float32Array.\n   * PCM16 is a common raw audio format, but the Web Audio API generally\n   * expects audio data as Float32Arrays with samples normalized between -1.0 and 1.0.\n   * This function handles that conversion.\n   * @param chunk The Uint8Array containing PCM16 audio data.\n   * @returns A Float32Array representing the converted audio data.\n   */\n  private _processPCM16Chunk(chunk: Uint8Array): Float32Array {\n    const float32Array = new Float32Array(chunk.length / 2);\n    const dataView = new DataView(chunk.buffer);\n\n    for (let i = 0; i < chunk.length / 2; i++) {\n      try {\n        const int16 = dataView.getInt16(i * 2, true);\n        float32Array[i] = int16 / 32768;\n      } catch (e) {\n        console.error(e);\n      }\n    }\n    return float32Array;\n  }\n\n  private createAudioBuffer(audioData: Float32Array): AudioBuffer {\n    const audioBuffer = this.context.createBuffer(\n      1,\n      audioData.length,\n      this.sampleRate\n    );\n    audioBuffer.getChannelData(0).set(audioData);\n    return audioBuffer;\n  }\n\n  private scheduleNextBuffer() {\n    const SCHEDULE_AHEAD_TIME = 0.4; // Increase from 0.2 to 0.4 seconds\n\n    // Log buffer state at start of scheduling\n    const scheduleData = {\n      queueLength: this.audioQueue.length,\n      scheduledTime: this.scheduledTime.toFixed(3),\n      currentTime: this.context.currentTime.toFixed(3),\n      timeDiff: (this.scheduledTime - this.context.currentTime).toFixed(3),\n      isPlaying: this.isPlaying,\n      timestamp: Date.now(),\n    };\n    if (this.debugAudioPerformance) {\n      console.log(\n        '🎵 [AUDIO-STREAMER] scheduleNextBuffer called',\n        scheduleData\n      );\n      audioStutterAnalyzer.addEvent(\n        'audio',\n        'schedule-start',\n        'scheduleNextBuffer called',\n        scheduleData\n      );\n    }\n\n    while (\n      this.audioQueue.length > 0 &&\n      this.scheduledTime < this.context.currentTime + SCHEDULE_AHEAD_TIME\n    ) {\n      const audioData = this.audioQueue.shift()!;\n      const audioBuffer = this.createAudioBuffer(audioData);\n      const source = this.context.createBufferSource();\n\n      // Log each buffer being scheduled\n      if (this.debugAudioPerformance) {\n        console.log('🎵 [AUDIO-STREAMER] Scheduling buffer', {\n          bufferDuration: audioBuffer.duration.toFixed(3),\n          scheduledAt: this.scheduledTime.toFixed(3),\n          remainingInQueue: this.audioQueue.length,\n          timestamp: Date.now(),\n        });\n      }\n\n      if (this.audioQueue.length === 0) {\n        if (this.endOfQueueAudioSource) {\n          this.endOfQueueAudioSource.onended = null;\n        }\n        this.endOfQueueAudioSource = source;\n        source.onended = () => {\n          if (this.debugAudioPerformance) {\n            console.log('🎵 [AUDIO-STREAMER] Buffer ended', {\n              wasLastBuffer: this.endOfQueueAudioSource === source,\n              queueEmpty: !this.audioQueue.length,\n              timestamp: Date.now(),\n            });\n            if (\n              !this.audioQueue.length &&\n              this.endOfQueueAudioSource === source\n            ) {\n              this.endOfQueueAudioSource = null;\n              this.onComplete();\n            }\n          }\n        };\n      }\n\n      source.buffer = audioBuffer;\n      source.connect(this.gainNode);\n\n      const worklets = registeredWorklets.get(this.context);\n\n      if (worklets) {\n        Object.entries(worklets).forEach(([_, graph]) => {\n          const { node, handlers } = graph;\n          if (node) {\n            source.connect(node);\n            node.port.onmessage = function (ev: MessageEvent) {\n              handlers.forEach((handler) => {\n                handler.call(node.port, ev);\n              });\n            };\n            node.connect(this.context.destination);\n          }\n        });\n      }\n      // Ensure we never schedule in the past\n      const startTime = Math.max(this.scheduledTime, this.context.currentTime);\n\n      // Log if we're scheduling in the past (potential underrun)\n      if (this.scheduledTime < this.context.currentTime) {\n        const underrunData = {\n          scheduledTime: this.scheduledTime.toFixed(3),\n          currentTime: this.context.currentTime.toFixed(3),\n          lateness: (this.context.currentTime - this.scheduledTime).toFixed(3),\n          timestamp: Date.now(),\n        };\n        if (this.debugAudioPerformance) {\n          console.warn(\n            '⚠️ [AUDIO-STREAMER] POTENTIAL UNDERRUN - scheduling in the past',\n            underrunData\n          );\n          audioStutterAnalyzer.addEvent(\n            'audio',\n            'underrun',\n            'POTENTIAL UNDERRUN - scheduling in the past',\n            underrunData\n          );\n        }\n      }\n\n      source.start(startTime);\n      this.scheduledTime = startTime + audioBuffer.duration;\n    }\n\n    // Log scheduling completion state\n    const completeData = {\n      queueLength: this.audioQueue.length,\n      nextScheduledTime: this.scheduledTime.toFixed(3),\n      currentTime: this.context.currentTime.toFixed(3),\n      bufferAhead: (this.scheduledTime - this.context.currentTime).toFixed(3),\n      timestamp: Date.now(),\n    };\n    if (this.debugAudioPerformance) {\n      console.log('🎵 [AUDIO-STREAMER] Scheduling complete', completeData);\n      audioStutterAnalyzer.addEvent(\n        'audio',\n        'schedule-complete',\n        'Scheduling complete',\n        completeData\n      );\n    }\n\n    if (this.audioQueue.length === 0) {\n      if (this.isStreamComplete) {\n        this.isPlaying = false;\n        if (this.checkInterval) {\n          clearInterval(this.checkInterval);\n          this.checkInterval = null;\n        }\n      } else {\n        if (!this.checkInterval) {\n          if (this.debugAudioPerformance) {\n            console.log(\n              '🎵 [AUDIO-STREAMER] Starting check interval - queue empty'\n            );\n          }\n          this.checkInterval = window.setInterval(() => {\n            if (this.audioQueue.length > 0) {\n              if (this.debugAudioPerformance) {\n                console.log(\n                  '🎵 [AUDIO-STREAMER] Check interval found new buffers',\n                  {\n                    queueLength: this.audioQueue.length,\n                    timestamp: Date.now(),\n                  }\n                );\n              }\n              this.scheduleNextBuffer();\n            }\n          }, 100) as unknown as number;\n        }\n      }\n    } else {\n      const nextCheckTime =\n        (this.scheduledTime - this.context.currentTime) * 1000;\n      if (this.debugAudioPerformance) {\n        console.log('🎵 [AUDIO-STREAMER] Scheduling next check', {\n          nextCheckMs: Math.max(0, nextCheckTime - 50),\n          timestamp: Date.now(),\n        });\n      }\n      setTimeout(\n        () => this.scheduleNextBuffer(),\n        Math.max(0, nextCheckTime - 50)\n      );\n    }\n  }\n\n  addPCM16(chunk: Uint8Array) {\n    // Log incoming chunk\n    const chunkData = {\n      chunkSize: chunk.length,\n      queueLengthBefore: this.audioQueue.length,\n      isPlaying: this.isPlaying,\n      timestamp: Date.now(),\n    };\n    if (this.debugAudioPerformance) {\n      console.log('🎵 [AUDIO-STREAMER] addPCM16 called', chunkData);\n      audioStutterAnalyzer.addEvent(\n        'audio',\n        'chunk-received',\n        'Audio chunk received',\n        chunkData\n      );\n    }\n\n    // Reset the stream complete flag when a new chunk is added.\n    this.isStreamComplete = false;\n    // Process the chunk into a Float32Array\n    let processingBuffer = this._processPCM16Chunk(chunk);\n    // Add the processed buffer to the queue if it's larger than the buffer size.\n    // This is to ensure that the buffer is not too large.\n    while (processingBuffer.length >= this.bufferSize) {\n      const buffer = processingBuffer.slice(0, this.bufferSize);\n      this.audioQueue.push(buffer);\n      processingBuffer = processingBuffer.slice(this.bufferSize);\n    }\n    // Add the remaining buffer to the queue if it's not empty.\n    if (processingBuffer.length > 0) {\n      this.audioQueue.push(processingBuffer);\n    }\n\n    // Log queue state after adding\n    if (this.debugAudioPerformance) {\n      console.log('🎵 [AUDIO-STREAMER] addPCM16 complete', {\n        queueLengthAfter: this.audioQueue.length,\n        buffersAdded:\n          this.audioQueue.length -\n          (chunk.length > 0\n            ? Math.ceil(chunk.length / 2 / this.bufferSize)\n            : 0),\n        timestamp: Date.now(),\n      });\n    }\n\n    // Start playing if not already playing.\n    if (!this.isPlaying) {\n      if (this.debugAudioPerformance) {\n        console.log('🎵 [AUDIO-STREAMER] Starting playback', {\n          initialBufferTime: this.initialBufferTime,\n          timestamp: Date.now(),\n        });\n      }\n      this.isPlaying = true;\n      // Initialize scheduledTime only when we start playing\n      this.scheduledTime = this.context.currentTime + this.initialBufferTime;\n      this.scheduleNextBuffer();\n    }\n  }\n\n  async addWorklet<T extends (d: any) => void>(\n    workletName: string,\n    workletSrc: string,\n    handler: T\n  ): Promise<this> {\n    let workletsRecord = registeredWorklets.get(this.context);\n    if (workletsRecord && workletsRecord[workletName]) {\n      // the worklet already exists on this context\n      // add the new handler to it\n      workletsRecord[workletName].handlers.push(handler);\n      return Promise.resolve(this);\n      //throw new Error(`Worklet ${workletName} already exists on context`);\n    }\n\n    if (!workletsRecord) {\n      registeredWorklets.set(this.context, {});\n      workletsRecord = registeredWorklets.get(this.context)!;\n    }\n\n    // create new record to fill in as becomes available\n    workletsRecord[workletName] = { handlers: [handler] };\n\n    const src = createWorketFromSrc(workletName, workletSrc);\n    await this.context.audioWorklet.addModule(src);\n    const worklet = new AudioWorkletNode(this.context, workletName);\n\n    //add the node into the map\n    workletsRecord[workletName].node = worklet;\n\n    return this;\n  }\n\n  complete() {\n    this.isStreamComplete = true;\n    this.onComplete();\n  }\n\n  async resume() {\n    if (this.context.state === 'suspended') {\n      await this.context.resume();\n    }\n    this.isStreamComplete = false;\n    this.scheduledTime = this.context.currentTime + this.initialBufferTime;\n    this.gainNode.gain.setValueAtTime(1, this.context.currentTime);\n  }\n\n  stop() {\n    this.isPlaying = false;\n    this.isStreamComplete = true;\n    this.audioQueue = [];\n    this.scheduledTime = this.context.currentTime;\n\n    if (this.checkInterval) {\n      clearInterval(this.checkInterval);\n      this.checkInterval = null;\n    }\n\n    this.gainNode.gain.linearRampToValueAtTime(\n      0,\n      this.context.currentTime + 0.1\n    );\n\n    setTimeout(() => {\n      this.gainNode.disconnect();\n      this.gainNode = this.context.createGain();\n      this.gainNode.connect(this.context.destination);\n    }, 200);\n  }\n}\n\n// // Usage example:\n// const audioStreamer = new AudioStreamer();\n//\n// // In your streaming code:\n// function handleChunk(chunk: Uint8Array) {\n//   audioStreamer.handleChunk(chunk);\n// }\n//\n// // To start playing (call this in response to a user interaction)\n// await audioStreamer.resume();\n//\n// // To stop playing\n// // audioStreamer.stop();\n","/**\n * Context-Aware Memory Manager\n *\n * Handles memory search operations and automatically updates the\n * context state manager with search results. This provides a seamless\n * integration between memory searches and context management.\n */\n\nimport { AgentSession } from '@/types/agent-types';\nimport { MemorySearchConfig, SearchRequest } from '@/types/memory-types';\n\nimport { MemoryService } from '../sammy-api/memory-service';\nimport { ContextStateManager } from './context-state-manager';\nimport { ObservabilityManager } from './observability-manager';\n\n// Default configuration values\nconst DEFAULT_CONFIG: Required<MemorySearchConfig> = {\n  userInputThreshold: 20,\n  agentOutputThreshold: 50,\n  searchLimit: 3,\n  similarityThreshold: 0.2,\n  useFeatureIdFilter: true,\n  searchGlobalOnly: false,\n  userContextPrefix: '[LIVE CONTEXT]',\n  userContextSuffix: 'Use this context for your response.',\n  agentContextPrefix: '[VALIDATION]',\n};\n\nexport class ContextMemoryManager {\n  private config: Required<MemorySearchConfig>;\n  private searchInProgress = false;\n  private lastSearchQuery = '';\n  private lastSearchTime = 0;\n  private searchDebounceMs = 2000; // Prevent too frequent searches\n  private observabilityManager?: ObservabilityManager;\n\n  constructor(\n    private memoryService: MemoryService,\n    private contextStateManager: ContextStateManager,\n    config?: MemorySearchConfig,\n    observabilityManager?: ObservabilityManager\n  ) {\n    this.config = { ...DEFAULT_CONFIG, ...config };\n    this.observabilityManager = observabilityManager;\n    console.log(\n      '💭 [CONTEXT-MEMORY-MANAGER] Initialized with config:',\n      this.config\n    );\n  }\n\n  /**\n   * Search for memories and update context state\n   */\n  async searchAndUpdateContext(\n    query: string,\n    source: 'agent' | 'user',\n    session: AgentSession | null\n  ): Promise<void> {\n    // Validate input\n    if (!query || query.trim().length === 0) {\n      console.log('💭 [CONTEXT-MEMORY-MANAGER] Skipping search - empty query');\n      return;\n    }\n\n    // Check if we should debounce\n    const now = Date.now();\n    if (\n      this.searchInProgress ||\n      (this.lastSearchQuery === query &&\n        now - this.lastSearchTime < this.searchDebounceMs)\n    ) {\n      console.log(\n        '💭 [CONTEXT-MEMORY-MANAGER] Skipping search - debouncing or in progress'\n      );\n      return;\n    }\n\n    // Apply thresholds based on source\n    const threshold =\n      source === 'user'\n        ? this.config.userInputThreshold\n        : this.config.agentOutputThreshold;\n\n    if (query.length < threshold) {\n      console.log(\n        `💭 [CONTEXT-MEMORY-MANAGER] Query too short (${query.length} < ${threshold})`\n      );\n      return;\n    }\n\n    // Mark search as in progress\n    this.searchInProgress = true;\n    this.lastSearchQuery = query;\n    this.lastSearchTime = now;\n\n    try {\n      console.log(\n        `💭 [CONTEXT-MEMORY-MANAGER] Searching memories for ${source}:`,\n        query.substring(0, 50) + '...'\n      );\n\n      // Track memory search event\n      if (this.observabilityManager) {\n        this.observabilityManager.trackEvent({\n          type: 'memory.search',\n          data: {\n            query,\n            source,\n            limit: this.config.searchLimit,\n            threshold: this.config.similarityThreshold,\n            resultsFound: 0, // Will update after search\n          },\n        });\n      }\n\n      // Build search options\n      const searchOptions: Omit<SearchRequest, 'query'> = {\n        limit: this.config.searchLimit,\n        similarity_threshold: this.config.similarityThreshold,\n        search_global_only: this.config.searchGlobalOnly,\n      };\n\n      // Add feature ID filter if configured and available\n      if (\n        this.config.useFeatureIdFilter &&\n        session?.sammyThreeOrganisationFeatureId\n      ) {\n        searchOptions.feature_id = session.sammyThreeOrganisationFeatureId;\n      }\n\n      // Execute search\n      const memories = await this.memoryService.search(query, searchOptions);\n\n      console.log(\n        `💭 [CONTEXT-MEMORY-MANAGER] Found ${memories.length} memories`\n      );\n\n      // Track updated results count\n      if (this.observabilityManager) {\n        this.observabilityManager.trackEvent({\n          type: 'memory.search',\n          data: {\n            query,\n            source,\n            limit: this.config.searchLimit,\n            threshold: this.config.similarityThreshold,\n            resultsFound: memories.length,\n          },\n        });\n      }\n\n      // Update context state with search results\n      this.contextStateManager.updateMemories(memories);\n\n      // Log memory summaries for debugging\n      if (memories.length > 0) {\n        memories.forEach((memory, index) => {\n          const preview = memory.memory.substring(0, 100);\n          const relevance = memory.similarity\n            ? `(${memory.similarity.toFixed(2)})`\n            : '';\n          console.log(`   ${index + 1}. ${preview}... ${relevance}`);\n        });\n      }\n    } catch (error) {\n      console.error('💭 [CONTEXT-MEMORY-MANAGER] Memory search error:', error);\n\n      // Track error event\n      if (this.observabilityManager) {\n        this.observabilityManager.trackEvent({\n          type: 'error',\n          data: {\n            error: error instanceof Error ? error.message : 'Unknown error',\n            stack: error instanceof Error ? error.stack : undefined,\n            context: 'memory_search',\n            severity: 'error',\n          },\n        });\n      }\n\n      // Don't clear memories on error - keep previous results\n    } finally {\n      this.searchInProgress = false;\n    }\n  }\n\n  /**\n   * Process user input transcription and trigger memory search if needed\n   */\n  async processUserTranscription(\n    transcription: string,\n    session: AgentSession | null\n  ): Promise<void> {\n    if (!transcription || transcription.includes('<noise>')) {\n      return;\n    }\n\n    await this.searchAndUpdateContext(transcription, 'user', session);\n  }\n\n  /**\n   * Process agent output transcription and trigger memory search if needed\n   */\n  async processAgentTranscription(\n    transcription: string,\n    session: AgentSession | null\n  ): Promise<void> {\n    if (!transcription) {\n      return;\n    }\n\n    await this.searchAndUpdateContext(transcription, 'agent', session);\n  }\n\n  /**\n   * Update configuration\n   */\n  updateConfig(config: Partial<MemorySearchConfig>) {\n    this.config = { ...this.config, ...config };\n    console.log('💭 [CONTEXT-MEMORY-MANAGER] Config updated:', this.config);\n  }\n\n  /**\n   * Get current configuration\n   */\n  getConfig(): Required<MemorySearchConfig> {\n    return { ...this.config };\n  }\n\n  /**\n   * Clear search state\n   */\n  clearSearchState() {\n    this.searchInProgress = false;\n    this.lastSearchQuery = '';\n    this.lastSearchTime = 0;\n    this.contextStateManager.clearMemories();\n  }\n}\n","/**\n * Centralized Context State Manager\n *\n * This class manages all contextual state for the agent including:\n * - User memories from memory searches\n * - Page metadata (URL, title, timestamp)\n * - User preferences and settings\n * - Custom contextual data\n *\n * It provides a single source of truth for context and handles\n * formatting for LLM consumption.\n */\n\nimport { MemoryEntry } from '../../types/memory-types';\nimport { ObservabilityManager } from './observability-manager';\n\nexport interface PageMetadata {\n  timestamp: string;\n  title: string;\n  url: string;\n  domain?: string;\n  path?: string;\n}\n\nexport interface UserPreferences {\n  name?: string;\n  preferences?: Record<string, any>;\n  settings?: Record<string, any>;\n}\n\nexport interface ContextState {\n  lastUpdated: {\n    pageMetadata?: Date;\n    userPreferences?: Date;\n    memories?: Date;\n  };\n  customContext: Record<string, any>;\n  memories: MemoryEntry[];\n  pageMetadata: PageMetadata | null;\n  userPreferences: UserPreferences | null;\n}\n\nexport interface ContextFormattingOptions {\n  format?: 'compact' | 'detailed';\n  includeCustomContext?: boolean;\n  includeMemories?: boolean;\n  includePageMetadata?: boolean;\n  includeUserPreferences?: boolean;\n  memoryLimit?: number;\n}\n\nexport class ContextStateManager {\n  private state: ContextState = {\n    memories: [],\n    pageMetadata: null,\n    userPreferences: null,\n    customContext: {},\n    lastUpdated: {},\n  };\n  private observabilityManager?: ObservabilityManager;\n\n  constructor(observabilityManager?: ObservabilityManager) {\n    this.observabilityManager = observabilityManager;\n    console.log('🎯 [CONTEXT-STATE-MANAGER] initialized');\n  }\n\n  // Memory Management\n  updateMemories(memories: MemoryEntry[]) {\n    console.log(\n      `📚 [CONTEXT-STATE-MANAGER] Updating memories: ${memories.length} entries`\n    );\n    this.state.memories = memories;\n    this.state.lastUpdated.memories = new Date();\n\n    // Track memory injection event\n    if (this.observabilityManager && memories.length > 0) {\n      this.observabilityManager.trackEvent({\n        type: 'memory.inject',\n        data: {\n          source: 'user', // Default to user since this is context management\n          contextType: 'live',\n          memories: memories.map((m) => ({\n            id: m.memory_id,\n            memory: m.memory,\n            score: m.similarity,\n          })),\n        },\n      });\n    }\n  }\n\n  getMemories(): MemoryEntry[] {\n    return [...this.state.memories];\n  }\n\n  clearMemories() {\n    this.state.memories = [];\n    this.state.lastUpdated.memories = new Date();\n  }\n\n  // Page Metadata Management\n  updatePageMetadata(metadata: Partial<PageMetadata>) {\n    console.log('🌐 [CONTEXT-STATE-MANAGER] Updating page metadata:', metadata);\n\n    // Check if URL has changed\n    const urlChanged =\n      metadata.url && this.state.pageMetadata?.url !== metadata.url;\n\n    this.state.pageMetadata = {\n      ...this.state.pageMetadata,\n      ...metadata,\n      timestamp: new Date().toISOString(),\n    } as PageMetadata;\n    this.state.lastUpdated.pageMetadata = new Date();\n\n    // Track URL change event\n    if (this.observabilityManager && urlChanged) {\n      this.observabilityManager.trackEvent({\n        type: 'url.change',\n        data: {\n          from: this.state.pageMetadata?.url || '',\n          to: metadata.url!,\n        },\n      });\n    }\n\n    // Track context update event\n    if (this.observabilityManager) {\n      this.observabilityManager.trackEvent({\n        type: 'context.update',\n        data: {\n          updateType: 'pageMetadata',\n          details: this.state.pageMetadata,\n        },\n      });\n    }\n  }\n\n  getPageMetadata(): PageMetadata | null {\n    return this.state.pageMetadata ? { ...this.state.pageMetadata } : null;\n  }\n\n  // User Preferences Management\n  updateUserPreferences(preferences: Partial<UserPreferences>) {\n    console.log('👤 [CONTEXT-STATE-MANAGER] Updating user preferences');\n    this.state.userPreferences = {\n      ...this.state.userPreferences,\n      ...preferences,\n    } as UserPreferences;\n    this.state.lastUpdated.userPreferences = new Date();\n\n    // Track context update event\n    if (this.observabilityManager) {\n      this.observabilityManager.trackEvent({\n        type: 'context.update',\n        data: {\n          updateType: 'userPreferences',\n          details: this.state.userPreferences,\n        },\n      });\n    }\n  }\n\n  getUserPreferences(): UserPreferences | null {\n    return this.state.userPreferences\n      ? { ...this.state.userPreferences }\n      : null;\n  }\n\n  // Custom Context Management\n  setCustomContext(key: string, value: any) {\n    console.log(`🔧 [CONTEXT-STATE-MANAGER] Setting custom context: ${key}`);\n    this.state.customContext[key] = value;\n\n    // Track context update event\n    if (this.observabilityManager) {\n      this.observabilityManager.trackEvent({\n        type: 'context.update',\n        data: {\n          updateType: 'customContext',\n          details: { key, value },\n        },\n      });\n    }\n  }\n\n  getCustomContext(key: string): any {\n    return this.state.customContext[key];\n  }\n\n  removeCustomContext(key: string) {\n    delete this.state.customContext[key];\n  }\n\n  // Main Context Formatting Methods\n  formatContext(options: ContextFormattingOptions = {}): string {\n    const {\n      includeMemories = true,\n      includePageMetadata = true,\n      includeUserPreferences = true,\n      includeCustomContext = true,\n      memoryLimit = 5,\n      format = 'detailed',\n    } = options;\n\n    const sections: string[] = [];\n\n    // Add page metadata section\n    if (includePageMetadata && this.state.pageMetadata) {\n      sections.push(this.formatPageMetadata(this.state.pageMetadata, format));\n    }\n\n    // Add user preferences section\n    if (includeUserPreferences && this.state.userPreferences) {\n      sections.push(\n        this.formatUserPreferences(this.state.userPreferences, format)\n      );\n    }\n\n    // Add memories section\n    if (includeMemories && this.state.memories.length > 0) {\n      const memoriesToInclude = this.state.memories.slice(0, memoryLimit);\n      sections.push(this.formatMemories(memoriesToInclude, format));\n    }\n\n    // Add custom context sections\n    if (\n      includeCustomContext &&\n      Object.keys(this.state.customContext).length > 0\n    ) {\n      sections.push(this.formatCustomContext(this.state.customContext, format));\n    }\n\n    // Join sections with appropriate spacing\n    return sections.join('\\n\\n');\n  }\n\n  // Specific formatting for tool response (following the workaround pattern)\n  formatForToolResponse(): string {\n    const sections: string[] = [];\n\n    sections.push('[CONTEXT RETRIEVED]');\n\n    // Current page context\n    if (this.state.pageMetadata) {\n      sections.push('📍 Current Page:');\n      sections.push(`   URL: ${this.state.pageMetadata.url}`);\n      sections.push(`   Title: ${this.state.pageMetadata.title}`);\n      sections.push(`   Timestamp: ${this.state.pageMetadata.timestamp}`);\n    }\n\n    // User information\n    if (this.state.userPreferences?.name) {\n      sections.push('\\n👤 User Information:');\n      sections.push(`   Name: ${this.state.userPreferences.name}`);\n      if (this.state.userPreferences.preferences) {\n        sections.push(\n          `   Preferences: ${JSON.stringify(this.state.userPreferences.preferences)}`\n        );\n      }\n    }\n\n    // Relevant memories\n    if (this.state.memories.length > 0) {\n      sections.push('\\n💭 Relevant Knowledge:');\n      this.state.memories.slice(0, 3).forEach((memory, index) => {\n        const relevance = memory.similarity\n          ? `(${memory.similarity.toFixed(2)})`\n          : '';\n        sections.push(`   ${index + 1}. ${memory.memory} ${relevance}`);\n        // Note: MemoryEntry doesn't have a metadata property, could add categories or other fields\n      });\n    }\n\n    // Custom context\n    if (Object.keys(this.state.customContext).length > 0) {\n      sections.push('\\n🔧 Additional Context:');\n      Object.entries(this.state.customContext).forEach(([key, value]) => {\n        sections.push(`   ${key}: ${JSON.stringify(value)}`);\n      });\n    }\n\n    sections.push(\n      '\\nUse this context to provide personalized and accurate assistance.'\n    );\n\n    return sections.join('\\n');\n  }\n\n  // Lightweight formatting for just current page context (no memories)\n  formatPageContextOnly(\n    options: {\n      includeTimestamp?: boolean;\n      additionalInfo?: Record<string, any>;\n      customContext?: string;\n    } = {}\n  ): string {\n    const { includeTimestamp = true, customContext, additionalInfo } = options;\n\n    const sections: string[] = ['=== CURRENT PAGE CONTEXT ==='];\n\n    // Get current page metadata\n    if (!this.state.pageMetadata) {\n      // Update with current browser state if available\n      if (typeof window !== 'undefined') {\n        this.updatePageMetadata({\n          url: window.location.href,\n          title: document.title,\n          domain: window.location.hostname,\n          path: window.location.pathname,\n        });\n      }\n    }\n\n    if (this.state.pageMetadata) {\n      sections.push(`Current URL: ${this.state.pageMetadata.url}`);\n      sections.push(`Page Title: ${this.state.pageMetadata.title}`);\n      if (this.state.pageMetadata.domain) {\n        sections.push(`Domain: ${this.state.pageMetadata.domain}`);\n      }\n      if (this.state.pageMetadata.path) {\n        sections.push(`Path: ${this.state.pageMetadata.path}`);\n      }\n    }\n\n    if (includeTimestamp) {\n      sections.push(`Updated: ${new Date().toISOString()}`);\n    }\n\n    if (customContext) {\n      sections.push(`Note: ${customContext}`);\n    }\n\n    // Add any additional info\n    if (additionalInfo) {\n      Object.entries(additionalInfo).forEach(([key, value]) => {\n        sections.push(`${key}: ${value}`);\n      });\n    }\n\n    sections.push('=== END CONTEXT ===');\n\n    return sections.join('\\n');\n  }\n\n  // Private formatting helpers\n  private formatPageMetadata(\n    metadata: PageMetadata,\n    format: 'compact' | 'detailed'\n  ): string {\n    if (format === 'compact') {\n      return `Page: ${metadata.title} (${metadata.url})`;\n    }\n\n    return [\n      '=== Current Page Context ===',\n      `URL: ${metadata.url}`,\n      `Title: ${metadata.title}`,\n      `Domain: ${metadata.domain || new URL(metadata.url).hostname}`,\n      `Path: ${metadata.path || new URL(metadata.url).pathname}`,\n      `Accessed: ${metadata.timestamp}`,\n    ].join('\\n');\n  }\n\n  private formatUserPreferences(\n    prefs: UserPreferences,\n    format: 'compact' | 'detailed'\n  ): string {\n    if (format === 'compact' && prefs.name) {\n      return `User: ${prefs.name}`;\n    }\n\n    const lines = ['=== User Information ==='];\n    if (prefs.name) lines.push(`Name: ${prefs.name}`);\n    if (prefs.preferences) {\n      lines.push('Preferences:');\n      Object.entries(prefs.preferences).forEach(([key, value]) => {\n        lines.push(`  - ${key}: ${value}`);\n      });\n    }\n    if (prefs.settings) {\n      lines.push('Settings:');\n      Object.entries(prefs.settings).forEach(([key, value]) => {\n        lines.push(`  - ${key}: ${value}`);\n      });\n    }\n    return lines.join('\\n');\n  }\n\n  private formatMemories(\n    memories: MemoryEntry[],\n    format: 'compact' | 'detailed'\n  ): string {\n    if (format === 'compact') {\n      return memories.map((m, i) => `${i + 1}. ${m.memory}`).join('\\n');\n    }\n\n    const lines = ['=== Relevant Memories ==='];\n    memories.forEach((memory, index) => {\n      lines.push(`\\n${index + 1}. ${memory.memory}`);\n      if (memory.similarity !== undefined) {\n        lines.push(`   Relevance: ${(memory.similarity * 100).toFixed(0)}%`);\n      }\n      if (memory.created_at) {\n        lines.push(\n          `   Created: ${new Date(memory.created_at).toLocaleDateString()}`\n        );\n      }\n      if (memory.categories && memory.categories.length > 0) {\n        lines.push(`   Categories: ${memory.categories.join(', ')}`);\n      }\n    });\n    return lines.join('\\n');\n  }\n\n  private formatCustomContext(\n    context: Record<string, any>,\n    format: 'compact' | 'detailed'\n  ): string {\n    const lines = ['=== Additional Context ==='];\n    Object.entries(context).forEach(([key, value]) => {\n      if (format === 'compact') {\n        lines.push(`${key}: ${JSON.stringify(value)}`);\n      } else {\n        lines.push(`\\n${key}:`);\n        if (typeof value === 'object') {\n          lines.push(\n            JSON.stringify(value, null, 2)\n              .split('\\n')\n              .map((l) => '  ' + l)\n              .join('\\n')\n          );\n        } else {\n          lines.push(`  ${value}`);\n        }\n      }\n    });\n    return lines.join('\\n');\n  }\n\n  // State management\n  getFullState(): ContextState {\n    return JSON.parse(JSON.stringify(this.state));\n  }\n\n  clearAll() {\n    console.log('🗑️ [CONTEXT-STATE-MANAGER] Clearing all context state');\n    this.state = {\n      memories: [],\n      pageMetadata: null,\n      userPreferences: null,\n      customContext: {},\n      lastUpdated: {},\n    };\n  }\n\n  // Utility methods\n  hasContext(): boolean {\n    return !!(\n      this.state.memories.length > 0 ||\n      this.state.pageMetadata ||\n      this.state.userPreferences ||\n      Object.keys(this.state.customContext).length > 0\n    );\n  }\n\n  getLastUpdated(\n    type?: 'memories' | 'pageMetadata' | 'userPreferences'\n  ): Date | null {\n    if (type) {\n      return this.state.lastUpdated[type] || null;\n    }\n    // Return most recent update across all types\n    const dates = Object.values(this.state.lastUpdated).filter(\n      Boolean\n    ) as Date[];\n    return dates.length > 0\n      ? new Date(Math.max(...dates.map((d) => d.getTime())))\n      : null;\n  }\n}\n","/**\n * Observability Manager\n * Central class for tracking all events during an agent session\n */\n\nimport { EventEmitter } from 'eventemitter3';\nimport { v4 as uuidv4 } from 'uuid';\n\nimport {\n  ObservabilityConfig,\n  SessionStatistics,\n  SessionTrace,\n  TraceEvent,\n  TraceEventType,\n} from '@/types/observability-types';\n\nexport interface ObservabilityEventMap {\n  statistics: (stats: SessionStatistics) => void;\n  trace: (event: TraceEvent) => void;\n}\n\nexport class ObservabilityManager {\n  private config: ObservabilityConfig;\n  private events: EventEmitter<ObservabilityEventMap>;\n  private sessionTrace: SessionTrace;\n  private sessionStats: SessionStatistics;\n  private sessionId: string;\n  private conversationId: string;\n  private startTime: Date;\n  private sequenceNumber: number = 0; // Add sequence counter for ordering\n  private baseTime: number; // Base time for high-resolution timing\n\n  constructor(\n    sessionId: string,\n    conversationId: string,\n    config: ObservabilityConfig\n  ) {\n    this.sessionId = sessionId;\n    this.conversationId = conversationId;\n    this.config = config;\n    this.events = new EventEmitter();\n    this.startTime = new Date();\n    this.baseTime = this.startTime.getTime(); // Base time for calculating high-res timestamps\n\n    // Initialize session trace\n    this.sessionTrace = {\n      sessionId,\n      conversationId,\n      startTime: this.startTime,\n      events: [],\n      summary: {\n        totalEvents: 0,\n        eventCounts: {} as Record<TraceEventType, number>,\n        messageCount: 0,\n        toolCallCount: 0,\n        errorCount: 0,\n      },\n    };\n\n    // Initialize statistics\n    this.sessionStats = {\n      sessionId,\n      startTime: this.startTime,\n      duration: 0,\n      eventCount: 0,\n      messagesSent: 0,\n      messagesReceived: 0,\n      audioBytesSent: 0,\n      audioBytesReceived: 0,\n      imageBytesSent: 0,\n      toolCalls: 0,\n      errors: 0,\n      memoriesSearched: 0,\n      memoriesInjected: 0,\n      criticalRenders: 0,\n    };\n  }\n\n  /**\n   * Get the event emitter for subscribing to events\n   */\n  getEventEmitter(): EventEmitter<ObservabilityEventMap> {\n    return this.events;\n  }\n\n  /**\n   * Generate a high-resolution timestamp to ensure event ordering\n   */\n  private generateHighResTimestamp(sequenceNum: number): Date {\n    // Use performance.now() for microsecond precision, add to base time\n    const highResTime = this.baseTime + performance.now();\n\n    // Add sequence number as microseconds to ensure uniqueness\n    // This ensures even simultaneous events have different timestamps\n    const uniqueTime = highResTime + sequenceNum / 1000000;\n\n    return new Date(uniqueTime);\n  }\n\n  /**\n   * Track an event\n   */\n  async trackEvent(\n    event: Omit<\n      TraceEvent,\n      'conversationId' | 'id' | 'sequenceNumber' | 'sessionId' | 'timestamp'\n    >\n  ): Promise<void> {\n    if (!this.config.enabled) return;\n\n    // Check if this event type is disabled\n    if (this.config.disableEventTypes?.includes(event.type)) return;\n\n    // Capture and increment sequence number atomically\n    const currentSequenceNumber = this.sequenceNumber++;\n\n    // Create full event with metadata using high-resolution timestamp and sequence number\n    const fullEvent: TraceEvent = {\n      ...event,\n      id: uuidv4(),\n      timestamp: this.generateHighResTimestamp(currentSequenceNumber), // Pass sequence number\n      sequenceNumber: currentSequenceNumber, // Use the captured sequence number\n      sessionId: this.sessionId,\n      conversationId: this.conversationId,\n      metadata: {\n        ...this.config.metadata,\n        ...event.metadata,\n      },\n    } as TraceEvent;\n\n    // Apply privacy settings\n    this.applyPrivacySettings(fullEvent);\n\n    // Update trace\n    this.sessionTrace.events.push(fullEvent);\n    this.sessionTrace.summary.totalEvents++;\n\n    // Update event counts\n    const eventType = fullEvent.type;\n    this.sessionTrace.summary.eventCounts[eventType] =\n      (this.sessionTrace.summary.eventCounts[eventType] || 0) + 1;\n\n    // Update statistics based on event type\n    this.updateStatistics(fullEvent);\n\n    // Log to console if enabled\n    if (this.config.logToConsole) {\n      console.log(`[Observability] ${fullEvent.type}:`, fullEvent);\n    }\n\n    // Emit event\n    this.events.emit('trace', fullEvent);\n\n    // Call custom callback if provided\n    if (this.config.callback) {\n      try {\n        await this.config.callback(fullEvent);\n      } catch (error) {\n        console.error('[Observability] Callback error:', error);\n      }\n    }\n  }\n\n  /**\n   * Apply privacy settings to redact sensitive data\n   */\n  private applyPrivacySettings(event: TraceEvent): void {\n    // Redact system prompt if not allowed\n    if (\n      !this.config.includeSystemPrompt &&\n      event.type === 'system_prompt.set'\n    ) {\n      (event as any).data.prompt = '[REDACTED]';\n    }\n\n    // Redact audio data if not allowed\n    if (!this.config.includeAudioData) {\n      if (event.type === 'audio.send' || event.type === 'audio.receive') {\n        delete (event as any).data.audioData;\n      }\n    }\n\n    // Redact image data if not allowed\n    if (!this.config.includeImageData && event.type === 'screen_capture.send') {\n      delete (event as any).data.imageData;\n    }\n  }\n\n  /**\n   * Update statistics based on event\n   */\n  private updateStatistics(event: TraceEvent): void {\n    this.sessionStats.eventCount++;\n    this.sessionStats.duration = Date.now() - this.startTime.getTime();\n\n    switch (event.type) {\n      case 'audio.receive':\n        this.sessionStats.audioBytesReceived +=\n          (event as any).data.sizeBytes || 0;\n        break;\n      case 'audio.send':\n        this.sessionStats.audioBytesSent += (event as any).data.sizeBytes || 0;\n        break;\n      case 'content.receive':\n        this.sessionStats.messagesReceived++;\n        break;\n      case 'content.send':\n        this.sessionStats.messagesSent++;\n        this.sessionTrace.summary.messageCount++;\n        break;\n      case 'error':\n        this.sessionStats.errors++;\n        this.sessionTrace.summary.errorCount++;\n        break;\n      case 'memory.inject':\n        this.sessionStats.memoriesInjected++;\n        break;\n      case 'memory.search':\n        this.sessionStats.memoriesSearched++;\n        break;\n      case 'screen_capture.critical':\n        this.sessionStats.criticalRenders++;\n        break;\n      case 'screen_capture.send':\n        this.sessionStats.imageBytesSent += (event as any).data.sizeBytes || 0;\n        break;\n      case 'tool.call':\n        this.sessionStats.toolCalls++;\n        this.sessionTrace.summary.toolCallCount++;\n        break;\n    }\n\n    // Emit updated statistics\n    this.events.emit('statistics', this.sessionStats);\n  }\n\n  /**\n   * End the session and return final trace\n   */\n  endSession(\n    reason:\n      | 'error'\n      | 'timeout'\n      | 'token_expired'\n      | 'user_initiated' = 'user_initiated'\n  ): SessionTrace {\n    const endTime = new Date();\n    const totalDuration = endTime.getTime() - this.startTime.getTime();\n\n    // Track session end event\n    this.trackEvent({\n      type: 'session.end',\n      data: {\n        reason,\n        totalDuration,\n        messageCount: this.sessionTrace.summary.messageCount,\n        toolCallCount: this.sessionTrace.summary.toolCallCount,\n      },\n    });\n\n    // Update trace\n    this.sessionTrace.endTime = endTime;\n    this.sessionTrace.summary.totalDuration = totalDuration;\n\n    return this.getTrace();\n  }\n\n  /**\n   * Get current trace\n   */\n  getTrace(): SessionTrace {\n    return JSON.parse(JSON.stringify(this.sessionTrace));\n  }\n\n  /**\n   * Get current statistics\n   */\n  getStatistics(): SessionStatistics {\n    return { ...this.sessionStats };\n  }\n\n  /**\n   * Serialize events for export\n   */\n  serializeEvents(events: TraceEvent[]): string {\n    return JSON.stringify(\n      events,\n      (key, value) => {\n        // Handle Date objects\n        if (value instanceof Date) {\n          return value.toISOString();\n        }\n        // Handle undefined values\n        if (value === undefined) {\n          return null;\n        }\n        // Handle functions\n        if (typeof value === 'function') {\n          return '[Function]';\n        }\n        return value;\n      },\n      2\n    );\n  }\n\n  /**\n   * Export trace as JSON\n   */\n  exportAsJson(): string {\n    return this.serializeEvents(this.sessionTrace.events);\n  }\n\n  /**\n   * Generate a summary report\n   */\n  generateSummaryReport(): string {\n    const summary = this.sessionTrace.summary;\n    const stats = this.sessionStats;\n\n    return `\n=== Observability Summary Report ===\nSession ID: ${this.sessionId}\nConversation ID: ${this.conversationId}\nDuration: ${stats.duration}ms\nTotal Events: ${summary.totalEvents}\n\nEvent Breakdown:\n${Object.entries(summary.eventCounts)\n  .map(([type, count]) => `  ${type}: ${count}`)\n  .join('\\n')}\n\nStatistics:\n  Messages Sent: ${stats.messagesSent}\n  Messages Received: ${stats.messagesReceived}\n  Audio Sent: ${(stats.audioBytesSent / 1024).toFixed(2)}KB\n  Audio Received: ${(stats.audioBytesReceived / 1024).toFixed(2)}KB\n  Images Sent: ${(stats.imageBytesSent / 1024).toFixed(2)}KB\n  Tool Calls: ${stats.toolCalls}\n  Memories Searched: ${stats.memoriesSearched}\n  Memories Injected: ${stats.memoriesInjected}\n  Errors: ${stats.errors}\n`;\n  }\n}\n","/* eslint-disable max-lines */\n/**\n * Core Sammy Agent implementation\n * This class encapsulates all the domain logic for the agent,\n * keeping it separate from React components and state management\n */\n\nimport { LiveConnectConfig, LiveServerToolCall } from '@google/genai';\nimport { v4 as uuidv4 } from 'uuid';\n\nimport { getAgentConfig } from '@/config/agent-config';\nimport { AudioRecorder } from '@/lib/audio/audio-recorder';\nimport { AudioStreamer } from '@/lib/audio/audio-streamer';\nimport { ContextMemoryManager } from '@/lib/core/context-memory-manager';\nimport { ContextStateManager } from '@/lib/core/context-state-manager';\nimport { ScreenCaptureManager } from '@/lib/core/screen-capture-manager';\nimport { GenAILiveClient } from '@/lib/genai-live-client';\nimport {\n  ObservableClient,\n  wrapGenAIClient,\n} from '@/lib/wrappers/wrap-genai-client';\nimport {\n  AgentMode,\n  AgentSession,\n  SammyAgentCallbacks,\n  SammyAgentConfig,\n} from '@/types/agent-types';\nimport { UpdateAgentState } from '@/types/agent-types';\nimport { audioContext } from '@/utils/audio-utils';\nimport VolMeterWorket from '@/worklets/vol-meter';\n\nimport {\n  ConversationService,\n  CoreService,\n  createConversationServices,\n  createCoreServices,\n  createMemoryServices,\n  MemoryService,\n  SammyApiClient,\n  GuidesService,\n  createGuidesServices,\n} from '../sammy-api';\nimport { createGetContextTool } from '../tools/definitions/get-context-tool';\nimport { ToolManager } from '../tools/tool-manager';\nimport { BaseToolEventMap, ToolDefinition, ToolServices } from '../tools/types';\nimport { ObservabilityManager } from './observability-manager';\n\nexport interface StartOptions {\n  agentMode: AgentMode;\n  guideId?: string;\n  sammyThreeOrganisationFeatureId?: string;\n}\n\nexport class SammyAgentCore<\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  TEventMap extends Record<string, (...args: any[]) => any> = BaseToolEventMap,\n> {\n  private config: SammyAgentConfig;\n  private callbacks: SammyAgentCallbacks;\n  private tools: ToolDefinition<TEventMap>[] = [];\n  private updateAgentState: UpdateAgentState;\n\n  // Core components\n  private sammyApiClient: SammyApiClient;\n  private client: ObservableClient | null = null;\n\n  // Services\n  private coreServices: CoreService;\n  private memoryServices: MemoryService;\n  private conversationServices: ConversationService;\n  public guidesServices: GuidesService;\n\n  private audioRecorder: AudioRecorder;\n  private audioStreamer: AudioStreamer | null = null;\n  private screenCapture: ScreenCaptureManager;\n  private toolManager: ToolManager<TEventMap>;\n  private contextStateManager: ContextStateManager;\n  private contextMemoryManager: ContextMemoryManager;\n\n  // State\n  private session: AgentSession | null = null;\n  private isConnected = false;\n  private currentToken: string | null = null;\n  public muted = false;\n  public isStreaming = false;\n  public agentVolume = 0;\n\n  // Transcription buffers\n  private outputTranscriptBuffer = '';\n  private inputTranscriptBuffer = '';\n\n  constructor(options: {\n    callbacks: SammyAgentCallbacks;\n    updateAgentState: UpdateAgentState;\n    config: SammyAgentConfig;\n    tools?: ToolDefinition<TEventMap>[];\n  }) {\n    this.config = options.config;\n    this.callbacks = options.callbacks;\n    this.tools = options.tools || [];\n    this.updateAgentState = options.updateAgentState;\n\n    // Initialize Sammy API client\n    this.sammyApiClient = new SammyApiClient(this.config.auth);\n    this.coreServices = createCoreServices(this.sammyApiClient);\n    this.memoryServices = createMemoryServices(this.sammyApiClient);\n    this.conversationServices = createConversationServices(this.sammyApiClient);\n    this.guidesServices = createGuidesServices(this.sammyApiClient);\n\n    // Initialize components\n    this.audioRecorder = new AudioRecorder({\n      onVolumeChange: (volume: number) => {\n        this.updateAgentState('userVolume', volume);\n      },\n    });\n    this.screenCapture = new ScreenCaptureManager(\n      this.config.screenCaptureCallbacks\n    );\n\n    // Initialize context management\n    this.contextStateManager = new ContextStateManager();\n    this.contextMemoryManager = new ContextMemoryManager(\n      this.memoryServices,\n      this.contextStateManager\n      // Config can be passed here if needed\n    );\n\n    // Create tool manager with custom tools and services\n    const services: ToolServices = {\n      conversationServices: this.conversationServices,\n      coreServices: this.coreServices,\n      memoryServices: this.memoryServices,\n    };\n    this.toolManager = new ToolManager(this.tools, services);\n\n    // Register the context tool dynamically after tool manager is created\n    this.registerContextTool();\n  }\n\n  /**\n   * Update observability manager in context management classes\n   * Called after the observable client is created\n   */\n  private updateObservabilityInContextManagers(\n    observabilityManager?: ObservabilityManager\n  ): void {\n    if (observabilityManager) {\n      // Update the context state manager with observability\n      (this.contextStateManager as any).observabilityManager =\n        observabilityManager;\n\n      // Update the context memory manager with observability\n      (this.contextMemoryManager as any).observabilityManager =\n        observabilityManager;\n\n      console.log(\n        '🔍 [SAMMY-AGENT-CORE] Updated context managers with observability'\n      );\n    }\n  }\n\n  /**\n   * Update page metadata in context state\n   */\n  updatePageContext(metadata: {\n    path?: string;\n    title?: string;\n    url?: string;\n    domain?: string;\n  }) {\n    this.contextStateManager.updatePageMetadata(metadata);\n    console.log('📍 [SAMMY-AGENT-CORE] Updated page context:', metadata);\n  }\n\n  /**\n   * Update user preferences in context state\n   */\n  updateUserContext(preferences: {\n    name?: string;\n    preferences?: Record<string, unknown>;\n    settings?: Record<string, unknown>;\n  }) {\n    this.contextStateManager.updateUserPreferences(preferences);\n    console.log('👤 [SAMMY-AGENT-CORE] Updated user context');\n  }\n\n  /**\n   * Get the context state manager for direct access\n   */\n  getContextStateManager(): ContextStateManager {\n    return this.contextStateManager;\n  }\n\n  /**\n   * Register the context tool with the tool manager\n   */\n  private registerContextTool(): void {\n    const contextTool = createGetContextTool(this.contextStateManager);\n    this.toolManager.register(contextTool as ToolDefinition<TEventMap>);\n    console.log('🔧 [SAMMY-AGENT-CORE] Registered getContext tool');\n  }\n\n  async start(options: StartOptions): Promise<AgentSession> {\n    try {\n      // Clean up any existing session\n      await this.destroy();\n\n      // Create new session\n      this.session = {\n        sessionId: uuidv4(),\n        conversationId: uuidv4(),\n        agentMode: options.agentMode,\n        sammyThreeOrganisationFeatureId:\n          options.sammyThreeOrganisationFeatureId,\n        guideId: options.guideId,\n        startedAt: new Date(),\n      };\n\n      // Get ephemeral token\n      const tokenResult = await this.coreServices.generateEphemeralToken({\n        model: this.config.model || 'models/gemini-live-2.5-flash-preview',\n        config: {\n          session_resumption: {},\n          response_modalities: ['AUDIO'],\n        },\n      });\n\n      this.currentToken = tokenResult.ephemeral_token || tokenResult.token;\n      if (!this.currentToken) throw new Error('Failed to get ephemeral token');\n\n      await this.coreServices.instantiateSession(this.session);\n\n      // Create base client\n      const baseClient = new GenAILiveClient({\n        apiKey: this.currentToken,\n        httpOptions: {\n          apiVersion: 'v1alpha',\n        },\n      });\n\n      // Wrap with observability if enabled\n      this.client = wrapGenAIClient(baseClient, {\n        sessionId: this.session.sessionId,\n        conversationId: this.session.conversationId,\n        auth: {\n          token: this.config.auth.token,\n          baseUrl: this.config.auth.baseUrl,\n        },\n        observability: {\n          ...this.config.observability,\n          enabled: this.config.observability?.enabled || false,\n          callback:\n            this.config.observability?.callback ||\n            ((event) => {\n              this.callbacks.onObservabilityEvent?.(event);\n            }),\n          // Pass through audioAggregation config if present\n          audioAggregation: this.config.observability?.audioAggregation,\n        },\n      });\n\n      // Track token generation event\n      if (\n        'observability' in this.client &&\n        this.client.observability?.manager\n      ) {\n        await this.client.observability.manager.trackEvent({\n          type: 'token.generated',\n          data: {\n            model: this.config.model || 'models/gemini-live-2.5-flash-preview',\n            expiresAt: tokenResult.expiresAt,\n          },\n        });\n      }\n\n      // Set up audio streaming\n      await this.setupAudioStreaming();\n\n      // Set up event handlers\n      this.setupEventHandlers();\n\n      // Update context managers with observability if enabled\n      if (this.client && 'observability' in this.client) {\n        this.updateObservabilityInContextManagers(\n          this.client.observability?.manager\n        );\n      }\n\n      const { systemPrompt } = await this.coreServices.getSystemPrompt({\n        agentMode: this.session.agentMode,\n        sammyThreeOrganisationFeatureId:\n          this.session.sammyThreeOrganisationFeatureId,\n        guideId: options.guideId,\n      });\n\n      // Get agent configuration\n      const agentConnectConfig: LiveConnectConfig = getAgentConfig(\n        this.toolManager.getAllDeclarations(),\n        systemPrompt\n      );\n\n      // Connect to Gemini Live\n      await this.client.connect(\n        this.config.model || 'models/gemini-live-2.5-flash-preview',\n        agentConnectConfig\n      );\n\n      // Start screen capture\n      await this.screenCapture.start(this.client as GenAILiveClient);\n      this.updateAgentState('isStreaming', true);\n\n      // Track screen capture start event\n      if (\n        this.client &&\n        'observability' in this.client &&\n        this.client.observability?.manager\n      ) {\n        this.client.observability.manager.trackEvent({\n          type: 'screen_capture.start',\n          data: {\n            method: 'screen', // Default to screen, could be extended\n          },\n        });\n\n        // Track streaming start\n        this.client.observability.manager.trackEvent({\n          type: 'agent.streaming_start',\n          data: {\n            streamType: 'both', // We're streaming both audio and screen\n          },\n        });\n      }\n\n      // Send initial hello\n      setTimeout(() => {\n        this.sendMessage('Hello');\n      }, 500);\n\n      return this.session;\n    } catch (error) {\n      // Handle token expiry\n      if (\n        error instanceof Error &&\n        (error.message.includes('401') ||\n          error.message.includes('unregistered callers'))\n      ) {\n        this.callbacks.onTokenExpired?.();\n      }\n\n      // Clean up any existing session\n      await this.destroy();\n\n      throw error;\n    }\n  }\n\n  async destroy(): Promise<void> {\n    // Track cleanup event if we have an active session\n    if (\n      this.client &&\n      'observability' in this.client &&\n      this.client.observability?.manager &&\n      this.session\n    ) {\n      await this.client.observability.manager.trackEvent({\n        type: 'agent.cleanup',\n        data: {\n          reason: 'user_initiated', // Default to user initiated\n        },\n      });\n    }\n\n    // End observability session if enabled\n    if (this.client && this.client.observability) {\n      const finalTrace = await this.client.observability.endSession();\n      this.callbacks.onSessionTraceComplete?.(finalTrace);\n    }\n\n    // Stop audio recording\n    this.audioRecorder.stop();\n\n    // Stop screen capture\n    await this.screenCapture.stop();\n\n    // Track screen capture stop and streaming stop events\n    if (\n      this.client &&\n      'observability' in this.client &&\n      this.client.observability?.manager\n    ) {\n      this.client.observability.manager.trackEvent({\n        type: 'screen_capture.stop',\n        data: {\n          reason: 'user_initiated',\n        },\n      });\n\n      this.client.observability.manager.trackEvent({\n        type: 'agent.streaming_stop',\n        data: {\n          streamType: 'both',\n          reason: 'user_initiated',\n        },\n      });\n    }\n\n    this.updateAgentState('isStreaming', false);\n\n    // Send any remaining transcriptions and mark conversation ready\n    if (this.session?.conversationId) {\n      try {\n        // Send any pending transcriptions first\n        await this.sendPendingTranscriptions();\n\n        // Mark conversation as ready for processing\n        const result =\n          await this.conversationServices.markConversationReadyToProcess(\n            this.session.conversationId\n          );\n\n        // Track conversation ready event before disconnecting client\n        if (\n          this.client &&\n          'observability' in this.client &&\n          this.client.observability?.manager\n        ) {\n          this.client.observability.manager.trackEvent({\n            type: 'conversation.ready',\n            data: {\n              conversationId: this.session.conversationId,\n              success: result.success,\n              message: result.message,\n            },\n          });\n        }\n\n        if (result.success) {\n          console.log('[Agent] Conversation marked as ready for processing');\n        } else {\n          console.warn(\n            '[Agent] Failed to mark conversation ready:',\n            result.message\n          );\n        }\n      } catch (error) {\n        console.error('[Agent] Error during conversation cleanup:', error);\n        // Don't throw - allow cleanup to continue\n      }\n    }\n\n    // Disconnect client\n    if (this.client) {\n      await this.client.disconnect();\n      this.client = null;\n    }\n\n    // Clean up audio streamer\n    if (this.audioStreamer) {\n      this.audioStreamer.stop();\n    }\n\n    // Reset state\n    this.session = null;\n    this.isConnected = false;\n    this.currentToken = null;\n    this.outputTranscriptBuffer = '';\n    this.inputTranscriptBuffer = '';\n  }\n\n  sendMessage(message: string): void {\n    if (!this.client || !this.isConnected) {\n      console.warn('Cannot send message: not connected');\n      return;\n    }\n\n    this.client.send([{ text: message }], true);\n  }\n\n  setMuted(muted: boolean): void {\n    this.muted = muted;\n\n    // Track mute/unmute events\n    if (\n      this.client &&\n      'observability' in this.client &&\n      this.client.observability?.manager\n    ) {\n      this.client.observability.manager.trackEvent({\n        type: muted ? 'agent.mute' : 'agent.unmute',\n        data: {\n          muted,\n        },\n      });\n    }\n\n    if (muted) {\n      this.audioRecorder.stop();\n    } else if (this.isConnected) {\n      this.startAudioRecording();\n    }\n  }\n\n  getToolEventEmitter() {\n    return this.toolManager.eventEmitter;\n  }\n\n  /**\n   * Get current observability trace\n   */\n  getObservabilityTrace() {\n    return this.client && this.client.observability\n      ? this.client.observability.getTrace()\n      : null;\n  }\n\n  /**\n   * Export observability trace as JSON\n   */\n  exportObservabilityTrace() {\n    return this.client && this.client.observability\n      ? this.client.observability.exportAsJson()\n      : null;\n  }\n\n  /**\n   * Generate observability summary report\n   */\n  generateObservabilitySummary() {\n    return this.client && this.client.observability\n      ? this.client.observability.manager.generateSummaryReport()\n      : null;\n  }\n\n  /**\n   * Get current session statistics\n   */\n  getSessionStatistics() {\n    return this.client && this.client.observability\n      ? this.client.observability.manager.getStatistics()\n      : null;\n  }\n\n  /**\n   * Get observability event emitter\n   */\n  getObservabilityEvents() {\n    return this.client && this.client.observability\n      ? this.client.observability.events\n      : null;\n  }\n\n  private async setupAudioStreaming(): Promise<void> {\n    const audioCtx = await audioContext({ id: 'audio-out' });\n    // Get debug flag from config\n    const debugAudioPerformance = this.config.debugAudioPerformance ?? false;\n    this.audioStreamer = new AudioStreamer(audioCtx, debugAudioPerformance);\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    this.audioStreamer.addWorklet<any>(\n      'vumeter-out',\n      VolMeterWorket,\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      (ev: any) => {\n        this.updateAgentState('agentVolume', ev.data.volume);\n      }\n    );\n\n    // Make audio streamer available to client\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    (this.client as any).audioStreamerRef = { current: this.audioStreamer };\n  }\n\n  private setupEventHandlers(): void {\n    if (!this.client) return;\n\n    this.client\n      .on('open', this.handleOpen.bind(this))\n      .on('close', this.handleClose.bind(this))\n      .on('error', (errorEvent: ErrorEvent) =>\n        this.handleError(new Error(errorEvent.message || 'Unknown error'))\n      )\n      .on('audio', (data: ArrayBuffer) =>\n        this.handleAudio({ audio: this.arrayBufferToBase64(data) })\n      )\n      .on('interrupted', this.handleInterrupted.bind(this))\n      .on('outputtranscription', this.handleOutputTranscription.bind(this))\n      .on('inputtranscription', this.handleInputTranscription.bind(this))\n      .on('turncomplete', this.handleTurnComplete.bind(this))\n      .on('toolcall', this.handleToolCall.bind(this));\n  }\n\n  private handleOpen(): void {\n    console.log('[Agent] Connection opened');\n    this.isConnected = true;\n    this.callbacks.onConnectionStateChange?.(true);\n\n    // Start audio recording\n    if (!this.muted) {\n      this.startAudioRecording();\n    }\n  }\n\n  private handleClose(): void {\n    console.log('[Agent] Connection closed');\n    this.isConnected = false;\n    this.callbacks.onConnectionStateChange?.(false);\n  }\n\n  private handleError(error: Error): void {\n    console.error('[Agent] Error:', error);\n    this.callbacks.onError?.(error);\n  }\n\n  private handleAudio(data: { audio: string }): void {\n    if (this.audioStreamer) {\n      const binaryString = atob(data.audio);\n      const bytes = new Uint8Array(binaryString.length);\n      for (let i = 0; i < binaryString.length; i++) {\n        bytes[i] = binaryString.charCodeAt(i);\n      }\n\n      // Pass Uint8Array directly instead of Float32Array\n      this.audioStreamer.addPCM16(bytes);\n    }\n  }\n\n  private handleInterrupted(): void {\n    console.log('[Agent] Interrupted');\n    this.audioStreamer?.stop();\n\n    // Send any pending transcriptions\n    this.sendPendingTranscriptions();\n  }\n\n  private handleOutputTranscription(transcription: { text: string }): void {\n    this.outputTranscriptBuffer += transcription.text;\n    console.log('🤖 Agent:', transcription.text);\n\n    // Use new context memory manager for memory search\n    this.contextMemoryManager.processAgentTranscription(\n      this.outputTranscriptBuffer,\n      this.session\n    );\n  }\n\n  private handleInputTranscription(transcription: { text: string }): void {\n    this.inputTranscriptBuffer += transcription.text;\n    console.log('👤 User:', transcription.text);\n\n    // Use new context memory manager for memory search\n    this.contextMemoryManager.processUserTranscription(\n      this.inputTranscriptBuffer,\n      this.session\n    );\n  }\n\n  private async handleTurnComplete(): Promise<void> {\n    console.log('[Agent] Turn complete');\n\n    // Capture buffer contents before sending (for callback)\n    const userMessage = this.inputTranscriptBuffer.trim();\n    const agentMessage = this.outputTranscriptBuffer.trim();\n\n    // Send transcriptions (this will clear the buffers)\n    await this.sendPendingTranscriptions();\n\n    // Notify callback with the captured values\n    this.callbacks.onTurnComplete?.({\n      user: userMessage,\n      agent: agentMessage,\n    });\n  }\n\n  private async handleToolCall(toolCall: LiveServerToolCall): Promise<void> {\n    if (!this.client || !this.session) {\n      console.error(\n        '[Agent] Cannot handle tool call: missing client or session'\n      );\n      return;\n    }\n\n    // Notify callback\n    this.callbacks.onToolCall?.(toolCall);\n\n    // Handle tool calls with current session as agent state\n    const functionResponses = await this.toolManager.handleToolCall(\n      toolCall,\n      this.session\n    );\n\n    // Send functionResponses\n    if (functionResponses && functionResponses.length > 0) {\n      this.client.sendToolResponse({ functionResponses });\n    }\n  }\n\n  private startAudioRecording(): void {\n    this.audioRecorder.on('data', this.handleAudioData.bind(this));\n    this.audioRecorder.start();\n    console.log('[Agent] Started audio recording');\n  }\n\n  private handleAudioData(base64Data: string): void {\n    if (!this.client || !this.isConnected) return;\n\n    this.client.sendRealtimeInput([\n      {\n        mimeType: 'audio/pcm;rate=48000',\n        data: base64Data,\n      },\n    ]);\n  }\n\n  private async sendPendingTranscriptions(): Promise<void> {\n    if (!this.session) return;\n\n    const messages = [];\n\n    // Add user message\n    if (this.inputTranscriptBuffer.trim()) {\n      messages.push({\n        sender: 'user' as const,\n        text: this.inputTranscriptBuffer.trim(),\n        timestamp: new Date().toISOString(),\n      });\n    }\n\n    // Add agent message\n    if (this.outputTranscriptBuffer.trim()) {\n      messages.push({\n        sender: 'agent' as const,\n        text: this.outputTranscriptBuffer.trim(),\n        timestamp: new Date().toISOString(),\n      });\n    }\n\n    // Send messages\n    if (messages.length > 0) {\n      try {\n        await this.conversationServices.sendMessages({\n          ...this.session,\n          messages,\n        });\n\n        // Clear buffers after successful send to prevent duplicate sends\n        this.inputTranscriptBuffer = '';\n        this.outputTranscriptBuffer = '';\n      } catch (error) {\n        console.error('[Agent] Failed to send messages:', error);\n      }\n    }\n  }\n\n  private arrayBufferToBase64(buffer: ArrayBuffer): string {\n    const bytes = new Uint8Array(buffer);\n    let binary = '';\n    for (let i = 0; i < bytes.byteLength; i++) {\n      binary += String.fromCharCode(bytes[i]);\n    }\n    return btoa(binary);\n  }\n}\n","/**\n * Screen Capture Manager\n * Handles all screen capture logic for the agent\n */\nimport { GenAILiveClient } from '@/lib/genai-live-client';\nimport {\n  ScreenCaptureCallbacks,\n  ScreenCaptureConfig,\n} from '@/types/media-types';\n\nexport interface ScreenCaptureManagerConfig extends ScreenCaptureConfig {\n  startStreaming: (client: GenAILiveClient) => Promise<MediaStream | void>;\n  stopStreaming: () => void;\n}\n\nexport class ScreenCaptureManager {\n  private startStreaming: (\n    client: GenAILiveClient\n  ) => Promise<MediaStream | void>;\n  private stopStreaming: () => void;\n\n  constructor(screenCaptureCallbacks: ScreenCaptureCallbacks) {\n    this.startStreaming = screenCaptureCallbacks.startStreaming;\n    this.stopStreaming = screenCaptureCallbacks.stopStreaming;\n  }\n\n  async start(client: GenAILiveClient): Promise<void> {\n    console.log('📱 [SCREEN-CAPTURE-MANAGER] start() called:', {\n      hasClient: !!client,\n      hasStartStreaming: typeof this.startStreaming === 'function',\n    });\n    await this.startStreaming(client);\n  }\n\n  async stop(): Promise<void> {\n    this.stopStreaming();\n  }\n}\n","/**\n * Copyright 2024 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n  Content,\n  GoogleGenAI,\n  LiveCallbacks,\n  LiveClientToolResponse,\n  LiveConnectConfig,\n  LiveServerContent,\n  LiveServerMessage,\n  LiveServerToolCall,\n  LiveServerToolCallCancellation,\n  Part,\n  Session,\n} from '@google/genai';\nimport { EventEmitter } from 'eventemitter3';\n\nimport { LiveClientOptions, StreamingLog } from '@/types/genai-types';\n\n/**\n * Event types that can be emitted by the MultimodalLiveClient.\n * Each event corresponds to a specific message from GenAI or client state change.\n */\nexport interface LiveClientEventTypes {\n  // Emitted when input transcription is received\n  inputtranscription: (transcription: {\n    finished: boolean;\n    text: string;\n  }) => void;\n  // Emitted when output transcription is received\n  outputtranscription: (transcription: {\n    finished: boolean;\n    text: string;\n  }) => void;\n  // Emitted when a tool call is cancelled\n  toolcallcancellation: (\n    toolcallCancellation: LiveServerToolCallCancellation\n  ) => void;\n  // Emitted when audio data is received\n  audio: (data: ArrayBuffer) => void;\n  // Emitted when the connection closes\n  close: (event: CloseEvent) => void;\n  // Emitted when content is received from the server\n  content: (data: LiveServerContent) => void;\n  // Emitted when an error occurs\n  error: (error: ErrorEvent) => void;\n  // Emitted when the server interrupts the current generation\n  interrupted: () => void;\n  // Emitted for logging events\n  log: (log: StreamingLog) => void;\n  // Emitted when the connection opens\n  open: () => void;\n  // Emitted when the initial setup is complete\n  setupcomplete: () => void;\n  // Emitted when a tool call is received\n  toolcall: (toolCall: LiveServerToolCall) => void;\n  // Emitted when the current turn is complete\n  turncomplete: () => void;\n}\n\n/**\n * A event-emitting class that manages the connection to the websocket and emits\n * events to the rest of the application.\n * If you dont want to use react you can still use this.\n */\nexport class GenAILiveClient extends EventEmitter<LiveClientEventTypes> {\n  private _model: string | null = null;\n\n  private _session: Session | null = null;\n  private _status: 'connected' | 'connecting' | 'disconnected' = 'disconnected';\n\n  protected client: GoogleGenAI;\n  protected config: LiveConnectConfig | null = null;\n\n  constructor(options: LiveClientOptions) {\n    super();\n    this.client = new GoogleGenAI(options);\n    this.send = this.send.bind(this);\n    this.onopen = this.onopen.bind(this);\n    this.onerror = this.onerror.bind(this);\n    this.onclose = this.onclose.bind(this);\n    this.onmessage = this.onmessage.bind(this);\n  }\n\n  protected log(\n    type: string,\n    message: StreamingLog['message'],\n    metadata?: { area?: string; latencyMs?: number }\n  ) {\n    const log: StreamingLog = {\n      date: new Date(),\n      type,\n      message,\n      ...(metadata || {}),\n    };\n    this.emit('log', log);\n  }\n\n  protected onclose(e: CloseEvent) {\n    console.log('🔴 WebSocket connection closed:', {\n      code: e.code,\n      reason: e.reason,\n      wasClean: e.wasClean,\n      timestamp: new Date().toISOString(),\n    });\n    this.log(\n      `server.close`,\n      `disconnected ${e.reason ? `with reason: ${e.reason}` : ``}`\n    );\n    this.emit('close', e);\n  }\n\n  protected onerror(e: ErrorEvent) {\n    console.error('🚨 WebSocket error occurred:', {\n      message: e.message,\n      filename: e.filename,\n      lineno: e.lineno,\n      colno: e.colno,\n      error: e.error,\n      timestamp: new Date().toISOString(),\n    });\n    this.log('server.error', e.message);\n    this.emit('error', e);\n  }\n\n  protected async onmessage(message: LiveServerMessage) {\n    if (message.setupComplete) {\n      this.log('server.send', 'setupComplete');\n      this.emit('setupcomplete');\n      return;\n    }\n    if (message.toolCall) {\n      console.log('🔧 Tool call message received:', message.toolCall);\n      this.log('server.toolCall', message);\n      this.emit('toolcall', message.toolCall);\n      return;\n    }\n    if (message.toolCallCancellation) {\n      console.log(\n        '❌ Tool call cancellation received:',\n        message.toolCallCancellation\n      );\n      this.log('server.toolCallCancellation', message);\n      this.emit('toolcallcancellation', message.toolCallCancellation);\n      return;\n    }\n\n    // this json also might be `contentUpdate { interrupted: true }`\n    // or contentUpdate { end_of_turn: true }\n    if (message.serverContent) {\n      const { serverContent } = message;\n\n      // Handle transcriptions within serverContent\n      if (serverContent.inputTranscription) {\n        this.emit('inputtranscription', {\n          text: serverContent.inputTranscription.text || '',\n          finished: serverContent.inputTranscription.finished || false,\n        });\n      }\n\n      if (serverContent.outputTranscription) {\n        this.emit('outputtranscription', {\n          text: serverContent.outputTranscription.text || '',\n          finished: serverContent.outputTranscription.finished || false,\n        });\n      }\n\n      if ('interrupted' in serverContent) {\n        this.log('server.content', 'interrupted');\n        this.emit('interrupted');\n        return;\n      }\n      if ('turnComplete' in serverContent) {\n        this.log('server.content', 'turnComplete');\n        this.emit('turncomplete');\n      }\n\n      if ('modelTurn' in serverContent) {\n        let parts: Part[] = serverContent.modelTurn?.parts || [];\n\n        // when its audio that is returned for modelTurn\n        const audioParts = parts.filter(\n          (p) => p.inlineData && p.inlineData.mimeType?.startsWith('audio/pcm')\n        );\n        const base64s = audioParts.map((p) => p.inlineData?.data);\n\n        // strip the audio parts out of the modelTurn\n        const otherParts = difference(parts, audioParts);\n\n        base64s.forEach((b64) => {\n          if (b64) {\n            const data = base64ToArrayBuffer(b64);\n            this.emit('audio', data);\n            this.log(`server.audio`, `buffer (${data.byteLength})`);\n          }\n        });\n        if (!otherParts.length) {\n          return;\n        }\n\n        parts = otherParts;\n\n        const content: { modelTurn: Content } = { modelTurn: { parts } };\n        this.emit('content', content);\n        this.log(`server.content`, message);\n      }\n    } else {\n      console.log('received unmatched message', message);\n    }\n  }\n\n  protected onopen() {\n    console.log('✅ WebSocket connection opened successfully:', {\n      model: this._model,\n      status: this._status,\n      timestamp: new Date().toISOString(),\n    });\n    this.log('client.open', 'Connected');\n    this.emit('open');\n  }\n\n  async connect(model: string, config: LiveConnectConfig): Promise<boolean> {\n    console.log('🔄 Starting connection process to model:', model);\n    console.log('🔧 Connection config:', {\n      responseModalities: config.responseModalities,\n      temperature: config.temperature,\n      speechConfig: config.speechConfig,\n      realtimeInputConfig: config.realtimeInputConfig,\n      tools: config.tools?.length ? `${config.tools.length} tools` : 'no tools',\n    });\n\n    if (this._status === 'connected' || this._status === 'connecting') {\n      console.warn(\n        '⚠️ Already connected or connecting, aborting new connection attempt'\n      );\n      return false;\n    }\n\n    this._status = 'connecting';\n    this.config = config;\n    this._model = model;\n\n    const callbacks: LiveCallbacks = {\n      onopen: this.onopen,\n      onmessage: this.onmessage,\n      onerror: this.onerror,\n      onclose: this.onclose,\n    };\n\n    console.log('🚀 Attempting to establish WebSocket connection...');\n    try {\n      this._session = await this.client.live.connect({\n        model,\n        config,\n        callbacks,\n      });\n      console.log('✅ Session object created successfully');\n    } catch (e) {\n      console.error('❌ Error during live.connect call:', e);\n      console.error('❌ Connect error details:', {\n        name: (e as Error).name,\n        message: (e as Error).message,\n        stack: (e as Error).stack,\n        cause: (e as Error).cause,\n      });\n      this._status = 'disconnected';\n      return false;\n    }\n\n    this._status = 'connected';\n    console.log('🎉 Connection established successfully');\n    return true;\n  }\n\n  public disconnect() {\n    if (!this.session) {\n      return false;\n    }\n    this.session?.close();\n    this._session = null;\n    this._status = 'disconnected';\n\n    this.log('client.close', `Disconnected`);\n    return true;\n  }\n\n  public getConfig() {\n    return { ...this.config };\n  }\n\n  /**\n   * send normal content parts such as { text }\n   */\n  send(parts: Part | Part[], turnComplete: boolean = true) {\n    this.session?.sendClientContent({ turns: parts, turnComplete });\n    this.log(`client.send`, {\n      turns: Array.isArray(parts) ? parts : [parts],\n      turnComplete,\n    });\n  }\n\n  /**\n   * send realtimeInput, this is base64 chunks of \"audio/pcm\" and/or \"image/jpg\"\n   */\n  sendRealtimeInput(chunks: Array<{ data: string; mimeType: string }>) {\n    let hasAudio = false;\n    let hasVideo = false;\n    for (const ch of chunks) {\n      this.session?.sendRealtimeInput({ media: ch });\n      if (ch.mimeType.includes('audio')) {\n        hasAudio = true;\n      }\n      if (ch.mimeType.includes('image')) {\n        hasVideo = true;\n      }\n      if (hasAudio && hasVideo) {\n        break;\n      }\n    }\n    const message =\n      hasAudio && hasVideo\n        ? 'audio + video'\n        : hasAudio\n          ? 'audio'\n          : hasVideo\n            ? 'video'\n            : 'unknown';\n    this.log(`client.realtimeInput`, message);\n  }\n\n  /**\n   *  send a response to a function call and provide the id of the functions you are responding to\n   */\n  sendToolResponse(toolResponse: LiveClientToolResponse) {\n    if (\n      toolResponse.functionResponses &&\n      toolResponse.functionResponses.length\n    ) {\n      this.session?.sendToolResponse({\n        functionResponses: toolResponse.functionResponses,\n      });\n      this.log(`client.toolResponse`, toolResponse);\n    }\n  }\n\n  public get model() {\n    return this._model;\n  }\n\n  public get session() {\n    return this._session;\n  }\n\n  public get status() {\n    return this._status;\n  }\n}\n\n// Note: base64ToArrayBuffer should be implemented or imported from a utility\nfunction base64ToArrayBuffer(base64: string): ArrayBuffer {\n  const binaryString = window.atob(base64);\n  const len = binaryString.length;\n  const bytes = new Uint8Array(len);\n  for (let i = 0; i < len; i++) {\n    bytes[i] = binaryString.charCodeAt(i);\n  }\n  return bytes.buffer;\n}\n\n// Simple difference function to replace lodash dependency\nfunction difference<T>(array: T[], values: T[]): T[] {\n  return array.filter((item) => !values.includes(item));\n}\n","import { concatenateArrayBuffers } from '@/utils/audio-utils';\n\ninterface AudioChunk {\n  data: ArrayBuffer;\n  timestamp: number;\n}\n\ninterface ConversationBuffer {\n  agentBytesProcessed: number;\n  agentChunks: AudioChunk[];\n  lastAgentFlushTime: number;\n  lastUserFlushTime: number;\n  turnNumber: number;\n  userBytesProcessed: number;\n  userChunks: AudioChunk[];\n  conversationStartTime?: number;\n  // eslint-disable-next-line no-undef\n  flushTimer?: NodeJS.Timeout | number;\n}\n\nexport interface FlushData {\n  conversationId: string;\n  endTime: number;\n  pcmData: ArrayBuffer;\n  sampleRate: number;\n  speaker: 'agent' | 'user';\n  startTime: number;\n  totalBytesProcessed: number;\n  turnNumber: number;\n  timestamps?: { end: number; start: number }[];\n}\n\nexport interface AudioAggregatorConfig {\n  flushIntervalMs?: number; // Default: 10000 (10 seconds)\n  includeGapSilence?: boolean; // Whether to pad with silence during gaps\n  onFlush?: (data: FlushData) => Promise<void>;\n}\n\nexport class AudioAggregator {\n  private buffers: Map<string, ConversationBuffer> = new Map();\n  private config: Required<AudioAggregatorConfig>;\n\n  constructor(config: AudioAggregatorConfig = {}) {\n    this.config = {\n      flushIntervalMs: config.flushIntervalMs || 10000, // 10 seconds default\n      onFlush: config.onFlush || (async () => {}),\n      includeGapSilence: config.includeGapSilence ?? true,\n    };\n  }\n\n  /**\n   * Add raw PCM audio chunk to the buffer with timing information\n   * @param conversationId - The conversation ID\n   * @param speaker - Whether this is user or agent audio\n   * @param pcmData - Raw PCM audio data as ArrayBuffer\n   * @param sampleRate - Sample rate of the audio (48000 for user, 24000 for agent)\n   * @param turnNumber - Optional turn number for correlation\n   * @param timestamp - Optional timestamp for this chunk (defaults to now)\n   */\n  addPCMChunk(\n    conversationId: string,\n    speaker: 'agent' | 'user',\n    pcmData: ArrayBuffer,\n    sampleRate: number,\n    turnNumber?: number,\n    timestamp?: number\n  ): void {\n    const now = timestamp || Date.now();\n\n    if (!this.buffers.has(conversationId)) {\n      const buffer: ConversationBuffer = {\n        userChunks: [],\n        agentChunks: [],\n        lastUserFlushTime: now,\n        lastAgentFlushTime: now,\n        conversationStartTime: now,\n        turnNumber: 0,\n        userBytesProcessed: 0,\n        agentBytesProcessed: 0,\n      };\n\n      // Start the flush timer for this conversation\n      buffer.flushTimer = setInterval(() => {\n        this.flushConversation(conversationId);\n      }, this.config.flushIntervalMs);\n\n      this.buffers.set(conversationId, buffer);\n    }\n\n    const buffer = this.buffers.get(conversationId)!;\n\n    // Update turn number if provided\n    if (turnNumber !== undefined) {\n      buffer.turnNumber = turnNumber;\n    }\n\n    // Add chunk with timestamp to appropriate buffer\n    const chunk: AudioChunk = { data: pcmData, timestamp: now };\n\n    if (speaker === 'user') {\n      buffer.userChunks.push(chunk);\n    } else {\n      buffer.agentChunks.push(chunk);\n    }\n  }\n\n  /**\n   * Update turn number for a conversation\n   */\n  updateTurnNumber(conversationId: string, turnNumber: number): void {\n    const buffer = this.buffers.get(conversationId);\n    if (buffer) {\n      buffer.turnNumber = turnNumber;\n    }\n  }\n\n  private async flushConversation(conversationId: string): Promise<void> {\n    const buffer = this.buffers.get(conversationId);\n    if (!buffer) return;\n\n    const now = Date.now();\n\n    // Flush user audio if we have chunks\n    if (buffer.userChunks.length > 0) {\n      const { pcmData, timestamps } = this.config.includeGapSilence\n        ? this.concatenateWithSilence(\n            buffer.userChunks,\n            48000,\n            buffer.conversationStartTime!\n          )\n        : {\n            pcmData: concatenateArrayBuffers(\n              buffer.userChunks.map((c) => c.data)\n            ),\n            timestamps: [],\n          };\n\n      await this.config.onFlush({\n        conversationId,\n        speaker: 'user',\n        pcmData,\n        sampleRate: 48000,\n        turnNumber: buffer.turnNumber,\n        startTime: buffer.lastUserFlushTime,\n        endTime: now,\n        totalBytesProcessed: buffer.userBytesProcessed + pcmData.byteLength,\n        timestamps,\n      });\n\n      buffer.userBytesProcessed += pcmData.byteLength;\n      buffer.userChunks = [];\n      buffer.lastUserFlushTime = now;\n    }\n\n    // Flush agent audio if we have chunks\n    if (buffer.agentChunks.length > 0) {\n      const { pcmData, timestamps } = this.config.includeGapSilence\n        ? this.concatenateWithSilence(\n            buffer.agentChunks,\n            24000,\n            buffer.conversationStartTime!\n          )\n        : {\n            pcmData: concatenateArrayBuffers(\n              buffer.agentChunks.map((c) => c.data)\n            ),\n            timestamps: [],\n          };\n\n      await this.config.onFlush({\n        conversationId,\n        speaker: 'agent',\n        pcmData,\n        sampleRate: 24000,\n        turnNumber: buffer.turnNumber,\n        startTime: buffer.lastAgentFlushTime,\n        endTime: now,\n        totalBytesProcessed: buffer.agentBytesProcessed + pcmData.byteLength,\n        timestamps,\n      });\n\n      buffer.agentBytesProcessed += pcmData.byteLength;\n      buffer.agentChunks = [];\n      buffer.lastAgentFlushTime = now;\n    }\n  }\n\n  /**\n   * Force flush all buffered audio for a conversation\n   */\n  async flush(conversationId: string): Promise<void> {\n    await this.flushConversation(conversationId);\n  }\n\n  /**\n   * Flush all conversations (useful for beforeunload)\n   */\n  async flushAll(): Promise<void> {\n    const promises: Promise<void>[] = [];\n\n    for (const conversationId of this.buffers.keys()) {\n      promises.push(this.flushConversation(conversationId));\n    }\n\n    await Promise.all(promises);\n  }\n\n  /**\n   * End a conversation and clean up resources\n   */\n  async endConversation(conversationId: string): Promise<void> {\n    const buffer = this.buffers.get(conversationId);\n    if (!buffer) return;\n\n    // Clear the timer\n    if (buffer.flushTimer) {\n      // eslint-disable-next-line no-undef\n      clearInterval(buffer.flushTimer as NodeJS.Timeout);\n    }\n\n    // Final flush\n    await this.flushConversation(conversationId);\n\n    // Clean up\n    this.buffers.delete(conversationId);\n  }\n\n  /**\n   * Get current buffer statistics for debugging\n   */\n  getBufferStats(conversationId: string): {\n    agentBytesBuffered: number;\n    userBytesBuffered: number;\n    agentBytesProcessed: number;\n    agentChunksCount: number;\n    userBytesProcessed: number;\n    userChunksCount: number;\n  } | null {\n    const buffer = this.buffers.get(conversationId);\n    if (!buffer) return null;\n\n    const userBytesBuffered = buffer.userChunks.reduce(\n      (sum, chunk) => sum + chunk.data.byteLength,\n      0\n    );\n    const agentBytesBuffered = buffer.agentChunks.reduce(\n      (sum, chunk) => sum + chunk.data.byteLength,\n      0\n    );\n\n    return {\n      userChunksCount: buffer.userChunks.length,\n      agentChunksCount: buffer.agentChunks.length,\n      userBytesBuffered,\n      agentBytesBuffered,\n      userBytesProcessed: buffer.userBytesProcessed,\n      agentBytesProcessed: buffer.agentBytesProcessed,\n    };\n  }\n\n  /**\n   * Utility method to create a standalone WAV file from PCM data\n   */\n  static pcm16ToWav(pcmData: ArrayBuffer, sampleRate: number): ArrayBuffer {\n    const pcmLength = pcmData.byteLength;\n    const wavBuffer = new ArrayBuffer(44 + pcmLength);\n    const view = new DataView(wavBuffer);\n\n    // WAV header\n    const writeString = (offset: number, string: string) => {\n      for (let i = 0; i < string.length; i++) {\n        view.setUint8(offset + i, string.charCodeAt(i));\n      }\n    };\n\n    writeString(0, 'RIFF');\n    view.setUint32(4, 36 + pcmLength, true);\n    writeString(8, 'WAVE');\n    writeString(12, 'fmt ');\n    view.setUint32(16, 16, true); // fmt chunk size\n    view.setUint16(20, 1, true); // PCM format\n    view.setUint16(22, 1, true); // Mono\n    view.setUint32(24, sampleRate, true);\n    view.setUint32(28, sampleRate * 2, true); // byte rate (16-bit mono)\n    view.setUint16(32, 2, true); // block align\n    view.setUint16(34, 16, true); // bits per sample\n    writeString(36, 'data');\n    view.setUint32(40, pcmLength, true);\n\n    // Copy PCM data\n    new Uint8Array(wavBuffer, 44).set(new Uint8Array(pcmData));\n\n    return wavBuffer;\n  }\n\n  /**\n   * Create just the WAV header (for multipart upload)\n   */\n  static createWavHeader(dataSize: number, sampleRate: number): ArrayBuffer {\n    const wavBuffer = new ArrayBuffer(44);\n    const view = new DataView(wavBuffer);\n\n    const writeString = (offset: number, string: string) => {\n      for (let i = 0; i < string.length; i++) {\n        view.setUint8(offset + i, string.charCodeAt(i));\n      }\n    };\n\n    writeString(0, 'RIFF');\n    view.setUint32(4, 36 + dataSize, true);\n    writeString(8, 'WAVE');\n    writeString(12, 'fmt ');\n    view.setUint32(16, 16, true); // fmt chunk size\n    view.setUint16(20, 1, true); // PCM format\n    view.setUint16(22, 1, true); // Mono\n    view.setUint32(24, sampleRate, true);\n    view.setUint32(28, sampleRate * 2, true); // byte rate (16-bit mono)\n    view.setUint16(32, 2, true); // block align\n    view.setUint16(34, 16, true); // bits per sample\n    writeString(36, 'data');\n    view.setUint32(40, dataSize, true);\n\n    return wavBuffer;\n  }\n\n  /**\n   * Concatenate audio chunks with silence padding to maintain timing\n   */\n  private concatenateWithSilence(\n    chunks: AudioChunk[],\n    sampleRate: number,\n    conversationStartTime: number\n  ): { pcmData: ArrayBuffer; timestamps: { end: number; start: number }[] } {\n    if (chunks.length === 0) {\n      return { pcmData: new ArrayBuffer(0), timestamps: [] };\n    }\n\n    // Sort chunks by timestamp\n    const sortedChunks = [...chunks].sort((a, b) => a.timestamp - b.timestamp);\n    const timestamps: { end: number; start: number }[] = [];\n    const outputChunks: ArrayBuffer[] = [];\n\n    // Calculate total duration needed\n    const firstChunkTime = sortedChunks[0].timestamp - conversationStartTime;\n\n    // Add initial silence if first chunk doesn't start at conversation beginning\n    if (firstChunkTime > 0) {\n      const silenceSamples = Math.floor((firstChunkTime / 1000) * sampleRate);\n      const silenceBytes = silenceSamples * 2; // 16-bit = 2 bytes per sample\n      outputChunks.push(new ArrayBuffer(silenceBytes)); // Zeros by default\n    }\n\n    // Process each chunk\n    let currentTimeMs = firstChunkTime;\n\n    for (let i = 0; i < sortedChunks.length; i++) {\n      const chunk = sortedChunks[i];\n      const chunkDurationMs = (chunk.data.byteLength / 2 / sampleRate) * 1000;\n\n      // Add silence gap if there's a time gap between chunks\n      if (i > 0) {\n        const expectedTime = currentTimeMs;\n        const actualTime = chunk.timestamp - conversationStartTime;\n        const gapMs = actualTime - expectedTime;\n\n        if (gapMs > 0) {\n          const gapSamples = Math.floor((gapMs / 1000) * sampleRate);\n          const gapBytes = gapSamples * 2;\n          outputChunks.push(new ArrayBuffer(gapBytes));\n          currentTimeMs += gapMs;\n        }\n      }\n\n      // Add the actual audio chunk\n      outputChunks.push(chunk.data);\n\n      // Track timestamp\n      timestamps.push({\n        start: currentTimeMs,\n        end: currentTimeMs + chunkDurationMs,\n      });\n\n      currentTimeMs += chunkDurationMs;\n    }\n\n    return {\n      pcmData: concatenateArrayBuffers(outputChunks),\n      timestamps,\n    };\n  }\n\n  /**\n   * Generate a synchronized stereo WAV file with user audio on left channel and agent on right\n   */\n  async generateStereoConversation(\n    conversationId: string\n  ): Promise<ArrayBuffer | null> {\n    const buffer = this.buffers.get(conversationId);\n    if (\n      !buffer ||\n      (buffer.userChunks.length === 0 && buffer.agentChunks.length === 0)\n    ) {\n      return null;\n    }\n\n    const conversationStart = buffer.conversationStartTime!;\n\n    // Get both audio streams with silence padding\n    const userAudio = this.concatenateWithSilence(\n      buffer.userChunks,\n      48000,\n      conversationStart\n    );\n    const agentAudio = this.concatenateWithSilence(\n      buffer.agentChunks,\n      24000,\n      conversationStart\n    );\n\n    // Resample agent audio to 48kHz to match user audio\n    const resampledAgent = this.resamplePCM(agentAudio.pcmData, 24000, 48000);\n\n    // Create stereo mix\n    return this.createStereoWav(userAudio.pcmData, resampledAgent, 48000);\n  }\n\n  /**\n   * Simple linear interpolation resampling\n   */\n  private resamplePCM(\n    pcmData: ArrayBuffer,\n    fromRate: number,\n    toRate: number\n  ): ArrayBuffer {\n    const ratio = toRate / fromRate;\n    const inputView = new Int16Array(pcmData);\n    const outputLength = Math.floor(inputView.length * ratio);\n    const outputBuffer = new ArrayBuffer(outputLength * 2);\n    const outputView = new Int16Array(outputBuffer);\n\n    for (let i = 0; i < outputLength; i++) {\n      const sourceIndex = i / ratio;\n      const index = Math.floor(sourceIndex);\n      const fraction = sourceIndex - index;\n\n      if (index + 1 < inputView.length) {\n        // Linear interpolation between samples\n        outputView[i] = Math.round(\n          inputView[index] * (1 - fraction) + inputView[index + 1] * fraction\n        );\n      } else {\n        outputView[i] = inputView[index] || 0;\n      }\n    }\n\n    return outputBuffer;\n  }\n\n  /**\n   * Create a stereo WAV with user audio on left channel and agent on right\n   */\n  private createStereoWav(\n    leftChannel: ArrayBuffer,\n    rightChannel: ArrayBuffer,\n    sampleRate: number\n  ): ArrayBuffer {\n    const leftView = new Int16Array(leftChannel);\n    const rightView = new Int16Array(rightChannel);\n    const maxLength = Math.max(leftView.length, rightView.length);\n\n    // Create stereo buffer (2 channels)\n    const stereoLength = maxLength * 2;\n    const wavBuffer = new ArrayBuffer(44 + stereoLength * 2);\n    const view = new DataView(wavBuffer);\n\n    // WAV header for stereo\n    const writeString = (offset: number, string: string) => {\n      for (let i = 0; i < string.length; i++) {\n        view.setUint8(offset + i, string.charCodeAt(i));\n      }\n    };\n\n    writeString(0, 'RIFF');\n    view.setUint32(4, 36 + stereoLength * 2, true);\n    writeString(8, 'WAVE');\n    writeString(12, 'fmt ');\n    view.setUint32(16, 16, true);\n    view.setUint16(20, 1, true); // PCM\n    view.setUint16(22, 2, true); // Stereo\n    view.setUint32(24, sampleRate, true);\n    view.setUint32(28, sampleRate * 4, true); // byte rate for stereo\n    view.setUint16(32, 4, true); // block align for stereo\n    view.setUint16(34, 16, true);\n    writeString(36, 'data');\n    view.setUint32(40, stereoLength * 2, true);\n\n    // Interleave stereo samples\n    const stereoData = new Int16Array(wavBuffer, 44);\n    for (let i = 0; i < maxLength; i++) {\n      stereoData[i * 2] = i < leftView.length ? leftView[i] : 0; // Left\n      stereoData[i * 2 + 1] = i < rightView.length ? rightView[i] : 0; // Right\n    }\n\n    return wavBuffer;\n  }\n}\n","// Auto-generated file - do not edit\n// Generated from src/lib/workers/observability.worker.ts\nexport const observabilityWorkerCode = \"\\\"use strict\\\";(()=>{var E=Object.defineProperty;var S=(r,e,t)=>e in r?E(r,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):r[e]=t;var p=(r,e,t)=>(S(r,typeof e!=\\\"symbol\\\"?e+\\\"\\\":e,t),t);var d=class extends Error{constructor(t,n,s,a){super(t);this.code=n;this.status=s;this.details=a;this.name=\\\"ServiceError\\\"}},m=class extends d{constructor(e=\\\"Token expired\\\",t){super(e,\\\"TOKEN_EXPIRED\\\",401,t)}};var f=class{constructor(e){p(this,\\\"token\\\");p(this,\\\"baseUrl\\\");p(this,\\\"onTokenExpired\\\");p(this,\\\"isTokenValid\\\",!0);this.token=e.token,this.baseUrl=e.baseUrl||\\\"https://api-dev.sammylabs.com\\\",this.onTokenExpired=e.onTokenExpired}updateAuthConfig(e){e.token&&(this.token=e.token,this.isTokenValid=!0),e.baseUrl&&(this.baseUrl=e.baseUrl),e.onTokenExpired!==void 0&&(this.onTokenExpired=e.onTokenExpired)}async validateToken(){try{let e=await fetch(`${this.baseUrl}/validate`,{headers:{Authorization:`Bearer ${this.token}`}});if(!e.ok){if(e.status===401)throw this.isTokenValid=!1,this.onTokenExpired&&this.onTokenExpired(),new m(\\\"Token validation failed\\\");return!1}return this.isTokenValid=!0,!0}catch(e){if(e instanceof m)throw e;return!1}}async makeRequest(e,t){let{path:n,body:s,headers:a={},timeout:l=3e4,retries:c=3,skipValidation:g=!1}=t;if(!g&&!await this.validateToken())throw new d(\\\"Invalid token - cannot make API call\\\",\\\"AUTH_ERROR\\\",401);let u=new URL(n,this.baseUrl).toString(),i=s instanceof FormData,h={Authorization:`Bearer ${this.token}`};Object.entries(a).forEach(([b,y])=>{b===\\\"Content-Type\\\"&&(i||y===\\\"\\\")||(h[b]=y)}),!i&&!h[\\\"Content-Type\\\"]&&(h[\\\"Content-Type\\\"]=\\\"application/json\\\");let R={method:e,headers:h,body:i?s:s?JSON.stringify(s):void 0};return this.attemptRequest(e,n,u,R,l,c,0)}async attemptRequest(e,t,n,s,a,l,c){let g=new AbortController,u=setTimeout(()=>g.abort(),a);try{let i=await fetch(n,{...s,signal:g.signal});if(clearTimeout(u),!i.ok){let v=await this.getErrorDetails(i);if(i.status===401)throw this.isTokenValid=!1,this.onTokenExpired&&this.onTokenExpired(),new m(\\\"API request failed: Token expired\\\",v);if(i.status>=400&&i.status<500&&i.status!==429)throw new d(`Failed to ${e} ${t}`,\\\"CLIENT_ERROR\\\",i.status,v);if(c===l)throw new d(`Failed to ${e} ${t}`,\\\"SERVER_ERROR\\\",i.status,v);return await this.delay(Math.pow(2,c)*1e3),this.attemptRequest(e,t,n,s,a,l,c+1)}return i.status===204||i.headers.get(\\\"content-length\\\")===\\\"0\\\"?void 0:await i.json()}catch(i){if(clearTimeout(u),i instanceof m||i instanceof d)throw i;let h=i;if(c===l)throw new d(`Failed to ${e} ${t} after ${l+1} attempts`,\\\"NETWORK_ERROR\\\",void 0,{error:h,path:t,method:e});return await this.delay(Math.pow(2,c)*1e3),this.attemptRequest(e,t,n,s,a,l,c+1)}}async getErrorDetails(e){try{return await e.json()}catch{return{message:e.statusText}}}delay(e){return new Promise(t=>setTimeout(t,e))}async get(e,t={}){return this.makeRequest(\\\"GET\\\",{path:e,...t})}async post(e,t,n={}){return this.makeRequest(\\\"POST\\\",{path:e,body:t,...n})}async put(e,t,n={}){return this.makeRequest(\\\"PUT\\\",{path:e,body:t,...n})}async patch(e,t,n={}){return this.makeRequest(\\\"PATCH\\\",{path:e,body:t,...n})}async delete(e,t={}){return this.makeRequest(\\\"DELETE\\\",{path:e,...t})}getTokenValidityStatus(){return this.isTokenValid}};var A=r=>async e=>{try{return await r.post(\\\"/api/v1/sammy-three/trace/\\\",e,{timeout:6e4})}catch(t){throw t}},I=r=>async(e,t)=>{try{let n=new URLSearchParams({conversationId:e.conversationId,speaker:e.speaker,sampleRate:e.sampleRate.toString(),turnNumber:e.turnNumber.toString(),startTime:e.startTime.toString(),endTime:e.endTime.toString(),totalBytes:e.totalBytes.toString()}),s=new FormData,a=`${e.speaker}-${Date.now()}.pcm`;return s.append(\\\"audio\\\",t,a),await r.post(`/api/v1/sammy-three/trace/audio/flush?${n}`,s,{headers:{\\\"Content-Type\\\":\\\"\\\"},timeout:6e4})}catch(n){throw n}},w=r=>({saveTraceEvents:A(r),flushAudio:I(r)});var T=class{constructor(e){p(this,\\\"client\\\");p(this,\\\"traceService\\\");let t={token:e.token||\\\"\\\",baseUrl:e.baseUrl,onTokenExpired:e.onTokenExpired||(()=>{typeof self<\\\"u\\\"&&\\\"postMessage\\\"in self&&self.postMessage({type:\\\"tokenExpired\\\"})})};this.client=new f(t),this.traceService=w(this.client)}async saveTraceEvents(e){return this.client.post(\\\"/api/v1/sammy-three/trace/\\\",e,{timeout:6e4,skipValidation:!0})}async flushAudio(e,t){let n=new URLSearchParams({conversationId:e.conversationId,speaker:e.speaker,sampleRate:e.sampleRate.toString(),turnNumber:e.turnNumber.toString(),startTime:e.startTime.toString(),endTime:e.endTime.toString(),totalBytes:e.totalBytes.toString()}),s=new FormData,a=`${e.speaker}-${Date.now()}.wav`;return s.append(\\\"audio\\\",t,a),this.client.post(`/api/v1/sammy-three/trace/audio/flush?${n}`,s,{headers:{\\\"Content-Type\\\":\\\"\\\"},timeout:6e4,skipValidation:!0})}updateToken(e){this.client.updateAuthConfig({token:e})}getTraceService(){return this.traceService}};var o=null,x={init:async r=>{let e=new T({baseUrl:r.config.auth?.baseUrl,token:r.config.auth?.token});return o={sessionId:r.sessionId,config:r.config,eventBatch:[],lastFlushTime:Date.now(),apiClient:e},{success:!0,ready:!0}},event:async r=>{if(!o)throw new Error(\\\"Worker not initialized\\\");let{event:e}=r;return o.eventBatch.push(e),o.eventBatch.length>=o.config.batchSize&&await k(),{success:!0}},audioFlush:async r=>{if(!o)throw new Error(\\\"Worker not initialized\\\");let{conversationId:e,speaker:t,sampleRate:n,turnNumber:s,startTime:a,endTime:l,totalBytesProcessed:c,pcmData:g}=r;try{let u=new Blob([g],{type:\\\"audio/pcm\\\"});return await o.apiClient.flushAudio({conversationId:e,speaker:t,sampleRate:n,turnNumber:s,startTime:a,endTime:l,totalBytes:c},u)}catch(u){return{success:!1,error:u instanceof Error?u.message:\\\"Unknown error\\\"}}},flush:async()=>{if(!o)throw new Error(\\\"Worker not initialized\\\");return await k(),{success:!0}},cleanup:async()=>(o&&o.eventBatch.length>0&&await k(),o=null,{success:!0})};async function k(){if(!o||o.eventBatch.length===0)return;let r=o.eventBatch.splice(0,o.eventBatch.length);try{for(let e=0;e<r.length;e+=o.config.batchSize){let t=r.slice(e,e+o.config.batchSize),n=t.find(c=>c.type===\\\"session.start\\\"),s;n&&(s={sessionId:n.sessionId,agentMode:n.data.agentMode,model:n.data.model,isInternal:n.data.isInternal,flowId:n.data.flowId});let a={sessionId:o.sessionId,events:t,metadata:o.config.metadata,...s&&{conversationData:s}},l=await o.apiClient.saveTraceEvents(a)}o.lastFlushTime=Date.now()}catch{o.eventBatch.unshift(...r)}}self.onmessage=async r=>{let{id:e,type:t,data:n}=r.data;try{let s=x[t];if(!s)throw new Error(`Unknown message type: ${t}`);let a=await s(n);self.postMessage({id:e,result:a})}catch(s){self.postMessage({id:e,error:s instanceof Error?s.message:\\\"Unknown error\\\"})}};})();\\n\";\n","/**\n * observability-worker-manager.ts\n * Manages the observability worker for processing events and API calls in the background\n */\n\nimport { FlushData } from '@/lib/audio/audio-aggregator';\nimport { TraceEvent } from '@/types/observability-types';\n\n// @ts-ignore - Generated file\nimport { observabilityWorkerCode } from './observability.worker.bundle';\n\nexport interface ObservabilityWorkerConfig {\n  auth?: {\n    token: string;\n    baseUrl?: string;\n  };\n  batchIntervalMs?: number;\n  batchSize?: number;\n  headers?: Record<string, string>;\n  metadata?: Record<string, any>;\n}\n\nexport class ObservabilityWorkerManager {\n  private worker: Worker | null = null;\n  private sessionId: string;\n  private messageId = 0;\n  private pendingMessages = new Map<\n    number,\n    { reject: Function; resolve: Function }\n  >();\n  private isInitialized = false;\n  private eventQueue: TraceEvent[] = [];\n  private flushTimer: number | null = null;\n\n  constructor(sessionId: string) {\n    this.sessionId = sessionId;\n  }\n\n  async init(config: ObservabilityWorkerConfig): Promise<void> {\n    if (this.isInitialized) {\n      console.warn('[ObservabilityWorkerManager] Already initialized');\n      return;\n    }\n\n    try {\n      // Create worker from bundled code using data URL\n      // Data URLs are not subject to CSP connect-src restrictions\n      const base64Code = btoa(observabilityWorkerCode);\n      const dataUrl = `data:application/javascript;base64,${base64Code}`;\n\n      console.log('[ObservabilityWorkerManager] Creating worker from data URL');\n      this.worker = new Worker(dataUrl);\n\n      this.worker.onmessage = this.handleWorkerMessage.bind(this);\n      this.worker.onerror = this.handleWorkerError.bind(this);\n\n      // Initialize worker with config\n      await this.sendMessage('init', {\n        sessionId: this.sessionId,\n        config: {\n          batchSize: config.batchSize || 50,\n          batchIntervalMs: config.batchIntervalMs || 5000,\n          headers: config.headers || {},\n          metadata: config.metadata || {},\n          auth: config.auth,\n        },\n      });\n\n      this.isInitialized = true;\n      console.log('[ObservabilityWorkerManager] Worker initialized');\n\n      // Start batch processing\n      this.startBatchProcessing(config.batchIntervalMs || 5000);\n    } catch (error) {\n      console.error(\n        '[ObservabilityWorkerManager] Failed to initialize worker:',\n        error\n      );\n      throw error;\n    }\n  }\n\n  async sendEvent(event: TraceEvent): Promise<void> {\n    if (!this.worker || !this.isInitialized) {\n      console.warn(\n        '[ObservabilityWorkerManager] Worker not initialized, queuing event'\n      );\n      this.eventQueue.push(event);\n      return;\n    }\n\n    // Send event to worker\n    await this.sendMessage('event', { event });\n  }\n\n  async sendAudioFlush(data: FlushData): Promise<void> {\n    if (!this.worker || !this.isInitialized) {\n      throw new Error('Worker not initialized');\n    }\n\n    // Convert ArrayBuffer to transferable\n    let pcmBuffer: ArrayBuffer;\n    if (data.pcmData instanceof ArrayBuffer) {\n      pcmBuffer = data.pcmData;\n    } else {\n      // Assume it's a typed array (Uint8Array, etc.)\n      const typedArray = data.pcmData as Uint8Array;\n      // Create a new ArrayBuffer with just the data we need\n      pcmBuffer = new ArrayBuffer(typedArray.byteLength);\n      new Uint8Array(pcmBuffer).set(typedArray);\n    }\n\n    // Send audio data to worker with transfer\n    await this.sendMessage(\n      'audioFlush',\n      {\n        conversationId: data.conversationId,\n        speaker: data.speaker,\n        sampleRate: data.sampleRate,\n        turnNumber: data.turnNumber,\n        startTime: data.startTime,\n        endTime: data.endTime,\n        totalBytesProcessed: data.totalBytesProcessed,\n        pcmData: pcmBuffer,\n      },\n      [pcmBuffer]\n    );\n  }\n\n  async flush(): Promise<void> {\n    if (!this.worker || !this.isInitialized) {\n      return;\n    }\n\n    // Force flush any pending events\n    await this.sendMessage('flush', {});\n  }\n\n  async destroy(): Promise<void> {\n    if (this.flushTimer) {\n      clearInterval(this.flushTimer);\n      this.flushTimer = null;\n    }\n\n    if (this.worker) {\n      // Send cleanup message\n      try {\n        await this.sendMessage('cleanup', {});\n      } catch (error) {\n        console.error(\n          '[ObservabilityWorkerManager] Error during cleanup:',\n          error\n        );\n      }\n\n      // Terminate worker\n      this.worker.terminate();\n      this.worker = null;\n      this.isInitialized = false;\n\n      // Clear pending messages\n      this.pendingMessages.forEach(({ reject }) => {\n        reject(new Error('Worker terminated'));\n      });\n      this.pendingMessages.clear();\n\n      console.log('[ObservabilityWorkerManager] Worker destroyed');\n    }\n  }\n\n  private startBatchProcessing(intervalMs: number): void {\n    if (this.flushTimer) {\n      clearInterval(this.flushTimer);\n    }\n\n    this.flushTimer = setInterval(() => {\n      this.flush().catch((error) => {\n        console.error(\n          '[ObservabilityWorkerManager] Error flushing events:',\n          error\n        );\n      });\n    }, intervalMs) as unknown as number;\n  }\n\n  private sendMessage(\n    type: string,\n    data: any,\n    transfer?: Transferable[]\n  ): Promise<any> {\n    return new Promise((resolve, reject) => {\n      if (!this.worker) {\n        reject(new Error('Worker not available'));\n        return;\n      }\n\n      const id = ++this.messageId;\n      this.pendingMessages.set(id, { resolve, reject });\n\n      const message = { id, type, data };\n\n      if (transfer) {\n        this.worker.postMessage(message, transfer);\n      } else {\n        this.worker.postMessage(message);\n      }\n\n      // Set timeout for response\n      setTimeout(() => {\n        if (this.pendingMessages.has(id)) {\n          this.pendingMessages.delete(id);\n          reject(new Error('Worker response timeout'));\n        }\n      }, 30000); // 30 second timeout\n    });\n  }\n\n  private handleWorkerMessage(event: MessageEvent): void {\n    const { id, error, result } = event.data;\n\n    const pending = this.pendingMessages.get(id);\n    if (pending) {\n      this.pendingMessages.delete(id);\n\n      if (error) {\n        pending.reject(new Error(error));\n      } else {\n        pending.resolve(result);\n      }\n    }\n\n    // Process queued events if worker just became ready\n    if (result?.ready && this.eventQueue.length > 0) {\n      console.log(\n        `[ObservabilityWorkerManager] Processing ${this.eventQueue.length} queued events`\n      );\n      const events = this.eventQueue.splice(0, this.eventQueue.length);\n      events.forEach((event) => {\n        this.sendEvent(event).catch((error) => {\n          console.error(\n            '[ObservabilityWorkerManager] Error sending queued event:',\n            error\n          );\n        });\n      });\n    }\n  }\n\n  private handleWorkerError(error: ErrorEvent): void {\n    console.error('[ObservabilityWorkerManager] Worker error:', error);\n\n    // Reject all pending messages\n    this.pendingMessages.forEach(({ reject }) => {\n      reject(new Error('Worker error: ' + error.message));\n    });\n    this.pendingMessages.clear();\n  }\n}\n","/* eslint-disable max-lines */\n/**\n * GenAI Client Wrapper\n * Wraps the GenAILiveClient to add observability tracking\n * Following the pattern used by LangSmith for OpenAI\n */\n\nimport {\n  LiveClientToolResponse,\n  LiveConnectConfig,\n  LiveServerToolCall,\n  Part,\n} from '@google/genai';\nimport { EventEmitter } from 'eventemitter3';\n\nimport { AudioAggregator } from '@/lib/audio/audio-aggregator';\nimport { ObservabilityManager } from '@/lib/core/observability-manager';\nimport { GenAILiveClient } from '@/lib/genai-live-client';\nimport { ObservabilityWorkerManager } from '@/lib/workers/observability-worker-manager';\nimport {\n  ObservabilityConfig,\n  SessionTrace,\n  TraceEvent,\n} from '@/types/observability-types';\nimport { arrayBufferToBase64, base64ToArrayBuffer } from '@/utils/audio-utils';\n\nexport interface WrapperOptions {\n  conversationId: string;\n  sessionId: string;\n  auth?: {\n    token: string;\n    baseUrl?: string;\n  };\n  observability?: ObservabilityConfig;\n}\n\nexport interface ObservableClient extends GenAILiveClient {\n  observability: {\n    events: EventEmitter<any>;\n    getTrace: () => SessionTrace;\n    manager: ObservabilityManager;\n    endSession: (\n      reason?: 'error' | 'timeout' | 'token_expired' | 'user_initiated'\n    ) => Promise<SessionTrace>;\n    exportAsJson: () => string;\n  };\n}\n\n// Global worker manager cache to prevent multiple instances\nconst workerManagerCache = new Map<string, ObservabilityWorkerManager>();\n\n/**\n * Wraps a GenAILiveClient instance to add observability\n */\nexport function wrapGenAIClient(\n  client: GenAILiveClient,\n  options: WrapperOptions\n): ObservableClient {\n  // Default config if not provided\n  const observabilityConfig: ObservabilityConfig = {\n    enabled: false,\n    logToConsole: false,\n    includeSystemPrompt: true,\n    includeAudioData: false,\n    includeImageData: false,\n    ...options.observability,\n  };\n\n  // CRITICAL: If observability is explicitly disabled, return unwrapped client immediately\n  // This prevents any observability tracking, audio aggregation, or callbacks from running\n  if (observabilityConfig.enabled === false) {\n    console.log(\n      '[Observability] Observability is disabled - returning unwrapped client'\n    );\n    return client as ObservableClient;\n  }\n\n  // Determine if we should use worker mode\n  const useWorker = observabilityConfig.useWorker === true;\n\n  // Create worker manager if using worker mode (with singleton pattern)\n  let workerManager: ObservabilityWorkerManager | undefined;\n\n  if (useWorker) {\n    const cacheKey = `${options.sessionId}`;\n\n    // Check if we already have a worker for this session\n    if (workerManagerCache.has(cacheKey)) {\n      workerManager = workerManagerCache.get(cacheKey);\n      console.log(\n        '[Observability] Reusing existing worker manager for:',\n        cacheKey\n      );\n    } else {\n      workerManager = new ObservabilityWorkerManager(options.sessionId);\n\n      // Cache the worker manager\n      workerManagerCache.set(cacheKey, workerManager);\n      console.log('[Observability] Created new worker manager for:', cacheKey);\n\n      // Initialize worker with config\n      const workerConfig = {\n        batchSize: observabilityConfig.workerConfig?.batchSize || 50,\n        batchIntervalMs:\n          observabilityConfig.workerConfig?.batchIntervalMs || 5000,\n        headers: {},\n        metadata: {\n          sessionId: options.sessionId,\n          conversationId: options.conversationId,\n          ...observabilityConfig.metadata,\n        },\n        auth: options.auth,\n      };\n\n      // Start initialization but don't wait for it\n      workerManager.init(workerConfig).catch((error) => {\n        console.error('[Observability] Failed to initialize worker:', error);\n      });\n    }\n  }\n\n  // Create audio aggregator only if both observability is enabled AND audioAggregation is configured\n  let audioAggregator: AudioAggregator | undefined;\n\n  if (\n    observabilityConfig.enabled &&\n    observabilityConfig.audioAggregation &&\n    (observabilityConfig.audioAggregation.onFlush || useWorker)\n  ) {\n    audioAggregator = new AudioAggregator({\n      flushIntervalMs:\n        observabilityConfig.audioAggregation.flushIntervalMs || 10000,\n      onFlush: async (data) => {\n        // If using worker, send to worker\n        if (useWorker && workerManager) {\n          await workerManager.sendAudioFlush(data);\n        }\n        // If callback provided, call it (for backward compatibility)\n        else if (observabilityConfig.audioAggregation?.onFlush) {\n          await observabilityConfig.audioAggregation.onFlush(data);\n        }\n      },\n    });\n  }\n\n  // Create observability manager\n  // When using worker, we still create the manager but override its trackEvent method\n  let manager: ObservabilityManager | null = null;\n\n  if (observabilityConfig.enabled) {\n    // Create manager with modified config if using worker\n    const managerConfig = useWorker\n      ? {\n          ...observabilityConfig,\n          // Replace callback with worker sender when using worker\n          callback: async (event: TraceEvent) => {\n            // Send to worker for API communication\n            await workerManager!.sendEvent(event);\n\n            // Also call original callback if provided (for custom processing)\n            if (observabilityConfig.callback) {\n              await observabilityConfig.callback(event);\n            }\n          },\n          // Keep logging if enabled\n          logToConsole: observabilityConfig.logToConsole,\n        }\n      : observabilityConfig;\n\n    manager = new ObservabilityManager(\n      options.sessionId,\n      options.conversationId,\n      managerConfig\n    );\n  }\n\n  // Track buffers for transcriptions\n  let inputTranscriptBuffer = '';\n  let outputTranscriptBuffer = '';\n  let turnNumber = 0;\n\n  // Wrap the connect method\n  const originalConnect = client.connect.bind(client);\n  client.connect = async function (\n    model: string,\n    config: LiveConnectConfig\n  ): Promise<boolean> {\n    // Track session start\n    if (manager) {\n      await manager.trackEvent({\n        type: 'session.start',\n        data: {\n          agentMode: 'user', // This should come from options if needed\n          isInternal: false,\n          model,\n        },\n      });\n\n      // Track configuration\n      await manager.trackEvent({\n        type: 'config.set',\n        data: {\n          config,\n          model,\n        },\n      });\n\n      // Track system prompt if present\n      if (config.systemInstruction) {\n        let promptText = '';\n        if (typeof config.systemInstruction === 'string') {\n          promptText = config.systemInstruction;\n        } else if (Array.isArray(config.systemInstruction)) {\n          promptText = config.systemInstruction\n            .map((p: any) => p.text || '')\n            .join('');\n        } else if ((config.systemInstruction as any).parts) {\n          promptText = (config.systemInstruction as any).parts\n            .map((p: any) => p.text || '')\n            .join('');\n        }\n\n        await manager.trackEvent({\n          type: 'system_prompt.set',\n          data: {\n            prompt: promptText,\n            source: 'custom',\n            characterCount: promptText.length,\n          },\n        });\n      }\n\n      // Track tool registrations\n      if (config.tools && config.tools.length > 0) {\n        for (const tool of config.tools) {\n          const functionDecl =\n            (tool as any).functionDeclarations?.[0] ||\n            (tool as any).function_declarations?.[0];\n          await manager.trackEvent({\n            type: 'tool.register',\n            data: {\n              toolName: functionDecl?.name || 'unknown',\n              description: functionDecl?.description,\n              parameters: functionDecl?.parameters,\n            },\n          });\n        }\n      }\n    }\n\n    // Call original method\n    const result = await originalConnect(model, config);\n    return result;\n  };\n\n  // Wrap the send method\n  const originalSend = client.send.bind(client);\n  client.send = function (parts: Part | Part[], turnComplete: boolean = true) {\n    const partsArray = Array.isArray(parts) ? parts : [parts];\n\n    if (manager) {\n      // Calculate character count for text parts\n      let characterCount = 0;\n      let messageType: 'mixed' | 'system' | 'text' = 'text';\n\n      for (const part of partsArray) {\n        if ('text' in part && part.text) {\n          characterCount += part.text.length;\n        }\n        if ('inlineData' in part) {\n          messageType = 'mixed';\n        }\n      }\n\n      // Track content send event\n      manager.trackEvent({\n        type: 'content.send',\n        data: {\n          parts: partsArray,\n          turnComplete,\n          messageType,\n          characterCount,\n        },\n      });\n    }\n\n    // Call original method\n    return originalSend(parts, turnComplete);\n  };\n\n  // Wrap sendRealtimeInput method\n  const originalSendRealtimeInput = client.sendRealtimeInput.bind(client);\n  client.sendRealtimeInput = function (\n    chunks: Array<{ data: string; mimeType: string }>\n  ) {\n    // Track audio/image sends\n    for (const chunk of chunks) {\n      if (chunk.mimeType.includes('audio')) {\n        const sizeBytes = Math.floor((chunk.data.length * 3) / 4); // Approximate base64 to bytes\n\n        // Add to audio aggregator if available\n        if (audioAggregator) {\n          const pcmData = base64ToArrayBuffer(chunk.data);\n          audioAggregator.addPCMChunk(\n            options.conversationId,\n            'user',\n            pcmData,\n            48000, // User audio is always 48kHz\n            turnNumber\n          );\n        }\n\n        // Track observability event if enabled\n        if (manager) {\n          manager.trackEvent({\n            type: 'audio.send',\n            data: {\n              sizeBytes,\n              mimeType: chunk.mimeType,\n              audioData: observabilityConfig.includeAudioData\n                ? chunk.data\n                : undefined,\n              turnNumber,\n            },\n          });\n        }\n      } else if (chunk.mimeType.includes('image') && manager) {\n        const sizeBytes = Math.floor((chunk.data.length * 3) / 4);\n        manager.trackEvent({\n          type: 'screen_capture.send',\n          data: {\n            method: 'screen', // Could be determined from context\n            width: 0, // Would need to extract from image data\n            height: 0,\n            sizeBytes,\n            mimeType: chunk.mimeType,\n            imageData: observabilityConfig.includeImageData\n              ? chunk.data\n              : undefined,\n          },\n        });\n      }\n    }\n\n    // Call original method\n    return originalSendRealtimeInput(chunks);\n  };\n\n  // Wrap sendToolResponse method\n  const originalSendToolResponse = client.sendToolResponse.bind(client);\n  client.sendToolResponse = function (toolResponse: LiveClientToolResponse) {\n    if (manager) {\n      // Track tool responses\n      if (toolResponse.functionResponses) {\n        for (const response of toolResponse.functionResponses) {\n          manager.trackEvent({\n            type: 'tool.response',\n            data: {\n              toolName: response.name || 'unknown',\n              functionCallId: response.id || 'unknown',\n              response: response.response,\n              success: true, // FunctionResponse doesn't have error property\n              error: undefined,\n            },\n          });\n        }\n      }\n    }\n\n    // Call original method\n    return originalSendToolResponse(toolResponse);\n  };\n\n  // Set up event listeners to track incoming events\n  const setupEventListeners = () => {\n    // Connection events\n    client.on('open', () => {\n      if (manager) {\n        manager.trackEvent({\n          type: 'connection.open',\n          data: {\n            model: options.conversationId, // We don't have access to model here\n            status: 'connected',\n          },\n        });\n      }\n    });\n\n    client.on('close', (e: CloseEvent) => {\n      if (manager) {\n        manager.trackEvent({\n          type: 'connection.close',\n          data: {\n            code: e.code,\n            reason: e.reason,\n            wasClean: e.wasClean,\n          },\n        });\n      }\n    });\n\n    client.on('error', (e: ErrorEvent) => {\n      if (manager) {\n        // Connection errors are already tracked as general errors\n        // but we can add a specific connection error event\n        manager.trackEvent({\n          type: 'connection.error',\n          data: {\n            error: e.error?.toString() || e.message,\n            message: e.message,\n          },\n        });\n      }\n    });\n\n    // Audio events\n    client.on('audio', (data: ArrayBuffer) => {\n      // Add to audio aggregator if available\n      if (audioAggregator) {\n        audioAggregator.addPCMChunk(\n          options.conversationId,\n          'agent',\n          data,\n          24000, // Agent audio is always 24kHz\n          turnNumber\n        );\n      }\n\n      // Track observability event if enabled\n      if (manager) {\n        manager.trackEvent({\n          type: 'audio.receive',\n          data: {\n            sizeBytes: data.byteLength,\n            mimeType: 'audio/pcm',\n            // Include audio data if configured\n            audioData: observabilityConfig.includeAudioData\n              ? arrayBufferToBase64(data)\n              : undefined,\n            turnNumber,\n          },\n        });\n      }\n    });\n\n    // Content events\n    if (manager) {\n      client.on('content', (content: any) => {\n        const hasAudio =\n          content.modelTurn?.parts?.some((p: any) =>\n            p.inlineData?.mimeType?.startsWith('audio/')\n          ) || false;\n\n        const hasText =\n          content.modelTurn?.parts?.some((p: any) => 'text' in p) || false;\n\n        let characterCount = 0;\n        if (hasText) {\n          content.modelTurn?.parts?.forEach((p: any) => {\n            if (p.text) characterCount += p.text.length;\n          });\n        }\n\n        manager.trackEvent({\n          type: 'content.receive',\n          data: {\n            content: content.modelTurn,\n            hasAudio,\n            hasText,\n            characterCount,\n          },\n        });\n      });\n\n      // Transcription events\n      client.on(\n        'inputtranscription',\n        (transcription: { finished: boolean; text: string }) => {\n          inputTranscriptBuffer = transcription.finished\n            ? ''\n            : transcription.text;\n\n          manager.trackEvent({\n            type: 'transcription.input',\n            data: {\n              text: transcription.text,\n              finished: transcription.finished,\n              characterCount: transcription.text.length,\n              turnNumber,\n            },\n          });\n        }\n      );\n\n      client.on(\n        'outputtranscription',\n        (transcription: { finished: boolean; text: string }) => {\n          outputTranscriptBuffer = transcription.finished\n            ? ''\n            : transcription.text;\n\n          manager.trackEvent({\n            type: 'transcription.output',\n            data: {\n              text: transcription.text,\n              finished: transcription.finished,\n              characterCount: transcription.text.length,\n              turnNumber,\n            },\n          });\n        }\n      );\n\n      // Tool call events\n      client.on('toolcall', (toolCall: LiveServerToolCall) => {\n        const toolName = toolCall.functionCalls?.[0]?.name || 'unknown';\n        const parameters = toolCall.functionCalls?.[0]?.args || {};\n\n        manager.trackEvent({\n          type: 'tool.call',\n          data: {\n            toolCall,\n            toolName,\n            parameters,\n          },\n        });\n      });\n\n      // Error events\n      client.on('error', (error: ErrorEvent) => {\n        manager.trackEvent({\n          type: 'error',\n          data: {\n            error: error.message,\n            stack: error.error?.stack,\n            context: 'websocket',\n            severity: 'error',\n          },\n        });\n      });\n    }\n\n    // Turn complete events (track regardless of manager for turn number)\n    client.on('turncomplete', () => {\n      turnNumber++;\n\n      // Update turn number in audio aggregator\n      if (audioAggregator) {\n        audioAggregator.updateTurnNumber(options.conversationId, turnNumber);\n      }\n\n      // Track event if manager enabled\n      if (manager) {\n        manager.trackEvent({\n          type: 'turn.complete',\n          data: {\n            userMessage: inputTranscriptBuffer,\n            agentMessage: outputTranscriptBuffer,\n            turnNumber,\n            toolsUsed: [], // Would need to track this\n          },\n        });\n      }\n    });\n  };\n\n  // Set up listeners\n  setupEventListeners();\n\n  // Add observability property to client\n  const observableClient = client as ObservableClient;\n\n  if (manager) {\n    observableClient.observability = {\n      manager,\n      events: manager.getEventEmitter(),\n      getTrace: () => manager.getTrace(),\n      exportAsJson: () => manager.exportAsJson(),\n      endSession: async (\n        reason?: 'error' | 'timeout' | 'token_expired' | 'user_initiated'\n      ) => {\n        // End audio aggregator if present\n        if (audioAggregator) {\n          await audioAggregator.endConversation(options.conversationId);\n        }\n\n        // Flush and destroy worker if present\n        if (workerManager) {\n          try {\n            await workerManager.flush();\n          } catch (error) {\n            console.error('[Observability] Error flushing worker:', error);\n          }\n          await workerManager.destroy();\n\n          // Remove from cache\n          const cacheKey = `${options.sessionId}-${options.conversationId}`;\n          workerManagerCache.delete(cacheKey);\n          console.log(\n            '[Observability] Removed worker manager from cache:',\n            cacheKey\n          );\n        }\n\n        return manager.endSession(reason || 'user_initiated');\n      },\n    };\n  }\n\n  return observableClient;\n}\n\n/**\n * Helper to serialize a single event\n */\nexport function serializeEvent(event: TraceEvent): string {\n  return JSON.stringify(\n    event,\n    (key, value) => {\n      if (value instanceof Date) {\n        return value.toISOString();\n      }\n      if (value === undefined) {\n        return null;\n      }\n      if (typeof value === 'function') {\n        return '[Function]';\n      }\n      return value;\n    },\n    2\n  );\n}\n\n/**\n * Helper to serialize multiple events\n */\nexport function serializeEvents(events: TraceEvent[]): string {\n  return JSON.stringify(\n    events,\n    (key, value) => {\n      if (value instanceof Date) {\n        return value.toISOString();\n      }\n      if (value === undefined) {\n        return null;\n      }\n      if (typeof value === 'function') {\n        return '[Function]';\n      }\n      return value;\n    },\n    2\n  );\n}\n","/**\n * Get Context Tool\n *\n * Retrieves the current context from the centralized context state manager.\n * This follows the workaround pattern for context injection where the model\n * naturally calls this tool when it needs contextual information.\n *\n * Important: This tool does NOT make any API calls. It simply formats and\n * returns the context that has already been gathered and stored in state.\n */\n\nimport { FunctionResponseScheduling, Type } from '@google/genai';\n\nimport { ContextStateManager } from '../../core/context-state-manager';\nimport {\n  BaseToolEventMap,\n  ToolCategories,\n  ToolDefinition,\n  ToolName,\n} from '../types';\n\n// Tool-specific event types\nexport interface GetContextToolEvents extends BaseToolEventMap {\n  'tool:getContext:called': (data: {\n    query: string;\n    timestamp: string;\n  }) => void;\n  'tool:getContext:success': (data: {\n    hasPageData: boolean;\n    contextLength: number;\n    hasMemories: boolean;\n  }) => void;\n  'tool:getContext:error': (data: { error: Error }) => void;\n}\n\n// Factory function to create the tool with context state manager\nexport const createGetContextTool = (\n  contextStateManager: ContextStateManager\n): ToolDefinition<GetContextToolEvents> => {\n  return {\n    declaration: {\n      name: ToolName.GET_CONTEXT,\n      description:\n        'Retrieves stored contextual information about the user, current page, memories, and preferences. Call this when you need information about the user or their context to provide personalized assistance.',\n      // NOTE: No NON_BLOCKING behavior - make it synchronous so model waits for response\n      parameters: {\n        type: Type.OBJECT,\n        properties: {\n          query: {\n            type: Type.STRING,\n            description:\n              'What contextual information you need (e.g., \"user information\", \"current page\", \"everything\", \"memories about X\")',\n          },\n        },\n        required: ['query'],\n      },\n    },\n    category: ToolCategories.CONTEXT,\n    handler: async (fc, context) => {\n      const args = typeof fc.args === 'string' ? JSON.parse(fc.args) : fc.args;\n\n      try {\n        console.log('🔍 [GET-CONTEXT-TOOL] Called with query:', args.query);\n\n        // Emit tool called event\n        context.emit('tool:getContext:called', {\n          query: args.query,\n          timestamp: new Date().toISOString(),\n        });\n\n        // Check if we have any context\n        if (!contextStateManager.hasContext()) {\n          console.log('🔍 [GET-CONTEXT-TOOL] No context available yet');\n          context.emit('tool:getContext:success', {\n            contextLength: 0,\n            hasMemories: false,\n            hasPageData: false,\n          });\n\n          return {\n            id: fc.id,\n            name: fc.name,\n            response: {\n              output:\n                '[CONTEXT RETRIEVED]\\n\\nNo contextual information is currently available. Context will be gathered as the conversation progresses.',\n              // Use INTERRUPT scheduling to force immediate processing (from workaround pattern)\n              scheduling: FunctionResponseScheduling.INTERRUPT,\n            },\n          };\n        }\n\n        // Check for specific query types\n        const queryLower = args.query.toLowerCase();\n        let formattedContext: string;\n\n        if (\n          queryLower.includes('current page') ||\n          queryLower.includes('page context') ||\n          queryLower === 'page'\n        ) {\n          // Return lightweight page context only\n          console.log(\n            '🔍 [GET-CONTEXT-TOOL] Returning lightweight page context'\n          );\n          formattedContext = contextStateManager.formatPageContextOnly({\n            includeTimestamp: true,\n            customContext: 'Current page information',\n            additionalInfo: {\n              'Context Type': 'Page Only',\n              Request: args.query,\n            },\n          });\n        } else {\n          // Return full context with memories\n          formattedContext = contextStateManager.formatForToolResponse();\n        }\n\n        // Get state info for event\n        const state = contextStateManager.getFullState();\n\n        // Emit success event\n        context.emit('tool:getContext:success', {\n          contextLength: formattedContext.length,\n          hasMemories: state.memories.length > 0,\n          hasPageData: state.pageMetadata !== null,\n        });\n\n        console.log('🔍 [GET-CONTEXT-TOOL] Returning context:', {\n          memories: state.memories.length,\n          hasPageData: !!state.pageMetadata,\n          hasUserPrefs: !!state.userPreferences,\n          contextLength: formattedContext.length,\n          queryType: queryLower.includes('current page') ? 'page-only' : 'full',\n        });\n\n        // Return formatted context\n        // Following the workaround pattern - use INTERRUPT scheduling\n        return {\n          id: fc.id,\n          name: fc.name,\n          response: {\n            output: formattedContext,\n            scheduling: FunctionResponseScheduling.INTERRUPT,\n          },\n        };\n      } catch (error) {\n        console.error('🔍 [GET-CONTEXT-TOOL] Error retrieving context:', error);\n\n        context.emit('tool:getContext:error', {\n          error: error instanceof Error ? error : new Error('Unknown error'),\n        });\n\n        return {\n          id: fc.id,\n          name: fc.name,\n          response: {\n            output:\n              '[CONTEXT ERROR]\\n\\nUnable to retrieve context at this time. Please continue without contextual information.',\n            scheduling: FunctionResponseScheduling.INTERRUPT,\n          },\n        };\n      }\n    },\n  };\n};\n","import {\n  FunctionCall,\n  FunctionDeclaration,\n  FunctionResponse,\n} from '@google/genai';\nimport { EventEmitter } from 'eventemitter3';\n\nimport type { AgentSession } from '@/types/agent-types';\nimport type {\n  ConversationService,\n  CoreService,\n  MemoryService,\n} from '../sammy-api';\n\nexport enum ToolName {\n  // Session management\n  END_SESSION = 'endSession',\n  GET_CONTEXT = 'getContext',\n  ESCALATE = 'escalate',\n}\n\n// Base tool event map - contains the core/default tool events\nexport interface BaseToolEventMap\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  extends Record<string, (...args: any[]) => any> {\n  [ToolName.END_SESSION]: () => void;\n  [ToolName.ESCALATE]: (data: {\n    reason: string;\n    context?: string;\n    apiResponse?: any;\n    error?: string;\n  }) => void;\n}\n\n// Generic tool event map that can be extended with custom events\nexport interface ToolEventMap<_TCustomEvents = {}> extends BaseToolEventMap {\n  // This allows extending with custom events: ToolEventMap<{ 'custom:event': (data: any) => void }>\n}\n\n// Helper type to merge base and custom events\nexport type ExtendedToolEventMap<TCustomEvents = {}> = BaseToolEventMap &\n  TCustomEvents;\n\n// Type-safe event emitter interface\nexport interface TypedEventEmitter<\n  TEventMap extends Record<string, (...args: unknown[]) => unknown>,\n> {\n  emit<K extends keyof TEventMap>(\n    event: K,\n    ...args: Parameters<TEventMap[K]>\n  ): boolean;\n  off<K extends keyof TEventMap>(event: K, listener: TEventMap[K]): this;\n  on<K extends keyof TEventMap>(event: K, listener: TEventMap[K]): this;\n}\n\n// Create a typed event emitter wrapper\nexport function createTypedEventEmitter<\n  TEventMap extends Record<string, (...args: unknown[]) => unknown>,\n>(): TypedEventEmitter<TEventMap> {\n  const emitter = new EventEmitter();\n\n  return {\n    on<K extends keyof TEventMap>(\n      event: K,\n      listener: TEventMap[K]\n    ): TypedEventEmitter<TEventMap> {\n      emitter.on(event as string, listener);\n      return this;\n    },\n    off<K extends keyof TEventMap>(\n      event: K,\n      listener: TEventMap[K]\n    ): TypedEventEmitter<TEventMap> {\n      emitter.off(event as string, listener);\n      return this;\n    },\n    emit<K extends keyof TEventMap>(\n      event: K,\n      ...args: Parameters<TEventMap[K]>\n    ): boolean {\n      return emitter.emit(event as string, ...args);\n    },\n  };\n}\n\nexport type TypedFunctionDeclaration = Omit<FunctionDeclaration, 'name'> & {\n  name: ToolName;\n};\n\n// Tool categories - loosely defined with common patterns\nexport type ToolCategory = 'action' | 'context';\n\n// Common tool categories (optional guidance)\nexport const ToolCategories = {\n  ACTION: 'action' as const,\n  CONTEXT: 'context' as const,\n  // Users can define their own categories as needed\n} as const;\n\n// Services interface for tool context\nexport interface ToolServices {\n  conversationServices: ConversationService;\n  coreServices: CoreService;\n  memoryServices: MemoryService;\n}\n\n// Context passed to tool handlers - now generic\nexport interface ToolContext<\n  TEventMap extends Record<\n    string,\n    (...args: unknown[]) => unknown\n  > = BaseToolEventMap,\n> {\n  emit: <K extends keyof TEventMap>(\n    event: K,\n    ...args: Parameters<TEventMap[K]>\n  ) => void;\n  getState: () => AgentSession; // Access to agent session state\n  services: ToolServices; // Access to service layer\n}\n\n// Tool definition with metadata - now generic\nexport interface ToolDefinition<\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  TEventMap extends Record<string, (...args: any[]) => any> = BaseToolEventMap,\n> {\n  handler: (\n    fc: FunctionCall,\n    context: ToolContext<TEventMap>\n  ) => Promise<FunctionResponse>;\n  category: ToolCategory;\n  declaration: TypedFunctionDeclaration;\n}\n","import {\n  FunctionCall,\n  FunctionResponse,\n  FunctionResponseScheduling,\n} from '@google/genai';\n\nexport const missingToolNameError = (fc: FunctionCall): FunctionResponse => {\n  console.warn(`[Tools] Function call has no name:`, fc);\n\n  return {\n    id: fc.id,\n    name: fc.name || 'unknown',\n    response: {\n      output: {\n        error: 'Function call has no name',\n        success: false,\n      },\n      scheduling: FunctionResponseScheduling.SILENT,\n    },\n  };\n};\n\nexport const missingToolHandlerError = (fc: FunctionCall): FunctionResponse => {\n  console.warn(`[Tools] No handler found for tool: ${fc.name}`);\n\n  return {\n    id: fc.id,\n    name: fc.name,\n    response: {\n      output: {\n        error: `Tool '${fc.name}' not found`,\n        success: false,\n      },\n      scheduling: FunctionResponseScheduling.SILENT,\n    },\n  };\n};\n\nexport const toolError = (\n  fc: FunctionCall,\n  error: unknown\n): FunctionResponse => {\n  console.error(`[Tools] Error in ${fc.name}:`, error);\n\n  return {\n    id: fc.id,\n    name: fc.name,\n    response: {\n      output: { error, success: false },\n      scheduling: FunctionResponseScheduling.SILENT,\n    },\n  };\n};\n","/**\n * Tool Definitions using the new registry system\n * All tools are defined in a centralized, type-safe manner\n */\n\nimport { Behavior, Type } from '@google/genai';\nimport { FunctionResponseScheduling } from '@google/genai';\n\nimport { BaseToolEventMap, ToolCategories, ToolDefinition } from '../types';\nimport { ToolName } from '../types';\n\n/**\n * End Session Tool\n */\nexport const endSessionTool: ToolDefinition<BaseToolEventMap> = {\n  declaration: {\n    name: ToolName.END_SESSION,\n    description: `Ends the agent session when user indicates they're finished...`,\n    behavior: Behavior.NON_BLOCKING,\n    parameters: {\n      type: Type.OBJECT,\n      properties: {\n        reason: {\n          type: Type.STRING,\n          description: 'Brief reason why the session is ending',\n        },\n        confirmed: {\n          type: Type.BOOLEAN,\n          description: 'Whether the user has confirmed they want to end',\n        },\n      },\n      required: ['reason', 'confirmed'],\n    },\n  },\n  category: ToolCategories.ACTION,\n  handler: async (fc, context) => {\n    const args = typeof fc.args === 'string' ? JSON.parse(fc.args) : fc.args;\n    const reason = args.reason || 'user requested';\n    const confirmed = args.confirmed || false;\n\n    if (confirmed) {\n      // Emit event through the context - type-safe!\n      context.emit(ToolName.END_SESSION);\n\n      return {\n        id: fc.id,\n        name: fc.name,\n        response: {\n          output: {\n            success: true,\n            message: 'Session ending after confirmation',\n            reason,\n            confirmed: true,\n          },\n          scheduling: FunctionResponseScheduling.WHEN_IDLE,\n        },\n      };\n    } else {\n      return {\n        id: fc.id,\n        name: fc.name,\n        response: {\n          output: {\n            success: true,\n            message: 'Confirmation required before ending session',\n            reason,\n            confirmed: false,\n            action: 'awaiting_confirmation',\n          },\n          scheduling: FunctionResponseScheduling.WHEN_IDLE,\n        },\n      };\n    }\n  },\n};\n","/**\n * Escalate Tool\n *\n * Escalates a complex issue or request to human support when the AI cannot\n * adequately help the user. Only call this when specialized assistance\n * beyond AI capabilities is needed.\n */\n\nimport { Behavior, Type } from '@google/genai';\nimport { FunctionResponseScheduling } from '@google/genai';\n\nimport { BaseToolEventMap, ToolCategories, ToolDefinition } from '../types';\nimport { ToolName } from '../types';\n\n/**\n * Escalate Tool\n */\nexport const escalateTool: ToolDefinition<BaseToolEventMap> = {\n  declaration: {\n    name: ToolName.ESCALATE,\n    description:\n      'Escalate a complex issue or request to a human support agent when you cannot adequately help the user. Use this when the user needs specialized assistance beyond your capabilities. ONLY call this ONCE per call - do not repeat the escalation.',\n    behavior: Behavior.NON_BLOCKING,\n    parameters: {\n      type: Type.OBJECT,\n      properties: {\n        reason: {\n          type: Type.STRING,\n          description:\n            'Brief explanation of why you are escalating this issue (e.g., \"Complex billing issue requiring manual review\", \"Technical problem outside training scope\")',\n        },\n        context: {\n          type: Type.STRING,\n          description:\n            \"Relevant context about the user's current situation, what they were trying to accomplish, and any relevant details that would help the human agent\",\n        },\n      },\n      required: ['reason'],\n    },\n  },\n  category: ToolCategories.ACTION,\n  handler: async (fc, context) => {\n    const args = typeof fc.args === 'string' ? JSON.parse(fc.args) : fc.args;\n    const reason = args.reason || 'User requested escalation';\n    const userContext = args.context || 'No additional context provided';\n\n    console.log('🚨 [ESCALATE-TOOL] Escalation requested:', {\n      reason,\n      context: userContext,\n      timestamp: new Date().toISOString(),\n    });\n\n    try {\n      // Get the conversation ID from agent session\n      const agentSession = context.getState();\n      const conversationId = agentSession.conversationId;\n\n      console.log('🚨 [ESCALATE-TOOL] Using conversation ID:', conversationId);\n\n      // Make API call to escalate conversation\n      const escalationResponse =\n        await context.services.conversationServices.escalateConversation({\n          conversation_id: conversationId,\n        });\n\n      console.log('🚨 [ESCALATE-TOOL] API response:', escalationResponse);\n\n      // Emit escalation event with API response\n      context.emit(ToolName.ESCALATE, {\n        reason,\n        context: userContext,\n        apiResponse: escalationResponse,\n      });\n\n      return {\n        id: fc.id,\n        name: fc.name,\n        response: {\n          output: {\n            success: escalationResponse.success,\n            message:\n              escalationResponse.message || 'Escalation request processed',\n            reason,\n            context: userContext,\n            status: escalationResponse.success ? 'escalated' : 'failed',\n            conversationId: escalationResponse.conversation_id,\n            timestamp: new Date().toISOString(),\n          },\n          scheduling: FunctionResponseScheduling.WHEN_IDLE,\n        },\n      };\n    } catch (error) {\n      console.error('🚨 [ESCALATE-TOOL] Error during escalation:', error);\n\n      // Emit escalation event with error\n      context.emit(ToolName.ESCALATE, {\n        reason,\n        context: userContext,\n        error: error instanceof Error ? error.message : 'Unknown error',\n      });\n\n      return {\n        id: fc.id,\n        name: fc.name,\n        response: {\n          output: {\n            success: false,\n            message: 'Escalation request failed due to technical error',\n            reason,\n            context: userContext,\n            status: 'error',\n            error: error instanceof Error ? error.message : 'Unknown error',\n            timestamp: new Date().toISOString(),\n          },\n          scheduling: FunctionResponseScheduling.WHEN_IDLE,\n        },\n      };\n    }\n  },\n};\n","import { BaseToolEventMap, ToolDefinition } from '../types';\nimport { endSessionTool } from './end-session';\nimport { escalateTool } from './escalate';\n\nexport const DefaultToolDefinitions: ToolDefinition<BaseToolEventMap>[] = [\n  endSessionTool,\n  escalateTool,\n];\n\nexport { endSessionTool, escalateTool };\n","/**\n * Tool Registry System\n * Encapsulates tool registration, handling, and event management\n * Provides type-safe tool registration with automatic event handling\n */\n\nimport {\n  FunctionCall,\n  FunctionResponse,\n  LiveServerToolCall,\n} from '@google/genai';\n\nimport {\n  missingToolHandlerError,\n  missingToolNameError,\n  toolError,\n} from '@/utils/tool-utils';\n\nimport { DefaultToolDefinitions } from './definitions';\nimport { AgentSession } from '@/types/agent-types';\n\nimport {\n  BaseToolEventMap,\n  createTypedEventEmitter,\n  ToolCategory,\n  ToolContext,\n  ToolDefinition,\n  ToolServices,\n  TypedEventEmitter,\n  TypedFunctionDeclaration,\n} from './types';\n\nexport class ToolManager<\n  TEventMap extends Record<\n    string,\n    (...args: unknown[]) => unknown\n  > = BaseToolEventMap,\n> {\n  public eventEmitter: TypedEventEmitter<TEventMap>;\n  private tools = new Map<string, ToolDefinition<TEventMap>>();\n  private services: ToolServices;\n\n  constructor(tools: ToolDefinition<TEventMap>[] = [], services: ToolServices) {\n    this.eventEmitter = createTypedEventEmitter<TEventMap>();\n    this.services = services;\n    this.registerAll(tools);\n  }\n\n  /**\n   * Register a tool with the registry\n   */\n  register(tool: ToolDefinition<TEventMap>): void {\n    this.tools.set(tool.declaration.name, tool);\n  }\n\n  /**\n   * Register multiple tools at once, including the default tools\n   */\n  registerAll(tools: ToolDefinition<TEventMap>[]): void {\n    DefaultToolDefinitions.forEach((tool) =>\n      this.register(tool as ToolDefinition<TEventMap>)\n    );\n    tools.forEach((tool) => this.register(tool));\n  }\n\n  /**\n   * Get all tool declarations for agent config\n   */\n  getAllDeclarations(): TypedFunctionDeclaration[] {\n    return Array.from(this.tools.values()).map((tool) => tool.declaration);\n  }\n\n  /**\n   * Handle a function call\n   */\n  async handleFunctionCall(\n    fc: FunctionCall,\n    agentState: AgentSession\n  ): Promise<FunctionResponse> {\n    if (!fc.name) return missingToolNameError(fc);\n\n    const tool = this.tools.get(fc.name);\n\n    if (!tool) return missingToolHandlerError(fc);\n\n    // Create context for the tool\n    const context: ToolContext<TEventMap> = {\n      emit: <K extends keyof TEventMap>(\n        event: K,\n        ...args: Parameters<TEventMap[K]>\n      ) => {\n        this.eventEmitter.emit(event, ...args);\n      },\n      getState: () => agentState,\n      services: this.services,\n    };\n\n    try {\n      return await tool.handler(fc, context);\n    } catch (error) {\n      return toolError(fc, error);\n    }\n  }\n\n  /**\n   * Handle multiple tool calls (class-based approach)\n   */\n  async handleToolCall(\n    toolCall: LiveServerToolCall,\n    agentState: AgentSession\n  ): Promise<FunctionResponse[]> {\n    const responses: FunctionResponse[] = [];\n\n    if (!toolCall.functionCalls) return responses;\n\n    // Process all function calls in parallel\n    const functionResponses = await Promise.all(\n      toolCall.functionCalls.map(async (fc) => {\n        return await this.handleFunctionCall(fc, agentState);\n      })\n    );\n\n    responses.push(...functionResponses);\n    return responses;\n  }\n\n  /**\n   * Subscribe to tool events\n   */\n  on<K extends keyof TEventMap>(event: K, listener: TEventMap[K]): void {\n    this.eventEmitter.on(event, listener);\n  }\n\n  /**\n   * Unsubscribe from tool events\n   */\n  off<K extends keyof TEventMap>(event: K, listener: TEventMap[K]): void {\n    this.eventEmitter.off(event, listener);\n  }\n\n  /**\n   * Get tools by category\n   */\n  getToolsByCategory(category: ToolCategory): ToolDefinition<TEventMap>[] {\n    return Array.from(this.tools.values()).filter(\n      (tool) => tool.category === category\n    );\n  }\n}\n","'use client';\n\nimport {\n  ComponentType,\n  createContext,\n  ReactNode,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useRef,\n} from 'react';\n\nimport { LiveServerToolCall } from '@google/genai';\n\nimport { useAgentState } from '@/hooks/use-agent-state';\nimport { GuidesState, useGuidesManager } from '@/hooks/use-guides-manager';\nimport { useScreenCapture } from '@/hooks/use-screen-capture';\nimport { SammyAgentCore, StartOptions } from '@/lib/core/sammy-agent-core';\nimport { ToolDefinition, ToolName } from '@/lib/tools/types';\nimport { useToolEvents } from '@/lib/tools/use-tool-events';\nimport {\n  AgentMode,\n  AgentSession,\n  SammyAgentConfig,\n  SammyAgentConfigProps,\n} from '@/types/agent-types';\nimport { ScreenCapture } from '@/types/media-types';\n\ninterface SammyAgentContextType {\n  // Guides (null when disabled, full state when enabled)\n  guides: GuidesState | null;\n  // State\n  activeSession: AgentSession | null;\n  agentStatus: 'connected' | 'connecting' | 'disconnected' | 'disconnecting';\n  agentVolume: number;\n  config: SammyAgentConfig;\n  error: Error | null;\n  muted: boolean;\n  screenCapture: Omit<ScreenCapture, 'startStreaming' | 'stopStreaming'>;\n\n  // Core methods\n  sendMessage: (message: string) => void;\n  startAgent: (options: StartOptions) => Promise<boolean>;\n  stopAgent: () => void;\n  toggleMuted: () => void;\n\n  userVolume: number;\n}\n\nconst SammyAgentContext = createContext<SammyAgentContextType | undefined>(\n  undefined\n);\n\nexport interface SammyAgentProviderProps {\n  children: ReactNode;\n  config: SammyAgentConfigProps;\n  guides?: boolean;\n  guidesDebug?: boolean; // Enable debug logging for guides\n  // New props for guides configuration\n  guidesQueryParam?: string; // Custom query parameter name (default: 'walkthrough')\n  autoStartFromURL?: boolean;\n  onConnectionStateChange?: (connected: boolean) => void;\n  onError?: (error: unknown) => void;\n  onMemoryUpdate?: (result: unknown) => void;\n  onTokenExpired?: () => void;\n  onToolCall?: (tool: LiveServerToolCall) => void;\n  onTurnComplete?: (summary: { agent: string; user: string }) => void;\n  onWalkthroughStart?: (guideId: string) => void;\n  tools?: ToolDefinition[];\n}\n\nexport const SammyAgentProvider: ComponentType<SammyAgentProviderProps> = ({\n  children,\n  config = {},\n  tools = [],\n  guides = false,\n  autoStartFromURL = false,\n  onWalkthroughStart,\n  guidesQueryParam = 'walkthrough',\n  guidesDebug = false,\n  onTokenExpired,\n  onError,\n  onTurnComplete,\n  onMemoryUpdate,\n  onToolCall,\n  onConnectionStateChange,\n}) => {\n  const { agentState, updateAgentState, agentCoreRef } = useAgentState();\n  const {\n    error,\n    activeSession,\n    agentVolume,\n    userVolume,\n    isConnected,\n    isConnecting,\n    isStreaming,\n  } = agentState;\n\n  // Use a ref to store the guides state to avoid circular dependency\n  const guidesStateRef = useRef<GuidesState | null>(null);\n\n  // Screen capture settings\n  const { startStreaming, stopStreaming, ...screenCapture } = useScreenCapture({\n    config: {\n      method: config.captureMethod || 'render',\n      debugLogs: config.debugLogs || false,\n    },\n  });\n\n  // Merged config with defaults\n  const finalConfig: SammyAgentConfig = useMemo(\n    () => {\n      const mergedConfig = {\n        captureMethod: config.captureMethod || 'render',\n        debugLogs: config.debugLogs || false,\n        toolUse: true,\n        ...config,\n        auth: config.auth!, // Non-null assertion since auth is required in props\n        screenCaptureCallbacks: {\n          startStreaming,\n          stopStreaming,\n        },\n      };\n      // Log configuration only when debug logging is enabled to avoid excessive console noise.\n      if (mergedConfig.debugLogs) {\n        console.log('🔧 [SCREEN-CAPTURE] finalConfig:', mergedConfig);\n      }\n      return mergedConfig;\n    },\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n    []\n  );\n\n  // Notify connection state changes\n  useEffect(() => {\n    onConnectionStateChange?.(isConnected);\n  }, [isConnected, onConnectionStateChange]);\n\n  // Handle errors\n  const handleError = useCallback(\n    (err: unknown) => {\n      const error = err instanceof Error ? err : new Error(String(err));\n      updateAgentState('error', error);\n      onError?.(error);\n    },\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n    [onError]\n  );\n\n  // Start the agent\n  const startAgent = useCallback(\n    async (options: StartOptions) => {\n      try {\n        console.log('Starting agent', options);\n        updateAgentState('isConnecting', true);\n        updateAgentState('error', null);\n\n        // Clean up any existing instance\n        if (agentCoreRef.current) {\n          console.log('Destroying existing agent');\n          await agentCoreRef.current.destroy();\n        }\n\n        // Create new agent core instance\n        agentCoreRef.current = new SammyAgentCore({\n          config: finalConfig,\n          tools,\n          callbacks: {\n            onTokenExpired,\n            onError: handleError,\n            onTurnComplete,\n            onMemoryUpdate,\n            onToolCall,\n            onConnectionStateChange: (connected) => {\n              updateAgentState('isConnected', connected);\n              updateAgentState('isConnecting', false);\n            },\n          },\n          updateAgentState,\n        });\n        console.log('Created new agent core instance');\n\n        // Start the agent\n        const session = await agentCoreRef.current.start({\n          agentMode: options.agentMode || AgentMode.USER,\n          sammyThreeOrganisationFeatureId:\n            options.sammyThreeOrganisationFeatureId,\n          guideId: options.guideId,\n        });\n\n        console.log('Started agent', session);\n        updateAgentState('activeSession', session);\n\n        // Mark guide as completed when conversation starts successfully (if guides enabled)\n        if (options.guideId && guidesStateRef.current?.markGuideAsCompleted) {\n          guidesStateRef.current.markGuideAsCompleted(options.guideId);\n        }\n\n        return true;\n      } catch (err) {\n        handleError(err);\n        updateAgentState('isConnecting', false);\n        return false;\n      }\n    },\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n    [\n      finalConfig,\n      onTokenExpired,\n      handleError,\n      onTurnComplete,\n      onMemoryUpdate,\n      onToolCall,\n    ]\n  );\n\n  // Use the guides manager hook\n  const guidesState = useGuidesManager({\n    authConfig: config.auth,\n    autoStartFromURL,\n    onStartAgent: startAgent,\n    onWalkthroughStart,\n    queryParamName: guidesQueryParam,\n    debug: guidesDebug,\n    enabled: guides,\n  });\n\n  // Keep the ref updated with the latest guides state\n  useEffect(() => {\n    guidesStateRef.current = guidesState;\n  }, [guidesState]);\n\n  // Stop the agent\n  const stopAgent = useCallback(async () => {\n    if (agentCoreRef.current) {\n      await agentCoreRef.current.destroy();\n      agentCoreRef.current = null;\n    }\n    updateAgentState('isConnected', false);\n    updateAgentState('isConnecting', false);\n    updateAgentState('activeSession', null);\n    updateAgentState('error', null);\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, []);\n\n  // Send message\n  const sendMessage = useCallback((message: string) => {\n    if (!agentCoreRef.current) {\n      console.warn('Cannot send message: agent not started');\n      return;\n    }\n    agentCoreRef.current.sendMessage(message);\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, []);\n\n  // Set muted state\n  const toggleMuted = useCallback(() => {\n    if (!agentCoreRef.current) {\n      console.warn('Cannot set muted state: agent not started');\n      return;\n    }\n    agentCoreRef.current.setMuted(!agentCoreRef.current.muted);\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, []);\n\n  const agentStatus = useMemo(() => {\n    if (isConnected && isStreaming) {\n      return 'connected';\n    } else if (isConnecting) {\n      return 'connecting';\n    } else if (isConnected && !isStreaming) {\n      return 'disconnecting';\n    } else {\n      return 'disconnected';\n    }\n  }, [isConnected, isConnecting, isStreaming]);\n\n  // Cleanup on unmount\n  useEffect(() => {\n    return () => {\n      if (agentCoreRef.current) {\n        agentCoreRef.current.destroy();\n      }\n    };\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, []);\n\n  useToolEvents({\n    agentCore: agentCoreRef.current,\n    toolCallbacks: {\n      [ToolName.END_SESSION]: () => {\n        stopAgent();\n      },\n      [ToolName.ESCALATE]: (data: {\n        reason: string;\n        apiResponse?: any;\n        context?: string;\n        error?: string;\n      }) => {\n        console.log('🚨 [ESCALATION-HANDLER] Processing escalation:', data);\n\n        if (data.apiResponse) {\n          console.log(\n            '🚨 [ESCALATION-HANDLER] API Response:',\n            data.apiResponse\n          );\n        }\n\n        if (data.error) {\n          console.error(\n            '🚨 [ESCALATION-HANDLER] Escalation Error:',\n            data.error\n          );\n        }\n\n        // TODO: Add your escalation handling logic here:\n        // - Show UI notification to user\n        // - Log to analytics/monitoring\n        // - Handle API response/errors\n        // - Update UI state to show escalation status\n        // - etc.\n      },\n    },\n  });\n\n  const contextValue: SammyAgentContextType = {\n    error,\n    activeSession,\n    agentStatus,\n    startAgent,\n    config: finalConfig,\n    stopAgent,\n    sendMessage,\n    toggleMuted,\n    agentVolume,\n    userVolume,\n    screenCapture,\n    muted: agentCoreRef.current?.muted || false,\n    guides: guidesState, // Will be null when guides are disabled\n  };\n\n  return (\n    <SammyAgentContext.Provider value={contextValue}>\n      {children}\n    </SammyAgentContext.Provider>\n  );\n};\n\nexport const useSammyAgentContext = () => {\n  const context = useContext(SammyAgentContext);\n  if (!context) {\n    throw new Error(\n      'useSammyAgentContext must be used within SammyAgentProvider'\n    );\n  }\n  return context;\n};\n","import { useRef, useState } from 'react';\n\nimport { SammyAgentCore } from '@/lib/core/sammy-agent-core';\nimport { AgentState, AgentStateContextType } from '@/types/agent-types';\n\nexport const useAgentState = (): AgentStateContextType => {\n  const [agentState, setAgentState] = useState<AgentState>({\n    isConnected: false,\n    isConnecting: false,\n    isStreaming: false,\n    error: null,\n    activeSession: null,\n    agentVolume: 0,\n    userVolume: 0,\n  });\n\n  const updateAgentState = (\n    variable: keyof AgentState,\n    value: AgentState[keyof AgentState]\n  ) => {\n    setAgentState((prevState) => ({\n      ...prevState,\n      [variable]: value,\n    }));\n  };\n\n  // Keep reference to the core agent instance\n  const agentCoreRef = useRef<SammyAgentCore | null>(null);\n\n  return {\n    agentCoreRef,\n    agentState,\n    updateAgentState,\n  };\n};\n","import { useEffect, useMemo } from 'react';\n\nimport { SammyAgentCore } from '../core/sammy-agent-core';\nimport { BaseToolEventMap } from './types';\n\nexport const useToolEvents = <\n  TEventMap extends Record<string, (...args: any[]) => any> = BaseToolEventMap,\n>({\n  agentCore,\n  toolCallbacks,\n}: {\n  agentCore: SammyAgentCore<TEventMap> | null;\n  toolCallbacks: Partial<TEventMap>;\n}) => {\n  const toolEventEmitter = useMemo(() => {\n    return agentCore?.getToolEventEmitter();\n  }, [agentCore]);\n\n  // eg\n  /**\n   * \n    useEffect(() => {\n        toolEventEmitter.on(ToolName.END_SESSION, ToolCallbacks.END_SESSION);\n\n        return () => {\n        toolEventEmitter.off(ToolName.END_SESSION, ToolCallbacks.END_SESSION);\n        };\n    }, [ToolCallbacks.END_SESSION]);\n    */\n\n  useEffect(() => {\n    if (toolEventEmitter) {\n      const cleanupFunctions: (() => void)[] = [];\n\n      (Object.keys(toolCallbacks) as Array<keyof TEventMap>).forEach(\n        (toolName) => {\n          const callback = toolCallbacks[toolName];\n          if (callback) {\n            toolEventEmitter.on(toolName, callback);\n            cleanupFunctions.push(() =>\n              toolEventEmitter.off(toolName, callback)\n            );\n          }\n        }\n      );\n\n      return () => {\n        cleanupFunctions.forEach((cleanup) => cleanup());\n      };\n    }\n  }, [toolEventEmitter, toolCallbacks]);\n\n  return toolEventEmitter;\n};\n","/**\n * Memory Types\n * Type definitions for memory-related operations and data structures.\n * Used by memory services for search and retrieval operations.\n * Aligned with Memory API v2.0 native Supabase implementation.\n */\n\n// ============================================================================\n// MEMORY-SPECIFIC TYPES & INTERFACES\n// ============================================================================\n\n// Memory source enum with specific allowed values\nexport enum MemorySourceType {\n  CKB = 'CKB',\n  DIRECT_CSM_ADD = 'DIRECT_CSM_ADD',\n  KNOWLEDGE_HUB = 'KNOWLEDGE_HUB',\n  TRAINING_SESSION = 'TRAINING_SESSION',\n  TRAINING_VIDEO = 'TRAINING_VIDEO',\n  USER_SESSION = 'USER_SESSION',\n}\n\n// Agent type enum\nexport enum AgentType {\n  ADMIN = 'ADMIN',\n  USER = 'USER',\n}\n\n// Memory category enum\nexport enum MemoryCategory {\n  AGENT_PERSONALITY = 'AGENT_PERSONALITY',\n  BUG_REPORT = 'BUG_REPORT',\n  DEPRECATED_INFO = 'DEPRECATED_INFO',\n  FEATURE_MEMORY = 'FEATURE_MEMORY',\n  FEATURE_REQUEST = 'FEATURE_REQUEST',\n  FEEDBACK = 'FEEDBACK',\n  GLOBAL_CONTEXTUAL_MEMORY = 'GLOBAL_CONTEXTUAL_MEMORY',\n\n  ONBOARDING_ISSUE = 'ONBOARDING_ISSUE',\n  // Admin Level Categories\n  PRODUCT_INFO = 'PRODUCT_INFO',\n  UPSELL_OPPORTUNITY = 'UPSELL_OPPORTUNITY',\n  USER_GOAL = 'USER_GOAL',\n  // User Level Categories\n  USER_PREFERENCES = 'USER_PREFERENCES',\n}\n\n// Memory entry structure returned by the API\nexport interface MemoryEntry {\n  memory_id: string;\n  organisation_id: string;\n  agent_type: AgentType;\n  categories: MemoryCategory[];\n  created_at: string; // ISO timestamp\n  global_memory: boolean;\n  memory: string;\n  source: MemorySourceType;\n  confidence_score?: number; // 0-1\n  conversation_id?: string;\n  sammy_three_organisation_feature_id?: string;\n  created_by?: string;\n  importance_score?: number; // 1-10\n  similarity?: number; // Only present in search results (0-1)\n  updated_at?: string; // ISO timestamp\n}\n\n// Memory metadata for creation requests\nexport interface MemoryMetadata {\n  agent_type: AgentType;\n  global_memory: boolean;\n  source: MemorySourceType;\n  confidence_score?: number; // 0-1\n  conversation_id?: string; // Required when source is TRAINING_SESSION\n  sammy_three_organisation_feature_id?: string;\n  categories?: MemoryCategory[];\n  importance_score?: number; // 1-10\n}\n\n// Request model for creating memories\nexport interface MemoryCreate {\n  content: string;\n  metadata: MemoryMetadata;\n  infer?: boolean; // Default: false\n}\n\n// Search options for memory queries\nexport interface SearchRequest {\n  query: string;\n  feature_id?: string; // Filter by specific sammy_three_organisation_feature_id\n  categories?: MemoryCategory[]; // Filter by memory categories\n  limit?: number; // Maximum number of results (1-100, default: 10)\n  search_global_only?: boolean | null; // null=all, true=global only, false=non-global only\n  similarity_threshold?: number; // Minimum similarity score (0.0-1.0, default: 0.0)\n}\n\n// Response structure for search endpoint\nexport interface SearchResponse {\n  memories: MemoryEntry[];\n}\n\n// Request model for updating memories\nexport interface MemoryUpdate {\n  content?: string;\n}\n\n// ============================================================================\n// DEPENDENCY INJECTION INTERFACES\n// ============================================================================\n\n// Interface for sending messages to the AI model\nexport interface MessageSender {\n  send(parts: Array<{ text: string }>, turnComplete: boolean): void;\n}\n\n// Configuration for memory search behavior\nexport interface MemorySearchConfig {\n  agentContextPrefix?: string; // Default: '[VALIDATION]'\n  agentOutputThreshold?: number; // Default: 50 characters\n\n  searchGlobalOnly?: boolean; // Default: true - if true, only search global memories\n  // Search parameters\n  searchLimit?: number; // Default: 3\n\n  similarityThreshold?: number; // Default: 0.2\n  // Search scope\n  useFeatureIdFilter?: boolean; // Default: false - if true, filter by session's feature_id\n\n  // Context formatting\n  userContextPrefix?: string; // Default: '[LIVE CONTEXT]'\n  userContextSuffix?: string; // Default: 'Use this context for your response.'\n  // Buffer thresholds for triggering search\n  userInputThreshold?: number; // Default: 20 characters\n}\n","/**\n * Saves a text string to a .txt file by triggering a browser download\n * @param content - The text content to save\n * @param filename - The filename for the downloaded file (without extension)\n */\nexport const saveTextToFile = (\n  content: string,\n  filename: string = 'system-prompt'\n) => {\n  try {\n    // Create a Blob with the text content\n    const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });\n\n    // Create a temporary URL for the blob\n    const url = URL.createObjectURL(blob);\n\n    // Create a temporary anchor element to trigger download\n    const link = document.createElement('a');\n    link.href = url;\n    link.download = `${filename}.txt`;\n\n    // Append to body, click, and remove\n    document.body.appendChild(link);\n    link.click();\n    document.body.removeChild(link);\n\n    // Clean up the URL object\n    URL.revokeObjectURL(url);\n\n    console.log(`📁 System prompt saved to ${filename}.txt`);\n  } catch (error) {\n    console.error('❌ Error saving system prompt to file:', error);\n  }\n};\n"]}