
    PL
jc                       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	m
Z
mZmZ ddlZddlmZmZ ddlmZ ddlmZ  ej        e          ZdZd	Zdad
ed<   dadIdZdJdZdJdZdKdZ dLdZ!dJdZ"dMdZ#dNdZ$dOdZ%i Z&ded <    ej'                    Z(dPd"Z)dQd#Z*dRdSd&Z+dTd(Z,dUdVd)Z-efdWd/Z.defdXd1Z/defdYd3Z0defdWd4Z1dUdZd5Z2	 	 d[d\d8Z3dUd]d:Z4dUd^d<Z5dUd_d>Z6dUd`d?Z7dUdadAZ8dUd`dBZ9dUd`dCZ:	 	 dbdcdFZ;dbdddHZ<dS )euA  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)AnyDictOptional)cfg_getload_config)get_camofox_identity)
tool_error   i8 Optional[str]_vnc_urlFreturnstrc                 R    t          j        dd                              d          S )z:Return the configured Camofox server URL, or empty string.CAMOFOX_URL /)osgetenvrstrip     9/home/kuhnn/.hermes/hermes-agent/tools/browser_camofox.pyget_camofox_urlr   3   s"    9]B''..s333r   boolc                     t          j        dd                                          rdS t          t	                                S )aq  True when Camofox backend is configured and no CDP override is active.

    When the user has explicitly connected to a live Chrome instance via
    ``/browser connect`` (which sets ``BROWSER_CDP_URL``), the CDP connection
    takes priority over Camofox so the browser tools operate on the real
    browser instead of being silently routed to the Camofox backend.
    BROWSER_CDP_URLr   F)r   r   stripr   r   r   r   r   is_camofox_moder   8   s>     
y"B''--// u!!"""r   c                    t                      } | sdS 	 t          j        |  dd          }|j        dk    rt          s	 |                                }|                    d          }t          |t                    r2d|cxk    rdk    r%n n"d	d
lm	}  ||           }|j
        pd}d| d| an# t          t          f$ r Y nw xY wda|j        dk    S # t          $ r Y dS w xY w)z'Verify the Camofox server is reachable.Fz/health   timeout   vncPort   i  r   )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<   E   sA    

C u|sOOOQ777s""+;"	yy{{88I..h,, ;h1G1G1G1G%1G1G1G1G1G555555%Xc]]F!?9kD:::::H)   #3&&   uus6   +C  A0B1 0C 1CC CC 
C#"C#c                 :    t           st                       t          S )z>Return the VNC URL if the Camofox server exposes one, or None.)r-   r<   r   r   r   r   get_vnc_urlr>   ^   s     "!!!Or   Dict[str, Any]c                    	 t                                          di                               di           } n4# t          $ r'}t                              d|           i cY d}~S d}~ww xY wt          | t                    r| ni S )z>Return the ``browser.camofox`` config block, or an empty dict.browsercamofoxz7camofox config check failed, defaulting to disabled: %sN)r   r+   r5   loggerwarningr/   dict)camofox_cfgexcs     r   _get_camofox_configrH   e   s    !mm''	266::9bII   PRUVVV						 %[$77?;;R?s   69 
A*A%A*%A*c                 ^    t          t                                          d                    S )al  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.
    managed_persistence)r   rH   r+   r   r   r   _managed_persistence_enabledrK   o   s(     #%%))*?@@AAAr   task_idrF   Optional[Dict[str, str]]c                   t          j        dd                                          p5t          |                    d          pd                                          }|sdS t          j        dd                                          pDt          |                    d          pd                                          pd| pddd	          }||d
S )zReturn an externally configured Camofox identity, if one is set.

    Integrations that own the visible Camofox browser can set a shared user ID
    so Hermes operates in the same browser profile instead of creating a
    separate private session.
    CAMOFOX_USER_IDr   user_idNCAMOFOX_SESSION_KEYsession_keytask_default   )rP   rR   )r   r   r   r   r+   )rL   rF   rP   rR   s       r   _camofox_identity_overriderV   {   s     i)2..4466g#kooi>X>X>^\^:_:_:e:e:g:gG t 		',,2244 	1{}--344::<<	10G(y#2#.00 
 {;;;r   nameOptional[bool]c                    t          j        | d                                                                          }|sd S |dv rdS |dv rdS t                              d| |           d S )Nr   >   1onyestrueT>   0noofffalseFz"Ignoring invalid boolean env %s=%r)r   r   r   lowerrC   debug)rW   raws     r   	_env_flagre      sv    
)D"


