
    i;{                       U d Z ddlmZ ddlZddlZddlZddlZddlZddlZddl	m
Z
mZ 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Zn# e$ r dZY nw xY w ej        e          Zh d	Zd
e d<   dZ!dZ"d7dZ#d8dZ$e
 G d d                      Z%e
 G d d                      Z& G d d          Z' G d d          Z(da)de d<   d9dZ*d:d!Z+d;d&Z,	 	 	 d<d=d/Z-d0 Z.d>d2Z/d?d4Z0d@d6Z1dS )AuR  
Hermes Plugin System
====================

Discovers, loads, and manages plugins from three sources:

1. **User plugins**   – ``~/.hermes/plugins/<name>/``
2. **Project plugins** – ``./.hermes/plugins/<name>/`` (opt-in via
   ``HERMES_ENABLE_PROJECT_PLUGINS``)
3. **Pip plugins**     – packages that expose the ``hermes_agent.plugins``
   entry-point group.

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>
   pre_llm_callpost_llm_callpre_tool_callon_session_endpost_tool_callpre_api_requeston_session_reseton_session_startpost_api_requeston_session_finalizezSet[str]VALID_HOOKSzhermes_agent.pluginshermes_pluginsnamestrreturnboolc                     t          |           S )z<Return True when an env var is set to a truthy opt-in value.r   )r   s    :/home/agentuser/.hermes/hermes-agent/hermes_cli/plugins.py_env_enabledr#   H   s    4       setc                    	 ddl m}   |             }|                    di                               dg           }t          |t                    rt          |          nt                      S # t          $ r t                      cY S w xY w)z0Read the disabled plugins list from config.yaml.r   )load_configpluginsdisabled)hermes_cli.configr'   get
isinstancelistr%   	Exception)r'   configr)   s      r"   _get_disabled_pluginsr0   M   s    111111::i,,00R@@ *8T : :Es8}}}E   uus   A+A. .B	B	c                      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S )PluginManifestz0Parsed representation of a plugin.yaml manifest.r   r    versiondescriptionauthordefault_factoryz List[Union[str, Dict[str, Any]]]requires_env	List[str]provides_toolsprovides_hookssourceNOptional[str]path)__name__
__module____qualname____doc____annotations__r4   r5   r6   r   r-   r9   r;   r<   r=   r?    r$   r"   r2   r2   \   s         ::IIIGKF5:U45P5P5PLPPPP %d ; ; ;N;;;; %d ; ; ;N;;;;FDr$   r2   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<   dZded<   dZded<   dS )LoadedPluginz)Runtime state for a single loaded plugin.r2   manifestNzOptional[types.ModuleType]moduler7   r:   tools_registeredhooks_registeredcommands_registeredFr    enabledr>   error)r@   rA   rB   rC   rD   rI   r   r-   rJ   rK   rL   rM   rN   rE   r$   r"   rG   rG   k   s         33)-F----"'%"="="====="'%"="="=====%*U4%@%@%@@@@@GEr$   rG   c                  v    e Zd ZdZd/dZ	 	 	 	 	 d0d1dZd2d3dZ	 	 d4d5d#Z	 d6d7d$Zd8d'Z	d9d(Z
d:d+Z	 d6d;d.ZdS )<PluginContextz=Facade given to plugins so they can register tools and hooks.rH   r2   manager'PluginManager'c                "    || _         || _        d S )N)rH   _manager)selfrH   rQ   s      r"   __init__zPluginContext.__init__   s     r$   NFr3   r   r   toolsetschemadicthandlerr   check_fnCallable | Noner9   list | Noneis_asyncr    r5   emojir   Nonec
                    ddl m}
 |
                    |||||||||		  	         | j        j                            |           t                              d| j        j	        |           dS )zKRegister a tool in the global registry **and** track it as plugin-provided.r   registry)	r   rW   rX   rZ   r[   r9   r^   r5   r_   zPlugin %s registered tool: %sN)
