Ë
    áß²jK  ã            
       ó"  — U d Z ddlZddlZddlmZ ddlmZmZmZ ddl	m
Z
mZmZ ddlmZmZ dZdZed	d
dddddddœ	Zeeef   ed<   dZddddddddddœ	Zeeef   ed<   dZd ed!ee   d"efd#„Zd$ed"ee   fd%„Zd&ed'ed(ed"efd)„Z G d*„ d+«      Zy),a  File-mutation verification footers and turn-completion explanations for ``AIAgent``.

The footer tells the model (and user) when a claimed file mutation did not land; the explainer
summarises why a turn ended without a final answer. Every method resolves through ``AIAgent``'s MRO.
é    N)Úsuppress)ÚAnyÚDictÚOptional)Ú_extract_error_previewÚ_extract_file_mutation_targetsÚ#_extract_landed_file_mutation_paths)ÚFILE_MUTATING_TOOL_NAMESÚfile_mutation_result_landedu   âš ï¸� No reply: zy{model} didn't produce a reply this time, even after retries. Send `continue` to try again, or switch models with /model.z^the model provider didn't answer after all retries. Send /retry, or switch models with /model.zsstreaming stopped early and only a partial response was recovered. Send `continue` to resume from where it stopped.zano new content was produced this turn; showing recovered prior context. Send `continue` to retry.z¤the request was cancelled by a new correction on every attempt, so the turn stopped instead of retrying forever. Your last correction is queued as the next message.zŒevery provider in the fallback chain kept failing over, so the turn stopped instead of retrying forever. Send `continue` or switch provider.zfthe per-turn iteration/cost budget was exhausted before a final answer. Send `continue` to keep going.zjthe local model's context window was too small to finish. Increase the context size or use a larger model.z…the turn stopped while a tool result was still pending and the model produced no follow-up text. Send `continue` to let it summarize.)	Úempty_response_exhaustedÚ!all_retries_exhausted_no_responseÚpartial_stream_recoveryÚfallback_prior_turn_contentÚredirect_restart_limit_exceededÚrebuilt_restart_limit_exceededÚbudget_exhaustedÚ ollama_runtime_context_too_smallÚpending_tool_resultÚ_EXIT_REASON_EXPLANATIONS))Úinterrupted_during_api_callz[the request was interrupted mid-call before a reply was received. Send `continue` to retry.)Úmax_iterations_reachedz}the maximum tool-iteration limit was reached before a final answer. Send `continue` to keep going, or raise `max_iterations`.)Úerror_near_max_iterationsztan error occurred near the iteration limit before a final answer. Check the tool output above, then send `continue`.)Úrepeated_outer_errorsz”the turn kept failing with repeated errors and was stopped early instead of retrying forever. Check the errors above, then send `continue` to retry.u¥   the turn was stopped because another process was compressing this session. Your message should already be saved â€” please send it again after compression completes.u  the turn was stopped because this session was rotated by context compression and its live continuation could not be adopted. The storage itself is healthy â€” refresh the client (or start a new turn) so it picks up the new session id, then send your message again.u¬   the turn was stopped because another Hermes process took over this session. Your reply was not saved â€” wait for the other process to finish, then send your message again.u¼   the turn was stopped because session storage was busy (another Hermes process was writing to the state database). Your message should already be saved â€” please send it again in a moment.u�  the session database file was replaced while Hermes was running, so this message was not saved (a copy is kept in {home}/sessions/). Stop Hermes (`hermes {profile_arg}gateway stop`), run `hermes {profile_arg}doctor` â€” not `hermes {profile_arg}doctor --fix`, which would repair the wrong file in place â€” then start it again and send your message once more. Advanced recovery steps are in the log.u8  another Hermes process still holds an old copy of the session database's write-ahead log, so Hermes stopped writing to keep the file safe and this message was not saved (a copy is kept in {home}/sessions/). Nothing is lost. Quit every Hermes process on this profile (Desktop app, `hermes {profile_arg}gateway stop`, dashboard, cron), run `hermes {profile_arg}doctor` â€” it names any process still holding the log â€” then start Hermes again and send your message once more. Do not run `doctor --fix` or delete any state.db files while they run. Guide: {recovery_docs}u­  the turn was stopped because the state database reported structural corruption (the transcript would have been lost on restart). Freeing disk space will not help. Recovery options:
1. Run `hermes {profile_arg}doctor --fix`
2. Stop the gateway, then recover with:
   hermes {profile_arg}sessions recover --source {db_path} --inspect-only
   (if it reports recoverable) hermes {profile_arg}sessions recover --source {db_path} --output recovered-state.db
   â€” recovery snapshots the damaged file first; do NOT run `sqlite3 ... ".recover"` against the live state.db, a vulnerable sqlite3 CLI can corrupt it further
