
    i?                       U 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mZ ddlm	Z	  e	e
      j                         j                  j                  Z ee      ej                  vr"ej                  j!                  d ee             ddlmZ  ee      Zej*                  j-                  dd      Z	 dd	lmZ d
di e       Zded<   dZdZdZ  G d d      Z!y# e$ r dddddddddd	ZY $w xY w)u~  
utils/session_resilience.py — 세션 복원력 오케스트레이터 (Phase 2)

Phase 1의 SessionMonitor + SessionAutoCompress를 연결하고,
전체 봇 세션의 토큰 사용량을 모니터링하여 자동 대응합니다.

Usage:
    from utils.session_resilience import SessionResilience

    resilience = SessionResilience()
    result = resilience.check_all_sessions()
    )annotationsN)datetime)Path)
get_loggerWORKSPACE_ROOTz/home/jay/workspace)build_team_bot_mapanuz
anu-directzdict[str, str]	BOT_TEAMSdev1dev2dev3dev4dev5dev6dev7dev8)	r	   z	dev1-teamz	dev2-teamz	dev3-teamz	dev4-teamz	dev5-teamz	dev6-teamz	dev7-teamz	dev8-teamnormalwarningcriticalc                      e Zd ZdZ	 	 	 	 d	 	 	 	 	 	 	 	 	 ddZddZddZddZ	 d	 	 	 	 	 	 	 	 	 ddZddZ	dd	Z
dd
ZddZddZ	 	 	 	 	 	 	 	 	 	 	 	 ddZy)SessionResilienceu7  전체 봇 세션 복원력 오케스트레이터.

    Args:
        workspace_root: 워크스페이스 루트 경로
        context_limit: 컨텍스트 윈도우 토큰 한도 (기본: 200,000)
        warning_pct: WARNING 임계값 (기본: 0.70)
        critical_pct: CRITICAL 임계값 (기본: 0.85)
    Nc                0   |t        |      nt        t              }|| _        || _        || _        || _        |dz  dz  | _        |dz  dz  | _        |dz  dz  | _        |dz  dz  | _	        t        | _        t        j                  d|||dz  |dz         y )Nmemoryztask-timers.jsonztoken-ledger.jsoneventssessionsuO   SessionResilience 초기화: root=%s, limit=%d, warning=%.0f%%, critical=%.0f%%d   )r   _WORKSPACE_ROOTworkspace_rootcontext_limitwarning_pctcritical_pcttimers_pathledger_path
events_dirsessions_dirr
   	bot_teamsloggerdebug)selfr   r   r    r!   roots         I/home/jay/workspace/.worktrees/task-2116-dev1/utils/session_resilience.py__init__zSessionResilience.__init__D   s     (6'AtN#tOG\$("/"-#/!%3E!E!%3F!F $x( :"&/J">)2]#3	
    c           	     D   | j                         }| j                         }g }g }d}|j                         D ]  \  }}|j                  |i       }| j	                  |||      }	|	d   }
|	d   }|
t
        k(  r/| j                  |||	      }|j                  ||	d   |d   d       n|
t        k(  r| j                  |      r1t        j                  d|       |j                  ||	d   ddd	d
       | j                  |      }| j                  |||	|      }|j                  ||	d   |d   |d   |d   d
       |dz  }
 t        |      }||||t        j                          j#                         d}t        j                  d|t        |      t        |      |       |S )u  모든 running 세션의 토큰 사용량을 체크하고 자동 대응.

        Returns:
            {
                "checked": N,
                "warnings": [{"task_id": ..., "usage_pct": ..., "event_path": ...}],
                "criticals": [{"task_id": ..., "usage_pct": ..., "event_path": ...,
                               "summary_path": ..., "resume_triggered": bool}],
                "normals": N,
                "timestamp": "..."
            }
        r   levelteam_id	usage_pct
