
    }-j                        d Z ddlmZ ddlZddlZddlZddlZddlZddlm	Z	 ddl
mZ dZddhZ ej        d	          Zd$dZd%d&dZd'dZddd(dZddd)dZd*dZd+dZddd,dZd-d!Zd.d#ZdS )/a  Crash-safe WebUI turn journal helpers.

The journal is deliberately tiny: one JSONL file per session, append-only events,
and read helpers that tolerate malformed lines. Recovery and repair can then
reason about submitted turns without depending on in-memory stream state.
    )annotationsN)Path)Iterable_turn_journal	completedinterruptedz^[A-Za-z0-9_.-]+$returnr   c                 ,    ddl m}  t          |           S )Nr   SESSION_DIR)
api.modelsr   r   r   s    &/root/hermes-webui/api/turn_journal.py_default_session_dirr      s"    &&&&&&    
session_idstrsession_dirPath | Nonec                   t          | pd                                          }|r"d|v sd|v st                              |          st	          d          |t          |          nt                      }|t          z  | dz  S )N /\zinvalid session_idz.jsonl)r   strip_SESSION_ID_RE	fullmatch
ValueErrorr   r   TURN_JOURNAL_DIR_NAME)r   r   sidroots       r   _journal_pathr       s    
jB


%
%
'
'C /#**>3K3KC3P3P-... + 74=Q=S=SD''S...88r   c                     t          j        dt          j                               dt          j                    j        d d          S )Nz%Y%m%dT%H%M%SZ-   )timestrftimegmtimeuuiduuid4hex r   r   _make_turn_idr+   $   s>    m,dkmm<<VVtz||?OPSQSPS?TVVVr   r   eventdictc                  t          |t                    st          d          t          |                    d          pd                                          }|st          d          t          |          }|                    dd           t          |           |d<   |                    dt                                 |                    d	t          j	                               t          | |
          }|j                            dd           t          j        |dd          dz   }t          j        |t          j        t          j        z  t          j        z  d          }t          j        |dd          5 }|                    |           |                                 t          j        |                                           ddd           n# 1 swxY w Y   	 t          j        |j        t          j                  }		 t          j        |	           t          j        |	           n# t          j        |	           w xY wn# t6          $ r Y nw xY w|S )zAppend one turn journal event and fsync it before returning.

    The returned event is the exact payload written, with default ``version``,
    ``session_id``, ``turn_id``, and ``created_at`` fields filled in.
    zevent must be a dictr-   r   zevent is requiredversion   r   turn_id
created_atr,   T)parentsexist_okF),:)ensure_ascii
separators
i  autf-8encodingN)
isinstancer.   	TypeErrorr   getr   r   
setdefaultr+   r$   r    parentmkdirjsondumpsosopenO_CREATO_APPENDO_WRONLYfdopenwriteflushfsyncfilenoO_DIRECTORYcloseOSError)
r   r-   r   
event_namepayloadpathlinefdfhdir_fds
             r   append_turn_journal_eventr[   (   sU    eT"" 0.///UYYw''-2..4466J .,---5kkGy!$$$
OOGLy-//222|TY[[111===DKdT222:gEjIIIDPD	rzBK/"+=u	E	EB	2sW	-	-	- 





              bn55	HVHVBHV   Ns=   AG  G$'G$,$I H: %I :II 
I! I!c               P   t          | |          }g }g }	 |                    d                                          }n## t          $ r t	          |           g g dcY S w xY wt          |d          D ]\  }}|                                s	 t          j        |          }n-# t          j	        $ r |
                    ||d           Y Yw xY wt          |t                    r|
                    |           |
                    ||d           t	          |           ||dS )zDRead a session journal, returning valid events plus malformed lines.r,   r<   r=   )r   events	malformedr1   )start)rW   raw)r    	read_text
