+
    lHàjI*  ã                  óN  a € 0 t $ R t^ RIHt ^ RIt^ RIt^ RIt^ RIt^ RIt^ RI	H
t
HtHt ^ RIHt ^ RIHtHtHtHt ^ RIHtHtHt  ^ RIt]P4                  ! ]4      tRtRt ! R	 R
]4      t ] ! R R4      4       t!R R lt"]PF                  ! 4       t$. t%R]&R&   R R lt'R R lt(R R lt)R R lt*R R lt+ ! R R4      t,R6R R llt-R R  lt.R! R" lt/R# R$ lt0R%RR&R'/R( R) llt1R6R%RR*R+/R, R- lllt2R6R. R/ llt3R%R/R0 R1 llt4R6R2 R3 llt5R4 R5 lt6R#   ] d    Rt L×i ; i)7uú  Who may drive a profile's Bot Desktop screen: the agent (default) or exactly one human viewer.

The lease is the single truth shared by the RFB bridge (drops human input from non-holders), the
``computer_use`` tool (refuses to act while a human holds control â€” the person may be typing a
credential, so even screenshots are refused; fail closed rather than trusting the agent to pause
itself) and the Desktop UI (Watch / Take over / Hand back).

Scope: the lease is a TOOL-LEVEL fence, not a property of the X server. ``computer_use`` and the browser
tools consult it; a process the agent starts by hand against the published ``DISPLAY``/``XAUTHORITY``
(the ``terminal`` tool, a script) is inside the documented same-user boundary and is not stopped by it
(bot-screen.md, "Threat model"; #110040).

Authority lives ON DISK, ``<HERMES_HOME>/bot-desktop/lease.json`` under an fcntl lock, because the
processes that must agree do not share memory: ``hermes serve`` (viewer bridge), the messaging
gateway, a CLI turn and isolated workers all drive the same display. Every read goes to the file;
the in-process Condition only wakes local waiters early. ``epoch`` increments on every transition so
an action admitted under one lease can tell that control changed underneath it.
)ÚannotationsN)ÚasdictÚ	dataclassÚfield)ÚPath)ÚCallableÚDictÚListÚOptional)Úget_hermes_homeÚhermes_home_keyÚsecure_parent_dirÚagentÚhumanc                  ó   € ] tR t^,tRtRtR# )ÚHumanHasControlz=Raised by screen-driving tools while a human holds the lease.© N)Ú__name__Ú
__module__Ú__qualname__Ú__firstlineno__Ú__doc__Ú__static_attributes__r   ó    Ú</home/ubuntu/.hermes/hermes-agent/tools/bot_desktop/lease.pyr   r   ,   s   † ÝGr   r   c                  óŒ   € ] tR t^0t$ ]tR]R&   RtR]R&   ]! ]	P                  R7      t
R]R&   R	tR]R
&   ^ tR]R&   R R ltRtR# )ÚLeaseÚstrÚholderNúOptional[str]Ú	viewer_id)Údefault_factoryÚfloatÚsinceÚ ÚreasonÚintÚepochc               ó   € V ^8„  d   QhRR/# )é   Úreturnúdict[str, object]r   )Úformats   "r   Ú__annotate__ÚLease.__annotate__8   s   € ÷ ñ Ð*ñ r   c                	ó   € \        V 4      # ©N)r   ©Úselfs   &r   Úas_dictÚLease.as_dict8   s   € Ü�d‹|Ðr   r   )r   r   r   r   ÚAGENTr   Ú__annotations__r    r   Útimer#   r%   r'   r3   r   r   r   r   r   r   0   sF   ‡ à€FˆCÓØ#€Iˆ}Ó#Ù¨¯©Ô3€Eˆ5Ó3Ø€FˆCÓØ€Eˆ3ƒN÷ñ r   r   c               ó    € V ^8„  d   QhRRRR/# )r)   Úleaser   r*   r+   r   )r,   s   "r   r-   r-   <   s   € ÷ ñ �uð Ð!2ñ r   c                óâ   € ^ RI pV P                  4       pRVR&   V P                  '       d?   VP                  V P                  P	                  4       4      P                  4       R,          MRVR&   V# )u4  The lease as anything outside the gateway may see it (RPC results, the ``display.lease`` broadcast,
the CLI): the holder's viewer id is a capability â€” whoever presents it co-drives or releases the lease â€”
so it is replaced by a short hash the holder can match against its own id to know it is in control.Nr    :Né   NÚviewer_hash)Úhashlibr3   r    Úsha256ÚencodeÚ	hexdigest)r9   r=   Úds   &  r   Úpublic_viewrB   <   s\   € ó Ø�‰‹€AØ€A€k�NØTY×Tc×TcÐTc�w—~‘~ e§o¡o×&<Ñ&<Ó&>Ó?×IÑIÓKÈCÖPÐim€A€mÑØ€Hr   z"list[Callable[[str, Lease], None]]Ú
_listenersc               ó    € V ^8„  d   QhRRRR/# )r)   Úprofile_keyr   r*   r   r   )r,   s   "r   r-   r-   K   s   € ÷ /ñ /�}ð /¨ñ /r   c                ó\   € V '       d   \        V 4      M	\        4       pVR,          R,          # )zª``profile_key`` is the HERMES_HOME path of the profile whose lease is meant (the RFB bridge
serves several profiles from one process); ``None`` means the current profile.úbot-desktopú
lease.json)r   r   )rE   Úhomes   & r   Ú_pathrJ   K   s%   € ÷ !,Œ4�Ô´Ó1B€DØ�-Õ ,Õ.Ð.r   c               ó    € V ^8„  d   QhRRRR/# )r)   Úpathr   r*   r   r   )r,   s   "r   r-   r-   R   s   € ÷ ^ñ ^�ð ^˜ñ ^r   c           
     óZ  €  V P                  RR7      p \
        P                  ! T4      p\        T\        4      '       d!   TP                  R4      \        \        39  d   \        \        RRR7      #  \        R	/ TP                  4        UUu/ uF  w  r4T\        P                  9   g   K  Y4bK   	  uppB #   \         d    \        4       u # \         d    \        \        RRR7      u # i ; i  \         d    Rp LÕi ; iu uppi   \         d    \        \        RRR7      u # i ; i)
