
    g+Qj              
         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Zddl	Z	ddl
Z
ddlmZmZ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ZddlZddhZh dZdZ eej4                  j7                  d	d
            Z eej4                  j7                  dd            Z eej4                  j7                  dd            Z eej4                  j7                  dd            Z ej@                  d      Z!i Z"de#d<   da$de#d<   d:dZ%d:dZ&d;dZ'd<dZ(d=dZ)d>dZ*d?dZ+d@dZ,ejZ                  dAd       Z. ede.      Z/ G d  d!      Z0dBd"Z1dCd#Z2dDd$Z3dEd%Z4dFd&Z5dFd'Z6dDd(Z7dGd)Z8dHd*Z9dDd+Z:dId,Z;e/j7                  d-      dDd.       Z< G d/ d0      Z=dJd1Z>e/j                  d2      dKd3       Z@eAd4k(  r:ddlBZB eBj                  d5d6 eDej4                  j7                  d7d8            9       yy)Luz  Jarvis gateway — FastAPI app. Everything runs over one WebSocket.

Endpoints:
  GET  /health   liveness probe (never loads the TTS model).
  WS   /ws       the whole protocol (see APP_PLAN.md §3).

Per-connection flow:
  1. First client frame MUST be ``hello`` with the correct ``auth`` — else the
     socket is closed. ``mode`` is voice | chat_voice | chat.
  2. On ``utterance`` the brain streams the reply; each finished sentence goes
     out as a ``sentence`` frame immediately, and in voice modes its WAV audio
     streams right after (``audio_start`` / binary / ``audio_end``).
  3. ``barge_in`` cancels the in-flight turn cleanly, then sends ``turn_end``.
  4. After each exchange a passive memory save fires (fire-and-forget).

Heavy things (the TTS model) are lazy: importing this module and hitting
/health never loads them, so a chat-only session pays nothing for voice.
    )annotationsN)FastAPI	WebSocketWebSocketDisconnectvoice
chat_voice>   chatr   r   (   DEVICE_ACTION_TIMEOUT25LOC_DEBOUNCE_SEC120LOC_DEBOUNCE_MKNOWN_PLACE_M150zjarvis.gatewayzdict[str, set['Session']]SESSIONSz!scheduler.RoutineScheduler | None_SCHEDc                r    t         j                  | j                  t                     j	                  |        y N)r   
setdefaultusersetadd)sesss    :/Users/ahmed/devFolder/Ultron/claude-voice/gateway/main.pyregister_sessionr   @   s#    		35)--d3    c                    t         j                  | j                        }|r5|j                  |        |s!t         j	                  | j                  d        y y y r   )r   getr   discardpop)r   peerss     r   unregister_sessionr#   D   sB    LL#EdLLD)  r   c                @    t        t        j                  | d            S )N )r   r   r   )r   s    r   live_sessionsr&   L   s    x||D"%&&r   c                   d}t        j                  |       t        j                  |      }}t        j                  || z
        }t        j                  ||z
        }t        j                  |dz        dz  t        j                  |      t        j                  |      z  t        j                  |dz        dz  z  z   }	d|z  t        j                  t        j
                  |	            z  S )z7Great-circle distance in metres between two GPS points.g    MXA   )mathradianssincosasinsqrt)
