@omnivoice/page-agent 0.6.1 → 0.6.2

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.
@@ -194,6 +194,7 @@ export declare class RealtimeEngine implements VoiceEngine {
194
194
  snapshot(): VoiceSnapshot;
195
195
  setMuted(muted: boolean): void;
196
196
  submit(_text: string): void;
197
+ sendFunctionResult(callId: string, result: Record<string, unknown>): void;
197
198
  events(): AsyncIterable<VoiceEvent>;
198
199
  private lease;
199
200
  private onMessage;
@@ -347,6 +348,8 @@ export declare interface VoiceEngine {
347
348
  snapshot(): VoiceSnapshot;
348
349
  /** Muta ou desmuta o microfone. */
349
350
  setMuted(muted: boolean): void;
351
+ /** Retorna ao servidor o resultado de uma função executada no navegador. */
352
+ sendFunctionResult?(callId: string, result: Record<string, unknown>): void;
350
353
  /** Submete texto como se tivesse sido ouvido (se TEXT_INPUT). */
351
354
  submit(text: string): void;
352
355
  /** Itera eventos públicos até fecha. */
@@ -382,6 +385,13 @@ export declare type VoiceEvent = {
382
385
  confidence?: number;
383
386
  channel?: InputChannel;
384
387
  };
388
+ } | {
389
+ kind: "function.call";
390
+ data: {
391
+ callId: string;
392
+ name: string;
393
+ arguments: Record<string, unknown>;
394
+ };
385
395
  } | {
386
396
  kind: "agent.started";
387
397
  data: {
@@ -435,6 +445,8 @@ export declare class VoiceSession implements VoiceSessionReader {
435
445
  enqueueInput(input: UserInput): void;
436
446
  /** Muta/desmuta o microfone. Sem efeito se mutações desabilitadas. */
437
447
  setMuted(muted: boolean): void;
448
+ /** Devolve o resultado de uma function/tool call ao agente Realtime que a solicitou. */
449
+ sendFunctionResult(callId: string, result: Record<string, unknown>): void;
438
450
  /** Eventos públicos a partir de agora, até a sessão fechar. Cada chamada tem a sua fila. */
439
451
  events(): AsyncIterable<VoiceEvent>;
440
452
  /**
@@ -597,6 +597,9 @@ class RealtimeEngine {
597
597
  submit(_text) {
598
598
  throw new VoiceError("UNSUPPORTED", "o motor realtime não aceita texto digitado: fale pelo microfone");
599
599
  }
600
+ sendFunctionResult(callId, result) {
601
+ this.sendJson({ type: "function.result", call_id: callId, result });
602
+ }
600
603
  events() {
601
604
  return this.hub.subscribe();
602
605
  }
@@ -649,6 +652,13 @@ class RealtimeEngine {
649
652
  case "state":
650
653
  this.onState(message);
651
654
  return;
655
+ case "function.call": {
656
+ const callId = typeof message["call_id"] === "string" ? message["call_id"] : "";
657
+ const name = typeof message["name"] === "string" ? message["name"] : "";
658
+ const args = asRecord(message["arguments"]);
659
+ if (callId && name && args) this.emit({ kind: "function.call", data: { callId, name, arguments: args } });
660
+ return;
661
+ }
652
662
  case "mark": {
653
663
  const { id, label, offset } = message;
654
664
  if (typeof id === "number" && typeof label === "string" && typeof offset === "number") this.playback?.mark({ id, label, offset });
@@ -787,6 +797,7 @@ function createEngine(settings, deps = {}) {
787
797
  }
788
798
  const FORWARDED_EVENTS = /* @__PURE__ */ new Set([
789
799
  "user.started",
800
+ "function.call",
790
801
  "agent.started",
791
802
  "agent.finished",
792
803
  "sound.started",
@@ -869,6 +880,11 @@ class VoiceSession {
869
880
  this.context.muted = muted;
870
881
  this.engine?.setMuted(muted);
871
882
  }
883
+ /** Devolve o resultado de uma function/tool call ao agente Realtime que a solicitou. */
884
+ sendFunctionResult(callId, result) {
885
+ if (!this.allowMutations) return;
886
+ this.engine?.sendFunctionResult?.(callId, result);
887
+ }
872
888
  /** Eventos públicos a partir de agora, até a sessão fechar. Cada chamada tem a sua fila. */
873
889
  events() {
874
890
  return this.hub.subscribe();
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../src/voice/errors.ts","../../src/voice/browser-speech.ts","../../src/voice/events.ts","../../src/voice/types.ts","../../src/voice/local-engine.ts","../../src/voice/audio.ts","../../src/voice/config.ts","../../src/voice/realtime-engine.ts","../../src/voice/engines.ts","../../src/voice/session.ts","../../src/voice/bridge.ts"],"sourcesContent":["/** Erros da voz com um `code` estável para quem precisa decidir o que fazer (não depender do texto). */\n\nexport type VoiceErrorCode =\n | \"INVALID_SETTINGS\"\n | \"UNSUPPORTED\"\n | \"LEASE_FAILED\"\n | \"CONNECTION_FAILED\"\n | \"REFUSED\"\n | \"TIMEOUT\"\n | \"CLOSED\"\n | \"MICROPHONE\";\n\nexport class VoiceError extends Error {\n readonly code: VoiceErrorCode;\n\n constructor(code: VoiceErrorCode, message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = \"VoiceError\";\n this.code = code;\n }\n}\n","/**\n * Voz do próprio navegador (Web Speech API): reconhecimento e síntese locais, sem servidor de áudio.\n * Só toca no microfone quando `listen()` é chamado.\n */\n\nimport { VoiceError } from \"./errors.ts\";\n\nexport interface LocalVoice {\n /** O navegador oferece reconhecimento de fala. */\n readonly supportsInput: boolean;\n /** Escuta uma fala e resolve com a transcrição final; resolve `\"\"` quando ninguém falou. */\n listen(): Promise<string>;\n stopListening(): void;\n speak(text: string): void;\n stopSpeaking(): void;\n}\n\ninterface RecognitionResultLike {\n readonly isFinal: boolean;\n readonly 0?: { readonly transcript: string };\n}\n\ninterface RecognitionEventLike {\n readonly resultIndex: number;\n readonly results: { readonly length: number; readonly [index: number]: RecognitionResultLike | undefined };\n}\n\ninterface RecognitionLike {\n lang: string;\n continuous: boolean;\n interimResults: boolean;\n maxAlternatives: number;\n onresult: ((event: RecognitionEventLike) => void) | null;\n onerror: ((event: { readonly error?: string }) => void) | null;\n onend: (() => void) | null;\n start(): void;\n stop(): void;\n}\n\ntype RecognitionConstructor = new () => RecognitionLike;\n\nfunction recognitionConstructor(): RecognitionConstructor | undefined {\n const scope = globalThis as { SpeechRecognition?: RecognitionConstructor; webkitSpeechRecognition?: RecognitionConstructor };\n return scope.SpeechRecognition ?? scope.webkitSpeechRecognition;\n}\n\nexport function createBrowserVoice(language: string, listenTimeoutMs = 30_000): LocalVoice {\n const Recognition = recognitionConstructor();\n let active: RecognitionLike | undefined;\n\n return {\n get supportsInput(): boolean {\n return Recognition !== undefined;\n },\n\n listen(): Promise<string> {\n if (!Recognition) return Promise.reject(new VoiceError(\"UNSUPPORTED\", \"este navegador não oferece reconhecimento de fala\"));\n return new Promise<string>((resolve, reject) => {\n const recognition = new Recognition();\n active = recognition;\n let transcript = \"\";\n let settled = false;\n\n const finish = (outcome: () => void): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n if (active === recognition) active = undefined;\n outcome();\n };\n const timer = setTimeout(() => {\n try {\n recognition.stop();\n } catch {\n /* já parado */\n }\n finish(() => resolve(transcript.trim()));\n }, listenTimeoutMs);\n\n recognition.lang = language;\n recognition.continuous = false;\n recognition.interimResults = false;\n recognition.maxAlternatives = 1;\n recognition.onresult = (event) => {\n for (let i = event.resultIndex; i < event.results.length; i++) {\n const result = event.results[i];\n if (result?.isFinal) transcript = result[0]?.transcript ?? transcript;\n }\n };\n recognition.onerror = (event) => {\n const code = event.error ?? \"desconhecido\";\n if (code === \"no-speech\" || code === \"aborted\") {\n finish(() => resolve(transcript.trim()));\n return;\n }\n finish(() => reject(new VoiceError(\"MICROPHONE\", `erro de reconhecimento de voz: ${code}`)));\n };\n recognition.onend = () => finish(() => resolve(transcript.trim()));\n\n try {\n recognition.start();\n } catch (error) {\n finish(() => reject(new VoiceError(\"MICROPHONE\", \"não foi possível iniciar o reconhecimento\", { cause: error })));\n }\n });\n },\n\n stopListening(): void {\n try {\n active?.stop();\n } catch {\n /* já parado */\n }\n },\n\n speak(text: string): void {\n const synth = globalThis.speechSynthesis;\n const clean = text.trim();\n if (!synth || !clean) return;\n synth.cancel();\n const utterance = new SpeechSynthesisUtterance(clean);\n utterance.lang = language;\n synth.speak(utterance);\n },\n\n stopSpeaking(): void {\n globalThis.speechSynthesis?.cancel();\n },\n };\n}\n","/**\n * Distribuição de eventos para vários consumidores. Cada `subscribe()` recebe a sua própria fila,\n * então um consumidor lento não perde eventos nem rouba os dos outros. Sem dependência de Node.\n */\n\ntype Waiter<T> = (result: IteratorResult<T>) => void;\n\nexport class EventHub<T> {\n private readonly subscribers = new Set<{ queue: T[]; waiters: Waiter<T>[] }>();\n private closed = false;\n\n get isClosed(): boolean {\n return this.closed;\n }\n\n emit(event: T): void {\n if (this.closed) return;\n for (const subscriber of this.subscribers) {\n const waiter = subscriber.waiters.shift();\n if (waiter) waiter({ value: event, done: false });\n else subscriber.queue.push(event);\n }\n }\n\n /** Encerra todos os iteradores: quem espera recebe `done`, quem tem fila a consome antes. */\n close(): void {\n if (this.closed) return;\n this.closed = true;\n for (const subscriber of this.subscribers) {\n for (const waiter of subscriber.waiters.splice(0)) waiter({ value: undefined, done: true });\n }\n }\n\n subscribe(): AsyncIterable<T> {\n const subscriber = { queue: [] as T[], waiters: [] as Waiter<T>[] };\n this.subscribers.add(subscriber);\n const hub = this;\n return {\n [Symbol.asyncIterator](): AsyncIterator<T> {\n return {\n next(): Promise<IteratorResult<T>> {\n const queued = subscriber.queue.shift();\n if (queued !== undefined) return Promise.resolve({ value: queued, done: false });\n if (hub.closed) {\n hub.subscribers.delete(subscriber);\n return Promise.resolve({ value: undefined, done: true });\n }\n return new Promise((resolve) => subscriber.waiters.push(resolve));\n },\n return(): Promise<IteratorResult<T>> {\n hub.subscribers.delete(subscriber);\n return Promise.resolve({ value: undefined, done: true });\n },\n };\n },\n };\n }\n}\n","/**\n * Types compartilhados para a integração de voz.\n * Baseado no pattern Conversation + Engine de NoStopPageTest.\n */\n\n/** Modo de fala: local (modelos no device) ou realtime (OpenAI full-duplex). */\nexport type SpeechMode = \"local\" | \"realtime\";\n\n/** Canal ativo de entrada: voz (microfone) ou chat (texto). */\nexport type InputChannel = \"voice\" | \"chat\";\n\n/** Tipo de efeito observável após ação. */\nexport type EffectKind = \"notification\" | \"navigation\" | \"loading\";\n\n/** Resultado da observação de efeito. */\nexport type EffectOutcome =\n | { status: \"observed\"; kind: EffectKind; detail: string }\n | { status: \"unknown\"; detail: string };\n\n/** Uma mensagem no histórico de conversa. */\nexport type Message = {\n role: \"user\" | \"assistant\";\n content: string;\n timestamp: number; // milliseconds since epoch\n channel: InputChannel; // voz ou chat\n confidence?: number; // 0-1, para speech-to-text\n};\n\n/** Estado de um turno: agente escutando, processando, falando. */\nexport type TurnState = \"idle\" | \"listening\" | \"processing\" | \"speaking\";\n\n/** Snapshot do estado de voz para o painel. */\nexport type VoiceSnapshot = {\n mode: SpeechMode;\n channel: InputChannel;\n state: TurnState;\n muted: boolean;\n sessionId: string;\n messageCount: number;\n lastMessageTime?: number;\n error?: string;\n};\n\n/** Estado compartilhado entre voz e navegação. */\nexport type VoiceContext = {\n mode: SpeechMode;\n channel: InputChannel;\n turn: TurnState;\n sessionId: string;\n conversationHistory: Message[];\n muted: boolean;\n lastTurnTime?: number;\n};\n\n/** Evento emitido pela sessão de voz. */\nexport type VoiceEvent =\n | { kind: \"session.ready\"; data: VoiceSnapshot }\n | { kind: \"user.started\"; data: { channel: InputChannel } }\n | { kind: \"turn.heard\"; data: { text: string; confidence?: number; channel?: InputChannel } }\n | { kind: \"agent.started\"; data: { turn: TurnState } }\n | { kind: \"agent.finished\"; data: { text: string } }\n | { kind: \"sound.started\"; data: object }\n | { kind: \"speaker.idle\"; data: object }\n | { kind: \"channel.switched\"; data: { from: InputChannel; to: InputChannel } }\n | { kind: \"error\"; data: { message: string; code: string } };\n\n/** Capacidades oferecidas pela engine de voz. */\nexport const TEXT_INPUT = \"text_input\";\nexport const MUTE = \"mute\";\nexport const REPLAY = \"replay\";\n\nexport type Capability = typeof TEXT_INPUT | typeof MUTE | typeof REPLAY | string;\n\n/**\n * Protocol para uma engine de voz (Local ou Realtime).\n * Baseado no EngineLike de NoStopPageTest.\n */\nexport interface VoiceEngine {\n readonly capabilities: ReadonlySet<Capability>;\n\n /** Abre a engine (inicializa modelos, conexões, etc). */\n enter(): Promise<void>;\n\n /** Fecha a engine. */\n exit(exc?: Error): Promise<void>;\n\n /** Snapshot atual para o painel. */\n snapshot(): VoiceSnapshot;\n\n /** Muta ou desmuta o microfone. */\n setMuted(muted: boolean): void;\n\n /** Submete texto como se tivesse sido ouvido (se TEXT_INPUT). */\n submit(text: string): void;\n\n /** Itera eventos públicos até fecha. */\n events(): AsyncIterable<VoiceEvent>;\n}\n","/**\n * Motor local: reconhecimento e síntese de fala do próprio navegador. Não precisa de servidor, mas a\n * qualidade e a disponibilidade dependem do navegador. O microfone só abre em `enter()` (e só se a\n * sessão não começar mutada).\n */\n\nimport { createBrowserVoice, type LocalVoice } from \"./browser-speech.ts\";\nimport type { VoiceSettings } from \"./config.ts\";\nimport { VoiceError } from \"./errors.ts\";\nimport { EventHub } from \"./events.ts\";\nimport { MUTE, TEXT_INPUT, type Capability, type TurnState, type VoiceEngine, type VoiceEvent, type VoiceSnapshot } from \"./types.ts\";\n\nexport interface LocalDeps {\n voice?: LocalVoice;\n}\n\n/** Pausa entre escutas vazias, para não girar sem parar quando ninguém fala. */\nconst IDLE_PAUSE_MS = 250;\n\nexport class LocalEngine implements VoiceEngine {\n readonly capabilities: ReadonlySet<Capability> = new Set<Capability>([TEXT_INPUT, MUTE]);\n\n private readonly settings: VoiceSettings;\n private readonly deps: LocalDeps;\n private readonly hub = new EventHub<VoiceEvent>();\n private voice: LocalVoice | undefined;\n private muted: boolean;\n private running = false;\n private turn: TurnState = \"idle\";\n private lastError: string | undefined;\n private unmute: (() => void) | undefined;\n\n constructor(settings: VoiceSettings, deps: LocalDeps = {}) {\n this.settings = settings;\n this.deps = deps;\n this.muted = !settings.mic;\n }\n\n async enter(): Promise<void> {\n if (this.running) throw new VoiceError(\"CONNECTION_FAILED\", \"o motor local já foi aberto\");\n const voice = this.deps.voice ?? createBrowserVoice(this.settings.language);\n if (!voice.supportsInput && !this.muted) {\n throw new VoiceError(\"UNSUPPORTED\", \"este navegador não oferece reconhecimento de fala\");\n }\n this.voice = voice;\n this.running = true;\n this.turn = \"idle\";\n this.hub.emit({ kind: \"session.ready\", data: this.snapshot() });\n void this.listenLoop(voice);\n }\n\n async exit(): Promise<void> {\n this.running = false;\n this.turn = \"idle\";\n this.voice?.stopListening();\n this.voice?.stopSpeaking();\n this.unmute?.();\n this.hub.close();\n }\n\n snapshot(): VoiceSnapshot {\n return {\n mode: \"local\",\n channel: \"voice\",\n state: this.turn,\n muted: this.muted,\n sessionId: this.settings.sessionId,\n messageCount: 0,\n ...(this.lastError ? { error: this.lastError } : {}),\n };\n }\n\n setMuted(muted: boolean): void {\n if (this.muted === muted) return;\n this.muted = muted;\n if (muted) this.voice?.stopListening();\n else this.unmute?.();\n }\n\n submit(text: string): void {\n if (!this.running) throw new VoiceError(\"CLOSED\", \"o motor local está fechado\");\n const clean = text.trim();\n if (clean) this.hub.emit({ kind: \"turn.heard\", data: { text: clean, channel: \"chat\" } });\n }\n\n /** Fala um texto com a síntese do navegador (a resposta do agente, por exemplo). */\n speak(text: string): void {\n this.voice?.speak(text);\n }\n\n events(): AsyncIterable<VoiceEvent> {\n return this.hub.subscribe();\n }\n\n private waitUnmuted(): Promise<void> {\n return new Promise((resolve) => {\n this.unmute = () => {\n this.unmute = undefined;\n resolve();\n };\n });\n }\n\n private async listenLoop(voice: LocalVoice): Promise<void> {\n while (this.running) {\n if (this.muted) {\n this.turn = \"idle\";\n await this.waitUnmuted();\n continue;\n }\n try {\n this.turn = \"listening\";\n const text = await voice.listen();\n if (!this.running) return;\n if (this.muted) continue;\n if (text) {\n this.hub.emit({ kind: \"user.started\", data: { channel: \"voice\" } });\n this.hub.emit({ kind: \"turn.heard\", data: { text } });\n } else {\n await new Promise((resolve) => setTimeout(resolve, IDLE_PAUSE_MS));\n }\n } catch (error) {\n if (!this.running) return;\n // Permissão negada ou falta de suporte não se resolvem tentando de novo: parar e avisar.\n this.lastError = error instanceof Error ? error.message : \"erro de reconhecimento de voz\";\n this.hub.emit({\n kind: \"error\",\n data: { message: this.lastError, code: error instanceof VoiceError ? error.code : \"AUDIO_ERROR\" },\n });\n this.running = false;\n this.turn = \"idle\";\n this.hub.close();\n return;\n }\n }\n }\n}\n","/**\n * Áudio do navegador para o modo realtime: captura do microfone e reprodução da resposta.\n *\n * Nada aqui roda ao importar o módulo. O microfone só é pedido quando `startBrowserMicrophone`\n * é chamado, e isso só acontece dentro de `RealtimeEngine.enter()`.\n *\n * Formato do protocolo do servidor de áudio:\n * - captura: blocos de 512 amostras int16 a 16 kHz (32 ms), contados a partir de 0;\n * - reprodução: PCM int16 na taxa anunciada pelo servidor (`ready.rate`).\n */\n\nimport { VoiceError } from \"./errors.ts\";\n\nexport const CAPTURE_RATE = 16_000;\nexport const CAPTURE_BLOCK = 512;\n\nexport interface MicrophoneFrame {\n readonly block: number;\n readonly pcm: Int16Array;\n /** Instante (ms) no relógio do AudioContext em que o bloco ficou completo. `performance` não existe no escopo do AudioWorklet. */\n readonly at: number;\n}\n\nexport interface MicrophoneCapture {\n stop(): Promise<void>;\n}\n\n/** Abre o microfone e entrega blocos até `stop()`. */\nexport type MicrophoneFactory = (onFrame: (frame: MicrophoneFrame) => void) => Promise<MicrophoneCapture>;\n\nexport interface PlaybackMark {\n readonly id: number;\n readonly label: string;\n readonly offset: number;\n}\n\n/** Relato do reprodutor ao servidor: `reached`, `rendered` ou `underrun` (a forma que o servidor lê). */\nexport type PlaybackReport = Readonly<Record<string, unknown>> & { readonly type: string };\n\nexport interface AudioPlayback {\n push(pcm16: Int16Array): void;\n mark(mark: PlaybackMark): void;\n clear(): void;\n close(): Promise<void>;\n}\n\nexport type PlaybackFactory = (rate: number, onReport: (report: PlaybackReport) => void) => Promise<AudioPlayback>;\n\nconst CAPTURE_WORKLET = `\nclass OmnivoiceCapture extends AudioWorkletProcessor {\n constructor() {\n super();\n this.block = 0;\n this.samples = new Float32Array(${CAPTURE_BLOCK});\n this.used = 0;\n }\n process(inputs) {\n const input = inputs[0] && inputs[0][0];\n if (!input) return true;\n let offset = 0;\n while (offset < input.length) {\n const take = Math.min(input.length - offset, ${CAPTURE_BLOCK} - this.used);\n this.samples.set(input.subarray(offset, offset + take), this.used);\n this.used += take;\n offset += take;\n if (this.used === ${CAPTURE_BLOCK}) {\n const pcm = new Int16Array(${CAPTURE_BLOCK});\n for (let i = 0; i < ${CAPTURE_BLOCK}; i++) {\n pcm[i] = Math.max(-32768, Math.min(32767, Math.round(this.samples[i] * 32768)));\n }\n this.port.postMessage({ block: this.block++, pcm: pcm.buffer, at: currentTime * 1000 }, [pcm.buffer]);\n this.samples = new Float32Array(${CAPTURE_BLOCK});\n this.used = 0;\n }\n }\n return true;\n }\n}\nregisterProcessor(\"omnivoice-capture\", OmnivoiceCapture);\n`;\n\nconst PLAYBACK_WORKLET = `\nclass OmnivoicePlayback extends AudioWorkletProcessor {\n constructor() {\n super();\n this.queue = [];\n this.offset = 0;\n this.rendered = 0;\n this.marks = [];\n this.port.onmessage = (event) => {\n const message = event.data || {};\n if (message.type === \"audio\") this.queue.push(new Float32Array(message.samples));\n if (message.type === \"mark\") this.marks.push({ id: message.id, label: message.label, offset: message.offset });\n if (message.type === \"clear\") { this.queue = []; this.offset = 0; this.marks = []; }\n };\n }\n process(_inputs, outputs) {\n const out = outputs[0][0];\n out.fill(0);\n let written = 0;\n while (written < out.length && this.queue.length) {\n const current = this.queue[0];\n const take = Math.min(out.length - written, current.length - this.offset);\n out.set(current.subarray(this.offset, this.offset + take), written);\n written += take;\n this.offset += take;\n if (this.offset === current.length) { this.queue.shift(); this.offset = 0; }\n }\n const before = this.rendered;\n this.rendered += written;\n while (this.marks.length && this.marks[0].offset < this.rendered) {\n const mark = this.marks.shift();\n this.port.postMessage({ type: \"reached\", id: mark.id, label: mark.label, at: currentTime * 1000 });\n }\n if (this.rendered - before || written < out.length) {\n this.port.postMessage({ type: \"rendered\", samples: this.rendered, at: currentTime * 1000 });\n }\n if (written < out.length && this.queue.length === 0) {\n this.port.postMessage({ type: \"underrun\", samples: out.length - written });\n }\n return true;\n }\n}\nregisterProcessor(\"omnivoice-playback\", OmnivoicePlayback);\n`;\n\nasync function loadWorklet(context: AudioContext, source: string): Promise<void> {\n const url = URL.createObjectURL(new Blob([source], { type: \"text/javascript\" }));\n try {\n await context.audioWorklet.addModule(url);\n } finally {\n URL.revokeObjectURL(url);\n }\n}\n\nfunction requireAudioSupport(): void {\n if (typeof AudioContext === \"undefined\" || typeof AudioWorkletNode === \"undefined\") {\n throw new VoiceError(\"UNSUPPORTED\", \"este navegador não oferece AudioContext/AudioWorklet\");\n }\n}\n\nfunction isCaptureMessage(data: unknown): data is { block: number; pcm: ArrayBuffer; at: number } {\n if (typeof data !== \"object\" || data === null) return false;\n const message = data as Record<string, unknown>;\n return typeof message[\"block\"] === \"number\" && message[\"pcm\"] instanceof ArrayBuffer && typeof message[\"at\"] === \"number\";\n}\n\nfunction isPlaybackReport(data: unknown): data is PlaybackReport {\n return typeof data === \"object\" && data !== null && typeof (data as Record<string, unknown>)[\"type\"] === \"string\";\n}\n\n/** Microfone real: `getUserMedia` + AudioWorklet a 16 kHz. Só pede permissão quando chamado. */\nexport const startBrowserMicrophone: MicrophoneFactory = async (onFrame) => {\n requireAudioSupport();\n if (!navigator.mediaDevices?.getUserMedia) {\n throw new VoiceError(\"UNSUPPORTED\", \"este navegador não oferece getUserMedia (precisa de https ou localhost)\");\n }\n\n const context = new AudioContext({ sampleRate: CAPTURE_RATE, latencyHint: \"interactive\" });\n let stream: MediaStream | undefined;\n try {\n await loadWorklet(context, CAPTURE_WORKLET);\n stream = await navigator.mediaDevices.getUserMedia({\n audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true },\n });\n const source = context.createMediaStreamSource(stream);\n const node = new AudioWorkletNode(context, \"omnivoice-capture\");\n node.port.onmessage = (event: MessageEvent<unknown>) => {\n if (!isCaptureMessage(event.data)) return;\n onFrame({ block: event.data.block, pcm: new Int16Array(event.data.pcm), at: event.data.at });\n };\n source.connect(node);\n node.connect(context.destination);\n await context.resume();\n\n const tracks = stream.getTracks();\n return {\n async stop(): Promise<void> {\n node.port.onmessage = null;\n source.disconnect();\n node.disconnect();\n for (const track of tracks) track.stop();\n await context.close();\n },\n };\n } catch (error) {\n stream?.getTracks().forEach((track) => track.stop());\n await context.close().catch(() => undefined);\n if (error instanceof VoiceError) throw error;\n throw new VoiceError(\"MICROPHONE\", error instanceof Error ? error.message : \"não foi possível abrir o microfone\", {\n cause: error,\n });\n }\n};\n\n/** Reprodutor real: AudioWorklet na taxa pedida; devolve ao servidor onde a reprodução está. */\nexport const startBrowserPlayback: PlaybackFactory = async (rate, onReport) => {\n requireAudioSupport();\n const context = new AudioContext({ sampleRate: rate, latencyHint: \"interactive\" });\n try {\n await loadWorklet(context, PLAYBACK_WORKLET);\n const node = new AudioWorkletNode(context, \"omnivoice-playback\");\n node.port.onmessage = (event: MessageEvent<unknown>) => {\n if (isPlaybackReport(event.data)) onReport(event.data);\n };\n node.connect(context.destination);\n await context.resume();\n\n return {\n push(pcm16: Int16Array): void {\n const samples = new Float32Array(pcm16.length);\n for (let i = 0; i < pcm16.length; i++) samples[i] = (pcm16[i] ?? 0) / 32768;\n node.port.postMessage({ type: \"audio\", samples: samples.buffer }, [samples.buffer]);\n },\n mark(mark: PlaybackMark): void {\n node.port.postMessage({ type: \"mark\", id: mark.id, label: mark.label, offset: mark.offset });\n },\n clear(): void {\n node.port.postMessage({ type: \"clear\" });\n },\n async close(): Promise<void> {\n node.port.onmessage = null;\n node.disconnect();\n await context.close();\n },\n };\n } catch (error) {\n await context.close().catch(() => undefined);\n throw error;\n }\n};\n","/**\n * Configuração da voz no cliente. Só o que o navegador usa: onde está o servidor de áudio (proxy),\n * como falar com ele e o comportamento inicial. Chaves de API e modelos ficam no servidor, nunca aqui.\n *\n * Nada aqui liga microfone ou abre conexão: ler configuração não tem efeito colateral.\n */\n\nimport type { SpeechMode } from \"./types.ts\";\n\nexport interface VoiceSettings {\n /** `realtime`: áudio full-duplex pelo servidor (proxy). `local`: reconhecimento/síntese do navegador. */\n readonly speechMode: SpeechMode;\n /** Idioma BCP-47 usado pelo modo local. */\n readonly language: string;\n\n /** Origem do servidor de áudio, ex.: `http://localhost:8765` ou `https://voz.exemplo.com/t/abc`. */\n readonly proxyServerUrl: string;\n /** Caminho (relativo à origem) que entrega o `session_id`. */\n readonly proxyLeaseEndpoint: string;\n /** Caminho (relativo à origem) do WebSocket de áudio. */\n readonly proxyWebSocketEndpoint: string;\n /** `session_id` já obtido por outro meio; vazio = pedir ao endpoint de lease. */\n readonly proxySessionId: string;\n /** Tempo máximo para o servidor aceitar a conexão e avisar que está pronto. */\n readonly connectTimeoutMs: number;\n\n /** Microfone ligado quando a sessão abre (`false` = abre mutado). */\n readonly mic: boolean;\n /** Identificador local da sessão (histórico, eventos). */\n readonly sessionId: string;\n readonly trace: boolean;\n}\n\nexport const VOICE_DEFAULTS: Omit<VoiceSettings, \"sessionId\"> = Object.freeze({\n speechMode: \"local\",\n language: \"pt-BR\",\n proxyServerUrl: \"http://localhost:8765\",\n proxyLeaseEndpoint: \"/lease\",\n proxyWebSocketEndpoint: \"/ws\",\n proxySessionId: \"\",\n connectTimeoutMs: 10_000,\n mic: true,\n trace: false,\n});\n\n/** Variáveis de ambiente/`import.meta.env`: valores podem faltar. */\nexport type VoiceEnv = Readonly<Record<string, string | undefined>>;\n\nexport function createVoiceSettings(overrides: Partial<VoiceSettings> = {}): VoiceSettings {\n return Object.freeze({\n ...VOICE_DEFAULTS,\n sessionId: `session-${Date.now()}`,\n ...overrides,\n });\n}\n\n/**\n * Lê a configuração de um objeto de ambiente. O argumento é obrigatório de propósito: o módulo roda\n * no navegador, onde não existe `process`. Em Node passe `process.env`; no Vite, `import.meta.env`.\n */\nexport function readVoiceSettings(env: VoiceEnv): VoiceSettings {\n const text = (key: string, fallback: string): string => {\n const value = env[key];\n return value === undefined || value === \"\" ? fallback : value;\n };\n const flag = (key: string, fallback: boolean): boolean => {\n const value = env[key];\n if (value === \"1\" || value === \"true\") return true;\n if (value === \"0\" || value === \"false\") return false;\n return fallback;\n };\n const positiveInt = (key: string, fallback: number): number => {\n const parsed = Number.parseInt(env[key] ?? \"\", 10);\n return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;\n };\n\n return createVoiceSettings({\n speechMode: parseSpeechMode(text(\"SPEECH_MODE\", text(\"SPEECH\", VOICE_DEFAULTS.speechMode))),\n language: text(\"VOICE_LANGUAGE\", VOICE_DEFAULTS.language),\n proxyServerUrl: text(\"PROXY_SERVER_URL\", VOICE_DEFAULTS.proxyServerUrl),\n proxyLeaseEndpoint: text(\"PROXY_LEASE_ENDPOINT\", VOICE_DEFAULTS.proxyLeaseEndpoint),\n proxyWebSocketEndpoint: text(\"PROXY_WEBSOCKET_ENDPOINT\", VOICE_DEFAULTS.proxyWebSocketEndpoint),\n proxySessionId: text(\"PROXY_SESSION_ID\", VOICE_DEFAULTS.proxySessionId),\n connectTimeoutMs: positiveInt(\"CONNECT_TIMEOUT_MS\", VOICE_DEFAULTS.connectTimeoutMs),\n mic: flag(\"MIC\", VOICE_DEFAULTS.mic),\n trace: flag(\"TRACE\", VOICE_DEFAULTS.trace),\n ...(env[\"SESSION_ID\"] ? { sessionId: env[\"SESSION_ID\"] } : {}),\n });\n}\n\n/** Aceita \"1\"/\"local\" e \"2\"/\"realtime\" (sem diferenciar maiúsculas); qualquer outra coisa vira `local`. */\nexport function parseSpeechMode(choice: string): SpeechMode {\n const normalized = choice.trim().toLowerCase();\n if (normalized === \"2\" || normalized === \"realtime\") return \"realtime\";\n return \"local\";\n}\n\n/** Mensagem de erro se a configuração não serve para o modo escolhido; `undefined` se está ok. */\nexport function validateSettings(settings: VoiceSettings): string | undefined {\n if (settings.speechMode !== \"realtime\") return undefined;\n\n let origin: URL;\n try {\n origin = new URL(settings.proxyServerUrl);\n } catch {\n return `proxyServerUrl inválida: ${settings.proxyServerUrl}`;\n }\n if (origin.protocol !== \"http:\" && origin.protocol !== \"https:\") {\n return `proxyServerUrl deve usar http: ou https: (recebido ${origin.protocol})`;\n }\n if (!settings.proxyWebSocketEndpoint) return \"proxyWebSocketEndpoint vazio\";\n if (!settings.proxySessionId && !settings.proxyLeaseEndpoint) {\n return \"defina proxyLeaseEndpoint ou proxySessionId\";\n }\n return undefined;\n}\n","/**\n * Motor realtime: conversa de voz full-duplex pelo servidor de áudio (proxy), que fala com o provedor\n * (ex.: OpenAI Realtime). A chave do provedor vive só no servidor; o navegador nunca a vê.\n *\n * Protocolo (WebSocket em `proxyWebSocketEndpoint?session=<id>`):\n * - servidor → navegador, texto JSON: `ready{rate}`, `rate{rate}`, `state{...}`, `mark{...}`, `clear`, `refused{reason}`;\n * - servidor → navegador, binário: PCM int16 da resposta, na taxa de `ready.rate`;\n * - navegador → servidor, binário: `[uint32 LE nº do bloco][int16 × 512]` a 16 kHz;\n * - navegador → servidor, texto JSON: `mute{on}` e os relatos do reprodutor (`reached`, `rendered`, `underrun`).\n *\n * Nada abre ao construir ou importar: tudo começa em `enter()`.\n */\n\nimport {\n CAPTURE_BLOCK,\n startBrowserMicrophone,\n startBrowserPlayback,\n type AudioPlayback,\n type MicrophoneCapture,\n type MicrophoneFactory,\n type MicrophoneFrame,\n type PlaybackFactory,\n type PlaybackReport,\n} from \"./audio.ts\";\nimport { validateSettings, type VoiceSettings } from \"./config.ts\";\nimport { VoiceError } from \"./errors.ts\";\nimport { EventHub } from \"./events.ts\";\nimport { MUTE, type Capability, type TurnState, type VoiceEngine, type VoiceEvent, type VoiceSnapshot } from \"./types.ts\";\n\nexport interface SocketHandlers {\n onOpen(): void;\n onMessage(data: string | ArrayBuffer): void;\n onError(): void;\n onClose(code: number, reason: string): void;\n}\n\nexport interface SocketLike {\n readonly isOpen: boolean;\n send(data: string | Uint8Array): void;\n close(code?: number, reason?: string): void;\n}\n\nexport type SocketFactory = (url: string, handlers: SocketHandlers) => SocketLike;\n\n/** Pontos de substituição (testes, ambientes sem DOM). O padrão usa as APIs do navegador. */\nexport interface RealtimeDeps {\n fetch?: typeof fetch;\n socket?: SocketFactory;\n microphone?: MicrophoneFactory;\n playback?: PlaybackFactory;\n}\n\nconst OPEN = 1;\n\nexport const browserSocket: SocketFactory = (url, handlers) => {\n const socket = new WebSocket(url);\n socket.binaryType = \"arraybuffer\";\n socket.addEventListener(\"open\", () => handlers.onOpen());\n socket.addEventListener(\"error\", () => handlers.onError());\n socket.addEventListener(\"close\", (event) => handlers.onClose(event.code, event.reason));\n socket.addEventListener(\"message\", (event: MessageEvent<unknown>) => {\n const data = event.data;\n if (typeof data === \"string\" || data instanceof ArrayBuffer) handlers.onMessage(data);\n });\n return {\n get isOpen(): boolean {\n return socket.readyState === OPEN;\n },\n send(data: string | Uint8Array): void {\n // Copia para um buffer próprio: `socket.send` só aceita `ArrayBuffer`, não `SharedArrayBuffer`.\n socket.send(typeof data === \"string\" ? data : new Uint8Array(data));\n },\n close(code?: number, reason?: string): void {\n socket.close(code, reason);\n },\n };\n};\n\n/** Junta a origem do servidor (que pode ter um prefixo, ex.: `/t/abc`) com um caminho relativo a ela. */\nexport function joinServerUrl(base: string, endpoint: string): URL {\n const url = new URL(base);\n const prefix = url.pathname.replace(/\\/+$/, \"\");\n url.pathname = `${prefix}/${endpoint.replace(/^\\/+/, \"\")}`;\n url.search = \"\";\n url.hash = \"\";\n return url;\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n return typeof value === \"object\" && value !== null && !Array.isArray(value) ? (value as Record<string, unknown>) : undefined;\n}\n\nfunction turnStateFrom(serverState: unknown, fallback: TurnState): TurnState {\n switch (serverState) {\n case \"LISTENING\":\n case \"INTERRUPTED\":\n return \"listening\";\n case \"TRANSCRIBING\":\n case \"THINKING\":\n case \"EXPLAINING\":\n return \"processing\";\n case \"SPEAKING\":\n return \"speaking\";\n default:\n return fallback;\n }\n}\n\nexport class RealtimeEngine implements VoiceEngine {\n readonly capabilities: ReadonlySet<Capability> = new Set<Capability>([MUTE]);\n\n private readonly settings: VoiceSettings;\n private readonly deps: RealtimeDeps;\n private readonly hub = new EventHub<VoiceEvent>();\n\n private socket: SocketLike | undefined;\n private playback: AudioPlayback | undefined;\n private playbackRate = 0;\n private microphone: MicrophoneCapture | undefined;\n private microphoneStarting: Promise<void> | undefined;\n private muted: boolean;\n private opened = false;\n private ready = false;\n private closing = false;\n private turn: TurnState = \"idle\";\n private proxySessionId = \"\";\n private lastError: string | undefined;\n private settle: { resolve(): void; reject(error: VoiceError): void } | undefined;\n\n constructor(settings: VoiceSettings, deps: RealtimeDeps = {}) {\n this.settings = settings;\n this.deps = deps;\n this.muted = !settings.mic;\n }\n\n async enter(): Promise<void> {\n if (this.opened) throw new VoiceError(\"CONNECTION_FAILED\", \"o motor realtime já foi aberto\");\n const problem = validateSettings(this.settings);\n if (problem) throw new VoiceError(\"INVALID_SETTINGS\", problem);\n this.opened = true;\n\n try {\n this.proxySessionId = this.settings.proxySessionId || (await this.lease());\n const url = joinServerUrl(this.settings.proxyServerUrl, this.settings.proxyWebSocketEndpoint);\n url.protocol = url.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n url.searchParams.set(\"session\", this.proxySessionId);\n\n const ready = new Promise<void>((resolve, reject) => {\n this.settle = { resolve, reject };\n });\n const timer = setTimeout(() => {\n this.settle?.reject(new VoiceError(\"TIMEOUT\", `o servidor de áudio não respondeu em ${this.settings.connectTimeoutMs} ms`));\n }, this.settings.connectTimeoutMs);\n\n const open = this.deps.socket ?? browserSocket;\n this.socket = open(url.href, {\n onOpen: () => this.trace(\"socket aberto\"),\n onMessage: (data) => void this.onMessage(data),\n onError: () => this.fail(new VoiceError(\"CONNECTION_FAILED\", `não foi possível conectar a ${url.origin}${url.pathname}`)),\n onClose: (code, reason) => this.onClose(code, reason),\n });\n\n try {\n await ready;\n } finally {\n clearTimeout(timer);\n }\n } catch (error) {\n await this.teardown();\n throw error;\n }\n }\n\n async exit(): Promise<void> {\n this.closing = true;\n await this.teardown();\n }\n\n snapshot(): VoiceSnapshot {\n return {\n mode: \"realtime\",\n channel: \"voice\",\n state: this.turn,\n muted: this.muted,\n sessionId: this.proxySessionId || this.settings.sessionId,\n messageCount: 0,\n ...(this.lastError ? { error: this.lastError } : {}),\n };\n }\n\n setMuted(muted: boolean): void {\n if (this.muted === muted) return;\n this.muted = muted;\n this.sendJson({ type: \"mute\", on: muted });\n if (!muted && this.ready) void this.ensureMicrophone();\n }\n\n submit(_text: string): void {\n throw new VoiceError(\"UNSUPPORTED\", \"o motor realtime não aceita texto digitado: fale pelo microfone\");\n }\n\n events(): AsyncIterable<VoiceEvent> {\n return this.hub.subscribe();\n }\n\n private async lease(): Promise<string> {\n const doFetch = this.deps.fetch ?? globalThis.fetch;\n if (typeof doFetch !== \"function\") throw new VoiceError(\"UNSUPPORTED\", \"fetch não está disponível neste ambiente\");\n const url = joinServerUrl(this.settings.proxyServerUrl, this.settings.proxyLeaseEndpoint);\n\n let response: Response;\n try {\n response = await doFetch(url.href, { credentials: \"include\" });\n } catch (error) {\n throw new VoiceError(\"LEASE_FAILED\", `não foi possível alcançar ${url.origin}${url.pathname}`, { cause: error });\n }\n const body = asRecord(await response.json().catch(() => undefined));\n if (!response.ok) {\n const reason = typeof body?.[\"error\"] === \"string\" ? body[\"error\"] : `HTTP ${response.status}`;\n throw new VoiceError(\"LEASE_FAILED\", `sessão de voz recusada: ${reason}`);\n }\n const id = body?.[\"session_id\"];\n if (typeof id !== \"string\" || id === \"\") throw new VoiceError(\"LEASE_FAILED\", \"o servidor não devolveu session_id\");\n return id;\n }\n\n private async onMessage(data: string | ArrayBuffer): Promise<void> {\n if (typeof data !== \"string\") {\n if (this.playback && data.byteLength >= 2) this.playback.push(new Int16Array(data, 0, Math.floor(data.byteLength / 2)));\n return;\n }\n\n let message: Record<string, unknown> | undefined;\n try {\n message = asRecord(JSON.parse(data));\n } catch {\n return;\n }\n const rate = typeof message?.[\"rate\"] === \"number\" ? message[\"rate\"] : 0;\n\n switch (message?.[\"type\"]) {\n case \"ready\":\n try {\n await this.startAudio(rate);\n this.ready = true;\n this.turn = \"listening\";\n this.emit({ kind: \"session.ready\", data: this.snapshot() });\n this.settle?.resolve();\n } catch (error) {\n this.fail(error instanceof VoiceError ? error : new VoiceError(\"CONNECTION_FAILED\", \"falha ao iniciar o áudio\", { cause: error }));\n }\n return;\n case \"rate\":\n if (this.ready) await this.startPlayback(rate).catch((error: unknown) => this.report(error));\n return;\n case \"state\":\n this.onState(message);\n return;\n case \"mark\": {\n const { id, label, offset } = message;\n if (typeof id === \"number\" && typeof label === \"string\" && typeof offset === \"number\") this.playback?.mark({ id, label, offset });\n return;\n }\n case \"clear\":\n this.playback?.clear();\n return;\n case \"refused\": {\n const reason = typeof message[\"reason\"] === \"string\" ? message[\"reason\"] : \"sem motivo informado\";\n this.fail(new VoiceError(\"REFUSED\", `o servidor recusou a sessão: ${reason}`));\n return;\n }\n default:\n return;\n }\n }\n\n private onState(message: Record<string, unknown>): void {\n this.turn = turnStateFrom(message[\"state\"], this.turn);\n const detail = typeof message[\"detail\"] === \"string\" ? message[\"detail\"] : \"\";\n switch (message[\"event\"]) {\n case \"user.started\":\n this.emit({ kind: \"user.started\", data: { channel: \"voice\" } });\n return;\n case \"agent.started\":\n this.emit({ kind: \"agent.started\", data: { turn: this.turn } });\n return;\n case \"sound.started\":\n this.emit({ kind: \"sound.started\", data: {} });\n return;\n case \"speaker.idle\":\n this.emit({ kind: \"speaker.idle\", data: {} });\n return;\n case \"error\":\n this.lastError = detail || \"erro no servidor de áudio\";\n this.emit({ kind: \"error\", data: { message: this.lastError, code: \"SERVER_ERROR\" } });\n return;\n case \"turn.heard\":\n if (detail.trim()) this.emit({ kind: \"turn.heard\", data: { text: detail, channel: \"voice\" } });\n return;\n default:\n return;\n }\n }\n\n private async startAudio(rate: number): Promise<void> {\n await this.startPlayback(rate);\n if (!this.muted) await this.ensureMicrophone();\n }\n\n private async startPlayback(rate: number): Promise<void> {\n if (!Number.isFinite(rate) || rate <= 0) throw new VoiceError(\"CONNECTION_FAILED\", `taxa de áudio inválida: ${rate}`);\n if (this.playback && this.playbackRate === rate) return;\n const previous = this.playback;\n this.playback = undefined;\n await previous?.close();\n const open = this.deps.playback ?? startBrowserPlayback;\n this.playback = await open(rate, (report) => this.sendPlaybackReport(report));\n this.playbackRate = rate;\n }\n\n private ensureMicrophone(): Promise<void> {\n if (this.microphone) return Promise.resolve();\n this.microphoneStarting ??= (async () => {\n const open = this.deps.microphone ?? startBrowserMicrophone;\n try {\n this.microphone = await open((frame) => this.sendFrame(frame));\n if (this.closing) await this.stopMicrophone();\n } finally {\n this.microphoneStarting = undefined;\n }\n })();\n return this.microphoneStarting;\n }\n\n private async stopMicrophone(): Promise<void> {\n const microphone = this.microphone;\n this.microphone = undefined;\n await microphone?.stop();\n }\n\n private sendFrame(frame: MicrophoneFrame): void {\n if (this.muted || !this.socket?.isOpen || frame.pcm.length !== CAPTURE_BLOCK) return;\n const bytes = new Uint8Array(4 + frame.pcm.byteLength);\n new DataView(bytes.buffer).setUint32(0, frame.block, true);\n bytes.set(new Uint8Array(frame.pcm.buffer, frame.pcm.byteOffset, frame.pcm.byteLength), 4);\n this.socket.send(bytes);\n }\n\n private sendPlaybackReport(report: PlaybackReport): void {\n this.sendJson(report);\n }\n\n private sendJson(message: Readonly<Record<string, unknown>>): void {\n if (this.socket?.isOpen) this.socket.send(JSON.stringify(message));\n }\n\n private onClose(code: number, reason: string): void {\n if (this.closing) return;\n const error = new VoiceError(\"CONNECTION_FAILED\", `conexão encerrada pelo servidor (código ${code}${reason ? `: ${reason}` : \"\"})`);\n if (!this.ready) {\n this.settle?.reject(error);\n return;\n }\n this.lastError = error.message;\n this.emit({ kind: \"error\", data: { message: error.message, code: \"CLOSED\" } });\n void this.teardown();\n }\n\n /** Falha durante a abertura rejeita `enter()`; depois dela vira evento de erro. */\n private fail(error: VoiceError): void {\n if (!this.ready) {\n this.settle?.reject(error);\n return;\n }\n this.report(error);\n }\n\n private report(error: unknown): void {\n const message = error instanceof Error ? error.message : \"erro de áudio\";\n this.lastError = message;\n this.emit({ kind: \"error\", data: { message, code: error instanceof VoiceError ? error.code : \"AUDIO_ERROR\" } });\n }\n\n private emit(event: VoiceEvent): void {\n this.hub.emit(event);\n }\n\n private trace(message: string): void {\n if (this.settings.trace) console.debug(`[RealtimeEngine] ${message}`);\n }\n\n private async teardown(): Promise<void> {\n this.closing = true;\n this.ready = false;\n this.turn = \"idle\";\n const socket = this.socket;\n const playback = this.playback;\n this.socket = undefined;\n this.playback = undefined;\n await this.stopMicrophone().catch(() => undefined);\n await playback?.close().catch(() => undefined);\n if (socket?.isOpen) socket.close(1000, \"fim da sessão\");\n this.hub.close();\n }\n}\n","/** Escolhe o motor de voz pela configuração. Criar o motor não abre microfone nem conexão: isso é `enter()`. */\n\nimport type { VoiceSettings } from \"./config.ts\";\nimport { LocalEngine, type LocalDeps } from \"./local-engine.ts\";\nimport { RealtimeEngine, type RealtimeDeps } from \"./realtime-engine.ts\";\nimport type { VoiceEngine } from \"./types.ts\";\n\nexport interface EngineDeps {\n realtime?: RealtimeDeps;\n local?: LocalDeps;\n}\n\nexport function createEngine(settings: VoiceSettings, deps: EngineDeps = {}): VoiceEngine {\n return settings.speechMode === \"realtime\"\n ? new RealtimeEngine(settings, deps.realtime)\n : new LocalEngine(settings, deps.local);\n}\n\nexport { LocalEngine, RealtimeEngine };\nexport type { LocalDeps, RealtimeDeps };\n","/**\n * Sessão de voz: gerencia o contexto compartilhado, enfileira inputs,\n * emite eventos públicos e coordena alternância entre voz e chat.\n *\n * Desenho:\n * - Estado mutável fica tudo aqui (contexto, histórico, turno).\n * - Um dono por estado: só VoiceSession muda VoiceContext.\n * - Eventos públicos vão para quem chamar `events()`; cada chamada recebe a sua fila.\n * - Bridge e controlador observam; nunca escrevem direto.\n * - Controle opcional de mutação (padrão: true / ativado).\n * Pode ser desativado para modo read-only (debug/auditoria).\n *\n * Criar a sessão não liga nada: o microfone/conexão só abrem em `abrir(engine)`.\n */\n\nimport type { UserInput, VoiceSessionReader } from \"./bridge.ts\";\nimport type { VoiceSettings } from \"./config.ts\";\nimport { createEngine, type EngineDeps } from \"./engines.ts\";\nimport { EventHub } from \"./events.ts\";\nimport type {\n InputChannel,\n TurnState,\n VoiceContext,\n VoiceEngine,\n VoiceEvent,\n VoiceSnapshot,\n} from \"./types.ts\";\n\n/**\n * Eventos que a engine pode repassar ao público. `session.ready` é da própria sessão e `turn.heard` também:\n * a engine só informa o que ouviu, e a sessão o aceita como entrada e o publica uma única vez.\n */\nconst FORWARDED_EVENTS: ReadonlySet<VoiceEvent[\"kind\"]> = new Set([\n \"user.started\",\n \"agent.started\",\n \"agent.finished\",\n \"sound.started\",\n \"speaker.idle\",\n \"error\",\n]);\n\nexport class VoiceSession implements VoiceSessionReader {\n readonly sessionId: string;\n readonly context: VoiceContext;\n\n private engine: VoiceEngine | null = null;\n private readonly settings: VoiceSettings;\n private readonly inputQueue: UserInput[] = [];\n private readonly hub = new EventHub<VoiceEvent>();\n private isClosed = false;\n\n /** Flag para ativar/desativar mutação de estado. Padrão: true (ativado). */\n private allowMutations: boolean;\n\n constructor(settings: VoiceSettings, allowMutations = true) {\n this.settings = settings;\n this.sessionId = settings.sessionId;\n this.allowMutations = allowMutations;\n\n this.context = {\n mode: settings.speechMode,\n channel: \"voice\",\n turn: \"idle\",\n sessionId: this.sessionId,\n conversationHistory: [],\n muted: !settings.mic,\n lastTurnTime: undefined,\n };\n }\n\n /** Abre a sessão: abre a engine e passa a repassar os eventos dela. */\n async abrir(engine: VoiceEngine): Promise<void> {\n if (this.engine) throw new Error(\"sessão já está aberta\");\n\n // Inscrever antes de abrir: eventos emitidos durante `enter()` não se perdem.\n const engineEvents = engine.events();\n this.engine = engine;\n this.isClosed = false;\n\n try {\n await engine.enter();\n } catch (error) {\n this.engine = null;\n throw error;\n }\n\n this.emitPublic({ kind: \"session.ready\", data: this.snapshot() });\n void this.processEngineEvents(engineEvents);\n }\n\n /** Fecha a sessão. */\n async fechar(): Promise<void> {\n this.isClosed = true;\n\n const engine = this.engine;\n this.engine = null;\n if (engine) {\n try {\n await engine.exit();\n } catch (error) {\n console.error(\"[VoiceSession.fechar]\", error);\n }\n }\n\n this.hub.close();\n }\n\n /** Snapshot atual para o painel. */\n snapshot(): VoiceSnapshot {\n return {\n mode: this.context.mode,\n channel: this.context.channel,\n state: this.context.turn,\n muted: this.context.muted,\n sessionId: this.sessionId,\n messageCount: this.context.conversationHistory.length,\n lastMessageTime: this.context.lastTurnTime,\n };\n }\n\n /** Habilita/desabilita mutação de estado. Padrão: true (ativado). false = modo auditoria (read-only). */\n setMutationsAllowed(allowed: boolean): void {\n this.allowMutations = allowed;\n }\n\n /** Enfileira entrada do usuário (voz transcrita ou texto). Sem efeito se mutações desabilitadas. */\n enqueueInput(input: UserInput): void {\n if (!this.allowMutations) return;\n this.inputQueue.push(input);\n this.processNextInput();\n }\n\n /** Muta/desmuta o microfone. Sem efeito se mutações desabilitadas. */\n setMuted(muted: boolean): void {\n if (!this.allowMutations) return;\n this.context.muted = muted;\n this.engine?.setMuted(muted);\n }\n\n /** Eventos públicos a partir de agora, até a sessão fechar. Cada chamada tem a sua fila. */\n events(): AsyncIterable<VoiceEvent> {\n return this.hub.subscribe();\n }\n\n /**\n * Alterna entre voz e chat.\n * O histórico é preservado; apenas a origem do próximo input muda.\n * Sem efeito se mutações desabilitadas.\n */\n switchToChannel(channel: InputChannel): void {\n if (!this.allowMutations) return;\n if (this.context.channel === channel) return;\n\n const from = this.context.channel;\n this.context.channel = channel;\n this.emitPublic({ kind: \"channel.switched\", data: { from, to: channel } });\n }\n\n /** Processa a fila: atualiza histórico e avisa o bridge. */\n private processNextInput(): void {\n if (!this.allowMutations) return;\n const input = this.inputQueue.shift();\n if (!input) return;\n\n this.context.turn = \"listening\";\n this.context.channel = input.channel;\n this.context.lastTurnTime = input.timestamp;\n\n this.context.conversationHistory.push({\n role: \"user\",\n content: input.utterance,\n timestamp: input.timestamp,\n channel: input.channel,\n confidence: input.confidence,\n });\n\n this.emitPublic({\n kind: \"turn.heard\",\n data: { text: input.utterance, confidence: input.confidence, channel: input.channel },\n });\n this.context.turn = \"processing\";\n }\n\n /** Repassa os eventos da engine enquanto a sessão estiver aberta. */\n private async processEngineEvents(stream: AsyncIterable<VoiceEvent>): Promise<void> {\n try {\n for await (const engineEvent of stream) {\n if (this.isClosed) break;\n this.handleEngineEvent(engineEvent);\n }\n } catch (error) {\n this.emitPublic({\n kind: \"error\",\n data: { message: error instanceof Error ? error.message : \"erro desconhecido\", code: \"ENGINE_ERROR\" },\n });\n }\n }\n\n private handleEngineEvent(event: VoiceEvent): void {\n if (event.kind === \"turn.heard\") {\n this.enqueueInput({\n utterance: event.data.text,\n channel: event.data.channel ?? \"voice\",\n confidence: event.data.confidence,\n timestamp: Date.now(),\n });\n return;\n }\n if (FORWARDED_EVENTS.has(event.kind)) this.emitPublic(event);\n\n if (event.kind === \"agent.finished\") {\n this.context.conversationHistory.push({\n role: \"assistant\",\n content: event.data.text,\n timestamp: Date.now(),\n channel: this.context.channel,\n });\n this.context.turn = \"idle\";\n }\n\n if (event.kind === \"agent.started\") this.context.turn = \"speaking\";\n }\n\n private emitPublic(event: VoiceEvent): void {\n this.hub.emit(event);\n }\n}\n\n/**\n * Cria a sessão e abre a engine escolhida por `settings.speechMode`.\n * É aqui — e só aqui — que o microfone/conexão são pedidos; importar o módulo ou criar `VoiceSession` não os liga.\n */\nexport async function createVoiceSession(settings: VoiceSettings, deps: EngineDeps = {}): Promise<VoiceSession> {\n const session = new VoiceSession(settings);\n await session.abrir(createEngine(settings, deps));\n return session;\n}\n\nexport type { TurnState };\n","/**\n * O PLUGUE DE VOZ. Conecta a sessão de voz ao page-agent e ao cérebro.\n *\n * Desenho (seção 5.1 da spec de voz):\n * - Este módulo é o ÚNICO que conhece tanto VoiceSession quanto page-agent.\n * - Abaixo dele fica a sessão de voz: testável sem navegador, sem LLM, sem page-agent.\n * - Acima ficam observadores (painel, brain, rotina-executor) que consomem eventos.\n *\n * O que conecta: OBSERVAÇÃO (leitura) apenas.\n * - Entrada de voz → prepara e passa ao agente como \"input do usuário\"\n * - Resposta do agente → transforma em fala (não está aqui; é na sessão)\n * - Contexto compartilhado: histórico de mensagens + estado do agente\n *\n * Um dono por estado mutável: VoiceSessionController é quem gerencia\n * alternância voz ↔ chat, e ninguém mais mexe no state de turno.\n */\n\nimport type { VoiceContext, Message, InputChannel, VoiceEvent, VoiceSnapshot } from \"./types.ts\";\n\n/**\n * Entrada de um usuário: voz transcrita ou texto digitado.\n * Ambos são normalizados para \"utterance\" (enunciado).\n */\nexport type UserInput = {\n utterance: string;\n channel: InputChannel;\n confidence?: number; // 0-1 para transcrição de fala\n timestamp: number;\n};\n\n/**\n * Contrato que a sessão de voz expõe para o bridge.\n * Apenas leitura: o bridge nunca escreve no estado da sessão.\n */\nexport interface VoiceSessionReader {\n readonly sessionId: string;\n readonly context: VoiceContext;\n readonly snapshot: () => VoiceSnapshot;\n\n /** Enfileira entrada do usuário (voz ou texto). */\n enqueueInput(input: UserInput): void;\n\n /** Itera eventos públicos. */\n events(): AsyncIterable<VoiceEvent>;\n\n /** Muta/desmuta. */\n setMuted(muted: boolean): void;\n}\n\n/**\n * Ponte entre a sessão e quem a consome.\n *\n * - Texto digitado entra por `onUserText`, que só o enfileira na sessão.\n * - Os eventos públicos da sessão entram por `onVoiceEvent`, que REGISTRA o que a sessão aceitou\n * (`turn.heard`) e o que o agente respondeu (`agent.finished`). Ele nunca reenfileira: a sessão\n * já absorveu a fala do motor, e reenfileirar o `turn.heard` público criaria um ciclo infinito.\n *\n * Exemplo de uso:\n * const bridge = new VoiceBridge(voiceSession);\n * chatInput.onSubmit((text) => bridge.onUserText(text));\n * for await (const ev of voiceSession.events()) bridge.onVoiceEvent(ev);\n */\nexport class VoiceBridge {\n private session: VoiceSessionReader;\n private messageBuffer: Message[] = [];\n\n constructor(session: VoiceSessionReader) {\n this.session = session;\n }\n\n /**\n * Entrada de texto do usuário (chat): vira UserInput e é enfileirada na sessão.\n * O registro no histórico da ponte acontece quando a sessão publicar o `turn.heard` correspondente.\n */\n onUserText(text: string): void {\n if (!text.trim()) return;\n\n this.session.enqueueInput({\n utterance: text.trim(),\n channel: \"chat\",\n timestamp: Date.now(),\n });\n }\n\n /**\n * Evento público da sessão. `turn.heard` e `agent.finished` entram no histórico da ponte;\n * nenhum evento aqui cria entrada nova na sessão.\n */\n onVoiceEvent(event: VoiceEvent): void {\n if (event.kind === \"turn.heard\") {\n const { text, confidence, channel } = event.data;\n if (!text.trim()) return;\n\n this.recordMessage({\n role: \"user\",\n content: text,\n channel: channel ?? \"voice\",\n confidence,\n timestamp: Date.now(),\n });\n }\n\n if (event.kind === \"agent.finished\") {\n const { text } = event.data;\n this.recordMessage({\n role: \"assistant\",\n content: text,\n channel: this.session.context.channel,\n timestamp: Date.now(),\n });\n }\n\n if (event.kind === \"channel.switched\") {\n const { from, to } = event.data;\n // Nada a fazer aqui: o histórico não muda.\n // O contexto já foi atualizado pela sessão.\n console.debug(`[VoiceBridge] canais alternados: ${from} → ${to}`);\n }\n }\n\n /**\n * Contexto atual: tudo o que o page-agent precisa saber sobre voz.\n * Leitura apenas; nunca muta.\n */\n get context(): VoiceContext {\n return this.session.context;\n }\n\n /**\n * Snapshot para o painel.\n */\n get snapshot(): VoiceSnapshot {\n return this.session.snapshot();\n }\n\n /**\n * Histórico de mensagens: construído incrementalmente\n * conforme entradas chegam e respostas são produzidas.\n */\n get messages(): readonly Message[] {\n return this.messageBuffer;\n }\n\n /**\n * Muta/desmuta o microfone.\n */\n setMuted(muted: boolean): void {\n this.session.setMuted(muted);\n }\n\n /**\n * Registra uma mensagem no histórico local do bridge.\n * Não persiste (isso é job do store se necessário).\n */\n private recordMessage(msg: Omit<Message, \"timestamp\"> & { timestamp: number }): void {\n this.messageBuffer.push(msg as Message);\n\n // Keeps recent history: discard older than 2 hours\n const cutoff = Date.now() - 2 * 60 * 60 * 1000;\n this.messageBuffer = this.messageBuffer.filter((m) => m.timestamp > cutoff);\n }\n\n /**\n * Reset para testes ou novo começo de conversa.\n */\n clearHistory(): void {\n this.messageBuffer = [];\n }\n}\n\n/**\n * Controlador de sessão: gerencia transições de canal (voz ↔ chat)\n * e manutenção de estado.\n *\n * Um dono por estado mutável: só aqui muda de \"voice\" para \"chat\".\n * Ninguém mais muda VoiceContext.channel.\n */\nexport class VoiceSessionController {\n private session: VoiceSessionReader;\n private bridge: VoiceBridge;\n\n constructor(session: VoiceSessionReader, bridge: VoiceBridge) {\n this.session = session;\n this.bridge = bridge;\n }\n\n /**\n * Muda de canal (voz → chat ou chat → voz).\n * O contexto é preservado: histórico segue intacto.\n * Só muda a ORIGEM do próximo input.\n *\n * Implementação: sinaliza à sessão e deixa ela gerir o event loop.\n */\n switchChannel(to: InputChannel): void {\n const from = this.session.context.channel;\n if (from === to) return; // noop\n\n // A sessão trata essa mudança: emite event \"channel.switched\"\n // e atualiza seu próprio context.channel.\n // O bridge recebe e registra no histórico se necessário.\n // (Implementação fica em session.ts)\n console.debug(`[Controller] solicitando mudança: ${from} → ${to}`);\n }\n\n /**\n * Contexto atual (apenas leitura).\n */\n get context(): VoiceContext {\n return this.session.context;\n }\n\n /**\n * Muta/desmuta.\n */\n setMuted(muted: boolean): void {\n this.bridge.setMuted(muted);\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../../src/voice/errors.ts","../../src/voice/browser-speech.ts","../../src/voice/events.ts","../../src/voice/types.ts","../../src/voice/local-engine.ts","../../src/voice/audio.ts","../../src/voice/config.ts","../../src/voice/realtime-engine.ts","../../src/voice/engines.ts","../../src/voice/session.ts","../../src/voice/bridge.ts"],"sourcesContent":["/** Erros da voz com um `code` estável para quem precisa decidir o que fazer (não depender do texto). */\n\nexport type VoiceErrorCode =\n | \"INVALID_SETTINGS\"\n | \"UNSUPPORTED\"\n | \"LEASE_FAILED\"\n | \"CONNECTION_FAILED\"\n | \"REFUSED\"\n | \"TIMEOUT\"\n | \"CLOSED\"\n | \"MICROPHONE\";\n\nexport class VoiceError extends Error {\n readonly code: VoiceErrorCode;\n\n constructor(code: VoiceErrorCode, message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = \"VoiceError\";\n this.code = code;\n }\n}\n","/**\n * Voz do próprio navegador (Web Speech API): reconhecimento e síntese locais, sem servidor de áudio.\n * Só toca no microfone quando `listen()` é chamado.\n */\n\nimport { VoiceError } from \"./errors.ts\";\n\nexport interface LocalVoice {\n /** O navegador oferece reconhecimento de fala. */\n readonly supportsInput: boolean;\n /** Escuta uma fala e resolve com a transcrição final; resolve `\"\"` quando ninguém falou. */\n listen(): Promise<string>;\n stopListening(): void;\n speak(text: string): void;\n stopSpeaking(): void;\n}\n\ninterface RecognitionResultLike {\n readonly isFinal: boolean;\n readonly 0?: { readonly transcript: string };\n}\n\ninterface RecognitionEventLike {\n readonly resultIndex: number;\n readonly results: { readonly length: number; readonly [index: number]: RecognitionResultLike | undefined };\n}\n\ninterface RecognitionLike {\n lang: string;\n continuous: boolean;\n interimResults: boolean;\n maxAlternatives: number;\n onresult: ((event: RecognitionEventLike) => void) | null;\n onerror: ((event: { readonly error?: string }) => void) | null;\n onend: (() => void) | null;\n start(): void;\n stop(): void;\n}\n\ntype RecognitionConstructor = new () => RecognitionLike;\n\nfunction recognitionConstructor(): RecognitionConstructor | undefined {\n const scope = globalThis as { SpeechRecognition?: RecognitionConstructor; webkitSpeechRecognition?: RecognitionConstructor };\n return scope.SpeechRecognition ?? scope.webkitSpeechRecognition;\n}\n\nexport function createBrowserVoice(language: string, listenTimeoutMs = 30_000): LocalVoice {\n const Recognition = recognitionConstructor();\n let active: RecognitionLike | undefined;\n\n return {\n get supportsInput(): boolean {\n return Recognition !== undefined;\n },\n\n listen(): Promise<string> {\n if (!Recognition) return Promise.reject(new VoiceError(\"UNSUPPORTED\", \"este navegador não oferece reconhecimento de fala\"));\n return new Promise<string>((resolve, reject) => {\n const recognition = new Recognition();\n active = recognition;\n let transcript = \"\";\n let settled = false;\n\n const finish = (outcome: () => void): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n if (active === recognition) active = undefined;\n outcome();\n };\n const timer = setTimeout(() => {\n try {\n recognition.stop();\n } catch {\n /* já parado */\n }\n finish(() => resolve(transcript.trim()));\n }, listenTimeoutMs);\n\n recognition.lang = language;\n recognition.continuous = false;\n recognition.interimResults = false;\n recognition.maxAlternatives = 1;\n recognition.onresult = (event) => {\n for (let i = event.resultIndex; i < event.results.length; i++) {\n const result = event.results[i];\n if (result?.isFinal) transcript = result[0]?.transcript ?? transcript;\n }\n };\n recognition.onerror = (event) => {\n const code = event.error ?? \"desconhecido\";\n if (code === \"no-speech\" || code === \"aborted\") {\n finish(() => resolve(transcript.trim()));\n return;\n }\n finish(() => reject(new VoiceError(\"MICROPHONE\", `erro de reconhecimento de voz: ${code}`)));\n };\n recognition.onend = () => finish(() => resolve(transcript.trim()));\n\n try {\n recognition.start();\n } catch (error) {\n finish(() => reject(new VoiceError(\"MICROPHONE\", \"não foi possível iniciar o reconhecimento\", { cause: error })));\n }\n });\n },\n\n stopListening(): void {\n try {\n active?.stop();\n } catch {\n /* já parado */\n }\n },\n\n speak(text: string): void {\n const synth = globalThis.speechSynthesis;\n const clean = text.trim();\n if (!synth || !clean) return;\n synth.cancel();\n const utterance = new SpeechSynthesisUtterance(clean);\n utterance.lang = language;\n synth.speak(utterance);\n },\n\n stopSpeaking(): void {\n globalThis.speechSynthesis?.cancel();\n },\n };\n}\n","/**\n * Distribuição de eventos para vários consumidores. Cada `subscribe()` recebe a sua própria fila,\n * então um consumidor lento não perde eventos nem rouba os dos outros. Sem dependência de Node.\n */\n\ntype Waiter<T> = (result: IteratorResult<T>) => void;\n\nexport class EventHub<T> {\n private readonly subscribers = new Set<{ queue: T[]; waiters: Waiter<T>[] }>();\n private closed = false;\n\n get isClosed(): boolean {\n return this.closed;\n }\n\n emit(event: T): void {\n if (this.closed) return;\n for (const subscriber of this.subscribers) {\n const waiter = subscriber.waiters.shift();\n if (waiter) waiter({ value: event, done: false });\n else subscriber.queue.push(event);\n }\n }\n\n /** Encerra todos os iteradores: quem espera recebe `done`, quem tem fila a consome antes. */\n close(): void {\n if (this.closed) return;\n this.closed = true;\n for (const subscriber of this.subscribers) {\n for (const waiter of subscriber.waiters.splice(0)) waiter({ value: undefined, done: true });\n }\n }\n\n subscribe(): AsyncIterable<T> {\n const subscriber = { queue: [] as T[], waiters: [] as Waiter<T>[] };\n this.subscribers.add(subscriber);\n const hub = this;\n return {\n [Symbol.asyncIterator](): AsyncIterator<T> {\n return {\n next(): Promise<IteratorResult<T>> {\n const queued = subscriber.queue.shift();\n if (queued !== undefined) return Promise.resolve({ value: queued, done: false });\n if (hub.closed) {\n hub.subscribers.delete(subscriber);\n return Promise.resolve({ value: undefined, done: true });\n }\n return new Promise((resolve) => subscriber.waiters.push(resolve));\n },\n return(): Promise<IteratorResult<T>> {\n hub.subscribers.delete(subscriber);\n return Promise.resolve({ value: undefined, done: true });\n },\n };\n },\n };\n }\n}\n","/**\n * Types compartilhados para a integração de voz.\n * Baseado no pattern Conversation + Engine de NoStopPageTest.\n */\n\n/** Modo de fala: local (modelos no device) ou realtime (OpenAI full-duplex). */\nexport type SpeechMode = \"local\" | \"realtime\";\n\n/** Canal ativo de entrada: voz (microfone) ou chat (texto). */\nexport type InputChannel = \"voice\" | \"chat\";\n\n/** Tipo de efeito observável após ação. */\nexport type EffectKind = \"notification\" | \"navigation\" | \"loading\";\n\n/** Resultado da observação de efeito. */\nexport type EffectOutcome =\n | { status: \"observed\"; kind: EffectKind; detail: string }\n | { status: \"unknown\"; detail: string };\n\n/** Uma mensagem no histórico de conversa. */\nexport type Message = {\n role: \"user\" | \"assistant\";\n content: string;\n timestamp: number; // milliseconds since epoch\n channel: InputChannel; // voz ou chat\n confidence?: number; // 0-1, para speech-to-text\n};\n\n/** Estado de um turno: agente escutando, processando, falando. */\nexport type TurnState = \"idle\" | \"listening\" | \"processing\" | \"speaking\";\n\n/** Snapshot do estado de voz para o painel. */\nexport type VoiceSnapshot = {\n mode: SpeechMode;\n channel: InputChannel;\n state: TurnState;\n muted: boolean;\n sessionId: string;\n messageCount: number;\n lastMessageTime?: number;\n error?: string;\n};\n\n/** Estado compartilhado entre voz e navegação. */\nexport type VoiceContext = {\n mode: SpeechMode;\n channel: InputChannel;\n turn: TurnState;\n sessionId: string;\n conversationHistory: Message[];\n muted: boolean;\n lastTurnTime?: number;\n};\n\n/** Evento emitido pela sessão de voz. */\nexport type VoiceEvent =\n | { kind: \"session.ready\"; data: VoiceSnapshot }\n | { kind: \"user.started\"; data: { channel: InputChannel } }\n | { kind: \"turn.heard\"; data: { text: string; confidence?: number; channel?: InputChannel } }\n | { kind: \"function.call\"; data: { callId: string; name: string; arguments: Record<string, unknown> } }\n | { kind: \"agent.started\"; data: { turn: TurnState } }\n | { kind: \"agent.finished\"; data: { text: string } }\n | { kind: \"sound.started\"; data: object }\n | { kind: \"speaker.idle\"; data: object }\n | { kind: \"channel.switched\"; data: { from: InputChannel; to: InputChannel } }\n | { kind: \"error\"; data: { message: string; code: string } };\n\n/** Capacidades oferecidas pela engine de voz. */\nexport const TEXT_INPUT = \"text_input\";\nexport const MUTE = \"mute\";\nexport const REPLAY = \"replay\";\n\nexport type Capability = typeof TEXT_INPUT | typeof MUTE | typeof REPLAY | string;\n\n/**\n * Protocol para uma engine de voz (Local ou Realtime).\n * Baseado no EngineLike de NoStopPageTest.\n */\nexport interface VoiceEngine {\n readonly capabilities: ReadonlySet<Capability>;\n\n /** Abre a engine (inicializa modelos, conexões, etc). */\n enter(): Promise<void>;\n\n /** Fecha a engine. */\n exit(exc?: Error): Promise<void>;\n\n /** Snapshot atual para o painel. */\n snapshot(): VoiceSnapshot;\n\n /** Muta ou desmuta o microfone. */\n setMuted(muted: boolean): void;\n\n /** Retorna ao servidor o resultado de uma função executada no navegador. */\n sendFunctionResult?(callId: string, result: Record<string, unknown>): void;\n\n /** Submete texto como se tivesse sido ouvido (se TEXT_INPUT). */\n submit(text: string): void;\n\n /** Itera eventos públicos até fecha. */\n events(): AsyncIterable<VoiceEvent>;\n}\n","/**\n * Motor local: reconhecimento e síntese de fala do próprio navegador. Não precisa de servidor, mas a\n * qualidade e a disponibilidade dependem do navegador. O microfone só abre em `enter()` (e só se a\n * sessão não começar mutada).\n */\n\nimport { createBrowserVoice, type LocalVoice } from \"./browser-speech.ts\";\nimport type { VoiceSettings } from \"./config.ts\";\nimport { VoiceError } from \"./errors.ts\";\nimport { EventHub } from \"./events.ts\";\nimport { MUTE, TEXT_INPUT, type Capability, type TurnState, type VoiceEngine, type VoiceEvent, type VoiceSnapshot } from \"./types.ts\";\n\nexport interface LocalDeps {\n voice?: LocalVoice;\n}\n\n/** Pausa entre escutas vazias, para não girar sem parar quando ninguém fala. */\nconst IDLE_PAUSE_MS = 250;\n\nexport class LocalEngine implements VoiceEngine {\n readonly capabilities: ReadonlySet<Capability> = new Set<Capability>([TEXT_INPUT, MUTE]);\n\n private readonly settings: VoiceSettings;\n private readonly deps: LocalDeps;\n private readonly hub = new EventHub<VoiceEvent>();\n private voice: LocalVoice | undefined;\n private muted: boolean;\n private running = false;\n private turn: TurnState = \"idle\";\n private lastError: string | undefined;\n private unmute: (() => void) | undefined;\n\n constructor(settings: VoiceSettings, deps: LocalDeps = {}) {\n this.settings = settings;\n this.deps = deps;\n this.muted = !settings.mic;\n }\n\n async enter(): Promise<void> {\n if (this.running) throw new VoiceError(\"CONNECTION_FAILED\", \"o motor local já foi aberto\");\n const voice = this.deps.voice ?? createBrowserVoice(this.settings.language);\n if (!voice.supportsInput && !this.muted) {\n throw new VoiceError(\"UNSUPPORTED\", \"este navegador não oferece reconhecimento de fala\");\n }\n this.voice = voice;\n this.running = true;\n this.turn = \"idle\";\n this.hub.emit({ kind: \"session.ready\", data: this.snapshot() });\n void this.listenLoop(voice);\n }\n\n async exit(): Promise<void> {\n this.running = false;\n this.turn = \"idle\";\n this.voice?.stopListening();\n this.voice?.stopSpeaking();\n this.unmute?.();\n this.hub.close();\n }\n\n snapshot(): VoiceSnapshot {\n return {\n mode: \"local\",\n channel: \"voice\",\n state: this.turn,\n muted: this.muted,\n sessionId: this.settings.sessionId,\n messageCount: 0,\n ...(this.lastError ? { error: this.lastError } : {}),\n };\n }\n\n setMuted(muted: boolean): void {\n if (this.muted === muted) return;\n this.muted = muted;\n if (muted) this.voice?.stopListening();\n else this.unmute?.();\n }\n\n submit(text: string): void {\n if (!this.running) throw new VoiceError(\"CLOSED\", \"o motor local está fechado\");\n const clean = text.trim();\n if (clean) this.hub.emit({ kind: \"turn.heard\", data: { text: clean, channel: \"chat\" } });\n }\n\n /** Fala um texto com a síntese do navegador (a resposta do agente, por exemplo). */\n speak(text: string): void {\n this.voice?.speak(text);\n }\n\n events(): AsyncIterable<VoiceEvent> {\n return this.hub.subscribe();\n }\n\n private waitUnmuted(): Promise<void> {\n return new Promise((resolve) => {\n this.unmute = () => {\n this.unmute = undefined;\n resolve();\n };\n });\n }\n\n private async listenLoop(voice: LocalVoice): Promise<void> {\n while (this.running) {\n if (this.muted) {\n this.turn = \"idle\";\n await this.waitUnmuted();\n continue;\n }\n try {\n this.turn = \"listening\";\n const text = await voice.listen();\n if (!this.running) return;\n if (this.muted) continue;\n if (text) {\n this.hub.emit({ kind: \"user.started\", data: { channel: \"voice\" } });\n this.hub.emit({ kind: \"turn.heard\", data: { text } });\n } else {\n await new Promise((resolve) => setTimeout(resolve, IDLE_PAUSE_MS));\n }\n } catch (error) {\n if (!this.running) return;\n // Permissão negada ou falta de suporte não se resolvem tentando de novo: parar e avisar.\n this.lastError = error instanceof Error ? error.message : \"erro de reconhecimento de voz\";\n this.hub.emit({\n kind: \"error\",\n data: { message: this.lastError, code: error instanceof VoiceError ? error.code : \"AUDIO_ERROR\" },\n });\n this.running = false;\n this.turn = \"idle\";\n this.hub.close();\n return;\n }\n }\n }\n}\n","/**\n * Áudio do navegador para o modo realtime: captura do microfone e reprodução da resposta.\n *\n * Nada aqui roda ao importar o módulo. O microfone só é pedido quando `startBrowserMicrophone`\n * é chamado, e isso só acontece dentro de `RealtimeEngine.enter()`.\n *\n * Formato do protocolo do servidor de áudio:\n * - captura: blocos de 512 amostras int16 a 16 kHz (32 ms), contados a partir de 0;\n * - reprodução: PCM int16 na taxa anunciada pelo servidor (`ready.rate`).\n */\n\nimport { VoiceError } from \"./errors.ts\";\n\nexport const CAPTURE_RATE = 16_000;\nexport const CAPTURE_BLOCK = 512;\n\nexport interface MicrophoneFrame {\n readonly block: number;\n readonly pcm: Int16Array;\n /** Instante (ms) no relógio do AudioContext em que o bloco ficou completo. `performance` não existe no escopo do AudioWorklet. */\n readonly at: number;\n}\n\nexport interface MicrophoneCapture {\n stop(): Promise<void>;\n}\n\n/** Abre o microfone e entrega blocos até `stop()`. */\nexport type MicrophoneFactory = (onFrame: (frame: MicrophoneFrame) => void) => Promise<MicrophoneCapture>;\n\nexport interface PlaybackMark {\n readonly id: number;\n readonly label: string;\n readonly offset: number;\n}\n\n/** Relato do reprodutor ao servidor: `reached`, `rendered` ou `underrun` (a forma que o servidor lê). */\nexport type PlaybackReport = Readonly<Record<string, unknown>> & { readonly type: string };\n\nexport interface AudioPlayback {\n push(pcm16: Int16Array): void;\n mark(mark: PlaybackMark): void;\n clear(): void;\n close(): Promise<void>;\n}\n\nexport type PlaybackFactory = (rate: number, onReport: (report: PlaybackReport) => void) => Promise<AudioPlayback>;\n\nconst CAPTURE_WORKLET = `\nclass OmnivoiceCapture extends AudioWorkletProcessor {\n constructor() {\n super();\n this.block = 0;\n this.samples = new Float32Array(${CAPTURE_BLOCK});\n this.used = 0;\n }\n process(inputs) {\n const input = inputs[0] && inputs[0][0];\n if (!input) return true;\n let offset = 0;\n while (offset < input.length) {\n const take = Math.min(input.length - offset, ${CAPTURE_BLOCK} - this.used);\n this.samples.set(input.subarray(offset, offset + take), this.used);\n this.used += take;\n offset += take;\n if (this.used === ${CAPTURE_BLOCK}) {\n const pcm = new Int16Array(${CAPTURE_BLOCK});\n for (let i = 0; i < ${CAPTURE_BLOCK}; i++) {\n pcm[i] = Math.max(-32768, Math.min(32767, Math.round(this.samples[i] * 32768)));\n }\n this.port.postMessage({ block: this.block++, pcm: pcm.buffer, at: currentTime * 1000 }, [pcm.buffer]);\n this.samples = new Float32Array(${CAPTURE_BLOCK});\n this.used = 0;\n }\n }\n return true;\n }\n}\nregisterProcessor(\"omnivoice-capture\", OmnivoiceCapture);\n`;\n\nconst PLAYBACK_WORKLET = `\nclass OmnivoicePlayback extends AudioWorkletProcessor {\n constructor() {\n super();\n this.queue = [];\n this.offset = 0;\n this.rendered = 0;\n this.marks = [];\n this.port.onmessage = (event) => {\n const message = event.data || {};\n if (message.type === \"audio\") this.queue.push(new Float32Array(message.samples));\n if (message.type === \"mark\") this.marks.push({ id: message.id, label: message.label, offset: message.offset });\n if (message.type === \"clear\") { this.queue = []; this.offset = 0; this.marks = []; }\n };\n }\n process(_inputs, outputs) {\n const out = outputs[0][0];\n out.fill(0);\n let written = 0;\n while (written < out.length && this.queue.length) {\n const current = this.queue[0];\n const take = Math.min(out.length - written, current.length - this.offset);\n out.set(current.subarray(this.offset, this.offset + take), written);\n written += take;\n this.offset += take;\n if (this.offset === current.length) { this.queue.shift(); this.offset = 0; }\n }\n const before = this.rendered;\n this.rendered += written;\n while (this.marks.length && this.marks[0].offset < this.rendered) {\n const mark = this.marks.shift();\n this.port.postMessage({ type: \"reached\", id: mark.id, label: mark.label, at: currentTime * 1000 });\n }\n if (this.rendered - before || written < out.length) {\n this.port.postMessage({ type: \"rendered\", samples: this.rendered, at: currentTime * 1000 });\n }\n if (written < out.length && this.queue.length === 0) {\n this.port.postMessage({ type: \"underrun\", samples: out.length - written });\n }\n return true;\n }\n}\nregisterProcessor(\"omnivoice-playback\", OmnivoicePlayback);\n`;\n\nasync function loadWorklet(context: AudioContext, source: string): Promise<void> {\n const url = URL.createObjectURL(new Blob([source], { type: \"text/javascript\" }));\n try {\n await context.audioWorklet.addModule(url);\n } finally {\n URL.revokeObjectURL(url);\n }\n}\n\nfunction requireAudioSupport(): void {\n if (typeof AudioContext === \"undefined\" || typeof AudioWorkletNode === \"undefined\") {\n throw new VoiceError(\"UNSUPPORTED\", \"este navegador não oferece AudioContext/AudioWorklet\");\n }\n}\n\nfunction isCaptureMessage(data: unknown): data is { block: number; pcm: ArrayBuffer; at: number } {\n if (typeof data !== \"object\" || data === null) return false;\n const message = data as Record<string, unknown>;\n return typeof message[\"block\"] === \"number\" && message[\"pcm\"] instanceof ArrayBuffer && typeof message[\"at\"] === \"number\";\n}\n\nfunction isPlaybackReport(data: unknown): data is PlaybackReport {\n return typeof data === \"object\" && data !== null && typeof (data as Record<string, unknown>)[\"type\"] === \"string\";\n}\n\n/** Microfone real: `getUserMedia` + AudioWorklet a 16 kHz. Só pede permissão quando chamado. */\nexport const startBrowserMicrophone: MicrophoneFactory = async (onFrame) => {\n requireAudioSupport();\n if (!navigator.mediaDevices?.getUserMedia) {\n throw new VoiceError(\"UNSUPPORTED\", \"este navegador não oferece getUserMedia (precisa de https ou localhost)\");\n }\n\n const context = new AudioContext({ sampleRate: CAPTURE_RATE, latencyHint: \"interactive\" });\n let stream: MediaStream | undefined;\n try {\n await loadWorklet(context, CAPTURE_WORKLET);\n stream = await navigator.mediaDevices.getUserMedia({\n audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true },\n });\n const source = context.createMediaStreamSource(stream);\n const node = new AudioWorkletNode(context, \"omnivoice-capture\");\n node.port.onmessage = (event: MessageEvent<unknown>) => {\n if (!isCaptureMessage(event.data)) return;\n onFrame({ block: event.data.block, pcm: new Int16Array(event.data.pcm), at: event.data.at });\n };\n source.connect(node);\n node.connect(context.destination);\n await context.resume();\n\n const tracks = stream.getTracks();\n return {\n async stop(): Promise<void> {\n node.port.onmessage = null;\n source.disconnect();\n node.disconnect();\n for (const track of tracks) track.stop();\n await context.close();\n },\n };\n } catch (error) {\n stream?.getTracks().forEach((track) => track.stop());\n await context.close().catch(() => undefined);\n if (error instanceof VoiceError) throw error;\n throw new VoiceError(\"MICROPHONE\", error instanceof Error ? error.message : \"não foi possível abrir o microfone\", {\n cause: error,\n });\n }\n};\n\n/** Reprodutor real: AudioWorklet na taxa pedida; devolve ao servidor onde a reprodução está. */\nexport const startBrowserPlayback: PlaybackFactory = async (rate, onReport) => {\n requireAudioSupport();\n const context = new AudioContext({ sampleRate: rate, latencyHint: \"interactive\" });\n try {\n await loadWorklet(context, PLAYBACK_WORKLET);\n const node = new AudioWorkletNode(context, \"omnivoice-playback\");\n node.port.onmessage = (event: MessageEvent<unknown>) => {\n if (isPlaybackReport(event.data)) onReport(event.data);\n };\n node.connect(context.destination);\n await context.resume();\n\n return {\n push(pcm16: Int16Array): void {\n const samples = new Float32Array(pcm16.length);\n for (let i = 0; i < pcm16.length; i++) samples[i] = (pcm16[i] ?? 0) / 32768;\n node.port.postMessage({ type: \"audio\", samples: samples.buffer }, [samples.buffer]);\n },\n mark(mark: PlaybackMark): void {\n node.port.postMessage({ type: \"mark\", id: mark.id, label: mark.label, offset: mark.offset });\n },\n clear(): void {\n node.port.postMessage({ type: \"clear\" });\n },\n async close(): Promise<void> {\n node.port.onmessage = null;\n node.disconnect();\n await context.close();\n },\n };\n } catch (error) {\n await context.close().catch(() => undefined);\n throw error;\n }\n};\n","/**\n * Configuração da voz no cliente. Só o que o navegador usa: onde está o servidor de áudio (proxy),\n * como falar com ele e o comportamento inicial. Chaves de API e modelos ficam no servidor, nunca aqui.\n *\n * Nada aqui liga microfone ou abre conexão: ler configuração não tem efeito colateral.\n */\n\nimport type { SpeechMode } from \"./types.ts\";\n\nexport interface VoiceSettings {\n /** `realtime`: áudio full-duplex pelo servidor (proxy). `local`: reconhecimento/síntese do navegador. */\n readonly speechMode: SpeechMode;\n /** Idioma BCP-47 usado pelo modo local. */\n readonly language: string;\n\n /** Origem do servidor de áudio, ex.: `http://localhost:8765` ou `https://voz.exemplo.com/t/abc`. */\n readonly proxyServerUrl: string;\n /** Caminho (relativo à origem) que entrega o `session_id`. */\n readonly proxyLeaseEndpoint: string;\n /** Caminho (relativo à origem) do WebSocket de áudio. */\n readonly proxyWebSocketEndpoint: string;\n /** `session_id` já obtido por outro meio; vazio = pedir ao endpoint de lease. */\n readonly proxySessionId: string;\n /** Tempo máximo para o servidor aceitar a conexão e avisar que está pronto. */\n readonly connectTimeoutMs: number;\n\n /** Microfone ligado quando a sessão abre (`false` = abre mutado). */\n readonly mic: boolean;\n /** Identificador local da sessão (histórico, eventos). */\n readonly sessionId: string;\n readonly trace: boolean;\n}\n\nexport const VOICE_DEFAULTS: Omit<VoiceSettings, \"sessionId\"> = Object.freeze({\n speechMode: \"local\",\n language: \"pt-BR\",\n proxyServerUrl: \"http://localhost:8765\",\n proxyLeaseEndpoint: \"/lease\",\n proxyWebSocketEndpoint: \"/ws\",\n proxySessionId: \"\",\n connectTimeoutMs: 10_000,\n mic: true,\n trace: false,\n});\n\n/** Variáveis de ambiente/`import.meta.env`: valores podem faltar. */\nexport type VoiceEnv = Readonly<Record<string, string | undefined>>;\n\nexport function createVoiceSettings(overrides: Partial<VoiceSettings> = {}): VoiceSettings {\n return Object.freeze({\n ...VOICE_DEFAULTS,\n sessionId: `session-${Date.now()}`,\n ...overrides,\n });\n}\n\n/**\n * Lê a configuração de um objeto de ambiente. O argumento é obrigatório de propósito: o módulo roda\n * no navegador, onde não existe `process`. Em Node passe `process.env`; no Vite, `import.meta.env`.\n */\nexport function readVoiceSettings(env: VoiceEnv): VoiceSettings {\n const text = (key: string, fallback: string): string => {\n const value = env[key];\n return value === undefined || value === \"\" ? fallback : value;\n };\n const flag = (key: string, fallback: boolean): boolean => {\n const value = env[key];\n if (value === \"1\" || value === \"true\") return true;\n if (value === \"0\" || value === \"false\") return false;\n return fallback;\n };\n const positiveInt = (key: string, fallback: number): number => {\n const parsed = Number.parseInt(env[key] ?? \"\", 10);\n return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;\n };\n\n return createVoiceSettings({\n speechMode: parseSpeechMode(text(\"SPEECH_MODE\", text(\"SPEECH\", VOICE_DEFAULTS.speechMode))),\n language: text(\"VOICE_LANGUAGE\", VOICE_DEFAULTS.language),\n proxyServerUrl: text(\"PROXY_SERVER_URL\", VOICE_DEFAULTS.proxyServerUrl),\n proxyLeaseEndpoint: text(\"PROXY_LEASE_ENDPOINT\", VOICE_DEFAULTS.proxyLeaseEndpoint),\n proxyWebSocketEndpoint: text(\"PROXY_WEBSOCKET_ENDPOINT\", VOICE_DEFAULTS.proxyWebSocketEndpoint),\n proxySessionId: text(\"PROXY_SESSION_ID\", VOICE_DEFAULTS.proxySessionId),\n connectTimeoutMs: positiveInt(\"CONNECT_TIMEOUT_MS\", VOICE_DEFAULTS.connectTimeoutMs),\n mic: flag(\"MIC\", VOICE_DEFAULTS.mic),\n trace: flag(\"TRACE\", VOICE_DEFAULTS.trace),\n ...(env[\"SESSION_ID\"] ? { sessionId: env[\"SESSION_ID\"] } : {}),\n });\n}\n\n/** Aceita \"1\"/\"local\" e \"2\"/\"realtime\" (sem diferenciar maiúsculas); qualquer outra coisa vira `local`. */\nexport function parseSpeechMode(choice: string): SpeechMode {\n const normalized = choice.trim().toLowerCase();\n if (normalized === \"2\" || normalized === \"realtime\") return \"realtime\";\n return \"local\";\n}\n\n/** Mensagem de erro se a configuração não serve para o modo escolhido; `undefined` se está ok. */\nexport function validateSettings(settings: VoiceSettings): string | undefined {\n if (settings.speechMode !== \"realtime\") return undefined;\n\n let origin: URL;\n try {\n origin = new URL(settings.proxyServerUrl);\n } catch {\n return `proxyServerUrl inválida: ${settings.proxyServerUrl}`;\n }\n if (origin.protocol !== \"http:\" && origin.protocol !== \"https:\") {\n return `proxyServerUrl deve usar http: ou https: (recebido ${origin.protocol})`;\n }\n if (!settings.proxyWebSocketEndpoint) return \"proxyWebSocketEndpoint vazio\";\n if (!settings.proxySessionId && !settings.proxyLeaseEndpoint) {\n return \"defina proxyLeaseEndpoint ou proxySessionId\";\n }\n return undefined;\n}\n","/**\n * Motor realtime: conversa de voz full-duplex pelo servidor de áudio (proxy), que fala com o provedor\n * (ex.: OpenAI Realtime). A chave do provedor vive só no servidor; o navegador nunca a vê.\n *\n * Protocolo (WebSocket em `proxyWebSocketEndpoint?session=<id>`):\n * - servidor → navegador, texto JSON: `ready{rate}`, `rate{rate}`, `state{...}`, `mark{...}`, `clear`, `refused{reason}`;\n * - servidor → navegador, binário: PCM int16 da resposta, na taxa de `ready.rate`;\n * - navegador → servidor, binário: `[uint32 LE nº do bloco][int16 × 512]` a 16 kHz;\n * - navegador → servidor, texto JSON: `mute{on}` e os relatos do reprodutor (`reached`, `rendered`, `underrun`).\n *\n * Nada abre ao construir ou importar: tudo começa em `enter()`.\n */\n\nimport {\n CAPTURE_BLOCK,\n startBrowserMicrophone,\n startBrowserPlayback,\n type AudioPlayback,\n type MicrophoneCapture,\n type MicrophoneFactory,\n type MicrophoneFrame,\n type PlaybackFactory,\n type PlaybackReport,\n} from \"./audio.ts\";\nimport { validateSettings, type VoiceSettings } from \"./config.ts\";\nimport { VoiceError } from \"./errors.ts\";\nimport { EventHub } from \"./events.ts\";\nimport { MUTE, type Capability, type TurnState, type VoiceEngine, type VoiceEvent, type VoiceSnapshot } from \"./types.ts\";\n\nexport interface SocketHandlers {\n onOpen(): void;\n onMessage(data: string | ArrayBuffer): void;\n onError(): void;\n onClose(code: number, reason: string): void;\n}\n\nexport interface SocketLike {\n readonly isOpen: boolean;\n send(data: string | Uint8Array): void;\n close(code?: number, reason?: string): void;\n}\n\nexport type SocketFactory = (url: string, handlers: SocketHandlers) => SocketLike;\n\n/** Pontos de substituição (testes, ambientes sem DOM). O padrão usa as APIs do navegador. */\nexport interface RealtimeDeps {\n fetch?: typeof fetch;\n socket?: SocketFactory;\n microphone?: MicrophoneFactory;\n playback?: PlaybackFactory;\n}\n\nconst OPEN = 1;\n\nexport const browserSocket: SocketFactory = (url, handlers) => {\n const socket = new WebSocket(url);\n socket.binaryType = \"arraybuffer\";\n socket.addEventListener(\"open\", () => handlers.onOpen());\n socket.addEventListener(\"error\", () => handlers.onError());\n socket.addEventListener(\"close\", (event) => handlers.onClose(event.code, event.reason));\n socket.addEventListener(\"message\", (event: MessageEvent<unknown>) => {\n const data = event.data;\n if (typeof data === \"string\" || data instanceof ArrayBuffer) handlers.onMessage(data);\n });\n return {\n get isOpen(): boolean {\n return socket.readyState === OPEN;\n },\n send(data: string | Uint8Array): void {\n // Copia para um buffer próprio: `socket.send` só aceita `ArrayBuffer`, não `SharedArrayBuffer`.\n socket.send(typeof data === \"string\" ? data : new Uint8Array(data));\n },\n close(code?: number, reason?: string): void {\n socket.close(code, reason);\n },\n };\n};\n\n/** Junta a origem do servidor (que pode ter um prefixo, ex.: `/t/abc`) com um caminho relativo a ela. */\nexport function joinServerUrl(base: string, endpoint: string): URL {\n const url = new URL(base);\n const prefix = url.pathname.replace(/\\/+$/, \"\");\n url.pathname = `${prefix}/${endpoint.replace(/^\\/+/, \"\")}`;\n url.search = \"\";\n url.hash = \"\";\n return url;\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n return typeof value === \"object\" && value !== null && !Array.isArray(value) ? (value as Record<string, unknown>) : undefined;\n}\n\nfunction turnStateFrom(serverState: unknown, fallback: TurnState): TurnState {\n switch (serverState) {\n case \"LISTENING\":\n case \"INTERRUPTED\":\n return \"listening\";\n case \"TRANSCRIBING\":\n case \"THINKING\":\n case \"EXPLAINING\":\n return \"processing\";\n case \"SPEAKING\":\n return \"speaking\";\n default:\n return fallback;\n }\n}\n\nexport class RealtimeEngine implements VoiceEngine {\n readonly capabilities: ReadonlySet<Capability> = new Set<Capability>([MUTE]);\n\n private readonly settings: VoiceSettings;\n private readonly deps: RealtimeDeps;\n private readonly hub = new EventHub<VoiceEvent>();\n\n private socket: SocketLike | undefined;\n private playback: AudioPlayback | undefined;\n private playbackRate = 0;\n private microphone: MicrophoneCapture | undefined;\n private microphoneStarting: Promise<void> | undefined;\n private muted: boolean;\n private opened = false;\n private ready = false;\n private closing = false;\n private turn: TurnState = \"idle\";\n private proxySessionId = \"\";\n private lastError: string | undefined;\n private settle: { resolve(): void; reject(error: VoiceError): void } | undefined;\n\n constructor(settings: VoiceSettings, deps: RealtimeDeps = {}) {\n this.settings = settings;\n this.deps = deps;\n this.muted = !settings.mic;\n }\n\n async enter(): Promise<void> {\n if (this.opened) throw new VoiceError(\"CONNECTION_FAILED\", \"o motor realtime já foi aberto\");\n const problem = validateSettings(this.settings);\n if (problem) throw new VoiceError(\"INVALID_SETTINGS\", problem);\n this.opened = true;\n\n try {\n this.proxySessionId = this.settings.proxySessionId || (await this.lease());\n const url = joinServerUrl(this.settings.proxyServerUrl, this.settings.proxyWebSocketEndpoint);\n url.protocol = url.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n url.searchParams.set(\"session\", this.proxySessionId);\n\n const ready = new Promise<void>((resolve, reject) => {\n this.settle = { resolve, reject };\n });\n const timer = setTimeout(() => {\n this.settle?.reject(new VoiceError(\"TIMEOUT\", `o servidor de áudio não respondeu em ${this.settings.connectTimeoutMs} ms`));\n }, this.settings.connectTimeoutMs);\n\n const open = this.deps.socket ?? browserSocket;\n this.socket = open(url.href, {\n onOpen: () => this.trace(\"socket aberto\"),\n onMessage: (data) => void this.onMessage(data),\n onError: () => this.fail(new VoiceError(\"CONNECTION_FAILED\", `não foi possível conectar a ${url.origin}${url.pathname}`)),\n onClose: (code, reason) => this.onClose(code, reason),\n });\n\n try {\n await ready;\n } finally {\n clearTimeout(timer);\n }\n } catch (error) {\n await this.teardown();\n throw error;\n }\n }\n\n async exit(): Promise<void> {\n this.closing = true;\n await this.teardown();\n }\n\n snapshot(): VoiceSnapshot {\n return {\n mode: \"realtime\",\n channel: \"voice\",\n state: this.turn,\n muted: this.muted,\n sessionId: this.proxySessionId || this.settings.sessionId,\n messageCount: 0,\n ...(this.lastError ? { error: this.lastError } : {}),\n };\n }\n\n setMuted(muted: boolean): void {\n if (this.muted === muted) return;\n this.muted = muted;\n this.sendJson({ type: \"mute\", on: muted });\n if (!muted && this.ready) void this.ensureMicrophone();\n }\n\n submit(_text: string): void {\n throw new VoiceError(\"UNSUPPORTED\", \"o motor realtime não aceita texto digitado: fale pelo microfone\");\n }\n\n sendFunctionResult(callId: string, result: Record<string, unknown>): void {\n this.sendJson({ type: \"function.result\", call_id: callId, result });\n }\n\n events(): AsyncIterable<VoiceEvent> {\n return this.hub.subscribe();\n }\n\n private async lease(): Promise<string> {\n const doFetch = this.deps.fetch ?? globalThis.fetch;\n if (typeof doFetch !== \"function\") throw new VoiceError(\"UNSUPPORTED\", \"fetch não está disponível neste ambiente\");\n const url = joinServerUrl(this.settings.proxyServerUrl, this.settings.proxyLeaseEndpoint);\n\n let response: Response;\n try {\n response = await doFetch(url.href, { credentials: \"include\" });\n } catch (error) {\n throw new VoiceError(\"LEASE_FAILED\", `não foi possível alcançar ${url.origin}${url.pathname}`, { cause: error });\n }\n const body = asRecord(await response.json().catch(() => undefined));\n if (!response.ok) {\n const reason = typeof body?.[\"error\"] === \"string\" ? body[\"error\"] : `HTTP ${response.status}`;\n throw new VoiceError(\"LEASE_FAILED\", `sessão de voz recusada: ${reason}`);\n }\n const id = body?.[\"session_id\"];\n if (typeof id !== \"string\" || id === \"\") throw new VoiceError(\"LEASE_FAILED\", \"o servidor não devolveu session_id\");\n return id;\n }\n\n private async onMessage(data: string | ArrayBuffer): Promise<void> {\n if (typeof data !== \"string\") {\n if (this.playback && data.byteLength >= 2) this.playback.push(new Int16Array(data, 0, Math.floor(data.byteLength / 2)));\n return;\n }\n\n let message: Record<string, unknown> | undefined;\n try {\n message = asRecord(JSON.parse(data));\n } catch {\n return;\n }\n const rate = typeof message?.[\"rate\"] === \"number\" ? message[\"rate\"] : 0;\n\n switch (message?.[\"type\"]) {\n case \"ready\":\n try {\n await this.startAudio(rate);\n this.ready = true;\n this.turn = \"listening\";\n this.emit({ kind: \"session.ready\", data: this.snapshot() });\n this.settle?.resolve();\n } catch (error) {\n this.fail(error instanceof VoiceError ? error : new VoiceError(\"CONNECTION_FAILED\", \"falha ao iniciar o áudio\", { cause: error }));\n }\n return;\n case \"rate\":\n if (this.ready) await this.startPlayback(rate).catch((error: unknown) => this.report(error));\n return;\n case \"state\":\n this.onState(message);\n return;\n case \"function.call\": {\n const callId = typeof message[\"call_id\"] === \"string\" ? message[\"call_id\"] : \"\";\n const name = typeof message[\"name\"] === \"string\" ? message[\"name\"] : \"\";\n const args = asRecord(message[\"arguments\"]);\n if (callId && name && args) this.emit({ kind: \"function.call\", data: { callId, name, arguments: args } });\n return;\n }\n case \"mark\": {\n const { id, label, offset } = message;\n if (typeof id === \"number\" && typeof label === \"string\" && typeof offset === \"number\") this.playback?.mark({ id, label, offset });\n return;\n }\n case \"clear\":\n this.playback?.clear();\n return;\n case \"refused\": {\n const reason = typeof message[\"reason\"] === \"string\" ? message[\"reason\"] : \"sem motivo informado\";\n this.fail(new VoiceError(\"REFUSED\", `o servidor recusou a sessão: ${reason}`));\n return;\n }\n default:\n return;\n }\n }\n\n private onState(message: Record<string, unknown>): void {\n this.turn = turnStateFrom(message[\"state\"], this.turn);\n const detail = typeof message[\"detail\"] === \"string\" ? message[\"detail\"] : \"\";\n switch (message[\"event\"]) {\n case \"user.started\":\n this.emit({ kind: \"user.started\", data: { channel: \"voice\" } });\n return;\n case \"agent.started\":\n this.emit({ kind: \"agent.started\", data: { turn: this.turn } });\n return;\n case \"sound.started\":\n this.emit({ kind: \"sound.started\", data: {} });\n return;\n case \"speaker.idle\":\n this.emit({ kind: \"speaker.idle\", data: {} });\n return;\n case \"error\":\n this.lastError = detail || \"erro no servidor de áudio\";\n this.emit({ kind: \"error\", data: { message: this.lastError, code: \"SERVER_ERROR\" } });\n return;\n case \"turn.heard\":\n if (detail.trim()) this.emit({ kind: \"turn.heard\", data: { text: detail, channel: \"voice\" } });\n return;\n default:\n return;\n }\n }\n\n private async startAudio(rate: number): Promise<void> {\n await this.startPlayback(rate);\n if (!this.muted) await this.ensureMicrophone();\n }\n\n private async startPlayback(rate: number): Promise<void> {\n if (!Number.isFinite(rate) || rate <= 0) throw new VoiceError(\"CONNECTION_FAILED\", `taxa de áudio inválida: ${rate}`);\n if (this.playback && this.playbackRate === rate) return;\n const previous = this.playback;\n this.playback = undefined;\n await previous?.close();\n const open = this.deps.playback ?? startBrowserPlayback;\n this.playback = await open(rate, (report) => this.sendPlaybackReport(report));\n this.playbackRate = rate;\n }\n\n private ensureMicrophone(): Promise<void> {\n if (this.microphone) return Promise.resolve();\n this.microphoneStarting ??= (async () => {\n const open = this.deps.microphone ?? startBrowserMicrophone;\n try {\n this.microphone = await open((frame) => this.sendFrame(frame));\n if (this.closing) await this.stopMicrophone();\n } finally {\n this.microphoneStarting = undefined;\n }\n })();\n return this.microphoneStarting;\n }\n\n private async stopMicrophone(): Promise<void> {\n const microphone = this.microphone;\n this.microphone = undefined;\n await microphone?.stop();\n }\n\n private sendFrame(frame: MicrophoneFrame): void {\n if (this.muted || !this.socket?.isOpen || frame.pcm.length !== CAPTURE_BLOCK) return;\n const bytes = new Uint8Array(4 + frame.pcm.byteLength);\n new DataView(bytes.buffer).setUint32(0, frame.block, true);\n bytes.set(new Uint8Array(frame.pcm.buffer, frame.pcm.byteOffset, frame.pcm.byteLength), 4);\n this.socket.send(bytes);\n }\n\n private sendPlaybackReport(report: PlaybackReport): void {\n this.sendJson(report);\n }\n\n private sendJson(message: Readonly<Record<string, unknown>>): void {\n if (this.socket?.isOpen) this.socket.send(JSON.stringify(message));\n }\n\n private onClose(code: number, reason: string): void {\n if (this.closing) return;\n const error = new VoiceError(\"CONNECTION_FAILED\", `conexão encerrada pelo servidor (código ${code}${reason ? `: ${reason}` : \"\"})`);\n if (!this.ready) {\n this.settle?.reject(error);\n return;\n }\n this.lastError = error.message;\n this.emit({ kind: \"error\", data: { message: error.message, code: \"CLOSED\" } });\n void this.teardown();\n }\n\n /** Falha durante a abertura rejeita `enter()`; depois dela vira evento de erro. */\n private fail(error: VoiceError): void {\n if (!this.ready) {\n this.settle?.reject(error);\n return;\n }\n this.report(error);\n }\n\n private report(error: unknown): void {\n const message = error instanceof Error ? error.message : \"erro de áudio\";\n this.lastError = message;\n this.emit({ kind: \"error\", data: { message, code: error instanceof VoiceError ? error.code : \"AUDIO_ERROR\" } });\n }\n\n private emit(event: VoiceEvent): void {\n this.hub.emit(event);\n }\n\n private trace(message: string): void {\n if (this.settings.trace) console.debug(`[RealtimeEngine] ${message}`);\n }\n\n private async teardown(): Promise<void> {\n this.closing = true;\n this.ready = false;\n this.turn = \"idle\";\n const socket = this.socket;\n const playback = this.playback;\n this.socket = undefined;\n this.playback = undefined;\n await this.stopMicrophone().catch(() => undefined);\n await playback?.close().catch(() => undefined);\n if (socket?.isOpen) socket.close(1000, \"fim da sessão\");\n this.hub.close();\n }\n}\n","/** Escolhe o motor de voz pela configuração. Criar o motor não abre microfone nem conexão: isso é `enter()`. */\n\nimport type { VoiceSettings } from \"./config.ts\";\nimport { LocalEngine, type LocalDeps } from \"./local-engine.ts\";\nimport { RealtimeEngine, type RealtimeDeps } from \"./realtime-engine.ts\";\nimport type { VoiceEngine } from \"./types.ts\";\n\nexport interface EngineDeps {\n realtime?: RealtimeDeps;\n local?: LocalDeps;\n}\n\nexport function createEngine(settings: VoiceSettings, deps: EngineDeps = {}): VoiceEngine {\n return settings.speechMode === \"realtime\"\n ? new RealtimeEngine(settings, deps.realtime)\n : new LocalEngine(settings, deps.local);\n}\n\nexport { LocalEngine, RealtimeEngine };\nexport type { LocalDeps, RealtimeDeps };\n","/**\n * Sessão de voz: gerencia o contexto compartilhado, enfileira inputs,\n * emite eventos públicos e coordena alternância entre voz e chat.\n *\n * Desenho:\n * - Estado mutável fica tudo aqui (contexto, histórico, turno).\n * - Um dono por estado: só VoiceSession muda VoiceContext.\n * - Eventos públicos vão para quem chamar `events()`; cada chamada recebe a sua fila.\n * - Bridge e controlador observam; nunca escrevem direto.\n * - Controle opcional de mutação (padrão: true / ativado).\n * Pode ser desativado para modo read-only (debug/auditoria).\n *\n * Criar a sessão não liga nada: o microfone/conexão só abrem em `abrir(engine)`.\n */\n\nimport type { UserInput, VoiceSessionReader } from \"./bridge.ts\";\nimport type { VoiceSettings } from \"./config.ts\";\nimport { createEngine, type EngineDeps } from \"./engines.ts\";\nimport { EventHub } from \"./events.ts\";\nimport type {\n InputChannel,\n TurnState,\n VoiceContext,\n VoiceEngine,\n VoiceEvent,\n VoiceSnapshot,\n} from \"./types.ts\";\n\n/**\n * Eventos que a engine pode repassar ao público. `session.ready` é da própria sessão e `turn.heard` também:\n * a engine só informa o que ouviu, e a sessão o aceita como entrada e o publica uma única vez.\n */\nconst FORWARDED_EVENTS: ReadonlySet<VoiceEvent[\"kind\"]> = new Set([\n \"user.started\",\n \"function.call\",\n \"agent.started\",\n \"agent.finished\",\n \"sound.started\",\n \"speaker.idle\",\n \"error\",\n]);\n\nexport class VoiceSession implements VoiceSessionReader {\n readonly sessionId: string;\n readonly context: VoiceContext;\n\n private engine: VoiceEngine | null = null;\n private readonly settings: VoiceSettings;\n private readonly inputQueue: UserInput[] = [];\n private readonly hub = new EventHub<VoiceEvent>();\n private isClosed = false;\n\n /** Flag para ativar/desativar mutação de estado. Padrão: true (ativado). */\n private allowMutations: boolean;\n\n constructor(settings: VoiceSettings, allowMutations = true) {\n this.settings = settings;\n this.sessionId = settings.sessionId;\n this.allowMutations = allowMutations;\n\n this.context = {\n mode: settings.speechMode,\n channel: \"voice\",\n turn: \"idle\",\n sessionId: this.sessionId,\n conversationHistory: [],\n muted: !settings.mic,\n lastTurnTime: undefined,\n };\n }\n\n /** Abre a sessão: abre a engine e passa a repassar os eventos dela. */\n async abrir(engine: VoiceEngine): Promise<void> {\n if (this.engine) throw new Error(\"sessão já está aberta\");\n\n // Inscrever antes de abrir: eventos emitidos durante `enter()` não se perdem.\n const engineEvents = engine.events();\n this.engine = engine;\n this.isClosed = false;\n\n try {\n await engine.enter();\n } catch (error) {\n this.engine = null;\n throw error;\n }\n\n this.emitPublic({ kind: \"session.ready\", data: this.snapshot() });\n void this.processEngineEvents(engineEvents);\n }\n\n /** Fecha a sessão. */\n async fechar(): Promise<void> {\n this.isClosed = true;\n\n const engine = this.engine;\n this.engine = null;\n if (engine) {\n try {\n await engine.exit();\n } catch (error) {\n console.error(\"[VoiceSession.fechar]\", error);\n }\n }\n\n this.hub.close();\n }\n\n /** Snapshot atual para o painel. */\n snapshot(): VoiceSnapshot {\n return {\n mode: this.context.mode,\n channel: this.context.channel,\n state: this.context.turn,\n muted: this.context.muted,\n sessionId: this.sessionId,\n messageCount: this.context.conversationHistory.length,\n lastMessageTime: this.context.lastTurnTime,\n };\n }\n\n /** Habilita/desabilita mutação de estado. Padrão: true (ativado). false = modo auditoria (read-only). */\n setMutationsAllowed(allowed: boolean): void {\n this.allowMutations = allowed;\n }\n\n /** Enfileira entrada do usuário (voz transcrita ou texto). Sem efeito se mutações desabilitadas. */\n enqueueInput(input: UserInput): void {\n if (!this.allowMutations) return;\n this.inputQueue.push(input);\n this.processNextInput();\n }\n\n /** Muta/desmuta o microfone. Sem efeito se mutações desabilitadas. */\n setMuted(muted: boolean): void {\n if (!this.allowMutations) return;\n this.context.muted = muted;\n this.engine?.setMuted(muted);\n }\n\n /** Devolve o resultado de uma function/tool call ao agente Realtime que a solicitou. */\n sendFunctionResult(callId: string, result: Record<string, unknown>): void {\n if (!this.allowMutations) return;\n this.engine?.sendFunctionResult?.(callId, result);\n }\n\n /** Eventos públicos a partir de agora, até a sessão fechar. Cada chamada tem a sua fila. */\n events(): AsyncIterable<VoiceEvent> {\n return this.hub.subscribe();\n }\n\n /**\n * Alterna entre voz e chat.\n * O histórico é preservado; apenas a origem do próximo input muda.\n * Sem efeito se mutações desabilitadas.\n */\n switchToChannel(channel: InputChannel): void {\n if (!this.allowMutations) return;\n if (this.context.channel === channel) return;\n\n const from = this.context.channel;\n this.context.channel = channel;\n this.emitPublic({ kind: \"channel.switched\", data: { from, to: channel } });\n }\n\n /** Processa a fila: atualiza histórico e avisa o bridge. */\n private processNextInput(): void {\n if (!this.allowMutations) return;\n const input = this.inputQueue.shift();\n if (!input) return;\n\n this.context.turn = \"listening\";\n this.context.channel = input.channel;\n this.context.lastTurnTime = input.timestamp;\n\n this.context.conversationHistory.push({\n role: \"user\",\n content: input.utterance,\n timestamp: input.timestamp,\n channel: input.channel,\n confidence: input.confidence,\n });\n\n this.emitPublic({\n kind: \"turn.heard\",\n data: { text: input.utterance, confidence: input.confidence, channel: input.channel },\n });\n this.context.turn = \"processing\";\n }\n\n /** Repassa os eventos da engine enquanto a sessão estiver aberta. */\n private async processEngineEvents(stream: AsyncIterable<VoiceEvent>): Promise<void> {\n try {\n for await (const engineEvent of stream) {\n if (this.isClosed) break;\n this.handleEngineEvent(engineEvent);\n }\n } catch (error) {\n this.emitPublic({\n kind: \"error\",\n data: { message: error instanceof Error ? error.message : \"erro desconhecido\", code: \"ENGINE_ERROR\" },\n });\n }\n }\n\n private handleEngineEvent(event: VoiceEvent): void {\n if (event.kind === \"turn.heard\") {\n this.enqueueInput({\n utterance: event.data.text,\n channel: event.data.channel ?? \"voice\",\n confidence: event.data.confidence,\n timestamp: Date.now(),\n });\n return;\n }\n if (FORWARDED_EVENTS.has(event.kind)) this.emitPublic(event);\n\n if (event.kind === \"agent.finished\") {\n this.context.conversationHistory.push({\n role: \"assistant\",\n content: event.data.text,\n timestamp: Date.now(),\n channel: this.context.channel,\n });\n this.context.turn = \"idle\";\n }\n\n if (event.kind === \"agent.started\") this.context.turn = \"speaking\";\n }\n\n private emitPublic(event: VoiceEvent): void {\n this.hub.emit(event);\n }\n}\n\n/**\n * Cria a sessão e abre a engine escolhida por `settings.speechMode`.\n * É aqui — e só aqui — que o microfone/conexão são pedidos; importar o módulo ou criar `VoiceSession` não os liga.\n */\nexport async function createVoiceSession(settings: VoiceSettings, deps: EngineDeps = {}): Promise<VoiceSession> {\n const session = new VoiceSession(settings);\n await session.abrir(createEngine(settings, deps));\n return session;\n}\n\nexport type { TurnState };\n","/**\n * O PLUGUE DE VOZ. Conecta a sessão de voz ao page-agent e ao cérebro.\n *\n * Desenho (seção 5.1 da spec de voz):\n * - Este módulo é o ÚNICO que conhece tanto VoiceSession quanto page-agent.\n * - Abaixo dele fica a sessão de voz: testável sem navegador, sem LLM, sem page-agent.\n * - Acima ficam observadores (painel, brain, rotina-executor) que consomem eventos.\n *\n * O que conecta: OBSERVAÇÃO (leitura) apenas.\n * - Entrada de voz → prepara e passa ao agente como \"input do usuário\"\n * - Resposta do agente → transforma em fala (não está aqui; é na sessão)\n * - Contexto compartilhado: histórico de mensagens + estado do agente\n *\n * Um dono por estado mutável: VoiceSessionController é quem gerencia\n * alternância voz ↔ chat, e ninguém mais mexe no state de turno.\n */\n\nimport type { VoiceContext, Message, InputChannel, VoiceEvent, VoiceSnapshot } from \"./types.ts\";\n\n/**\n * Entrada de um usuário: voz transcrita ou texto digitado.\n * Ambos são normalizados para \"utterance\" (enunciado).\n */\nexport type UserInput = {\n utterance: string;\n channel: InputChannel;\n confidence?: number; // 0-1 para transcrição de fala\n timestamp: number;\n};\n\n/**\n * Contrato que a sessão de voz expõe para o bridge.\n * Apenas leitura: o bridge nunca escreve no estado da sessão.\n */\nexport interface VoiceSessionReader {\n readonly sessionId: string;\n readonly context: VoiceContext;\n readonly snapshot: () => VoiceSnapshot;\n\n /** Enfileira entrada do usuário (voz ou texto). */\n enqueueInput(input: UserInput): void;\n\n /** Itera eventos públicos. */\n events(): AsyncIterable<VoiceEvent>;\n\n /** Muta/desmuta. */\n setMuted(muted: boolean): void;\n}\n\n/**\n * Ponte entre a sessão e quem a consome.\n *\n * - Texto digitado entra por `onUserText`, que só o enfileira na sessão.\n * - Os eventos públicos da sessão entram por `onVoiceEvent`, que REGISTRA o que a sessão aceitou\n * (`turn.heard`) e o que o agente respondeu (`agent.finished`). Ele nunca reenfileira: a sessão\n * já absorveu a fala do motor, e reenfileirar o `turn.heard` público criaria um ciclo infinito.\n *\n * Exemplo de uso:\n * const bridge = new VoiceBridge(voiceSession);\n * chatInput.onSubmit((text) => bridge.onUserText(text));\n * for await (const ev of voiceSession.events()) bridge.onVoiceEvent(ev);\n */\nexport class VoiceBridge {\n private session: VoiceSessionReader;\n private messageBuffer: Message[] = [];\n\n constructor(session: VoiceSessionReader) {\n this.session = session;\n }\n\n /**\n * Entrada de texto do usuário (chat): vira UserInput e é enfileirada na sessão.\n * O registro no histórico da ponte acontece quando a sessão publicar o `turn.heard` correspondente.\n */\n onUserText(text: string): void {\n if (!text.trim()) return;\n\n this.session.enqueueInput({\n utterance: text.trim(),\n channel: \"chat\",\n timestamp: Date.now(),\n });\n }\n\n /**\n * Evento público da sessão. `turn.heard` e `agent.finished` entram no histórico da ponte;\n * nenhum evento aqui cria entrada nova na sessão.\n */\n onVoiceEvent(event: VoiceEvent): void {\n if (event.kind === \"turn.heard\") {\n const { text, confidence, channel } = event.data;\n if (!text.trim()) return;\n\n this.recordMessage({\n role: \"user\",\n content: text,\n channel: channel ?? \"voice\",\n confidence,\n timestamp: Date.now(),\n });\n }\n\n if (event.kind === \"agent.finished\") {\n const { text } = event.data;\n this.recordMessage({\n role: \"assistant\",\n content: text,\n channel: this.session.context.channel,\n timestamp: Date.now(),\n });\n }\n\n if (event.kind === \"channel.switched\") {\n const { from, to } = event.data;\n // Nada a fazer aqui: o histórico não muda.\n // O contexto já foi atualizado pela sessão.\n console.debug(`[VoiceBridge] canais alternados: ${from} → ${to}`);\n }\n }\n\n /**\n * Contexto atual: tudo o que o page-agent precisa saber sobre voz.\n * Leitura apenas; nunca muta.\n */\n get context(): VoiceContext {\n return this.session.context;\n }\n\n /**\n * Snapshot para o painel.\n */\n get snapshot(): VoiceSnapshot {\n return this.session.snapshot();\n }\n\n /**\n * Histórico de mensagens: construído incrementalmente\n * conforme entradas chegam e respostas são produzidas.\n */\n get messages(): readonly Message[] {\n return this.messageBuffer;\n }\n\n /**\n * Muta/desmuta o microfone.\n */\n setMuted(muted: boolean): void {\n this.session.setMuted(muted);\n }\n\n /**\n * Registra uma mensagem no histórico local do bridge.\n * Não persiste (isso é job do store se necessário).\n */\n private recordMessage(msg: Omit<Message, \"timestamp\"> & { timestamp: number }): void {\n this.messageBuffer.push(msg as Message);\n\n // Keeps recent history: discard older than 2 hours\n const cutoff = Date.now() - 2 * 60 * 60 * 1000;\n this.messageBuffer = this.messageBuffer.filter((m) => m.timestamp > cutoff);\n }\n\n /**\n * Reset para testes ou novo começo de conversa.\n */\n clearHistory(): void {\n this.messageBuffer = [];\n }\n}\n\n/**\n * Controlador de sessão: gerencia transições de canal (voz ↔ chat)\n * e manutenção de estado.\n *\n * Um dono por estado mutável: só aqui muda de \"voice\" para \"chat\".\n * Ninguém mais muda VoiceContext.channel.\n */\nexport class VoiceSessionController {\n private session: VoiceSessionReader;\n private bridge: VoiceBridge;\n\n constructor(session: VoiceSessionReader, bridge: VoiceBridge) {\n this.session = session;\n this.bridge = bridge;\n }\n\n /**\n * Muda de canal (voz → chat ou chat → voz).\n * O contexto é preservado: histórico segue intacto.\n * Só muda a ORIGEM do próximo input.\n *\n * Implementação: sinaliza à sessão e deixa ela gerir o event loop.\n */\n switchChannel(to: InputChannel): void {\n const from = this.session.context.channel;\n if (from === to) return; // noop\n\n // A sessão trata essa mudança: emite event \"channel.switched\"\n // e atualiza seu próprio context.channel.\n // O bridge recebe e registra no histórico se necessário.\n // (Implementação fica em session.ts)\n console.debug(`[Controller] solicitando mudança: ${from} → ${to}`);\n }\n\n /**\n * Contexto atual (apenas leitura).\n */\n get context(): VoiceContext {\n return this.session.context;\n }\n\n /**\n * Muta/desmuta.\n */\n setMuted(muted: boolean): void {\n this.bridge.setMuted(muted);\n }\n}\n"],"names":[],"mappings":"AAYO,MAAM,mBAAmB,MAAM;AAAA,EAGpC,YAAY,MAAsB,SAAiB,SAA+B;AAChF,UAAM,SAAS,OAAO;AACtB,SAAK,OAAO;AACZ,SAAK,OAAO;AAAA,EACd;AACF;ACqBA,SAAS,yBAA6D;AACpE,QAAM,QAAQ;AACd,SAAO,MAAM,qBAAqB,MAAM;AAC1C;AAEO,SAAS,mBAAmB,UAAkB,kBAAkB,KAAoB;AACzF,QAAM,cAAc,uBAAA;AACpB,MAAI;AAEJ,SAAO;AAAA,IACL,IAAI,gBAAyB;AAC3B,aAAO,gBAAgB;AAAA,IACzB;AAAA,IAEA,SAA0B;AACxB,UAAI,CAAC,YAAa,QAAO,QAAQ,OAAO,IAAI,WAAW,eAAe,mDAAmD,CAAC;AAC1H,aAAO,IAAI,QAAgB,CAAC,SAAS,WAAW;AAC9C,cAAM,cAAc,IAAI,YAAA;AACxB,iBAAS;AACT,YAAI,aAAa;AACjB,YAAI,UAAU;AAEd,cAAM,SAAS,CAAC,YAA8B;AAC5C,cAAI,QAAS;AACb,oBAAU;AACV,uBAAa,KAAK;AAClB,cAAI,WAAW,YAAa,UAAS;AACrC,kBAAA;AAAA,QACF;AACA,cAAM,QAAQ,WAAW,MAAM;AAC7B,cAAI;AACF,wBAAY,KAAA;AAAA,UACd,QAAQ;AAAA,UAER;AACA,iBAAO,MAAM,QAAQ,WAAW,KAAA,CAAM,CAAC;AAAA,QACzC,GAAG,eAAe;AAElB,oBAAY,OAAO;AACnB,oBAAY,aAAa;AACzB,oBAAY,iBAAiB;AAC7B,oBAAY,kBAAkB;AAC9B,oBAAY,WAAW,CAAC,UAAU;AAChC,mBAAS,IAAI,MAAM,aAAa,IAAI,MAAM,QAAQ,QAAQ,KAAK;AAC7D,kBAAM,SAAS,MAAM,QAAQ,CAAC;AAC9B,gBAAI,QAAQ,QAAS,cAAa,OAAO,CAAC,GAAG,cAAc;AAAA,UAC7D;AAAA,QACF;AACA,oBAAY,UAAU,CAAC,UAAU;AAC/B,gBAAM,OAAO,MAAM,SAAS;AAC5B,cAAI,SAAS,eAAe,SAAS,WAAW;AAC9C,mBAAO,MAAM,QAAQ,WAAW,KAAA,CAAM,CAAC;AACvC;AAAA,UACF;AACA,iBAAO,MAAM,OAAO,IAAI,WAAW,cAAc,kCAAkC,IAAI,EAAE,CAAC,CAAC;AAAA,QAC7F;AACA,oBAAY,QAAQ,MAAM,OAAO,MAAM,QAAQ,WAAW,KAAA,CAAM,CAAC;AAEjE,YAAI;AACF,sBAAY,MAAA;AAAA,QACd,SAAS,OAAO;AACd,iBAAO,MAAM,OAAO,IAAI,WAAW,cAAc,6CAA6C,EAAE,OAAO,MAAA,CAAO,CAAC,CAAC;AAAA,QAClH;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IAEA,gBAAsB;AACpB,UAAI;AACF,gBAAQ,KAAA;AAAA,MACV,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,IAEA,MAAM,MAAoB;AACxB,YAAM,QAAQ,WAAW;AACzB,YAAM,QAAQ,KAAK,KAAA;AACnB,UAAI,CAAC,SAAS,CAAC,MAAO;AACtB,YAAM,OAAA;AACN,YAAM,YAAY,IAAI,yBAAyB,KAAK;AACpD,gBAAU,OAAO;AACjB,YAAM,MAAM,SAAS;AAAA,IACvB;AAAA,IAEA,eAAqB;AACnB,iBAAW,iBAAiB,OAAA;AAAA,IAC9B;AAAA,EAAA;AAEJ;AC1HO,MAAM,SAAY;AAAA,EAAlB,cAAA;AACL,SAAiB,kCAAkB,IAAA;AACnC,SAAQ,SAAS;AAAA,EAAA;AAAA,EAEjB,IAAI,WAAoB;AACtB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,KAAK,OAAgB;AACnB,QAAI,KAAK,OAAQ;AACjB,eAAW,cAAc,KAAK,aAAa;AACzC,YAAM,SAAS,WAAW,QAAQ,MAAA;AAClC,UAAI,OAAQ,QAAO,EAAE,OAAO,OAAO,MAAM,OAAO;AAAA,UAC3C,YAAW,MAAM,KAAK,KAAK;AAAA,IAClC;AAAA,EACF;AAAA;AAAA,EAGA,QAAc;AACZ,QAAI,KAAK,OAAQ;AACjB,SAAK,SAAS;AACd,eAAW,cAAc,KAAK,aAAa;AACzC,iBAAW,UAAU,WAAW,QAAQ,OAAO,CAAC,EAAG,QAAO,EAAE,OAAO,QAAW,MAAM,KAAA,CAAM;AAAA,IAC5F;AAAA,EACF;AAAA,EAEA,YAA8B;AAC5B,UAAM,aAAa,EAAE,OAAO,CAAA,GAAW,SAAS,CAAA,EAAC;AACjD,SAAK,YAAY,IAAI,UAAU;AAC/B,UAAM,MAAM;AACZ,WAAO;AAAA,MACL,CAAC,OAAO,aAAa,IAAsB;AACzC,eAAO;AAAA,UACL,OAAmC;AACjC,kBAAM,SAAS,WAAW,MAAM,MAAA;AAChC,gBAAI,WAAW,OAAW,QAAO,QAAQ,QAAQ,EAAE,OAAO,QAAQ,MAAM,OAAO;AAC/E,gBAAI,IAAI,QAAQ;AACd,kBAAI,YAAY,OAAO,UAAU;AACjC,qBAAO,QAAQ,QAAQ,EAAE,OAAO,QAAW,MAAM,MAAM;AAAA,YACzD;AACA,mBAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,QAAQ,KAAK,OAAO,CAAC;AAAA,UAClE;AAAA,UACA,SAAqC;AACnC,gBAAI,YAAY,OAAO,UAAU;AACjC,mBAAO,QAAQ,QAAQ,EAAE,OAAO,QAAW,MAAM,MAAM;AAAA,UACzD;AAAA,QAAA;AAAA,MAEJ;AAAA,IAAA;AAAA,EAEJ;AACF;ACWO,MAAM,aAAa;AACnB,MAAM,OAAO;AACb,MAAM,SAAS;ACrDtB,MAAM,gBAAgB;AAEf,MAAM,YAAmC;AAAA,EAa9C,YAAY,UAAyB,OAAkB,IAAI;AAZ3D,SAAS,eAAwC,oBAAI,IAAgB,CAAC,YAAY,IAAI,CAAC;AAIvF,SAAiB,MAAM,IAAI,SAAA;AAG3B,SAAQ,UAAU;AAClB,SAAQ,OAAkB;AAKxB,SAAK,WAAW;AAChB,SAAK,OAAO;AACZ,SAAK,QAAQ,CAAC,SAAS;AAAA,EACzB;AAAA,EAEA,MAAM,QAAuB;AAC3B,QAAI,KAAK,QAAS,OAAM,IAAI,WAAW,qBAAqB,6BAA6B;AACzF,UAAM,QAAQ,KAAK,KAAK,SAAS,mBAAmB,KAAK,SAAS,QAAQ;AAC1E,QAAI,CAAC,MAAM,iBAAiB,CAAC,KAAK,OAAO;AACvC,YAAM,IAAI,WAAW,eAAe,mDAAmD;AAAA,IACzF;AACA,SAAK,QAAQ;AACb,SAAK,UAAU;AACf,SAAK,OAAO;AACZ,SAAK,IAAI,KAAK,EAAE,MAAM,iBAAiB,MAAM,KAAK,SAAA,GAAY;AAC9D,SAAK,KAAK,WAAW,KAAK;AAAA,EAC5B;AAAA,EAEA,MAAM,OAAsB;AAC1B,SAAK,UAAU;AACf,SAAK,OAAO;AACZ,SAAK,OAAO,cAAA;AACZ,SAAK,OAAO,aAAA;AACZ,SAAK,SAAA;AACL,SAAK,IAAI,MAAA;AAAA,EACX;AAAA,EAEA,WAA0B;AACxB,WAAO;AAAA,MACL,MAAM;AAAA,MACN,SAAS;AAAA,MACT,OAAO,KAAK;AAAA,MACZ,OAAO,KAAK;AAAA,MACZ,WAAW,KAAK,SAAS;AAAA,MACzB,cAAc;AAAA,MACd,GAAI,KAAK,YAAY,EAAE,OAAO,KAAK,UAAA,IAAc,CAAA;AAAA,IAAC;AAAA,EAEtD;AAAA,EAEA,SAAS,OAAsB;AAC7B,QAAI,KAAK,UAAU,MAAO;AAC1B,SAAK,QAAQ;AACb,QAAI,MAAO,MAAK,OAAO,cAAA;AAAA,cACb,SAAA;AAAA,EACZ;AAAA,EAEA,OAAO,MAAoB;AACzB,QAAI,CAAC,KAAK,eAAe,IAAI,WAAW,UAAU,4BAA4B;AAC9E,UAAM,QAAQ,KAAK,KAAA;AACnB,QAAI,MAAO,MAAK,IAAI,KAAK,EAAE,MAAM,cAAc,MAAM,EAAE,MAAM,OAAO,SAAS,OAAA,GAAU;AAAA,EACzF;AAAA;AAAA,EAGA,MAAM,MAAoB;AACxB,SAAK,OAAO,MAAM,IAAI;AAAA,EACxB;AAAA,EAEA,SAAoC;AAClC,WAAO,KAAK,IAAI,UAAA;AAAA,EAClB;AAAA,EAEQ,cAA6B;AACnC,WAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,WAAK,SAAS,MAAM;AAClB,aAAK,SAAS;AACd,gBAAA;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,WAAW,OAAkC;AACzD,WAAO,KAAK,SAAS;AACnB,UAAI,KAAK,OAAO;AACd,aAAK,OAAO;AACZ,cAAM,KAAK,YAAA;AACX;AAAA,MACF;AACA,UAAI;AACF,aAAK,OAAO;AACZ,cAAM,OAAO,MAAM,MAAM,OAAA;AACzB,YAAI,CAAC,KAAK,QAAS;AACnB,YAAI,KAAK,MAAO;AAChB,YAAI,MAAM;AACR,eAAK,IAAI,KAAK,EAAE,MAAM,gBAAgB,MAAM,EAAE,SAAS,QAAA,GAAW;AAClE,eAAK,IAAI,KAAK,EAAE,MAAM,cAAc,MAAM,EAAE,KAAA,GAAQ;AAAA,QACtD,OAAO;AACL,gBAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,aAAa,CAAC;AAAA,QACnE;AAAA,MACF,SAAS,OAAO;AACd,YAAI,CAAC,KAAK,QAAS;AAEnB,aAAK,YAAY,iBAAiB,QAAQ,MAAM,UAAU;AAC1D,aAAK,IAAI,KAAK;AAAA,UACZ,MAAM;AAAA,UACN,MAAM,EAAE,SAAS,KAAK,WAAW,MAAM,iBAAiB,aAAa,MAAM,OAAO,cAAA;AAAA,QAAc,CACjG;AACD,aAAK,UAAU;AACf,aAAK,OAAO;AACZ,aAAK,IAAI,MAAA;AACT;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AC3HO,MAAM,eAAe;AACrB,MAAM,gBAAgB;AAkC7B,MAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA,sCAKc,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qDAQE,aAAa;AAAA;AAAA;AAAA;AAAA,0BAIxC,aAAa;AAAA,qCACF,aAAa;AAAA,8BACpB,aAAa;AAAA;AAAA;AAAA;AAAA,0CAID,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUvD,MAAM,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA6CzB,eAAe,YAAY,SAAuB,QAA+B;AAC/E,QAAM,MAAM,IAAI,gBAAgB,IAAI,KAAK,CAAC,MAAM,GAAG,EAAE,MAAM,kBAAA,CAAmB,CAAC;AAC/E,MAAI;AACF,UAAM,QAAQ,aAAa,UAAU,GAAG;AAAA,EAC1C,UAAA;AACE,QAAI,gBAAgB,GAAG;AAAA,EACzB;AACF;AAEA,SAAS,sBAA4B;AACnC,MAAI,OAAO,iBAAiB,eAAe,OAAO,qBAAqB,aAAa;AAClF,UAAM,IAAI,WAAW,eAAe,sDAAsD;AAAA,EAC5F;AACF;AAEA,SAAS,iBAAiB,MAAwE;AAChG,MAAI,OAAO,SAAS,YAAY,SAAS,KAAM,QAAO;AACtD,QAAM,UAAU;AAChB,SAAO,OAAO,QAAQ,OAAO,MAAM,YAAY,QAAQ,KAAK,aAAa,eAAe,OAAO,QAAQ,IAAI,MAAM;AACnH;AAEA,SAAS,iBAAiB,MAAuC;AAC/D,SAAO,OAAO,SAAS,YAAY,SAAS,QAAQ,OAAQ,KAAiC,MAAM,MAAM;AAC3G;AAGO,MAAM,yBAA4C,OAAO,YAAY;AAC1E,sBAAA;AACA,MAAI,CAAC,UAAU,cAAc,cAAc;AACzC,UAAM,IAAI,WAAW,eAAe,yEAAyE;AAAA,EAC/G;AAEA,QAAM,UAAU,IAAI,aAAa,EAAE,YAAY,cAAc,aAAa,eAAe;AACzF,MAAI;AACJ,MAAI;AACF,UAAM,YAAY,SAAS,eAAe;AAC1C,aAAS,MAAM,UAAU,aAAa,aAAa;AAAA,MACjD,OAAO,EAAE,kBAAkB,MAAM,kBAAkB,MAAM,iBAAiB,KAAA;AAAA,IAAK,CAChF;AACD,UAAM,SAAS,QAAQ,wBAAwB,MAAM;AACrD,UAAM,OAAO,IAAI,iBAAiB,SAAS,mBAAmB;AAC9D,SAAK,KAAK,YAAY,CAAC,UAAiC;AACtD,UAAI,CAAC,iBAAiB,MAAM,IAAI,EAAG;AACnC,cAAQ,EAAE,OAAO,MAAM,KAAK,OAAO,KAAK,IAAI,WAAW,MAAM,KAAK,GAAG,GAAG,IAAI,MAAM,KAAK,IAAI;AAAA,IAC7F;AACA,WAAO,QAAQ,IAAI;AACnB,SAAK,QAAQ,QAAQ,WAAW;AAChC,UAAM,QAAQ,OAAA;AAEd,UAAM,SAAS,OAAO,UAAA;AACtB,WAAO;AAAA,MACL,MAAM,OAAsB;AAC1B,aAAK,KAAK,YAAY;AACtB,eAAO,WAAA;AACP,aAAK,WAAA;AACL,mBAAW,SAAS,OAAQ,OAAM,KAAA;AAClC,cAAM,QAAQ,MAAA;AAAA,MAChB;AAAA,IAAA;AAAA,EAEJ,SAAS,OAAO;AACd,YAAQ,YAAY,QAAQ,CAAC,UAAU,MAAM,MAAM;AACnD,UAAM,QAAQ,MAAA,EAAQ,MAAM,MAAM,MAAS;AAC3C,QAAI,iBAAiB,WAAY,OAAM;AACvC,UAAM,IAAI,WAAW,cAAc,iBAAiB,QAAQ,MAAM,UAAU,sCAAsC;AAAA,MAChH,OAAO;AAAA,IAAA,CACR;AAAA,EACH;AACF;AAGO,MAAM,uBAAwC,OAAO,MAAM,aAAa;AAC7E,sBAAA;AACA,QAAM,UAAU,IAAI,aAAa,EAAE,YAAY,MAAM,aAAa,eAAe;AACjF,MAAI;AACF,UAAM,YAAY,SAAS,gBAAgB;AAC3C,UAAM,OAAO,IAAI,iBAAiB,SAAS,oBAAoB;AAC/D,SAAK,KAAK,YAAY,CAAC,UAAiC;AACtD,UAAI,iBAAiB,MAAM,IAAI,EAAG,UAAS,MAAM,IAAI;AAAA,IACvD;AACA,SAAK,QAAQ,QAAQ,WAAW;AAChC,UAAM,QAAQ,OAAA;AAEd,WAAO;AAAA,MACL,KAAK,OAAyB;AAC5B,cAAM,UAAU,IAAI,aAAa,MAAM,MAAM;AAC7C,iBAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,IAAK,SAAQ,CAAC,KAAK,MAAM,CAAC,KAAK,KAAK;AACtE,aAAK,KAAK,YAAY,EAAE,MAAM,SAAS,SAAS,QAAQ,OAAA,GAAU,CAAC,QAAQ,MAAM,CAAC;AAAA,MACpF;AAAA,MACA,KAAK,MAA0B;AAC7B,aAAK,KAAK,YAAY,EAAE,MAAM,QAAQ,IAAI,KAAK,IAAI,OAAO,KAAK,OAAO,QAAQ,KAAK,QAAQ;AAAA,MAC7F;AAAA,MACA,QAAc;AACZ,aAAK,KAAK,YAAY,EAAE,MAAM,SAAS;AAAA,MACzC;AAAA,MACA,MAAM,QAAuB;AAC3B,aAAK,KAAK,YAAY;AACtB,aAAK,WAAA;AACL,cAAM,QAAQ,MAAA;AAAA,MAChB;AAAA,IAAA;AAAA,EAEJ,SAAS,OAAO;AACd,UAAM,QAAQ,MAAA,EAAQ,MAAM,MAAM,MAAS;AAC3C,UAAM;AAAA,EACR;AACF;ACrMO,MAAM,iBAAmD,OAAO,OAAO;AAAA,EAC5E,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,gBAAgB;AAAA,EAChB,oBAAoB;AAAA,EACpB,wBAAwB;AAAA,EACxB,gBAAgB;AAAA,EAChB,kBAAkB;AAAA,EAClB,KAAK;AAAA,EACL,OAAO;AACT,CAAC;AAKM,SAAS,oBAAoB,YAAoC,IAAmB;AACzF,SAAO,OAAO,OAAO;AAAA,IACnB,GAAG;AAAA,IACH,WAAW,WAAW,KAAK,IAAA,CAAK;AAAA,IAChC,GAAG;AAAA,EAAA,CACJ;AACH;AAMO,SAAS,kBAAkB,KAA8B;AAC9D,QAAM,OAAO,CAAC,KAAa,aAA6B;AACtD,UAAM,QAAQ,IAAI,GAAG;AACrB,WAAO,UAAU,UAAa,UAAU,KAAK,WAAW;AAAA,EAC1D;AACA,QAAM,OAAO,CAAC,KAAa,aAA+B;AACxD,UAAM,QAAQ,IAAI,GAAG;AACrB,QAAI,UAAU,OAAO,UAAU,OAAQ,QAAO;AAC9C,QAAI,UAAU,OAAO,UAAU,QAAS,QAAO;AAC/C,WAAO;AAAA,EACT;AACA,QAAM,cAAc,CAAC,KAAa,aAA6B;AAC7D,UAAM,SAAS,OAAO,SAAS,IAAI,GAAG,KAAK,IAAI,EAAE;AACjD,WAAO,OAAO,UAAU,MAAM,KAAK,SAAS,IAAI,SAAS;AAAA,EAC3D;AAEA,SAAO,oBAAoB;AAAA,IACzB,YAAY,gBAAgB,KAAK,eAAe,KAAK,UAAU,eAAe,UAAU,CAAC,CAAC;AAAA,IAC1F,UAAU,KAAK,kBAAkB,eAAe,QAAQ;AAAA,IACxD,gBAAgB,KAAK,oBAAoB,eAAe,cAAc;AAAA,IACtE,oBAAoB,KAAK,wBAAwB,eAAe,kBAAkB;AAAA,IAClF,wBAAwB,KAAK,4BAA4B,eAAe,sBAAsB;AAAA,IAC9F,gBAAgB,KAAK,oBAAoB,eAAe,cAAc;AAAA,IACtE,kBAAkB,YAAY,sBAAsB,eAAe,gBAAgB;AAAA,IACnF,KAAK,KAAK,OAAO,eAAe,GAAG;AAAA,IACnC,OAAO,KAAK,SAAS,eAAe,KAAK;AAAA,IACzC,GAAI,IAAI,YAAY,IAAI,EAAE,WAAW,IAAI,YAAY,MAAM,CAAA;AAAA,EAAC,CAC7D;AACH;AAGO,SAAS,gBAAgB,QAA4B;AAC1D,QAAM,aAAa,OAAO,KAAA,EAAO,YAAA;AACjC,MAAI,eAAe,OAAO,eAAe,WAAY,QAAO;AAC5D,SAAO;AACT;AAGO,SAAS,iBAAiB,UAA6C;AAC5E,MAAI,SAAS,eAAe,WAAY,QAAO;AAE/C,MAAI;AACJ,MAAI;AACF,aAAS,IAAI,IAAI,SAAS,cAAc;AAAA,EAC1C,QAAQ;AACN,WAAO,4BAA4B,SAAS,cAAc;AAAA,EAC5D;AACA,MAAI,OAAO,aAAa,WAAW,OAAO,aAAa,UAAU;AAC/D,WAAO,sDAAsD,OAAO,QAAQ;AAAA,EAC9E;AACA,MAAI,CAAC,SAAS,uBAAwB,QAAO;AAC7C,MAAI,CAAC,SAAS,kBAAkB,CAAC,SAAS,oBAAoB;AAC5D,WAAO;AAAA,EACT;AACA,SAAO;AACT;AC/DA,MAAM,OAAO;AAEN,MAAM,gBAA+B,CAAC,KAAK,aAAa;AAC7D,QAAM,SAAS,IAAI,UAAU,GAAG;AAChC,SAAO,aAAa;AACpB,SAAO,iBAAiB,QAAQ,MAAM,SAAS,QAAQ;AACvD,SAAO,iBAAiB,SAAS,MAAM,SAAS,SAAS;AACzD,SAAO,iBAAiB,SAAS,CAAC,UAAU,SAAS,QAAQ,MAAM,MAAM,MAAM,MAAM,CAAC;AACtF,SAAO,iBAAiB,WAAW,CAAC,UAAiC;AACnE,UAAM,OAAO,MAAM;AACnB,QAAI,OAAO,SAAS,YAAY,gBAAgB,YAAa,UAAS,UAAU,IAAI;AAAA,EACtF,CAAC;AACD,SAAO;AAAA,IACL,IAAI,SAAkB;AACpB,aAAO,OAAO,eAAe;AAAA,IAC/B;AAAA,IACA,KAAK,MAAiC;AAEpC,aAAO,KAAK,OAAO,SAAS,WAAW,OAAO,IAAI,WAAW,IAAI,CAAC;AAAA,IACpE;AAAA,IACA,MAAM,MAAe,QAAuB;AAC1C,aAAO,MAAM,MAAM,MAAM;AAAA,IAC3B;AAAA,EAAA;AAEJ;AAGO,SAAS,cAAc,MAAc,UAAuB;AACjE,QAAM,MAAM,IAAI,IAAI,IAAI;AACxB,QAAM,SAAS,IAAI,SAAS,QAAQ,QAAQ,EAAE;AAC9C,MAAI,WAAW,GAAG,MAAM,IAAI,SAAS,QAAQ,QAAQ,EAAE,CAAC;AACxD,MAAI,SAAS;AACb,MAAI,OAAO;AACX,SAAO;AACT;AAEA,SAAS,SAAS,OAAqD;AACrE,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK,IAAK,QAAoC;AACrH;AAEA,SAAS,cAAc,aAAsB,UAAgC;AAC3E,UAAQ,aAAA;AAAA,IACN,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EAAA;AAEb;AAEO,MAAM,eAAsC;AAAA,EAqBjD,YAAY,UAAyB,OAAqB,IAAI;AApB9D,SAAS,eAAwC,oBAAI,IAAgB,CAAC,IAAI,CAAC;AAI3E,SAAiB,MAAM,IAAI,SAAA;AAI3B,SAAQ,eAAe;AAIvB,SAAQ,SAAS;AACjB,SAAQ,QAAQ;AAChB,SAAQ,UAAU;AAClB,SAAQ,OAAkB;AAC1B,SAAQ,iBAAiB;AAKvB,SAAK,WAAW;AAChB,SAAK,OAAO;AACZ,SAAK,QAAQ,CAAC,SAAS;AAAA,EACzB;AAAA,EAEA,MAAM,QAAuB;AAC3B,QAAI,KAAK,OAAQ,OAAM,IAAI,WAAW,qBAAqB,gCAAgC;AAC3F,UAAM,UAAU,iBAAiB,KAAK,QAAQ;AAC9C,QAAI,QAAS,OAAM,IAAI,WAAW,oBAAoB,OAAO;AAC7D,SAAK,SAAS;AAEd,QAAI;AACF,WAAK,iBAAiB,KAAK,SAAS,kBAAmB,MAAM,KAAK,MAAA;AAClE,YAAM,MAAM,cAAc,KAAK,SAAS,gBAAgB,KAAK,SAAS,sBAAsB;AAC5F,UAAI,WAAW,IAAI,aAAa,WAAW,SAAS;AACpD,UAAI,aAAa,IAAI,WAAW,KAAK,cAAc;AAEnD,YAAM,QAAQ,IAAI,QAAc,CAAC,SAAS,WAAW;AACnD,aAAK,SAAS,EAAE,SAAS,OAAA;AAAA,MAC3B,CAAC;AACD,YAAM,QAAQ,WAAW,MAAM;AAC7B,aAAK,QAAQ,OAAO,IAAI,WAAW,WAAW,wCAAwC,KAAK,SAAS,gBAAgB,KAAK,CAAC;AAAA,MAC5H,GAAG,KAAK,SAAS,gBAAgB;AAEjC,YAAM,OAAO,KAAK,KAAK,UAAU;AACjC,WAAK,SAAS,KAAK,IAAI,MAAM;AAAA,QAC3B,QAAQ,MAAM,KAAK,MAAM,eAAe;AAAA,QACxC,WAAW,CAAC,SAAS,KAAK,KAAK,UAAU,IAAI;AAAA,QAC7C,SAAS,MAAM,KAAK,KAAK,IAAI,WAAW,qBAAqB,+BAA+B,IAAI,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;AAAA,QACxH,SAAS,CAAC,MAAM,WAAW,KAAK,QAAQ,MAAM,MAAM;AAAA,MAAA,CACrD;AAED,UAAI;AACF,cAAM;AAAA,MACR,UAAA;AACE,qBAAa,KAAK;AAAA,MACpB;AAAA,IACF,SAAS,OAAO;AACd,YAAM,KAAK,SAAA;AACX,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,OAAsB;AAC1B,SAAK,UAAU;AACf,UAAM,KAAK,SAAA;AAAA,EACb;AAAA,EAEA,WAA0B;AACxB,WAAO;AAAA,MACL,MAAM;AAAA,MACN,SAAS;AAAA,MACT,OAAO,KAAK;AAAA,MACZ,OAAO,KAAK;AAAA,MACZ,WAAW,KAAK,kBAAkB,KAAK,SAAS;AAAA,MAChD,cAAc;AAAA,MACd,GAAI,KAAK,YAAY,EAAE,OAAO,KAAK,UAAA,IAAc,CAAA;AAAA,IAAC;AAAA,EAEtD;AAAA,EAEA,SAAS,OAAsB;AAC7B,QAAI,KAAK,UAAU,MAAO;AAC1B,SAAK,QAAQ;AACb,SAAK,SAAS,EAAE,MAAM,QAAQ,IAAI,OAAO;AACzC,QAAI,CAAC,SAAS,KAAK,MAAO,MAAK,KAAK,iBAAA;AAAA,EACtC;AAAA,EAEA,OAAO,OAAqB;AAC1B,UAAM,IAAI,WAAW,eAAe,iEAAiE;AAAA,EACvG;AAAA,EAEA,mBAAmB,QAAgB,QAAuC;AACxE,SAAK,SAAS,EAAE,MAAM,mBAAmB,SAAS,QAAQ,QAAQ;AAAA,EACpE;AAAA,EAEA,SAAoC;AAClC,WAAO,KAAK,IAAI,UAAA;AAAA,EAClB;AAAA,EAEA,MAAc,QAAyB;AACrC,UAAM,UAAU,KAAK,KAAK,SAAS,WAAW;AAC9C,QAAI,OAAO,YAAY,kBAAkB,IAAI,WAAW,eAAe,0CAA0C;AACjH,UAAM,MAAM,cAAc,KAAK,SAAS,gBAAgB,KAAK,SAAS,kBAAkB;AAExF,QAAI;AACJ,QAAI;AACF,iBAAW,MAAM,QAAQ,IAAI,MAAM,EAAE,aAAa,WAAW;AAAA,IAC/D,SAAS,OAAO;AACd,YAAM,IAAI,WAAW,gBAAgB,6BAA6B,IAAI,MAAM,GAAG,IAAI,QAAQ,IAAI,EAAE,OAAO,OAAO;AAAA,IACjH;AACA,UAAM,OAAO,SAAS,MAAM,SAAS,OAAO,MAAM,MAAM,MAAS,CAAC;AAClE,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,SAAS,OAAO,OAAO,OAAO,MAAM,WAAW,KAAK,OAAO,IAAI,QAAQ,SAAS,MAAM;AAC5F,YAAM,IAAI,WAAW,gBAAgB,2BAA2B,MAAM,EAAE;AAAA,IAC1E;AACA,UAAM,KAAK,OAAO,YAAY;AAC9B,QAAI,OAAO,OAAO,YAAY,OAAO,GAAI,OAAM,IAAI,WAAW,gBAAgB,oCAAoC;AAClH,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,UAAU,MAA2C;AACjE,QAAI,OAAO,SAAS,UAAU;AAC5B,UAAI,KAAK,YAAY,KAAK,cAAc,EAAG,MAAK,SAAS,KAAK,IAAI,WAAW,MAAM,GAAG,KAAK,MAAM,KAAK,aAAa,CAAC,CAAC,CAAC;AACtH;AAAA,IACF;AAEA,QAAI;AACJ,QAAI;AACF,gBAAU,SAAS,KAAK,MAAM,IAAI,CAAC;AAAA,IACrC,QAAQ;AACN;AAAA,IACF;AACA,UAAM,OAAO,OAAO,UAAU,MAAM,MAAM,WAAW,QAAQ,MAAM,IAAI;AAEvE,YAAQ,UAAU,MAAM,GAAA;AAAA,MACtB,KAAK;AACH,YAAI;AACF,gBAAM,KAAK,WAAW,IAAI;AAC1B,eAAK,QAAQ;AACb,eAAK,OAAO;AACZ,eAAK,KAAK,EAAE,MAAM,iBAAiB,MAAM,KAAK,SAAA,GAAY;AAC1D,eAAK,QAAQ,QAAA;AAAA,QACf,SAAS,OAAO;AACd,eAAK,KAAK,iBAAiB,aAAa,QAAQ,IAAI,WAAW,qBAAqB,4BAA4B,EAAE,OAAO,MAAA,CAAO,CAAC;AAAA,QACnI;AACA;AAAA,MACF,KAAK;AACH,YAAI,KAAK,MAAO,OAAM,KAAK,cAAc,IAAI,EAAE,MAAM,CAAC,UAAmB,KAAK,OAAO,KAAK,CAAC;AAC3F;AAAA,MACF,KAAK;AACH,aAAK,QAAQ,OAAO;AACpB;AAAA,MACF,KAAK,iBAAiB;AACpB,cAAM,SAAS,OAAO,QAAQ,SAAS,MAAM,WAAW,QAAQ,SAAS,IAAI;AAC7E,cAAM,OAAO,OAAO,QAAQ,MAAM,MAAM,WAAW,QAAQ,MAAM,IAAI;AACrE,cAAM,OAAO,SAAS,QAAQ,WAAW,CAAC;AAC1C,YAAI,UAAU,QAAQ,KAAM,MAAK,KAAK,EAAE,MAAM,iBAAiB,MAAM,EAAE,QAAQ,MAAM,WAAW,KAAA,GAAQ;AACxG;AAAA,MACF;AAAA,MACA,KAAK,QAAQ;AACX,cAAM,EAAE,IAAI,OAAO,OAAA,IAAW;AAC9B,YAAI,OAAO,OAAO,YAAY,OAAO,UAAU,YAAY,OAAO,WAAW,SAAU,MAAK,UAAU,KAAK,EAAE,IAAI,OAAO,QAAQ;AAChI;AAAA,MACF;AAAA,MACA,KAAK;AACH,aAAK,UAAU,MAAA;AACf;AAAA,MACF,KAAK,WAAW;AACd,cAAM,SAAS,OAAO,QAAQ,QAAQ,MAAM,WAAW,QAAQ,QAAQ,IAAI;AAC3E,aAAK,KAAK,IAAI,WAAW,WAAW,gCAAgC,MAAM,EAAE,CAAC;AAC7E;AAAA,MACF;AAAA,MACA;AACE;AAAA,IAAA;AAAA,EAEN;AAAA,EAEQ,QAAQ,SAAwC;AACtD,SAAK,OAAO,cAAc,QAAQ,OAAO,GAAG,KAAK,IAAI;AACrD,UAAM,SAAS,OAAO,QAAQ,QAAQ,MAAM,WAAW,QAAQ,QAAQ,IAAI;AAC3E,YAAQ,QAAQ,OAAO,GAAA;AAAA,MACrB,KAAK;AACH,aAAK,KAAK,EAAE,MAAM,gBAAgB,MAAM,EAAE,SAAS,QAAA,GAAW;AAC9D;AAAA,MACF,KAAK;AACH,aAAK,KAAK,EAAE,MAAM,iBAAiB,MAAM,EAAE,MAAM,KAAK,KAAA,GAAQ;AAC9D;AAAA,MACF,KAAK;AACH,aAAK,KAAK,EAAE,MAAM,iBAAiB,MAAM,CAAA,GAAI;AAC7C;AAAA,MACF,KAAK;AACH,aAAK,KAAK,EAAE,MAAM,gBAAgB,MAAM,CAAA,GAAI;AAC5C;AAAA,MACF,KAAK;AACH,aAAK,YAAY,UAAU;AAC3B,aAAK,KAAK,EAAE,MAAM,SAAS,MAAM,EAAE,SAAS,KAAK,WAAW,MAAM,eAAA,EAAe,CAAG;AACpF;AAAA,MACF,KAAK;AACH,YAAI,OAAO,KAAA,EAAQ,MAAK,KAAK,EAAE,MAAM,cAAc,MAAM,EAAE,MAAM,QAAQ,SAAS,QAAA,GAAW;AAC7F;AAAA,MACF;AACE;AAAA,IAAA;AAAA,EAEN;AAAA,EAEA,MAAc,WAAW,MAA6B;AACpD,UAAM,KAAK,cAAc,IAAI;AAC7B,QAAI,CAAC,KAAK,MAAO,OAAM,KAAK,iBAAA;AAAA,EAC9B;AAAA,EAEA,MAAc,cAAc,MAA6B;AACvD,QAAI,CAAC,OAAO,SAAS,IAAI,KAAK,QAAQ,EAAG,OAAM,IAAI,WAAW,qBAAqB,2BAA2B,IAAI,EAAE;AACpH,QAAI,KAAK,YAAY,KAAK,iBAAiB,KAAM;AACjD,UAAM,WAAW,KAAK;AACtB,SAAK,WAAW;AAChB,UAAM,UAAU,MAAA;AAChB,UAAM,OAAO,KAAK,KAAK,YAAY;AACnC,SAAK,WAAW,MAAM,KAAK,MAAM,CAAC,WAAW,KAAK,mBAAmB,MAAM,CAAC;AAC5E,SAAK,eAAe;AAAA,EACtB;AAAA,EAEQ,mBAAkC;AACxC,QAAI,KAAK,WAAY,QAAO,QAAQ,QAAA;AACpC,SAAK,wBAAwB,YAAY;AACvC,YAAM,OAAO,KAAK,KAAK,cAAc;AACrC,UAAI;AACF,aAAK,aAAa,MAAM,KAAK,CAAC,UAAU,KAAK,UAAU,KAAK,CAAC;AAC7D,YAAI,KAAK,QAAS,OAAM,KAAK,eAAA;AAAA,MAC/B,UAAA;AACE,aAAK,qBAAqB;AAAA,MAC5B;AAAA,IACF,GAAA;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAc,iBAAgC;AAC5C,UAAM,aAAa,KAAK;AACxB,SAAK,aAAa;AAClB,UAAM,YAAY,KAAA;AAAA,EACpB;AAAA,EAEQ,UAAU,OAA8B;AAC9C,QAAI,KAAK,SAAS,CAAC,KAAK,QAAQ,UAAU,MAAM,IAAI,WAAW,cAAe;AAC9E,UAAM,QAAQ,IAAI,WAAW,IAAI,MAAM,IAAI,UAAU;AACrD,QAAI,SAAS,MAAM,MAAM,EAAE,UAAU,GAAG,MAAM,OAAO,IAAI;AACzD,UAAM,IAAI,IAAI,WAAW,MAAM,IAAI,QAAQ,MAAM,IAAI,YAAY,MAAM,IAAI,UAAU,GAAG,CAAC;AACzF,SAAK,OAAO,KAAK,KAAK;AAAA,EACxB;AAAA,EAEQ,mBAAmB,QAA8B;AACvD,SAAK,SAAS,MAAM;AAAA,EACtB;AAAA,EAEQ,SAAS,SAAkD;AACjE,QAAI,KAAK,QAAQ,OAAQ,MAAK,OAAO,KAAK,KAAK,UAAU,OAAO,CAAC;AAAA,EACnE;AAAA,EAEQ,QAAQ,MAAc,QAAsB;AAClD,QAAI,KAAK,QAAS;AAClB,UAAM,QAAQ,IAAI,WAAW,qBAAqB,2CAA2C,IAAI,GAAG,SAAS,KAAK,MAAM,KAAK,EAAE,GAAG;AAClI,QAAI,CAAC,KAAK,OAAO;AACf,WAAK,QAAQ,OAAO,KAAK;AACzB;AAAA,IACF;AACA,SAAK,YAAY,MAAM;AACvB,SAAK,KAAK,EAAE,MAAM,SAAS,MAAM,EAAE,SAAS,MAAM,SAAS,MAAM,SAAA,EAAS,CAAG;AAC7E,SAAK,KAAK,SAAA;AAAA,EACZ;AAAA;AAAA,EAGQ,KAAK,OAAyB;AACpC,QAAI,CAAC,KAAK,OAAO;AACf,WAAK,QAAQ,OAAO,KAAK;AACzB;AAAA,IACF;AACA,SAAK,OAAO,KAAK;AAAA,EACnB;AAAA,EAEQ,OAAO,OAAsB;AACnC,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AACzD,SAAK,YAAY;AACjB,SAAK,KAAK,EAAE,MAAM,SAAS,MAAM,EAAE,SAAS,MAAM,iBAAiB,aAAa,MAAM,OAAO,cAAA,GAAiB;AAAA,EAChH;AAAA,EAEQ,KAAK,OAAyB;AACpC,SAAK,IAAI,KAAK,KAAK;AAAA,EACrB;AAAA,EAEQ,MAAM,SAAuB;AACnC,QAAI,KAAK,SAAS,eAAe,MAAM,oBAAoB,OAAO,EAAE;AAAA,EACtE;AAAA,EAEA,MAAc,WAA0B;AACtC,SAAK,UAAU;AACf,SAAK,QAAQ;AACb,SAAK,OAAO;AACZ,UAAM,SAAS,KAAK;AACpB,UAAM,WAAW,KAAK;AACtB,SAAK,SAAS;AACd,SAAK,WAAW;AAChB,UAAM,KAAK,eAAA,EAAiB,MAAM,MAAM,MAAS;AACjD,UAAM,UAAU,MAAA,EAAQ,MAAM,MAAM,MAAS;AAC7C,QAAI,QAAQ,OAAQ,QAAO,MAAM,KAAM,eAAe;AACtD,SAAK,IAAI,MAAA;AAAA,EACX;AACF;ACnZO,SAAS,aAAa,UAAyB,OAAmB,IAAiB;AACxF,SAAO,SAAS,eAAe,aAC3B,IAAI,eAAe,UAAU,KAAK,QAAQ,IAC1C,IAAI,YAAY,UAAU,KAAK,KAAK;AAC1C;ACgBA,MAAM,uCAAwD,IAAI;AAAA,EAChE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,MAAM,aAA2C;AAAA,EAatD,YAAY,UAAyB,iBAAiB,MAAM;AAT5D,SAAQ,SAA6B;AAErC,SAAiB,aAA0B,CAAA;AAC3C,SAAiB,MAAM,IAAI,SAAA;AAC3B,SAAQ,WAAW;AAMjB,SAAK,WAAW;AAChB,SAAK,YAAY,SAAS;AAC1B,SAAK,iBAAiB;AAEtB,SAAK,UAAU;AAAA,MACb,MAAM,SAAS;AAAA,MACf,SAAS;AAAA,MACT,MAAM;AAAA,MACN,WAAW,KAAK;AAAA,MAChB,qBAAqB,CAAA;AAAA,MACrB,OAAO,CAAC,SAAS;AAAA,MACjB,cAAc;AAAA,IAAA;AAAA,EAElB;AAAA;AAAA,EAGA,MAAM,MAAM,QAAoC;AAC9C,QAAI,KAAK,OAAQ,OAAM,IAAI,MAAM,uBAAuB;AAGxD,UAAM,eAAe,OAAO,OAAA;AAC5B,SAAK,SAAS;AACd,SAAK,WAAW;AAEhB,QAAI;AACF,YAAM,OAAO,MAAA;AAAA,IACf,SAAS,OAAO;AACd,WAAK,SAAS;AACd,YAAM;AAAA,IACR;AAEA,SAAK,WAAW,EAAE,MAAM,iBAAiB,MAAM,KAAK,SAAA,GAAY;AAChE,SAAK,KAAK,oBAAoB,YAAY;AAAA,EAC5C;AAAA;AAAA,EAGA,MAAM,SAAwB;AAC5B,SAAK,WAAW;AAEhB,UAAM,SAAS,KAAK;AACpB,SAAK,SAAS;AACd,QAAI,QAAQ;AACV,UAAI;AACF,cAAM,OAAO,KAAA;AAAA,MACf,SAAS,OAAO;AACd,gBAAQ,MAAM,yBAAyB,KAAK;AAAA,MAC9C;AAAA,IACF;AAEA,SAAK,IAAI,MAAA;AAAA,EACX;AAAA;AAAA,EAGA,WAA0B;AACxB,WAAO;AAAA,MACL,MAAM,KAAK,QAAQ;AAAA,MACnB,SAAS,KAAK,QAAQ;AAAA,MACtB,OAAO,KAAK,QAAQ;AAAA,MACpB,OAAO,KAAK,QAAQ;AAAA,MACpB,WAAW,KAAK;AAAA,MAChB,cAAc,KAAK,QAAQ,oBAAoB;AAAA,MAC/C,iBAAiB,KAAK,QAAQ;AAAA,IAAA;AAAA,EAElC;AAAA;AAAA,EAGA,oBAAoB,SAAwB;AAC1C,SAAK,iBAAiB;AAAA,EACxB;AAAA;AAAA,EAGA,aAAa,OAAwB;AACnC,QAAI,CAAC,KAAK,eAAgB;AAC1B,SAAK,WAAW,KAAK,KAAK;AAC1B,SAAK,iBAAA;AAAA,EACP;AAAA;AAAA,EAGA,SAAS,OAAsB;AAC7B,QAAI,CAAC,KAAK,eAAgB;AAC1B,SAAK,QAAQ,QAAQ;AACrB,SAAK,QAAQ,SAAS,KAAK;AAAA,EAC7B;AAAA;AAAA,EAGA,mBAAmB,QAAgB,QAAuC;AACxE,QAAI,CAAC,KAAK,eAAgB;AAC1B,SAAK,QAAQ,qBAAqB,QAAQ,MAAM;AAAA,EAClD;AAAA;AAAA,EAGA,SAAoC;AAClC,WAAO,KAAK,IAAI,UAAA;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,gBAAgB,SAA6B;AAC3C,QAAI,CAAC,KAAK,eAAgB;AAC1B,QAAI,KAAK,QAAQ,YAAY,QAAS;AAEtC,UAAM,OAAO,KAAK,QAAQ;AAC1B,SAAK,QAAQ,UAAU;AACvB,SAAK,WAAW,EAAE,MAAM,oBAAoB,MAAM,EAAE,MAAM,IAAI,QAAA,GAAW;AAAA,EAC3E;AAAA;AAAA,EAGQ,mBAAyB;AAC/B,QAAI,CAAC,KAAK,eAAgB;AAC1B,UAAM,QAAQ,KAAK,WAAW,MAAA;AAC9B,QAAI,CAAC,MAAO;AAEZ,SAAK,QAAQ,OAAO;AACpB,SAAK,QAAQ,UAAU,MAAM;AAC7B,SAAK,QAAQ,eAAe,MAAM;AAElC,SAAK,QAAQ,oBAAoB,KAAK;AAAA,MACpC,MAAM;AAAA,MACN,SAAS,MAAM;AAAA,MACf,WAAW,MAAM;AAAA,MACjB,SAAS,MAAM;AAAA,MACf,YAAY,MAAM;AAAA,IAAA,CACnB;AAED,SAAK,WAAW;AAAA,MACd,MAAM;AAAA,MACN,MAAM,EAAE,MAAM,MAAM,WAAW,YAAY,MAAM,YAAY,SAAS,MAAM,QAAA;AAAA,IAAQ,CACrF;AACD,SAAK,QAAQ,OAAO;AAAA,EACtB;AAAA;AAAA,EAGA,MAAc,oBAAoB,QAAkD;AAClF,QAAI;AACF,uBAAiB,eAAe,QAAQ;AACtC,YAAI,KAAK,SAAU;AACnB,aAAK,kBAAkB,WAAW;AAAA,MACpC;AAAA,IACF,SAAS,OAAO;AACd,WAAK,WAAW;AAAA,QACd,MAAM;AAAA,QACN,MAAM,EAAE,SAAS,iBAAiB,QAAQ,MAAM,UAAU,qBAAqB,MAAM,eAAA;AAAA,MAAe,CACrG;AAAA,IACH;AAAA,EACF;AAAA,EAEQ,kBAAkB,OAAyB;AACjD,QAAI,MAAM,SAAS,cAAc;AAC/B,WAAK,aAAa;AAAA,QAChB,WAAW,MAAM,KAAK;AAAA,QACtB,SAAS,MAAM,KAAK,WAAW;AAAA,QAC/B,YAAY,MAAM,KAAK;AAAA,QACvB,WAAW,KAAK,IAAA;AAAA,MAAI,CACrB;AACD;AAAA,IACF;AACA,QAAI,iBAAiB,IAAI,MAAM,IAAI,EAAG,MAAK,WAAW,KAAK;AAE3D,QAAI,MAAM,SAAS,kBAAkB;AACnC,WAAK,QAAQ,oBAAoB,KAAK;AAAA,QACpC,MAAM;AAAA,QACN,SAAS,MAAM,KAAK;AAAA,QACpB,WAAW,KAAK,IAAA;AAAA,QAChB,SAAS,KAAK,QAAQ;AAAA,MAAA,CACvB;AACD,WAAK,QAAQ,OAAO;AAAA,IACtB;AAEA,QAAI,MAAM,SAAS,gBAAiB,MAAK,QAAQ,OAAO;AAAA,EAC1D;AAAA,EAEQ,WAAW,OAAyB;AAC1C,SAAK,IAAI,KAAK,KAAK;AAAA,EACrB;AACF;AAMA,eAAsB,mBAAmB,UAAyB,OAAmB,IAA2B;AAC9G,QAAM,UAAU,IAAI,aAAa,QAAQ;AACzC,QAAM,QAAQ,MAAM,aAAa,UAAU,IAAI,CAAC;AAChD,SAAO;AACT;ACrLO,MAAM,YAAY;AAAA,EAIvB,YAAY,SAA6B;AAFzC,SAAQ,gBAA2B,CAAA;AAGjC,SAAK,UAAU;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,WAAW,MAAoB;AAC7B,QAAI,CAAC,KAAK,OAAQ;AAElB,SAAK,QAAQ,aAAa;AAAA,MACxB,WAAW,KAAK,KAAA;AAAA,MAChB,SAAS;AAAA,MACT,WAAW,KAAK,IAAA;AAAA,IAAI,CACrB;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa,OAAyB;AACpC,QAAI,MAAM,SAAS,cAAc;AAC/B,YAAM,EAAE,MAAM,YAAY,QAAA,IAAY,MAAM;AAC5C,UAAI,CAAC,KAAK,OAAQ;AAElB,WAAK,cAAc;AAAA,QACjB,MAAM;AAAA,QACN,SAAS;AAAA,QACT,SAAS,WAAW;AAAA,QACpB;AAAA,QACA,WAAW,KAAK,IAAA;AAAA,MAAI,CACrB;AAAA,IACH;AAEA,QAAI,MAAM,SAAS,kBAAkB;AACnC,YAAM,EAAE,SAAS,MAAM;AACvB,WAAK,cAAc;AAAA,QACjB,MAAM;AAAA,QACN,SAAS;AAAA,QACT,SAAS,KAAK,QAAQ,QAAQ;AAAA,QAC9B,WAAW,KAAK,IAAA;AAAA,MAAI,CACrB;AAAA,IACH;AAEA,QAAI,MAAM,SAAS,oBAAoB;AACrC,YAAM,EAAE,MAAM,GAAA,IAAO,MAAM;AAG3B,cAAQ,MAAM,oCAAoC,IAAI,MAAM,EAAE,EAAE;AAAA,IAClE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,UAAwB;AAC1B,WAAO,KAAK,QAAQ;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,WAA0B;AAC5B,WAAO,KAAK,QAAQ,SAAA;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,WAA+B;AACjC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,OAAsB;AAC7B,SAAK,QAAQ,SAAS,KAAK;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,cAAc,KAA+D;AACnF,SAAK,cAAc,KAAK,GAAc;AAGtC,UAAM,SAAS,KAAK,IAAA,IAAQ,IAAI,KAAK,KAAK;AAC1C,SAAK,gBAAgB,KAAK,cAAc,OAAO,CAAC,MAAM,EAAE,YAAY,MAAM;AAAA,EAC5E;AAAA;AAAA;AAAA;AAAA,EAKA,eAAqB;AACnB,SAAK,gBAAgB,CAAA;AAAA,EACvB;AACF;AASO,MAAM,uBAAuB;AAAA,EAIlC,YAAY,SAA6B,QAAqB;AAC5D,SAAK,UAAU;AACf,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,cAAc,IAAwB;AACpC,UAAM,OAAO,KAAK,QAAQ,QAAQ;AAClC,QAAI,SAAS,GAAI;AAMjB,YAAQ,MAAM,qCAAqC,IAAI,MAAM,EAAE,EAAE;AAAA,EACnE;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,UAAwB;AAC1B,WAAO,KAAK,QAAQ;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,OAAsB;AAC7B,SAAK,OAAO,SAAS,KAAK;AAAA,EAC5B;AACF;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnivoice/page-agent",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "description": "GUI agent for web applications - add intelligent automation to any webpage with a single script, with an optional realtime voice module",
5
5
  "keywords": [
6
6
  "ai",