+
    iO                    r  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	t	^ RI
t
^ RIHt ^ RIHtHtHt ^ RIt^ RIHt ^ RIHt ]P,                  ! ]4      t^tRtRsR]R	&   R
sR R ltR R ltR R lt R R lt!R R lt"/ t#R]R&   ]PH                  ! 4       t%R R lt&R=R R llt'R R lt(]3R R llt)R]3R R  llt*R]3R! R" llt+R]3R# R$ llt,R>R% R& llt-R?R' R( llt.R>R) R* llt/R>R+ R, llt0R>R- R. llt1R>R/ R0 llt2R>R1 R2 llt3R>R3 R4 llt4R>R5 R6 llt5R@R7 R8 llt6R@R9 R: llt7R; R< lt8R# )AuA  Camofox browser backend — local anti-detection browser via REST API.

Camofox-browser is a self-hosted Node.js server wrapping Camoufox (Firefox
fork with C++ fingerprint spoofing).  It exposes a REST API that maps 1:1
to our browser tool interface: accessibility snapshots with element refs,
click/type/scroll by ref, screenshots, etc.

When ``CAMOFOX_URL`` is set (e.g. ``http://localhost:9377``), the browser
tools route through this module instead of the ``agent-browser`` CLI.

Setup::

    # Option 1: npm
    git clone https://github.com/jo-inc/camofox-browser && cd camofox-browser
    npm install && npm start   # downloads Camoufox (~300MB) on first run

    # Option 2: Docker
    docker run -p 9377:9377 -e CAMOFOX_PORT=9377 jo-inc/camofox-browser

Then set ``CAMOFOX_URL=http://localhost:9377`` in ``~/.hermes/.env``.
)annotationsN)Path)AnyDictOptionalload_config)get_camofox_identityi8 Optional[str]_vnc_urlFc                   V ^8  d   QhRR/# )   returnstr )formats   "2/home/ubuntu/hermes-agent/tools/browser_camofox.py__annotate__r   4   s     4 4 4    c                 N    \         P                  ! RR4      P                  R4      # )z:Return the configured Camofox server URL, or empty string.CAMOFOX_URL /)osgetenvrstripr   r   r   get_camofox_urlr   4   s    99]B'..s33r   c                   V ^8  d   QhRR/# r   r   boolr   )r   s   "r   r   r   9   s     # # #r   c                 (    \        \        4       4      # )z(True when Camofox backend is configured.)r   r   r   r   r   is_camofox_moder!   9   s    !""r   c                   V ^8  d   QhRR/# r   r   )r   s   "r   r   r   >   s       r   c                    \        4       p V '       g   R#  \        P                  ! V  R2^R7      pVP                  ^8X  d   \        '       gy    VP                  4       pVP                  R4      p\        V\        4      '       d?   ^Tu;8:  d   R8:  d0   M M,^ RIH	p V! V 4      pVP                  ;'       g    RpRV R	V 2sR
sVP                  ^8H  #   \        \        3 d     L$i ; i  \         d     R# i ; i)z'Verify the Camofox server is reachable.Fz/healthtimeoutvncPorti  )urlparse	localhostzhttp://:T)r   requestsgetstatus_code_vnc_url_checkedjson
isinstanceinturllib.parser'   hostnamer   
ValueErrorKeyError	Exception)urlrespdatavnc_portr'   parsedhosts          r   check_camofox_availabler<   >   s     
C||se7OQ7s"+;+;	yy{88I.h,,h1G%1G5%c]F!??99kD!(az:H  $3&& )   sH   7C, AC C,  C 9C C, C)&C, (C))C, ,C;:C;c                   V ^8  d   QhRR/# )r   r   r
   r   )r   s   "r   r   r   W   s      ] r   c                 :    \         '       g   \        4        \        # )z>Return the VNC URL if the Camofox server exposes one, or None.)r-   r<   r   r   r   r   get_vnc_urlr?   W   s    !Or   c                   V ^8  d   QhRR/# r   r   )r   s   "r   r   r   ^   s     8 8d 8r   c                      \        4       P                  R/ 4      P                  R/ 4      p \        T P                  R4      4      #   \         d     R# i ; i)aX  Return whether Hermes-managed persistence is enabled for Camofox.