#
#
%
%
+
+
-
-C t
(((t
)))u
LL5tSAAA4r   c                l    t          d          }||S t          |                     d                    S )z@Return whether Hermes should recover an existing Camofox tab ID.CAMOFOX_ADOPT_EXISTING_TABNadopt_existing_tab)re   r   r+   )rF   	env_values     r   _adopt_existing_tab_enabledrj      s7    677I 455666r   zDict[str, Dict[str, Any]]	_sessionssessionc                
   |                      d          s|                      d          s| S t                      s| S 	 t          dd| d         id                               dg           }nH# t          $ r;}t                              d	|                      d          |           | cY d
}~S d
}~ww xY wt          |t                    r|s| S |                      d          fd|D             }|pd |D             }|r|d         nd
}t          |t                    r|                     d          nd
}t          |t                    r6|r4|| d<   t                              d||                      d                     | S )a0  Attach process-local state to an already-open managed Camofox tab.

    Some integrations own the visible Camofox tab outside Hermes. Gateway
    restarts can leave this module's in-memory session cache empty even though
    Camofox still has that tab, so rehydrate tab_id before creating a new tab.
    tab_idrh   /tabsuserIdrP   r!   paramsr#   tabsz&Camofox tab adoption failed for %s: %sNrR   c                p    g | ]2}t          |t                    r|                    d           k    0|3S )
listItemId)r/   rE   r+   ).0tabrR   s     r   
<listcomp>z'_adopt_existing_tab.<locals>.<listcomp>   sN       c4   &)WW\%:%:k%I%I 	%I%I%Ir   c                <    g | ]}t          |t                    |S r   )r/   rE   )rv   rw   s     r   rx   z'_adopt_existing_tab.<locals>.<listcomp>   s'    "P"P"P3*S$:O:O"P3"P"P"Pr   tabIdz&Adopted existing Camofox tab %s for %s)
r+   r   _getr5   rC   rc   r/   listrE   r   )rl   rs   rG   matching_tabs
candidateslatestrn   rR   s          @r   _adopt_existing_tabr      s    {{8 GKK0D$E$E  GXwy/A$BANNNRRSY[]^^   =w{{9?U?UWZ[[[ dD!!  ++m,,K     M
 P"P"P$"P"P"PJ)3Z^^tF$.vt$<$<FVZZ   $F&# _6 _"=vw{{S\G]G]^^^Ns   .A. .
B380B.(B3.B3c                n   | pd} t           5  | t          v r&t          t          |                    cddd           S t                      }t	          | |          }|r"|d         d|d         dt          |          d}nt          |                    d                    r1t          |           }|d         d|d         dt          |          d}n4dt          j
                    j        dd	          dd
| dd          ddd}|t          | <   t          |          cddd           S # 1 swxY w Y   dS )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.
    rT   NrP   rR   T)rP   rn   rR   managedrh   rJ   hermes_
   rS   rU   F)_sessions_lockrk   r   rH   rV   rj   r   r+   r	   uuiduuid4hex)rL   rF   identity_overriderl   identitys        r   _get_sessionr      s    "G	  ,  ,i&y'9:: ,  ,  ,  ,  ,  ,  ,  , *++6wLL 	,Y70?&A+&N&N GG +//"78899 	+G44H#I.'6&A+&N&N GG =TZ\\%5crc%:<<5wss|55 &+ G %	'"7++A ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,s   #D*C!D**D.1D.about:blankr6   c                2   t          |           }|d         r|S t                      }t          j        | d|d         |d         |dt                    }|                                 |                                }|                    d          |d<   |S )z<Ensure a tab exists for the session, creating one if needed.rn   ro   rP   rR   )rp   
sessionKeyr6   r.   r#   r{   )r   r   r*   post_DEFAULT_TIMEOUTraise_for_statusr.   r+   )rL   r6   rl   baser7   r8   s         r   _ensure_tabr      s    7##Gx D=i(!-0
 

 !  D 	99;;D))GHNr   Optional[Dict[str, Any]]c                    | pd} t           5  t                              | d          cddd           S # 1 swxY w Y   dS )zRemove and return session info.rT   N)r   rk   pop)rL   s    r   _drop_sessionr     s    "G	 , ,}}Wd++, , , , , , , , , , , , , , , , , ,s   488c                    t                      }t          |                    d                    st          | |          r,t	          |            t
                              d|            dS dS )a  Release the in-memory session without destroying the server-side context.

    When managed persistence is enabled the browser profile (and its cookies)
    must survive across agent tasks.  This helper drops only the local tracking
    entry and returns ``True``.  When managed persistence is *not* enabled it
    does nothing and returns ``False`` so the caller can fall back to
    :func:`camofox_close`.
    rJ   z6Camofox soft cleanup for task %s (managed persistence)TF)rH   r   r+   rV   r   rC   rc   )rL   rF   s     r   camofox_soft_cleanupr     sm     &''KKOO12233 7QRY[f7g7g gMwWWWt5r   pathbodyrE   r#   r0   c                    t                       |  }t          j        |||          }|                                 |                                S )z0POST JSON to camofox and return parsed response.r   )r   r*   r   r   r.   r   r   r#   r6   r7   s        r   _postr   ,  sN    
&
&
&C=4999D99;;r   rr   c                    t                       |  }t          j        |||          }|                                 |                                S )z,GET from camofox and return parsed response.rq   )r   r*   r+   r   r.   r   rr   r#   r6   r7   s        r   r|   r|   4  sN    
&
&
&C<FG<<<D99;;r   requests.Responsec                    t                       |  }t          j        |||          }|                                 |S )z;GET from camofox and return raw response (for binary data).rq   )r   r*   r+   r   r   s        r   _get_rawr   <  sE    
&
&
&C<FG<<<DKr   c                    t                       |  }t          j        |||          }|                                 |                                S )z-DELETE to camofox and return parsed response.r   )r   r*   deleter   r.   r   s        r   _deleter   D  sN    
&
&
&C?3T7;;;D99;;r   c                   	 t          |          }|d         st          ||           }d| d}n%t          d|d          d|d         | dd	          }d|                    d
|           |                    dd          d}t	                      }|r
||d<   d|d<   	 t          d|d          dd|d         i          }|                    dd          }ddlm}m}	 t          |          |k    r |	|          }||d<   |                    dd          |d<   n# t          $ r Y nw xY wt          j        |          S # 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.rn   T)okr6   /tabs/z	/navigaterP   )rp   r6   <   r"   r6   titler   )successr6   r   vnc_urlz]Browser is visible via VNC. Share this link with the user so they can watch the browser live.vnc_hint	/snapshotrp   rr   snapshotr   )SNAPSHOT_SUMMARIZE_THRESHOLD_truncate_snapshot	refsCount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|   tools.browser_toolr   r   lenr5   r.   dumpsr*   	HTTPErrorr
   ConnectionErrorr   r   )r6   rL   rl   r8   resultvnc	snap_datasnapshot_textr   r   es              r   camofox_navigater   P  s   71w''x  
	!'3//Gs++DD 5*555"9-c::  D 88E3''XXgr**
 

 mm 	 #F9T :	5*555 ')"45  I &MM*b99M        =!!$@@@ 2 2= A A!.F:&/mmK&C&CF?## 	 	 	D	 z&!!! D D D333UCCCCCCCCC#   z__5F5F _ _ _
 
   	 	 	  1 1 1#a&&%0000000001s[   BD? A=D D? 
