+
    ?4Wj}              +        a  0 t $ R t^ RIHt ^ RIt^ RIt^ RIt^ RItR R lt	R R lt
R R ltR	 R
 ltR R ltRR R lltRR R lltRtRtR R ltR^ R^R^R^R^R^R^/tR^R^R^R^R^R^R ^R!^R"^	R#^
R$^R%^/tR& R' ltRsR( R) ltR* R+ ltR, R- ltRR. R/ lltR0 R1 ltR2 R3 lt0 RmtR4 R5 ltRR6 R7 llt]! R8R9R:R;R<R=R>/R?R;R@R=RA//R:.4      ]! RBRCRDR;R<R=RE/RFR;R<R=RG//RD.4      ]! RHRIRDR;R<R=RJ/RKR;R<R=RL//RD.4      ]! RMRN/ 4      ]! RORPRQR;R<R=RR//RQ.4      ]! RSRTR:R;R<R=RU/RVR;R<R=RW/RXR;R<RY. ROR=RZ//. 4      ]! R[R\/ 4      ]! R]R^/ 4      ]! R_R`RaR;R@R=Rb//. 4      ]! RcRdReR;R<R=Rf/RgR;R<R=Rh//ReRg.4      ]! RiRjReR;R<R=Rk//Re.4      ]! RlRm/ 4      ]! RnRoRpR;R<R=Rq/RrR;R<R=Rs/RtR;R<R=Ru//RpRr.4      ]! RvRwRpR;R<R=Rx//Rp.4      ]! RyRz/ 4      ]! R{R|R}R;R<R=R~//R}.4      ]! RRR}R;R<R=R//R}.4      ]! RR/ 4      ]! RRRpR;R<R=R/RR;RR=RRR;R;R<RY. RO/RR;R<R=R/RaR;RRR;R</R=R/RR;R<R=R//RR;./RR;RR=RRR;R;R<RY. RO/RDR;R<R=R/RR;R<RYR.R=R//RR;.//. RO4      ]! RR/ 4      ]! RRRpR;R<R=R//Rp.4      .t 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)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t0R R lt1R R lt2R R lt3R R lt4R R lt5R R lt6R R lt7R R lt8R R lt9R R lt:R R lt;/ R8]"bRB]#bRH]$bRM]%bRO]&bRS](bR[])bR]]*bR_]+bRc]-bRi]/bRl]0bRn]1bRv]3bRy]4bR{]5bR]6bR]7R]9R]:R];/Ct<R R lt=R# )u  Tool definitions + executors for the gateway brain.

Two backends, both reached over plain async HTTP (httpx):
  * The memory service (Graphiti/FalkorDB on Railway) — same endpoints and
    bearer-header shape as ``voice/memory_control.py`` / ``voice/tasks_control.py``
    (MEMORY_API_URL + MEMORY_API_KEY, group namespace MEMORY_GROUP).
  * Supabase (the Ultron master DB) via PostgREST RPCs — the exact call shape
    copied from ``dashboard/server.js``: POST ``{SUPABASE_REST}/rpc/<name>``
    with ``{apikey, Authorization: Bearer <SUPABASE_SECRET>}`` headers.

Tools exposed to the LLM (OpenAI function-calling schema, ``TOOL_SPECS``):
  memory_search, memory_remember,
  task_add, tasks_list, task_done,
  ultron_leads, ultron_team_activity, ultron_stats, ultron_contact_stats.

