+
    „WÝjÂ*  ã                  ó(  € R t ^ RIHt ^ RIt^ RIt^ RIHt ^ RIHt ^ RI	H
t
HtHt ^ RIHt ]P                  ! ]4      t]
! 4       tRtRtRt]! R	R
04      tRtRtRtRtRtR R ltR R lt]P?                  R4      R R l4       t R R lt!R R lt"R# )u7  ``/api/display/ws`` â€” raw RFB over WebSocket for the Bot Desktop viewer.

The Desktop renderer calls ``display.observe`` on its authenticated ``/api/ws`` connection, gets a
single-use 30 s ticket pinned to that profile's RFB socket, then opens this route with
``?display_ticket=``. No websockify, no new port: the bridge splices the profile's 0600 Unix socket
into the WebSocket as binary frames with backpressure both ways, and runs the client stream through
:class:`tools.bot_desktop.rfb_filter.RfbClientFilter` so keyboard, pointer and clipboard reach Xvnc
only from the viewer that currently holds the lease. noVNC's ``viewOnly`` is UX; this is the gate.

A lease change closes the evicted viewer's socket with 4000 ``control-taken`` so its UI drops back to
Watch mode and reconnects.

Design notes. The ticket rides in the query string on purpose: noVNC's Websock owns the socket and
cannot negotiate a subprotocol or add a header, and the ticket is single-use and expires in 30 s, so
a logged URL is spent by the time anyone reads it. The bridge's input gate re-reads ``lease.json`` at
most every ``_LEASE_REFRESH_S`` (250 ms) between in-process ``on_change`` callbacks: that bounds how
long another PROCESS's takeover can go unnoticed, and is the price of not stat-ing the lease file on
the event loop for every pointer move.
)ÚannotationsN)ÚPath)ÚOptional)Ú	APIRouterÚ	WebSocketÚWebSocketDisconnect)Ú_ws_request_is_allowedg      N@i   iè  ié  i¡  i1  i3  ië  g      Ð?c               ó$   € V ^8„  d   QhRRRRRR/# )é   ÚheldÚdictÚ	viewer_idÚstrÚreturnÚbool© )Úformats   "ÚRC:\Users\johnn\AppData\Local\hermes\hermes-agent\hermes_cli\web_routers\display.pyÚ__annotate__r   .   s!   € ÷ ñ ˜ð °ð ¸ñ ó    c                ó¦   € ^ RI Hp VP                  VP                  8w  d   RV R&   R# VP                  V8X  d   RV R&   R# \        V R,          4      # )a~  A viewer that held control during this connection and lost it to ANOTHER human is kicked so its
UI repaints; a plain hand-back to the agent, pure watchers and the new holder stay connected. The
hand-back also forgets that this viewer ever held: after it, they are a plain watcher again and a
later takeover by someone else must not evict them. ``held`` is the per-connection memory.©ÚleaseFÚeverT)Útools.bot_desktopr   ÚholderÚHUMANr   r   )r   r   r   Ú_leases   &&& r   Ú_should_evictr   .   sJ   € õ
 2Ø‡|�|�v—|‘|Ô#ØˆˆV‰ÙØ‡�˜)Ô#ØˆˆV‰ÙÜ��V•ÓÐr   c               ó    € V ^8„  d   QhRRRR/# )r
   Úwsr   r   zOptional[dict]r   )r   s   "r   r   r   =   s   € ÷ ñ  	ð ¨nñ r   c                óî   € ^ RI HpHp V P                  P	                  RR4      pV'       g   R#  V! V4      pTP	                  R4      R8w  g   TP	                  R4      '       g   R# T#   T d     R# i ; i)é    )ÚTicketInvalidÚconsume_ticketÚdisplay_ticketÚ NÚproviderúbot-desktopÚhermes_home)Ú$hermes_cli.dashboard_auth.ws_ticketsr#   r$   Úquery_paramsÚget)r    r#   r$   ÚticketÚinfos   &    r   Ú_consume_display_ticketr/   =   sn   € ßRØ�_‰_× Ñ Ð!1°2Ó6€FßÙðÙ˜fÓ%ˆð ‡x�x�
Ó˜}Ô,°D·H±H¸]×4KÒ4KÙØ€Køð	 ô Úðús   °A) Á)A4Á3A4z/api/display/wsc               ó    € V ^8„  d   QhRRRR/# )r
   r    r   r   ÚNoner   )r   s   "r   r   r   L   s   € ÷ ñ ˜ð  tñ r   c              ƒ  ó6  "  € \        V 4      '       g!   V P                  \        R 7      G Rj  x€L
  R# V P                  4       G Rj  x€L
  \	        V 4      pVf"   V P                  \
        RR7      G Rj  x€L
  R# \        W4      G Rj  x€L
  R#  Lc LK L L5i))ÚcodeNz'display ticket missing, expired or used©r3   Úreason)r   ÚcloseÚ_CLOSE_NOT_ALLOWEDÚacceptr/   Ú_CLOSE_BAD_TICKETÚ_bridge)r    r.   s   & r   Ú
display_wsr;   K   s‚   é € ô " "×%Ò%Ø�h‰hÔ.ˆhÓ/×/Ð/ÙØ
�)‰)‹+×ÐÜ" 2Ó&€DØ‚|Ø�h‰hÔ-Ð6_ˆhÓ`×`Ð`ÙÜ
�"Ó
×Òñ 	0áñ 	aáùsE   ‚+B­B®BÁBÁ-BÁ5BÁ6BÂ
BÂBÂBÂBÂBc               ó   € V ^8„  d   QhRR/# )r
   Úprofile_homer   r   )r   s   "r   r   r   ]   s   € ÷ !ñ ! $ñ !r   c              ƒ  óº  a"  € ^ RI HpHp ^ RIHp V! V 4      p VP                  4       '       d   VP                  4       pMhV R,          R,          pVP                  4       '       g   \        R4      h\        P                  ! \        V4      4      G Rj  x€L
 w  opSVR3V! V4       #  V! V4       \        P                  ! 4       p\        P                  ! 4       oVP                  V3R lVP                  4      G Rj  x€L
  VP!                  R VP"                  4      G Rj  x€L
 w  rš\        P$                  ! WšRV4      pSWu3#  L²  T! T4       i ; i LV L35i)	a  ``(reader, writer, relay)`` for THIS profile's Xvnc. Gateway-hosted screen: its unix socket. Screen inside
the terminal backend: a ``docker exec`` / ``ssh`` relay whose stdio IS the RFB stream (``relay`` is that
Popen; None for a socket). Raises OSError when nothing is running.)Úreset_hermes_home_overrideÚset_hermes_home_override)Úruntimer(   zrfb.sockzrfb.sock missingNc                 ó0   <€ \         P                  ! S 4      # ©N)ÚasyncioÚStreamReaderProtocol)Úreaders   €r   Ú<lambda>Ú_open_rfb.<locals>.<lambda>q   s   ø€ ¬×)EÒ)EÀfÔ)Mr   c                 óT   € \         P                  ! \         P                  ! 4       4      # rC   )rD   rE   ÚStreamReaderr   r   r   rG   rH   t   s   € ÄG×D`ÒD`Ôah×auÒauÓawÔDxr   )Úhermes_constantsr?   r@   r   rA   Úsandbox_screen_runningÚopen_rfb_streamÚexistsÚOSErrorrD   Úopen_unix_connectionr   Úget_running_looprJ   Úconnect_read_pipeÚstdoutÚconnect_write_pipeÚstdinÚStreamWriter)r=   r?   r@   Ú_bd_runtimeÚtokenÚrelayÚsockÚwriterÚloopÚw_transportÚ
w_protocolrF   s   &          @r   Ú	_open_rfbr_   ]   sA  øé € ÷ VÝ8Ù$ \Ó2€Eð
*Ø×-Ñ-×/Ò/Ø×/Ñ/Ó1‰Eà -Õ/°*Õ<ˆDØ—;‘;—=’=ÜÐ0Ó1Ð1Ü#*×#?Ò#?ÄÀDÃ	Ó#J×J‰NˆF�FØ˜6 4Ð'á" 5Õ)ð ñ 	# 5Ô)Ü×#Ò#Ó%€DÜ×!Ò!Ó#€FØ
×
 Ñ
 Ô!MÈuÏ|É|Ó
