
    iV                    x   U d 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	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mZ ddlmZ ddlmZ ddlmZ ddlmZ ddlmZ 	 dd	lm Z  n# e!$ r d
 Z Y nw xY w	 ddl"m#Z$ n# e!$ r d Z$Y nw xY wddl%m&Z& ddl'm(Z( ddl)m*Z* ddl+m,Z, ddl-m.Z. 	 ddl/m0Z1 n# e2$ r d Z1Y nw xY w ej3        e4          Z5dZ6ej7        8                    e6          Z9 ej:        d          de;e<df         fd            Z=de>e<         fdZ?dde<de<fdZ@i ZAeBe<eCf         eDd<   dZEd ZF eGd!d"h          ZHeGeDd#<   daIeeJ         eDd$<   d%aKdeJfd&ZLdee<         fd'ZMdee<         fd(ZNd)e<de<fd*ZOde<fd+ZPdee<eCf         fd,ZQd-e<ddfd.ZRd-e<ddfd/ZSe(e*e,d0ZTee<eUf         eDd1<   daVee&         eDd2<   d%aWd%aXdaYeeZ         eDd3<   da[ee<         eDd4<   d%a\dee&         fd5Z]dd6lm^Z_ de<fd7Z`d8e<deZfd9Zade<fd:ZbdeZfd;ZcdeZfd<Zdd%aed=afeZeDd><   deZfd?Zgd@e<deZfdAZhd-e<d@e<de<fdBZidCe<deZfdDZjd-e<de<fdEZkdeZfdFZlde<fdGZmi Znee<ee<e<f         f         eDdH<    eo            ZpeoeDdI<   i Zqee<e<f         eDdJ<   dKZrd%as eJejt        u                    dLdM                    Zvi Zwee<eCf         eDdN<   daxd%ay ejz                    Z{dO Z| ej}        e|           dP Z~dQe<dRe<ddfdSZdT ZdU ZdV ZdW Zd-e<fdXZ ej}        e           dYdZd[d@d\d]d^id@gd_d`dadbd[dcddded%dfig d_d`dgdhd[did\djd^idigd_d`dkdld[d\dmd^d\dnd^dodidpgd_d`dqdrd[dsd\dtdugdvdwidsgd_d`dxdyd[i g d_d`dzd{d[d|d\d}d^id|gd_d`d~dd[i g d_d`ddd[d\dd^ddd%ddddgd_d`ddd[ddd%ddd\dd^dg d_d`g
Zd-e<dee<e<f         fdZd-e<d)e<dee<e<f         fdZdd-ee<         dee<e<f         fdZde<fdZdpe<dee<         fdZ	 	 dd-e<de<dee<         deeJ         dee<ef         f
dZ	 dde<dee<         de<fdZdde<deJde<fdZdd@e<d-ee<         de<fdZ	 	 	 ddceZd-ee<         dee<         de<fdZddie<d-ee<         de<fdZddie<dpe<d-ee<         de<fdZddse<d-ee<         de<fdZdd-ee<         de<fdZdd|e<d-ee<         de<fdZddeZdee<         d-ee<         de<fdZdde<d-ee<         de<fdZdde<d-ee<         de<fdZd-e<fdZd-e<fdZdd-ee<         de<fdZdde<deZd-ee<         de<fdZddZddZdd-ee<         ddfdZd-e<ddfdZddZdaeeZ         eDd<   dee<         fdZdeZfdZdeZfdZdeZfdZe4dk    r	  ed            ed            e]            Zednde                                 dZ ede             e            r ed           n8 ed           	  e            Z eae          r" ed            ed e`                        ne e            sz ed¦           dà8                     e                      pdZ ede             e            r edƦ            edǦ           n! edȦ            edɦ            edʦ           n,# e$ r$  ed˦            ed e`                        Y nw xY we@e                                s, ede                                 d͝            edΦ            edϦ           eD ](Z eded          ded         ddԅ          d՝           ) ed֦            edצ            edئ            ed٦           ddlmZmZ dۄ eD             Z ej}        dYdedY         d݄ edެߦ            ej}        dadeda         d edߦ            ej}        dgdedg         d edߦ            ej}        dkdedk         d edߦ            ej}        dqdedq         d edߦ            ej}        dxdedx         d edߦ            ej}        dzdedz         d edߦ            ej}        d~ded~         d edߦ            ej}        dded         d edߦ            ej}        dded         d edߦ           dS )u  
Browser Tool Module

This module provides browser automation tools using agent-browser CLI.  It
supports multiple backends — **Browser Use** (cloud, default for Nous
subscribers), **Browserbase** (cloud, direct credentials), and **local
Chromium** — with identical agent-facing behaviour.  The backend is
auto-detected from config and available credentials.

The tool uses agent-browser's accessibility tree (ariaSnapshot) for text-based
page representation, making it ideal for LLM agents without vision capabilities.

Features:
- **Local mode** (default): zero-cost headless Chromium via agent-browser.
  Works on Linux servers without a display.  One-time setup:
  ``agent-browser install`` (downloads Chromium) or
  ``agent-browser install --with-deps`` (also installs system libraries for
  Debian/Ubuntu/Docker).
- **Cloud mode**: Browserbase or Browser Use cloud execution when configured.
- Session isolation per task ID
- Text-based page snapshots using accessibility tree
- Element interaction via ref selectors (@e1, @e2, etc.)
- Task-aware content extraction using LLM summarization
- Automatic cleanup of browser sessions

Environment Variables:
- BROWSERBASE_API_KEY: API key for direct Browserbase cloud mode
- BROWSERBASE_PROJECT_ID: Project ID for direct Browserbase cloud mode
- BROWSER_USE_API_KEY: API key for direct Browser Use cloud mode
- BROWSERBASE_PROXIES: Enable/disable residential proxies (default: "true")
- BROWSERBASE_ADVANCED_STEALTH: Enable advanced stealth mode with custom Chromium,
  requires Scale Plan (default: "false")
- BROWSERBASE_KEEP_ALIVE: Enable keepAlive for session reconnection after disconnects,
  requires paid plan (default: "true")
- BROWSERBASE_SESSION_TIMEOUT: Custom session timeout in milliseconds. Set to extend
  beyond project default. Common values: 600000 (10min), 1800000 (30min) (default: none)

Usage:
    from tools.browser_tool import browser_navigate, browser_snapshot, browser_click
    
    # Navigate to a page
    result = browser_navigate("https://example.com", task_id="task_123")
    
    # Get page snapshot
    snapshot = browser_snapshot(task_id="task_123")
    
    # Click an element
    browser_click("@e5", task_id="task_123")
    N)DictAnyOptionalListTuple)Path)call_llm)get_hermes_home)is_truthy_value)cfg_get)check_website_accessc                     d S N urls    7/home/ubuntu/.hermes/hermes-agent/tools/browser_tool.py<lambda>r   L   s    t     )is_safe_urlc                     dS NFr   r   s    r   r   r   Q       u r   )CloudBrowserProvider)BrowserbaseProvider)BrowserUseProvider)FirecrawlProvider) normalize_browser_cloud_provider)is_camofox_modec                      dS r   r   r   r   r   r   r   ^   r   r   )
z#/data/data/com.termux/files/usr/binz$/data/data/com.termux/files/usr/sbinz/opt/homebrew/binz/opt/homebrew/sbinz/usr/local/sbinz/usr/local/binz	/usr/sbinz/usr/binz/sbinz/bin   )maxsizereturn.c                     g } d}t           j                            |          st          |           S 	 t          j        |          D ]r}|                    d          r[|dk    rUt           j                            ||d          }t           j                            |          r|                     |           sn# t          $ r Y nw xY wt          |           S )a+  Find Homebrew versioned Node.js bin directories (e.g. node@20, node@24).

    When Node is installed via ``brew install node@24`` and NOT linked into
    /opt/homebrew/bin, agent-browser isn't discoverable on the default PATH.
    This function finds those directories so they can be prepended.
    z/opt/homebrew/optnodebin)	ospathisdirtuplelistdir
startswithjoinappendOSError)dirshomebrew_optentrybin_dirs       r   _discover_homebrew_node_dirsr4   t   s     D&L7==&& T{{Z-- 	) 	)E'' )EVOO',,|UEBB7==)) )KK(((		)
    ;;s   BB< <
C	C	c                      t                      } t          | dz  dz            }|gt          t                                t          S )zMReturn ordered browser CLI PATH candidates shared by discovery and execution.r%   r&   )r
   strlistr4   _SANE_PATH_DIRS)hermes_homehermes_node_bins     r   _browser_candidate_path_dirsr;      sF    !##K+.677OUd#?#A#ABBU_UUr    existing_pathc                 ^   d | pd                     t          j                  D             }t          |          }g }t	                      D ]A}|r||v s||v rt          j                            |          r|                    |           Bt          j                            ||z             S )zLPrepend browser-specific PATH fallbacks without reordering existing entries.c                     g | ]}||S r   r   ).0ps     r   
<listcomp>z'_merge_browser_path.<locals>.<listcomp>   s    JJJJ!JJJr   r<   )	splitr'   pathsepsetr;   r(   r)   r.   r-   )r=   
path_partsexisting_partsprefix_partsparts        r   _merge_browser_pathrJ      s    JJm1r88DDJJJJ__N L,.. & & 	t~--1E1E7== 	&%%%:??<*4555r   _last_screenshot_cleanup_by_dir   @  closerecord_EMPTY_OK_COMMANDS_cached_command_timeoutFc                      t           rt          S da t          } 	 ddlm}  |            }t          |dd          }|t          t          |          d          } n2# t          $ r%}t          
                    d|           Y d}~nd}~ww xY w| a| S )	a  Return the configured browser command timeout from config.yaml.

    Reads ``config["browser"]["command_timeout"]`` and falls back to
    ``DEFAULT_COMMAND_TIMEOUT`` (30s) if unset or unreadable.  Result is
    cached after the first call and cleared by ``cleanup_all_browsers()``.
    Tr   read_raw_configbrowsercommand_timeoutN   z.Could not read command_timeout from config: %s)_command_timeout_resolvedrQ   DEFAULT_COMMAND_TIMEOUThermes_cli.configrT   r   maxint	Exceptionloggerdebug)resultrT   cfgvales        r   _get_command_timeoutrd      s     ! '&& $$FJ555555oc9&788?S1%%F J J JEqIIIIIIIIJ$Ms   A A 
B	$BB	c                  T    t          j        dd                                          pdS )u>   Model for browser_vision (screenshot analysis — multimodal).AUXILIARY_VISION_MODELr<   Nr'   getenvstripr   r   r   _get_vision_modelrj      s&    9-r2288::BdBr   c                  T    t          j        dd                                          pdS )uC   Model for page snapshot text summarization — same as web_extract.AUXILIARY_WEB_EXTRACT_MODELr<   Nrg   r   r   r   _get_extraction_modelrm      s&    92B77==??G4Gr   cdp_urlc                 P   | pd                                 }|sdS |                                }d|v r|S |}|                    d          r|                    d          dk    r|                    d                              dd          d                                         rUd|                    dd          d         vr7|                    d	          rd
nd|                    dd          d         z   }n|S |                                                    d          r|}n|                    d          dz   }	 t          j
        |d          }|                                 |                                }n6# t          $ r)}t                              d|||           |cY d}~S d}~ww xY wt!          |
                    d          pd                                           }|rt                              d||           |S t                              d|           |S )a  Normalize a user-supplied CDP endpoint into a concrete connectable URL.

    Accepts:
    - full websocket endpoints: ws://host:port/devtools/browser/...
    - HTTP discovery endpoints: http://host:port or http://host:port/json/version
    - bare websocket host:port values like ws://host:port

    For discovery-style endpoints we fetch /json/version and return the
    webSocketDebuggerUrl so downstream tools always receive a concrete browser
    websocket instead of an ambiguous host:port URL.
    r<   z/devtools/browser/)ws://zwss://:   /r!   rp   zhttp://zhttps://z://z/json/version
   timeoutz,Failed to resolve CDP endpoint %s via %s: %sNwebSocketDebuggerUrlzResolved CDP endpoint %s -> %szKCDP discovery at %s did not return webSocketDebuggerUrl; using raw endpoint)ri   lowerr,   countrstriprsplitisdigitrC   endswithrequestsgetraise_for_statusjsonr]   r^   warningr6   info)	rn   rawlowereddiscovery_urlversion_urlresponsepayloadexcws_urls	            r   _resolve_cdp_overrider      s9    =b