The HTTP is funnelled through ``_mem_request`` and ``_rpc`` so tests can
monkeypatch those two functions and never touch the network.
)annotationsNc                   V ^8  d   QhRR/#    returnstr )formats   "gateway/tools.py__annotate__r      s     < <# <    c                 `    \         P                  P                  R R4      P                  R4      # )MEMORY_API_URL /osenvirongetrstripr   r   r
   _mem_urlr      s#    ::>>*B/66s;;r   c                   V ^8  d   QhRR/# r   r   )r	   s   "r
   r   r   !   s     0 0# 0r   c                 B    \         P                  P                  R R4      # )MEMORY_API_KEYr   r   r   r   r   r   r
   _mem_keyr   !   s    ::>>*B//r   c                   V ^8  d   QhRR/# r   r   )r	   s   "r
   r   r   %   s     3 3 3r   c                 B    \         P                  P                  R R4      # )MEMORY_GROUPahmedr   r   r   r
   _groupr    %   s    ::>>.'22r   c                   V ^8  d   QhRR/# r   r   )r	   s   "r
   r   r   )   s     ; ;s ;r   c                 `    \         P                  P                  R R4      P                  R4      # )SUPABASE_RESTr   r   r   r   r   r
   _restr$   )   s"    ::>>/2.55c::r   c                   V ^8  d   QhRR/# r   r   )r	   s   "r
   r   r   -   s     1 1# 1r   c                 B    \         P                  P                  R R4      # )SUPABASE_SECRETr   r   r   r   r
   _sup_keyr(   -   s    ::>>+R00r   c               0    V ^8  d   QhRRRRRRRRRRR	R
/# )r   methodr   pathparamsdict | Nonebodytimeoutfloatr   dictr   )r	   s   "r
   r   r   2   s>     . .s .# .*.(. !&. 26.r   c           	     n  "   ^ RI p\        4       pV'       g   \        R4      hWa,           pV'       d.   VR\        P                  P                  V4      ,           ,          pRR/p\        4       p	V	'       d	   RV	 2VR&   VP                  VR7      ;_uu_4       GRj  xL
 p
T
P                  YTVe   TMRR	7      G Rj  xL
 pVP                  4        VP                  pV'       d   \        P                  ! V4      M/ uuRRR4      GRj  xL
  #  Lt LT L	  + GRj  xL 
 '       g   i     R# ; i5i)
z@Call the memory service. Returns parsed JSON ({} on empty body).NzMEMORY_API_URL not set?Content-Typeapplication/jsonBearer Authorizationr/   headersjson)httpxr   RuntimeErrorurllibparse	urlencoder   AsyncClientrequestraise_for_statuscontentr;   loads)r*   r+   r,   r.   r/   r<   baseurlr:   keyclientrespraws   &&&&&        r
   _mem_requestrL   2   s     
 :D344
+CsV\\++F33312G
*C
%,SE?    111V^^F151At $ M Mll"%tzz#2 211M 2111sf   BD5DD5!D?D 'D(D?D5DD5DD5D2	D!
D2	*D2	,	D5c               (    V ^8  d   QhRRRRRRRR/# )	r   fnr   argsr-   r/   r0   r   objectr   )r	   s   "r
   r   r   J   s*      3 k )/r   c                  "   ^ RI p\        4       pV'       g   \        R4      h\        4       pRVRRV 2RR/pVP	                  VR7      ;_uu_4       GRj  xL
 pTP                  V R	V  2TT;'       g    / R
7      G Rj  xL
 pVP                  4        VP                  4       uuRRR4      GRj  xL
  #  La L8 L	  + GRj  xL 
 '       g   i     R# ; i5i)uD   Call a Supabase PostgREST RPC — same shape as dashboard/server.js.NzSUPABASE_REST not setapikeyr7   r6   r4   r5   r8   z/rpc/r9   )r<   r$   r=   r(   rA   postrC   r;   )	rN   rO   r/   r<   restrH   r:   rI   rJ   s	   &&&      r
   _rpcrU   J   s      7D233
*Co13G   111V[[D6rd!3W&*jjb ! 2 2yy{	 2112 2111s`   ACB9C'B?B;#B?&C3B=4C;B?=C?C	C
C	C		Cc                    V ^8  d   QhRRRR/# )r   textr   r   r0   r   )r	   s   "r
   r   r   c   s      c e r   c                d  a T ;'       g    R P                  4       o\        ;QJ d#    V3R l\         4       F  '       g   K   RM	  RM! V3R l\         4       4      '       d   R# \        ;QJ d#    V3R l\         4       F  '       g   K   RM	  RM! V3R l\         4       4      '       d   R# R# )r   c              3  ,   <"   T F	  qS9   x  K  	  R # 5iNr   .0wts   & r
   	<genexpr>_confidence.<locals>.<genexpr>e   s     
!5a65   TFg?c              3  ,   <"   T F	  qS9   x  K  	  R # 5irZ   r   r[   s   & r
   r_   r`   g   s     
"6a66ra   g      ?g      ?)lowerany_FIRM_HEDGE)rW   r^   s   &@r
   _confidencerg   c   sb    	A
s
!5
!sss
!5
!!!
s
"6
"sss
"6
"""r   mondaytuesday	wednesdaythursdayfridaysaturdaysundayjanuaryfebruarymarchaprilmayjunejulyaugust	septemberoctobernovemberdecemberc                    V ^8  d   QhRRRR/# )r   qr   r   ztuple[str | None, str | None]r   )r	   s   "r
   r   r   u   s     5 5s 5< 5r   c           	       a ^ RI o^ RIpT ;'       g    RP                  4       pSP                  P	                  4       pR pV3R lpVP                  RV4      pV'       dm    SP                  \        VP                  ^4      4      \        VP                  ^4      4      \        VP                  ^4      4      4      pV! V4      V! V4      3# RV9   d   V! V4      V! V4      3# RV9   d*   VSP                  ^R7      ,
          pV! V4      V! V4      3# R	V9   dO   VSP                  VP                  4       R7      ,
          p	V! V	4      V! V	SP                  ^R7      ,           4      3# R
V9   dV   VSP                  VP                  4       ^,           R7      ,
          p	V! V	4      V! V	SP                  ^R7      ,           4      3# RV9   d)   VP                  ^R7      p	V! V	4      V! V! V	4      4      3# RV9   dJ   VP                  ^R7      SP                  ^R7      ,
          p
V! V
P                  ^R7      4      V! V
4      3# VP                  RV4      pV'       dl   VP                  4       \        VP                  ^4      ,          ,
          ^,          ;'       g    ^pVSP                  VR7      ,
          pV! V4      V! V4      3# VP                  RV4      pV'       dt   \        VP                  ^4      ,          pWP                  8:  d   VP                  MVP                  ^,
          pSP                  W^4      p	V! V	4      V! V! V	4      4      3# R#   \         d     ELi ; i)zTurn a relative-time phrase in the query into an ISO (when_from, when_to)
window for /search. English phrases + ISO numeric dates only; (None, None)
if nothing time-like is present. No deps.Nr   c                "    V P                  4       # rZ   )	isoformat)ds   &r
   _iso_temporal_range.<locals>._iso~   s    {{}r   c                   < V P                  ^R7      SP                  ^ R7      ,           P                  ^R7      SP                  ^R7      ,
          # )   daydays)replace	timedelta)r   _dts   &r
   
_month_end#_temporal_range.<locals>._month_end   sG    		a	 3==b=#99BBqBImmm#$ 	$r   z\b(\d{4})-(\d{2})-(\d{2})\btoday	yesterdayr   z	this weekz	last weekz
this monthr   z
last monthzH\b(?:on\s+)?(monday|tuesday|wednesday|thursday|friday|saturday|sunday)\bz`\bin\s+(january|february|march|april|may|june|july|august|september|october|november|december)\b)NN)datetimererc   dater   searchintgroup
ValueErrorr   weekdayr   	_WEEKDAYS_MONTHSmonthyear)r|   _rer^   r   r   r   mr   ystartenddeltamor   r   s   &             @r
   _temporal_ranger   u   s    	
