Ë
    ãß²jo  ã                  ó  — U d Z ddlmZ ddlZddlZddlZddlZddlZddlZddl	Z	ddl
Z
ddlmZmZmZmZ ddlmZmZmZ ddlZddlmZ ddlmZmZmZ ddlmZmZ dd	lmZ dd
l m!Z!  ejD                  e#«      Z$dZ%dZ&da'de(d<   da)i Z*de(d<   da+de(d<   da,dGd„Z-dHd„Z.dId„Z/dId„Z0dJd„Z1dKd„Z2dJd„Z3dLd„Z4dMd„Z5dNdOd„Z6ddœdPd„Z7dQd „Z8dRd!„Z9dSd"„Z:dTd#„Z;i Z<d$e(d%<    e	jz                  «       Z>dUd&„Z?dVd'„Z@dWdXd(„ZAdYd)„ZBdNdZd*„ZCdNd[d+„ZDdNd\d,„ZEd]d^d-„ZFd]d_d.„ZGd]d\d/„ZHd`d0„ZIdad1„ZJdbd2„ZKdcd3„ZLddd4„ZMded5„ZNdNdfd6„ZOdgd7„ZPdNdhd8„ZQdid9„ZR	 	 	 	 	 	 djd:„ZSdkdld;„ZTdNdmd<„ZUdNdnd=„ZVdNdod>„ZWdNdpd?„ZXdNdqd@„ZYdNdpdA„ZZdNdpdB„Z[drdC„Z\dsdD„Z]dtdudE„Z^dtdvdF„Z_y)wuT  Camofox browser backend â€” local anti-detection browser via REST API.

Camofox-browser (https://github.com/jo-inc/camofox-browser) is a self-hosted Node.js
server wrapping Camoufox (Firefox fork with C++ fingerprint spoofing); its REST API maps
1:1 to our browser tool interface (accessibility snapshots with element refs, click/type/
scroll by ref, screenshots). Setup: ``npm start`` in a checkout or ``docker run -p 9377:9377
-e CAMOFOX_PORT=9377 jo-inc/camofox-browser``, then ``CAMOFOX_URL=http://localhost:9377`` in
``~/.hermes/.env`` (Docker: see ``CAMOFOX_REWRITE_LOOPBACK_URLS`` below).
é    )ÚannotationsN)ÚAnyÚCallableÚDictÚOptional)ÚSplitResultÚurlsplitÚ
urlunsplit)Ú
get_secret)Úcfg_getÚload_configÚread_raw_config)Úget_hermes_home_overrideÚhermes_home_key)Úget_camofox_identity)Ú
tool_erroré   z0No browser session. Call browser_navigate first.úOptional[str]Ú_vnc_urlFzDict[str, Optional[str]]Ú_vnc_url_by_camofox_urlzOptional[Dict[str, int]]Ú_cached_cmd_timeoutc                 ó,  — t        «       } t        €i at        r| t        v r	t        |    S t        }	 t	        t        «       dd«      }|�t        t        |«      d«      }|t        | <   da|S # t        $ r }t        j                  d|«       Y d}~Œ1d}~ww xY w)z^``browser.command_timeout`` (floor 5s, default 30s), cached per profile home after first read.NÚbrowserÚcommand_timeouté   z*Could not read browser.command_timeout: %sT)r   r   Ú_cmd_timeout_resolvedÚ_DEFAULT_TIMEOUTr   r   ÚmaxÚintÚ	ExceptionÚloggerÚdebug)ÚhomeÚtimeoutÚvalÚexcs       ú:/home/ubuntu/.hermes/hermes-agent/tools/browser_camofox.pyÚ_get_command_timeoutr(   1   sž   € ô Ó€DÜÐ"Ø ÐÝ Ô)<Ñ!<Ü" 4Ñ(Ð(Ü€GðHÜ”oÓ'¨Ð4EÓFˆØˆ?Üœ#˜c›( AÓ&ˆGð !(Ô˜ÑØ ÐØ€Nøô	 ò HÜ�‰ÐAÀ3×GÑGûðHús   ±,A* Á*	BÁ3BÂBc                 óT   — t        dd«      xs dj                  «       } | rdd| › �iS i S )z8Return Authorization header when CAMOFOX_API_KEY is set.ÚCAMOFOX_API_KEYÚ ÚAuthorizationzBearer )r   Ústrip)Úkeys    r'   Ú_auth_headersr/   E   s5   € äÐ'¨Ó,Ò2°×
9Ñ
9Ó
;€CÙ14ˆO˜w s e˜_Ð-Ð<¸"Ð<ó    c                 ó@   — t        dd«      xs dj                  d«      S )z:Return the configured Camofox server URL, or empty string.ÚCAMOFOX_URLr+   ú/)r   Úrstrip© r0   r'   Úget_camofox_urlr6   K   s   € ä�} bÓ)Ò/¨R×7Ñ7¸Ó<Ð<r0   c                 óÚ   — 	 ddl m}   | «       j                  di «      }t        |t        «      r-t        |j                  dd«      xs d«      j                  «       S 	 y# t        $ r Y yw xY w)u‹   Persistent ``browser.cdp_url`` from config.yaml, or "" (read here, not via
    ``browser_tool_cdp._get_cdp_override`` â€” circular import).r   )r   r   Úcdp_urlr+   )Úhermes_cli.configr   ÚgetÚ
isinstanceÚdictÚstrr-   r    )r   Úbrowser_cfgs     r'   Ú_config_cdp_urlr?   P   sn   € ðÝ5Ù%Ó'×+Ñ+¨I°rÓ:ˆÜ�k¤4Ô(Ü�{—‘ y°"Ó5Ò;¸Ó<×BÑBÓDÐDð )ð øô ò ØØðús   ‚AA Á	A*Á)A*c                 óÔ   — t        j                  dd«      j                  «       s
t        «       ry	 ddlm}   | d«      }|�|dk(  S t        t        «       «      S # t        $ r d}Y Œ'w xY w)	aÁ  True when the Camofox backend is selected and no CDP override is active.

    Selection is ``browser.cloud_provider: camofox``; ``CAMOFOX_URL`` is only the address
    and never overrides a different stored selection (legacy: with no selection ever
    written, a set ``CAMOFOX_URL`` still activates Camofox). A CDP override (``BROWSER_CDP_URL``
    env or ``browser.cdp_url``, same precedence as ``browser_tool_cdp._get_cdp_override()``) wins.
    ÚBROWSER_CDP_URLr+   Fr   )Úread_selectionr   NÚcamofox)	ÚosÚgetenvr-   r?   Útools.tool_backend_helpersrB   r    Úboolr6   )rB   Úselecteds     r'   Úis_camofox_moderI   ]   sm   € ô 