\×\Ð\ð %)×$;Ñ$;Ñ<xÐz÷  {Fñ  {Fó  %G÷  GÑ€KÜ×!Ò! +¸4ÀÓF€FØ�6Ð Ð ñ Køñ 	# 5Õ)úñ ]ñ GùsZ   ƒE›AE
 Á(-E
 ÂEÂE
 Â!	EÂ+AEÄ EÄ$EÄ%EÄ&"EÅE
 Å

EÅEÅEc               ó$   € V ^8„  d   QhRRRRRR/# )r
   r    r   r.   r   r   r1   r   )r   s   "r   r   r   y   s&   € ÷ {ñ {�ið { tð {°ñ {r   c              ƒ  ó$	  a aaaaaaaaaaaaaaaa"  € ^ RI Hp ^ RIHo ^ RIHp \        VR,          4      oV! S4      o\        VP                  R4      ;'       g    VP                  R4      ;'       g    R4      o \        \        S4      4      G Rj  x€L
 w  oop\        P                   ! 4       o\        P"                  ! 4       oRSP%                  SSR7      /oRSR,          RSP'                  4       /oR%R TTTTT3R llloR TTT3R llpR TTTTTT3R llpSP)                  T4      pT! T4      o\        P"                  ! 4       o\        S4      R,          R,          oRR/oR TTT3R lloR TTT 3R llp	R TTTT 3R llp
R TT 3R  llp\        P*                  ! T	! 4       4      \        P*                  ! T
! 4       4      \        P*                  ! T! 4       4      .p \        P,                  ! T\        P.                  R!7      G Rj  x€L
 w  rÞT F  pTP1                  4        K  	  \        P2                  ! TR"R#/ G Rj  x€L
  T FQ  pTP5                  4       pT'       g   K  \7        T\8        \:        34      '       d   K;  \        P=                  R$T4       KS  	  T! 4        SP                  4         SP?                  4       G Rj  x€L
  Te   TPA                  4        SPC                  4       '       d-   SP%                  SSR7      '       d   SPE                  SSR7        S P                  4       G Rj  x€L
  R#  EL²  \         dC   p\        P                  R	ST4       S P                  \        R
R7      G Rj  x€L 
   Rp?R# Rp?ii ; i EL‰ ELS LÎ  \         d     LÙi ; i Lr  \F         d     R# i ; i  T! 4        SP                  4         SP?                  4       G Rj  x€L 
  M  \         d     Mi ; iTe   TPA                  4        SPC                  4       '       d-   SP%                  SSR7      '       d   SPE                  SSR7        S P                  4       G Rj  x€L 
  i   \F         d     i i ; i; i5i)&zhPump RFB bytes between the viewer socket (already accepted) and THIS profile's Xvnc, gated by
the lease.)Úhermes_home_keyr   )ÚRfbClientFilterr)   r   Úuser_idÚviewerNz0display ws: cannot reach RFB endpoint for %s: %szBot Desktop is not runningr4   r   ©Úprofile_keyÚinputÚatc               ó   € V ^8„  d   QhRR/# ©r
   r   r1   r   )r   s   "r   r   Ú_bridge.<locals>.__annotate__’   s   € ÷ $ñ $¨ñ $r   c                óÀ   <€ V f   SP                  SR7      p V P                  SP                  8H  ;'       d    V P                  S8H  SR&   SP	                  4       SR&   R # )Nrf   rh   ri   )r,   r   r   r   Útime)r   r   Úallowedr\   r=   r   s   &€€€€€r   Ú_refresh_allowedÚ!_bridge.<locals>._refresh_allowed’   sO   ø€ ØŠ=Ø—J‘J¨<�JÓ8ˆEØ Ÿ<™<¨6¯<©<Ñ7×XÐX¸E¿O¹OÈyÑ<Xˆ�ÑØŸ	™	›ˆ�‹r   c               ó   € V ^8„  d   QhRR/# )r
   r   r   r   )r   s   "r   r   rl   ˜   s   € ÷  ñ  ˜Tñ  r   c                 ór   <€ SP                  4       SR ,          ,
          \        8”  d   S ! 4        SR,          # )ri   rh   )rn   Ú_LEASE_REFRESH_S)rp   ro   r\   s   €€€r   Ú_may_send_inputÚ _bridge.<locals>._may_send_input˜   s,   ø€ Ø�9‰9‹;˜ �Õ&Ô)9Ô9ÙÔØ�wÕÐr   c               ó    € V ^8„  d   QhRRRR/# )r
   Úkeyr   r   r1   r   )r   s   "r   r   rl   �   s   € ÷ 3ñ 3�sð 3 dñ 3r   c                óž   <€ V S8w  d   R # SP                  SV4       \        SVS4      '       d   SP                  SP                  4       R # R # rC   )Úcall_soon_threadsafer   Úset)rx   r   rp   Úevictedr   r\   rg   r   s   &&€€€€€€r   Ú	_on_leaseÚ_bridge.<locals>._on_lease�   sE   ø€ Ø�+ÔÙØ×!Ñ!Ð"2°EÔ:Ü˜˜u i×0Ò0Ø×%Ñ% g§k¡kÖ2ñ 1r   r(   Úactivityg        c               ó   € V ^8„  d   QhRR/# rk   r   )r   s   "r   r   rl   «   s   € ÷ ñ ˜Tñ r   c                 óÆ   <€ SP                  4       SR ,          ,
          \        8  d   R# SP                  4       SR &    S P                  4        R#   \         d     R# i ; i)ri   N)rn   Ú_ACTIVITY_STAMP_SÚtouchrO   )Úactivity_filer\   Ústampeds   €€€r   Ú_stamp_activityÚ _bridge.<locals>._stamp_activity«   sP   ø€ à�9‰9‹;˜ �Õ&Ô):Ô:ÙØŸ	™	›ˆ�‰ð	Ø×ÑÖ!øÜô 	Úð	ús   ¿A ÁA ÁA c               ó   € V ^8„  d   QhRR/# rk   r   )r   s   "r   r   rl   µ   s   € ÷ 'ñ '˜Tñ 'r   c               “  ó¨   <"  €  SP                  \        4      G Rj  x€L
 p V '       g   R# S! 4        SP                  V 4      G Rj  x€L
  KJ   L0 L5i)TN)ÚreadÚ_READ_CHUNKÚ
