
    Jjy                     0   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	m
Z
mZ ddlmZ ddlmZmZ ej        dk    rddlmZ nddl	mZ dd	lmZmZ d
a ej                    ZdZdZd ZdZdedz  de fdZ!dZ"de#ddfdZ$dDdZ%dDdZ& e&              G d dej'                  Z(dddddddZ)dddddd
d d!ee         d"ee#         d#ee*         d$ee*         d%ee#         d&e defd'Z+dDd(Z, G d) d*e          Z-da.d+e/d,<   da0ee         e/d-<   g Z1e2e/d.<   d
a3 ej4                    Z5 G d/ d0e
          Z6dDd1Z7dDd2Z8d3ej9        ddfd4Z:dDd5Z;dEd7e<ddfd8Z=de2fd9Z>dDd:Z?dd;d<ej@        d=ed>e*d?e*d$e*d@ejA        dAeej'                 ddfdBZBdC ZCdS )Fu   Centralized logging setup for Hermes Agent.

Provides a single ``setup_logging()`` entry point that both the CLI and
gateway call early in their startup path.  All log files live under
``~/.hermes/logs/`` (profile-aware via ``get_hermes_home()``).

Log files produced:
    agent.log   — INFO+, all agent/tool/session activity (the main log)
    errors.log  — WARNING+, errors and warnings only (quick triage)
    gateway.log — INFO+, gateway-only events (created when mode="gateway")
    gui.log     — INFO+, dashboard/websocket/TUI-gateway events
                  (created when mode="gui")

All files use ``RotatingFileHandler`` with ``RedactingFormatter`` so
secrets are never written to disk.

Component separation:
    gateway.log only receives records from ``gateway.*`` loggers —
    platform adapters, session management, slash commands, delivery.
    gui.log receives dashboard-side records from ``hermes_cli.web_server``,
    ``hermes_cli.pty_bridge``, ``tui_gateway.*``, and ``uvicorn.*``.
    agent.log remains the catch-all (everything goes there).

Session context:
    Call ``set_session_context(session_id)`` at the start of a conversation
    and ``clear_session_context()`` when done.  All log lines emitted on
    that thread will include ``[session_id]`` for filtering/correlation.
    N)QueueHandlerQueueListener)Path)OptionalSequencewin32)ConcurrentRotatingFileHandler)RotatingFileHandler)get_config_pathget_hermes_homeFz>%(asctime)s %(levelname)s%(session_tag)s %(name)s: %(message)szC%(asctime)s - %(name)s - %(levelname)s%(session_tag)s - %(message)sc                  *   t           j        } t          | dd          pd}|                                                    dd          dv r| S 	 t          | dd          }|"t          j        |ddd	
          }d |_        |S n# t          $ r Y nw xY w| S )u  Return a stderr stream that tolerates Unicode on all platforms.

    On Windows the console encoding is often a legacy MBCS codec
    (cp949, cp1252, …) that raises ``UnicodeEncodeError`` for characters
    like the em-dash (U+2014).  We wrap ``sys.stderr`` in a
    ``TextIOWrapper`` with ``errors='replace'`` so log lines are never
    lost — un-encodable characters are replaced with ``?`` instead of
    crashing the process.
    encodingNutf-8- )utf8utf8surrogateescapebufferreplaceT)r   errorsline_bufferingc                      d S N r       4/home/rurouni/.hermes/hermes-agent/hermes_logging.py<lambda>z_safe_stderr.<locals>.<lambda>s   s    D r   )	sysstderrgetattrlowerr   ioTextIOWrapperclose	Exception)streamr   bufwrappeds       r   _safe_stderrr)   Y   s     ZFvz400;GH~~R((,KKKfh--?&  #	  G )LGMN      Ms   4B 
BBz%Cannot acquire lock after 20 attemptsexcreturnc                 x    t           j        dk    o*t          | t                    ot          t          |           v S )a  Return True for concurrent-log-handler's Windows lock timeout.

    On Windows Desktop, slash-command workers and the gateway can all write to
    the same rotating log files. ``concurrent-log-handler`` serializes rollover
    with a cross-process lock, but when another process holds that lock too
    long it raises this RuntimeError. Logging failures should not escape into
    Desktop chat output.
    r   )r   platform