‡y�yÐ" BÓ'×-Ñ-Ô/´?Ô3DØðÝ=Ù! )Ó,ˆð ÐØ˜9Ñ$Ð$Ü”Ó!Ó"Ð"øô	 ò ØŠðús   ±A ÁA'Á&A'c                óè   — 	 |j                  «       j                  d«      }t        |t        «      r-d|cxk  rdk  r"n y dt	        | «      j
                  xs d› d|› �S y # t        t        f$ r Y y w xY w)NÚvncPorté   iÿÿ  zhttp://Ú	localhostú:)Újsonr:   r;   r   r	   ÚhostnameÚ
ValueErrorÚKeyError)ÚurlÚrespÚvnc_ports      r'   Ú_vnc_url_from_healthrV   q   s|   € ðØ—9‘9“;—?‘? 9Ó-ˆÜ�h¤Ô$¨¨hÔ)?¸%Ô)?ð ð œX c›]×3Ñ3ÒB°{ÐCÀ1ÀXÀJÐOÐOð øô œÐ!ò ØØðús   ‚<A ¿A ÁA1Á0A1c                 ó0  — t        «       } | sy	 t        j                  | › d�d¬«      }|j                  dk(  rBt        «       �| t        vr0t        | |«      t        | <   nt        st        | |«      xs t        a	da|j                  dk(  S # t        $ r Y yw xY w)zDVerify the Camofox server is reachable (and cache its VNC URL once).Fz/healthr   ©r$   éÈ   T)
r6   Úrequestsr:   r    Ústatus_coder   r   rV   Ú_vnc_url_checkedr   )rS   rT   s     r'   Úcheck_camofox_availabler]   {   s    € ô Ó
€CÙØðÜ�|‰|˜s˜e 7˜O°QÔ7ˆð ×Ñ˜3ÒÜ#Ó%Ð1ØÔ1Ñ1Ü/CÀCÈÓ/NÔ'¨Ò,Ý!Ü+¨C°Ó6ÒB¼(ˆHØ#ÐØ×Ñ˜sÑ"Ð"øô ò Ùðús   �B	 Â		BÂBc                 ó¤   — t        «       �1t        «       } | t        vr
t        «        t        j	                  | «      S t
        s
t        «        t        S )z>Return the VNC URL if the Camofox server exposes one, or None.)r   r6   r   r]   r:   r\   r   )rS   s    r'   Úget_vnc_urlr_   �   sA   € äÓ!Ð-ÜÓˆØÔ-Ñ-Ü#Ô%Ü&×*Ñ*¨3Ó/Ð/ÝÜÔ!Ü€Or0   c                 óÜ   — 	 t        «       j                  di «      j                  di «      } t        | t        «      r| S i S # t        $ r"}t        j	                  d|«       i cY d}~S d}~ww xY w)z>Return the ``browser.camofox`` config block, or an empty dict.r   rC   z7camofox config check failed, defaulting to disabled: %sN)r   r:   r    r!   Úwarningr;   r<   )Úcamofox_cfgr&   s     r'   Ú_get_camofox_configrc   ›   se   € ðÜ!“m×'Ñ'¨	°2Ó6×:Ñ:¸9ÀbÓIˆô % [´$Ô7ˆ;Ð?¸RÐ?øô ò Ü�‰ÐPÐRUÔVØ�	ûðús   ‚*A  Á 	A+Á	A&Á A+Á&A+c                óN   — t        | €
t        «       n| j                  d«      «      S )z\``browser.camofox.managed_persistence``: stable profile-scoped userId vs random per session.Úmanaged_persistence)rG   rc   r:   )rb   s    r'   Ú_managed_persistence_enabledrf   ¥   s$   € ä¨+Ð*=Ô$Ô&À;×SÑSÐTiÓjÓkÐkr0   ©Úsecretc               óÎ   — |rt        | d«      nt        j                  | d«      }|xs dj                  «       xs, t	        |j                  |«      xs d«      j                  «       S )zOEnv/secret-scope value first, then the ``browser.camofox`` config key, else "".r+   )r   rD   rE   r-   r=   r:   )Úenv_namerb   Úcfg_keyrh   Úraws        r'   Ú_env_or_cfgrm   ª   sQ   € á&,Œ*�X˜rÔ
"´"·)±)¸HÀbÓ2I€CØŠI�2×ÑÓÒM¤# k§o¡o°gÓ&>Ò&DÀ"Ó"E×"KÑ"KÓ"MÐMr0   c                ój   — t        d|dd¬«      }|syt        d|dd¬«      }||xs d| xs d	dd
 › �dœS )zŽExternally configured identity (integrations owning the visible Camofox browser
    share a user ID so Hermes uses the same profile), or None.ÚCAMOFOX_USER_IDÚuser_idTrg   NÚCAMOFOX_SESSION_KEYÚsession_keyÚtask_Údefaulté   ©rp   rr   )rm   )Útask_idrb   rp   rr   s       r'   Ú_camofox_identity_overriderx   °   sT   € ô Ð+¨[¸)ÈDÔQ€GÙØÜÐ3°[À-ÐX\Ô]€KØ¨{Ò/cÀÀwÒG[ÐR[Ð]`Ð^`ÐFaÐEbÐ>cÑdÐdr0   c                óà   — t        j                  | d«      j                  «       j                  «       }|dv ry|dv ry|rt        j                  d| |«       t        |j                  |«      «      S )zHBoolean toggle: env var wins when set to a valid value, else config key.r+   >   Ú1ÚonÚyesÚtrueT>   Ú0ÚnoÚoffÚfalseFz"Ignoring invalid boolean env %s=%r)rD   rE   r-   Úlowerr!   r"   rG   r:   )rj   rb   rk   rl   s       r'   Ú_flagrƒ   º   sd   € ä
