+
    g+Qj              
      P  a  0 t $ R t^ RIHt ^ RIt^ RIt^ RIt^ RIt^ RIt^ RI	t	^ RI
t
^ RIt^ RIHtHtHt ^ RIt^ RIt^ RIt^ RIt^ RIt^ RIt^ RItRR0t0 RNmt^(t]! ]	P6                  P9                  RR4      4      t]! ]	P6                  P9                  RR	4      4      t]! ]	P6                  P9                  R
R	4      4      t]! ]	P6                  P9                  RR4      4      t ]PB                  ! R4      t"/ t#R]$R&   Rs%R]$R&   R R lt&R R lt'R R lt(R R lt)R R lt*R R lt+R R lt,R  R! lt-]P\                  R" R# l4       t/]! R$]/R%7      t0 ! R& R'4      t1R( R) lt2R* R+ lt3R, R- lt4R. R/ lt5R0 R1 lt6R2 R3 lt7R4 R5 lt8R6 R7 lt9R8 R9 lt:R: R; lt;R< R= lt<]0P9                  R>4      R? R@ l4       t= ! RA RB4      t>RC RD lt?]0P                  RE4      RF RG l4       tA]BRH8X  d<   ^ RICtC]CP                  ! RIRJ]E! ]	P6                  P9                  RKRL4      4      RM7       R# R# )Ouz  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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                    V ^8  d   QhRRRR/#    sessz	'Session'returnNone )formats   "main.py__annotate__r   @   s     4 49 4 4    c                t    \         P                  V P                  \        4       4      P	                  V 4       R # N)r   
setdefaultusersetadd)r   s   &r   register_sessionr"   @   s#    		35)--d3r   c                    V ^8  d   QhRRRR/# r   r   )r   s   "r   r   r   D   s     * *Y *4 *r   c                    \         P                  V P                  4      pV'       d>   VP                  V 4       V'       g#   \         P	                  V P                  R 4       R # R # R # r   )r   getr   discardpop)r   peerss   & r   unregister_sessionr)   D   sB    LL#EdLLD)  r   c                    V ^8  d   QhRRRR/# )r   r   strr   zset['Session']r   )r   s   "r   r   r   L   s     ' ' ' 'r   c                @    \        \        P                  V R4      4      # )Nr   )r    r   r%   )r   s   &r   live_sessionsr-   L   s    x||D"%&&r   c          
     ,    V ^8  d   QhRRRRRRRRRR/# )r   lat1floatlng1lat2lng2r   r   )r   s   "r   r   r   P   s/     + +u +E + +e + +r   c                B   Rp\         P                  ! V 4      \         P                  ! V4      re\         P                  ! W ,
          4      p\         P                  ! W1,
          4      p\         P                  ! V^,          4      ^,          \         P                  ! V4      \         P                  ! V4      ,          \         P                  ! V^,          4      ^,          ,          ,           p	^V,          \         P                  ! \         P
                  ! V	4      4      ,          # )z7Great-circle distance in metres between two GPS points.g    MXA)mathradianssincosasinsqrt)
