Ë
    ãß²j�(  ã                  ó  — 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dd	„Zdd
„Zdd„Zdd„Zdd„Z G d„ d«      Z e«       Zdd„Zddœdd„Zd d„Z d!d„Z!d"d„Z"d#d„Z#d$d„Z$g d¢Z%y)%a   Cross-agent file state coordination.

Prevents mangled edits when concurrent subagents (same process, same
filesystem) touch the same file: B writes a file A already read, so A's next
write would clobber B's changes. Complements the single-agent path-overlap
check in ``agent.tool_dispatch_helpers._should_parallelize_tool_batch``. A process-wide
``FileStateRegistry`` tracks per-agent read stamps, the global last writer and
a per-path lock; every method is a no-op under ``HERMES_DISABLE_FILE_STATE_GUARD=1``.
é    )ÚannotationsN)Údefaultdict)Úcontextmanager)ÚPath)ÚDictÚIterableÚListÚOptionalÚTuplei   c                 ód   — t         j                  j                  dd«      j                  «       dk(  S )NÚHERMES_DISABLE_FILE_STATE_GUARDÚ Ú1)ÚosÚenvironÚgetÚstrip© ó    ú5/home/ubuntu/.hermes/hermes-agent/tools/file_state.pyÚ	_disabledr      s&   € ä�:‰:�>‰>Ð;¸RÓ@×FÑFÓHÈCÑOÐOr   c                 ó   — t        «       S )z‘True when the user switched the read-before-write guard off; the file
    tools then warn instead of refusing stale/unread write_file overwrites.)r   r   r   r   Úguard_disabledr   #   s   € ô ‹;Ðr   c                ó`   — 	 t         j                  j                  | «      S # t        $ r Y y w xY w©N)r   ÚpathÚgetmtimeÚOSError)Úresolveds    r   Ú_mtime_or_noner    )   s.   € ðÜ�w‰w×Ñ Ó)Ð)øÜò Ùðús   ‚! ¡	-¬-c                óT   — t        j                  dt        j                  | «      «      S )Nz%H:%M:%S)ÚtimeÚstrftimeÚ	localtime)Útss    r   Ú_fmt_tsr&   0   s   € ä�=‰=˜¤T§^¡^°BÓ%7Ó8Ð8r   c                óò   — t        t        | «      |z
  «      D ]G  }	 t        | t        «      r| j	                  «        n#| j	                  t        t        | «      «      «       ŒI y# t        t        f$ r Y  yw xY w)z§Pop entries until *container* is within *cap* (sets: arbitrary; dicts: oldest
    by insertion order). An eviction only costs one redundant re-send or staleness check.N)	ÚrangeÚlenÚ
isinstanceÚsetÚpopÚnextÚiterÚStopIterationÚKeyError)Ú	containerÚcapÚ_s      r   Ú_evict_oldestr4   5   sh   € ô ”3�y“> CÑ'Ó(ò ˆð	Ü˜)¤SÔ)Ø—‘•à—‘œd¤4¨	£?Ó3Ô4øñøô œxÐ(ò 	Úð	ús   œAA#Á#A6Á5A6c                  ó†   — e Zd ZdZdd„Zedd„«       Z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d„Zy)ÚFileStateRegistryz4Process-wide coordinator for cross-agent file edits.c                óº   — t        t        «      | _        i | _        i | _        i | _        t        j                  «       | _        t        j                  «       | _	        y r   )
r   ÚdictÚ_readsÚ_last_writerÚ_path_locksÚ_path_lock_usersÚ	threadingÚLockÚ
_meta_lockÚ_state_lock©Úselfs    r   Ú__init__zFileStateRegistry.__init__E   sC   € Ü7BÄ4Ó7HˆŒØ:<ˆÔØ68ˆÔØ02ˆÔÜ#Ÿ.™.Ó*ˆŒÜ$Ÿ>™>Ó+ˆÕr   c              #  ój  K  — | j                   5  | j                  j                  |t        j                  «       «      }| j
                  j                  |d«      dz   | j
                  |<   ddd«       j                  «        	 d–— |j                  «        | j                   5  | j
                  |   dz
  }|r|| j
                  |<   n8| j
                  j                  |d«       | j                  j                  |d«       ddd«       y# 1 sw Y   Œ xY w# 1 sw Y   yxY w# |j                  «        | j                   5  | j
                  |   dz
  }|r|| j
                  |<   n8| j
                  j                  |d«       | j                  j                  |d«       ddd«       w # 1 sw Y   w xY wxY w­w)z�Per-path lock: threads on the same path serialize, different paths proceed.
        The lock entry is dropped once the last holder/waiter exits.r   é   N)