�)‰)�H˜bÓ
!×
'Ñ
'Ó
)×
/Ñ
/Ó
1€CØ
Ð(Ñ(ØØ
Ð)Ñ)ØÙ
Ü�‰Ð9¸8ÀSÔIÜ�—‘ Ó(Ó)Ð)r0   c                óÊ   — | sy| j                  «       j                  d«      j                  «       }|dv ry	 t        j                  |«      j                  S # t
        $ r Y yw xY w)NFz[]>   úlocalhost.localdomainrM   T)r-   r‚   Ú	ipaddressÚ
ip_addressÚis_loopbackrQ   )rP   Úhosts     r'   Ú_is_loopback_hostnamerŠ   Æ   sb   € ÙØØ�>‰>Ó×!Ñ! $Ó'×-Ñ-Ó/€DØÐ5Ñ5ØðÜ×#Ñ# DÓ)×5Ñ5Ð5øÜò Ùðús   ·A Á	A"Á!A"c           	     óŽ  — t        «       }t        d|d«      s| dfS 	 t        | «      }t	        d|d«      xs d}|j
                  dvst        |j                  «      r|s| dfS |j                  r-|j                  |j                  rd|j                  › �nd	z   d
z   nd	}d|v r|j                  d«      sd|› d�n|}|j                  rd|j                  › �nd	}t        t        |j
                  |› |› |› �|j                  |j                  |j                   «      «      }||j                  xs d	|| |dœfS # t        $ r | dfcY S w xY w)a
  Rewrite loopback page URLs for Docker-hosted Camofox, if configured.

    ``CAMOFOX_URL`` may point at a host-published Docker port, but page URLs are opened by
    the browser *inside* the container, where loopback is the container, not the host.
    Opt-in (``CAMOFOX_REWRITE_LOOPBACK_URLS`` / config) because non-Docker installs run the
    browser on the host. Returns ``(rewritten_url, metadata)``; ``metadata`` is present only
    when a rewrite happened so the tool result can disclose the change to the model.
    ÚCAMOFOX_REWRITE_LOOPBACK_URLSÚrewrite_loopback_urlsNÚCAMOFOX_LOOPBACK_HOST_ALIASÚloopback_host_aliaszhost.docker.internal>   ÚhttpÚhttpsrN   r+   ú@ú[ú])ÚfromÚtoÚoriginal_urlÚrewritten_url)rc   rƒ   r	   rQ   rm   ÚschemerŠ   rP   ÚusernameÚpasswordÚ
startswithÚportr
   r   ÚpathÚqueryÚfragment)rS   rb   ÚparsedÚaliasÚuserinfoÚ	host_partÚ	port_partÚ	rewrittens           r'   Ú!_rewrite_loopback_url_for_camofoxr§   Ò   sW  € ô &Ó'€KÜÐ0°+Ð?VÔWØ�DˆyÐðÜ˜#“ˆô Ð5°{ÐDYÓZÒtÐ^t€EØ‡}�}Ð-Ñ-Ô5JÈ6Ï?É?Ô5[ÑchØ�DˆyÐØ_e×_nÒ_n�—‘¸F¿OºO Q v§¡Ð&7Ñ#8ÐQSÑTÐWZÒZÐtv€HØ # u¡°U×5EÑ5EÀcÔ5J�!�E�7˜!‘ÐPU€IØ%+§[¢[�!�F—K‘K�=Ñ!°b€IÜÜ�F—M‘M h Z°	¨{¸9¸+Ð#FÈÏÉÐU[×UaÑUaÐci×crÑcrÓsóu€Ià˜vŸ™Ò4°"¸EÐSVÐirÑsÐsÐsøô ò Ø�DˆyÒðús   �D4 Ä4EÅEzDict[str, Dict[str, Any]]Ú	_sessionsc                óÂ  — | j                  d«      s| j                  d«      r
t        «       s| S 	 t        dt        | «      d¬«      j                  dg «      }t        |t        «      r!|D �cg c]  }t        |t        «      sŒ|‘Œ c}ng }|D �cg c](  }|j                  d
«      | j                  d«      k(  sŒ'|‘Œ* c}xs |}|r|d   j                  d«      nd	}t        |t        «      r-|r+|| d<   t
        j                  d|| j                  d«      «       | S # t        $ r2}t
        j                  d| j                  d«      |«       | cY d	}~S d	}~ww xY wc c}w c c}w )z–Rehydrate tab_id from an already-open managed tab: gateway restarts empty the
    in-memory cache while Camofox still holds the integration-owned tab.Útab_idÚadopt_existing_tabú/tabsr   )Úparamsr$   Útabsz&Camofox tab adoption failed for %s: %srp   NÚ