u  No file = fresh profile, agent holds. A file that exists but cannot be parsed is a torn write
or tampering: fail CLOSED (human holds) â€” an unreadable lease must never let the agent act on a
screen a human may be using; the next successful write repairs it.z	utf-8-sig)Úencodingzunreadable-leasezlease file unreadable)r   r    r%   Nr   zlease file corruptr   )Ú	read_textÚFileNotFoundErrorr   ÚOSErrorÚHUMANÚjsonÚloadsÚ
ValueErrorÚ
isinstanceÚdictÚgetr5   ÚitemsÚ__dataclass_fields__Ú	TypeError)rL   ÚrawÚdataÚkÚvs   &    r   Ú_readr`   R   s	  € ðaØ�n‰n kˆnÓ2ˆð
Ü�zŠz˜#‹ˆô �dœD×!Ò! T§X¡X¨hÓ%7ÄÄu¸~Ô%MÜœEÐ-?ÐH\Ô]Ð]ð^ÜÑZ¨¯©¬ÔY©¡ ¸¼e×>XÑ>XÑ9Xœ˜š©ÒYÑZÐZøô ô Ü‹wŠÜô aÜœEÐ-?ÐH_Ô`Ò`ðaûô ô ØŠðüó
 ZøÜô ^ÜœEÐ-?ÐH\Ô]Ò]ð^úsX   ‚B8 •C1 Á4D
 ÂDÂ,DÂ2D
 Â8C.ÃC.ÃC.Ã-C.Ã1DÄ DÄD
 Ä
D*Ä)D*c               ó    € V ^8„  d   QhRRRR/# )r)   rL   r   r*   ÚNoner   )r,   s   "r   r-   r-   h   s   € ÷ ñ �tð  ñ r   c                óV   € V P                   P                  RRR7       \        V 4       R# )z³``bot-desktop/`` owner-only even when the lease is the first thing written there (a takeover can be
recorded before start() ever ran, and the umask would otherwise leave it 0755).T)ÚparentsÚexist_okN)ÚparentÚmkdirr   )rL   s   &r   Ú_private_dirrh   h   s#   € ð 	‡K�K×Ñ˜d¨TÐÔ2Ü�dÖr   c               ó$   € V ^8„  d   QhRRRRRR/# )r)   rL   zstr | bytes | os.PathLikeÚflagsr&   r*   r   )r,   s   "r   r-   r-   o   s"   € ÷ 'ñ 'Ð1ð '¸#ð 'À#ñ 'r   c                ó0   € \         P                  ! WR4      # )zV``open(..., opener=_open_private)``: the file is created 0600 regardless of the umask.i€  )ÚosÚopen)rL   rj   s   &&r   Ú_open_privatern   o   s   € ä�7Š7�4 Ó&Ð&r   c               ó$   € V ^8„  d   QhRRRRRR/# )r)   rL   r   r9   r   r*   rb   r   )r,   s   "r   r-   r-   t   s!   € ÷ ñ �ð ˜eð ¨ñ r   c                ó>  € \        V 4       V P                  R 4      p\        VRR\        R7      ;_uu_ 4       pVP	                  \
        P                  ! VP                  4       4      4       RRR4       \        P                  ! W 4       R#   + '       g   i     L(; i)ú	.json.tmpÚwúutf-8©rN   ÚopenerN)
rh   Úwith_suffixrm   rn   ÚwriterS   Údumpsr3   rl   Úreplace)rL   r9   ÚtmpÚfhs   &&  r   Ú_writer|   t   sd   € Ü�ÔØ
×
Ñ
˜;Ó
'€CÜ	ˆc�3 ´×	?Õ	?À2Ø
�‰”—’˜EŸM™M›OÓ,Ô-÷ 
@ä‡J‚JˆsÖ÷ 
@×	?ús   ¸4BÂB	c                  ó2   € ] tR t^|tRtR R ltR tR tRtR# )Ú_lockedzRCross-process critical section over the lease file (fcntl on a sibling lock file).c               ó   € V ^8„  d   QhRR/# )r)   rL   r   r   )r,   s   "r   r-   Ú_locked.__annotate__   s   € ÷ ñ ˜Tñ r   c                	ó@   € VP                  R 4      V n        RV n        R# )ú.lockN)rv   Ú	_lockfileÚ_fh)r2   rL   s   &&r   Ú__init__Ú_locked.__init__   s   € Ø×)Ñ)¨'Ó2ˆŒØˆŽr   c                	ó  € \         f   V # \        V P                  4       \        V P                  RR\        R7      V n        \         P                  ! V P
                  P                  4       \         P                  4       V # )Nza+rs   rt   )	Úfcntlrh   rƒ   rm   rn   r„   ÚflockÚfilenoÚLOCK_EXr1   s   &r   Ú	__enter__Ú_locked.__enter__ƒ   sP   € ÜŠ=ØˆKÜ�T—^‘^Ô$Ü˜Ÿ™¨°wÄ}ÔUˆŒÜ�Š�D—H‘H—O‘OÓ%¤u§}¡}Ô5Øˆr   c                	óÔ   € V P                   f   R # \        P                  ! V P                   P                  4       \        P                  4       V P                   P                  4        R # r0   )r„   rˆ   r‰   rŠ   ÚLOCK_UNÚclose)r2   Úexcs   &*r   Ú__exit__Ú_locked.__exit__‹   s:   € Ø�8‰8ÒÙÜ�Š�D—H‘H—O‘OÓ%¤u§}¡}Ô5Ø�‰�‰Ör   )r„   rƒ   N)	r   r   r   r   r   r…   rŒ   r’   r   r   r   r   r~   r~   |   s   † Ù\õòör   r~   c               ó    € V ^8„  d   QhRRRR/# ©r)   rE   r   r*   r   r   )r,   s   "r   r-   r-   ’   s   € ÷ %ñ %�]ð %¨eñ %r   c                ó*   € \        \        V 4      4      # r0   )r`   rJ   ©rE   s   &r   rX   rX   ’   s   € Ü”�{Ó#Ó$Ð$r   c               ó    € V ^8„  d   QhRRRR/# )r)   ÚlistenerzCallable[[str, Lease], None]r*   zCallable[[], None]r   )r,   s   "r   r-   r-   –   s   € ÷ 
ñ 
Ð4ð 
Ð9Kñ 
r   c                ó˜   a € \         ;_uu_ 4        \        P                  S 4       RRR4       R V 3R llpV#   + '       g   i     L; i)z³Subscribe to lease transitions made IN THIS PROCESS (the gateway broadcasts them to Desktop
clients). Transitions made by another process are observed by reading, not by callback.Nc               ó   € V ^8„  d   QhRR/# ©r)   r*   rb   r   )r,   s   "r   r-   Úon_change.<locals>.__annotate__œ   s   € ÷ ,ñ ,�$ñ ,r   c                 óž   <€ \         ;_uu_ 4        S \        9   d   \        P                  S 4       R R R 4       R #   + '       g   i     R # ; ir0   )Ú_lockrC   Úremove)r™   s   €r   Ú_offÚon_change.<locals>._offœ   s*   ø€ ßŽUØœ:Ô%Ü×!Ñ! (Ô+÷ �U�UŠUús	   �!;»A	)rŸ   rC   Úappend)r™   r¡   s   f r   Ú	on_changer¤   –   s6   ø€ ÷ 
ŽÜ×Ñ˜(Ô#÷ 
÷,ð ,ð €K÷ 
�ús	   �9¹A		c               ó$   € V ^8„  d   QhRRRRRR/# )r)   Úkeyr   r9   r   r*   rb   r   )r,   s   "r   r-   r-   £   s!   € ÷ 
ñ 
�ð 
˜Uð 
 tñ 
r   c                óÀ   € \         ;_uu_ 4        \        \        4      pR R R 4       X F  p V! W4       K  	  R #   + '       g   i     L$; i  \         d     K3  i ; ir0   )rŸ   ÚlistrC   Ú	Exception)r¦   r9   Ú	listenersÚcbs   &&  r   Ú_notifyr¬   £   sI   € ÷ 
ŽÜœÓ$ˆ	÷ 
ãˆð	ÙˆsŽNó ÷ 
�ûô
 ô 	Úð	ús   �;­A»A	ÁAÁAc               ó$   € V ^8„  d   QhRRRRRR/# )r)   rE   r   ÚmutatezCallable[[Lease], bool]r*   r   r   )r,   s   "r   r-   r-   °   s#   € ÷ ñ ˜]ð Ð4Kð ÐPUñ r   c                óØ  € V '       d   \        V 4      M	\        4       \        V 4      r2\        V4      ;_uu_ 4        \        V4      pV! V4      '       g   VuuR R R 4       # V;P                  ^,          un        \        W44       R R R 4       \        ;_uu_ 4        \        P                  4        R R R 4       \        VX4       V#   + '       g   i     LI; i  + '       g   i     L1; ir0   )	r   rJ   r~   r`   r'   r|   rŸ   Ú
notify_allr¬   )rE   r®   r¦   rL   r9   s   &&   r   Ú_transitionr±   °   s•   € ß0;” Ô,ÄÓARÔTYÐZeÓTfˆÜ	���Ü�d“ˆÙ�e�}Š}Ø÷ 
Šð 	�Š�qÕ�ÜˆtÔ÷ 
÷ 
ŽÜ×ÑÔ÷ 
äˆC�ÔØ€L÷ 
�ú÷ 
�ús   »CÁ!$CÂCÃC	ÃC)	rE   r%   r$   c               ó(   € V ^8„  d   QhRRRRRRRR/# )r)   r    r   rE   r   r%   r*   r   r   )r,   s   "r   r-   r-   ¾   s)   € ÷ (ñ (�sð (¨Mð (È#ð (ÐW\ñ (r   c               ó0   a a€ R VV 3R llp\        W4      # )z¡Human ``viewer_id`` takes control. Last writer wins: a second viewer evicts the first, and the
RFB bridge closes the evicted socket so its UI drops to view-only.c               ó    € V ^8„  d   QhRRRR/# ©r)   r9   r   r*   Úboolr   )r,   s   "r   r-   Úacquire.<locals>.__annotate__Á   s   € ÷ ñ �%ð ˜Dñ r   c                óÔ   <€ V P                   \        8X  d   V P                  S8X  d   R # \        S\        P                  ! 4       uV n         V n        V n        S;'       g    RV n        R# )Fr$   T)r   rR   r    r7   r#   r%   )r9   r%   r    s   &€€r   Ú_mÚacquire.<locals>._mÁ   sK   ø€ Ø�<‰<œ5Ô  U§_¡_¸	Ô%AÙô 6;¸IÄtÇyÂyÃ{Ð2ˆŒ�e”o u¤{Ø—|�| ˆŒÙr   ©r±   )r    rE   r%   r¹   s   f$d r   Úacquirer¼   ¾   s   ù€ ÷ñ ô �{Ó'Ð'r   Úunless_humanFc               ó(   € V ^8„  d   QhRRRRRRRR/# )r)   r    r   rE   r½   r¶   r*   r   r   )r,   s   "r   r-   r-   Ì   s*   € ÷ (ñ (�}ð (¸]ð (Øð(Ø+0ñ(r   c               ó0   a a€ R VV 3R llp\        W4      # )a®  Return control to the agent. With ``viewer_id`` only that holder may release (a stale viewer
closing its window must not yank control from the one who took over after it). ``unless_human``
makes a bare release a no-op while any human holds: the decision is taken under the file lock,
so a takeover racing a check-then-release cannot be silently revoked. Callers read the returned
lease's holder to learn whether anything happened.c               ó    € V ^8„  d   QhRRRR/# rµ   r   )r,   s   "r   r-   Úrelease.<locals>.__annotate__Ó   s   € ÷ ñ �%ð ˜Dñ r   c                óŽ  <€ S'       d-   V P                   \        8X  d   \        P                  R 4       R# Se?   V P                   \        8X  d*   V P                  S8w  d   \        P                  RS4       R# V P                   \
        8X  d   R# \
        R\        P                  ! 4       R3w  V n         V n        V n        V n        R# )z6bot-desktop lease: bare release ignored, a human holdsFNz>bot-desktop lease: release by %r ignored, another viewer holdsr$   T)	r   rR   ÚloggerÚinfor    r5   r7   r#   r%   )r9   r½   r    s   &€€r   r¹   Úrelease.<locals>._mÓ   s‘   ø€ ß˜EŸL™L¬EÔ1Ü�K‰KÐPÔQÙØÒ  U§\¡\´UÔ%:¸u¿¹ÐR[Ô?[ô �K‰KÐXÐZcÔdÙØ�<‰<œ5Ô ñ ÜCHÈ$ÔPT×PYÒPYÓP[Ð]_ÐC_Ñ@ˆŒ�e”o u¤{°E´LÙr   r»   )r    rE   r½   r¹   s   f$d r   ÚreleaserÆ   Ì   s   ù€ ÷ñ ô �{Ó'Ð'r   c               ó    € V ^8„  d   QhRRRR/# )r)   rE   r   r*   r¶   r   )r,   s   "r   r-   r-   å   s   € ÷ ,ñ ,˜]ð ,°dñ ,r   c                ó:   € \        V 4      P                  \        8H  # r0   )rX   r   rR   r—   s   &r   Úhuman_holdsrÉ   å   s   € Üˆ{Ó×"Ñ"¤eÑ+Ð+r   c               ó$   € V ^8„  d   QhRRRRRR/# )r)   r    r   rE   r   r*   r¶   r   )r,   s   "r   r-   r-   é   s'   € ÷ Bñ B Sð B¸-ð BÐSWñ Br   c               ól   € \        V4      pVP                  \        8H  ;'       d    VP                  V 8H  # r0   )rX   r   rR   r    )r    rE   r9   s   &$ r   Úviewer_may_send_inputrÌ   é   s,   € Ü�Ó€EØ�<‰<œ5Ñ ×AÐA U§_¡_¸	Ñ%AÐAr   c               ó    € V ^8„  d   QhRRRR/# r•   r   )r,   s   "r   r-   r-   î   s   € ÷ ñ  mð ¸uñ r   c                ó\   € \        V 4      pVP                  \        8X  d   \        R4      hV# )z»The lease as of now, or ``HumanHasControl``. Callers keep the returned ``epoch`` and compare it
