Ë
    ãß²j><  ã                   ó€  — U d 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
mZmZ  ej                  d«      Zdede
d	eee
f   fd
„Zd	efd„Zded	efd„Z G d„ d«      Z ej*                  dd¬«      Zded<   ded	dfd„Zded	efd„Zd,d„Zdefd„Zded	efd„Zded	efd„Zded	ee   fd„Zdeded	ee   fd„Zded	ee   fd „Z deded!ed	eeee
f      fd"„Z!ded#ed!ed$ed	eeee
f      f
d%„Z"d!eded	eeee
f      fd&„Z#ded'ee   d	eeee
f      fd(„Z$ded	efd)„Z%deded	ee   fd*„Z&deded!ed	eeee
f      fd+„Z'y)-a  Write/delete guards for ``skill_manage``. Every guard returns ``None`` when the
operation may proceed, else a refusal (error dict or message). Origin-owned state
(``_find_skill``, ``_skills_dir``) is reached lazily via ``tools.skill_manager_tool``
so test patches keep working.é    N)Úsuppress)ÚPath)ÚAnyÚDictÚOptionalztools.skill_manager_toolÚmessageÚextraÚreturnc                 ó   — d| dœ|¥S )NF)ÚsuccessÚerror© )r   r	   s     ú?/home/ubuntu/.hermes/hermes-agent/tools/skill_manager_guards.pyÚ_refusalr      s   € Ø wÑ8°%Ð8Ð8ó    c                  óN   — 	 ddl m}  t         | «       «      S # t        $ r Y yw xY w)zLTrue inside the autonomous curator review fork; False on any lookup failure.r   ©Úis_background_reviewF)Útools.skill_provenancer   ÚboolÚ	Exceptionr   s    r   Ú_is_background_reviewr      s+   € ðÝ?ÜÑ(Ó*Ó+Ð+øÜò Ùðús   ‚ ˜	$£$Úpathc                 ó’   — t        t        «      5  t        | j                  «       «      cd d d «       S # 1 sw Y   t        | «      S xY w©N)r   r   ÚstrÚresolve©r   s    r   Ú_resolved_strr      s7   € Ü	”)Ó	ñ #Ü�4—<‘<“>Ó"÷#÷ #ñ #äˆt‹9Ðús	   �3³Ac                   ó8   — e Zd ZdZdd„Zdeddfd„Zdedefd„Zy)	Ú_BackgroundReviewReadMarksz@Read marks shared by copied tool contexts within one review run.r
   Nc                 óT   — t        j                  «       | _        t        «       | _        y r   )Ú	threadingÚLockÚ_lockÚsetÚ_paths)Úselfs    r   Ú__init__z#_BackgroundReviewReadMarks.__init__&   s   € Ü—^‘^Ó%ˆŒ
Ü #£ˆ�r   r   c                 ó|   — | j                   5  | j                  j                  |«       d d d «       y # 1 sw Y   y xY wr   )r%   r'   Úadd©r(   r   s     r   r+   z_BackgroundReviewReadMarks.add*   s.   € Ø�Z‰Zñ 	"Ø�K‰K�O‰O˜DÔ!÷	"÷ 	"ñ 	"ús   �2²;c                 ób   — | j                   5  || j                  v cd d d «       S # 1 sw Y   y xY wr   )r%   r'   r,   s     r   Úcontainsz#_BackgroundReviewReadMarks.contains.   s*   € Ø�Z‰Zñ 	'Ø˜4Ÿ;™;Ð&÷	'÷ 	'ò 	'ús   �%¥.©r
   N)	Ú__name__Ú
__module__Ú__qualname__Ú__doc__r)   r   r+   r   r.   r   r   r   r!   r!   #   s0   „ ÙJó&ð"˜ð " ó "ð'˜Sð ' Tô 'r   r!   Úbackground_review_read_paths)Údefaultz9_ctxvars.ContextVar[Optional[_BackgroundReviewReadMarks]]Ú_background_review_read_pathsc                 ó¸   — t        «       syt        j                  «       x}€t        j                  t	        «       x}«       |j                  t        | «      «       y)zÙRecord that the active background-review fork has read a skill file. The fork must not
    patch content it only inferred from the transcript: skill_view/read_file call this, and
    the write guards require the mark.N)r   r6   Úgetr&   r!   r+   r   ©r   Úmarkss     r   Ú!mark_background_review_skill_readr;   7   sI   € ô !Ô"ØÜ.×2Ñ2Ó4Ð4ˆÐ=Ü%×)Ñ)Ô3MÓ3OÐ*O¨%ÔPØ	‡I�IŒm˜DÓ!Õ"r   c                 ój   — t         j                  «       }|d uxr |j                  t        | «      «      S r   )r6   r8   r.   r   r9   s     r   Ú_background_review_has_readr=   B   s.   € Ü)×-Ñ-Ó/€EØ˜ÐÒD §¡´¸dÓ0CÓ!DÐDr   c                  ó>   — t         j                  t        «       «       y)z@Start a fresh, isolated read set for the current review context.N)r6   r&   r!   r   r   r   Ú#_reset_background_review_read_marksr?   G   s   € ä!×%Ñ%Ô&@Ó&BÕCr   Ú
