
    Si                    >   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m	Z	m
Z
 ddlmZ ddlmZ ddlmZ ddlmZmZ  ee      Zej,                  j/                  d	d
      Z e ee      dz  dz        Z ej6                  d      ZddZddZ G d de      Zy)u   
utils/session_store.py — SQLite WAL 기반 세션 저장소 (CRUD + 초기화)

FTS5 검색은 SearchMixin(session_store_search.py), 스키마 DDL은 ALL_DDL에서 제공.
Usage: from utils.session_store import SessionStore
    )annotationsN)datetime	timedeltatimezone)Path)Any)
get_logger)ALL_DDLSearchMixinWORKSPACE_ROOTz/home/jay/workspacememoryzsessions.dbzH[\x00-\x1f\x7f\x80-\x9f\u200b\u200c\u200d\u200e\u200f\u2028\u2029\ufeff]c                .    t         j                  d|       S )uI   제어문자와 zero-width 문자를 제거한 문자열을 반환한다. )	_STRIP_REsub)texts    D/home/jay/workspace/.worktrees/task-2117-dev1/utils/session_store.pysanitize_titler       s    ==T""    c                 f    t        j                  t        j                        j	                         S )u9   UTC 기준 현재 시각을 ISO 8601 형식으로 반환.tz)r   nowr   utc	isoformat r   r   _now_isor   %   s    <<8<<(2244r   c                      e Zd ZdZdddZddZ	 	 	 d	 	 	 	 	 	 	 	 	 	 	 ddZddZddZdddZ		 	 	 d	 	 	 	 	 	 	 	 	 	 	 dd	Z
dd
ZdddZddZy)SessionStoreuL   SQLite WAL 모드 세션 저장소. FTS5 검색은 SearchMixin에서 제공.Nc                `   |xs t         | _        t        | j                        j                  j	                  dd       t        j                         | _        t        j                  | j                  dd      | _
        t        j                  | j                  _        | j                          y)uA   db_path: SQLite 파일 경로. None이면 DEFAULT_DB_PATH 사용.T)parentsexist_okFN)check_same_threadisolation_level)DEFAULT_DB_PATHdb_pathr   parentmkdir	threadingLock_locksqlite3connect_connRowrow_factory_setup)selfr&   s     r   __init__zSessionStore.__init__-   sv    #6T\\!!''t'D^^%
__LL# 


 ")

r   c                *   | j                   5  | j                  j                         }|j                  d       |j                  d       t        D ]  }|j                  |        | j                  j                          ddd       y# 1 sw Y   yxY w)u    WAL 설정 및 스키마 생성.zPRAGMA journal_mode=WAL;zPRAGMA foreign_keys=ON;N)r+   r.   cursorexecuter
   commit)r2   curddls      r   r1   zSessionStore._setup;   su    ZZ 	 **##%CKK23KK12 !C !JJ	  	  	 s   A3B		Bc                   |rt        |      nd}d}| j                  5  | j                  j                  ||||||t	               f       | j                  j                          ddd       t        j                  d||       y# 1 sw Y   !xY w)u   새 세션을 생성한다.NzpINSERT INTO sessions (session_id, source, model, parent_session_id, title, created_at) VALUES (?, ?, ?, ?, ?, ?)zSession created: %s (source=%s))r   r+   r.   r6   r   r7   loggerdebug)r2   
session_idsourcemodelparent_session_idtitleclean_titlesqls           r   create_sessionzSessionStore.create_sessionE   s     05nU+$ AZZ 	 JJsZ@QS^`h`j$klJJ	  	6
FK	  	 s   ABBc                    | j                   5  | j                  j                  dt               ||f       | j                  j	                          ddd       t
        j                  d||       y# 1 sw Y   !xY w)u.   세션을 종료 상태로 업데이트한다.z?UPDATE sessions SET ended_at=?, end_reason=? WHERE session_id=?NzSession ended: %s (reason=%s))r+   r.   r6   r   r7   r;   r<   )r2   r=   
end_reasons      r   end_sessionzSessionStore.end_sessionU   sd    ZZ 	 JJQZ4 JJ	  	4j*M	  	 s   AA//A8c                z    | j                   j                  d|f      }|j                         }|rt        |      S dS )u;   세션을 조회한다. 존재하지 않으면 None 반환.z)SELECT * FROM sessions WHERE session_id=?N)r.   r6   fetchonedict)r2   r=   r8   rows       r   get_sessionzSessionStore.get_session_   s7    jj  !Lzm\llntCy)T)r   c                    || j                   j                  d||f      }n| j                   j                  d|f      }|j                         D cg c]  }t        |       c}S c c}w )uX   세션 목록을 반환한다 (생성 시각 내림차순). source 필터 선택 가능.zFSELECT * FROM sessions WHERE source=? ORDER BY created_at DESC LIMIT ?z7SELECT * FROM sessions ORDER BY created_at DESC LIMIT ?)r.   r6   fetchallrJ   )r2   r>   limitr8   rK   s        r   list_sessionszSessionStore.list_sessionse   sd    **$$XC
 **$$IC &)\\^4cS	444s   A&c                   |t        j                  |d      nd}d}| j                  5  | j                  j	                  ||||||t               f       | j                  j                          ddd       y# 1 sw Y   yxY w)u$   세션에 메시지를 추가한다.NF)ensure_asciizpINSERT INTO messages (session_id, role, content, tool_calls, tool_call_id, created_at) VALUES (?, ?, ?, ?, ?, ?))jsondumpsr+   r.   r6   r   r7   )r2   r=   rolecontent
tool_callstool_call_idtool_calls_jsonrC   s           r   append_messagezSessionStore.append_messages   sx     ISH^$**ZeDdh AZZ 	 JJsZwQ]_g_i$jkJJ	  	  	 s   AA88Bc                >   | j                   j                  d|f      }g }|j                         D ]K  }t        |      }|j	                  d      r	 t        j                  |d         |d<   |j                  |       M |S # t
        j                  t        f$ r Y 0w xY w)uU   세션의 모든 메시지를 생성 순서(created_at 오름차순)로 반환한다.zISELECT * FROM messages WHERE session_id=? ORDER BY created_at ASC, id ASCrW   )
r.   r6   rN   rJ   getrS   loadsJSONDecodeError	TypeErrorappend)r2   r=   r8   rowsrK   ds         r   get_messageszSessionStore.get_messages   s    jj  WM
 <<> 	CS	Auu\"&*jj<&AAlO KKN	  ,,i8 s   B  BBc                z   t        j                  t        j                        t	        |      z
  j                         }| j                  5  | j                  j                  d|f      }| j                  j                          ddd       j                  }t        j                  d||       |S # 1 sw Y   .xY w)uU   종료된 오래된 세션을 삭제하고 삭제 수를 반환한다 (기본 90일).r   )daysz@DELETE FROM sessions WHERE ended_at IS NOT NULL AND ended_at < ?Nz6prune_sessions: deleted %d sessions older than %d days)r   r   r   r   r   r   r+   r.   r6   r7   rowcountr;   info)r2   older_than_dayscutoffr8   deleteds        r   prune_sessionszSessionStore.prune_sessions   s    ,,(,,/)2QQ\\^ZZ 	 **$$R	C JJ	  ,,LgWfg	  	 s   8B11B:c                    	 | j                   j                          t        j                  d| j                         y# t
        $ r }t        j                  d|       Y d}~yd}~ww xY w)u   DB 연결을 닫는다.zSessionStore closed: %szSessionStore.close() error: %sN)r.   closer;   r<   r&   	Exceptionwarning)r2   excs     r   rm   zSessionStore.close   sO    	BJJLL2DLLA 	BNN;SAA	Bs   := 	A&A!!A&)N)r&   
str | NonereturnNone)rr   rs   )NNN)r=   strr>   rt   r?   rq   r@   rq   rA   rq   rr   rs   )r=   rt   rF   rt   rr   rs   )r=   rt   rr   zdict[str, Any] | None)N   )r>   rq   rO   intrr   list[dict[str, Any]])r=   rt   rU   rt   rV   rq   rW   zlist[dict[str, Any]] | NonerX   rq   rr   rs   )r=   rt   rr   rw   )Z   )rh   rv   rr   rv   )__name__
__module____qualname____doc__r3   r1   rD   rG   rL   rP   rZ   rc   rk   rm   r   r   r   r   r   *   s    V  !(, LL L 	L
 &L L 
L N*5$ #26#'     	 
 0  !  
 "Br   r   )r   rt   rr   rt   )rr   rt   ) r|   
__future__r   rS   osrer,   r)   r   r   r   pathlibr   typingr   utils.loggerr	   utils.session_store_searchr
   r   ry   r;   environr\   _WORKSPACE_ROOTrt   r%   compiler   r   r   r   r   r   r   <module>r      s    #  	 	   2 2   # ;	H	**..!13HId?+h6FG BJJbc	#
5
|B; |Br   