
    `gjF                    4   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Zddl	m
Z
 ddlmZmZmZmZ  ej                   e      ZdZdZdZd	Zd
ZdZdZddZddZddZddZ G d d      ZddZ	 	 d	 	 	 	 	 	 	 ddZ d dZ!	 	 	 	 d!dZ"	 	 	 	 d"dZ#efd#dZ$y)$u&  Live, tail-able transcripts for delegated subagents.

Every ``delegate_task`` dispatch creates one append-only, human-readable log
per child under::

    <hermes_home>/cache/delegation/live/<delegation_id>/task-<n>.log

The files are pre-created with a header at dispatch time (so ``tail -f``
attaches immediately) and then stream one line per child event: assistant
text, thinking, tool calls, tool results, and lifecycle markers. The paths
are returned from ``delegate_task`` so the parent agent (or the user) can
watch a child work instead of waiting blind for the consolidated summary.

Placement under ``cache/delegation`` is deliberate: that directory is
mounted read-only into remote terminal backends (Docker/Modal/SSH) via
``credential_files._CACHE_DIRS``, so the logs are readable from any backend.

Design constraints:

* **Never raise into the agent loop.** Every write is wrapped; the first
  failure disables the writer and degrades to a debug log.
* **Survive child crashes.** Files are opened in append mode per write —
  no long-lived handle to lose, every event is flushed when written.
* **Side-channel only.** Nothing here touches message content, so prompt
  caching is unaffected.
* **No config knobs.** Retention is a module constant (7 days), pruned
  opportunistically on each new dispatch.
    )annotationsN)Path)AnyDictListOptional   iX  i,     i    i  c                 &    ddl m}   | dd      dz  S )zDRoot directory for live transcripts (profile-safe, never ~/.hermes).r   get_hermes_dirzcache/delegationdelegation_cachelive)hermes_constantsr   r   s    L/root/.hermes/venv/lib/python3.12/site-packages/tools/delegation_live_log.pylive_transcript_rootr   =   s    /,.@AFJJ    c                 J    dt        j                         j                  dd  S )zHSame shape as async_delegation's ids so the dir name matches the handle.deleg_N   )uuiduuid4hex r   r   new_live_delegation_idr   D   s#    DJJL$$Ra()**r   c                    t        | xs d      }dj                  |j                               }t        |      |kD  rt        |      |z
  }|d| d| dz   }|S )zACollapse to a single line and truncate with an elided-chars note.  Nu    …(+z chars))strjoinsplitlen)textlimitsomitteds       r   	_one_liner(   I   sY    DJBAA
1v~a&5.fuI&	11Hr   c                R    | s| S 	 ddl m}  || d      xs dS # t        $ r Y yw xY w)u(  Mask credentials before anything reaches the transcript file.

    These logs live under ``cache/delegation``, which ``delegate_tool`` mounts
    READ-ONLY into remote terminal backends — so every line written here is
    readable from inside the sandbox. The events rendered here carry exactly
    the data that tends to hold secrets: tool args (a bearer header on a
    curl), tool results (a ``.env`` dump, a provider error echoing the key
    back) and streamed assistant text. Every other sink for that data already
    routes through this same redactor — search results via
    ``redact_sensitive_text``, terminal output via ``redact_terminal_output``
    — so a transcript that skipped it is the one place the operator's keys
    land in plaintext.

    ``force=True``: this is a safety boundary, so it must redact even when the
    global toggle is off. Withholds the line rather than emitting raw text if
    the redactor is somehow unavailable — losing a debug line costs less than
    writing a live credential into a sandbox-readable file.
    r   )redact_sensitive_textT)forcer   z&[line withheld: redaction unavailable])agent.redactr*   	Exception)r$   r*   s     r   _redactr.   S   s8    & 86$T6<"< 878s    	&&c                      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dZ	dd	Z
dd
ZddZ	 	 d	 	 	 	 	 	 	 ddZddZy)LiveTranscriptWriterzAppend-only human-readable event log for ONE subagent task.

    All methods are best-effort: the first write failure flips ``_ok`` off
    and subsequent calls become no-ops (debug-logged). Never raises.
    Nc           	        || _         || _        d| _        t        j                         | _        g | _        d| _        	 ||n	t               }||z  }|j                  dd       |d| dz  | _
        dd| d| d	t        t        |t                     d
t        j                  d       ddg}| j                  j!                  dj#                  |      dz   d       | j%                  ddt        |t              z   |rdt        |t               ndz          y # t&        $ r0}	t(        j+                  d|||	       d| _        d | _
        Y d }	~	y d }	~	ww xY w)NTr   )parentsexist_okztask-z.logz'=== Hermes subagent live transcript ===zdelegation: z	   task: zgoal: z	started: %Y-%m-%d %H:%M:%Su=   (append-only; streams while the subagent runs — tail -f me)z(========================================
utf-8encodinguserz	kickoff: z | context: r   z,Live transcript init failed (%s task %s): %sF)delegation_id
task_index_ok	threadingLock_lock_stream_buf_stream_lenr   mkdirpathr.   r(   _KICKOFF_MAXtimestrftime
write_textr!   eventr-   loggerdebug)
selfr:   r;   goalcontextrootbasedheaderexcs
             r   __init__zLiveTranscriptWriter.__init__w   s_   *$^^%
&(	 ,D2F2HD}$AGGD4G0()eJ<t,D(DDI9}oYzlC 4!>?@ADMM*=>?@O	F II  6!2T!9G LJJv{Yt\-JJOVL7L)I(JK\^ ` a 	LLG&
C9DHDII		s   CD 	E$&EEc                   | j                   r| j                  yt        j                  d       d|ddt	        |       d}	 | j
                  5  t        | j                  dd	      5 }|j                  |       ddd       ddd       y# 1 sw Y   xY w# 1 sw Y   yxY w# t        $ r2}d