splitlinesFileNotFoundErrorr   	enumerater   rE   loadsJSONDecodeErrorappendr?   r.   )	r   r   rV   r]   r^   linesline_nor`   r-   s	            r   read_turn_journalrj   Q   sd   ===DFIN00;;== N N N!*oo"MMMMMN!%q111 < <yy{{ 		JsOOEE# 	 	 	gc::;;;H	 eT"" 	<MM%    gc::;;;;j//V)TTTs#   (A   A A B$$'CCr]   Iterable[dict]"tuple[dict[str, dict], list[dict]]c                2   i }i }| D ]}t          |t                    st          |                    d          pd                                          }|sQt          |          r)|                    |g                               |           |                    |          }|Jt          |                    d          pd          t          |                    d          pd          k    r|||<   d |	                                D             }||fS )a  Return the latest event per ``turn_id`` and any terminal-collision entries.

    The first element is the latest event per turn_id (same overwrite-by-timestamp
    behaviour as before).  The second element is a list of collision records, one
    per turn_id that had more than one terminal event.  Each collision record
    contains ``turn_id`` and the ``events`` list (in ascending created_at order).

    A collision means the same logical turn recorded both ``completed`` and
    ``interrupted`` terminal events -- the derived state still picks the latest
    by timestamp, but callers can now detect and audit the double-terminal
    situation explicitly rather than having it silently collapse.
    r2   r   Nr3   r   c                d    g | ]-\  }}t          |          d k    |t          |d           d.S )r1   c                J    t          |                     d          pd          S )Nr3   r   )floatrA   )es    r   <lambda>z7derive_turn_journal_states.<locals>.<listcomp>.<lambda>   s     eAEE,DWDWD\[\>]>] r   )key)r2   r]   )lensorted).0tidevtss      r   
<listcomp>z.derive_turn_journal_states.<locals>.<listcomp>   sL       Ct99q== 6$4]4]#^#^#^__==r   )
r?   r.   r   rA   r   is_terminal_turn_eventrB   rg   rp   items)r]   statesterminal_eventsr-   r2   previous
collisionss          r   derive_turn_journal_statesr   i   s2    !F-/O $ $%&& 	eii	**0b117799 	!%(( 	B&&w33::5AAA::g&&uUYY|%<%<%ABBeHLLYeLfLfLkjkFlFlll#F7O (..00  J
 :r   	stream_id
str | Nonec                T   t          |pd                                          }|sd S d }| D ]{}t          |t                    st          |                    d          pd          |k    rAt          |                    d          pd                                          }|r|}||S )Nr   r   r2   )r   r   r?   r.   rA   )r]   r   streamlatestr-   r2   s         r   _latest_turn_id_for_streamr      s    b!!''))F tF  %&& 	uyy%%+,,66eii	**0b117799 	FMr   c                  t          |          }t          |          |d<   |                    d          s=t          | |          }t	          |                    d          pg |          }|r||d<   t          | ||          S )zDAppend a lifecycle event for the turn associated with ``stream_id``.r   r2   r,   r]   )r.   r   rA   rj   r   r[   )r   r   r-   r   rU   journalr2   s          r   $append_turn_journal_event_for_streamr      s     5kkGy>>GK;;y!! )#JKHHH,W[[-B-B-Hb)TT 	)!(GI$ZkRRRRr   	list[str]c                    t          |           t          z  }|                                sg S t          d |                    d          D                       S )Nc              3  L   K   | ]}|                                 |j        V   d S N)is_filestem)rv   rV   s     r   	<genexpr>z0iter_turn_journal_session_ids.<locals>.<genexpr>   s1      VVt||~~V$)VVVVVVr   z*.jsonl)r   r   existsru   glob)r   journal_dirs     r   iter_turn_journal_session_idsr      sY    {##&;;K 	VV(8(8(C(CVVVVVVr   boolc                \    t          | pi                     d          pd          t          v S )Nr-   r   )r   rA   _TERMINAL_EVENTS)r-   s    r   rz   rz      s-      ))/R004DDDr   )r	   r   r   )r   r   r   r   r	   r   )r	   r   )r   r   r-   r.   r   r   r	   r.   )r   r   r   r   r	   r.   )r]   rk   r	   rl   )r]   rk   r   r   r	   r   )
r   r   r   r   r-   r.   r   r   r	   r.   )r   r   r	   r   )r-   r.   r	   r   )__doc__
__future__r   rE   rG   rer$   r'   pathlibr   typingr   r   r   compiler   r   r    r+   r[   rj   r   r   r   r   rz   r*   r   r   <module>r      s    # " " " " "  				 				              ' / 011   9 9 9 9 9W W W W  $	& & & & & &R FJ U U U U U U0$ $ $ $L   *  $S S S S S S$W W W WE E E E E Er   