bAHHNNE$ 	

115A	QWWQZ#aggaj/3qwwqz?KA7DG## !|E{DK''aCMMqM))AwQa5==?;;E{DA)>!>???a5==?Q+>??E{DA)>!>???q!$E{DE!2333qmmm"S]]]%::CKKAK&'c22

 )*+	-A9QWWQZ#88A=BBCMMuM--AwQ

 DEF	HAQWWQZ ;;.uzzEJJN1%E{DE!2333A  		s   "A*M M'&M'c                   V ^8  d   QhRR/# r   r   Noner   )r	   s   "r
   r   r      s      D r   c                
    V s R# )zBmain registers an async callback fired after any routine mutation.N)_ROUTINE_HOOK)rN   s   &r
   set_routine_hookr      s	     Mr   c                   V ^8  d   QhRR/# r   r   )r	   s   "r
   r   r      s      d r   c                 p   "   \         e    \        4       G R j  xL
  R # R #  L  \         d     R # i ; i5irZ   )r   	Exceptionr   r   r
   _notify_routine_changer      s4      	/!! !! 		s(   	6$ "$ 6$ 3636c                    V ^8  d   QhRRRR/# )r   numberr   r   r   )r	   s   "r
   r   r      s     - - - -r   c                    RP                  R \        T ;'       g    R4       4       4      pV'       g   R# RRP                  VRR 4      ,           # )u   A phone number rendered for the model/logs so a full number is never
echoed or read aloud — only the last three digits survive.r   c              3  T   "   T F  qP                  4       '       g   K  Vx  K   	  R # 5irZ   )isdigit)r\   chs   & r
   r_   mask_number.<locals>.<genexpr>   s     D"3Bzz|RR"3s   (
(z(no number)zending  N)joinr   )r   digitss   & r
   mask_numberr      sC     WWD#fll"3DDFsxxrs,,,r   c                    V ^8  d   QhRRRR/# )r   limitr   r   r   r   )r	   s   "r
   r   r      s      3  r   c                  "    \        RRR\        4       RV /R7      G Rj  xL
 p. pTP                  R. 4       Fc  pTP                  R	4      ;'       g    TP                  R
4      ;'       g    RP	                  4       pT'       g   KO  TP                  RT 24       Ke  	  RP                  T4      #  L  \         d     R# i ; i5i)u   A short 'index' of recent long-term memories for the system prompt.

The desktop reads a local memory/MEMORY.md; the phone has no filesystem
memory, so we summarise the newest facts from the service instead. Never
raises — returns "" on any failure.GETz	/memoriesr   r   r,   Nr   memoriesrW   fact- 
)rL   r    r   r   stripappendr   )r   outlinesr   txts   &    r
   memory_index_textr      s      )0&(GU(KM M EWWZ$uuV}33f33::<3LL2cU$ % 99UM sI   CB4 B2B4 .CC.C	)C2B4 4C?CCCc                   V ^8  d   QhRR/# r   r   )r	   s   "r
   r   r      s     + +3 +r   c                    "    \        RRR\        4       /R7      G Rj  xL
 p T P                  R4      ;'       g    RP	                  4       #  L-  \         d     R# i ; i5i)zBAhmed's north-star profile from the memory service. "" on failure.r   z/valuesr   r   Nr   rW   )rL   r    r   r   r   )r   s    r
   values_textr      sZ      	7FH:MNN GGFO!!r((** O s<   A!A AA A!A!A AA!AA!c                    V ^8  d   QhRRRR/# )r   	user_textr   r   r   r   )r	   s   "r
   r   r      s      c d r   c                   "   T ;'       g    RP                  4       p\        V4      '       g   R#  \        RRRVR\        4       RRR	R
R\	        V4      RRR^/R7      G Rj  xL
  R#  L  \
         d     R# i ; i5i)zFire-and-forget passive save after an exchange, with a simple v1
salience gate. Mirrors the desktop's auto-remember intent: only durable-
looking utterances are stored, as observations. Never raises.r   NPOST	/rememberrW   r   sourcegatewaykindobservation
confidencerK   T
importancer.   )r   _is_salientrL   r    rg   r   )r   r^   s   & r
   passive_rememberr      s      
b!Aq>>	 6;!'GVX!)9fm!-{1~!&lA!?@ 	@ 	@
  s:   .A<0A* !A("A* &A<(A* *A95A<8A99A<c                    V ^8  d   QhRRRR/# )r   rW   r   r   boolr   )r	   s   "r
   r   r     s      c d r   c                d  a V P                  4       pVP                  4       P                  R4      o\        V4      ^8  d   R# S\        9   d   R# VP                  R4      '       dJ   RV9   dC   \        ;QJ d    V3R lR 4       F  '       g   K   RM	  RM! V3R lR 4       4      '       g   R# R# )zCheap v1 gate: keep declarative, substantive utterances; drop greetings,
one-liners, and bare questions (those are asks, not facts to store).z!.Fr3   r   c              3  ,   <"   T F	  qS9   x  K  	  R # 5irZ   r   )r\   clows   & r
   r_   _is_salient.<locals>.<genexpr>  s      0KIHIra   T)rememberz	note thatzfor the record)r   rc   r   len_TRIVIALendswithrd   )rW   r^   r   s   & @r
   r   r     s     	