!
!C riikkGw&&
M-.. 99S>>Q3::c??#9#9#q#A#A"#E#M#M#O#OTW_b_h_hilno_p_pqs_tTtTt*1*<*<W*E*EUYY:Y\YbYbchjkYlYlmnYooMMJ%%o66 B##**3///A<R888!!###--//   EsKY\]]]





 344:;;AACCF 4c6BBB
NN`bmnnnJs    >E? ?
F2	F-'F2-F2c                     t           j                            dd                                          } | rt	          |           S 	 ddlm}  |            }|                    di           }t          |t                    r2t	          t          |                    dd          pd                    S n2# t          $ r%}t                              d|           Y d}~nd}~ww xY wdS )	a|  Return a normalized CDP URL override, or empty string.

    Precedence is:
    1. ``BROWSER_CDP_URL`` env var (live override from ``/browser connect``)
    2. ``browser.cdp_url`` in config.yaml (persistent config)

    When either is set, we skip both Browserbase and the local headless
    launcher and connect directly to the supplied Chrome DevTools Protocol
    endpoint.
    BROWSER_CDP_URLr<   r   rS   rU   rn   z.Could not read browser.cdp_url from config: %sN)r'   environr   ri   r   rZ   rT   
isinstancedictr6   r]   r^   r_   )env_overriderT   ra   browser_cfgrc   s        r   _get_cdp_overrider     s    :>>"3R88>>@@L 3$\222J555555oggi,,k4(( 	T([__Y-K-K-Qr)R)RSSS	T J J JEqIIIIIIIIJ 2s   A,B3 3
C"=CC"c                     ddl m} m}m} 	 ddlm}  |            }t          |t                    r|                    di           ni }t          |t                    s| |fS t          |                    d          p|           }||vrt                              d|           | }|                    d          }	 |t          |          n|}|dk    r|}n# t          t          f$ r |}Y nw xY w||fS # t          $ r | |fcY S w xY w)zRead ``browser.dialog_policy`` + ``browser.dialog_timeout_s`` from config.

    Returns a ``(policy, timeout_s)`` tuple, falling back to the supervisor's
    defaults when keys are absent or invalid.
    r   )DEFAULT_DIALOG_POLICYDEFAULT_DIALOG_TIMEOUT_S_VALID_POLICIESrS   rU   dialog_policyz/Invalid browser.dialog_policy=%r; using defaultdialog_timeout_s)tools.browser_supervisorr   r   r   rZ   rT   r   r   r   r6   r^   r_   float	TypeError
ValueErrorr]   )	r   r   r   rT   ra   r   policytimeout_raw	timeout_ss	            r   _get_dialog_policy_configr   $  s            ?555555o0:30E0EMcggi,,,2+t,, 	C(*BBB[___55N9NOO((LLJFSSS*F!oo&899	1.9.Ek***KcIA~~4	:& 	1 	1 	10III	1y   ? ? ?$&>>>>>?s=   AC6 "AC6 =C C6 C/,C6 .C//C6 6DDtask_idc                    t                      }|sot          5  t                              | i           }ddd           n# 1 swxY w Y   t	          |                    d          pd          }|rt          |          }|sdS 	 ddlm} t                      \  }}|	                    | |||           dS # t          $ r'}t                              d| |           Y d}~dS d}~ww xY w)u~  Start a CDP supervisor for ``task_id`` if an endpoint is reachable.

    Idempotent — delegates to ``SupervisorRegistry.get_or_start`` which skips
    when a supervisor for this ``(task_id, cdp_url)`` already exists and
    tears down + restarts on URL change. Safe to call on every
    ``browser_navigate`` / ``/browser connect`` without worrying about
    double-attach.

    Resolves the CDP URL in this order:
      1. ``BROWSER_CDP_URL`` / ``browser.cdp_url`` — covers ``/browser connect``
         and config-set overrides.
      2. ``_active_sessions[task_id]["cdp_url"]`` — covers Browserbase + any
         other cloud provider whose ``create_session`` returns a raw CDP URL.

    Swallows all errors — failing to attach the supervisor must not break
    the browser session itself.  The agent simply won't see
    ``pending_dialogs`` / ``frame_tree`` fields in snapshots.
    Nrn   r<   r   SUPERVISOR_REGISTRY)r   rn   r   r   z8CDP supervisor attach for task=%s failed (non-fatal): %s)r   _cleanup_lock_active_sessionsr   r6   r   r   r   r   get_or_startr]   r^   r_   )r   rn   session_infomayber   r   r   r   s           r   _ensure_cdp_supervisorr   H  s   &  !!G 3  	= 	=+//<<L	= 	= 	= 	= 	= 	= 	= 	= 	= 	= 	= 	= 	= 	= 	=L$$Y//5266 	3+E22G 
@@@@@@577	(( &	 	) 	
 	
 	
 	
 	
  
 
 
F	
 	
 	
 	
 	
 	
 	
 	
 	

s)   A  AA0B7 7
C(C##C(c                     	 ddl m} |                    |            dS # t          $ r'}t                              d| |           Y d}~dS d}~ww xY w)zGStop the CDP supervisor for ``task_id`` if one exists. No-op otherwise.r   r   z6CDP supervisor stop for task=%s failed (non-fatal): %sN)r   r   stopr]   r^   r_   )r   r   r   s      r   _stop_cdp_supervisorr   x  s    ]@@@@@@  ))))) ] ] ]MwX[\\\\\\\\\]s    
AAA)browserbasezbrowser-use	firecrawl_PROVIDER_REGISTRY_cached_cloud_provider_cached_allow_private_urls_cached_agent_browserc                  P   t           rt          S da 	 ddlm}   |             }|                    di           }d}t          |t                    r0d|v r,t          |                    d                    }|dk    rdadS |r|t          v rt          |                     an2# t          $ r%}t                              d|           Y d}~nd}~ww xY wt          It                      }|                                r|an$t                      }|                                r|at          S )	aW  Return the configured cloud browser provider, or None for local mode.

    Reads ``config["browser"]["cloud_provider"]`` once and caches the result
    for the process lifetime. An explicit ``local`` provider disables cloud
    fallback. If unset, fall back to Browserbase when direct or managed
    Browserbase credentials are available.
    Tr   rS   rU   Ncloud_providerlocalz-Could not read cloud_provider from config: %s)_cloud_provider_resolvedr   rZ   rT   r   r   r   r   r   r]   r^   r_   r   is_configuredr   )rT   ra   r   provider_keyrc   fallback_providers         r   _get_cloud_providerr     su      &%%#I555555oggi,,k4(( 	-=-L-L; 011 L w&&)-&t 	HL,>>>%7%E%G%G" I I IDaHHHHHHHHI % /00**,, 	;%6"" 3 5 5 ..00 ;):&!!s   A+B ?B 
C)C		C)	is_termuxc                  &    t                      rdS dS )Nz5npm install -g agent-browser && agent-browser installzAnpm install -g agent-browser && agent-browser install --with-deps)_is_termux_environmentr   r   r   _browser_install_hintr     s     GFFNNr   browser_cmdc                 j    t                      o%t                      o|                                 dk    S )Nnpx agent-browser)r   _is_local_moderi   )r   s    r   %_requires_real_termux_browser_installr     s0    !##g(8(8g[=N=N=P=PTg=ggr   c                  $    dt                       S )NzqLocal browser automation on Termux cannot rely on the bare npx fallback. Install agent-browser explicitly first: )r   r   r   r   _termux_browser_install_errorr     s"    	M3H3J3J	M 	Mr   c                  B    t                      rdS t                      du S )zCReturn True when the browser tool will use a local browser backend.FN)r   r   r   r   r   r   r     s&     u  D((r   c                  >    t                      pt                      du S )u  Return True when the browser runs locally (no cloud provider).

    SSRF protection is only meaningful for cloud backends (Browserbase,
    BrowserUse) where the agent could reach internal resources on a remote
    machine.  For local backends — Camofox, or the built-in headless
    Chromium without a cloud provider — the user already has full terminal
    and network access on the same machine, so the check adds no security
    value.
    N)_is_camofox_moder   r   r   r   _is_local_backendr     s!     >!4!6!6$!>>r   T#_cached_auto_local_for_private_urlsc                  Z   t           rt          S da 	 ddlm}   |             }|                    di           }t          |t                    r&d|v r"t          |                    d                    an2# t          $ r%}t          
                    d|           Y d}~nd}~ww xY wt          S )a$  Return whether a cloud-configured install should auto-spawn a local
    Chromium for LAN/localhost URLs.

    Reads ``browser.auto_local_for_private_urls`` once (default ``True``) and
    caches it for the process lifetime.  When enabled, ``browser_navigate``
    routes URLs whose host resolves to a private/loopback/LAN address to a
    local headless Chromium sidecar even when a cloud provider (Browserbase
    / Browser-Use / Firecrawl) is configured globally.  Public URLs continue
    to use the cloud provider in the same conversation.
    Tr   rS   rU   auto_local_for_private_urlsz:Could not read auto_local_for_private_urls from config: %sN)%_auto_local_for_private_urls_resolvedr   rZ   rT   r   r   r   boolr]   r^   r_   rT   ra   r   rc   s       r   _auto_local_for_private_urlsr     s     - 322,0)	V555555oggi,,k4(( 	-Jk-Y-Y26 =>>3 3/  V V VQSTUUUUUUUUV..s   A!A4 4
B#>BB#r   c                    	 ddl m} ddl}ddl} ||           }|j        pd                                                                                    d          }|sdS 	 |                    |          }|j	        p$|j
        p|j        p||                    d          v S # t          $ r Y nw xY w|dv s|                    d	          rd
S |                    d          s*|                    d          s|                    d          rd
S 	 |                    |d|j        |j                  }n# |j        $ r Y dS w xY w|D ]d\  }}}}}		 |                    |	d                   }n# t          $ r Y 1w xY w|j	        s%|j
        s|j        s||                    d          v r d
S edS # t&          $ r'}
t(                              d| |
           Y d}
~
dS d}
~
ww xY w)u  Return True when the URL's host resolves to a private/LAN/loopback address.

    Reuses ``tools.url_safety.is_safe_url`` as the oracle — if the SSRF check
    would reject the URL, we treat it as "private" for routing purposes.  DNS
    resolution failures are treated as NOT private (fall through to whatever
    backend is configured, which will surface the DNS error naturally).
    r   )urlparseNr<   .Fz100.64.0.0/10)	localhostz
.localhostTz.localz.lanz	.internalz#URL-privacy check failed for %s: %s)urllib.parser   	ipaddresssockethostnameri   ry   r{   
ip_address
is_privateis_loopbackis_link_local
ip_networkr   r~   getaddrinfo	AF_UNSPECSOCK_STREAMgaierrorr]   r^   r_   )r   r   r   r   parsedr   ip	addr_info_sockaddrr   s              r   _url_is_privater   	  s   0 	*)))))#O)r002288::AA#FF 	5		%%h//B ?>?#? --o>>>	  	 	 	D	
 ~%%):):<)H)H%4X&& 	(*;*;F*C*C 	xGXGXYdGeGe 	4	**8T6;KVM_``II 	 	 	55	$- 	 	 Aq!Q))(1+66    > # --o>>>>tt ? u   :CEEEuuuuus   AF+  A B! !
B.+F+ -B..F+ ?F+ "D1 0F+ 1
D?;F+ >D??F+ E)(F+ )
E63F+ 5E660F+ (F+ +
G5GGc                     | d} t                      r| S t                      r| S t                      | S t                      s| S t	          |          s| S |  t
           S )a  Pick the session key that should handle ``url`` for ``task_id``.

    Returns the bare task_id unless ALL of these are true:
      1. A cloud provider is configured (``_get_cloud_provider()`` is not None).
      2. Auto-local routing is enabled (``browser.auto_local_for_private_urls``,
         default True).
      3. The URL resolves to a private/LAN/loopback address.
      4. A CDP override is not active (that path owns the whole session).
      5. Camofox mode is not active (Camofox is already local-only).

    When all are true, returns ``f"{task_id}::local"`` so the hybrid-routing
    path spawns a local Chromium sidecar while the cloud session (if any)
    continues to serve public URLs.
    Ndefault)r   r   r   r   r   _LOCAL_SUFFIX)r   r   s     r   _navigation_session_keyr   D  s~       $')) 3 &}&&&r   session_keyc                 6    |                      t                    S )zCReturn True when ``session_key`` is a hybrid-routing local sidecar.)r~   r   )r   s    r   _is_local_sidecar_keyr   b  s    ...r   c                 @    | d} t                               | |           S )aK  Return the session key to use for a non-nav browser tool call.

    If a previous ``browser_navigate`` on this task_id set a last-active key,
    use it so snapshot/click/fill/etc. hit the same session.  Otherwise fall
    back to the bare task_id (matches original behavior for tasks that never
    triggered hybrid routing).
    Nr   )_last_active_session_keyr   r   s    r   _last_session_keyr   g  s$     #''999r   c                  Z   t           rt          S da da	 ddlm}   |             }|                    di           }t          |t                    r$t          |                    d          d          an2# t          $ r%}t          
                    d|           Y d	}~nd	}~ww xY wt          S )
zReturn whether the browser is allowed to navigate to private/internal addresses.

    Reads ``config["browser"]["allow_private_urls"]`` once and caches the result
    for the process lifetime.  Defaults to ``False`` (SSRF protection active).
    TFr   rS   rU   allow_private_urlsr   z1Could not read allow_private_urls from config: %sN)_allow_private_urls_resolvedr   rZ   rT   r   r   r   r   r]   r^   r_   r   s       r   _allow_private_urlsr   t  s     $ *))#' !&	M555555oggi,,k4(( 	)8 455u* * *&  M M MH!LLLLLLLLM%%s   AA4 4
B#>BB#c                  L    t           j        dk    rdS t          j                    S )ue  Return a short temp directory path suitable for Unix domain sockets.

    macOS sets ``TMPDIR`` to ``/var/folders/xx/.../T/`` (~51 chars).  When we
    append ``agent-browser-hermes_…`` the resulting socket path exceeds the
    104-byte macOS limit for ``AF_UNIX`` addresses, causing agent-browser to
    fail with "Failed to create socket directory" or silent screenshot failures.

    Linux ``tempfile.gettempdir()`` already returns ``/tmp``, so this is a
    no-op there.  On macOS we bypass ``TMPDIR`` and use ``/tmp`` directly
    (symlink to ``/private/tmp``, sticky-bit protected, always available).
    darwinz/tmp)sysplatformtempfile
gettempdirr   r   r   _socket_safe_tmpdirr    s%     |xv   r   r   _recording_sessionsr   z::localBROWSER_INACTIVITY_TIMEOUT300_session_last_activityc                  B   t           rdS da t          rIt                              dt	          t                               	 t                       n2# t          $ r%} t                              d|            Y d} ~ nd} ~ ww xY wt          5  t          	                                 t          	                                 t          	                                 ddd           n# 1 swxY w Y   no# t          5  t          	                                 t          	                                 t          	                                 ddd           w # 1 swxY w Y   w xY w	 t                       dS # t          $ r&} t                              d|            Y d} ~ dS d} ~ ww xY w)ue  
    Emergency cleanup of all active browser sessions.
    Called on process exit or interrupt to prevent orphaned sessions.

    Also runs the orphan reaper to clean up daemons left behind by previously
    crashed hermes processes — this way every clean hermes exit sweeps
    accumulated orphans, not just ones that actively used the browser tool.
    NTz2Emergency cleanup: closing %s active session(s)...zEmergency cleanup error: %szOrphan reap on exit failed: %s)_cleanup_doner   r^   r   lencleanup_all_browsersr]   errorr   clearr  r  _reap_orphaned_browser_sessionsr_   )rc   s    r   _emergency_cleanup_all_sessionsr    s&     M  ,H())	+ 	+ 	+	, """" 	; 	; 	;LL6::::::::	;  , , &&(((&,,...#))+++, , , , , , , , , , , , , , , , , &&(((&,,...#))+++, , , , , , , , , , , , , , , ,:'))))) : : :5q999999999:s   A C. 
B A;6C. ;B  C. 
AC""C&)C&.E6AEEEEEEE. .
F8FFc                     t          j                     } g }t          5  t          t                                                    D ](\  }}| |z
  t
          k    r|                    |           )	 ddd           n# 1 swxY w Y   |D ]}	 t          | t                              ||           z
            }t          
                    d||           t          |           t          5  |t          v rt          |= ddd           n# 1 swxY w Y   # t          $ r&}t                              d||           Y d}~d}~ww xY wdS )a7  
    Clean up browser sessions that have been inactive for longer than the timeout.
    
    This function is called periodically by the background cleanup thread to
    automatically close sessions that haven't been used recently, preventing
    orphaned sessions (local or Browserbase) from accumulating.
    Nz<Cleaning up inactive session for task: %s (inactive for %ss)z)Error cleaning up inactive session %s: %s)timer   r7   r  items"BROWSER_SESSION_INACTIVITY_TIMEOUTr.   r\   r   r^   r   cleanup_browserr]   r   )current_timesessions_to_cleanupr   	last_timeelapsedrc   s         r   "_cleanup_inactive_browser_sessionsr    s    9;;L	 4 4"&'='C'C'E'E"F"F 	4 	4GYi'*LLL#**7333	44 4 4 4 4 4 4 4 4 4 4 4 4 4 4
 ' 	T 	T	T,)?)C)CG\)Z)ZZ[[GKKVX_ahiiiG$$$ 8 8444.w78 8 8 8 8 8 8 8 8 8 8 8 8 8 8  	T 	T 	TNNFQRSSSSSSSS	T	T 	TsO   AA::A>A>
AD'D9DD			DD		D
ED<<E
socket_dirsession_namec                 n   	 t           j                            | | d          }t          |d          5 }|                    t          t          j                                         ddd           dS # 1 swxY w Y   dS # t          $ r'}t          	                    d||           Y d}~dS d}~ww xY w)u  Record the current hermes PID as the owner of a browser socket dir.

    Written atomically to ``<socket_dir>/<session_name>.owner_pid`` so the
    orphan reaper can distinguish daemons owned by a live hermes process
    (don't reap) from daemons whose owner crashed (reap).  Best-effort —
    an OSError here just falls back to the legacy ``tracked_names``
    heuristic in the reaper.
    
.owner_pidwNz)Could not write owner_pid file for %s: %s)
r'   r(   r-   openwriter6   getpidr/   r^   r_   )r  r  r(   fr   s        r   _write_owner_pidr"    s   (w||J<(C(C(CDD$__ 	&GGC	$$%%%	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& ( ( (@!3	( 	( 	( 	( 	( 	( 	( 	( 	((s:   3B 4A6)B 6A::B =A:>B 
B4B//B4c                     ddl } t                      }t          j                            |d          }|                      |          }||                      t          j                            |d                    z  }||                      t          j                            |d                    z  }|sdS t
          5  d t                                          D             }ddd           n# 1 swxY w Y   d}|D ]}t          j                            |          }|	                    d          }|s:t          j                            || d          }	d}
t          j        
                    |	          r	 t          t          |	                                                                                    }	 t          j        |d           d	}
n# t           $ r d
}
Y nt"          $ r d	}
Y nw xY wn# t$          t&          f$ r d}
Y nw xY w|
d	u r|
||v rt          j                            || d          }t          j        
                    |          st)          j        |d	           y	 t          t          |                                                                                    }n.# t$          t&          f$ r t)          j        |d	           Y w xY w	 t          j        |d           n3# t           $ r t)          j        |d	           Y #t"          $ r Y /w xY w	 t          j        |t,          j                   t0                              d||           |dz  }n# t           t"          t&          f$ r Y nw xY wt)          j        |d	           |rt0                              d|           dS dS )uh  Scan for orphaned agent-browser daemon processes from previous runs.

    When the Python process that created a browser session exits uncleanly
    (SIGKILL, crash, gateway restart), the in-memory ``_active_sessions``
    tracking is lost but the node + Chromium processes keep running.

    This function scans the tmp directory for ``agent-browser-*`` socket dirs
    left behind by previous runs, reads the daemon PID files, and kills any
    daemons whose owning hermes process is no longer alive.

    Ownership detection priority:
      1. ``<session>.owner_pid`` file (written by current code) — if the
         referenced hermes PID is alive, leave the daemon alone regardless
         of whether it's in *this* process's ``_active_sessions``.  This is
         cross-process safe: two concurrent hermes instances won't reap each
         other's daemons.
      2. Fallback for daemons that predate owner_pid: check
         ``_active_sessions`` in the current process.  If not tracked here,
         treat as orphan (legacy behavior).

    Safe to call from any context — atexit, cleanup thread, or on demand.
    r   Nzagent-browser-h_*zagent-browser-cdp_*zagent-browser-hermes_*c                 b    h | ],}|                     d           |                     d           -S )r  )r   )r@   r   s     r   	<setcomp>z2_reap_orphaned_browser_sessions.<locals>.<setcomp>P  sF     
 
 
xx''
HH^$$
 
 
r   agent-browser-r  TF.pidignore_errorsz2Reaped orphaned browser daemon PID %d (session %s)r!   z:Reaped %d orphaned browser session(s) from previous run(s))globr  r'   r(   r-   r   r   valuesbasenameremoveprefixisfiler\   r   	read_textri   killProcessLookupErrorPermissionErrorr   r/   shutilrmtreesignalSIGTERMr^   r   )r*  tmpdirpatternsocket_dirstracked_namesreapedr  dir_namer  owner_pid_fileowner_alive	owner_pidpid_file
daemon_pids                 r   r  r  *  s_   . KKK ""Fgll6#677G))G$$K499RW\\&2GHHIIIK499RW\\&2JKKLLLK  
 
 

 
(//11
 
 

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 F! E6 E6
7##J//,,-=>> 	 j\2M2M2MNN&*7>>.)) 	##^ 4 4 > > @ @ F F H HII	'GIq)))"&KK) ( ( ("'KKK& ' ' ' #'KKK' ( # # #"# $ },, 7<<
|,A,A,ABBw~~h'' 	M*D9999	T(^^5577==??@@JJG$ 	 	 	M*D9999H	
	GJ""""! 	 	 	M*D9999H 	 	 	H	
	GJ///KKL"L2 2 2aKFF"OW= 	 	 	D	 	j55555 ZPRXYYYYYZ Zs   ?$C//C36C3=A G6>GG6G2"G6$G2/G61G22G66HH8A J99'K$#K$(K>> L.!	L.-L.2A M33NNc                     	 t                       n2# t          $ r%} t                              d|            Y d} ~ nd} ~ ww xY wt          rz	 t                       n2# t          $ r%} t                              d|            Y d} ~ nd} ~ ww xY wt          d          D ]}t          s nt          j        d            t          xdS dS )a*  
    Background thread that periodically cleans up inactive browser sessions.
    
    Runs every 30 seconds and checks for sessions that haven't been used
    within the BROWSER_SESSION_INACTIVITY_TIMEOUT period.
    On first run, also reaps orphaned sessions from previous process lifetimes.
    zOrphan reap error: %sNzCleanup thread error: %srL   r!   )	r  r]   r^   r   _cleanup_runningr  ranger  sleep)rc   r   s     r   _browser_cleanup_thread_workerrF    s   3')))) 3 3 3.222222223  
	:.0000 	: 	: 	:NN5q99999999	: r 	 	A# JqMMMM  
 
 
 
 
s*    
A ;A A 
B	$BB	c                  4   t           5  t          t                                          sWdat	          j        t          dd          at                                           t          	                    dt                     ddd           dS # 1 swxY w Y   dS )z;Start the background cleanup thread if not already running.NTzbrowser-cleanup)targetdaemonnamez0Started inactivity cleanup thread (timeout: %ss))r   _cleanup_threadis_aliverC  	threadingThreadrF  startr^   r   r  r   r   r   _start_browser_cleanup_threadrP    s     
 	p 	p"/*B*B*D*D"#'.5&  O
 !!###KKJLnooo	p 	p 	p 	p 	p 	p 	p 	p 	p 	p 	p 	p 	p 	p 	p 	p 	p 	ps   A8BBBc                  R    da t          t                              d           dS dS )z#Stop the background cleanup thread.FNrW   rv   )rC  rK  r-   r   r   r   _stop_browser_cleanup_threadrR    s5     "Q''''' #"r   c                 |    t           5  t          j                    t          | <   ddd           dS # 1 swxY w Y   dS )z1Update the last activity timestamp for a session.N)r   r  r  r   s    r   _update_session_activityrT    s}    	 6 6*.)++w'6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6s   155browser_navigateu  Navigate to a URL in the browser. Initializes the session and loads the page. Must be called before other browser tools. For simple information retrieval, prefer web_search or web_extract (faster, cheaper). For plain-text endpoints — URLs ending in .md, .txt, .json, .yaml, .yml, .csv, .xml, raw.githubusercontent.com, or any documented API endpoint — prefer curl via the terminal tool or web_extract; the browser stack is overkill and much slower for these. Use browser tools when you need to interact with a page (click, fill forms, dynamic content). Returns a compact page snapshot with interactive elements and ref IDs — no need to call browser_snapshot separately after navigating.objectstringz4The URL to navigate to (e.g., 'https://example.com'))typedescription)rX  
propertiesrequired)rJ  rY  
parametersbrowser_snapshotu   Get a text-based snapshot of the current page's accessibility tree. Returns interactive elements with ref IDs (like @e1, @e2) for browser_click and browser_type. full=false (default): compact view with interactive elements. full=true: complete page content. Snapshots over 8000 chars are truncated or LLM-summarized. Requires browser_navigate first. Note: browser_navigate already returns a compact snapshot — use this to refresh after interactions that change the page, or with full=true for complete content.fullbooleanzpIf true, returns complete page content. If false (default), returns compact view with interactive elements only.)rX  rY  r   browser_clickzClick on an element identified by its ref ID from the snapshot (e.g., '@e5'). The ref IDs are shown in square brackets in the snapshot output. Requires browser_navigate and browser_snapshot to be called first.refz=The element reference from the snapshot (e.g., '@e5', '@e12')browser_typezType text into an input field identified by its ref ID. Clears the field first, then types the new text. Requires browser_navigate and browser_snapshot to be called first.z5The element reference from the snapshot (e.g., '@e3')zThe text to type into the field)ra  textrc  browser_scrollzScroll the page in a direction. Use this to reveal more content that may be below or above the current viewport. Requires browser_navigate to be called first.	directionupdownzDirection to scroll)rX  enumrY  browser_backzdNavigate back to the previous page in browser history. Requires browser_navigate to be called first.browser_presszPress a keyboard key. Useful for submitting forms (Enter), navigating (Tab), or keyboard shortcuts. Requires browser_navigate to be called first.keyz:Key to press (e.g., 'Enter', 'Tab', 'Escape', 'ArrowDown')browser_get_imageszGet a list of all images on the current page with their URLs and alt text. Useful for finding images to analyze with the vision tool. Requires browser_navigate to be called first.browser_visiona  Take a screenshot of the current page and analyze it with vision AI. Use this when you need to visually understand what's on the page - especially useful for CAPTCHAs, visual verification challenges, complex layouts, or when the text snapshot doesn't capture important visual information. Returns both the AI analysis and a screenshot_path that you can share with the user by including MEDIA:<screenshot_path> in your response. Requires browser_navigate to be called first.zYWhat you want to know about the page visually. Be specific about what you're looking for.zIf true, overlay numbered [N] labels on interactive elements. Each [N] maps to ref @eN for subsequent browser commands. Useful for QA and spatial reasoning about page layout.)rX  r   rY  )questionannotatern  browser_consoleu  Get browser console output and JavaScript errors from the current page. Returns console.log/warn/error/info messages and uncaught JS exceptions. Use this to detect silent JavaScript errors, failed API calls, and application warnings. Requires browser_navigate to be called first. When 'expression' is provided, evaluates JavaScript in the page context and returns the result — use this for DOM inspection, reading page state, or extracting data programmatically.z0If true, clear the message buffers after readingu   JavaScript expression to evaluate in the page context. Runs in the browser like DevTools console — full access to DOM, window, document. Return values are serialized to JSON. Example: 'document.title' or 'document.querySelectorAll("a").length')r  
expressionc                     dd l }d|                                j        d d          }t                              d||            |d d ddidS )Nr   h_ru   z,Created local browser session %s for task %sr   Tr  bb_session_idrn   featuresuuiduuid4hexr^   r   )r   rx  r  s      r   _create_local_sessionr{  }  sk    KKK/

("-//L
KK>g' ' ' %dO	  r   c                     ddl }d|                                j        dd          }t                              d|||            |d|ddidS )	z?Create a session that connects to a user-supplied CDP endpoint.r   Ncdp_ru   u1   Created CDP browser session %s → %s for task %scdp_overrideTrt  rw  )r   rn   rx  r  s       r   _create_cdp_sessionr    sn    KKK1$**,,*3B3/11L
KKCgw0 0 0 %#T*	  r   c                    | d} t                       t          |            t          5  | t          v rt          |          cddd           S 	 ddd           n# 1 swxY w Y   t	          |           }t                      }|r|st          | |          }n|rt          |           }nqt                      }|t          |           }nP	 |	                    |           }|rt          |t                    st          d|          |                    d          r4t          |          }t          t          |d                             |d<   n# t           $ r}t#          |          j        }t&                              d||| d           	 t          |           }n,# t           $ r}t+          d| d	| d
| d          |d}~ww xY wt          |t                    r+t          |          }d|d<   t          |          |d<   ||d<   Y d}~nd}~ww xY wt          5  | t          v rt          |          cddd           S |t          | <   ddd           n# 1 swxY w Y   |st-          |            |S )u  
    Get or create session info for the given session key.

    In cloud mode, creates a Browserbase session with proxies enabled.
    In local mode, generates a session name for agent-browser --session.
    Also starts the inactivity cleanup thread and updates activity tracking.
    Thread-safe: multiple subagents can call this concurrently.

    Args:
        task_id: Session key.  Normally the task_id as-is, but may carry the
            ``::local`` suffix for the hybrid-routing local sidecar — in that
            case the cloud provider is skipped even when one is configured,
            and a local Chromium session is created instead.

    Returns:
        Dict with session_name (always), bb_session_id + cdp_url (cloud only)
    Nr   z)Cloud provider returned invalid session: rn   zPCloud provider %s failed (%s); attempting fallback to local Chromium for task %sTexc_infozCloud provider z	 failed (z") and local fallback also failed ()fallback_from_cloudfallback_reasonr   )rP  rT  r   r   r   r   r  r{  r   create_sessionr   r   r   r   r   r6   r]   rX  __name__r^   r   RuntimeErrorr   )r   force_localr~  r   providerrc   provider_namelocal_errors           r   _get_session_infor    s   $  "### W%%%	 - -&&&#G,- - - - - - - -&- - - - - - - - - - - - - - - (00K %&&L 'FK 'F*7LAA	 %F,W55&((099LLF'66w??# c:lD+I+I c$%aQ]%a%abbb##I.. b $(#5#5L.CCU^H_D`D`.a.aL+ F F F $X 7+!1g!	    #8#A#ALL    &@- @ @! @ @1<@ @ @   lD11 F#'#5#5L:>L!6769!ffL!238EL!45)F, 
 1 1 &&&#G,1 1 1 1 1 1 1 1 %1!1 1 1 1 1 1 1 1 1 1 1 1 1 1 1  (w'''sg   AA ABE 
H3HF"!H"
G,GGAHH"I
II"Ic                  b   t           r-t          t          dt                       d          t          S t	          j        d          } | r| ada | S t          d          }|rt	          j        d|          } | r| ada | S t          t                    j	        j	        }|dz  d	z  dz  }|
                                rt          |          ada t          S t	          j        d
          }|s|rt	          j        d
|          }|rdada t          S da t          dt                       d          )a@  
    Find the agent-browser CLI executable.
    
    Checks in order: current PATH, Homebrew/common bin dirs, Hermes-managed
    node, local node_modules/.bin/, npx fallback.
    
    Returns:
        Path to agent-browser executable
        
    Raises:
        FileNotFoundError: If agent-browser is not installed
    Nz7agent-browser CLI not found (cached). Install it with: zc
Or run 'npm install' in the repo root to install locally.
Or ensure npx is available in your PATH.agent-browserTr<   )r(   node_modulesz.binnpxr   z.agent-browser CLI not found. Install it with: )_agent_browser_resolvedr   FileNotFoundErrorr   r3  whichrJ   r   __file__parentexistsr6   )which_resultextended_path	repo_root	local_binnpx_paths        r   _find_agent_browserr    s     % (#;(**; ; ;   %$ <00L  ,"& (++M  |O-HHH 	 $0!&*# X%,IN*V3oEI % #I"&$$ |E""H ; ;<M::: % 3"&$$ #
	3 ""	3 	3 	3  r   c                     | sdS g d}|D ]Y}t          j        ||           }|r@|                    d                                                              d          }|r|c S ZdS )zHExtract a screenshot file path from agent-browser human-readable output.N)z6Screenshot saved to ['\"](?P<path>/[^'\"]+?\.png)['\"]z0Screenshot saved to (?P<path>/\S+?\.png)(?:\s|$)z(?P<path>/\S+?\.png)(?:\s|$)r(   z'")researchgroupri   )rc  patternsr8  matchr(   s        r   "_extract_screenshot_path_from_textr  D  s     t  H   	'4(( 	;;v&&,,..44U;;D 4r   commandargsrw   c           	      N   |t                      }|pg }	 t                      }nD# t          $ r7}t                              d|           dt          |          dcY d}~S d}~ww xY wt          |          r.t                      }t                              d|           d|dS t                      rAt                      s3t                      rd}nd}t                              d|           d|dS d	d
lm}  |            rdddS 	 t          |           }	nH# t          $ r;}t                              d| |           ddt          |           dcY d}~S d}~ww xY w|	                    d          rd|	d         g}
n
d|	d         g}
|dk    rddgn|g}||
z   d|gz   |z   }	 t           j                            t'                      d|	d                    }t!          j        |dd           t+          ||	d                    t                              d|| |t/          |                     i t           j        }t3          |                    dd                    |d<   ||d<   d|vrt          t4          dz            }||d<   t           j                            |d |           }t           j                            |d!|           }t!          j        |t           j        t           j        z  t           j        z  d"          }t!          j        |t           j        t           j        z  t           j        z  d"          }	 t?          j         |||t>          j!        |#          }t!          j"        |           t!          j"        |           n-# t!          j"        |           t!          j"        |           w xY w	 |#                    |$           nd# t>          j$        $ rR |%                                 |#                                 t                              d%||| |           dd&| d'dcY S w xY wt7          |d(          5 }|&                                }ddd           n# 1 swxY w Y   t7          |d(          5 }|&                                }ddd           n# 1 swxY w Y   |j'        }||fD ]'}	 t!          j(        |           # tR          $ r Y $w xY w|ri|*                                rU|d	k    rtV          j,        ntV          j-        }t          .                    |d)||*                                dd*                    |*                                }|s3|d	k    r-|t^          vr$t                              d+|           dd,| d-dS |rp	 ta          j1        |          }|d.k    rp|                    d/          r[|                    d0i           }|                    d.          s0|                    d1          st                              d2|           |S # t`          j2        $ r |dd3         }t                              d4|||dd*                    |d5k    r|pd*                                }d6                    d7 ||fD                       } tg          |           }!|!rFti          |!          5                                r%t          6                    d8|!           d|!|d9d:cY S dd;| d<| dcY S w xY w|d	k    rE|r|*                                nd=| }"t                              d>|||"dd?                    d|"dS di d:S # t          $ r:}t                              d@||dA           dt          |          dcY d}~S d}~ww xY w)Ba  
    Run an agent-browser CLI command using our pre-created Browserbase session.
    
    Args:
        task_id: Task identifier to get the right session
        command: The command to run (e.g., "open", "click")
        args: Additional arguments for the command
        timeout: Command timeout in seconds.  ``None`` reads
                 ``browser.command_timeout`` from config (default 30s).
        
    Returns:
        Parsed JSON response from agent-browser
    Nzagent-browser CLI not found: %sFsuccessr  z%browser command blocked on Termux: %su   Chromium browser is missing. You're running in Docker — pull the latest image to get the bundled Chromium: docker pull ghcr.io/nousresearch/hermes-agent:latestzChromium browser is missing. Install it with: npx agent-browser install --with-deps (or: npx playwright install --with-deps chromium)zbrowser command blocked: %sr   )is_interruptedInterruptedz0Failed to create browser session for task=%s: %sz"Failed to create browser session: rn   z--cdpz	--sessionr  r   r  r  z--jsonr&  i  T)modeexist_okz/browser cmd=%s task=%s socket_dir=%s (%d chars)PATHr<   AGENT_BROWSER_SOCKET_DIRAGENT_BROWSER_IDLE_TIMEOUT_MSi  _stdout__stderr_i  )stdoutstderrstdinenvrv   z9browser '%s' timed out after %ds (task=%s, socket_dir=%s)zCommand timed out after z secondsrzbrowser '%s' stderr: %s  z)browser '%s' returned empty output (rc=0)zBrowser command 'z' returned no outputsnapshotr  datarefsz]snapshot returned empty content. Possible stale daemon or CDP connection issue. returncode=%s  z1browser '%s' returned non-JSON output (rc=%s): %s