isinstanceRuntimeError_CONCURRENT_LOG_LOCK_TIMEOUTstr)r*   s    r   '_is_windows_concurrent_log_lock_timeoutr2   ~   s8     	 	5sL))	5(CHH4r   )openaizopenai._base_clienthttpxhttpcoreasynciohpackzhpack.hpackgrpcmodalurllib3zurllib3.connectionpool
websocketscharset_normalizermarkdown_it
session_idc                     | t           _        dS )zSet the session ID for the current thread.

    All subsequent log records on this thread will include ``[session_id]``
    in the formatted output.  Call at the start of ``run_conversation()``.
    N_session_contextr>   )r>   s    r   set_session_contextrB      s     #-r   c                      dt           _        dS )z,Clear the session ID for the current thread.Nr@   r   r   r   clear_session_contextrD      s    "&r   c                      t          j                    t          dd          rdS fd} d| _        t          j        |            dS )ub  Replace the global LogRecord factory with one that adds ``session_tag``.

    Unlike a ``logging.Filter`` on a handler or logger, the record factory
    runs for EVERY record in the process — including records that propagate
    from child loggers and records handled by third-party handlers.  This
    guarantees ``%(session_tag)s`` is always available in format strings,
    eliminating the KeyError that would occur if a handler used our format
    without having a ``_SessionFilter`` attached.

    Idempotent — checks for a marker attribute to avoid double-wrapping if
    the module is reloaded.
    _hermes_session_injectorFNc                  b     | i |}t          t          dd           }|rd| dnd|_        |S )Nr>   z []r   )r    rA   session_tag)argskwargsrecordsidcurrent_factorys       r   _session_record_factoryz@_install_session_record_factory.<locals>._session_record_factory   sG     $1&11&d;;,/7[#[[[[Rr   T)logginggetLogRecordFactoryr    rF   setLogRecordFactory)rO   rN   s    @r   _install_session_record_factoryrS      si     133O :EBB      8<4 788888r   c                   R     e Zd ZdZdee         ddf fdZdej        de	fdZ
 xZS )_ComponentFilterzOnly pass records whose logger name starts with one of *prefixes*.

    Used to route gateway-specific records to ``gateway.log`` while
    keeping ``agent.log`` as the catch-all.
    prefixesr+   Nc                 p    t                                                       t          |          | _        d S r   )super__init__tuple	_prefixes)selfrV   	__class__s     r   rY   z_ComponentFilter.__init__   s*    xr   rL   c                 @    |j                             | j                  S r   )name
startswithr[   r\   rL   s     r   filterz_ComponentFilter.filter   s    {%%dn555r   )__name__
__module____qualname____doc__r   r1   rY   rP   	LogRecordboolrb   __classcell__r]   s   @r   rU   rU      s~         )# )4 ) ) ) ) ) )6W. 64 6 6 6 6 6 6 6 6r   rU   )gatewayhermes_pluginszplugins.platforms)agent	run_agentmodel_toolsbatch_runner)tools)
hermes_clicli)cron)zhermes_cli.web_serverzhermes_cli.pty_bridgetui_gatewayuvicorn)rk   rm   rq   rs   rt   gui)hermes_home	log_levelmax_size_mbbackup_countmodeforcerx   ry   rz   r{   r|   r}   c                 *   | pt                      }|dz  }|                    dd           t                      \  }}	}
|p|pd                                }t	          t
          |t
          j                  }|p|	pddz  dz  }|p|