tools.registryrc   registerrT   _plugin_tool_namesaddloggerdebugrH   r   )rU   r   rW   rX   rZ   r[   r9   r^   r5   r_   rc   s              r"   register_toolzPluginContext.register_tool   s     	,+++++%# 	 
	
 
	
 
	
 	(,,T2224dm6H$OOOOOr$   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)Frk   [z] _agent_runningT)rT   _cli_refrh   warninggetattr_interrupt_queueput_pending_input)rU   rl   rm   climsgs        r"   inject_messagezPluginContext.inject_message   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=...)``.)r   rz   r5   r{   r|   pluginz$Plugin %s registered CLI command: %sN)rH   r   rT   _cli_commandsrh   ri   )rU   r   rz   r{   r|   r5   s         r"   register_cli_commandz"PluginContext.register_cli_command   sV     & $m(-
 -
#D) 	;T]=OQUVVVVVr$   c                   |                                                                                     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        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.

        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 command)rZ   r5   r~   z!Plugin %s registered command: /%s)lowerstriplstripreplacerh   rr   rH   r   hermes_cli.commandsr   r.   rT   _plugin_commandsri   )rU   r   rZ   r5   cleanr   s         r"   register_commandzPluginContext.register_command   s5   " 

""$$++C0088cBB 	NNM"   F
	;;;;;;u%%19M&  
  2  	 	 	D	 &:*:m(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   rb   parent_agentagentN)rd   rc   rT   rq   rs   dispatch)rU   r   r   kwargsrc   rw   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)
rT   _context_enginerh   rr   rH   r   agent.context_enginer   r,   info)rU   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$   	hook_namecallbackc           
     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)r   rh   rr   rH   r   joinsortedrT   _hooks
setdefaultappendri   )rU   r   r   s      r"   register_hookzPluginContext.register_hookG  s     K''NN"		&--..   	''	266==hGGG4dm6H)TTTTTr$   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_namer5   zPlugin %s registered skill: %sN)agent.skill_utilsr   
ValueErrorrH   r   matchexistsFileNotFoundErrorrT   _plugin_skillsrh   ri   )rU   r   r?   r5   r   	qualifieds         r"   register_skillzPluginContext.register_skillZ  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$   )rH   r2   rQ   rR   )NNFr3   r3   )r   r   rW   r   rX   rY   rZ   r   r[   r\   r9   r]   r^   r    r5   r   r_   r   r   r`   )rk   )rl   r   rm   r   r   r    )Nr3   )r   r   rz   r   r{   r   r|   r\   r5   r   r   r`   )r3   )r   r   rZ   r   r5   r   r   r`   )r   r   r   rY   r   r   r   r`   )r   r   r   r   r   r`   )r   r   r?   r   r5   r   r   r`   )r@   rA   rB   rC   rV   rj   ry   r   r   r   r   r   r   rE   r$   r"   rP   rP   |   s        GG        %)$(P P P P P>    B '+W W W W W: 	+U +U +U +U +U^< < < <>
 
 
 
@U U U U. 	+
 +
 +
 +
 +
 +
 +
r$   rP   c                  r    e Zd ZdZd"dZd"dZd#dZd$dZd%dZd&dZ	d&dZ
d'dZd(dZd)dZd*dZd+d Zd!S ),PluginManagerz;Central manager that discovers, loads, and invokes plugins.r   r`   c                    i | _         i | _        t                      | _        i | _        d | _        i | _        d| _        d | _        i | _	        d S )NF)
_pluginsr   r%   rf   r   r   r   _discoveredrq   r   )rU   s    r"   rV   zPluginManager.__init__  sQ    1313,/EE.0#13!&9;r$   c           	     D   | j         rdS d| _         g }t                      dz  }|                    |                     |d                     t	          d          rCt          j                    dz  dz  }|                    |                     |d                     |                    |                                            t                      }|D ]h}|j	        |v rHt          |d	
          }d|_        || j        |j	        <   t                              d|j	                   S|                     |           i|r^t                              dt#          | j                  t%          d | j                                        D                                  dS dS )z3Scan all plugin sources and load each plugin found.NTr(   rk   )r=   HERMES_ENABLE_PROJECT_PLUGINSz.hermesprojectF)rH   rM   zdisabled via configzSkipping disabled plugin '%s'z/Plugin discovery complete: %d found, %d enabledc              3  (   K   | ]}|j         	d V  dS )   N)rM   ).0ps     r"   	<genexpr>z2PluginManager.discover_and_load.<locals>.<genexpr>  s)      CC!CACCCCCCr$   )r   r   extend_scan_directoryr#   r   cwd_scan_entry_pointsr0   r   rG   rN   r   rh   ri   _load_pluginr   lensumvalues)rU   	manifestsuser_dirproject_dirr)   rH   loadeds          r"   discover_and_loadzPluginManager.discover_and_load  s    	F*,	 #$$y0--hv-FFGGG 788 	R(**y09<KT11+i1PPQQQ 	0022333 )**! 	( 	(H}((%xGGG4/5hm,<hmLLLh'''' 	KKADM""CCt}3355CCCCC    	 	r$   r?   r   r=   r   List[PluginManifest]c                   g }|                                 s|S t          |                                          D ]}|                                 s|dz  }|                                s|dz  }|                                st                              d|           f	 t          t                              d|           t          j        |	                                          pi }t          |                    d|j                  t          |                    dd                    |                    d	d          |                    d