3. Restore from a backup in {backups_dir}/
Then send your message again.aT  the turn was stopped because the session search index (FTS5) is corrupt and could not be detached, so this message was not saved. The message store itself is not damaged: do not run recovery tools or restore a backup. Run `hermes {profile_arg}doctor --fix` (or restart Hermes, which repairs the index on open), then send your message again.zÓHermes couldn't save this conversation to disk, so it stopped rather than lose your messages. The disk is probably full: free some space (or fix the permissions on {home}/state.db), then send your message again.)	ÚcompressionÚcompression_closedÚ
turn_leaseÚlockedÚreplacedÚdeleted_walÚcorruptÚ	fts_indexÚdiskÚ_PERSISTENCE_CAUSE_EXPLANATIONSa   Hermes couldn't save this conversation, so it stopped rather than lose your messages. Possible causes: the drive is out of room, or another Hermes process is holding the database. Close other Hermes windows, run `hermes {profile_arg}doctor` to check storage, then send your message again.ÚpathÚtask_idÚreturnc                 óJ  — 	 ddl m} t         || |xs d«      «      }t        j
                  j                  t        j
                  j                  |«      «      S # t        $ r? t        j
                  j                  t        j
                  j                  | «      «      }Y Œƒw xY w)a  One key per on-disk target: the file tools' task-resolved absolute path, case-folded
    on case-insensitive hosts. A failure recorded as ``notes.md`` and the write that later
    lands as ``/repo/notes.md`` (or ``Notes.md`` on Windows) must meet on the same key.r   )Ú_resolve_path_for_taskÚdefault)
Útools.file_tools_pathsr(   ÚstrÚ	ExceptionÚosr$   ÚabspathÚ
expanduserÚnormcaseÚnormpath)r$   r%   r(   Úresolveds       ú:/home/ubuntu/.hermes/hermes-agent/agent/turn_explainers.pyÚ_file_mutation_identityr4   °   sy   € ð=ÝAäÑ-¨d°GÒ4H¸yÓIÓJˆô �7‰7×ÑœBŸG™G×,Ñ,¨XÓ6Ó7Ð7øô ò =Ü—7‘7—?‘?¤2§7¡7×#5Ñ#5°dÓ#;Ó<Šð=ús   ‚A ÁAB"Â!B"Úidentityc                 ó|   — 	 t        j                  | «      }|j                  |j                  fS # t        $ r Y yw xY w)zX``(mtime_ns, size)`` of the target, ``None`` when it does not exist (or cannot be read).N)r-   ÚstatÚOSErrorÚst_mtime_nsÚst_size)r5   Ústs     r3   Ú_file_stat_signaturer<   ½   s>   € ðÜ�W‰W�XÓˆð �N‰N˜BŸJ™JÐ'Ð'øô ò Ùðús   ‚/ ¯	;º;Úenv_varÚ
config_keyÚ
cache_attrc                óÒ  — 	 t         j                  j                  |«      }|� |j                  «       j	                  «       dvS t        | |d«      }|�|S 	 ddlm}  |«       xs i }t        |t        «      r|j                  d«      nd}t        |t        «      r||v rt        |j                  |«      «      }	nd}	t        | ||	«       |	S # t        $ r i }Y Œpw xY w# t        $ r Y yw xY w)ub  ``display.<config_key>`` (default True), cached per agent on ``cache_attr``.

    ``env_var`` overrides on every call and is never cached. Reads the persisted config.yaml
    so gateway and CLI share the setting; ``load_config`` is imported lazily (startup cycle,
    and tests patch it at ``hermes_cli.config``). Any failure â†’ True (safe default: on).N>   Ú0ÚnoÚoffÚfalser   )Úload_configÚdisplayT)r-   ÚenvironÚgetÚstripÚlowerÚgetattrÚhermes_cli.configrE   r,   Ú
isinstanceÚdictÚboolÚsetattr)
Úagentr=   r>   r?   ÚenvÚcachedÚ_load_configÚ_cfgÚ_displayÚenableds
             r3   Ú_display_flag_enabledrX   Æ   sä   € ðÜ�j‰j�n‰n˜WÓ%ˆØˆ?Ø—9‘9“;×$Ñ$Ó&Ð.IÐIÐIÜ˜ 
