
    ,jC                   B   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
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mZmZmZmZmZ ddlmZ ddlmZ dd	lmZ dd
l m!Z!m"Z" dXdZ#	 ddl$Z$n# e%$ r dZ$Y nw xY w ej&        e'          Z( e	j)        dd          *                                +                                dv a,da-dYdZdZ. e.             h dZ/de0d<   dZ1dZ2d[dZ3d\dZ4d]d!Z5h d"Z6de0d#<   e G d$ d%                      Z7e G d& d'                      Z8 G d( d)          Z9 G d* d+          Z:da;d,e0d-<   d^d.Z<dYdZd/Z=d_d4Z>d`d6Z?dad7Z@dbd8ZA ejB                    ZC	 dcddd=ZDded>ZE	 	 	 	 	 	 dfdgdJZFdYdhdKZGdL ZHdidNZIdOZJdjdQZKdkdSZLdldUZMdmdWZNdS )nu  
Hermes Plugin System
====================

Discovers, loads, and manages plugins from four sources:

1. **Bundled plugins** – ``<repo>/plugins/<name>/`` (shipped with hermes-agent;
   ``memory/`` and ``context_engine/`` subdirs are excluded — they have their
   own discovery paths)
2. **User plugins**   – ``~/.hermes/plugins/<name>/``
3. **Project plugins** – ``./.hermes/plugins/<name>/`` (opt-in via
   ``HERMES_ENABLE_PROJECT_PLUGINS``)
4. **Pip plugins**     – packages that expose the ``hermes_agent.plugins``
   entry-point group.

Later sources override earlier ones on name collision, so a user or project
plugin with the same name as a bundled plugin replaces it.

Each directory plugin must contain a ``plugin.yaml`` manifest **and** an
``__init__.py`` with a ``register(ctx)`` function.

Lifecycle hooks
---------------
Plugins may register callbacks for any of the hooks in ``VALID_HOOKS``.
The agent core calls ``invoke_hook(name, **kwargs)`` at the appropriate
points.

Tool registration
-----------------
``PluginContext.register_tool()`` delegates to ``tools.registry.register()``
so plugin-defined tools appear alongside the built-in tools.
    )annotationsN)	dataclassfield)Path)AnyCallableDictListOptionalSetUnion)get_hermes_homeenv_var_enabled)cfg_get)OBSERVER_SCHEMA_VERSIONVALID_MIDDLEWAREreturnr   c                     t          j        d          } | rt          |           S t          t                                                    j        j        dz  S )zLocate the bundled ``plugins/`` directory.

    Honours ``HERMES_BUNDLED_PLUGINS`` (set by the Nix wrapper / packaged
    installs) so read-only store paths are consulted first.  Falls back to
    the in-repo path used during development.
    HERMES_BUNDLED_PLUGINSplugins)osgetenvr   __file__resolveparent)env_overrides    7/home/ubuntu/.hermes/hermes-agent/hermes_cli/plugins.pyget_bundled_plugins_dirr   7   sM     9566L "L!!!>>!!##*1I==    HERMES_PLUGINS_DEBUG >   1onyestrueFforceboolNonec                4   | r;t          j        dd                                                                          dv at          rt
          rdS t          j        t          j	                  }|
                    t          j                   |                    t          j        d                     t                              |           t          
                    t          j                   dt          _        dat                              d           dS )zWhen HERMES_PLUGINS_DEBUG is on, tee plugin logs to stderr at DEBUG.

    Idempotent: only attaches the handler once per process unless ``force``
    is passed. Does not touch the root logger or other Hermes loggers.
    r!   r"   >   r#   r$   r%   r&   Nz#[plugins] %(levelname)s %(message)sTuC   HERMES_PLUGINS_DEBUG=1 — verbose plugin discovery logging enabled)r   r   striplower_PLUGINS_DEBUG_DEBUG_HANDLER_INSTALLEDloggingStreamHandlersysstderrsetLevelDEBUGsetFormatter	Formatterlogger
addHandler	propagatedebug)r'   handlers     r   _install_plugin_debug_handlerr<   _   s      
#92>>DDFFLLNN S
 
  5 #CJ//GW]###*+PQQRRR