r?   r;   Ú
setdefaultr=   r>   r<   r   ÚacquireÚreleaser,   )rB   r   ÚlockÚuserss       r   Ú	lock_pathzFileStateRegistry.lock_pathM   s|  è ø€ ð �_‰_ñ 	YØ×#Ñ#×.Ñ.¨x¼¿¹Ó9IÓJˆDØ.2×.CÑ.C×.GÑ.GÈÐRSÓ.TÐWXÑ.XˆD×!Ñ! (Ñ+÷	Yð 	�‰Œð
	9Ûà�L‰LŒNØ—‘ñ 9Ø×-Ñ-¨hÑ7¸!Ñ;�ÙØ6;�D×)Ñ)¨(Ò3à×)Ñ)×-Ñ-¨h¸Ô=Ø×$Ñ$×(Ñ(¨°4Ô8÷9ð 9÷	Yð 	Yú÷9ð 9ûð �L‰LŒNØ—‘ñ 9Ø×-Ñ-¨hÑ7¸!Ñ;�ÙØ6;�D×)Ñ)¨(Ò3à×)Ñ)×-Ñ-¨h¸Ô=Ø×$Ñ$×(Ñ(¨°4Ô8÷9÷ 9ñ 9ÿsg   ‚F3�AD	Á*F3ÂD! ÂF3Â#ADÄ 	F3Ä	DÄF3ÄDÄF3Ä!F0Ä>AF$Æ	F0Æ$F-Æ)F0Æ0F3c                óv   — | j                   |   }t        |«      |t        |«      f||<   t        |t        «       y)zCaller holds ``_state_lock``.N)r9   ÚfloatÚboolr4   Ú_MAX_PATHS_PER_AGENT)rB   Útask_idr   ÚmtimeÚnowÚpartialÚagent_readss          r   Ú_stampzFileStateRegistry._stampa   s4   € à—k‘k 'Ñ*ˆÜ!& u£¨s´D¸³MÐ Bˆ�HÑÜ�kÔ#7Õ8r   FN)rS   rQ   c               óÎ   — t        «       ry |€t        |«      n|}|€y | j                  5  | j                  |||t	        j                  «       |«       d d d «       y # 1 sw Y   y xY wr   )r   r    r@   rU   r"   )rB   rP   r   rS   rQ   s        r   Úrecord_readzFileStateRegistry.record_readg   s^   € äŒ;ØØ,1¨M”˜xÔ(¸uˆØˆ=ØØ×Ññ 	HØ�K‰K˜ ¨5´$·)±)³+¸wÔG÷	H÷ 	Hñ 	Hús   ª(AÁA$)rQ   c               ó(  — t        «       ry|€t        |«      n|}|€yt        j                  «       }| j                  5  ||f| j                  |<   t        | j                  t        «       | j                  ||||d«       ddd«       y# 1 sw Y   yxY w)z‹Record a successful write: global last-writer AND this agent's own
        read stamp (a write is an implicit read of the current content).NF)r   r    r"   r@   r:   r4   Ú_MAX_GLOBAL_WRITERSrU   )rB   rP   r   rQ   rR   s        r   Ú
note_writezFileStateRegistry.note_writeq   s†   € ô Œ;ØØ,1¨M”˜xÔ(¸uˆØˆ=ØÜ�i‰i‹kˆØ×Ññ 	>Ø+2°C¨.ˆD×Ñ˜hÑ'Ü˜$×+Ñ+Ô-@ÔAØ�K‰K˜ ¨5°#°uÔ=÷	>÷ 	>ñ 	>ús   ¾ABÂBc           	     óÊ  — t        «       ry| j                  5  | j                  j                  |i «      j                  |«      }| j                  j                  |«      }ddd«       €€yt        |«      }|€y�>|\  }}||k7  r4|€|› d|›d�S |d   }||kD  r |› d|›dt        |«      › dt        |«      › d�S |�|\  }	}
}||	k7  r|› d�S |r|› d	�S y|› d
�S # 1 sw Y   Œ{xY w)z¡Model-facing warning if this write would be stale, else ``None``. Severity
        order: sibling wrote after our read > mtime drift / partial read > never read.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.rE   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.zz was last read with offset/limit pagination (partial view). Read the remaining pages, or use patch, before overwriting it.zS was not read by this agent. Read the file first so you can write an informed edit.)r   r@   r9   r   r:   r    r&   )rB   rP   r   ÚstampÚlast_writerÚcurrent_mtimeÚ