¨DÓ1ˆØÐØˆMð	ÝEÙ“>Ò' RˆDô +5°T¼4Ô*@�4—8‘8˜IÔ&ÀdˆÜ�h¤Ô%¨*¸Ñ*@Ü˜8Ÿ<™<¨
Ó3Ó4‰GàˆGÜ��z 7Ô+Øˆøô ò 	ØŠDð	ûô ò Ùðús=   ‚A C ÁC ÁC	 Á&A"C Ã	CÃC ÃCÃC Ã	C&Ã%C&c                   ó8  — e Zd ZdZddœdedeeef   dededee   d	dfd
„Z	e
deeeeef   f   d	eeeeef   f   fd„«       Zd	efd„Zd	efd„Z ej                  d«      Zeded	efd„«       Zedeeeeef   f   d	efd„«       Ze
	 ddedee   ded	efd„«       Zy)ÚTurnExplainersMixinzPFile-mutation failure footer + turn-completion explainer (see module docstring).N)r%   Ú	tool_nameÚargsÚresultÚis_errorr%   r&   c          
      ó  — |t         vryt        | dd«      }|€yt        ||«      }|syt        ||«      }|r‹t	        |||«      }	t        | dd«      }
|
�|
j                  |	«       t        | dd«      }|�Ot        |dd«      rBddlm}  ||xs d	«      €0|	D ]+  }t        t        «      5  |j                  |«       ddd«       Œ- |rA|s?t        |«      }|D ].  }t        ||«      }|j                  ||||t        |«      d
œ«       Œ0 y|r	n|D �ch c]  }t        ||«      ’Œ }}t        |j!                  «       «      D ]6  \  }}|j#                  dt        ||«      «      |v sŒ%|j%                  |d«       Œ8 y# 1 sw Y   ŒêxY wc c}w )aÕ  Record a ``write_file`` / ``patch`` outcome for the turn-end verifier.

        Failures store ``{path: {error_preview, tool, identity, stat}}`` keyed by the model's
        spelling; ``identity`` is the resolved on-disk target and ``stat`` its signature at
        failure time. A later success on the same identity (any spelling) removes the entry.
        No-op when the per-turn state dict is not initialised (tool dispatched outside ``run_conversation``).
        NÚ_turn_failed_file_mutationsÚ_turn_file_mutation_pathsÚ_checkpoint_mgrrW   Fr   )Úcontainer_backend_for_taskr)   )ÚtoolÚerror_previewr5   r7   r5   )Ú_FILE_MUTATING_TOOLSrK   r   r   r	   Úupdater*   rc   r   r,   Úrecord_agent_writer   r4   Ú
setdefaultr<   ÚlistÚitemsrH   Úpop)Úselfr[   r\   r]   r^   r%   ÚstateÚtargetsÚlandedÚlanded_pathsÚchangedÚmgrrc   Ú_pÚpreviewr$   r5   ÚpÚclearedÚinfos                       r3   Ú_record_file_mutation_resultz0TurnExplainersMixin._record_file_mutation_resultæ   s´  € ð Ô0Ñ0ØÜ˜Ð;¸TÓBˆØˆ=ØÜ0°¸DÓAˆÙØÜ,¨Y¸Ó?ˆÙÜ>¸yÈ$ÐPVÓWˆLÜ˜dÐ$?ÀÓFˆGØÐ"Ø—‘˜|Ô,ô ˜$Ð 1°4Ó8ˆCØˆ¤7¨3°	¸5Ô#AÝMÙ-¨gÒ.B¸ÓCÐKØ*ò 7˜Ü%¤iÓ0ñ 7Ø×2Ñ2°2Ô6÷7ð 7ð7ñ ™Fä,¨VÓ4ˆGØò �Ü2°4¸ÓA�Ø× Ñ  Ø%¸Ø (Ô2FÀxÓ2Pñ(õ ññ +1™,°göàô (¨¨7Õ3ðˆGð ô # 5§;¡;£=Ó1ò *‘
��dØ—8‘8˜JÔ(?ÀÀgÓ(NÓOÐSZÒZØ—I‘I˜d DÕ)ñ*÷!7ð 7üòs   Â&E5ÄFÅ5E>	Úfailedc                 ó†   — | j                  «       D ��ci c]   \  }}d|vst        |d   «      |d   k(  r||“Œ" c}}S c c}}w )a�  Drop entries whose target changed on disk since the failed call.

        The recorder only sees write_file/patch receipts; a terminal redirect or an
        execute_code write leaves none. Re-checking the stat signature at turn end keeps
        the footer from listing a file that was in fact modified later this turn. Entries
        without a snapshot (hand-built dicts) are kept as-is.
        r7   r5   )rk   r<   )rz   r$   rx   s      r3   Ú_file_mutations_still_failedz0TurnExplainersMixin._file_mutations_still_failed  sQ   € ð *0¯©«÷