listItemIdrr   éÿÿÿÿÚtabIdz&Adopted existing Camofox tab %s for %s)r:   r6   Ú_getÚ_user_paramsr    r!   r"   r;   Úlistr<   r=   )Úsessionr®   r&   ÚtabÚ	dict_tabsÚ
candidatesrª   s          r'   Ú_adopt_existing_tabr¹   ò   s-  € ð ‡{�{�8Ô G§K¡KÐ0DÔ$EÌ_ÔM^ØˆðÜ�G¤L°Ó$9À1ÔE×IÑIÈ&ÐRTÓUˆô CMÈTÔSWÔBX Ö>˜¬
°3¼Õ(=’Ó>Ð^`€IØ!*Öb˜#¨c¯g©g°lÓ.CÀwÇ{Á{ÐS`ÓGaÓ.a’#ÒbÒoÐfo€JÙ,6ˆZ˜‰^×Ñ Ô(¸D€FÜ�&œ#Ô¡6Ø"ˆ�ÑÜ�‰Ð=¸vÀwÇ{Á{ÐS\ÓG]Ô^Ø€Nøô ò Ü�‰Ð=¸w¿{¹{È9Ó?UÐWZÔ[Ø�ûðüò ?ùÚbs5   °'D Á+EÂEÂ(EÂ7EÄ	EÄ"'EÅ	EÅEc                ó´  — | xs d} t         5  | t        v rt        t        |    «      cddd«       S t        «       }t	        | |«      }|€t        |«      rt        | «      }|€3dt        j                  «       j                  dd › �d| dd › �dœ}d\  }}nd	t        d
|d«      }}|d   d|d   ||dœ}|t        | <   t        |«      cddd«       S # 1 sw Y   yxY w)uÃ   Get or create the task's session. Identity precedence: external override
    (CAMOFOX_USER_ID / config) â†’ profile-scoped identity when managed persistence
    is on â†’ random ephemeral userId.rt   NÚhermes_é
   rs   ru   rv   )FFTÚCAMOFOX_ADOPT_EXISTING_TABr«   rp   rr   )rp   rª   rr   Úmanagedr«   )Ú_sessions_lockr¨   r¹   rc   rx   rf   r   ÚuuidÚuuid4Úhexrƒ   )rw   rb   Úidentityr¾   Úadoptrµ   s         r'   Ú_get_sessionrÅ     s   € ð Ò"˜€GÜ	ñ ,Ø”iÑÜ&¤y°Ñ'9Ó:÷,ñ ,ô *Ó+ˆÜ-¨g°{ÓCˆØÐÔ <¸[Ô IÜ+¨GÓ4ˆHØÐØ%,¬T¯Z©Z«\×-=Ñ-=¸c¸rÐ-BÐ,CÐ#DÐW\Ð]dÐehÐfhÐ]iÐ\jÐUkÑlˆHØ)‰NˆG‘Uà!¤5Ð)EÀ{ÐThÓ#i�UˆGØ& yÑ1¸TÐRZÐ[hÑRiØ%¸UñDˆà$Œ	�'ÑÜ" 7Ó+÷,÷ ,ò ,ús   �C±BCÃCc                óz   — t        | «      }|d   s*t        d|d   |d   |dœ«      }|j                  d«      |d<   |S )z<Ensure a tab exists for the session, creating one if needed.rª   r¬   rp   rr   )ÚuserIdr¯   rS   r±   )rÅ   Ú_postr:   )rw   rS   rµ   Údatas       r'   Ú_ensure_tabrÊ     sK   € ä˜7Ó#€GØ�8ÒÜ�W¨°Ñ);È7ÐS`ÑKaÐjmÑnÓoˆØ ŸH™H WÓ-ˆ�ÑØ€Nr0   c                ón   — t         5  t        j                  | xs dd«      cddd«       S # 1 sw Y   yxY w)zRemove and return session info.rt   N)r¿   r¨   Úpop©rw   s    r'   Ú_drop_sessionrÎ   %  s-   € ä	ñ 9Ü�}‰}˜WÒ1¨	°4Ó8÷9÷ 9ò 9ús   ‡+«4c                óŠ   — t        «       }t        |«      st        | |«      r"t        | «       t        j                  d| «       yy)zÆDrop only the local tracking entry (``True``) for managed profiles, which must
    survive across agent tasks; ``False`` for ephemeral sessions so the caller falls back
    to :func:`camofox_close`.z6Camofox soft cleanup for task %s (managed persistence)TF)rc   rf   rx   rÎ   r!   r"   )rw   rb   s     r'   Úcamofox_soft_cleanuprÐ   +  s<   € ô &Ó'€KÜ# KÔ0Ô4NÈwÐXcÔ4dÜ�gÔÜ�‰ÐMÈwÔWØØr0   c                óš   —  t        t        | «      t        «       › |› �ft        «       |€
t	        «       n|dœ|¤Ž}|j                  «        |S )zTIssue an authenticated request to camofox and return the raised-for-status response.)Úheadersr$   )ÚgetattrrZ   r6   r/   r(   Úraise_for_status)Úmethodrž   r$   ÚkwargsrT   s        r'   Ú_requestr×   8  sW   € à$Œ7”8˜VÓ$¬Ó(9Ð':¸4¸&Ð%Að oÌ=Ë?ØGNÀÔ-AÔ-CÐ\cñoØgmño€Dà×ÑÔØ€Kr0   c                ó<   — t        d| ||¬«      j                  «       S )NÚpost©rO   ©r×   rO   ©rž   Úbodyr$   s      r'   rÈ   rÈ   @  s   € Ü�F˜D '°Ô5×:Ñ:Ó<Ð<r0   c                ó<   — t        d| ||¬«      j                  «       S )Nr:   ©r­   rÛ   ©rž   r­   r$   s      r'   r²   r²   D  s   € Ü�E˜4 °Ô8×=Ñ=Ó?Ð?r0   c                ó    — t        d| ||¬«      S )z2GET and return the raw response (for binary data).r:   rß   )r×   rà   s      r'   Ú_get_rawrâ   H  s   € ä�E˜4 °Ô8Ð8r0   c                ó<   — t        d| ||¬«      j                  «       S )NÚdeleterÚ   rÛ   rÜ   s      r'   Ú_deleterå   M  s   € Ü�H˜d G°$Ô7×<Ñ<Ó>Ð>r0   c                ó   — d| d   › d|› �S )Nz/tabs/rª   r3   r5   )rµ   Úsuffixs     r'   Ú	_tab_pathrè   R  s   € Ø�G˜HÑ%Ð& a¨ xÐ0Ð0r0   c                ó   — d| d   iS ©NrÇ   rp   r5   ©rµ   s    r'   r³   r³   V  s   € Ø�g˜iÑ(Ð)Ð)r0   c                óB   — t        t        | d«      t        | «      ¬«      S )NÚsnapshotrß   )r²   rè   r³   rë   s    r'   Ú_snapshot_datarî   Z  s   € Ü”	˜' :Ó.´|ÀGÓ7LÔMÐMr0   c                óÂ  — g }| j                  d«      }t        |«      D ]½  \  }}|j                  «       }|j                  d«      sŒ(t	        j
                  d|«      }|dz   t        |«      k  r*t	        j
                  d||dz      j                  «       «      nd}|r|j                  d«      nd|r|j                  d«      nd}	}|s|	sŒª|j                  |	|dœ«       Œ¿ |S )	z•Images from an accessibility snapshot: ``img "alt" [eN]`` entries with the URL on
    the following ``/url:`` line (Camofox has no /images endpoint).ú
)z- img zimg zimg\s+"([^"]*)"rL   z/url:\s*(\S+)Nr+   )ÚsrcÚalt)	ÚsplitÚ	enumerater-   rœ   ÚreÚsearchÚlenÚgroupÚappend)
rí   ÚimagesÚlinesÚiÚlineÚstrippedÚ	alt_matchÚ	url_matchrò   rñ   s
             r'   Ú_parse_snapshot_imagesr  ^  sÖ   € ð €FØ�N‰N˜4Ó €EÜ˜UÓ#ò 4‰ˆˆ4Ø—:‘:“<ˆØ×"Ñ"Ð#5Ô6ØÜ—I‘IÐ0°(Ó;ˆ	ØIJÈQÉÔQTÐUZÓQ[ÒI[”B—I‘IÐ.°°a¸!±e±×0BÑ0BÓ0DÔEÐaeˆ	Ù*3�I—O‘O AÔ&¸ÑU^¸y¿¹ÈqÔ?QÐdfˆSˆÙ’#Ø�M‰M #¨cÑ2Õ3ð4ð €Mr0   c                óº   — ddl m} ddlm} t	        | «      }|j                  dd«       |«       }}t        |«      |kD  r
 |||¬«      }||j                  dd«      fS )zÍ``(snapshot_text, refs_count)`` truncated like the main browser tool (line boundaries,
    full tree stored to cache/web, read_file pointer appended). Lazy import: ``browser_tool``
    imports this module.r   )Ú_truncate_snapshot)Úget_browser_snapshot_thresholdrí   r+   )Ú	max_charsÚ	refsCount)Útools.browser_tool_snapshotr  Útools.browser_toolr  rî   r:   r÷   )rµ   r  r  rÉ   rí   Ú	thresholds         r'   Ú_fetch_snapshotr
  o  sZ   € õ ?ÝAÜ˜'Ó"€DØŸ(™( :¨rÓ2Ñ4RÓ4Tˆi€HÜ
