
    iq                    >   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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dBdZdCdZdCdZddlmZmZmZmZ  ej        e          ZdDdZdEdZdFdGd#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)<   da,d(e&d*<   da-d+e&d,<   da.d-Z/dHd.Z0dId0Z1	 	 	 	 	 dJdKd;Z2dLdMd=Z3dEd>Z4dHd?Z5dNdAZ6dS )Ou  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     5/home/piyush/.hermes/hermes-agent/hermes_cli/voice.pyvoice_record_key_from_configr%   V   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   l   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_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)rU   s    r$   _debugrc      s     
z~~*B//55773>>SZt<<<<<<W%   s   A A0/A0boolc                     	 ddl m}   |                                 di           }t          |t                    r#t          |                    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_configr   beep_enabledT)hermes_cli.configrf   r#   r!   r"   rd   	Exception)rf   	voice_cfgs     r$   _beeps_enabledrk      s    111111KMM%%gr22	i&& 	=	nd;;<<<	=   4s   AA 
A,+A,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rl   rn   zbeep zHz failed: )rk   tools.voice_moderp   ri   rc   )rl   rn   rp   es       r$   
_play_beeprt     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[[str], None]]_continuous_on_transcript_continuous_on_statusOptional[Callable[[], None]]_continuous_on_silent_limit   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   5  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
transcriptrY   )r   r   stoprT   ri   loggerwarningr]   pathisfileunlinkr#   r,   rR   )r   wav_pathresultrs   texts        r$   stop_and_transcriber   D  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_restartc                
   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|rdat          t                      a	|t          _        |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t'          |          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 )u  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.
    u*   start_continuous: already active — no-opNTu6   start_continuous: stop/transcribe in progress — busyFr   z#start_continuous: begin (threshold=z, duration=zs)p  r2   rq   on_silence_stopz(failed to start continuous recording: %sz#start_continuous: rec.start raised : 	listening)_continuous_lock_continuous_activerc   _continuous_stoppingru   rx   ry   r{   _continuous_no_speech_countrv   rQ   _silence_threshold_silence_durationrt   r   _continuous_on_silenceri   r   errortype__name__)r   r   r   r   r   r   r   rs   s           r$   start_continuousr   q  s   : 
 # # 	?@@@# # # # # # # #   	KLLL# # # # # # # # "#/ $1! )&5# 	,*+''#8#:#: 2C/1A.")# # # # # # # # # # # # # # #, `.?``L\```   A&&&&		"8	9999   ?CCCLT!WW5ELLLLMMM 	' 	'!&	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	'  	Ik"""" 	 	 	D	 4sl   BBABB#&B#C. .
E!8A	EEEE	EE	EE!'E3 3
F ?F force_transcribec                  	
 t           5  t          s	 ddd           dS dat          }t          t          t
          t          }| o| 	|duadadada	sdaddd           n# 1 swxY w Y   |0| 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| r?	  |            n2# t          $ r%}t          
                    d|           Y d }~nd }~ww xY wrbt          5  | rda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 # | r?	  |            n2# t          $ r%}t          
                    d|           Y d }~nd }~ww xY wrbt          5  | rda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   rY   z&failed to stop/transcribe recorder: %s!on_transcript callback raised: %sr   r2     r1   rq   idle)rT   r#   r,   rR   r]   r   r   r   ri   r   r   r   r   _CONTINUOUS_NO_SPEECH_LIMITrt   r   )
r   should_haltr   r   rs   r   r   r   track_no_speechr   s
        r$   _transcribe_and_cleanupz0stop_continuous.<locals>._transcribe_and_cleanup  sv   ,0
#-! 	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 " SS)M*5555( S S S"NN+NPQRRRRRRRRS ' %- 
D 
D) 	D>? ; ; ;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!! !7 " SS)M*5555( S S S"NN+NPQRRRRRRRRS ' %- 
D 
D) 	D>? ; ; ;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!!sa  C A B *4C 6CC H 
D"D=H DH D 
E#EE$FFF
F 
F,+F,GGG$G1 1
G?>G?K<HK<
IH>9K<>IK<$I?3K<?JK<JK<
JK<
J'$K<&J''K<KK<KK<KK<K+*K<+
K85K<7K88K<T)targetdaemonr   r1   rq   r   )r   r   rv   ry   rx   r{   ru   r   r   ri   r   r   r   cancel	threadingThreadr   rt   )r   r   r   rs   cancel_errorr   r   r   r   r   r   s         @@@@@r$   stop_continuousr     sy    
 , ,! 	, , , , , , , , #")	15/*?</?"$$(! $&*# 	,*+', , , , , , , , , , , , , , ,"  L	C L	C In----    D 88::      <a@@@RJJLLLL  R R RNN#BLQQQQQQQQR 2! 2! 2! 2! 2! 2! 2! 2! 2!h $;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   	A'8A''A+.A+<B 