lat1lng1lat2lng2rp1p2dphidlmbas
             r   _haversine_mr9   P   s    A\\$d!3B<<t$D<<t$D	$(	q	 88B<$((2,&$();q)@@
AAq5499TYYq\***r   c                t   K   | dk(  r	 t        j                  i        d{   S y7 # t        $ r Y yw xY ww)z<Assemble a morning-brief body from live data (tasks for v1).tasksNzNo open tasks. )tools_tasks_list	Exception)kinds    r   _build_briefrA   `   sA     w	$**2...  / 	$#	$s(   8) ') 8) 	5858c                  K   | j                  d      xs i }|j                  d      }|dk(  r>t        |j                  d      xs d       d{   }d| j                  d       d	|z   S |d
k(  r'd| j                  d       d|j                  dd       S d| j                  d       d|j                  dd       S 7 ow)zeThe synthetic instruction fed to the brain when a routine fires so Jarvis
    delivers it in persona.actiontypespeak_briefbriefr;   Nz8[ROUTINE BRIEF] It's time for Ahmed's scheduled brief ('namezK'). Deliver it warmly and briefly in your own voice. Here is the material:
say_textz#[ROUTINE] Your scheduled routine ('z4') has fired. Say this to Ahmed, in your own voice: textr<   z8') has fired. Carry out this instruction for Ahmed now: )r   rA   )routinerC   atypebodys       r   _routine_injectionrM   j   s      [["(bFJJvE!&**W"5"@AA[[() *::<@A 	B 
[[() *##)::fb#9":< 	=V$% &$jj457 8 Bs   ACB?A0Cc                D  K   | j                  d      xs t        j                         }t        |      }t	        |        d{   }|r,t        t        |            j                  |       d{    yt        j                  d| j                  d             y7 X7 -w)zCDeliver a fired routine to a connected phone, or log it if none is.groupNz9ROUTINE name=%s fired but no live session; TODO(fcm) pushrG   )
r   r=   _groupr&   rM   nextiter_deliver_proactiveloggerinfo)rJ   r   sessionsinjecteds       r   fire_routinerX   ~   s~     ;;w15<<>DT"H'00H4>"55h??? 	 %&-kk&&9	; 1?s$   AB B,B 0B1,B B c                 V   K   t         t         j                          d {    y y 7 wr   )r   reloadr%   r   r   _rescheduler[      s#     mmo s   )')c                 K   t        j                  t        d       at	        j
                  t               	 t        j                          d {    	 d  t        t        j                          d {    y y 7 .# t        $ r,}t        j                  dt        |      d d        Y d }~\d }~ww xY w7 @# t        t        j                          d {  7   w w xY ww)Nc                 H    t        j                  t         j                        S r   )phone_store
list_itemsKIND_ROUTINEr%   r   r   <lambda>z_lifespan.<locals>.<lambda>   s    k44[5M5MNr   )dispatchlist_routinesz%routine scheduler failed to start: %s   )	schedulerRoutineSchedulerrX   r   r=   set_routine_hookr[   startr?   rT   warningstrshutdown)appes     r   	_lifespanrn      s      ''NPF 
;'Nlln$//###  	 N>AtMMN $ //### sj   6C$B A?B B; C$8B99C$?B 	B6
"B1,C$1B66C$;C!CC!!C$zJarvis Gateway)titlelifespanc                       e Zd ZdZddZddZy)
_TurnTimer)idt0c                D    || _         t        j                         | _        y r   )rs   time	monotonicrt   )selfturn_ids     r   __init__z_TurnTimer.__init__   s    .."r   c                \    t        t        j                         | j                  z
  dz        S )Ni  )intrv   rw   rt   rx   s    r   msz_TurnTimer.ms   s"    DNN$tww.$677r   N)ry   rj   returnNone)r   r|   )__name__
__module____qualname__	__slots__rz   r~   r%   r   r   rr   rr      s    I#8r   rr   c                    dj                  d |j                         D              }t        j                  d| j                  | j                         ||       y )Nr<   c              3  2   K   | ]  \  }}d | d|   yw) =Nr%   ).0kvs      r   	<genexpr>z_tlog.<locals>.<genexpr>   s!     7JDAqas!A3KJs   zTIMING turn=%s ms=%d event=%s%s)joinitemsrT   rU   rs   r~   )turneventkvextras       r   _tlogr      s>    GG7BHHJ77E
KK1477DGGIur   c                    d| dS )Nready)rD   sessionr%   )
session_ids    r   f_readyr      s    
33r   c                 
    ddiS )NrD   ackr%   r%   r   r   f_ackr      s    E?r   c                    d| ||dS )Nsentence)rD   seqrI   emotionr%   )r   rI   r   s      r   
f_sentencer      s    sDWMMr   c                    d| dS )Naudio_startrD   r   r%   r   s    r   f_audio_startr      s    !#..r   c                    d| dS )N	audio_endr   r%   r   s    r   f_audio_endr      s    ,,r   c                 
    ddiS )NrD   turn_endr%   r%   r   r   
f_turn_endr      s    Jr   c                    d| ||dS )u   A device-action request sent to the phone (see APP_PLAN.md §3b). The phone
    replies with an ``action_result`` carrying the same id.rC   )rD   rs   rC   paramsr%   )	action_idrC   r   s      r   f_actionr      s     I r   c                    d| dS )Nerror)rD   messager%   )r   s    r   f_errorr      s    00r   c                 
    ddiS )NrD   pongr%   r%   r   r   f_pongr      s    Fr   c                R    t         j                  j                  dd      }|sy| |k(  S )z?hello-frame gate. If GATEWAY_KEY is unset we allow (local dev).GATEWAY_KEYr<   T)osenvironr   )authkeys     r   _auth_okr      s&    
**..
+C3;r   z/healthc            
     "  K   ddt         j                  j                  dd      t         j                  j                  dd      t         j                  j                  dd      d	k7  t        t         j                  j                  d
            dS w)Nokzjarvis-gatewayMODELzdeepseek-v4-pro