When enabled, sessions use a stable profile-scoped userId so the
Camofox server can map it to a persistent browser profile directory.
When disabled (default), each session gets a random userId (ephemeral).

Controlled by ``browser.camofox.managed_persistence`` in config.yaml.
browsercamofoxFmanaged_persistence)r   r+   r5   r   )camofox_cfgs    r   _managed_persistence_enabledrF   ^   sS    !m''	26::9bI  5677  s   *A AAzDict[str, Dict[str, Any]]	_sessionsc                    V ^8  d   QhRRRR/# )r   task_idr
   r   Dict[str, Any]r   )r   s   "r   r   r   v   s      - N r   c           
        T ;'       g    Rp \         ;_uu_ 4        V \        9   d   \        V ,          uuRRR4       # \        4       '       d%   \        V 4      pRVR,          RRRVR,          RR/pM;RR\        P
                  ! 4       P                  R	,           2RRRR
V R,           2RR/pV\        V &   VuuRRR4       #   + '       g   i     R# ; i)zGet or create a camofox session for the given task.

When managed persistence is enabled, uses a deterministic userId
derived from the Hermes profile so the Camofox server can map it
to the same persistent browser profile across restarts.
defaultNuser_idtab_idsession_keymanagedThermes_:N
   Ntask_:N   NF)_sessions_lockrG   rF   r	   uuiduuid4hex)rI   identitysessions   &  r   _get_sessionr[   v   s     ""G	iW% 
 ())+G4H8I.$x64	G WTZZ\%5%5c%:$;<$ws|n55	G %	'' 
s   CA9CC	c               $    V ^8  d   QhRRRRRR/# )r   rI   r
   r6   r   r   rJ   r   )r   s   "r   r   r      s!       S ^ r   c           	     "   \        V 4      pVR,          '       d   V# \        4       p\        P                  ! V R2RVR,          RVR,          RV/\        R7      pVP                  4        VP                  4       pVP                  R	4      VR&   V# )
z<Ensure a tab exists for the session, creating one if needed.rN   z/tabsuserIdrM   
sessionKeyrO   r6   r.   r%   tabId)r[   r   r*   post_DEFAULT_TIMEOUTraise_for_statusr.   r+   )rI   r6   rZ   baser7   r8   s   &&    r   _ensure_tabrf      s    7#GxD==&gi('-03

 !D 	99;D)GHNr   c                    V ^8  d   QhRRRR/# )r   rI   r
   r   zOptional[Dict[str, Any]]r   )r   s   "r   r   r      s     , ,= ,-E ,r   c                    T ;'       g    Rp \         ;_uu_ 4        \        P                  V R4      uuRRR4       #   + '       g   i     R# ; i)zRemove and return session info.rL   N)rU   rG   pop)rI   s   &r   _drop_sessionrj      s.    ""G	}}Wd+ 
