
    Pmj|                       U d Z ddlmZ ddlZddlZddlZddlZddlZddlm	Z	m
Z
mZ ddddddZddd	d	d	d
dddddddZ eh d          Z eh d          ZdZdNdZdOdZdOdZddlmZmZmZmZmZ  ej        e          ZdPdZdQdZdRdSd#Z da! ej"                    Z# ej"                    Z$d$a%d$a&d%a'de(d&<   da)de(d'<    ej*                    Z+e+,                                 da-d(e(d)<   dTd+Z.dQd,Z/da0d-e(d.<   da1d-e(d/<   da2d0e(d1<   da3d-e(d2<   da4d3Z5dUd4Z6dVd6Z7	 	 	 	 	 	 	 dWdXdDZ8dYdZdFZ9dQdGZ:dUdHZ;d[d\dLZ<d[d]dMZ=dS )^u  Process-wide voice recording + TTS API for the TUI gateway.

Wraps ``tools.voice_mode`` (recording/transcription) and ``tools.tts_tool``
(text-to-speech) behind idempotent, stateful entry points that the gateway's
``voice.record``, ``voice.toggle``, and ``voice.tts`` JSON-RPC handlers can
call from a dedicated thread. The gateway imports this module lazily so that
missing optional audio deps (sounddevice, faster-whisper, numpy) surface as
an ``ImportError`` at call time, not at startup.

Two usage modes are exposed:

* **Push-to-talk** (``start_recording`` / ``stop_and_transcribe``) — single
  manually-bounded capture used when the caller drives the start/stop pair
  explicitly.
