+
    iV                      a  0 t $ R t^ RIHt ^ RIt^ RIt^ RIt^ RIt^ RIt^ RI	t	^ RI
t
^ RIHtHt ^ RIHt ^ RIHtHtHtHtHtHtHt ^ RIHt  ^ RIt]P8                  ! ]4      t0 R%mtR] R&   R	t!R
t"R R lt#R R lt$] ! R R4      4       t%] ! R R4      4       t& ! R R4      t' ! R R4      t(Rs)R] R&   R R lt*R R lt+R R lt,R R  lt-R! R" lt.R# R$ lt/R#   ] d    Rt Li ; i)&uR  
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env_var_enabledSet[str]VALID_HOOKSzhermes_agent.pluginshermes_pluginsc                    V ^8  d   QhRRRR/# )   namestrreturnbool )formats   "//home/ubuntu/hermes-agent/hermes_cli/plugins.py__annotate__r   F   s     ! !s !t !    c                    \        V 4      # )z<Return True when an env var is set to a truthy opt-in value.r   )r   s   &r   _env_enabledr   F   s    4  r   c                   V ^8  d   QhRR/# )r   r   setr   )r   s   "r   r   r   K   s      s r   c                      ^ RI Hp  V ! 4       pVP                  R/ 4      P                  R. 4      p\        V\        4      '       d   \        V4      # \        4       #   \         d    \        4       u # i ; i)z0Read the disabled plugins list from config.yaml.)load_configpluginsdisabled)hermes_cli.configr"   get
isinstancelistr    	Exception)r"   configr$   s      r   _get_disabled_pluginsr+   K   sa    1::i,00R@ *8T : :s8}EE us   AA 	A A43A4c                      ] tR t^Zt$ RtR]R&   RtR]R&   RtR]R&   RtR]R&   ]	! ]
R7      tR	]R
&   ]	! ]
R7      tR]R&   ]	! ]
R7      tR]R&   RtR]R&   RtR]R&   RtR# )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]pathr   )__name__
__module____qualname____firstlineno____doc____annotations__r/   r0   r1   r   r(   r4   r6   r7   r8   r:   __static_attributes__r   r   r   r-   r-   Z   sj    :
IGSKFC5:45PL2P %d ;NI; %d ;NI;FCD-r   r-   c                      ] tR t^it$ RtR]R&   RtR]R&   ]! ]R7      t	R]R	&   ]! ]R7      t
R]R
&   RtR]R&   RtR]R&   RtR# )LoadedPluginz)Runtime state for a single loaded plugin.r-   manifestNzOptional[types.ModuleType]moduler2   r5   tools_registeredhooks_registeredFr   enabledr9   errorr   )r;   r<   r=   r>   r?   r@   rE   r   r(   rF   rG   rH   rI   rA   r   r   r   rC   rC   i   sJ    3)-F&-"'"=i="'"=i=GTE=r   rC   c                  b    ] tR t^ytRtR R ltRR R lltRR R lltRR	 R
 lltR R lt	Rt
R# )PluginContextz=Facade given to plugins so they can register tools and hooks.c                    V ^8  d   QhRRRR/# )r   rD   r-   managerz'PluginManager'r   )r   s   "r   r   PluginContext.__annotate__|   s          /  r   c                	    Wn         W n        R # )N)rD   _manager)selfrD   rM   s   &&&r   __init__PluginContext.__init__|   s     r   Nc               @    V ^8  d   QhRRRRRRRRRR	R
RRRRRRRRR/
# )r   r   r   toolsetschemadicthandlerr   check_fnCallable | Noner4   zlist | Noneis_asyncr   r0   emojir   Noner   )r   s   "r   r   rN      sz     P PP P 	P
 P "P "P P P P 
Pr   c
                    ^ RI Hp
 V