writer_tidÚ	writer_tsÚread_tsÚ
read_mtimeÚ_read_tsrS   s               r   Úcheck_stalezFileStateRegistry.check_stale   so  € ô Œ;ØØ×Ññ 	:Ø—K‘K—O‘O G¨RÓ0×4Ñ4°XÓ>ˆEØ×+Ñ+×/Ñ/°Ó9ˆK÷	:ð ˆ=˜[Ð0ØÜ& xÓ0ˆØÐ ØàÐ"Ø$/Ñ!ˆJ˜	Ø˜WÒ$Ø�=à#˜*Ð$FØ%˜.ð )1ð1ð2ð
   ™(�Ø˜wÒ&à#˜*Ð$FØ%˜.¨¬W°YÓ-?Ð,@ð A5Ü5<¸WÓ5EÐ4Fð G;ð;ð<ð ÐØ,1Ñ)ˆJ˜ 'Ø 
Ò*à�jð !7ð 7ð8ñ à�jð !-ð -ð.ð ð ˆjð Eð Eð	F÷S	:ð 	:ús   ˜ACÃC"c                óP  — t        «       ri S t        |«      }t        t        «      }| j                  5  | j
                  j                  «       D ]-  \  }\  }}||k7  sŒ||k\  sŒ||v sŒ||   j                  |«       Œ/ 	 ddd«       t        |«      S # 1 sw Y   t        |«      S xY w)z¾``{writer_task_id: [paths]}`` for writes after ``since_ts`` by agents
        other than ``exclude_task_id`` (delegate_task's "subagent modified files
        you previously read" reminder).N)	r   r+   r   Úlistr@   r:   ÚitemsÚappendr8   )	rB   Úexclude_task_idÚsince_tsÚpathsÚ	paths_setÚoutÚpr_   r%   s	            r   Úwrites_sincezFileStateRegistry.writes_since±   s    € ô
 Œ;ØˆIÜ˜“Jˆ	Ü$/´Ó$5ˆØ×Ññ 	.Ø'+×'8Ñ'8×'>Ñ'>Ó'@ò .Ñ#�Ñ#�J Ø Ó0°R¸8³^ÈÈYÊØ˜
‘O×*Ñ*¨1Õ-ñ.÷	.ô �C‹yÐ÷		.ô �C‹yÐús   ³)BÁBÁ#BÁ(BÂB%c                óÄ   — t        «       rg S | j                  5  t        | j                  j	                  |i «      j                  «       «      cddd«       S # 1 sw Y   yxY w)z#Resolved paths this agent has read.N)r   r@   rf   r9   r   Úkeys)rB   rP   s     r   Úknown_readszFileStateRegistry.known_readsÀ   sJ   € äŒ;ØˆIØ×Ññ 	=Ü˜Ÿ™Ÿ™¨°Ó4×9Ñ9Ó;Ó<÷	=÷ 	=ò 	=ús   ™3AÁAc                ó"  — | j                   5  | j                  j                  |d«       | j                  j	                  «       D ���cg c]  \  }\  }}||k(  sŒ|‘Œ c}}}D ]  }| j                  |= Œ 	 ddd«       yc c}}}w # 1 sw Y   yxY w)ac  Release read stamps and writer claims owned by a task after its lifecycle ends.

        A finished task is not a concurrent sibling: leaving its writer claims behind makes
        the next run of the same job (a fresh ``cron:<job>:<uuid>`` id) refuse to write the
        same scratch path as "modified by sibling subagent" hours after the writer exited.N)r@   r9   r,   r:   rg   )rB   rP   rn   r_   Ú_tss        r   Úforget_taskzFileStateRegistry.forget_taskÇ   s‰   € ð ×Ññ 	)Ø�K‰K�O‰O˜G TÔ*Ø48×4EÑ4E×4KÑ4KÓ4M×gÐgÑ0˜AÑ0 