* **Continuous (VAD)** (``start_continuous`` / ``stop_continuous``) — mirrors
  the classic CLI voice mode: recording auto-stops on silence, transcribes,
  hands the result to a callback, and then auto-restarts for the next turn.
  Three consecutive no-speech cycles stop the loop and fire
  ``on_silent_limit`` so the UI can turn the mode off.
    )annotationsN)AnyCallableOptionalc-a-)ctrlcontrolaltoptionoptspaceentertabescape	backspacedelete)r   spcr   returnretr   r   escr   bsr   del>   cdlzc-bcfgr   r   c                    t          | t                    sdS |                     d          }t          |t                    sdS |                    d          S )a  Shape-safe ``cfg.voice.record_key`` lookup.

    ``load_config()`` deep-merges raw YAML and preserves scalar
    overrides, so a hand-edited ``voice: true`` / ``voice: cmd+b``
    leaves ``cfg["voice"]`` as a bool/str instead of a dict, and the
    naive ``.get("voice", {}).get("record_key")`` chain raises
    AttributeError before voice can even start (Copilot round-11 on
    #19835). Return ``None`` for malformed shapes so call sites can
    feed the result straight into the normalizer/formatter and get
    the documented default.
    Nvoice
record_key)
isinstancedictget)r   r   s     6/home/thesage/.hermes/hermes-agent/hermes_cli/voice.pyvoice_record_key_from_configr%   W   sU     c4   tGGGEeT"" t99\"""    rawstrc                   t          | t                    st          S |                                                                 }|st          S d |                    d          D             }|st          S t          |          dk    rt          S t          |          dk    rt          S |\  }}|dv rt          S t                              |          }|st          S t          |          dk    rB|dk    r|t          v rt          S |dk    r t          j        dk    r|t          v rt          S | | S t                              |          }|st          S | | S )	u  Coerce ``voice.record_key`` into prompt_toolkit's ``c-x`` / ``a-x`` format.

    Mirrors the TUI parser contract (``ui-tui/src/lib/platform.ts``)
    so one config value binds the same shortcut in both runtimes:

    * non-string / empty / typo'd / bare-char / multi-modifier / reserved
      ``ctrl+c|d|l`` → documented default ``c-b``
    * single-char keys: ``ctrl+o`` → ``c-o``
    * named keys: ``ctrl+space`` → ``c-space`` (aliases collapse:
      ``ctrl+return`` → ``c-enter``)
    * ``super`` / ``win`` / ``windows`` → ``c-b`` (TUI-only modifiers —
      prompt_toolkit has no super mod; the CLI binding site is
      expected to warn when this fallback fires so users see the
      cross-runtime split, Copilot round-11 on #19835)
    c                ^    g | ]*}|                                 |                                 +S  )strip).0ps     r$   
<listcomp>zAnormalize_voice_record_key_for_prompt_toolkit.<locals>.<listcomp>   s-    @@@1aggii@QWWYY@@@r&   +      >   winsuperwindowsr   r   darwin)r!   r(   _DEFAULT_PT_KEYr,   lowersplitlen_VOICE_MOD_ALIASESr#   _VOICE_RESERVED_CTRL_CHARSsysplatform_VOICE_RESERVED_ALT_CHARS_MAC_VOICE_NAMED_KEYS)r'   loweredpartsmodifier_token	key_tokennormalized_modnameds          r$   -normalize_voice_record_key_for_prompt_toolkitrG   m   s{     c3 iikk!!G @@c 2 2@@@E 
 5zzA~~
 5zzQ %NI 444'++N;;N  9~~T!!i3M&M&M""d""((:::"" -)---
 !!),,E %e%%%r&   c                
   t          |           }|                    d          rd|dd         }}nj|                    d          rd|dd         }}nHd|v rB|                    dd          \  }}|d	                                         |dd         z   dz   }nd
S |s|                    d          S t          |          dk    r||                                z   S ||d	                                         z   |dd         z   S )aF  Render ``voice.record_key`` for ``/voice status`` in CLI-friendly form.

    Mirrors the TUI's ``formatVoiceRecordKey``: returns ``Ctrl+B`` /
    ``Alt+Space`` / ``Ctrl+Enter``. Malformed configs surface as the
    documented default so status never advertises a shortcut that
    won't bind (Copilot round-10 on #19835).
    r   zCtrl+r1   Nr   zAlt+r0   r2   r   zCtrl+B)rG   
startswithr9   upperrstripr:   )r'   
normalizedprefixkeymods        r$   "format_voice_record_key_for_statusrP      s    ?sCCJT"" 
z!""~			t	$	$ jn	
		 ##C++SQ#abb')C/x "}}S!!!
3xx1}}		##CFLLNN"SW,,r&   )create_audio_recorderis_voice_stop_phraseis_whisper_hallucinationplay_audio_filetranscribe_recordingmsgNonec                    t           j                            dd                                          dk    rdS 	 t	          d|  t
          j        d           dS # t          t          f$ r Y dS w xY w)u^  Emit a debug breadcrumb when HERMES_VOICE_DEBUG=1.

    Goes to stderr so the TUI gateway wraps it as a gateway.stderr event,
    which createGatewayEventHandler shows as an Activity line — exactly
    what we need to diagnose "why didn't the loop auto-restart?" in the
    user's real terminal without shipping a separate debug RPC.

    Any OSError / BrokenPipeError is swallowed because this fires from
    background threads (silence callback, TTS daemon, beep) where a
    broken stderr pipe must not kill the whole gateway — the main
    command pipe (stdin+stdout) is what actually matters.
    HERMES_VOICE_DEBUG 1Nz[voice] T)fileflush)	osenvironr#   r,   printr=   stderrBrokenPipeErrorOSError)rV   s    r$   _debugrd      s     
z~~*B//55773>>SZt<<<<<<W%   s   A A0/A0boolc                     	 ddl m}  ddlm}  |                                 di           }t          |t                    r! ||                    dd          d          S n# t          $ r Y nw xY wdS )z=CLI parity: voice.beep_enabled in config.yaml (default True).r   )load_config)is_truthy_valuer   beep_enabledT)default)hermes_cli.configrg   utilsrh   r#   r!   r"   	Exception)rg   rh   	voice_cfgs      r$   _beeps_enabledro      s    
111111))))))KMM%%gr22	i&& 	V #?9==#F#FPTUUUU	V    4s   AA# #
A0/A0r2   	frequencyintcountc                    t                      sdS 	 ddlm}  || |           dS # t          $ r }t	          d|  d|            Y d}~dS d}~ww xY w)uU  Audible cue matching cli.py's record/stop beeps.

    880 Hz single-beep on start (cli.py:_voice_start_recording line 7532),
    660 Hz double-beep on stop (cli.py:_voice_stop_and_transcribe line 7585).
    Best-effort — sounddevice failures are silently swallowed so the
    voice loop never breaks because a speaker was unavailable.
    Nr   )	play_beeprp   rr   zbeep zHz failed: )ro   tools.voice_modert   rm   rd   )rp   rr   rt   es       r$   
_play_beeprx   	  s      2......	IU333333 2 2 20y00Q001111111112s   ' 
AAAFT_continuous_auto_restart_continuous_recorderOptional[Callable[[], bool]]_voice_busy_probeprobec                
    | a dS )u$  Register a callable that returns True while the agent is mid-turn.

    Called by the hosting surface (tui_gateway registers one that checks
    every session's ``running`` flag). ``None`` clears it. The probe must
    be cheap and thread-safe — it runs on the silence-callback thread.
    N)r|   r}   s    r$   set_voice_busy_prober   ;  s     r&   c                     t                                           sdS t          } | dS 	 t           |                       S # t          $ r Y dS w xY w)a0  True while silent cycles must NOT count toward the no-speech limit.

    Held when TTS is playing (the user is listening) or when the
    registered busy probe reports the agent mid-turn (the user is
    waiting). Fail-open to "not held" so a broken probe can never make
    the voice chat immortal.
    TNF)_tts_playingis_setr|   re   rm   r   s    r$   _voice_activity_heldr   F  se        tE}uEEGG}}   uus   ? 
AAOptional[Callable[[str], None]]_continuous_on_transcript_continuous_on_statusOptional[Callable[[], None]]_continuous_on_silent_limit_continuous_on_stop_phrase   c                     t           5  t          $t          t          dd          r	 ddd           dS t                      } |                                  | addd           dS # 1 swxY w Y   dS )u   Begin capturing from the default input device (push-to-talk).

    Idempotent — calling again while a recording is in progress is a no-op.
    Nis_recordingF)_recorder_lock	_recordergetattrrQ   start)recs    r$   start_recordingr   i  s     
   WY%N%N         $%%			                 s   A%$A%%A),A)Optional[str]c                 j   t           5  t          } daddd           n# 1 swxY w Y   | dS |                                 }|sdS 	 t          |          }nz# t          $ rm}t
                              d|           Y d}~	 t          j        	                    |          rt          j
        |           dS dS # t          $ r Y dS w xY wd}~ww xY w	 	 t          j        	                    |          rt          j
        |           nZ# t          $ r Y nNw xY w# 	 t          j        	                    |          rt          j
        |           w w # t          $ r Y w w xY wxY w|                    d          sdS |                    d          pd                                }|rt          |          rdS |S )zStop the active push-to-talk recording, transcribe, return text.

    Returns ``None`` when no recording is active, when the microphone
    captured no speech, or when Whisper returned a known hallucination.
    Nzvoice transcription failed: %ssuccess
transcriptrZ   )r   r   stoprU   rm   loggerwarningr^   pathisfileunlinkr#   r,   rS   )r   wav_pathresultrw   texts        r$   stop_and_transcriber   x  sD    
  	               {txxzzH t
%h//   7;;;ttt	w~~h'' $	(#####$ $ 	 	 	DD	 	
	w~~h'' $	(### 	 	 	D		w~~h'' $	(####$ 	 	 	D	
 ::i   tJJ|$$*1133D +D11 tKs   
""A D 
C C;D  3B7 7
CCCD 3D 
DDE3EE
EEEE         @        on_transcriptCallable[[str], None]	on_statuson_silent_limitsilence_thresholdsilence_durationfloatauto_restartmax_recording_secondson_stop_phrasec                   t           5  t          rt          d           	 ddd           dS t          rt          d           	 ddd           dS da|a| a|a|a|a|rda	t          t                      a
|t          _        |t          _        t          |t          t           f          rt          |t"                    s|dk    r|ndt          _        t          }ddd           n# 1 swxY w Y   t          d| d	| d
           t'          dd           	 |                    t*                     nv# t,          $ ri}	t.                              d|	           t          dt3          |	          j         d|	            t           5  daddd           n# 1 swxY w Y    d}	~	ww xY w|r	  |d           n# t,          $ r Y nw xY wdS )uB  Start a VAD-driven continuous recording loop.

    The loop calls ``on_transcript(text)`` each time speech is detected and
    transcribed successfully. If ``auto_restart`` is True, it auto-restarts
    for the next turn and resets the no-speech counter for that loop. If
    ``auto_restart`` is False, the first silence-triggered transcription ends
    the loop and reports ``"idle"``; no-speech counts are retained across
    starts so a push-to-talk caller can still enforce the three-strikes guard.
    After ``_CONTINUOUS_NO_SPEECH_LIMIT`` consecutive silent cycles (no speech
    picked up at all) the loop stops itself and calls ``on_silent_limit`` so the
    UI can reflect "voice off". Returns False if a previous stop is still
    transcribing/cleaning up; otherwise returns True. Idempotent — calling while
    already active is a successful no-op.

    ``on_status`` is called with ``"listening"`` / ``"transcribing"`` /
    ``"idle"`` so the UI can show a live indicator.

    ``max_recording_seconds`` is the hard cap on a single recording's length
    (``voice.max_recording_seconds``); any non-positive or non-numeric value
    disables the cap, preserving the previous unbounded behaviour.

    ``on_stop_phrase`` is called with the (stripped) transcript when the user
    utters a bare voice stop phrase (``voice.stop_phrases``, default "stop").
    The loop halts first, so the consumer only needs to reflect "voice off" —
    exactly like the user pressing the manual stop control. When omitted,
    ``on_silent_limit`` fires instead so legacy callers still turn voice off.
    u*   start_continuous: already active — no-opNTu6   start_continuous: stop/transcribe in progress — busyFr   r   z#start_continuous: begin (threshold=z, duration=zs)p  r2   ru   on_silence_stopz(failed to start continuous recording: %sz#start_continuous: rec.start raised : 	listening)_continuous_lock_continuous_activerd   _continuous_stoppingry   r   r   r   r   _continuous_no_speech_countrz   rQ   _silence_threshold_silence_durationr!   rq   r   re   _max_recording_secondsrx   r   _continuous_on_silencerm   r   errortype__name__)
r   r   r   r   r   r   r   r   r   rw   s
             r$   start_continuousr     s   T 
 # # 	?@@@# # # # # # # #   	KLLL# # # # # # # # "#/ $1! )&5#%3" 	,*+''#8#:#: 2C/1A.
 /#u>>4d;; &)) "!  	3 #=# # # # # # # # # # # # # # #@ `.?``L\```   A&&&&		"8	9999   ?CCCLT!WW5ELLLLMMM 	' 	'!&	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	'  	Ik"""" 	 	 	D	 4sl   C&C&B	C&&C*-C*D5 5
