+
    Ӄi                        R 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	 ^ RI
H
t
Ht ^ RIHt ^ RIHtHtHtHt ]P&                  ! ]4      tR R lt]P.                  ! R4      tR	 R
 ltR R ltR R ltR R lt^RIHtHtH t H!t! ] ! R R4      4       t"] ! R R4      4       t#]$! ]PJ                  ]PL                  ]PN                  04      t( RR/R R llt)] ! R R4      4       t*R"R R llt+ ! R R4      t,R#R  R! llt-R# )$a  
Session management for the gateway.

Handles:
- Session context tracking (where messages come from)
- Session storage (conversations persisted to disk)
- Reset policy evaluation (when to start fresh)
- Dynamic system prompt injection (agent knows its context)
N)Path)datetime	timedelta)	dataclass)DictListOptionalAnyc                $    V ^8  d   QhR\         /#    return)r   )formats   ",/home/ubuntu/hermes-agent/gateway/session.py__annotate__r      s      h     c                 ,    \         P                  ! 4       # )zReturn the current local time.)r   now r   r   _nowr      s    <<>r   z^\+?\d[\d\-\s]{6,}$c                0    V ^8  d   QhR\         R\         /# r   valuer   str)r   s   "r   r   r   &   s     B BC BC Br   c                v    \         P                  ! V P                  R4      4      P                  4       R,          # )z0Deterministic 12-char hex hash of an identifier.utf-8:N   N)hashlibsha256encode	hexdigestr   s   &r   _hash_idr#   &   s(    >>%,,w/0::<SAAr   c                0    V ^8  d   QhR\         R\         /# r   r   )r   s   "r   r   r   +   s     % %3 %3 %r   c                    R\        V 4       2# )z%Hash a sender ID to ``user_<12hex>``.user_)r#   r"   s   &r   _hash_sender_idr'   +   s    8E?#$$r   c                0    V ^8  d   QhR\         R\         /# r   r   )r   s   "r   r   r   0   s     
 
 
 
r   c                    V P                  R4      pV^ 8  d   V RV pV R\        W^,           R 4       2# \        V 4      # )u   Hash the numeric portion of a chat ID, preserving platform prefix.

``telegram:12345`` → ``telegram:<hash>``
``12345``          → ``<hash>``
:N)findr#   )r   colonprefixs   &  r   _hash_chat_idr.   0   sM     JJsOEqyv8E!)*$56788E?r   c                0    V ^8  d   QhR\         R\        /# r   )r   bool)r   s   "r   r   r   =   s     0 0S 0T 0r   c                Z    \        \        P                  V P                  4       4      4      # )zDReturn True if *value* looks like a phone number (E.164 or similar).)r0   	_PHONE_REmatchstripr"   s   &r   _looks_like_phoner5   =   s    	.//r   )PlatformGatewayConfigSessionResetPolicyHomeChannelc                      a  ] tR t^It o RtRtRtRtRtRt	Rt
RtRt]V 3R lR l4       tV 3R lR lt]V 3R lR	 l4       t]V 3R
 lR l4       tV 3R ltRtV tR# )SessionSourcez
Describes where a message originated from.

This information is used to:
1. Route responses back to the right place
2. Inject context into the system prompt
3. Track origin for cron job delivery
Ndmc                    < V ^8  d   QhRS[ /# r   r   )r   __classdict__s   "r   r   SessionSource.__annotate___   s        S  r   c                   V P                   \        P                  8X  d   R# . pV P                  R8X  d>   TP	                  RV P
                  ;'       g    V P                  ;'       g    R 24       MV P                  R8X  d4   TP	                  RV P                  ;'       g    V P                   24       MsV P                  R8X  d4   TP	                  RV P                  ;'       g    V P                   24       M/TP	                  V P                  ;'       g    V P                  4       V P                  '       d   VP	                  R	V P                   24       R
P                  V4      # )z)Human-readable description of the source.CLI terminalr<   DM with usergroupgroup: channel	channel: zthread: , )platformr6   LOCAL	chat_typeappend	user_nameuser_id	chat_namechat_id	thread_idjoin)selfpartss   & r   descriptionSessionSource.description^   s     ==HNN*!>>T!LL8DNN$L$Ldll$L$Lf#MNO^^w&LL74>>#A#AT\\"BCD^^y(LL9T^^%C%Ct||$DEFLL774<<8>>>LL8DNN#345yyr   c                6   < V ^8  d   QhRS[ S[S[3,          /# r   r   r   r	   )r   r>   s   "r   r   r?   s   s      c3h r   c                b   R V P                   P                  RV P                  RV P                  RV P                  RV P
                  RV P                  RV P                  RV P                  /pV P                  '       d   V P                  VR&   V P                  '       d   V P                  VR	&   V# 
rI   rP   rO   rK   rN   rM   rQ   
chat_topicuser_id_altchat_id_alt)rI   r   rP   rO   rK   rN   rM   rQ   r[   r\   r]   )rS   ds   & r   to_dictSessionSource.to_dicts   s    ++t||t||$//	
 #//Am#//Amr   c                :   < V ^8  d   QhRS[ S[S[3,          RR/# )r   datar   r;   rX   )r   r>   s   "r   r   r?      s#     
 
T#s(^ 
 
r   c                X   V ! \        VR ,          4      \        VR,          4      VP                  R4      VP                  RR4      VP                  R4      VP                  R4      VP                  R4      VP                  R4      VP                  R	4      VP                  R
4      R7
      # )rI   rP   rO   rK   r<   rN   rM   rQ   r[   r\   r]   rZ   )r6   r   get)clsrb   s   &&r   	from_dictSessionSource.from_dict   s    d:./Y(hh{+hh{D1HHY'hh{+hh{+xx-//
 	