skill_pathc                 ó   — ddl m} 	 | j                  «       }g } |«       D ];  }t	        t        «      5  |j                  ||j                  «       f«       ddd«       Œ= ||fS # t        $ r | }Y ŒXw xY w# 1 sw Y   Œ\xY w)zZ``(resolved skill_path, [(root, resolved_root), ...])`` over every resolvable skills root.r   )Úget_all_skills_dirsN)Úagent.skill_utilsrB   r   ÚOSErrorr   Úappend)r@   rB   ÚresolvedÚrootsÚroots        r   Ú_resolved_rootsrI   L   sŒ   € å5ðØ×%Ñ%Ó'ˆð €EÙ#Ó%ò 1ˆÜ”gÓñ 	1Ø�L‰L˜$ §¡£Ð/Ô0÷	1ð 	1ð1ð �Uˆ?Ðøô ò ØŠðú÷	1ð 	1ús   ˆA# ³"A4Á#A1Á0A1Á4A=	c                 ór   ‡— ddl m} t        | «      \  Š}t        ˆfd„|D «       |j	                  «       «      S )zXSkills root (local or external_dirs) containing ``skill_path``; local dir if none match.r   ©Úskill_manager_toolc              3   óL   •K  — | ]  \  }}‰j                  |«      sŒ|–— Œ y ­wr   )Úis_relative_to)Ú.0rH   ÚrrF   s      €r   ú	<genexpr>z*_containing_skills_root.<locals>.<genexpr>^   s"   øè ø€ ÒI™'˜$ ¨h×.EÑ.EÀaÕ.H”ÑIùs   ƒ$�$)ÚtoolsrL   rI   ÚnextÚ_skills_dir)r@   Ú_smtrG   rF   s      @r   Ú_containing_skills_rootrV   Z   s0   ø€ å0Ü% jÓ1�O€HˆeÜÓI UÔIÈ4×K[ÑK[ÓK]Ó^Ð^r   c                 ó‚   — 	 | j                  «       xs t        | d«      xr | j                  «       S # t        $ r Y yw xY w)u\   Symlink or (Windows 3.12+) junction â€” either lets a poisoned tree redirect rmtree outside.Úis_junctionF)Ú
is_symlinkÚhasattrrX   rD   r   s    r   Ú_is_path_redirectr[   a   s@   € ðØ�‰Ó ÒY¤W¨T°=Ó%AÒ%XÀd×FVÑFVÓFXÐYøÜò Ùðús   ‚/2 ²	>½>Ú	skill_dirc                 ó   — t        | «      rd| › d�S 	 | j                  «        t        | «      \  }}|D ]%  \  }}||k(  rd| › d�c S |j	                  |«      sŒ% y d| › d�S # t        $ r}d| › d|› d�cY d}~S d}~ww xY w)aí  Last-line guard before rmtree: even a poisoned tree must never delete (1) a path outside
    every known skills root, (2) a skills root itself, (3) a symlink/junction (rmtree follows it).

    ``_find_skill`` already restricts ``skill_dir`` to a real ``SKILL.md`` parent discovered by walking the
    skills roots, so the agent cannot inject an arbitrary path the way Kilo Code's HTTP endpoint could
    (their issue 11227: a built-in-skill sentinel resolved to the server cwd and a recursive delete wiped
    the user's entire working directory). This is the matching defense-in-depth for our agent-facing
    ``skill_manage`` delete path: even if discovery or a poisoned tree hands us a bad directory, never
    recursively delete See #11227.
    zRefusing to delete 'zZ': the skill directory is a symlink/junction. Remove the link target manually if intended.z': could not resolve path (z).NzP': resolves to the skills root itself, which would remove every installed skill.z6': path does not resolve inside any known skills root.)r[   r   rD   rI   rN   )r\   ÚexcrF   rG   Ú_rootrH   s         r   Ú_validate_delete_targetr`   i   sÉ   € ô ˜Ô#Ø& y kð 2Qð Rð 	SðTØ×ÑÔô & iÓ0�O€HˆeØò ‰ˆˆtØ�tÒØ*¨9¨+ð 6Hð Iò Jà×"Ñ" 4Õ(Ùðð " ) Ð,bÐcÐcøô ò TØ% i [Ð0KÈCÈ5ÐPRÐSÕSûðTús   “A! Á!	A=Á*A8Á2A=Á8A=ÚnameÚwhatc                 ó²   — 	 ddl m} t        |j                  | «      j	                  d«      «      S # t
        $ r t        j                  d|| d¬«       Y yw xY w)zNskill_usage pinned flag; None (logged at debug) when the record is unreadable.r   ©Úskill_usageÚpinnedz%s lookup failed for %sT©Úexc_infoN)rR   re   r   Ú