D(%D? 'D((D? ?GE'!G'9G"	G+GGGfull	user_taskc                   	 t          |          }|d         st          dd          S t          d|d          dd|d         i	          }|                    d
d          }|                    dd          }ddlm}m}m}	 t          |          |k    r|r |||          }n |	|          }t          j
        d||d          S # t          $ r(}
t          t          |
          d          cY d}
~
S d}
~
ww xY w)z-Get accessibility tree snapshot from Camofox.rn   0No browser session. Call browser_navigate first.Fr   r   r   rp   rP   r   r   r   r   r   )r   _extract_relevant_contentr   T)r   r   r   N)r   r
   r|   r+   r   r   r   r   r   r.   r   r5   r   )r   rL   r   rl   r8   r   
refs_countr   r   r   r   s              r   camofox_snapshotr     su    1w''x  	aPZ_````1WX&111gi01
 
 

 88J++XXk1--
	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 x==777 844XyII--h77z '
 
   	
  1 1 1#a&&%0000000001s#   'C BC 
C:C5/C:5C:refc                   	 t          |          }|d         st          dd          S |                     d          }t          d|d          d|d         |d	          }t	          j        d
||                    dd          d          S # t          $ r(}t          t          |          d          cY d}~S d}~ww xY w)z$Click an element by ref via Camofox.rn   r   Fr   @r   z/clickrP   )rp   r   Tr6   r   )r   clickedr6   N)	r   r
   lstripr   r.   r   r+   r5   r   )r   rL   rl   	clean_refr8   r   s         r   camofox_clickr     s    1w''x  	aPZ_```` JJsOO	.WX&...y))<<
 
 z 88E2&&
 
   	
  1 1 1#a&&%0000000001s#   'B A#B 