with ``get().epoch`` after an admitted action: a change means a human took over mid-flight.z¹A human has taken over this desktop (they may be entering a credential). Screen actions and captures are refused until they hand control back. Tell the user what you need in your reply.)rX   r   rR   r   )rE   r9   s   & r   Úassert_agent_may_actrÏ   î   s6   € ô �Ó€EØ‡|�|”uÔÜðlómð 	mð €Lr   c               ó   € V ^8„  d   QhRR/# rœ   r   )r,   s   "r   r-   r-   ù   s   € ÷ ñ ˜$ñ r   c                 óJ  € \         ;_uu_ 4        \        P                  4        R R R 4       \        4       R,          R,          p W P	                  R4      V P	                  R4      3 F  p VP                  4        K  	  R #   + '       g   i     Ld; i  \         d     K;  i ; i)NrG   rH   r‚   rq   )rŸ   rC   Úclearr   rv   ÚunlinkrQ   )ÚpÚfs     r   Ú_reset_for_testsrÖ   ù   su   € ß	ŽÜ×ÑÔ÷ 
äÓ˜MÕ)¨LÕ8€AØ—‘˜wÓ'¨¯©°{Ó)CÓDˆð	Ø�H‰HŽJó E÷ 
�ûô ô 	Úð	ús   �B Á*BÂ B	ÂB"Â!B"r0   )7Ú__conditional_annotations__r   Ú
__future__r   rS   Úloggingrl   Ú	threadingr7   Údataclassesr   r   r   Úpathlibr   Útypingr   r   r	   r
   Úhermes_constantsr   r   r   rˆ   ÚImportErrorÚ	getLoggerr   rÃ   r5   rR   ÚRuntimeErrorr   r   rB   Ú	ConditionrŸ   rC   r6   rJ   r`   rh   rn   r|   r~   rX   r¤   r¬   r±   r¼   rÆ   rÉ   rÌ   rÏ   rÖ   )r×   s   @r   Ú<module>rã      s8  øðôõ& #ã Û Û 	Û Û ß 0Ñ 0Ý ß 1Ó 1ç PÑ PðÛð 
×	Ò	˜8Ó	$€à€Ø€ôH�lô Hð ÷ð ó ðõð 	×ÒÓ€Ø13€
Ð.Ó 3õ/õ^õ,õ'õ
÷ñ ÷,%õ
õ
õð(¸Dð (ÐPR÷ (ñ(ÈTð (Ø!&÷(ð (÷2,ðBÈ$÷ B÷
÷øðm ô Ø‚Eðús   ÁD Ä	D$Ä#D$