A
'')

4
 C
1v{
hzz#3!8CC 0KI0KCCC 0KI0K -K -Kr   c          
     ,    V ^8  d   QhRRRRRRRRRR/# )	r   namer   description
propertiesr1   requiredzlist[str] | Noner   r   )r	   s   "r
   r   r     s1      c   ".2r   c                :    R RRRT RTRR RRTRT;'       g    . ///# )typefunctionr   r   
parametersrP   r   r   r   )r   r   r   r   s   &&&&r
   _fnr     sB     	
D;jHNN
 r   memory_searchu  Search Ahmed's long-term memory by meaning and get back connected facts. Use whenever the answer might depend on something he told you before — a person, client, meeting, number, price, project or plan. Read every fact returned; the specific one is often not first.queryr   stringr   z$What to recall, in natural language.kintegerz!Max facts to return (default 10).memory_rememberu   Store a durable new fact Ahmed just told you (a preference, a person, a decision, a number). Saving is quick — acknowledge and move on. Do NOT use for transient chit-chat.rW   zOne clear fact to remember.r   z1fact|preference|event|observation (default fact).task_adduu   Add a task or reminder to Ahmed's shared, cross-device list. Use for 'remind me to…', 'add a task', 'I need to…'.z0Short imperative task, e.g. 'Call the supplier'.duezdISO local time for a timed reminder, e.g. 2026-07-08T17:00:00 (Asia/Riyadh). Omit for a plain to-do.
tasks_listz[List Ahmed's open tasks and reminders. Use for 'what are my tasks', 'what do I have to do'.	task_donez=Mark a task or reminder complete by its id (from tasks_list).idzThe task id.ultron_leadszSearch the Ultron lead database (Saudi business leads scraped from Google Maps). Use when Ahmed asks about leads, businesses, or prospects in a city.z.Free-text search (company/category), optional.cityz+City filter, e.g. 'Al Khobar'. Default all.phone_filterenumu?   'mobile' = has a WhatsApp-able Saudi 05… number. Default all.ultron_team_activityzWho on the agency team contacted which leads recently (last 30 days). Use for 'what has the team been doing', outreach activity.ultron_statszThe Ultron dashboard funnel/summary numbers (total leads, verified emails, phones, recent haul). Use for 'how many leads do we have'.ultron_contact_statszsOutreach contact volume per day over the last N days (per client). Use for 'how much outreach did we do this week'.r   z&Look-back window in days (default 30).
save_placeu   Remember a place by a short label so Ahmed can later say 'navigate to the office'. Use when he names a location — 'this is my gym', 'save home as…'. Store an address, or GPS as 'lat,lng' (e.g. from a new-location prompt).labelz'Short name, e.g. 'work office', 'home'.address_or_coordsz>A street address, OR 'lat,lng' coordinates like '26.30,50.20'.	get_placez]Look up a saved place by its label. Use before navigating or when he refers to a named place.zThe place label.list_placesz;List every place Ahmed has saved (labels + where they are).save_contactu   Remember a person's phone number so Ahmed can later say 'call my father'. Use when he gives you a name + number. Numbers are private — just confirm you saved it, never read the number back.r   z#Who this is, e.g. 'father', 'Sara'.r   zTheir phone number.relationz1Optional relationship, e.g. 'family', 'supplier'.get_contactu   Look up a saved contact by name. Returns the name/relation and a MASKED number (last digits only) — to actually dial, use call_contact.zWho to look up.list_contactszRList everyone in Ahmed's saved phonebook (names + relations; numbers stay masked).sms_watch_addu   Add a sender to the SMS watch-list — texts from it get forwarded to you to summarise/act on. Senders like 'SNB-AlAhli', 'Barq', or '*' for ALL senders.senderz#Sender id, or '*' for every sender.sms_watch_removez(Remove a sender from the SMS watch-list.zSender id to stop watching.sms_watch_listz1List the SMS senders currently on the watch-list.routine_createu   Create a routine that fires automatically — e.g. 'each morning at seven give me a brief of my tasks' (time 07:00 daily → speak_brief tasks) or 'when I get to the office, read my tasks' (place trigger). Resolve times yourself in 24h Asia/Riyadh.z(Short unique name, e.g. 'morning brief'.triggerrP   zWhen it fires.r   placeatz24h time 'HH:MM' (time/both).arrayitemsz1Weekdays mon..sun; omit or ['daily'] = every day.z!A saved place label (place/both).r   actionzWhat Jarvis does when it fires.z!For say_text/run: what to say/do.brieftaskszFor speak_brief: which brief.routine_listz;List Ahmed's routines (name, when they fire, what they do).routine_cancelz Cancel/delete a routine by name.zThe routine name.z
list[dict]
TOOL_SPECSc                    V ^8  d   QhRRRR/# r   rO   r1   r   r   r   )r	   s   "r
   r   r     s      t  r   c           
       "   \        V P                  R R4      4      P                  4       pV'       g   R# \        ^\	        \        V P                  R4      ;'       g    ^
4      ^4      4      pRVRVR\        4       /p\        V4      w  rEV'       d   WCR&   V'       d   WSR&   \        RR	VR
7      G Rj  xL
 pVP                  R. 4      pV'       g   R# . pV F  p	V	P                  R4      '       d   RMRp
V	P                  R4      ;'       g    RR,          pTP                  RV	P                  RR4       V
 2V'       d   RV R2MR,           RV	P                  RR4       R2,           4       K  	  RP                  V4      #  L5i)r   r   zmemory_search failed: no query.r   r|   r   	when_fromwhen_tor   z/searchr   Nfactsz (nothing relevant in memory yet)linkedz [connected]	refers_to:N
   Nr   r   z  (re: )  [id r   r3   ]r   )r   r   r   maxminr   r    r   rL   r   r   )rO   r|   r   r,   wfwtr   r  r   ftagwhens   &           r
   _memory_searchr-    sX    DHHWb!"((*A0As3txx}**+R01A1c1gvx0FQFB	 {	yUIf=
