
    i'              
          U d Z ddl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 ej                  j                  dd      Z ee d      Z ee d      Z ee d	      Z ee d
      ZdZdZ e	 ed            Z	 ddlZeej.                  vrej.                  j1                  de       ddlmZ ddiZeeef   ed<   ejA                   e       D  ci c]  } | d|  
 c}        eZ!eeef   ed<   deddfdZ#defdZ$dede%fdZ&dedz  dedz  fd Z'd!ede(e)   fd"Z*d!ed#ededz  fd$Z+d!ed#ededz  fd%Z,d!ed#ed&e-de.e%ef   fd'Z/de)fd(Z0d-d)Z1d-d*Z2d-d+Z3e4d,k(  r e3        yyc c} w # e"$ r
 dddddZ!Y w xY w).u  Stuck bot status watchdog — processing 상태가 30분 이상 지속되면 자동 idle 전환

프로세스 생존 체크 추가: 실제 claude 프로세스가 살아있으면 idle 전환 보류

Usage:
    python3 scripts/bot-status-watchdog.py          # 1회 실행
    python3 scripts/bot-status-watchdog.py --daemon # 5분마다 반복

Log: /home/jay/workspace/logs/bot-watchdog.log
    N)datetime	timedeltatimezone)PathWORKSPACE_ROOTz/home/jay/workspacez /memory/events/bot-activity.jsonz/logs/bot-watchdog.logz/memory/eventsz/memory/reportsi,     	   )hours)get_dev_short_idsanuzworkspace/autoset	_patternszteams/BOT_WORKSPACE_PATTERNSz
teams/dev1z
teams/dev2z
teams/dev3)r   dev1dev2dev3messagereturnc                 6   t        j                  t              j                         }d| d|  d}	 t        j
                  j                  dd       t        t        dd      5 }|j                  |       d	d	d	       y	# 1 sw Y   y	xY w# t        $ r Y y	w xY w)
u   워치독 로그 기록[z] 
Tparentsexist_okautf-8encodingN)
r   nowKST	isoformatWATCHDOG_LOGparentmkdiropenwriteOSError)r   tslinefs       2/home/jay/workspace/scripts/bot-status-watchdog.pylog_watchdogr+   4   s    	c		$	$	&Brd"WIR D!!$!>,g6 	!GGDM	 	 	 s/   3B %B 7B  B	B 	B 	BBc                     	 t         j                         sdi iS t        t         dd      5 } t        j                  |       cddd       S # 1 sw Y   yxY w# t        j
                  t        f$ r}t        d|        di icY d}~S d}~ww xY w)u   bot-activity.json 로드botsrr   r   Nu(   ERROR: bot-activity.json 로드 실패: )BOT_ACTIVITY_FILEexistsr$   jsonloadJSONDecodeErrorr&   r+   )r)   es     r*   load_bot_activityr5   @   s     '')B<#S7; 	 q99Q<	  	  	   '* ?sCD|s?   A A A	A AA A B0BBBdatac                 \   	 t         j                  j                  dd       t         j                  d      }t	        |dd      5 }t        j                  | |dd	       d
d
d
       |j                  t                y# 1 sw Y   xY w# t        $ r}t        d|        Y d
}~yd
}~ww xY w)u+   bot-activity.json 저장 (원자적 쓰기)Tr   z.tmpwr   r      F)indentensure_asciiNu(   ERROR: bot-activity.json 저장 실패: )
r/   r"   r#   with_suffixr$   r1   dumpreplacer&   r+   )r6   	temp_filer)   r4   s       r*   save_bot_activityr@   L   s      &&td&C%11&9	)S73 	=qIIdAae<	= 	+,		= 	=
  ?sCDs0   AB
 A> B
 >BB
 
	B+B&&B+	since_strc                     | y	 | j                  d      r4t        j                  | d      j                  t        j
                        S t        j                  |       S # t        t        f$ r Y yw xY w)u   since 필드 파싱 (ISO 8601)NZ%Y-%m-%dT%H:%M:%SZ)tzinfo)	endswithr   strptimer>   r   utcfromisoformat