event_path)task_idr1   r2   u?   세션 %s 이미 CRITICAL 처리됨 — 중복 트리거 방지 F)r3   r1   r2   summary_pathresume_triggeredr5   r6      )checkedwarnings	criticalsnormals	timestampuL   check_all_sessions 완료: checked=%d, warnings=%d, criticals=%d, normals=%d)_load_running_tasks_load_ledger_tasksitemsgetcheck_session_LEVEL_WARNINGhandle_warningappend_LEVEL_CRITICAL_is_already_handledr'   info_get_original_task_deschandle_criticallenr   now	isoformat)r)   running_tasksledger_tasksr9   r:   r;   r3   	task_infoledger_infostatusr/   r0   w_resultoriginal_descc_resultr8   results                    r+   check_all_sessionsz$SessionResilience.check_all_sessionse   s    002..0! "	"/"5"5"7 (	GY&**7B7K''KHF7OEY'G&..wH#*%+K%8&.|&< /)++G4KK acjk$$'.)/)<*,,.05 %)$@$@$IM#33GWfm\H$$'.)/)<*2<*@,4^,D089K0L 1Q(	T m$ "!113
 	ZM	N	
 r-   c           	     f   t        |j                  dd            }t        |j                  d|j                  dd                  }| j                  dkD  r|| j                  z  }t	        |dz  d      }nd}d}|| j
                  k\  rt        }n|| j                  k\  rt        }nt        }|||||dS )	u  개별 세션 토큰 상태 확인.

        Returns:
            {
                "task_id": str,
                "team_id": str,
                "total_tokens": int,
                "usage_pct": float,
                "level": "normal"|"warning"|"critical"
            }
        total_tokensr   r0   r4   r              )r3   r0   rX   r1   r/   )
intr@   strr   roundr!   rE   r    rB   _LEVEL_NORMAL)	r)   r3   rO   rP   rX   r0   pctr1   r/   s	            r+   rA   zSessionResilience.check_session   s     ;??>1=>immI{y"/MNO!!3!33CcCi+ICI$####ED$$$"E!E ("
 	
r-   c           	        t        j                         j                  d      }| j                  j	                  dd       d| d| d}| j                  |z  }t
        |||j                  dd      |j                  d	d
      |d}t        |dd      5 }t        j                  ||dd       ddd       t        j                  d|||j                  d	d
      |       t        |      t
        dS # 1 sw Y   DxY w)u   WARNING 임계값 도달: 이벤트 파일 생성 + 로그 기록.

        이벤트 파일: memory/events/session-warning-{task_id}-{timestamp}.json

        Returns:
            {"event_path": str, "level": "warning"}
        %Y%m%dT%H%M%STparentsexist_okzsession-warning--.jsonrX   r   r1   rZ   r/   r3   r0   rX   r1   r<   wutf-8encodingFrY   ensure_asciiindentNuF   WARNING 이벤트 기록: task=%s, team=%s, usage_pct=%.1f%%, event=%s)r2   r/   )r   rK   strftimer$   mkdirrB   r@   openjsondumpr'   r   r\   )	r)   r3   r0   session_statusr<   filenamer2   
event_datafs	            r+   rC   z SessionResilience.handle_warning   s     LLN++O<	dT:%gYa	{%@__x/
 $*..~qA'++K="

 *cG4 	CIIj!%B	C 	T{C0	
 "*oGG	C 	Cs   C11C:c           	        t        j                         j                  d      }| j                  j	                  dd       d| d| d}| j                  |z  }t
        |||j                  dd      |j                  d	d
      |d}t        |dd      5 }	t        j                  ||	dd       ddd       | j                  j	                  dd       d| d| d}
| j                  |
z  }| j                  |||||      }|j                  |d       t        j                  d|||j                  d	d
      ||       | j                  ||t!        |            }t!        |      t!        |      |t
        dS # 1 sw Y   xY w)ud  CRITICAL 임계값 도달: 이벤트 + 요약 저장 + resume 트리거.

        이벤트 파일: memory/events/session-critical-{task_id}-{timestamp}.json
        세션 요약: memory/sessions/summary-{task_id}-{timestamp}.md

        Returns:
            {"event_path": str, "summary_path": str, "resume_triggered": bool, "level": "critical"}
        ra   Trb   session-critical-re   rf   rX   r   r1   rZ   rg   rh   ri   rj   FrY   rl   Nzsummary-.md)r3   r0   r<   rt   original_task_descuS   CRITICAL 이벤트 기록: task=%s, team=%s, usage_pct=%.1f%%, event=%s, summary=%s)r2   r5   r6   r/   )r   rK   ro   r$   rp   rE   r@   rq   rr   rs   r%   _build_summary_markdown
