
    `gjA                        U d dl Z d dlZd dlZ ej                          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
 d dlmZ d dlmZmZmZmZ d dlmZ  ej(                  e      ZdaddZdZdefd	Zd
eddfdZ eed      r' ej                  ej>                  ej@                          eed      r ej                  ejB                  e        eed      r ej                  ejD                  e       n& eed      r ej                  ejF                  e        eed      r' ej                  ejH                  ej@                         de%ddfdZ&dddZ'de(fdZ)ddedz  de(fdZ*g Z+e,e   e-d<   d Z.edk(  r e.        yy)    N)handle_spurious_eof)server)
_CRASH_LOGdispatchresolve_skin
write_json)TeeTransportreturnc                      t         j                  j                  d      } | syddlm} t        t        j                   ||             t        _        y)a  Mirror every dispatcher emit to the dashboard sidebar via WS.

    Activated by `HERMES_TUI_SIDECAR_URL`, set by the dashboard's
    ``/api/pty`` endpoint when a chat tab passes a ``channel`` query param.
    Best-effort: connect failure or runtime drop falls back to stdio-only.
    HERMES_TUI_SIDECAR_URLNr   )WsPublisherTransport)osenvirongettui_gateway.event_publisherr   r	   r   _stdio_transport)urlr   s     D/root/.hermes/venv/lib/python3.12/site-packages/tui_gateway/entry.py_install_sidecar_publisherr   !   s>     **..1
2C@*!5c!:F    g      ?c                      t         j                  j                  d      xs dj                         } | st        S 	 t        |       }|dkD  r|S t        S # t        $ r	 t        cY S w xY w)N#HERMES_TUI_GATEWAY_SHUTDOWN_GRACE_S r   )r   r   r   strip_DEFAULT_SHUTDOWN_GRACE_Sfloat
ValueError)rawvalues     r   _shutdown_grace_secondsr    ?   se    ::>>?@FB
M
M
OC(()c
 AI5<#<<  )(()s   A A%$A%signumc                 x   i }dD ]$  }t        t        |d      }|||t        |      <   & |j                  | d|        }	 t	        j
                  t        j                  j                  t              d       t        t        dd      5 }|j                  d	| d
t        j                  d       d       |(|j                  d       t        j                  ||       ddl}|j                   j#                         D ]{  \  }}	|j                  d|	j$                   d| d       |j                  dj'                  t        j(                  t+        j,                         j                  |                         } 	 ddd       t1        d| t*        j2                  d       ddl}dd}
|j5                  t7               |
      }d|_        |j;                          	 ddlm}  |        	 t+        j@                  d       y# 1 sw Y   xY w# t.        $ r Y w xY w# t.        $ r Y =w xY w# tB        $ r  w xY w)u  Capture WHICH thread and WHERE a termination signal hit us.

    SIG_DFL for SIGPIPE kills the process silently the instant any
    background thread (TTS playback, beep, voice status emitter, etc.)
    writes to a stdout the TUI has stopped reading.  Without this
    handler the gateway-exited banner in the TUI has no trace — the
    crash log never sees a Python exception because the kernel reaps
    the process before the interpreter runs anything.

    Termination semantics: ``sys.exit(0)`` here used to race the worker
    pool — a thread holding ``_stdout_lock`` mid-flush would block the
    interpreter shutdown indefinitely.  We now log the stack, give the
    process the configured shutdown grace
    (``HERMES_TUI_GATEWAY_SHUTDOWN_GRACE_S``, default
    ``_DEFAULT_SHUTDOWN_GRACE_S``) to drain naturally on a background
    thread, and fall back to ``os._exit(0)`` so a wedged write/flush
    can never strand the process.
    )SIGPIPESIGTERMSIGHUPSIGINTSIGBREAKNzsignal Texist_okautf-8encodingz
=== u    received · %Y-%m-%d %H:%M:%S ===
z&main-thread stack at signal delivery:
)filer   z
--- thread z (id=z) ---
r   z[gateway-signal] r0   flushc                  .    t        j                  d       y )Nr   )r   _exit r   r   
_hard_exitz_log_signal.<locals>._hard_exitz   s     	r   )_shutdown_sessionsr
   N)"getattrsignalintr   r   makedirspathdirnamer   openwritetimestrftime	tracebackprint_stack	threading_activeitemsnamejoinformat_stacksys_current_frames	ExceptionprintstderrTimerr    daemonstarttui_gateway.serverr7   exit
SystemExit)r!   frame_signal_names_attr_sigrH   f
_threadingtidthr6   timerr7   s                r   _log_signalr_   J   s   * %'MG -vud+',M#d)$- Vwvh%78D
BGGOOJ/$?*cG4 	YGGmDMM:M,N+OvV  AB%%e!4 +%--335 YR-ycU'BC	 6 6s7J7J7L7P7PQT7U VWXY	Y 
dV
$3::TB" 46
CEEL	KKM9	[	Y 	Y  8  
   	sJ   A
H C2H?H  H .H. H	H 	HH	H+*H+.H9r#   r$   r%   r'   r&   reasonc                 v   	 t        j                  t         j                  j                  t              d       t        t        dd      5 }|j                  dt        j                  d       d|  d	       d
d
d
       t        d|  t        j                  d       y
# 1 sw Y   )xY w# t        $ r Y 7w xY w)u  Record why the gateway subprocess is shutting down.

    Three exit paths (startup write fail, parse-error-response write fail,
    dispatch-response write fail, stdin EOF) all collapse into a silent
    sys.exit(0) here.  Without this trail the TUI shows "gateway exited"
    with no actionable clue about WHICH broken pipe or WHICH message
    triggered it — the main reason voice-mode turns look like phantom
    crashes when the real story is "TUI read pipe closed on this event".
    Tr(   r*   r+   r,   u   
=== gateway exit · r.   u    · reason=r/   Nz[gateway-exit] r1   )r   r<   r=   r>   r   r?   r@   rA   rB   rM   rN   rK   rO   )r`   rZ   s     r   	_log_exitrb      s    
BGGOOJ/$?*cG4 	GG(7J)K(L M#HF,	 
OF8
$3::TB	 	
  s*   A
B, ,B 8B,  B)%B, ,	B87B8timeoutc                     t         }||j                         sy	 ddlm}  ||       }|j                  |       y# t        $ r	 | | nd}Y $w xY w)a  Block until background MCP discovery finishes, up to the resolved bound.

    MCP discovery runs in a daemon thread spawned at startup (see main()) so a
    slow/dead server can't freeze ``gateway.ready``.  But the agent snapshots
    its tool list ONCE at build time and never re-reads it, so a reachable-but-
    slow server that finishes connecting *after* the first prompt would be
    invisible for the whole session.  Joining with a bounded timeout before the
    first agent build lets already-spawning servers land without re-introducing
    the startup hang: ``thread.join(timeout)`` returns the instant discovery
    completes (so fast/no-MCP startups pay ~0s), and a dead server is simply not
    waited on beyond the bound.  No-op when no discovery thread was started.

    The bound comes from ``mcp_discovery_timeout`` in config (shared with the
    CLI path via ``hermes_cli.mcp_startup``); ``timeout`` overrides it.
    Nr   )_resolve_discovery_timeoutg      ?rc   )_mcp_discovery_threadis_alivehermes_cli.mcp_startupre   rM   rI   )rc   threadre   bounds       r   wait_for_mcp_discoveryrl      sY      #F~V__.9E*73 KKK  9".D9s   < AAc                  n    t         } | | j                         ry	 ddlm}  |       S # t        $ r Y yw xY w)u  Return True if ANY background MCP discovery thread is still running.

    Used by the agent-build path to decide whether to schedule a late tool
    snapshot refresh: if discovery didn't land within the bounded
    ``wait_for_mcp_discovery`` join, the agent was built without those tools
    and the banner/tool count will be stale until they arrive.

    There are two independent discovery-thread owners by surface: the stdio
    ``hermes --tui`` path spawns ITS thread here (``_mcp_discovery_thread``),
    while the desktop app + dashboard WebSocket sidecar (``tui_gateway/ws.py``)
    and ``hermes dashboard`` spawn theirs via
    ``hermes_cli.mcp_startup.start_background_mcp_discovery``. The late-refresh
    scheduler imports this function regardless of surface, so it MUST consult
    both — checking only the entry thread left the desktop/dashboard surfaces
    with no late refresh, so a slow MCP server's tools never surfaced for the
    whole session (#51587).
    Tr   )mcp_discovery_in_flightF)rg   rh   ri   rn   rM   )rj   _startup_in_flights     r   rn   rn      sB    $ #Ffoo/	
 "## s   ( 	44c                     d}t         }|#|j                  |        |j                          }	 ddlm}  ||       }|xr |S # t
        $ r d}Y w xY w)a  Block until background MCP discovery finishes, up to ``timeout`` seconds.

    Returns True if discovery has completed (both thread owners absent or no
    longer alive), False if either is still running after the timeout. Unlike
    ``wait_for_mcp_discovery`` this accepts an unbounded/long wait and reports
    the outcome, for the off-critical-path late-refresh waiter.

    Joins both discovery-thread owners (see ``mcp_discovery_in_flight``): the
    entry thread first, then the ``hermes_cli.mcp_startup`` thread used by the
    desktop/dashboard surfaces. ``timeout`` bounds EACH join, mirroring the
    pre-#51587 single-owner behavior for the entry thread.
    Trf   r   )join_mcp_discovery)rg   rI   rh   ri   rq   rM   )rc   