ˆ8ƒ}�yÒ Ù% h¸)ÔDˆØ�T—X‘X˜k¨1Ó-Ð-Ð-r0   c                óR  — t        | «      }|d   r#	 t        t        |d«      |d   |dœd¬«      }||fS t        | |«      d
|dœfS # t        j                  $ rN}|j
                  �|j
                  j                  dk7  r‚ t        j                  d	|d   «       d|d<   Y d}~Œmd}~ww xY w)uÕ   Open ``browser_url`` in the task's tab (creating it if missing) and return
    ``(session, navigate_response)``. A 404 on the existing tab means the server
    garbage-collected it â€” recreate instead of failing.rª   Únavigaterp   )rÇ   rS   é<   rX   Ni”  uP   Camofox tab %s returned 404 â€” tab was garbage collected. Creating a fresh tab.T)ÚokrS   )
rÅ   rÈ   rè   rZ   Ú	HTTPErrorÚresponser[   r!   ra   rÊ   )rw   Úbrowser_urlrµ   rÉ   Úes        r'   Ú_navigate_tabr  |  s¶   € ô ˜7Ó#€GØˆxÒð	%Üœ 7¨JÓ7ÀGÈIÑDVÐ_jÑ9kÐuwÔxˆDØ˜D�=Ð ô �w Ó,°TÀ+Ñ.NÐNÐNøô ×!Ñ!ò 	%Ø�z‰zÐ! Q§Z¡Z×%;Ñ%;¸sÒ%BØÜ�N‰NÐmØ" 8Ñ,ô.à $ˆG�HÖûð	%ús   ’!A ÁB&ÁAB!Â!B&c                ó‚  — 	 t        | «      \  }}t        ||«      \  }}d|j                  d|«      |j                  dd«      dœ}|r| |c|d<   |d<   d|d	   › d
|d   › �|d<   t        «       }|r
||d<   d|d<   	 t	        |«      \  |d<   |d<   t        j                  |«      S # t
        $ r Y Œ w xY w# t        j                  $ r}t        d|› �d¬«      cY d}~S d}~wt        j                  $ r' t        j                  ddt        «       › d�dœ«      cY S t
        $ r }t        t        |«      d¬«      cY d}~S d}~ww xY w)zNavigate to a URL via Camofox.TrS   Útitler+   )ÚsuccessrS   r  Úrequested_urlÚurl_rewritez0Rewrote loopback URL for Docker-hosted Camofox: r•   z -> r–   ra   Úvnc_urlz]Browser is visible via VNC. Share this link with the user so they can watch the browser live.Úvnc_hintrí   Úelement_countzNavigation failed: F©r  NzCannot connect to Camofox at z�. Is the server running? Start with: npm start (in camofox-browser dir) or: docker run -p 9377:9377 -e CAMOFOX_PORT=9377 jo-inc/camofox-browser©r  Úerror)r§   r  r:   r_   r
  r    rO   ÚdumpsrZ   r  r   ÚConnectionErrorr6   r=   )	rS   rw   r  Úrewrite_inforµ   rÉ   ÚresultÚvncr  s	            r'   Úcamofox_navigater$  Ž  s{  € ð1Ü$EÀcÓ$JÑ!ˆ�\Ü% g¨{Ó;‰ˆ�Ø!¨$¯(©(°5¸+Ó*FÐQU×QYÑQYÐZaÐceÓQfÑgˆÙØ=@À,Ð:ˆF�?Ñ# V¨MÑ%:Ø!SØ$0°Ñ$8Ð#9¸¸lÈ4Ñ>PÐ=Qð"SˆF�9Ñä‹mˆÙØ #ˆF�9Ñð#fˆF�:Ñð	Ü:IÈ'Ó:RÑ7ˆF�:Ñ  Ñ 7ô �z‰z˜&Ó!Ð!øô ò 	Ùð	ûô ×Ñò DÜÐ/°¨sÐ3¸UÔCÕCûÜ×#Ñ#ò YÜ�z‰z eØ+¬OÓ,=Ð+>ð ?Vð VñXó Yò 	Yô ò 1Üœ#˜a›&¨%Ô0Õ0ûð1úsZ   ‚A7B2 Á:B# ÂB2 Â#	B/Â,B2 Â.B/Â/B2 Â2D>ÃCÃD>Ã:D>ÄD>ÄD9Ä3D>Ä9D>c                ó’   — ddl m}m}  ||xs d«      sy || d   | d   «      }|syt        j                  dd|› d	|› d