screenshot
c              3      K   | ]}||V  	d S r   r   )r@   rI   s     r   	<genexpr>z'_run_browser_command.<locals>.<genexpr>  s;       . .!%t.. . . . . .r   z<browser 'screenshot' recovered file from non-JSON output: %s)r(   r   )r  r  z(Non-JSON output from agent-browser for 'z': zCommand failed with code zbrowser '%s' failed (rc=%s): %si,  zbrowser '%s' exception: %sr  )7rd   r  r  r^   r   r6   r   r   r   _chromium_installed_running_in_dockertools.interruptr  r  r]   r   r'   r(   r-   r  makedirsr"  r_   r	  r   rJ   r  r  O_WRONLYO_CREATO_TRUNC
subprocessPopenDEVNULLrN   waitTimeoutExpiredr0  read
returncodeunlinkr/   ri   loggingWARNINGDEBUGlogrP   r   loadsJSONDecodeErrorr  r   r  r   )#r   r  r  rw   r   rc   r  hintr  r   backend_args
cmd_prefix	cmd_partstask_socket_dirbrowser_envidle_msstdout_pathstderr_path	stdout_fd	stderr_fdprocr!  r  r  r  rA   levelstdout_textr   	snap_datar   stderr_textcombined_textrecovered_path	error_msgs#                                      r   _run_browser_commandr  Y  s	   & &((:2D3)++ 3 3 38!<<< 3q66222222223 -[99 2-//>FFF 5111  1 3 5 5 1 	G DD 
 	4d;;; 4000......~ : =999Z(11 Z Z ZI7TUVVV +XPSTUPVPV+X+XYYYYYYYYZ 	"" C  i!89 $\.%AB .9<O-O-O%))VaUbJ\)-  	I