Ù%˜4 Ø˜TÑ!Ô%9¸$¸zÑ:JÓ%KÈtÐTZÉ|Ò%[ð �$‰Jó
ð 	
ùó 
s   ”%=c                 ó    — t        | ddd¬«      S )zZ``display.file_mutation_verifier`` / ``HERMES_FILE_MUTATION_VERIFIER`` (a patchable seam).ÚHERMES_FILE_MUTATION_VERIFIERÚfile_mutation_verifierÚ%_file_mutation_verifier_enabled_cache©r=   r>   r?   ©rX   ©rm   s    r3   Ú_file_mutation_verifier_enabledz3TurnExplainersMixin._file_mutation_verifier_enabled(  s   € ä$ØÐ9ÐF^Ø>ô
ð 	
ó    c                 ó    — t        | ddd¬«      S )zM``display.turn_completion_explainer`` / ``HERMES_TURN_COMPLETION_EXPLAINER``.Ú HERMES_TURN_COMPLETION_EXPLAINERÚturn_completion_explainerÚ(_turn_completion_explainer_enabled_cacher�   r‚   rƒ   s    r3   Ú"_turn_completion_explainer_enabledz6TurnExplainersMixin._turn_completion_explainer_enabled/  s   € ä$ØÐ<ÐIdØAô
ð 	
r…   zE(?<![/:\w.`])(?:~/|/|[A-Za-z]:[/\\])(?:[\w.\-]+[/\\])*[\w.\-]+\.[\w]+Útextc                 óD   — |s|S | j                   j                  d„ |«      S )zÎBacktick bare file paths so the gateway's ``extract_local_files`` never auto-attaches them.

        The extractor skips inline-code spans; already-backticked paths are left alone (no double-wrap).
        c                 ó,   — d| j                  d«      › d�S )Nú`r   )Úgroup)Úms    r3   ú<lambda>z>TurnExplainersMixin._neutralize_footer_paths.<locals>.<lambda>D  s   € °1°Q·W±W¸Q³Z°LÀÐ1B€ r…   )Ú_FOOTER_PATH_REÚsub)Úclsr‹   s     r3   Ú_neutralize_footer_pathsz,TurnExplainersMixin._neutralize_footer_paths<  s&   € ñ ØˆKØ×"Ñ"×&Ñ&Ñ'BÀDÓIÐIr…   c           
      óÂ  — |sydt        |«      › d�g}t        |j                  «       «      dd }|D ][  \  }}|j                  d«      xs dj	                  «       }|j                  d«      xs d}|j                  d	|› d
|› d|xs d› �«       Œ] t        |«      t        |«      z
  }|dkD  r|j                  d|› d�«       | j                  dj                  |«      «      S )a/  Render the per-turn failed-mutation dict as a user-facing footer.

        Up to 10 paths with their first error preview, then an overflow count; "" when nothing failed.
        Every path is backtick-wrapped via ``_neutralize_footer_paths`` so protected files cannot be
        auto-delivered.
        Ú u   âš ï¸� File-mutation verifier: z• file edit(s) FAILED this turn despite any wording above that may suggest otherwise. Run `git status` or `read_file` to confirm what actually landed.Né
   re   rd   Úpatchu     â€¢ `u   ` â€” [z] rz   r   u     â€¢ â€¦ and z moreÚ
)Úlenrj   rk   rH   rI   Úappendr•   Újoin)	r”   rz   ÚlinesÚshownr$   rx   ru   rd   Ú	remainings	            r3   Ú$_format_file_mutation_failure_footerz8TurnExplainersMixin._format_file_mutation_failure_footerF  só   € ñ Øà-Ü�6‹{ˆmð ;ð;ð
ˆô �V—\‘\“^Ó$ S bÐ)ˆØò 	O‰JˆD�$Ø—x‘x Ó0Ò6°B×=Ñ=Ó?ˆGØ—8‘8˜FÓ#Ò. wˆDØ�L‰L˜7 4 &¨°¨v°R¸Ò8KÀ8Ð7LÐMÕNð	Oô ˜“K¤# e£*Ñ,ˆ	Ø�qŠ=Ø�L‰L˜>¨)¨°EÐ:Ô;à×+Ñ+¨D¯I©I°eÓ,<Ó=Ð=r…   Úturn_exit_reasonÚpersistence_causeÚmodelc                 ó�  — | syt        | «      }|j                  d«      ryt        j                  |«      }|€#t        D ]  \  }}|j                  |«      sŒ|} n |�d|v r|j                  |xs d¬«      }|€Â|dk(  r½ddlm}m}	 dd	l	m
}
 t        j                  |xs d