�dœd¬«      S )a­  Blocked payload when the current page is private/internal, else None.

    Mirrors the ``_camofox_eval`` guard in browser_tool.py: page-state reads on a non-local
    backend can leak an intranet/metadata page the terminal can't reach. Only active when
    the SSRF guard applies (non-local backend, not a local sidecar, ``allow_private_urls``
    unset); fail-open on probe failure like sibling guards. Lazy import (cycle).
    r   )Ú!_camofox_current_page_private_urlÚ_eval_ssrf_guard_activert   Nrª   rp   Fz9Blocked: page URL targets a private or internal address (z). Refusing to z# on this page in this browser mode.r  )Úensure_ascii)Útools.browser_tool_eval_policyr&  r'  rO   r  )rµ   rw   Úactionr&  r'  Úblocked_urls         r'   Ú_camofox_private_page_blockr,  ­  sk   € ÷ jÙ" 7Ò#7¨iÔ8ØÙ3°G¸HÑ4EÀwÈyÑGYÓZ€KÙØÜ�:‰: %ð	Øˆ=˜¨ xÐ/Rð	TñVàdiôkð kr0   c                óp   — t        | «      }|d   s|t        t        d¬«      fS ||�t        || |«      fS dfS )zkReturn ``(session, error_payload)``: error when no tab exists or, if ``action`` given, the page is private.rª   Fr  N)rÅ   r   Ú_NO_SESSION_ERRORr,  )rw   r*  rµ   s      r'   Ú_require_tabr/  À  sJ   € ä˜7Ó#€GØ�8ÒØœ
Ô#4¸eÔDÐDÐDØÈfÐN`Ô0°¸'À6ÓJÐkÐkÐfjÐkÐkr0   c                ó’   — 	 t        | |«      \  }}|r|S  ||«      S # t        $ r }t        t        |«      d¬«      cY d}~S d}~ww xY w)zŠRequire a tab (+ private-page guard when ``guard_action`` is set), then run ``body(session)``;
    any exception becomes a ``tool_error``.Fr  N)r/  r    r   r=   )rw   Úguard_actionrÝ   rµ   Úblockedr  s         r'   Ú	_with_tabr3  È  sK   € ð1Ü'¨°Ó>Ñˆ�ÙØˆNÙ�G‹}ÐøÜò 1Üœ#˜a›&¨%Ô0Õ0ûð1ús   ‚ • �	A¦A»AÁAc                ó,   ‡‡‡— t        | |ˆˆˆfd„«      S )zNSimple tab action: POST ``body`` to ``/tabs/<id>/<suffix>``, build the result.c           
     óp   •— t        j                   ‰t        t        | ‰«      d| d   i‰¥«      «      «      S rê   )rO   r  rÈ   rè   )rµ   rÝ   r"  rç   s    €€€r'   ú<lambda>z_tab_action.<locals>.<lambda>×  s7   ø€ ¼D¿J¹JÙŒu”Y˜w¨Ó/°(¸GÀIÑ<NÐ1WÐRVÐ1WÓXÓYó=[€ r0   ©r3  )rw   r1  rç   rÝ   r"  s     ```r'   Ú_tab_actionr8  Ô  s   ú€ ô �W˜lõ -[ó \ð \r0   c                ó"   — d„ }t        |d|«      S )u’   Accessibility tree snapshot. ``user_task`` is deprecated and ignored â€”
    oversized snapshots always truncate-and-store (no LLM summarization).c                óP   — t        | «      \  }}t        j                  d||dœ«      S )NT)r  rí   r  )r
  rO   r  )rµ   rí   Ú
refs_counts      r'   rÝ   zcamofox_snapshot.<locals>.bodyÞ  s(   € Ü.¨wÓ7Ñˆ�*Ü�z‰z d¸ÐS]Ñ^Ó_Ð_r0   zread a page snapshotr7  )Úfullrw   Ú	user_taskrÝ   s       r'   Úcamofox_snapshotr>  Û  s   € ò`ô �WÐ4°dÓ;Ð;r0   c                óN   ‡— | j                  d«      Št        |ddd‰iˆfd„«      S )z$Click an element by ref via Camofox.r’   ÚclickÚrefc                ó0   •— d‰| j                  dd«      dœS )NTrS   r+   )r  ÚclickedrS   ©r:   )rÉ   Ú	clean_refs    €r'   r6  zcamofox_click.<locals>.<lambda>è  s   ø€ °ÀÐSW×S[ÑS[Ð\aÐceÓSfÑ$g€ r0   )Úlstripr8  )rA  rw   rE  s     @r'   Úcamofox_clickrG  ä  s/   ø€ à—
‘
˜3“€IÜ�w ¨°5¸)Ð2DÛgóið ir0   c                óp  — 	 t        |d«      \  }}|r|S | j                  d«      }t        t        |d«      |d   ||dœ«       ddlm}m}  |dd|i«      xs i d   }d	||d
œ}	t        j                   ||	|«      «      S # t        $ r-}
ddlm} t         |t        |
«      |«      d¬«      cY d}
~
S d}
~
ww xY w)z-Type text into an element by ref via Camofox.Útyper’   rp   )rÇ   rA  Útextr   )Ú%redact_browser_typed_text_for_displayÚredact_tool_args_for_displayÚbrowser_typerJ  T)r  ÚtypedÚelement)rK  Fr  N)r/  rF  rÈ   rè   Úagent.displayrK  rL  rO   r  r    r   r=   )rA  rJ  rw   rµ   r2  rE  rK  rL  Údisplay_textr  r  s              r'   Úcamofox_typerR  ë  sÀ   € ð^Ü'¨°Ó8Ñˆ�ÙØˆNØ—J‘J˜s“Oˆ	ÜŒi˜ Ó(°W¸YÑ5GÐPYÐcgÑ*hÔißeñ 5°^ÀfÈdÀ^ÓTÒZÐXZÐ\bÑcˆØ#¨lÀyÑQˆÜ�z‰zÑ?ÀÈ$ÓOÓPÐPøÜò ^ÝGÜÑ?ÄÀAÃÈÓMÐW\Ô]Õ]ûð^ús#   ‚A? •A)A? Á?	B5Â"B0Â*B5Â0B5c                ó,   ‡ — t        |ddd‰ iˆ fd„«      S )zScroll the page via Camofox.NÚscrollÚ	directionc                ó   •— d‰dœS )NT)r  Úscrolledr5   )rÉ   rU  s    €r'   r6  z camofox_scroll.<locals>.<lambda>  s   ø€ °À)Ñ$L€ r0   ©r8  )rU  rw   s   ` r'   Úcamofox_scrollrY  ÿ  s"   ø€ ä�w  h°¸iÐ0HÛLóNð Nr0   c                ó"   — t        | ddi d„ «      S )zNavigate back via Camofox.NÚbackc                ó,   — d| j                  dd«      dœS )NTrS   r+   )r  rS   rD  )rÉ   s    r'   r6  zcamofox_back.<locals>.<lambda>  s   € È4ÐX\×X`ÑX`ÐafÐhjÓXkÑ?l€ r0   rX  rÍ   s    r'   Úcamofox_backr]    s   € ä�w  f¨bÑ2lÓmÐmr0   c                ó,   ‡ — t        |ddd‰ iˆ fd„«      S )z!Press a keyboard key via Camofox.Úpressr.   c                ó   •— d‰dœS )NT)r  Úpressedr5   )rÉ   r.   s    €r'   r6  zcamofox_press.<locals>.<lambda>  s   ø€ ÐY]ÐjmÑMn€ r0   rX  )r.   rw   s   ` r'   Úcamofox_pressrb  
  s   ø€ ä�w ¨°5¸#°,Ó@nÓoÐor0   c                óà   — 	 t        | «      }|rt        d|d   › �«       t        j                  dddœ«      S # t        $ r,}t        j                  ddt        |«      dœ«      cY d}~S d}~ww xY w)z&Close the browser session via Camofox.z
/sessions/rp   T)r  Úclosed)r  rd  ra   N)rÎ   rå   rO   r  r    r=   )rw   rµ   r  s      r'   Úcamofox_closere    sj   € ðPÜ Ó(ˆÙÜ�j ¨Ñ!3Ð 4Ð5Ô6Ü�z‰z d°dÑ;Ó<Ð<øÜò PÜ�z‰z d°dÄsÈ1ÃvÑNÓOÕOûðPús   ‚58 ¸	A-Á!A(Á"A-Á(A-c                ó"   — d„ }t        | d|«      S )zFGet images on the current page via Camofox (parsed from the snapshot).c                óŽ   — t        t        | «      j                  dd«      «      }t        j                  d|t        |«      dœ«      S )Nrí   r+   T)r  rú   Úcount)r  rî   r:   rO   r  r÷   )rµ   rú   s     r'   rÝ   z camofox_get_images.<locals>.body  s9   € Ü'¬°wÓ(?×(CÑ(CÀJÐPRÓ(SÓTˆÜ�z‰z d°fÄsÈ6Ã{ÑSÓTÐTr0   zextract page imagesr7  )rw   rÝ   s     r'   Úcamofox_get_imagesri    s   € òUô �WÐ3°TÓ:Ð:r0   c                 ó¼   — 	 t        t        «       ddi ¬«      } t        | j                  dd«      «      t        | j                  dd«      «      fS # t        $ r Y yw xY w)	zY``auxiliary.vision`` ``(timeout, temperature)``; defaults 120s / 0.1 on any config error.Ú	auxiliaryÚvision)rt   r$   éx   Útemperatureçš™™™™™¹?)g      ^@ro  )r   r   Úfloatr:   r    )Úcfgs    r'   Ú_vision_llm_settingsrr  "  sV   € ðÜ”k“m [°(ÀBÔGˆÜ�S—W‘W˜Y¨Ó,Ó-¬u°S·W±W¸]ÈCÓ5PÓ/QÐQÐQøÜò Ùðús   ‚AA Á	AÁAc                ó  — ddl m}  |«       dz  }|j                  dd¬«       t        |dt	        j
                  «       j                  dd › d	�z  «      }t        |d