F(?A	F#FF#F	F#F	F##F(.F: :
GGforce_transcribec                
  	
 t           5  t          s	 ddd           dS dat          }t          t          	t
          t          t          }| o| 
|duadadadada
sda	ddd           n# 1 swxY w Y   |1| r	rr	  d           n# t          $ r Y nw xY w	 |                                n|# t          $ ro}t                              d|           	 |                                 n2# t          $ r%}t                              d|           Y d}~nd}~ww xY wdY d}~nd}~ww xY w	
fd}t          j        |d	                                           dS 	 |                                 n2# t          $ r%}t                              d|           Y d}~nd}~ww xY wt           5  daddd           n# 1 swxY w Y   t%          d
d           r	  d           dS # t          $ r Y dS w xY wdS )u?  Stop the active continuous loop and release the microphone.

    Idempotent — calling while not active is a no-op. If ``force_transcribe`` is
    True, the recorder stops synchronously, then transcription/cleanup runs on a
    background thread before reporting ``"idle"``. Otherwise the buffer is
    discarded.
    NFr   transcribingzfailed to stop recorder: %szfailed to cancel recorder: %sc                    d } d}	 r	 t                    }|                    d          r<|                    d          pd                                }|rt          |          s|} t          j                                      rt	          j                   n9# t          j                                      rt	          j                   w w xY wn2# t          $ r%}t          
                    d|           Y d }~nd }~ww xY wt          | ot          |                     }|rEt          d| d           | pd}d } 	 
 