send_bytes)Úchunkr†   rF   r    s    €€€r   Ú	rfb_to_wsÚ_bridge.<locals>.rfb_to_wsµ   sB   øé € ØØ Ÿ+™+¤kÓ2×2ˆEßÙÙÔØ—-‘- Ó&×&Ò&ñ	 3ñ 'ùs&   ƒA�AžAªAÁAÁAÁAc               ó   € V ^8„  d   QhRR/# rk   r   )r   s   "r   r   rl   ½   s   € ÷ %ñ %˜Tñ %r   c               “  óZ  <"  €  SP                  4       G Rj  x€L
 p V P                  R4      R8X  d-   V P                  R4      \        9   d   SP                  4        R# V P                  R4      pVf"   SP	                  \
        RR7      G Rj  x€L
  R#  SP                  V4      pT'       g   K­  SP                  T4       SP                  4       G Rj  x€L
  KØ   LÃ LO  \         d<   pSP	                  \
        \        T4      R,          R7      G Rj  x€L 
   Rp?R# Rp?ii ; i LS5i)	TNÚtypezwebsocket.disconnectr3   ÚbyteszRFB is binaryr4   :Néd   N)Úreceiver,   Ú_CLEAN_CLOSEr{   r6   Ú_CLOSE_PROTOCOLÚfeedÚ