=CGGGR E1E !hnRk"((b#.r!%%+,SE2-1'$q)r;dC 0134 	5  99U >s+   BFF4F5F;F2FAFc                    V ^8  d   QhRRRR/# r  r   )r	   s   "r
   r   r     s       # r   c                Z  "   \        V P                  R R4      4      P                  4       pV'       g   R# \        V P                  R4      ;'       g    R4      P                  4       ;'       g    Rp\        RRR VR\	        4       RR	RVR
\        V4      RRR^/R7      G Rj  xL
  R#  L5i)rW   r   z memory_remember failed: no text.r   r   r   r   r   r   r   r   rK   Tr   r   NzRemembered.)r   r   r   rL   r    rg   )rO   rW   r   s   &  r
   _memory_rememberr0    s     txx#$**,D1txx))6*002<<fD v{#T7FH%y&$);t+<"D,;< < <
 <s   AB+B+/3B+"B)#B+c                    V ^8  d   QhRRRR/# r  r   )r	   s   "r
   r   r     s     	Q 	Q$ 	Q3 	Qr   c                  "   \        V P                  R R4      4      P                  4       pV'       g   R# R VR\        4       RR/pV P                  R4      '       d   \        V R,          4      VR&   \	        RRVR	7      G R
j  xL
  RV 2VP                  R4      '       d   RVR,           R2,           # R,           #  L:5i)rW   r   ztask_add failed: no task text.r   r   jarvisr   r   z/taskr   NzAdded: z (reminder set for r#  )r   r   r   r    rL   )rO   rW   r.   s   &  r
   	_task_addr4    s     txx#$**,D/D'68Xx@Dxx$u+&U
vwT
222dV7;xx$T%[M3P QLNP Q 3s   BC B>C #C c                    V ^8  d   QhRRRR/# r   _argsr1   r   r   r   )r	   s   "r
   r   r     s     
 
T 
c 
r   c                  "   \        R RR\        4       /R7      G Rj  xL
 pVP                  R. 4      pV'       g   R# . pV Fz  pVP                  R4      '       d%   RVR,          R	,          P                  R
R4       R2MRpVP	                  RVP                  RR4       V RVP                  RR4       R24       K|  	  RP                  V4      #  L5i)r   z/tasksr   r   Nr  zNo open tasks.r   z  (due :N   NTr   r#  r   r   rW   r$  r   r3   r%  r   )rL   r    r   r   r   r   )r7  r   r  r   r^   r   s   &     r
   _tasks_listr;    s     UHgvx5HI
ICGGGR EE%%,, 5#..sC89;$& 	r!%%+,SEdC8H7IKL  99U Js   CCCBCc                    V ^8  d   QhRRRR/# r  r   )r	   s   "r
   r   r     s      4 C r   c           	        "   \        V P                  R R4      4      P                  4       pV'       g   R# \        RRR VR\	        4       /R7      G Rj  xL
  R#  L5i)	r   r   ztask_done failed: no task id.r   z
/task/doner   r   NzMarked done.)r   r   r   rL   r    )rO   tids   & r
   
_task_doner?    sO     
dhhtR 
!
'
'
)C.
v|4gvx2P
QQQ Rs   AAAAc                    V ^8  d   QhRRRR/# )r   resultrP   r   listr   )r	   s   "r
   r   r     s      & T r   c                h    \        V \        4      '       d   V # \        V \        4      '       d   V .# . # )zERPCs return either a list of rows or a single JSON object. Normalise.)
isinstancerB  r1   )rA  s   &r
   _rowsrE    s-    &$&$xIr   c                    V ^8  d   QhRRRR/# r  r   )r	   s   "r
   r   r   "  s     6 6d 6s 6r   c                H  "   \        V P                  R R4      4      P                  4       p\        V P                  R4      ;'       g    R4      P                  4       ;'       g    Rp\        V P                  R4      ;'       g    R4      P                  4       ;'       g    Rp\        RRVRRRVR	RR
RRVRRRRR^R^ /
4      G Rj  xL
 p\	        V4      pV'       g   R# . pVR,           EF
  pVP                  R4      ;'       g    VP                  R4      ;'       g    RpV.p	VP                  R4      '       d"   V	P                  \        VR,          4      4       VP                  R4      '       d"   V	P                  \        VR,          4      4       VP                  R4      '       d"   V	P                  \        VR,          4      4       VP                  RRP                  V	4      ,           4       EK  	  \        V4       R2RP                  V4      ,           #  ELX5i)r   r   r   allr   search_leadsr|   