C B;5C ;C textc                `   	 t          |          }|d         st          dd          S |                     d          }t          d|d          d|d         ||d	           t	          j        d
||d          S # t          $ r(}t          t          |          d          cY d}~S d}~ww xY w)z-Type text into an element by ref via Camofox.rn   r   Fr   r   r   z/typerP   )rp   r   r   T)r   typedelementN)r   r
   r   r   r.   r   r5   r   )r   r   rL   rl   r   r   s         r   camofox_typer     s    1w''x  	aPZ_````JJsOO	-WX&---y))TJJ	
 	
 	
 z 
 
   	
  1 1 1#a&&%0000000001s#   'A; AA; ;
B-B("B-(B-	directionc                2   	 t          |          }|d         st          dd          S t          d|d          d|d         | d           t          j        d	| d
          S # t
          $ r(}t          t          |          d          cY d}~S d}~ww xY w)zScroll the page via Camofox.rn   r   Fr   r   z/scrollrP   )rp   r   T)r   scrolledNr   r
   r   r.   r   r5   r   )r   rL   rl   r   s       r   camofox_scrollr     s    1w''x  	aPZ_````/WX&///y)	BB	
 	
 	
 zd	BBCCC 1 1 1#a&&%0000000001"   'A$ 9A$ $
B.BBBc                X   	 t          |           }|d         st          dd          S t          d|d          dd|d         i          }t          j        d	|                    d
d          d          S # t          $ r(}t          t          |          d          cY d}~S d}~ww xY w)zNavigate back via Camofox.rn   r   Fr   r   z/backrp   rP   Tr6   r   )r   r6   N)r   r
   r   r.   r   r+   r5   r   )rL   rl   r8   r   s       r   camofox_backr     s    1w''x  	aPZ_````-WX&---wy)*
 
 zd488E23F3FGGHHH 1 1 1#a&&%0000000001s#   'A7 AA7 7
B)B$B)$B)keyc                2   	 t          |          }|d         st          dd          S t          d|d          d|d         | d           t          j        d	| d
          S # t
          $ r(}t          t          |          d          cY d}~S d}~ww xY w)z!Press a keyboard key via Camofox.rn   r   Fr   r   z/pressrP   )rp   r   T)r   pressedNr   )r   rL   rl   r   s       r   camofox_pressr     s    1w''x  	aPZ_````.WX&...y)#66	
 	
 	
 zds;;<<< 1 1 1#a&&%0000000001r   c                *   	 t          |           }|st          j        ddd          S 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.T)r   closedz
/sessions/rP   )r   r   rD   N)r   r.   r   r   r5   r   )rL   rl   r   s      r   camofox_closer     s    
P(( 	A:$$??@@@-+--	
 	
 	
 zdd;;<<< P P Pzdds1vvNNOOOOOOOOPs"   'A .A 
B#$BBBc                   	 t          |           }|d         st          dd          S ddl}t          d|d          dd	|d
         i          }|                    dd          }g }|                    d          }t          |          D ]\  }}|                                }	|	                    d          r|	                    d|	          }
|
r|

                    d          nd}d}|dz   t          |          k     rH|	                    d||dz                                                      }|r|
                    d          }|s|r|                    ||d           t          j        d|t          |          d          S # t          $ r(}t          t!          |          d          cY d}~S d}~ww xY w)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   rp   rP   r   r   r   
)z- img zimg zimg\s+"([^"]*)"r&   z/url:\s*(\S+))srcaltT)r   imagescount)r   r
   rer|   r+   split	enumerater   
startswithsearchgroupr   appendr.   r   r5   r   )rL   rl   r   r8   r   r   linesilinestripped	alt_matchr   r   	url_matchr   s                  r   camofox_get_imagesr     s
   &1w''x  	aPZ_````			1WX&111gi01
 
 
 88J++
 t$$ '' 	< 	<GAtzz||H""#566 
<II&8(CC	,5=iooa(((2q53u::%% "		*:E!a%L<N<N<P<P Q QI  1'ooa00 <# <MM#c":":;;;z[[
 
   	
  1 1 1#a&&%0000000001s#   'F E!F 
F>F93F>9F>questionannotatec                   	 t          |          }|d         st          dd          S t          d|d          dd|d         i	          }d
dlm}  |            dz  }|                    dd           t          |dt          j                    j	        dd          dz            }t          |d          5 }|                    |j                   ddd           n# 1 swxY w Y   t          j        |j                                      d          }	d}
|rV	 t!          d|d          dd|d         i	          }d|                    dd          dd          }
n# t$          $ r Y nw xY wd
dlm}  ||
          }
d
dlm} d|  |
 }	 t/                      }t1          |ddi           }t3          |                    d d!                    }t3          |                    d"d#                    }n# t$          $ r d$}d#}Y nw xY w |d%d&|d'd(d)d*|	 id+gd,gd||-          }|j        r+|j        d
         j        j        pd                                nd}d
dlm}  ||          }t;          j        d||d.          S # t$          $ r(}t          t          |          d          cY d}~S d}~ww xY w)/z<Take a screenshot and analyze it with vision AI via Camofox.rn   r   Fr   r   z/screenshotrp   rP   r   r   )get_hermes_homebrowser_screenshotsT)parentsexist_okbrowser_screenshot_N   z.pngwbzutf-8r   r   z5

Accessibility tree (element refs for interaction):
r   i  )redact_sensitive_text)call_llmz,Analyze this browser screenshot and answer: 	auxiliaryvision)rT   r#   x   temperatureg?g      ^@userr   )typer   	image_urlr6   zdata:image/png;base64,)r  r  )rolecontent)messagestaskr
  r#   )r   analysisscreenshot_path)r   r
   r   hermes_constantsr   mkdirr   r   r   r   openwriter  base64	b64encodedecoder|   r+   r5   agent.redactr  agent.auxiliary_clientr  r   r   floatchoicesmessager   r.   r   )r   r   rL   rl   r7   r   screenshots_dirr  fimg_b64annotation_contextr   r  r  vision_prompt_cfg_vision_cfg_vision_timeout_vision_temperatureresponser  r   s                         r   camofox_visionr*  M  s<   W1w''x  	aPZ_```` 3WX&333gi01
 
 
 	544444)/++.CCdT:::o0`djllFVWYXYWYFZ0`0`0``aa/4(( 	"AGGDL!!!	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" "4<0077@@   	 9WX.999$gi&89  	 &G`i`m`mnxz|`}`}  D  @D  D  aE  &G  &G""    	766666223EFF 	433333$8 $!$ $ 	
	&==D!$XrJJJK#KOOIs$C$CDDO"'s(K(K"L"L 	& 	& 	&#O"%	& 8#];; +!#EG#E#E&    +#
 
 
" KSJZbH$Q'/7=2DDFFF`b 	766666((22z .
 
   	
  1 1 1#a&&%0000000001s   'J BJ ;C"J "C&&J )C&*3J AE# "J #
E0-J /E00!J A'G: 9J :HJ 
HA?J 
J=J82J=8J=clearc           	     8    t          j        dg g dddd          S )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.
    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)r.   r   )r+  rL   s     r   camofox_consoler2    s7     :P    r   )r   r   )r   r   )r   r   )r   r?   )rL   r   rF   r?   r   rM   )rW   r   r   rX   )rF   r?   r   r   )rl   r?   r   r?   )rL   r   r   r?   )r   )rL   r   r6   r   r   r?   )rL   r   r   r   )N)rL   r   r   r   )r   r   r   rE   r#   r0   r   rE   )r   r   rr   rE   r#   r0   r   rE   )r   r   rr   rE   r#   r0   r   r   )r6   r   rL   r   r   r   )FNN)r   r   rL   r   r   r   r   r   )r   r   rL   r   r   r   )r   r   r   r   rL   r   r   r   )r   r   rL   r   r   r   )rL   r   r   r   )r   r   rL   r   r   r   )FN)r   r   r   r   rL   r   r   r   )r+  r   rL   r   r   r   )=__doc__
__future__r   r  r.   loggingr   	threadingr   typingr   r   r   r*   hermes_cli.configr   r   tools.browser_camofox_stater	   tools.registryr
   	getLogger__name__rC   r   _SNAPSHOT_MAX_CHARSr   __annotations__r-   r   r   r<   r>   rH   rK   rV   re   rj   rk   Lockr   r   r   r   r   r   r   r|   r   r   r   r   r   r   r   r   r   r   r   r*  r2  r   r   r   <module>r@     s    , # " " " " "    				      & & & & & & & & & &  2 2 2 2 2 2 2 2 < < < < < < % % % % % %		8	$	$        4 4 4 4

# 
# 
# 
#   2   @ @ @ @	B 	B 	B 	B< < < <&	 	 	 	7 7 7 7 (*	 ) ) ) )!!# # # #L(, (, (, (,V    *, , , ,    * 1A      $(8H      (,<L      %)9I     91 91 91 91 91x CG04#1 #1 #1 #1 #1L1 1 1 1 1.1 1 1 1 1,1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 P P P P P,1 ,1 ,1 ,1 ,1^ 49,0Z1 Z1 Z1 Z1 Z1z      r   