§
    øžyjA0  ã                  ó  — d Z ddlmZ ddlZddlZddlZddlmZ ddlm	Z	 ddl
mZ ddlmZmZmZmZmZ eeeef         ZdZdZ G d	„ d
¦  «        Z e¦   «         Zd-d„Zd.d„Zd/d„Zd0d„Zddœd1d„Zd2d „Zd3d"„Zd4d#„Z d5d)„Z!d6d+„Z"g d,¢Z#dS )7u›  Cross-agent file state coordination.

Prevents mangled edits when concurrent subagents (same process, same
filesystem) touch the same file. Complements the single-agent path-overlap
check in ``run_agent._should_parallelize_tool_batch`` â€” this module catches
the case where subagent B writes a file that subagent A already read, so
A's next write would overwrite B's changes with stale content.

Design
------
A process-wide singleton ``FileStateRegistry`` tracks, per resolved path:

  * per-agent read stamps: {task_id: {path: (mtime, read_ts, partial)}}
  * last writer globally: {path: (task_id, write_ts)}
  * per-path ``threading.Lock`` for readâ†’modifyâ†’write critical sections

Three public hooks are used by the file tools:

  * ``record_read(task_id, path, *, partial)`` â€” called by read_file
  * ``note_write(task_id, path)`` â€” called after write_file / patch
  * ``check_stale(task_id, path)`` â€” called BEFORE write_file / patch

Plus ``lock_path(path)`` â€” a context-manager returning a per-path lock to
wrap the whole readâ†’modifyâ†’write block. And ``writes_since(task_id,
since_ts, paths)`` for the subagent-completion reminder in delegate_tool.

All methods are no-ops when ``HERMES_DISABLE_FILE_STATE_GUARD=1`` is set.

