§
    øžyjy)  ã                  óÄ   — 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          ¦  «        rt          |¦  «        dk    rdS ddlm}  ||¦  «        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)Úhas_enabled_agent_plugin_mcp)	Úhermes_cli.configr
   ÚgetÚ
isinstanceÚdictÚlenÚhermes_cli.agent_pluginsr   Ú	Exception)r
   Ú
raw_configr   r   s       ú</home/ragecks/.hermes/hermes-agent/hermes_cli/mcp_startup.pyÚ_has_configured_mcp_serversr      s¬   € ðØ5Ð5Ð5Ð5Ð5Ð5à$�_Ñ&Ô&Ð,¨"ˆ
Ø —n’n ]Ñ3Ô3ˆÝ�k¥4Ñ(Ô(ð 	­S°Ñ-=Ô-=ÀÒ-AÐ-AØ�4ØIÐIÐIÐIÐIÐIà+Ð+¨JÑ7Ô7Ð7øÝð ð ð ð ˆtˆtðøøøs   ‚AA$ ÁA$ Á$
A2Á1A2Úthread_nameÚstrÚNonec                óz  ‡ ‡— t           5  t          r›t          }|�"|                     ¦   «         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>3   s.   è è € ÐBÐB°%�u—y’y Ñ-Ô-Ð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>]   s.   è è € ÐJÐJ¸%˜uŸyšy¨Ñ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>._discoverP   sÅ  ø€ ØˆEðØEÐEÐEÐEÐEÐEà0Ð0°Ñ?Ô?��øÝð ð ð Ø���ðøøøð1Ý=Ñ?Ô?Ð?ðkØ=Ð=Ð=Ð=Ð=Ð=Ø+˜^Ñ-Ô-Ð3°�FÝÐJÐJÀ6ÐJÑJÔJÑJÔJð ØŸšØ\ñô ð øøõ !ð kð kð kØ—L’LÐ!ZÐei�LÑjÔjÐjÐjÐjðkøøøøøåð Tð Tð TØ—’ÐCÈd�ÑSÔSÐSÐSÐSðTøøøð Ð$ðØOÐOÐOÐOÐOÐOà2Ð2°5Ñ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Ð2°5Ñ9Ô9Ð9Ð9øÝ$ð ð ð Ø˜ðøøøå(ð 1ð 1à,0Ð)ð1ð 1ð 1ñ 1ô 1ð 1ð 1ð 1ð 1ð 1ð 1øøøð 1ð 1ð 1ð 1ð 1øøøsÅ   … —&¥&ª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   )r3   Ú_mcp_discovery_startedr   Úis_aliver/   r   r0   r   r1   r   r-   r&   Ú	threadingÚThreadÚstart)r7   r   Úthreadr   r5   r&   r8   r6   s   `      @r   Ústart_background_mcp_discoveryrC       s¾  øø€ õ 
ð Mð MÝ!ð 	)Ý*ˆFØÐ! f§o¢oÑ&7Ô&7Ð!Øð	Mð Mð Mñ Mô Mð Mð Mð Mð
Ø9Ð9Ð9Ð9Ð9Ð9à'˜Ñ)Ô)Ð/¨R�ÝÐBÐB¸6ÐBÑBÔBÑBÔBð ØðMð Mð Mñ Mô Mð Mð Mð Mðøåð ð ð ØðMð Mð Mñ Mô Mð Mð Mð Mðøøøà�NŠNð5ñô ð ð &+Ð"Ø$(Ð!à!%ÐÝ*Ñ,Ô,ð 	Øð-Mð Mð Mñ Mô Mð Mð Mð Mð>	!ØAÐAÐAÐAÐAÐAà4Ð4Ñ6Ô6ˆMˆMøÝð 	!ð 	!ð 	!Ø ˆMˆMˆMð	!øøøð	1ð 	1ð 	1ð 	1ð 	1ð 	1ð 	1õB Ô!ØØØð
ñ 
ô 
ˆð
 !'ÐØ�Š‰Œˆð[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'rE   Ú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   rL   rM   rH   r   r   )	rF   rE   ÚkeyÚfallbackrL   rM   ÚdefaultÚrawÚvals	            r   Ú_resolve_discovery_timeoutrS   z   sû   € ð$ ÐØˆð ð	%Ð,Ð,à$ð ð
 $Ð,ˆtˆt¨€HðØAÐAÐAÐAÐAÐAÐAÐAå˜×*Ò*¨3°Ñ9Ô9Ñ:Ô:ˆð	Ø�;‘=”=Ð& B×+Ò+¨C°Ñ9Ô9ˆCÝ˜‘*”*ˆCØ š'˜'�3�3 wÐ.øÝð 	ð 	ð 	ØˆNˆNˆNð	øøøøåð ð ð Øˆˆˆðøøø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_oauthrU   r   r   r/   rV   )rU   rV   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.
    NrD   ©rX   )r   r>   ÚjoinrS   )rX   rE   rB   s      r   Úwait_for_mcp_discoveryr\   ¯   sI   € õ  #€FØ€~˜VŸ_š_Ñ.Ô.€~ØˆØ
‡K‚KÕ2°7ÈÐVÑVÔV€KÑ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>   )rB   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.
    NTrZ   )r   r[   r>   )rX   rB   s     r   Újoin_mcp_discoveryr`   Ô   s:   € õ #€FØ€~ØˆtØ
‡K‚K˜€KÑ Ô Ð Ø�ŠÑ Ô Ð Ð r%   zcli-mcp-discovery)rX   rE   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.
    )r7   r   )rX   rE   z7MCP discovery readiness check failed before agent buildTr*   N)rC   r\   r   r2   )r7   rX   rE   r   s       r   Ú'ensure_mcp_discovery_before_agent_buildrb   ã   sŠ   € ð8

Ý&ØØ#ð	
ñ 	
ô 	
ð 	
õ 	 w¸\ÐJÑJÔJÐJÐJÐJøÝð 
ð 
ð 
Ø�ŠØEØð 	ñ 	
ô 	
ð 	
ð 	
ð 	
ð 	
ð
øøøs   ‚"& ¦!AÁ
A)r   r   )r   r   r   r   )rF   rG   rE   r   r   rH   r<   )N)rX   rG   rE   r   r   r   )rX   rG   r   r   )rX   rG   rE   r   r   r   r   r   )Ú__doc__Ú
__future__r   r?   Ú
contextlibr   Útypingr   ÚLockr3   r=   r   Ú__annotations__r   rC   rS   r.   r\   r^   r`   rb   © r%   r   ú<module>rj      so  ðØ ?Ð ?Ð ?à "Ð "Ð "Ð "Ð "Ð "à Ð Ð Ð Ø "Ð "Ð "Ð "Ð "Ð "Ø Ð Ð Ð Ð Ð à$�i”nÑ&Ô&Ð ØÐ Ø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%   