pd}dd	lm} t          j	                    }t          ||d
z  ||| |t                               t          ||dz  t
          j        dd |t                               |dk    rIt          ||dz  t
          j        dd |t                    t          t          d                              |dk    rIt          ||dz  t
          j        dd |t                    t          t          d                              t          r|s|S |j        t
          j        k    s|j        |k    r|                    |           t&          D ]3}t          j	        |                              t
          j                   4da|S )u@  Configure the Hermes logging subsystem.

    Safe to call multiple times — the second call is a no-op unless
    *force* is ``True``.

    Parameters
    ----------
    hermes_home
        Override for the Hermes home directory.  Falls back to
        ``get_hermes_home()`` (profile-aware).
    log_level
        Minimum level for the ``agent.log`` file handler.  Accepts any
        standard Python level name (``"DEBUG"``, ``"INFO"``, ``"WARNING"``).
        Defaults to ``"INFO"`` or the value from config.yaml ``logging.level``.
    max_size_mb
        Maximum size of each log file in megabytes before rotation.
        Defaults to 5 or the value from config.yaml ``logging.max_size_mb``.
    backup_count
        Number of rotated backup files to keep.
        Defaults to 3 or the value from config.yaml ``logging.backup_count``.
    mode
        Caller context: ``"cli"``, ``"gateway"``, ``"gui"``, ``"cron"``.
        When ``"gateway"``, an additional ``gateway.log`` file is created
        that receives only gateway-component records.
        When ``"gui"``, an additional ``gui.log`` file is created that
        receives dashboard and TUI-gateway component records.
    force
        Re-run setup even if it has already been called.

    Returns
    -------
    Path
        The ``logs/`` directory where files are written.
    logsTparentsexist_okINFO   i      r   RedactingFormatterz	agent.log)level	max_bytesr{   	formatterz
errors.logi       rk   zgateway.logi  P )r   r   r{   r   
log_filterrw   zgui.logi   )r   mkdir_read_logging_configupperr    rP   r   agent.redactr   	getLogger_add_rotating_handler_LOG_FORMATWARNINGrU   COMPONENT_PREFIXES_logging_initializedr   NOTSETsetLevel_NOISY_LOGGERS)rx   ry   rz   r{   r|   r}   homelog_dir	cfg_levelcfg_max_size
cfg_backup
level_namer   r   backupsr   rootr_   s                     r   setup_loggingr     sm   X +/++DVmGMM$M... +?*@*@'I|Z2y2F99;;JGZ66E11T9D@I-j-AG 0/////D +$$[11    ,o!$$[11    ym#,%((55'(:9(EFF	
 	
 	
 	
 u}}i,&((55'(:5(ABB	
 	
 	
 	
  E  zW^##tzE'9'9e  : :$((9999Nr   c                     ddl m}  t          j                    }|j        D ]E}t          |t          j                  r)t          |t                    st          |dd          r dS Ft          j        t                                }|
                    t          j                   |                     | t          d                     d|_        |                    |           |j        t          j        k    r|
                    t          j                   t"          D ]3}t          j        |          
                    t          j                   4t          j        d	          
                    t          j                   dS )
zEnable DEBUG-level console logging for ``--verbose`` / ``-v`` mode.

    Called by ``AIAgent.__init__()`` when ``verbose_logging=True``.
    r   r   _hermes_verboseFNz%H:%M:%S)datefmtTz
rex-deploy)r   r   rP   r   handlersr.   StreamHandlerr
   r    r)   r   DEBUGsetFormatter_LOG_FORMAT_VERBOSEr   
addHandlerr   r   r   r   )r   r   hhandlerr_   s        r   setup_verbose_loggingr   {  sf   
 0/////D ]  a.// 	
1FY8Z8Z 	q+U33 #LNN33GW]###++,?TTTUUU"GOOG zGM!!gm$$$  : :$((9999l##,,W\:::::r   c                        e Zd ZdZ fdZd ZddZddZdej	        ddf fd	Z
dej	        ddf fd
Z fdZ fdZ xZS )_ManagedRotatingFileHandleru  RotatingFileHandler that ensures group-writable perms in managed mode
    AND survives external rotation.

    Two responsibilities:

    1.  In managed mode (NixOS), the stateDir uses setgid (2770) so new files
        inherit the hermes group. However, both ``_open()`` (initial creation)
        and ``doRollover()`` create files via ``open()``, which uses the
        process umask — typically 0022, producing 0644. This subclass applies
        ``chmod 0660`` after both operations so the gateway and interactive
        users can share log files.

    2.  ``RotatingFileHandler`` keeps an open file descriptor.  If anything
        rotates the file *externally* (``logrotate``, manual ``mv``,
        another process rotating under us, a transient unlink), our fd
        keeps pointing at the renamed/unlinked inode and every subsequent
        write goes to ``gateway.log.1`` instead of ``gateway.log`` — silent
        log loss for the file every operator expects to read.  Before each
        emit we ``stat`` ``baseFilename`` and compare it against the open
        stream's inode; on mismatch we reopen.  This is the same pattern
        as stdlib ``WatchedFileHandler.reopenIfNeeded()``, adapted for
        rotating handlers.
    c                     ddl m}  |            | _         t                      j        |i | d | _        d | _        |                                  d S )Nr   )