FAST_MODELdeepseek-v4-flash	FAST_CHAT10	VOICE_DIR)statusservicemodel
fast_model	fast_chatvoice_dir_configured)r   r   r   boolr%   r   r   healthr      sj      #):;jjnn\3FGZZ^^K5< $RZZ^^K%@ A s   BBc                     e Zd ZdZddZd dZd!dZd"dZd#dZd$dZ	d#dZ
d%d	Zd&d
Zd'd(dZd)dZd&dZ	 	 	 	 d*dZ	 	 	 	 d+dZd,dZd-dZd-dZed.d       Zef	 	 	 d/dZd&dZd0dZd0dZd&dZd&dZd1dZd2dZd3dZ d4dZ!y)5SessionzDOne WebSocket connection: auth gate, history, brain, turn lifecycle.c                h   || _         d| _        t        j                         j                  d d | _        t        j                         | _        g | _	        ddd| _
        ddd| _        d | _        d | _        d | _        d | _        d| _        t#        j$                         | _        i | _        d | _        y )Nr      systemr<   rolecontentr   )wsmodeuuiduuid4hexr   r=   rP   r   history
system_msgfast_system_msgadapterfast_adapterbrain	turn_task_turn_noasyncioLock
_send_lock_pending_last_loc_prompt)rx   r   s     r   rz   zSession.__init__   s    	**,**3B/LLN	#%)1b A.62%F0459)-
.2!,,.35CGr   c                   K   | j                   4 d {    | j                  j                  |       d {    d d d       d {    y 7 87 7 	# 1 d {  7  sw Y   y xY wwr   )r   r   	send_json)rx   frames     r   _sendzSession._send  sA     ???''##E*** #??* #???S   A'AA'AAAA'AA'AA'A$AA$ A'c                   K   | j                   4 d {    | j                  j                  |       d {    d d d       d {    y 7 87 7 	# 1 d {  7  sw Y   y xY wwr   )r   r   
send_bytes)rx   datas     r   _send_byteszSession._send_bytes  sA     ???''$$T*** #??* #???r   c                  K   | j                   j                          d{   }	 t        j                  |      }t	        |t
              r|S i S 7 .# t        $ r( | j                  t        d             d{  7   i cY S w xY ww)zCReceive one JSON frame. Returns None on disconnect, {} on bad JSON.Nzinvalid JSON)	r   receive_textjsonloads
isinstancedict
ValueErrorr   r   )rx   txtmsgs      r   _recvzSession._recv  sr     GG((**	**S/C$S$/37R7 +  	**W^4555I	sJ   BAB&A BA B&B7A:8B?BBBc           	     $  K   | j                   j                          d {    	 | j                          d {   }|r.|j                  d      dk7  st	        |j                  d            s| j                  t        d             d {    | j                   j                  d       d {    	 t        |        | j                          d {    | j                  d       | j                  | j                  fD ]  }|	 |j                          d {      y |j                  d      }|t        v r|| _        t#        |j                  d	      xs d
      j%                         }|r|| _        | j)                          d {    t+        |        | j                  t-        | j.                               d {    	 | j                          d {   }|n| j1                  |       d {    5	 t        |        | j                          d {    | j                  d       | j                  | j                  fD ]  }|	 |j                          d {      y 7 L7 67 7 7 7 `# t        $ r Y w xY w7 7 7 7 # t2        $ r Y t        $ rM}	 | j                  t        dt#        |      d d               d {  7   n# t        $ r Y nw xY wY d }~d }~ww xY w7 7 # t        $ r Y w xY w# t        |        | j                          d {  7   | j                  d       | j                  | j                  fD ].  }|	 |j                          d {  7   !# t        $ r Y ,w xY w w xY ww)NrD   hellor   z?unauthorized: hello with valid auth required as the first framei1  )codezconnection closedr   r   r<   zfatal:    )r   acceptr   r   r   r   r   closer#   _cancel_turn_fail_pendingr   r   acloser?   VALID_MODESr   rj   stripr   _init_brainr   r   r   _handler   )rx   r   adr   r   r   rm   s          r   runzSession.run&  s/    ggnn'	**,&EEIIf-8#EIIf$56jj *G "H I I Iggmmm...0 t$##%%%23||T%6%67> iik)) 85 99V$D{" 	uyy(.B/557D 	""$$$T"**WT__5666 JJL(;ll3'''	   t$##%%%23||T%6%67> iik)) 8G 	&I.4 &
 *$ 1 %6 ) (" 	 	jj73q6$3<.)A!BCCC 	 &
 *$  t$##%%%23||T%6%67> iik))$ 	 8s  NI,NJ I/AJ I2#J /I50J 5NI82NI>I;I> N#A(J J6J JJ JJ 7J8J =L >NK:2NK>$K<%K>)N/J 2J 5J 8N;I>>	JN
JNJ J J J 	K7 L "K7+,KKKK2	K*'K2)K**K2-L 2K77L :N<K>>	L
N	L

NN,L/-3N"M<5M8
6M<;N<	N	NN	NNc                    t        | j                  j                               D ]'  }|j                         r|j	                  d|d       ) | j                  j                          y)ztResolve any outstanding device-action futures so a closing socket
        never leaves a tool call awaiting forever.Fr   r   N)listr   valuesdone
set_resultclear)rx   reasonfuts      r   r  zSession._fail_pendingQ  sL     ,,./C88:ef=> 0 	r   c                J  K   t        j                          d {   }t        j                          d {   }t        j                  ||      }d|d| _        t        j                         | _        t         j                  t        j                  z   }t        j                  | j                  || j                        | _        t        j                  j!                  dd      }t        j                  |      | _        dt        j$                         d| _        y 7 7 w)N)memory_indexr  r   r   r   r   )r   )r=   memory_index_textvalues_textpersonabuild_system_promptr   r   DeepSeekAdapterr   
TOOL_SPECSdevice_toolsDEVICE_TOOL_SPECSBrain_dispatch_toolr   r   r   r   fast_system_promptr   )rx   	mem_indexr  system_prompt	all_specsr   s         r   r  zSession._init_brainY  s       1133	((**33"63#+F,,. $$|'E'EE	[[y$:M:MN
 ZZ^^L2EF
!11
C(0+2+E+E+G I 4*s    D#DD#D!C(D#!D#c                h    t         j                  j                  dd      dk7  xr | j                  duS )z=Fast lane is on unless FAST_CHAT=0 (or it never initialised).r   r   r   N)r   r   r   r   r}   s    r   _fast_enabledzSession._fast_enabledn  s1    

{C0C7 2%%T1	3r   c                V  K   |j                  d      }|dk(  r"| j                  t                      d {    y |dk(  r"|j                  d      }|t        v r|| _        y y |dk(  rB| j                          d {    t        j                  | j                  |            | _	        y |dk(  r| j                  d       d {    y |d	k(  r| j                  |       y |d
k(  r| j                  |       d {    y |dk(  r| j                  |       d {    y y 7 7 7 _7 +7 w)NrD   pingset_moder   	utterancebarge_inT)send_turn_endaction_resultr   sms_in)r   r   r   r  r   r  r   create_task	_run_turnr   _resolve_action_handle_event_handle_sms)rx   r   tr   s       r   r  zSession._handles  s    GGFO;**VX&&&*_776?D{" 	 #+ ##%%%$001DEDN*_##$#777/!  %'\$$S)))(]""3''' ! ' & 8 *'sY   3D)DAD):D!;AD)D#5D)9D%:D)D'D)!D)#D)%D)'D)c                  K   | j                   }d | _         |Q|j                         s@|j                          	 | d {    |r"| j                  t                      d {    y y y y 7 +# t        j                  t
        f$ r Y Dw xY w7 )wr   )r   r  cancelr   CancelledErrorr?   r   r   )rx   r+  tasks      r   r  zSession._cancel_turn  s     ~~DIIKKKM

 jj...  %0 **I6  /sD   6BA, A*A, B!B"B*A, ,BBBBc                0   t        |j                  dd            j                         }t        |j                  dd            dk(  rd|z   }t        |j                  dd            j                         }|r|j                  d      sd	| d
}| d| }|S )NrI   r<   sourcer   typedz[TYPED] tone(z(voice: )z  )rj   r   r  
startswith)rx   r   rI   r;  s       r   _compose_userzSession._compose_user  s    37762&'--/swwx)*g5$D37762&'--/??3'!$q)V2dV$Dr   c           	       K   | xj                   dz  c_         t        | j                   d| j                          }t        |j	                  dd            j                         }	 |s"| j                  t                      d {    y | j                  |      }| j                  t        v }t        |d| j                  t        |      t        |             d}| j                         r| j                  |||       d {   }|s| j!                  |||       d {    t        |d|rd	nd
       | j                  t                      d {    t#        j$                  t'        j(                  |             y 7 7 7 d7 3# t"        j*                  $ r  t,        $ r[}| j                  t/        t        |      d d              d {  7   | j                  t                      d {  7   Y d }~y d }~ww xY ww)N   .rI   r<   utterance_received)r   charsr   Fr   fastfulllane,  )r   rr   r   rj   r   r  r   r   r?  r   VOICE_MODESr   lenr|   r%  _serve_fast_serve_fullr   r.  r=   passive_rememberr6  r?   r   )rx   r   r   raw_textuser_contentr   served_fastrm   s           r   r/  zSession._run_turn  s    T__-Qt}}o>?swwvr*+113	+jj...--c2LII,E$,499HSZ9
  K!!#$($4$4\5$$OO&&|UDAAA$
K&VM**Z\*** 6 6x @A# / PA* %% 	 	+**WSVDS\2333**Z\***	+s   A#H&F E?F 	H
A7F FF F2F F,F >H?F F F F G>#)G9G!G9.G1/G94H9G>>Hc                  K   | j                   J t        j                  | j                   | j                  d         }|j	                  | j
                  |fd      }d}d}d}d}		 |2 3 d{   \  }
}|
dk(  r*t        d	fi d
dd  |j                          d{    y|
dk(  r|}Ct        j                  |      \  }}|j                         sl|dz  }|sd}t        dd
       | j                  t        |||             d{    |s| j                  ||       d{   s|	rd}	t        dd
       7 7 7 :7 6 	 |j                          d{  7   n# |j                          d{  7   w xY w|dk(  ry| j                  | j
                  d|dd|xs ddgz         | _        t        dd
|       yw)zRun the FAST lane. Returns True if it served the turn (spoke + shared
        history committed), False to escalate (nothing spoken, history left
        untouched so the full brain owns the turn).Nr   c                      t         dd      S )Nfirst_tokenrE  rG  r   r   s   r   ra   z%Session._serve_fast.<locals>.<lambda>  s    t])Pr   on_tokenr   r<   FhandoffescalaterE  rF  )fromtofinalrA  Tfirst_sentencerG  first_audior   r   	assistantr   servedrH  	sentences)r   r   FastLaner   r
  r   r   r  ttsemotion_labelr  r   r   _synth_and_send_trim)rx   rP  r   r   rH  agenr   
final_textgot_sentence	got_audior@   payloadcleanr   s      `          r   rL  zSession._serve_fast  s    
   ,,,~~d//1E1Ei1PQxxl!P  R
		 '+ @mdG9$$
MvV.LM $ ++-# 7?!(J!$!2!27!;w{{}q##'L$ 0v>jjC!@AAA4#7#7W#EEE$$(	dM?'@*   BE! (,* ++-$++-!8 zzLL<8#
0AcBDDE 	dH6S9s   A%G)E( +E/E0E3E( G E!G&A'E( EE( E( *E
+E( 0E( 3E( EGE( 
E( E( G!E$"G(F<E?=FAGc                T  K   | j                   J | j                  g| j                  z   d|dgz   }d}d}d}| j                   j                  |fd      2 3 d{   }t	        j
                  |      \  }	}
|	j                         s2|dz  }|sd	}t        d
d       | j                  t        ||	|
             d{    |sp| j                  ||       d{   s|rd	}t        dd       7 7 87 6 | j                  |dd       | _        t        dd|       yw)z>Run the FULL tool-calling brain and commit the shared history.Nr   r   r   Fc                      t         dd      S )NrT  rF  rG  rU  rV  s   r   ra   z%Session._serve_full.<locals>.<lambda>  s    t]!Hr   rW  rA  Tr^  rF  rG  r_  ra  rb  )r   r   r   r
  re  rf  r  r   r   r   rg  rh  )rx   rP  r   r   messagesr   rk  rl  rawrn  r   s      `       r   rM  zSession._serve_full  s4     zz%%%__%4 &<@AB	H ( J 	<# !..s3NE7;;=1HC#d,6:**ZUG<===t33C===  $I$F;	< >=J  zz(12,/dH6S9sa   AD(C>C8C>!A D(C:D(	D(C<D($D('D(8C>:D(<D(>*D(c           	       K   	 t        j                         }|j                  dt        |       d{   }|sy| j                  t        |             d{    | j                  |       d{    | j                  t        |             d{    y7 e# t        $ r<}| j                  t        dt        |      dd               d{  7   Y d}~yd}~ww xY w7 7 s7 Sw)zSynthesize + stream one sentence's audio. TTS runs OFF the event loop
        (threadpool) so a ~40-200ms synth never blocks the socket. Returns True
        if audio was sent, False if voice was unavailable/empty (chat goes on).Nztts unavailable: rd   FT)r   get_running_looprun_in_executor_tts_wavr?   r   r   rj   r   r   r   )rx   r   raw_sentenceloopwavrm   s         r   rg  zSession._synth_and_send  s     	++-D,,T8\JJC jjs+,,,s###jjS)*** K 	**W'8Q%GHIII	
 	-#*sy   C(.B BB  C(C"C(0C$1!C(C&C(B 	C#,CCCC(CC($C(&C(c                   K   |t         j                  v r| j                  ||       d{   S t        j                  ||       d{   S 7 "7 w)zThe brain's tool executor. Device tools go over the WS bridge to the
        phone; everything else runs server-side in ``tools.execute``.N)r  DEVICE_TOOL_NAMES_dispatch_devicer=   execute)rx   rG   argss      r   r  zSession._dispatch_tool  sI      <111..tT:::]]4... ;.s!   (AAAA	AAc                F  K   |xs i }|dk(  rt        |j                  dd            j                         }|syd|i}t        j                  t        j
                  |       d{   }|r)dD ]  }||v s||   ||<    |j                  d|      |d<   | j                  d|       d{   }| j                  |d| d	      S |d
k(  rt        |j                  dd            j                         }t        j                  t        j                  |       d{   }	|	r)t        |	j                  dd            j                         sd| dS | j                  d|	j                  d|      |	d   d       d{   }| j                  |d|	j                  d|       d	      S |dk(  r| j                  di        d{   }|j                  d      s| j                  |d      S |j                  d      xs i }
	 t        |
d         t        |
d         }}| j                  ||       d{   }|rd| d	S d|dd|dd	S ddt        |j                  dd            id|j                  dd       d	fdi d fd!d"t        |j                  d"      xs d#      id$fd%d&t        |j                  d&d            id'|j                  d&d       d	fd(d)t        |j                  d)d            id*fd+t        |j                  d,      xs d-      t        |j                  d      xs d      d.d/fd0}||v r5||   \  }}}| j                  ||       d{   }| j                  ||      S d1| d2S 7 7 7 _7 7 # t        t        t        f$ r Y yw xY w7 m7 Hw)3zResolve any saved state, emit the WS ``action`` frame, await the
        phone's result, and hand the brain a short spoken-friendly summary. A
        timeout / no-client yields a tool error, never a hang.navigatedestinationr<   z navigate failed: no destination.N)addresslatlnglabelzNavigating to rB  call_contactrG   numberzNo number saved for 'z7'. Ask Ahmed for it and I'll save it with save_contact.call)rG   r  zCalling get_locationr   r   r  r  z.Got a location fix, but no usable coordinates.zHe's at zCurrent location: .5f, 
play_musicqueryzPlaying take_screenshotzScreenshot taken.open_camerar   photozOpening the camera.open_apptargetzOpening open_urlurlzOpening the link.	set_timersecondsr   )r  r  z
Timer set.)r  r  r  r  r  r  zunknown device tool '')rj   r   r  r^   get_item
KIND_PLACE_device_call_device_replyKIND_CONTACTfloatKeyError	TypeErrorr   _match_placer|   )rx   rG   r~  destr   placer   reswhocontactr   r  r  r  simplerC   ok_msgs                    r   r|  zSession._dispatch_device%  s     zr:txxr2399;D9)40F%..{/E/EtLLE2AEz$)!Hq	 3 #())GT":w))*f==C%%c^D6+CDD>!dhhvr*+113C'001I1I3OOG#gkk(B&?"@"F"F"H/u 55 5 6))VS!9#*8#467 7C %%cXgkk&#6N5Oq+QRR>!))."==C774=))#r22776?(bDH e-uT%[/AS ++C55E!%**'Cy3s)1== ('3txx7L3M)N%dhhw&;%<A>@ 127JK)#S&)9)DW%EF13 $hDHHXr4J0K%L#DHHXr$:#;1=?#eS%1D-E%F,.%&)$((9*=*B&C$'(9(?R$@B&(
  6>%+D\"FFF))&&99C%%c622&tfA..o M >
 P7 > i4 HGH5. :s   A#N!%M3&N!64N!*M6+A/N!M9A N!;M<<AN! M?<N!>N N!/N0D&N!NN!6N!9N!<N!?N!NN!NN!N!c                   | j                  d      rF| j                  d      }t        |t              r|j                  d      rt        |d         S |xs dS t        | j                  d      xs d      }|dk(  ryd	| d
S )z=Turn a phone action_result into a compact line for the brain.r   r   r   zDone.r   zno responsetimeoutz'Your phone didn't respond in time, sir.zCouldn't do that on the phone (z).)r   r   r   rj   )r  r  r   errs       r   r  zSession._device_replyi  s}     774=776?D$%$((9*=4	?++$W$#'''"3m4)<0R88r   c                ^  K   t        j                         j                  dd }t        j                         }|j                         }|| j                  |<   	 | j                  t        |||             d{    t        j                  ||       d{   }t        |t              r|nddd	 | j                  j                  |d       S 7 V7 :# t        j                  $ r$ dddcY | j                  j                  |d       S w xY w# | j                  j                  |d       w xY ww)zEmit an ``action`` frame and await the matching ``action_result``.
        Returns the result dict; on timeout/closed socket returns an error dict.Nr   Fz
bad resultr  r  )r   r   r   r   rt  create_futurer   r   r   wait_forr   r   r!   TimeoutError)rx   rC   r   r  aidrx  r  r  s           r   r  zSession._device_callv  s     jjls#'')"002 c	)**Xc66:;;;((g66C$S$/3E?K6M M
 MMc4( <6 ## 	5)44MMc4(	5 MMc4(s`   AD-C 7C8C CC 1D-C C D	*D +D-D		D D**D-c                .   t        |j                  dd            }| j                  j                  |      }|]|j                         sL|j	                  t        |j                  d            |j                  d      |j                  d      d       yyy)z;Deliver a phone ``action_result`` to whoever awaits its id.rs   r<   Nr   r   r   )r   r   r   )rj   r   r   r  r  r   )rx   r   r  r  s       r   r0  zSession._resolve_action  sv    #''$#$mm$?388:NN$swwt}"5$'GGFO%(WWW%57 8 $.?r   c                   K   | j                   1| j                   j                         st        j                  d|       yt	        j
                  | j                  |            | _         yw)zStart a proactive spoken turn (Jarvis speaks first). Skipped if a turn
        is already in flight so an event never talks over a live exchange.Nz)proactive skipped (turn in flight): %.40s)r   r  rT   rU   r   r.  _proactive_turn)rx   rW   s     r   rS   zSession._deliver_proactive  sP      >>%dnn.A.A.CKKCXN ,,T-A-A(-KLs   A(A*c                L  K   | xj                   dz  c_         t        | j                   d| j                          }	 | j                  t        v }t        |d       | j                  |||       d {    t        |dd       | j                  t                      d {    y 7 47 # t        j                  $ r  t        $ r[}| j                  t        t        |      d d              d {  7   | j                  t                      d {  7   Y d }~y d }~ww xY ww)NrA  z.pproactive_startr   	proactiverG  rI  )r   rr   r   r   rJ  r   rM  r   r   r   r6  r?   r   rj   )rx   rW   r   r   rm   s        r   r  zSession._proactive_turn  s     T__-R?@
	+II,E$)*""8UD999$
5**Z\*** :*%% 	 	+**WSVDS\2333**Z\***	+sj   :D$4B* 1B&2.B*  B(!B* %D$&B* (B* *D!)D/C20!DDDD$D!!D$c                l  K   |j                  d      dk7  ry |j                  d      xs i }	 t        |j                  d            t        |j                  d            }}t	        |j                  d      xs d      j                         }|r| j                  |       d {    y | j                  ||       d {   ry | j                  ||      sy ||t        j                         f| _        d|d	d
|d	d}| j                  |       d {    y # t        t        f$ r Y y w xY w7 7 p7 w)Nr@   new_locationcoordsr  r  r  r<   zG[LOCATION EVENT] Ahmed has arrived somewhere you don't have saved (GPS r  r  z). Greet him briefly and ask where he is, so you can save it with save_place (pass these coordinates as 'lat,lng') once he tells you. Keep the numbers to yourself.)r   r  r  r   rj   r  _fire_place_routinesr  _should_prompt_locationrv   rw   r   rS   )rx   r   r  r  r  place_labelrW   s          r   r1  zSession._handle_event  s<    776?n,"(b	VZZ./vzz%7H1IC #'''*0b1779 ++K888""3,,,++C5!$c4>>+; <9Bs3i (JJ 	
 %%h///' :& 		 9 - 	0sZ   +D44D "AD4$D.%D4?D0 AD4D2D4D+(D4*D++D40D42D4c           	        K   t        |j                  dd            j                         }t        |j                  dd            j                         }|sy 	 t        j                  t        j
                         d {   }|D cg c]  }t        |j                  dd             }}t        j                  ||      sy d| d| d}| j                  |       d {    y 7 f# t        $ r Y y w xY wc c}w 7 w)Nsenderr<   rL   z&[SMS EVENT] A text just arrived from "z": "z". Summarise it for Ahmed in a sentence and flag anything that needs action. If it contains a one-time code or OTP, read that out.)	rj   r   r  r^   r_   KIND_SMS_WATCHr?   sms_matchesrS   )rx   r   r  rL   watchwsendersrW   s           r   r2  zSession._handle_sms  s     SWWXr*+11337762&'--/	%001K1KLLE 6;;U3quuXr*+U;&&vw75fXVD6 JL L 	 %%h/// M 		; 	0sT   AC>&C( ?C& C( C>"C7*6C> C<!C>&C( (	C41C>3C44	C>c                    | j                   y| j                   \  }}}t        j                         |z
  t        k  rt	        ||||      t
        k  ryy)zDebounce: suppress a new-location prompt if we recently prompted at a
        point this close (GPS wobble), otherwise allow it.TF)r   rv   rw   r   r9   r   )rx   r  r  platplngptss         r   r  zSession._should_prompt_location  sR       (//dCNNs"%55 c46Gr   c           
     r  K   	 t        j                  t         j                         d{   }|D ]\  }d|v sd|v s	 t	        ||t        |d         t        |d               }|t        k  s>t        |j                  d      xs d      c S  y7 f# t        $ r Y yw xY w# t        t        f$ r Y w xY ww)zcReturn a saved place's label if these coords are within
        ``KNOWN_PLACE_M`` of it, else None.Nr  r  r  r<   )r^   r_   r  r?   r9   r  r  r   r   rj   r   )rx   r  r  placespds         r   r  zSession._match_place  s     	&11+2H2HIIF Azeqj$S#uQuXahPA }$quuW~344   J 		 ":. se   B7&B BB 	B7B7&B"%	B7/"B7B 	BB7BB7"B41B73B44B7c                  K   	 t        j                  t         j                         d{   }|j	                         j                         }|D ]y  }|j                  d      xs i }|j                  d      dv s,t        |j                  dd            j	                         j                         |k(  sgt        |       d{    { y7 # t        $ r Y yw xY w7 w)zCFire any place/both routines whose trigger place matches ``label``.NtriggerrD   )r  bothr  r<   )	r^   r_   r`   r?   r  lowerr   rj   rX   )rx   r  routineswantr3   trigs         r   r  zSession._fire_place_routines  s     	(33K4L4LMMH {{}""$A55#)rDxx#44DHHWb1288:@@BdJ"1o%%	 	 N 		 &sP   C&C CC AC;:C6CCCC 	CCCCc                    t        |      D cg c]  \  }}|j                  d      dk(  s| }}}t        |      t        k  r|S ||t        |      t        z
     d S c c}}w )zKeep the last MAX_TURNS user-turns, cutting only at a user boundary
        so an assistant tool-call and its tool results are never orphaned.r   r   N)	enumerater   rK  	MAX_TURNS)rx   msgsimuser_idxs        r   rh  zSession._trim   sb     #,D/M/$!QQUU6]f5LA/Mx=I%KHS]Y67899 Ns
   AANr   r   r   r   )r   r   r   r   )r   bytesr   r   )r   zdict | Noner   r   )r  rj   r   r   )r   r   )r   r   r   r   )F)r+  r   r   r   )r   r   r   rj   )rP  rj   r   r   r   rr   r   r   )rP  rj   r   r   r   rr   r   r   )r   r|   rw  rj   r   r   )rG   rj   r~  r   r   rj   )r  r   r  rj   r   rj   )rC   rj   r   r   r  r  r   r   )rW   rj   r   r   )r  r  r  r  r   r   )r  r  r  r  r   