t        «      j                  d |«       «      j                  d |	«       «      j                  d|
«      }|dv rSddlm} ddlm} |j                  dt        |xs  |«       «      «      }|j                  dt         |«       dz  «      «      }|r	t"        |z   S dS )a  User-facing explanation for an abnormal turn ending, or "" for normal / unknown reasons.

        ``text_response(...)`` is the healthy terminal; unknown/diagnostic-only reasons (e.g.
        ``guardrail_halt``, which surfaces its own message) are not second-guessed.
        r—   Útext_responsez{model}z	The model)r¤   Úsession_persistence_failedr   )Údisplay_hermes_homeÚprofile_cli_selector)ÚSTORAGE_RECOVERY_DOCS_URLÚunknownz{home}z{profile_arg}z{recovery_docs})r    r!   )Úget_default_hermes_root)Ú_default_db_pathz	{db_path}z{backups_dir}Úbackups)r+   Ú
startswithr   rH   Ú _EXIT_REASON_PREFIX_EXPLANATIONSÚformatÚhermes_constantsr¨   r©   Úhermes_state_errorsrª   r#   Ú _PERSISTENCE_DEFAULT_EXPLANATIONÚreplacer¬   Úhermes_stater­   Ú	_NO_REPLY)r¢   r£   Údb_pathr¤   ÚreasonÚbodyÚprefixr‹   r¨   r©   rª   r¬   r­   s                r3   Ú#_format_turn_completion_explanationz7TurnExplainersMixin._format_turn_completion_explanationa  sO  € ñ  ØÜÐ%Ó&ˆØ×Ñ˜_Ô-ØÜ(×,Ñ,¨VÓ4ˆØˆ<Ü @ò ‘�˜Ø×$Ñ$ VÕ,Ø�DÙðð Ð 	¨TÑ 1Ø—;‘; UÒ%9¨k�;Ó:ˆDØˆ<˜FÐ&BÒBßRÝEô 0×3Ñ3Ø%Ò2¨Ô4Tó÷ ‘˜Ñ#6Ó#8Ó9ß‘˜Ñ*>Ó*@ÓAß‘Ð*Ð,EÓFð ð !Ð$<Ñ<ÝDÝ9à—|‘| K´°WÒ5RÑ@PÓ@RÓ1SÓT�Ø—|‘|Ø#¤SÑ)@Ó)BÀYÑ)NÓ%Oó�ñ $(Œy˜4ÑÐ/¨RÐ/r…   )NNr—   )Ú__name__Ú
__module__Ú__qualname__Ú__doc__r+   r   r   rO   r   ry   Ústaticmethodr|   r„   rŠ   ÚreÚcompiler’   Úclassmethodr•   r¡   r¼   © r…   r3   rZ   rZ   ã   s]  „ ÙZð %)ò2*Øð2*Ø$(¨¨c¨¡Nð2*Ø<?ð2*ØKOð2*à˜S‘Mð2*ð 
ó2*ðh ð
¨T°#°t¸CÀ¸H±~Ð2EÑ-Fð 
È4ÐPSÐUYÐZ]Ð_bÐZbÑUcÐPcÑKdò 
ó ð
ð
°ó 
ð
°Dó 
ð !�b—j‘jØPó€Oð ðJ¨Cð J°Cò Jó ðJð ð>¸$¸sÀDÈÈcÈÁNÐ?RÑ:Sð >ÐX[ò >ó ð>ð4 àceñ,0Øð,0Ø2:¸3±-ð,0Ø]`ð,0à	ò,0ó ñ,0r…   rZ   )rÀ   r-   rÂ   Ú
contextlibr   Útypingr   r   r   Úagent.tool_dispatch_helpersr   r   r	   Ú agent.tool_result_classificationr
   rf   r   r·   ÚEMPTY_RESPONSE_EXPLANATIONr   r+   Ú__annotations__r°   r#   r´   r4   Útupler<   rO   rX   rZ   rÅ   r…   r3   ú<module>rÍ      sB  ðòó
 
Û 	Ý ß &Ñ &÷ñ ÷ð  €	ð
Bð ð !;ð	5ð	Fð	3ð	4ð
	ð
	7ð	;ð	ñC%-Ð ˜4  S ™>ó %ðP$Ð  ð2	Fð
	4ð	ð	,ð	ð	Dð	(ð&	#ð	=ñQL3Ð   c¨3 h¡ó Lð\-ð !ð
8 #ð 
8°¸±ð 
8À#ó 
8ð( 3ð (¨8°E©?ó (ð¨Sð ¸cð Èsð ÐW[ó ÷:k0ò k0r…   