get_recordr8   r   ÚloggerÚdebug)ra   rb   re   s      r   Ú
_is_pinnedrl   …   sR   € ðÝ%Ü�K×*Ñ*¨4Ó0×4Ñ4°XÓ>Ó?Ð?øÜò Ü�‰Ð.°°dÀTˆÔJÙðús   ‚.1 ±"AÁAc                 ó    — 	 ddl m} | |v rd| › d�S 	 t        | d«      r	d| › d	| › d
�S y# t        $ r t        j	                  d| d¬«       Y Œ9w xY w)a  Refusal message if *name* is pinned or essential, else None. Pin only guards DELETION;
    patches/edits stay allowed. ESSENTIAL_SKILLS are permanently pinned (the system prompt
    references them). Best-effort: an unreadable sidecar lets the delete through.r   )ÚESSENTIAL_SKILLSzSkill 'z—' is essential to Hermes (the agent's own operating manual referenced by the system prompt) and cannot be deleted. Patches and edits are still allowed.z$essential-guard lookup failed for %sTrg   zpinned-guardz]' is pinned and cannot be deleted by skill_manage. Ask the user to run `hermes curator unpin zf` if they want to delete it. Patches and edits are allowed on pinned skills; only deletion is blocked.N)rC   rn   r   rj   rk   rl   )ra   rn   s     r   Ú_pinned_guardro   �   sŒ   € ðRÝ6ØÐ#Ñ#à˜$˜ð  Jð KðLð $ô �$˜Ô'à�d�Vð )Ø)-¨ð /FðGð	Hð øô ò RÜ�‰Ð;¸TÈDˆÖQðRús   ‚) ©!AÁAÚactionc           
      óî  — t        «       syd|› d�}t        | d«      rt        |› d| › d| › d�«      S 	 dd	lm}  ||«      rt        |› d
| › d�«      S 	 	 ddlm	} |j                  df|j                  df|j                  dffD ]#  \  }} || «      sŒt        |› d|› d
| › d�«      c S  |j                  «       j                  | «      }|j                  |«      s=t!        |t"        «      rd|j                  d«      ›�nd}	t        |› d
| › d|	› d| › d�«      S 	 y# t
        $ r t        j                  d| d¬«       Y Œèw xY w# t
        $ r, t        j%                  d| d¬«       t        |› d
| › d�«      cY S w xY w)z²Refuse autonomous curator writes to anything but curator-owned sediment. The review fork
    has no user in the loop, so it is also blocked on pinned/external/bundled/hub skills.NúRefusing background curator z forzpinned skill guardz pinned skill 'ze': pinned skills are off-limits to autonomous maintenance. Ask the user to run `hermes curator unpin z` if they want it changed.r   )Úis_external_skill_pathz skill 'zl': the skill lives in skills.external_dirs, which are externally owned and read-only to autonomous curation.z)external skill guard lookup failed for %sTrg   rd   zprotected built-inzhub-installedÚbundledÚ z'.zcreated_by=Ú