BBB. .
D'8D"C)(D")
D3DD"DD""D'E3 3
F"=FF",F;;F?F?G' '
G54G5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   9  so    	 " "!" " " " " " " " " " " " " " " " " "s     c                 r   t          d           t          5  t          st          d           	 ddd           dS 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          5  t          st          d           	 ddd           dS |rdan
t0          dz  at0          t2          k    }t0          }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| 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	 |                                  n# t          $ r Y nw xY w|r	  |d           n# t          $ r Y nw xY wdS t6                                          st          d            t6                              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   t@          rt          d%| d
           t          d&d           	 | !                    tD          '           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 ),u  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   rq   r   r   rY   r   z._continuous_on_silence: transcribe -> success=z text=z err=z#continuous transcription failed: %sz*_continuous_on_silence: transcribe raised r   u@   _continuous_on_silence: stopped during transcribe — no restartr   r2   r   z_continuous_on_silence: u    silent cycles — haltingFr   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)$rc   r   r   rv   rx   ry   r{   ri   r   r   rt   rT   rd   r#   r,   rR   r   r   r   r   r]   r   r   r   r   r   r   _tts_playingis_setwaittimesleepru   r   r   r   )r   r   r   r   r   peak_rmsr   r   r   r   errrs   r   	no_speech_times                  r$   r   r   ?  sW	    *+++	 6 6! 	DEEE6 6 6 6 6 6 6 6 #1)	56 6 6 6 6 6 6 6 6 6 6 6 6 6 6 {>??? 	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''''(    
 
0 
0! 	UVVV	
0 
0 
0 
0 
0 
0 
0 
0
  	-*+'''1,'15PP/	
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  O)OOOPPP 	, 	,!&*+'	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	, 	,  	!!!!   	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(+A(B 
BB3BF H. 
G&AG!H. !G&&H. *3H 
H+*H+.I503I%#I5%
I2/I51I22I5?K$&KKK&K2 2
L!<LL! MMM
M* *
M76M7;N 
NN#N/ /
N<;N<$QQQR) )
U 3A	T;<T?T;T	T;T	T;T%$T;%
T2/T;1T22T;;U U 
U! U!<VVVV& &
V43V4r   c                t   | 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} t#          |           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|          }|                                 }|s	 t                                           t          d           |r|                    d           t          5  t
          rjt          c	 t                              t.                     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 t1          j        t0          j                            |                                d          d            t0          j                            |                                dd!|                    d"           d#          }t          d$t#          |           d%|             |||&           t0          j                            |          rt0          j                            |          dk    rt          d'| d(t0          j                            |           d)           tA          |           	 t1          j!        |           |"                    d*d+          d         d,z   }	t0          j                            |	          rt1          j!        |	           n"# tF          $ r Y nw xY wt          d-|            nY# t          $ rL}t                              d.|           t          d/tI          |          j%         d0|            Y d}~nd}~ww xY wt                                           t          d           |r|                    d           t          5  t
          rjt          c	 t                              t.                     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                              t.                     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)1u  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_tooli  z```[\s\S]*?``` z\[([^\]]+)\]\([^)]+\)z\1zhttps?://\S+rY   z\*\*(.+?)\*\*z	\*(.+?)\*z`(.+?)`z^#+\s*)flagsz^\s*[-*]\s+z---+z\n{3,}z

zspeak_text: TTS doner   r   z'speak_text: recording resumed after TTSz'failed to resume recorder after TTS: %shermes_voice)exist_oktts_z%Y%m%d_%H%M%Sz.mp3zspeak_text: synthesizing z
 chars -> )r   output_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   )&r,   retempfiler   r   r   rv   r   r   ri   r   r   r   clearrc   tools.tts_toolr   r:   sub	MULTILINEsetr   r   r   r]   makedirsr   join
gettempdirstrftimer   getsizerS   r   rsplitrb   r   r   )
r   r   r   r   paused_recordingrs   r   tts_textmp3_path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A666666"%d))d"2"24;;66+S(;;662E8DD66/2x8866*E8<<66,x8866*eX6666)R6FF66."hbl6KK66'2x0066)VX66>>## 	> 	%&&&
  	JJsOOO! 
 
% 	*>*J,22,B 3    HIIII$   Eq       
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
	 	A 	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x@@@@7>>(## 	L(A(AA(E(EX(XXbgooh6O6OXXXYYYH%%%	(####??32215>7>>(++ (Ih'''    JJJKKK = = =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&D/R8 K,/JK
K&KKKKK"K*ER8 .A&R R8 
R"R8 !R""R8 7W% 8
TAT	W% 	TW% W'/VW
W!W<WWWWW%AZ7,Z*</Y,+Z*,
Z	6Z	Z*Z	Z*Z7*Z..Z71Z.2Z7)r   r   r   r   )r'   r   r   r(   )rU   r(   r   rV   )r   rd   )r2   )rl   rm   rn   rm   r   rV   )r   rV   )r   r   )NNr   r   T)r   r   r   rw   r   rz   r   rm   r   r   r   rd   r   rd   )F)r   rd   r   rV   )r   r(   r   rV   )7__doc__
__future__r   loggingr]   r=   r   typingr   r   r   r;   r@   	frozensetr<   r?   r7   r%   rG   rP   rr   rQ   rR   rS   rT   	getLoggerr   r   rc   rk   rt   r   Lockr   r   r   r   ru   __annotations__rv   Eventr   r   rx   ry   r{   r   r   r   r   r   r   r   r   r   r+   r&   r$   <module>r      sG    * # " " " " "  				 



     * * * * * * * * * *    
  & 'Y77  !*	/// : : # # # #,J& J& J& J&Z- - - ->            
	8	$	$   *
 
 
 
2 2 2 2 2$ 	!! "9>##   !%  % % % %           y        =A  A A A A9=  = = = =<@  @ @ @ @     ' ' ' '^ 2648 !K K K K K\w w w w wt" " " "_ _ _ _Jj j j j j jr&   