
    Pmj+                    h   U d Z ddlmZ ddlmZmZ ddlmZ g dZdZ	dZ
dZi d	d
dd
ddddddddddddddddddddd d!d"d#d$d%d&d'Zd(ed)<   d*Zd+Zd,ed-<    edh          Ze G d. d/                      ZdQd4ZdRd9Z G d: d;          ZdSd>ZdTdBZdUdEZedFdVdJZdKdLdWdOZdPS )XuT  Per-turn accounting for the interactive CLI.

Two display-only pieces live here:

*   :class:`TurnSummaryCollector` — a tiny observer that rides the existing
    ``tool_progress_callback`` feed (``tool.completed`` events already carry
    the tool name and its raw result) and tallies what a turn actually did.
    It holds **no** agent-loop state: the display layer already sees every
    tool call, so nothing new is threaded through the conversation loop.
*   :func:`format_turn_summary` — a pure formatter that turns a tally plus a
    wall-clock duration into one dim line, e.g.::

        ⋯ 12.4s · edited 2 files +18 -3 · read 4 files · ran 3 commands

    Ported from Claude Code's post-turn accounting line
    ("Edited 1 file +6 -2, read 1 file … Worked for 10s").

:func:`format_token_flow` is the spinner-side counterpart: a cumulative
token readout appended to the live elapsed timer (``↓ 1.2k tok``).

Everything in this module is pure/side-effect free apart from the
collector's own counters, which makes it directly unit-testable without a
terminal, an agent, or a network call.
    )annotations)	dataclassfield)Any)TurnSummaryCollector	TurnTallyformat_turn_summaryformat_token_flowformat_elapsedu   ⋯g       @   
write_file)editedfilefilespatch	read_file)readr   r   web_extract)r   pagepagesterminal)rancommandcommandsexecute_code)r   scriptscriptssearch_files)searchedpathpaths
web_search)zsearched the webtimetimessession_search)zsearched sessionsr#   r$   browser_navigate)browsedr   r   
skill_view)r   skillskillsskill_manage)updatedr)   r*   skills_list)zlisted skillsr#   r$   todo)r,   z	task listz
task listsdelegate_task)	delegatedtasktasksmemory)r,   r3   memorieszdict[str, tuple[str, str, str]]_VERB_GROUPSr   )r   r   r   ztuple[str, ...]_VERB_PRIORITYc                      e Zd ZU dZ ee          Zded<   dZded<   dZ	ded<   dZ
ded	<   d
Zded<   edd            ZdS )r   z@What a single turn did, as observed from the tool-progress feed.)default_factoryzdict[str, dict[str, int]]verbsr   intother_toolslines_addedlines_removedFboolhas_line_deltasreturnc                v    t          d | j                                        D                       }|| j        z   S )Nc              3  X   K   | ]%}t          |                                          V  &d S N)sumvalues).0nounss     8/home/thesage/.hermes/hermes-agent/agent/turn_summary.py	<genexpr>z(TurnTally.total_tools.<locals>.<genexpr>i   s2      KKec%,,..))KKKKKK    )rD   r9   rE   r;   )selfcounteds     rH   total_toolszTurnTally.total_toolsg   s:    KKtz7H7H7J7JKKKKK)))rJ   N)r@   r:   )__name__
__module____qualname____doc__r   dictr9   __annotations__r;   r<   r=   r?   propertyrM    rJ   rH   r   r   X   s         JJ (-uT'B'B'BEBBBBKKM "O!!!!* * * X* * *rJ   r   diffstrr@   tuple[int, int]c                    dx}}|                                  D ]b}|                    d          s|                    d          r-|                    d          r|dz  }H|                    d          r|dz  }c||fS )zCount added/removed lines in unified-diff text.

    File headers (``+++``/``---``) are excluded so a one-line edit does not
    read as three additions.
    r   z+++z---+   -)
