cursor-buddy 0.0.10 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-D7kFGsuH.mjs","names":[],"sources":["../src/core/atoms.ts","../src/core/utils/error.ts","../src/core/services/audio-playback.ts","../src/core/utils/web-speech.ts","../src/core/services/browser-speech.ts","../src/core/services/live-transcription.ts","../src/core/bezier.ts","../src/core/services/pointer-controller.ts","../src/core/utils/dom-snapshot.ts","../src/core/utils/screenshot.ts","../src/core/services/screen-capture.ts","../src/core/services/tts-playback-queue.ts","../src/core/utils/audio.ts","../src/core/utils/audio-worklet.ts","../src/core/services/voice-capture.ts","../src/core/state-machine.ts","../src/core/stream/parser.ts","../src/core/speech/sentences.ts","../src/core/stream/processor.ts","../src/core/tools/labels.ts","../src/core/tools/types.ts","../src/core/tools/manager.ts","../src/core/client.ts"],"sourcesContent":["import { atom } from \"nanostores\"\nimport type { ConversationMessage, Point, PointingTarget } from \"./types\"\n\n/**\n * Nanostores atoms for reactive values that don't need state machine semantics.\n * These update frequently (e.g., 60fps audio levels) and are framework-agnostic.\n */\n\n// Audio level during recording (0-1, updates at ~60fps)\nexport const $audioLevel = atom<number>(0)\n\n// Mouse cursor position (real cursor, not buddy)\nexport const $cursorPosition = atom<Point>({ x: 0, y: 0 })\n\n// Buddy animated position (follows cursor with spring physics, or flies to target)\nexport const $buddyPosition = atom<Point>({ x: 0, y: 0 })\n\n// Buddy rotation in radians (direction of travel during pointing)\nexport const $buddyRotation = atom<number>(0)\n\n// Buddy scale (1.0 normal, up to 1.3 during flight)\nexport const $buddyScale = atom<number>(1)\n\n// Current pointing target parsed from AI response\nexport const $pointingTarget = atom<PointingTarget | null>(null)\n\n// Whether buddy overlay is enabled/visible\nexport const $isEnabled = atom<boolean>(true)\n\n// Whether TTS is currently playing\nexport const $isSpeaking = atom<boolean>(false)\n\n// Conversation history for context\nexport const $conversationHistory = atom<ConversationMessage[]>([])\n","/**\n * Normalize unknown thrown values into Error instances.\n */\nexport function toError(\n error: unknown,\n fallbackMessage: string = \"Unknown error\",\n): Error {\n if (error instanceof Error) {\n return error\n }\n\n if (typeof error === \"string\" && error) {\n return new Error(error)\n }\n\n return new Error(fallbackMessage)\n}\n","import type { AudioPlaybackPort } from \"../types\"\nimport { toError } from \"../utils/error\"\n\n/**\n * Framework-agnostic service for audio playback with abort support.\n */\nexport class AudioPlaybackService implements AudioPlaybackPort {\n private audio: HTMLAudioElement | null = null\n private currentUrl: string | null = null\n private settlePlayback:\n | ((outcome: \"resolve\" | \"reject\", error?: Error) => void)\n | null = null\n private removeAbortListener: (() => void) | null = null\n\n /**\n * Play audio from a blob. Stops any currently playing audio first.\n * @param blob - Audio blob to play\n * @param signal - Optional AbortSignal to cancel playback\n * @returns Promise that resolves when playback completes\n */\n async play(blob: Blob, signal?: AbortSignal): Promise<void> {\n // Stop any current playback\n this.stop()\n\n // Check if already aborted\n if (signal?.aborted) return\n\n const url = URL.createObjectURL(blob)\n this.currentUrl = url\n this.audio = new Audio(url)\n\n return new Promise<void>((resolve, reject) => {\n if (!this.audio) {\n this.cleanup()\n resolve()\n return\n }\n\n let settled = false\n const audio = this.audio\n\n const settle = (outcome: \"resolve\" | \"reject\", error?: Error) => {\n if (settled) return\n settled = true\n\n if (this.settlePlayback === settle) {\n this.settlePlayback = null\n }\n\n this.removeAbortListener?.()\n this.removeAbortListener = null\n\n if (this.audio === audio) {\n this.audio.onended = null\n this.audio.onerror = null\n this.audio = null\n }\n\n this.cleanup()\n\n if (outcome === \"resolve\") {\n resolve()\n return\n }\n\n reject(error ?? new Error(\"Audio playback failed\"))\n }\n\n this.settlePlayback = settle\n\n const abortHandler = () => {\n audio.pause()\n settle(\"resolve\")\n }\n\n if (signal) {\n signal.addEventListener(\"abort\", abortHandler, { once: true })\n this.removeAbortListener = () => {\n signal.removeEventListener(\"abort\", abortHandler)\n }\n }\n\n this.audio.onended = () => {\n settle(\"resolve\")\n }\n\n this.audio.onerror = () => {\n settle(\"reject\", new Error(\"Audio playback failed\"))\n }\n\n this.audio.play().catch((err) => {\n settle(\"reject\", toError(err, \"Audio playback failed\"))\n })\n })\n }\n\n /**\n * Stop any currently playing audio.\n */\n stop(): void {\n if (this.audio) {\n this.audio.pause()\n }\n\n if (this.settlePlayback) {\n const settlePlayback = this.settlePlayback\n this.settlePlayback = null\n settlePlayback(\"resolve\")\n return\n }\n\n this.removeAbortListener?.()\n this.removeAbortListener = null\n\n if (this.audio) {\n this.audio.onended = null\n this.audio.onerror = null\n this.audio = null\n }\n\n this.cleanup()\n }\n\n private cleanup(): void {\n if (this.currentUrl) {\n URL.revokeObjectURL(this.currentUrl)\n this.currentUrl = null\n }\n }\n}\n","/**\n * Normalize browser speech input and transcript output to a single-space form\n * so UI state and speech synthesis stay stable across browser event quirks.\n */\nexport function normalizeSpeechText(text: string): string {\n return text.replace(/\\s+/g, \" \").trim()\n}\n\n/**\n * Resolve the best browser locale to use for Web Speech APIs.\n *\n * We prefer the document language when the host app declares one, then fall\n * back to the browser locale, and finally to English as a stable default.\n */\nexport function resolveBrowserLanguage(): string {\n if (typeof document !== \"undefined\") {\n const documentLanguage = document.documentElement.lang.trim()\n if (documentLanguage) return documentLanguage\n }\n\n if (typeof navigator !== \"undefined\" && navigator.language) {\n return navigator.language\n }\n\n return \"en-US\"\n}\n","import type { BrowserSpeechPort } from \"../types\"\nimport { toError } from \"../utils/error\"\nimport {\n normalizeSpeechText,\n resolveBrowserLanguage,\n} from \"../utils/web-speech\"\n\nfunction getSpeechSynthesis(): SpeechSynthesis | undefined {\n return typeof globalThis.speechSynthesis === \"undefined\"\n ? undefined\n : globalThis.speechSynthesis\n}\n\nfunction getSpeechSynthesisUtterance():\n | typeof SpeechSynthesisUtterance\n | undefined {\n return typeof globalThis.SpeechSynthesisUtterance === \"undefined\"\n ? undefined\n : globalThis.SpeechSynthesisUtterance\n}\n\nfunction toSpeechError(event?: SpeechSynthesisErrorEvent): Error {\n const errorCode = event?.error\n\n return new Error(\n errorCode ? `Browser speech failed: ${errorCode}` : \"Browser speech failed\",\n )\n}\n\n/**\n * Browser-backed speech synthesis using the Web Speech API.\n */\nexport class BrowserSpeechService implements BrowserSpeechPort {\n private removeAbortListener: (() => void) | null = null\n private settleSpeech:\n | ((outcome: \"resolve\" | \"reject\", error?: Error) => void)\n | null = null\n private utterance: SpeechSynthesisUtterance | null = null\n\n /**\n * Report whether this runtime exposes the browser Web Speech synthesis APIs.\n */\n isAvailable(): boolean {\n return Boolean(getSpeechSynthesis() && getSpeechSynthesisUtterance())\n }\n\n /**\n * Speak a single text segment in the browser.\n *\n * Each queue item owns its own utterance. We only stop an existing utterance\n * when this service still has one in flight, so streamed playback does not\n * spam global `speechSynthesis.cancel()` between already-completed segments.\n */\n async speak(text: string, signal?: AbortSignal): Promise<void> {\n const speechSynthesis = getSpeechSynthesis()\n const SpeechSynthesisUtteranceCtor = getSpeechSynthesisUtterance()\n\n if (!speechSynthesis || !SpeechSynthesisUtteranceCtor) {\n throw new Error(\"Browser speech is not supported\")\n }\n\n if (this.hasActiveSpeech()) {\n this.stop()\n }\n\n const normalizedText = normalizeSpeechText(text)\n if (!normalizedText || signal?.aborted) return\n\n const utterance = new SpeechSynthesisUtteranceCtor(normalizedText)\n utterance.lang = resolveBrowserLanguage()\n this.utterance = utterance\n\n return new Promise<void>((resolve, reject) => {\n let settled = false\n\n const settle = (outcome: \"resolve\" | \"reject\", error?: Error) => {\n if (settled) return\n settled = true\n\n if (this.settleSpeech === settle) {\n this.settleSpeech = null\n }\n\n this.removeAbortListener?.()\n this.removeAbortListener = null\n this.clearUtterance(utterance)\n\n if (outcome === \"resolve\") {\n resolve()\n return\n }\n\n reject(error ?? new Error(\"Browser speech failed\"))\n }\n\n this.settleSpeech = settle\n\n const abortHandler = () => {\n try {\n speechSynthesis.cancel()\n } catch {\n // Ignore cancel failures during abort cleanup.\n }\n\n settle(\"resolve\")\n }\n\n if (signal) {\n signal.addEventListener(\"abort\", abortHandler, { once: true })\n this.removeAbortListener = () => {\n signal.removeEventListener(\"abort\", abortHandler)\n }\n }\n\n utterance.onend = () => {\n settle(\"resolve\")\n }\n\n utterance.onerror = (event) => {\n if (signal?.aborted) {\n settle(\"resolve\")\n return\n }\n\n settle(\"reject\", toSpeechError(event))\n }\n\n try {\n speechSynthesis.speak(utterance)\n } catch (error) {\n settle(\"reject\", toError(error, \"Browser speech failed to start\"))\n }\n })\n }\n\n /**\n * Stop the current utterance owned by this service, if one is active.\n *\n * We intentionally do nothing when the service is idle so we do not cancel\n * unrelated speech synthesis work that host apps may be doing elsewhere.\n */\n stop(): void {\n if (!this.hasActiveSpeech()) {\n return\n }\n\n const speechSynthesis = getSpeechSynthesis()\n\n if (speechSynthesis) {\n try {\n speechSynthesis.cancel()\n } catch {\n // Ignore cancel failures during cleanup.\n }\n }\n\n if (this.settleSpeech) {\n const settleSpeech = this.settleSpeech\n this.settleSpeech = null\n settleSpeech(\"resolve\")\n return\n }\n\n this.removeAbortListener?.()\n this.removeAbortListener = null\n this.clearUtterance(this.utterance)\n }\n\n private hasActiveSpeech(): boolean {\n return Boolean(this.utterance || this.settleSpeech)\n }\n\n private clearUtterance(utterance: SpeechSynthesisUtterance | null): void {\n if (!utterance) return\n\n utterance.onend = null\n utterance.onerror = null\n\n if (this.utterance === utterance) {\n this.utterance = null\n }\n }\n}\n","import type { LiveTranscriptionPort } from \"../types\"\nimport { toError } from \"../utils/error\"\nimport {\n normalizeSpeechText,\n resolveBrowserLanguage,\n} from \"../utils/web-speech\"\n\ninterface SpeechRecognitionAlternativeLike {\n transcript: string\n}\n\ninterface SpeechRecognitionResultLike {\n isFinal: boolean\n length: number\n [index: number]: SpeechRecognitionAlternativeLike\n}\n\ninterface SpeechRecognitionResultListLike {\n length: number\n [index: number]: SpeechRecognitionResultLike\n}\n\ninterface SpeechRecognitionEventLike {\n results: SpeechRecognitionResultListLike\n}\n\ninterface SpeechRecognitionErrorEventLike {\n error?: string\n message?: string\n}\n\ninterface SpeechRecognitionLike {\n continuous: boolean\n interimResults: boolean\n lang: string\n maxAlternatives: number\n onend: (() => void) | null\n onerror: ((event: SpeechRecognitionErrorEventLike) => void) | null\n onresult: ((event: SpeechRecognitionEventLike) => void) | null\n onstart: (() => void) | null\n abort(): void\n start(): void\n stop(): void\n}\n\ntype SpeechRecognitionConstructor = new () => SpeechRecognitionLike\n\ntype GlobalWithSpeechRecognition = typeof globalThis & {\n SpeechRecognition?: SpeechRecognitionConstructor\n webkitSpeechRecognition?: SpeechRecognitionConstructor\n}\n\nfunction getSpeechRecognitionConstructor():\n | SpeechRecognitionConstructor\n | undefined {\n const globalScope = globalThis as GlobalWithSpeechRecognition\n\n return globalScope.SpeechRecognition ?? globalScope.webkitSpeechRecognition\n}\n\nfunction toRecognitionError(event?: SpeechRecognitionErrorEventLike): Error {\n const errorCode = event?.error\n const message =\n event?.message ||\n (errorCode\n ? `Browser transcription failed: ${errorCode}`\n : \"Browser transcription failed\")\n\n return new Error(message)\n}\n\nfunction buildTranscripts(results: SpeechRecognitionResultListLike): {\n finalTranscript: string\n liveTranscript: string\n} {\n let finalTranscript = \"\"\n let interimTranscript = \"\"\n\n // Web Speech returns the running recognition result list on every event.\n // We rebuild both views each time so the client always sees the freshest\n // \"confirmed + in-progress\" transcript.\n for (let index = 0; index < results.length; index += 1) {\n const result = results[index]\n const alternative = result?.[0]\n const transcript = alternative?.transcript ?? \"\"\n\n if (!transcript) continue\n\n if (result.isFinal) {\n finalTranscript += `${transcript} `\n } else {\n interimTranscript += `${transcript} `\n }\n }\n\n const normalizedFinal = normalizeSpeechText(finalTranscript)\n const normalizedInterim = normalizeSpeechText(interimTranscript)\n\n return {\n finalTranscript: normalizedFinal,\n liveTranscript: normalizeSpeechText(\n [normalizedFinal, normalizedInterim].filter(Boolean).join(\" \"),\n ),\n }\n}\n\n/**\n * Browser-backed live transcription using the Web Speech API.\n */\nexport class LiveTranscriptionService implements LiveTranscriptionPort {\n private finalTranscript = \"\"\n private hasStarted = false\n private hasEnded = false\n private lastError: Error | null = null\n private partialCallback: ((text: string) => void) | null = null\n private recognition: SpeechRecognitionLike | null = null\n private startReject: ((reason?: unknown) => void) | null = null\n private startResolve: (() => void) | null = null\n private stopReject: ((reason?: unknown) => void) | null = null\n private stopResolve: ((value: string) => void) | null = null\n\n isAvailable(): boolean {\n return Boolean(getSpeechRecognitionConstructor())\n }\n\n /**\n * Register a callback for the latest browser transcript while the user is\n * still speaking.\n */\n onPartial(callback: (text: string) => void): void {\n this.partialCallback = callback\n }\n\n /**\n * Start a new Web Speech recognition session.\n */\n async start(): Promise<void> {\n const SpeechRecognitionCtor = getSpeechRecognitionConstructor()\n if (!SpeechRecognitionCtor) {\n throw new Error(\"Browser transcription is not supported\")\n }\n\n // Each push-to-talk turn owns a fresh recognition session. We clear any\n // previous session first so late events do not leak into the next turn.\n this.dispose()\n\n const recognition = new SpeechRecognitionCtor()\n this.recognition = recognition\n recognition.continuous = true\n recognition.interimResults = true\n recognition.maxAlternatives = 1\n recognition.lang = resolveBrowserLanguage()\n recognition.onstart = () => {\n this.hasStarted = true\n this.startResolve?.()\n this.startResolve = null\n this.startReject = null\n }\n recognition.onresult = (event) => {\n const transcripts = buildTranscripts(event.results)\n this.finalTranscript = transcripts.finalTranscript\n this.partialCallback?.(transcripts.liveTranscript)\n }\n recognition.onerror = (event) => {\n this.lastError = toRecognitionError(event)\n\n // Errors before `onstart` should reject startup immediately. Errors after\n // startup are handled when the session ends or when stop() awaits it.\n if (!this.hasStarted) {\n this.startReject?.(this.lastError)\n this.startResolve = null\n this.startReject = null\n }\n }\n recognition.onend = () => {\n this.hasEnded = true\n\n // Some browsers can jump straight to `end` when recognition is blocked\n // or cancelled before startup. Convert that into a startup failure.\n if (!this.hasStarted) {\n const error =\n this.lastError ??\n new Error(\"Browser transcription ended before it could start\")\n\n this.startReject?.(error)\n this.startResolve = null\n this.startReject = null\n }\n\n // Once stop() is waiting, settle it on the terminal `end` event so we\n // capture the last finalized transcript from the browser.\n if (this.stopResolve || this.stopReject) {\n if (this.lastError) {\n this.stopReject?.(this.lastError)\n } else {\n this.stopResolve?.(normalizeSpeechText(this.finalTranscript))\n }\n\n this.stopResolve = null\n this.stopReject = null\n }\n }\n\n const started = new Promise<void>((resolve, reject) => {\n this.startResolve = resolve\n this.startReject = reject\n })\n\n try {\n recognition.start()\n } catch (error) {\n this.clearRecognition()\n throw toError(error, \"Browser transcription failed to start\")\n }\n\n try {\n await started\n } catch (error) {\n this.clearRecognition()\n throw toError(error, \"Browser transcription failed to start\")\n }\n }\n\n /**\n * Stop the current recognition session and resolve with the final transcript.\n */\n async stop(): Promise<string> {\n if (!this.recognition) {\n if (this.lastError) {\n throw this.lastError\n }\n\n return normalizeSpeechText(this.finalTranscript)\n }\n\n if (this.hasEnded) {\n const transcript = normalizeSpeechText(this.finalTranscript)\n const error = this.lastError\n this.clearRecognition()\n\n if (error) {\n throw error\n }\n\n return transcript\n }\n\n const recognition = this.recognition\n\n const transcript = await new Promise<string>((resolve, reject) => {\n this.stopResolve = resolve\n this.stopReject = reject\n\n try {\n recognition.stop()\n } catch (error) {\n reject(toError(error, \"Browser transcription failed to stop\"))\n }\n }).finally(() => {\n this.clearRecognition()\n })\n\n return normalizeSpeechText(transcript)\n }\n\n /**\n * Abort the current recognition session and reset the service for reuse.\n */\n dispose(): void {\n if (this.recognition) {\n try {\n this.recognition.abort()\n } catch {\n // Ignore abort failures during cleanup.\n }\n }\n\n this.startReject?.(new Error(\"Browser transcription aborted\"))\n this.stopResolve?.(normalizeSpeechText(this.finalTranscript))\n this.startResolve = null\n this.startReject = null\n this.stopResolve = null\n this.stopReject = null\n this.clearRecognition()\n this.resetSessionState()\n }\n\n private clearRecognition(): void {\n if (!this.recognition) return\n\n // Drop event handlers explicitly so a late browser callback cannot mutate\n // the service after the turn has already moved on.\n this.recognition.onstart = null\n this.recognition.onresult = null\n this.recognition.onerror = null\n this.recognition.onend = null\n this.recognition = null\n }\n\n private resetSessionState(): void {\n this.finalTranscript = \"\"\n this.hasStarted = false\n this.hasEnded = false\n this.lastError = null\n // Clear the live transcript view at the start and end of each turn.\n this.partialCallback?.(\"\")\n }\n}\n","import type { Point } from \"./types\"\n\n/**\n * Bezier flight animation for cursor pointing.\n */\n\n/**\n * Quadratic bezier curve: B(t) = (1-t)²P₀ + 2(1-t)t·P₁ + t²P₂\n */\nfunction quadraticBezier(p0: Point, p1: Point, p2: Point, t: number): Point {\n const oneMinusT = 1 - t\n return {\n x: oneMinusT * oneMinusT * p0.x + 2 * oneMinusT * t * p1.x + t * t * p2.x,\n y: oneMinusT * oneMinusT * p0.y + 2 * oneMinusT * t * p1.y + t * t * p2.y,\n }\n}\n\n/**\n * Bezier tangent (derivative): B'(t) = 2(1-t)(P₁-P₀) + 2t(P₂-P₁)\n */\nfunction bezierTangent(p0: Point, p1: Point, p2: Point, t: number): Point {\n const oneMinusT = 1 - t\n return {\n x: 2 * oneMinusT * (p1.x - p0.x) + 2 * t * (p2.x - p1.x),\n y: 2 * oneMinusT * (p1.y - p0.y) + 2 * t * (p2.y - p1.y),\n }\n}\n\n/**\n * Ease-in-out cubic for smooth acceleration/deceleration\n */\nfunction easeInOutCubic(t: number): number {\n return t < 0.5 ? 4 * t * t * t : 1 - (-2 * t + 2) ** 3 / 2\n}\n\nexport interface BezierFlightCallbacks {\n onFrame: (position: Point, rotation: number, scale: number) => void\n onComplete: () => void\n}\n\n/**\n * Animate cursor along a parabolic bezier arc from start to end.\n * Used when the AI points at a UI element.\n *\n * @param from - Starting position\n * @param to - Target position\n * @param durationMs - Flight duration in milliseconds\n * @param callbacks - Frame and completion callbacks\n * @returns Cancel function to stop the animation\n */\nexport function animateBezierFlight(\n from: Point,\n to: Point,\n durationMs: number,\n callbacks: BezierFlightCallbacks,\n): () => void {\n const startTime = performance.now()\n const distance = Math.hypot(to.x - from.x, to.y - from.y)\n\n // Control point: offset upward by 20% of distance (creates parabolic arc)\n const controlPoint: Point = {\n x: (from.x + to.x) / 2,\n y: Math.min(from.y, to.y) - distance * 0.2,\n }\n\n let animationFrameId: number\n\n function animate(now: number) {\n const elapsed = now - startTime\n const linearProgress = Math.min(elapsed / durationMs, 1)\n const easedProgress = easeInOutCubic(linearProgress)\n\n const position = quadraticBezier(from, controlPoint, to, easedProgress)\n const tangent = bezierTangent(from, controlPoint, to, easedProgress)\n const rotation = Math.atan2(tangent.y, tangent.x)\n\n // Scale pulse: grows to 1.3x at midpoint, returns to 1x\n const scale = 1 + Math.sin(linearProgress * Math.PI) * 0.3\n\n callbacks.onFrame(position, rotation, scale)\n\n if (linearProgress < 1) {\n animationFrameId = requestAnimationFrame(animate)\n } else {\n callbacks.onComplete()\n }\n }\n\n animationFrameId = requestAnimationFrame(animate)\n\n return () => cancelAnimationFrame(animationFrameId)\n}\n","import {\n $buddyPosition,\n $buddyRotation,\n $buddyScale,\n $cursorPosition,\n $pointingTarget,\n} from \"../atoms\"\nimport { animateBezierFlight } from \"../bezier\"\nimport type { PointerControllerPort, PointingTarget } from \"../types\"\n\nconst POINTING_LOCK_TIMEOUT_MS = 10_000\n\ntype PointerMode = \"follow\" | \"flying\" | \"anchored\"\n\n/**\n * Controller for cursor pointing behavior.\n * Manages the pointer state machine (follow -> flying -> anchored -> follow)\n * and cursor animation.\n */\nexport class PointerController implements PointerControllerPort {\n private mode: PointerMode = \"follow\"\n private cancelAnimation: (() => void) | null = null\n private releaseTimeout: ReturnType<typeof setTimeout> | null = null\n private listeners = new Set<() => void>()\n\n /**\n * Animate cursor to point at a target.\n */\n pointAt(target: PointingTarget): void {\n // Clear any previous pointing state\n this.release()\n\n this.mode = \"flying\"\n $pointingTarget.set(target)\n\n const startPos = $buddyPosition.get()\n const endPos = { x: target.x, y: target.y }\n\n this.cancelAnimation = animateBezierFlight(startPos, endPos, 800, {\n onFrame: (position, rotation, scale) => {\n $buddyPosition.set(position)\n $buddyRotation.set(rotation)\n $buddyScale.set(scale)\n },\n onComplete: () => {\n this.cancelAnimation = null\n this.mode = \"anchored\"\n $buddyPosition.set(endPos)\n $buddyRotation.set(0)\n $buddyScale.set(1)\n this.scheduleRelease()\n this.notify()\n },\n })\n\n this.notify()\n }\n\n /**\n * Release the cursor from pointing mode back to follow mode.\n */\n release(): void {\n // Cancel any in-progress animation\n if (this.cancelAnimation) {\n this.cancelAnimation()\n this.cancelAnimation = null\n }\n\n // Clear release timeout\n if (this.releaseTimeout) {\n clearTimeout(this.releaseTimeout)\n this.releaseTimeout = null\n }\n\n // Reset to follow mode\n this.mode = \"follow\"\n $pointingTarget.set(null)\n $buddyPosition.set($cursorPosition.get())\n $buddyRotation.set(0)\n $buddyScale.set(1)\n\n this.notify()\n }\n\n /**\n * Check if cursor is currently pointing (flying or anchored).\n */\n isPointing(): boolean {\n return this.mode !== \"follow\"\n }\n\n /**\n * Get current pointer mode.\n */\n getMode(): PointerMode {\n return this.mode\n }\n\n /**\n * Subscribe to pointer state changes.\n */\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener)\n return () => this.listeners.delete(listener)\n }\n\n /**\n * Update buddy position to follow cursor when in follow mode.\n * Call this on cursor position changes.\n */\n updateFollowPosition(): void {\n if (this.mode === \"follow\") {\n $buddyPosition.set($cursorPosition.get())\n $buddyRotation.set(0)\n $buddyScale.set(1)\n }\n }\n\n private scheduleRelease(): void {\n this.releaseTimeout = setTimeout(() => {\n this.releaseTimeout = null\n this.release()\n }, POINTING_LOCK_TIMEOUT_MS)\n }\n\n private notify(): void {\n this.listeners.forEach((listener) => listener())\n }\n}\n","/**\n * Builds a compact snapshot of the visible DOM for LLM context.\n *\n * Design goals:\n * - Keep the implementation simple and predictable.\n * - Do not infer semantics like roles, labels, or state.\n * - Only exclude a very small set of tags.\n * - Preserve tree structure for reasoning.\n * - Attach a numeric selector ID to each emitted element.\n */\nexport type SnapshotOptions = {\n /**\n * Maximum amount of text to keep per element.\n */\n maxTextLength?: number\n\n /**\n * Hard cap on emitted nodes.\n */\n maxNodes?: number\n\n /**\n * Whether to include element bounding rects.\n */\n includeRects?: boolean\n\n /**\n * Label for the header line.\n */\n rootLabel?: string\n\n /**\n * Which attributes to keep in the snapshot.\n * Keep this small to stay token-efficient.\n */\n includedAttributes?: string[]\n}\n\nexport type SnapshotResult = {\n text: string\n idToElement: Map<number, HTMLElement>\n nodeCount: number\n}\n\ntype SnapshotNode = {\n id: number\n tag: string\n text: string\n attrs: Record<string, string>\n rect?: { x: number; y: number; w: number; h: number }\n children: SnapshotNode[]\n}\n\nconst EXCLUDED_TAGS = new Set([\"script\", \"link\", \"style\", \"noscript\", \"head\"])\n\nconst DEFAULT_INCLUDED_ATTRIBUTES = [\n \"id\",\n \"name\",\n \"type\",\n \"placeholder\",\n \"href\",\n \"title\",\n \"value\",\n \"role\",\n]\n\nexport function buildVisibleDomSnapshot(\n root: Element | Document,\n options: SnapshotOptions = {},\n): SnapshotResult {\n const {\n maxTextLength = 80,\n maxNodes = 1500,\n includeRects = true,\n rootLabel = \"viewport\",\n includedAttributes = DEFAULT_INCLUDED_ATTRIBUTES,\n } = options\n\n const doc = root instanceof Document ? root : root.ownerDocument || document\n const startRoot = root instanceof Document ? root.documentElement : root\n const win = doc.defaultView || window\n\n const viewportW = win.innerWidth || 0\n const viewportH = win.innerHeight || 0\n\n let nextId = 1\n let nodeCount = 0\n\n const idToElement = new Map<number, HTMLElement>()\n const lines: string[] = [`# ${rootLabel} ${viewportW}x${viewportH}`]\n\n /**\n * Returns true when the element is worth considering for the snapshot.\n *\n * This is intentionally simple:\n * - skip excluded tags\n * - skip hidden/display:none/visibility:hidden/etc\n * - skip zero-size elements\n * - skip elements fully outside the viewport\n */\n function isElementVisible(el: Element): boolean {\n const tag = el.tagName.toLowerCase()\n if (EXCLUDED_TAGS.has(tag)) return false\n\n if (!(el instanceof HTMLElement)) return false\n if (el.hidden) return false\n if (el.closest(\"head\")) return false\n\n if (typeof el.checkVisibility === \"function\") {\n try {\n if (\n !el.checkVisibility({\n opacityProperty: true,\n visibilityProperty: true,\n contentVisibilityAuto: true,\n })\n ) {\n return false\n }\n } catch {\n // Ignore unsupported browser behavior and continue with manual checks.\n }\n }\n\n const style = win.getComputedStyle(el)\n\n if (style.display === \"none\") return false\n if (style.visibility === \"hidden\" || style.visibility === \"collapse\") {\n return false\n }\n if (style.opacity === \"0\") return false\n if (style.contentVisibility === \"hidden\") return false\n\n const rect = el.getBoundingClientRect()\n\n if (rect.width <= 0 || rect.height <= 0) return false\n if (rect.bottom <= 0 || rect.right <= 0) return false\n if (rect.top >= viewportH || rect.left >= viewportW) return false\n\n return true\n }\n\n /**\n * Extracts a compact text representation from the element itself.\n *\n * No semantic guessing:\n * - prefer innerText when available\n * - otherwise fall back to textContent\n * - normalize whitespace\n * - truncate aggressively\n */\n function getElementText(el: HTMLElement): string {\n const text = normalizeWhitespace(el.innerText || el.textContent || \"\")\n if (!text) return \"\"\n return truncate(text, maxTextLength)\n }\n\n /**\n * Keeps only a small allowlist of raw DOM attributes.\n *\n * This avoids dumping the full attribute bag, which is usually noisy\n * and expensive in tokens.\n */\n function getIncludedAttributes(el: HTMLElement): Record<string, string> {\n const attrs: Record<string, string> = {}\n\n for (const name of includedAttributes) {\n const value = el.getAttribute(name)\n if (value == null) continue\n\n const clean = truncate(normalizeWhitespace(value), maxTextLength)\n if (!clean) continue\n\n attrs[name] = clean\n }\n\n return attrs\n }\n\n /**\n * Rounds the client rect so the output is smaller and more stable.\n */\n function quantizeRect(el: HTMLElement) {\n const r = el.getBoundingClientRect()\n return {\n x: Math.max(0, Math.round(r.left)),\n y: Math.max(0, Math.round(r.top)),\n w: Math.round(r.width),\n h: Math.round(r.height),\n }\n }\n\n /**\n * Decides whether this node should be emitted.\n *\n * Simple rule:\n * - keep it if it has visible kept children\n * - or keep it if it has some text\n * - or keep it if it has at least one included attribute\n *\n * This allows non-semantic div-heavy UIs to survive without trying\n * to guess intent.\n */\n function shouldKeepNode(\n text: string,\n attrs: Record<string, string>,\n children: SnapshotNode[],\n ): boolean {\n if (children.length > 0) return true\n if (text.length > 0) return true\n if (Object.keys(attrs).length > 0) return true\n return false\n }\n\n /**\n * Single DFS traversal over the DOM.\n *\n * Complexity target:\n * - O(N) DOM walk\n * - O(1) work per element, aside from browser layout/style calls\n */\n function walk(el: Element): SnapshotNode | null {\n if (nodeCount >= maxNodes) return null\n if (!(el instanceof HTMLElement)) return null\n if (!isElementVisible(el)) return null\n\n const children: SnapshotNode[] = []\n\n for (const child of Array.from(el.children)) {\n const childNode = walk(child)\n if (childNode) children.push(childNode)\n if (nodeCount >= maxNodes) break\n }\n\n const text = getElementText(el)\n const attrs = getIncludedAttributes(el)\n\n if (!shouldKeepNode(text, attrs, children)) return null\n\n const id = nextId++\n nodeCount++\n\n idToElement.set(id, el)\n\n return {\n id,\n tag: el.tagName.toLowerCase(),\n text,\n attrs,\n rect: includeRects ? quantizeRect(el) : undefined,\n children,\n }\n }\n\n /**\n * Emits the final compact line-based format.\n *\n * Example:\n * @12 div \"Settings\" [id=\"settings\"] [x=10 y=20 w=200 h=40]\n */\n function emit(node: SnapshotNode, depth: number) {\n const indent = \" \".repeat(depth)\n const parts: string[] = [`${indent}@${node.id} ${node.tag}`]\n\n if (node.text) {\n parts.push(`\"${escapeQuotes(node.text)}\"`)\n }\n\n for (const [key, value] of Object.entries(node.attrs)) {\n parts.push(`[${key}=\"${escapeQuotes(value)}\"]`)\n }\n\n if (node.rect) {\n parts.push(\n `[x=${node.rect.x} y=${node.rect.y} w=${node.rect.w} h=${node.rect.h}]`,\n )\n }\n\n lines.push(parts.join(\" \"))\n\n for (const child of node.children) {\n emit(child, depth + 1)\n }\n }\n\n const tree = walk(startRoot)\n if (tree) emit(tree, 0)\n\n return {\n text: lines.join(\"\\n\"),\n idToElement,\n nodeCount,\n }\n}\n\nfunction normalizeWhitespace(text: string): string {\n return text.replace(/\\s+/g, \" \").trim()\n}\n\nfunction truncate(text: string, maxLength: number): string {\n if (text.length <= maxLength) return text\n return text.slice(0, maxLength - 1).trimEnd() + \"…\"\n}\n\nfunction escapeQuotes(text: string): string {\n return text.replace(/\"/g, '\\\\\"')\n}\n","import html2canvas from \"html2canvas-pro\"\nimport type { ScreenshotResult } from \"../types\"\nimport { buildVisibleDomSnapshot } from \"./dom-snapshot\"\n\nconst CLONE_RESOURCE_TIMEOUT_MS = 3000\n\n/** Maximum width for compressed screenshots (maintains aspect ratio) */\nconst MAX_SCREENSHOT_WIDTH = 1920\n\n/** JPEG quality for compressed screenshots (0-1) - higher quality for clearer details */\nconst JPEG_QUALITY = 0.95\n\n/**\n * Compression result with compressed image data and dimensions.\n */\ninterface CompressionResult {\n /** Base64-encoded compressed image data */\n imageData: string\n /** Width of the compressed image */\n width: number\n /** Height of the compressed image */\n height: number\n}\n\n/**\n * Compress a canvas image by downscaling and converting to JPEG.\n * Maintains aspect ratio and falls back to original if compression fails.\n *\n * @param sourceCanvas - The source canvas to compress\n * @param maxWidth - Maximum width for the compressed image (default: MAX_SCREENSHOT_WIDTH)\n * @param quality - JPEG quality 0-1 (default: JPEG_QUALITY)\n * @returns Compression result with compressed image data and dimensions\n */\nfunction compressImage(\n sourceCanvas: HTMLCanvasElement,\n maxWidth: number = MAX_SCREENSHOT_WIDTH,\n quality: number = JPEG_QUALITY,\n): CompressionResult {\n const sourceWidth = sourceCanvas.width\n const sourceHeight = sourceCanvas.height\n\n // If source is already smaller than max width, just convert to JPEG\n if (sourceWidth <= maxWidth) {\n return {\n imageData: sourceCanvas.toDataURL(\"image/jpeg\", quality),\n width: sourceWidth,\n height: sourceHeight,\n }\n }\n\n // Calculate scaled dimensions maintaining aspect ratio\n const scale = maxWidth / sourceWidth\n const targetWidth = Math.round(maxWidth)\n const targetHeight = Math.round(sourceHeight * scale)\n\n // Create canvas for compressed image\n const canvas = document.createElement(\"canvas\")\n canvas.width = targetWidth\n canvas.height = targetHeight\n\n const ctx = canvas.getContext(\"2d\")\n if (!ctx) {\n // Fallback: return original as JPEG\n return {\n imageData: sourceCanvas.toDataURL(\"image/jpeg\", quality),\n width: sourceWidth,\n height: sourceHeight,\n }\n }\n\n // Use better quality scaling\n ctx.imageSmoothingEnabled = true\n ctx.imageSmoothingQuality = \"high\"\n\n // Draw scaled image\n ctx.drawImage(sourceCanvas, 0, 0, targetWidth, targetHeight)\n\n // Export as JPEG\n return {\n imageData: canvas.toDataURL(\"image/jpeg\", quality),\n width: targetWidth,\n height: targetHeight,\n }\n}\n\nfunction getCaptureMetrics() {\n return {\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n }\n}\n\nfunction waitForNextPaint(doc: Document): Promise<void> {\n const view = doc.defaultView\n if (!view?.requestAnimationFrame) return Promise.resolve()\n\n return new Promise((resolve) => {\n view.requestAnimationFrame(() => {\n view.requestAnimationFrame(() => resolve())\n })\n })\n}\n\nfunction isStylesheetReady(link: HTMLLinkElement): boolean {\n const sheet = link.sheet\n if (!sheet) return false\n\n try {\n void sheet.cssRules\n return true\n } catch (error) {\n return error instanceof DOMException && error.name === \"SecurityError\"\n }\n}\n\nfunction waitForStylesheetLink(link: HTMLLinkElement): Promise<void> {\n if (isStylesheetReady(link)) return Promise.resolve()\n\n return new Promise((resolve) => {\n let settled = false\n let timeoutId = 0\n\n const finish = () => {\n if (settled) return\n settled = true\n window.clearTimeout(timeoutId)\n link.removeEventListener(\"load\", handleReady)\n link.removeEventListener(\"error\", handleReady)\n resolve()\n }\n\n const handleReady = () => {\n if (isStylesheetReady(link)) {\n finish()\n return\n }\n\n window.requestAnimationFrame(() => {\n if (isStylesheetReady(link)) {\n finish()\n }\n })\n }\n\n timeoutId = window.setTimeout(finish, CLONE_RESOURCE_TIMEOUT_MS)\n link.addEventListener(\"load\", handleReady, { once: true })\n link.addEventListener(\"error\", finish, { once: true })\n\n handleReady()\n })\n}\n\nasync function waitForClonedDocumentStyles(doc: Document): Promise<void> {\n const stylesheetLinks = Array.from(\n doc.querySelectorAll<HTMLLinkElement>('link[rel=\"stylesheet\"][href]'),\n )\n\n await Promise.all(stylesheetLinks.map(waitForStylesheetLink))\n\n if (doc.fonts?.ready) {\n await doc.fonts.ready\n }\n\n await waitForNextPaint(doc)\n}\n\nfunction getHtml2CanvasOptions(\n captureMetrics: ReturnType<typeof getCaptureMetrics>,\n) {\n return {\n scale: window.devicePixelRatio,\n useCORS: true,\n logging: false,\n width: captureMetrics.viewportWidth,\n height: captureMetrics.viewportHeight,\n windowWidth: captureMetrics.viewportWidth,\n windowHeight: captureMetrics.viewportHeight,\n x: window.scrollX,\n y: window.scrollY,\n scrollX: window.scrollX,\n scrollY: window.scrollY,\n // In production Next.js emits external stylesheet links; wait for the\n // cloned iframe to finish applying them before html2canvas renders.\n onclone: async (doc: Document) => {\n await waitForClonedDocumentStyles(doc)\n },\n } as const\n}\n\n/**\n * Create a fallback canvas when screenshot capture fails.\n * Returns a simple gray canvas with an error message.\n */\nfunction createFallbackCanvas(): HTMLCanvasElement {\n const canvas = document.createElement(\"canvas\")\n canvas.width = window.innerWidth\n canvas.height = window.innerHeight\n\n const ctx = canvas.getContext(\"2d\")\n if (ctx) {\n ctx.fillStyle = \"#f0f0f0\"\n ctx.fillRect(0, 0, canvas.width, canvas.height)\n ctx.fillStyle = \"#666\"\n ctx.font = \"16px sans-serif\"\n ctx.textAlign = \"center\"\n ctx.fillText(\"Screenshot unavailable\", canvas.width / 2, canvas.height / 2)\n }\n\n return canvas\n}\n\n/**\n * Capture a screenshot and DOM snapshot of the current viewport.\n * Uses html2canvas to render the DOM to a canvas, compresses to high-quality JPEG,\n * and builds a token-efficient DOM snapshot for AI context.\n * Falls back to a placeholder if capture fails.\n */\nexport async function captureViewport(): Promise<ScreenshotResult> {\n const captureMetrics = getCaptureMetrics()\n\n // 1. Capture screenshot\n let canvas: HTMLCanvasElement\n try {\n canvas = await html2canvas(\n document.body,\n getHtml2CanvasOptions(captureMetrics),\n )\n } catch {\n canvas = createFallbackCanvas()\n }\n\n // 2. Compress the screenshot (with fallback to uncompressed on error)\n let compressed: CompressionResult\n try {\n compressed = compressImage(canvas)\n } catch {\n // Fallback: use uncompressed PNG\n compressed = {\n imageData: canvas.toDataURL(\"image/png\"),\n width: canvas.width,\n height: canvas.height,\n }\n }\n\n // 3. Build DOM snapshot for AI context\n const snapshot = buildVisibleDomSnapshot(document.body, {\n maxNodes: 1500,\n maxTextLength: 80,\n includeRects: true,\n })\n\n return {\n imageData: compressed.imageData,\n width: compressed.width,\n height: compressed.height,\n viewportWidth: captureMetrics.viewportWidth,\n viewportHeight: captureMetrics.viewportHeight,\n domSnapshot: snapshot.text,\n elementRegistry: snapshot.idToElement,\n }\n}\n","import type { ScreenCapturePort, ScreenshotResult } from \"../types\"\nimport { captureViewport } from \"../utils/screenshot\"\n\n/**\n * Framework-agnostic service for capturing viewport screenshots.\n */\nexport class ScreenCaptureService implements ScreenCapturePort {\n /**\n * Capture a screenshot and DOM snapshot of the current viewport.\n * @returns Screenshot result with image data, dimensions, and DOM snapshot\n */\n async capture(): Promise<ScreenshotResult> {\n return captureViewport()\n }\n}\n","import { toError } from \"../utils/error\"\n\nexport type SpeechPlaybackTask = () => Promise<void>\n\ninterface TTSPlaybackQueueOptions {\n onError?: (error: Error) => void\n onPlaybackStart?: () => void\n signal?: AbortSignal\n prepare: (text: string, signal?: AbortSignal) => Promise<SpeechPlaybackTask>\n}\n\n/**\n * Queues sentence-level speech preparation immediately while keeping playback\n * strictly ordered.\n *\n * Preparation is allowed to run ahead of playback so server synthesis can\n * overlap with the currently playing segment, but the returned playback tasks\n * still execute one-by-one in enqueue order.\n *\n * Supports pausing after the current segment completes (for approval flows).\n */\nexport class TTSPlaybackQueue {\n private error: Error | null = null\n private hasStartedPlayback = false\n private onError?: (error: Error) => void\n private onPlaybackStart?: () => void\n private playbackChain = Promise.resolve()\n private prepare: (\n text: string,\n signal?: AbortSignal,\n ) => Promise<SpeechPlaybackTask>\n private signal?: AbortSignal\n\n // Pause/resume state\n private isPaused = false\n private pausePromise: Promise<void> | null = null\n private pauseResolver: (() => void) | null = null\n private pendingSegments: string[] = []\n\n constructor(options: TTSPlaybackQueueOptions) {\n this.onError = options.onError\n this.onPlaybackStart = options.onPlaybackStart\n this.prepare = options.prepare\n this.signal = options.signal\n }\n\n /**\n * Queue a speakable text segment.\n */\n enqueue(text: string): void {\n const normalizedText = text.trim()\n if (!normalizedText || this.error || this.signal?.aborted) return\n\n // If paused, discard new segments (per spec: discard unspoken queue)\n if (this.isPaused) {\n return\n }\n\n // Kick off preparation immediately so synthesis/download work can overlap\n // with the segment currently playing.\n const preparedPlaybackTask = this.prepare(normalizedText, this.signal)\n\n // Track pending segments for potential clearing\n this.pendingSegments.push(normalizedText)\n\n // Preparation can finish after the queue has already been aborted. Attach\n // a background rejection handler so those late failures are still recorded\n // and do not surface as unhandled promise rejections in tests or apps.\n void preparedPlaybackTask.catch((error) => {\n this.fail(toError(error))\n })\n\n const nextStep = this.playbackChain.then(async () => {\n if (this.signal?.aborted) return\n\n // Wait if paused\n if (this.pausePromise) {\n await this.pausePromise\n }\n\n // Check abort again after potential pause\n if (this.signal?.aborted) return\n\n const play = await preparedPlaybackTask\n if (this.signal?.aborted) return\n\n if (!this.hasStartedPlayback) {\n this.hasStartedPlayback = true\n this.onPlaybackStart?.()\n }\n\n await play()\n\n // Remove from pending after successful playback\n const index = this.pendingSegments.indexOf(normalizedText)\n if (index !== -1) {\n this.pendingSegments.splice(index, 1)\n }\n })\n\n this.playbackChain = nextStep.catch((error) => {\n this.fail(toError(error))\n })\n }\n\n /**\n * Pause the queue after the currently playing segment completes.\n * New segments enqueued while paused will be discarded.\n */\n pauseAfterCurrent(): void {\n if (this.isPaused) return\n\n this.isPaused = true\n this.pausePromise = new Promise((resolve) => {\n this.pauseResolver = resolve\n })\n\n // Clear pending segments that haven't started playing yet\n this.pendingSegments = []\n }\n\n /**\n * Resume playback after a pause.\n */\n resume(): void {\n if (!this.isPaused) return\n\n this.isPaused = false\n if (this.pauseResolver) {\n this.pauseResolver()\n this.pauseResolver = null\n this.pausePromise = null\n }\n }\n\n /**\n * Check if the queue is currently paused.\n */\n isPausedState(): boolean {\n return this.isPaused\n }\n\n /**\n * Wait until every queued segment has either played or the queue failed.\n */\n async waitForCompletion(): Promise<void> {\n await this.playbackChain\n\n if (this.error) {\n throw this.error\n }\n }\n\n private fail(error: Error): void {\n if (this.error) return\n\n this.error = error\n this.onError?.(error)\n }\n}\n","/**\n * Audio conversion utilities for voice capture.\n * Converts Float32 audio data to WAV format for server transcription.\n */\n\n/**\n * Merge multiple Float32Array chunks into a single array\n */\nexport function mergeAudioChunks(chunks: Float32Array[]): Float32Array {\n const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0)\n const result = new Float32Array(totalLength)\n\n let offset = 0\n for (const chunk of chunks) {\n result.set(chunk, offset)\n offset += chunk.length\n }\n\n return result\n}\n\n/**\n * Convert Float32 audio data to 16-bit PCM\n */\nfunction floatTo16BitPCM(\n output: DataView,\n offset: number,\n input: Float32Array,\n): void {\n for (let i = 0; i < input.length; i++, offset += 2) {\n const sample = Math.max(-1, Math.min(1, input[i]))\n output.setInt16(\n offset,\n sample < 0 ? sample * 0x8000 : sample * 0x7fff,\n true,\n )\n }\n}\n\n/**\n * Write a string to a DataView\n */\nfunction writeString(view: DataView, offset: number, string: string): void {\n for (let i = 0; i < string.length; i++) {\n view.setUint8(offset + i, string.charCodeAt(i))\n }\n}\n\n/**\n * Encode Float32 audio data as a WAV file\n */\nexport function encodeWAV(samples: Float32Array, sampleRate: number): Blob {\n const numChannels = 1\n const bitsPerSample = 16\n const bytesPerSample = bitsPerSample / 8\n const blockAlign = numChannels * bytesPerSample\n\n const dataLength = samples.length * bytesPerSample\n const buffer = new ArrayBuffer(44 + dataLength)\n const view = new DataView(buffer)\n\n // RIFF header\n writeString(view, 0, \"RIFF\")\n view.setUint32(4, 36 + dataLength, true)\n writeString(view, 8, \"WAVE\")\n\n // fmt chunk\n writeString(view, 12, \"fmt \")\n view.setUint32(16, 16, true) // chunk size\n view.setUint16(20, 1, true) // audio format (PCM)\n view.setUint16(22, numChannels, true)\n view.setUint32(24, sampleRate, true)\n view.setUint32(28, sampleRate * blockAlign, true) // byte rate\n view.setUint16(32, blockAlign, true)\n view.setUint16(34, bitsPerSample, true)\n\n // data chunk\n writeString(view, 36, \"data\")\n view.setUint32(40, dataLength, true)\n\n floatTo16BitPCM(view, 44, samples)\n\n return new Blob([buffer], { type: \"audio/wav\" })\n}\n","/**\n * AudioWorklet processor code for voice capture.\n * Inlined as a blob URL to avoid separate file serving requirements.\n */\nconst workletCode = `\nclass AudioCaptureProcessor extends AudioWorkletProcessor {\n constructor() {\n super()\n this.isRecording = true\n this.audioChunkSize = 2048\n this.audioBuffer = new Float32Array(this.audioChunkSize)\n this.audioBufferIndex = 0\n this.levelFramesPerUpdate = 4\n this.levelFrameCount = 0\n this.levelRmsSum = 0\n this.levelPeak = 0\n\n this.port.onmessage = (event) => {\n if (event.data?.type === \"flush\") {\n this.flushAudio()\n this.flushLevel()\n this.port.postMessage({ type: \"flush-complete\" })\n }\n }\n }\n\n flushAudio() {\n if (this.audioBufferIndex === 0) return\n\n const chunk = this.audioBuffer.slice(0, this.audioBufferIndex)\n this.port.postMessage({\n type: \"audio\",\n data: chunk\n })\n this.audioBufferIndex = 0\n }\n\n flushLevel() {\n if (this.levelFrameCount === 0) return\n\n this.port.postMessage({\n type: \"level\",\n rms: this.levelRmsSum / this.levelFrameCount,\n peak: this.levelPeak\n })\n\n this.levelFrameCount = 0\n this.levelRmsSum = 0\n this.levelPeak = 0\n }\n\n process(inputs) {\n if (!this.isRecording) return false\n\n const input = inputs[0]\n if (input && input.length > 0) {\n const channelData = input[0]\n let sum = 0\n let peak = 0\n for (let i = 0; i < channelData.length; i++) {\n const sample = channelData[i]\n sum += sample * sample\n const absolute = Math.abs(sample)\n if (absolute > peak) peak = absolute\n }\n\n this.levelRmsSum += Math.sqrt(sum / channelData.length)\n this.levelPeak = Math.max(this.levelPeak, peak)\n this.levelFrameCount += 1\n\n if (this.levelFrameCount >= this.levelFramesPerUpdate) {\n this.flushLevel()\n }\n\n let readOffset = 0\n while (readOffset < channelData.length) {\n const remaining = this.audioBuffer.length - this.audioBufferIndex\n const copyLength = Math.min(remaining, channelData.length - readOffset)\n\n this.audioBuffer.set(\n channelData.subarray(readOffset, readOffset + copyLength),\n this.audioBufferIndex\n )\n\n this.audioBufferIndex += copyLength\n readOffset += copyLength\n\n if (this.audioBufferIndex >= this.audioBuffer.length) {\n this.flushAudio()\n }\n }\n }\n\n return true\n }\n}\n\nregisterProcessor(\"audio-capture-processor\", AudioCaptureProcessor)\n`\n\nlet cachedBlobURL: string | null = null\n\n/**\n * Create a blob URL for the audio worklet processor.\n * Caches the URL to avoid creating multiple blobs.\n */\nexport function createWorkletBlobURL(): string {\n if (!cachedBlobURL) {\n const blob = new Blob([workletCode], { type: \"application/javascript\" })\n cachedBlobURL = URL.createObjectURL(blob)\n }\n return cachedBlobURL\n}\n\n/**\n * Clean up the cached worklet blob URL.\n * Call this when the app unmounts if needed.\n */\nexport function revokeWorkletBlobURL(): void {\n if (cachedBlobURL) {\n URL.revokeObjectURL(cachedBlobURL)\n cachedBlobURL = null\n }\n}\n","import type { VoiceCapturePort } from \"../types\"\nimport { encodeWAV, mergeAudioChunks } from \"../utils/audio\"\nimport { createWorkletBlobURL } from \"../utils/audio-worklet\"\n\nconst SAMPLE_RATE = 16000\nconst AUDIO_LEVEL_NOISE_GATE = 0.0005\nconst AUDIO_LEVEL_INPUT_GAIN = 600\nconst AUDIO_LEVEL_ATTACK = 0.7\nconst AUDIO_LEVEL_RELEASE = 0.25\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max)\n}\n\nfunction normalizeAudioLevel(rms: number): number {\n const gatedRms = Math.max(0, rms - AUDIO_LEVEL_NOISE_GATE)\n return clamp(\n Math.log1p(gatedRms * AUDIO_LEVEL_INPUT_GAIN) /\n Math.log1p(AUDIO_LEVEL_INPUT_GAIN),\n 0,\n 1,\n )\n}\n\nfunction smoothAudioLevel(current: number, target: number): number {\n const smoothing = target > current ? AUDIO_LEVEL_ATTACK : AUDIO_LEVEL_RELEASE\n return current + (target - current) * smoothing\n}\n\n/**\n * Framework-agnostic service for voice capture using AudioWorkletNode.\n */\nexport class VoiceCaptureService implements VoiceCapturePort {\n private audioContext: AudioContext | null = null\n private workletNode: AudioWorkletNode | null = null\n private sourceNode: MediaStreamAudioSourceNode | null = null\n private silentGainNode: GainNode | null = null\n private stream: MediaStream | null = null\n private chunks: Float32Array[] = []\n private levelCallback: ((level: number) => void) | null = null\n private visualLevel = 0\n private flushResolve: (() => void) | null = null\n\n /**\n * Register a callback to receive audio level updates (0-1).\n * Called at ~60fps during recording for waveform visualization.\n */\n onLevel(callback: (level: number) => void): void {\n this.levelCallback = callback\n }\n\n /**\n * Start recording audio from the microphone.\n * @throws Error if microphone access is denied\n */\n async start(): Promise<void> {\n this.chunks = []\n this.visualLevel = 0\n\n const stream = await navigator.mediaDevices.getUserMedia({\n audio: {\n sampleRate: SAMPLE_RATE,\n channelCount: 1,\n echoCancellation: true,\n noiseSuppression: true,\n },\n })\n this.stream = stream\n\n const audioContext = new AudioContext({ sampleRate: SAMPLE_RATE })\n this.audioContext = audioContext\n await audioContext.resume()\n\n // Load worklet from blob URL\n const workletURL = createWorkletBlobURL()\n await audioContext.audioWorklet.addModule(workletURL)\n\n const source = audioContext.createMediaStreamSource(stream)\n this.sourceNode = source\n const workletNode = new AudioWorkletNode(\n audioContext,\n \"audio-capture-processor\",\n )\n this.workletNode = workletNode\n const silentGainNode = audioContext.createGain()\n silentGainNode.gain.value = 0\n this.silentGainNode = silentGainNode\n\n workletNode.port.onmessage = (event) => {\n const { type, data, rms, peak } = event.data\n\n if (type === \"audio\") {\n this.chunks.push(data)\n } else if (type === \"level\" && this.levelCallback) {\n const signalLevel = Math.max(rms ?? 0, (peak ?? 0) * 0.6)\n const targetLevel = normalizeAudioLevel(signalLevel)\n this.visualLevel = smoothAudioLevel(this.visualLevel, targetLevel)\n this.levelCallback(this.visualLevel)\n } else if (type === \"flush-complete\") {\n this.flushResolve?.()\n this.flushResolve = null\n }\n }\n\n source.connect(workletNode)\n workletNode.connect(silentGainNode)\n silentGainNode.connect(audioContext.destination)\n }\n\n /**\n * Stop recording and return the captured audio as a WAV blob.\n */\n async stop(): Promise<Blob> {\n await this.flushPendingAudio()\n\n // Stop the media stream tracks\n if (this.stream) {\n this.stream.getTracks().forEach((track) => track.stop())\n this.stream = null\n }\n\n // Disconnect and close audio nodes\n if (this.sourceNode) {\n this.sourceNode.disconnect()\n this.sourceNode = null\n }\n\n if (this.workletNode) {\n this.workletNode.disconnect()\n this.workletNode = null\n }\n\n if (this.silentGainNode) {\n this.silentGainNode.disconnect()\n this.silentGainNode = null\n }\n\n if (this.audioContext) {\n await this.audioContext.close()\n this.audioContext = null\n }\n\n // Reset audio level\n this.visualLevel = 0\n this.levelCallback?.(0)\n\n // Encode captured audio as WAV\n const audioData = mergeAudioChunks(this.chunks)\n const wavBlob = encodeWAV(audioData, SAMPLE_RATE)\n\n this.chunks = []\n\n return wavBlob\n }\n\n /**\n * Clean up all resources.\n *\n * The level callback is intentionally preserved so the same service instance\n * can be reused across multiple push-to-talk turns without re-registering\n * the waveform subscription from the client.\n */\n dispose(): void {\n if (this.stream) {\n this.stream.getTracks().forEach((track) => track.stop())\n this.stream = null\n }\n if (this.sourceNode) {\n this.sourceNode.disconnect()\n this.sourceNode = null\n }\n if (this.workletNode) {\n this.workletNode.disconnect()\n this.workletNode = null\n }\n if (this.silentGainNode) {\n this.silentGainNode.disconnect()\n this.silentGainNode = null\n }\n if (this.audioContext) {\n this.audioContext.close()\n this.audioContext = null\n }\n this.chunks = []\n this.visualLevel = 0\n this.levelCallback?.(0)\n this.flushResolve = null\n }\n\n private async flushPendingAudio(): Promise<void> {\n if (!this.workletNode) return\n\n await new Promise<void>((resolve) => {\n const timeoutId = setTimeout(() => {\n this.flushResolve = null\n resolve()\n }, 50)\n\n this.flushResolve = () => {\n clearTimeout(timeoutId)\n resolve()\n }\n\n this.workletNode?.port.postMessage({ type: \"flush\" })\n })\n }\n}\n","import type { VoiceEvent, VoiceState } from \"./types\"\n\n/**\n * State transition table for the voice interaction flow.\n * Maps current state + event type to next state.\n */\nconst transitions: Record<\n VoiceState,\n Partial<Record<VoiceEvent[\"type\"], VoiceState>>\n> = {\n idle: {\n HOTKEY_PRESSED: \"listening\",\n },\n listening: {\n HOTKEY_RELEASED: \"processing\",\n ERROR: \"idle\",\n },\n processing: {\n RESPONSE_STARTED: \"responding\",\n TTS_COMPLETE: \"idle\",\n HOTKEY_PRESSED: \"listening\", // Interruption\n ERROR: \"idle\",\n },\n responding: {\n TTS_COMPLETE: \"idle\",\n HOTKEY_PRESSED: \"listening\", // Interruption\n ERROR: \"idle\",\n },\n}\n\nexport interface StateMachine {\n /** Get current state */\n getState(): VoiceState\n /** Attempt a state transition. Returns true if transition was valid. */\n transition(event: VoiceEvent): boolean\n /** Subscribe to state changes */\n subscribe(listener: () => void): () => void\n /** Reset to idle state */\n reset(): void\n}\n\n/**\n * Create a simple typed state machine for the voice interaction flow.\n *\n * States: idle -> listening -> processing -> responding -> idle\n *\n * Supports interruption: pressing hotkey during processing or responding\n * immediately transitions back to listening.\n */\nexport function createStateMachine(initial: VoiceState = \"idle\"): StateMachine {\n let state = initial\n const listeners = new Set<() => void>()\n\n function notify() {\n listeners.forEach((listener) => listener())\n }\n\n return {\n getState: () => state,\n\n transition: (event: VoiceEvent): boolean => {\n const nextState = transitions[state][event.type]\n if (!nextState) return false\n\n state = nextState\n notify()\n return true\n },\n\n subscribe: (listener: () => void) => {\n listeners.add(listener)\n return () => listeners.delete(listener)\n },\n\n reset: () => {\n state = \"idle\"\n notify()\n },\n }\n}\n","import type { UIStreamChunk } from \"./types\"\n\n/**\n * Parse a single line from the UI message stream.\n * The stream format is SSE with \"data: \" prefix followed by JSON.\n */\nexport function parseStreamLine(line: string): UIStreamChunk | null {\n const trimmed = line.trim()\n if (!trimmed) return null\n\n // Handle SSE format: strip \"data: \" prefix\n let jsonStr = trimmed\n if (trimmed.startsWith(\"data: \")) {\n jsonStr = trimmed.slice(6)\n }\n\n // Skip [DONE] marker\n if (jsonStr === \"[DONE]\") return null\n\n try {\n const chunk = JSON.parse(jsonStr) as Record<string, unknown>\n const type = chunk.type\n\n switch (type) {\n case \"text-delta\":\n return {\n type: \"text-delta\",\n delta: typeof chunk.delta === \"string\" ? chunk.delta : \"\",\n }\n\n case \"tool-call\":\n return {\n type: \"tool-call\",\n toolCallId:\n typeof chunk.toolCallId === \"string\" ? chunk.toolCallId : \"\",\n toolName: typeof chunk.toolName === \"string\" ? chunk.toolName : \"\",\n args: chunk.args,\n }\n\n // AI SDK v6 emits this for tools with needsApproval\n case \"tool-approval-request\":\n return {\n type: \"tool-approval-request\",\n approvalId:\n typeof chunk.approvalId === \"string\" ? chunk.approvalId : \"\",\n toolCallId:\n typeof chunk.toolCallId === \"string\" ? chunk.toolCallId : \"\",\n toolName: typeof chunk.toolName === \"string\" ? chunk.toolName : \"\",\n args: chunk.args,\n }\n\n case \"tool-result\":\n return {\n type: \"tool-result\",\n toolCallId:\n typeof chunk.toolCallId === \"string\" ? chunk.toolCallId : \"\",\n result: chunk.result,\n }\n\n // Handle tool execution errors\n case \"tool-result-error\":\n return {\n type: \"tool-result-error\",\n toolCallId:\n typeof chunk.toolCallId === \"string\" ? chunk.toolCallId : \"\",\n error: typeof chunk.error === \"string\" ? chunk.error : \"Unknown error\",\n }\n\n case \"finish\":\n return { type: \"finish\" }\n\n case \"error\":\n return {\n type: \"error\",\n errorText:\n typeof chunk.errorText === \"string\"\n ? chunk.errorText\n : \"Unknown error\",\n }\n\n // Provider-executed tools use tool-input-available instead of tool-call\n case \"tool-input-available\":\n return {\n type: \"tool-call\",\n toolCallId:\n typeof chunk.toolCallId === \"string\"\n ? chunk.toolCallId\n : `legacy-${Date.now()}`,\n toolName: typeof chunk.toolName === \"string\" ? chunk.toolName : \"\",\n args: chunk.input,\n }\n\n // Provider-executed tools use tool-output-available instead of tool-result\n case \"tool-output-available\":\n return {\n type: \"tool-result\",\n toolCallId:\n typeof chunk.toolCallId === \"string\" ? chunk.toolCallId : \"\",\n result: chunk.output,\n }\n\n default:\n return { type: \"unknown\" }\n }\n } catch {\n return null\n }\n}\n\n/**\n * Parse multiple lines from the stream buffer\n */\nexport function parseStreamBuffer(buffer: string): {\n chunks: UIStreamChunk[]\n remainder: string\n} {\n const lines = buffer.split(\"\\n\")\n const remainder = lines.pop() ?? \"\"\n const chunks: UIStreamChunk[] = []\n\n for (const line of lines) {\n const chunk = parseStreamLine(line)\n if (chunk) {\n chunks.push(chunk)\n }\n }\n\n return { chunks, remainder }\n}\n","const CLOSING_PUNCTUATION = new Set([\n '\"',\n \"'\",\n \"\\u201D\",\n \"\\u2019\",\n \")\",\n \"]\",\n \"}\",\n])\n\nconst SHORT_SEGMENT_THRESHOLD = 24\n\nfunction isLikelySentenceBoundary(text: string, index: number): boolean {\n const char = text[index]\n if (char === \"!\" || char === \"?\" || char === \"\\u2026\" || char === \"\\n\") {\n return true\n }\n\n if (char !== \".\") return false\n\n const previousChar = text[index - 1] ?? \"\"\n const nextChar = text[index + 1] ?? \"\"\n\n // Decimal numbers\n if (/\\d/.test(previousChar) && /\\d/.test(nextChar)) {\n return false\n }\n\n return true\n}\n\nfunction findBoundaryEnd(text: string, start: number): number | null {\n for (let index = start; index < text.length; index++) {\n const char = text[index]\n\n if (char === \"\\n\") {\n let end = index + 1\n while (end < text.length && /\\s/.test(text[end] ?? \"\")) {\n end++\n }\n return end\n }\n\n if (!isLikelySentenceBoundary(text, index)) continue\n\n let end = index + 1\n while (end < text.length && CLOSING_PUNCTUATION.has(text[end] ?? \"\")) {\n end++\n }\n\n // Only treat as boundary if followed by whitespace or end of text\n if (end < text.length && !/\\s/.test(text[end] ?? \"\")) {\n continue\n }\n\n while (end < text.length && /\\s/.test(text[end] ?? \"\")) {\n end++\n }\n\n return end\n }\n\n return null\n}\n\n/**\n * Extract completed sentences from text.\n * Returns the consumed length and extracted segments.\n */\nexport function extractCompletedSentences(text: string): {\n consumedLength: number\n segments: string[]\n} {\n const segments: string[] = []\n let consumedLength = 0\n\n while (consumedLength < text.length) {\n const boundaryEnd = findBoundaryEnd(text, consumedLength)\n if (boundaryEnd === null) break\n\n const segment = text.slice(consumedLength, boundaryEnd).trim()\n if (segment) {\n segments.push(segment)\n }\n\n consumedLength = boundaryEnd\n }\n\n return { consumedLength, segments }\n}\n\n/**\n * Buffer that accumulates text and emits complete sentences for TTS.\n * Coalesces short segments to avoid choppy speech.\n */\nexport class SentenceBuffer {\n private text = \"\"\n private consumedLength = 0\n private pendingShortSegment = \"\"\n\n /**\n * Add text to the buffer and extract any complete sentences.\n */\n push(delta: string): string[] {\n this.text += delta\n\n const unprocessedText = this.text.slice(this.consumedLength)\n const { consumedLength, segments } =\n extractCompletedSentences(unprocessedText)\n this.consumedLength += consumedLength\n\n return this.coalesceSegments(segments)\n }\n\n /**\n * Flush any remaining text as a final segment.\n */\n flush(): string {\n const trailingText = this.text.slice(this.consumedLength).trim()\n const finalParts = [this.pendingShortSegment, trailingText].filter(Boolean)\n\n this.pendingShortSegment = \"\"\n this.text = \"\"\n this.consumedLength = 0\n\n return finalParts.length ? finalParts.join(\" \").trim() : \"\"\n }\n\n private coalesceSegments(segments: string[]): string[] {\n const speechSegments: string[] = []\n\n for (const segment of segments) {\n const normalizedSegment = segment.trim()\n if (!normalizedSegment) continue\n\n const candidate = this.pendingShortSegment\n ? `${this.pendingShortSegment} ${normalizedSegment}`\n : normalizedSegment\n\n if (candidate.length < SHORT_SEGMENT_THRESHOLD) {\n this.pendingShortSegment = candidate\n continue\n }\n\n this.pendingShortSegment = \"\"\n speechSegments.push(candidate)\n }\n\n return speechSegments\n }\n}\n","import { SentenceBuffer } from \"../speech/sentences\"\nimport { parseStreamBuffer } from \"./parser\"\nimport type { StreamProcessorCallbacks, StreamTurnResult } from \"./types\"\n\n/**\n * Processes a streaming AI SDK UI message stream.\n * Extracts text, tool calls, and speech segments.\n */\nexport class StreamProcessor {\n private callbacks: StreamProcessorCallbacks\n private sentenceBuffer: SentenceBuffer\n private buffer = \"\"\n private responseText = \"\"\n private pendingApproval: StreamTurnResult[\"pendingApproval\"] = undefined\n\n constructor(callbacks: StreamProcessorCallbacks) {\n this.callbacks = callbacks\n this.sentenceBuffer = new SentenceBuffer()\n }\n\n /**\n * Process a raw chunk from the stream.\n */\n processChunk(chunk: string): void {\n this.buffer += chunk\n const { chunks, remainder } = parseStreamBuffer(this.buffer)\n this.buffer = remainder\n\n for (const parsed of chunks) {\n this.handleParsedChunk(parsed)\n }\n }\n\n /**\n * Finalize processing and return turn result.\n */\n finish(): StreamTurnResult {\n // Process any remaining buffer\n if (this.buffer) {\n const { chunks } = parseStreamBuffer(this.buffer + \"\\n\")\n for (const parsed of chunks) {\n this.handleParsedChunk(parsed)\n }\n this.buffer = \"\"\n }\n\n // Flush remaining text for TTS\n const remainingText = this.sentenceBuffer.flush()\n if (remainingText) {\n this.callbacks.onSpeechSegment(remainingText)\n }\n\n return {\n responseText: this.responseText.trim(),\n requiresApprovalContinuation: this.pendingApproval !== undefined,\n pendingApproval: this.pendingApproval,\n }\n }\n\n /**\n * Get the current response text.\n */\n getResponseText(): string {\n return this.responseText\n }\n\n private handleParsedChunk(\n chunk: ReturnType<typeof parseStreamBuffer>[\"chunks\"][number],\n ): void {\n switch (chunk.type) {\n case \"text-delta\":\n this.responseText += chunk.delta\n this.callbacks.onTextDelta(chunk.delta)\n\n // Extract complete sentences for TTS\n const sentences = this.sentenceBuffer.push(chunk.delta)\n for (const sentence of sentences) {\n this.callbacks.onSpeechSegment(sentence)\n }\n break\n\n case \"tool-call\":\n this.callbacks.onToolCall({\n toolCallId: chunk.toolCallId,\n toolName: chunk.toolName,\n args: chunk.args,\n })\n break\n\n case \"tool-approval-request\":\n this.pendingApproval = {\n approvalId: chunk.approvalId,\n toolCallId: chunk.toolCallId,\n toolName: chunk.toolName,\n args: chunk.args,\n }\n this.callbacks.onApprovalRequest({\n approvalId: chunk.approvalId,\n toolCallId: chunk.toolCallId,\n toolName: chunk.toolName,\n args: chunk.args,\n })\n break\n\n case \"tool-result\":\n this.callbacks.onToolResult({\n toolCallId: chunk.toolCallId,\n result: chunk.result,\n })\n break\n\n case \"tool-result-error\":\n this.callbacks.onToolError({\n toolCallId: chunk.toolCallId,\n error: chunk.error,\n })\n break\n\n case \"finish\":\n this.callbacks.onFinish()\n break\n\n case \"error\":\n this.callbacks.onError(chunk.errorText)\n break\n\n case \"unknown\":\n // Ignore unknown chunk types\n break\n }\n }\n}\n","import type { ToolCallStatus, ToolDisplayConfig } from \"./types\"\n\n/**\n * Capitalize the first letter of a string.\n */\nfunction capitalize(str: string): string {\n if (!str) return str\n return str.charAt(0).toUpperCase() + str.slice(1)\n}\n\n/**\n * Convert a tool name to human-readable format.\n * e.g., \"web_search\" -> \"web search\", \"createNote\" -> \"create note\"\n */\nfunction humanizeToolName(toolName: string): string {\n return toolName\n .replace(/_/g, \" \")\n .replace(/([a-z])([A-Z])/g, \"$1 $2\")\n .toLowerCase()\n}\n\n/**\n * Generate a default label for a tool based on its name and status.\n */\nexport function generateToolLabel(\n toolName: string,\n status: ToolCallStatus,\n): string {\n const humanName = humanizeToolName(toolName)\n\n switch (status) {\n case \"pending\":\n return `${capitalize(humanName)}...`\n case \"awaiting_approval\":\n return `Approve ${humanName}?`\n case \"approved\":\n return `${capitalize(humanName)}...`\n case \"denied\":\n return \"Cancelled\"\n case \"completed\":\n return capitalize(humanName)\n case \"failed\":\n return `${capitalize(humanName)} failed`\n }\n}\n\n/**\n * Resolve the label for a tool call using the display config.\n * Falls back to auto-generated label if no config is provided.\n */\nexport function resolveToolLabel(\n toolName: string,\n args: unknown,\n status: ToolCallStatus,\n config?: ToolDisplayConfig,\n): string {\n // Try tool-specific config first\n const toolConfig = config?.[toolName]\n if (toolConfig?.label) {\n if (typeof toolConfig.label === \"function\") {\n return toolConfig.label(args, status)\n }\n return toolConfig.label\n }\n\n // Try default config\n const defaultConfig = config?.[\"*\"]\n if (defaultConfig?.label) {\n if (typeof defaultConfig.label === \"function\") {\n return defaultConfig.label(args, status)\n }\n return defaultConfig.label\n }\n\n // Fall back to auto-generated label\n return generateToolLabel(toolName, status)\n}\n","/**\n * Tool call lifecycle status\n */\nexport type ToolCallStatus =\n | \"pending\" // Tool called, waiting for result or approval request\n | \"awaiting_approval\" // Needs user consent\n | \"approved\" // User approved, executing\n | \"denied\" // User denied\n | \"completed\" // Finished successfully\n | \"failed\" // Execution failed\n\n/**\n * Internal tool call state tracked by ToolCallManager\n */\nexport interface ToolCallState {\n /** Unique tool call ID from the stream */\n id: string\n /** Name of the tool */\n toolName: string\n /** Arguments passed to the tool */\n args: unknown\n /** Current status */\n status: ToolCallStatus\n /** Pre-resolved label from config or auto-generated */\n label: string\n /** Tool result when completed */\n result?: unknown\n /** Error message when failed */\n error?: string\n /** Approval ID for approval flow */\n approvalId?: string\n /** Timestamp when tool entered the queue */\n enteredQueueAt: number\n}\n\n/**\n * Render props for tool bubble components\n */\nexport interface ToolBubbleRenderProps {\n /** Name of the tool */\n toolName: string\n /** Arguments passed to the tool */\n args: unknown\n /** Current status */\n status: ToolCallStatus\n /** Pre-resolved label */\n label: string\n /** Tool result when completed */\n result?: unknown\n /** Error message when failed */\n error?: string\n /** Approve the tool call (only when status === \"awaiting_approval\") */\n approve?: () => void\n /** Deny the tool call (only when status === \"awaiting_approval\") */\n deny?: () => void\n /** Dismiss the bubble manually */\n dismiss: () => void\n}\n\n/**\n * Error handler return types\n */\nexport type ToolErrorHandlerResult =\n | void\n | { label: string }\n | { hide: true }\n | { render: (props: ToolBubbleRenderProps) => React.ReactNode }\n\n/**\n * Configuration for displaying a single tool\n */\nexport interface ToolDisplayOptions {\n /** Display mode. Default: \"bubble\" */\n mode?: \"bubble\" | \"hidden\"\n /** Label shown in bubble. Auto-generated if omitted. */\n label?: string | ((args: unknown, status: ToolCallStatus) => string)\n /** Minimum display time in ms. Default: 1500 */\n minDisplayTime?: number\n /** Custom render for bubble content */\n render?: (props: ToolBubbleRenderProps) => React.ReactNode\n /** Error handling */\n onError?: (error: string, args: unknown) => ToolErrorHandlerResult\n}\n\n/**\n * Tool display configuration map\n * Use \"*\" key for default options applied to all tools\n */\nexport interface ToolDisplayConfig {\n [toolName: string]: ToolDisplayOptions\n}\n\n/**\n * Event emitted when a tool is called\n */\nexport interface ToolCallEvent {\n /** Unique tool call ID */\n id: string\n /** Name of the tool */\n toolName: string\n /** Arguments passed to the tool */\n args: unknown\n}\n\n/**\n * Event emitted when a tool completes\n */\nexport interface ToolResultEvent {\n /** Tool call ID */\n id: string\n /** Name of the tool */\n toolName: string\n /** Tool result */\n result: unknown\n}\n\n/**\n * Callbacks for ToolCallManager\n */\nexport interface ToolCallManagerCallbacks {\n /** Called when tool calls change */\n onChange: () => void\n /** Send approval response to server and continue */\n onApprovalResponse: (approvalId: string, approved: boolean) => Promise<void>\n}\n\n/**\n * Default minimum display time for tool bubbles in ms\n */\nexport const DEFAULT_MIN_DISPLAY_TIME = 1500\n\n/**\n * Brief display time after result arrives before removing bubble\n */\nexport const RESULT_LINGER_TIME = 300\n","import { resolveToolLabel } from \"./labels\"\nimport {\n DEFAULT_MIN_DISPLAY_TIME,\n RESULT_LINGER_TIME,\n type ToolCallManagerCallbacks,\n type ToolCallState,\n type ToolCallStatus,\n type ToolDisplayConfig,\n} from \"./types\"\n\n/**\n * Manages tool call state, display timing, and approval flow.\n */\nexport class ToolCallManager {\n private toolCalls: Map<string, ToolCallState> = new Map()\n private displayConfig: ToolDisplayConfig\n private callbacks: ToolCallManagerCallbacks\n private removalTimers: Map<string, ReturnType<typeof setTimeout>> = new Map()\n\n constructor(\n callbacks: ToolCallManagerCallbacks,\n displayConfig?: ToolDisplayConfig,\n ) {\n this.callbacks = callbacks\n this.displayConfig = displayConfig ?? {}\n }\n\n /**\n * Update the display configuration.\n */\n setDisplayConfig(config: ToolDisplayConfig): void {\n this.displayConfig = config\n }\n\n /**\n * Handle a new tool call from the stream.\n */\n handleToolCall(event: {\n toolCallId: string\n toolName: string\n args: unknown\n }): void {\n const status: ToolCallStatus = \"pending\"\n const label = resolveToolLabel(\n event.toolName,\n event.args,\n status,\n this.displayConfig,\n )\n\n const toolCall: ToolCallState = {\n id: event.toolCallId,\n toolName: event.toolName,\n args: event.args,\n status,\n label,\n enteredQueueAt: Date.now(),\n }\n\n this.toolCalls.set(event.toolCallId, toolCall)\n this.callbacks.onChange()\n }\n\n /**\n * Handle an approval request for a tool call.\n */\n handleApprovalRequest(event: {\n approvalId: string\n toolCallId: string\n toolName: string\n args: unknown\n }): void {\n const existing = this.toolCalls.get(event.toolCallId)\n\n if (existing) {\n // Update existing tool call with approval info\n existing.status = \"awaiting_approval\"\n existing.approvalId = event.approvalId\n existing.label = resolveToolLabel(\n existing.toolName,\n existing.args,\n \"awaiting_approval\",\n this.displayConfig,\n )\n } else {\n // Create new tool call entry if we missed the initial tool-call event\n const label = resolveToolLabel(\n event.toolName,\n event.args,\n \"awaiting_approval\",\n this.displayConfig,\n )\n\n const toolCall: ToolCallState = {\n id: event.toolCallId,\n toolName: event.toolName,\n args: event.args,\n status: \"awaiting_approval\",\n label,\n approvalId: event.approvalId,\n enteredQueueAt: Date.now(),\n }\n\n this.toolCalls.set(event.toolCallId, toolCall)\n }\n\n this.callbacks.onChange()\n }\n\n /**\n * Handle a successful tool result.\n */\n handleToolResult(event: { toolCallId: string; result: unknown }): void {\n const toolCall = this.toolCalls.get(event.toolCallId)\n if (!toolCall) return\n\n toolCall.status = \"completed\"\n toolCall.result = event.result\n toolCall.label = resolveToolLabel(\n toolCall.toolName,\n toolCall.args,\n \"completed\",\n this.displayConfig,\n )\n\n this.scheduleRemoval(toolCall)\n this.callbacks.onChange()\n }\n\n /**\n * Handle a tool execution error.\n */\n handleToolError(event: { toolCallId: string; error: string }): void {\n const toolCall = this.toolCalls.get(event.toolCallId)\n if (!toolCall) return\n\n const config = this.getConfigFor(toolCall.toolName)\n const errorResult = config?.onError?.(event.error, toolCall.args)\n\n // Check for hide directive\n if (errorResult && \"hide\" in errorResult && errorResult.hide) {\n this.toolCalls.delete(event.toolCallId)\n this.callbacks.onChange()\n return\n }\n\n toolCall.status = \"failed\"\n toolCall.error = event.error\n\n // Use custom label or fall back to auto-generated\n if (errorResult && \"label\" in errorResult) {\n toolCall.label = errorResult.label\n } else {\n toolCall.label = resolveToolLabel(\n toolCall.toolName,\n toolCall.args,\n \"failed\",\n this.displayConfig,\n )\n }\n\n this.scheduleRemoval(toolCall)\n this.callbacks.onChange()\n }\n\n /**\n * Approve a pending tool call.\n */\n async approve(toolCallId: string): Promise<void> {\n const toolCall = this.toolCalls.get(toolCallId)\n if (!toolCall || toolCall.status !== \"awaiting_approval\") return\n if (!toolCall.approvalId) return\n\n toolCall.status = \"approved\"\n toolCall.label = resolveToolLabel(\n toolCall.toolName,\n toolCall.args,\n \"approved\",\n this.displayConfig,\n )\n this.callbacks.onChange()\n\n await this.callbacks.onApprovalResponse(toolCall.approvalId, true)\n }\n\n /**\n * Deny a pending tool call.\n */\n async deny(toolCallId: string): Promise<void> {\n const toolCall = this.toolCalls.get(toolCallId)\n if (!toolCall || toolCall.status !== \"awaiting_approval\") return\n if (!toolCall.approvalId) return\n\n toolCall.status = \"denied\"\n toolCall.label = resolveToolLabel(\n toolCall.toolName,\n toolCall.args,\n \"denied\",\n this.displayConfig,\n )\n this.scheduleRemoval(toolCall)\n this.callbacks.onChange()\n\n await this.callbacks.onApprovalResponse(toolCall.approvalId, false)\n }\n\n /**\n * Manually dismiss a tool call bubble.\n */\n dismiss(toolCallId: string): void {\n this.clearRemovalTimer(toolCallId)\n this.toolCalls.delete(toolCallId)\n this.callbacks.onChange()\n }\n\n /**\n * Get a tool call by ID.\n */\n getToolCall(id: string): ToolCallState | undefined {\n return this.toolCalls.get(id)\n }\n\n /**\n * Get all tool calls.\n */\n getToolCalls(): ToolCallState[] {\n return Array.from(this.toolCalls.values())\n }\n\n /**\n * Get active (visible, non-expired) tool calls.\n */\n getActiveToolCalls(): ToolCallState[] {\n const now = Date.now()\n\n return Array.from(this.toolCalls.values()).filter((toolCall) => {\n const config = this.getConfigFor(toolCall.toolName)\n\n // Hidden tools are not active\n if (config?.mode === \"hidden\") return false\n\n // Awaiting approval is always active\n if (toolCall.status === \"awaiting_approval\") return true\n\n // Pending and approved are always active\n if (toolCall.status === \"pending\" || toolCall.status === \"approved\") {\n return true\n }\n\n // Completed/failed/denied: check if still within display time\n const minTime = config?.minDisplayTime ?? DEFAULT_MIN_DISPLAY_TIME\n const elapsed = now - toolCall.enteredQueueAt\n\n return elapsed < minTime + RESULT_LINGER_TIME\n })\n }\n\n /**\n * Get the tool call awaiting approval, if any.\n */\n getPendingApproval(): ToolCallState | null {\n for (const toolCall of this.toolCalls.values()) {\n if (toolCall.status === \"awaiting_approval\") {\n return toolCall\n }\n }\n return null\n }\n\n /**\n * Clear all tool calls and timers.\n */\n reset(): void {\n for (const timer of this.removalTimers.values()) {\n clearTimeout(timer)\n }\n this.removalTimers.clear()\n this.toolCalls.clear()\n this.callbacks.onChange()\n }\n\n private getConfigFor(toolName: string) {\n return this.displayConfig[toolName] ?? this.displayConfig[\"*\"]\n }\n\n private scheduleRemoval(toolCall: ToolCallState): void {\n this.clearRemovalTimer(toolCall.id)\n\n const config = this.getConfigFor(toolCall.toolName)\n const minTime = config?.minDisplayTime ?? DEFAULT_MIN_DISPLAY_TIME\n const elapsed = Date.now() - toolCall.enteredQueueAt\n const remaining = Math.max(0, minTime - elapsed) + RESULT_LINGER_TIME\n\n const timer = setTimeout(() => {\n this.toolCalls.delete(toolCall.id)\n this.removalTimers.delete(toolCall.id)\n this.callbacks.onChange()\n }, remaining)\n\n this.removalTimers.set(toolCall.id, timer)\n }\n\n private clearRemovalTimer(toolCallId: string): void {\n const existing = this.removalTimers.get(toolCallId)\n if (existing) {\n clearTimeout(existing)\n this.removalTimers.delete(toolCallId)\n }\n }\n}\n","import type { PointToolInput } from \"./tools/point-tool\"\nimport { $audioLevel, $conversationHistory, $isEnabled } from \"./atoms\"\nimport { AudioPlaybackService } from \"./services/audio-playback\"\nimport { BrowserSpeechService } from \"./services/browser-speech\"\nimport { LiveTranscriptionService } from \"./services/live-transcription\"\nimport { PointerController } from \"./services/pointer-controller\"\nimport { ScreenCaptureService } from \"./services/screen-capture\"\nimport {\n type SpeechPlaybackTask,\n TTSPlaybackQueue,\n} from \"./services/tts-playback-queue\"\nimport { VoiceCaptureService } from \"./services/voice-capture\"\nimport { createStateMachine, type StateMachine } from \"./state-machine\"\nimport { StreamProcessor } from \"./stream\"\nimport { ToolCallManager } from \"./tools\"\nimport type {\n AudioPlaybackPort,\n BrowserSpeechPort,\n ConversationMessage,\n CursorBuddyClientOptions,\n CursorBuddyMediaMode,\n CursorBuddyServices,\n CursorBuddySnapshot,\n LiveTranscriptionPort,\n PointerControllerPort,\n PointingTarget,\n ScreenCapturePort,\n ScreenshotResult,\n VoiceCapturePort,\n} from \"./types\"\nimport { toError } from \"./utils/error\"\n\nasync function readErrorMessage(\n response: Response,\n fallbackMessage: string,\n): Promise<string> {\n try {\n const contentType = response.headers.get(\"Content-Type\") ?? \"\"\n if (contentType.includes(\"application/json\")) {\n const body = (await response.json()) as { error?: string }\n if (body?.error) return body.error\n }\n const text = await response.text()\n if (text) return text\n } catch {\n // Fall back to the generic message\n }\n return fallbackMessage\n}\n\nexport type { CursorBuddyServices } from \"./types\"\n\n/**\n * Framework-agnostic client for cursor buddy voice interactions.\n *\n * Manages the complete voice interaction flow:\n * idle -> listening -> processing -> responding -> idle\n *\n * Supports:\n * - Interruption via hotkey\n * - Tool call display with approval flow\n * - Point tool for cursor movement\n */\nexport class CursorBuddyClient {\n private endpoint: string\n private options: CursorBuddyClientOptions\n\n // Services\n private voiceCapture: VoiceCapturePort\n private audioPlayback: AudioPlaybackPort\n private browserSpeech: BrowserSpeechPort\n private liveTranscription: LiveTranscriptionPort\n private screenCapture: ScreenCapturePort\n private pointerController: PointerControllerPort\n private stateMachine: StateMachine\n private toolManager: ToolCallManager\n\n // State\n private liveTranscript = \"\"\n private transcript = \"\"\n private response = \"\"\n private error: Error | null = null\n private abortController: AbortController | null = null\n private speechProviderForTurn: \"browser\" | \"server\" | null = null\n private screenshotPromise: Promise<ScreenshotResult> | null = null\n private currentScreenshot: ScreenshotResult | null = null\n private pendingApprovalResolver: ((approved: boolean) => void) | null = null\n private playbackQueue: TTSPlaybackQueue | null = null\n\n // Cached snapshot for useSyncExternalStore\n private cachedSnapshot: CursorBuddySnapshot\n\n // Subscriptions\n private listeners = new Set<() => void>()\n\n constructor(\n endpoint: string,\n options: CursorBuddyClientOptions = {},\n services: CursorBuddyServices = {},\n ) {\n this.endpoint = endpoint\n this.options = options\n\n // Initialize services\n this.voiceCapture = services.voiceCapture ?? new VoiceCaptureService()\n this.audioPlayback = services.audioPlayback ?? new AudioPlaybackService()\n this.browserSpeech = services.browserSpeech ?? new BrowserSpeechService()\n this.liveTranscription =\n services.liveTranscription ?? new LiveTranscriptionService()\n this.screenCapture = services.screenCapture ?? new ScreenCaptureService()\n this.pointerController =\n services.pointerController ?? new PointerController()\n this.stateMachine = createStateMachine()\n\n // Initialize tool manager\n this.toolManager = new ToolCallManager(\n {\n onChange: () => this.notify(),\n onApprovalResponse: async () => {\n // Approvals are handled via pendingApprovalResolver\n },\n },\n options.toolDisplay,\n )\n\n // Initialize cached snapshot\n this.cachedSnapshot = this.buildSnapshot()\n\n // Wire up audio level\n this.voiceCapture.onLevel((level) => $audioLevel.set(level))\n this.liveTranscription.onPartial((text) => {\n if (this.liveTranscript === text) return\n this.liveTranscript = text\n this.notify()\n })\n\n // Wire up state machine changes\n this.stateMachine.subscribe(() => {\n this.options.onStateChange?.(this.stateMachine.getState())\n this.notify()\n })\n\n // Wire up pointer controller\n this.pointerController.subscribe(() => this.notify())\n }\n\n // === Public API ===\n\n startListening(): void {\n this.abort()\n\n // Clear UI state\n this.liveTranscript = \"\"\n this.transcript = \"\"\n this.response = \"\"\n this.error = null\n this.speechProviderForTurn = null\n this.currentScreenshot = null\n this.pointerController.release()\n this.toolManager.reset()\n\n // Transition state\n this.stateMachine.transition({ type: \"HOTKEY_PRESSED\" })\n this.notify()\n\n // Start mic\n this.abortController = new AbortController()\n const signal = this.abortController.signal\n\n // Capture screenshot in parallel\n this.screenshotPromise = this.screenCapture.capture()\n\n this.beginListeningSession(signal).catch((error) => {\n if (signal.aborted) return\n this.voiceCapture.dispose()\n this.liveTranscription.dispose()\n this.handleError(toError(error, \"Failed to start listening\"))\n })\n }\n\n async stopListening(): Promise<void> {\n if (this.stateMachine.getState() !== \"listening\") return\n\n this.stateMachine.transition({ type: \"HOTKEY_RELEASED\" })\n const signal = this.abortController?.signal\n let turnFailure: Error | null = null\n\n const failTurn = (error: Error) => {\n if (turnFailure || signal?.aborted) return\n turnFailure = error\n this.audioPlayback.stop()\n this.browserSpeech.stop()\n this.abortController?.abort()\n }\n\n try {\n const [audioBlob, browserTranscript] = await Promise.all([\n this.voiceCapture.stop(),\n this.stopLiveTranscription(),\n ])\n\n // Get screenshot\n if (!this.screenshotPromise) {\n throw new Error(\"Screenshot was not started\")\n }\n this.currentScreenshot = await this.screenshotPromise\n\n if (turnFailure) throw turnFailure\n if (signal?.aborted) return\n\n // Resolve transcript\n const transcript = await this.resolveTranscript(\n browserTranscript,\n audioBlob,\n signal,\n )\n if (turnFailure) throw turnFailure\n if (signal?.aborted) return\n\n this.liveTranscript = \"\"\n this.transcript = transcript\n this.options.onTranscript?.(transcript)\n this.notify()\n\n this.prepareSpeechMode()\n\n // Build initial messages\n const history = $conversationHistory.get()\n const messages: ConversationMessage[] = [\n ...history,\n { role: \"user\", content: transcript },\n ]\n\n // Process chat with potential approval loop\n const { responseText, updatedMessages } = await this.processChatLoop(\n messages,\n this.currentScreenshot,\n signal,\n failTurn,\n )\n\n if (turnFailure) throw turnFailure\n if (signal?.aborted) return\n\n this.options.onResponse?.(responseText)\n\n // Wait for TTS to complete\n if (this.playbackQueue) {\n await this.playbackQueue.waitForCompletion()\n }\n\n if (turnFailure) throw turnFailure\n if (signal?.aborted) return\n\n // Update history\n $conversationHistory.set(updatedMessages)\n\n this.stateMachine.transition({ type: \"TTS_COMPLETE\" })\n } catch (err) {\n if (turnFailure) {\n this.handleError(turnFailure)\n return\n }\n if (signal?.aborted) return\n this.handleError(toError(err))\n }\n }\n\n setEnabled(enabled: boolean): void {\n $isEnabled.set(enabled)\n this.notify()\n }\n\n pointAt(x: number, y: number, label: string): void {\n this.pointerController.pointAt({ x, y, label })\n }\n\n dismissPointing(): void {\n this.pointerController.release()\n }\n\n reset(): void {\n this.abort()\n this.liveTranscript = \"\"\n this.transcript = \"\"\n this.response = \"\"\n this.error = null\n this.currentScreenshot = null\n this.pointerController.release()\n this.toolManager.reset()\n this.stateMachine.reset()\n this.notify()\n }\n\n updateCursorPosition(): void {\n this.pointerController.updateFollowPosition()\n }\n\n // Tool actions\n async approveToolCall(id: string): Promise<void> {\n if (this.pendingApprovalResolver) {\n this.pendingApprovalResolver(true)\n this.pendingApprovalResolver = null\n }\n await this.toolManager.approve(id)\n }\n\n async denyToolCall(id: string): Promise<void> {\n if (this.pendingApprovalResolver) {\n this.pendingApprovalResolver(false)\n this.pendingApprovalResolver = null\n }\n await this.toolManager.deny(id)\n }\n\n dismissToolCall(id: string): void {\n this.toolManager.dismiss(id)\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener)\n return () => this.listeners.delete(listener)\n }\n\n getSnapshot(): CursorBuddySnapshot {\n return this.cachedSnapshot\n }\n\n private buildSnapshot(): CursorBuddySnapshot {\n return {\n state: this.stateMachine.getState(),\n liveTranscript: this.liveTranscript,\n transcript: this.transcript,\n response: this.response,\n error: this.error,\n isPointing: this.pointerController.isPointing(),\n isEnabled: $isEnabled.get(),\n toolCalls: this.toolManager.getToolCalls(),\n activeToolCalls: this.toolManager.getActiveToolCalls(),\n pendingApproval: this.toolManager.getPendingApproval(),\n }\n }\n\n // === Private Methods ===\n\n private abort(): void {\n this.abortController?.abort()\n this.abortController = null\n this.screenshotPromise = null\n this.voiceCapture.dispose()\n this.liveTranscription.dispose()\n this.audioPlayback.stop()\n this.browserSpeech.stop()\n this.speechProviderForTurn = null\n this.pendingApprovalResolver = null\n this.toolManager.reset()\n $audioLevel.set(0)\n }\n\n /**\n * Process chat with approval loop.\n * Returns when the turn is complete (no pending approvals).\n */\n private async processChatLoop(\n messages: ConversationMessage[],\n screenshot: ScreenshotResult,\n signal: AbortSignal | undefined,\n onFailure: (error: Error) => void,\n ): Promise<{\n responseText: string\n updatedMessages: ConversationMessage[]\n }> {\n let currentMessages = [...messages]\n let fullResponseText = \"\"\n let hasStartedPlayback = false\n\n // Create playback queue\n this.playbackQueue = new TTSPlaybackQueue({\n onError: onFailure,\n onPlaybackStart: () => {\n if (!hasStartedPlayback) {\n hasStartedPlayback = true\n this.stateMachine.transition({ type: \"RESPONSE_STARTED\" })\n }\n },\n prepare: (text, currentSignal) =>\n this.prepareSpeechSegment(text, currentSignal),\n signal,\n })\n\n const shouldStreamSpeech = this.isSpeechStreamingEnabled()\n\n while (true) {\n if (signal?.aborted) break\n\n // Capture fresh screenshot for continuation requests\n let currentScreenshot = screenshot\n if (currentMessages.length > messages.length) {\n // This is a continuation - capture fresh screenshot\n currentScreenshot = await this.screenCapture.capture()\n }\n\n // Fetch chat stream\n const response = await this.fetchChatStream(\n currentMessages,\n currentScreenshot,\n signal,\n )\n\n // Process the stream\n const { responseText, requiresApprovalContinuation, pendingApproval } =\n await this.consumeStream(\n response,\n currentScreenshot,\n shouldStreamSpeech,\n signal,\n )\n\n fullResponseText = responseText\n this.response = responseText\n this.notify()\n\n // Add assistant message to history\n currentMessages = [\n ...currentMessages,\n { role: \"assistant\", content: responseText },\n ]\n\n if (!requiresApprovalContinuation || !pendingApproval) {\n // No approval needed, turn complete\n break\n }\n\n // Pause TTS for approval\n this.playbackQueue.pauseAfterCurrent()\n\n // Wait for user approval\n const approved = await this.waitForApproval()\n\n // Resume TTS\n this.playbackQueue.resume()\n\n // Add approval response to messages\n currentMessages = [\n ...currentMessages,\n {\n role: \"tool\",\n content: [\n {\n type: \"tool-approval-response\" as const,\n approvalId: pendingApproval.approvalId,\n approved,\n },\n ],\n },\n ]\n\n // Continue the loop with updated messages\n }\n\n return {\n responseText: fullResponseText,\n updatedMessages: currentMessages,\n }\n }\n\n private async fetchChatStream(\n messages: ConversationMessage[],\n screenshot: ScreenshotResult,\n signal: AbortSignal | undefined,\n ): Promise<Response> {\n const response = await fetch(`${this.endpoint}/chat`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n messages,\n screenshot: screenshot.imageData,\n capture: {\n width: screenshot.width,\n height: screenshot.height,\n },\n domSnapshot: screenshot.domSnapshot,\n }),\n signal,\n })\n\n if (!response.ok) {\n throw new Error(await readErrorMessage(response, \"Chat request failed\"))\n }\n\n return response\n }\n\n private async consumeStream(\n response: Response,\n screenshot: ScreenshotResult,\n shouldStreamSpeech: boolean,\n signal: AbortSignal | undefined,\n ): Promise<{\n responseText: string\n requiresApprovalContinuation: boolean\n pendingApproval?: {\n approvalId: string\n toolCallId: string\n toolName: string\n args: unknown\n }\n }> {\n const reader = response.body?.getReader()\n if (!reader) throw new Error(\"No response body\")\n\n const decoder = new TextDecoder()\n const state: { pointToolCall: PointToolInput | null } = {\n pointToolCall: null,\n }\n\n const processor = new StreamProcessor({\n onTextDelta: () => {\n // Text is accumulated in the processor\n },\n onSpeechSegment: (text) => {\n if (shouldStreamSpeech && this.playbackQueue) {\n this.playbackQueue.enqueue(text)\n }\n },\n onToolCall: (event) => {\n // Check if it's the point tool\n if (event.toolName === \"point\") {\n const input = event.args as { elementId?: number; label?: string }\n if (\n input &&\n typeof input.elementId === \"number\" &&\n typeof input.label === \"string\"\n ) {\n state.pointToolCall = {\n elementId: input.elementId,\n label: input.label,\n }\n }\n } else {\n // Regular tool - add to manager\n this.toolManager.handleToolCall(event)\n this.options.onToolCall?.({\n id: event.toolCallId,\n toolName: event.toolName,\n args: event.args,\n })\n }\n },\n onApprovalRequest: (event) => {\n this.toolManager.handleApprovalRequest(event)\n },\n onToolResult: (event) => {\n const toolCall = this.toolManager.getToolCall(event.toolCallId)\n this.toolManager.handleToolResult(event)\n if (toolCall) {\n this.options.onToolResult?.({\n id: event.toolCallId,\n toolName: toolCall.toolName,\n result: event.result,\n })\n }\n },\n onToolError: (event) => {\n this.toolManager.handleToolError(event)\n },\n onFinish: () => {\n // Stream finished\n },\n onError: (error) => {\n throw new Error(error)\n },\n })\n\n // Read the stream\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n\n const chunk = decoder.decode(value, { stream: true })\n processor.processChunk(chunk)\n\n // Update visible response\n this.response = processor.getResponseText()\n this.notify()\n }\n\n // Process any remaining data\n const trailingChunk = decoder.decode()\n if (trailingChunk) {\n processor.processChunk(trailingChunk)\n }\n\n const result = processor.finish()\n\n // Handle point tool\n if (state.pointToolCall !== null) {\n const pointCall = state.pointToolCall\n const element = screenshot.elementRegistry.get(pointCall.elementId)\n if (element) {\n const rect = element.getBoundingClientRect()\n const x = Math.round(rect.left + rect.width / 2)\n const y = Math.round(rect.top + rect.height / 2)\n const target: PointingTarget = {\n x,\n y,\n label: pointCall.label,\n }\n this.options.onPoint?.(target)\n this.pointerController.pointAt(target)\n }\n }\n\n // Queue full response if not streaming speech\n if (!shouldStreamSpeech && this.playbackQueue) {\n this.playbackQueue.enqueue(result.responseText)\n }\n\n return result\n }\n\n private waitForApproval(): Promise<boolean> {\n return new Promise((resolve) => {\n this.pendingApprovalResolver = resolve\n })\n }\n\n private async transcribe(blob: Blob, signal?: AbortSignal): Promise<string> {\n const formData = new FormData()\n formData.append(\"audio\", blob, \"recording.wav\")\n\n const response = await fetch(`${this.endpoint}/transcribe`, {\n method: \"POST\",\n body: formData,\n signal,\n })\n\n if (!response.ok) {\n throw new Error(await readErrorMessage(response, \"Transcription failed\"))\n }\n\n const data = (await response.json()) as { text: string }\n return data.text\n }\n\n private async synthesizeSpeech(\n text: string,\n signal?: AbortSignal,\n ): Promise<Blob> {\n const response = await fetch(`${this.endpoint}/tts`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ text }),\n signal,\n })\n\n if (!response.ok) {\n throw new Error(await readErrorMessage(response, \"TTS request failed\"))\n }\n\n return response.blob()\n }\n\n private prepareSpeechMode(): void {\n const speechMode = this.getSpeechMode()\n\n if (speechMode === \"browser\" && !this.browserSpeech.isAvailable()) {\n throw new Error(\"Browser speech is not supported\")\n }\n\n if (speechMode === \"server\") {\n this.speechProviderForTurn = \"server\"\n return\n }\n\n if (speechMode === \"browser\") {\n this.speechProviderForTurn = \"browser\"\n return\n }\n\n this.speechProviderForTurn = this.browserSpeech.isAvailable()\n ? \"browser\"\n : \"server\"\n }\n\n private async prepareSpeechSegment(\n text: string,\n signal?: AbortSignal,\n ): Promise<SpeechPlaybackTask> {\n switch (this.getSpeechMode()) {\n case \"server\":\n return this.prepareServerSpeechTask(text, signal)\n case \"browser\":\n return this.prepareBrowserSpeechTask(text, signal)\n default:\n return this.prepareAutoSpeechTask(text, signal)\n }\n }\n\n private async prepareServerSpeechTask(\n text: string,\n signal?: AbortSignal,\n ): Promise<SpeechPlaybackTask> {\n const blob = await this.synthesizeSpeech(text, signal)\n return () => this.audioPlayback.play(blob, signal)\n }\n\n private async prepareBrowserSpeechTask(\n _text: string,\n signal?: AbortSignal,\n ): Promise<SpeechPlaybackTask> {\n return () => this.browserSpeech.speak(_text, signal)\n }\n\n private async prepareAutoSpeechTask(\n text: string,\n signal?: AbortSignal,\n ): Promise<SpeechPlaybackTask> {\n if (this.getAutoSpeechProvider() === \"server\") {\n return this.prepareServerSpeechTask(text, signal)\n }\n\n return async () => {\n if (this.getAutoSpeechProvider() === \"server\") {\n const fallback = await this.prepareServerSpeechTask(text, signal)\n await fallback()\n return\n }\n\n try {\n await this.browserSpeech.speak(text, signal)\n } catch {\n if (signal?.aborted) return\n this.speechProviderForTurn = \"server\"\n const fallback = await this.prepareServerSpeechTask(text, signal)\n await fallback()\n }\n }\n }\n\n private getAutoSpeechProvider(): \"browser\" | \"server\" {\n if (this.speechProviderForTurn) {\n return this.speechProviderForTurn\n }\n\n this.speechProviderForTurn = this.browserSpeech.isAvailable()\n ? \"browser\"\n : \"server\"\n\n return this.speechProviderForTurn\n }\n\n private handleError(err: Error): void {\n this.liveTranscript = \"\"\n this.error = err\n this.stateMachine.transition({ type: \"ERROR\", error: err })\n this.options.onError?.(err)\n this.notify()\n }\n\n private getTranscriptionMode(): CursorBuddyMediaMode {\n return this.options.transcription?.mode ?? \"auto\"\n }\n\n private getSpeechMode(): CursorBuddyMediaMode {\n return this.options.speech?.mode ?? \"server\"\n }\n\n private isSpeechStreamingEnabled(): boolean {\n return this.options.speech?.allowStreaming ?? false\n }\n\n private shouldAttemptBrowserTranscription(): boolean {\n return this.getTranscriptionMode() !== \"server\"\n }\n\n private isBrowserTranscriptionRequired(): boolean {\n return this.getTranscriptionMode() === \"browser\"\n }\n\n private async beginListeningSession(signal: AbortSignal): Promise<void> {\n const shouldAttemptBrowser = this.shouldAttemptBrowserTranscription()\n const isBrowserTranscriptionAvailable =\n shouldAttemptBrowser && this.liveTranscription.isAvailable()\n\n if (shouldAttemptBrowser && !isBrowserTranscriptionAvailable) {\n if (this.isBrowserTranscriptionRequired()) {\n throw new Error(\"Browser transcription is not supported\")\n }\n }\n\n const [voiceCaptureResult, browserTranscriptionResult] =\n await Promise.allSettled([\n this.voiceCapture.start(),\n isBrowserTranscriptionAvailable\n ? this.liveTranscription.start()\n : Promise.resolve(undefined),\n ])\n\n if (signal.aborted) return\n\n if (voiceCaptureResult.status === \"rejected\") {\n throw toError(voiceCaptureResult.reason, \"Failed to start microphone\")\n }\n\n if (\n browserTranscriptionResult.status === \"rejected\" &&\n this.isBrowserTranscriptionRequired()\n ) {\n throw toError(\n browserTranscriptionResult.reason,\n \"Browser transcription failed to start\",\n )\n }\n\n if (browserTranscriptionResult.status === \"rejected\") {\n this.liveTranscription.dispose()\n }\n }\n\n private async stopLiveTranscription(): Promise<string> {\n if (\n !this.shouldAttemptBrowserTranscription() ||\n !this.liveTranscription.isAvailable()\n ) {\n return \"\"\n }\n\n try {\n return await this.liveTranscription.stop()\n } catch (error) {\n if (this.isBrowserTranscriptionRequired()) {\n throw toError(error, \"Browser transcription failed\")\n }\n return \"\"\n }\n }\n\n private async resolveTranscript(\n browserTranscript: string,\n audioBlob: Blob,\n signal?: AbortSignal,\n ): Promise<string> {\n const normalizedBrowserTranscript = browserTranscript.trim()\n if (normalizedBrowserTranscript) {\n return normalizedBrowserTranscript\n }\n\n if (this.getTranscriptionMode() === \"browser\") {\n throw new Error(\n \"Browser transcription did not produce a final transcript\",\n )\n }\n\n return this.transcribe(audioBlob, signal)\n }\n\n private notify(): void {\n this.cachedSnapshot = this.buildSnapshot()\n this.listeners.forEach((listener) => listener())\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ import { a as CursorBuddySnapshot, i as CursorBuddyServices, n as CursorBuddyClientOptions } from "./types-BU0Gegg2.mjs";
2
+
3
+ //#region src/core/client.d.ts
4
+ /**
5
+ * Framework-agnostic client for cursor buddy voice interactions.
6
+ *
7
+ * Manages the complete voice interaction flow:
8
+ * idle -> listening -> processing -> responding -> idle
9
+ *
10
+ * Supports:
11
+ * - Interruption via hotkey
12
+ * - Tool call display with approval flow
13
+ * - Point tool for cursor movement
14
+ */
15
+ declare class CursorBuddyClient {
16
+ private endpoint;
17
+ private options;
18
+ private voiceCapture;
19
+ private audioPlayback;
20
+ private browserSpeech;
21
+ private liveTranscription;
22
+ private screenCapture;
23
+ private pointerController;
24
+ private stateMachine;
25
+ private toolManager;
26
+ private liveTranscript;
27
+ private transcript;
28
+ private response;
29
+ private error;
30
+ private abortController;
31
+ private speechProviderForTurn;
32
+ private screenshotPromise;
33
+ private currentScreenshot;
34
+ private pendingApprovalResolver;
35
+ private playbackQueue;
36
+ private cachedSnapshot;
37
+ private listeners;
38
+ constructor(endpoint: string, options?: CursorBuddyClientOptions, services?: CursorBuddyServices);
39
+ startListening(): void;
40
+ stopListening(): Promise<void>;
41
+ setEnabled(enabled: boolean): void;
42
+ pointAt(x: number, y: number, label: string): void;
43
+ dismissPointing(): void;
44
+ reset(): void;
45
+ updateCursorPosition(): void;
46
+ approveToolCall(id: string): Promise<void>;
47
+ denyToolCall(id: string): Promise<void>;
48
+ dismissToolCall(id: string): void;
49
+ subscribe(listener: () => void): () => void;
50
+ getSnapshot(): CursorBuddySnapshot;
51
+ private buildSnapshot;
52
+ private abort;
53
+ /**
54
+ * Process chat with approval loop.
55
+ * Returns when the turn is complete (no pending approvals).
56
+ */
57
+ private processChatLoop;
58
+ private fetchChatStream;
59
+ private consumeStream;
60
+ private waitForApproval;
61
+ private transcribe;
62
+ private synthesizeSpeech;
63
+ private prepareSpeechMode;
64
+ private prepareSpeechSegment;
65
+ private prepareServerSpeechTask;
66
+ private prepareBrowserSpeechTask;
67
+ private prepareAutoSpeechTask;
68
+ private getAutoSpeechProvider;
69
+ private handleError;
70
+ private getTranscriptionMode;
71
+ private getSpeechMode;
72
+ private isSpeechStreamingEnabled;
73
+ private shouldAttemptBrowserTranscription;
74
+ private isBrowserTranscriptionRequired;
75
+ private beginListeningSession;
76
+ private stopLiveTranscription;
77
+ private resolveTranscript;
78
+ private notify;
79
+ }
80
+ //#endregion
81
+ export { CursorBuddyClient as t };
82
+ //# sourceMappingURL=client-DoqSfCbo.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-DoqSfCbo.d.mts","names":[],"sources":["../src/core/client.ts"],"mappings":";;;;AA+DA;;;;;;;;;;cAAa,iBAAA;EAAA,QACH,QAAA;EAAA,QACA,OAAA;EAAA,QAGA,YAAA;EAAA,QACA,aAAA;EAAA,QACA,aAAA;EAAA,QACA,iBAAA;EAAA,QACA,aAAA;EAAA,QACA,iBAAA;EAAA,QACA,YAAA;EAAA,QACA,WAAA;EAAA,QAGA,cAAA;EAAA,QACA,UAAA;EAAA,QACA,QAAA;EAAA,QACA,KAAA;EAAA,QACA,eAAA;EAAA,QACA,qBAAA;EAAA,QACA,iBAAA;EAAA,QACA,iBAAA;EAAA,QACA,uBAAA;EAAA,QACA,aAAA;EAAA,QAGA,cAAA;EAAA,QAGA,SAAA;cAGN,QAAA,UACA,OAAA,GAAS,wBAAA,EACT,QAAA,GAAU,mBAAA;EAkDZ,cAAA,CAAA;EAgCM,aAAA,CAAA,GAAiB,OAAA;EAwFvB,UAAA,CAAW,OAAA;EAKX,OAAA,CAAQ,CAAA,UAAW,CAAA,UAAW,KAAA;EAI9B,eAAA,CAAA;EAIA,KAAA,CAAA;EAaA,oBAAA,CAAA;EAKM,eAAA,CAAgB,EAAA,WAAa,OAAA;EAQ7B,YAAA,CAAa,EAAA,WAAa,OAAA;EAQhC,eAAA,CAAgB,EAAA;EAIhB,SAAA,CAAU,QAAA;EAKV,WAAA,CAAA,GAAe,mBAAA;EAAA,QAIP,aAAA;EAAA,QAiBA,KAAA;EApER;;;;EAAA,QAsFc,eAAA;EAAA,QAuGA,eAAA;EAAA,QA2BA,aAAA;EAAA,QAgIN,eAAA;EAAA,QAMM,UAAA;EAAA,QAkBA,gBAAA;EAAA,QAkBN,iBAAA;EAAA,QAsBM,oBAAA;EAAA,QAcA,uBAAA;EAAA,QAQA,wBAAA;EAAA,QAOA,qBAAA;EAAA,QA0BN,qBAAA;EAAA,QAYA,WAAA;EAAA,QAQA,oBAAA;EAAA,QAIA,aAAA;EAAA,QAIA,wBAAA;EAAA,QAIA,iCAAA;EAAA,QAIA,8BAAA;EAAA,QAIM,qBAAA;EAAA,QAwCA,qBAAA;EAAA,QAkBA,iBAAA;EAAA,QAmBN,MAAA;AAAA"}
package/dist/index.d.mts CHANGED
@@ -1,3 +1,4 @@
1
- import { a as CursorBuddySnapshot, f as VoiceEvent, i as CursorBuddyMediaMode, l as Point, n as BrowserSpeechPort, o as CursorBuddySpeechConfig, p as VoiceState, r as CursorBuddyClientOptions, s as CursorBuddyTranscriptionConfig, t as CursorBuddyClient, u as PointingTarget } from "./client-sjVVGYPU.mjs";
2
- import { n as pointTool, t as PointToolInput } from "./point-tool-l3FewgM9.mjs";
1
+ import { a as CursorBuddySnapshot, f as VoiceEvent, l as Point, n as CursorBuddyClientOptions, o as CursorBuddySpeechConfig, p as VoiceState, r as CursorBuddyMediaMode, s as CursorBuddyTranscriptionConfig, t as BrowserSpeechPort, u as PointingTarget } from "./types-BU0Gegg2.mjs";
2
+ import { t as CursorBuddyClient } from "./client-DoqSfCbo.mjs";
3
+ import { n as pointTool, t as PointToolInput } from "./point-tool-B_s8op--.mjs";
3
4
  export { type BrowserSpeechPort, CursorBuddyClient, type CursorBuddyClientOptions, type CursorBuddyMediaMode, type CursorBuddySnapshot, type CursorBuddySpeechConfig, type CursorBuddyTranscriptionConfig, type Point, type PointToolInput, type PointingTarget, type VoiceEvent, type VoiceState, pointTool };
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { t as CursorBuddyClient } from "./client-CliXcNch.mjs";
1
+ import { t as CursorBuddyClient } from "./client-D7kFGsuH.mjs";
2
2
  import { t as pointTool } from "./point-tool-DZJmhD8e.mjs";