K3 ',,!!;\.9;;
 
 	O%$???? 	,~*FGGGFgO8L8L	N 	N 	N %n 2+//&"2M2MNNF2A./ ++==<tCDDG;BK78 gll?4Hw4H4HIIgll?4Hw4H4HIIGKrz)ABJ)NPUVV	GKrz)ABJ)NPUVV	
	 #   (  D HYHY HYHY	]IIgI&&&&( 	] 	] 	]IIKKKIIKKKNNV"GWoG G G$/['/[/[/[\\\\\	] +s## 	qVVXXF	 	 	 	 	 	 	 	 	 	 	 	 	 	 	+s## 	qVVXXF	 	 	 	 	 	 	 	 	 	 	 	 	 	 	_
 {+ 	 	A	!     	Xfllnn 	X'1QGOOGMEJJu7&,,..QURUQUBVWWWllnn
  	bzQ7BT3T3TNNFPPP$/`7/`/`/`aaa '	&K00j((VZZ	-B-B( &

62 6 6I$==44 DY]]6=R=R D (78BD D D '   !%4%(R&
CI? ? ? l**#)<R"6"6"8"8K$(II . .*5{)C. . . % %M &H%V%VN% $~*>*>*E*E*G*G Z*  
 (,(6'*% %        %YYYTWYY    3> ??*0^6^R\6^6^INN<gzS\]a^a]aSbccc$y999,,, 3 3 33Wa$OOO 3q66222222223s:  % 
A&,A!A&!A&D 
E %0EE E (F ]  	#N ,)]  *N??]  O ]  AP;8]  :P;;]  Q/#]  /Q33]  6Q37]  
R+]  +R//]  2R/3]  S]  
S&#]  %S&&B6]  ]  !BX- -C\=]  ?\
]  \A]  ]   
^$*/^^$^$snapshot_text	user_taskc                 j   |r
d| d|  d}nd|  d}ddl m}  ||          }	 dd	|d
gddd}t                      }|r||d<   t          di |}|j        d         j        j        pd                                pt          |           } ||          S # t          $ r t          |           cY S w xY w)zUse LLM to extract relevant content from a snapshot based on the user's task.

    Falls back to simple truncation when no auxiliary text model is configured.
    zQYou are a content extractor for a browser automation agent.