is_managed)hermes_cli.configr   _managedrX   rY   	_stat_dev	_stat_ino_record_stream_stat)r\   rJ   rK   r   r]   s       r   rY   z$_ManagedRotatingFileHandler.__init__  sf    000000"
$)&))) )-(,  """""r   c                 p    | j         r.	 t          j        | j        d           d S # t          $ r Y d S w xY wd S )Ni  )r   oschmodbaseFilenameOSError)r\   s    r   _chmod_if_managedz-_ManagedRotatingFileHandler._chmod_if_managed  sX    = 	*E22222   	 	s   % 
33r+   Nc                     	 t          j        | j                  }|j        |j        c| _        | _        dS # t          $ r d\  | _        | _        Y dS w xY w)zHSnapshot dev/ino of ``baseFilename`` so we can detect external rotation.)NNN)r   statr   st_devst_inor   r   r   r\   sts     r   r   z/_ManagedRotatingFileHandler._record_stream_stat  s`    	8*++B-/Y	*DNDNNN 	8 	8 	8-7*DNDNNNN	8s   26 AAc                    	 t          j        | j                  }n# t          $ r| 	 | j        | j                                         n# t          $ r Y nw xY wd| _        	 |                                 | _        |                                  n# t          $ r Y nw xY wY dS t          $ r Y dS w xY w| j
        | j        |j        |j        c| _
        | _        dS |j        |j        f| j
        | j        fk    r	 | j        | j                                         n# t          $ r Y nw xY wd| _        	 |                                 | _        |j        |j        c| _
        | _        dS # t          $ r Y dS w xY wdS )aa  Reopen the stream when ``baseFilename`` no longer matches our fd.

        Triggered when ``baseFilename`` was renamed (logrotate), unlinked,
        or replaced by a different inode.  Silent + best-effort: any error
        falls back to the existing (possibly stale) stream so logging keeps
        working instead of dying on a stat failure.
        N)r   r   r   FileNotFoundErrorr&   r$   r%   _openr   r   r   r   r   r   r   s     r   _reopen_if_externally_rotatedz9_ManagedRotatingFileHandler._reopen_if_externally_rotated  s   	*++BB  	 	 	;*K%%'''   DK"jjll((****     FF 	 	 	FF	 >!T^%;-/Y	*DNDNFIry!dndn%EEE;*K%%'''   DK"jjll13BI.    FEs    
B. AB.
AB.A
B. -BB.
BB.BB.!	B.-B.9 D 
D'&D'22E& &
E43E4rL   c                     | j         $t          j                            | j                  r|                                  t                                          |           d S r   )r&   r   pathexistsr   r   rX   emit)r\   rL   r]   s     r   r   z _ManagedRotatingFileHandler.emit  sQ     ;"bgnnT5F&G&G"..000Vr   c                     t          j                    d         }t          |          rdS t                                          |           dS )uw  Suppress the known Windows ``concurrent-log-handler`` lock timeout
        instead of printing a traceback.

        CLH's own ``emit()`` wraps its body in ``try/except Exception:
        self.handleError(record)``, so the ``"Cannot acquire lock after N
        attempts"`` RuntimeError raised in ``_do_lock()`` is caught inside CLH
        and routed here — it never propagates out of ``super().emit()``.  This
        override is the single point where that timeout can be silenced before
        the stdlib handler prints it to stderr (which, under the Desktop
        slash-worker, is captured and surfaced into chat output).   N)r   exc_infor2   rX   handleError)r\   rL   r*   r]   s      r   r   z'_ManagedRotatingFileHandler.handleError	  sH     lnnQ2377 	FF#####r   c                 p    t                                                      }|                                  |S r   )rX   r   r   )r\   r&   r]   s     r   r   z!_ManagedRotatingFileHandler._open  s+       r   c                     t                                                       |                                  |                                  d S r   )rX   
doRolloverr   r   )r\   r]   s    r   r   z&_ManagedRotatingFileHandler.doRollover  sE        	  """""r   r+   N)rc   rd   re   rf   rY   r   r   r   rP   rg   r   r   r   r   ri   rj   s   @r   r   r     s
        0# # # # #  8 8 8 8/ / / /b7,       $'"3 $ $ $ $ $ $ $     
