
    Pmj(                        U d Z ddlmZ ddlZddlmZ ddlmZ  ej                    Z	da
daded<   ddZddZddd dZd!dZ	 d"ddd#dZddZd"d$dZddddd%dZdS )&z9Shared CLI/TUI-safe helpers for background MCP discovery.    )annotationsN)nullcontext)OptionalFzOptional[threading.Thread]_mcp_discovery_threadreturnboolc                     	 ddl m}   |             pi                     d          }t          |t                    ot          |          dk    S # t          $ r Y dS w xY w)zBCheap config probe so non-MCP users avoid importing the MCP stack.r   )read_raw_configmcp_serversT)hermes_cli.configr
   get
isinstancedictlen	Exception)r
   r   s     </home/thesage/.hermes/hermes-agent/hermes_cli/mcp_startup.py_has_configured_mcp_serversr      s    555555&((.B33MBB+t,,E[1A1AA1EE    tts   AA 
AAthread_namestrNonec                z    t           5  t          rt          }|"|                                r	 ddd           dS 	 ddlm}  |            pg }t          d |D                       r	 ddd           dS n# t          $ r Y ddd           dS w xY w                     d           dadadat                      s	 ddd           dS 	 ddl
m}  |            n# t          $ r dY nw xY wd fd}t          j        ||d          }|a|                                 ddd           dS # 1 swxY w Y   dS )af  Spawn one shared background MCP discovery thread for this process.

    If the first background discovery run exits without connecting any MCP
    server (for example after startup cancellation / OOM restart), later calls
    are allowed to retry instead of permanently pinning the process in a
    "discovery already started" state with zero MCP tools.
    Nr   get_mcp_statusc              3  @   K   | ]}|                     d           V  dS 	connectedNr   .0entrys     r   	<genexpr>z1start_background_mcp_discovery.<locals>.<genexpr>.   s.      BB%uyy--BBBBBB    z_Background MCP discovery previously exited with no connected servers; retrying discovery threadFT)get_hermes_home_overrider   r   c                    d } 	 ddl m}  |          } n# t          $ r d } Y nw xY w	 t                       	 ddlm}  |            pg }t          d |D                       s                    d           n'# t          $ r                     dd           Y nw xY wn'# t          $ r                     d	d           Y nw xY w| #	 dd
l m	}  ||            n# t          $ r Y nw xY wt          5  d ad d d            d S # 1 swxY w Y   d S # | #	 dd
l m	}  ||            n# t          $ r Y nw xY wt          5  d ad d d            w # 1 swxY w Y   w xY w)Nr   )set_hermes_home_overrider   c              3  @   K   | ]}|                     d           V  dS r   r   r   s     r   r!   zDstart_background_mcp_discovery.<locals>._discover.<locals>.<genexpr>X   s.      JJ%uyy55JJJJJJr"   z>Background MCP discovery completed with zero connected serversz7Failed to inspect MCP status after background discoveryTexc_infoz$Background MCP tool discovery failed)reset_hermes_home_override)hermes_constantsr%   r   -_discover_mcp_tools_without_interactive_oauthtools.mcp_toolr   anywarningdebugr)   _mcp_discovery_lockr   )tokenr%   r   statusr)   home_overrideloggers        r   	_discoverz1start_background_mcp_discovery.<locals>._discoverK   s   EEEEEEE00??   1=???k======+^--3FJJ6JJJJJ \   ! k k kLL!ZeiLjjjjjk T T TCdSSSSST $OOOOOO2259999$   ( 1 1,0)1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 $OOOOOO2259999$   ( 1 1,0)1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1s    &&B" A A: 9B" :!BB" BB" !D "!CD CD C 
C+*C+5DD	D	ED('E(
D52E4D55
E?EEEEEE)targetnamedaemonr   r   )r0   _mcp_discovery_startedr   is_aliver,   r   r-   r   r.   r   r*   r#   	threadingThreadstart)r4   r   threadr   r2   r#   r5   r3   s   `      @r   start_background_mcp_discoveryr@      s    
 M M! 	)*F!foo&7&7!	M M M M M M M M
999999'))/RBB6BBBBB M M M M M M M M   M M M M M M M MNN5   &+"$(!!%*,, 	-M M M M M M M M>	!AAAAAA4466MM 	! 	! 	! MMM	!	1 	1 	1 	1 	1 	1 	1B !
 
 

 !'[M M M M M M M M M M M M M M M M M MsX   &D0+A87D08