The user's task is: a  

Given the following page snapshot (accessibility tree representation), extract and summarize the most relevant information for completing this task. Focus on:
1. Interactive elements (buttons, links, inputs) that might be needed
2. Text content relevant to the task (prices, descriptions, headings, important info)
3. Navigation structure if relevant

Keep ref IDs (like [ref=e5]) for interactive elements so the agent can use them.

Page Snapshot:
zW

Provide a concise summary that preserves actionable information and relevant content.zSummarize this page snapshot, preserving:
1. All interactive elements with their ref IDs (like [ref=e5])
2. Key text content and headings
3. Important information visible on the page

Page Snapshot:
zL

Provide a concise summary focused on interactive elements and key content.r   redact_sensitive_textweb_extractuserrolecontenti  皙?)taskmessages
max_tokenstemperaturemodelr<   Nr   )
agent.redactr  rm   r	   choicesmessager  ri   _truncate_snapshotr]   )r  r  extraction_promptr  call_kwargsr  r   	extracteds           r   _extract_relevant_contentr  =  sV     
	e#,	e 	e  -	e 	e 	e 	Z  -	Z Z Z 	 322222--.?@@1!"(5FGGH	
 
 &'' 	)#(K **k**%a(08>BEEGGlK]^kKlKl	$$Y/// 1 1 1!-000001s   A0B B21B2	max_charsc                    t          |           |k    r| S |                     d          }g }d}|D ]J}|t          |          z   dz   |dz
  k    r n+|                    |           |t          |          dz   z  }Kt          |          t          |          z
  }|dk    r|                    d| d           d                    |          S )a{  Structure-aware truncation for snapshots.

    Cuts at line boundaries so that accessibility tree elements are never
    split mid-line, and appends a note telling the agent how much was
    omitted.

    Args:
        snapshot_text: The snapshot text to truncate
        max_chars: Maximum characters to keep

    Returns:
        Truncated text with indicator if truncated
    r  r   r!   P   z
[... z= more lines truncated, use browser_snapshot for full content])r	  rC   r.   r-   )r  r  linesr`   charsline	remainings          r   r  r  u  s     =Y&&%%EFE  3t99q 9r>11EdTQE

S[[(I1}}h	hhhiii99Vr   c           	         ddl }ddlm} |j                            |           }|                    |           s|                    |          rt          j        ddd          S |pd}t          ||           }t          |          }t                      s6|s4t                      s&t          |           st          j        ddd          S t          |           }|r4t          j        d|d	         |d
         |d         |d         dd          S t                      rddlm}	  |	| |          S |rJt"                              d| t'                      r t)          t'                                j        nd           t-          |          }
|
                    dd          }|rd|
d<   t1          |           t3          |d| gt5          t7                      d                    }|t8          |<   |                    d          r$|                    di           }|                    dd          }|                    d|           }t                      sR|sPt                      sB|r@|| k    r:t          |          s+t3          |ddgd           t          j        ddd          S d||d}g d }|                                t=          fd!|D                       r	d"| d#|d$<   |rId%|
v rE|
d%         }d& |                                D             }|                    d'          sd(|d)<   ||d*<   	 t3          |d+d,g          }|                    d          r|                    di           }|                    d+d          }|                    d-i           }tA          |          tB          k    rtE          |          }||d+<   |rtA          |          nd|d.<   n2# tF          $ r%}t"          $                    d/|           Y d}~nd}~ww xY wt          j        |d0          S t          j        d|                    d1d2          dd0          S )3a  
    Navigate to a URL in the browser.
    
    Args:
        url: The URL to navigate to
        task_id: Task identifier for session isolation
        
    Returns:
        JSON string with navigation result (includes stealth features info on first nav)
    r   N)
_PREFIX_REFz_Blocked: URL contains what appears to be an API key or token. Secrets must not be sent in URLs.r  r   z2Blocked: URL targets a private or internal addressr  hostrulesource)r  r  r  )r  r  blocked_by_policy)camofox_navigatezbrowser_navigate: auto-routing %s to local Chromium sidecar (cloud provider %s stays on cloud for public URLs; set browser.auto_local_for_private_urls: false to disable)none
_first_navTr  <   rv   r  r  titler<   r   zabout:blankru   z6Blocked: redirect landed on a private/internal address)r  r   r  )zaccess deniedz#access to this page has been deniedblockedzbot detectedzverification requiredzplease verifyzare you a robotcaptcha
cloudflarezddos protectionzchecking your browserzjust a momentzattention requiredc              3       K   | ]}|v V  	d S r   r   )r@   r8  title_lowers     r   r  z#browser_navigate.<locals>.<genexpr>  s(      FF'w+%FFFFFFr   zPage title 'a0  ' suggests bot detection. The site may have blocked this request. Options: 1) Try adding delays between actions, 2) Access different pages first, 3) Enable advanced stealth (BROWSERBASE_ADVANCED_STEALTH=true, requires Scale plan), 4) Some sites have very aggressive bot detection that may be unavoidable.bot_detection_warningrv  c                     g | ]	\  }}||
S r   r   )r@   kvs      r   rB   z$browser_navigate.<locals>.<listcomp>#  s!    CCCTQCqCCCr   proxieszRunning WITHOUT residential proxies. Bot detection may be more aggressive. Consider upgrading Browserbase plan for proxy support.stealth_warningstealth_featuresr  -cr  element_countz'Auto-snapshot after navigate failed: %sensure_asciir  zNavigation failed)%r   r  r
  parseunquoter  r   dumpsr   r   r   r   _is_safe_urlr   r   tools.browser_camofoxr  r^   r   r   rX  r  r  r   _maybe_start_recordingr  r[   rd   r   ry   anyr  r	  SNAPSHOT_SUMMARIZE_THRESHOLDr  r]   r_   )r   r   urllibr
  url_decodedeffective_task_idnav_session_keyauto_local_this_navr  r  r   is_first_navr`   r  r  	final_urlr   blocked_patternsrv  active_featuressnap_resultr  r  r  rc   r  s                            @r   rU  rU    s    '''''',&&s++K !2!2;!?!? z9
 
   	  ,9-.?EEO/@@ 	#	 $%%	 S!!		 zI
 
   	 #3''G zY'*1&/76?^efn^o!p!p
 
   	  .::::::W--- 
I 4G4I4IUD$&&''00v	
 	
 	
 %_55L##L$77L  0%*\"///!/6C5#NbNdNdfhJiJijjjF
 3B./zz) Ozz&"%%"%%HHUC((	 "##	'	 ())	 		 (3..|I7N7N. !&=/SUVVVV: Q     
 

 
 
 kkmmFFFF5EFFFFF 	\u \ \ \ ,-  	;J,66#J/HCCX^^-=-=CCCO<<	** M *+ ,;H'(	G.
TFSSKy)) E'OOFB77	 )j" = = }}VR00}%%(DDD$6}$E$EM'4$9=,DCIII1) 	G 	G 	GLLBAFFFFFFFF	G z(7777zZZ)<==
 
    	s   8B+O$ $
P.PPc                    t                      rddlm}  || ||          S t          |pd          }g }| s|                    dg           t          |d|          }|                    d          rT|                    di           }|                    dd          }|                    d	i           }	t          |          t          k    r|rt          ||          }n't          |          t          k    rt          |          }d
||	rt          |	          ndd}
	 ddlm} |                    |          }|B|                                }|j        r'|
                    |                                           n2# t"          $ r%}t$                              d|           Y d}~nd}~ww xY wt)          j        |
d          S t)          j        d|                    dd          dd          S )ag  
    Get a text-based snapshot of the current page's accessibility tree.
    
    Args:
        full: If True, return complete snapshot. If False, return compact view.
        task_id: Task identifier for session isolation
        user_task: The user's current task (for task-aware extraction)
        
    Returns:
        JSON string with page snapshot
    r   )camofox_snapshotr   r   r  r  r  r<   r  T)r  r  r!  r   Nz$supervisor snapshot merge failed: %sFr"  r  zFailed to get snapshotr  )r   r(  r7  r   extendr  r   r	  r+  r  r  r   r   r  activeupdateto_dictr]   r^   r_   r   r&  )r^  r   r  r7  r.  r  r`   r  r  r  r   r   _supervisor_sv_snap_sv_excs                  r   r]  r]  B  sH      :::::::gy999)'*>Y?? D TF!"3ZFFFzz) #zz&"%%R00xx## } <<<<5mYOOMM">>>.}==M %*.5SYYYA
 
	JDDDDDD-112CDDK&&//11? 8OOH$4$4$6$6777 	J 	J 	JLL?IIIIIIII	J z(7777zZZ)ABB
 
    	s    AF   