r/   r1   r2   r3   rp1p2dphidlmbas
   &&&&      r   _haversine_mrA   P   s    A\\$d!3<<$D<<$D	$(	q	 88B<$((2,&$();q)@@
AAq5499TYYq\***r   c                    V ^8  d   QhRRRR/# )r   kindr+   r   r   )r   s   "r   r   r   `   s      S S r   c                   "   V R8X  d     \         P                  ! / 4      G Rj  xL
 # R#  L  \         d     R# i ; i5i)z<Assemble a morning-brief body from live data (tasks for v1).tasksNzNo open tasks. )tools_tasks_list	Exception)rC   s   &r   _build_briefrJ   `   s@     w	$**2...  / 	$#	$s(   ?- +- ?- <?<?c                    V ^8  d   QhRRRR/# )r   routinedictr   r+   r   )r   s   "r   r   r   j   s     8 8d 8s 8r   c                  "   V P                  R4      ;'       g    / pVP                  R4      pVR8X  dI   \        VP                  R4      ;'       g    R4      G Rj  xL
 pRV P                  R4       R	2V,           # VR
8X  d(   RV P                  R4       RVP                  RR4       2# RV P                  R4       RVP                  RR4       2#  Lu5i)zaThe synthetic instruction fed to the brain when a routine fires so Jarvis
delivers it in persona.actiontypespeak_briefbriefrE   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: textrF   z8') has fired. Carry out this instruction for Ahmed now: )r%   rJ   )rL   rO   atypebodys   &   r   _routine_injectionrX   j   s      [["((bFJJvE!&**W"5"@"@AA[[() *::<@A 	B 
[[() *##)::fb#9":< 	=V$% &$jj457 8 Bs   AC
CCA6Cc                    V ^8  d   QhRRRR/# )r   rL   rM   r   r   r   )r   s   "r   r   r   ~   s     ; ; ; ;r   c                b  "   V P                  R4      ;'       g    \        P                  ! 4       p\        V4      p\	        V 4      G Rj  xL
 pV'       d.   \        \        V4      4      P                  V4      G Rj  xL
  R# \        P                  RV P                  R4      4       R#  L` L/5i)zCDeliver a fired routine to a connected phone, or log it if none is.groupNz9ROUTINE name=%s fired but no live session; TODO(fcm) pushrS   )
r%   rG   _groupr-   rX   nextiter_deliver_proactiveloggerinfo)rL   r   sessionsinjecteds   &   r   fire_routinerd   ~   s     ;;w115<<>DT"H'00H4>"55h??? 	 %&-kk&&9	; 1?s*   AB/
B+B/&B/=B->.B/-B/c                   V ^8  d   QhRR/# r   r   r   r   )r   s   "r   r   r      s      4 r   c                 ^   "   \         e   \         P                  4       G R j  xL
  R # R #  L5ir   )r   reloadr   r   r   _rescheduleri      s"     mmo s    -+	-c                   V ^8  d   QhRR/# )r   appr   r   )r   s   "r   r   r      s     $ $ $r   c                 "   \         P                  ! \        R  R7      s\        P
                  ! \        4        \        P                  4       G Rj  xL
   R5x  \        e   \        P                  4       G Rj  xL
  R# R#  L2  \         d1   p\        P                  R\        T4      R,          4        Rp?LfRp?ii ; i LH  \        e   \        P                  4       G Rj  xL 
  i i ; i5i)c                 J    \         P                  ! \         P                  4      # r   )phone_store
list_itemsKIND_ROUTINEr   r   r   <lambda>_lifespan.<locals>.<lambda>   s    k44[5M5MNr   )dispatchlist_routinesNz%routine scheduler failed to start: %sN   N)	schedulerRoutineSchedulerrd   r   rG   set_routine_hookri   startrI   r`   warningr+   shutdown)rk   es   & r   	_lifespanr~      s      ''NPF 
;'Nlln$//###  	 N>AtMMN $ //### sj   8C2B BB C C2<C=C2B C&B=8C2=CC2 C/'C*(C//C2zJarvis Gateway)titlelifespanc                  2    ] tR t^tRtR R ltR R ltRtR# )
_TurnTimerc                    V ^8  d   QhRRRR/# )r   turn_idr+   r   r   r   )r   s   "r   r   _TurnTimer.__annotate__   s     # # # #r   c                	F    Wn         \        P                  ! 4       V n        R # r   )idtime	monotonict0)selfr   s   &&r   __init___TurnTimer.__init__   s    .."r   c                   V ^8  d   QhRR/# )r   r   intr   )r   s   "r   r   r      s     8 8C 8r   c                	n    \        \        P                  ! 4       V P                  ,
          R ,          4      # )i  )r   r   r   r   r   s   &r   ms_TurnTimer.ms   s"    DNN$tww.$677r   )r   r   N)__name__
__module____qualname____firstlineno__	__slots__r   r   __static_attributes__r   r   r   r   r      s    I#8 8r   r   c               (    V ^8  d   QhRRRRRRRR/# )	r   turnr   eventr+   kvobjectr   r   r   )r   s   "r   r   r      s(      
 3 f  r   c                    R P                  R VP                  4        4       4      p\        P                  RV P                  V P                  4       W4       R# )rF   c              3  6   "   T F  w  rR V RV 2x  K  	  R# 5i) =Nr   ).0kvs   &  r   	<genexpr>_tlog.<locals>.<genexpr>   s     7JDAas!A3KJs   zTIMING turn=%s ms=%d event=%s%sN)joinitemsr`   ra   r   r   )r   r   r   extras   &&, r   _tlogr      s<    GG7BHHJ77E
KK1477DGGIr   c                    V ^8  d   QhRRRR/# )r   
session_idr+   r   rM   r   )r   s   "r   r   r      s     4 4 4 4r   c                    R RRV /# )rP   readysessionr   )r   s   &r   f_readyr      s    GY
33r   c                   V ^8  d   QhRR/# r   r   rM   r   )r   s   "r   r   r      s      t r   c                 
    R R/# )rP   ackr   r   r   r   f_ackr      s    E?r   c               (    V ^8  d   QhRRRRRRRR/# )	r   seqr   rU   r+   emotion
str | Noner   rM   r   )r   s   "r   r   r      s.     N NC Ns NZ ND Nr   c                    R RRV RVRV/# )rP   sentencer   rU   r   r   )r   rU   r   s   &&&r   
f_sentencer      s    JsFD)WMMr   c                    V ^8  d   QhRRRR/# r   r   r   r   rM   r   )r   s   "r   r   r      s     / /s /t /r   c                    R RRV /# )rP   audio_startr   r   r   s   &r   f_audio_startr      s    M5#..r   c                    V ^8  d   QhRRRR/# r   r   )r   s   "r   r   r      s     - -S -T -r   c                    R RRV /# )rP   	audio_endr   r   r   s   &r   f_audio_endr      s    K,,r   c                   V ^8  d   QhRR/# r   r   )r   s   "r   r   r      s        D  r   c                 
    R R/# )rP   turn_endr   r   r   r   