BD0B-D0CD0C,)D0+C,,7D00D47D4single_queryexplicit'float | None'rB   floatc               6   | | S |rdnd}|rdnd}	 ddl m}m} t          |                    ||                    }	  |            pi                     ||          }t          |          }|dk    r|n|S # t
          $ r |cY S w xY w# t
          $ r |cY S w xY w)u  Resolve the MCP discovery wait bound: explicit arg > config > default.

    Reads ``mcp_discovery_timeout`` from config.yaml, defaulting to the value in
    ``DEFAULT_CONFIG`` (single source of truth) when the key is absent. Kept lazy
    and fail-safe — a missing/invalid value or a broken config falls back to a
    short safe bound so startup can never hang or crash.

    When ``single_query`` is True (``hermes -z "..."`` / ``-q``), the larger
    ``mcp_single_query_discovery_timeout`` bound is used instead. In single-query
    mode there is only ONE turn, so the between-turns late-binding refresh never
    runs — a server that misses the small interactive bound would be invisible to
    the LLM for the whole session. The wait still returns the instant discovery
    completes (see ``wait_for_mcp_discovery``), so fast servers pay ~0s; the
    larger bound only caps how long a genuinely slow cold-start may block.
    N"mcp_single_query_discovery_timeoutmcp_discovery_timeoutg      .@g      ?r   )load_configDEFAULT_CONFIG)r   rI   rJ   rE   r   r   )	rC   rB   keyfallbackrI   rJ   defaultrawvals	            r   _resolve_discovery_timeoutrP   u   s    $  	%,,$ 
 $,ttHAAAAAAAA**399::	;==&B++C99C**C''33w. 	 	 	NNN	   s.   +B	 8A7 7BB	 BB	 	BBc                     	 ddl m}  n# t          $ r
 t          } Y nw xY w |             5  ddlm}  |             ddd           dS # 1 swxY w Y   dS )zCRun MCP discovery without letting OAuth read from the user's stdin.r   )suppress_interactive_oauth)discover_mcp_toolsN)tools.mcp_oauthrR   r   r   r,   rS   )rR   rS   s     r   r+   r+      s    1>>>>>>> 1 1 1%0"""1 
$	#	%	%  555555                 s   	 AAAtimeoutc                   t           }||                                sdS |                    t          | |                     dS )u  Wait for background MCP discovery before the first tool snapshot.

    ``thread.join(timeout)`` returns the INSTANT discovery completes, so this
    only ever blocks for the real connect time of a still-pending server —
    users with no MCP servers or fast servers pay ~0s.  The bound (from
    ``mcp_discovery_timeout`` in config) just caps the wait so a dead server
    can't freeze startup; servers that miss it are picked up by the automatic
    late-binding refresh.

    When ``single_query`` is True, the bound comes from
    ``mcp_single_query_discovery_timeout`` instead (default 15s vs 1.5s
    interactive) because one-shot sessions have no second turn to recover.
    NrA   rU   )r   r;   joinrP   )rU   rB   r?   s      r   wait_for_mcp_discoveryrY      sI      #F~V__..~
KK27VVVKWWWWWr"   c                 @    t           } | duo|                                 S )a5  Return True if THIS module's background discovery thread is still running.

    Mirrors ``tui_gateway.entry.mcp_discovery_in_flight`` for the surfaces that
    start discovery through ``start_background_mcp_discovery`` here (the desktop
    app + dashboard WebSocket sidecar via ``tui_gateway/ws.py``, and
    ``hermes dashboard``).  Those processes populate THIS module's
    ``_mcp_discovery_thread``, not ``tui_gateway.entry``'s, so the late-refresh
    scheduler must consult both to decide whether a slow server's tools are
    still pending (see #51587).
    N)r   r;   )r?   s    r   mcp_discovery_in_flightr[      s#     #F3&//"3"33r"   c                n    t           }|dS |                    |            |                                 S )al  Block until THIS module's background discovery finishes, up to ``timeout``.

    Returns True if discovery has completed (thread absent or no longer alive),
    False if it 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.
    NTrW   )r   rX   r;   )rU   r?   s     r   join_mcp_discoveryr]      s:     #F~t
KKK       r"   zcli-mcp-discovery)rU   rB   r   c                    	 t          | |           t          ||           dS # t          $ r |                     dd           Y dS w xY w)uL  Give configured MCP tools a bounded chance to register before AIAgent.

    Non-interactive first turns (``chat -q``, ``hermes -z``) can construct
    ``AIAgent`` before the normal banner or tool-list paths touch
    ``get_tool_definitions()``.  Because the agent snapshots its tool
    registry at construction time, the first and only model turn can miss
    native ``mcp__...`` tools even when the MCP server is healthy.

    ``wait_for_mcp_discovery()`` only joins an already-created discovery
    thread, so it no-ops if a direct/single-query path reaches agent
    construction before MCP startup created that thread.  This helper makes
    the construction site self-sufficient: start discovery if needed, then
    wait up to the configured bound.

    When ``single_query`` is True, the larger
    ``mcp_single_query_discovery_timeout`` bound is used (default 15s vs 1.5s
    interactive) because one-shot sessions have no second turn to recover.

    Failures are swallowed so a broken MCP config never aborts agent
    construction — the agent runs without MCP tools, same as before.
    )r4   r   )rU   rB   z7MCP discovery readiness check failed before agent buildTr'   N)r@   rY   r   r/   )r4   rU   rB   r   s       r   'ensure_mcp_discovery_before_agent_buildr_      s    8

&#	
 	
 	
 	
 	w\JJJJJJ 
 
 
E 	 	
 	
 	
 	
 	
 	

s   "& !A
A)r   r   )r   r   r   r   )rC   rD   rB   r   r   rE   r9   )N)rU   rD   rB   r   r   r   )rU   rD   r   r   )rU   rD   rB   r   r   r   r   r   )__doc__
__future__r   r<   
contextlibr   typingr   Lockr0   r:   r   __annotations__r   r@   rP   r+   rY   r[   r]   r_    r"   r   <module>rg      so   ? ? ? " " " " " "     " " " " " "      $in&&  48  8 8 8 8
 
 
 
W W W Wv 7<% % % % % %P
 
 
 
 #X<AX X X X X X,4 4 4 4! ! ! ! !$ #*&
 &
 &
 &
 &
 &
 &
 &
r"   