created_byzno usage recordz%': the skill is not curator-managed (zV). User-owned skills are off-limits to autonomous curation. Run `hermes curator adopt z` to opt it in.z&owned skill guard lookup failed for %szd': agent ownership could not be verified because the provenance record is unavailable or unreadable.)r   rl   r   rC   rs   r   rj   rk   rR   re   Úis_protected_builtinÚis_hub_installedÚ
is_bundledÚ
load_usager8   Ú_is_curator_managed_recordÚ
isinstanceÚdictÚwarning)
ra   r\   rp   Úrefusers   re   Ú	predicateÚlabelÚ	usage_recÚ_details
             r   Ú_background_review_write_guardr„   ¤   s  € ô !Ô"ØØ+¨F¨8°4Ð8€FÜ�$Ð,Ô-ÜØˆh�o d Vð ,à�FÐ4ð6ó7ð 	7ðWÝ<Ù! )Ô,ÜØ�(˜( 4 &ð )Ið JóKð Kð -ð XÝ%à×-Ñ-Ð/CÐDØ×)Ñ)¨?Ð;Ø×#Ñ# YÐ/ð!1ò 	EÑˆI�uñ ˜�Ü 6 (¨!¨E¨7°(¸4¸&ÀÐ CÓDÒDð	Eð  ×*Ñ*Ó,×0Ñ0°Ó6ˆ	ð ×5Ñ5°iÔ@ÜHRÐS\Ô^bÔHc˜ Y§]¡]°<Ó%@Ð$CÑDØ-ð äØ�(˜( 4 &ð )$Ø$+ 9ð -7Ø7;°f¸OðMóNð Nð Að øôG ò WÜ�‰Ð@À$ÐQUˆÖVðWûô< ò XÜ�‰Ð?ÀÐPTˆÔUÜØˆh�h˜t˜fð %Vð WóXò 	XðXús0   ³D Á=D? ÂD? Â(A-D? Ä!D<Ä;D<Ä?2E4Å3E4ÚtargetÚ
file_labelc           	      ó\   — t        «       rt        |«      ryt        d|› d| › d|› d�d¬«      S )zARequire review forks to load the exact target before mutating it.Nrr   z for skill 'z': the current zÅ content has not been loaded in this review turn. Call skill_view(name) for SKILL.md, or skill_view(name, file_path=...) for a supporting file, then retry the write using the content just returned.T)Ú_read_before_write_required)r   r=   r   )ra   r…   rp   r†   s       r   Ú*_background_review_read_before_write_guardr‰   Ü   sF   € ô !Ô"Ô&AÀ&Ô&IØÜØ
& v h¨l¸4¸&ÀÐPZÈ|ð \!ð 	"ð %)ô*ð *r   c                 ób   — | dvry ddl m} |j                  |«      }|rt        ||d   | «      S d S )N>   ÚeditÚpatchÚdeleteÚ
write_fileÚremove_filer   rK   r   )rR   rL   Ú_find_skillr„   )rp   ra   rU   Úexistings       r   Ú_background_review_preflightr’   é   s>   € ØÐMÑMØÝ0Ø×Ñ Ó%€HÙMUÔ)¨$°¸Ñ0@À&ÓIÐ_Ð[_Ð_r   Úabsorbed_intoc                 ó€   — t        «       r t        |t        «      r|j                  «       ryt	        d| › d| › d�d¬«      S )uö  Fail closed on unverified deletes during the curator consolidation pass. The fork's only
    legitimate delete is a consolidation declared via ``absorbed_into=<umbrella>`` (existence
    validated in ``_delete_skill``); the deterministic inactivity prune never calls skill_manage,
    so a bare delete here can only be the LLM pass pruning without evidence.

    A delete with no forwarding target â€” ``absorbed_into`` omitted (``None``) or empty (``""``) â€” is the
    fail-open behavior reported in #29912: the consolidation pass archived whole clusters of active skills
    with zero verified consolidations (``consolidated_this_run == 0``), leaving active automations pointing
    at names that no longer resolve. Refuse it; keep the skill active.
    Nz-Refusing background curator delete of skill 'uP  ': the consolidation pass may only archive a skill it has absorbed into an umbrella. Pass absorbed_into=<umbrella> (the umbrella must already exist) to record a verified consolidation. Pruning a skill with no forwarding target is not permitted here â€” the deterministic inactivity prune handles staleness archival separately. Keeping 'z	' active.T)Ú_fail_closed)r   r|   r   Ústripr   )ra   r“   s     r   Ú#_curator_consolidation_delete_guardr—   ñ   sN   € ô !Ô"¤z°-ÄÔ'EÈ-×J]ÑJ]ÔJ_ØÜØ
7¸°vð >3ð 48°&¸	ð		Cð
 ôð r   c                 óH   — ddl m} ddlm}  || |j	                  «       «      S )Nr   )Úis_org_mirror_pathrK   )rC   r™   rR   rL   rT   )r@   r™   rU   s      r   Ú_is_org_mirrorrš     s   € Ý4Ý0Ù˜j¨$×*:Ñ*:Ó*<Ó=Ð=r   c                 ó$  — 	 ddl m} t        |«      sy|j                  «       sd| › d�S ddlm}  || «      }|j                  d«      rd|j                  d	«      › d