f_turn_endr      s    Jr   c               (    V ^8  d   QhRRRRRRRR/# )r   	action_idr+   rO   paramsrM   r   r   )r   s   "r   r   r      s(       S $ 4 r   c                    RRRV RVRV/# )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.rP   rO   r   r   r   )r   rO   r   s   &&&r   f_actionr      s      HdIxf r   c                    V ^8  d   QhRRRR/# )r   messager+   r   rM   r   )r   s   "r   r   r      s     1 1S 1T 1r   c                    R RRV /# )rP   errorr   r   )r   s   &r   f_errorr      s    GY00r   c                   V ^8  d   QhRR/# r   r   )r   s   "r   r   r      s       r   c                 
    R R/# )rP   pongr   r   r   r   f_pongr      s    Fr   c                    V ^8  d   QhRRRR/# )r   authr   r   boolr   )r   s   "r   r   r      s      : $ r   c                ^    \         P                  P                  RR4      pV'       g   R# W8H  # )z?hello-frame gate. If GATEWAY_KEY is unset we allow (local dev).GATEWAY_KEYrF   T)osenvironr%   )r   keys   & r   _auth_okr      s$    
**..
+C;r   z/healthc                   V ^8  d   QhRR/# r   r   )r   s   "r   r   r      s      d r   c                 ,  "   R RRRR\         P                  P                  RR4      R\         P                  P                  RR	4      R
\         P                  P                  RR4      R8g  R\        \         P                  P                  R4      4      /# 5i)statusokservicezjarvis-gatewaymodelMODELzdeepseek-v4-pro
fast_model
FAST_MODELdeepseek-v4-flash	fast_chat	FAST_CHAT10voice_dir_configured	VOICE_DIR)r   r   r%   r   r   r   r   healthr      sv      	$#):;bjjnn\3FGRZZ^^K5<RZZ^^K%@ A s   BBc                  ~   ] tR t^tRtR R ltR R ltR R ltR R	 ltR
 R lt	R R lt
R R ltR R ltR R ltR<R R lltR R ltR R ltR R ltR R ltR R ltR  R! ltR" R# lt]R$ R% l4       t]3R& R' lltR( R) ltR* R+ ltR, R- ltR. R/ ltR0 R1 ltR2 R3 ltR4 R5 lt R6 R7 lt!R8 R9 lt"R:t#R;# )=SessionzDOne WebSocket connection: auth gate, history, brain, turn lifecycle.c                    V ^8  d   QhRRRR/# r   wsr   r   r   r   )r   s   "r   r   Session.__annotate__   s     H H9 H Hr   c                	z   Wn         R V n        \        P                  ! 4       P                  R,          V n        \        P                  ! 4       V n        . V n	        RRRR/V n
        RRRR/V n        RV n        RV n        RV n        RV n        ^ V n        \"        P$                  ! 4       V n        / V n        RV n        R# )r   N   NrolesystemcontentrF   N)r   modeuuiduuid4hexr   rG   r\   r   history
system_msgfast_system_msgadapterfast_adapterbrain	turn_task_turn_noasyncioLock
_send_lock_pending_last_loc_prompt)r   r   s   &&r   r   Session.__init__   s    	**,**3/LLN	#%!'9b A&,h	2%F0459)-
.2!,,.35CGr   c                    V ^8  d   QhRRRR/# )r   framerM   r   r   r   )r   s   "r   r   r     s     + + +$ +r   c                	   "   V P                   ;_uu_4       GR j  xL
  V P                  P                  V4      G R j  xL
  R R R 4      GR j  xL
  R #  L9 L L
  + GR j  xL 
 '       g   i     R # ; i5ir   )r  r   	send_json)r   r  s   &&r   _sendSession._send  s?     ????''##E*** #??* #???Z   A8AA8AAAA8AA8AA8A5	!A$"
A5	-A5	/	A8c                    V ^8  d   QhRRRR/# )r   databytesr   r   r   )r   s   "r   r   r     s     + +e + +r   c                	   "   V P                   ;_uu_4       GR j  xL
  V P                  P                  V4      G R j  xL
  R R R 4      GR j  xL
  R #  L9 L L
  + GR j  xL 
 '       g   i     R # ; i5ir   )r  r   
send_bytes)r   r  s   &&r   _send_bytesSession._send_bytes  s?     ????''$$T*** #??* #???r  c                   V ^8  d   QhRR/# )r   r   zdict | Noner   )r   s   "r   r   r     s      [ r   c                   "   V P                   P                  4       G Rj  xL
 p \        P                  ! V4      p\	        V\
        4      '       d   V# / #  L5  \         d)    T P                  \        R4      4      G Rj  xL 
  / u # i ; i5i)zCReceive one JSON frame. Returns None on disconnect, {} on bad JSON.Nzinvalid JSON)	r   receive_textjsonloads
isinstancerM   
ValueErrorr  r   )r   txtmsgs   &  r   _recvSession._recv  st     GG((**	**S/C$S$//37R7 +  	**W^4555I	sJ   BAB-A BA B(B BBB
BBc                   V ^8  d   QhRR/# rf   r   )r   s   "r   r   r   &  s     ) )4 )r   c           	     	  "   V P                   P                  4       G R j  xL
   V P                  4       G R j  xL
 pV'       d7   VP                  R4      R8w  g!   \	        VP                  R4      4      '       g   V P                  \        R4      4      G R j  xL
  V P                   P                  RR7      G R j  xL
   \        V 4       V P                  4       G R j  xL
  V P                  R4       V P                  V P                  3 F"  pVf   K	   VP                  4       G R j  xL
  K$  	  R # VP                  R4      pV\        9   d   W0n        \#        VP                  R	4      ;'       g    R
4      P%                  4       pV'       d   W@n        V P)                  4       G R j  xL
  \+        V 4       V P                  \-        V P.                  4      4      G R j  xL
   V P                  4       G R j  xL
 pVf   MV P1                  V4      G R j  xL
  K9  \        V 4       V P                  4       G R j  xL
  V P                  R4       V P                  V P                  3 F"  pVf   K	   VP                  4       G R j  xL
  K$  	  R #  ELu EL_ EL EL EL ELx  \         d     EK  i ; i EL L L L  \2         d     L\         dX   p T P                  \        R\#        T4      R,           24      4      G R j  xL 
   R p?L  \         d	      R p?ELi ; iR p?ii ; i L L  \         d     K  i ; i  \        T 4       T P                  4       G R j  xL 
  T P                  R4       T P                  T P                  3 F5  pTf   K	   TP                  4       G R j  xL 
  K%    \         d     K3  i ; i	  i ; i5i)NrP   hellor   z?unauthorized: hello with valid auth required as the first framei1  )codezconnection closedr   r   rF   zfatal: :N   N)r   acceptr)  r%   r   r  r   closer)   _cancel_turn_fail_pendingr  r  acloserI   VALID_MODESr   r+   stripr   _init_brainr"   r   r   _handler   )r   r-  adr   r   r(  r}   s   &      r   runSession.run&  s    ggnn'	**,&EEIIf-8#EIIf$56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$<.)A!BCCC 	 &
 *$  t$##%%%23||T%6%67> iik))$ 	 8s  OJOK JK AK J#K =J>K O!J!"1OJ',J$-J'1O7>K 6K K ,J:-6K #J=$K <J?=K M
 K KK !O?L4 1O7L8