P                  VVVVVVVVV	R7	       V P                  P                  P                  V4       \        P                  RV P                  P                  V4       R# )zKRegister a tool in the global registry **and** track it as plugin-provided.registry)	r   rU   rV   rX   rY   r4   r[   r0   r\   zPlugin %s registered tool: %sN)
tools.registryr`   registerrP   _plugin_tool_namesaddloggerdebugrD   r   )rQ   r   rU   rV   rX   rY   r4   r[   r0   r\   r`   s   &&&&&&&&&& r   register_toolPluginContext.register_tool   sj     	,%# 	 
	
 	((,,T24dmm6H6H$Or   c               $    V ^8  d   QhRRRRRR/# )r   contentr   roler   r   r   )r   s   "r   r   rN      s!      c  $ r   c                    V P                   P                  pVf   \        P                  R4       R# VR8X  d   TMRV RV 2p\	        VRR4      '       d   VP
                  P                  V4       R# VP                  P                  V4       R# )at  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.
z@inject_message: no CLI reference (not available in gateway mode)Fuser[z] _agent_runningT)rP   _cli_refre   warninggetattr_interrupt_queueput_pending_input)rQ   rj   rk   climsgs   &&&  r   inject_messagePluginContext.inject_message   s     mm$$;NN]^gqb	-B3(%00  $$S)  ""3'r   c               0    V ^8  d   QhRRRRRRRRRRR	R
/# )r   r   r   helpsetup_fnr   
handler_fnrZ   r0   r   r]   r   )r   s   "r   r   rN      sN     W WW W 	W
 $W W 
Wr   c                    RVRVRVRVRVRV P                   P                  /V P                  P                  V&   \        P                  RV P                   P                  V4       R# )	zRegister 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   r{   r0   r|   r}   pluginz$Plugin %s registered CLI command: %sN)rD   r   rP   _cli_commandsre   rf   )rQ   r   r{   r|   r}   r0   s   &&&&&&r   register_cli_command"PluginContext.register_cli_command   s`     DD;*dmm((-
##D) 	;T]]=O=OQUVr   c               $    V ^8  d   QhRRRRRR/# )r   	hook_namer   callbackr   r   r]   r   )r   s   "r   r   rN      s&     U Us Uh U4 Ur   c           
     l   V\         9  dI   \        P                  RV P                  P                  VRP                  \        \         4      4      4       V P                  P                  P                  V. 4      P                  V4       \        P                  RV P                  P                  V4       R# )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   re   rq   rD   r   joinsortedrP   _hooks
setdefaultappendrf   )rQ   r   r   s   &&&r   register_hookPluginContext.register_hook   s     K'NN""		&-. 	''	26==hG4dmm6H6H)Tr   )rP   rD   )NNFr.   r.   )rm   )Nr.   )r;   r<   r=   r>   r?   rR   rg   rx   r   r   rA   r   r   r   rK   rK   y   s'    G P>8W4U Ur   rK   c                      ] tR t^tRtR R ltR R ltR R ltR R	 ltR
 R lt	R R lt