�S y# t        $ r'}t        j                  d| |«       d| › d�cY d}~S d}~ww xY w)z¹Submit an org-skill edit upstream when `sync.org_auto_propose` is on. Returns a note for
    the tool result or None; never raises (the edit is saved locally and can be proposed later).r   )Úskills_sync_clientNz‹This skill is shared by your organisation. Your edit is saved locally and will not be overwritten by org updates. Run `hermes sync propose z` to share it back.)Úpropose_skillÚproposal_pendingz0Auto-proposed to your organisation as proposal #Úproposal_idz (pending admin review).z@Auto-proposed to your organisation (merged into the shared set).zauto-propose skipped for %s: %sud   Edit saved locally. Could not submit it to your organisation right now â€” run `hermes sync propose z` to retry.)
rR   rœ   rš   Úsync_org_auto_proposeÚtools.skills_sync_client_orgr�   r8   r   rj   rk   )ra   r@   Ússcr�   ÚresultÚes         r   Ú_maybe_auto_propose_org_editr¥     s»   € ðIÝ3Ü˜jÔ)ØØ×(Ñ(Ô*ð,à,0¨6Ð1DðFðGõ 	?Ù˜tÓ$ˆØ�:‰:Ð(Ô)ðØ—J‘J˜}Ó-Ð.Ð.FðHðIð RøÜò IÜ�‰Ð6¸¸aÔ@ð6Ø6:°V¸;ðHõ	IûðIús'   ‚A ”A ª3A Á	BÁ(B
ÂBÂ
Bc           	      ó    — |dvry	 t        |«      rt        d|› d| › d| › d�«      S 	 y# t        $ r t        j	                  d| d¬	«       Y yw xY w)
ug  Org-shared skills are EDITABLE IN PLACE â€” this only blocks deletion. Edits land in the
    mirror, survive the next org pull (baseline sidecar in skills_sync_client) and reach the org
    via `hermes sync propose`. Deletion stays refused: the mirror is a view of org HEAD, so a
    local delete just comes back, and removing for everyone is an admin action.>   r�   r�   NzCannot z 'u÷   ' locally: it is shared by your organisation, so a local delete would just come back on the next sync. Ask an org admin to remove it for everyone. (Editing it IS allowed â€” your changes are kept and can be proposed back with `hermes sync propose z`.)z%org mirror guard lookup failed for %sTrg   )rš   r   r   rj   rk   )ra   r@   rp   s      r   Ú_org_mirror_write_guardr§   (  s~   € ð
 Ð.Ñ.ØðSÜ˜*Ô%ÜØ˜&˜  D 6ð *2ð 37°°sð<ó=ð =ð &ð øô ò SÜ�‰Ð<¸dÈTˆÕRØðSús   ‡) ©!AÁAr/   )(r3   ÚcontextvarsÚ_ctxvarsÚloggingr#   Ú
contextlibr   Úpathlibr   Útypingr   r   r   Ú	getLoggerrj   r   r   r   r   r   r!   Ú
ContextVarr6   Ú__annotations__r;   r=   r?   rI   rV   r[   r`   rl   ro   r„   r‰   r’   r—   rš   r¥   r§   r   r   r   ú<module>r±      s„  ðò!ó
 Û Û Ý Ý ß &Ñ &à	ˆ×	Ñ	Ð5Ó	6€ð9�cð 9 Cð 9¨D°°c°©Nó 9ð˜tó ð˜ð  ó ÷'ñ 'ð" €H×ÑÐ6ÀÔEð ÐZó Gð#¨Dð #°Tó #ðE dð E¨tó Eó
Dð
 ó ð_¨ð _°ó _ð˜Dð  Tó ðd tð d°¸±ó dð8�Sð  ð ¨°©ó ð˜ð  ¨¡ó ð*5Ø
ð5Øð5Ø(+ð5Ø08¸¸cÀ3¸h¹Ñ0Hó5ðp
*Ø
ð
*Øð
*Ø%(ð
*Ø69ð
*Ø>FÀtÈCÐQTÈHÁ~Ñ>Vó
*ð`¨ð `°Cð `¸HÀTÈ#ÈsÈ(Á^Ñ<Tó `ðØ
ðØ& s™mðØ08¸¸cÀ3¸h¹Ñ0Hóð.>˜tð >¨ó >ðI sð I¸ð IÀÈ#Áó Ið4 #ð °4ð Àð ÈÐRVÐWZÐ\_ÐW_ÑR`ÑIaô r   