d          |                    dg           |                    dg           |                    dg           |t          |          	  	        }|                    |           # t          $ r'}t                              d||           Y d}~d}~ww xY w|S )z=Read ``plugin.yaml`` manifests from subdirectories of *path*.zplugin.yamlz
plugin.ymlzSkipping %s (no plugin.yaml)Nu'   PyYAML not installed – cannot load %sr   r4   r3   r5   r6   r9   r;   r<   )	r   r4   r5   r6   r9   r;   r<   r=   r?   zFailed to parse %s: %s)is_dirr   iterdirr   rh   ri   yamlrr   	safe_load	read_textr2   r+   r   r   r   r.   )	rU   r?   r=   r   childmanifest_filedatarH   excs	            r"   r   zPluginManager._scan_directory  s   *,	{{}} 	DLLNN++ 	M 	ME<<>> !M1M '')) 5 % 4 '')) ;UCCCM<NN#Lm\\\~m&=&=&?&?@@FB)&%*55B 7 788 $ ; ;88Hb11!%."!=!=#'88,<b#A#A#'88,<b#A#A!U
 
 
   **** M M M7LLLLLLLLM s   !"GD G
G7G22G7c                   g }	 t           j                                        }t          |d          r|                    t
                    }n=t          |t                    r|                    t
          g           }nd |D             }|D ]3}t          |j
        d|j                  }|                    |           4n2# 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 rE   r   ENTRY_POINTS_GROUPr   eps     r"   
<listcomp>z4PluginManager._scan_entry_points.<locals>.<listcomp>  s%    PPPB=O1O1OR1O1O1Or$   
entrypoint)r   r=   r?   zEntry-point scan failed: %sN)	importlibmetadataentry_pointshasattrr   r   r,   rY   r+   r2   r   valuer   r.   rh   ri   )rU   r   eps	group_epsr   rH   r   s          r"   r   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   B=C 
C1C,,C1rH   r2   c                (    t                    }	 j        dv r                               }n                               }||_        t          |dd          }|(d|_        t                              dj	                   nt                     } ||            fd j        D             |_        t          d  j                                        D             d	  j                                        D             z
            |_         fd
 j        D             |_        d|_        nL# t*          $ r?}t-          |          |_        t                              dj	        |           Y d}~nd}~ww xY w| j        j	        <   dS )z?Import a plugin module and call its ``register(ctx)`` function.)rH   )rk   r   re   Nzno register() functionz&Plugin '%s' has no register() functionc                ^    g | ])}|d  j                                         D             v'|*S )c                ,    h | ]\  }}|j         D ]}|S rE   )rJ   )r   r   r   ns       r"   	<setcomp>z8PluginManager._load_plugin.<locals>.<listcomp>.<setcomp>&  sF     ! ! !#D!!"!3! !  ! ! ! !r$   )r   items)r   trU   s     r"   r   z.PluginManager._load_plugin.<locals>.<listcomp>$  s`     + + + ! !'+}':':'<'<! ! !     r$   c                    h | ]	\  }}||