write_textr'   r   _trigger_resumer\   )r)   r3   r0   rt   r{   r<   event_filenamer2   rv   rw   summary_filenamer5   summary_contentr6   s                 r+   rI   z!SessionResilience.handle_critical   s    LLN++O<	 	dT:,WIQykG__~5
 %*..~qA'++K="

 *cG4 	CIIj!%B	C 	t<%gYa	{#>((+;;66)1 7 
 	'Ba{C0	
  //#lBST j/- 0$	
 	
;	C 	Cs   E77F c                    | j                   j                         syd| }| j                   j                         D ]  }|j                  j	                  |      s y y)u   이 세션이 이미 CRITICAL 처리된 적 있는지 확인 (중복 방지).
        events/ 디렉토리에서 session-critical-{task_id} 파일 존재 여부로 판단.
        Fry   T)r$   existsiterdirname
startswith)r)   r3   prefixps       r+   rF   z%SessionResilience._is_already_handledA  sY     %%'$WI.((* 	Avv  (	 r-   c                    | j                   dz  dz  | dz  }	 |j                  d      S # t        $ r t        j	                  d|       Y yt
        $ r!}t        j                  d||       Y d	}~yd	}~ww xY w)
u<   memory/tasks/{task_id}.md에서 원래 작업 설명 읽기.r   tasksrz   ri   rj   u"   태스크 설명 파일 없음: %sr4   u0   태스크 설명 파일 읽기 오류: %s — %sN)r   	read_textFileNotFoundErrorr'   r(   OSErrorr   )r)   r3   	task_fileexcs       r+   rH   z)SessionResilience._get_original_task_descN  sz    ''(2W<'#N		&&&88  	LL=yI 	NNMyZ]^	s   , A6A6A11A6c                   | j                   dz  }t        j                  t        |      d|d|g}t        j                  d|||       	 t        j                  |ddd      }|j                  dk(  rt        j                  d	|       yt        j                  d
||j                  |j                         y# t        j                  $ r t        j                  d|       Y yt        $ r!}t        j                  d||       Y d}~yd}~ww xY w)u   dispatch.py --resume-from 호출하여 새 세션 시작.
        subprocess.run으로 dispatch.py 호출.

        Returns:
            성공 여부 (bool)
        zdispatch.pyz--teamz--resume-fromu.   resume 트리거: task=%s, team=%s, summary=%sT   )capture_outputtexttimeoutr   u   resume 성공: task=%su0   resume 실패: task=%s, returncode=%d, stderr=%sFu   resume 타임아웃: task=%su$   resume 실행 오류: task=%s — %sN)r   sys