g
OOGM""" F#
LLM    r    >   pre_llm_callpost_llm_callpre_tool_callsubagent_stopon_session_endpost_tool_callsubagent_startpre_api_requeston_session_reseton_session_startpost_api_requestapi_request_erroron_session_finalizepre_approval_requestpre_gateway_dispatchtransform_llm_outputtransform_tool_resultpost_approval_responsetransform_terminal_outputzSet[str]VALID_HOOKSzhermes_agent.pluginshermes_pluginsnamestrc                     t          |           S )z<Return True when an env var is set to a truthy opt-in value.r   )rR   s    r   _env_enabledrU      s    4   r    setc                     	 ddl m}   |             }t          |ddg           }t          |t                    rt          |          nt                      S # t          $ r t                      cY S w xY w)zRead the disabled plugins list from config.yaml.

    Kept for backward compat and explicit deny-list semantics. A plugin
    name in this set will never load, even if it appears in
    ``plugins.enabled``.
    r   load_configr   disabled)default)hermes_cli.configrY   r   
isinstancelistrV   	Exception)rY   configrZ   s      r   _get_disabled_pluginsra      s    11111169j"EEE *8T : :Es8}}}E   uus   AA A21A2Optional[set]c                     	 ddl m}   |             }|                    d          }t          |t                    sdS d|vrdS |                    d          }t          |t
                    sdS t          |          S # t          $ r Y dS w xY w)uL  Read the enabled-plugins allow-list from config.yaml.

    Plugins are opt-in by default — only plugins whose name appears in
    this set are loaded. Returns:

    * ``None`` — the key is missing or malformed. Callers should treat
      this as "nothing enabled yet" (the opt-in default); the first
      ``migrate_config`` run populates the key with a grandfathered set
      of currently-installed user plugins so existing setups don't
      break on upgrade.
    * ``set()`` — an empty list was explicitly set; nothing loads.
    * ``set(...)`` — the concrete allow-list.
    r   rX   r   Nenabled)r\   rY   getr]   dictr^   rV   r_   )rY   r`   plugins_cfgrd   s       r   _get_enabled_pluginsrh      s    111111jj+++t,, 	4K''4//),,'4(( 	47||   tts"   :A? A? *A? 0A? ?
BB>   backendplatform	exclusive
standalonemodel-provider_VALID_PLUGIN_KINDSc                      e Zd ZU dZded<   dZded<   dZded<   dZded<    ee	          Z
d	ed
<    ee	          Zded<    ee	          Zded<   dZded<   dZded<   dZded<   dZded<   dS )PluginManifestz0Parsed representation of a plugin.yaml manifest.rS   rR   r"   versiondescriptionauthordefault_factoryz List[Union[str, Dict[str, Any]]]requires_env	List[str]provides_toolsprovides_hookssourceNOptional[str]pathrl   kindkey)__name__
__module____qualname____doc____annotations__rq   rr   rs   r   r^   rv   rx   ry   rz   r|   r}   r~    r    r   rp   rp      s         ::IIIGKF5:U45P5P5PLPPPP %d ; ; ;N;;;; %d ; ; ;N;;;;FD  D CMMMMMMr    rp   c                      e Zd ZU dZded<   dZded<    ee          Zded	<    ee          Z	ded
<    ee          Z
ded<    ee          Zded<   dZded<   dZded<   dS )LoadedPluginz)Runtime state for a single loaded plugin.rp   manifestNzOptional[types.ModuleType]modulert   rw   tools_registeredhooks_registeredmiddleware_registeredcommands_registeredFr(   rd   r{   error)r   r   r   r   r   r   r   r^   r   r   r   r   rd   r   r   r    r   r   r     s         33)-F----"'%"="="====="'%"="="=====',uT'B'B'BBBBB%*U4%@%@%@@@@@GEr    r   c                      e Zd ZdZdKdZedLd	            Z	 	 	 	 	 	 dMdNdZdOdPd"Z	 	 dQdRd&Z		 	 dSdTd(Z
dUd+ZdVd,ZdVd-ZdVd.ZdVd/ZdVd0ZdVd1ZdVd2ZdVd3Z	 	 	 dWdXd:ZdYd=Zd
d>dZdCZd[dEZd\dGZ	 d]d^dJZd
S )_PluginContextz=Facade given to plugins so they can register tools and hooks.r   rp   manager'PluginManager'c                0    || _         || _        d | _        d S N)r   _manager_llm)selfr   r   s      r   __init__zPluginContext.__init__%  s     			r    r   r   c                |    | j         /ddlm} | j        j        p| j        j        } ||          | _         | j         S )a  Return the plugin's :class:`agent.plugin_llm.PluginLlm` facade.

        Lets trusted plugins run host-owned chat or structured completions
        against the user's active model and auth without bringing their
        own provider keys. Override capability (model, agent id, auth
        profile) is fail-closed by default and gated through
        ``plugins.entries.<plugin_id>.llm.*`` config keys.

        See :mod:`agent.plugin_llm` for the full surface.Nr   )	PluginLlm)	plugin_id)r   agent.plugin_llmr   r   r~   rR   )r   r   r   s      r   llmzPluginContext.llm-  sN     9222222)?T]-?I!	I666DIyr    NFr"   rR   rS   toolsetschemarf   r;   r   check_fnCallable | Nonerv   list | Noneis_asyncr(   rr   emojioverrider)   c                    ddl m} |                    |||||||||	|

  
         | j        j                            |           t                              d| j        j	        ||
rdnd           dS )aw  Register a tool in the global registry **and** track it as plugin-provided.

        Pass ``override=True`` to replace an existing built-in tool with the
        same name (e.g. swap the default ``browser_navigate`` for a custom
        CDP-backed implementation). Without it, attempting to register a name
        already claimed by a different toolset is rejected.
        r   registry)
rR   r   r   r;   r   rv   r   rr   r   r   zPlugin %s registered tool: %s%sz (override)r"   N)
tools.registryr   registerr   _plugin_tool_namesaddr7   r:   r   rR   )r   rR   r   r   r;   r   rv   r   rr   r   r   r   s               r   register_toolzPluginContext.register_tool@  s    ( 	,+++++%# 	 	
 	
 	
 	(,,T222-Mx&GmmR	
 	
 	
 	
 	
r    usercontentrolec                   | j         j        }|t                              d           dS |dk    r|nd| d| }t	          |dd          r|j                            |           n|j                            |           dS )	a  Inject a message into the active conversation.

        If the agent is idle (waiting for user input), this starts a new turn.
        If the agent is running, this interrupts and injects the message.

        This enables plugins (e.g. remote control viewers, messaging bridges)
        to send messages into the conversation from external sources.

        Returns True if the message was queued successfully.
        Nz@inject_message: no CLI reference (not available in gateway mode)Fr   [z] _agent_runningT)r   _cli_refr7   warninggetattr_interrupt_queueput_pending_input)r   r   r   climsgs        r   inject_messagezPluginContext.inject_messagej  s     m$;NN]^^^5gg-B-B-B-B-B3(%00 	( $$S)))) ""3'''tr    helpsetup_fn
handler_fnc                    |||||| j         j        d| j        j        |<   t                              d| j         j        |           dS )a  Register a CLI subcommand (e.g. ``hermes honcho ...``).

        The *setup_fn* receives an argparse subparser and should add any
        arguments/sub-subparsers.  If *handler_fn* is provided it is set
        as the default dispatch function via ``set_defaults(func=...)``.)rR   r   rr   r   r   pluginz$Plugin %s registered CLI command: %sN)r   rR   r   _cli_commandsr7   r:   )r   rR   r   r   r   rr   s         r   register_cli_commandz"PluginContext.register_cli_command  sV     & $m(-
 -
#D) 	;T]=OQUVVVVVr    	args_hintc                <   |                                                                                     d                              dd          }|s't                              d| j        j                   dS 	 ddlm	}  ||          (t                              d| j        j        |           dS n# t          $ r Y nw xY w||pd	| j        j        |pd
                                d| j        j        |<   t                              d| j        j        |           dS )u  Register a slash command (e.g. ``/lcm``) available in CLI and gateway sessions.

        The handler signature is ``fn(raw_args: str) -> str | None``.
        It may also be an async callable — the gateway dispatch handles both.

        Unlike ``register_cli_command()`` (which creates ``hermes <subcommand>``
        terminal commands), this registers in-session slash commands that users
        invoke during a conversation.

        ``args_hint`` is an optional short string (e.g. ``"<file>"`` or
        ``"dias:7 formato:json"``) used by gateway adapters to surface the
        command with an argument field — for example Discord's native slash
        command picker. Plugin commands without ``args_hint`` register as
        parameterless in Discord and still accept trailing text when invoked
        as free-form chat.

        Names conflicting with built-in commands are rejected with a warning.
        / -z;Plugin '%s' tried to register a command with an empty name.Nr   )resolve_commandz^Plugin '%s' tried to register command '/%s' which conflicts with a built-in command. Skipping.zPlugin commandr"   )r;   rr   r   r   z!Plugin %s registered command: /%s)r,   r+   lstripreplacer7   r   r   rR   hermes_cli.commandsr   r_   r   _plugin_commandsr:   )r   rR   r;   rr   r   cleanr   s          r   register_commandzPluginContext.register_command  sH   2 

""$$++C0088cBB 	NNM"   F
	;;;;;;u%%19M&  
  2  	 	 	D	 &:*:m(#/r0022	1
 1
&u- 	8$-:LeTTTTTs   87B2 2
B?>B?	tool_nameargsc                    ddl m} d|vr(| j        j        }|rt	          |dd          nd}|||d<    |j        ||fi |S )u  Dispatch a tool call through the registry, with parent agent context.

        This is the public interface for plugin slash commands that need to call
        tools like ``delegate_task`` without reaching into framework internals.
        The parent agent (if available) is resolved automatically — plugins never
        need to access the agent directly.

        Args:
            tool_name: Registry name of the tool (e.g. ``"delegate_task"``).
            args: Tool arguments dict (same as what the model would pass).
            **kwargs: Extra keyword args forwarded to the registry dispatch.

        Returns:
            JSON string from the tool handler (same format as model tool calls).
        r   r   parent_agentagentN)r   r   r   r   r   dispatch)r   r   r   kwargsr   r   r   s          r   dispatch_toolzPluginContext.dispatch_tool  su      	,+++++
 ''-(C36@GC$///DE ).~& x D;;F;;;r    c                T   | j         j        't                              d| j        j                   dS ddlm} t          ||          s't                              d| j        j                   dS || j         _        t          	                    d| j        j        |j                   dS )a%  Register a context engine to replace the built-in ContextCompressor.

        Only one context engine plugin is allowed. If a second plugin tries
        to register one, it is rejected with a warning.

        The engine must be an instance of ``agent.context_engine.ContextEngine``.
        NzyPlugin '%s' tried to register a context engine, but one is already registered. Only one context engine plugin is allowed.r   )ContextEnginezbPlugin '%s' tried to register a context engine that does not inherit from ContextEngine. Ignoring.z)Plugin '%s' registered context engine: %s)
r   _context_enginer7   r   r   rR   agent.context_enginer   r]   info)r   enginer   s      r   register_context_enginez%PluginContext.register_context_engine  s     =(4NNQ"  
 F666666&-00 	NN8"  
 F(.%7M	
 	
 	
 	
 	
r    c                    ddl m} ddlm} t	          ||          s't
                              d| j        j                   dS  ||           t
          	                    d| j        j        |j                   dS )a=  Register an image generation backend.

        ``provider`` must be an instance of
        :class:`agent.image_gen_provider.ImageGenProvider`. The
        ``provider.name`` attribute is what ``image_gen.provider`` in
        ``config.yaml`` matches against when routing ``image_generate``
        tool calls.
        r   )ImageGenProviderregister_providerzjPlugin '%s' tried to register an image_gen provider that does not inherit from ImageGenProvider. Ignoring.Nz-Plugin '%s' registered image_gen provider: %s)
agent.image_gen_providerr   agent.image_gen_registryr   r]   r7   r   r   rR   r   )r   providerr   r   s       r   register_image_gen_providerz)PluginContext.register_image_gen_provider  s     	>=====>>>>>>($455 	NN?"  
 F(###;M	
 	
 	
 	
 	
r    c           
        ddl m}m} t          ||          s't                              d| j        j                   dS 	  ||           nU# t          t          f$ rA}t                              d| j        j        t          |dd          |           Y d}~dS d}~ww xY wt                              d| j        j        |j        |j                   dS )	u$  Register a dashboard authentication provider.

        ``provider`` must be an instance of
        :class:`hermes_cli.dashboard_auth.DashboardAuthProvider`. Used by
        the dashboard OAuth auth gate, which engages when the dashboard
        binds to a non-loopback host without ``--insecure``.

        Misbehaving providers (wrong type, duplicate name) are logged at
        WARNING and silently ignored — never raised — so a broken plugin
        cannot crash the host. Same convention as
        ``register_image_gen_provider``.
        r   )DashboardAuthProviderr   zsPlugin '%s' tried to register a dashboard-auth provider that does not inherit from DashboardAuthProvider. Ignoring.Nz=Plugin '%s' failed to register dashboard-auth provider %r: %srR   ?z7Plugin '%s' registered dashboard-auth provider: %s (%s))hermes_cli.dashboard_authr   r   r]   r7   r   r   rR   	TypeError
ValueErrorr   r   display_name)r   r   r   r   es        r    register_dashboard_auth_providerz.PluginContext.register_dashboard_auth_provider1  s   	
 	
 	
 	
 	
 	
 	
 	
 ($9:: 	NNN"  
 F	h'''':& 	 	 	NN"GHfc$B$BA  
 FFFFF	 	EMx/D	
 	
 	
 	
 	
s   A B6BBc                    ddl m} ddlm} t	          ||          s't
                              d| j        j                   dS  ||           t
          	                    d| j        j        |j                   dS )a<  Register a video generation backend.

        ``provider`` must be an instance of
        :class:`agent.video_gen_provider.VideoGenProvider`. The
        ``provider.name`` attribute is what ``video_gen.provider`` in
        ``config.yaml`` matches against when routing ``video_generate``
        tool calls.
        r   )VideoGenProviderr   ziPlugin '%s' tried to register a video_gen provider that does not inherit from VideoGenProvider. Ignoring.Nz-Plugin '%s' registered video_gen provider: %s)
agent.video_gen_providerr   agent.video_gen_registryr   r]   r7   r   r   rR   r   )r   r   r   _register_video_providers       r   register_video_gen_providerz)PluginContext.register_video_gen_providerY  s     	>=====ZZZZZZ($455 	NN?"  
 F  ***;M	
 	
 	
 	
 	
r    c                    ddl m} ddlm} t	          ||          s't
                              d| j        j                   dS  ||           t
          	                    d| j        j        |j                   dS )a  Register a web search/extract backend.

        ``provider`` must be an instance of
        :class:`agent.web_search_provider.WebSearchProvider`. The
        ``provider.name`` attribute is what ``web.search_backend`` /
        ``web.extract_backend`` / ``web.backend`` in ``config.yaml``
        matches against when routing ``web_search`` / ``web_extract``
        tool calls.
        r   )WebSearchProviderr   zdPlugin '%s' tried to register a web provider that does not inherit from WebSearchProvider. Ignoring.Nz'Plugin '%s' registered web provider: %s)
agent.web_search_providerr   agent.web_search_registryr   r]   r7   r   r   rR   r   )r   r   r   _register_web_providers       r   register_web_search_providerz*PluginContext.register_web_search_providert  s     	@?????YYYYYY($566 	NN@"  
 Fx(((5M	
 	
 	
 	
 	
r    c                    ddl m} ddlm} t	          ||          s't
                              d| j        j                   dS  ||           t
          	                    d| j        j        |j                   dS )uQ  Register a cloud browser backend.

        ``provider`` must be an instance of
        :class:`agent.browser_provider.BrowserProvider`. The
        ``provider.name`` attribute is what ``browser.cloud_provider`` in
        ``config.yaml`` matches against when routing cloud-mode
        ``browser_*`` tool calls.

        Mirrors :meth:`register_web_search_provider` exactly — same
        registration shape, same gating, same logging. The browser
        subsystem's dispatcher (:func:`tools.browser_tool._get_cloud_provider`)
        consults the registry built up by these calls.
        r   )BrowserProviderr   zfPlugin '%s' tried to register a browser provider that does not inherit from BrowserProvider. Ignoring.Nz+Plugin '%s' registered browser provider: %s)
agent.browser_providerr   agent.browser_registryr   r]   r7   r   r   rR   r   )r   r   r   _register_browser_providers       r   register_browser_providerz'PluginContext.register_browser_provider  s     	;:::::ZZZZZZ(O44 	NN>"  
 F""8,,,9M	
 	
 	
 	
 	
r    c                    ddl m} ddlm} t	          ||          s't
                              d| j        j                   dS  ||           t
          	                    d| j        j        |j                   dS )u|  Register a text-to-speech backend.

        ``provider`` must be an instance of
        :class:`agent.tts_provider.TTSProvider`. The ``provider.name``
        attribute is what ``tts.provider`` in ``config.yaml`` matches
        against when routing ``text_to_speech`` tool calls — **but
        only when**:

        1. ``provider.name`` is NOT a built-in TTS provider name
           (``edge``, ``openai``, ``elevenlabs``, …). Built-ins always
           win — the registry rejects shadowing names with a warning.
        2. There is NO ``tts.providers.<name>: type: command`` entry
           with the same name. Command-providers (PR #17843) win on
           name collision because config is more local than plugin
           install.

        Coexists with the command-provider registry rather than
        replacing it — see issue #30398 for the full design rationale.
        r   )TTSProviderr   z^Plugin '%s' tried to register a TTS provider that does not inherit from TTSProvider. Ignoring.Nz'Plugin '%s' registered TTS provider: %s)
agent.tts_providerr  agent.tts_registryr   r]   r7   r   r   rR   r   )r   r   r  _register_tts_providers       r   register_tts_providerz#PluginContext.register_tts_provider  s    ( 	322222RRRRRR(K00 	NN:"  
 Fx(((5M	
 	
 	
 	
 	
r    c                    ddl m} ddlm} t	          ||          s't
                              d| j        j                   dS  ||           t
          	                    d| j        j        |j                   dS )u  Register a speech-to-text backend.

        ``provider`` must be an instance of
        :class:`agent.transcription_provider.TranscriptionProvider`.
        The ``provider.name`` attribute is what ``stt.provider`` in
        ``config.yaml`` matches against when routing
        :func:`tools.transcription_tools.transcribe_audio` calls —
        **but only when**:

        1. ``provider.name`` is NOT a built-in STT provider name
           (``local``, ``local_command``, ``groq``, ``openai``,
           ``mistral``, ``xai``). Built-ins always win — the registry
           rejects shadowing names with a warning.
        2. There is NO ``stt.providers.<name>: type: command`` entry
           with the same name. Command-providers win on name
           collision because config is more local than plugin install
           — same precedence rule as TTS.

        Coexists with the in-tree dispatcher and the STT
        command-provider registry rather than replacing them. The 6
        built-in STT backends keep their native implementations in
        ``tools/transcription_tools.py``; this hook is for *new* Python
        engines (OpenRouter, SenseAudio, Gemini-STT, custom proprietary
        backends).
        r   )TranscriptionProviderr   zrPlugin '%s' tried to register a transcription provider that does not inherit from TranscriptionProvider. Ignoring.Nz1Plugin '%s' registered transcription provider: %s)
agent.transcription_providerr  agent.transcription_registryr   r]   r7   r   r   rR   r   )r   r   r  _register_stt_providers       r   register_transcription_providerz-PluginContext.register_transcription_provider  s    4 	GFFFFF\\\\\\($9:: 	NNI"  
 Fx(((?M	
 	
 	
 	
 	
r    labeladapter_factoryvalidate_configrequired_envinstall_hintentry_kwargsc                0   ddl m}	m}
 |                    d| j        j                    |
d||||||pg |dd|}|	                    |           | j        j        	                    |           t                              d| j        j        |           dS )	u  Register a gateway platform adapter.

        The adapter_factory receives a ``PlatformConfig`` and returns a
        ``BasePlatformAdapter`` subclass instance.  The gateway calls
        ``check_fn()`` before instantiation to verify dependencies.

        Extra keyword arguments are forwarded to ``PlatformEntry`` (e.g.
        ``setup_fn``, ``emoji``, ``allowed_users_env``, ``platform_hint``).
        Unknown keys raise TypeError from the dataclass constructor.

        Example::

            ctx.register_platform(
                name="irc",
                label="IRC",
                adapter_factory=lambda cfg: IRCAdapter(cfg),
                check_fn=lambda: True,
                emoji="💬",
                setup_fn=irc_interactive_setup,
            )
        r   )platform_registryPlatformEntryplugin_namer   )rR   r  r  r   r  r  r  rz   z!Plugin %s registered platform: %sNr   )gateway.platform_registryr  r  
setdefaultr   rR   r   r   _plugin_platform_namesr   r7   r:   )r   rR   r  r  r   r  r  r  r  r  r  entrys               r   register_platformzPluginContext.register_platform  s    @ 	ONNNNNNNt}/ABBB 

++%+%

 

 

 

 	""5))),00666/M	
 	
 	
 	
 	
r    	action_idcallbackc                   t          |          st          d| j        j         d          |)t	          |t
                    r1|                                st          d| j        j         d          | j        j        	                    ||| j        j        f           t                              d| j        j        |           dS )u  Register a Slack Block Kit action handler from a plugin.

        Hermes' Slack adapter wires registered handlers into its
        ``slack_bolt.AsyncApp`` at connect time. The callback is invoked
        when a user clicks a button (or interacts with another Block Kit
        action element) whose ``action_id`` matches.

        Callback signature follows the slack_bolt convention::

            async def handler(ack, body, action) -> None:
                await ack()  # required, within 3 seconds
                ...

        Args:
            action_id: Whatever ``slack_bolt.App.action()`` accepts —
                a literal ``action_id`` string, a compiled ``re.Pattern``
                for matching multiple ids, or a constraint dict
                (e.g. ``{"action_id": "...", "block_id": "..."}``).
            callback: Async callable receiving ``(ack, body, action)``.

        Raises:
            ValueError: if ``callback`` is not callable, or ``action_id``
                is empty/None.

        Example::

            async def _on_approve(ack, body, action):
                await ack()
                # apply some workflow keyed on action["value"]

            ctx.register_slack_action_handler("inbox_sweep_approve", _on_approve)
        Plugin 'zH' tried to register a Slack action handler with a non-callable callback.NzC' tried to register a Slack action handler with an empty action_id.z-Plugin %s registered Slack action handler: %s)callabler   r   rR   r]   rS   r+   r   _slack_action_handlersappendr7   r:   )r   r  r  s      r   register_slack_action_handlerz+PluginContext.register_slack_action_handler:  s    J !! 	@4=- @ @ @   Is!;!;IOODUDU;4=- ; ; ;   	,33$-"45	
 	
 	
 	;M	
 	
 	
 	
 	
r    )defaultsr~   r   r$  Optional[Dict[str, Any]]c               D   |rt          |t                    st          d| j        j         d|          t          d |D                       s t          d| j        j         d|d          ddlm} d |D             }||v r0t          d| j        j         d	|d
| j        j         d| d	          | j        j	        
                    |          }|Y|
                    d          | j        j        k    r6t          d| j        j         d	|d|
                    d           d          dddddi d}|r|                                D ]
\  }	}
|
||	<   ||||| j        j        d| j        j	        |<   t                              d| j        j        ||           dS )u  Register a plugin-defined auxiliary LLM task.

        Auxiliary tasks are LLM-backed side jobs (vision analysis, web extraction,
        compression, smart-approval, etc.) that route through ``auxiliary_client.py``.
        Each task has its own ``auxiliary.<key>`` config block where users can
        pin a provider/model independent of the main chat model.

        Plugins use this to declare their own auxiliary tasks without touching
        core files. After registration, the task:

          - Appears in the ``hermes model → Configure auxiliary models`` picker
          - Has its provider/model/base_url/api_key bridged from config.yaml to
            ``AUXILIARY_<KEY_UPPER>_*`` env vars at gateway startup
          - Gets default routing fields (provider="auto", model="", etc.) merged
            into loaded configs so ``cfg.get("auxiliary", {}).get(key)`` works

        Args:
            key: stable task key (snake_case). Used in config ``auxiliary.<key>``
                and env vars ``AUXILIARY_<KEY_UPPER>_*``. Must not shadow a
                built-in task key (vision, compression, web_extract, approval,
                mcp, title_generation, skills_hub, curator).
            display_name: human-readable name shown in the picker.
            description: short one-line description shown next to the name.
            defaults: optional dict of default routing fields. Recognized keys:
                ``provider`` (default "auto"), ``model`` (default ""),
                ``base_url`` (default ""), ``api_key`` (default ""),
                ``timeout`` (default 60), ``extra_body`` (default {}),
                plus any task-specific extras (e.g. ``download_timeout``).
                Unknown keys are preserved verbatim — the plugin owns the
                schema for its own task.

        Raises:
            ValueError: if *key* is empty, contains invalid characters, or
                shadows a built-in auxiliary task key.

        Example:
            ctx.register_auxiliary_task(
                key="memory_retain_filter",
                display_name="Memory retain filter",
                description="hindsight pre-retain dedup/extract",
                defaults={"provider": "auto", "timeout": 30},
            )
        r  z4' tried to register auxiliary task with invalid key c              3  J   K   | ]}|                                 p|d k    V  dS )_N)isalnum).0cs     r   	<genexpr>z8PluginContext.register_auxiliary_task.<locals>.<genexpr>  s3      88q199;;*!s(888888r    z' auxiliary task key z: must contain only alphanumeric characters and underscoresr   )
_AUX_TASKSc                    h | ]\  }}}|	S r   r   )r*  k_name_descs       r   	<setcomp>z8PluginContext.register_auxiliary_task.<locals>.<setcomp>  s    DDDoaDDDr    z!' cannot register auxiliary task uS    — that key is reserved for a built-in task. Pick a plugin-namespaced key (e.g. 'r(  z').Nr   u#    — already registered by plugin ''autor"   <   )r   modelbase_urlapi_keytimeout
extra_body)r~   r   rr   r$  r   z,Plugin %s registered auxiliary task: %s (%s))r]   rS   r   r   rR   allhermes_cli.mainr-  r   
_aux_tasksre   itemsr7   r:   )r   r~   r   rr   r$  _BUILTIN_AUX_TASKSbuiltin_keysexistingmerged_defaultsr/  vs              r   register_auxiliary_taskz%PluginContext.register_auxiliary_taskv  s   h  	*S#.. 	,4=- , ,$', ,   88C88888 	M4=- M MC M M M   	EDDDDDDD1CDDD,U4=- U UU U7;}7IU ULOU U U   =+//44HLL$:$:dm>P$P$P.4=- . .. .LL**. . .   +
 +
  	' (( ' '1%&"" (&'m()
 )
 % 	:M		
 	
 	
 	
 	
r    	hook_namec           
     b   |t           vrLt                              d| j        j        |d                    t          t                                          | j        j        	                    |g           
                    |           t                              d| j        j        |           dS )zRegister a lifecycle hook callback.

        Unknown hook names produce a warning but are still stored so
        forward-compatible plugins don't break.
        z4Plugin '%s' registered unknown hook '%s' (valid: %s), zPlugin %s registered hook: %sN)rP   r7   r   r   rR   joinsortedr   _hooksr  r"  r:   )r   rE  r  s      r   register_hookzPluginContext.register_hook  s     K''NN"		&--..   	''	266==hGGG4dm6H)TTTTTr    r}   c           
     b   |t           vrLt                              d| j        j        |d                    t          t                                          | j        j        	                    |g           
                    |           t                              d| j        j        |           dS )aS  Register a behavior-changing middleware callback.

        Middleware is separate from observer hooks: request middleware may
        rewrite the effective payload, and execution middleware may wrap the
        real callback. Unknown kinds are stored for forward compatibility but
        warned so plugin authors can catch typos.
        z:Plugin '%s' registered unknown middleware '%s' (valid: %s)rG  z#Plugin %s registered middleware: %sN)r   r7   r   r   rR   rH  rI  r   _middlewarer  r"  r:   )r   r}   r  s      r   register_middlewarez!PluginContext.register_middleware  s     '''NN"		&!12233   	!,,T266==hGGG:DM<NPTUUUUUr    r|   r   c                   ddl m} d|v r t          d| d| j        j         d          |r|                    |          st          d| d          |                                st          d	|           | j        j         d| }|| j        j        ||d
| j        j	        |<   t                              d| j        j        |           dS )u  Register a read-only skill provided by this plugin.

        The skill becomes resolvable as ``'<plugin_name>:<name>'`` via
        ``skill_view()``.  It does **not** enter the flat
        ``~/.hermes/skills/`` tree and is **not** listed in the system
        prompt's ``<available_skills>`` index — plugin skills are
        opt-in explicit loads only.

        Raises:
            ValueError: if *name* contains ``':'`` or invalid characters.
            FileNotFoundError: if *path* does not exist.
        r   )_NAMESPACE_RE:zSkill name 'zG' must not contain ':' (the namespace is derived from the plugin name 'z' automatically).zInvalid skill name 'z'. Must match [a-zA-Z0-9_-]+.zSKILL.md not found at )r|   r   	bare_namerr   zPlugin %s registered skill: %sN)agent.skill_utilsrP  r   r   rR   matchexistsFileNotFoundErrorr   _plugin_skillsr7   r:   )r   rR   r|   rr   rP  	qualifieds         r   register_skillzPluginContext.register_skill  s0   $ 	433333$;;:t : :M&: : :  
  	=..t44 	JtJJJ   {{}} 	E#$CT$C$CDDD})22D22	m(&	3
 3
$Y/ 	,M		
 	
 	
 	
 	
r    )r   rp   r   r   )r   r   )NNFr"   r"   F)rR   rS   r   rS   r   rf   r;   r   r   r   rv   r   r   r(   rr   rS   r   rS   r   r(   r   r)   )r   )r   rS   r   rS   r   r(   )Nr"   )rR   rS   r   rS   r   r   r   r   rr   rS   r   r)   )r"   r"   )
rR   rS   r;   r   rr   rS   r   rS   r   r)   )r   rS   r   rf   r   rS   r   r)   )NNr"   )rR   rS   r  rS   r  r   r   r   r  r   r  r   r  rS   r  r   r   r)   )r  r   r  r   r   r)   )
r~   rS   r   rS   rr   rS   r$  r%  r   r)   )rE  rS   r  r   r   r)   )r}   rS   r  r   r   r)   )r"   )rR   rS   r|   r   rr   rS   r   r)   )r   r   r   r   r   propertyr   r   r   r   r   r   r   r   r   r   r   r   r  r  r  r#  rD  rK  rN  rY  r   r    r   r   r   "  sN       GG       X0 %)$(&
 &
 &
 &
 &
T    B '+W W W W W: 4U 4U 4U 4U 4Up< < < <>
 
 
 
@
 
 
 
6$
 $
 $
 $
P
 
 
 
6
 
 
 
8
 
 
 
@"
 "
 "
 "
L(
 (
 (
 (
d ,0$(4
 4
 4
 4
 4
p6
 6
 6
 6
D .2m
 m
 m
 m
 m
 m
^U U U U&V V V V2 	+
 +
 +
 +
 +
 +
 +
r    r   c                      e Zd ZdZd6dZd7d8dZd6d	Z	 d9d:dZd;dZd<dZ	d=dZ
d>dZd?d Zd?d!Zd@d&ZdAd'ZdBd)ZdCd*ZdDd,ZdEd.ZdFd1ZdGd4ZdHd5Zd
S )IPluginManagerz;Central manager that discovers, loads, and invokes plugins.r   r)   c                    i | _         i | _        i | _        t                      | _        t                      | _        i | _        d | _        i | _        d| _	        d | _
        i | _        i | _        g | _        d S )NF)_pluginsrJ  rM  rV   r   r  r   r   r   _discoveredr   rW  r=  r!  r   s    r   r   zPluginManager.__init__B  sx    131368,/EE03#.0#13!&9; 68 46###r    Fr'   r(   c                   | j         r|sdS |r| j                                         | j                                         | j                                         | j                                         | j                                         | j                                         | j                                         | j	                                         | j
                                         | j                                         d| _        d| _         	 |                                  dS # t          $ r	 d| _          w xY w)a  Scan all plugin sources and load each plugin found.

        When ``force`` is true, clear cached discovery state first so config
        changes or newly-added bundled backends become visible in long-lived
        sessions without requiring a full agent restart.
        NTF)r`  r_  clearrJ  rM  r   r  r   r   rW  r=  r!  r   _discover_and_load_innerBaseException)r   r'   s     r   discover_and_loadzPluginManager.discover_and_load^  sG     	E 	F 	(M!!!K""$$$#))+++'--///$$&&&!'')))%%'''O!!###'--///#'D   	))+++++ 	 	 	$D	s   D. .Ec           	     *	   g }t                      }t                              d|           |                     |dh d          }t                              dt	          |                     |                    |           |                     |dz  d          }t                              dt	          |                     |                    |           t                      d	z  }t                              d
|           |                     |d          }t                              dt	          |                     |                    |           t          d          rt          j	                    dz  d	z  }t                              d|           |                     |d          }t                              dt	          |                     |                    |           nt                              d           | 
                                }	t                              dt	          |	                     |                    |	           t                      }
t                      }i }|D ]}|||j        p|j        <   |                                D ]}|j        p|j        }||
v s	|j        |
v r>t!          |d          }d|_        || j        |<   t                              d|           \|j        dk    r>t!          |d          }d|_        || j        |<   t                              d|           |j        dk    r7t!          |d          }|| j        |<   t                              d|           |j        dk    r |j        dv r|                     |           |duo||v p|j        |v }|sRt!          |d          }d                     |          |_        || j        |<   t                              d!|           w|                     |           |r^t                              d"t	          | j                  t1          d# | j                                        D                                  dS dS )$u=   The actual discovery sweep — see :meth:`discover_and_load`.zScanning bundled plugins: %sbundled>   model-providersmemory	platformscontext_engine)rz   
skip_namesz%  bundled (top-level): %d manifest(s)rk  )rz   z#  bundled/platforms: %d manifest(s)r   zScanning user plugins: %sr   z  user: %d manifest(s)HERMES_ENABLE_PROJECT_PLUGINSz.hermeszScanning project plugins: %sprojectz  project: %d manifest(s)zHProject plugins disabled (set HERMES_ENABLE_PROJECT_PLUGINS=1 to enable)z  entrypoints: %d manifest(s)F)r   rd   zdisabled via configzSkipping disabled plugin '%s'rk   u<   exclusive plugin — activate via <category>.provider configz8Skipping '%s' (exclusive, handled by category discovery)rm   Tz?Skipping '%s' (model-provider, handled by providers/ discovery)>   ri   rj   NzBnot enabled in config (run `hermes plugins enable {}` to activate)z&Skipping '%s' (not in plugins.enabled)z/Plugin discovery complete: %d found, %d enabledc              3  (   K   | ]}|j         	d V  dS )   N)rd   )r*  ps     r   r,  z9PluginManager._discover_and_load_inner.<locals>.<genexpr>  s)      CC!CACCCCCCr    )r   r7   r:   _scan_directorylenextendr   rU   r   cwd_scan_entry_pointsra   rh   r~   rR   valuesr   r   r_  r}   rz   _load_pluginformatr   sum)r   	manifestsrepo_pluginsrh  bundled_platformsuser_diruser_manifestsproject_dirproject_manifestsep_manifestsrZ   rd   winnersr   
lookup_keyloaded
is_enableds                    r   rd  z&PluginManager._discover_and_load_inner  s   *,	 /003\BBB&&SSS ' 
 

 	<c'llKKK!!! 00;&y 1 
 
 	:C@Q<R<RSSS*+++ #$$y00(;;;--hv-FF-s>/B/BCCC((( 788 		(**y09<KLL7EEE $ 4 4[ 4 S SLL4c:K6L6LMMM.////LLZ  
 ..004c,6G6GHHH&&& )**&((-/! 	> 	>H5=GHL1HM22(( I	( I	(H!6J X%%()B)B%xGGG4,2j)<jIII
 }++%xGGGR  -3j)N    } 000%xFFF,2j)U    )++AX0X0X!!(+++ t# H7*Fhmw.F   
%xGGGXVJ''  -3j)<j   h'''' 	KKADM""CCt}3355CCCCC    	 	r    Nr|   r   rz   rS   rm  Optional[Set[str]]List[PluginManifest]c                6    |                      |||dd          S )u  Read ``plugin.yaml`` manifests from subdirectories of *path*.

        Supports two layouts, mixed freely:

        * **Flat** — ``<root>/<plugin-name>/plugin.yaml``. Key is
          ``<plugin-name>`` (e.g. ``disk-cleanup``).
        * **Category** — ``<root>/<category>/<plugin-name>/plugin.yaml``,
          where the ``<category>`` directory itself has no ``plugin.yaml``.
          Key is ``<category>/<plugin-name>`` (e.g. ``image_gen/openai``).
          Depth is capped at two segments.

        *skip_names* is an optional allow-list of names to ignore at the
        top level (kept for back-compat; the current call sites no longer
        pass it now that categories are first-class).
        r"   r   rm  prefixdepth)_scan_directory_level)r   r|   rz   rm  s       r   rs  zPluginManager._scan_directory  s,    * ))&Z! * 
 
 	
r    r  r  intc               ^   g }|                                 s|S t          |                                          D ]}|                                 s|dk    r|r
|j        |v r)|dz  }|                                s|dz  }|                                r0|                     ||||          }	|	|                    |	           |dk    rt                              d|           |r| d|j         n|j        }
|	                    | 
                    ||d|
|dz                        |S )	a  Recursive implementation of :meth:`_scan_directory`.

        ``prefix`` is the category path already accumulated ("" at root,
        "image_gen" one level in). ``depth`` is the recursion depth; we
        cap at 2 so ``<root>/a/b/c/`` is ignored.
        r   zplugin.yamlz
plugin.ymlNrq  z/Skipping %s (no plugin.yaml, depth cap reached)r   r  )is_dirrI  iterdirrR   rU  _parse_manifestr"  r7   r:   ru  r  )r   r|   rz   rm  r  r  r|  childmanifest_filer   
sub_prefixs              r   r  z#PluginManager._scan_directory_level3  s{    +-	{{}} 	DLLNN++ !	 !	E<<>> zzjzUZ:-E-E!M1M '')) 5 % 4##%% //!5&&  '$$X...
 zzNPUVVV5;KF11UZ111J**#%!) +      r    r  
plugin_dirOptional[PluginManifest]c                ~   	 t           t                              d|           dS t          j        |                    d                    pi }|                    d|j                  }|r| d|j         n|}|                    dd          }t          |t                    sd}|	                                
                                }	|	t          vrDt                              d	||d
                    t          t                                         d}	|	dk    rd|vr|dz  }
|
                                r{	 |
                    d          dd         }d|v sd|v rd}	t                              d|           n%d|v r!d|v rd}	t                              d|           n# t           $ r Y nw xY wt                              d|||	||           t#          |t          |                    dd                    |                    dd          |                    dd          |                    dg           |                    dg           |                    dg           |t          |          |	|          S # t           $ r.}t                              d ||t$          !           Y d}~dS d}~ww xY w)"zParse a single ``plugin.yaml`` into a :class:`PluginManifest`.

        Returns ``None`` on parse failure (logs a warning).
        Nu'   PyYAML not installed – cannot load %szutf-8)encodingrR   r   r}   rl   zBPlugin %s: unknown kind '%s' (valid: %s); treating as 'standalone'rG  __init__.pyr   )errorsi    register_memory_providerMemoryProviderrk   zAPlugin %s: detected memory provider, treating as kind='exclusive'r   ProviderProfilerm   zEPlugin %s: detected model provider, treating as kind='model-provider'z9Parsed manifest: key=%s name=%s kind=%s source=%s path=%srq   r"   rr   rs   rv   rx   ry   )rR   rq   rr   rs   rv   rx   ry   rz   r|   r}   r~   zFailed to parse %s: %sexc_info)yamlr7   r   	safe_load	read_textre   rR   r]   rS   r+   r,   rn   rH  rI  rU  r:   r_   rp   r-   )r   r  r  rz   r  datarR   r~   raw_kindr}   	init_filesource_textexcs                r   r  zPluginManager._parse_manifestk  s   N	|H-XXXt>-"9"97"9"K"KLLRPRD88FJO44D39CV//jo///tCxx55Hh,, ('>>##))++D...X499V4G-H-H#I#I   $ |##d(:(:&6	##%% &/&9&9&9&K&KETE&R6+EE/;>>#.D"LL!? #    0;>> 1[ @ @
 $4D"LL!D #  
 %    LLKT4   "DHHY3344 HH]B77xx"--!XXnb99#xx(8"==#xx(8"==__     	 	 	NN(-~     44444		sC   "J DJ ?A)F) (J )
F63J 5F66CJ 
J<#J77J<c                   g }	 t           j                                        }t          |d          r|                    t
                    }n=t          |t                    r|                    t
          g           }nd |D             }|D ]9}t          |j
        d|j        |j
                  }|                    |           :n2# t          $ r%}t                              d|           Y d}~nd}~ww xY w|S )z7Check ``importlib.metadata`` for pip-installed plugins.selectgroupc                2    g | ]}|j         t          k    |S r   r  ENTRY_POINTS_GROUPr*  eps     r   
<listcomp>z4PluginManager._scan_entry_points.<locals>.<listcomp>  s%    PPPB=O1O1OR1O1O1Or    
entrypoint)rR   rz   r|   r~   zEntry-point scan failed: %sN)	importlibmetadataentry_pointshasattrr  r  r]   rf   re   rp   rR   valuer"  r_   r7   r:   )r   r|  eps	group_epsr  r   r  s          r   rw  z PluginManager._scan_entry_points  s%   *,		=$1133CsH%% QJJ-?J@@		C&& QGG$6;;		PP#PPP	 + +)'	     ****+  	= 	= 	=LL6<<<<<<<<	= s   CC 
C7C22C7r   rp   c                t   	 t                    }t                              dj        pj        j        j        j                   	 j        dv r                               }n 	                              }||_
        t          |dd          }|)d|_        t                              dj                   nt                     }t           j                  	d  j                                        D             d	  j                                        D              ||           	fd
 j        D             |_        fd j                                        D             |_        fd j                                        D             |_         fd j        D             |_        d|_        t                              dt5          |j                  t5          |j                  t5          |j                  t5          |j                  t7           fd j        D                                  nS# t:          $ rF}t=          |          |_        t                              dj        |t>                     Y d}~nd}~ww xY w| j         j        pj        <   dS )z?Import a plugin module and call its ``register(ctx)`` function.)r   z1Loading plugin '%s' (source=%s, kind=%s, path=%s)>   r   rh  ro  r   Nzno register() functionz&Plugin '%s' has no register() functionc                4    i | ]\  }}|t          |          S r   rt  )r*  hcbss      r   
<dictcomp>z.PluginManager._load_plugin.<locals>.<dictcomp>  s1     ' ' '$*AsAs3xx' ' 'r    c                4    i | ]\  }}|t          |          S r   r  )r*  r}   r  s      r   r  z.PluginManager._load_plugin.<locals>.<dictcomp>
  s1     % % %'0tSD#c((% % %r    c                    g | ]}|v|	S r   r   )r*  t_tools_befores     r   r  z.PluginManager._load_plugin.<locals>.<listcomp>  s.     + + +-- ---r    c                h    g | ].\  }}t          |                              |d           k    ,|/S r   rt  re   )r*  r  r  _hook_counts_befores      r   r  z.PluginManager._load_plugin.<locals>.<listcomp>  sI     + + +33xx"5"9"9!Q"?"??? ???r    c                h    g | ].\  }}t          |                              |d           k    ,|/S r  r  )r*  r}   r  _mw_counts_befores      r   r  z.PluginManager._load_plugin.<locals>.<listcomp>  sI     0 0 0!c3xx"3"7"7a"@"@@@ @@@r    c                f    g | ]-}j         |                             d           j        k    +|.S )r   )r   re   rR   r*  r+  r   r   s     r   r  z.PluginManager._load_plugin.<locals>.<listcomp>  sF     . . .,Q/33H==NN NNNr    Tz[  registered: %d tool(s), %d hook(s), %d middleware, %d slash command(s), %d CLI command(s)c              3  n   K   | ]/}j         |                             d           j        k    +dV  0dS )r   rq  N)r   re   rR   r  s     r   r,  z-PluginManager._load_plugin.<locals>.<genexpr>'  sR        -a044X>>(-OO OOOO r    zFailed to load plugin '%s': %sr  )!r   r7   r:   r~   rR   rz   r}   r|   _load_directory_module_load_entrypoint_moduler   r   r   r   r   rV   r   rJ  r>  rM  r   r   r   r   r   rd   rt  r{  r   r_   rS   r-   r_  )
r   r   r  r   register_fnctxr  r  r  r  s
   ``     @@@r   ry  zPluginManager._load_plugin  sH   x000?L)HM8?HM8=	
 	
 	

B	"@@@44X>>55h??"FM "&*d;;K"7GWWWW#Hd33 !$D$; < <' '.2k.?.?.A.A' ' '#% %484D4J4J4L4L% % %! C   + + + +#6+ + +'+ + + +"&+"3"3"5"5+ + +'
0 0 0 0%)%5%;%;%=%=0 0 0,
. . . . .#4. . .* "&q/00/00455233     #'#5    
 
 
  	 	 	s88FLNN0s^         	 8>hl3hm444s   G>I 
J<JJtypes.ModuleTypec                   t          |j                  }|dz  }|                                st          d|           t          t
          j        vr@t          j        t                    }g |_	        t          |_
        |t
          j        t          <   |j        p|j        }|                    dd                              dd          }t           d| }t          j                            ||t#          |          g          }||j        t'          d
|           t          j                            |          }	||	_
        t#          |          g|	_	        |	t
          j        |<   |j                            |	           |	S )a/  Import a directory-based plugin as ``hermes_plugins.<slug>``.

        The module slug is derived from ``manifest.key`` so category-namespaced
        plugins (``image_gen/openai``) import as
        ``hermes_plugins.image_gen__openai`` without colliding with any
        future ``tts/openai``.
        r  zNo __init__.py in r   __r   r(  .)submodule_search_locationsNzCannot create module spec for )r   r|   rU  rV  
_NS_PARENTr1   modulestypes
ModuleType__path____package__r~   rR   r   r  utilspec_from_file_locationrS   loaderImportErrormodule_from_specexec_module)
r   r   r  r  ns_pkgr~   slugmodule_namespecr   s
             r   r  z$PluginManager._load_directory_module6  sn    (-((
.	!! 	G#$E$E$EFFF S[((%j11F FO!+F&,CK
#l+hm{{3%%--c377#,,d,,~55(+J'8 6 
 

 <4;.JyJJKKK0066(z??+#)K '''r    c                   t           j                                        }t          |d          r|                    t
                    }n=t          |t                    r|                    t
          g           }nd |D             }|D ](}|j	        |j	        k    r|
                                c S )t          d|j	         dt
           d          )z:Load a pip-installed plugin via its entry-point reference.r  r  c                2    g | ]}|j         t          k    |S r   r  r  s     r   r  z9PluginManager._load_entrypoint_module.<locals>.<listcomp>d  s%    LLLRX9K-K-K-K-K-Kr    zEntry point 'z' not found in group 'r3  )r  r  r  r  r  r  r]   rf   re   rR   loadr  )r   r   r  r  r  s        r   r  z%PluginManager._load_entrypoint_module\  s     --//3!! 	M

);
<<IIT"" 	M 2B77IILLcLLLI 	! 	!Bw(-''wwyy    ( VHMVVASVVV
 
 	
r    rE  r   r   	List[Any]c                `   |                     dt                     | j                            |g           }g }|D ]r}	  |di |}||                    |           ## t
          $ rC}t                              d|t          |dt          |                    |           Y d}~kd}~ww xY w|S )u   Call all registered callbacks for *hook_name*.

        Each callback is wrapped in its own try/except so a misbehaving
        plugin cannot break the core agent loop.

        Returns a list of non-``None`` return values from callbacks.

        For ``pre_llm_call``, callbacks may return a dict describing
        context to inject into the current turn's user message::

            {"context": "recalled text..."}
            "recalled text..."          # plain string, equivalent

        Context is ALWAYS injected into the user message, never the
        system prompt.  This preserves the prompt cache prefix — the
        system prompt stays identical across turns so cached tokens
        are reused.  All injected context is ephemeral — never
        persisted to session DB.
        telemetry_schema_versionNz Hook '%s' callback %s raised: %sr   r   )
r  r   rJ  re   r"  r_   r7   r   r   repr)r   rE  r   	callbacksresultscbretr  s           r   invoke_hookzPluginManager.invoke_hookr  s    ( 	46MNNNKOOIr22	 	 	B
bll6ll?NN3'''   6B
DHH55	        s   A
B+(9B&&B+c                P    t          | j                            |                    S )z@Return True when at least one callback is registered for a hook.)r(   rJ  re   )r   rE  s     r   has_hookzPluginManager.has_hook  s    DKOOI..///r    r}   c                P    t          | j                            |                    S )zDReturn True when at least one callback is registered for middleware.)r(   rM  re   )r   r}   s     r   has_middlewarezPluginManager.has_middleware  s!    D$((..///r    c                *   | j                             |g           }g }|D ]r}	  |di |}||                    |           ## t          $ rC}t                              d|t          |dt          |                    |           Y d}~kd}~ww xY w|S )a	  Call registered middleware callbacks for *kind*.

        Each callback is isolated so one plugin cannot break the base runtime
        path. Middleware that wants to change behavior must return the shape
        documented by the caller-specific contract.
        Nz&Middleware '%s' callback %s raised: %sr   r   )rM  re   r"  r_   r7   r   r   r  )r   r}   r   r  r  r  r  r  s           r   invoke_middlewarezPluginManager.invoke_middleware  s     $((r22	 	 	B
bll6ll?NN3'''   <B
DHH55	        s   A
B9BBList[tuple]c                *    t          | j                  S )aa  Return the list of plugin-registered Slack action handlers.

        Each entry is a ``(action_id, callback, plugin_name)`` tuple.
        Consumed by the Slack adapter at connect time to wire callbacks
        into its ``slack_bolt.AsyncApp``.

        Plugins register handlers via
        :meth:`PluginContext.register_slack_action_handler`.
        )r^   r!  ra  s    r   get_slack_action_handlersz'PluginManager.get_slack_action_handlers  s     D/000r    List[Dict[str, Any]]c                   g }t          | j                                                  D ]\  }}|                    |j        j        |j        j        p|j        j        |j        j        |j        j        |j        j	        |j        j
        |j        t          |j                  t          |j                  t          |j                  t          |j                  |j        d           |S )z7Return a list of info dicts for all discovered plugins.)rR   r~   r}   rq   rr   rz   rd   toolshooks
middlewarecommandsr   )rI  r_  r>  r"  r   rR   r~   r}   rq   rr   rz   rd   rt  r   r   r   r   r   )r   resultr~   r  s       r   list_pluginszPluginManager.list_plugins  s    ')!$-"5"5"7"788 	 	KCMM"O0!?.F&/2F"O0%6#)?#>$o4%~ !899 !899"%f&B"C"C #F$> ? ?#\      r    qualified_nameOptional[Path]c                N    | j                             |          }|r|d         ndS )z>Return the ``Path`` to a plugin skill's SKILL.md, or ``None``.r|   N)rW  re   )r   r  r  s      r   find_plugin_skillzPluginManager.find_plugin_skill  s+    #''77 %/uV}}4/r    r  rw   c                r    | dt          fd| j                                        D                       S )zCReturn sorted bare names of all skills registered by *plugin_name*.rQ  c              3  X   K   | ]$\  }}|                               |d          V  %dS )rR  N)
startswith)r*  qnr   r  s      r   r,  z3PluginManager.list_plugin_skills.<locals>.<genexpr>  sQ       
 
A}}V$$
kN
 
 
 
 
 
r    )rI  rW  r>  )r   r  r  s     @r   list_plugin_skillsz PluginManager.list_plugin_skills  sX    """ 
 
 
 