splitlines
startswith)rV   addedremovedlines       rH   _count_diff_linesrb   m   s     EG!!  ??5!! 	T__U%;%; 	??3 	QJEE__S!! 	qLG'>rJ   	tool_nameresultr   tuple[int, int] | Nonec                   | t           vrdS |}t          |t                    rY|                                }|                    d          sdS 	 ddl}|                    |d          }n# t          $ r Y dS w xY wt          |t                    sdS |	                    d          }t          |t                    r|                                sdS t          |          \  }}|dk    r|dk    rdS ||fS )u   Pull (added, removed) from a tool result, or None when unavailable.

    Only tools that already report a diff in their result payload are
    inspected — we never shell out to git and never re-read files to
    synthesise a delta.
    N{r   F)strictrV   )_DIFF_RESULT_TOOLS
isinstancerW   stripr^   jsonloads	ExceptionrR   getrb   )rc   rd   payloadtextrl   rV   r_   r`   s           rH   _extract_line_deltasrr   ~   s    ***tG'3 }}s## 	4	KKK
 jjej44GG 	 	 	44	gt$$ t;;vDdC   

 t&t,,NE7 zzgllt'>s   A+ +
A98A9c                  R    e Zd ZdZddZddZdddddZedd            ZddZ	dS )r   a  Accumulate per-turn tool tallies from the tool-progress feed.

    Wired into the CLI's existing ``_on_tool_progress`` handler: the display
    layer already receives every ``tool.completed`` event with the tool name
    and raw result, so no agent-loop bookkeeping is added.
    r@   Nonec                ,    t                      | _        d S rC   r   _tallyrK   s    rH   __init__zTurnSummaryCollector.__init__   s    kkrJ   c                ,    t                      | _        dS )z+Start a fresh turn (drops any prior tally).Nrv   rx   s    rH   beginzTurnSummaryCollector.begin   s    kkrJ   NF)rd   is_errorrc   
str | Nonerd   r   r|   r>   c                  |r|rdS |                     d          rdS t                              |          }|| j        xj        dz  c_        dS |\  }}}| j        j                            |i           }|                    |d          dz   ||<   |t          k    rOt          ||          }	|	?|	\  }
}| j        xj	        |
z  c_	        | j        xj
        |z  c_
        d| j        _        dS dS dS )zRecord one completed tool call.

        Failed calls are skipped: a summary claiming "edited 2 files" when one
        write was denied would be exactly the over-claim the file-mutation
        verifier exists to catch.
        N_r[   r   T)r^   r5   ro   rw   r;   r9   
setdefault
_EDIT_VERBrr   r<   r=   r?   )rK   rc   rd   r|   groupverb	_singularpluralrG   deltasr_   r`   s               rH   record_toolz TurnSummaryCollector.record_tool   s     	H 	F$$ 	F  ++=K##q(##F"'i!,,T266		&!,,q0f:))V<<F!!'w''50''))W4)).2+++ !!rJ   r   c                    | j         S rC   )rw   rx   s    rH   tallyzTurnSummaryCollector.tally   s
    {rJ   elapsed_secondsfloatrW   c                ,    t          || j                  S )zBRender this turn's summary line (see :func:`format_turn_summary`).)r	   rw   )rK   r   s     rH   renderzTurnSummaryCollector.render   s    "?DK@@@rJ   )r@   rt   )rc   r}   rd   r   r|   r>   r@   rt   )r@   r   )r   r   r@   rW   )