«      5 }|j                  | «       ddd«       |S # 1 sw Y   |S xY w)zOWrite PNG bytes under ``$HERMES_HOME/browser_screenshots`` and return the path.r   )Úget_hermes_homeÚbrowser_screenshotsT)ÚparentsÚexist_okÚbrowser_screenshot_Né   z.pngÚwb)	Úhermes_constantsrt  Úmkdirr=   rÀ   rÁ   rÂ   ÚopenÚwrite)Úcontentrt  Úscreenshots_dirÚscreenshot_pathÚfs        r'   Ú_save_screenshotrƒ  +  sŠ   € å0Ù%Ó'Ð*?Ñ?€OØ×Ñ $°ÐÔ6Ü˜/Ð.AÄ$Ç*Á*Ã,×BRÑBRÐSUÐTUÐBVÐAWÐW[Ð,\Ñ\Ó]€OÜ	ˆo˜tÓ	$ð ¨Ø	�‰�Ô÷àÐ÷àÐús   Á!A=Á=Bc                ó,   ‡ ‡— ˆˆ fd„}t        |d|«      S )z<Take a screenshot and analyze it with vision AI via Camofox.c                ó‚  •— t        t        | d«      t        | «      ¬«      }t        |j                  «      }t        j                  |j                  «      j                  d«      }d}‰r$	 t        | «      j                  dd«      }d|d d › �}dd	lm} dd
lm} d‰›  ||«      › �}t        «       \  }	}
 |dd|dœddd|› �idœgdœgd|
|	¬«      }|j                   r5|j                   d   j"                  j                  xs dj%                  «       nd}t'        j(                  d ||«      |dœ«      S # t        $ r Y Œ°w xY w)NÚ
screenshotrß   zutf-8r+   rí   z5