F/
F**F/c                 x   t                      rddlm}  || |          S t          |pd          }|                     d          sd|  } t          |d| g          }|                    d          rt          j        d| dd	
          S t          j        d	|                    dd|            dd	
          S )z
    Click on an element.
    
    Args:
        ref: Element reference (e.g., "@e5")
        task_id: Task identifier for session isolation
        
    Returns:
        JSON string with click result
    r   )camofox_clickr   @clickr  T)r  clickedFr"  r  zFailed to click r  )	r   r(  r@  r   r,   r  r   r   r&  )ra  r   r@  r.  r`   s        r   r`  r`    s     +777777}S'***)'*>Y?? >># #ii!"3WseDDFzz) 	z
 
    	
 zZZ)AC)A)ABB
 
    	r   c                 ~   t                      rddlm}  || ||          S t          |pd          }|                     d          sd|  } t          |d| |g          }|                    d          rt          j        d|| dd	
          S t          j        d	|                    dd|            dd	
          S )z
    Type text into an input field.
    
    Args:
        ref: Element reference (e.g., "@e3")
        text: Text to type
        task_id: Task identifier for session isolation
        
    Returns:
        JSON string with type result
    r   )camofox_typer   rA  fillr  T)r  typedelementFr"  r  zFailed to type into r  )	r   r(  rE  r   r,   r  r   r   r&  )ra  rc  r   rE  r.  r`   s         r   rb  rb    s     0666666|Cw///)'*>Y?? >># #ii ""3Vc4[IIFzz) 
z
 
 	   	 zZZ)E)E)EFF
 
    	r   c                    | dvrt          j        dd|  ddd          S d}t                      r*dd	lm} d
}d}t          |          D ]} || |          }|S t          |pd          }t          |d| t          |          g          }|	                    d          s0t          j        d|	                    dd|            dd          S t          j        d| dd          S )z
    Scroll the page.
    
    Args:
        direction: "up" or "down"
        task_id: Task identifier for session isolation
        
    Returns:
        JSON string with scroll result
    )rf  rg  FzInvalid direction 'z'. Use 'up' or 'down'.r  r"  r  r   )camofox_scrollrW   Nr   scrollr  r  zFailed to scroll T)r  scrolled)
r   r&  r   r(  rJ  rD  r   r  r6   r   )re  r   _SCROLL_PIXELSrJ  _SCROLL_REPEATSr`   r   r.  s           r   rd  rd    sc    &&zL9LLL
 
    	 N 888888'' 	8 	8A#^Iw77FF)'*>Y??!"3X	3~K^K^?_``F::i   zZZ)HY)H)HII
 
    	
 :     r   c                    t                      rddlm}  ||           S t          | pd          }t	          |dg           }|                    d          rC|                    di           }t          j        d|                    dd	          d
d          S t          j        d|                    dd          dd          S )z
    Navigate back in browser history.
    
    Args:
        task_id: Task identifier for session isolation
        
    Returns:
        JSON string with navigation result
    r   )camofox_backr   backr  r  Tr   r<   )r  r   Fr"  r  zFailed to go backr  )r   r(  rP  r   r  r   r   r&  )r   rP  r.  r`   r  s        r   ri  ri    s      %666666|G$$$)'*>Y??!"3VR@@Fzz) 
zz&"%%z88E2&&
 
    	
 zZZ)<==
 
    	r   c                 D   t                      rddlm}  || |          S t          |pd          }t	          |d| g          }|                    d          rt          j        d| dd	          S t          j        d|                    d
d|            dd	          S )z
    Press a keyboard key.
    
    Args:
        key: Key to press (e.g., "Enter", "Tab")
        task_id: Task identifier for session isolation
        
    Returns:
        JSON string with key press result
    r   )camofox_pressr   pressr  T)r  pressedFr"  r  zFailed to press r  )r   r(  rS  r   r  r   r   r&  )rk  r   rS  r.  r`   s        r   rj  rj    s      +777777}S'***)'*>Y??!"3WseDDFzz) 	z
 
    	
 zZZ)AC)A)ABB
 
    	r   r  rq  c           	      >   |t          ||          S t                      rddlm}  || |          S t	          |pd          }| rdgng }| rdgng }t          |d|          }t          |d|          }g }	|                    d          rn|                    d	i                               d
g           D ]C}
|	                    |
                    dd          |
                    dd          dd           Dg }|                    d          rY|                    d	i                               dg           D ].}|                    |                    dd          dd           /t          j	        d|	|t          |	          t          |          dd          S )aa  Get browser console messages and JavaScript errors, or evaluate JS in the page.
    
    When ``expression`` is provided, evaluates JavaScript in the page context
    (like the DevTools console) and returns the result.  Otherwise returns
    console output (log/warn/error/info) and uncaught exceptions.
    
    Args:
        clear: If True, clear the message/error buffers after reading
        expression: JavaScript expression to evaluate in the page context
        task_id: Task identifier for session isolation
        
    Returns:
        JSON string with console messages/errors, or eval result
    Nr   )camofox_consoler   z--clearconsoleerrorsr  r  r  rX  r  rc  r<   )rX  rc  r  r  	exception)r  r  T)r  console_messages	js_errorstotal_messagestotal_errorsFr"  )_browser_evalr   r(  rW  r   r  r   r.   r   r&  r	  )r  rq  r   rW  r.  console_args
error_argsconsole_resulterrors_resultr  msgrY  errs                r   rp  rp  <  s     Z111  /999999ug...)'*>Y??"'/I;;RL %-)2J)*;YUUN():HjQQMH)$$ !%%fb1155j"EE 	 	COO..++#      F##  $$VR0044XrBB 	 	CMM779b11%     
 :$h--F     r   c                    t                      rt          | |          S t          |pd          }t          |d| g          }|                    d          sb|                    dd          t          fddD                       rt          j        dd	 d
          S t          j        dd
          S |                    di           }|                    d          }|}t          |t                    r2	 t          j
        |          }n# t          j        t          f$ r Y nw xY wt          j        d|t          |          j        ddt                    S )zKEvaluate a JavaScript expression in the page context and return the result.r   evalr  r  zeval failedc              3   D   K   | ]}|                                 v V  d S r   )ry   )r@   r  re  s     r   r  z _browser_eval.<locals>.<genexpr>  s0      ttttsyy{{"ttttttr   )zunknown commandznot supportedz	not foundzno such commandFz@JavaScript evaluation is not supported by this browser backend. r  r  r`   Tr  r`   result_typer#  r   )r   _camofox_evalr   r  r   r*  r   r&  r   r6   r  r  r   rX  r  )rq  r   r.  r`   r  
raw_resultr   re  s          @r   r_  r_  v  s    2Z111)'*>Y??!"3Vj\JJF::i   jj-00tttt/sttttt 	: a\_aa     z
 
   	
 ::fb!!D(##J F*c"" 	Z
++FF$j1 	 	 	D	 :F||,  3	( ( ( (s   =D D+*D+c                   
 ddl m}m} 	  ||pd          }|                    d          p|                    d          } |d| d| |d         d	
          }t	          |t
                    r|                    d          n|}|}t	          |t                    r2	 t          j        |          }n# t          j	        t          f$ r Y nw xY wt          j        d|t          |          j        ddt                    S # t          $ ra}	t          |	          
t          
fddD                       rt          j        ddd          cY d}	~	S t!          
d          cY d}	~	S d}	~	ww xY w)zFEvaluate JS via Camofox's /tabs/{tab_id}/eval endpoint (if available).r   )_ensure_tab_postr   tab_ididz/tabs/z	/evaluateuser_id)rq  userId)bodyr`   Tri  Frk  c              3       K   | ]}|v V  	d S r   r   )r@   coder  s     r   r  z _camofox_eval.<locals>.<genexpr>  s(      CCTty CCCCCCr   )404405501z|JavaScript evaluation is not supported by this Camofox server. Use browser_snapshot or browser_vision to inspect page state.r  N)r  )r(  ro  rp  r   r   r   r6   r   r  r  r   r&  rX  r  r]   r*  
tool_error)rq  r   ro  rp  tab_inforq  resprm  r   rc   r  s             @r   rl  rl    s   888888884;w3)44h''=8<<+=+=u/f///Z[cdm[n6o6oppp ,6dD+A+AKTXXh'''t
j#&& 	J//(*5    z<<0
 
 s	, , , 	,
  	4 	4 	4FF	CCCC-BCCCCC 	: Y         
 )U333333333	4sO   BD  B5 4D 5CD C4D 
E.A E)E.E)#E.)E.c                 b   t           5  | t          v r	 ddd           dS 	 ddd           n# 1 swxY w Y   	 ddlm} t	                      } |            }t          |ddd          }|sdS |dz  }|                    d	d	
           t          d           t          j	        d          }|d| d| dd          dz  }t          | ddt          |          g          }|                    d          rWt           5  t                              |            ddd           n# 1 swxY w Y   t                              d| |           dS t                              d|                    d                     dS # t"          $ r&}	t                              d|	           Y d}	~	dS d}	~	ww xY w)z@Start recording if browser.record_sessions is enabled in config.Nr   rS   rU   record_sessionsFr   browser_recordingsTparentsr  H   max_age_hoursz%Y%m%d_%H%M%Ssession_r      z.webmrO   rO  r  z'Auto-recording browser session %s to %sz"Could not start auto-recording: %sr  zAuto-recording setup failed: %s)r   r  rZ   rT   r
   r   mkdir_cleanup_old_recordingsr  strftimer  r6   r   addr^   r   r_   r]   )
r   rT   r9   ra   record_enabledrecordings_dir	timestamprecording_pathr`   rc   s
             r   r)  r)    s   	  )))       )              ;555555%''o i1BERRR 	F$';;TD999b1111M/22	'*TY*T*T"*T*T*TT%gx'3~CVCV9WXX::i   	T 1 1#''0001 1 1 1 1 1 1 1 1 1 1 1 1 1 1KKA7N[[[[[LL=vzz'?R?RSSSSS ; ; ;6:::::::::;sT   -113E> .BE> >D%E> %D))E> ,D)-E> .E> >
F.F))F.c                    t           5  | t          vr	 ddd           dS 	 ddd           n# 1 swxY w Y   	 t          | ddg          }|                    d          rF|                    di                               dd          }t                              d| |           n3# t          $ r&}t                              d	| |           Y d}~nd}~ww xY wt           5  t                              |            ddd           dS # 1 swxY w Y   dS # t           5  t                              |            ddd           w # 1 swxY w Y   w xY w)
z1Stop recording if one is active for this session.NrO   r   r  r  r(   r<   z*Saved browser recording for session %s: %sz#Could not stop recording for %s: %s)	r   r  r  r   r^   r   r]   r_   discard)r   r`   r(   rc   s       r   _maybe_stop_recordingr    s?   	  ---       -              	1%gx&BB::i   	U::fb))--fb99DKKDgtTTT H H H:GQGGGGGGGGH  	1 	1''000	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1] 	1 	1''000	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1sr   -11A-B' &D '
C1CD CD !D		DDEE9EE		EE	Ec                 n   t                      rddlm}  ||           S t          | pd          }d}t	          |d|g          }|                    d          r|                    di           }|                    dd	          }	 t          |t                    rt          j	        |          }n|}t          j
        d
|t          |          dd          S # t          j        $ r t          j
        d
g dddd          cY S w xY wt          j
        d|                    dd          dd          S )z
    Get all images on the current page.
    
    Args:
        task_id: Task identifier for session isolation
        
    Returns:
        JSON string with list of images (src and alt)
    r   )camofox_get_imagesr   a  JSON.stringify(
        [...document.images].map(img => ({
            src: img.src,
            alt: img.alt || '',
            width: img.naturalWidth,
            height: img.naturalHeight
        })).filter(img => img.src && !img.src.startsWith('data:'))
    )rg  r  r  r`   z[]T)r  imagesrz   Fr"  zCould not parse image data)r  r  rz   r   r  zFailed to get imagesr  )r   r(  r  r   r  r   r   r6   r   r  r&  r	  r  )r   r  r.  js_coder`   r  rm  r  s           r   rl  rl    s     +<<<<<<!!'***)'*>Y??	G ""3VgYGGFzz) zz&"%%XXh--
	#*c** $J//#: V  "	# # # #
 # 	# 	# 	#:7	 
 "# # # # # #	# zZZ)?@@
 
    	s   AC *DDro  c                 
   t                      rddlm}  || ||          S ddl}ddl}t          |pd          }ddlm}  |dd          }|d|                                j	         d	z  }		 |
                    d
