+
    6*Qj                        R t ^ RIHt ^ RIt^ RIt^ RItRtRtRt	Rt
]R]R]	R	]
R/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# )!u  Structured phone-power storage on top of the memory service.

Phase-2 "Jarvis runs my phone" needs a handful of small, structured, mutable
collections — saved *places*, a *contacts* phonebook, an *SMS watch-list*, and
*routines* — that must survive restarts. Rather than stand up a new database we
persist each item as a **typed memory record in a DEDICATED group namespace**
(``PHONE_STORE_GROUP``, default ``"<MEMORY_GROUP>_phone"``). That gives three
things for free:

  * durability — the memory service is FalkorDB-backed, so records outlive any
    gateway restart or redeploy;
  * isolation — a separate ``group`` means these machine records never pollute
    Ahmed's semantic long-term memory (``memory_search`` / the persona memory
    index both read the ``ahmed`` group, not ``ahmed_phone``);
  * deterministic CRUD — ``GET /memories?group=&kind=`` lists newest-first and
    filters by an exact ``kind``, ``POST /remember`` creates, ``POST /forget``
    deletes by id. No semantic ranking is ever involved, so listing/removing an
    item is exact, not fuzzy.

One record per item. ``kind`` names the collection; the item's fields live as a
compact JSON object in the record ``text``. Numbers are treated as sensitive —
this module never logs a record's contents.

All HTTP funnels through :func:`_mem_request` so tests monkeypatch exactly one
function and never touch the network. This module deliberately does NOT import
``tools`` (which imports us), keeping its own tiny HTTP helper to avoid a
circular import.
)annotationsNphone_placephone_contactphone_sms_watchphone_routinelabelnamesenderc                   V ^8  d   QhRR/#    returnstr )formats   "phone_store.py__annotate__r   4   s     < <# <    c                 `    \         P                  P                  R R4      P                  R4      # )MEMORY_API_URL /)osenvirongetrstripr   r   r   _mem_urlr   4   s#    ::>>*B/66s;;r   c                   V ^8  d   QhRR/# r   r   )r   s   "r   r   r   8   s     0 0# 0r   c                 B    \         P                  P                  R R4      # )MEMORY_API_KEYr   r   r   r   r   r   r   _mem_keyr!   8   s    ::>>*B//r   c                   V ^8  d   QhRR/# r   r   )r   s   "r   r   r   <   s     3 3S 3r   c                 B    \         P                  P                  R R4      # )MEMORY_GROUPahmedr    r   r   r   _base_groupr&   <   s    ::>>.'22r   c                   V ^8  d   QhRR/# r   r   )r   s   "r   r   r   @   s     M MS Mr   c                 r    \         P                  P                  R4      ;'       g    \        4       R,           # )zThe dedicated namespace for phone-power records (kept OFF the semantic
memory group so these never surface in recall or the persona index).PHONE_STORE_GROUP_phone)r   r   r   r&   r   r   r   phone_groupr+   @   s'     ::>>-.LL;=83KLr   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   G   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?zContent-Typezapplication/jsonzBearer Authorization)r2   )headersjson)httpxr   RuntimeErrorurllibparse	urlencoder!   AsyncClientrequestraise_for_statuscontentr9   loads)r-   r.   r/   r1   r2   r:   baseurlr8   keyclientrespraws   &&&&&        r   _mem_requestrJ   G   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   valuer   r   r   )r   s   "r   r   r   `   s     ( ( ( (r   c                h    T ;'       g    RP                  4       pVR8X  d   V# VP                  4       # )zGCase/space-insensitive key for matching. ``"*"`` is preserved verbatim.r   *)striplower)rL   vs   & r   _normrR   `   s-    	"AS1'aggi'r   c                    V ^8  d   QhRRRR/# )r   kindr   r   z
list[dict]r   )r   s   "r   r   r   g   s      3 : r   c           
       "   \        RRR\        4       RV R^/R7      G Rj  xL
 p. pVP                  R. 4       F  pVP                  R	4      ;'       g    VP                  R
4      ;'       g    Rp \        P                  ! V4      p\        T\        4      '       g   Kf  TP                  R4      TR&   TP                  T4       K  	  V#  L  \
        \        3 d     K  i ; i5i)zEvery item in a collection, newest-first. Each item is the stored JSON