S rE   rE   )r   hcbss      r"   r   z-PluginManager._load_plugin.<locals>.<setcomp>-  s2       "As  r$   c                ,    h | ]\  }}|j         D ]}|S rE   )rK   )r   r   r   r   s       r"   r   z-PluginManager._load_plugin.<locals>.<setcomp>2  sF       #D!!"!3      r$   c                f    g | ]-}j         |                             d           j        k    +|.S )r~   )r   r+   r   )r   crH   rU   s     r"   r   z.PluginManager._load_plugin.<locals>.<listcomp>8  sF     . . .,Q/33H==NN NNNr$   TzFailed to load plugin '%s': %s)rG   r=   _load_directory_module_load_entrypoint_modulerI   rs   rN   rh   rr   r   rP   rf   rJ   r-   r   r   r   rK   r   rL   rM   r.   r   )rU   rH   r   rI   register_fnctxr   s   ``     r"   r   zPluginManager._load_plugin  s   x000,	Q"55544X>>55h??"FM "&*d;;K"7GWWWW#Hd33C   + + + +#6+ + +' +/ &*k&7&7&9&9  
 '+}':':'<'<  	+ +'. . . . .#4. . .* "& 	Q 	Q 	Qs88FLNN;X]CPPPPPPPP	Q (.hm$$$s   D"D7 7
F 5E;;F types.ModuleTypec                   t          |j                  }|dz  }|                                st          d|           t          t
          j        vr@t          j        t                    }g |_	        t          |_
        |t
          j        t          <   t           d|j                            dd           }t          j                            ||t!          |          g          }||j        t%          d|           t          j                            |          }||_
        t!          |          g|_	        |t
          j        |<   |j                            |           |S )	z=Import a directory-based plugin as ``hermes_plugins.<name>``.z__init__.pyzNo __init__.py in .r   _)submodule_search_locationsNzCannot create module spec for )r   r?   r   r   
_NS_PARENTsysmodulestypes
ModuleType__path____package__r   r   r   utilspec_from_file_locationr   loaderImportErrormodule_from_specexec_module)rU   rH   
plugin_dir	init_filens_pkgmodule_namespecrI   s           r"   r   z$PluginManager._load_directory_moduleD  sM   (-((
.	!! 	G#$E$E$EFFF S[((%j11F FO!+F&,CK
##GGhm&;&;C&E&EGG~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 rE   r   r   s     r"   r   z9PluginManager._load_entrypoint_module.<locals>.<listcomp>j  s%    LLLRX9K-K-K-K-K-Kr$   zEntry point 'z' not found in group '')r   r   r   r   r   r   r,   rY   r+   r   loadr  )rU   rH   r   r   r   s        r"   r   z%PluginManager._load_entrypoint_moduleb  s     --//3!! 	M

);
<<IIT"" 	M 2B77IILLcLLLI 	! 	!Bw(-''wwyy    ( VHMVVASVVV
 
 	
r$   r   r   r   	List[Any]c                *   | 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.
        Nz Hook '%s' callback %s raised: %sr@   rE   )r   r+   r   r.   rh   rr   rs   repr)rU   r   r   	callbacksresultscbretr   s           r"   invoke_hookzPluginManager.invoke_hookx  s    ( KOOIr22	 	 	B