d
           t          |d           g }
|r|
                    d           |
                    d           |
                    t          |	                     t          |d|
          }|                    d          s_|                    dd          }t!                      }|dnd|                                 d}t%          j        dd| d| dd          S |                    di                               d          }|rt)          |          }	|	                                sJt!                      }|dnd|                                 d}t%          j        dd|	 d| d dd          S |	                                }|                    |                              d!          }d"| }d#|  d$}t3                      }t4                              d%t9          |                     d&}d'}	 dd(lm}  |            }t?          |d)d*i +          }|                    d,          }|tA          |          }|                    d-          }|tA          |          }n# tB          $ r Y nw xY wd*d.d/|d0d1d2|id3gd4gd5||d6}|r||d7<   	 tE          dKi |}n# tB          $ r}dd8l#m$}m%} m&}!  ||          rt9          |          |!k    rlt4          '                    d9t9          |          d:z  |!d:z              | |	d;<          }||d=         d         d>         d?         d1         d2<   tE          dKi |}n Y d}~nd}~ww xY w|j(        d         j)        j*        pd@+                                }"ddAl,m-}#  |#|"          }"d
|"pdBt          |	          dC}$|r:|                    di                               dD          r|d         dD         |$dD<   t%          j        |$d          S # tB          $ r}}%t4          .                    dE|%d
F           ddGt          |%           d}&|	                                rt          |	          |&dH<   dI|&dJ<   t%          j        |&d          cY d}%~%S d}%~%ww xY w)La6  
    Take a screenshot of the current page and analyze it with vision AI.
    
    This tool captures what's visually displayed in the browser and sends it
    to Gemini for analysis. Useful for understanding visual content that the
    text-based snapshot may not capture (CAPTCHAs, verification challenges,
    images, complex layouts, etc.).
    
    The screenshot is saved persistently and its file path is returned alongside
    the analysis, so it can be shared with users via MEDIA:<path> in the response.
    
    Args:
        question: What you want to know about the page visually
        annotate: If True, overlay numbered [N] labels on interactive elements
        task_id: Task identifier for session isolation
        
    Returns:
        JSON string with vision analysis results and screenshot_path
    r   )camofox_visionNr   )get_hermes_dirzcache/screenshotsbrowser_screenshotsbrowser_screenshot_z.pngTr     r  z
--annotatez--fullr  r  r  zUnknown errorr   cloud (r  FzFailed to take screenshot (z mode): r  r"  r  r(   z#Screenshot file was not created at z (z mode). This may indicate a socket path issue (macOS /var/folders/), a missing Chromium install ('agent-browser install'), or a stale daemon process.asciizdata:image/png;base64,zCYou are analyzing a screenshot of a web browser.

User's question: a"  

Provide a detailed and helpful answer based on what you see in the screenshot. If there are interactive elements, describe them. If there are verification challenges or CAPTCHAs, describe what type they are and what action might be needed. Focus on answering the user's specific question.z/browser_vision: analysing screenshot (%d bytes)g      ^@r  )load_config	auxiliaryvisionr   rw   r  r  rc  )rX  rc  	image_urlr   )rX  r  r  r  )r  r  r  r  rw   r  )_is_image_size_error_resize_image_for_vision_RESIZE_TARGET_BYTESzSVision API rejected screenshot (%.1f MB); auto-resizing to ~%.0f MB and retrying...i   z	image/png)	mime_typer  r  r!   r<   r  z$Vision analysis returned no content.)r  analysisscreenshot_pathannotationszbrowser_vision failed: %sr  zError during vision analysis: r  z\Screenshot was captured but vision analysis failed. You can still share it via MEDIA:<path>.noter   )/r   r(  r  base64rx  r   hermes_constantsr  ry  rz  r  _cleanup_old_screenshotsr.   r6   r  r   r   r  r   r&  r   r  
read_bytes	b64encodedecoderj   r^   r_   r	  rZ   r  r   r   r]   r	   tools.vision_toolsr  r  r  r   r  r  r  ri   r  r  r   )'rn  ro  r   r  r  uuid_modr.  r  screenshots_dirr  screenshot_argsr`   error_detail_cpr  actual_screenshot_path_screenshot_bytes_screenshot_b64data_urlvision_promptvision_modelvision_timeoutvision_temperaturer  _cfg_vision_cfg_vt_vtempr  r   _api_errr  r  r  r  r  response_datarc   
error_infos'                                          r   rm  rm  +	  s   (  ;888888~h':::MMM)'*>Y?? 0/////$n%8:OPPO%(Xhnn>N>N>R(X(X(XXOQ:dT::: 	!CCCC  	1""<000x(((s?33444%
 
 zz)$$ 	#!::g??L%''C!k77/O9J9J9L9L/O/O/OD: StSS\SS  "# # # #
 "(FB!7!7!;!;F!C!C! 	;"#9::O %%'' 	#%''C!k77/O9J9J9L9L/O/O/OD: 2/ 2 2T 2 2 2  "# # # # ,6688 **+<==DDWMM=O==@ (@ @ @ 	 )**F*++	- 	- 	-  	555555;==D!$XrJJJK//),,C!&s __]33F!%*6]]" 	 	 	D	  #!'??!,E8;LMM   -%
 
  	0#/K 	..+..HH 	 	 	          %$X.. H(<<<@MM[1(K8	   43#{< < <OWJ'*95a8EeL#22k22 	& $Q'/7=2DDFF666666((22 J$J"?33
 
  	I

62..22=AA 	I+1&>-+HM-(z-e<<<< 
: 
: 
:
 	2AEEE!&1ZRUVWRXRX1Z1Z[[
!!## 	@,/,@,@J()!Jvz*5999999999
:s   .C<Q? +BQ? BQ? A/K5 4Q? 5
L?Q? L Q? #L0 /Q? 0
O:BOQ? OB%Q? ?
T	A2T;TTr  c                 :   t          |           }t          j                    }|t                              |d          z
  dk     rdS |t          |<   	 t          j                    |dz  z
  }|                     d          D ]g}	 |                                j        |k     r|                                 5# t          $ r&}t          
                    d||           Y d}~`d}~ww xY wdS # t          $ r&}t          
                    d|           Y d}~dS d}~ww xY w)zRemove browser screenshots older than max_age_hours to prevent disk bloat.

    Throttled to run at most once per hour per directory to avoid repeated
    scans on screenshot-heavy workflows.
    g          Nzbrowser_screenshot_*.pngz%Failed to clean old screenshot %s: %sz+Screenshot cleanup error (non-critical): %s)r6   r  rK   r   r*  statst_mtimer  r]   r^   r_   )r  r  rk  nowcutoffr!  rc   s          r   r  r  	  sS    o

C
)++C
,00c:::TAA+.#C(	G 45 %%&@AA 	L 	LAL6688$v--HHJJJ L L LDaKKKKKKKKL		L 	L  G G GBAFFFFFFFFFGsB   0C* 1B54C* 5
C%?C C*  C%%C* *
D4DDr  c                    	 t                      }|dz  }|                                sdS t          j                    | dz  z
  }|                    d          D ]g}	 |                                j        |k     r|                                 5# t          $ r&}t          	                    d||           Y d}~`d}~ww xY wdS # t          $ r&}t          	                    d|           Y d}~dS d}~ww xY w)zIRemove browser recordings older than max_age_hours to prevent disk bloat.r  Nr  zsession_*.webmz$Failed to clean old recording %s: %sz*Recording cleanup error (non-critical): %s)
r
   r  r  r*  r  r  r  r]   r^   r_   )r  r9   r  r  r!  rc   s         r   r  r  	  s7   F%''$';;$$&& 	F 45$$%566 	K 	KAK6688$v--HHJJJ K K KCQJJJJJJJJK		K 	K  F F FA1EEEEEEEEEFsF   'C 0C 1BC 
B>B94C 9B>>C 
C3C..C3c                    | d} t          |           r!| g}| dt          t                              }nM| g}|  t           }t          5  |t          v r|                    |           ddd           n# 1 swxY w Y   | }|D ]}t          |           t          |           st                              |d           dS dS )a  
    Clean up browser session(s) for a task.

    Called automatically when a task completes or when inactivity timeout is reached.
    Closes both the agent-browser/Browserbase session and Camofox sessions.

    When ``task_id`` is a bare task identifier (no ``::local`` suffix), reaps
    BOTH the cloud/primary session AND any hybrid-routing local sidecar that
    may have been spawned for LAN/localhost URLs in the same task.  When
    ``task_id`` already carries a ``::local`` suffix (called from the inactivity
    cleanup loop against a specific session key), reaps only that one.

    Args:
        task_id: Task identifier (or explicit session key)
    Nr   )	r   r	  r   r   r   r.   _cleanup_single_browser_sessionr   pop)r   session_keysbare_task_idsidecar_keyr   s        r   r  r  
  s5      W%% 	y4#m"4"4!445y 1-11 	1 	1...##K000	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 # 5 5'4444 !)) 9 $$\4888889 9s   
A55A9<A9c                     t          |            t                      rS	 ddlm}m}  ||           s ||            n3# t
          $ r&}t                              d| |           Y d}~nd}~ww xY wt                              d|            t                              dt          t          
                                                     t          5  t                              |           }ddd           n# 1 swxY w Y   |r|                    dd          }t                              d	| |           t          |            	 t          | d
g d           t                              d|            n3# t
          $ r&}t                              d| |           Y d}~nd}~ww xY wt          5  t                              | d           t"                              | d           ddd           n# 1 swxY w Y   |rYt%                      }|I	 |                    |           n2# t
          $ r%}t                              d|           Y d}~nd}~ww xY w|                    dd          }|rbt(          j                            t/                      d|           }t(          j                            |          rt(          j                            || d          }	t(          j                            |	          r	 t5          t7          |	                                                                                    }
t)          j        |
t>          j                    t                              d|
|           n># tB          tD          tF          tH          f$ r t                              d|           Y nw xY wtK          j&        |d           t                              d|            dS t                              d|            dS )zAInternal: reap a single browser session by its exact session key.r   )camofox_closecamofox_soft_cleanupzCamofox cleanup for task %s: %sNz&cleanup_browser called for task_id: %szActive sessions: %sru  unknownz+Found session for task %s: bb_session_id=%srN   ru   rv   z1agent-browser close command completed for task %sz*agent-browser close failed for task %s: %sz)Could not close cloud browser session: %sr  r<   r&  r'  zKilled daemon pid %s for %sz?Could not kill daemon pid for %s (already dead or inaccessible)Tr(  z$Removed task %s from active sessionsz'No active session found for task_id: %s)'r   r   r(  r  r  r]   r^   r_   r7   r   keysr   r   r  r  r   r  r  r   close_sessionr'   r(   r-   r  r  r.  r\   r   r/  ri   r0  r5  r6  r1  r   r2  r/   r3  r4  )r   r  r  rc   r   ru  r  r  r  r@  rA  s              r   r  r  7
  s    !!!  H	HQQQQQQQQ''00 'g&&& 	H 	H 	HLL:GQGGGGGGGG	H LL97CCC
LL&-=-B-B-D-D(E(EFFF 
 5 5'++G445 5 5 5 5 5 5 5 5 5 5 5 5 5 5  /I$(()DDBG][[[ 	g&&&	U '2rBBBBLLLgVVVV 	U 	U 	UNNGRSTTTTTTTT	U  	6 	6  $///"&&w555	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6  	S*,,H#S**=9999  S S SNN#NPQRRRRRRRRS $'';; 	>&9&;&;=\l=\=\]]Jw~~j)) 
>7<<
|4I4I4IJJ7>>(++ vv%(h)A)A)C)C)I)I)K)K%L%L