entry_donerj   _startup_joinstartup_dones        r   rq   rq     sf     J"FG$**
N$W5 &,&  s   A AA_recovery_timesc                     t                	 ddlm}   |        xs i j                  d      }t	        |t
              xr t        |      dkD  }|r.dd}dd l}|j                  |dd      }|j                          |at        dd	d
dt               idd      s t        d       t        j                   d       	 t        j"                  j%                         }|st'        t(        t              sy 7|j+                         }|sJ	 t-        j.                  |      }t	        |t
              r|j                  d      nd }	t3        |      }
|
/t        |
      s$t        d|	d       t        j                   d       # t        $ r d}Y :w xY w# t,        j0                  $ r6 t        ddddd d      s t        d       t        j                   d       Y w xY w)Nr   )read_raw_configmcp_serversTc                  l    	 ddl m}   |         y # t        $ r t        j	                  dd       Y y w xY w)Nr   -_discover_mcp_tools_without_interactive_oauthz$Background MCP tool discovery failedT)exc_info)ri   r{   rM   loggerwarningrz   s    r   _discover_mcp_backgroundz&main.<locals>._discover_mcp_backgroundK  s9    	 >? :T  s     33ztui-mcp-discovery)targetrH   rQ   z2.0eventzgateway.readyskin)typepayload)jsonrpcmethodparamsz<startup write failed (broken stdout pipe before first event)iDzparse error)codemessage)r   erroridz6parse-error-response write failed (broken stdout pipe)r   z!response write failed for method=z (broken stdout pipe)r8   )r   hermes_cli.configrw   r   