p_categoryp_cityp_fresh
p_verifiedp_phonep_clientNp_assigned_onlyFlimoffzNo matching leads.:N   Ncompanyr   z	(unnamed)categoryphoner   u    · z leads:
r   )r   r   r   rU   rE  r   r   r   )
rO   r|   r   rV  resrowsr   rr   partss
   &         r
   _ultron_leadsr[  "  s    DHHWb!"((*Atxx((5)//1::UD(11E288:CCeE^QeXt5,y%D+Ur5!	&  C :D#E#YYuuY??155=??K55LLQz]+,55==LLQvY(55>>LLQwZ)TFKK../  $i[	"TYYu%555)sC   A"H"%H"H"!&H"H?H"H"!H">7H"67H".A2H"c                    V ^8  d   QhRRRR/# r6  r   )r	   s   "r
   r   r   =  s     5 5t 5 5r   c                   "   \        R RR/4      G Rj  xL
 p\        V4      pV'       g   R# \        P                  ! VR,          RR7      #  L85i)get_team_activityp_range30dNz%No team activity in the last 30 days.:N   NFensure_ascii)rU   rE  r;   dumps)r7  rW  rX  s   &  r
   _ultron_team_activityre  =  sC     (9e*<=
=C:D6::d3ie44	 >s   AA9Ac                    V ^8  d   QhRRRR/# r6  r   )r	   s   "r
   r   r   E  s     6 6t 6 6r   c                x   "   \        R RR/4      G Rj  xL
 p\        P                  ! VRR7      R,          #  L#5i)get_dashboard_statsr_  rH  NFrb  Ni  N)rU   r;   rd  )r7  rW  s   & r
   _ultron_statsrj  E  s6     *Y,>?
?C::c.u55 @s   :8$:c                    V ^8  d   QhRRRR/# r  r   )r	   s   "r
   r   r   J  s     6 6d 6s 6r   c                   "   \        V P                  R 4      ;'       g    ^4      p\        RRV R2/4      G Rj  xL
 p\        P                  ! VRR7      R,          #  L#5i)r   get_contact_statsr_  r   NFrb  ri  )r   r   rU   r;   rd  )rO   r   rW  s   &  r
   _ultron_contact_statsrn  J  sW     txx%%2&D(9aj*AB
BC::c.u55 Cs   9A!A$A!c                    V ^8  d   QhRRRR/# )r   sr   r   ztuple[float, float] | Noner   )r	   s   "r
   r   r   Q  s      S 7 r   c                   \        T ;'       g    R4      P                  RR4      P                  R4       Uu. uF  q'       g   K  VNK  	  pp\        V4      ^8w  d   R#  \	        V^ ,          4      \	        V^,          4      rCRTu;8:  d   ^Z8:  d   M R# RTu;8:  d   ^8:  d    Y43#  R# R# u upi   \
         d     R# i ; i)z6Parse 'lat,lng' (or 'lat lng') into floats, else None.r   r   ,NiiL)r   r   splitr   r0   r   )rp  prZ  latlngs   &    r
   _parse_coordsrw  Q  s    AGG,,S#6<<SAGA1QQQAEG
5zQq?E%(OS cR !C.3.x / H
  s   	B.B. #B3 3CCc                    V ^8  d   QhRRRR/# r  r   )r	   s   "r
   r   r   _  s      D S r   c                  "   \        V P                  R R4      4      P                  4       p\        V P                  RR4      4      P                  4       pV'       d	   V'       g   R# R V/p\        V4      pV'       d   Vw  VR&   VR&   MW#R&   \        P
                  ! \        P                  V4      G Rj  xL
  RV R	2#  L
5i)
r  r   r  z>save_place failed: need a label and an address or coordinates.ru  rv  addressNzSaved ''.)r   r   r   rw  phone_storeput_item
KIND_PLACE)rO   r  wherepayloadcoordss   &    r
   _save_placer  _  s     "%&,,.E,b1288:EOe$G5!F)/&"	


{55w
???UG2 @s   A"C%AC6C7Cc                    V ^8  d   QhRRRR/# )r   rt  r1   r   r   r   )r	   s   "r
   r   r   n  s     # #$ #3 #r   c                
   V P                  R 4      '       d   V P                  R4       RV R ,           2# RV 9   d/   RV 9   d(   V P                  R4       RV R,           RV R,           2# \        V P                  RR4      4      # )rz  r  z: ru  rv  , r   )r   r   )rt  s   &r
   
_fmt_placer  n  sz    uuY%%.!AiL>22zeqj%%.!AeH:R%z::quuWb!""r   c                    V ^8  d   QhRRRR/# r  r   )r	   s   "r
   r   r   v  s      4 C r   c                   "   \        V P                  R R4      4      P                  4       p\        P                  ! \        P
                  V4      G Rj  xL
 pV'       g   RV R2# \        V4      #  L5i)r  r   NzNo place saved as 'r{  )r   r   r   r|  get_itemr~  r  )rO   r  rt  s   &  r
   
_get_placer  v  s`     "%&,,.E"";#9#95AAA$UG2..a= 	B   AA4A2A4!A4c                    V ^8  d   QhRRRR/# r6  r   )r	   s   "r
   r   r   ~  s     : :d :s :r   c                   "   \         P                  ! \         P                  4      G R j  xL
 pV'       g   R# RP                  R V 4       4      #  L&5i)NzNo places saved yet.r   c              3  F   "   T F  pR \        V4      ,           x  K  	  R# 5ir   N)r  )r\   rt  s   & r
   r_   _list_places.<locals>.<genexpr>  s     95aTJqM))5   !)r|  
list_itemsr~  r   r7  r  s   & r
   _list_placesr  ~  s?     (()?)?@@E%9995999 A   (AAAAc                    V ^8  d   QhRRRR/# r  r   )r	   s   "r
   r   r     s     
 
d 
s 
r   c                  "   \        V P                  R R4      4      P                  4       p\        V P                  RR4      4      P                  4       pV'       d	   V'       g   R# R VRV/p\        V P                  RR4      4      P                  4       pV'       d   WCR&   \        P                  ! \        P
                  V4      G Rj  xL
  RV R2#  L
5i)r   r   r   z.save_contact failed: need a name and a number.r  NzSaved .)r   r   r   r|  r}  KIND_CONTACT)rO   r   r   r  rels   &    r
   _save_contactr    s     txx#$**,D(B'(..0Fv?tXv.G
dhhz2&
'
-
-
/C
!



{77
AAAD6 Bs   A"C%A%C
CCc                    V ^8  d   QhRRRR/# )r   r   r1   r   r   r   )r	   s   "r
   r   r     s     J JD JS Jr   c           	         V P                  R 4      '       d   RV R ,           R2MRpV P                  R4       V R\        V P                  RR4      4       2# )r  z (r#  r   r   u    — r   )r   r   )r   r  s   & r
   _fmt_contactr    sR    #$55#4#4Bq}oQ
"CeeFm_SE{15523F'G&HIIr   c                    V ^8  d   QhRRRR/# r  r   )r	   s   "r
   r   r     s      T c r   c                   "   \        V P                  R R4      4      P                  4       p\        P                  ! \        P
                  V4      G Rj  xL
 pV'       g   RV R2# \        V4      #  L5i)r   r   NzNo contact saved for 'r{  )r   r   r   r|  r  r  r  )rO   r   r   s   &  r
   _get_contactr    s`     txx#$**,D"";#;#;TBBA'vR00? 	Cr  c                    V ^8  d   QhRRRR/# r6  r   )r	   s   "r
   r   r     s     < < < <r   c                   "   \         P                  ! \         P                  4      G R j  xL
 pV'       g   R# RP                  R V 4       4      #  L&5i)NzNo contacts saved yet.r   c              3  F   "   T F  pR \        V4      ,           x  K  	  R# 5ir  )r  )r\   r   s   & r
   r_   !_list_contacts.<locals>.<genexpr>       ;UTLO++Ur  )r|  r  r  r   r  s   & r
   _list_contactsr    s?     (()A)ABBE'99;U;;; Cr  c                    V ^8  d   QhRRRR/# r  r   )r	   s   "r
   r   r     s     , ,t , ,r   c                   "   \        V P                  R R4      4      P                  4       pV'       g   R# \        P                  ! \        P
                  R V/4      G Rj  xL
  VR8X  d   R# RV R2#  L5i)r  r   z sms_watch_add failed: no sender.N*zWatching all SMS senders now.zWatching texts from r  )r   r   r   r|  r}  KIND_SMS_WATCH)rO   r  s   & r
   _sms_watch_addr    si     (B'(..0F1


{99Hf;M
NNN}.!&++ Os   AA6 A4!A6c                    V ^8  d   QhRRRR/# r  r   )r	   s   "r
   r   r     s     S S$ S3 Sr   c                   "   \        V P                  R R4      4      P                  4       pV'       g   R# \        P                  ! \        P
                  V4      G Rj  xL
 pV'       d   RV R2# V R2#  L5i)r  r   z#sms_watch_remove failed: no sender.NzStopped watching r  z wasn't on the list.)r   r   r   r|  delete_itemr  )rO   r  ns   &  r
   _sms_watch_remover    si     (B'(..0F4%%k&@&@&IIA,-vha(RfX=Q3RR 	Js   AA8A6A8+A8c                    V ^8  d   QhRRRR/# r6  r   )r	   s   "r
   r   r     s     M M M# Mr   c                   "   \         P                  ! \         P                  4      G R j  xL
 pV'       g   R# RRP                  R V 4       4      ,           #  L-5i)NzThe SMS watch-list is empty.z
Watching: r  c              3  X   "   T F   p\        VP                  R R4      4      x  K"  	  R# 5i)r  r   N)r   r   )r\   is   & r
   r_   "_sms_watch_list.<locals>.<genexpr>  s#     #LeCh(;$<$<es   (*)r|  r  r  r   r  s   & r
   _sms_watch_listr    sD     (()C)CDDE-$))#Le#LLLL Es   (AAA"Ac                    V ^8  d   QhRRRR/# )r   rY  r1   r   r   r   )r	   s   "r
   r   r     s     9 9D 9S 9r   c                   V P                  R 4      ;'       g    / pV P                  R4      ;'       g    / pVP                  RR4      pVP                  R4      '       d   VRVR,           2,          pVP                  R4      '       d'   VRRP                  VR,          4      ,           ,          pVP                  R4      '       d   VR	VR,           2,          pVP                  RR4      pVP                  R
4      '       d   VRVR
,           2,          pM2VP                  R4      '       d   VRVR,          R,           R2,          pV P                  R4       RV RV 2# )r  r  r   r3   r  r   r   r   r  z @r  :rW   z ":N(   N"r   u
    — when u    → )r   r   )rY  trigactr,  whats   &    r
   _fmt_routiner    s+   55!!rD