FN;;;%BJP\]]]].
OWU v v v%fhtuuuuuvj====;WEEEEE>HHHHHs|   > 
A.A))A.C99C= C=	.E8 8
F(F##F(27G55G9<G9H) )
I3IIA;N 8N=<N=c                  V   t           5  t          t                                                    } ddd           n# 1 swxY w Y   | D ]}t	          |           	 ddlm} |                                 n# t          $ r Y nw xY wda	da
t                                           dadadadS )zX
    Clean up all active browser sessions.
    
    Useful for cleanup on shutdown.
    Nr   r   F)r   r7   r   r  r  r   r   stop_allr]   r   r  r4   cache_clearrQ   rX   _cached_chromium_installed)task_idsr   r   s      r   r
  r
  
  s    
 1 1(--//001 1 1 1 1 1 1 1 1 1 1 1 1 1 1 ! !    @@@@@@$$&&&&    !# ,,..." %!%s   ';??A6 6
BBr  c                     g } t           j                            dd                                          }|r|dk    r|                     |           t           j                            d          }|                     t           j                            |dd                     t          j	        dk    r5|                     t           j                            |dd	d                     t          j	        d
k    rst           j                            d          p t           j                            |dd          }|                     t           j                            |d                     | S )u  Directories to scan for a Chromium / headless-shell build.

    Order mirrors what agent-browser and Playwright actually probe:

    1. ``PLAYWRIGHT_BROWSERS_PATH`` when set (Docker image sets this to
       ``/opt/hermes/.playwright``).
    2. ``~/.cache/ms-playwright`` — Playwright's default on Linux/macOS.
    3. ``~/Library/Caches/ms-playwright`` — Playwright's default on macOS.
    4. ``%USERPROFILE%\AppData\Local\ms-playwright`` — Playwright's default
       on Windows.
    PLAYWRIGHT_BROWSERS_PATHr<   0~z.cachezms-playwrightr   LibraryCacheswin32LOCALAPPDATAAppDataLocal)
r'   r   r   ri   r.   r(   
expanduserr-   r   r   )rootsenv_pathhomer   s       r   _chromium_search_rootsr  
  s     Ez~~8"==CCEEH HOOX7c""D	LLdHo>>???
|xRW\\$	8_MMNNN
|w
~.. 
"',,)W3
 3
 	RW\\%99:::Lr   c                  B   t           t           S t                      D ]} | rt          j                            |           s$	 t          j        |           }n# t          $ r Y Fw xY w|D ]2}|                    d          s|                    d          rda   dS 3da dS )a  Return True when a usable Chromium (or headless-shell) build is on disk.

    agent-browser (0.26+) downloads Playwright's chromium / headless-shell
    builds into ``PLAYWRIGHT_BROWSERS_PATH`` and won't start without them.
    When the CLI is present but no browser build is, the first browser tool
    call hangs for the full command timeout (often ~30s each) before
    surfacing a useless error. Guarding the tool behind this check prevents
    advertising a capability that will fail at runtime.
    Nz	chromium-zchromium_headless_shell-TF)r  r  r'   r(   r)   r+   r/   r,   )rootentriesr2   s      r   r  r  
  s     "-))&((   	27==.. 		j&&GG 	 	 	H	  	 	E,, 0@0@*1 1  .2*ttt		 "'5s   A
A$#A$c                      t           j                            d          rdS 	 t          dd          5 } d|                                 v cddd           S # 1 swxY w Y   dS # t
          $ r Y dS w xY w)zABest-effort detection of whether we're inside a Docker container.z/.dockerenvTz/proc/1/cgrouprtdockerNF)r'   r(   r  r  r  r/   )fps    r   r  r  
  s    	w~~m$$ t"D)) 	)Rrwwyy(	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	)   uus4   A# A	A# AA# AA# #
A10A1c                      t                      rdS 	 t                      } n# t          $ r Y dS w xY wt          |           rdS t	                      }||                                S t                      sdS dS )a  
    Check if browser tool requirements are met.

    In **local mode** (no cloud provider configured): the ``agent-browser``
    CLI must be findable *and* a Chromium build must be installed on disk.

    In **cloud mode** (Browserbase, Browser Use, or Firecrawl): the CLI
    and the provider's required credentials must be present. The cloud
    provider hosts its own Chromium, so no local browser binary is needed.

    Returns:
        True if all requirements are met, False otherwise
    TF)r   r  r  r   r   r   r  )r   r  s     r   check_browser_requirementsr  
  s      t)++   uu -[99 u #$$H%%'''    u4s   ! 
//__main__u   🌐 Browser Tool Modulez(========================================r   r  r  z	   Mode: u   ✅ All requirements metu   ❌ Missing requirements:z@   - bare npx fallback found (insufficient on Termux local mode)z     Install: z&   - Chromium browser binary not foundz, z(no candidate paths)z     Searched: u\        Docker: pull the latest image — the current one predates the bundled Chromium installz;       docker pull ghcr.io/nousresearch/hermes-agent:latestz     Install it with:z,       npx agent-browser install --with-depsz5     Or:  npx playwright install --with-deps chromiumz    - agent-browser CLI not foundz   - z credentials not configuredzL   Tip: set browser.cloud_provider to 'local' to use free local mode insteadu   
📋 Available Browser Tools:u     🔹 rJ  z: rY  r  z...u   
💡 Usage:zC  from tools.browser_tool import browser_navigate, browser_snapshotzE  result = browser_navigate('https://example.com', task_id='my_task')z0  snapshot = browser_snapshot(task_id='my_task'))registryr{  c                      i | ]}|d          |S )rJ  r   )r@   ss     r   
<dictcomp>r  \  s    BBBqy!BBBr   rU   c                 r    t          |                     dd          |                    d                    S )Nr   r<   r   )r   r   )rU  r   r  kws     r   r   r   b  s2    /DHHUB4G4GQSQWQWXaQbQbccc r   u   🌐)rJ  toolsetschemahandlercheck_fnemojic                     t          |                     dd          |                    d          |                    d                    S )Nr^  Fr   r  )r^  r   r  )r]  r   r  s     r   r   r   j  sG    /XXfe$$bffY.?.?266R]K^K^ `  `  ` r   u   📸c                 r    t          |                     dd          |                    d                    S )Nra  r<   r   )ra  r   )r`  r   r  s     r   r   r   s  .    }%1D1DbffU^N_N_``` r   u   👆c                     t          |                     dd          |                     dd          |                    d                    S )Nra  r<   rc  r   )ra  rc  r   )rb  r   r  s     r   r   r   {  sC    |0C0C$((SY[]J^J^hjhnhnoxhyhyzzz r   u   ⌨️c                 r    t          |                     dd          |                    d                    S )Nre  rg  r   )re  r   )rd  r   r  s     r   r   r     s1    ~f8U8U_a_e_efo_p_pqqq r   u   📜c                 H    t          |                    d                    S Nr   r   )ri  r   r  s     r   r   r     s    |BFF94E4EFFF r   u   ◀️c                 r    t          |                     dd          |                    d                    S )Nrk  r<   r   )rk  r   )rj  r   r  s     r   r   r     r  r   c                 H    t          |                    d                    S r	  )rl  r   r  s     r   r   r     s    1"&&:K:KLLL r   u   🖼️c                     t          |                     dd          |                     dd          |                    d                    S )Nrn  r<   ro  Fr   )rn  ro  r   )rm  r   r  s     r   r   r     sg    ~txx
B7O7OZ^ZbZbcmotZuZu  @B  @F  @F  GP  @Q  @Q   R   R   R r   u   👁️c                     t          |                     dd          |                     d          |                    d                    S )Nr  Frq  r   )r  rq  r   )rp  r   r  s     r   r   r     s`    TXXgu5M5MZ^ZbZbcoZpZpz|  {A  {A  BK  {L  {L   M   M   M r   u   🖥️)r<   r   )NN)rM   )FNN)FN)r  )r  )r#   N)__doc__atexit	functoolsr   r  r'   r  r5  r  r3  r   r   rM  r  r   typingr   r   r   r   r   pathlibr   agent.auxiliary_clientr	   r  r
   utilsr   rZ   r   tools.website_policyr   r]   tools.url_safetyr   r'  tools.browser_providers.baser   #tools.browser_providers.browserbaser   #tools.browser_providers.browser_user   !tools.browser_providers.firecrawlr   tools.tool_backend_helpersr   r(  r   r   ImportError	getLoggerr  r^   r8   rD   r-   
_SANE_PATH	lru_cacher*   r6   r4   r7   r;   rJ   rK   r   r   __annotations__rY   r+  	frozensetrP   rQ   r\   rX   rd   rj   rm   r   r   r   r   r   r   rX  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   rE   r  r   r   r  r   r   r  r  rK  rC  Lockr   r  registerr  r"  r  rF  rP  rR  rT  BROWSER_TOOL_SCHEMASr{  r  r  r  r  r  r  r  rU  r]  r`  rb  rd  ri  rj  rp  r_  rl  r)  r  rl  rm  r  r  r  r  r
  r  r  r  r  r  printr  r  r  r   searchedr  r   r  tools.registryr  r{  _BROWSER_SCHEMA_MAPr   r   r   <module>r)     s  0 0 0d        				 				       



        3 3 3 3 3 3 3 3 3 3 3 3 3 3       + + + + + + , , , , , , ! ! ! ! ! ! % % % % % %,9999999 , , ,++,%<<<<<<< % % %$$LLL% = = = = = = C C C C C C B B B B B B ? ? ? ? ? ? G G G G G G
%IIIIIII % % %$}% 
	8	$	$
 Z___--
 QeCHo     ,Vd3i V V V V6 6s 6C 6 6 6 6 57 c5j!1 6 6 6    $  !*	7H*= > > I > > >)- # - - -! c    2C8C= C C C C
Hx} H H H H
.3 .3 . . . .b3    8!?5e#4 !? !? !? !?H-
C -
D -
 -
 -
 -
`]# ]$ ] ] ] ] '%"' ' DdO    :> !56 = = =  $ -1 HTN 1 1 1'+ x} + + + )"X&:; )" )" )" )"X A @ @ @ @ @Os O O O Ohs ht h h h hs    ) ) ) ) )
?4 
? 
? 
? 
? ). %,0 #T 0 0 0/d / / / /:8 8 8 8 8 8v'S 's 's ' ' ' '</s /t / / / /

:s 
:s 
: 
: 
: 
:&T & & & &2!S ! ! ! !4 /1 $sDcN*+ 0 0 0355 S       ,. $sCx. - - -  &)S8TV[)\)\%]%] " ,. S%Z( - - -   	  #: #: #:X / 0 0 0T T T8( (C (D ( ( ( ($uZ uZ uZp  6p p p ( ( (6c 6 6 6 6 , - - - # N$#Y  	
 	
  # Z% $V$  

 

     k$#b  	
 	
   E %#Z 
 %#D 	 	 
 
 & ! x$!6N#8  %

 

   }
 
    k$#_  	
 	
  % M
 
  ! s %#~ 
 &$ $T 
 
 $
 
 ( " j &$#U  % $] 
 
 
 
 AS t
3 
4S> 
 
 
 
 s tCH~    ` `x} `S#X ` ` ` `HES E E E EPS Xc]    0 !	a3 a3a3a3 s)a3 c]	a3
 
#s(^a3 a3 a3 a3L  $51 5151}51 	51 51 51 51p c c S    Fg g# g g g g g gV !#@ @
@c]@ }@ 		@ @ @ @F   s  Xc]  c        F# #c # #x} # # # # #L, ,c ,HSM ,S , , , ,^ (3- 3    < s Xc] c    B7 74 7Xc] 7T\]`Ta 7mp 7 7 7 7t%( %(c %(HSM %(S %( %( %( %(P4 4c 4HSM 4S 4 4 4 4D;C ; ; ; ;@13 1 1 1 1"7 7 7 7 7 7 7tr: r:S r:D r:8C= r:\_ r: r: r: r:jG G G G0F F F F,&9 &9Xc] &9d &9 &9 &9 &9RIIS IIT II II II IIX& & & &F .2 HTN 1 1 1S	    8T    DD    *D * * * *b z 
E
$%%%	E(OOO



Ck77'G1B1B1D1D'G'G'GD	E
d

 "!## b()))))***	>--//K44[AA SXYYY@'<'<'>'>@@AAAA%8%8%:%:>???99%;%;%=%=>>XBX222333%%'' 	SE@   EWXXXXE1222EHIIIEQRRR  	> 	> 	>E4555E<#8#8#:#:<<=====	> ?3#4#4#6#6?EJ#++--JJJKKKE`aaa	E
+,,,& K KIvII&*?*DIIIJJJJ	E/	E
OPPP	E
QRRR	E
<=== 0 / / / / / / /BB-ABBB   	12cc'
     	12` `'
     	/``'
     	~.zz'
     	/0qq'
     	~.FF'
     	/``'
     	34LL'
     	/0 R  R'
     	01 M  M'
     sI   )A0 0A;:A;?B BB3B: :CC6B=Z4 4&[[