| _         t        j                  d| j                  |       Y d}~yd}~ww xY w)u>   Append one ``HH:MM:SS role ⟩ text`` line. Flushed per event.Nz%H:%M:%Sr   z<9z| r5   ar6   r7   Fz%Live transcript write failed (%s): %s)r<   rC   rE   rF   r.   r?   openwriter-   rI   rJ   )rK   roler$   linefhrR   s         r   rH   zLiveTranscriptWriter.event   s    xx499, --
+,Ad2YbrJ	R # $))S7; #rHHTN## ## ## #
  	RDHLL@$))SQQ	RsN   B# B(B:BB# B	BB B#  B# #	C,(CCc                N    t        |t              }|r| j                  d|       y y )N	assistant)r(   _ASSISTANT_MAXrH   rK   r$   ts      r   assistant_textz#LiveTranscriptWriter.assistant_text   s$    dN+JJ{A& r   c                N    t        |t              }|r| j                  d|       y y )Nthink)r(   _THINKING_MAXrH   r^   s      r   thinkingzLiveTranscriptWriter.thinking   s$    dM*JJw" r   c                ~    | j                          t        |t              }| j                  dd|xs d d| d       y )Ntoolz-> ?())flush_streamr(   	_ARGS_MAXrH   )rK   nameargs_previewargss       r   
tool_startzLiveTranscriptWriter.tool_start   s;    y1

6SQtfA67r   c                    |rdnd}d}	 |dt        |      dd}| j                  d|xs d d| | d	t	        |t
                      y # t        t        f$ r Y @w xY w)
NERRORokr   r   z.1fr&   resultrg   z: )float	TypeError
ValueErrorrH   r(   _RESULT_MAX)rK   rl   rs   durationis_errorstatusdurs          r   tool_resultz LiveTranscriptWriter.tool_result   s    $$	#%/#.a0 	

8}AfXcU" )&+ >?A 	B :& 		s   A AAc                d    | j                          | j                  dt        |t                     y)z=Lifecycle marker: start / final / error / interrupt / budget.finalN)rj   rH   r(   r]   rK   r$   s     r   markerzLiveTranscriptWriter.marker   s#    

7IdN;<r   c                    |r| j                   sy| j                  j                  |       | xj                  t	        |      z  c_        | j                  t
        k\  r| j                          yy)z:Buffer streamed assistant reply text; flushed as one line.N)r<   r@   appendrA   r#   _STREAM_BUFFER_FLUSH_CHARSrj   )rK   deltas     r   add_stream_deltaz%LiveTranscriptWriter.add_stream_delta   sW    DHH&CJ&99 :r   c                    | j                   sy dj                  | j                         }g | _         d| _        | j                  |       y )Nr   r   )r@   r!   rA   r`   r   s     r   rj   z!LiveTranscriptWriter.flush_stream   s@    wwt''(D!r   c           
        t        |xs d      }|dk(  r)| j                  t        |xs d      |r|       y|       y|dk(  rY| j                  t        |xs d      |j                  d      |j                  d      t	        |j                  d                   y|dk(  r#| j                  t        |xs |xs d             y|d	k(  r| j                  t        |xs d             y|d
k(  r| j                  t        |xs d             y|dk(  r!| j                  dt        |t                     y|dk(  r| j                          |j                  dd      }|j                  d      }d| g}	||	j                  d| d       |j                  d      xs |}
|
r"|	j                  dt        |
t                      | j                  dj                  |	             yy)a  Map a child tool_progress_callback event onto transcript lines.

        Mirrors the shapes emitted by agent/tool_executor.py,
        agent/conversation_loop.py, and tools/delegate_tool._run_single_child.
        Unknown events are ignored. Never raises (event() swallows I/O).
        r   ztool.startedztool.completedrs   rx   ry   )rs   rx   ry   	_thinkingzreasoning.availablezsubagent.textzsubagent.startstartzsubagent.completerz   rg   duration_secondszstatus=Nz	duration=r&   summaryz	summary: r   )r    ro   r|   getboolrd   r   rH   r(   rD   rj   r   rw   r   r!   )rK   