%%/

RC88FC Dxx~~!DJ<  xxchhtF|,,,xx"T']O$$7763D
www!CL>""	#c&k#&'r**eeFm_JtfE$88r   c                    V ^8  d   QhRRRR/# r  r   )r	   s   "r
   r   r     s     $ $ $ $r   c           	       "   \        V P                  R R4      4      P                  4       pV P                  R4      pV P                  R4      pV'       d-   \        V\        4      '       d   \        V\        4      '       g   R# VP                  R4      R9  d   R# VP                  R4      R9  d   R# VR,          R9   d2   \        VP                  R	R4      4      P                  4       '       g   R
# VR,          R9   d2   \        VP                  RR4      4      P                  4       '       g   R# \
        P                  ! \
        P                  R VRVRV/4      G Rj  xL
  \        4       G Rj  xL
  RV R2#  L L5i)r   r   r  r  z<routine_create failed: need a name, a trigger and an action.r   r  z@routine_create failed: trigger.type must be time, place or both.zHroutine_create failed: action.type must be speak_brief, say_text or run.r  z=routine_create failed: a time/both trigger needs a time 'at'.z@routine_create failed: a place/both trigger needs a place label.Nz	Routine 'z' set.timer  bothspeak_briefsay_textrun)r  r  )r  r  )	r   r   r   rD  r1   r|  r}  KIND_ROUTINEr   )rO   r   r  r  s   &   r
   _routine_creater    s=    txx#$**,Dhhy!GXXhFz'400