|           n
              n# t          $ r Y nw xY w| r?	  |            n2# t          $ r%}t          
                    d|           Y d }~nd }~ww xY wrt                      }t          5  | s|rd	an0|rt          d
           nt           dz  at           t"          k    }|rd	ad d d            n# 1 swxY w Y   |rr	               n# t          $ r Y nw xY wt%          dd           t          5  dad d d            n# 1 swxY w Y   	r	  	d           d S # t          $ r Y d S w xY wd S # t          | ot          |                     }|rEt          d| d           | pd}d } 	 
 
|           n
              n# t          $ r Y nw xY w| r?	  |            n2# t          $ r%}t          
                    d|           Y d }~nd }~ww xY wrt                      }t          5  | s|rd	an0|rt          d
           nt           dz  at           t"          k    }|rd	ad d d            n# 1 swxY w Y   |rr	               n# t          $ r Y nw xY wt%          dd           t          5  dad d d            n# 1 swxY w Y   	r	  	d           w # t          $ r Y w w xY ww xY w)NFr   r   rZ   z&failed to stop/transcribe recorder: %szstop_continuous: stop phrase u    — ending voice chat!on_transcript callback raised: %sr   zAstop_continuous: silent cycle ignored (agent busy or TTS playing)r2     r1   ru   idle)rU   r#   r,   rS   r^   r   r   r   rm   r   r   re   rR   rd   r   r   r   _CONTINUOUS_NO_SPEECH_LIMITrx   r   )r   should_haltr   r   rw   stop_phrase	stop_textheldr   r   r   r   track_no_speechr   s           r$   _transcribe_and_cleanupz0stop_continuous.<locals>._transcribe_and_cleanup;  sD   ,0
#J! 	44%9(%C%CF%zz)44 6(.