This module is intentionally separate from ``_read_tracker`` in
``file_tools.py`` â€” that tracker is per-task and handles consecutive-read
loop detection, which is a different concern.
é    )ÚannotationsN)Údefaultdict)Úcontextmanager)ÚPath)ÚDictÚIterableÚListÚOptionalÚTuplei   c                  óx   — e Zd ZdZd!d„Zd"d„Zed#d	„¦   «         Zd
ddœd$d„Zddœd%d„Z	d&d„Z
d'd„Zd(d„Zd!d „ZdS ))ÚFileStateRegistryz4Process-wide coordinator for cross-agent file edits.ÚreturnÚNonec                ó´   — t          t          ¦  «        | _        i | _        i | _        t          j        ¦   «         | _        t          j        ¦   «         | _        d S ©N)	r   ÚdictÚ_readsÚ_last_writerÚ_path_locksÚ	threadingÚLockÚ
_meta_lockÚ_state_lock©Úselfs    ú6/home/ragecks/.hermes/hermes-agent/tools/file_state.pyÚ__init__zFileStateRegistry.__init__>   sF   € Ý7BÅ4Ñ7HÔ7HˆŒØ:<ˆÔØ68ˆÔÝ#œ.Ñ*Ô*ˆŒÝ$œ>Ñ+Ô+ˆÔÐÐó    ÚresolvedÚstrúthreading.Lockc                óº   — | j         5  | j                             |¦  «        }|€t          j        ¦   «         }|| j        |<   |cd d d ¦  «         S # 1 swxY w Y   d S r   )r   r   Úgetr   r   ©r   r   Úlocks      r   Ú	_lock_forzFileStateRegistry._lock_forF   s¦   € ØŒ_ð 	ð 	ØÔ#×'Ò'¨Ñ1Ô1ˆDØˆ|Ý ”~Ñ'Ô'�Ø-1�Ô  Ñ*Øð	ð 	ð 	ð 	ñ 	ô 	ð 	ð 	ð 	ð 	ð 	ð 	øøøð 	ð 	ð 	ð 	ð 	ð 	s   ˆ;AÁAÁAc              #  óÀ   K  — |                       |¦  «        }|                     ¦   «          	 dV — |                     ¦   «          dS # |                     ¦   «          w xY w)uÃ   Acquire the per-path lock for a readâ†’modifyâ†’write section.

        Same process, same filesystem â€” threads on the same path serialize.
        Different paths proceed in parallel.
        N)r&   ÚacquireÚreleaser$   s      r   Ú	lock_pathzFileStateRegistry.lock_pathN   sW   è è € ð �~Š~˜hÑ'Ô'ˆØ�Š‰Œˆð	ØˆEˆEˆEà�LŠL‰NŒNˆNˆNˆNøˆD�LŠL‰NŒNˆNˆNøøøs   ­A ÁAFN)ÚpartialÚmtimeÚtask_idr+   Úboolr,   úOptional[float]c               ó|  — t          ¦   «         rd S |€2	 t          j                             |¦  «        }n# t          $ r Y d S w xY wt          j        ¦   «         }| j        5  | j        |         }t          |¦  «        |t          |¦  «        f||<   t          |t          ¦  «         d d d ¦  «         d S # 1 swxY w Y   d S r   )Ú	_disabledÚosÚpathÚgetmtimeÚOSErrorÚtimer   r   Úfloatr.   Ú	_cap_dictÚ_MAX_PATHS_PER_AGENT)r   r-   r   r+   r,   ÚnowÚagent_readss          r   Úrecord_readzFileStateRegistry.record_read]   s
  € õ ‰;Œ;ð 	ØˆFØˆ=ðÝœ×(Ò(¨Ñ2Ô2��øÝð ð ð Ø��ðøøøåŒi‰kŒkˆØÔð 	9ð 	9Øœ+ gÔ.ˆKÝ%*¨5¡\¤\°3½¸W¹¼Ð$FˆK˜Ñ!Ý�kÕ#7Ñ8Ô8Ð8ð	9ð 	9ð 	9ñ 	9ô 	9ð 	9ð 	9ð 	9ð 	9ð 	9ð 	9ð 	9øøøð 	9ð 	9ð 	9ð 	9ð 	9ð 	9s"   ”4 ´
AÁAÁAB1Â1B5Â8B5)r,   c               óÀ  — t          ¦   «         rdS |€2	 t          j                             |¦  «        }n# t          $ r Y dS w xY wt          j        ¦   «         }| j        5  ||f| j        |<   t          | j        t          ¦  «         t          |¦  «        |df| j        |         |<   t          | j        |         t          ¦  «         ddd¦  «         dS # 1 swxY w Y   dS )uÊ   Record a successful write.

        Updates the global last-writer map AND this agent's own read stamp
        (a write is an implicit read â€” the agent now knows the current
        content).
        NF)r1   r2   r3   r4   r5   r6   r   r   r8   Ú_MAX_GLOBAL_WRITERSr7   r   r9   )r   r-   r   r,   r:   s        r   Ú
note_writezFileStateRegistry.note_writer   s?  € õ ‰;Œ;ð 	ØˆFØˆ=ðÝœ×(Ò(¨Ñ2Ô2��øÝð ð ð Ø��ðøøøåŒi‰kŒkˆØÔð 	Bð 	BØ+2°C¨.ˆDÔ˜hÑ'Ý�dÔ'Õ)<Ñ=Ô=Ð=å.3°E©l¬l¸CÀÐ-GˆDŒK˜Ô  Ñ*Ý�d”k 'Ô*Õ,@ÑAÔAÐAð	Bð 	Bð 	Bñ 	Bô 	Bð 	Bð 	Bð 	Bð 	Bð 	Bð 	Bð 	Bøøøð 	Bð 	Bð 	Bð 	Bð 	Bð 	Bs"   ”4 ´
AÁAÁA'CÃCÃCúOptional[str]c           	     ó@  — t          ¦   «         rdS | j        5  | j                             |i ¦  «                             |¦  «        }| j                             |¦  «        }ddd¦  «         n# 1 swxY w Y   |€|€dS 	 t
          j                             |¦  «        }n# t          $ r Y dS w xY w|�K|\  }}||k    r@|€|› d|›d�S |d         }||k    r(|› d|›dt          |¦  «        › dt          |¦  «        › d�S |�|\  }	}
}||	k    r|› d�S |r|› d	�S |€|› d
�S dS )uÆ  Return a model-facing warning if this write would be stale.

        Three staleness classes, in order of severity:

          1. Sibling subagent wrote this file after this agent's last read.
          2. External/unknown change (mtime differs from our last read).
          3. Agent never read the file (write-without-read).

        Returns ``None`` when the write is safe.  Does not raise â€” callers
        decide whether to block or warn.
        Nz" was modified by sibling subagent zg but this agent never read it. Read the file before writing to avoid overwriting the sibling's changes.é   z at u%    â€” after this agent's last read at z". Re-read the file before writing.zs was modified since you last read it on disk (external edit or unrecorded writer). Re-read the file before writing.zi was last read with offset/limit pagination (partial view). Re-read the whole file before overwriting it.zS was not read by this agent. Read the file first so you can write an informed edit.)