r   c                   < V ^8  d   QhRR/# )r   r   r;   r   )r   r>   s   "r   r   r?      s     
 
/ 
r   c                6    V ! \         P                  RRRR7      # )z+Create a source representing the local CLI.clirA   r<   )rI   rP   rO   rK   )r6   rJ   )re   s   &r   	local_cliSessionSource.local_cli   s!     ^^$	
 	
r   c                  < V ^8  d   Qh/ S[ ;R&   S[;R&   S[S[,          ;R&   S[;R&   S[S[,          ;R&   S[S[,          ;R&   S[S[,          ;R&   S[S[,          ;R&   S[S[,          ;R	&   S[S[,          ;R
&   # )r   rI   rP   rO   rK   rN   rM   rQ   r[   r\   r]   )r6   r   r   )r   r>   s   "r   r   r?   I   s        L  }#    c]!  }#   }#! " $# $ #%% & #%' r   r   )__name__
__module____qualname____firstlineno____doc__rO   rK   rN   rM   rQ   r[   r\   r]   propertyrU   r_   classmethodrf   rk   __annotate_func____static_attributes____classdictcell__r>   s   @r   r;   r;   I   s       $II!G#I#I $J!%K!%K   ( " 
 
 
 
W  r   r;   c                   R   a  ] tR t^t o RtRtRtRtRtV 3R lR lt	V 3R lt
RtV tR# )SessionContextz
Full context for a session, used for dynamic system prompt injection.

The agent receives this information to understand:
- Where messages are coming from
- What platforms are available
- Where it can deliver scheduled task outputs
 Nc                6   < V ^8  d   QhRS[ S[S[3,          /# r   rX   )r   r>   s   "r   r   SessionContext.__annotate__   s     
 
c3h 
r   c                   R V P                   P                  4       RV P                   Uu. uF  qP                  NK  	  upRV P                  P                  4        UUu/ uF   w  rVP                  VP                  4       bK"  	  uppRV P                  RV P                  RV P                  '       d   V P                  P                  4       MRRV P                  '       d   V P                  P                  4       /# R/# u upi u uppi )sourceconnected_platformshome_channelssession_key
session_id
created_atN
updated_at)r   r_   r   r   r   itemsr   r   r   	isoformatr   )rS   phcs   &  r   r_   SessionContext.to_dict   s    dkk))+!T5M5M#N5MGG5M#N373E3E3K3K3M3M%!%3M 4++$//$//335d$//335

 
	
 OS

 
	
#Ns   C>
&Dc                   < V ^8  d   Qh/ S[ ;R&   S[S[,          ;R&   S[S[S[3,          ;R&   S[;R&   S[;R&   S[S[,          ;R&   S[S[,          ;R&   # )r   r   r   r   r   r   r   r   )r;   r   r6   r   r9   r   r   r   )r   r>   s   "r   r   r}      sx        h'  +-..     ! " ")# $ ")% r   r   )rn   ro   rp   rq   rr   r   r   r   r   r_   ru   rv   rw   rx   s   @r   rz   rz      s1      KJ%)J%)J
 
)  r   rz   
redact_piiFc                <    V ^8  d   QhR\         R\        R\        /# )r   contextr   r   )rz   r0   r   )r   s   "r   r   r      s-     J JJ J 		Jr   c          
        T;'       d    V P                   P                  \        9   pRR.pV P                   P                  P                  P	                  4       pV P                   P                  \
        P                  8X  d   VP                  RV R24       MV P                   pV'       d   VP                  ;'       g*    VP                  '       d   \        VP                  4      MRpVP                  ;'       g    \        VP                  4      pVP                  R8X  d   RV 2pM=VP                  R8X  d   R	V 2pM&VP                  R
8X  d   RV 2pMTpMVP                  pVP                  RV RV R24       V P                   P                   '       d)   VP                  RV P                   P                    24       V P                   P                  R8g  ;'       d    V P                   P"                  pV'       d   VP                  R4       MV P                   P                  '       d*   VP                  RV P                   P                   24       MYV P                   P                  '       d>   V P                   P                  p	V'       d   \        V	4      p	VP                  RV	 24       V P                   P                  \
        P$                  8X  d$   VP                  R4       VP                  R4       MKV P                   P                  \
        P&                  8X  d#   VP                  R4       VP                  R4       R.p
V P(                   F8  pV\
        P                  8w  g   K  V
P                  VP                   R24       K:  	  VP                  RRP+                  V
4       24       V P,                  '       d   VP                  R4       VP                  R4       V P,                  P/                  4        F]  w  rV'       d   \        VP                  4      MVP                  pVP                  RVP                   RVP0                   RV R24       K_  	  VP                  R4       VP                  R4       V P                   P                  \
        P                  8X  d   VP                  R4       MpV P                   P                  ;'       g>    V'       d    \        V P                   P                  4      MV P                   P                  pVP                  RV R24       VP                  R4       V P,                  P/                  4        F1  w  rVP                  R VP                   R!VP0                   R24       K3  	  VP                  R4       VP                  R"4       R#P+                  V4      # )$aM  
Build the dynamic system prompt section that tells the agent about its context.

This is injected into the system prompt so the agent knows:
- Where messages are coming from
- What platforms are connected
- Where it can deliver scheduled task outputs

When *redact_pii* is True **and** the source platform is in
``_PII_SAFE_PLATFORMS``, phone numbers are stripped and user/chat IDs
are replaced with deterministic hashes before being sent to the LLM.
Platforms like Discord are excluded because mentions need real IDs.
Routing still uses the original values (they stay in SessionSource).
z## Current Session Contextr{   z**Source:** z! (the machine running this agent)rC   r<   rB   rD   rE   rF   rG   z ()z**Channel Topic:** uq   **Session type:** Multi-user thread — messages are prefixed with [sender name]. Multiple users may participate.z
**User:** z**User ID:** u?  **Platform notes:** You are running inside Slack. You do NOT have access to Slack-specific APIs — you cannot search channel history, pin/unpin messages, manage channels, or list users. Do not promise to perform these actions. If the user asks, explain that you can only read messages sent directly to you and respond.uC  **Platform notes:** You are running inside Discord. You do NOT have access to Discord-specific APIs — you cannot search channel history, pin messages, manage roles, or list server members. Do not promise to perform these actions. If the user asks, explain that you can only read messages sent directly to you and respond.zlocal (files on this machine)u   : Connected ✓z**Connected Platforms:** rH   z)**Home Channels (default destinations):**z  - z: z (ID: z)**Delivery options for scheduled tasks:**u.   - `"origin"` → Local output (saved to files)u$   - `"origin"` → Back to this chat (uA   - `"local"` → Save to local files only (~/.hermes/cron/output/)z- `"u   "` → Home channel (zb*For explicit targeting, use `"platform:chat_id"` format if the user provides a specific chat ID.*
)r   rI   _PII_SAFE_PLATFORMSr   titler6   rJ   rL   rM   rN   r'   rO   r.   rP   rK   rU   r[   rQ   SLACKDISCORDr   rR   r   r   name)r   r   linesplatform_namesrc_uname_cnamedesc_is_shared_threaduidplatforms_listr   rI   homehc_id_origin_labels   &$              r   build_session_context_promptr      s   ( NN 7 7;N NJ$
E NN++11779M~~(..0|M?2STU nn]]  03,  ]]@@mCKK&@F}}$!&*') ))+"6(+??D|M?"TF!<= ~~   *7>>+D+D*EFG 	  D( 	% 	%NN$$  B	
 
	!	!	!z'..":":!;<=				nn$$!#&C}SE*+ ~~(..0RP	
 
	 	 H$4$4	4RP	
 66N((!!QWWI_"=> ) 
LL,TYY~-F,GHI R@A%3399;NH3=M$,,/4<<ELL4/r$))F5'KL <
 
LL	LL<= ~~(..0GH00 
 
5?M'..001W^^E[E[ 	 	=m_ANO 
LLVW "//557uX^^,,B499+QOP 8 
LL	LLwx99Ur   c                      a  ] tR tRt o RtRtRtRtRt^ t	^ t
^ t^ t^ tRtRt^ tRtRtRtRtV 3R lR	 lt]V 3R
 lR l4       tV 3R ltRtV tR# )SessionEntryiW  zY
Entry in the session store.

Maps a session key to its current session ID and metadata.
Nr<           unknownFc                6   < V ^8  d   QhRS[ S[S[3,          /# r   rX   )r   r>   s   "r   r   SessionEntry.__annotate__  s      c3h r   c                |   / R V P                   bRV P                  bRV P                  P                  4       bRV P                  P                  4       bRV P
                  bRV P                  '       d   V P                  P                  MRbRV P                  bRV P                  bR	V P                  bR
V P                  bRV P                  bRV P                  bRV P                  bRV P                  bRV P                   bRV P"                  bpV P$                  '       d   V P$                  P'                  4       VR&   V# )r   r   r   r   display_namerI   NrK   input_tokensoutput_tokenscache_read_tokenscache_write_tokenstotal_tokenslast_prompt_tokensestimated_cost_usdcost_statusmemory_flushedorigin)r   r   r   r   r   r   rI   r   rK   r   r   r   r   r   r   r   r   r   r   r_   )rS   results   & r   r_   SessionEntry.to_dict  sd   
4++
$//
 $//335
 $//335	

 D--
 t}}}++$
 
 D--
 T//
  !7!7
 !$"9"9
 D--
 !$"9"9
 !$"9"9
 4++
  d11!
$ ;;;#{{224F8r   c                :   < V ^8  d   QhRS[ S[S[3,          RR/# )r   rb   r   r   rX   )r   r>   s   "r   r   r     s#     
 
T#s(^ 
 
r   c           	        R pRV9   d,   VR,          '       d   \         P                  VR,          4      pR pVP                  R4      '       d    \        VR,          4      pV ! R/ RVR,          bRVR,          bR\        P                  ! VR,          4      bR\        P                  ! VR,          4      bRVbRVP                  R4      bRVbR	VP                  R	R
4      bRVP                  R^ 4      bRVP                  R^ 4      bRVP                  R^ 4      bRVP                  R^ 4      bRVP                  R^ 4      bRVP                  R^ 4      bRVP                  RR4      bRVP                  RR4      bRVP                  RR4      b #   \         d*   p\
        P                  RTR,          T4        R p?EL[R p?ii ; i)Nr   rI   zUnknown platform value %r: %sr   r   r   r   r   rK   r<   r   r   r   r   r   r   r   r   r   r   r   Fr   )	r;   rf   rd   r6   
ValueErrorloggerdebugr   fromisoformat)re   rb   r   rI   es   &&   r   rf   SessionEntry.from_dict  s   tX",,T(^<F88JS#D$45  
]+
L)
  --d<.@A
  --d<.@A	

 
 .1
 
 hh{D1
 .!4
 ((?A6
 #hh':A>
  $xx(<a@
 .!4
  $xx(<a@
  $xx(<cB
  	:!
"  88$4e<#
 	
  S<d:>NPQRRSs   F GF<<Gc                J  < V ^8  d   Qh/ S[ ;R&   S[ ;R&   S[;R&   S[;R&   S[S[,          ;R&   S[S[ ,          ;R&   S[S[,          ;R&   S[ ;R&   S[;R	&   S[;R
&   S[;R&   S[;R&   S[;R&   S[;R&   S[ ;R&   S[;R&   S[;R&   S[S[ ,          ;R&   S[;R&   S[;R&   # )r   r   r   r   r   r   r   rI   rK   r   r   r   r   r   r   r   r   was_auto_resetauto_reset_reasonreset_had_activityr   )r   r   r   r;   r6   intfloatr0   )r   r>   s   "r   r   r   W  s"       O      ]#*   3-&! " x '# $ % * + , - . / 0 1 2 3 4 #5 6  7 < = D  E F  }+G H $I T  U r   r   )rn   ro   rp   rq   rr   r   r   rI   rK   r   r   r   r   r   r   r   r   r   r   r   r   r_   rt   rf   ru   rv   rw   rx   s   @r   r   r   W  s      '+F #'L#'HI LML # K   !N'+$ !N . 
 
I  r   r   c                H    V ^8  d   QhR\         R\        R\        R\        /# )r   r   group_sessions_per_userthread_sessions_per_userr   )r;   r0   r   )r   s   "r   r   r     s0     8 88!8 #8 		8r   c                   V P                   P                  pV P                  R8X  d   V P                  '       dD   V P                  '       d    RV RV P                   RV P                   2# RV RV P                   2# V P                  '       d   RV RV P                   2# RV R2# V P
                  ;'       g    V P                  pRW0P                  .pV P                  '       d   VP                  V P                  4       V P                  '       d   VP                  V P                  4       TpV P                  '       d   V'       g   RpV'       d#   V'       d   VP                  \        V4      4       RP                  V4      # )u  Build a deterministic session key from a message source.

This is the single source of truth for session key construction.

DM rules:
  - DMs include chat_id when present, so each private conversation is isolated.
  - thread_id further differentiates threaded DMs within the same DM chat.
  - Without chat_id, thread_id is used as a best-effort fallback.
  - Without thread_id or chat_id, DMs share a single session.

Group/channel rules:
  - chat_id identifies the parent group/channel.
  - user_id/user_id_alt isolates participants within that parent chat when available when
    ``group_sessions_per_user`` is enabled.
  - thread_id differentiates threads within that parent chat.  When
    ``thread_sessions_per_user`` is False (default), threads are *shared* across all
    participants — user_id is NOT appended, so every user in the thread
    shares a single session.  This is the expected UX for threaded
    conversations (Telegram forum topics, Discord threads, Slack threads).
  - Without participant identifiers, or when isolation is disabled, messages fall back to one
    shared session per chat.
  - Without identifiers, messages fall back to one session per platform/chat_type.
r<   zagent:main:z:dm:r*   z:dmz
agent:mainF)
rI   r   rK   rP   rQ   r\   rN   rL   r   rR   )r   r   r   rI   participant_id	key_partsisolate_users   &&&    r   build_session_keyr     sL   8 $$H4>>>$XJd6>>2B!FDTDTCUVV 
$v~~.>?? 
$v/?/?.@AAXJc**''996>>Nx)9)9:I~~~())*
 +L 8^,-88Ir   c                   j  a  ] tR tRt o RtR'V 3R lR lltV 3R lR ltV 3R lR	 ltV 3R
 lR ltV 3R lR lt	V 3R lR lt
V 3R lR ltV 3R lR ltR(V 3R lR lltR)V 3R lR lltV 3R lR ltV 3R lR ltR)V 3R lR lltV 3R lR ltR(V 3R  lR! lltV 3R" lR# ltV 3R$ lR% ltR&tV tR# )*SessionStorei  z
Manages session storage and retrieval.

Uses SQLite (via SessionDB) for session metadata and message transcripts.
Falls back to legacy JSONL files if SQLite is unavailable.
Nc                &   < V ^8  d   QhRS[ RS[/# )r   sessions_dirconfig)r   r7   )r   r>   s   "r   r   SessionStore.__annotate__  s     f fT f= fr   c                    Wn         W n        / V n        R V n        \        P
                  ! 4       V n        W0n        RV n         ^ RI	H
p V! 4       V n        R#   \         d   p\        RT 24        Rp?R# Rp?ii ; i)FN)	SessionDBzL[gateway] Warning: SQLite session store unavailable, falling back to JSONL: )r   r   _entries_loaded	threadingLock_lock_has_active_processes_fn_dbhermes_stater   	Exceptionprint)rS   r   r   has_active_processes_fnon_auto_resetr   r   s   &&&&&  r   __init__SessionStore.__init__  ss     )13^^%
(?% 	f. {DH 	f`ab`cdee	fs   A A;"A66A;c                   < V ^8  d   QhRR/# r   r   Nr   )r   r>   s   "r   r   r     s     ) ) )r   c                    V P                   ;_uu_ 4        V P                  4        RRR4       R#   + '       g   i     R# ; i)z4Load sessions index from disk if not already loaded.N)r   _ensure_loaded_lockedrS   s   &r   _ensure_loadedSessionStore._ensure_loaded  s#    ZZZ&&( ZZZs	   0A	c                   < V ^8  d   QhRR/# r   r   )r   r>   s   "r   r   r     s      t r   c                N   V P                   '       d   R# V P                  P                  RRR7       V P                  R,          pVP                  4       '       ds    \	        VRRR7      ;_uu_ 4       p\
        P                  ! V4      pVP                  4        F(  w  rE \        P                  V4      V P                  V&   K*  	  RRR4       RV n         R#   \        \        3 d     KQ  i ; i  + '       g   i     L1; i  \         d   p\        RT 24        Rp?LRRp?ii ; i)	zCLoad sessions index from disk. Must be called with self._lock held.NTparentsexist_oksessions.jsonrr   encodingz,[gateway] Warning: Failed to load sessions: )r   r   mkdirexistsopenjsonloadr   r   rf   r   r   KeyErrorr   r   )rS   sessions_filefrb   key
entry_datar   s   &      r   r   "SessionStore._ensure_loaded_locked  s    <<<t<))O;!!
J-w??199Q<D+/::<%1=1G1G
1SDMM#. ,8 @  !+H5 %$% @?  JDQCHIIJsZ   D 2,C."CC.D C+	'C.*C+	+C..C>	9D >D D$DD$c                   < V ^8  d   QhRR/# r   r   )r   r>   s   "r   r   r   -  s      t r   c                ,   ^ RI pV P                  P                  RRR7       V P                  R,          pV P                  P	                  4        UUu/ uF  w  r4W4P                  4       bK  	  pppVP                  \        V P                  4      RRR7      w  rg \        P                  ! VRR	R
7      ;_uu_ 4       p\        P                  ! WX^R7       VP                  4        \        P                  ! VP                  4       4       RRR4       \        P                  ! Wr4       R# u uppi   + '       g   i     L.; i  \          dH     \        P"                  ! T4       h   \$         d!   p	\&        P)                  RYy4        Rp	?	h Rp	?	ii ; ii ; i)zASave sessions index to disk (kept for session key -> ID mapping).NTr   r   z.tmpz
.sessions_)dirsuffixr-   wr   r   )indentz!Could not remove temp file %s: %s)tempfiler   r   r   r   r_   mkstempr   osfdopenr   dumpflushfsyncfilenoreplaceBaseExceptionunlinkOSErrorr   r   )
rS   r  r   r   entryrb   fdtmp_pathr   r   s
   &         r   _saveSessionStore._save-  s:   t<))O;7;}}7J7J7LM7L]]_$7LM''D%%&vl ( 
	2sW55		$!,	$ 6 JJx/ N
 65
  	O		(#   O@(NNO	sU   D(!E ;AD.E .D>	9E FE$#F$F/F
F
FFc                &   < V ^8  d   QhRS[ RS[/# )r   r   r   )r;   r   )r   r>   s   "r   r   r   D  s     
 
M 
c 
r   c           	     r    \        V\        V P                  RR4      \        V P                  RR4      R7      # )z%Generate a session key from a source.r   Tr   F)r   r   )r   getattrr   )rS   r   s   &&r   _generate_session_key"SessionStore._generate_session_keyD  s6     $+DKK9RTX$Y%,T[[:TV[%\
 	
r   c                &   < V ^8  d   QhRS[ RS[/# )r   r  r   )r   r0   )r   r>   s   "r   r   r   L  s     $ $ $$ $r   c                n   V P                   '       d$   V P                  VP                  4      '       d   R# V P                  P                  VP                  VP
                  R7      pVP                  R8X  d   R# \        4       pVP                  R9   d0   VP                  \        VP                  R7      ,           pW48  d   R# VP                  R	9   da   VP                  VP                  ^ ^ ^ R7      pVP                  VP                  8  d   V\        ^R7      ,          pVP                  V8  d   R# R# )
u   Check if a session has expired based on its reset policy.

Works from the entry alone — no SessionSource needed.
Used by the background expiry watcher to proactively flush memories.
Sessions with active background processes are never considered expired.
FrI   session_typenoneminutesThourminutesecondmicroseconddaysidlebothdailyr+  )r   r   r   get_reset_policyrI   rK   moder   r   r   idle_minutesr  at_hourr#  )rS   r  policyr   idle_deadlinetoday_resets   &&    r   _is_session_expired SessionStore._is_session_expiredL  s     (((,,U->->??--^^ . 

 ;;& f;;**!,,yATAT/UUM";;++++^^ & K xx&..(ya00+-r   c                <   < V ^8  d   QhRS[ RS[RS[S[,          /# )r   r  r   r   )r   r;   r   r   )r   r>   s   "r   r   r   r  s'     * *< * *8TW= *r   c                |   V P                   '       d+   V P                  V4      pV P                  V4      '       d   R# V P                  P                  VP                  VP
                  R7      pVP                  R8X  d   R# \        4       pVP                  R	9   d0   VP                  \        VP                  R7      ,           pWV8  d   R# VP                  R
9   da   VP                  VP                  ^ ^ ^ R7      pVP                  VP                  8  d   V\        ^R7      ,          pVP                  V8  d   R# R# )z
Check if a session should be reset based on policy.

Returns the reset reason ("idle" or "daily") if a reset is needed,
or None if the session is still valid.

Sessions with active background processes are never reset.
Nr  r  r*  r   r-  r"  r'  r)  r,  )r   r  r   r.  rI   rK   r/  r   r   r   r0  r  r1  r#  )rS   r  r   r   r2  r   r3  r4  s   &&&     r   _should_resetSessionStore._should_resetr  s    (((44V<K,,[99--__)) . 

 ;;& f;;**!,,yATAT/UUM";;++++^^	 & K xx&..(ya00+-r   c                    < V ^8  d   QhRS[ /# r   )r0   )r   r>   s   "r   r   r     s     * *$ *r   c                :   V P                   '       d    V P                   P                  4       ^8  # V P                  ;_uu_ 4        V P	                  4        \        V P                  4      ^8  uuRRR4       #   \         d     LTi ; i  + '       g   i     R# ; i)u  Check if any sessions have ever been created (across all platforms).

Uses the SQLite database as the source of truth because it preserves
historical session records (ended sessions still count).  The in-memory
``_entries`` dict replaces entries on reset, so ``len(_entries)`` would
stay at 1 for single-platform users — which is the bug this fixes.

The current session is already in the DB by the time this is called
(get_or_create_session runs first), so we check ``> 1``.
N)r   session_countr   r   r   lenr   r   s   &r   has_any_sessionsSessionStore.has_any_sessions  sw     888xx--/!33
 ZZZ&&(t}}%) Z	   ZZs   A8 (B	8BB	B	c                ,   < V ^8  d   QhRS[ RS[RS[/# )r   r   	force_newr   )r;   r0   r   )r   r>   s   "r   r   r     s.     s ss s 
	sr   c                   V P                  V4      p\        4       pRpRpV P                  ;_uu_ 4        V P                  4        W0P                  9   dw   V'       go   V P                  V,          pV P                  Wq4      pV'       g#   WGn        V P                  4        VuuRRR4       # Rp	Tp
VP                  ^ 8  pVP                  pMRp	Rp
RpVP                  R4       R\        P                  ! 4       P                  R,           2p\        VVVVVVP                  VP                   VP"                  V	V
VR7      pWpP                  V&   V P                  4        RVR	VP                   P$                  R
VP&                  /pRRR4       V P(                  '       d&   V'       d    V P(                  P+                  VR4       V P(                  '       d&   V'       d    V P(                  P2                  ! R/ VB  VP"                  R8X  EdI   VP6                  '       Ed6   XP8                  VP                  8X  Ed   X	'       Eg   \;        VP                   VP<                  RVP&                  R7      pV P                  V4      pV P                  ;_uu_ 4        V P                  P?                  V4      pRRR4       X'       d   VP                  VP                  8w  dx    V PA                  VP                  4      pV'       dR   V PC                  VP                  V4       \.        PE                  RVP                  \G        V4      VP                  4       V# X#   + '       g   i     EL; i  \,         d"   p\.        P1                  RT4        Rp?ELRp?ii ; i  \,         d   p\5        RT 24        Rp?ELRp?ii ; i  + '       g   i     EL; i  \,         d"   p\.        PI                  RT4        Rp?T# Rp?ii ; i)z
Get an existing session or create a new one.

Evaluates reset policy to determine if the existing session is stale.
Creates a session record in SQLite when a new session starts.
NTF%Y%m%d_%H%M%S_N   N)r   r   r   r   r   r   rI   rK   r   r   r   r   r   rN   session_resetSession DB operation failed: %sz4[gateway] Warning: Failed to create SQLite session: r<   )rI   rP   rK   rN   zE[Session] Seeded DM thread session %s with %d messages from parent %sz+[Session] Failed to seed thread session: %sr   )%r  r   r   r   r   r9  r   r  r   r   strftimeuuiduuid4hexr   rO   rI   rK   r   rN   r   end_sessionr   r   r   create_sessionr   rQ   r   r;   rP   rd   load_transcriptrewrite_transcriptinfor>  warning)rS   r   rB  r   r   db_end_session_iddb_create_kwargsr  reset_reasonr   r   r   r   r   parent_source
parent_keyparent_entryparent_historys   &&&               r   get_or_create_session"SessionStore.get_or_create_session  sR    008f !ZZZ&&(mm+Ik2#11%@#'*$JJL  Z &*N(4%).););a)?&(-(8(8%!&$(!%*"  LL9:!DJJL<L<LR<P;QRJ '%#-- **-"3#5E */MM+&JJLj&////6>> U b 888)C$$%6H 888(R'';*:; $     E$4$44"N)	M 33MBJ#}}00<  7 75;K;K K	U%)%9%9,:Q:Q%RN%//0@0@.Qc!,,c..A<CZCZ uA ZZh  C>BBC  RLQCPQQR0  ! UNN#PRSTTUst   (MAM+CM&M) N N?"O  AO M&	)N4NNN<#N77N<?O	O?O::O?c                *   < V ^8  d   QhRS[ RS[RR/# )r   r   r   r   N)r   r   )r   r>   s   "r   r   r   )  s)         
	r   c                    V P                   ;_uu_ 4        V P                  4        WP                  9   d=   V P                  V,          p\        4       Vn        Ve   W#n        V P                  4        RRR4       R#   + '       g   i     R# ; i)z9Update lightweight session metadata after an interaction.N)r   r   r   r   r   r   r  )rS   r   r   r  s   &&& r   update_sessionSessionStore.update_session)  s\     ZZZ&&(mm+k2#'6 %1/A,

 ZZZs   AA<<B	c                6   < V ^8  d   QhRS[ RS[S[,          /# )r   r   r   r   r   r   )r   r>   s   "r   r   r   9  s      1 1 1,1G 1r   c                   RpRpRpV P                   ;_uu_ 4        V P                  4        WP                  9  d    RRR4       R# V P                  V,          pVP                  p\	        4       pVP                  R4       R\        P                  ! 4       P                  R,           2p\        VVVVVP                  VP                  VP                  VP                  R7      pW@P                  V&   V P                  4        RTRVP                  '       d   VP                  P                  MRR	VP                  '       d   VP                  P                   MR/pRRR4       V P"                  '       d&   V'       d    V P"                  P%                  VR
4       V P"                  '       d(   V'       d     V P"                  P,                  ! R/ VB  V# V#   + '       g   i     L; i  \&         d!   p\(        P+                  RT4        Rp?LtRp?ii ; i  \&         d"   p\(        P+                  RT4        Rp?T# Rp?ii ; i)z1Force reset a session, creating a new session ID.NrD  rE  rF  r   r   r   r   r   r   rI   rK   r   r   r   rN   rH  rI  r   )r   r   r   r   r   rJ  rK  rL  rM  r   r   r   rI   rK   r  r   rN   r   rN  r   r   r   rO  )	rS   r   rT  rU  	new_entry	old_entryr   r   r   s	   &&       r   reset_sessionSessionStore.reset_session9  s    	ZZZ&&(--/	 Z k2I ) 4 4&CLL9:!DJJL<L<LR<P;QRJ$'% ''&33"++#--	I *3MM+&JJLji6H6H6H),,22iy7G7G7G9++33T 3 > 888)C$$%6H 888(C'';*:; yW ZD  C>BBC  C>BBCsH   "GC:GG>G( 5H G%	(H3HHI!H==Ic                <   < V ^8  d   QhRS[ RS[ RS[S[,          /# )r   r   target_session_idr   rb  )r   r>   s   "r   r   r   l  s(     . .# .# .(S_J` .r   c                   RpRpV P                   ;_uu_ 4        V P                  4        WP                  9  d    RRR4       R# V P                  V,          pVP                  V8X  d   VuuRRR4       # VP                  p\	        4       p\        VVVVVP                  VP                  VP                  VP                  R7      pW@P                  V&   V P                  4        RRR4       V P                  '       d(   V'       d     V P                  P                  VR4       V# V#   + '       g   i     LK; i  \         d"   p\        P                  RT4        Rp?T# Rp?ii ; i)a'  Switch a session key to point at an existing session ID.

Used by ``/resume`` to restore a previously-named session.
Ends the current session in SQLite (like reset), but instead of
generating a fresh session ID, re-uses ``target_session_id`` so the
old transcript is loaded on the next message.
Nrd  session_switchz!Session DB end_session failed: %s)r   r   r   r   r   r   r   r   rI   rK   r  r   rN  r   r   r   )rS   r   rj  rT  re  rf  r   r   s   &&&     r   switch_sessionSessionStore.switch_sessionl  s:    !	ZZZ&&(--/	 Z k2I ##'88  Z !* 4 4&C$', ''&33"++#--	I *3MM+&JJL7 : 888)E$$%68HI yG Z@  E@!DDEs0   "D'%D'5A/D'D: 'D7	:E&E!!E&c                F   < V ^8  d   QhRS[ S[,          RS[S[,          /# )r   active_minutesr   )r   r   r   r   )r   r>   s   "r   r   r     s$      HSM T,EW r   c                   V P                   ;_uu_ 4        V P                  4        \        V P                  P	                  4       4      pRRR4       Ve?   \        4       \        VR7      ,
          pX Uu. uF  qDP                  V8  g   K  VNK  	  ppXP                  R RR7       V#   + '       g   i     Lh; iu upi )z3List all sessions, optionally filtered by activity.Nr   c                     V P                   # N)r   )r   s   &r   <lambda>,SessionStore.list_sessions.<locals>.<lambda>  s    1<<r   T)r   reverse)	r   r   listr   valuesr   r   r   sort)rS   rp  entriescutoffr   s   &&   r   list_sessionsSessionStore.list_sessions  s    ZZZ&&(4==//12G  %Vi??F")D'Q\\V-Cqq'GD/> Z Es   4B)4B<B<)B9	c                &   < V ^8  d   QhRS[ RS[/# r   r   r   )r   r   )r   r>   s   "r   r   r     s     9 9c 9d 9r   c                .    V P                   V R2,          # )z3Get the path to a session's legacy transcript file.z.jsonl)r   )rS   r   s   &&r   get_transcript_path SessionStore.get_transcript_path  s      j\#888r   c                F   < V ^8  d   QhRS[ RS[S[ S[3,          RS[RR/# )r   r   messageskip_dbr   N)r   r   r	   r0   )r   r>   s   "r   r   r     s9     D Ds DT#s(^ DVZ Dgk Dr   c           
     V   V P                   '       dw   V'       go    V P                   P                  VVP                  RR4      VP                  R4      VP                  R4      VP                  R4      VP                  R4      R7       V P                  V4      p\        VR
RR7      ;_uu_ 4       pVP                  \        P                  ! VRR7      R,           4       R	R	R	4       R	#   \         d!   p\        P                  RT4        R	p?LR	p?ii ; i  + '       g   i     R	# ; i)aJ  Append a message to a session's transcript (SQLite + legacy JSONL).

Args:
    skip_db: When True, only write to JSONL and skip the SQLite write.
             Used when the agent already persisted messages to SQLite
             via its own _flush_messages_to_session_db(), preventing
             the duplicate-write bug (#860).
roler   content	tool_name
tool_callstool_call_id)r   r  r  r  r  r  rI  Nar   r   Fensure_asciir   )r   append_messagerd   r   r   r   r  r   writer   dumps)rS   r   r  r  r   transcript_pathr   s   &&&&   r   append_to_transcript!SessionStore.append_to_transcript  s     888G
C'') VY7#KK	2%kk+6&{{<8!(^!< (  22:>/399QGGDJJwU;dBC :9  C>BBC
 :99s$   A-C) 0/D)D4DDD(	c                P   < V ^8  d   QhRS[ RS[S[S[ S[3,          ,          RR/# )r   r   messagesr   Nr   r   r   r	   )r   r>   s   "r   r   r     s5     D DS DDc3h<P DUY Dr   c                :   V P                   '       d    V P                   P                  V4       V F  pVP                  RR4      pV P                   P                  TTVP                  R4      VP                  R4      VP                  R4      VP                  R4      VR8X  d   VP                  R4      MR	VR8X  d   VP                  R
4      MR	VR8X  d   VP                  R4      MR	R7	       K  	  V P                  V4      p\        VRRR7      ;_uu_ 4       pV F1  pVP                  \        P                  ! VRR7      R,           4       K3  	  R	R	R	4       R	#   \         d!   p\
        P                  RT4        R	p?LR	p?ii ; i  + '       g   i     R	# ; i)zReplace the entire transcript for a session with new messages.

Used by /retry, /undo, and /compress to persist modified conversation history.
Rewrites both SQLite and legacy JSONL storage.
r  r   r  r  r  r  	assistant	reasoningNreasoning_detailscodex_reasoning_items)	r   r  r  r  r  r  r  r  r  z&Failed to rewrite transcript in DB: %sr  r   r   Fr  r   )r   clear_messagesrd   r  r   r   r   r  r   r  r   r  )rS   r   r  msgr  r   r  r   s   &&&     r   rQ  SessionStore.rewrite_transcript  sU    888J''
3#C77695DHH++#-! #	 2"%''+"6#&77<#8%(WW^%<:>+:M#''+"6SWJNR]J]#''2E*FcgRVZeRecgg6M.Nko , 
 $" 22:>/399Q

3U;dBC   :9  JEqIIJ
 :99s$   CE 8F	F&FF	F	c                L   < V ^8  d   QhRS[ RS[S[S[ S[3,          ,          /# r  r  )r   r>   s   "r   r   r     s(     . .# .$tCH~2F .r   c           
        . pV P                   '       d    V P                   P                  V4      pV P                  V4      p. pVP                  4       '       di   \        VRRR7      ;_uu_ 4       pV FC  pVP                  4       pV'       g   K   VP                  \        P                  ! V4      4       KE  	  RRR4       \        V4      \        V4      8  d5   V'       d+   \        P	                  RV\        V4      \        V4      4       V# V#   \         d"   p\        P	                  RT4        Rp?ELRp?ii ; i  \        P                   d#    \        P                  RYR,          4        EK  i ; i  + '       g   i     L; i)	z.Load all messages from a session's transcript.z#Could not load messages from DB: %sNr   r   r   z*Skipping corrupt line in transcript %s: %s:Nx   Nuf   Session %s: JSONL has %d messages vs SQLite %d — using JSONL (legacy session not yet fully migrated))r   get_messages_as_conversationr   r   r   r  r   r   r4   rL   r   loadsJSONDecodeErrorrS  r>  )rS   r   db_messagesr   r  jsonl_messagesr   lines   &&      r   rP  SessionStore.load_transcript  sE   888G"hhCCJO 22:>!!##osW==D::<Dt*11$**T2BC	  >, ~[!11JN 3S5E
 "!O  GBAFFG  $33 "NN L *J  >=sG   D 0E:%E 6E:D=D88D= 2E7	2E:6E7	7E::F
	)r   r   r   r   r   r   r   )NN)Frs  )rn   ro   rp   rq   rr   r   r   r   r  r  r5  r9  r?  r[  r_  rg  rm  r|  r  r  rQ  rP  rv   rw   rx   s   @r   r   r     s     f f$) )
 . .
 
$ $L* *X* *,s sj  1 1f. .` 9 9D D8D D@. .r   r   c                ^    V ^8  d   QhR\         R\        R\        \        ,          R\        /# )r   r   r   session_entryr   )r;   r7   r   r   rz   )r   s   "r   r   r     s5       L) 	r   c                .   VP                  4       p/ pV F"  pVP                  V4      pV'       g   K  WdV&   K$  	  \        V VVR7      pV'       dE   VP                  Vn        VP                  Vn        VP
                  Vn        VP                  Vn        V# )zx
Build a full session context from a source and config.

This is used to inject context into the agent's system prompt.
)r   r   r   )get_connected_platformsget_home_channelrz   r   r   r   r   )r   r   r  	connectedr   rI   r   r   s   &&&     r   build_session_contextr    s     ..0IM&&x04&*(# 
 %#G +77*55*55*55Nr   )TFrs  ).rr   r   loggingr  r   rer   rK  pathlibr   r   r   dataclassesr   typingr   r   r   r	   	getLoggerrn   r   r   compiler2   r#   r'   r.   r5   r   r6   r7   r8   r9   r;   rz   	frozensetWHATSAPPSIGNALTELEGRAMr   r   r   r   r   r  r   r   r   <module>r     s(     	  	    ( ! , ,			8	$ JJ-.	B
%

0  Q
 Q
 Q
h 
 
 
B  OO!  
/
J JZ a
 a
 a
H8va aH r   