s	   <A	c               (    V ^8  d   QhRRRRRRRR/# r   pathr   bodydictr%   r0   r   r   )r   s   "r   r   r      s(       4 # T r   c                    \        4        V  2p\        P                  ! W1VR7      pVP                  4        VP	                  4       # )z0POST JSON to camofox and return parsed response.r`   )r   r*   rb   rd   r.   rm   rn   r%   r6   r7   s   &&&  r   _postrr      s<    v
&C==9D99;r   c               (    V ^8  d   QhRRRRRRRR/# )r   rm   r   paramsro   r%   r0   r   r   )r   s   "r   r   r      s(      s D # T r   c                    \        4        V  2p\        P                  ! W1VR7      pVP                  4        VP	                  4       # )z,GET from camofox and return parsed response.rt   r%   )r   r*   r+   rd   r.   rm   rt   r%   r6   r7   s   &&&  r   _getrx      s<    v
&C<<G<D99;r   c               (    V ^8  d   QhRRRRRRRR/# )	r   rm   r   rt   ro   r%   r0   r   zrequests.Responser   )r   s   "r   r   r      s)      3  c Qb r   c                r    \        4        V  2p\        P                  ! W1VR7      pVP                  4        V# )z;GET from camofox and return raw response (for binary data).rv   )r   r*   r+   rd   rw   s   &&&  r   _get_rawr{      s5    v
&C<<G<DKr   c               (    V ^8  d   QhRRRRRRRR/# rl   r   )r   s   "r   r   r      s(      # T 3 d r   c                    \        4        V  2p\        P                  ! W1VR7      pVP                  4        VP	                  4       # )z-DELETE to camofox and return parsed response.r`   )r   r*   deleterd   r.   rq   s   &&&  r   _deleter      s<    v
&C??37;D99;r   c               $    V ^8  d   QhRRRRRR/# )r   r6   r   rI   r
   r   r   )r   s   "r   r   r      s!     &? &?# &? &? &?r   c           	         \        V4      pVR,          '       g   \        W4      pRRRV /pM$\        RVR,           R2RVR,          RV /^<R	7      pR
RRVP                  RV 4      RVP                  RR4      /p\	        4       pV'       d
   WTR&   RVR&   \
        P                  ! V4      #   \        P                   d(   p\
        P                  ! R
RRRT 2/4      u Rp?# Rp?i\        P                   d*    \
        P                  ! R
RRR\        4        R2/4      u # \         d.   p\
        P                  ! R
RR\        T4      /4      u Rp?# Rp?ii ; i)zNavigate to a URL via Camofox.rN   okTr6   /tabs/z	/navigater^   rM   r$   successtitler   vnc_urlz]Browser is visible via VNC. Share this link with the user so they can watch the browser live.vnc_hintFerrorzNavigation failed: 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[   rf   rr   r+   r?   r.   dumpsr*   	HTTPErrorConnectionErrorr   r5   r   )r6   rI   rZ   r8   resultvnces   &&     r   camofox_navigater      sk   $?w'x  !'/G$s+D *+9579-uc:D t488E3'TXXgr*

 m #9T : zz&!! Rzz9eW8KA36OPQQ## zzu4_5F4G H_ _
  	  ?zz9eWc!f=>>?sB   B(B+ +E C"E"E9'E"E+E,"EEEc               (    V ^8  d   QhRRRRRRRR/# )r   fullr   rI   r
   	user_taskr   r   r   )r   s   "r   r   r     s*     #? #?4 #?- #? -#?9<#?r   c           	     
    \        V4      pVR,          '       g   \        P                  ! RRRR/4      # \        RVR,           R2RVR	,          /R
7      pVP	                  RR4      pVP	                  R^ 4      p^ RIHpHpHp	 \        V4      V8  d   V'       d
   V! WR4      pMV	! V4      p\        P                  ! RRRVRV/4      #   \         d.   p
\        P                  ! RRR\        T
4      /4      u Rp
?
# Rp
?
ii ; i)z-Get accessibility tree snapshot from Camofox.rN   r   Fr   0No browser session. Call browser_navigate first.r   	/snapshotr^   rM   rt   snapshotr   	refsCount)SNAPSHOT_SUMMARIZE_THRESHOLD_extract_relevant_content_truncate_snapshotTelement_countN)r[   r.   r   rx   r+   tools.browser_toolr   r   r   lenr5   r   )r   rI   r   rZ   r8   r   
refs_countr   r   r   r   s   &&&        r   camofox_snapshotr     s    ?w'x  ::y%:lmnnWX&'y1gi01

 88J+XXk1-
	
 	
 x=774XI-h7zztZ
  	
  ?zz9eWc!f=>>?s)   3C
 A&C
 ,C
 
D"C=7D=Dc               $    V ^8  d   QhRRRRRR/# )r   refr   rI   r
   r   r   )r   s   "r   r   r   '  s!     ? ?s ?] ?c ?r   c                    \        V4      pVR,          '       g   \        P                  ! RRRR/4      # V P                  R4      p\	        RVR,           R2R	VR