<(@(@(FB'M'M'O'O#' !60H0N0N !615J!w~~h77 4 "	( 3 3 3  "w~~h77 4 "	( 3 3 3 34  P P PNN#KQOOOOOOOOP #'z'V6J:6V6V"W"WK" ! `J```   %/$4"	%)
!-9 .y 9 9 9 9!0!< / 1 1 1( ! ! ! D!! SS)M*5555( S S S"NN+NPQRRRRRRRRS ' %355- D D) D[ D>? ; ;!% D !'%B!" !" !" !"
 !<q @ ;$?'B%C !, $/ !DBC$?#D D D D D D D D D D D D D D D$ ' %? %% / 1 1 1 1#, % % % $% A6666) 5 5/4,5 5 5 5 5 5 5 5 5 5 5 5 5 5 5  !!%If-----( ! ! ! DD!! !q #'z'V6J:6V6V"W"WK" ! `J```   %/$4"	%)
!-9 .y 9 9 9 9!0!< / 1 1 1( ! ! ! D!! SS)M*5555( S S S"NN+NPQRRRRRRRRS ' %355- D D) D[ D>? ; ;!% D !'%B!" !" !" !"
 !<q @ ;$?'B%C !, $/ !DBC$?#D D D D D D D D D D D D D D D$ ' %? %% / 1 1 1 1#, % % % $% A6666) 5 5/4,5 5 5 5 5 5 5 5 5 5 5 5 5 5 5  !!%If----( ! ! ! D!!s  C A B *4C 6CC J 
D"D=J DJ E 
E,+E,2E> >
F-F((F-8HHH
H& &
H32H3II!$I!+I8 8
JJ:P
K! P
!
K.+P
-K..P
4L ?P
 
L/
L*%P
*L//P
	8NP
NP
NP

N('P
(
N52P
4N55P
OP
O##P
&O#'P
-O98P
9
PP
PP
T)targetdaemonr   r1   ru   r   )r   r   rz   r   r   r   r   ry   r   r   rm   r   r   r   cancel	threadingThreadr   rx   )r   r   r   rw   cancel_errorr   r   r   r   r   r   r   s         @@@@@@r$   stop_continuousr   
  s    
 , ,! 	, , , , , , , , #")	153/*?</?"$$(! $&*#%)" 	,*+'#, , , , , , , , , , , , , , ,&  i	C i	C In----    D 88::      <a@@@RJJLLLL  R R RNN#BLQQQQQQQQR O! O! O! O! O! O! O! O! O! O!b $;DIIIOOQQQFC 

 C C C>BBBBBBBBC 
 % %$% % % % % % % % % % % % % % %
 A&&&& 	If 	 	 	DD	 s   	A1AA11A58A5B 
BB#B8 8
D1D,C32D,3
D"=DD,D""D,,D1)E> >
F-F((F-7GG
G
%G2 2
H ?H c                 R    t           5  t          cddd           S # 1 swxY w Y   dS )z5Whether a continuous voice loop is currently running.N)r   r   r+   r&   r$   is_continuous_activer     so    	 " "!" " " " " " " " " " " " " " " " " "s     c                    t          d           t          5  t          st          d           	 ddd           dS t          } t          }t
          }t          }t          }ddd           n# 1 swxY w Y   | t          d           dS |r	  |d           n# t          $ r Y nw xY w| 	                                }t          | dd          }t          d|d	| d
           t          dd           d}|r	 t          |          }t          |                    d                    }	|                    d          pd                                }
|                    d          }t          d|	 d|
d|           |	r|
rt!          |
          s|
}nY# t          $ rL}t"                              d|           t          dt'          |          j         d|            Y d}~nd}~ww xY w	 t*          j                            |          rt+          j        |           nZ# t          $ r Y nNw xY w# 	 t*          j                            |          rt+          j        |           w w # t          $ r Y w w xY wxY wt          |ot3          |                    }|r|pdnd}|rt          d|d           d}|du o| ot5                      }t          5  t          st          d           	 ddd           dS |rdan|rt          d           n|s
t6          dz  a|pt6          t8          k    }t6          }ddd           n# 1 swxY w Y   |rA|r?	  ||           n2# t          $ r%}t"                              d|           Y d}~nd}~ww xY w|rt          d|rd n| d! d
           t          5  d"adaddd           n# 1 swxY w Y   |r|	  ||           n.# t          $ r Y n"w xY w|r	  |             n# t          $ r Y nw xY w	 |                                  n# t          $ r Y nw xY w|r	  |d#           n# t          $ r Y nw xY wdS t<                                          st          d$           t<                               d%&           ddl!}|"                    d'           t          5  t          st          d(           	 ddd           dS 	 ddd           n# 1 swxY w Y   tF          rt          d)| d
           t          d*d           	 | $                    tJ          +           n# t          $ r}t"          &                    d,|           t          d-t'          |          j         d|            t          5  d"addd           n# 1 swxY w Y   |r	  |d#           n# t          $ r Y nw xY wY d}~dS d}~ww xY w|r	  |d.           dS # t          $ r Y dS w xY wdS t          d/           t          5  d"addd           n# 1 swxY w Y   |r	  |d#           dS # t          $ r Y dS w xY wdS )0u  AudioRecorder silence callback — runs in a daemon thread.

    Stops the current capture, transcribes, delivers the text via
    ``on_transcript``, and — if the loop is still active — starts the
    next capture. Three consecutive silent cycles end the loop.
    z_continuous_on_silence: firedu/   _continuous_on_silence: loop inactive — abortNu-   _continuous_on_silence: no recorder — abortr   	_peak_rmsz$_continuous_on_silence: rec.stop -> z (peak_rms=)r   r1   ru   r   r   rZ   r   z._continuous_on_silence: transcribe -> success=z text=z err=z#continuous transcription failed: %sz*_continuous_on_silence: transcribe raised r   z$_continuous_on_silence: stop phrase u    — ending loopu@   _continuous_on_silence: stopped during transcribe — no restartr   zH_continuous_on_silence: silent cycle ignored (agent busy or TTS playing)r2   r   z!_continuous_on_silence: halting (zstop phrasez silent cyclesFr   z1_continuous_on_silence: waiting for TTS to finish<   )timeout333333?z5_continuous_on_silence: stopped while waiting for TTSz3_continuous_on_silence: restarting loop (no_speech=r   r   z*failed to restart continuous recording: %sz'_continuous_on_silence: restart raised r   z9_continuous_on_silence: auto_restart=False, stopping loop)'rd   r   r   rz   r   r   r   r   rm   r   r   rx   rU   re   r#   r,   rS   r   r   r   r   r^   r   r   r   rR   r   r   r   r   r   r   waittimesleepry   r   r   r   )r   r   r   r   r   r   peak_rmsr   r   r   r   errrw   r   r   _silence_heldr   	no_speech_times                      r$   r   r     s
    *+++	 4 4! 	DEEE4 4 4 4 4 4 4 4 #1)	534 4 4 4 4 4 4 4 4 4 4 4 4 4 4 {>??? 	In%%%% 	 	 	D	 xxzzH sK,,H