# # # # # # # # #r   r   zOptional[queue.SimpleQueue]
_log_queue_queue_listener_queued_file_handlersc                   6    e Zd ZdZdej        dej        fdZdS )_NonFormattingQueueHandleru  ``QueueHandler`` for an in-process queue.

    Stdlib ``prepare()`` formats the record and drops ``args``/``exc_info`` so it
    can be pickled to another process.  Our queue is in-process, so we skip that
    and hand the target file handlers an unformatted record — they apply their
    own ``RedactingFormatter`` and component filters on the listener thread.

    We return a **shallow copy** rather than the original record: the same
    record is still owned by the emitting thread (and any synchronous handler
    on it, e.g. a ``StreamHandler``), which may format/mutate ``record.message``
    while our listener thread reads it. Copying preserves ``msg``/``args``/
    ``exc_info`` for the deferred format while removing the cross-thread
    mutation race on a shared object.
    rL   r+   c                 *    t          j         |          S r   )copyra   s     r   preparez"_NonFormattingQueueHandler.prepareO  s    y   r   N)rc   rd   re   rf   rP   rg   r   r   r   r   r   r   ?  sF         !g/ !G4E ! ! ! ! ! !r   r   c                  n    t           dc} a | (	 |                                  dS # t          $ r Y dS w xY wdS )zAStop the listener assuming ``_queue_state_lock`` is already held.N)r   stopr%   listeners    r   _stop_queue_listener_lockedr   S  sV     !0Ho	MMOOOOO 	 	 	DD	 s   $ 
22c                  b    t           5  t                       ddd           dS # 1 swxY w Y   dS )zFlush and stop the background log listener (idempotent, thread-safe).

    This is the atexit hook, so it must acquire the state lock itself.
    N)_queue_state_lockr   r   r   r   _stop_queue_listenerr   ^  sz    
 
 & &#%%%& & & & & & & & & & & & & & & & & &s   $((r   c                    t           5  t          Tt          j                    at	          t                    }d|_        t          j                                        |           t          
                    |            t          t                                           t          t          gt          R ddiat                                           t          st!          j        t$                     daddd           dS # 1 swxY w Y   dS )a  Route *handler* through the shared async queue instead of attaching it to
    *root* directly, so emitting threads never block on file I/O or the
    cross-process rotation lock.  The ``QueueListener`` applies each handler's
    own level and filters on its worker thread.NTrespect_handler_level)r   r   queueSimpleQueuer   _hermes_queuerP   r   r   r   appendr   r   r   start_queue_atexit_registeredatexitregisterr   )r   qhs     r   _register_queued_handlerr   g  sE    
 , ,*,,J+J77B#B **2...$$W--- &  """'
.
 
 
FJ
 
 	' 	, O0111'+$/, , , , , , , , , , , , , , , , , ,s   C+D  DDc                      t           5  t          } | (|                                  |                                  ddd           dS # 1 swxY w Y   dS )u  Block until all queued records have been written, then resume.

    Draining is done by stopping the listener (which processes every pending
    record before joining) and restarting it.  Used by tests that read a log
    file right after emitting to it.

    NOTE: ``stop()`` joins the worker thread, so this blocks until the queue
    is empty. Do NOT call this on a hard-exit path where the listener may be
    wedged on the rotation lock — use ``drain_log_queue()`` there instead,
    which bounds the wait.
    N)r   r   r   r   r   s    r   flush_log_queuer     s     
  "MMOOONN	                 s   2AAA      ?timeoutc                     t           dS dfd}t          j        |dd          }|                                 |                    |            dS )u  Best-effort, time-bounded drain for hard-exit paths (no restart).

    Unlike ``flush_log_queue()``, this stops the listener WITHOUT restarting it
    (the process is about to exit) and bounds the drain: if the listener's
    worker thread is wedged on the cross-process rotation lock — the very
    failure this async-logging change exists to survive — an unbounded
    ``stop()``/join would re-freeze the shutdown path. We run ``stop()`` on a
    throwaway thread and only wait ``timeout`` seconds for it; if it hasn't
    drained by then we abandon the last few records and let ``os._exit``
    proceed. Availability beats the last log line when the disk is already
    wedged.
    Nr+   c                  T    	                                    d S # t          $ r Y d S w xY wr   )r   r%   r   s   r   _drainzdrain_log_queue.<locals>._drain  s;    	MMOOOOO 	 	 	DD	s    