,          RV/4      p\        P                  ! RRRVRVP                  RR4      /4      #   \         d.   p\        P                  ! RRR\        T4      /4      u Rp?# Rp?ii ; i)z$Click an element by ref via Camofox.rN   r   Fr   r   @r   z/clickr^   rM   r   Tclickedr6   r   N)r[   r.   r   lstriprr   r+   r5   r   )r   rI   rZ   	clean_refr8   r   s   &&    r   camofox_clickr   '  s    ?w'x  ::y%:lmnn JJsO	WX&'v.wy)5)<
 zzty488E2&
  	
  ?zz9eWc!f=>>?s#   3B AB C "CCCc               (    V ^8  d   QhRRRRRRRR/# )r   r   r   textrI   r
   r   r   )r   s   "r   r   r   >  s(     ? ?c ? ?} ? ?r   c           	         \        V4      pVR,          '       g   \        P                  ! RRRR/4      # V P                  R4      p\	        RVR,           R2R	VR
,          RVRV/4       \        P                  ! RRRVRV/4      #   \
         d.   p\        P                  ! RRR\        T4      /4      u Rp?# Rp?ii ; i)z-Type text into an element by ref via Camofox.rN   r   Fr   r   r   r   z/typer^   rM   r   r   TtypedelementN)r[   r.   r   r   rr   r5   r   )r   r   rI   rZ   r   r   s   &&&   r   camofox_typer   >  s    ?w'x  ::y%:lmnnJJsO	WX&'u-wy)5)VTJ	
 zztTy
  	
  ?zz9eWc!f=>>?s#   3B AB B?"B:4B?:B?c               $    V ^8  d   QhRRRRRR/# )r   	directionr   rI   r
   r   r   )r   s   "r   r   r   T  s!     ? ?c ?M ?S ?r   c           	     Z    \        V4      pVR,          '       g   \        P                  ! RRRR/4      # \        RVR,           R2RVR	,          R
V /4       \        P                  ! RRRV /4      #   \         d.   p\        P                  ! RRR\        T4      /4      u Rp?# Rp?ii ; i)zScroll the page via Camofox.rN   r   Fr   r   r   z/scrollr^   rM   r   TscrolledNr[   r.   r   rr   r5   r   )r   rI   rZ   r   s   &&  r   camofox_scrollr   T  s    ?w'x  ::y%:lmnnWX&'w/wy);	B	
 zz9dJ	BCC ?zz9eWc!f=>>?"   3A2 ;A2 2B*="B%B*%B*c                    V ^8  d   QhRRRR/# r   rI   r
   r   r   r   )r   s   "r   r   r   d  s     ? ?- ?3 ?r   c           	     v    \        V 4      pVR,          '       g   \        P                  ! RRRR/4      # \        RVR,           R2RVR	,          /4      p\        P                  ! RR
RVP	                  RR4      /4      #   \
         d.   p\        P                  ! RRR\        T4      /4      u Rp?# Rp?ii ; i)zNavigate back via Camofox.rN   r   Fr   r   r   z/backr^   rM   Tr6   r   N)r[   r.   r   rr   r+   r5   r   )rI   rZ   r8   r   s   &   r   camofox_backr   d  s    ?w'x  ::y%:lmnnWX&'u-wy)*
 zz9dE488E23FGHH ?zz9eWc!f=>>?s#   3B  A	B   B8"B3-B83B8c               $    V ^8  d   QhRRRRRR/# )r   keyr   rI   r
   r   r   )r   s   "r   r   r   t  s!     ? ?s ?] ?c ?r   c           	     Z    \        V4      pVR,          '       g   \        P                  ! RRRR/4      # \        RVR,           R2RVR	,          R