str | None)r  rj   r   r   )r  
list[dict]r   r  )"r   r   r   __doc__rz   r   r   r   r
  r  r  r%  r  r  r?  r/  rL  rM  rg  r  r|  staticmethodr  DEVICE_TIMEOUTr  r0  rS   r  r1  r2  r  r  r  rh  r%   r   r   r   r      s    NH(++)VI*3
(.
/	+>- *-/3-^: *:/3:8$/B/H 
9 
9 -;)$))?C)$8M+ 060$	"&:r   r   c                H    t        j                         j                  |       S )zJBlocking synth (runs in a threadpool). Lazy-loads the model on first call.)re  get_tts	synth_wav)rw  s    r   rv  rv  	  s    ;;=""<00r   z/wsc                R   K   t        |       j                          d {    y 7 wr   )r   r
  )r   s    r   ws_endpointr    s     
"+//
s   '%'__main__zmain:appz0.0.0.0PORT8000)hostport)r   z	'Session'r   r   )r   rj   r   zset['Session'])
r/   r  r0   r  r1   r  r2   r  r   r  )r@   rj   r   rj   )rJ   r   r   rj   )rJ   r   r   r   r  )rl   r   )r   rr   r   rj   r   objectr   r   )r   rj   r   r   )r   r   )r   r|   rI   rj   r   r  r   r   )r   r|   r   r   )r   rj   rC   rj   r   r   r   r   )r   rj   r   r   )r   r  r   r   )rw  rj   r   r  r  )Er  
__future__r   r   
contextlibr   loggingr)   r   rv   r   fastapir   r   r   r   r  r  r^   re   r=   re  rJ  r  r  r  r   r   r  r   r   r   	getLoggerrT   r   __annotations__r   r   r#   r&   r9   rA   rM   rX   r[   asynccontextmanagerrn   rl   rr   r   r   r   r   r   r   r   r   r   r   r   r   r   rv  	websocketr  r   uvicornr
  r|   r%   r   r   <module>r     s  & #      	   ; ;       
%-	 rzz~~&=tDE(:EBC rzz~~&6>?bjjnn_e<=			+	, ')
# (,0) 04*'+ 8(;
 $  $" $y98 84N/- 1  J: J:Z1
 u  zGKK
78: r   