L6L8OK K K !O$J''J72O6J77O:K =K ?K K L1M
 L1L10LLLM
 L)!L,"M
 (L))L,,L11M
 4O6L88MOMO
O)M,*2O"N=5N8
6N=;O=O	OO	OOc                    V ^8  d   QhRRRR/# )r   reasonr+   r   r   r   )r   s   "r   r   r   Q  s      C D r   c                    \        V P                  P                  4       4       F0  pVP                  4       '       d   K  VP	                  RRRV/4       K2  	  V P                  P                  4        R# )zlResolve any outstanding device-action futures so a closing socket
never leaves a tool call awaiting forever.r   Fr   N)listr  valuesdone
set_resultclear)r   r=  futs   && r   r3  Session._fail_pendingQ  sR     ,,./C88::eWf=> 0 	r   c                   V ^8  d   QhRR/# rf   r   )r   s   "r   r   r   Y  s     I I4 Ir   c                	b  "   \         P                  ! 4       G R j  xL
 p\         P                  ! 4       G R j  xL
 p\        P                  ! WR7      pRRRV/V n        \        P                  ! 4       V n        \         P                  \        P                  ,           p\        P                  ! V P                  W@P                  4      V n        \        P                  P!                  RR4      p\        P                  ! VR7      V n        RRR\        P$                  ! 4       /V n        R #  EL L5i)N)memory_indexr@  r   r   r   r   r   )r   )rG   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  )r   	mem_indexr@  system_prompt	all_specsr   s   &     r   r7  Session._init_brainY  s       1133	((**33"3!8YF,,. $$|'E'EE	[[y:M:MN
 ZZ^^L2EF
!11
C & )7+E+E+G I 4*s    D/D*D/D-C2D/-D/c                   V ^8  d   QhRR/# )r   r   r   r   )r   s   "r   r   r   n  s     3 3t 3r   c                t    \         P                  P                  RR4      R8g  ;'       d    V P                  RJ# )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_enabledSession._fast_enabledn  s6    

{C0C7 2 2%%T1	3r   c                    V ^8  d   QhRRRR/# r   r(  rM   r   r   r   )r   s   "r   r   r   s  s     ( ( ($ (r   c                	  "   VP                  R 4      pVR8X  d$   V P                  \        4       4      G Rj  xL
  R# VR8X  d'   VP                  R4      pV\        9   d	   W0n        R# R# VR8X  dE   V P                  4       G Rj  xL
  \        P                  ! V P                  V4      4      V n	        R# VR8X  d   V P                  RR7      G Rj  xL
  R# VR	8X  d   V P                  V4       R# VR
8X  d   V P                  V4      G Rj  xL
  R# VR8X  d   V P                  V4      G Rj  xL
  R# R#  EL L Lk L1 L5i)rP   pingNset_moder   	utterancebarge_inT)send_turn_endaction_resultr   sms_in)r%   r  r   r5  r   r2  r  create_task	_run_turnr
  _resolve_action_handle_event_handle_sms)r   r(  tr   s   &&  r   r8  Session._handles  s    GGFO;**VX&&&*_776?D{" 	 #+ ##%%%$001DEDN*_##$#777/!  %'\$$S)))(]""3''' ! ' & 8 *'sY   5ED9AED<AED>;EE !E0E1	E<E>E EEc                    V ^8  d   QhRRRR/# )r   rc  r   r   r   r   )r   s   "r   r   r     s     
/ 
/ 
/ 
/r   c                	F  "   V P                   pR V n         Vea   VP                  4       '       gI   VP                  4         VG R j  xL
  V'       d$   V P                  \        4       4      G R j  xL
  R # R # R # R #  L5  \        P                  \
        3 d     LPi ; i L/5ir   )r
  rA  cancelr  CancelledErrorrI   r  r   )r   rc  tasks   && r   r2  Session._cancel_turn  s     ~~DIIKKKKM

 jj...  %0 **I6  /sR   -B!B!A> A<A> B!B!/B0B!<A> >BB!BB!c                    V ^8  d   QhRRRR/# )r   r(  rM   r   r+   r   )r   s   "r   r   r     s     	 	 	# 	r   c                	T   \        VP                  R R4      4      P                  4       p\        VP                  RR4      4      R8X  d
   RV,           p\        VP                  RR4      4      P                  4       pV'       d%   VP                  R4      '       g   RV R	2pV R
