
    i!                    .   U d Z ddlmZ ddlZddlZddlZddlZddlZddlZddl	m
Z
mZmZmZmZ  eej        ej        ej        ej         eedd           eedd          hdhz
            Z ej        e          Zej                            dd	          pd	                                                                d
v Ze G d de                      Z ej        dd          Z de!d<   ddZ"ddZ#ddZ$ G d d          Z% G d d          Z&dS )uC  Transport abstraction for the tui_gateway JSON-RPC server.

Historically the gateway wrote every JSON frame directly to real stdout.  This
module decouples the I/O sink from the handler logic so the same dispatcher
can be driven over stdio (``tui_gateway.entry``) or WebSocket
(``tui_gateway.ws``) without duplicating code.

A :class:`Transport` is anything that can accept a JSON-serialisable dict and
forward it to its peer.  The active transport for the current request is
tracked in a :class:`contextvars.ContextVar` so handlers — including those
dispatched onto the worker pool — route their writes to the right peer.

Backward compatibility
----------------------
``tui_gateway.server.write_json`` still works without any transport bound.
When nothing is on the contextvar and no session-level transport is found,
it falls back to the module-level :class:`StdioTransport`, which wraps the
original ``_real_stdout`` + ``_stdout_lock`` pair.  Tests that monkey-patch
``server._real_stdout`` continue to work because the stdio transport resolves
the stream lazily through a callback.
    )annotationsN)AnyCallableOptionalProtocolruntime_checkableWSAECONNRESETWSAESHUTDOWNHERMES_TUI_GATEWAY_NO_FLUSH >   1onyestruec                  "    e Zd ZdZd
dZddZd	S )	Transportz-Minimal interface every transport implements.objdictreturnboolc                    dS )z<Emit one JSON frame. Return ``False`` when the peer is gone.N )selfr   s     4/usr/local/lib/hermes-agent/tui_gateway/transport.pywritezTransport.writeF             Nonec                    dS )z.Release any resources owned by this transport.Nr   r   s    r   closezTransport.closeI   r   r   Nr   r   r   r   r   r   )__name__
__module____qualname____doc__r   r"   r   r   r   r   r   B   sF        77K K K K= = = = = =r   r   hermes_gateway_transport)defaultz+contextvars.ContextVar[Optional[Transport]]_current_transportr   Optional[Transport]c                 4    t                                           S )z;Return the transport bound for the current request, if any.)r+   getr   r   r   current_transportr/   U   s    !!###r   	transportc                6    t                               |           S )zVBind *transport* for the current context. Returns a token for :func:`reset_transport`.)r+   set)r0   s    r   bind_transportr3   Z   s    !!),,,r   r   c                :    t                               |            dS )zARestore the transport binding captured by :func:`bind_transport`.N)r+   reset)tokens    r   reset_transportr7   _   s    U#####r   c                  .    e Zd ZdZdZdd	ZddZddZdS )StdioTransportu/  Writes JSON frames to a stream (usually ``sys.stdout``).

    The stream is resolved via a callable so runtime monkey-patches of the
    underlying stream continue to work — this preserves the behaviour the
    existing test suite relies on (``monkeypatch.setattr(server, "_real_stdout", ...)``).
    _stream_getter_lockstream_getterCallable[[], Any]lockthreading.Lockr   r   c                "    || _         || _        d S Nr:   )r   r=   r?   s      r   __init__zStdioTransport.__init__n   s    +


r   r   r   r   c                   t          j        |d          dz   }| j        5  |                                 }	 |                    |           n# t
          $ r Y ddd           dS t          $ r=}t          |t                    sdt          |          vr Y d}~ddd           dS d}~wt          $ r@}|j        t          vr t                              d|           Y d}~ddd           dS d}~ww xY wt          s	 |                                 n# t
          $ r Y ddd           dS t          $ r=}t          |t                    sdt          |          vr Y d}~ddd           dS d}~wt          $ r@}|j        t          vr t                              d|           Y d}~ddd           dS d}~ww xY wddd           n# 1 swxY w Y   dS )	u  Return ``True`` on success, ``False`` ONLY when the peer is gone.

        Returning ``False`` is the dispatcher's "broken stdout pipe" signal
        — ``entry.py`` calls ``sys.exit(0)`` when ``write_json`` reports
        ``False``.  So programming errors (non-JSON-safe payloads, encoding
        misconfig, unexpected ValueErrors, host I/O bugs like ENOSPC) MUST
        NOT return ``False``, otherwise a real bug looks like a clean
        disconnect and is harder to diagnose.  Those re-raise so the
        existing crash-log infrastructure records the traceback.

        Peer-gone branches:
          * ``BrokenPipeError``
          * ``ValueError("...closed file...")``
          * ``OSError`` whose errno is in :data:`_PEER_GONE_ERRNOS`
            (EPIPE / ECONNRESET / EBADF / ESHUTDOWN; plus WSA mappings
            on Windows).  Other OSError errnos (ENOSPC, EACCES, ...) are
            real host problems and re-raise.
        F)ensure_ascii