3
3
  export { CursorBuddyClient, pointTool };
@@ -1,17 +1,11 @@
1
1
  import * as _$ai from "ai";
2
2
  import { z } from "zod";
3
3
 
4
- //#region src/shared/point-tool.d.ts
4
+ //#region src/core/tools/point-tool.d.ts
5
5
  declare const pointToolInputSchema: z.ZodObject<{
6
6
  elementId: z.ZodNumber;
7
7
  label: z.ZodString;
8
- }, "strip", z.ZodTypeAny, {
9
- elementId: number;
10
- label: string;
11
- }, {
12
- elementId: number;
13
- label: string;
14
- }>;
8
+ }, z.core.$strip>;
15
9
  type PointToolInput = z.infer<typeof pointToolInputSchema>;
16
10
  declare const pointTool: _$ai.Tool<{
17
11
  elementId: number;
@@ -19,4 +13,4 @@ declare const pointTool: _$ai.Tool<{
19
13
  }, string>;
20
14
  //#endregion
21
15
  export { pointTool as n, PointToolInput as t };
22
- //# sourceMappingURL=point-tool-l3FewgM9.d.mts.map
16
+ //# sourceMappingURL=point-tool-B_s8op--.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"point-tool-B_s8op--.d.mts","names":[],"sources":["../src/core/tools/point-tool.ts"],"mappings":";;;;cAGa,oBAAA,EAAoB,CAAA,CAAA,SAAA;;;;KAmBrB,cAAA,GAAiB,CAAA,CAAE,KAAA,QAAa,oBAAA;AAAA,cAE/B,SAAA,EAcX,IAAA,CAdoB,IAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"point-tool-DZJmhD8e.mjs","names":[],"sources":["../src/shared/point-tool.ts"],"sourcesContent":["import { tool } from \"ai\"\nimport { z } from \"zod\"\n\nexport const pointToolInputSchema = z.object({\n elementId: z\n .number()\n .int()\n .min(1)\n .describe(\n \"The element ID from the DOM snapshot (e.g., @12 becomes elementId: 12). \" +\n \"Each element in the DOM snapshot has an @X identifier - use that number here.\",\n ),\n\n label: z\n .string()\n .min(1)\n .max(24)\n .describe(\n \"A very short label for the pointer bubble, ideally 2 to 4 words.\",\n ),\n})\n\nexport type PointToolInput = z.infer<typeof pointToolInputSchema>\n\nexport const pointTool = tool({\n description:\n \"Visually point at an element on the user's screen. \" +\n \"Use this tool when the user asks you to locate, indicate, highlight, or show a specific visible target on screen. \" +\n \"Each element in the visible DOM snapshot has an @X identifier (like @5, @12, @34). \" +\n \"Find the element you want to point at in the DOM snapshot, note its @X ID, and pass that ID here. \" +\n \"Do not describe a pointing action in plain text instead of calling this tool. \" +\n \"Call this tool at most once per response, and only after your spoken reply.\",\n\n inputSchema: pointToolInputSchema,\n\n execute: async (params) => {\n return `Pointed at \"${params.label}\" (element @${params.elementId}) on the user's screen.`\n },\n})\n"],"mappings":";;AAwBA,MAAa,YAAY,KAAK;CAC5B,aACE;CAOF,aA9BkC,EAAE,OAAO;EAC3C,WAAW,EACR,QAAQ,CACR,KAAK,CACL,IAAI,EAAE,CACN,SACC,wJAED;EAEH,OAAO,EACJ,QAAQ,CACR,IAAI,EAAE,CACN,IAAI,GAAG,CACP,SACC,mEACD;EACJ,CAAC;CAeA,SAAS,OAAO,WAAW;AACzB,SAAO,eAAe,OAAO,MAAM,cAAc,OAAO,UAAU;;CAErE,CAAC"}
1
+ {"version":3,"file":"point-tool-DZJmhD8e.mjs","names":[],"sources":["../src/core/tools/point-tool.ts"],"sourcesContent":["import { tool } from \"ai\"\nimport { z } from \"zod\"\n\nexport const pointToolInputSchema = z.object({\n elementId: z\n .number()\n .int()\n .min(1)\n .describe(\n \"The element ID from the DOM snapshot (e.g., @12 becomes elementId: 12). \" +\n \"Each element in the DOM snapshot has an @X identifier - use that number here.\",\n ),\n\n label: z\n .string()\n .min(1)\n .max(24)\n .describe(\n \"A very short label for the pointer bubble, ideally 2 to 4 words.\",\n ),\n})\n\nexport type PointToolInput = z.infer<typeof pointToolInputSchema>\n\nexport const pointTool = tool({\n description:\n \"Visually point at an element on the user's screen. \" +\n \"Use this tool when the user asks you to locate, indicate, highlight, or show a specific visible target on screen. \" +\n \"Each element in the visible DOM snapshot has an @X identifier (like @5, @12, @34). \" +\n \"Find the element you want to point at in the DOM snapshot, note its @X ID, and pass that ID here. \" +\n \"Do not describe a pointing action in plain text instead of calling this tool. \" +\n \"Call this tool at most once per response, and only after your spoken reply.\",\n\n inputSchema: pointToolInputSchema,\n\n execute: async (params) => {\n return `Pointed at \"${params.label}\" (element @${params.elementId}) on the user's screen.`\n },\n})\n"],"mappings":";;AAwBA,MAAa,YAAY,KAAK;CAC5B,aACE;CAOF,aA9BkC,EAAE,OAAO;EAC3C,WAAW,EACR,QAAQ,CACR,KAAK,CACL,IAAI,EAAE,CACN,SACC,wJAED;EAEH,OAAO,EACJ,QAAQ,CACR,IAAI,EAAE,CACN,IAAI,GAAG,CACP,SACC,mEACD;EACJ,CAAC;CAeA,SAAS,OAAO,WAAW;AACzB,SAAO,eAAe,OAAO,MAAM,cAAc,OAAO,UAAU;;CAErE,CAAC"}