payload plus an ``_id`` (the memory-record id, used for deletes).GETz	/memoriesgrouprT   limit)r/   Nmemoriesfacttextr   id_id)
rJ   r+   r   r9   rC   
ValueError	TypeError
isinstancer4   append)rT   outitemsmrI   payloads   &     r   
list_itemsrf   g   s      UK%,kmVT%,c%34 4C EWWZ$eeFm22quuV}22	jjoG gt$$UU4[GENLL! % L4 I& 		sE    C&C2C&C&/C&3C	C&!,C&C#C&"C##C&c               $    V ^8  d   QhRRRRRR/# )r   rT   r   rF   r   r0   r   )r   s   "r   r   r   z   s!     	 	 	3 	; 	r   c           	        "   \        V4      pV'       g   R# \        V ,          p\        V 4      G Rj  xL
  F0  p\        \        VP	                  VR4      4      4      V8X  g   K.  Vu # 	  R#  L:5i)z=Fetch one item by its key field (label/name/sender), or None.Nr   )rR   
_KEY_FIELDrf   r   r   )rT   rF   wantfielditems   &&   r   get_itemrm   z   s_     :DtE &&&TXXeR()*d2K '  's   1A0A.-A0&	A0c               $    V ^8  d   QhRRRRRR/# )r   rT   r   rF   r   intr   )r   s   "r   r   r      s!      C c c r   c           
     ~  "   \        V4      pV'       g   ^ # \        V ,          p^ p\        V 4      G Rj  xL
  Fy  p\        \        VP	                  VR4      4      4      V8X  g   K.  VP	                  R4      '       g   KG  \        RRRVR,          R\        4       /R7      G Rj  xL
  V^,          pK{  	  V#  L L5i)	u   Delete every item whose key field matches. Returns how many were removed
(0 if none). Idempotent — used both to remove and to make put_item an
upsert.Nr   r]   POSTz/forgetr\   rW   r1   )rR   ri   rf   r   r   rJ   r+   )rT   rF   rj   rk   removedrl   s   &&    r   delete_itemrt      s      :DtEG &&&TXXeR()*d2txxvy%)4;$OQ Q QqLG	 '
 N 'Qs-   3B=B9-B=(B=$B=%B;&B=;B=c               $    V ^8  d   QhRRRRRR/# )r   rT   r   re   r4   r   r   )r   s   "r   r   r      s!       t  r   c                  "   \         V ,          p\        VP                  VR4      4      P                  4       pV'       g   \	        V  RV R24      hVP                  4        UUu/ uF  w  rEVR8w  g   K  WEbK  	  ppp\        W4      G Rj  xL
  \        RRR\        P                  ! VR	R
7      R\        4       RV RRRR/R7      G Rj  xL
  V# u uppi  LJ L5i)zUpsert an item (replace any existing one with the same key), then store
it. Returns the stored payload (without the transient ``_id``).r   z: missing required ''r]   Nrq   z	/rememberr[   F)ensure_asciirW   rT   sourcephone
confidenceg?rr   )ri   r   r   rO   r^   rc   rt   rJ   r9   dumpsr+   )rT   re   rk   rF   krQ   cleans   &&     r   put_itemr      s      tE
gkk%$
%
+
+
-CD6!5eWA>??%mmo<odaeTQToE<
d
   
v{#TZZE%J$kmVT%wcCD D D L = Ds6   A#C%C5C;CC=C
CCCc               $    V ^8  d   QhRRRRRR/# )r   r	   r   watchz	list[str]r   boolr   )r   s   "r   r   r      s!       I $ r   c                    \        V 4      pV'       g   R# V F:  p\        \        V4      4      pVR8X  d    R# V'       g   K+  WB8X  g	   WB9   g   K9   R# 	  R# )zDoes ``sender`` match the watch-list? ``"*"`` matches everything; a stored
entry matches on exact (case-insensitive) equality OR as a substring of the
sender (so ``"SNB"`` catches ``"SNB-AlAhli"``).FrN   T)rR   r   )r	   r   swwns   &&   r   sms_matchesr      sI     	fA3q6]9227bg  r   )NNg      4@)__doc__
__future__r   r9   r   urllib.parser<   
KIND_PLACEKIND_CONTACTKIND_SMS_WATCHKIND_ROUTINEri   r   r!   r&   r+   rJ   rR   rf   rm   rt   r   r   r   r   r   <module>r      s   : #  	  
" &H&	
<03M.2(&	""r   