ValueError	TypeError)rA   s    r*   parse_since_timerL   \   so    c"$$Y0DEMMU]UaUaMbb%%i00	" s   AA 
A A10A1bot_namec                    t         j                  | d      }|sg S 	 t        j                  ddd| gddd      }|j                  dk7  rg S g }|j
                  j                         j                  d	      D ]0  }|j                         }|s	 |j                  t        |             2 |S # t        $ r Y @w xY w# t        j                  t        f$ r}t        d
|  d|        g cY d}~S d}~ww xY w)u   해당 봇의 claude 프로세스 PID 목록 반환

    Args:
        bot_name: 봇 이름 (anu, dev1, dev2, dev3)

    Returns:
        PID 목록 (프로세스 없으면 빈 리스트)
     pgrepz-fzclaude.*T   )capture_outputtexttimeoutr   r   WARN: u    프로세스 체크 실패: N)r   get
subprocessrun
returncodestdoutstripsplitappendintrJ   TimeoutExpiredr&   r+   )rM   patternresultpidsr(   r4   s         r*   find_bot_processrc   j   s    %((26G	dhwi01	
 !IMM'')//5 	D::<DKKD	*		  "  %%w/ vhZ'DQCHI	sH   /B; A B; B,(B; ,	B85B; 7B88B; ;C1C,&C1,C1
since_timec                 >   	 t         j                         syt         j                  d      D ]`  }t        j                  |j                         j                  t        j                        }||k\  sFd|  d|j                  v s| dk(  s^|c S  y# t        $ r Y yw xY w)u   해당 봇의 최근 .done 파일 찾기

    Args:
        bot_name: 봇 이름
        since_time: 작업 시작 시간

    Returns:
        .done 파일 경로 (없으면 None)
    Nz*.donetz.r   )
EVENTS_DIRr0   globr   fromtimestampstatst_mtimer   rH   namer&   )rM   rd   	done_filemtimes       r*   find_recent_done_filerq      s      " $63 	%I**9>>+;+D+DVE
"xj?inn4E8I$$	%  s)   B AB 2B 
B B 	BBc                    	 t         j                         syt         j                  d      D ]H  }t        j                  |j                         j                  t        j                        }||k\  sF|c S  y# t        $ r Y yw xY w)u   해당 봇의 최근 보고서 파일 찾기

    Args:
        bot_name: 봇 이름
        since_time: 작업 시작 시간

    Returns:
        보고서 파일 경로 (없으면 None)
    Nz*.mdrf   )
REPORTS_DIRr0   rj   r   rk   rl   rm   r   rH   r&   )rM   rd   report_filerp   s       r*   find_recent_reportru      s|    !!# '++F3 	#K**;+;+;+=+F+F8<<XE
"""	#
  s#   A8 AA8 2A8 6A8 8	BBelapsed_minutesc                    |t         k  ryt        |       }|r%dddj                  t        t        |             dfS t        | |      }|rdd|j                   dfS t        | |      }|rdd|j                   dfS dd	t        |       d
fS )u   idle 전환 여부 결정

    Args:
        bot_name: 봇 이름
        since_time: 작업 시작 시간
        elapsed_minutes: 경과 시간 (분)

    Returns:
        (idle 전환 여부, 사유)
    )Fnot_timeoutFu0   still_running - 프로세스 살아있음 (PID: z, )Tu   completed - .done 발견 (u   completed - 보고서 발견 (u   timeout - 프로세스 없음, u
   분 초과)	TIMEOUT_MINUTESrc   joinmapstrrq   rn   ru   r^   )rM   rd   rv   rb   ro   rt   s         r*   should_transition_to_idler~      s     /)# H%DHSVWZ\`SaIbHccdeee &h
;I1)..1ACCC %Xz:K5k6F6F5GqIII 233G2H
SSS    c            
      ,   t               } | j                  di       }d}t        j                  t        j
                        }|j                         D ]  \  }}|j                  dd      }|j                  dd      }|dk7  r1t        |      }|st        d| d	| d
       Q||z
  }	|	j                         dz  }
t        |||
      \  }}|rQ|j                  d      }t        d| d| d| d
       d| d   |   d<   |j                  d      | d   |   d<   |dz  }t        |
      dz  dk(  st        |
      dkD  s|j                  d      }t        d| d| d|         |dkD  r(t        |       rt        d| d       |S t        d       y|S )uV   stuck 상태 봇 확인 및 자동 복구

    Returns:
        복구된 봇 수
    r-   r   statusidlesincerO   
processingrU   u   : since 파싱 실패 (ry   <   z%H:%M[WATCHDOG] u   : processing → idle (z, since rD      
   u   : 30분 초과 (since u   ) — u   개 봇 복구 완료u   ERROR: 복구 후 저장 실패)r5   rV   r   r   r   rH   itemsrL   r+   total_secondsr~   strftimer^   r@   )r6   r-   recovered_countr   rM   bot_datar   rA   rd   elapsedrv   should_idlereasonsince_shorts                 r*   check_and_recover_stuck_botsr      s    D88FBDO
,,x||
$C"jjl !h(h/LL"-	 \! &i0
6(+B9+QOP 
"!//1B6 8*o^V$--g6K;xj0GxxXcWddefg/5DL"8,.1ll;O.PDL"7+q O ?#b(A-#o2F2J(11':{8*4J;-W]^d]efgC!hH T";&77LMN
  :;r   c                  d    t        d       t               } | dk(  rt        d       t        d       y)u   1회 실행u   [WATCHDOG] 1회 실행 시작r   u   [WATCHDOG] stuck 봇 없음u   [WATCHDOG] 1회 실행 완료N)r+   r   )	recovereds    r*   run_oncer      s,    01,.IA~2301r   c                      t        d       t        dt         d       	 	 t               } | dkD  rt        d|  d       t        j                  t               9# t        $ r}t        d|        Y d}~6d}~ww xY w)	!   데몬 모드 (5분마다 반복)u   [WATCHDOG] 데몬 모드 시작u)   [WATCHDOG] 데몬 모드 시작 (간격: u   초)r   r   u   개 봇 복구u   ERROR: 예외 발생: N)r+   printDAEMON_INTERVALr   	Exceptiontimesleep)r   r4   s     r*   
run_daemonr   )  s~    23	5o5Fd
KL
	746I1}I;n=> 	

?# 
  	71!566	7s   A 	A:"A55A:c                      t        j                  d      } | j                  ddd       | j                         }|j                  rt                y t                y )NzBot status watchdog)descriptionz--daemon
store_truer   )actionhelp)argparseArgumentParseradd_argument
parse_argsdaemonr   r   )parserargss     r*   mainr   9  sH    $$1FGF

<>abD{{
r   __main__)r   N)5__doc__r   r1   osrW   sysr   r   r   r   pathlibr   environrV   r   r/   r!   ri   rs   r   rz   r   _syspathinsertutils.org_loaderr   _get_dev_short_idsr   dictr}   __annotations__updater   ImportErrorr+   r5   boolr@   rL   listr^   rc   rq   ru   floattupler~   r   r   r   r   __name__)sids   0r*   <module>r      sO  	   	  
  2 2  02GHN++KLM ~&&<=>^$N34
n%_56yq!"TYY&		N+H!&(; <ItCH~<5G5IJccVC5>)JK-6DcN6	# 	$ 		4 	D T  d
 x$ "s "tCy "JC X $+ 8 ( td{ 2T T TTY T^cdhjmdm^n TD7c 7t2$  zF w K "	s%   AE= E8+E= 8E= =FF