V /4       \        P                  ! RRRV /4      #   \         d.   p\        P                  ! RRR\        T4      /4      u Rp?# Rp?ii ; i)z!Press a keyboard key via Camofox.rN   r   Fr   r   r   z/pressr^   rM   r   TpressedNr   )r   rI   rZ   r   s   &&  r   camofox_pressr   t  s    ?w'x  ::y%:lmnnWX&'v.wy)5#6	
 zz9dIs;<< ?zz9eWc!f=>>?r   c                    V ^8  d   QhRRRR/# r   r   )r   s   "r   r   r     s     P P= PC Pr   c                6    \        V 4      pV'       g   \        P                  ! RRRR/4      # \        RVR,           24       \        P                  ! RRRR/4      #   \         d0   p\        P                  ! RRRRR\        T4      /4      u Rp?# Rp?ii ; i)z&Close the browser session via Camofox.r   Tclosed
/sessions/rM   warningN)rj   r.   r   r   r5   r   )rI   rZ   r   s   &  r   camofox_closer     s    
P(::y$$?@@+,-	
 zz9dHd;<< Pzz9dHdIs1vNOOPs"   ,A .A B)$BBBc                    V ^8  d   QhRRRR/# r   r   )r   s   "r   r   r     s     ,? ,? ,? ,?r   c           
         \        V 4      pVR,          '       g   \        P                  ! RRRR/4      # ^ RIp\	        RVR,           R2R	VR
,          /R7      pVP                  RR4      p. pVP                  R4      p\        V4       F  w  rxVP                  4       p	V	P                  R4      '       g   V	P                  R4      '       g   KE  VP                  RV	4      p
V
'       d   V
P                  ^4      MRpRpV^,           \        V4      8  dG   VP                  RWg^,           ,          P                  4       4      pV'       d   VP                  ^4      pV'       g   V'       g   K  VP                  RVRV/4       K  	  \        P                  ! RRRVR\        V4      /4      #   \         d.   p\        P                  ! RRR\        T4      /4      u Rp?# Rp?ii ; i)zGet images on the current page via Camofox.

Extracts image information from the accessibility tree snapshot,
since Camofox does not expose a dedicated /images endpoint.
rN   r   Fr   r   Nr   r   r^   rM   r   r   r   
z- img zimg zimg\s+"([^"]*)"z/url:\s*(\S+)srcaltTimagescount)r[   r.   r   rerx   r+   split	enumeratestrip
startswithsearchgroupr   appendr5   r   )rI   rZ   r   r8   r   r   linesilinestripped	alt_matchr   r   	url_matchr   s   &              r   camofox_get_imagesr     s   &?w'x  ::y%:lmnnWX&'y1gi01
 88J+
 t$ 'GAzz|H""8,,0C0CF0K0KII&8(C	,5iooa(2q53u:% "		*:Ea%L<N<N<P QI 'ooa0##MM5#uc":; ( zztfS[
  	
  ?zz9eWc!f=>>?s<   3F, BF, F, *A9F, $F, .=F, ,G$7"GG$G$c               (    V ^8  d   QhRRRRRRRR/# )r   questionr   annotater   rI   r
   r   r   )r   s   "r   r   r     s0     W? W?S W?D W?)W?58W?r   c                    \        V4      pVR,          '       g   \        P                  ! RRRR/4      # \        RVR,           R2RVR	,          /R
7      p^ RIHp V! 4       R,          pVP                  RRR7       \        VR\        P                  ! 4       P                  R,           R2,          4      p\        VR4      ;_uu_ 4       pVP                  VP                  4       RRR4       \        P                  ! VP                  4      P!                  R4      p	Rp
V'       d?    \#        RVR,           R2RVR	,          /R
7      pRVP%                  RR4      R,           2p
^ RIHp V! V
4      p
^ RIHp RV  V
 2p ^ RIHp V! 4       p\5        VP%                  R/ 4      P%                  R/ 4      P%                  R ^x4      4      pV! R!R"R#R$R%R%V/R$R&R&R'R(V	 2//./.RVR)7      pVP6                  '       d@   VP6                  ^ ,          P8                  P                  ;'       g    RP;                  4       MRp^ RIHp V! V4      p\        P                  ! RRR*VR+V/4      #   + '       g   i     EL; i  \&         d     EL"i ; i  \&         d    ^xp Li ; i  \&         d.   p\        P                  ! RRR\        T4      /4      u Rp?# Rp?ii ; i),z<Take a screenshot and analyze it with vision AI via Camofox.rN   r   Fr   r   r   z/screenshotr^   rM   r   )get_hermes_homebrowser_screenshotsT)parentsexist_okbrowser_screenshot_:N   Nz.pngwbNzutf-8r   r   z5