bll6ll?NN3'''   6B
DHH55	        s   A
B9BBList[Dict[str, Any]]c                \   g }t          | j                                                  D ]\  }}|                    ||j        j        |j        j        |j        j        |j        t          |j
                  t          |j                  t          |j                  |j        d	           |S )z7Return a list of info dicts for all discovered plugins.)	r   r4   r5   r=   rM   toolshookscommandsrN   )r   r   r   r   rH   r4   r5   r=   rM   r   rJ   rK   rL   rN   )rU   resultr   r   s       r"   list_pluginszPluginManager.list_plugins  s    ')"4=#6#6#8#899 	 	LD&MM %6#)?#>$o4%~ !899 !899 #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)r   r+   )rU   r&  entrys      r"   find_plugin_skillzPluginManager.find_plugin_skill  s+    #''77 %/uV}}4/r$   plugin_namer:   c                r    | dt          fd| j                                        D                       S )zCReturn sorted bare names of all skills registered by *plugin_name*.r   c              3  X   K   | ]$\  }}|                               |d          V  %dS )r   N)
startswith)r   qneprefixs      r"   r   z3PluginManager.list_plugin_skills.<locals>.<genexpr>  sQ       
 
A}}V$$
kN
 
 
 
 
 
r$   )r   r   r   )rU   r+  r1  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)r   pop)rU   r&  s     r"   remove_plugin_skillz!PluginManager.remove_plugin_skill  s!    55555r$   Nr   )r?   r   r=   r   r   r   )r   r   )rH   r2   r   r`   )rH   r2   r   r   r   r   r   r   r   r  )r   r  )r&  r   r   r'  )r+  r   r   r:   )r&  r   r   r`   )r@   rA   rB   rC   rV   r   r   r   r   r   r   r  r%  r*  r2  r5  rE   r$   r"   r   r     s       EE
< 
< 
< 
< $ $ $ $T$ $ $ $T   :2. 2. 2. 2.h   <
 
 
 
," " " "P   .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.)r7  r   rE   r$   r"   get_plugin_managerr9    s     '//r$   r`   c                 F    t                                                       dS )z+Discover and load all plugins (idempotent).N)r9  r   rE   r$   r"   discover_pluginsr;    s     **,,,,,r$   r   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.
    )r9  r  )r   r   s     r"   r  r    s&    
 ,+I@@@@@r$   r3   r   r   Optional[Dict[str, Any]]task_id
session_idtool_call_idr>   c                (   t          d| t          |t                    r|ni |||          }|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   )r   r   r>  r?  r@  actionblockmessageN)r  r,   rY   r+   r   )r   r   r>  r?  r@  hook_resultsr$  rD  s           r"   get_pre_tool_call_block_messagerF    s    $ d++3TT!  L   &$'' 	::h7****Y''gs## 	 	NNN4r$   c                 (    t                      j        S )z5Return the plugin-registered context engine, or None.)r9  r   rE   r$   r"   get_plugin_context_enginerH    s    //r$   Optional[Callable]c                f    t                      j                            |           }|r|d         ndS )zFReturn the handler for a plugin-registered slash command, or ``None``.rZ   N)r9  r   r+   )r   r)  s     r"   get_plugin_command_handlerrK    s3      155d;;E$.5$.r$   Dict[str, dict]c                 (    t                      j        S )u   Return the full plugin commands dict (name → {handler, description, plugin}).

    Safe to call before discovery — returns an empty dict if no plugins loaded.
    )r9  r   rE   r$   r"   get_plugin_commandsrN    s    
 00r$   List[tuple]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   rb   u   🔌 r   r   r   )r9  rf   rd   rc   r.   	get_entryrW   r   r   r   r   r   rJ   r   r+   r   titlerH   r5   r   )rQ   rc   toolset_toolstoolset_pluginr   r)  ts_namer   r$  ts_keyr~   labeldescs                 r"   get_plugin_toolsetsrZ  !  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    )r   r%   )r   r   r   r6  )r3   r3   r3   )r   r   r   r=  r>  r   r?  r   r@  r   r   r>   )r   r   r   rI  )r   rL  )r   rO  )2rC   
__future__r   r   importlib.metadataimportlib.utilloggingr  r  dataclassesr   r   pathlibr   typingr   r   r	   r
   r   r   r   hermes_constantsr   utilsr   r   r  	getLoggerr@   rh   r   rD   r   r  r#   r0   r2   rG   rP   r   r7  r9  r;  r  rF  rH  rK  rN  rZ  rE   r$   r"   <module>re     s$    6 # " " " " "              



  ( ( ( ( ( ( ( (       B B B B B B B B B B B B B B B B B B , , , , , , ! ! ! ! ! !KKKK   DDD 
	8	$	$       , 
! ! ! !
            	  	  	  	  	  	  	  	  I
 I
 I
 I
 I
 I
 I
 I
`{6 {6 {6 {6 {6 {6 {6 {6D
 ,0 / / / /   - - - -
A A A A $ $ $ $ $N0 0 0
/ / / /1 1 1 1* * * * * *s   A AA