executabler\   r'   rG   
subprocessrun
returncodeerrorstderrTimeoutExpiredr   )r)   r3   r0   r5   dispatch_pathcmdprocr   s           r+   r~   z!SessionResilience._trigger_resumeZ  s     ++m;NN
 	<		
	>>#	D !#4g>FOOKK	 (( 	LL7A 	LL?#N	s$   >B4 ,B4 4)DD'DDc                   i }	 t        | j                  d      5 }t        j                  |      }ddd       j	                  di       j                         D ]  \  }}|j	                  d      dk(  s|||<   ! 	 |S # 1 sw Y   NxY w# t        $ r$ t        j                  d| j                         Y |S t        j                  t        f$ r!}t        j                  d|       Y d}~|S d}~ww xY w)	u7   task-timers.json에서 running 상태 태스크 로드.ri   rj   Nr   rQ   runningu   task-timers.json 없음: %su"   task-timers.json 파싱 오류: %s)rq   r"   rr   loadr@   r?   r   r'   r   JSONDecodeErrorKeyErrorr   )r)   r   rw   datar3   rO   r   s          r+   r=   z%SessionResilience._load_running_tasks  s    #%		Dd&&9 $Qyy|$&*hhw&;&A&A&C 1"==*i7'0GG$1 $ $
 ! 	LNN8$:J:JK  $$h/ 	DLL=sCC	Ds:   B
 A>AB
 4B
 >BB
 
)C/6C/C**C/c                   i }	 t        | j                  d      5 }t        j                  |      }ddd       j	                  di       }|S # 1 sw Y   xY w# t
        $ r$ t        j                  d| j                         Y |S t        j                  t        f$ r!}t        j                  d|       Y d}~|S d}~ww xY w)u-   token-ledger.json에서 토큰 정보 로드.ri   rj   Nr   u   token-ledger.json 없음: %su#   token-ledger.json 파싱 오류: %s)rq   r#   rr   r   r@   r   r'   r   r   r   r   )r)   ledgerrw   r   r   s        r+   r>   z$SessionResilience._load_ledger_tasks  s    "$	Ed&&9 $Qyy|$XXgr*F
 $ $ ! 	MNN94;K;KL  $$h/ 	ELL>DD	Es3   A AA AA )B>B>B99B>c                $   |j                  dd      }|j                  dd      }d| ddd| d	| d
| d|dd| j                  dd|ddddd|r|ndddd| d| j                  dz  ddd|dddddd| g}dj                  |      dz   S ) u,   세션 요약 마크다운 문자열 생성.r1   rZ   rX   r   u   # 세션 요약: r4   u   ## 기본 정보u   - 작업 ID: u   - 팀: u   - 생성 시각: u   - 토큰 사용량: ,z / z (z.1fz%)u   - 레벨: CRITICALu   ## 원래 작업 설명u   (없음)u   ## 자동 요약u   세션 u   이 CRITICAL 임계값(r   z.0fu6   %)에 도달하여 자동으로 요약되었습니다.u   토큰 사용량: %u   ## 재시작 안내uG   이 요약 파일을 기반으로 새 세션이 자동 시작됩니다.u   팀: 
)r@   r   r!   join)	r)   r3   r0   r<   rt   r{   r1   rX   liness	            r+   r|   z)SessionResilience._build_summary_markdown  s     #&&{C8	%)).!<  y)G9%gY	{+"<"2#d6H6H5K2iX[_\^_ %"4*gY5d6G6G#6Mc5R  SI  J 3q1!UG9'
, yy$&&r-   )Ni@ gffffff?g333333?)
r   z
str | Noner   r[   r    floatr!   r   returnNone)r   dict)r3   r\   rO   r   rP   r   r   r   )r3   r\   r0   r\   rt   r   r   r   )r4   )
r3   r\   r0   r\   rt   r   r{   r\   r   r   )r3   r\   r   bool)r3   r\   r   r\   )r3   r\   r0   r\   r5   r\   r   r   )r   zdict[str, dict])r3   r\   r0   r\   r<   r\   rt   r   r{   r\   r   r\   )__name__
__module____qualname____doc__r,   rV   rA   rC   rI   rF   rH   r~   r=   r>   r|    r-   r+   r   r   :   s     &*$!"
"
 
 	

 
 

BN`#
J"HR #%A
A
 A
 	A

  A
 
A
F
/j"'"' "' 	"'
 "'  "' 
"'r-   r   )"r   
__future__r   rr   osr   r   r   pathlibr   __file__resolveparent_SCRIPT_DIRr\   pathinsertutils.loggerr   r   r'   environr@   r   utils.org_loaderr   _build_team_bot_mapr
   __annotations__ImportErrorr^   rB   rE   r   r   r-   r+   <module>r      s    #  	  
   8n$$&--44{388#HHOOAs;'( #	H	**..!13HIJ!& N8K8M NI~N S' S'%  
Is   1C C,+C,