
    `gjq                        d Z ddlmZ ddlZddlZddlmZ dZdZ G d d      Z	 G d	 d
      Z
ddlmZmZmZ  G d de      ZddddZ G d d      Zy)ar  Keep-alive PTY sessions for dashboard terminals.

A PTY process outlives the WebSocket that created it: a single drain task
always reads the PTY into a bounded RingBuffer and forwards to the attached
socket when present. Reconnecting with the same opaque token replays the
buffer and resumes live. See
docs/superpowers/specs/2026-06-20-pty-keepalive-reattach-design.md.
    )annotationsN)Optionali:  i9  c                  :    e Zd ZdZddZddZd	dZed
d       Zy)
RingBufferz=Keeps only the most recent ``capacity`` bytes appended to it.c                >    || _         t               | _        d| _        y NF)_cap	bytearray_buf
_truncated)selfcapacitys     I/root/.hermes/venv/lib/python3.12/site-packages/hermes_cli/pty_session.py__init__zRingBuffer.__init__   s    	K	    c                    | j                   j                  |       t        | j                         | j                  z
  }|dkD  r| j                   d |= d| _        y y )Nr   T)r   extendlenr	   r   )r   dataoverflows      r   appendzRingBuffer.append   sM    		tyy>DII-a<		)8)$"DO r   c                ,    t        | j                        S N)bytesr   r   s    r   snapshotzRingBuffer.snapshot"   s    TYYr   c                    | j                   S r   )r   r   s    r   	truncatedzRingBuffer.truncated%   s    r   N)r   intreturnNone)r   r   r    r!   )r    r   )r    bool)	__name__
__module____qualname____doc__r   r   r   propertyr    r   r   r   r      s(    G 
#   r   r   c                  <    e Zd ZddZd	dZd	dZd	dZd	dZd	dZy)

PtySessionc                   || _         || _        t        |      | _        d| _        d| _        d | _        || _        d | _        d | _	        y )NTF)
keybridger   bufferaliveattachedlast_detached_at_read_timeout_ws_drain_task)r   r,   r-   
buffer_capread_timeouts        r   r   zPtySession.__init__+   sI     ,
15)37r   c                \   K   t        j                  | j                               | _        y wr   )asynciocreate_task_drainr4   r   s    r   startzPtySession.start6   s     "..t{{}=s   *,c                *  K   t        j                         }	 |j                  d | j                  j                  | j
                         d {   }|6d| _        | j                  }| 	 |j                  t               d {    y y |st        j                  d       d {    | j                  j                  |       | j                  }|	 |j                  |       d {    7 7 l# t        $ r Y y w xY w7 \7 # t        $ r Y #w xY ww)NFcoder   )r8   get_running_looprun_in_executorr-   readr2   r/   r3   closeWS_CLOSE_PROCESS_EXITED	Exceptionsleepr.   r   
send_bytes)r   loopchunkwss       r   r:   zPtySession._drain9   s    '')..tT[[5E5EtGYGYZZE}"
XX> hh,ChDDD mmA&&&KKu%B~--...% Z E$  ' /  s   A
DC-D)C1 C/C1 D#D $.DD 'D(D ,D/C1 1	C=:D<C==DD 	DDDDc                *  K   | j                   }|#||ur	 |j                  t               d {    || _         d| _        d | _        | j                  j                         }|r|j                  |       d {    y y 7 P# t        $ r Y Yw xY w7 w)Nr=   T)	r3   rB   WS_CLOSE_SUPERSEDEDrD   r0   r1   r.   r   rF   )r   rI   oldsnaps       r   attachzPtySession.attachQ   s     hh?s"}ii%8i999  ${{##%--%%%  :  &sD   BB B B AB9B:B B 	BBBBc                p    | j                   |ury d | _         d| _        t        j                         | _        y r   )r3   r0   time	monotonicr1   )r   rI   s     r   detachzPtySession.detach_   s0     882 $ 0r   c                J  K   | j                   /| j                   j                          	 | j                    d {    	 t        j
                  | j                  j                         d {    y 7 7# t        j                  t        f$ r Y Pw xY w7 &# t        $ r Y y w xY wwr   )r4   cancelr8   CancelledErrorrD   	to_threadr-   rB   r   s    r   rB   zPtySession.closek   s     '##%&&&&	 ##DKK$5$5666 '**I6 
 7 		sg   'B#A3 A1A3 ,B +B,B 0B#1A3 3BB#BB#B 	B B#B  B#N)r,   strr5   r   r6   floatr    r!   r    r!   )	r#   r$   r%   r   r;   r:   rN   rR   rB   r(   r   r   r*   r*   *   s     	8>0&
1r   r*   )CallableDictTuplec                      e Zd Zy)RegistryFullN)r#   r$   r%   r(   r   r   r^   r^   }   s    r   r^   g      N@)intervalc                  K   	 t        j                  |       d{    	 | j                          d{    77 7 # t        $ r Y w xY ww)zGPeriodically reap idle/dead keep-alive sessions. Cancelled on shutdown.N)r8   rE   	reap_idlerD   )registryr_   s     r   
run_reaperrc      sO     
mmH%%%	$$&&& %& 		s:   A;A? =? A? 	AA
AAc                  N    e Zd Z	 	 	 	 	 	 ddZ	 	 d	dZd
dZdddZddZddZy)PtySessionRegistryc               J    || _         || _        || _        || _        i | _        y r   )_ttl_max_buffer_capr2   	_sessions)r   ttlmax_sessionsr5   r6   s        r   r   zPtySessionRegistry.__init__   s'    	 	%)02r   c               L  K   | j                          d {    | j                  j                  |      }||j                  r|dfS |4|j	                          d {    | j                  j                  |d        t        | j                        | j                  k\  r| j                          t        j                  |       d {   }t        ||| j                  | j                        }|j                          d {    || j                  |<   |dfS 7 7 7 W7 w)NF)r5   r6   T)ra   rj   getr/   rB   popr   rh   _reap_one_idle_or_raiser8   rV   r*   ri   r2   r;   )r   r,   spawnexistingr-   sessions         r   attach_or_spawnz"PtySessionRegistry.attach_or_spawn   s     nn>>%%c*HNNU?".."""NNsD)t~~$))+((* ((//S&T5E5E*.*<*<>mmo%s}! 	
 # 0 	sG   D$DAD$DA*D$D 	:D$D"D$D$ D$"D$c                b    | j                   j                  |      }||j                  |       y y r   )rj   rn   rR   )r   r,   rI   ss       r   rR   zPtySessionRegistry.detach   s+    NNs#=HHRL r   Nc                  K   |t        j                         n|}| j                  j                         D cg c]G  \  }}|j                  r4|j
                  s*|j                  ||j                  z
  | j                  kD  r|I }}}|D ]3  }| j                  j                  |      j                          d {    5 y c c}}w 7 wr   )
rP   rQ   rj   itemsr/   r0   r1   rg   ro   rB   )r   nowr,   rv   doomeds        r   ra   zPtySessionRegistry.reap_idle   s     "%+dnn3"nn224
CGGJJ1#5#5#A1---: 
 
  	2C..$$S)//111	2
 2s   6CAC 4C8C9Cc                R   | j                   j                         D cg c]  }|j                  s|j                  | }}|s
t	               t        |d       }| j                   j                  |j                  d        t        j                  |j                                y c c}w )Nc                "    | j                   xs dS )Ng        )r1   )rv   s    r   <lambda>z<PtySessionRegistry._reap_one_idle_or_raise.<locals>.<lambda>   s    ););)Bs r   )r,   )rj   valuesr0   r1   r^   minro   r,   r8   r9   rB   )r   rv   idleoldests       r   rp   z*PtySessionRegistry._reap_one_idle_or_raise   s    >>002 Fazza&8&8&D  F F. TBC6::t,FLLN+Fs   !B$c                   K   t        | j                        D ]3  }| j                  j                  |      j                          d {    5 y 7 wr   )listrj   ro   rB   )r   r,   s     r   	close_allzPtySessionRegistry.close_all   sB     ' 	2C..$$S)//111	21s   AAAA)
rk   rX   rl   r   r5   r   r6   rX   r    r!   )r,   rW   rq   zCallable[[], object]r    zTuple[PtySession, bool])r,   rW   r    r!   r   )ry   zOptional[float]r    r!   rY   )	r#   r$   r%   r   rt   rR   ra   rp   r   r(   r   r   re   re      s@    3 3053:>3#:(
	2,2r   re   )rb   z'PtySessionRegistry'r_   rX   r    r!   )r&   
__future__r   r8   rP   typingr   rC   rK   r   r*   rZ   r[   r\   rD   r^   rc   re   r(   r   r   <module>r      se    #      .M M` ) (	9 	 KO 82 82r   