isinstancedictlenrM   rE   ThreadrR   rg   r   r   rb   rK   rT   stdinreadliner   ru   r   jsonloadsJSONDecodeErrorr   )rw   _mcp_servers_has_mcp_serversr   _mcp_threading_mcp_threadr   linereqr   resps              r   mainr   +  s    , 5')/R44]C%lD9Sc,>ORS>S
 
	 	+$+++$ , 

 	 !,*7OP 
 	PQ
ii  " '	Byy{	**T"C '1d&;"}d#=fZG\]^5 K      h ## 	%6Vc:dlpqrRS		s%   A E8 F
 8FF
AGG__main__r8   )N)rc   zfloat | Noner
   N)/r   rK   hermes_bootstrapharden_import_pathr   loggingr:   rA   rC   tui_gateway._stdin_recoveryr   tui_gatewayr   rS   r   r   r   r   tui_gateway.transportr	   	getLogger__name__r}   rg   r   r   r   r    r;   r_   hasattrr#   SIG_IGNr$   r%   r'   r&   strrb   rl   boolrn   rq   ru   list__annotations__r   r5   r   r   <module>r      s   	 
  #  # # %      ;  M M .			8	$
  6   = =R Rt RF 69FMM&..&..1
69FMM&..+.
68FMM&---VZ  FMM&//;/
68FMM&--0Cc Cd C,8 >' ' '8  "e !Zz zF r   