rN   rO   rP   rQ   ry   r{   r   rT   r   r   rU   rJ   rH   r   r      s         " " " "" " " " "3 "3 "3 "3 "3 "3H    XA A A A A ArJ   r   secondsr   c                    | dk     rd} | dk     r| ddS t          t          t          |                     d          \  }}| d|ddS )z?Format a wall-clock duration compactly (``12.4s`` / ``2m05s``).r   g        <   .1fsm02d)divmodr:   round)r   minutesrests      rH   r   r      sf    {{||     3uW~~..33MGT#######rJ   countr:   plural_nounc                    | dk    ri|}|                     d          r|dd         dz   }n?|                     d          r|dd         }n|                     d          r
|dd	         }d
| S |  d| S )z<Return ``"1 file"`` / ``"3 files"`` from a plural noun form.r[   iesNysesr   z1  )endswith)r   r   singulars      rH   
_pluralizer      s    zz&& 	("3B3'#-HH!!%(( 	("3B3'HH!!#&& 	("3B3'HH##k###rJ   r   	list[str]c                     t           j                                                  } fdt          D             }|d |D             z  }|S )zCVerbs in render order: priority verbs first, then first-seen order.c                &    g | ]}|j         v |S rU   )r9   )rF   vr   s     rH   
<listcomp>z"_ordered_verbs.<locals>.<listcomp>   s%    <<<A1+;+;a+;+;+;rJ   c                $    g | ]}|t           v|S rU   )r6   )rF   r   s     rH   r   z"_ordered_verbs.<locals>.<listcomp>   s"    :::Q!>"9"9q"9"9"9rJ   )listr9   keysr6   )r   seenrankeds   `  rH   _ordered_verbsr      sU      ""##D<<<<<<<F
::$::::FMrJ   )max_segmentsr   TurnTally | Noner   c                  |t                      }g }t          |          D ]}|j        |         }d |                                D             }|s0| dd                    |           }|t
          k    r|j        r|d|j         d|j         z  }|	                    |           |j
        r+|	                    dt          |j
        d                      |s|j        d	k    r| t          k     rd
S |d	k    r7t          |          |k    r$t          |          |z
  }|d|         d| dgz   }t          |           g|z   }	t            dd                    |	          z   S )u   Render the per-turn accounting line, or ``""`` when there's nothing to say.

    Pure function — no config lookups, no terminal access, no I/O. Gating
    (``display.turn_summary``, quiet mode, CLI-only) is the caller's job.
    Nc                8    g | ]\  }}|t          ||          S rU   )r   )rF   r   r   s      rH   r   z'format_turn_summary.<locals>.<listcomp>  s,    WWW}vuQVWE6**WWWrJ   r   z, z +z -zcalled toolsr    rZ   z moreu    · )r   r   r9   itemsjoinr   r?   r<   r=   appendr;   r   rM   _MIN_TOOLLESS_SECONDSlenr   SUMMARY_PREFIX)
r   r   r   segmentsr   rG   partssegmenthiddenpiecess
             rH   r	   r	      s    }Hu%% ! !D!WWWWW 	..DIIe,,..:%"7FE-FF1DFFFG     LJ*U->"H"HJJKKK )Q..?EZ3Z3ZraCMML88X-M\M*.?&.?.?.?-@@_--.9F&++f"5"555rJ   u   ↓)arrowoutput_tokensr   c                   	 t          |           }n# t          t          f$ r Y dS w xY w|dk    rdS |dk     r| d| dS |dk     r| d|dz  ddS | d|dz  dd	S )
u   Render cumulative turn tokens for the live spinner (``↓ 1.2k tok``).

    Returns ``""`` for a non-positive count so the spinner shows nothing
    rather than a misleading ``↓ 0 tok`` before the first API response lands.
    r   r   i  r   z toki@B r   zk tokzM tok)r:   	TypeError
ValueError)r   r   r   s      rH   r
   r
   &  s    M""z"   rrzzrt||%%%%%%%y11%$,1111122ei'22222s    ''N)rV   rW   r@   rX   )rc   rW   rd   r   r@   re   )r   r   r@   rW   )r   r:   r   rW   r@   rW   )r   r   r@   r   )r   r   r   r   r   r:   r@   rW   )r   r   r   rW   r@   rW   )rQ   
__future__r   dataclassesr   r   typingr   __all__r   r   _MAX_SEGMENTSr5   rS   r   r6   	frozensetri   r   rb   rr   r   r   r   r   r	   r
   rU   rJ   rH   <module>r      s    2 # " " " " " ( ( ( ( ( ( ( (            1-1(1 *1 ,	1
 .1 01 11 71 <1 41 -1 21 51 21 31  /!1    ( 
 #< ; ; ; ; Yy))  * * * * * * * *(   "" " " "J9A 9A 9A 9A 9A 9A 9A 9Ax$ $ $ $$ $ $ $    &	$6 $6 $6 $6 $6 $6N ;@ 3 3 3 3 3 3 3 3rJ   