Accessibility tree (element refs for interaction):
r   :Ni  N)redact_sensitive_text)call_llmz,Analyze this browser screenshot and answer: r   	auxiliaryvisionr%   roleusercontenttyper   	image_urlr6   zdata:image/png;base64,)messagestaskr%   analysisscreenshot_path)r[   r.   r   r{   hermes_constantsr   mkdirr   rV   rW   rX   openwriter   base64	b64encodedecoderx   r+   r5   agent.redactr   agent.auxiliary_clientr   hermes_cli.configr   r0   choicesmessager   )r   r   rI   rZ   r7   r   screenshots_dirr   fimg_b64annotation_context	snap_datar   r   vision_promptr   _cfg_vision_timeoutresponser   r   s   &&&                  r   camofox_visionr    s   T?w'x  ::y%:lmnn WX&'{3gi01
 	5)+.CCdT:o2EdjjlFVFVWYFZE[[_0``a/4((AGGDLL! ) ""4<<077@   WX./y9$gi&89	 (``i`m`mnxz|`}  D  aE  `F  &G" 	723EF 	4 ;8*!"$ 	
	"5=D!$((;";"?"?""M"Q"QR[]`"abO VV];#!%;G9#E&  #
  KSJZJZJZH$$Q'//77==2DDF`b 	7(2zzt
  	{ )((  *  	"!O	"<  ?zz9eWc!f=>>?s   3J  BJ  I'(A J  *=I; 'J  AJ 0J  <-J  *<J  'I8	2	J  ;J
J  	J

J  JJ  JJ   K+"KKKc               $    V ^8  d   QhRRRRRR/# )r   clearr   rI   r
   r   r   r   )r   s   "r   r   r     s!      4 - 3 r   c                F    \         P                  ! RRR. R. R^ R^ RR/4      # )	u   Get console output — limited support in Camofox.

Camofox does not expose browser console logs via its REST API.
Returns an empty result with a note.
r   Tconsole_messages	js_errorstotal_messagestotal_errorsnotez|Console log capture is not available with the Camofox backend. Use browser_snapshot or browser_vision to inspect page state.)r.   r   )r
  rI   s   &&r   camofox_consoler    s<     ::4BR! P  r   c                   V ^8  d   QhRR/# )r   r   Noner   )r   s   "r   r   r   1  s     
 
d 
r   c                 x   \         ;_uu_ 4        \        \        P                  4       4      p RRR4       X  F  w  r \	        RVR,           24       K  	  \         ;_uu_ 4        \        P                  4        RRR4       R#   + '       g   i     L^; i  \
         d     Km  i ; i  + '       g   i     R# ; i)z"Close all active camofox sessions.Nr   rM   )rU   listrG   itemsr   r5   r
  )sessionsrI   rZ   s      r   cleanup_all_camofox_sessionsr  1  s    		)* 
$	j!3 456 %
 
 
 

  			s(   BB$B(B	B%$B%(B9	)zabout:blank)N)FNN)FN)9__conditional_annotations____doc__
__future__r   r   r.   loggingr   	threadingtimerV   pathlibr   typingr   r   r   r*   r   r   tools.browser_camofox_stater	   	getLogger__name__loggerrc   _SNAPSHOT_MAX_CHARSr   __annotations__r-   r   r!   r<   r?   rF   rG   LockrU   r[   rf   rj   rr   rx   r{   r   r   r   r   r   r   r   r   r   r   r  r  r  )r  s   @r   <module>r(     s  , #    	     & &  ) <			8	$   -  4
#
28( (*	$ )!<*, 1A  $(8H  (,<L  %)9I &?R#?L?.?,? ? ? P,?^W?t*
r   