event_type	tool_namepreviewrn   kwargsetrz   r{   partsr   s              r   observezLiveTranscriptWriter.observe   s    !r"OOC	R0W'O$O##IO$zz(+J/fjj45	   ; MM#i878b9:((MM#gm,-?"!!#gm"45##JJw	'< @A&&ZZ#.F**/0Cvh'(EyQ/0jj+6wGy7K)H(IJKKK( 'r   c                V   d|j                  dd       g}|j                  d      }|r|j                  d|        |dk(  r|j                  d       |j                  d      r%|j                  d	t        |d   t                      | j	                  d
j                  |             y)zTerminal marker from the aggregated result entry.

        Adds exit-reason detail the subagent.complete event doesn't carry
        (budget exhaustion via exit_reason=max_iterations, errors, etc.).
        zend status=rz   rg   exit_reasonzexit_reason=max_iterationsz(iteration budget exhausted)errorzerror: r   N)r   r   r(   rw   r   r!   )rK   entryr   r   s       r   finalizezLiveTranscriptWriter.finalize  s     uyy3789:ii.LL<}56**LL7899WLL79U7^[#I"JKLCHHUO$r   NN)
r:   r    r;   intrL   r    rM   Optional[str]rN   zOptional[Path])rX   r    r$   r    returnNone)r$   r    r   r   )N)rl   r    rm   r   r   r   )NNF)
rl   r    rs   r   rx   r   ry   r   r   r   )r   r    r   r   )r   r   NNN)r   r   r   r   r   r   rn   r   r   r   r   r   )r   zDict[str, Any]r   r   )__name__
__module____qualname____doc__rS   rH   r`   rd   ro   r|   r   r   rj   r   r   r   r   r   r0   r0   p   s     HL'6DBR&'
#
8
 48;@
B!
B48
BEI
B= " 9=15')')+.')AD')IM')R%r   r0   c                2     d fd	} fd}||_         |S )us  Wrap a child's tool_progress_callback so events also land in the log.

    ``inner_cb`` may be None (no parent display) — the wrapper still records.
    Writer failures never propagate; inner callback behavior is unchanged
    (its own exceptions are handled by callers exactly as before).
    Preserves the ``_flush`` attribute contract used by _run_single_child.
    c                    	  j                   | |||fi |  | |||fi | y y # t        $ r }t        j                  d|       Y d }~4d }~ww xY w)Nz"Live transcript observe failed: %s)r   r-   rI   rJ   )r   r   r   rn   r   rR   inner_cbwriters         r   _cbz#wrap_progress_callback.<locals>._cb!  sh    	DFNN:y'4J6J ZGTDVD    	DLL=sCC	Ds   ) 	AAAc                     	 j                          t        dd       } t        |       r |         y y # t        $ r Y ,w xY w)N_flush)rj   r-   getattrcallable)inner_flushr   r   s    r   r   z&wrap_progress_callback.<locals>._flush)  sJ    	! h$7K M !  		s   4 	A A r   )r   )r   r   r   r   s   ``  r   wrap_progress_callbackr     s    E CJJr   c                R   t        |       }	 t                	 |xs
 t               }g }g }t	        |       D ]  \  }}t        ||t        |j                  dd            |j                  d      xs |      }	|j                  |	j                  |	nd       |	j                  m|j                  t        |	j                                |s	ddg|z  g fS t        || |       |||fS # t        $ r Y w xY w# t        $ r)}
t        j                  d|
       ddg|z  g fcY d}
~
S d}
~
ww xY w)a  Create one pre-headered writer per task + a manifest.json.

    Returns ``(delegation_id, writers, paths)``. On any top-level failure
    returns ``(None, [None]*n, [])`` so delegation proceeds untouched.
    Also opportunistically prunes stale live dirs (retention).
    rL   r   rM   )rM   Nz#Live transcript creation failed: %s)r#   prune_stale_live_dirsr-   r   	enumerater0   r    r   r   rC   _write_manifestrI   rJ   )	task_listrM   r:   ndeleg_idwriterspathsir_   wrR   s              r   create_live_transcriptsr   8  s.    	IA$ <$:$<8:i( 	*DAq$!Svr!23i(3GA NN 21=vv!S[)	* $!R'')U3%''#  $  $:C@dVaZ##$s;   
C% B	C4 "0C4 C4 %	C10C14	D&=D!D&!D&c                "    t               | z  dz  S )Nzmanifest.json)r   )r:   s    r   _manifest_pathr   ]  s    !M1OCCr   c                   	 | t        j                  d      t        |      t        |      D cg c]D  \  }}|t	        t        |j                  dd            d d       |t        |      k  r||   nd ddF c}}d}t        |       j                  t        j                  |dd	
      d       y c c}}w # t        $ r }t        j                  d|       Y d }~y d }~ww xY w)Nr4   rL   r   r   running)indexrL   logrz   )r:   started
task_counttasks   Findentensure_asciir6   r7   z)Live transcript manifest write failed: %s)rE   rF   r#   r   r.   r    r   r   rG   jsondumpsr-   rI   rJ   )r:   r   r   r   r_   manifestrR   s          r   r   r   a  s    G*}}%89i. &i0 Aq  $Cfb(9$:4C$@A'(3u:~584'
	
& 	}%00JJx> 	1 	
$  G@#FFGs)   -B7 A	B188B7 1B7 7	C  CC c                   | sy	 t        |       }t        j                  |j                  d            }|D ci c]%  }t	        |t
              s|j                  d      |' }}|j                  dg       D ]c  }|j                  |j                  d            }|&|j                  d|j                  d            |d<   |j                  d      s\|d   |d<   e t        j                  d	      |d
<   |j                  t        j                  |dd      d       yc c}w # t        $ r }t        j                  d|       Y d}~yd}~ww xY w)zABest-effort per-task status update once the batch has aggregated.Nr6   r7   r;   r   r   rz   r   r4   	completedr   Fr   z*Live transcript manifest update failed: %s)r   r   loads	read_text
isinstancedictr   rE   rF   rG   r   r-   rI   rJ   )r:   resultsmpr   rby_indextaskrR   s           r   update_manifest_statusesr   ~  s0    HM*::bllGl<=4;Sqz!T?RAEE,'*SSLL"- 	;DTXXg./A}!"x(1C!DX55'*+M*:D'	; !%.A B
djj!%H& 	 	( T  HA3GGHs;   4D$ DD#9D$ 5D$ AD$ D$ $	E-EEc                   d}	 t               }|j                         syt        j                         | dz  z
  }|j                         D ]L  }	 |j                         r9|j	                         j
                  |k  rt        j                  |d       |dz  }N 	 |S # t        $ r Y ]w xY w# t        $ r!}t        j                  d|       Y d}~|S d}~ww xY w)z}Remove live/<delegation_id> dirs older than the retention window.

    Returns how many were removed. Fully best-effort.
    r   iQ T)ignore_errors   z"Live transcript pruning failed: %sN)r   is_dirrE   iterdirstatst_mtimeshutilrmtreeOSErrorr-   rI   rJ   )max_age_daysremovedrN   cutoffchildrR   s         r   r   r     s    
 G@#%{{}|e33\\^ 	E<<>ejjl&;&;f&DMM%t<qLG		 N	   @93??N@sA   B* -B* A	BB* 	B'$B* &B''B* *	C3CC)r   r   )r   r    )r$   r   r%   r   r   r    )r$   r    r   r    )r   r0   r   )r   List[Dict[str, Any]]rM   r   r:   r   r   zEtuple[Optional[str], List[Optional[LiveTranscriptWriter]], List[str]])r:   r    r   r   )r:   r    r   r   r   z	List[str]r   r   )r:   r   r   r   r   r   )r   r   r   r   )%r   
__future__r   r   loggingr   r=   rE   r   pathlibr   typingr   r   r   r   	getLoggerr   rI   LIVE_RETENTION_DAYSr]   rc   rk   rw   rD   r   r   r   r(   r.   r0   r   r   r   r   r   r   r   r   r   <module>r      s  : #        , ,			8	$  
 	
 " K+
8:e% e%PD "#'"$#"$"$ !"$ K	"$JDG$G)-G:H&:H?CH, /B r   