ValueErrorr   ÚwriteÚdrain)ÚmessageÚdataro   ÚexcÚ
rfb_filterÚviewer_closedr[   r    s       €€€€r   Ú	ws_to_rfbÚ_bridge.<locals>.ws_to_rfb½   sí   øé € ØØŸJ™J›L×(ˆGØ�{‰{˜6Ó"Ð&<Ô<à—;‘;˜vÓ&¬,Ô6Ø!×%Ñ%Ô'ÙØ—;‘;˜wÓ'ˆDØŠ|Ø—h‘h¤O¸O�hÓL×LÐLÙðØ$Ÿ/™/¨$Ó/�÷ ‰wØ—‘˜WÔ%Ø—l‘l“n×$Ò$ñ# )ñ Møô ô Ø—h‘h¤O¼CÀ»HÀT½N�hÓK×KÑKÝûðúñ
 %ùsj   ƒD+˜C™A5D+ÂCÂD+ÂC  Â'D+Â1$D+ÃD)ÃD+ÃD+Ã D&Ã+*D!ÄDÄD!ÄD+Ä!D&Ä&D+c               ó   € V ^8„  d   QhRR/# rk   r   )r   s   "r   r   rl   Ò   s   € ÷ Jñ J $ñ Jr   c               “  ó†   <"  € S P                  4       G R j  x€L
  SP                  \        RR7      G R j  x€L
  R #  L% L5i)Nzcontrol-takenr4   )Úwaitr6   Ú_CLOSE_CONTROL_TAKEN)r|   r    s   €€r   Úwatch_evictionÚ_bridge.<locals>.watch_evictionÒ   s4   øé € Ø�l‰l‹n×ÐØ�h‰hÔ0¸ˆhÓI×IÒIñ 	ÙIùs   ƒA—=˜A¶?·A¿A)Úreturn_whenÚreturn_exceptionsTzdisplay ws ended: %rrC   )$rK   rb   r   r   Útools.bot_desktop.rfb_filterrc   r   r,   r_   r   rO   Ú_logÚwarningr6   Ú_CLOSE_DESKTOP_GONErD   rQ   ÚEventÚviewer_may_send_inputrn   Ú	on_changeÚcreate_taskr¥   ÚFIRST_COMPLETEDÚcancelÚgatherÚ	exceptionÚ
isinstancer   ÚConnectionErrorÚdebugÚwait_closedÚkillÚis_setÚreleaseÚ	Exception) r    r.   rb   rc   rY   rž   ru   r}   ÚunsubscriberŽ   r¡   r§   ÚtasksÚdoneÚpendingÚtr   rp   r†   r„   ro   r|   r   r\   r=   rg   rF   rŸ   r…   r    r   r[   s    f&              @@@@@@@@@@@@@@@@r   r:   r:   y   s¶  ÿÿøé € õ 1Ý1Ý<ä�t˜MÕ*Ó+€LÙ! ,Ó/€KÜ�D—H‘H˜[Ó)×LÐL¨T¯X©X°iÓ-@×LÐLÀHÓM€IðÜ&/´°\Ó0BÓ&C× CÑˆ�˜ô ×#Ò#Ó%€DÜ�mŠm‹o€GØ�F×0Ñ0°ÈÐ0ÓUÐV€Dð ˜˜V� d¨D¯I©I«KÐ8€G÷$ö $÷ ò  ÷
3õ 3ð ×"Ñ" 9Ó-€Ká  Ó1€Jä—M’M“O€MÜ˜Ó&¨Õ6¸ÕC€MØ�Sˆk€G÷ò ÷'ò '÷%ó %÷*Jñ Jô × Ò ¡£Ó-¬w×/BÒ/BÁ9Ã;Ó/OÜ× Ò ¡Ó!1Ó2ð4€EðÜ%Ÿlšl¨5¼g×>UÑ>UÔV×V‰ˆÛˆAØ�H‰HŽJñ ô �nŠn˜gÐ>¸Ñ>×>Ð>ÛˆAØ—+‘+“-ˆCß‰sœ: cÔ,?ÄÐ+Q×RÔRÜ—
‘
Ð1°3Ö7ñ ñ
 	ŒØ�‰Œð	Ø×$Ñ$Ó&×&Ð&ð ÒØ�J‰JŒLð ×Ñ×!Ò! f×&BÑ&BÀ9ÐZfÐ&B×&gÒ&gØ�N‰N˜9°,ˆNÔ?ð	Ø—(‘(“*×Òò] !DøÜô Ü�‰ÐGÈÐWZÔ[Ø�h‰hÔ/Ð8TˆhÓU×UÑUÝûðúòh Wò
 	?ñ 'øÜô 	Ùð	úñ øÜô 	Úð	ûñ 	ŒØ�‰Œð	Ø×$Ñ$Ó&×&Ò&øÜô 	Ùð	úàÒØ�J‰JŒLð ×Ñ×!Ò! f×&BÑ&BÀ9ÐZfÐ&B×&gÒ&gØ�N‰N˜9°,ˆNÔ?ð	Ø—(‘(“*×ÒøÜô 	Ùð	ÿs�  “A"RÁ6RÁ>M
 ÂMÂM
 ÂE	RÇ()O ÈNÈ8O É
NÉO É.O ÊO Ê&RÊ>N" ËN ËN" Ë)RÌ RÌRÌ-N5 Í N3ÍN5 ÍRÍM
 Í
NÍ1NÎN	ÎNÎRÎNÎRÎO ÎO Î N" Î"N0Î-RÎ/N0Î0RÎ3N5 Î5OÏ RÏOÏRÏRÏ O:Ï3O6Ï4O:Ï9RÏ:PÐRÐPÐ,RÐ5RÑRÑ"Q<Ñ5Q8Ñ6Q<Ñ;RÑ<R
ÒRÒ	R
Ò
RÒRi   )#Ú__doc__Ú
__future__r   rD   ÚloggingÚpathlibr   Útypingr   Úfastapir   r   r   Úhermes_cli.web_server_chatr   Ú	getLoggerÚ__name__r¬   Úrouterr‚   r‹   r¦   Ú	frozensetr–   r®   r9   r7   r—   rt   r   r/   Ú	websocketr;   r_   r:   r   r   r   Ú<module>rÐ      s¬   ðñõ( #ã Û Ý Ý ç =Ñ =å =à×Ò˜Ó"€Ù	‹€àÐ Ø€ØÐ Ù˜$ ˜Ó&€ØÐ ØÐ ØÐ Ø€ØÐ õõð ×ÑÐ#Ó$ôó %ðõ"!÷8{r   