Nzclosed filez"StdioTransport write peer gone: %sz"StdioTransport flush peer gone: %sT)jsondumpsr<   r;   r   BrokenPipeError
ValueError
isinstanceUnicodeEncodeErrorstrOSErrorerrno_PEER_GONE_ERRNOSloggerdebug_DISABLE_FLUSHflush)r   r   linestreames        r   r   zStdioTransport.writer   s   . z#E222T9Z '	! '	!((**FT"""""   '	! '	! '	! '	! '	! '	! '	! '	!     a!344 SQRVV8S8Suuu'	! '	! '	! '	! '	! '	! '	! '	!    7"333A1EEEuuu''	! '	! '	! '	! '	! '	! '	! '	! " !!LLNNNN& ! ! ! ='	! '	! '	! '	! '	! '	! '	! '	!> " ! ! !!!%788 MQTUVQWQW<W<W 555E'	! '	! '	! '	! '	! '	! '	! '	!F  ! ! !w&777LL!EqIII 555O'	! '	! '	! '	! '	! '	! '	! '	!F!G'	! '	! '	! '	! '	! '	! '	! '	! '	! '	! '	! '	! '	! '	! '	!R ts   GAG
C3G%	C3.'B&G&C33*C.G.C33
G>DG
F9G+	F94'E,G,F99*F4#G4F99GGGc                    d S rB   r   r!   s    r   r"   zStdioTransport.close   s    tr   N)r=   r>   r?   r@   r   r   r#   r$   r%   r&   r'   r(   	__slots__rC   r   r"   r   r   r   r9   r9   d   sf          ,I   B B B BH     r   r9   c                  .    e Zd ZdZdZddZddZddZdS )TeeTransportu]  Mirrors writes to one primary plus N best-effort secondaries.

    The primary's return value (and exceptions) determine the result —
    secondaries swallow failures so a wedged sidecar never stalls the
    main IO path.  Used by the PTY child so every dispatcher emit lands
    on stdio (Ink) AND on a back-WS feeding the dashboard sidebar.
    _primary_secondariesprimary'Transport'secondariesr   r   c                "    || _         || _        d S rB   r]   )r   r`   rb   s      r   rC   zTeeTransport.__init__   s    'r   r   r   r   c                    | j                             |          }| j        D ](}	 |                    |           # t          $ r Y %w xY w|S rB   )r^   r   r_   	Exception)r   r   oksecs       r   r   zTeeTransport.write   sd    ]  %%$ 	 	C		#   	s   ;
AAc                    	 | j                                          | j        D ]'}	 |                                 # t          $ r Y $w xY wd S # | j        D ]'}	 |                                 # t          $ r Y $w xY ww xY wrB   )r^   r"   r_   re   )r   rg   s     r   r"   zTeeTransport.close   s    	M!!!(  IIKKKK    D t(  IIKKKK    Ds>   A :
AA
A=A,+A=,
A9	6A=8A9	9A=N)r`   ra   rb   ra   r   r   r#   r$   rY   r   r   r   r\   r\      sa          -I( ( ( (        r   r\   )r   r,   )r0   r,   r$   )'r(   
__future__r   contextvarsrO   rG   loggingos	threadingtypingr   r   r   r   r   	frozensetEPIPE
ECONNRESETEBADF	ESHUTDOWNgetattrrP   	getLoggerr%   rQ   environr.   striplowerrS   r   
ContextVarr+   __annotations__r/   r3   r7   r9   r\   r   r   r   <module>r{      sL    , # " " " " "        				     G G G G G G G G G G G G G G
 I	K		K	OGE?B''GE>2&& D	 
 
  
	8	$	$ *..!>CCIrPPRRXXZZ _  = = = = = = = = K"       $ $ $ $
- - - -
$ $ $ $
S S S S S S S Sl! ! ! ! ! ! ! ! ! !r   