''zhermes-log-drainT)targetr_   daemonr   )r   	threadingThreadr   join)r   r   tr   s      @r   drain_log_queuer     sm     H      	-?MMMAGGIIIFF7OOOOOr   c                  *    t          t                    S )zReturn the live rotating file handlers.

    They are attached to the async ``QueueListener`` rather than the root
    logger, so callers/tests must use this instead of scanning
    ``logging.getLogger().handlers``.)listr   r   r   r   rotating_file_handlersr     s     %&&&r   c                     t           5  t                       t          j                    } t	          | j                  D ](}t          |dd          r|                     |           )t	          t                    D ]'}	 |	                                 # t          $ r Y $w xY wt                                           daddd           dS # 1 swxY w Y   dS )zETear down the async logging queue + listener (test-isolation helper).r   FN)r   r   rP   r   r   r   r    removeHandlerr   r$   r%   clearr   )r   r   s     r   _reset_queued_handlersr    s-    
  #%%% ""dm$$ 	& 	&Aq/511 &""1%%%+,, 	 	A				   ##%%%
                 s6   A5C>BC
B CB  CCC)r   loggerr   r   r   r   r   c                   |                                 }t          D ]N}t          |t                    r7t	          t          |dd                                                     |k    r dS O|j                            dd           t          t          |          ||d          }	|	
                    |           |	                    |           ||	                    |           t          |	           dS )a  Add a ``RotatingFileHandler`` to *logger*, skipping if one already
    exists for the same resolved file path (idempotent).

    Parameters
    ----------
    log_filter
        Optional filter to attach to the handler (e.g. ``_ComponentFilter``
        for gateway.log).
    r   r   NTr   r   )maxBytesbackupCountr   )resolver   r.   r
   r   r    parentr   r   r1   r   r   	addFilterr   )
r  r   r   r   r{   r   r   resolvedexistingr   s
             r   r   r     s    & ||~~H)  x!455	WX~r::;;CCEEQQFFKdT222)D		I<  G U###*%%% W%%%%%r   c                     	 ddl m}  t                      }|                                rt	          |dd          5 } | |          pi }ddd           n# 1 swxY w Y   	 ddlm} |                    |          }n# t          $ r Y nw xY w|	                    di           }t          |t                    r>|	                    d	          |	                    d
          |	                    d          fS n# t          $ r Y nw xY wdS )u   Best-effort read of ``logging.*`` from config.yaml.

    Returns ``(level, max_size_mb, backup_count)`` — any may be ``None``.
    r   )fast_safe_loadrr   )r   N)managed_scoperP   r   rz   r{   )NNN)utilsr  r   r   openrr   r  apply_managed_overlayr%   getr.   dict)r  config_pathfcfgr  log_cfgs         r   r   r     s   
((((((%'' 	k3999 .Q$nQ''-2. . . . . . . . . . . . . . .444444#99#>>   ggi,,G'4(( KK((KK..KK// 
    sY   :C8 A
C8 AC8 AC8 "A> =C8 >
BC8 
BA+C8 8
DDr   )r   )Drf   r   r   r"   rP   r   r   r   r   logging.handlersr   r   pathlibr   typingr   r   r-   concurrent_log_handlerr	   r
   hermes_constantsr   r   r   localrA   r   r   r)   r0   BaseExceptionrh   r2   r   r1   rB   rD   rS   FilterrU   r   intr   r   r   r   __annotations__r   r   r   r   Lockr   r   r   r   Handlerr   r   floatr   r   r  Logger	Formatterr   r   r   r   r   <module>r*     s    :   				  				  



     8 8 8 8 8 8 8 8       % % % % % % % %0 <7       544444 > = = = = = = =
   #9?$$ 
 O[   D  G 1E $    ",-C -D - - - -' ' ' '9 9 9 9:    ! ! !6 6 6 6 6w~ 6 6 6, BB   2 #'#!%"&u u u$u }u #	u
 3-u 3-u u 
u u u up; ; ; ;HD# D# D# D# D#"5 D# D# D#f -1
) 0 0 0+/-( / / /  t         #IN$$ ! ! ! ! ! ! ! !(   & & & &,go ,$ , , , ,@   & U T    8' ' ' ' '   4 ,0&& && &&N&&
&& 	&&
 && &&  && (&& 
&& && && &&R    r   