,2244
 
 
 
 
 	
r    c                <    | j                             |d           dS )z>Remove a stale registry entry (silently ignores missing keys).N)rW  pop)r   r  s     r   remove_plugin_skillz!PluginManager.remove_plugin_skill  s!    55555r    rZ  Fr'   r(   r   r)   r   )r|   r   rz   rS   rm  r  r   r  )r|   r   rz   rS   rm  r  r  rS   r  r  r   r  )
r  r   r  r   rz   rS   r  rS   r   r  )r   r  )r   rp   r   r)   )r   rp   r   r  rE  rS   r   r   r   r  rE  rS   r   r(   r}   rS   r   r(   r}   rS   r   r   r   r  r   r  r   r  )r  rS   r   r  )r  rS   r   rw   )r  rS   r   r)   )r   r   r   r   r   rf  rd  rs  r  r  rw  ry  r  r  r  r  r  r  r  r  r  r  r  r   r    r   r]  r]  ?  s       EE6 6 6 68         DT T T T| *.	
 
 
 
 
26 6 6 6pY Y Y Y~   <L> L> L> L>\$ $ $ $L
 
 
 
,# # # #J0 0 0 00 0 0 0   6
1 
1 
1 
1    40 0 0 0

 
 
 
6 6 6 6 6 6r    r]  zOptional[PluginManager]_plugin_managerc                 :    t           t                      a t           S )z>Return (and lazily create) the global PluginManager singleton.)r  r]  r   r    r   get_plugin_managerr    s     '//r    c                J    t                                          |            dS )zDiscover and load all plugins.

    Default behavior is idempotent. Pass ``force=True`` to rescan plugin
    manifests and reload state in the current process.
    r'   Nr  rf  r  s    r   discover_pluginsr    s'     ***77777r    rE  r   r   r  c                6     t                      j        | fi |S )z|Invoke a lifecycle hook on all loaded plugins.

    Returns a list of non-``None`` return values from plugin callbacks.
    )r  r  )rE  r   s     r   r  r    s&    
 ,+I@@@@@r    r}   c                6     t                      j        | fi |S )zyInvoke registered middleware callbacks.

    Returns a list of non-``None`` return values from middleware callbacks.
    )r  r  )r}   r   s     r   r  r    s&    
 21$AA&AAAr    c                    t                      }t          |dd          }t          |          rt           ||                     S t          t          |di                               |                     S )zBReturn True when middleware callbacks are registered for ``kind``.r  NrM  )r  r   r   r(   re   )r}   r   methods      r   r  r    sm     ""GW.55F "FF4LL!!!3377==>>>r    c                D    t                                          |           S )z1Return True when a hook has registered callbacks.)r  r  )rE  s    r   r  r  (  s    ((333r    >Tool '{tool_name}' denied: not in this thread's tool whitelistallowedr  deny_msg_fmtc                6    | t           _        |t           _        d S r   )_thread_tool_whitelistr&  fmt)r&  r'  s     r   set_thread_tool_whitelistr+  0  s     &-"!-r    c                     d t           _        d S r   )r)  r&  r   r    r   clear_thread_tool_whitelistr-  8  s    %)"""r    r   r   r%  task_id
session_idtool_call_idturn_idapi_request_idmiddleware_traceOptional[List[Dict[str, Any]]]r{   c                   t          t          dd          }|0| |vr,t          t          dd          }	|	                    |           S t          d| t	          |t
                    r|ni |||||t          |pg           	  	        }
|
D ]b}t	          |t
                    s|                    d          d	k    r2|                    d
          }t	          |t                    r|r|c S cdS )a  Check ``pre_tool_call`` hooks for a blocking directive.

    Plugins that need to enforce policy (rate limiting, security
    restrictions, approval workflows) can return::

        {"action": "block", "message": "Reason the tool was blocked"}

    from their ``pre_tool_call`` callback.  The first valid block
    directive wins.  Invalid or irrelevant hook return values are
    silently ignored so existing observer-only hooks are unaffected.
    r&  Nr*  zTool '{tool_name}' denied)r   r?   )r   r   r.  r/  r0  r1  r2  r3  actionblockmessage)	r   r)  rz  r  r]   rf   r^   re   rS   )r   r   r.  r/  r0  r1  r2  r3  r&  r*  hook_resultsr  r8  s                r   get_pre_tool_call_block_messager:  <  s   * ,i>>Gy77,e5PQQzzIz...d++3TT!%.4"55
 
 
L   &$'' 	::h7****Y''gs## 	 	NNN4r    c                N    t                      }|                    |            |S )zReturn the global manager after ensuring plugin discovery has run.

    Pass ``force=True`` to rescan in the current process.
    r  r  )r'   r   s     r   _ensure_plugins_discoveredr<  n  s+    
 !""GE***Nr    c                 (    t                      j        S )z5Return the plugin-registered context engine, or None.)r<  r   r   r    r   get_plugin_context_enginer>  x  s    %''77r    Optional[Callable]c                f    t                      j                            |           }|r|d         ndS )zFReturn the handler for a plugin-registered slash command, or ``None``.r;   N)r<  r   re   )rR   r  s     r   get_plugin_command_handlerrA  }  s3    &((9==dCCE$.5$.r    g      >@r  c                    t          j                   s S 	 t          j                     n$# t          $ r t          j                   cY S w xY wi i t          j                    d fd}t          j        |dd          }|	                                 
                    t                    st          dt          d	d
          dv rd                             d          S )a  Resolve a plugin command return value, awaiting async handlers when needed.

    Sync CLI/TUI dispatch sites call plugin handlers from plain functions.
    If a handler is async, await it directly when no loop is running; if
    we're already inside an active loop, run it in a helper thread with its
    own loop so the caller still gets a concrete result synchronously. The
    threaded path is bounded by a 30s timeout so a hung async handler cannot
    wedge the terminal indefinitely.
    r   r)   c                     	 t          j                  d<   n# t          $ r} | d<   Y d } ~ nd } ~ ww xY w                                 d S #                                  w xY w)Nr  r  )asynciorunre  rV   )r  donefailureoutcomer  s    r   _runnerz.resolve_plugin_command_result.<locals>._runner  sv    	&{622GG 	! 	! 	! GENNNNNN	! HHJJJJJDHHJJJJs%    A 
4/A 4A A#zhermes-plugin-command-awaitT)targetrR   daemon)r9  z5Plugin command async handler did not complete within z.0fsr  r  rZ  )inspectisawaitablerD  get_running_loopRuntimeErrorrE  	threadingEventThreadstartwait"_PLUGIN_COMMAND_AWAIT_TIMEOUT_SECSTimeoutErrorre   )r  rI  threadrF  rG  rH  s   `  @@@r   resolve_plugin_command_resultrY    sK    v&& # """" # # #{6"""""# !G(*G?D         *  F
 LLNNN99?9@@ 
9189 9 9
 
 	
 en;;ws   0 AADict[str, dict]c                 (    t                      j        S )u   Return the full plugin commands dict (name → {handler, description, plugin}).

    Triggers idempotent plugin discovery so callers can use plugin commands
    before any explicit discover_plugins() call.
    )r<  r   r   r    r   get_plugin_commandsr\    s     &''88r    r  c                 `     t                        fdt           j                  D             S )a  Return all plugin-registered auxiliary tasks as a stable-ordered list.

    Each entry is the registration dict from
    :meth:`PluginContext.register_auxiliary_task`:
    ``{key, display_name, description, defaults, plugin}``.

    Triggers idempotent plugin discovery so callers can read the registry
    before any explicit ``discover_plugins()`` call. Sorted by ``key`` for
    deterministic ordering in pickers and tests.
    c                *    g | ]}j         |         S r   )r=  )r*  r/  r   s     r   r  z.get_plugin_auxiliary_tasks.<locals>.<listcomp>  s!    FFFaGq!FFFr    )r<  rI  r=  )r   s   @r   get_plugin_auxiliary_tasksr_    s5     )**GFFFF6'2D+E+EFFFFr    r  c                 D   t                      } | j        sg S 	 ddlm} n# t          $ r g cY S w xY wi }i }| j        D ]O}|                    |          }|s|j        }|                    |g                               |j	                   P| j
                                        D ]J\  }}|j        D ]=}|                    |          }|r$|j        |v r|                    |j        |           >Kg }	t          |          D ]}
|                    |
          }d|
                    dd                                           }|r|j        j        r|j        j        }n(d                    t          ||
                             }|	                    |
||f           |	S )zReturn plugin toolsets as ``(key, label, description)`` tuples.

    Used by the ``hermes tools`` TUI so plugin-provided toolsets appear
    alongside the built-in ones and can be toggled on/off per platform.
    r   r   u   🔌 r(  r   rG  )r  r   r   r   r_   	get_entryr   r  r"  rR   r_  r>  r   rI  re   r   titler   rr   rH  )r   r   toolset_toolstoolset_pluginr   r  tsr0  r  r  ts_keyr   r  descs                 r   get_plugin_toolsetsrh    s    !""G% 	+++++++   			 +-M.0N/ < <	""9-- 	]  R((//
;;;; !)//11 A Av0 	A 	AI&&y11E A-77))%-@@@	A
 F'' - -##F++:sC006688:: 	<fo1 	<?.DD99VM&$9::;;Dvud+,,,,Ms     //)r   r   r  r  )rR   rS   r   r(   )r   rV   )r   rb   )r   r]  r  r  r  r  )r%  )r&  r  r'  rS   r   r)   rZ  )r"   r"   r"   r"   r"   N)r   rS   r   r%  r.  rS   r/  rS   r0  rS   r1  rS   r2  rS   r3  r4  r   r{   )r'   r(   r   r]  )rR   rS   r   r?  )r  r   r   r   )r   rZ  r  r  )Or   
__future__r   rD  importlib.metadatar  importlib.utilrM  r/   r   r1   rQ  r  dataclassesr   r   pathlibr   typingr   r   r	   r
   r   r   r   hermes_constantsr   utilsr   r\   r   hermes_cli.middlewarer   r   r   r  r  	getLoggerr   r7   r   r+   r,   r-   r.   r<   rP   r   r  r  rU   ra   rh   rn   rp   r   r   r]  r  r  r  r  r  r  r  localr)  r+  r-  r:  r<  r>  rA  rV  rY  r\  r_  rh  r   r    r   <module>rt     s    B # " " " " "            				 



      ( ( ( ( ( ( ( (       B B B B B B B B B B B B B B B B B B , , , , , , ! ! ! ! ! ! % % % % % % K K K K K K K K
> 
> 
> 
>KKKK   DDD 
	8	$	$" 1266<<>>DDFF K  !     6     * * * * * * *X , 
! ! ! !
       D !e d d  d d d d ! ! ! ! ! ! ! !H 
  
  
  
  
  
  
  
 "V
 V
 V
 V
 V
 V
 V
 V
zu
6 u
6 u
6 u
6 u
6 u
6 u
6 u
6x ,0 / / / /   8 8 8 8 8A A A AB B B B? ? ? ?4 4 4 4
 )** 
 Y. . . . .* * * * 7;/ / / / /d    8 8 8
/ / / / &* "+  +  +  + \9 9 9 9G G G G* * * * * *s   -A2 2A<;A<