r1   r   r   r#   r   r2   r3   r4   r5   Ú_fmt_ts)r   r-   r   ÚstampÚlast_writerÚcurrent_mtimeÚ
writer_tidÚ	writer_tsÚread_tsÚ
read_mtimeÚ_read_tsr+   s               r   Úcheck_stalezFileStateRegistry.check_staleŽ   s$  € õ ‰;Œ;ð 	Ø�4ØÔð 	:ð 	:Ø”K—O’O G¨RÑ0Ô0×4Ò4°XÑ>Ô>ˆEØÔ+×/Ò/°Ñ9Ô9ˆKð	:ð 	:ð 	:ñ 	:ô 	:ð 	:ð 	:ð 	:ð 	:ð 	:ð 	:øøøð 	:ð 	:ð 	:ð 	:ð ˆ=˜[Ð0Ø�4ð	ÝœG×,Ò,¨XÑ6Ô6ˆMˆMøÝð 	ð 	ð 	à�4�4ð	øøøð
 Ð"Ø$/Ñ!ˆJ˜	Ø˜WÒ$Ð$Ø�=à#ð 1ð 1Ø%ð1ð 1ð 1ðð   œ(�Ø˜wÒ&Ð&à#ð ;ð ;Ø%ð;ð ;Ý-4°YÑ-?Ô-?ð;ð ;å5<¸WÑ5EÔ5Eð;ð ;ð ;ðð ÐØ,1Ñ)ˆJ˜ 'Ø 
Ò*Ð*àð 7ð 7ð 7ðð
 ð àð &ð &ð &ðð ˆ=àð Ið Ið Iðð
 ˆts$   ˜A	A-Á-A1Á4A1Á?B Â
B-Â,B-Úexclude_task_idÚsince_tsr7   ÚpathsúIterable[str]úDict[str, List[str]]c                ód  — t          ¦   «         ri S t          |¦  «        }t          t          ¦  «        }| j        5  | j                             ¦   «         D ]5\  }\  }}||k    rŒ||k     rŒ||v r||                              |¦  «         Œ6	 ddd¦  «         n# 1 swxY w Y   t          |¦  «        S )a  Return ``{writer_task_id: [paths]}`` for writes done after
        ``since_ts`` by agents OTHER than ``exclude_task_id``.

        Used by delegate_task to append a "subagent modified files the
        parent previously read" reminder to the delegation result.
        N)	r1   Úsetr   Úlistr   r   ÚitemsÚappendr   )	r   rM   rN   rO   Ú	paths_setÚoutÚprG   Útss	            r   Úwrites_sincezFileStateRegistry.writes_sinceÚ   s  € õ ‰;Œ;ð 	ØˆIÝ˜‘J”Jˆ	Ý$/µÑ$5Ô$5ˆØÔð 	.ð 	.Ø'+Ô'8×'>Ò'>Ñ'@Ô'@ð .ð .Ñ#�Ñ#�J Ø Ò0Ð0ØØ˜’=�=ØØ˜	�>�>Ø˜