R R ltR R ltR R ltRtR# )PluginManagerz;Central manager that discovers, loads, and invokes plugins.c                   V ^8  d   QhRR/# r   r   r]   r   )r   s   "r   r   PluginManager.__annotate__   s      $ r   c                	j    / V n         / V n        \        4       V n        / V n        R V n        RV n        R# )FN)_pluginsr   r    rc   r   _discoveredrp   )rQ   s   &r   rR   PluginManager.__init__   s1    1313,/E.0!&r   c                   V ^8  d   QhRR/# r   r   )r   s   "r   r   r      s     % %4 %r   c           	        V P                   '       d   R# RV n         . p\        P                  P                  R\        P                  P                  R4      4      p\        V4      R,          pVP                  V P                  VRR7      4       \        R4      '       dF   \        P                  ! 4       R	,          R,          pVP                  V P                  VR
R7      4       VP                  V P                  4       4       \        4       pV Fs  pVP                  V9   dO   \        VRR7      pRVn        WpP                   VP                  &   \"        P%                  RVP                  4       Kb  V P'                  V4       Ku  	  V'       dU   \"        P)                  R\+        V P                   4      \-        R V P                   P/                  4        4       4      4       R# R# )z3Scan all plugin sources and load each plugin found.NTHERMES_HOMEz	~/.hermesr#   rm   )r8   HERMES_ENABLE_PROJECT_PLUGINSz.hermesprojectF)rD   rH   zdisabled via configzSkipping disabled plugin '%s'z/Plugin discovery complete: %d found, %d enabledc              3  L   "   T F  qP                   '       g   K  ^x  K  	  R# 5i)   N)rH   ).0ps   & r   	<genexpr>2PluginManager.discover_and_load.<locals>.<genexpr>   s     C5!AA5s   $
$)r   osenvironr&   r:   
expanduserr   extend_scan_directoryr   cwd_scan_entry_pointsr+   r   rC   rI   r   re   rf   _load_plugininfolensumvalues)rQ   	manifestshermes_homeuser_dirproject_dirr$   rD   loadeds   &       r   discover_and_loadPluginManager.discover_and_load   sq   *,	 jjnn]BGG4F4F{4ST$y0--hv-FG 788((*y09<KT11+i1PQ 	0023 )*!H}}(%xG4/5hmm,<hmmLh' " KKADMM"Ct}}335CC r   c               $    V ^8  d   QhRRRRRR/# )r   r:   r   r8   r   r   List[PluginManifest]r   )r   s   "r   r   r   '  s"     $ $D $# $:N $r   c                   . pVP                  4       '       g   V# \        VP                  4       4       EFr  pVP                  4       '       g   K  VR,          pVP                  4       '       g
   VR,          pVP                  4       '       g   \        P                  RV4       Kr   \        f   \        P                  RV4       K  \        P                  ! VP                  4       4      ;'       g    / p\        VP                  RVP                  4      \        VP                  RR4      4      VP                  R	R4      VP                  R
R4      VP                  R. 4      VP                  R. 4      VP                  R. 4      V\        V4      R7	      pVP                  V4       EKu  	  V#   \         d#   p\        P                  RYX4        Rp?EK  Rp?ii ; i)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   r/   r.   r0   r1   r4   r6   r7   )	r   r/   r0   r1   r4   r6   r7   r8   r:   zFailed to parse %s: %s)is_dirr   iterdirexistsre   rf   yamlrq   	safe_load	read_textr-   r&   r   r   r   r)   )	rQ   r:   r8   r   childmanifest_filedatarD   excs	   &&&      r   r   PluginManager._scan_directory'  sz   *,	{{}}DLLN+E<<>>!M1M '')) % 4 ''));UCM<NN#Lm\~~m&=&=&?@FFB)&%**5B 78 $ ;88Hb1!%."!=#'88,<b#A#'88,<b#A!U
   *5 ,<   M7LLMs%   'F-*F-2B4F--G8GGc                   V ^8  d   QhRR/# )r   r   r   r   )r   s   "r   r   r   Q  s      $8 r   c                "   . p \         P                  P                  4       p\        VR4      '       d   VP	                  \
        R7      pMT\        V\        4      '       d   VP                  \
        . 4      pM'V Uu. uF  qDP                  \
        8X  g   K  VNK  	  ppV F6  p\        VP                  RVP                  R7      pVP                  V4       K8  	  V# u upi   \         d"   p\        P!                  RT4        Rp?T# Rp?ii ; i)z7Check ``importlib.metadata`` for pip-installed plugins.selectgroup
entrypoint)r   r8   r:   zEntry-point scan failed: %sN)	importlibmetadataentry_pointshasattrr   ENTRY_POINTS_GROUPr'   rW   r&   r   r-   r   valuer   r)   re   rf   )rQ   r   eps	group_epseprD   r   s   &      r   r    PluginManager._scan_entry_pointsQ  s    *,		=$$113CsH%%JJ-?J@	C&&GG$6;	*-P#B=O1ORR#	P)'
   *    Q  	=LL6<<	=s0   A8C" <CC>C" C" "D-D		Dc                    V ^8  d   QhRRRR/# )r   rD   r-   r   r]   r   )r   s   "r   r   r   n  s     .. ..^ .. ..r   c                6   \        VR7      p VP                  R9   d   V P                  V4      pMV P                  V4      pW2n        \        VRR4      pVf*   RVn        \        P                  RVP                  4       EM\        W4      pV! V4       V P                   UUUU	u. uFO  pTV P                  P                  4        UUU	u0 uF  w  rxVP                   F  p	V	kK  	  K  	  up	pp9  g   KM  VNKQ  	  up	pppVn        \        V P                   P                  4        U
Uu0 uF  w  rV'       g   K  V
kK  	  upp
V P                  P                  4        UUU
u0 uF  w  rxVP"                   F  p
V
kK  	  K  	  up
pp,
          4      Vn        RVn         W P                  VP                  &   R# u up	ppi u up	pppi u upp