V 2pV# )rU   rF   sourcer   typedz[TYPED] tone(z(voice: )z  )r+   r%   r6  
startswith)r   r(  rU   rw  s   &&  r   _compose_userSession._compose_user  s    37762&'--/swwx)*g5$D37762&'--/??3''!$q)V2dV$Dr   c                    V ^8  d   QhRRRR/# r]  r   )r   s   "r   r   r     s     + +4 +D +r   c           	     	X  "   V ;P                   ^,          un         \        V P                   RV P                    24      p\        VP	                  RR4      4      P                  4       p V'       g$   V P                  \        4       4      G Rj  xL
  R# V P                  V4      pV P                  \        9   p\        VRV P                  \        V4      \        V4      R7       RpV P                  4       '       d   V P                  WEV4      G Rj  xL
 pV'       g   V P!                  WEV4      G Rj  xL
  \        TRV'       d   R	MR
R7       V P                  \        4       4      G Rj  xL
  \"        P$                  ! \&        P(                  ! V4      4       R#  EL L Ln L7  \"        P*                   d    h \,         da   pT P                  \/        \        T4      R,          4      4      G Rj  xL 
  T P                  \        4       4      G Rj  xL 
   Rp?R# Rp?ii ; i5i)   .rU   rF   Nutterance_received)r   charsr   Fr   fastfulllaneNi,  N)r  r   r   r+   r%   r6  r  r   r{  r   VOICE_MODESr   lenr   rZ  _serve_fast_serve_fullr  rf  rG   passive_rememberrp  rI   r   )r   r(  r   raw_textuser_contentr   served_fastr}   s   &&      r   rg  Session._run_turn  s    T__-Qt}}o>?swwvr*+113	+jj...--c2LII,E$,499HSZ9
  K!!##$($4$4\$$OO&&|DAAA$
K&VM**Z\*** 6 6x @A# / PA* %% 	 	+**WSVD\2333**Z\***	+s   A'H**$F' FF' H*A=F' F!F' F' 4F#58F' -F%..F' H*F' !F' #F' %F' 'H'H'-H"4G75!H"HH"H*"H''H*c               (    V ^8  d   QhRRRRRRRR/# )r   r  r+   r   r   r   r   r   r   )r   s   "r   r   r     s*     - -c -$ - *-/3-r   c           	     (  a"   V P                   f   Q h\        P                  ! V P                   V P                  R,          4      pVP	                  V P
                  VV3R lR7      p^ pRpRpRp	 V  Rj  xL
  w  rV
R8X  d-   \        SR3/ R	R
RR/B   VP                  4       G Rj  xL
  R# V
R8X  d   TpKJ  \        P                  ! V4      w  rVP                  4       '       g   Kz  V^,          pV'       g   Rp\        SRR
R7       V P                  \        WlV4      4      G Rj  xL
  V'       g   K  V P                  Wk4      G Rj  xL
 '       g   K  V	'       d   K  Rp	\        SRR
R7       EK   EL L LQ L0ED
 TP                  4       G Rj  xL 
  M  TP                  4       G Rj  xL 
  i ; iT^ 8X  d   R# T P                  T P
                  RRRT/RRRT;'       g    R/.,           4      T n        \        SRR
TR7       R# 5i)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                     < \        S R RR7      # )first_tokenr  r  r   r   s   r   rq   %Session._serve_fast.<locals>.<lambda>  s    t])Pr   on_tokenrF   Fhandoffescalatefromr  tor  finalTfirst_sentencer  first_audior   r   	assistantr   servedr  	sentences)r  r	  FastLaner  r:  r  r   r4  ttsemotion_labelr6  r  r   _synth_and_send_trim)r   r  r   r   r  agenr   
final_textgot_sentence	got_audiorC   payloadcleanr   s   &&&f          r   r  Session._serve_fast  s    
   ,,,~~d//1E1Ei1PQxxl!P  R
		 '+ @ @md9$$