”O×*Ò*¨1Ñ-Ô-Ð-øð.ð	.ð 	.ð 	.ñ 	.ô 	.ð 	.ð 	.ð 	.ð 	.ð 	.ð 	.øøøð 	.ð 	.ð 	.ð 	.õ �C‰yŒyÐs   »ABÂBÂBú	List[str]c                óØ   — t          ¦   «         rg S | j        5  t          | j                             |i ¦  «                             ¦   «         ¦  «        cddd¦  «         S # 1 swxY w Y   dS )z6Return the list of resolved paths this agent has read.N)r1   r   rT   r   r#   Úkeys)r   r-   s     r   Úknown_readszFileStateRegistry.known_readsô   s§   € å‰;Œ;ð 	ØˆIØÔð 	=ð 	=Ý˜œŸš¨°Ñ4Ô4×9Ò9Ñ;Ô;Ñ<Ô<ð	=ð 	=ð 	=ð 	=ñ 	=ô 	=ð 	=ð 	=ð 	=ð 	=ð 	=ð 	=øøøð 	=ð 	=ð 	=ð 	=ð 	=ð 	=s   ˜:AÁA#Á&A#c                ó  — | j         5  | j                             ¦   «          | j                             ¦   «          ddd¦  «         n# 1 swxY w Y   | j        5  | j                             ¦   «          ddd¦  «         dS # 1 swxY w Y   dS )z*Reset all state.  Intended for tests only.N)r   r   Úclearr   r   r   r   s    r   ra   zFileStateRegistry.clearü   s  € àÔð 	&ð 	&ØŒK×ÒÑÔÐØÔ×#Ò#Ñ%Ô%Ð%ð	&ð 	&ð 	&ñ 	&ô 	&ð 	&ð 	&ð 	&ð 	&ð 	&ð 	&øøøð 	&ð 	&ð 	&ð 	&ð Œ_ð 	%ð 	%ØÔ×"Ò"Ñ$Ô$Ð$ð	%ð 	%ð 	%ñ 	%ô 	%ð 	%ð 	%ð 	%ð 	%ð 	%ð 	%ð 	%øøøð 	%ð 	%ð 	%ð 	%ð 	%ð 	%s#   ˆ3AÁAÁAÁB Â BÂB)r   r   )r   r    r   r!   )r   r    )
r-   r    r   r    r+   r.   r,   r/   r   r   )r-   r    r   r    r,   r/   r   r   )r-   r    r   r    r   r@   )rM   r    rN   r7   rO   rP   r   rQ   ©r-   r    r   r\   )Ú__name__Ú
__module__Ú__qualname__Ú__doc__r   r&   r   r*   r<   r?   rL   r[   r_   ra   © r   r   r   r   ;   s  € € € € € Ø>Ð>ð,ð ,ð ,ð ,ðð ð ð ð ðð ð ñ „^ðð& Ø!%ð9ð 9ð 9ð 9ð 9ð 9ð4 "&ðBð Bð Bð Bð Bð Bð8Ið Ið Ið IðXð ð ð ð4=ð =ð =ð =ð%ð %ð %ð %ð %ð %r   r   r   c                 ó   — t           S r   )Ú	_registryrg   r   r   Úget_registryrj   	  s   € ÝÐr   r.   c                 ón   — t           j                             dd¦  «                             ¦   «         dk    S )NÚHERMES_DISABLE_FILE_STATE_GUARDÚ Ú1)r2   Úenvironr#   Ústriprg   r   r   r1   r1     s*   € åŒ:�>Š>Ð;¸RÑ@Ô@×FÒFÑHÔHÈCÒOÐOr   rZ   r7   r    c                óP   — t          j        dt          j        | ¦  «        ¦  «        S )Nz%H:%M:%S)r6   ÚstrftimeÚ	localtime)rZ   s    r   rC   rC     s    € õ Œ=˜¥T¤^°BÑ%7Ô%7Ñ8Ô8Ð8r   Údr   ÚlimitÚintr   c                óô   — t          | ¦  «        |z
  }|dk    rdS t          | ¦  «        }t          |¦  «        D ]>}	 |                      t	          |¦  «        ¦  «         Œ&# t
          t          f$ r Y  dS w xY wdS )zDTrim a dict to ``limit`` entries by dropping insertion-order oldest.r   N)ÚlenÚiterÚrangeÚpopÚnextÚStopIterationÚKeyError)rt   ru   ÚoverÚitÚ_s        r   r8   r8     sŽ   € åˆq‰6Œ6�E‰>€DØˆq‚y€yØˆå	ˆa‰Œ€BÝ�4‰[Œ[ð ð ˆð	Ø�EŠE•$�r‘(”(‰OŒOˆOˆOøÝ�xÐ(ð 	ð 	ð 	ØˆEˆEˆEð	øøøðð s   ¼"AÁA5Á4A5F©r+   r-   Úresolved_or_pathú
str | Pathr+   c               óZ   — t                                | t          |¦  «        |¬¦  «         d S )Nr‚   )ri   r<   r    )r-   rƒ   r+   s      r   r<   r<   '  s,   € Ý×Ò˜'¥3Ð'7Ñ#8Ô#8À'ÐÑJÔJÐJÐJÐJr   c                óV   — t                                | t          |¦  «        ¦  «         d S r   )ri   r?   r    ©r-   rƒ   s     r   r?   r?   +  s'   € Ý×Ò˜¥#Ð&6Ñ"7Ô"7Ñ8Ô8Ð8Ð8Ð8r   r@   c                óR   — t                                | t          |¦  «        ¦  «        S r   )ri   rL   r    r‡   s     r   rL   rL   /  s!   € Ý× Ò  ­#Ð.>Ñ*?Ô*?Ñ@Ô@Ð@r   c                óP   — t                                t          | ¦  «        ¦  «        S r   )ri   r*   r    )rƒ   s    r   r*   r*   3  s   € Ý×Ò�sÐ#3Ñ4Ô4Ñ5Ô5Ð5r   rM   rN   rO   úIterable[str | Path]rQ   c                óN   — t                                | |d„ |D ¦   «         ¦  «        S )Nc                ó,   — g | ]}t          |¦  «        ‘ŒS rg   )r    )Ú.0rY   s     r   ú
<listcomp>z writes_since.<locals>.<listcomp><  s   € Ð=TÐ=TÐ=TÈ½cÀ!¹f¼fÐ=TÐ=TÐ=Tr   )ri   r[   )rM   rN   rO   s      r   r[   r[   7  s+   € õ
 ×!Ò! /°8Ð=TÐ=TÈeÐ=TÑ=TÔ=TÑUÔUÐUr   r\   c                ó6   — t                                | ¦  «        S r   )ri   r_   )r-   s    r   r_   r_   ?  s   € Ý× Ò  Ñ)Ô)Ð)r   )r   rj   r<   r?   rL   r*   r[   r_   )r   r   )r   r.   )rZ   r7   r   r    )rt   r   ru   rv   r   r   )r-   r    rƒ   r„   r+   r.   r   r   )r-   r    rƒ   r„   r   r   )r-   r    rƒ   r„   r   r@   )rƒ   r„   )rM   r    rN   r7   rO   rŠ   r   rQ   rb   )$rf   Ú