¨CÐQ[Ð_fÓQf’aÔgò )�Ø×%Ñ% aÑ(ñ)÷	)ð 	)ùäg÷	)ð 	)ús#   �;BÁA>
ÁA>
ÁBÁ>BÂBc                óX  — | j                   5  | j                  j                  «        | j                  j                  «        ddd«       | j                  5  | j
                  j                  «        | j                  j                  «        ddd«       y# 1 sw Y   ŒSxY w# 1 sw Y   yxY w)z)Reset all state. Intended for tests only.N)r@   r9   Úclearr:   r?   r;   r<   rA   s    r   rw   zFileStateRegistry.clearÒ   s‰   € à×Ññ 	&Ø�K‰K×ÑÔØ×Ñ×#Ñ#Ô%÷	&ð �_‰_ñ 	*Ø×Ñ×"Ñ"Ô$Ø×!Ñ!×'Ñ'Ô)÷	*ð 	*÷	&ð 	&ú÷	*ð 	*ús   �5BÁ5B ÂBÂ B))ÚreturnÚNone)r   Ústr)rP   rz   r   rz   rQ   rM   rR   rM   rS   rN   rx   ry   )
rP   rz   r   rz   rS   rN   rQ   úOptional[float]rx   ry   )rP   rz   r   rz   rQ   r{   rx   ry   )rP   rz   r   rz   rx   úOptional[str])ri   rz   rj   rM   rk   zIterable[str]rx   úDict[str, List[str]]©rP   rz   rx   z	List[str])rP   rz   rx   ry   )Ú__name__Ú
__module__Ú__qualname__Ú__doc__rC   r   rK   rU   rW   rZ   rd   ro   rr   ru   rw   r   r   r   r6   r6   B   s|   „ Ù>ó,ð ò9ó ð9ó&9ð KPØ-1ñHØ*ðHØ6:óHð SWõ >ó0FðdØ)ðØ.Bóó=ó	)ô*r   r6   c                 ó   — t         S r   )Ú	_registryr   r   r   Úget_registryr…   ß   s   € ÜÐr   F©rS   c               óF   — t         j                  | t        |«      |¬«       y )Nr†   )r„   rW   rz   )rP   Úresolved_or_pathrS   s      r   rW   rW   ä   s   € Ü×Ñ˜'¤3Ð'7Ó#8À'ÐÕJr   c                óB   — t         j                  | t        |«      «       y r   )r„   rZ   rz   ©rP   rˆ   s     r   rZ   rZ   è   s   € Ü×Ñ˜¤#Ð&6Ó"7Õ8r   c                ó@   — t         j                  | t        |«      «      S r   )r„   rd   rz   rŠ   s     r   rd   rd   ì   s   € Ü× Ñ  ¬#Ð.>Ó*?Ó@Ð@r   c                ó>   — t         j                  t        | «      «      S r   )r„   rK   rz   )rˆ   s    r   rK   rK   ð   s   € Ü×ÑœsÐ#3Ó4Ó5Ð5r   c           
     óh   — t         j                  | ||D �cg c]  }t        |«      ‘Œ c}«      S c c}w r   )r„   ro   rz   )ri   rj   rk   rn   s       r   ro   ro   ô   s)   € Ü×!Ñ! /°8ÈeÖ=TÈ¼cÀ!½fÒ=TÓUÐUùÒ=Ts   –/c                ó,   — t         j                  | «      S r   )r„   rr   )rP   s    r   rr   rr   ø   s   € Ü× Ñ  Ó)Ð)r   )r6   r…   rW   rZ   rd   rK   ro   rr   )rx   rN   )r   rz   rx   r{   )r%   rM   rx   rz   )r2   Úintrx   ry   )rx   r6   )rP   rz   rˆ   ú
str | PathrS   rN   rx   ry   )rP   rz   rˆ   r�   rx   ry   )rP   rz   rˆ   r�   rx   r|   )rˆ   r�   )ri   rz   rj   rM   rk   zIterable[str | Path]rx   r}   r~   )&r‚   Ú
__future__r   r   r=   r"   Úcollectionsr   Ú
contextlibr   Úpathlibr   Útypingr   r   r	   r
   r   rM   rN   Ú	ReadStamprO   rY   r   r   r    r&   r4   r6   r„   r…   rW   rZ   rd   rK   ro   rr   Ú__all__r   r   r   ú<module>r˜      s­   ðñõ #ã 	Û Û Ý #Ý %Ý ß 8Õ 8ð
 �%˜ Ð$Ñ%€	ð Ð ØÐ óPó
óó9ó

÷W*ñ W*ñt Ó€	óð
 PUõ Kó9óAó6óVó*ò�r   