Accessibility tree (element refs for interaction):
i¸  r   )Úredact_sensitive_text)Úcall_llmz,Analyze this browser screenshot and answer: ÚuserrJ  )rI  rJ  Ú	image_urlrS   zdata:image/png;base64,)rI  rŠ  )Úroler  rl  )ÚmessagesÚtaskrn  r$   T)r  Úanalysisr�  )râ   rè   r³   rƒ  r  Úbase64Ú	b64encodeÚdecoderî   r:   r    Úagent.redactr‡  Úagent.auxiliary_clientrˆ  rr  ÚchoicesÚmessager-   rO   r  )rµ   rT   r�  Úimg_b64Úannotation_contextrí   r‡  rˆ  Úvision_promptr$   rn  r  rŽ  ÚannotateÚquestions                €€r'   rÝ   zcamofox_vision.<locals>.body8  sd  ø€ Üœ	 '¨<Ó8ÄÈgÓAVÔWˆÜ*¨4¯<©<Ó8ˆÜ×"Ñ" 4§<¡<Ó0×7Ñ7¸Ó@ˆØÐÙðÜ)¨'Ó2×6Ñ6°zÀ2ÓF�Ø'_Ð`hÐinÐjnÐ`oÐ_pÐ%qÐ"õ
 	7Ý3ØFÀxÀjÑQfÐgyÓQzÐP{Ð|ˆÜ3Ó5Ñˆ�ÙØ%Ø¨Ñ7Ø$°EÐ=SÐT[ÐS\Ð;]Ð3^Ñ_ð3añ bð cð  {¸Gô	Eˆð
 KS×JZÒJZ�H×$Ñ$ QÑ'×/Ñ/×7Ñ7Ò=¸2×DÑDÔFÐ`bˆä�z‰z dÑ8MÈhÓ8WÐl{Ñ|Ó}Ð}øô ò Ùðús   Á*#D2 Ä2	D>Ä=D>zcapture a screenshotr7  )rš  r™  rw   rÝ   s   ``  r'   Úcamofox_visionr›  6  s   ù€ õ~ô2 �WÐ4°dÓ;Ð;r0   c           	     ó:   — t        j                  dg g ddddœ«      S )zZConsole output is not exposed by the Camofox REST API; return an empty result with a note.Tr   z|Console log capture is not available with the Camofox backend. Use browser_snapshot or browser_vision to inspect page state.)r  Úconsole_messagesÚ	js_errorsÚtotal_messagesÚtotal_errorsÚnote)rO   r  )Úclearrw   s     r'   Úcamofox_consoler£  T  s.   € ä�:‰:Ø¨R¸bÐTUÐghðPñQó Rð Rr0   )Úreturnr   )r¤  úDict[str, str])r¤  r=   )r¤  rG   )rS   r=   rT   r   r¤  r   )r¤  r   )r¤  úDict[str, Any])N)rb   úOptional[Dict[str, Any]]r¤  rG   )
rj   r=   rb   r¦  rk   r=   rh   rG   r¤  r=   )rw   r   rb   r¦  r¤  zOptional[Dict[str, str]])rj   r=   rb   r¦  rk   r=   r¤  rG   )rP   r   r¤  rG   )rS   r=   r¤  z$tuple[str, Optional[Dict[str, str]]])rµ   r¦  r¤  r¦  )rw   r   r¤  r¦  )zabout:blank)rw   r   rS   r=   r¤  r¦  )rw   r   r¤  r§  )rw   r   r¤  rG   )
rÕ   r=   rž   r=   r$   úOptional[int]rÖ   r   r¤  úrequests.Response)rž   r=   rÝ   r<   r$   r¨  r¤  r<   )NN)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=   r¤  zlist[Dict[str, str]])rµ   r¦  r¤  ztuple[str, int])rw   r   r  r=   r¤  ztuple[Dict[str, Any], dict])rS   r=   rw   r   r¤  r=   )rµ   r¦  rw   r   r*  r=   r¤  r   )rw   r   r*  r   r¤  z$tuple[Dict[str, Any], Optional[str]])rw   r   r1  r   rÝ   zCallable[[Dict[str, Any]], str]r¤  r=   )rw   r   r1  r   rç   r=   rÝ   r¦  r"  zCallable[[dict], dict]r¤  r=   )FNN)r<  rG   rw   r   r=  r   r¤  r=   )rA  r=   rw   r   r¤  r=   )rA  r=   rJ  r=   rw   r   r¤  r=   )rU  r=   rw   r   r¤  r=   )rw   r   r¤  r=   )r.   r=   rw   r   r¤  r=   )r¤  ztuple[float, float])r  Úbytesr¤  r=   )FN)rš  r=   r™  rG   rw   r   r¤  r=   )r¢  rG   rw   r   r¤  r=   )`Ú__doc__Ú
__future__r   r�  r†   rO   ÚloggingrD   rõ   Ú	threadingrÀ   Útypingr   r   r   r   Úurllib.parser   r	   r
   rZ   Úagent.secret_scoper   r9   r   r   r   r{  r   r   Útools.browser_camofox_stater   Útools.registryr   Ú	getLoggerÚ__name__r!   r   r.  r   Ú__annotations__r\   r   r   r   r(   r/   r6   r?   rI   rV   r]   r_   rc   rf   rm   rx   rƒ   rŠ   r§   r¨   ÚLockr¿   r¹   rÅ   rÊ   rÎ   rÐ   r×   rÈ   r²   râ   rå   rè   r³   rî   r  r
  r  r$  r,  r/  r3  r8  r>  rG  rR  rY  r]  rb  re  ri  rr  rƒ  r›  r£  r5   r0   r'   ú<module>r¸     sÙ  ðòõ #ã Û Û Û Û 	Û 	Û Û ß 0Ó 0ß :Ñ :ã å )ß CÑ Cß FÝ <Ý %à	ˆ×	Ñ	˜8Ó	$€ð Ð ØFÐ Ø€ˆ-Ó ØÐ ð 57Ð Ð1Ó 6ð 15Ð Ð-Ó 4ØÐ óó(=ó=ó

ó#ó(ó#ó(	ó@ôlð
 ]bõ Nóeó	*ó	ótð8 (*€	Ð$Ó )Ø�—‘Ó!€óó&,ô.ó9ô	ôô=ô@ô9ô
?ó
1ó*óNóó"
.óOô$1ó>kô&ló	1ð\Ø$ð\Ø.Dð\ØILó\ô<ôiô^ô(Nônô
pô
Pô;óóô<õ<Rr0   