__future__r   r2   r   r6   Úcollectionsr   Ú
contextlibr   Úpathlibr   Útypingr   r   r	   r
   r   r7   r.   Ú	ReadStampr9   r>   r   ri   rj   r1   rC   r8   r<   r?   rL   r*   r[   r_   Ú__all__rg   r   r   ú<module>r—      s  ððð ð@ #Ð "Ð "Ð "Ð "Ð "à 	€	€	€	Ø Ð Ð Ð Ø €€€Ø #Ð #Ð #Ð #Ð #Ð #Ø %Ð %Ð %Ð %Ð %Ð %Ø Ð Ð Ð Ð Ð Ø 8Ð 8Ð 8Ð 8Ð 8Ð 8Ð 8Ð 8Ð 8Ð 8Ð 8Ð 8Ð 8Ð 8ð �%˜ Ð$Ô%€	ð
 Ð ð Ð ðG%ð G%ð G%ð G%ð G%ñ G%ô G%ð G%ðV ÐÑÔ€	ðð ð ð ðPð Pð Pð Pð
9ð 9ð 9ð 9ðð ð ð ð PUð Kð Kð Kð Kð Kð Kð9ð 9ð 9ð 9ðAð Að Að Að6ð 6ð 6ð 6ðVð Vð Vð Vð*ð *ð *ð *ð	ð 	ð 	€€€r   