MvvtV.LM $ ++-# 7?!(J!$!2!27!;{{}}q##'L$ 0v>jjC!@AAA54#7#7#EEEE$9$(	dM??'@*   BE! (,* ++-$++-!8 zzLL	<8Y
0A0AcBDDE 	dH6S9s   A-H1F 3F7E98F;F H+E<,H2AF >.F ,E>-	F ;F F F F &F 9F<H>F  F F HFHF:3F64F::2H-%Hc               (    V ^8  d   QhRRRRRRRR/# )	r   r  r+   r   r   r   r   r   r   r   )r   s   "r   r   r     s*     : :c :$ : *:/3:r   c                  a"   V P                   f   Q hV P                  .V P                  ,           RRRV/.,           p^ pRpRpV P                   P                  VV3R lR7        Rj  xL
  p\        P
                  ! V4      w  rV	P                  4       '       g   K:  V^,          pV'       g   Rp\        SR	R
R7       V P                  \        WYV
4      4      G Rj  xL
  V'       g   K  V P                  WX4      G Rj  xL
 '       g   K  V'       d   K  Rp\        SRR
R7       K   L LM L,DT P                  TR,          4      T n        \        SRR
TR7       R# 5i)z>Run the FULL tool-calling brain and commit the shared history.Nr   r   r   Fc                     < \        S R RR7      # )r  r  r  r  r  s   r   rq   %Session._serve_full.<locals>.<lambda>  s    t]!Hr   r  Tr  r  r  r  :r  NNr  r  )r	  r  r  r:  r  r  r6  r   r  r   r  r  )r   r  r   r   messagesr   r  r  rawr  r   s   &&&f       r   r  Session._serve_full  s:     zz%%%__%4	<@AB	H ( J 	< 	<# !..s3NE;;==1HC#d,6:**ZG<===ut33C==== y $I$F;	< >=J  zz(2,/dH6S9sg   A"E%D0)D**D0-AE0.ED,	E-ED.EEE*D0,E.E0/Ec               $    V ^8  d   QhRRRRRR/# )r   r   r   raw_sentencer+   r   r   r   )r   s   "r   r   r     s!       C D r   c           	       "    \         P                  ! 4       pVP                  R\        V4      G Rj  xL
 pT'       g   R# T P                  \        T4      4      G Rj  xL
  T P                  T4      G Rj  xL
  T P                  \        T4      4      G Rj  xL
  R#  Lm  \         dB   pT P                  \        R\        T4      R,           24      4      G Rj  xL 
   Rp?R# Rp?ii ; i L L{ L[5i)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: ru   FT)r  get_running_looprun_in_executor_tts_wavrI   r  r   r+   r   r  r   )r   r   r  loopwavr}   s   &&&   r   r  Session._synth_and_send  s     	++-D,,T8\JJC jjs+,,,s###jjS)*** K 	**W'8Q%GHIII	
 	-#*s   C8/B# B!B# C8 C8C2 C88C49!C8C6C8!B# #C/.0C*C!C*$C8*C//C84C86C8c               $    V ^8  d   QhRRRRRR/# r   rS   r+   argsrM   r   r   )r   s   "r   r   r     s!     / / /D /S /r   c                   "   V\         P                  9   d   V P                  W4      G Rj  xL
 # \        P                  ! W4      G Rj  xL
 #  L" L5i)zThe brain's tool executor. Device tools go over the WS bridge to the
phone; everything else runs server-side in ``tools.execute``.N)rO  DEVICE_TOOL_NAMES_dispatch_devicerG   execute)r   rS   r  s   &&&r   rR  Session._dispatch_tool  sC      <111..t:::]]4... ;.s!   *AAA
AAAc               $    V ^8  d   QhRRRRRR/# r  r   )r   s   "r   r   r   %  s&     B/ B/3 B/d B/s B/r   c                  "   T;'       g    / pVR8X  d   \        VP                  RR4      4      P                  4       pV'       g   R# RV/p\        P                  ! \        P
                  V4      G Rj  xL
 pV'       d1   R. F  pWe9   g   K  WV,          WF&   K  	  VP                  RV4      VR&   V P                  RV4      G Rj  xL
 pV P                  VR	V R
24      # VR8X  d   \        VP                  RR4      4      P                  4       p\        P                  ! \        P                  V4      G Rj  xL
 p	V	'       d0   \        V	P                  RR4      4      P                  4       '       g   RV R2# V P                  RRV	P                  RV4      RV	R,          /4      G Rj  xL
 pV P                  VRV	P                  RV4       R
24      # VR8X  d   V P                  R/ 4      G Rj  xL
 pVP                  R4      '       g   V P                  VR4      # VP                  R4      ;'       g    / p
 \        V
R,          4      \        V
R,          4      rT P                  Y4      G Rj  xL
 pT'       d   RT R
2# RTR RTR R
2# RRR\        VP                  RR4      4      /RVP                  RR4       R
23RR/ R3RRR \        VP                  R 4      ;'       g    R!4      /R"3R#R#R$\        VP                  R$R4      4      /R%VP                  R$R4       R
23R&R&R'\        VP                  R'R4      4      /R(3R)R)R*\        VP                  R*4      ;'       g    ^ 4      R\        VP                  R4      ;'       g    R4      /R+3/pW9   d7   W,          w  rpV P                  W4      G Rj  xL
 pV P                  VV4      # R,V R-2#  ELd EL EL EL6 EL  \        \        \        3 d     R# i ; i EL LK5i)/zResolve 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destinationrF   z navigate failed: no destination.NlatlnglabelzNavigating to r  call_contactrS   numberzNo number saved for 'z7'. Ask Ahmed for it and I'll save it with save_contact.callzCalling get_locationr   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secondsz
Timer set.zunknown device tool '')addressr  r  )r+   r%   r6  rn   get_item
KIND_PLACE_device_call_device_replyKIND_CONTACTr0   KeyError	TypeErrorr&  _match_placer   )r   rS   r  destr   placer   reswhocontactr  r  r  r  simplerO   ok_msgs   &&&              r   r  Session._dispatch_device%  s     zzr:txxr2399;D9)40F%..{/E/EtLLE2Az$)H	 3 #())GT":w))*f==C%%c^D6+CDD>!dhhvr*+113C'001I1I3OOG#gkk(B&?"@"F"F"H"H/u 55 5 6))VS!9!78#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M#S&)9)D)DW%EF13 hDHHXr4J0K%L#DHHXr$:#;1=?eS%1D-E%F,.+$c$((9*=*B*B&C"C(9(?(?R$@B&(
  >%+\"FF))&99C%%c622&tfA..o M >
 P7 > i4 HGH5. :s   A3P