i u up
ppi   \&         d<   p\)        T4      Tn        \        P                  RTP                  T4        Rp?LwRp?ii ; i)	z?Import a plugin module and call its ``register(ctx)`` function.)rD   rb   Nzno register() functionz&Plugin '%s' has no register() functionTzFailed to load plugin '%s': %s)rm   r   )rC   r8   _load_directory_module_load_entrypoint_modulerE   rr   rI   re   rq   r   rK   rc   r   itemsrF   r(   r   rG   rH   r)   r   )rQ   rD   r   rE   register_fnctxtr   r   nhcbsr   s   &&           r   r   PluginManager._load_pluginn  s   x0(	Q"5544X>55h?"M "&*d;K"7GW#H3C #66+6!'+}}':':'<!'<GD!"!3!3A !3 '<!  A6+' +/ '+kk&7&7&9&9FA &9 (,}}':':'<'<GD!"!3!3A !3 '<	+' "& (.hmm$1!+
  	Qs8FLNN;X]]CPP	Qsf   A2G $G &$F=

 F6*F=
5F=
;-G (G
9G
?!G   G G 6F=
=G H1HHc                    V ^8  d   QhRRRR/# r   rD   r-   r   ztypes.ModuleTyper   )r   s   "r   r   r     s      ~ BR r   c                   \        VP                  4      pVR,          pVP                  4       '       g   \        RV 24      h\        \
        P                  9  dD   \        P                  ! \        4      p. Vn	        \        Vn
        V\
        P                  \        &   \         RVP                  P                  RR4       2p\        P                  P                  VV\!        V4      .R7      pVe   VP"                  f   \%        RV 24      h\        P                  P'                  V4      pWWn
        \!        V4      .Vn	        V\
        P                  V&   VP"                  P)                  V4       V# )z=Import a directory-based plugin as ``hermes_plugins.<name>``.z__init__.pyzNo __init__.py in .-_)submodule_search_locationszCannot create module spec for )r   r:   r   FileNotFoundError
_NS_PARENTsysmodulestypes
ModuleType__path____package__r   replacer   utilspec_from_file_locationr   loaderImportErrormodule_from_specexec_module)rQ   rD   
plugin_dir	init_filens_pkgmodule_namespecrE   s   &&      r   r   $PluginManager._load_directory_module  s2   (--(
.	!!#&8$EFF S[[(%%j1F FO!+F&,CKK
##Ahmm&;&;C&E%FG~~55(+J'8 6 

 <4;;. >ykJKK006(z?+#)K 'r   c                    V ^8  d   QhRRRR/# r   r   )r   s   "r   r   r     s     
 
 
CS 
r   c                   \         P                  P                  4       p\        VR4      '       d   VP	                  \
        R7      pMT\        V\        4      '       d   VP                  \
        . 4      pM'V Uu. uF  qDP                  \
        8X  g   K  VNK  	  ppV F0  pVP                  VP                  8X  g   K   VP                  4       u # 	  \        RVP                   R\
         R24      hu upi )z:Load a pip-installed plugin via its entry-point reference.r   r   zEntry point 'z' not found in group '')r   r   r   r   r   r   r'   rW   r&   r   r   loadr   )rQ   rD   r   r   r   s   &&   r   r   %PluginManager._load_entrypoint_module  s      --/3!!

);
<IT"" 2B7I&)LcXX9K-KcILBww(--'wwy   HMM?*@AS@TTUV
 	
 Ms   9C2C2c               $    V ^8  d   QhRRRRRR/# r   r   r   kwargsr   r   z	List[Any]r   )r   s   "r   r   r     s!     " "S "C "I "r   c                   V P                   P                  V. 4      p. pV F#  p V! R/ VB pVe   VP                  V4       K#  K%  	  V#   \         d8   p\        P                  RT\        TR\        T4      4      T4        Rp?Kf  Rp?ii ; i)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;   r   )r   r&   r   r)   re   rq   rr   repr)rQ   r   r  	callbacksresultscbretr   s   &&,     r   invoke_hookPluginManager.invoke_hook  s    ( KKOOIr2	B
l6l?NN3' #    6B
DH5	 s   A

B,BBc                   V ^8  d   QhRR/# )r   r   zList[Dict[str, Any]]r   )r   s   "r   r   r     s      2 r   c                   . p\        V P                  P                  4       4       F  w  r#VP                  RVRVP                  P
                  RVP                  P                  RVP                  P                  RVP                  R\        VP                  4      R\        VP                  4      RVP                  /4       K  	  V# )	z7Return a list of info dicts for all discovered plugins.r   r/   r0   r8   rH   toolshooksrI   )r   r   r   r   rD   r/   r0   r8   rH   r   rF   rG   rI   )rQ   resultr   r   s   &   r   list_pluginsPluginManager.list_plugins  s    ')"4==#6#6#89LDMMDv66!6??#>#>foo44v~~S!8!89S!8!89V\\	 : r   )r   rp   r   r   rc   r   N)r;   r<   r=   r>   r?   rR   r   r   r   r   r   r   r
  r  rA   r   r   r   r   r      s;    E%V$T:..`<
,"P r   r   zOptional[PluginManager]_plugin_managerc                   V ^8  d   QhRR/# )r   r   r   r   )r   s   "r   r   r     s      M r   c                 2    \         f   \        4       s \         # )z>Return (and lazily create) the global PluginManager singleton.)r  r   r   r   r   get_plugin_managerr    s     '/r   c                   V ^8  d   QhRR/# r   r   )r   s   "r   r   r     s     - -$ -r   c                 6    \        4       P                  4        R# )z+Discover and load all plugins (idempotent).N)r  r   r   r   r   discover_pluginsr    s    **,r   c               $    V ^8  d   QhRRRRRR/# r  r   )r   s   "r   r   r   !  s&     A A3 A# A) Ar   c                8    \        4       P                  ! V 3/ VB # )ztInvoke a lifecycle hook on all loaded plugins.

Returns a list of non-``None`` return values from plugin callbacks.
)r  r
  )r   r  s   &,r   r
  r
  !  s    
 ++I@@@r   c                   V ^8  d   QhRR/# )r   r   r   r   )r   s   "r   r   r   )  s     3 3x 3r   c                 *    \        4       P                  # )z3Return the set of tool names registered by plugins.)r  rc   r   r   r   get_plugin_tool_namesr  )  s    222r   c                   V ^8  d   QhRR/# )r   r   zDict[str, dict]r   )r   s   "r   r   r   .  s     4 4 4r   c                 <    \        \        4       P                  4      # )zReturn CLI commands registered by general plugins.

Returns a dict of ``{name: {help, setup_fn, handler_fn, ...}}``
suitable for wiring into argparse subparsers.
)rW   r  r   r   r   r   get_plugin_cli_commandsr!  .  s     "$2233r   c                   V ^8  d   QhRR/# )r   r   zList[tuple]r   )r   s   "r   r   r   7  s     * *[ *r   c                    \        4       p V P                  '       g   . #  ^ RIHp / p/ pT P                   F_  pTP
                  P                  T4      pT'       g   K(  TP                  pTP                  T. 4      P                  TP                  4       Ka  	  T P                  P                  4        Fl  w  rxTP                   FW  pTP
                  P                  T4      pT'       g   K(  TP                  T9   g   K;  TP                  TP                  T4       KY  	  Kn  	  . p	\        T4       F  p
TP                  T
4      pRT
P                  RR4      P!                  4        2pT'       d4   TP"                  P$                  '       d   TP"                  P$                  pM RP'                  \        Y*,          4      4      pT	P                  YT34       K  	  T	#   \         d    . u # i ; i)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_   u   🔌 r    r   )r  rc   ra   r`   r)   _toolsr&   rU   r   r   r   r   r   rF   r   r   titlerD   r0   r   )rM   r`   toolset_toolstoolset_plugin	tool_nameentryts_namer   r  ts_keyr   labeldescs                 r   get_plugin_toolsetsr0  7  s    !"G%%%	+
 +-M.0N//	##I.]]  R(//

; 0 !))//100IOO''	2Eu-7))%--@ 1 2 F'##F+sC06689:foo111??..D99VM$9:;Dvd+, ( M=  	s   G G+*G+>   pre_llm_callpost_llm_callpre_tool_callon_session_endpost_tool_callpre_api_requeston_session_startpost_api_request)0__conditional_annotations__r?   
__future__r   r   importlib.metadataimportlib.utilloggingr   r   r   dataclassesr   r   pathlibr   typingr   r   r   r	   r
   r   r   utilsr   r   r   	getLoggerr;   re   r   r@   r   r   r   r+   r-   rC   rK   r   r  r  r  r
  r  r!  r0  )r9  s   @r   <module>rC     s  6 #     	 
  (  B B B ! 
		8	$	X 	 , 
!
         mU mUh] ]H	 ,0( /-
A3
4*U  Ds   C 	C*)C*