QxQQhQQQ   A&&&& $J 	)(33F
 6::i0011GJJ|,,299;;D**W%%C- - -- -%(- -    "4 "(@(F(F "!
 	Y 	Y 	YNN@!DDDWQ@PWWTUWWXXXXXXXX	Y7>>(++ (Ih'''   7>>(++ (Ih''''(    zF&::&F&FGGK&19!rrI 
 	TjTTTUUU
  4' 0O 0-//  
 0 0! 	UVVV	0 0 0 0 0 0 0 0
  	-*+'' 	-.     	-'1,'! 
'+FF 	 0	#0 0 0 0 0 0 0 0 0 0 0 0 0 0 0&  Cm C	CM*%%%% 	C 	C 	CNN>BBBBBBBB	C  R!,NY2N2N2NR R R	
 	
 	
  	, 	,!&*+'	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	,  	>5y))))    	!!!!   	JJLLLL 	 	 	D	 		&!!!!       
BCCC"%%%C  	 	% NOOO	 	 	 	 	 	 	 		 	 	 	 	 	 	 	 	 	 	 	 	 	 	
    QYQQQRRRS****	II&<I==== 
	 
	 
	LLEqIIITT!WW=MTTQRTTUUU! + +%*"+ + + + + + + + + + + + + + + If%%%%    DFFFFF
	  		+&&&&&   	 	 	JKKK 	' 	'!&	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 		&!!!!!   	 	s  A+#A++A/2A/B 
B%$B%:BF H5 
G-!AG(#H5 (G--H5 13H% %
H21H25I<73I,*I<,
I96I<8I99I<M=<MM	M	M! !
N+NN6OOOO# #
O0/O06
P 
PPP' '
P43P4:Q 
QQ;S--S14S1$U   
W
A	WV"W"V&	&W)V&	*W0V<;W<
W	WW		WWW* *
W87W8X""X&)X&0X= =
Y
Yr   
stop_eventOptional[threading.Event]c                0   ddl }ddl}ddlm} |                                }|                    |            |                    d           ||                                }|                                } ||||           |                                S )ue  Speak ``text`` via the generic streaming dispatcher; True on success.

    Bridges the one-shot ``speak_text`` contract onto the shared
    ``stream_tts_to_speaker`` pipeline (tools.tts_tool): the full reply is
    fed as a single delta + end-of-text sentinel, and we block until the
    pipeline's done event fires — same blocking semantics the sync path
    has, so callers (and the mic re-arm logic in ``speak_text``) see no
    behavioral difference beyond earlier first audio.

    ``stop_event`` (optional) is wired straight into the pipeline so
    external barge-in / stop paths can cut streaming playback — without
    it the pipeline's stop event was private and speech over this path
    was uninterruptible (the desktop/TUI fallback-speak hole).

    Returns False when playback produced nothing (caller falls back to the
    whole-file sync path).
    r   N)stream_tts_to_speaker)queuer   tools.tts_toolr   QueueputEventr   )r   r   _queue