5O6P
P
8P
OA2P
:O;P
A%P
,O"-AP
3O%4P
)P
9P
=#O(  P
4P5P
A)P
+B P
0P
<P=P
P
P
"P
%P
(P>P
PP
P
c               $    V ^8  d   QhRRRRRR/# )r   r  rM   r  r+   r   r   )r   s   "r   r   r   j  s!     
9 
94 
9 
9 
9r   c                N   V P                  R4      '       d]   V P                  R4      p\        V\        4      '       d*   VP                  R4      '       d   \        VR,          4      # T;'       g    R# \        V P                  R4      ;'       g    R4      pVR8X  d   R# R	V R
2# )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%  rM   r+   )r  r  r  errs   &&  r   r  Session._device_replyi  s     774==776?D$%%$((9*=*=4	?++$$W$#'''"33m4)<0R88r   c               (    V ^8  d   QhRRRRRRRR/# )r   rO   r+   r   rM   r  r0   r   r   )r   s   "r   r   r   v  s*     ) ) )d )$))?C)r   c                z  "   \         P                  ! 4       P                  R,          p\        P                  ! 4       pVP                  4       pW`P                  V&    V P                  \        WAV4      4      G Rj  xL
  \        P                  ! Wc4      G Rj  xL
 p\        V\        4      '       d   TMRRRR/ V P                  P                  VR4       #  L] LA  \        P                   d&    RRRR/u T P                  P                  TR4       # i ; i  T P                  P                  TR4       i ; i5i)zEmit an ``action`` frame and await the matching ``action_result``.
Returns the result dict; on timeout/closed socket returns an error dict.r   Nr   Fr   z
bad resultr  )r  r  r  r  r  create_futurer  r  r   wait_forr%  rM   r'   TimeoutError)r   rO   r   r  aidr  rD  r  s   &&&&    r   r  Session._device_callv  s     jjls#'')"002 c	)**Xc6:;;;((66C$S$//3dE6=|6M M
 MMc4( <6 ## 	5%)44MMc4(	5 MMc4(s`   AD;C ;C<C C!C <D;C C D8D 9D;DD D88D;c                    V ^8  d   QhRRRR/# r]  r   )r   s   "r   r   r     s     8 84 8D 8r   c           
     H   \        VP                  RR4      4      pV P                  P                  V4      pVeh   VP                  4       '       gP   VP	                  R\        VP                  R4      4      RVP                  R4      RVP                  R4      /4       R# R# R# )z;Deliver a phone ``action_result`` to whoever awaits its id.r   rF   Nr   r  r   )r+   r%   r  rA  rB  r   )r   r(  r  rD  s   &&  r   rh  Session._resolve_action  s~    #''$#$mm$?388::NND$swwt}"5"CGGFO#SWWW%57 8 $.?r   c                    V ^8  d   QhRRRR/# r   rc   r+   r   r   r   )r   s   "r   r   r     s     M M M Mr   c                   "   V P                   e9   V P                   P                  4       '       g   \        P                  RV4       R# \        P
                  ! V P                  V4      4      V n         R# 5i)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
  rA  r`   ra   r  rf  _proactive_turn)r   rc   s   &&r   r_   Session._deliver_proactive  sR      >>%dnn.A.A.C.CKKCXN ,,T-A-A(-KLs   .A5AA5c                    V ^8  d   QhRRRR/# r  r   )r   s   "r   r   r     s     + +c +d +r   c                	h  "   V ;P                   ^,          un         \        V P                   RV P                    24      p V P                  \        9   p\        VR4       V P                  WV4      G Rj  xL
  \        VRRR7       V P                  \        4       4      G Rj  xL
  R#  L5 L  \        P                   d    h \         da   pT P                  \        \        T4      R,          4      4      G Rj  xL 
  T P                  \        4       4      G Rj  xL 
   Rp?R# Rp?ii ; i5i)r  z.pproactive_startNr   	proactiver  r  )r  r   r   r   r  r   r  r  r   r  rp  rI   r   r+   )r   rc   r   r   r}   s   &&   r   r  Session._proactive_turn  s     T__-R?@
	+II,E$)*""8D999$
5**Z\*** :*%% 	 	+**WSVD\2333**Z\***	+sq   >D24B/ 5B+6.B/ $B-%B/ )D2+B/ -B/ /D/D/-D*<C?=!D*D!D*$D2*D//D2c                    V ^8  d   QhRRRR/# r]  r   )r   s   "r   r   r     s     0 0t 0 0r   c                	  "   VP                  R 4      R8w  d   R# VP                  R4      ;'       g    / p \        VP                  R4      4      \        VP                  R4      4      rC\	        TP                  R4      ;'       g    R4      P                  4       pT'       d   T P                  T4      G Rj  xL
  R# T P                  Y44      G Rj  xL
 '       d   R# T P                  Y44      '       g   R# Y4\        P                  ! 4       3T n        RTR	 R
TR	 R2pT P                  T4      G Rj  xL
  R#   \        \        3 d     R# i ; i L L L"5i)rC   new_locationNcoordsr  r  r  rF   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%   r0   r  r&  r+   r6  _fire_place_routinesr  _should_prompt_locationr   r   r  r_   )r   r(  r  r  r  place_labelrc   s   &&     r   ri  Session._handle_event  s:    776?n,"((b	VZZ./vzz%7H1I #'''*00b1779 ++K888""3,,,++C55!$4>>+; <9Bs3i (JJ 	
 %%h///' :& 		 9 - 	0sp   0EE3D; *EE$E8E9EE	EAE4E5E;EEEEEEc                    V ^8  d   QhRRRR/# r]  r   )r   s   "r   r   r     s     0 0T 0d 0r   c           	     	*  "   \        VP                  R R4      4      P                  4       p\        VP                  RR4      4      P                  4       pV'       g   R#  \        P                  ! \        P
                  4      G Rj  xL
 pT Uu. uF  p\        TP                  R R4      4      NK   	  pp\        P                  ! Y&4      '       g   R# RT RT R2pT P                  T4      G Rj  xL
  R#  Lp  \         d     R# i ; iu upi  L5i)senderrF   rW   Nz&[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.)	r+   r%   r6  rn   ro   KIND_SMS_WATCHrI   sms_matchesr_   )r   r(  r  rW   watchwsendersrc   s   &&      r   rj  Session._handle_sms  s     SWWXr*+11337762&'--/	%001K1KLLE 6;;U3quuXr*+U;&&v775fXVD6 JL L 	 %%h/// M 		; 	0sT   AD 'C: C8C: D$D4=D1D2D8C: :D	DD			Dc               $    V ^8  d   QhRRRRRR/# )r   r  r0   r  r   r   r   )r   s   "r   r   r     s!     	 	5 	u 	 	r   c                    V P                   f   R# V P                   w  r4p\        P                  ! 4       V,
          \        8  d   \	        WW44      \
        8  d   R# R# )zxDebounce: suppress a new-location prompt if we recently prompted at a
point this close (GPS wobble), otherwise allow it.TF)r  r   r   r
   rA   r   )r   r  r  platplngptss   &&&   r   r  Session._should_prompt_location  sL       (//CNNs"%55 46Gr   c               $    V ^8  d   QhRRRRRR/# )r   r  r0   r  r   r   r   )r   s   "r   r   r     s!      e % J r   c           
       "    \         P                  ! \         P                  4      G Rj  xL
 pT Ft  pRT9   g   K  RT9   g   K   \	        Y\        TR,          4      \        TR,          4      4      pT\        8  g   KP  \        TP                  R4      ;'       g    R4      u # 	  R#  L  \         d     R# i ; i  \        \        3 d     K  i ; i5i)z[Return a saved place's label if these coords are within
``KNOWN_PLACE_M`` of it, else None.Nr  r  r  rF   )rn   ro   r  rI   rA   r0   r  r&  r   r+   r%   )r   r  r  placespds   &&&   r   r  Session._match_place  s     	&11+2H2HIIF Azeqj$SuQuXahPA }$quuW~3344   J 		 ":. sl   C'B. B,B. 	CC-C 5CCC,B. .B=9C<B==C CCCCc                    V ^8  d   QhRRRR/# )r   r  r+   r   r   r   )r   s   "r   r   r     s     & & & &r   c                  "    \         P                  ! \         P                  4      G Rj  xL
 pTP	                  4       P                  4       pT F  pTP                  R4      ;'       g    / pTP                  R4      R9   g   K6  \        TP                  RR4      4      P	                  4       P                  4       T8X  g   Kt  \        T4      G Rj  xL
  K  	  R#  L  \         d     R# i ; i L5i)zCFire any place/both routines whose trigger place matches ``label``.NtriggerrP   r  rF   )r  both)	rn   ro   rp   rI   r6  lowerr%   r+   rd   )r   r  routineswantr;   trigs   &&    r   r  Session._fire_place_routines  s     	(33K4L4LMMH {{}""$A55#))rDxx#44DHHWb1288:@@BdJ"1o%%	 	 N 		 &sU   C3'C CC :C3+C39C3C3C1
C3C C.*C3-C..C3c                    V ^8  d   QhRRRR/# )r   msgsz
list[dict]r   r   )r   s   "r   r   r      s     : :* : :r   c                    \        V4       UUu. uF  w  r#VP                  R4      R8X  g   K  VNK!  	  ppp\        V4      \        8:  d   V# W\        V4      \        ,
          ,          R # u uppi )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%   r  	MAX_TURNS)r   r3  imuser_idxs   &&   r   r  Session._trim   s`     #,D/M/$!QUU6]f5LAA/Mx=I%KS]Y67899 Ns
   A,A,)r  r  r  r  r  r	  r  r  r  r   r   r  r
  r   r   N)F)$r   r   r   r   __doc__r   r  r  r)  r:  r3  r7  rZ  r8  r2  r{  rg  r  r  r  rR  r  staticmethodr  DEVICE_TIMEOUTr  rh  r_   r  ri  rj  r  r  r  r  r   r   r   r   r   r      s    NH(++)VI*3
(.
/	+>-^:8$/B/H 
9 
9 -;)$8M+ 060$	"&: :r   r   c                    V ^8  d   QhRRRR/# )r   r  r+   r   r  r   )r   s   "r   r   r   	  s     1 13 15 1r   c                J    \         P                  ! 4       P                  V 4      # )zJBlocking synth (runs in a threadpool). Lazy-loads the model on first call.)r  get_tts	synth_wav)r  s   &r   r  r  	  s    ;;=""<00r   z/wsc                    V ^8  d   QhRRRR/# r   r   )r   s   "r   r   r     s      )  r   c                T   "   \        V 4      P                  4       G R j  xL
  R #  L5ir   )r   r:  )r   s   &r   ws_endpointrD    s     
"+//
s   (&(__main__zmain:appz0.0.0.0PORT8000)hostport>   chatr   r   )F__conditional_annotations__r;  
__future__r   r  
contextlibr#  loggingr5   r   r   r  fastapir   r   r   r	  rO  rK  rn   rw   rG   r  r  r5  r6  r0   r   r%   r=  r
   r   r   	getLoggerr`   r   __annotations__r   r"   r)   r-   rA   rJ   rX   rd   ri   asynccontextmanagerr~   rk   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r  	websocketrD  r   uvicornr:  r   )rK  s   @r   <module>rU     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KK
78: r   