648P8PM{{6";;Qzz&!CCYv**3w{{47L3M3S3S3U3UNv++CGR8P4Q4W4W4Y4YQ


{77 &i (& 23 3 3 !
"""tfF##	3 #s$   EF E=!F2E?3F?Fc                    V ^8  d   QhRRRR/# r6  r   )r	   s   "r
   r   r     s     < <t < <r   c                   "   \         P                  ! \         P                  4      G R j  xL
 pV'       g   R# RP                  R V 4       4      #  L&5i)NzNo routines set.r   c              3  F   "   T F  pR \        V4      ,           x  K  	  R# 5ir  )r  )r\   rY  s   & r
   r_    _routine_list.<locals>.<genexpr>  r  r  )r|  r  r  r   r  s   & r
   _routine_listr    s?     (()A)ABBE!99;U;;; Cr  c                    V ^8  d   QhRRRR/# r  r   )r	   s   "r
   r   r     s     J J J Jr   c                  "   \        V P                  R R4      4      P                  4       pV'       g   R# \        P                  ! \        P
                  V4      G Rj  xL
 p\        4       G Rj  xL
  V'       d   RV R2# RV R2#  L* L5i)r   r   zroutine_cancel failed: no name.NzCancelled 'r{  zNo routine named ')r   r   r   r|  r  r  r   )rO   r   r  s   &  r
   _routine_cancelr    sy     txx#$**,D0%%k&>&>EEA
 
"""%&[b!I.@b,II 	F"s*   ABB	B0B1B=BBc               $    V ^8  d   QhRRRRRR/# )r   r   r   rO   r1   r   r   )r	   s   "r
   r   r     s!     0 0 04 0C 0r   c                   "   \         P                  V 4      pVf   RV  R2#  T! T;'       g    / 4      G Rj  xL
 #  L  \         d"   pT  R\        T4      R,           2u Rp?# Rp?ii ; i5i)zEDispatch a tool call. Returns a compact string for the model to read.Nzunknown tool ''z	 failed: :N   N)
_EXECUTORSr   r   r   )r   rO   rN   es   &&  r
   executer    sj     		B	zvQ''0

^### 0yQ//0sA    A.
? 
? =? A.? A+
A& A+!A.&A++A.)NNg      4@)Ng      >@)maybemightpossiblyperhapszi thinkznot surearoundroughlyapproximatelyzor soprobablyzcould bezi guess)	signed	confirmed
definitelyzfor sureagreedpaid	committed	finalizedz	locked in)   >   hinookyobyeheyhmmsupyescoolnicenopeokaystopwaityeahhellocheersthanks	goodnight	thank you
good nightgood morningrZ   )rH  r  mobiler  r  )r   r  r  )>__conditional_annotations____doc__
__future__r   r;   r   urllib.parser>   r|  r   r   r    r$   r(   rL   rU   rf   re   rg   r   r   r   r   r   r   r   r   r   r   r   r   r   r  __annotations__r-  r0  r4  r;  r?  rE  r[  re  rj  rn  rw  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  )r  s   @r
   <module>r     s  & #  	  <03;1
.0$
!	0 q)QQ
Aq*a16	aQGQq1fa1k1b*b*b:
5x -&+&&" 	I 
68 "HJ	vyAC	D 
		 	+ 
&(!>@	&(!TV	W 
 
	;	&(!SU	 !/0	1 
	 	"
 G	-@A	 	 
68 "RT	&(!NP	&( ":' *FG		H 	 	E
 	M
 	;	&)!IK 	L
 	  
68 "KM	vx, /PQ	R
 
%&
( 	#	68]4FGH			
 E
 	A 
&(!FH	FHm5JK	fh# &34	5 

 	L	&(M3DEF		
 	

 	 
FH!#HJ 	K	
 2	FHm5RST	
 ;
 	' 
&(!KM	VX"$4!&("(*C"E -/N P&'7VX<N"/ 2J"K !68#0 3B#C
$D  &+ 
FH!#D !')K!M!.0S!U&(FWI"/1P"R#S 	*!	+4 	&?'@ E
 *	&(M3FGH	{a
J aN4	Q
6656
6#:
J
<,SM9$$*<J^' 	 +	
  M 1 M 1 +  < M < ^  ^!" )#$ ooMo+
20r   