_threadingr   
text_queue
done_events          r$   _speak_text_streamingr   t  s    $ """"444444!'JNN4NN4%%''
!!##J*j*===r&   c                   | r|                                  sdS ddl}ddl}ddl}d}t          5  t
          rlt          et          t          dd          rO	 t                                           d}n2# t          $ r%}t                              d|           Y d}~nd}~ww xY wddd           n# 1 swxY w Y   t                                           t          d| d           	 dd	lm} 	 dd
lm} ddlm}	  | |	                      t)          | |          r	 t                                           t          d           |r|                    d           t          5  t
          rjt          c	 t                              t0                     t          d           n2# t          $ r%}t                              d|           Y d}~nd}~ww xY wddd           dS # 1 swxY w Y   dS dS n*# t          $ r}t          d| d           Y d}~nd}~ww xY w	 ddlm}
  |
| d          }n8# t          $ r* t7          |           dk    r
| dd         n| }|                    dd|          }|                    dd|          }|                    dd|          }|                    dd|          }|                    dd|          }|                    dd|          }|                    dd||j                   }|                    d!d||j                   }|                    d"d|          }|                    d#d$|          }|                                 }Y nw xY w|s	 t                                           t          d           |r|                    d           t          5  t
          rjt          c	 t                              t0                     t          d           n2# t          $ r%}t                              d|           Y d}~nd}~ww xY wddd           dS # 1 swxY w Y   dS dS t=          j        t<          j         !                    |"                                d%          d&           t<          j         !                    |"                                d%d'|#                    d(           d)          }t          d*t7          |           d+|             |||,          }	 tI          |tJ                    rtM          j'        |          ni }n# t          $ r i }Y nw xY w|}t<          j         (                    |          r#t<          j         )                    |          dk    r|*                    d-          p|}t<          j         (                    |          rt<          j         )                    |          dk    rt          d.| d/t<          j         )                    |           d0           tW          |           	 ||h}tY          |          D ]6}|-                    d1d2          d         d3z   }|.                    |           7|D ]5}t<          j         (                    |          rt=          j/        |           6n"# t`          $ r Y nw xY wt          d4|            nY# t          $ rL}t                              d5|           t          d6tc          |          j2         d7|            Y d}~nd}~ww xY wt                                           t          d           |r|                    d           t          5  t
          rjt          c	 t                              t0                     t          d           n2# t          $ r%}t                              d|           Y d}~nd}~ww xY wddd           dS # 1 swxY w Y   dS dS # t                                           t          d           |r|                    d           t          5  t
          rjt          c	 t                              t0                     t          d           n2# t          $ r%}t                              d|           Y d}~nd}~ww xY wddd           w # 1 swxY w Y   w w xY w)8u  Synthesize ``text`` with the configured TTS provider and play it.

    Mirrors cli.py:_voice_speak_response exactly — same markdown strip
    pipeline, same 4000-char cap, same explicit mp3 output path, same
    MP3-over-OGG playback choice (afplay misbehaves on OGG), same cleanup
    of both extensions. Keeping these in sync means a voice-mode TTS
    session in the TUI sounds identical to one in the classic CLI.

    While playback is in flight the module-level _tts_playing Event is
    cleared so the continuous-recording loop knows to wait before
    re-arming the mic (otherwise the agent's spoken reply feedback-loops
    through the microphone and the agent ends up replying to itself).
    Nr   Fr   Tz$failed to pause recorder for TTS: %sz(speak_text: TTS begin (paused_recording=r   )text_to_speech_tool)resolve_streaming_provider)_load_tts_configzspeak_text: TTS doner   r   z'speak_text: recording resumed after TTSz'failed to resume recorder after TTS: %sz,speak_text: streaming dispatch unavailable (z); using sync path)prepare_spoken_texti  )	max_charsz```[\s\S]*?``` z\[([^\]]+)\]\([^)]+\)z\1zhttps?://\S+rZ   z\*\*(.+?)\*\*z	\*(.+?)\*z`(.+?)`z^#+\s*)flagsz^\s*[-*]\s+z---+z\n{3,}z

hermes_voice)exist_oktts_z%Y%m%d_%H%M%Sz.mp3zspeak_text: synthesizing z
 chars -> )r   output_path	file_pathzspeak_text: playing z (z bytes).r2   z.oggz*speak_text: TTS tool produced no audio at zVoice TTS playback failed: %szspeak_text raised r   )3r,   retempfiler   r   r   rz   r   r   rm   r   r   r   clearrd   r   r   tools.tts_streamingr   r   r   setr   r   r   tools.tts_text_normalizer   r:   sub	MULTILINEr^   makedirsr   join
gettempdirstrftimer!   r(   jsonloadsr   getsizer#   rT   listrsplitaddr   rc   r   r   )r   r   r  r  r   paused_recordingrw   r   r   r   r   tts_textmp3_path
raw_result
tts_result
audio_pathcleanup_pathsr   ogg_paths                      r$   
speak_textr    s     tzz|| IIIOOOKKK 	 
J 
J		J$0,neDD 1J$++---#'   J J JEqIIIIIIIIJ
J 
J 
J 
J 
J 
J 
J 
J 
J 
J 
J 
J 
J 
J 
J 
I6FIIIJJJf666666	YFFFFFF777777))*:*:*<*<==I(z:: J 	%&&&
  	JJsOOO! 
 
% 	*>*J,22,B 3    HIIII$   Eq       
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
	 	U  	Y 	Y 	YW!WWWXXXXXXXX	Y
	(DDDDDD**44@@@HH 	( 	( 	(&)$ii$&6&6tETE{{DHvv/h??Hvv6xHHHvvor8<<Hvv.x@@HvvlE8<<Hvvj%::HvviXR\vJJHvvnb(",vOOHvvgr844Hvvi::H~~''HHH	(  	Z 	%&&&
  	JJsOOO! 
 
% 	*>*J,22,B 3    HIIII$   Eq       
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
	 	] 	BGLL!4!4!6!6GGRVWWWW7<<!!74==11777
 
 	N3x==NNHNNOOO((hHMMM
	3=j#3N3NVJ///TVJJ 	 	 	JJJ	 
w~~h'' 	A27??8+D+D+I+I#44@J7>>*%% 	N"'//**E*E*I*I\*\\
8S8S\\\]]]J'''	!+X 6 // 0 0D#{{32215>H!%%h////) ( (Dw~~d++ (	$(     L
LLMMM = = =6:::;DGG$4;;;;<<<<<<<<= 	%&&&
  	JJsOOO! 
 
% 	*>*J,22,B 3    HIIII$   Eq       
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
	 	 	%&&&
  	JJsOOO! 
 
% 	*>*J,22,B 3    HIIII$   Eq       
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
	s  %B.A0/B.0
B:BB.BB..B25B2&[ -/G2 #G"3/F#"G"#
G-GG"GG""G&)G&1[ 2
H<H[ H[ H2 0[ 2D2M'$[ &M''[ 3P2/O32P23
P"=PP2P""P22P69P6B?[ +T- ,[ -T<9[ ;T<<C"[ BZ" ![ "
Z/,[ .Z//[ _2 
\A\_2 \_2 $_#4/^$#_#$
_._	_#__##_'*_'2Ac9b7	/a98b79
b(	b#	b7#b(	(b7+c7b;;c>b;?c)r   r   r   r   )r'   r   r   r(   )rV   r(   r   rW   )r   re   )r2   )rp   rq   rr   rq   r   rW   )r}   r{   r   rW   )r   rW   )r   r   )NNr   r   Tr   N)r   r   r   r   r   r   r   rq   r   r   r   re   r   r   r   r   r   re   )F)r   re   r   rW   )N)r   r(   r   r   r   re   )r   r(   r   r   r   rW   )>__doc__
__future__r   r  loggingr^   r=   r   typingr   r   r   r;   r@   	frozensetr<   r?   r7   r%   rG   rP   rv   rQ   rR   rS   rT   rU   	getLoggerr   r   rd   ro   rx   r   Lockr   r   r   r   ry   __annotations__rz   r   r   r  r|   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r  r+   r&   r$   <module>r&     s    * # " " " " "   				 



     * * * * * * * * * *    
  & 'Y77  !*	/// : : # # # #,J& J& J& J&Z- - - ->              
	8	$	$   *    2 2 2 2 2$ 	!! "9>##   !%  % % % %           y         37  6 6 6 6      & >B  A A A A9=  = = = =<@  @ @ @ @ ?C  B B B B     ' ' ' '^ 2648 !#&6:b b b b bJW W W W Wt" " " "D D D DT    BO O O O O O Or&   