
    sj\?                   ,
   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ZddlZddlZddlZddlZddlmZ ddlmZmZ ddlmZ ddlmZ ddlmZmZmZmZm Z m!Z!m"Z"m#Z#m$Z$m%Z%m&Z& dd	l'm(Z(m)Z)m*Z*m+Z+ dd
l,m-Z- ddl.m/Z/m0Z0 ddl1m2Z2m3Z3 ddl4m5Z5m6Z6 ddl7m8Z8m9Z9m:Z: ddl7m;Z< ddl=m>Z>m?Z?m@Z@ ddZA	 ddlBZBn# eC$ r dZBY nw xY w G d deD          ZE ejF        eG          ZH ejI        dd          J                                K                                dv aLdaMdddZN eN             h dZOdePd <   d!hZQdePd"<   d#ZRd$ZSdd*ZTdd,ZUdd.ZV eWd/h          ZXd0ZYd0ZZd1Z[d2Z\ ej]        d3          Z^d4Z_d5Z`d6Zadd7Zbdd:Zcdd<Zdd=Zed>Zfd?Zg eh            Zid@Zj ejk                    Zli ZmdAePdB<   dC ZneddE            ZoddGZpddIZqddKZrh dLZsdePdM<   ddOZtddQZuh dRZvdePdS<   dTZwexfexfeyfeyfeyezfeyezfe{fe{fe|fe|fe}fe}fdUZ~dVePdW<   dd[Zdd`ZddcZd dfZd!d"dkZd#dmZd!d$dnZe G do dp                      Z edqr           G ds dt                      Z edqr           G du dv                      Z edqr           G dw dx                      Z edqr           G dy dz                      Ze G d{ d|                      Ze G d} d~                      Z ej]        d          Z eWh d          Z ej]        d          ZdZi ZdePd<    ej                    Zd%dZd&dZd'dZd(dZd)dZed*d            Z G d d          Z G d d          Z G d d          ZdadePd<   i ZdePd<    ejk                    ZddZd+dZd,dZd-dZd.dZdddZdadePd<    ej                    Zd-dZd/d0dZd Zd-dZd1dZd2dZd2dZ	 d3d4dZd,dZd5dZd6dZd7dZd8dZd9dZd:dĄZdddŜd;d̄Z ej                    Z edqr           G d̈́ dΦ                      Z	 d<d=dӄZd-dԄZ	 	 	 	 	 	 d>d?d߄Z	 	 	 	 	 	 d>d@dZ	 	 	 	 	 	 d>dAdZ	 	 	 	 	 	 d>dAdZdddddBdZ	 	 	 	 	 	 d>dCdZdddddddddDdZdddddddddddddEdZĐddFd ZŐd ZƐdGdZdZȐdHdZɐdIdZʐdJd	ZːdKdZ̐dLdZdS (M  u  
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)contextmanager)	dataclassfieldwraps)Path)AnyCallableDictIterableListMappingOptionalSetTupleTypeUnion)get_hermes_homehermes_home_keyreset_hermes_home_overrideset_hermes_home_override)replacement_coordinator)env_var_enabledfast_safe_load)cfg_getload_config_readonly)OBSERVER_SCHEMA_VERSIONVALID_MIDDLEWARE)CAPABILITY_REGISTRYVALID_CAPABILITY_IDSplugin_capability_granted)parse_declared_capabilities)LEGACY_RELAY_PLUGIN_KEYSRELAY_PLUGINS_CONFIG_ENVlegacy_relay_plugin_keys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    6/home/agent/.hermes/hermes-agent/hermes_cli/plugins.pyget_bundled_plugins_dirr2   Q   sM     9566L "L!!!>>!!##*1I==    c                      e Zd ZdZdS )PluginToolOverrideErrorzRaised when a plugin attempts to override a built-in tool without
    operator opt-in via ``plugins.entries.<plugin_id>.allow_tool_override``.
    N)__name__
__module____qualname____doc__ r3   r1   r5   r5   c   s           r3   r5   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)rA   handlers     r1   _install_plugin_debug_handlerrV      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    r3   >%   
pre_verifypre_commandpre_llm_callon_stream_endpost_llm_callpre_tool_callsubagent_stopon_session_endpost_tool_callsubagent_starton_stream_deltaon_stream_startpre_api_requeston_session_reseton_session_startpost_api_requestapi_request_errorpre_transcriptionon_interim_messageon_skill_lifecyclekanban_task_blockedkanban_task_claimedon_session_finalizepre_approval_requestpre_gateway_dispatchtransform_llm_outputkanban_task_completedtransform_tool_resultgateway_platform_eventon_kanban_task_updatedpost_approval_responseon_kanban_dispatch_tickon_kanban_worker_exitedon_kanban_worker_spawnedtransform_terminal_outputon_kanban_worker_stale_claim"transform_api_error_classificationzSet[str]VALID_HOOKSr{   SHELL_UNSUPPORTED_HOOKSzhermes_agent.pluginsz hermes_agent.plugin_capabilitiesentry_pointsr
   groupstrlistc                    t          | d          r#t          |                                         S t          | t                    r#t          |                     g                     S fd| D             S )zCReturn one metadata entry-point group across supported Python APIs.selectr   c                *    g | ]}|j         k    |S r:   r   ).0epr   s     r1   
<listcomp>z-_select_entry_point_group.<locals>.<listcomp>  s%    ;;;2U):):B):):):r3   )hasattrr   r   
isinstancedictget)r~   r   s    `r1   _select_entry_point_groupr     s~    |X&& 6L''e'44555,%% 1L$$UB//000;;;;;;;;r3   List['PluginManifest']c                   
 g } 	 t           j                                        }t          |t                    }t          |t
                    }n4# t          $ r'}t                              d|           | cY d}~S d}~ww xY w|D ]]	 g }t          D ]?}j
         d| 
t          
fd|D                       r|                    |           @t          dd          }t          |dd          }t          j
        t          t          |dd          pd          |%t          |                    d	d          pd          ndd
j        j
        t%          |j
                            }	t'          j                  |	_        |                     |	           # t          $ r6}t                              dt          dd          |           Y d}~Wd}~ww xY w| S )u  Return metadata-only manifests for installed entry-point plugins.

    Composes the full entry-point manifest contract in one place:

    * **Kind classification** — the module source is resolved import-free
      (``_resolve_module_source``) and scanned for provider markers
      (``_detect_kind_from_source``), so memory providers (``exclusive``)
      and model providers (``model-provider``) are routed to their own
      discovery systems instead of being eagerly imported here.
    * **Capability declarations** — read from the companion
      ``hermes_agent.plugin_capabilities`` entry-point group (declarations
      named ``<plugin-id>.<capability-id>`` pointing at the same object),
      so consent/introspection is accurate without importing plugin code.

    Failures are isolated per entry point: one malformed distribution must
    not blank the manifests of every other installed plugin.
    zEntry-point scan failed: %sN.c              3  N   K   | ]}|j         k    o|j        j        k    V   d S N)namevalue)r   declarationdeclaration_namer   s     r1   	<genexpr>z0discover_entrypoint_manifests.<locals>.<genexpr>  sT         $  $(88 6#)RX5     r3   distmetadataversionr<   Summary
entrypoint)r   r   descriptionsourcepathkeycapabilitiesz'Entry-point manifest for %r skipped: %sr   ?)	importlibr   r~   r   ENTRY_POINTS_GROUPENTRY_POINT_CAPABILITIES_GROUP	ExceptionrQ   rT   r!   r   anyappendgetattrPluginManifestr   r   r   _parse_declared_capabilities_classify_entrypoint_value_kindkind)	manifestseps	group_epscapability_epsexcr   
capabilityr   r   manifestr   r   s             @@r1   discover_entrypoint_manifestsr     sg   $ ')I --//-c3EFF	2/
 
    2C888  # #"	L2 4 4
&(g#<#<
#<#<       (6     4
 !''
3332vt,,DtZ66H%WGD)R88>B??  + Y339r:::#XG9 "'   H <BHEEHMX&&&& 	 	 	LL9FC((       	 s7   AA 
B A;5B ;B 	DF!!
G!++GG!r   c                    	 t          |                               dd          d                                         }|sdS t          t	          |                    pdS # t
          $ r Y dS w xY w)a?  Classify an entry-point target by import-free source scan.

    Module-level twin of ``PluginManager._classify_entrypoint_kind`` so
    ``discover_entrypoint_manifests()`` callers outside the manager (the
    CLI capabilities path) get identical routing. Unresolvable or
    non-Python modules stay ``standalone``.
    :   r   
standalone)r   splitrE   _detect_kind_from_source_resolve_module_sourcer   )r   module_names     r1   r   r     s    %jj&&sA..q17799 	 <'";//
 
 	    ||s   =A A 
A-,A-after_memoryi      i@  z^[a-z0-9][a-z0-9._-]{0,127}$z## Plugin Context: z%<!-- hermes-plugin-sections:start -->z#<!-- hermes-plugin-sections:end -->c                z    t          | t                    o&t          t                              |                     S )zDReturn whether *value* is a stable, heading-safe section identifier.)r   r   rB   _SYSTEM_PROMPT_SECTION_ID_RE	fullmatchr   s    r1   !is_valid_system_prompt_section_idr     s/    eS!!Yd+G+Q+QRW+X+X&Y&YYr3   
section_idcontentc                >    t            |  dt          |           d| S )zJRender an auditable, length-framed block recoverable from the full prompt.z"
<!-- hermes-plugin-section-chars:z -->

)%_SYSTEM_PROMPT_SECTION_HEADING_PREFIXlen)r   r   s     r1   format_system_prompt_sectionr     s=     1 	* 	 	,/LL	 		 	r3   sectionsc                x    | sdS d | D             }t            dd                    |          z   dt           z   S )z=Render the canonical container used for persistence recovery.r<   c                B    g | ]}t          |j        |j                  S r:   )r   idr   r   items     r1   r   z1format_system_prompt_sections.<locals>.<listcomp>  s'    WWWd*47DLAAWWWr3   
z

)PLUGIN_SECTIONS_STARTjoinPLUGIN_SECTIONS_END)r   blockss     r1   format_system_prompt_sectionsr     sQ     rWWhWWWF#'''&++f*=*==@ZEX@Z@ZZZr3   hermes   @   hermes_pluginszDict[str, str]_BARE_MODULE_SCOPEc                <     t                      fd            }|S )uD   Make snapshot → write → lease attachment one atomic transaction.c                 n    t          j                    5   | i |cd d d            S # 1 swxY w Y   d S r   )r   transaction)argskwargsmethods     r1   wrappedz(_serialized_replacement.<locals>.wrapped(  s    $022 	+ 	+64*6**	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+s   *..r   )r   r   s   ` r1   _serialized_replacementr   &  s3    
6]]+ + + + ]+ Nr3   homec              #  x   K   t          |           }	 dV  t          |           dS # t          |           w xY w)zBBind discovery and loading to the manager's immutable Hermes home.N)r   r   )r   tokens     r1   _plugin_home_scoper   0  sL       %T**E*"5)))))"5))))s   ( 9r   c                     t          |           S )z<Return True when an env var is set to a truthy opt-in value.)r   r   s    r1   _env_enabledr   :  s    4   r3   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.configr   r   r   r   r   r   )r   configr   s      r1   _get_disabled_pluginsr   ?  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   r   r*   Nenabled)r   r   r   r   r   r   r   r   )r   r   plugins_cfgr   s       r1   _get_enabled_pluginsr   O  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	exclusiver   model-provider_VALID_PLUGIN_KINDSr   c                $   d                     d |                                 D                       }|                    d          pd}t          j        |                     d                                                    dd         }d| d	| S )
zGReturn a readable, collision-resistant namespace for a portable plugin.r<   c              3  v   K   | ]4}|                                 r|                                s|d v r|ndV  5dS )z_--N)isasciiisalnum)r   chs     r1   r   z,_portable_skill_namespace.<locals>.<genexpr>w  s\         jjllD

Dd

     r3   z-_pluginutf-8Nr   agent-plugin-r  )r   rF   rE   hashlibsha256encode	hexdigest)r   slugdigests      r1   _portable_skill_namespacer  t  s     77  ))++    D ::d'xD^CJJw//00::<<RaR@F*4**&***r3   objectc                     t           t                    r!d                     fddD                       S  dnt                     S )zFNormalize a manifest author value for the string PluginManifest field., c              3  l   K   | ].}                     |          t          |                   V  /d S r   )r   r   )r   r   r   s     r1   r   z"_display_author.<locals>.<genexpr>  sU       
 
yy
e
 
 
 
 
 
r3   )r   emailurlNr<   )r   r   r   r   r   s   `r1   _display_authorr    sg    %!! 
yy 
 
 
 
1
 
 
 
 
 	

 22CJJ.r3   >   r   r   tagsemitshookslabelauthorr   dependslicenselistensr   homepage	platformsapi_versionr   r   optional_envrequires_envconfig_schemaprovides_hooksprovides_toolsmanifest_versionpip_dependenciesrequires_pluginspython_dependenciesexternal_dependenciesprovides_web_providersprovides_browser_providers_KNOWN_MANIFEST_FIELDS   )r   stringintintegerfloatnumberrB   booleanr   arrayr   r  zDict[str, tuple]_CONFIG_SCHEMA_TYPESdatar   Dict[str, Any]c                
   i }|                      dd          }	 t          |          }n5# t          t          f$ r! t                              d||           d}Y nw xY w|t          k    r"t                              d||t                     ||d<   |                      d          }|d|d<   nL	 t          |          |d<   n8# t          t          f$ r$ t                              d||           d|d<   Y nw xY wg }|                      d          }|2t          |t                    st                              d	|           d}|pg D ]}t          |t                    r|
                    |dd
           0t          |t                    rut          |                     d          t                    rM|d         rE|                     d          }	|
                    |d         |	t          |	          ndd
           t                              d||           ||d<   g }
|                      d          }|2t          |t                    st                              d|           d}|pg D ]o}t          |t                    r<|                                r(|

                    |                                           St                              d||           p|
|d<   |                      d          }i }|2t          |t                    st                              d|           d}|pi                                 D ]\  }}t          |t                    st                              d||           7|                     d          }|kt          |                                          t          vrCt                              d|||d                    t#          t                                         t%          |          |t          |          <   ||d<   t          |                      d          pd          |d<   t          |                      d          pd          |d<   |                      d          }|2t          |t                    st                              d|           d}d |pg D             |d<   t#          t'          |                                           t*          z
            }|r>|dk    rt          j        nt          j        } |d|d                    |                     |S )u   Validate and normalize the manifest v2 fields (#64165).

    Returns kwargs for :class:`PluginManifest`. Every problem is a warning,
    never a load failure — v2 metadata is advisory and additive.
    r&  r   z?Plugin %s: manifest_version %r is not an integer; treating as 1zrPlugin %s: manifest_version %d is newer than this Hermes supports (%d); loading anyway and ignoring unknown fieldsr   Nz5Plugin %s: api_version %r is not an integer; ignoringr(  z4Plugin %s: requires_plugins must be a list; ignoring)r   version_ranger   r:  zjPlugin %s: requires_plugins entry %r must be a plugin id string or a {id, version_range} mapping; skippingr)  zNPlugin %s: python_dependencies must be a list of requirement strings; ignoringzXPlugin %s: python_dependencies entry %r must be a non-empty requirement string; skippingr#  z4Plugin %s: config_schema must be a mapping; ignoringzPPlugin %s: config_schema entry %r must be a mapping (e.g. {type: str}); skippingtypezgPlugin %s: config_schema key %r declares unknown type %r (known: %s); type check will be skipped for itr  r  r<   r  r  z(Plugin %s: tags must be a list; ignoringc                ,    g | ]}t          |          S r:   r   )r   ts     r1   r   z-_parse_manifest_v2_fields.<locals>.<listcomp>#  s    444a3q66444r3   r.  zdPlugin %s: unknown manifest field(s) ignored: %s (newer manifest schema or typo; plugin still loads))r   r0  	TypeError
ValueErrorrQ   warningSUPPORTED_MANIFEST_VERSIONr   r   r   r   r   rE   itemsrF   r6  r   sortedr   r   keysr-  rT   )r7  r   outraw_mvmvraw_apidepsraw_depsr   vrpydeps
raw_pydeps
raw_schemaschemaskeyspecstyperaw_tagsunknownlogs                       r1   _parse_manifest_v2_fieldsrW    s    C XX(!,,F[[z"   M	
 	
 	
  
&&&H/	
 	
 	

 !C hh}%%G!M	&!$WC:& 	& 	& 	&NNGg   "&C		& "$Dxx*++HJx$>$>BC	
 	
 	
 B  dC   	KKtd;;<<<<g&& 
	:dhhtnnc+J+J 
	tTXz 
	/**BKK4j,.NR     
 NNDEH$    #C F/00JjT&B&B !$	
 	
 	
 
 b  dC   	TZZ\\ 	MM$**,,''''NN/03T    "(C /**JFjW&E&EBC	
 	
 	
 
!'R..00 ' '
d$(( 	NN/03T     U!1!1!3!3;O!O!ONNAT5$))F3G,H,H"I"I  
 !JJs4yy!C ),,233C	N$((:..4"55C
OxxHJx$>$>A3GGG44HN444CK S%%(>>??G 
 "afnnV\B7##	
 	
 	
 Js!   * /AA/C 2C76C7	plugin_idrP  settings	List[str]c                   g }t          |t                    rt          |t                    s|S |                                D ]\  }}t          |t                    s||v }|s6|                    d          r d|vr|                    d|  d| d           X|                    d          }|r9t
                              t          |                                                    nd}|m||         }	t          |	|          ot          |	t                    ot          |v }
|
s4|                    d|  d| d| d	t          |	          j
         d
	           |S )zValidate a plugin's config entry against its declared config_schema.

    Returns a list of human-actionable warning strings. Never raises;
    schema mismatches must not block plugin load (#64165).
    requiredr   zplugins.entries.z
.settings.z9 is required by the plugin's config_schema but is not setr;  Nz should be z (got ))r   r   rC  r   r   r6  r   rF   rB   r;  r6   )rX  rP  rY  warningsrQ  rR  presentrS  expectedr   oks              r1   validate_config_schemarb  3  s    Hfg&& j7.K.K llnn  
d$(( 	(" 	xx
## 	(=(=Cy C CD C C C     CHR'++CJJ,<,<,>,>???dTNEE8,, 5$''@D,@2B  <y < <D < << <$(KK$8< < <   Or3   r   Mapping[str, 'PluginManifest']c                *    ddl }t                                                     }i |D ]} |         j        }|r	|vr||<   d fd}d |D             }|D ]} |         j        D ]}t          |t                    r|                    d	          nd}|s1 ||          }	|	t          	                    d
|||           \|	|k    rt          	                    d|           ~||         
                    |	           |                    |          }
	 |
                                 nx# |j        $ rk}t          |j                  dk    r|j        d         ng }t          	                    dd                    d |D                                  |cY d}~S d}~ww xY wg }|
                                rTt          |
                                          }|                    |            |
j        |  |
                                T|S )u  Return plugin keys in dependency-respecting load order (#64165).

    When A requires B, B sorts before A (so B's ``register()`` runs first).
    Ties break alphabetically for determinism. Dependency cycles are
    detected, warned about, and the members of the cycle fall back to
    alphabetical order after every non-cycle plugin they depend on.
    Missing dependencies are warned about here (once, at discovery) but do
    not remove the dependent plugin from the order — loads never hard-fail
    on a missing advisory dependency.
    r   Ndep_idr   r'   Optional[str]c                :    | v r| S                      |           S r   )r   )re  by_namer   s    r1   _resolve_depz/resolve_plugin_load_order.<locals>._resolve_depq  s%    YM{{6"""r3   c                ,    i | ]}|t                      S r:   )r   r   ks     r1   
<dictcomp>z-resolve_plugin_load_order.<locals>.<dictcomp>v  s    !9!9!9q!SUU!9!9!9r3   r   zPlugin %s requires plugin '%s' which is not enabled/installed; loading anyway (probe availability at runtime via ctx.has_plugin). Run `hermes plugins enable %s` if it is installed.z3Plugin %s declares a dependency on itself; ignoringr   z^Plugin dependency cycle detected (%s); falling back to alphabetical load order for all pluginsz -> c              3  4   K   | ]}t          |          V  d S r   r=  r   cs     r1   r   z,resolve_plugin_load_order.<locals>.<genexpr>  s(      ..1A......r3   )re  r   r'   rf  )graphlibrD  rE  r   r(  r   r   r   rQ   rA  addTopologicalSorterprepare
CycleErrorr   r   r   	is_active	get_readyextenddone)r   rq  rE  rl  r   ri  edgesdepre  resolvedsorterr   cycleorderedreadyrh  s   `              @r1   resolve_plugin_load_orderr  [  s    OOO)..""##D G  |  	D''GDM# # # # # # #
 ":!9D!9!9!9E # #Q<0 	# 	#C&0g&>&>HSWWT]]]DF #|F++H' vv   1}}TVWXXX!HLL""""#	#& ''..F	   "38}}q00b6KK.......	
 	
 	

  G




 v''))**uU 



  Ns   D1 1
F&;A F!F&!F&source_textrf  c                .    d| v sd| v rdS d| v rd| v rdS dS )aO  Return the plugin kind implied by source markers, or ``None``.

    Mirrors ``plugins/memory/__init__.py:_is_memory_provider_dir``: a
    module that registers a memory provider (``register_memory_provider``
    or ``MemoryProvider``) belongs to the memory-provider discovery
    system (``exclusive``); a module that registers a model provider
    (``register_provider`` + ``ProviderProfile``) belongs to the
    providers discovery (``model-provider``). Applied to both directory
    plugins and pip entry-point plugins so neither is eagerly imported
    by the general PluginManager.
    register_memory_providerMemoryProviderr   register_providerProviderProfiler   Nr:   )r  s    r1   r   r     s@     "[004D4S4S{k)).?;.N.N4r3       originlimitr0  c                B   | sdS |                      d          r2	 t          j                            |           } n# t          $ r Y dS w xY w|                      d          sdS 	 t          |                               dd          d|         S # t          $ r Y dS w xY w)a  Read the first ``limit`` chars of a module's source file.

    Returns ``""`` on any failure (callers fall back to ``standalone``).
    ``.pyc``/``.pyo`` origins are mapped back to their source path so
    source is still scanned when only the bytecode cache is present.
    r<   )z.pycz.pyo.pyr  replace)encodingerrorsN)endswithr   utilsource_from_cacher   r	   	read_text)r  r  s     r1   _read_source_from_originr    s      r'(( 	^55f==FF 	 	 	22	??5!! rF||%%wy%II&5&QQ   rrs!   ; 
A	A	$+B 
BBr   c                   d |                      d          D             }|sdS 	 t          j                            |d                   }||j        sdS t          |          dk    r|j        S |j        }|sdS t          |dd         d          D ]\  }}d}d}|D ]q}t          |          }||z  dz  }	|		                                rt          |	          }||z  g} n.||d	z   z  }
|
	                                rt          |
          } nr| dS |t          |          k    s||c S |}dS # t          $ r Y dS w xY w)
uA  Return a module's source path WITHOUT importing it, or ``None``.

    ``importlib.util.find_spec`` on a dotted name imports the parent
    package first (executing its ``__init__.py``), which would run
    arbitrary package initialization during discovery and pay the very
    import cost this exists to avoid — a provider whose heavy imports
    live in ``package/__init__.py`` would still pay them.

    Only the top-level name is resolved with ``find_spec`` (import-free
    for top-level names); the remaining dotted segments are walked
    through ``submodule_search_locations`` by hand, mirroring the file
    layout conventions of the default PathFinder (``part.py`` module or
    ``part/__init__.py`` package). Namespace packages, zipped modules,
    extension modules, and anything else unexpected return ``None``.

    Shared with ``plugins/memory/__init__.py``, which needs the directory
    of a pip-installed provider to find its ``config_schema.py`` and
    ``cli.py`` — both of which are loaded by path precisely so the
    provider module never has to be imported.
    c                    g | ]}||S r:   r:   r   ps     r1   r   z)resolve_module_origin.<locals>.<listcomp>  s    4441!4Q444r3   r   Nr   r   r.  )start__init__.pyr  )r   r   r  	find_specr  r   submodule_search_locations	enumerater	   is_filer   r   )r   partsrR  search_pathsipartfound_origin
next_pathsbasepkg_initmod_files              r1   resolve_module_originr    s   * 54))#..444E t~''a11<t{<4u::??;6 	4 qrr!444 	& 	&GAtLJ$ 
 
Dzz$;6##%% #&x==L"&+JE4%<0##%% #&x==LE #ttCJJ*"4####%LLt   tts0   .D3 D3 /	D3 :BD3 D3 .D3 3
E Ec                <    t          t          |           |          S )u   First ``limit`` chars of a module's source, without importing it.

    Empty string when the module cannot be resolved or read, which
    callers treat as ``standalone`` — the safe default.
    )r  r  )r   r  s     r1   r   r     s     $$9+$F$FNNNr3   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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<   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%<    ee	          Zded&<    ee	          Zded'<    ee	          Zded(<   dS ))r   z0Parsed representation of a plugin.yaml manifest.r   r   r<   r   r   r  default_factoryz List[Union[str, Dict[str, Any]]]r"  rZ  r%  r$  r   Nrf  r   r   r   r   FrB   portableskill_namespacer   r   r0  r&  Optional[int]r   List[Dict[str, Any]]r(  r)  r8  r#  r  r  r  r  r  )r6   r7   r8   r9   __annotations__r   r   r  r   r   r"  r%  r$  r   r   r   r   r  r  r   r&  r   r(  r)  r   r#  r  r  r  r  r  r:   r3   r1   r   r     s;        ::IIIGKF5:U45P5P5PLPPPP %d ; ; ;N;;;; %d ; ; ;N;;;;FD  D CMMMMHO $eD999L9999
  "&K%%%%
 .3U4-H-H-HHHHH &+U4%@%@%@@@@@ %*E$$?$?$?M????GHeD111D1111 uT222E2222t444G444444r3   r   T)frozenc                  F    e Zd ZU dZded<   ded<   ded<   ded<   ded	<   d
S )PluginSystemPromptSectionz:A plugin-owned section rendered once for each new session.r   r   .Union[str, Callable[[Mapping[str, Any]], str]]r   positionr0  	max_charsr  Nr6   r7   r8   r9   r  r:   r3   r1   r  r  Z  sF         DDGGG;;;;MMMNNNKKKKKr3   r  c                  <    e Zd ZU dZded<   ded<   ded<   ded<   dS )!RenderedPluginSystemPromptSectionz4Validated prompt bytes frozen on the owning AIAgent.r   r   r   r  r  Nr  r:   r3   r1   r  r  e  s:         >>GGGLLLMMMKKKKKr3   r  c                  (    e Zd ZU dZded<   ded<   dS )_EventSubscriptionz%Host-owned subscription ledger entry.r   ownerr   callbackNr  r:   r3   r1   r  r  o  s-         //JJJr3   r  c                  F    e Zd ZU dZded<   ded<   ded<   ded	<   ded
<   dS )_QueuedPluginEventz9Immutable dispatch envelope consumed by the event worker.r   eventr8  payloadztuple[_EventSubscription, ...]subscriptionsr0  depth
generationNr  r:   r3   r1   r  r  w  sI         CCJJJ1111JJJOOOOOr3   r  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Zded<   dS )LoadedPluginz)Runtime state for a single loaded plugin.r   r   NzOptional[types.ModuleType]moduler  rZ  tools_registeredhooks_registeredmiddleware_registeredcommands_registeredFrB   r   rf  errordeferred)r6   r7   r8   r9   r  r  r   r   r  r  r  r  r   r  r  r:   r3   r1   r  r    s         33)-F----"'%"="="====="'%"="="=====',uT'B'B'BBBBB%*U4%@%@%@@@@@GE Hr3   r  c                      e Zd ZU dZded<   ded<   ded<   dZded<    ed	d	d	
          Zded<    edd	d	
          Zded<   e	dd            Z
ddZdS )PluginRegistrationam  One host-owned registration made while loading a plugin.

    Plugins only receive the context registration APIs; the manager owns the
    matching cleanup operation.  Keeping that inverse operation beside the
    registration lets a force reload unwind global registries in reverse
    order, including an override that needs to restore the entry it replaced.
    r   r   r   Callable[[], None]releaser<   
plugin_keyF)r   initreprrB   	_disposedNz0Optional[Callable[['PluginRegistration'], None]]_on_disposer'   c                    | j          S )z6Whether this handle still owns an active registration.)r  selfs    r1   activezPluginRegistration.active  s     >!!r3   rC   c                    | j         rdS d| _         	 |                                  | j        |                     |            dS dS # | j        |                     |            w w xY w)z>Release this registration once; repeated disposal is harmless.NT)r  r  r  r  s    r1   disposezPluginRegistration.dispose  s}    > 	F	'LLNNN+  &&&&& ,+t+  &&&& ,s   A A%r'   rB   r'   rC   )r6   r7   r8   r9   r  r  r   r  r  propertyr  r  r:   r3   r1   r  r    s           IIIHHHJeEEBBBIBBBBDIE5uE E EK     " " " X"	' 	' 	' 	' 	' 	'r3   r  z!^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$>   modelr*   securityrY  z#^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$i   zDict[str, threading.RLock]_PLUGIN_STATE_LOCKStuple[str, ...]c                8   t          | t                    st          d          t          |                     d                    }| rBd| v s>d| v s:t          d |D                       s!|d                                         t          v rt          d          |S )a$  Validate and split a plugin-relative settings key.

    The public API accepts only relative keys (``endpoint`` or
    ``retry.policy``).  Full Hermes paths, traversal syntax, and the security-
    sensitive core roots called out in #64227 are rejected before any config
    read occurs.
    z,Expected a plugin-relative config key stringr   /\c              3  L   K   | ]}t                               |           V   d S r   )_PLUGIN_SETTING_SEGMENT_REr   )r   segments     r1   r   z,_plugin_relative_segments.<locals>.<genexpr>  sE       
 
BI*44W===
 
 
 
 
 
r3   r   zExpected a plugin-relative config key such as 'endpoint' or 'retry.policy'; global, cross-plugin, and traversal paths are forbidden)r   r   r@  tupler   r   rF   _PLUGIN_SETTING_RESERVED_ROOTS)r   segmentss     r1   _plugin_relative_segmentsr    s     c3 IGHHHSYYs^^$$H
#::3;; 
 
MU
 
 
 
 
  A;"@@@V
 
 	
 Or3   rootr  r   c                ^    | }|D ]'}t          |t                    r||vr|c S ||         }(|S r   )r   r   )r  r  r   currentr  s        r1   _nested_plugin_valuer    sL    G # #'7++ 	wg/E/ENNN'"Nr3   dict[str, Any]c                6    |}t          |           D ]}||i}|S r   )reversed)r  r   nestedr  s       r1   _nested_plugin_mappingr    s/    FH%% # #6"Mr3   r  c                    |p| }|r,|                     d          rt          j        d|          r|S t          |          S )zBReturn one Windows-safe directory component for plugin-owned data.r  z[A-Za-z0-9][A-Za-z0-9_-]{0,191})
startswithrer   r  )rX  r  	candidates      r1   _plugin_data_namespacer    sY    ,9I  11 L;YGG  %Y///r3   r   threading.RLockc                    t          |                     d                    }t          5  t                              |t          j                              cd d d            S # 1 swxY w Y   d S )NF)strict)r   r.   _PLUGIN_STATE_LOCKS_GUARDr  
setdefault	threadingRLock)r   r   s     r1   _state_thread_lockr    s    
dll%l((
)
)C	" F F"--c9?3D3DEEF F F F F F F F F F F F F F F F F Fs   ,A$$A(+A(c           	   #    K   |                      d| j         d          }t          |          }|5  |j                            dd           t          |d          5 }t          j        dk    rddl}|                    dt          j	                  dk    r)|
                    d	           |                                 |                    d           |                    |                                |j        d
           n1ddl}|                    |                                |j                   	 dV  t          j        dk    rD|                    d           |                    |                                |j        d
           n|                    |                                |j                   n# t          j        dk    rD|                    d           |                    |                                |j        d
           w |                    |                                |j                   w xY wddd           n# 1 swxY w Y   ddd           dS # 1 swxY w Y   dS )a  Serialize state read-modify-write across threads and processes.

    ``fcntl`` is used on POSIX and ``msvcrt`` on native Windows.  The lock is
    kept in a sibling file because atomic replacement changes the inode/file
    handle of the target itself.
    r   z.lockTparentsexist_okza+bntr   N    r   )	with_namer   r  r/   mkdiropenr+   msvcrtseekSEEK_ENDwriteflushlockingfilenoLK_LOCKfcntlflockLOCK_EXLK_UNLCKLOCK_UN)r   	lock_paththread_lockhandler  r  s         r1   _locked_plugin_stater    s      34933344I$Y//K	 @ @td;;;)U## 	@vw$;;q"+..!33LL'''LLNNNAv}}BBBBFMMOOU];;;@7d??KKNNNNN6==??FOQGGGGKK????	 7d??KKNNNNN6==??FOQGGGGKK????)	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @sP   -I'CI7F=;BI=BI  II'I	I'I	I''I+.I+c                      e Zd ZdZdddZedd
            Zedd            Zedd            Ze	dd            Z
ddZdddZd dZdS )!PluginStatez?Atomic, quota-bounded JSON key/value state owned by one plugin.r<   rX  r   r  r'   rC   c                0    t          ||          | _        d S r   )r  _data_namespace)r  rX  r  s      r1   __init__zPluginState.__init__+  s    5iQQr3   r	   c                4    t                      dz  | j        z  S )z@Profile-scoped directory matching portable plugins' PLUGIN_DATA.plugin-data)r   r"  r  s    r1   data_dirzPluginState.data_dir.  s       =043GGGr3   c                    | j         dz  S )Nz
state.json)r&  r  s    r1   r   zPluginState.path3  s    }|++r3   r0  c                    t           S r   )_PLUGIN_STATE_QUOTA_BYTESr  s    r1   quota_byteszPluginState.quota_bytes7  s    ((r3   r   c                    t          | t                    rt                              |           rd| v rt	          d          d S )Nz..zgPlugin state keys must be 1-128 characters using letters, numbers, '_', '-', '.', or ':' (without '..'))r   r   _PLUGIN_STATE_KEY_REr   r@  )r   s    r1   _validate_keyzPluginState._validate_key;  sV     3$$	'11#66	 s{{@   {r3   r  c                j   	 t          | j        d          5 }t          j        |          }d d d            n# 1 swxY w Y   nA# t          $ r i cY S t
          t          f$ r }t          d| j         d|           |d }~ww xY wt          |t                    st          d| j         d          |S )Nr  r  zCannot parse plugin state z: z: root must be an object)
r  r   jsonloadFileNotFoundErrorOSErrorr@  RuntimeErrorr   r   )r  r  r7  r   s       r1   _read_unlockedzPluginState._read_unlockedG  s!   	Ydi'222 )fy(() ) ) ) ) ) ) ) ) ) ) ) ) ) )  	 	 	III$ 	Y 	Y 	YNDINNNNOOUXX	Y$%% 	PTYPPP   s:   A 9A =A  =A BB#A>>BNr   r
   c                    |                      |           t          | j                  5  |                                                     ||          cddd           S # 1 swxY w Y   dS )z>Read a JSON value, returning *default* when the key is absent.N)r-  r  r   r5  r   )r  r   r   s      r1   r   zPluginState.getU  s    3!$),, 	; 	;&&((,,S'::	; 	; 	; 	; 	; 	; 	; 	; 	; 	; 	; 	; 	; 	; 	; 	; 	; 	;s   (AA#&A#r   c                    |                      |           t          | j                  5  |                                 }|||<   	 t	          j        |dd                              d          }n-# t          t          f$ r}t          d|d          |d}~ww xY wt          |          | j
        k    r(t          dt          |           d	| j
         d
          ddlm}  || j        |d           ddd           dS # 1 swxY w Y   dS )zBAtomically set one JSON value without dropping concurrent updates.Fr.  )ensure_asciiindentr  zPlugin state value for z is not JSON-serializableNzPlugin state quota exceeded: z bytes is greater than the z-byte per-plugin quotar   )atomic_json_writei  )mode)r-  r  r   r5  r0  dumpsr
  r?  r@  r   r*  utilsr:  )r  r   r   r7  encodedr   r:  s          r1   r   zPluginState.set[  s   3!$),, 	; 	;&&((DDI*TaHHHOOPWXXz*    NcNNN  7||d... ICLL I I $ 0I I I   0/////diE::::!	; 	; 	; 	; 	; 	; 	; 	; 	; 	; 	; 	; 	; 	; 	; 	; 	; 	;s6   D*A0/D0BBBADD
D)r<   )rX  r   r  r   r'   rC   r'   r	   r'   r0  )r   r   r'   rC   )r'   r  r   r   r   r   r
   r'   r
   r   r   r   r
   r'   rC   )r6   r7   r8   r9   r#  r  r&  r   r*  staticmethodr-  r5  r   r   r:   r3   r1   r   r   (  s        IIR R R R R H H H XH , , , X, ) ) ) X) 	 	 	 \	   ; ; ; ; ;; ; ; ; ; ;r3   r   c                  Z   e Zd ZdZddZedd	            ZddZdddZddZ	edd            Z
ed             ZddZdddd&Zedd'            Zedd(            Zedd)            Zdd+Zdd,dd0Zdd3Ze	 	 	 	 	 	 dddC            ZddEZ	 	 dddNZdOZeddQ            ZeddS            ZddUZ	 dddWdd\Zdd]Ze	 	 ddda            Z e	 	 dddc            Z!ddeZ"eddf            Z#ddgZ$ddhZ%eddi            Z&eddj            Z'eddk            Z(eddl            Z)eddm            Z*eddn            Z+eddo            Z,eddp            Z-e	 	 	 dddw            Z.ddyZ/dd{Z0edd|dd            Z1ddZ2ddZ3de4dddZ5dddZ6ddZ7ddZ8e	 	 ddd            Z9dS )PluginContextz=Facade given to plugins so they can register tools and hooks.r   r   manager'PluginManager'c                Z    || _         || _        d | _        d | _        d | _        d | _        d S r   )r   _manager_llm_subagent_lifecycle_state_platform_actions)r  r   rF  s      r1   r#  zPluginContext.__init__t  s4     	(, *.&*r3   r'   r   c                2    | j         j        p| j         j        S )zCReturn the effective registry id used for this plugin's namespaces.)r   r   r   r  s    r1   rX  zPluginContext.plugin_id~  s     } 6DM$66r3   rX  rB   c                    | j         j                                        D ]&\  }}|j        s||k    s|j        j        |k    r dS 'dS )a&  Return True when another plugin is loaded and enabled (#64165).

        Companion to the advisory ``requires_plugins`` manifest field: a
        missing dependency never blocks load, so plugins probe availability
        at runtime with this. Matches on registry key or manifest name.
        TF)rI  _pluginsrC  r   r   r   )r  rX  r   loadeds       r1   
has_pluginzPluginContext.has_plugin  sa      =17799 	 	KC> i6?#79#D#Dtt $Eur3   Nr   r   r
   c                   	 t          |          }n0# t          $ r# t                              d|| j                    w xY wddlm}  |            pi }t          |t                    r|	                    d          nd}t          |t                    r|	                    d          nd}t          |t                    r|	                    | j                  nd}t          |t                    s|S t                      }	t          |	                    d          ||	          }
|
|	ur|
S t          |	                    d          ||          S )	a  Read ``plugins.entries.<plugin_id>.settings.<key>``.

        ``key`` is always plugin-relative.  For migration compatibility, a
        missing canonical value falls back to the former ``config`` subtree;
        no global config paths are exposed.
        &Rejected config path %r from plugin %sr   )r   r*   NentriesrY  r   )r  r@  rQ   rA  rX  r   r   r   r   r   r  r  )r  r   r   r  r   r   r*   rU  entrymissingr   s              r1   
get_configzPluginContext.get_config  sY   	055HH 	 	 	NN8#t~   		
 	;:::::%%''-2+5fg+F+FP&**Y'''D,6w,H,HR'++i(((d/9'7/K/KUDN+++QU%)) 	N(($UYYz%:%:HgNNL#EIIh$7$77KKKs    -?r   rC   c                   	 t          |          }n0# t          $ r# t                              d|| j                    w xY wddlm} |                                rt          d          ddlm	} d
                    dd| j        d	g|R           }|                    |          rt          d
|d          dd| j        d	t          ||          iiii}dd| j        d	g|R }t          |                                          5  |j        5  |                                 |                    ||hd           ddd           n# 1 swxY w Y   ddd           dS # 1 swxY w Y   dS )zAAtomically write one value in this plugin's ``settings`` subtree.rT  r   r   z6Plugin settings cannot be changed in a managed install)managed_scoper   r*   rU  rY  zPlugin setting z is administrator-managedT)preserve_keysmerge_existingN)r  r@  rQ   rA  rX  
hermes_clir   
is_managedPermissionErrorr[  r   is_key_managedr  r  get_config_path_CONFIG_LOCKread_user_config_rawsave_config)	r  r   r   r  
config_modr[  dotted_pathpartial	full_paths	            r1   
set_configzPluginContext.set_config  sz   	055HH 	 	 	NN8#t~   		
 	433333  "" 	!H   	-,,,,,hhN	 

  
  
   ''44 	!J+JJJ   N"$:8U$K$K%
 	4>:QQQ	 "*"<"<">">?? 
	 
	( 	 	 //111&&#,+#' '   	 	 	 	 	 	 	 	 	 	 	 	 	 	 	
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	s>    -?E .E<E E	E E	E  E$'E$r   c                f    | j         $t          | j        | j        j                  | _         | j         S )z>Return this plugin's profile-scoped durable JSON state facade.)rL  r   rX  r   r  r  s    r1   statezPluginContext.state  s,     ;%dndm6STTDK{r3   c                T    | j         ddlm}  || j                  | _         | j         S )u  Capability-gated platform action facade (#64176, v1).

        Minimal verb set (``add_reaction``, ``set_thread_title``) routed
        through the live gateway adapter registry. Every call re-checks the
        ``gateway.platform_actions`` capability (legacy gate:
        ``plugins.entries.<id>.allow_platform_actions``, default OFF) and
        returns a structured ``{"ok": bool, ...}`` dict — verbs never raise
        into hook dispatch. No adapter handles or raw SDK objects are exposed.
        Nr   )PlatformActions)rM  hermes_cli.platform_actionsrn  rX  )r  rn  s     r1   platform_actionszPluginContext.platform_actions  s<     !)CCCCCC%4_T^%D%DD"%%r3   r   r  r  r  c                F    | j                             | j        |||          S )z8Record host-owned cleanup for a successful registration.)rI  _track_registrationr   )r  r   r   r  s       r1   _trackzPluginContext._track  s)     }00M4g
 
 	
r3   )finalizeslotr  r  previousrestoreCallable[[Any], bool]rt  Optional[Callable[[], None]]c               l    t          j        |||||          }|                     |||j                  S )z8Track one generation in a replaceable registration slot.r  rv  rw  rt  )r   acquirers  r  )	r  r   r   ru  r  rv  rw  rt  leases	            r1   _track_replacementz PluginContext._track_replacement
  sD     (/
 
 
 {{4em444r3   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rX  )rJ  agent.plugin_llmr  r   r   r   )r  r  rX  s      r1   llmzPluginContext.llm!  sN     9222222)?T]-?I!	I666DIyr3   c                N    | j         ddlm}m}  ||          | _         | j         S )a(  Return the public, plugin-safe subagent lifecycle service.

        The service only resolves the active host-owned parent agent when a
        child is launched. Plugins receive serializable handles and immutable
        snapshots; they never receive a live agent or a private registry.
        Nr   )SubagentLifecycleServiceget_active_subagent_parent)rK  agent.subagent_lifecycler  r  )r  r  r  s      r1   subagent_lifecyclez PluginContext.subagent_lifecycle2  s[     #+        (@'?*( (D$ ''r3   c                F    	 ddl m}  |            S # t          $ r Y dS w xY w)uX  Return the active Hermes profile name (e.g. ``"default"``).

        Derived from ``HERMES_HOME`` via
        :func:`hermes_cli.profiles.get_active_profile_name`, so it works in
        every execution context — interactive CLI, gateway, and
        kanban-spawned worker sessions alike — without depending on
        ``_cli_ref`` (which is ``None`` outside an interactive CLI run).

        Returns ``"default"`` for the default profile, the profile id when
        running under ``~/.hermes/profiles/<name>``, or ``"custom"`` when
        ``HERMES_HOME`` points somewhere unrecognized.
        r   )get_active_profile_namer   )hermes_cli.profilesr  r   )r  r  s     r1   profile_namezPluginContext.profile_nameF  sJ    	CCCCCC**,,, 	 	 	99	s    
  r  c                    t          |          st          d          |                     dt          |dd          |          }t                              d| j        j                   |S )u`  Register a cleanup callback that runs when this plugin unloads.

        Callbacks are recorded in the ownership ledger, so they run in
        reverse acquisition order interleaved with registration teardown,
        and each is isolated — an exception is logged, never propagated
        (see :meth:`PluginManager._dispose_registrations`).
        z#on_unload callback must be callable	on_unloadr6   r  z'Plugin %s registered on_unload callback)callabler?  rs  r   rQ   rT   r   r   )r  r  r  s      r1   r  zPluginContext.on_unload\  se     !! 	CABBB['(J
*S*SU]^^>@RSSSr3   r   r   rf  'asyncio.Task'c               t   t          j        |          st          d          t          j                    }|p
d| j         d}|                    ||          dfd}|                     d||                              fd	           t          	                    d
| j
        j        |           S )zSpawn a supervised background asyncio task owned by this plugin.

        The task is recorded in the ownership ledger; unloading the plugin
        (or a force reload) cancels it. Requires a running event loop.
        zspawn_task expects a coroutineplugin:z:taskr   r'   rC   c                 \                                      s                                  d S d S r   )ry  cancel)tasks   r1   _cancel_taskz.PluginContext.spawn_task.<locals>._cancel_taskv  s.    99;;  r3   background_taskc                ,                                     S r   )r  )_tr  s    r1   <lambda>z*PluginContext.spawn_task.<locals>.<lambda>{  s    &..*:*: r3   z%Plugin %s spawned supervised task: %sr  )asyncioiscoroutiner?  get_running_looprX  create_taskrs  add_done_callbackrQ   rT   r   r   )r  coror   loop	task_namer  r  r  s         @@r1   
spawn_taskzPluginContext.spawn_taskj  s     "4(( 	><==='));;dn;;;	955	 	 	 	 	 	 .	<HH::::;;;3T]5G	
 	
 	
 r3   
present_fnr   c           
          j                             || j        j        p j        j                   t          |                                                                           j         j        	                              < 
                    ddt           j         j                  fd fd           dS dS )a  Register a human approval presentation transport.

        The transport is inactive until the operator explicitly selects
        ``security.approval.transport: <name>``. It receives a host-created,
        redacted ``ApprovalRequest`` and may only return a correlated human
        decision; command policy and approval persistence remain host-owned.
        ``present_fn`` may be synchronous or async.
        r  Napproval_transportmanager_mappingc                R    j                             j         j        |           S r   )rI  _restore_mapping_approval_transportsreplacementcleanrV  r  s    r1   r  z;PluginContext.register_approval_transport.<locals>.<lambda>  s'    DM,J,JM6uk- - r3   ru  r  rv  rw  )rI  register_approval_transportr   r   r   r   rE   rF   r  r   r~  r   )r  r   r  r  rV  s   `  @@r1   r  z)PluginContext.register_approval_transport  s     	11m'=4=+= 	2 	
 	
 	
 D		!!''))266u==##$%t}9::
       $      r3   Fr<   toolsetrP  r   rU   check_fnCallable | Noner"  list | Noneis_asyncr   emojioverrideOptional[PluginRegistration]c                    |
rP                                s; j        j        p j        j        }t	          d j        j        dd| d          ddlm  j        j        	                              }
                              }|,|*|
s(t                              d	 j        j                   dS                     ||||||||	|

           	                              Y|urUj        |u rL j        j                                       dfd}                     ddf|| fd          }nd}t                              d j        j        |
rdnd           |S )uS  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.

        ``override=True`` against a built-in tool requires the operator to
        opt in via ``plugins.entries.<plugin_id>.allow_tool_override: true``
        in config.yaml — mirrors the trust gate pattern used for
        ``ctx.llm`` provider/model overrides (#23194). Without that gate,
        any enabled plugin could silently replace a privileged built-in
        like ``shell_exec`` or ``write_file`` and exfiltrate everything
        the model invokes through it.
        Plugin z cannot override built-in tool z. Set plugins.entries.zY.allow_tool_override: true in config.yaml to allow this plugin to replace built-in tools.r   registryscopeNz>Plugin %s tried to shadow global tool %s without override=True)r   r  rP  rU   r  r"  r  r   r  r  r  r  r
   r'   rB   c                6                         |           S Nr  restore_registration)r  r   
registeredr  r  s    r1   _restore_toolz2PluginContext.register_tool.<locals>._restore_tool  s)    44*k 5   r3   toolc                 8    j                                        S r   )rI  _remove_tool_name_if_unownedr   r  s   r1   r  z-PluginContext.register_tool.<locals>.<lambda>  s    !K!KD!Q!Q r3   ru  r  rv  rw  rt  zPlugin %s registered tool: %s%sz (override)r<   )r  r
   r'   rB   )_tool_override_allowedr   r   r   r5   tools.registryr  rI  	scope_keysnapshot_registration	get_entryrQ   rA  registerrU   _plugin_tool_namesrr  r~  rT   )r  r   r  rP  rU   r  r"  r  r   r  r  rX  rv  	effectiver  r  r  r  r  s   ``              @@@r1   register_toolzPluginContext.register_tool  sQ   :  	D77== 	)?T]-?I)R$-, R RR R#,R R R   	,+++++'11$e1DD&&t5&99		 5h 5NNP"  
 4%# 	 	
 	
 	
 33D3FF
"(**"g--M,00666        
 ,,eT*"!%QQQQQ -  FF F-Mx&GmmR	
 	
 	
 r3   r   c                    t          | j        dd          pd}|dk    r|dk    rdS | j        j        p| j        j        }t	          ||          S )a  Return True when *capability* is live for this plugin.

        Plugins should probe with this and degrade gracefully instead of
        crashing when a gated host surface refuses them. Bundled plugins are
        trusted for ``tools.override`` (mirrors the registration gate); for
        everything else the answer comes from the granted-capability set or
        the deprecated legacy ``allow_*`` config key. Unknown capability ids
        and unreadable consent state return False (fail closed).
        r   r<   bundledtools.overrideT)r   r   r   r   r"   )r  r   r   rX  s       r1   has_capabilityzPluginContext.has_capability  s[     "55;Y:1A#A#A4M%;);	(J???r3      serverr  	argumentsOptional[Dict[str, Any]]timeoutr2  r8  c           
     .   | j         j        p| j         j        }|                     |          }||vr#t	          d| j         j        d|d| d          	 t          |          }n# t          t          f$ r d}Y nw xY wt          dt          |d                    }dd	l
m}  ||||          } |t          |pi                     }	t                              d
| j         j        |||t          |	pd                     |                     |	          S )u  Call a tool on a configured MCP server (#64204, capability-gated).

        Synchronous; safe to call from plugin hooks and tools. Routes through
        the EXISTING native MCP client machinery in :mod:`tools.mcp_tool`
        (background loop, trust-tier gates, circuit breaker, reconnect and
        result rendering) — never a parallel client or connection.

        Default-off: a plugin has NO MCP access until the operator lists the
        servers it may reach under ``plugins.entries.<plugin_id>.mcp_allowlist``
        in config.yaml::

            plugins:
              entries:
                my-plugin:
                  mcp_allowlist: ["knowledge_rag", "github"]

        Calls to unlisted servers raise :class:`PermissionError`. This is a
        per-server grant, deliberately not ambient authority over every
        configured server.
        # TODO(#64228): swap the per-server allowlist for the declared
        # capability model once it lands (per-tool grants, expiry, ro/rw).

        Args:
            server: MCP server name as configured in ``mcp.servers``.
            tool: Tool name on that server (unprefixed).
            arguments: JSON-serializable arguments dict for the tool.
            timeout: Seconds to wait for the call (default 30) so a hung
                MCP server can never stall the hook/tool pipeline.

        Returns:
            Envelope dict: ``{"ok": True, "result": <parsed result>}`` on
            success or ``{"ok": False, "error": <message>}`` when the MCP
            call itself failed. Results larger than ~64KB are truncated
            with a marker.

        Raises:
            PermissionError: server not in this plugin's ``mcp_allowlist``.
        r  z# is not allowed to call MCP server z. Add it to plugins.entries.zI.mcp_allowlist in config.yaml to grant access (default is no MCP access).      >@g      ?g     @r   )_make_tool_handlerz;Plugin %s called MCP %s/%s (timeout=%ss, %d chars returned)r<   )r   r   r   _mcp_allowlistr`  r2  r?  r@  maxmintools.mcp_toolr  r   rQ   rT   r   _mcp_envelope)
r  r  r  r  r  rX  	allowlistr  rU   raws
             r1   call_mcpzPluginContext.call_mcp  s^   Z M%;);	''	22	""!?$-, ? ? ? ?#,? ? ?  	GnnGG:& 	 	 	GGG	c3w..// 	655555$$VT7;;gd9?++,,IMgs39"~~	
 	
 	
 !!#&&&s   A& &A<;A<i   r  c                   t          |t                    s|dnt          |          }t          |          | j        k    r|d| j                 dz   }d}nd}d}	 t	          j        |          }n# t          t          f$ r d}Y nw xY wt          |t                    rd|v rd|d         d}n=t          |t                    rd|v rd|d         d	}d
|v r|d
         |d
<   n	d||n|d	}|rd|d<   |S )z>Normalize an MCP handler result string into a stable envelope.Nr<   u   … [truncated]TFr  )ra  r  result)ra  r  structuredContent	truncated)	r   r   r   _MCP_RESULT_CHAR_CAPr0  loadsr@  r?  r   )clsr  r  parsedenvelopes        r1   r  zPluginContext._mcp_enveloped  sN    #s## 	2""SCs88c...00014EECIII	Z__FFI& 	 	 	FFF	fd## 	U6(9(9.3fWo'N'NHH%% 	U(f*<*<"fX.>??H"f,,067J0K,-"8JffPSTTH 	)$(H[!s   A0 0BBrZ  c                D   	 ddl m}  |            pi }n# t          $ r g cY S w xY w|                    d          pi                     d          pi }|                    |           pi }|                    d          }t	          |t
                    sg S d |D             S )u   Return the operator-granted MCP server allowlist for a plugin.

        Missing key or unreadable config → empty list (fail closed,
        default-deny).
        r   r   r*   rU  mcp_allowlistc                ,    g | ]}t          |          S r:   r=  r   s     r1   r   z0PluginContext._mcp_allowlist.<locals>.<listcomp>  s    000dD		000r3   )r   r   r   r   r   r   )rX  r   cfgrU  rV  r  s         r1   r  zPluginContext._mcp_allowlist  s    	555555+--%2CC 	 	 	III	779%%+00;;ArI&&,"IIo..	)T** 	I00i0000s    $$	tool_namec                B   t          | j        dd          pd}|dk    rdS 	 ddlm} t	          | j        j                  5   |            pi }ddd           n# 1 swxY w Y   n# t          $ r Y dS w xY w| j        j        p| j        j	        }t          |d	|
          S )u  Return True if this plugin is configured to override built-in tools.

        Bundled plugins (shipped with Hermes core) are trusted by default —
        an override there is a deliberate maintainer choice, not a third-party
        plugin trying to elevate privilege. For every other source, the
        canonical check is :func:`plugin_capability_granted` with the
        ``tools.override`` capability — satisfied by EITHER the consent-flow
        grant (``plugins.entries.<plugin_id>.granted_capabilities``) OR the
        deprecated legacy key ``allow_tool_override: true`` (still honored
        for backward compatibility; #64228 reference migration).
        r   r<   r  Tr   r   NFr  rZ  )r   r   r   r   r   rI  	home_pathr   r   r   r"   )r  r  r   r   r  rX  s         r1   r  z$PluginContext._tool_override_allowed  s    "55;Y4	555555#DM$;<< * *!kmm)r* * * * * * * * * * * * * * * 	 	 	 55	 M%;);	
 )4DSQQQQs5   A& AA& AA& !A"A& &
A43A4user)session_keyr   roler  
str | Nonec                  | j         j        }|dk    r|nd| d| }|Ht          |dd          r|j                            |           n|j                            |           dS |st                              d           dS |                                 s6| j	        j
        p| j	        j        }t                              d	||           dS | j         j        st                              d
           dS | j	        j
        p| j	        j        }	 t          | j                             |||                    S # t          $ r! t                              d|d           Y dS w xY w)a  Inject a message into a CLI or gateway 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.

        Gateway injection requires an existing ``session_key`` and an explicit
        ``plugins.entries.<plugin_id>.allow_gateway_injection`` config grant.
        A ``True`` return means the live gateway accepted the request for
        asynchronous dispatch, not that platform delivery has completed.

        Returns True if the message was queued successfully.
        r  [z] N_agent_runningFTz=inject_message: gateway mode requires an existing session_keyzxinject_message: gateway injection denied for plugin %s; set plugins.entries.%s.allow_gateway_injection: true to allow itz,inject_message: no live gateway is available)r  r   rX  z7inject_message: gateway scheduling failed for plugin %sexc_info)rI  _cli_refr   _interrupt_queueput_pending_inputrQ   rA  _gateway_injection_allowedr   r   r   has_gateway_message_injectorrB   inject_gateway_messager   )r  r   r  r  climsgrX  s          r1   inject_messagezPluginContext.inject_message  s   , m$gg-B-B-B-B-B?s,e44 ,$((---- "&&s+++4 	NNO   5..00 	)?T]-?INNO	   5}9 	NNIJJJ5M%;);		44 +' 5      	 	 	NNI    
 55	s   )D: :'E%$E%c                    	 t                      pi }n# t          $ r Y dS w xY w| j        j        p| j        j        }t          |dd|dd          du S )z=Return whether this plugin may trigger gateway session turns.Fr*   rU  allow_gateway_injectionr   T)r   r   r   r   r   r   )r  r  rX  s      r1   r  z(PluginContext._gateway_injection_allowed  s    	&((.BCC 	 	 	55	 M%;);	)   
	
s    
!!helpsetup_fn
handler_fnc           
     |     j         j                                      }|||| j        j         j        j        p j        j        d j         j        <                        ddt           j         j                  f| fd          }t          	                    d j        j                   |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   r  r   r  r  r  r  cli_commandr  c                R    j                             j         j        |           S r   )rI  r  _cli_commands)r  rV  r   r  s    r1   r  z4PluginContext.register_cli_command.<locals>.<lambda>0  s'    (F(F+T5+) ) r3   r  z$Plugin %s registered CLI command: %s)
rI  r  r   r   r   r   r~  r   rQ   rT   )	r  r   r  r  r  r   rv  r  rV  s	   ``      @r1   register_cli_commandz"PluginContext.register_cli_command  s     =.22488& $m(-+At}/A
 
 -2#D)((#R(C%D%DdK      ) 	
 	
 	;T]=OQUVVVr3   	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 j        j                                      }||pd	 j        j         j        j        p j        j        |pd
                                d		 j        j        <                        ddt!           j        j                  f	|	 fd          }t                              d j        j                   |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.
        r   r  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<   )rU   r   r  r  r  commandr  c                R    j                             j         j        |           S r   )rI  r  _plugin_commandsr  s    r1   r  z0PluginContext.register_command.<locals>.<lambda>w  s'    (F(F.uk) ) r3   r  z!Plugin %s registered command: /%s)rF   rE   lstripr  rQ   rA  r   r   hermes_cli.commandsr  r   rI  r  r   r   r~  r   rT   )
r  r   rU   r   r  r  rv  r  r  rV  s
   `       @@r1   register_commandzPluginContext.register_command9  s   4 

""$$++C0088cBB 	NNM"   F
	;;;;;;u%%19M&  
  2  	 	 	D	 =155e<<&:*:m(-+At}/A#/r0022
 
 16&u-((#R(F%G%GO      ) 	
 	
 	8$-:LeTTTs   ;7B5 5
CCr   c                    ddl m} d|vr(| j        j        }|rt	          |dd          nd}|||d<    |j        ||fd| j        j        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agentNr  )r  r  rI  r  r   dispatchr  )r  r  r   r   r  r  r  s          r1   dispatch_toolzPluginContext.dispatch_tool  s      	,+++++
 ''-(C36@GC$///DE ).~& x t
 
#'=#:
>D
 
 	
r3   c           	          j         j        't                              d j        j                   dS ddlm} t          |          s't                              d j        j                   dS  j         j        } j         _         	                    dj        dt           j                   df| fd	
          }t                              d j        j        j                   |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.context_enginemanager_value_context_enginec                <    j                             d|           S )Nr&  )rI  _restore_value)r  enginer  s    r1   r  z7PluginContext.register_context_engine.<locals>.<lambda>  s     (D(D!6;) ) r3   r  z)Plugin '%s' registered context engine: %s)rI  r&  rQ   rA  r   r   agent.context_enginer#  r   r~  r   info)r  r)  r#  rv  r  s   ``   r1   register_context_enginez%PluginContext.register_context_engine  s"    =(4NNQ"  
 F666666&-00 	NN8"  
 F=0(.%((K!2dm#4#46GH     ) 	
 	
 	7M	
 	
 	
 r3   c                p   ddl m}m} t          ||          s't                              d| j        j                   dS 	  ||           n># t          $ r1}t                              d| j        j        |           Y d}~dS d}~ww xY wt          	                    d| j        j        |j
                   dS )a  Register a custom @-prefix context reference provider.

        ``provider`` must be an instance of
        :class:`agent.context_references.ContextReferenceProvider`.  The
        ``provider.prefix`` attribute defines the @-prefix (e.g. ``"issue"``
        creates ``@issue:...``).  Built-in prefixes (diff, staged, file,
        folder, git, url) are reserved and will be rejected.
        r   )ContextReferenceProvider#register_context_reference_providerzyPlugin '%s' tried to register a context reference provider that does not inherit from ContextReferenceProvider. Ignoring.Nz5Plugin '%s' context reference registration failed: %sz.Plugin '%s' registered context reference: @%s:)agent.context_referencesr.  r/  r   rQ   rA  r   r   r@  r+  prefix)r  provider_CRP	_registerr   s        r1   register_context_referencez(PluginContext.register_context_reference  s   	
 	
 	
 	
 	
 	
 	
 	

 (D)) 	NNQ"  
 F	Ih 	 	 	NNG"C   FFFFF	 	<M	
 	
 	
 	
 	
s   A 
B&BBc           	         ddl m} t          ||          s't                              d| j        j                   dS || _        t                              d| j        j        t          |dd                     dS )u  Register a memory provider.

        Memory providers are activated exclusively, by name, through
        ``memory.provider`` in config.yaml, and ``plugins/memory/__init__.py``
        owns that path with its own collector. A provider reaching *this*
        implementation is therefore one the general PluginManager loaded — it
        was not classified ``exclusive`` — so the call is recorded and
        otherwise inert. Without it, such a plugin's ``register()`` dies on a
        missing attribute and the plugin fails to load at all.

        Memory was the only provider category with no ``register_*`` here,
        which is what made that failure mode possible. The provider must be an
        instance of ``agent.memory_provider.MemoryProvider``.
        r   )r  zdPlugin '%s' tried to register a memory provider that does not inherit from MemoryProvider. Ignoring.Nz*Plugin '%s' registered memory provider: %sr   r   )
agent.memory_providerr  r   rQ   rA  r   r   _memory_providerrT   r   )r  r2  r  s      r1   r  z&PluginContext.register_memory_provider  s     	988888(N33 	NN9"  
 F (8M&# > >	
 	
 	
 	
 	
r3   c                  	
 ddl m} ddlm}m	m} t          |          s't                              d| j	        j
                   dS j
                                        | j        j        
 |
          } |
            |
          }|urdS |                     dd
f|	
fd	          }t                              d
| j	        j
                   |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r  r  r  zjPlugin '%s' tried to register an image_gen provider that does not inherit from ImageGenProvider. Ignoring.Nr  image_gen_providerc                "     |           S r  r:   r  r2  registry_namer  r  s    r1   r  z;PluginContext.register_image_gen_provider.<locals>.<lambda>7	  #    (<(<xE) ) ) r3   r  z-Plugin '%s' registered image_gen provider: %s)agent.image_gen_providerr:  agent.image_gen_registryr  r  r  r   rQ   rA  r   r   rE   rI  r  r~  r+  )r  r2  r:  r  r  rv  r  r  r?  r  r  s    `      @@@r1   register_image_gen_providerz)PluginContext.register_image_gen_provider	  sw    	>=====	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 ($455 	NN?"  
 F ++--'((eDDD(%0000**=FFF
X%%4(( &}=       ) 	
 	
 	;M	
 	
 	
 r3   c                r  	
 ddl m}m} ddlm
 ddlm} t          |          s't                              d| j	        j
                   dS j
        	| j        j         |	          }	  |           nU# t          t          f$ rA}t                              d| j	        j
        t          d	d
          |           Y d}~dS d}~ww xY w |	          }|urdS |                     d	d	f|	
fd          }t                              d| j	        j
        	j                   |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  r  )r  zsPlugin '%s' tried to register a dashboard-auth provider that does not inherit from DashboardAuthProvider. Ignoring.Nr  z=Plugin '%s' failed to register dashboard-auth provider %r: %sr   r   dashboard_auth_providerc                "     |           S r  r:   r>  s    r1   r  z@PluginContext.register_dashboard_auth_provider.<locals>.<lambda>t	  r@  r3   r  z7Plugin '%s' registered dashboard-auth provider: %s (%s))hermes_cli.dashboard_authrE  r  "hermes_cli.dashboard_auth.registryr  r  r   rQ   rA  r   r   rI  r  r?  r@  r   r~  r+  display_name)r  r2  rE  r  r  rv  er  r  r?  r  r  s    `       @@@r1    register_dashboard_auth_providerz.PluginContext.register_dashboard_auth_providerC	  s   	
 	
 	
 	
 	
 	
 	
 	
 	LKKKKKLLLLLL($9:: 	NNN"  
 F '((eDDD	he44444:& 	 	 	NN"GHfc$B$BA  
 FFFFF	 +*=FFF
X%%4((%+UMB       ) 	
 	
 	EMx/D	
 	
 	
 s   1A? ?C6CCc                  	
 ddl m} ddlm}m	m} t          |          s't                              d| j	        j
                   dS j
                                        | j        j        
 |
          } |
            |
          }|urdS |                     dd
f|	
fd	          }t                              d
| j	        j
                   |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.Nr  video_gen_providerc                "     |           S r  r:   r>  s    r1   r  z;PluginContext.register_video_gen_provider.<locals>.<lambda>	  r@  r3   r  z-Plugin '%s' registered video_gen provider: %s)agent.video_gen_providerrN  agent.video_gen_registryr  r  r  r   rQ   rA  r   r   rE   rI  r  r~  r+  )r  r2  rN  _register_video_providerr  rv  r  r  r?  r  r  s    `      @@@r1   register_video_gen_providerz)PluginContext.register_video_gen_provider	  sw    	>=====	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 ($455 	NN?"  
 F ++--'((eDDD  7777**=FFF
X%%4(( &}=       ) 	
 	
 	;M	
 	
 	
 r3   c                  	
 ddl m} ddlm}m	m} t          |          s't                              d| j	        j
                   dS j
                                        | j        j        
 |
          } |
            |
          }|urdS |                     dd
f|	
fd	          }t                              d
| j	        j
                   |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.Nr  web_search_providerc                "     |           S r  r:   r>  s    r1   r  z<PluginContext.register_web_search_provider.<locals>.<lambda>	  r@  r3   r  z'Plugin '%s' registered web provider: %s)agent.web_search_providerrV  agent.web_search_registryr  r  r  r   rQ   rA  r   r   rE   rI  r  r~  r+  )r  r2  rV  _register_web_providerr  rv  r  r  r?  r  r  s    `      @@@r1   register_web_search_providerz*PluginContext.register_web_search_provider	  sy    	@?????	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 ($566 	NN@"  
 F ++--'((eDDDxu5555**=FFF
X%%4((!'>       ) 	
 	
 	5M	
 	
 	
 r3   c                  	
 ddl m} ddlm}m	m} t          |          s't                              d| j	        j
                   dS j
                                        | j        j        
 |
          } |
            |
          }|urdS |                     dd
f|	
fd	          }t                              d
| j	        j
                   |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.Nr  browser_providerc                "     |           S r  r:   r>  s    r1   r  z9PluginContext.register_browser_provider.<locals>.<lambda>
  r@  r3   r  z+Plugin '%s' registered browser provider: %s)agent.browser_providerr^  agent.browser_registryr  r  r  r   rQ   rA  r   r   rE   rI  r  r~  r+  )r  r2  r^  _register_browser_providerr  rv  r  r  r?  r  r  s    `      @@@r1   register_browser_providerz'PluginContext.register_browser_provider	  sv    	;:::::	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 (O44 	NN>"  
 F ++--'((eDDD""859999**=FFF
X%%4(($e];       ) 	
 	
 	9M	
 	
 	
 r3   c                  	
 ddl m} ddlm}m	m} t          |          s't                              d| j	        j
                   dS j
        | j        j        
 |
          } |
          r_ |
          }|urdS |                     dd
f|	
fd	          }t                              d
| j	        j
                   |S dS )u  Register an external secret-manager backend.

        ``source`` must be an instance of
        :class:`agent.secret_sources.base.SecretSource`.  Registered
        sources run during ``load_hermes_dotenv()`` startup — after
        ``~/.hermes/.env`` loads, before Hermes reads credentials — when
        their ``secrets.<source.name>`` config section is enabled.  The
        orchestrator (``agent.secret_sources.registry.apply_all``) owns
        ordering, mapped-vs-bulk precedence, conflict warnings, and
        provenance; the source only fetches.

        NOTE ON TIMING: ``load_hermes_dotenv()`` usually runs at import
        *before* plugin discovery.  After discovery completes, the plugin
        manager re-pulls enabled plugin secret sources (``reset_secret_source_cache``
        + ``load_hermes_dotenv``) so the first process sees them (#64177).
        Child processes that load env after plugins still work without that
        re-pull.  Failed re-pulls never block startup.

        Contract requirements (rejected with a warning otherwise):
        inherit from ``SecretSource``, ``api_version`` matching
        ``SECRET_SOURCE_API_VERSION``, lowercase unique ``name``,
        ``shape`` of ``"mapped"`` or ``"bulk"``, unique ``scheme`` (when
        set), and a ``fetch()`` that never raises and never prompts.
        See the base-module docstring for the full contract.
        r   )SecretSource)register_sourcer  r  z`Plugin '%s' tried to register a secret source that does not inherit from SecretSource. Ignoring.Nr  secret_sourcec                "     |           S r  r:   )r  r?  r  r  r   s    r1   r  z6PluginContext.register_secret_source.<locals>.<lambda>O
  s#    ,@,@!6;e- - - r3   r  z(Plugin '%s' registered secret source: %s)agent.secret_sources.baserf  agent.secret_sources.registryrg  r  r  r   rQ   rA  r   r   rI  r  r~  r+  )r  r   rf  rg  r  rv  r  r  r?  r  r  s    `      @@@r1   register_secret_sourcez$PluginContext.register_secret_source
  sp   6 	;:::::	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 &,// 	NN;"  
 F'((eDDD?6/// 	..}EJJJJ''t,,%um<!       - 	 	F KK:"M   Mtr3   c                  	
 ddl m} ddlm}m	m} t          |          s't                              d| j	        j
                   dS j
                                                                        | j        j        
 |
          } |
            |
          }|urdS |                     dd
f|	
fd	          }t                              d
| j	        j
                   |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.Nr  tts_providerc                "     |           S r  r:   r>  s    r1   r  z5PluginContext.register_tts_provider.<locals>.<lambda>
  r@  r3   r  z'Plugin '%s' registered TTS provider: %s)agent.tts_providerrn  agent.tts_registryr  r  r  r   rQ   rA  r   r   rE   rF   rI  r  r~  r+  )r  r2  rn  _register_tts_providerr  rv  r  r  r?  r  r  s    `      @@@r1   register_tts_providerz#PluginContext.register_tts_provider\
  s   * 	322222	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 (K00 	NN:"  
 F ++--3355'((eDDDxu5555**=FFF
X%%4(( %7       ) 	
 	
 	5M	
 	
 	
 r3   c                  	
 ddl m} ddlm}m	m} t          |          s't                              d| j	        j
                   dS j
                                                                        | j        j        
 |
          } |
            |
          }|urdS |                     dd
f|	
fd	          }t                              d
| j	        j
                   |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.Nr  transcription_providerc                "     |           S r  r:   r>  s    r1   r  z?PluginContext.register_transcription_provider.<locals>.<lambda>
  r@  r3   r  z1Plugin '%s' registered transcription provider: %s)agent.transcription_providerrv  agent.transcription_registryr  r  r  r   rQ   rA  r   r   rE   rF   rI  r  r~  r+  )r  r2  rv  _register_stt_providerr  rv  r  r  r?  r  r  s    `      @@@r1   register_transcription_providerz-PluginContext.register_transcription_provider
  s   6 	GFFFFF	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 ($9:: 	NNI"  
 F ++--3355'((eDDDxu5555**=FFF
X%%4(($*E=A       ) 	
 	
 	?M	
 	
 	
 r3   r  adapter_factoryvalidate_configrequired_envinstall_hintentry_kwargsc                .    ddl mm}	 |                    d j        j                    |	d|||||pg |dd|}
 j        j                                      }	                    |
                                         d         |
usd         dS  j        j
                                                            d	d	f| fd
 fd          }t                              d j        j                   |S )u  Register a gateway platform adapter.

        The adapter_factory receives a ``PlatformConfig`` and returns a
        ``BasePlatformAdapter`` subclass instance.

        ``check_fn`` is a PASSIVE dependency probe — "are deps importable
        right now?".  It must never install anything: status displays and
        config loading call it freely.  If your platform's SDK is
        lazy-installable, pass the ACTIVE installer separately as
        ``ensure_deps_fn`` (forwarded via ``entry_kwargs``); the gateway
        calls it from ``create_adapter()`` when ``check_fn`` is False,
        right before connecting the platform.

        Extra keyword arguments are forwarded to ``PlatformEntry`` (e.g.
        ``setup_fn``, ``emoji``, ``allowed_users_env``, ``platform_hint``,
        ``ensure_deps_fn``).  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  )r   r  r}  r  r~  r  r  r   r  r   Nr   c                6                         |           S r   )_restore_platform_registration)r  r  r   r  r  r  s    r1   r  z1PluginContext.register_platform.<locals>.<lambda>  s"    (K(K!4+u) ) r3   c                 8    j                                        S r   )rI   _remove_platform_name_if_unownedr  s   r1   r  z1PluginContext.register_platform.<locals>.<lambda>!  s    T]KKDQQ r3   r  z!Plugin %s registered platform: %sr:   )gateway.platform_registryr  r  r  r   r   rI  r  r  r  _plugin_platform_namesrr  r~  rQ   rT   )r  r   r  r}  r  r~  r  r  r  r  rV  rv  r  r  r  r  s   ``           @@@r1   register_platformzPluginContext.register_platform
  s   R 	ONNNNNNNt}/ABBB 

++%+%

 

 

 

 '$::4u:MM""5"666#99$e9LL1:U""gaj&<4,00666((eT*        RQQQQ ) 

 

 	/M	
 	
 	

 r3   r  c                4    |                     ||||          S r  r  r  r  r   r  r  r  s         r1   r  z,PluginContext._restore_platform_registration*  *     !55';e 6 
 
 	
r3   	action_idc                    t          |          st          d j        j         d          |)t	          |t
                    r1|                                st          d j        j         d          || j        j        f j        j        	                                
                    dt          |           fd          }t                              d j        j        |           |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.slack_action_handlerc                 N    j                             j         j                   S r   )rI  _remove_identity_slack_action_handlers)rV  r  s   r1   r  z=PluginContext.register_slack_action_handler.<locals>.<lambda>l  s#    DM224e  r3   z-Plugin %s registered Slack action handler: %s)r  r@  r   r   r   r   rE   rI  r  r   rs  r  rQ   rT   )r  r  r  r  rV  s   `   @r1   register_slack_action_handlerz+PluginContext.register_slack_action_handler8  s,   J !! 	@4=- @ @ @   Is!;!;IOODUDU;4=- ; ; ;   Hdm&89,33E:::"OO    
 
 	;M	
 	
 	

 r3   )defaultsrJ  r  c                   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        p j        j        } j	        j
                                      }|O|                    d          |k    r6t          d j        j         d	d|                    d           d          dddddi d}	|r|                                D ]
\  }
}||	|
<   |||	||d j	        j
        <    j	        j
                                      ddt           j	        j
                  f| fd          }t                              d j        j        |           |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)r  ro  s     r1   r   z8PluginContext.register_auxiliary_task.<locals>.<genexpr>  s3      88q199;;*!s(888888r3   z' auxiliary task key z: must contain only alphanumeric characters and underscoresr   )
_AUX_TASKSc                    h | ]\  }}}|	S r:   r:   )r   rl  _name_descs       r1   	<setcomp>z8PluginContext.register_auxiliary_task.<locals>.<setcomp>  s    DDDoaDDDr3   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<   <   )r2  r  base_urlapi_keyr  
extra_body)r   rJ  r   r  r  r  auxiliary_taskr  c                R    j                             j         j        |           S r   )rI  r  
_aux_tasks)r  rV  r   r  s    r1   r  z7PluginContext.register_auxiliary_task.<locals>.<lambda>  s'    (F(F(#uk) ) r3   r  z,Plugin %s registered auxiliary task: %s (%s))r   r   r@  r   r   allhermes_cli.mainr  r   rI  r  r   rC  r~  r   rQ   rT   )r  r   rJ  r   r  _BUILTIN_AUX_TASKSbuiltin_keysowner_idexistingmerged_defaultsrl  vr  rV  s   ``           @r1   register_auxiliary_taskz%PluginContext.register_auxiliary_task{  s   j  	*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$:$:h$F$F.4=- . .. .LL**. . .   +
 +
  	' (( ' '1%&"" (&'")
 )
 % (-((#R(@%A%A3G      ) 	
 	
 	:M		
 	
 	
 r3   r0  c                   ddl m} 	  ||d| j        j                   }n># t          $ r1}t
                              d| j        j        |           Y d}~dS d}~ww xY wt
                              d| j        j        |           |S )u  Additively register secret-token regexes with the redaction engine.

        Accepted patterns join the vendor-prefix alternation in
        :mod:`agent.redact` and are masked everywhere built-in patterns
        apply — logs, terminal output, transport errors, transcripts —
        with the same head/tail masking and the same non-reusable
        sentinel on ``file_read`` content. Historically every new vendor
        token format required a core PR appending to
        ``_PREFIX_PATTERNS``; provider plugins should own their own
        format instead.

        The registry is **additive-only**: plugins can extend what gets
        masked but cannot remove or weaken built-in patterns, so a
        plugin can only ever over-redact, never expose. The operator's
        global opt-out (``security.redact_secrets: false``) applies to
        plugin patterns exactly as it does to built-ins.

        Each pattern must compile as a regex and start with at least 2
        literal characters (e.g. ``r"nvapi-[A-Za-z0-9_-]{20,}"``).
        Invalid entries are warned and skipped — never raised.

        Returns the number of patterns accepted.
        r   )register_redaction_patternsr  r   z5Plugin '%s' redaction pattern registration failed: %sNz,Plugin %s registered %d redaction pattern(s))agent.redactr  r   r   r   rQ   rA  rT   )r  patternsr4  countr   s        r1   r  z)PluginContext.register_redaction_patterns   s    0 	JIIIII		I!?4=+=!?!?  EE  	 	 	NNG"C   11111	 	:M	
 	
 	
 s   # 
A&AA	hook_namec           
         t           vrLt                              d j        j        d                    t          t                                           j        j        	                    g           }|
                                                    d fd          }t                              d j        j                   |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)r  hookc                 P    j                             j         j                   S r   )rI  _remove_callback_hooks)r  r  r  s   r1   r  z-PluginContext.register_hook.<locals>.<lambda><  s%    DM22$i  r3   zPlugin %s registered hook: %s)r|   rQ   rA  r   r   r   rD  rI  r  r  r   rs  rT   )r  r  r  	callbacksr  s   ```  r1   register_hookzPluginContext.register_hook*  s     K''NN"		&--..   M(33IrBB	"""I     
 
 	4dm6H)TTTr3   r   )r  r  r   r  r  r  c          
        	 t                    st          d          t          |t                    st	          |          st          d          |t          vr7t          dd                    t          t                              z             t          |t                    s)t          |t                    rd|cxk     rt          k    sn t          dt                      j        j                                      }|t          dd	|j                   j        j        p j        j        }t'          ||||
          		 j        j        <   ddl}                     dd|                     j        j                  f	|	 fd          }t.                              d j        j                   |S )a3  Register bounded context that is frozen into each new session prompt.

        Callables receive a read-only session-info mapping. The rendered full
        system prompt is already persisted by core and restored verbatim, so no
        parallel plugin-section state is needed for process restarts.
        zdsystem prompt section id must be 1-128 lowercase characters using letters, numbers, '.', '_', or '-'z:system prompt section content must be a string or callablez/system prompt section position must be one of: r  r   z6system prompt section max_chars must be between 1 and Nzsystem prompt section z! is already registered by plugin )r   r   r  r  r  system_prompt_sectionr  c                R    j                             j         j        |           S r   )rI  r  _system_prompt_sections)r  r   sectionr  s    r1   r  z>PluginContext.register_system_prompt_section.<locals>.<lambda>  s'    (F(F5r7K) ) r3   r  z.Plugin %s registered system prompt section: %s)r   r@  r   r   r  r?  SYSTEM_PROMPT_SECTION_POSITIONSr   rD  rB   r0  MAX_SYSTEM_PROMPT_SECTION_CHARSrI  r  r   r  r   r   r   r  builtinsr~  r   rQ   rT   )
r  r   r   r  r  r  rX  r  r  r  s
   ``       @r1   register_system_prompt_sectionz,PluginContext.register_system_prompt_sectionC  sF    144 	;   '3'' 	Z0A0A 	ZXYYY:::A))F#BCCDDE  
 y$''	i--	 yCCCC$CCCCC525 5   =8<<R@@. . ."/. .   M%;);	+
 
 
 5<-b1
 	((#!DMABB
       ) 
 
 	<M	
 	
 	

 r3   r  r  Optional[dict]c                   | j         j        p| j         j        }|rt          |t                    s/t
                              d||           t          d| d          d|v rDt
                              d|||t                     t          d| d| d| dt           d		          |(t          |t                    st          d| d          | d| }| j                            ||pi           S )u  Publish *event* to all subscribers; return the number invoked.

        The event is delivered as ``<plugin_key>:<event>`` where
        ``plugin_key`` is FORCED to this plugin's own registry key
        (``manifest.key or manifest.name``). Pass only the bare event name —
        a plugin may only publish under its own namespace.

        Passing an already-namespaced name (anything containing ``':'``,
        including ``hermes:x`` or a foreign ``other:x``) is rejected with a
        ``ValueError`` and a logged warning — fail-closed. The ``hermes:``
        prefix is reserved for core.

        Delivery is fire-and-forget through a host-owned, single-worker queue:
        registration order is preserved, while a blocking subscriber cannot
        stall the emitter. The queue has a bounded pending budget; a full
        budget drops the new event with a warning. Each subscriber receives a
        deep-copied payload and is isolated in its own ``try/except``. Awaitable
        results are resolved through the existing loop-safe plugin path.

        Returns the count of subscriber callbacks scheduled (0 when there are
        no subscribers, or when the pending/recursion budget drops the emit).
        z2Plugin '%s' tried to emit an invalid event name %rr  z(' emit() requires a non-empty event namer   u   Plugin '%s' tried to emit namespaced/reserved event '%s' — a plugin may only emit bare event names under its own '%s:' namespace (the '%s:' prefix is reserved for core, and foreign namespaces are forbidden)z' may not emit 'z>': emit only the bare event name; the namespace is forced to 'z:' and the 'z:' prefix is reserved for coreNz'' emit() payload must be a dict or None)r   r   r   r   r   rQ   rA  r@  HERMES_EVENT_NAMESPACEr   r?  rI  _dispatch_event)r  r  r  r  
full_events        r1   emitzPluginContext.emit  se   . ]&<$-*<
 	Juc22 	NNDE   O:OOO   %<<NN, E:/E   S: S Su S S@JS S2S S S  
 z'4'@'@N:NNN   #,,U,,
},,ZBGGGr3   c                "   |rt          |t                    st          d| j        j         d          | j        j        p| j        j        }| j                            |||           t          	                    d| j        j        |           dS )u  Subscribe *callback* to a fully-qualified event name.

        *event* is the full ``<plugin_key>:<event>`` name (or ``hermes:<event>``
        if core ever emits). Subscribing is unrestricted — any plugin may
        listen to any published event; only *emitting* is namespace-gated.

        Callbacks are stored in registration order as host-owned ledger
        entries. The owner key lets plugin unload/reload remove subscriptions
        before any later event can invoke a zombie callback.
        r  z-' subscribe() requires a non-empty event namez!Plugin %s subscribed to event: %sN)
r   r   r@  r   r   r   rI  _subscribe_eventrQ   rT   )r  r  r  r  s       r1   	subscribezPluginContext.subscribe  s      	Juc22 	(4=- ( ( (   ]&<$-*<
&&z5(CCC/1CU	
 	
 	
 	
 	
r3   c           
         t           vrLt                              d j        j        d                    t          t                                           j        j        	                    g           }|
                                                    d fd          }t                              d j        j                   |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)r  
middlewarec                 P    j                             j         j                   S r   )rI  r  _middleware)r  r   r  s   r1   r  z3PluginContext.register_middleware.<locals>.<lambda>  s%    DM22)4  r3   z#Plugin %s registered middleware: %s)r   rQ   rA  r   r   r   rD  rI  r  r  r   rs  rT   )r  r   r  r  r  s   ```  r1   register_middlewarez!PluginContext.register_middleware  s     '''NN"		&!12233   M-88rBB	"""$     
 
 	:DM<NPTUUUr3   r   r	   frontmatterOptional[Mapping[str, Any]]c           
        	
 ddl m} d|v r t          d| d j        j         d          |r|                    |          st          d| d          |                                st          d	|            j        j        p j        j        }| d| 
 j        j	        r!
 j
        j        v rt          d

 d           j
        j                            
          }|| j        j        p j        j        ||t          |pi           d		 j
        j        
<                        d
dt!           j
        j                  
f	|	
 fd          }t"                              d j        j        
           |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_REr   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 zPlugin skill 'z' is already registered)r   r  r  	bare_namer   r  skillr  c                R    j                             j         j        |           S r   )rI  r  _plugin_skills)r  rV  	qualifiedr  s    r1   r  z.PluginContext.register_skill.<locals>.<lambda>2  s'    (F(F,i) ) r3   r  zPlugin %s registered skill: %s)agent.skill_utilsr  r@  r   r   matchexistsr2  r  r  rI  r  r   r   r   r~  r   rQ   rT   )r  r   r   r   r  r  	namespacerv  r  rV  r  s   `        @@r1   register_skillzPluginContext.register_skill  s   ( 	433333$;;:t : :M&: : :  
  	=..t44 	JtJJJ   {{}} 	E#$CT$C$CDDDM1GT]5G	 ))4))	=! 	Ri4=3O&O&OPiPPPQQQ=/33I>>-+At}/A& 1r22
 
 38$Y/((#R(D%E%EyQ      ) 	
 	
 	,M		
 	
 	
 r3   )r   r   rF  rG  r'   r   )rX  r   r'   rB   r   rA  rB  )r'   r   )r   r   r   r   r  r  r'   r  )r   r   r   r   ru  r  r  r
   rv  r
   rw  rx  rt  ry  r'   r  )r'   r
   )r  r  r'   r  )r   rf  r'   r  )r   r   r  r   r'   rC   )NNFr<   r<   F)r   r   r  r   rP  r   rU   r   r  r  r"  r  r  rB   r   r   r  r   r  rB   r'   r  )r   r   r'   rB   )Nr  )
r  r   r  r   r  r  r  r2  r'   r8  )r  r
   r'   r8  )rX  r   r'   rZ  )r  r   r'   rB   )r  )r   r   r  r   r  r  r'   rB   r  )Nr<   )r   r   r  r   r  r   r  r  r   r   r'   r  )r<   r<   )
r   r   rU   r   r   r   r  r   r'   r  )r  r   r   r   r'   r   )r'   r  r  )NNr<   )r   r   r  r   r}  r   r  r   r~  r  r  r  r  r   r  r
   r'   r  r   r   r  r   r'   rB   )r  r
   r  r   r'   r  )
r   r   rJ  r   r   r   r  r  r'   r  r@  )r  r   r  r   r'   r  )
r   r   r   r  r  r   r  r0  r'   r  )r  r   r  r  r'   r0  )r  r   r  r   r'   rC   )r   r   r  r   r'   r  )r<   N)
r   r   r   r	   r   r   r  r  r'   r  ):r6   r7   r8   r9   r#  r  rX  rR  rX  rj  rl  rp  rs  r~  r  r  r  r  r  r  r   r  r  r  r  classmethodr  rC  r  r  r  r  r  r  r!  r,  r5  r  rC  rL  rT  r\  rd  rl  rt  r|  r  r  r  r  r  r  'DEFAULT_SYSTEM_PROMPT_SECTION_MAX_CHARSr  r  r  r  r  r:   r3   r1   rE  rE  q  s       GG+ + + + 7 7 7 X7    L L L L L86 6 6 6p    X & & X& 	
 	
 	
 	
( 265 5 5 5 5 5.    X  ( ( ( X(&    X*    9=      2! ! ! !J  %)$(Y Y Y Y Yz@ @ @ @, /3H' H' H' H' H'T !   [4 1 1 1 \1(R R R RH D
 #'D D D D D DL
 
 
 
,  '+# # # # #N 
 B B B B BL
 
 
 
B ( ( ( (X 
  
  
  
H
 
 
 
@ , , , ,` 8 8 8 8x , , , ,` - - - -b 1 1 1 1j > > > >D 7 7 7 7v = = = =B  ,0$(M M M M M^

 

 

 

= = = =F  .2@ @ @ @ @ @H( ( ( (T   < '@K K K K K K^2H 2H 2H 2H 2Hh
 
 
 
0   : 
 37> > > > > > >r3   rE  c                     e Zd ZdZdddZddZedd            ZddZdd Z	dd"Z
dd$Zdd%Zdd(Zdd)Zedd,            Z	 ddd.Z	 ddd/Zedd0            Zdd5Zdd6Zdd8Zddd;Zdd<Zdd=Zdd>ZddAZddBZddDZddGZ	 dddMZddQZ ddUZ!ddVZ"ddWZ#ddXZ$e%ddY            Z&dd\Z'dd]Z(dd^Z)dd`Z*ddaZ+ddbZ,ddcZ-dddZ.ddeZ/ddfddiZ0ddjZ1eddl            Z2ddoZ3ddqZ4ddrZ5ddsZ6ddtZ7ddwZ8ddyZ9dd|Z:dddZ;ddZ<ddZ=ddZ>ddZ?ddZ@ddZAddZBddZCddZDddZEddZFddZGddZHddZIdS )PluginManagerz;Central manager that discovers, loads, and invokes plugins.Nr  rf  r'   rC   c                   |pt                      | _        t          | j                  | _        t	          j                    | _        i | _        i | _        i | _	        t                      | _        t                      | _        i | _        d | _        i | _        i | _        d| _        d | _        d | _        i | _        i | _        i | _        i | _        i | _        t	          j                    | _        t	          j        | j                  | _        d| _        ddi| _        t=          j        t@                    | _!        d | _"        t	          j#                    | _$        g | _%        i | _&        g | _'        i | _(        i | _)        d S )NFr   maxsize)*r   r  r	   r  r  r  _discovery_lockrP  r  r  r   r  r  r  r&  r  r  _discoveredr  _gateway_message_injectorr  _portable_mcp_serversr  r  _subscriptions_event_lock	Condition_event_idle_event_generation_event_pending_by_generationqueueQueue_EVENT_PENDING_CAP_event_queue_event_workerlocal_emit_depthr  _ownership_ledger_registration_order_predeclared_modules_predeclared_tools)r  r  s     r1   r#  zPluginManager.__init__D  ss    #7o&7&7dn--(00131368,/EE03#.0#13MO$!&IM&9;@B" 6846! DF$?,,$.t/?@@!"=>F).3k&/
 /
 /
 :> %?,, 46#  GI=?  BD!8:r3   r   r   r   r   r   r  r  r  c                     |j         p|j        }t          ||||          } fd|_         j                            |g                               |            j                            |           |S )zBRecord one successful registration under its canonical plugin key.)r   r   r  r  c                0                         | g          S r   )_forget_registrations)disposedr  s    r1   r  z3PluginManager._track_registration.<locals>.<lambda>  s    D4N4NJ5
 5
 r3   )r   r   r  r  r   r  r   r  )r  r   r   r   r  r  registrations   `      r1   rr  z!PluginManager._track_registration  s     \2X]
)!	
 
 
$
 $
 $
 $
  	))*b99@@NNN ''555r3   valuesr   targetr
   rB   c                n    t          t          |           dz
  dd          D ]}| |         |u r| |=  dS dS )z<Remove the last exact object match from a registration list.r   TF)ranger   )r	  r
  indexs      r1   r  zPluginManager._remove_identity  sR     3v;;?B33 	 	Ee}&&5Mtt ' ur3   mappingDict[str, List[Callable]]r  r   c                    |                     |          }|d S |                     ||           |s|                    |d            d S d S r   )r   r  pop)r  r  r   r  r  s        r1   r  zPluginManager._remove_callback  sb     KK$$	Fi222 	#KKT"""""	# 	#r3   r8  r  rv  Optional[Any]c                t    |                     |          |urdS ||                    |d           n|||<   dS )zERestore a manager-local mapping only when *current* is still present.FNT)r   r  )r  r  r   r  rv  s        r1   r  zPluginManager._restore_mapping  sK     ;;s7**5KKT""""#GCLtr3   	attributec                P    t          | |          |urdS t          | ||           dS )zBRestore a manager-local value only when *current* is still active.FT)r   setattr)r  r  r  rv  s       r1   r(  zPluginManager._restore_value  s5     4##7225i***tr3   r   c                    t          fd| j        D                       s| j                                       d S d S )Nc              3  R   K   | ]!}|j         o|j        d k    o
|j        k    V  "dS )r  Nr  r   r   r   r  r   s     r1   r   z=PluginManager._remove_tool_name_if_unowned.<locals>.<genexpr>  s\       
 
   )!V+) D(
 
 
 
 
 
r3   )r   r  r  discardr  r   s    `r1   r  z*PluginManager._remove_tool_name_if_unowned  si     
 
 
 
 !% 8	
 
 
 
 
 	2 #++D11111	2 	2r3   c                    t          fd| j        D                       s| j                                       d S d S )Nc              3  R   K   | ]!}|j         o|j        d k    o
|j        k    V  "dS )r   Nr  r  s     r1   r   zAPluginManager._remove_platform_name_if_unowned.<locals>.<genexpr>  s\       
 
   )!Z/) D(
 
 
 
 
 
r3   )r   r  r  r  r  s    `r1   r  z.PluginManager._remove_platform_name_if_unowned  si     
 
 
 
 !% 8	
 
 
 
 
 	6 '//55555	6 	6r3   registrationsList[PluginRegistration]c                   |sd S d |D             fd| j         D             | _         t          | j                                                  D ];\  }}fd|D             }|r|| j        |<    | j                            |d            <d S )Nc                ,    h | ]}t          |          S r:   r   r   r  s     r1   r  z6PluginManager._forget_registrations.<locals>.<setcomp>  s     OOOB|,,OOOr3   c                6    g | ]}t          |          v|S r:   r$  r   r  registration_idss     r1   r   z7PluginManager._forget_registrations.<locals>.<listcomp>  s7     $
 $
 $
,'777 777r3   c                6    g | ]}t          |          v|S r:   r$  r'  s     r1   r   z7PluginManager._forget_registrations.<locals>.<listcomp>  s7        l##+;;; ;;;r3   )r  r   r   rC  r  )r  r   r  owned	remainingr(  s        @r1   r  z#PluginManager._forget_registrations  s      	FOOOOO$
 $
 $
 $
 $ 8$
 $
 $
 
 "&d&<&B&B&D&D!E!E 		= 		=J   $)  I
  =5>&z22&**:t<<<<		= 		=r3   c           	         t          |          D ]\}	 |                                 # t          $ r8}t                              d|j        |j        |t                     Y d}~Ud}~ww xY wdS )z@Dispose registrations in reverse acquisition order, best effort.z.Failed to unload plugin registration %s/%s: %sr  N)r  r  r   rQ   rA  r  r   rG   )r  r   r  r   s       r1   _dispose_registrationsz$PluginManager._dispose_registrations  s    
 %]33 
	 
	L	$$&&&&   D + $+         
	 
	s   (
A*.A%%A*r  (Union[str, PluginManifest, LoadedPlugin]c                    t          | t                    r| j        j        p| j        j        S t          | t
                    r| j        p| j        S t          |           S r   )r   r  r   r   r   r   r   r  s    r1   _resolve_plugin_keyz!PluginManager._resolve_plugin_key  sX     fl++ 	??&>&/*>>fn-- 	-:,,6{{r3   .Union[str, PluginManifest, LoadedPlugin, None]c                    | j         5  t          | j                  5  |                     |          cddd           cddd           S # 1 swxY w Y   ddd           dS # 1 swxY w Y   dS )z@Unload registrations while excluding discovery/deferred loading.N)r  r   r  _unload_scoped)r  r  s     r1   unloadzPluginManager.unload  s   
 ! 	/ 	/#5dn#E#E 	/ 	/&&v..	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/s3   A#AA#A	A#A	A##A'*A'c                   |du }|r>t          | j                  t          | j                  z  t          | j                  }n|                     |          | j        v s	| j        v rhnt                      }|r|nKfd| j                                        D                                 fd| j        D                        fd| j        D             }t          p|          }| 	                    |           | 
                    |           |rddlm} t          | j                  D ]}|                    |           d |D             t          fd| j        D                       }|r	 dd	lm}	 |D ]K}
	 |	                    |
           # t(          $ r&}t*                              d
|
|           Y d}~Dd}~ww xY wn2# t(          $ r%}t*                              d|           Y d}~nd}~ww xY w| j                                         | j                                         | j                                         | j                                         | j                                         | j                                         | j                                         | j                                         | j                                         | j                                         | j                                         | j                                         | j                                          | j!                                         | j"                                         | j#                                         d| _$        d| _%        n D ]}| j        &                    |d           |S )a  Unload one plugin or all plugins owned by this manager.

        Every registration made through :class:`PluginContext` is disposed in
        reverse acquisition order.  Registry inverses are conditional on the
        exact object still being current, so a later registration is never
        removed accidentally.  ``plugin=None`` is the lifecycle operation
        used by force rediscovery.  ``on_unload`` callbacks and supervised
        background tasks registered through :class:`PluginContext` are
        disposed through the same reverse-order ledger walk.

        Returns ``True`` when at least one plugin or registration was found.
        Nc                :    h | ]\  }}|j         j        k    |S r:   )r   r   )r   r   rQ  	requesteds      r1   r  z/PluginManager._unload_scoped.<locals>.<setcomp>C  s7       #V+y88 888r3   c              3  (   K   | ]}|k    |V  d S r   r:   )r   r   r8  s     r1   r   z/PluginManager._unload_scoped.<locals>.<genexpr>H  s:       # #i'' ''''# #r3   c                &    g | ]}|j         v |S r:   r  )r   r  target_keyss     r1   r   z0PluginManager._unload_scoped.<locals>.<listcomp>M  s1        *k99 999r3   r   r  c                2    h | ]}|j         d k    |j        S r  r   r   r%  s     r1   r  z/PluginManager._unload_scoped.<locals>.<setcomp>n  s3     ! ! ! $..  ...r3   c              3  $   K   | ]
}|v|V  d S r   r:   )r   r   ledger_tool_namess     r1   r   z/PluginManager._unload_scoped.<locals>.<genexpr>s  s;       $ $000 0000$ $r3   r  z%unload: tool deregister %s failed: %sz&unload: tools.registry unavailable: %sF)'r   r   rP  r   r  r1  rC  updaterB   r-  r  r  r  r  r  
unregisterr  r  r  
deregisterr   rQ   rT   clearr  r  r  r  r  r  r  r  r  r  r  r  r&  r  r  )r  r  
unload_allr   exactfoundr  platform_namepreledger_toolstool_registryr  r   r   rB  r8  r<  s                @@@r1   r4  zPluginManager._unload_scoped'  s     t^
 	d455DM8J8JJK !9::MM0088I d444	T]8R8R  X[X]X]   #   '+}':':'<'<  
 "" # # # ##5# # #   
   $($<  M [1M22##M222""=111 D	- DCCCCC!&t'B!C!C < <!,,];;;;! !$1! ! !
 $ $ $ $ $ 3$ $ $  O
  HHHHHH &5  	)44Y????(   "LL G ) #        ! P P PLL!I3OOOOOOOOP "((***M!!!K""$$$#))+++'--///$$&&&!'')))%%'''&,,...O!!###(..000%++---'--///%++---#))+++#'D $D" - -!!#t,,,,s0   G F##
G-GG
H!HHc                    | j         duS )z@Return whether a live gateway can accept plugin-triggered turns.Nr  r  s    r1   r  z*PluginManager.has_gateway_message_injector  s     -T99r3   r  r  injectorCallable[..., bool]c                    ||f| _         dS )z8Publish a live gateway injector and its lifecycle owner.NrN  )r  r  rO  s      r1   set_gateway_message_injectorz*PluginManager.set_gateway_message_injector  s     +0):&&&r3   c                B    | j         }||d         |u rd| _         dS dS dS )z;Clear the injector only when it still belongs to ``owner``.Nr   rN  )r  r  r  s      r1   clear_gateway_message_injectorz,PluginManager.clear_gateway_message_injector  s:    3
!jmu&<&<-1D*** "!&<&<r3   r   c                N    | j         }|dS t           |d         di |          S )z3Submit a plugin-triggered turn to the live gateway.NFr   r:   )r  rB   )r  r   r  s      r1   r  z$PluginManager.inject_gateway_message  s7    3
5MJqM++F++,,,r3   FrA   c                Z   | j         5  t          | j                  5  | j        r|s	 ddd           ddd           dS |r|                                  t          d          r:t                              d           d| _        	 ddd           ddd           dS d| _        	 |                                  | 	                                 |r| 
                                 n# t          $ r	 d| _         w xY wddd           n# 1 swxY w Y   ddd           dS # 1 swxY w Y   dS )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.
        NHERMES_SAFE_MODEu/   HERMES_SAFE_MODE=1 — plugin discovery skippedTF)r  r   r  r  r5  r   rQ   r+  _discover_and_load_inner'_refresh_secret_sources_after_discovery$_re_register_shell_hooks_after_forceBaseException)r  rA   s     r1   discover_and_loadzPluginManager.discover_and_load  s8    ! !	 !	#5dn#E#E !	 !	  !	 !	 !	 !	 !	 !	 !	 !	 !	 !	 !	 !	 !	 !	   122 MNNN#' !	 !	 !	 !	 !	 !	 !	 !	 !	 !	 !	 !	 !	 !	"  $D--/// <<>>> @ ==???    #( ?!	 !	 !	 !	 !	 !	 !	 !	 !	 !	 !	 !	 !	 !	 !	 !	 !	 !	 !	 !	 !	 !	 !	 !	 !	 !	 !	 !	 !	 !	 !	 !	 !	sd   D DD  ADD D'>C&%D&C99D<D D	D D	D  D$'D$c                    	 ddl m}  |             dS # t          $ r&}t                              d|           Y d}~dS d}~ww xY w)zCRestore config.yaml shell hooks wiped by force-clear of ``_hooks``.r   )re_register_config_hooksz/force-reload shell-hook re-register skipped: %sN)agent.shell_hooksr^  r   rQ   rT   )r  r^  r   s      r1   rZ  z2PluginManager._re_register_shell_hooks_after_force  sy    	QBBBBBB$$&&&&& 	Q 	Q 	QLLJCPPPPPPPPP	Qs    
A?Ac                   	 ddl m} ddlm}m} n# t
          $ r Y dS w xY w	  |            }n# t
          $ r Y dS w xY w|sdS 	 ddlm}  |            pi }|                    d          pi }n# t
          $ r i }Y nw xY wg }|D ]|}	t          |	dd          }
|                    |
          }t          |t                    r|ni }	 |	                    |          r|                    |
           m# t
          $ r Y yw xY w|sdS 	  |              |             t                              d	d
                    t#          |                               dS # t
          $ r&}t                              d|           Y d}~dS d}~ww xY w)u  If any plugin secret source is enabled, reset cache and re-apply.

        Enablement is delegated to each source's ``is_enabled(cfg)`` — the
        same contract the orchestrator uses (``registry._ordered_enabled_sources``)
        — so a source with custom activation logic is honored, not just
        ``secrets.<name>.enabled``.

        No-op when only bundled sources exist or none are enabled.
        Fail-open: never raise into discover_and_load.
        r   )list_plugin_sources)load_hermes_dotenvreset_secret_source_cacheNr   secretsr   r<   z8Re-applied secret sources after plugin discovery for: %sr  z1secret source re-apply after discovery failed: %s)rk  ra  hermes_cli.env_loaderrb  rc  r   r   r   r   r   r   r   
is_enabledr   rQ   rT   r   rD  )r  ra  rb  rc  plugin_sourcesr   r  rd  enabled_namesr   r   r  r   s                r1   rY  z5PluginManager._refresh_secret_sources_after_discovery  sV   	IIIIII[[[[[[[[[ 	 	 	FF		0022NN 	 	 	FF	 	F	555555+--%2Cggi((.BGG 	 	 	GGG	$ 
	 
	F662..Dkk$''G!+GT!:!:BggG$$W-- /!((...      	F	S%%'''   LLJ		&//00      	S 	S 	SLLLcRRRRRRRRR	SsU    

. 
<<)A. .A=<A=*C11
C>=C>AE 
F!FFc           	        |                                  }|                                 }t                              dt	          |                     |                    |           t                      }t                      }t          |          }|r4t          	                    dd
                    |          t                     i }|D ]}|||j        p|j        <   i }|                                D ]}|j        p|j        }	|	t          v s|j        t          v rMt!          |d          }
dt           d|
_        |
| j        |	<   t          	                    d|	|
j                   u|	|v s	|j        |v r>t!          |d          }
d	|
_        |
| j        |	<   t                              d
|	           |j        dk    r?t!          |d          }
d|
_        |
| j        |	<   t                              d|	           
|j        dk    r8t!          |d          }
|
| j        |	<   t                              d|	           M|j        dk    r"|j        dk    r|                     |           z|j        dk    r"|j        dk    r|                     |           |duo|	|v p|j        |v }|sRt!          |d          }
d                    |	          |
_        |
| j        |	<   t                              d|	           |||	<   t1          |          D ]I}	||	         }|                     |           |                     |           |                     |           J|r^t                              dt	          | j                  t9          d | j                                        D                                  dS dS )u=   The actual discovery sweep — see :meth:`discover_and_load`.z  entrypoints: %d manifest(s)zqRemoved Hermes plugin %s is still listed in plugins.enabled; remove it and configure native Relay plugins with %sr  Fr   r   u?   removed — Relay lifecycle is owned by Hermes core; configure z insteadz5Refusing to load removed Hermes Relay plugin '%s'; %szdisabled via configzSkipping disabled plugin '%s'r   u<   exclusive plugin — activate via <category>.provider configz8Skipping '%s' (exclusive, handled by category discovery)r   Tz?Skipping '%s' (model-provider, handled by providers/ discovery)r  r   r   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 )r   N)r   r  s     r1   r   z9PluginManager._discover_and_load_inner.<locals>.<genexpr>  s)      CC!CACCCCCCr3   )_collect_directory_manifests_scan_entry_pointsrQ   rT   r   rx  r   r   r&   rA  r   r%   r   r   r	  r$   r  r  rP  r   r   _load_plugin_register_deferred_platformformatr  _warn_python_dependencies_validate_plugin_config_schemar+  sum)r  r   ep_manifestsr   r   stale_relay_keyswinnersr   to_load
lookup_keyrQ  rf  s               r1   rX  z&PluginManager._discover_and_load_inner(  sV   *.*K*K*M*M	
 ..004c,6G6GHHH&&& )**&((3G<< 	NNG		*++(	   .0! 	> 	>H5=GHL1HM22 .0(( h	+ h	+H!6J 666=$<<<%xGGG:/: : :  -3j)KL  
  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    )++0J0J!!(+++ )++0K0K00::: t# H7*Fhmw.F   
%xGGGXVJ''  -3j)<j   "*GJ 4G<< 	( 	(Jz*H**8444//999h'''' 	KKADM""CCt}3355CCCCC    	 	r3   r  rX  c          	     B   ddl }ddlm} t          |                                                                          }|dk    rt          d           |j        d|          st          d          t          |          st          d          || j
        v r'| j
        |         j        }t          d	|d
|           ||||t          t                                                                          | j
        |<   t                              d||           dS )z>Register one plugin-owned approval transport for this profile.r   N)RegisteredApprovalTransportbuiltinz-approval transport name 'builtin' is reservedz[a-z0-9][a-z0-9_-]{0,63}z;approval transport name must match [a-z0-9][a-z0-9_-]{0,63}z.approval transport present_fn must be callablezapproval transport z is already registered by )r   r_  rX  profile_homez+Plugin %s registered approval transport: %s)r  hermes_cli.approval_transportrz  r   rE   rF   r@  r   r  r?  r  rX  r   r.   rQ   r+  )r  r   r  rX  r  rz  r  r  s           r1   r  z)PluginManager.register_approval_transport  sR    				MMMMMMD		!!''))ILMMMr|7?? 	M   
## 	NLMMMD----e4>EReRRRR   ,G+F_..668899	,
 ,
 ,
!%( 	A9eTTTTTr3   c                   | j                             t          |                                                                                    }|dS |j        t          t                                                                k    rdS |S )z>Return a transport only inside the profile that registered it.N)r  r   r   rE   rF   r|  r   r.   )r  r   r  s      r1   get_approval_transportz$PluginManager.get_approval_transport  sr    .223t99??3D3D3J3J3L3LMM
4"c/*;*;*C*C*E*E&F&FFF4r3   List[PluginManifest]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           |S )a  Collect directory manifests in the same order as full discovery.

        This method only reads manifests. It does not load native plugin
        modules, register deferred platforms, or otherwise mutate manager
        registries. Keeping the source ordering and scanner calls here lets
        startup probes share the exact precedence and containment rules used
        by :meth:`_discover_and_load_inner`.
        zScanning bundled plugins: %sr  >   model-providersmemoryr  r$  )r   
skip_namesz%  bundled (top-level): %d manifest(s)r  r  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))
r2   rQ   rT   _scan_directoryr   rx  r   r   r	   cwd)	r  r   repo_pluginsr  bundled_platformsuser_diruser_manifestsproject_dirproject_manifestss	            r1   rl  z*PluginManager._collect_directory_manifests  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   r3   
raw_configMapping[str, Any]c                *   t          d          rdS |                    d          }t          |t                    sdS |                    d          }t          |t                    sdS d |D             }|                    dg           }t          |t                    rd |D             nt                      }|sdS i }|                                 D ]}|||j        p|j        <   |	                                D ]}|j
        s
|j        p|j        }	|	|v s	|j        |v r&|	|vr
|j        |vr4	 dd	lm}
  |
t          |j                  t                      d
z  |j        p|	z  g d          r dS y# t"          t$          t&          f$ r Y w xY wdS )a*  Probe enabled portable MCP packages without loading plugins.

        The directory manifest collection is shared with full discovery, so
        native ``plugin.yaml`` precedence, source ordering, depth limits, and
        project-plugin gating cannot diverge between startup and runtime.
        rW  Fr*   r   c                <    h | ]}t          |t                    |S r:   r   r   r   r   s     r1   r  z9PluginManager.has_enabled_portable_mcp.<locals>.<setcomp>4  s'    NNNUz%7M7MN5NNNr3   r   c                <    h | ]}t          |t                    |S r:   r  r  s     r1   r  z9PluginManager.has_enabled_portable_mcp.<locals>.<setcomp>7  s'    IIIu*UC2H2HIUIIIr3   r   )_discover_mcpr%  )create_dataT)r   r   r   r   r   r   rl  r   r   r	  r  hermes_cli.agent_pluginsr  r	   r   r   r  r3  r4  r@  )r  r  plugins_configenabled_valuer   disabled_valuer   rv  r   rx  r  s              r1   has_enabled_portable_mcpz&PluginManager.has_enabled_portable_mcp$  s    *++ 	5#	22.$// 	5&**955-.. 	5NNmNNN'++J;; .$//IIIIII 	
  	5-/99;; 	> 	>H5=GHL1HM22(( 	 	H$ !6JX%%()B)B((X]'-I-IBBBBBB =''#%%#$/=:?  %      44  \:6     us   2A E66FFr   r	   r   r  Optional[Set[str]]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   r  r1  r  )_scan_directory_level)r  r   r   r  s       r1   r  zPluginManager._scan_directory`  s,    * ))&Z! * 
 
 	
r3   r1  r  r0  c                  g }|                                 s|S t          |                                          D ]=}|                                 s|dk    r|r
|j        |v r*|dz  }|                                s|dz  }|                                r0|                     ||||          }	|	|                    |	           |dz  }
|
                                s|
                                r	 ddlm	}  ||          \  }}|D ]#}t                              d||j                   $|r| d|j         n|d	         }|                    t          |d	         |                    d
d          |                    dd          t          |                    dd                    |t!          |          |dt#          |          	  	                   n3# t$          $ r&}t                              d|
|           Y d}~nd}~ww xY w|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.ymlNzplugin.json)read_agent_plugin_manifestzAgent Plugin '%s': %sr  r   r   r<   r   r  T)	r   r   r   r  r   r   r   r  r  Failed to parse %s: %sr   z/Skipping %s (no plugin.yaml, depth cap reached)r  )is_dirrD  iterdirr   r  _parse_manifestr   
is_symlinkr  r  rQ   rA  messager   r   r  r   r  r   rT   rx  r  )r  r   r   r  r1  r  r   childmanifest_filer   portable_filer  r7  diagnostics
diagnosticr   r   
sub_prefixs                     r1   r  z#PluginManager._scan_directory_levely  s    +-	{{}} 	DLLNN++ ?	 ?	E<<>> zzjzUZ:-E-E!M1M '')) 5 % 4##%% //!5&&  '$$X...!M1M##%% )A)A)C)C QSSSSSS(B(B5(I(I%D+&1  
3!&.   
 7=NV22ej222$v,C$$&!%f$(HHY$;$;(,(C(C#2488Hb3I3I#J#J#)!$U #%),Ec,J,J
 
 
    ! Q Q QNN#;]CPPPPPPPPQ
 zzNPUVVV5;KF11UZ111J**#%!) +      s   5C$G
H
$HH
r  
plugin_dirOptional[PluginManifest]c                   	 t           t                              d|           dS t          |                    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    r|d|vrx|dz  }
|
                                r_	 t          |
                    dd          dd                   }|r|}	t                              d|||           n# t"          $ r Y nw xY wt                              d|||	||           t%          ||          }t'          d|t          |                    dd                    |                    dd          t)          |                    dd                    |                    dg           |                    dg           |                    dg           |t          |          |	|t+          |                    d          |          d||                    d          pg |                    d          pg d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 %sr  r/  r   r  r   r   zBPlugin %s: unknown kind '%s' (valid: %s); treating as 'standalone'r  r  r  )r  r  r  z-Plugin %s: detected %s, treating as kind='%s'z9Parsed manifest: key=%s name=%s kind=%s source=%s path=%sr   r<   r   r  r"  r%  r$  r   )r   r   r   r  r"  r%  r$  r   r   r   r   r   r  r  )r  r  r  r  r:   )yamlrQ   rA  r   r  r   r   r   r   rE   rF   r   r   rD  r  r   rT   r   rW  r   r  r   rG   )r  r  r  r   r1  r7  r   r   raw_kindr   	init_filedetected	v2_fieldsr   s                 r1   r  zPluginManager._parse_manifest  se   I	|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	##%% #;%//'07 0  #te%$ $
 $ #+D"LL O #Xx   %    LLKT4   2$<<I! DHHY3344 HH]B77&txx"'='=>>!XXnb99#xx(8"==#xx(8"==__9HH^,,d     hhw''-2++1r#    &  	 	 	NN(-~     44444		sC   "K DK :AF K 
FK FD9K 
L#LLc                    	 |j                             dd          d                                         }|sdS t          |          }t	          |          pdS # t
          $ r Y dS w xY w)u  Classify a pip entry-point plugin by scanning its module source.

        The ``kind`` semantics are the same for pip entry points as for
        directory plugins: memory providers (``exclusive``) and model
        providers (``model-provider``) have their own discovery systems,
        so importing them here registers nothing and only pays the
        module's import cost in every Hermes process (e.g. a pip
        memory-provider plugin pulling in onnxruntime via fastembed —
        ~60 MB RSS on startup).

        The module source is read without importing the module or any
        of its parent packages (see ``_resolve_module_source``); only
        the first 8192 chars are scanned, mirroring the directory-plugin
        heuristic. Unresolvable or non-Python modules stay ``standalone``.

        Activation contract: this method only decides whether the general
        manager imports the module — it does not activate anything.
        Memory and model providers activate through their own systems
        (``memory.provider`` config via ``plugins/memory`` directory
        discovery; ``providers/`` lazy directory discovery). Both are
        directory-based today, so a pip-only provider is recorded for
        introspection but not activatable until those systems gain
        entry-point discovery (tracked for memory: #40644). That is not
        a regression: pre-change such a provider was equally
        unactivatable — it was merely imported first, at full cost
        (e.g. fastembed -> onnxruntime), and logged
        ``no register() function``. Classification removes the cost
        without changing the activation surface, and is the prerequisite
        that prevents double-import once entry-point activation lands.
        r   r   r   r   )r   r   rE   r   r   r   )r  r   r   r  s       r1   _classify_entrypoint_kindz'PluginManager._classify_entrypoint_kind)  s|    >	 (..a00399;;K $#|0==K+K88HLH 	  	  	 <<	 s   5A A 
A'&A'c                    t                      S )a8  Read installed plugin and companion capability entry points.

        Delegates to ``discover_entrypoint_manifests()``, which composes
        kind classification (import-free source scan routing memory/model
        providers away from the general manager) with capability
        declarations from the ``hermes_agent.plugin_capabilities`` group.
        Capability declarations live in distribution metadata so discovery
        is available before importing untrusted plugin code and does not
        depend on a package-data ``plugin.yaml`` being present.
        )r   r  s    r1   rm  z PluginManager._scan_entry_pointsQ  s     -...r3   c                    |j         pd}|                    d          r|dt          d                    S |j        rt	          |j                  j         S |S )a  Derive the gateway platform name (e.g. ``feishu``) for a platform plugin.

        The platform name registered via ``register_platform(name=...)`` lives
        inside the adapter module (which we are explicitly trying NOT to import
        early). It is not carried in ``plugin.yaml``. Across every bundled
        platform plugin the manifest name is ``<platform>-platform`` and the
        plugin directory basename is ``<platform>``, so we derive the name
        without importing: strip a trailing ``-platform`` from the manifest
        name, falling back to the directory basename. This is also a sensible
        convention for third-party platform plugins.
        r<   z	-platformN)r   r  r   r   r	   )r  r   r   s      r1   _platform_name_from_manifestz*PluginManager._platform_name_from_manifestb  sb     }"==%% 	-+3{++++,,= 	,&&++r3   c           
     $   	
 |j         p|j        }                     |          t          |d          }d|_        | j        |<   	 ddlm	  j        
|fd	
 fd	}		                    

          }	
                    |

           		                    

          d         hd         |u r^ j                                       t          j        d
f|	
 fd fd          }                     |d|j                   t"                              d|           nC# t&          $ r6 t"                              d|d                                |           Y dS w xY w                     ||           dS )a  Register a lazy loader for a bundled platform plugin.

        The platform adapter module is imported only when the gateway / cron /
        setup / send_message path first asks the ``platform_registry`` for this
        platform. Until then we record a lightweight ``LoadedPlugin`` so
        ``hermes plugins list`` still shows the platform as available, and we
        hand the registry a loader that runs the normal eager-load path.
        Trj  r   r=  	_manifestr   r'   rC   c                *   j         5  t          j                  5                                r	 d d d            d d d            d S                     |            d d d            n# 1 swxY w Y   d d d            d S # 1 swxY w Y   d S r  )r  r   r  is_deferred_load_cancelled_load_plugin_scoped)r  rJ  r  r  r  s    r1   _loaderz:PluginManager._register_deferred_platform.<locals>._loader  s[    ) 8 8+=dn+M+M 8 8(CC%U D    	8 8 8 8 8 8 8 8 8 8 8 8 8 8
 ,,Y7778 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8s?   BA0BA0$B0A4	4B7A4	8BBBr  Nr   r   c                6                         |           S r   )_restore_deferred_platform)r  r  rJ  r  r  r  s    r1   r  z;PluginManager._register_deferred_platform.<locals>.<lambda>  s&    0O0O)%#1 1 r3   c                 .                                    S r   )r  )rJ  r  s   r1   r  z;PluginManager._register_deferred_platform.<locals>.<lambda>  s    T%J%J%& & r3   r{  z3Registered deferred platform loader: %s (plugin=%s)z=Deferred platform registration failed for '%s'; eager-loadingr  )r  r   r'   rC   )r   r   r  r  r  rP  r  r  r  r  register_deferredr  rr  r   r|  rr  r  rQ   rT   r   rn  !_register_deferred_platform_tools)r  r   rx  rQ  r  rv  r}  r  rJ  r  r  s   `      @@@@r1   ro  z)PluginManager._register_deferred_platformu  sr    \2X]
99(CC
 x>>>$*j!@	CCCCCCNE4< 8 8 8 8 8 8 8 8 8 8 )>>U ?  H //we 0    (==U >  G qz!gajG&;&;+//>>>/76#%              ((!M	   LLE   
  		 		 		 LLO    
 h'''FF		 	..x@@@@@s   C*D7 7<E76E7rQ  r  c           
        |j         sdS |j        p|j        }|j        rt	          |j                  nd}||dz                                  s0t                              d|t          |j                              dS t          | j
                  	 |                     |          }||_        || j        |<   t          j        |j         d          }t#          |dd          }|0t                              d|t          |j                              dS  |t%          ||                      fd| j
        D             }||_        || j        |<   t                              d|t-          |          |           dS # t.          $ r}	fd	| j
        D             }
|
r|
|_        |
| j        |<   t-          |j                   }|
sd
| d}n0t-          |
          |k    rd| d}ndt-          |
           d| d}t                              d|||	t-          |
          |k    rdndt0                     Y d}	~	dS d}	~	ww xY w)u  Register a deferred platform's *client* tools without its adapter.

        A platform plugin can ship two independent things: an inbound adapter
        (heavy — it imports the platform SDK) and outbound client tools the
        agent calls like any other tool. Deferring the plugin defers both, so
        in a CLI/TUI process the client tools never register at all:
        ``resolve_toolset()`` returns ``[]``, the toolset is missing from the
        ``hermes tools`` checklist, and even an explicit ``platform_toolsets``
        entry is dropped because the key is unknown. The same tools work in
        gateway/web processes only because those materialize every platform at
        startup (issue #78050).

        Client tools that live in a dedicated ``tools`` submodule can be
        registered at discovery time instead: importing ``<plugin>/tools.py``
        does not import the adapter, so the SDK stays unloaded and startup
        stays cheap. A plugin taking this path must therefore keep its package
        ``__init__`` import-light and pull the adapter in from inside
        ``register()`` (as ``plugins/platforms/a2a`` does).

        Opting in is explicit: the manifest must declare ``provides_tools``
        (the field the plugin list and web server already read to name a
        plugin's tools, per #78538). Keying off the mere presence of a
        ``tools.py`` would opt a plugin in by accident — a platform is free to
        put internal helpers there — and would leave the contract invisible to
        anyone reading the manifest. ``tools.py`` remains where the code is
        imported from; ``provides_tools`` is what asks for it. A platform that
        does not declare the field is untouched and stays fully deferred.
        Nztools.pyzrPlugin '%s' declares provides_tools %s but has no tools.py; those tools will not be available in CLI/TUI sessions.z.toolsregister_toolszPlugin '%s' declares provides_tools %s but its tools.py has no register_tools(ctx); those tools will not be available in CLI/TUI sessions.c                    g | ]}|v|	S r:   r:   r   r>  befores     r1   r   zCPluginManager._register_deferred_platform_tools.<locals>.<listcomp>  s%       avooooor3   z;Deferred platform '%s': pre-registered %d client tool(s) %sc                    g | ]}|v|	S r:   r:   r  s     r1   r   zCPluginManager._register_deferred_platform_tools.<locals>.<listcomp>'  s    MMMQQf__q___r3   zbefore registering any of its z declared tool(s)zafter registering all zafter registering z of z;Plugin '%s': client-tool pre-registration failed %s (%s).%sr<   z5 The remainder will be missing from CLI/TUI sessions.r  )r%  r   r   r   r	   r  rQ   rA  r   r   r  _load_directory_moduler  r  r   import_moduler6   r   rE  r  r  rT   r   r   rG   )r  r   rQ  rx  r  r  tools_moduler  r  r   rh  declaredr  r  s                @r1   r  z/PluginManager._register_deferred_platform_tools  s   > & 	F\2X]
,4MCT(-(((t
j:&=%F%F%H%H NNIX,--	   F T,--E	00::F #FM4:D%j1$2fo3M3M3MNNL$\3CTJJN%5 011   N=488999   2  J '1F#2<D#J/LLMJ	      $	 $	 $	 NMMM$"9MMMG >*1'6='
3 8233H [TTTTW))LLLLZS\\ZZxZZZNNM'llh..G'          9$	s!   BF A'F 
IB-H??Ic           	     ^   |j         }|sdS |j        p|j        }g }|D ]}t          j        d|d          d                                         }|s4	 t          j                            |           U# t          j        j	        $ r |
                    |           Y t          $ r Y w xY w|rOt                              d|d                    |          d                    d	 |D                                  dS t                              d
|d                    |                     dS )u  Surface declared pip dependencies (#64165).

        python_dependencies is a declaration seam ONLY: Hermes validates and
        prints the requirements with an install hint but NEVER auto-installs
        them. The isolation design (constraints installs vs. vendored dirs
        vs. conflict-detection-and-refusal) is an explicitly deferred
        follow-up — see the round-2 review on #64165 and #15220.
        Nz[<>=!~\[;\s]r   )maxsplitr   zPlugin %s declares Python dependencies that are not installed: %s. Hermes does not install plugin dependencies automatically; install them yourself, e.g.: pip install %sr  r  c              3  "   K   | ]
}d | d V  dS )r  Nr:   )r   ms     r1   r   z:PluginManager._warn_python_dependencies.<locals>.<genexpr>f  s*      33aQ333333r3   z+Plugin %s python_dependencies satisfied: %s)r)  r   r   r  r   rE   r   r   r   PackageNotFoundErrorr   r   rQ   rA  r   rT   )r  r   rJ  r   rW  reqr   s          r1   rq  z'PluginManager._warn_python_dependenciesG  so    + 	Fl+hm 
	 
	C8OS1===a@FFHHD "**40000%: $ $ $s#####    	NNM TYYw''33733333     LL=TYYt__    s   A33)B*	B*)B*c                   |j         sdS |j        p|j        }i }	 ddlm}  |            pi }|                    d          pi                     d          pi }t          |t                    r|                    |          nd}t          |t                    r|                    d          nd}t          |t                    s,t          |t                    r|                    d          nd}t          |t                    r|ni }n# t          $ r i }Y nw xY wt          ||j         |          D ]}	t                              d||	           dS )	zCheck plugins.entries.<id> settings against config_schema (#64165).

        Mismatches log actionable warnings naming the key and expected type;
        they never block the plugin from loading.
        Nr   r   r*   rU  rY  r   zPlugin %s config: %s)r#  r   r   r   r   r   r   r   r   rb  rQ   rA  )
r  r   rX  rY  r   r  rU  rV  r  rA  s
             r1   rr  z,PluginManager._validate_plugin_config_scheman  s    % 	FL1HM	&(	555555+--%2Cwwy))/R44Y??E2G.8'.J.JTGKK	***PTE+5eW+E+EO%))J'''4Cc7++ R-7w-G-GQeii)))T(g66>ssBHH 	 	 	HHH	-x-x
 
 	G 	GG NN19gFFFF	G 	Gs   C0D DDr  c                4    |                     ||||          S r  r  r  s         r1   r  z(PluginManager._restore_deferred_platform  r  r3   c                    | j         5  t          | j                  5  |                     |           ddd           n# 1 swxY w Y   ddd           dS # 1 swxY w Y   dS )z?Import a plugin module and call its ``register(ctx)`` function.N)r  r   r  r  )r  r   s     r1   rn  zPluginManager._load_plugin  s    ! 	/ 	/#5dn#E#E 	/ 	/$$X...	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/s1   A?AA	AA	AAAc                D    t          |          }t                              d|j        p|j        |j        |j        |j                   |j        r 	                    ||           dS ddl
m t           j                  }|j        p|j                             |          t          j                    5                       j                  }                    t)          |                               d           j                  t          j        d j        f| fd	
          }                     |d|j                   ddd           n# 1 swxY w Y   	  j                            d          }||}n6|j        dv r                     |          }n                     |          }||_        t=          |dd          }|)d|_        t                               d|j                   n3t)          |           }	 ||	           fd j        |d         D              fd j!                            g           D             fdD             z   |_"        d D             |_#        d D             |_$        d D             |_%        d|_&        t                              dt          |j"                  t          |j#                  t          |j$                  t          |j%                  tO          fd j(        D                                  n# tR          $ r}
fd j        D             } *                    |            +                    |           tY          |
          |_         -                               t                               d|j        |
t\                     Y d}
~
nd}
~
ww xY w|j&        s j!                            d           | j/        |j        p|j        <   dS )z9Load one plugin with the manager's home bound as current.)r   z1Loading plugin '%s' (source=%s, kind=%s, path=%s)Nr   r  r  r<   tool_override_policyc                @                         | j                  S r  )restore_plugin_override_policyr  )r  _module_name	_registrycurrent_policyr  s    r1   r  z3PluginManager._load_plugin_scoped.<locals>.<lambda>  s+    I,T,T ".	 -U - - r3   )r  rv  rw  >   r  r  r  r   r  zno register() functionz&Plugin '%s' has no register() functionc                8    g | ]}|j         k    |j        |S r:   )r  r  r   r  r  s     r1   r   z5PluginManager._load_plugin_scoped.<locals>.<listcomp>  s9     ! ! !$#.*<<AT< !<<<r3   c                &    g | ]}|j         v |S r:   )r  )r   r>  r  s     r1   r   z5PluginManager._load_plugin_scoped.<locals>.<listcomp>  s1          D333 333r3   c                F    g | ]}|j         d k    r|j        v|j        S r?  r@  )r   r  _predeclareds     r1   r   z5PluginManager._load_plugin_scoped.<locals>.<listcomp>  sD     : : :$#(F22$(<< !$ =<<r3   c                2    g | ]}|j         d k    |j        S )r  r@  r%  s     r1   r   z5PluginManager._load_plugin_scoped.<locals>.<listcomp>  s3     + + +$#(F22 !$222r3   c                2    g | ]}|j         d k    |j        S )r  r@  r%  s     r1   r   z5PluginManager._load_plugin_scoped.<locals>.<listcomp>  s3     0 0 0$#(L88 !$888r3   c                2    g | ]}|j         d k    |j        S )r  r@  r%  s     r1   r   z5PluginManager._load_plugin_scoped.<locals>.<listcomp>  s3     . . .$#(I55 !$555r3   Tz[  registered: %d tool(s), %d hook(s), %d middleware, %d slash command(s), %d CLI command(s)c              3  V   K   | ]"t          fd D                       dV  #dS )c              3  h   K   | ],}|j         o |j        k    o|j        d k    o
|j        k    V  -dS )r  N)r  r  r   r   )r   r  rp  r  s     r1   r   z>PluginManager._load_plugin_scoped.<locals>.<genexpr>.<genexpr>	  sn        
 !-	 )/ 6 , 7: E6 , 1] B6 !- 0A 5	     r3   r   N)r   )r   rp  r  r   s    @r1   r   z4PluginManager._load_plugin_scoped.<locals>.<genexpr>  su       	 	     
 1>    		 	 	 	 	 	r3   c                *    g | ]}|j         k    |S r:   r;  r  s     r1   r   z5PluginManager._load_plugin_scoped.<locals>.<listcomp>  s1        *j88 888r3   zFailed to load plugin '%s': %sr  )0r  rQ   rT   r   r   r   r   r   r  _load_portable_pluginr  r  r   r  _policy_module_namer   r   snapshot_plugin_override_policyr  register_plugin_override_policyrE  r  r|  rr  r  r  r  r  _load_entrypoint_moduler  r   r  rA  r  r  r  r  r  r   rs  r  r   r-  r  r   _remove_plugin_subscriptionsrG   rP  )r  r   rQ  registration_startprevious_policypolicy_lease	preloadedr  register_fnctxr   r*  r  r  r  r  r  r   s   `           @@@@@@r1   r  z!PluginManager._load_plugin_scoped  s}   x000?L)HM8?HM8=	
 	
 	

  	&&x888F888888 !9::\2X]
//99$022 	 	'GGDN H  O 'FFh--DDRHHn G  N
 3:'F&(      	
 
 
L $$&$	  )	 	 	 	 	 	 	 	 	 	 	 	 	 	 	4^	 155j$GGI$"$BBB44, 5   55h??"FM "&*d;;K"7GWWWW#Hd33C   ! ! ! !(,(@ASATAT(U! ! !       #6:::rJJ      +7 : : : :(5: : : +'+ +(5+ + +'
0 0(50 0 0,
. .(5. . .*
 "&q/00/00455233 	 	 	 	 	#'#5	 	 	 	 	  $  	 	 	   $($<  E
 ''...&&u---s88FL --j999NN0s^         	2 ~ 	:#''
D9997=hl3hm444s-    BE**E.1E.6GM 
O%BO  O%c                ~   |j         p|j        }	 ddlm}  |t	          |j                  t                      dz  |j        z            }t          ||           }|j	        D ])}t                              d||j        |j                   *|j        D ]h}	 |                    |j        |j        |j        |j                   0# t&          $ r,}	t                              d||j        |	           Y d}	~	ad}	~	ww xY w|j                                        D ]N\  }
}|j         d|
 }|| j        v rt                              d||           7t/          |          | j        |<   Od	|_        nG# t&          $ r:}	t3          |	          |_        t                              d
||	           Y d}	~	nd}	~	ww xY w|| j        |<   dS )zALoad validated portable components without importing Python code.r   )load_agent_pluginr%  zAgent Plugin '%s' [%s]: %sz(Agent Plugin '%s' skill '%s' skipped: %sN__z*Agent Plugin '%s' MCP server collision: %sTz$Failed to load Agent Plugin '%s': %s)r   r   r  r  r	   r   r   r  rE  r  rQ   rA  r  r  skillsr  skill_mdr   r  r   mcp_serversrC  r  r   r   r   r  rP  )r  r   rQ  rx  r  packager  r  r  r   server_namer   internal_names                r1   r  z#PluginManager._load_portable_plugin0  sP   
 \2X]
+	TBBBBBB''X]##!!M1H4LL G  $//C%1  
0$&	    !  &&
))	    !   NNB"
	        (/':'@'@'B'B 	I 	I#V#+#; L L{ L L D$>>>NND"%  
 <@LL*=99!FNN 	T 	T 	Ts88FLNNA:sSSSSSSSS	T %+j!!!sC   BE, ,CE, 
C9"C4/E, 4C99A2E, ,
F060F++F0c                
   |j         p|j        }|                    dd                              dd          }t           d| }t          5  t
                              |          }|| j        t
          |<   |cddd           S || j        k    r|cddd           S t          j	        | j        
                    d                                                    dd         }| d	| cddd           S # 1 swxY w Y   dS )
z>Return a profile-safe import namespace for a directory plugin.r  r  r  r  r   Nr     __home_)r   r   r  
_NS_PARENT_MODULE_NAMESPACE_LOCKr   r   r  r  r	  r
  r  )r  r   r   r  r  r  r  s          r1   _directory_module_namez$PluginManager._directory_module_named  s   l+hm{{3%%--c377!**D**	# 	1 	1&**955E}04"9- 		1 	1 	1 	1 	1 	1 	1 	1
 && 	1 	1 	1 	1 	1 	1 	1 	1 ^DN$9$9'$B$BCCMMOOPSQSPSTF0000	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1s   -C8C8AC88C<?C<c                    |j         dk    rJ|j        rCt          |j                                      d          d                                         }|r|S |                     |          S )z?Return the module prefix whose callbacks inherit plugin policy.r   r   r   )r   r   r   	partitionrE   r  )r  r   r   s      r1   r  z!PluginManager._policy_module_names  sf    ?l**x}*hm,,66s;;A>DDFFK #""**8444r3   r  r   types.ModuleTypec                 	 t          |j                  }|dz  }|                                st          d|           t          t
          j        vr@t          j        t                    }g |_	        t          |_
        |t
          j        t          <   p|                     |           d		fdt
          j        D             D ]}t
          j        |= t          j                            |t          |          g          }||j        t#          d|           t          j                            |          }|_
        t          |          g|_	        |t
          j        <   	 |j                            |           n8# t(          $ r+ 	fdt
          j        D             D ]}t
          j        |=  w xY w|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   c                J    g | ]}|k    s|                               | S r:   r  r   nr   stale_prefixs     r1   r   z8PluginManager._load_directory_module.<locals>.<listcomp>  s5    ^^^1qK/?/?1<<P\C]C]/?Q/?/?/?r3   )r  NzCannot create module spec for c                J    g | ]}|k    s|                               | S r:   r  r  s     r1   r   z8PluginManager._load_directory_module.<locals>.<listcomp>  s5    bbbq13C3Cq||T`GaGa3C3C3C3Cr3   )r	   r   r  r2  r  rK   modulestypes
ModuleType__path____package__r  r   r  spec_from_file_locationr   loaderImportErrormodule_from_specexec_moduler[  )
r  r   r   r  r  ns_pkgr   rR  r  r  s
     `      @r1   r  z$PluginManager._load_directory_module{  s    (-((
.	!! 	G#$E$E$EFFF S[((%j11F FO!+F&,CK
#!JT%@%@%J%J &(((^^^^^^^^ 	" 	"DD!!~55(+J'8 6 
 

 <4;.JyJJKKK0066(z??+#)K 		K##F++++ 	 	 	
 cbbbbCKbbb & &K%%	 s   0F 5G 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   )r   r   s     r1   r   z9PluginManager._load_entrypoint_module.<locals>.<listcomp>  s%    LLLRX9K-K-K-K-K-Kr3   zEntry point 'z' not found in group 'r  )r   r   r~   r   r   r   r   r   r   r   r1  r  )r  r   r   r   r   s        r1   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
 
 	
r3   r  c                .   	 t          j        |           j        n# t          t          f$ r  | di |cY S w xY wt          d                                 D                       r | di |S fd|                                D             } | di |S )zCInvoke a hook while withholding additive fields from old callbacks.c              3  J   K   | ]}|j         t          j        j        k    V  d S r   )r   inspect	ParameterVAR_KEYWORD)r   	parameters     r1   r   z6PluginManager._invoke_hook_callback.<locals>.<genexpr>  sC       
 
 Ng/;;
 
 
 
 
 
r3   c                    i | ];\  }}|v r2|         j         t          j        j        t          j        j        hv 8||<S r:   )r   r!  r"  POSITIONAL_OR_KEYWORDKEYWORD_ONLY)r   r   r   
parameterss      r1   rm  z7PluginManager._invoke_hook_callback.<locals>.<dictcomp>  sf     	
 	
 	
ez!!4 %!7!.  %  r3   r:   )r!  	signaturer(  r?  r@  r   r	  rC  )r  r  accepted_payloadr(  s      @r1   _invoke_hook_callbackz#PluginManager._invoke_hook_callback  s    	' *844?JJ:& 	' 	' 	' 8&&g&&&&&	'
  
 
'..00
 
 
 
 
 	' 8&&g&&&	
 	
 	
 	
&}}	
 	
 	
 x++*+++s    99r  	List[Any]c                   |dk    r|                     dt                     | j                            |g           }g }|D ]}	 |                     ||          }||                    |           1# t          $ rC}t                              d|t          |dt          |                    |           Y d}~yd}~ww xY w|S )u  Call all registered callbacks for *hook_name*.

        Hook payloads evolve additively. Callbacks that accept ``**kwargs``
        receive the complete payload; older callbacks with a narrow signature
        receive only the keyword arguments they declare. 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.
        rs   telemetry_schema_versionNz Hook '%s' callback %s raised: %sr6   )r  r   r  r   r+  r   r   rQ   rA  r   r  )r  r  r   r  resultscbretr   s           r1   invoke_hookzPluginManager.invoke_hook  s    6 0008:QRRRKOOIr22	 	 	B
00V<<?NN3'''   6B
DHH55	        s   -A22
B?<9B::B?r  c                    t          |          st          d          t          ||          }| j        5  | j                            |g                               |           ddd           dS # 1 swxY w Y   dS )z=Add an owner-tagged event subscription in registration order.z*Event subscriber callback must be callable)r  r  N)r  r?  r  r  r  r  r   )r  r  r  r  rV  s        r1   r  zPluginManager._subscribe_event  s     !! 	JHIII"BBB 	D 	D**5"55<<UCCC	D 	D 	D 	D 	D 	D 	D 	D 	D 	D 	D 	D 	D 	D 	D 	D 	D 	Ds   /A33A7:A7c                   d}| j         5  t          | j                  D ]T}| j        |         }fd|D             }|t          |          t          |          z
  z  }|r|| j        |<   L| j        |= U	 ddd           n# 1 swxY w Y   |S )aR  Remove every subscription owned by *owner* and return the count.

        Queued dispatch envelopes re-check ledger membership before each
        callback, so removing an owner also cancels callbacks already snapshotted
        by an event that has not reached that subscriber yet.

        TODO(#64229): when the central plugin ownership ledger / registration
        handles land, route this owner-tagged bookkeeping through that ledger
        so per-plugin unload cancels event subscriptions alongside every other
        registration surface. This method is the integration seam.
        r   c                *    g | ]}|j         k    |S r:   )r  )r   rV  r  s     r1   r   z>PluginManager._remove_plugin_subscriptions.<locals>.<listcomp>:  s%    OOOe%+:N:NE:N:N:Nr3   N)r  r   r  r   )r  r  removedr  rU  retaineds    `    r1   r  z*PluginManager._remove_plugin_subscriptions*  s      	3 	3d122 3 3-e4OOOOwOOO3w<<#h--77 319D'..+E223	3 	3 	3 	3 	3 	3 	3 	3 	3 	3 	3 	3 	3 	3 	3 s   A*BB	Bc                   | j         5  | j        }| j        du}| xj        dz  c_        | j                                         t          j        t                    | _        d| _        | j	        
                    | j        d           	 	 |                                }n# t          j        $ r Y nWw xY w	 |t          ur|                     |j                   |                                 n# |                                 w xY w~|r|                    t                     | j                                         ddd           dS # 1 swxY w Y   dS )z@Cancel the current event generation and clear its subscriptions.Nr   r  r   )r  r  r  r  r  rF  r  r  r  r  r  
get_nowaitEmpty_EVENT_WORKER_STOP_mark_event_doner  	task_done
put_nowaitr  
notify_all)r  	old_queue
had_workerr   s       r1   _reset_event_buszPluginManager._reset_event_busB  s    	* 	*)I+47J""a'""%%''' %4F G G GD!%D-88&  	*$//11DD{   E*#555--do>>>''))))I''))))	*  9$$%7888'')))5	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	*sN   BE	
BE	B1.E	0B11E	5#C-E	-D9E		EEc                    | j         }||                                rd S | j        }t          j        | j        |fdd          }|| _         |                                 d S )Nzhermes-plugin-eventsT)r
  r   r   daemon)r  is_aliver  r  Thread_event_worker_loopr  )r  workerdispatch_queues      r1   _ensure_event_worker_lockedz)PluginManager._ensure_event_worker_locked`  sp    #&//"3"3F*!* "'	
 
 
 $r3   rI  queue.Queue[Any]c                   	 |                                 }	 |t          u r:	 |t          ur|                     |j                   |                                 d S |                     |           |t          ur|                     |j                   |                                 n<# |t          ur|                     |j                   |                                 w xY wr   )r   r;  r<  r  r=  _deliver_event)r  rI  r   s      r1   rG  z PluginManager._event_worker_loopn  s    		+!%%''D+--- 111))$/:::((*****	 ##D)))111))$/:::((**** 111))$/:::((****		+s   	B' B' '9C r  c                    | j         5  | j                            |d          }|dk    r|dz
  | j        |<   | j                                          d d d            d S # 1 swxY w Y   d S )Nr   r   )r  r  r   r?  )r  r  pendings      r1   r<  zPluginManager._mark_event_donez  s     	* 	*7;;JJJG{{@G!1*='')))		* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	*s   AAA!$A!r   r  c                H   | j         5  |j        | j        k    r	 ddd           dS 	 ddd           n# 1 swxY w Y   t          | j        dd          }|j        | j        _        	 |j        D ]| j         5  |j        | j        k    r	 ddd            nt          fd| j	        
                    |j        g           D                       s	 ddd           o	 ddd           n# 1 swxY w Y   j        }	 t          j        |j                  } |di |}t!          |           # t"          $ rI}t$                              d|j        t          |dt)          |                    |           Y d}~d}~ww xY w|| j        _        dS # || j        _        w xY w)z9Deliver one queued event on the host-owned worker thread.Nr   r   c              3      K   | ]}|u V  	d S r   r:   )r   r  subscriptions     r1   r   z/PluginManager._deliver_event.<locals>.<genexpr>  s<        #  </     r3   z#Event '%s' subscriber %s raised: %sr6   r:   )r  r  r  r   r  r  r   r  r   r  r   r  r  copydeepcopyr  resolve_plugin_command_resultr   rQ   rA  r  )r  r   previous_depthr  owned_payloadr  r   rR  s          @r1   rM  zPluginManager._deliver_event  s    	 	$"888	 	 	 	 	 	 	 	8	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 !!17A>>!%	4 $ 2  % 	! 	!$*@@@	! 	! 	! 	! 	! 	! 	! 	!
     '+':'>'>tz2'N'N     ! !	! 	! 	! 	! 	! 	! 	!
!	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! (0 %)M$,$?$?M%X6666F1&9999    NN=
*d8nnEE	        &4D"""^D"3333sx   599(F 9C+F :C+F F +C/	/F 2C/	3
F >0D/.F /
F9>E=7F =FF F!       @r  r2  c                      j         5   j         j                              fd|          cddd           S # 1 swxY w Y   dS )zCWait for the current event generation to become idle (test helper).c                 B    j                              d          dk    S )Nr   )r  r   )r  r  s   r1   r  z8PluginManager._wait_for_event_dispatch.<locals>.<lambda>  s     9==j!LLPQQ r3   r  N)r  r  wait_for)r  r  r  s   ` @r1   _wait_for_event_dispatchz&PluginManager._wait_for_event_dispatch  s     	 	/J#,,QQQQQ -  	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	s   '>AAc                X   t          | j        dd          }|t          k    r#t                              dt          |           dS | j        5  t          | j                            |g                     }|s	 ddd           dS | j	        }| j
                            |d          }|t          k    r/t                              dt          |           	 ddd           dS t          |t          |          ||dz   |          }	 | j                            |           nB# t           j        $ r0 t                              dt          |           Y ddd           dS w xY w|dz   | j
        |<   |                                  t'          |          cddd           S # 1 swxY w Y   dS )a7  Queue *event* without blocking; return subscriber count scheduled.

        A single daemon worker preserves registration order. Pending work is
        bounded per manager generation so a blocking subscriber can consume at
        most one worker while later emits are dropped once the budget is full.
        r   r   uo   Event bus recursion cap (%d) exceeded while dispatching '%s' — dropping this emit to prevent an infinite loopNuU   Event bus pending budget (%d) exhausted while dispatching '%s' — dropping this emitr   )r  r  r  r  r  )r   r  _EVENT_EMIT_DEPTH_CAPrQ   rA  r  r  r  r   r  r  r  r  r   r  r>  r  FullrJ  r   )r  r  r  r  r  r  rO  r   s           r1   r  zPluginManager._dispatch_event  ss    ('155)))NNE%u  
 1 !	& !	&!$"5"9"9%"D"DEEM  !	& !	& !	& !	& !	& !	& !	& !	& /J7;;JJJG,,,2&	   !	& !	& !	& !	& !	& !	& !	& !	& &W+ai%  D	!,,T2222:   2&	   =!	& !	& !	& !	& !	& !	& !	& !	&. =DaKD-j9,,...}%%C!	& !	& !	& !	& !	& !	& !	& !	& !	& !	& !	& !	& !	& !	& !	& !	& !	& !	&sC   ,FAF!$FD! F!0E FE  2FF#&F#c                P    t          | j                            |                    S )z@Return True when at least one callback is registered for a hook.)rB   r  r   r  r  s     r1   has_hookzPluginManager.has_hook  s    DKOOI..///r3   tuple[Callable, ...]c                R    t          | j                            |d                    S )<Return a stable snapshot of callbacks registered for a hook.r:   )r  r  r   rb  s     r1   iter_hook_callbacksz!PluginManager.iter_hook_callbacks  s     T[__Y33444r3   session_info'List[RenderedPluginSystemPromptSection]c           
        t          j        t          |                    }g }t          t                    t          t
                    z   dz   }t          | j                  D ]}| j        |         }t          |          t          k    r't          
                    d|j        t                     O	 t          |j                  r|                    |          n|j        }n># t          $ r1}t          
                    d|j        |j        |           Y d}~d}~ww xY wt!          |t"                    s;t          
                    d|j        |j        t%          |          j                   |                                }	|	s't          |	v s	t
          |	v r(t          
                    d|j        |j                   at          |	          |j        k    r<t          
                    d|j        |j        t          |	          |j                   t          t-          |j        |	                    }
|r|
dz  }
||
z   t.          k    r.t          
                    d|j        |j        t.                     |                    t3          |j        |	|j        |j        	                     ||
z  }t                              d
|j        |j        |j        t          |	                     |S )z?Render all registered sections deterministically and fail open.r.  zVPlugin system prompt section %s exceeded the section-count budget (%d) and was skippedz?Plugin system prompt section %s (%s) raised and was skipped: %sNzBPlugin system prompt section %s (%s) returned %s, not str; skippedz\Plugin system prompt section %s (%s) contained a reserved persistence marker and was skippedzQPlugin system prompt section %s (%s) exceeded max_chars (%d > %d) and was skippedzePlugin system prompt section %s (%s) exceeded the aggregate session budget (%d chars) and was skipped)r   r   r  r  zCSession plugin prompt section: id=%s plugin=%s position=%s chars=%d)r  MappingProxyTyper   r   r   r   rD  r  MAX_SYSTEM_PROMPT_SECTIONSrQ   rA  r   r  r   r   r  r   r   r;  r6   rE   r  r   &MAX_SYSTEM_PROMPT_SECTIONS_TOTAL_CHARSr   r  r  r+  )r  rh  frozen_inforenderedtotal_charsr   r  r   r   textrendered_charss              r1   render_system_prompt_sectionsz+PluginManager.render_system_prompt_sections  s    ,T,-?-?@@<>/0037J3K3KKaO !=>> P	 P	J2:>G8}} :::2J.	     00)GOOK000  
    UJN	    eS)) XJNKK(	   ;;==D $,,0Ct0K0K9JN	   4yy7,,,0JNII%    !=gj$!O!OPPN $!#^+.TTT@JN:   OO1z $-">	     >)KKKU
 D		    s   40C%%
D /'DD c                P    t          | j                            |                    S )zDReturn True when at least one callback is registered for middleware.)rB   r  r   )r  r   s     r1   has_middlewarezPluginManager.has_middlewareD  s!    D$((..///r3   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: %sr6   r:   )r  r   r   r   rQ   rA  r   r  )r  r   r   r  r/  r0  r1  r   s           r1   invoke_middlewarezPluginManager.invoke_middlewareH  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  r  s    r1   get_slack_action_handlersz'PluginManager.get_slack_action_handlersc  s     D/000r3   r  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.)r   r   r   r   r   r   r   toolsr  r  commandsr  )rD  rP  rC  r   r   r   r   r   r   r   r   r   r   r  r  r  r  r  )r  r  r   rQ  s       r1   list_pluginszPluginManager.list_pluginss  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$> ? ?#\      r3   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   )r  r  rV  s      r1   find_plugin_skillzPluginManager.find_plugin_skill  s+    #''77 %/uV}}4/r3   r  rZ  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  Nr  )r   qnrK  r1  s      r1   r   z3PluginManager.list_plugin_skills.<locals>.<genexpr>  sQ       
 
A}}V$$
kN
 
 
 
 
 
r3   rD  r  rC  )r  r  r1  s     @r1   list_plugin_skillsz PluginManager.list_plugin_skills  sX    """ 
 
 
 
,2244
 
 
 
 
 	
r3   c                b    d t          | j                                                  D             S )zDReturn progressive-disclosure metadata for registered plugin skills.c                    g | ]N\  }}|t          |                    d d                    dt          |                    di                     dOS )r   r<   r  r  )r   r   categoryr  )r   r   r   )r   r  rV  s      r1   r   z<PluginManager.list_plugin_skill_metadata.<locals>.<listcomp>  sm     
 
 
 !	5 ""599]B#?#?@@$#EIImR$@$@AA	 
 
 
r3   r  r  s    r1   list_plugin_skill_metadataz(PluginManager.list_plugin_skill_metadata  s<    
 
 %+4+>+D+D+F+F$G$G
 
 
 	
r3   Dict[str, Dict[str, Any]]c                H    d | j                                         D             S )z?Return a defensive copy of enabled portable MCP server configs.c                4    i | ]\  }}|t          |          S r:   )r   )r   r   r   s      r1   rm  z:PluginManager.get_portable_mcp_servers.<locals>.<dictcomp>  s4     
 
 
f $v,,
 
 
r3   )r  rC  r  s    r1   get_portable_mcp_serversz&PluginManager.get_portable_mcp_servers  s2    
 
 $ : @ @ B B
 
 
 	
r3   c                *    t          | j                  S r   )rB   r  r  s    r1   has_portable_mcp_serversz&PluginManager.has_portable_mcp_servers  s    D.///r3   c                <    | j                             |d           dS )z>Remove a stale registry entry (silently ignores missing keys).N)r  r  )r  r  s     r1   remove_plugin_skillz!PluginManager.remove_plugin_skill  s!    55555r3   r   )r  rf  r'   rC   )
r   r   r   r   r   r   r  r  r'   r  )r	  r   r
  r
   r'   rB   )r  r  r   r   r  r   r'   rC   )
r  r8  r   r   r  r
   rv  r  r'   rB   )r  r   r  r
   rv  r
   r'   rB   )r   r   r'   rC   )r   r!  r'   rC   )r  r.  r'   r   r  r2  r'   rB   r  )r  r  rO  rP  r'   rC   )r  r  r'   rC   )r   r
   r'   rB   FrA   rB   r'   rC   r  )r   r   r  r   rX  r   r'   rC   )r   r   )r'   r  r  r  r'   rB   )r   r	   r   r   r  r  r'   r  )r   r	   r   r   r  r  r1  r   r  r0  r'   r  )
r  r	   r  r	   r   r   r1  r   r'   r  r  )r   r   r'   r   )r   r   r'   rC   )r   r   rQ  r  r'   rC   r  )r   r   r   rf  r'   r
  )r   r   r'   r
  )r  r   r  r8  r'   r
   r  r   r   r
   r'   r,  )r  r   r  r   r  r   r'   rC   )r  r   r'   r0  )rI  rK  r'   rC   )r  r0  r'   rC   )r   r  r'   rC   )rX  )r  r2  r'   rB   )r  r   r  r8  r'   r0  r  r   r'   rB   r  r   r'   rd  rh  r  r'   ri  r   r   r'   rB   r   r   r   r
   r'   r,  r'   rx  r'   r  )r  r   r'   r  )r  r   r'   rZ  )r'   r  )r  r   r'   rC   )Jr6   r7   r8   r9   r#  rr  rC  r  r  r  r(  r  r  r  r-  r1  r5  r4  r  r  rR  rT  r  r\  rZ  rY  rX  r  r  rl  r  r  r  r  r  rm  r  r   ro  r  rq  rr  r  rn  r  r  r  r  r  r  r+  r2  r  r  rB  rJ  rG  r<  rM  r]  r  rc  rg  rs  ru  rw  rz  r~  r  r  r  r  r  r  r:   r3   r1   r  r  A  s,       EEI; I; I; I; I;^   ,    \# # # #    
 
 
 
2 2 2 26 6 6 6= = = =.   "    \ BF/ / / / / BFv v v v vx : : : X:; ; ; ;2 2 2 2- - - -( ( ( ( (TQ Q Q Q5S 5S 5S 5Sn\ \ \ \| U  U  U  UD   1 1 1 1f6 6 6 6@ *.	
 
 
 
 
2T T T TlT T T Tt&  &  &  & P/ / / /"   & UA UA UA UAnx x x xt% % % %NG G G G8

 

 

 

/ / / /
S> S> S> S>j2+ 2+ 2+ 2+h1 1 1 15 5 5 5 &*	A A A A A AF
 
 
 
, , , , \,6+ + + +ZD D D D   0* * * *<   
+ 
+ 
+ 
+* * * *#4 #4 #4 #4J    1& 1& 1& 1&f0 0 0 05 5 5 5X X X Xt0 0 0 0   6
1 
1 
1 
1    40 0 0 0

 
 
 

 
 
 

 
 
 
0 0 0 06 6 6 6 6 6r3   r  Optional[PluginManager]_plugin_managerzDict[Path, PluginManager]_plugin_managers_by_homec                     	 t                                                                                      S # t          $ r# t                                                      cY S w xY w)a   Return the profile/home key for process-global plugin state.

    Plugins are discovered from ``get_hermes_home() / "plugins"`` and some
    plugins (notably context engines such as hermes-lcm) capture that home
    at registration time for profile-scoped storage. A long-lived process
    can temporarily switch Hermes home (env var *or* the context-local
    ``set_hermes_home_override()``) while serving another profile, so the
    plugin manager must be scoped to the active Hermes home instead of
    being one process-wide singleton.
    )r   
expanduserr.   r   r:   r3   r1   _plugin_home_keyr    sd    .  ++--55777 . . .  ++-----.s   14 *A! A!rF  c                   | dS t          | di                                           D ]}t          |dd          }t          |dd          r                    t           d          sD dfdt          j        D             D ]}t          j        |= t          5  t                                        | j	        k    rt          
                    d           ddd           n# 1 swxY w Y   dS )a  Purge ``sys.modules`` entries for directory-loaded plugins.

    ``PluginManager._load_directory_module`` imports each plugin as
    ``hermes_plugins.<slug>`` and registers that top-level module in
    ``sys.modules``. Anything the plugin's ``__init__.py`` imports with a
    *relative* import (``from . import foo``, ``from .sub import bar``)
    ends up cached in ``sys.modules`` too, under
    ``hermes_plugins.<slug>.<submodule>``. When we swap in a fresh manager
    for a new home, replacing only the parent module leaves those
    submodules behind: if a same-named plugin in the new profile does a
    relative import, Python resolves it from ``sys.modules`` first and
    silently reuses the *previous* profile's already-imported submodule
    (and any module-level state it captured), instead of re-executing the
    new profile's code. We must evict the package itself and every module
    whose name is prefixed with ``"<module_name>."`` before (or when)
    discarding a manager, not just drop our reference to it.
    NrP  r  r6   r   c                J    g | ]}|k    s|                               | S r:   r  )r   r  r   r1  s     r1   r   z,_clear_plugin_submodules.<locals>.<listcomp>  s5    XXX1qK/?/?1<<PVCWCW/?Q/?/?/?r3   )r   r	  r  r  rK   r  r  r   r   r  r  )rF  rQ  r  r   r   r1  s       @@r1   _clear_plugin_submodulesr    s]   $ ':r2299;; 
: 
:400fj$77 	+"8"8J9I9I9I"J"J 	"""XXXXXXXX 	" 	"DD!!# 	: 	:!%%k22g6GGG"&&{D999	: 	: 	: 	: 	: 	: 	: 	: 	: 	: 	: 	: 	: 	: 	:
: 
:s   #?C..C2	5C2	c                    t                      } t          5  t          Bt          t                                          vr"t          t          | <   t          cddd           S t                              |           }|'t          t          |                     }|t          | <   |a|cddd           S # 1 swxY w Y   dS )u  Return the plugin manager for the active Hermes profile/home.

    Managers are cached per resolved home so repeated calls within the
    same profile reuse discovery state (normal performance), while a
    profile switch — via ``HERMES_HOME`` or the context-local
    ``set_hermes_home_override()`` — gets its own manager with its own
    plugin submodules, instead of silently inheriting another profile's
    context engine or stale relative-import state.
    N)r  )r  _plugin_managers_lockr  r  r	  r   r  r   )current_homerF  s     r1   get_plugin_managerr    s    $%%L	   ''?'F'F'H'HHH5D$\2"        +..|<<?#ol.K.KLLLG5<$\2!%                 s   =B3 AB33B7:B7c                    t           5  t          t                              t                                                              } t          #t          | vr|                     t                     | D ]R}t          |           	 |	                                 '# t          $ r t                              dd           Y Ow xY wt                                           daddd           dS # 1 swxY w Y   dS )u  Test-only helper: drop every cached manager and its submodules.

    Not used by production code paths — tests that want a fully clean
    slate (rather than adopting/injecting a specific manager) can call
    this instead of reaching into the module's private dict directly.
    Nz!test plugin-manager unload failedTr  )r  r   r   fromkeysr  r	  r  r   r  r5  r   rQ   rT   rF  )managersrF  s     r1    _reset_plugin_managers_for_testsr  $  sA    
  &>&E&E&G&GHHII&?(+J+JOOO,,, 	Q 	QG$W---Q     Q Q Q@4PPPPPQ &&(((                 s6   A<C.BC.&C C.CC..C25C2r  r  c                D    t                                          |           S )zReturn whether config enables a portable package with MCP servers.

    A fresh manager performs manifest-only scanning, so this startup gate does
    not mutate the process-wide plugin registry or import native plugin code.
    )r  r  )r  s    r1   has_enabled_agent_plugin_mcpr  :  s     ??33J???r3   c                f    t                       t                                          |            dS )aF  Discover and load all plugins.

    Default behavior is idempotent. Pass ``force=True`` to rescan plugin
    manifests and reload state in the current process.

    If a background discovery started via
    :func:`start_background_plugin_discovery` is still running, this waits
    for it instead of racing a second scan.
    rA   N)_join_background_discoveryr  r\  r  s    r1   discover_pluginsr  C  s3        ***77777r3   zOptional[threading.Thread]_background_discovery_threadc                 >   t                      j        rdS t          5  t          't                                          r	 ddd           dS dfd} t          j        | dd          at                                           ddd           dS # 1 swxY w Y   dS )	uE  Run plugin discovery in a daemon thread (startup-latency overlap).

    Discovery costs ~150ms of manifest scanning + module imports on the CLI
    startup path. Interactive chat doesn't need plugins until the first
    agent turn, so callers on that path can start discovery here and let it
    overlap the CPU/subprocess-heavy rest of startup. Every synchronous
    consumer goes through :func:`discover_plugins`, which joins this thread
    first — so no caller can observe a half-loaded registry. Idempotent;
    no-op when discovery already ran or is already in flight.
    Nr'   rC   c                     	                                    t                       d S # t          $ r  t                              dd           Y d S w xY w)Nz"background plugin discovery failedTr  )r\  _persist_plugin_toolset_keysr   rQ   rA  rF  s   r1   _runz/start_background_plugin_discovery.<locals>._runh  sl    T))+++,..... T T TCdSSSSSSTs   "' &AAzplugin-discoveryTr
  r   rD  r  )r  r  _background_discovery_lockr  rE  r  rF  r  )r  rF  s    @r1   !start_background_plugin_discoveryr  U  s'    !""G 	# - -'38T8]8]8_8_3- - - - - - - -	T 	T 	T 	T 	T 	T (1'70(
 (
 (
$ 	%**,,,- - - - - - - - - - - - - - - - - -s   "B6BBBr  r  r2  c                    t           }|)|                                r|t          j                    u rdS |                    |            dS )zGWait for an in-flight background discovery (no-op from its own thread).Nr[  )r  rE  r  current_threadr   )r  r>  s     r1   r  r  u  sJ    $Ay

yY-E-G-G(G(GFF7Fr3   c                 .    ddl m}   |             dz  dz  S )Nr   r   cachezplugin_toolset_keys.json)hermes_constantsr   r  s    r1   _plugin_toolset_keys_cache_pathr  }  s-    000000?w&)CCCr3   c                    	 ddl } ddl}ddl}t          d t	                      D                       }	 t          t                                                                }n# t          $ r g }Y nw xY wt                      }|j	        
                    dd           |                    t          |j	                  d          \  }}|                    |dd	
          5 }|                     ||d|           ddd           n# 1 swxY w Y   |                    ||           dS # t          $ r  t                               dd           Y dS w xY w)zJPersist discovered plugin toolset keys + portable MCP names (best-effort).r   Nc                    h | ]\  }}}|	S r:   r:   r   ts_keyr  s      r1   r  z/_persist_plugin_toolset_keys.<locals>.<setcomp>  s    HHH,&!QvHHHr3   Tr  z	.pt_keys.)dirr1  wr  r/  )toolset_keysportable_mcpz!plugin toolset key persist failedr  )r0  r+   tempfilerD  get_plugin_toolsetsr  r  r   r  r/   r  mkstempr   fdopendumpr  rQ   rT   )	_json_os	_tempfilerE  r  r   fdtmpfhs	            r1   r  r    s   I$$$$HH2E2G2GHHHII	022KKMMNNHH 	 	 	HHH	.00$666##DK(8(8#MMCZZC'Z22 	MbJJhGGLLL	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	MC I I I84HHHHHHIsY   1D+ -A" !D+ "A1.D+ 0A11A1D+ "D<D+ DD+ DD+ +&EEr  c                     	 dd l } |                     t                                          d                    }t	          |t
                    r|S n# t          $ r Y nw xY wd S )Nr   r  r/  )r0  r  r  r  r   r   r   )r  blobs     r1   _read_plugin_keys_cacher    s    {{+--777II
 
 dD!! 	K	   4s   AA 
A A 
'set[str]'c                    t                      } t          }| j        r.||                                sd t	                      D             S |v|                                rbt                      }|R|                    d          }t          |t                    r(t          d |D                       rt          |          S t                       d t	                      D             S )u@  Plugin toolset keys without blocking on in-flight discovery.

    When discovery already completed in this process, reads the live
    registry. While a background discovery is still running, falls back to
    the key set persisted by the previous run — callers on the startup path
    (platform toolset resolution) only use these keys to EXCLUDE plugin
    toolsets from composite expansion, so a stale set from the last launch
    is harmless and self-heals as soon as discovery lands. When neither is
    available, blocks via discover_plugins() (correctness first).
    Nc                    h | ]\  }}}|	S r:   r:   r  s      r1   r  z1get_plugin_toolset_keys_nowait.<locals>.<setcomp>  s    AAA<61aAAAr3   r  c              3  @   K   | ]}t          |t                    V  d S r   r  rk  s     r1   r   z1get_plugin_toolset_keys_nowait.<locals>.<genexpr>  s,      -O-OQjC.@.@-O-O-O-O-O-Or3   c                    h | ]\  }}}|	S r:   r:   r  s      r1   r  z1get_plugin_toolset_keys_nowait.<locals>.<setcomp>  s    ===|vq!F===r3   )r  r  r  rE  r  r  r   r   r   r  r   r  )rF  r>  r  rE  s       r1   get_plugin_toolset_keys_nowaitr    s     !""G$A B		AA+>+@+@AAAA}}&((88N++D$%% !#-O-O$-O-O-O*O*O !4yy ==':'<'<====r3   c                    t                      } t          }| j        r7||                                s!t	          |                                           S |v|                                rbt                      }|R|                    d          }t          |t                    r(t          d |D                       rt	          |          S t                       t	          |                                           S )a  Portable MCP server names without blocking on in-flight discovery.

    Same contract as :func:`get_plugin_toolset_keys_nowait`: live registry
    when discovery finished, last launch's persisted set while a background
    discovery is running, blocking discovery otherwise.
    Nr  c              3  @   K   | ]}t          |t                    V  d S r   r  )r   r  s     r1   r   z7get_portable_mcp_server_names_nowait.<locals>.<genexpr>  s,      .Q.Qaz!S/A/A.Q.Q.Q.Q.Q.Qr3   )r  r  r  rE  r   r  r  r   r   r   r  r  )rF  r>  r  namess       r1   $get_portable_mcp_server_names_nowaitr    s     !""G$A 7		73355666}}&((HH^,,E%&& "3.Q.Q5.Q.Q.Q+Q+Q "5zz!w//11222r3   r  r2  c                `    t                       t                                          |           S )zUnload one plugin or all plugins from the process-global manager.

    Wait for background discovery first so teardown cannot race an in-flight
    registration sweep introduced by the warm-start discovery path.
    )r  r  r5  r0  s    r1   unload_pluginsr    s*        &&v...r3   c                     t                      } t          | dd          s"t                       |                                  | S )u  Return the active manager, lazily running discovery if it never ran.

    Hook/middleware delivery must not depend on WHICH surface imported us:
    dashboards, TUI slash workers, query mode, and cron delivery paths never
    import ``model_tools`` (whose import side-effect is the discovery trigger
    on the interactive CLI path), so hooks registered by user plugins were
    silently dead on those surfaces (#50776, #67597, #67890, #50937;
    tracking #64178 — salvaged from PR #64188).

    ``getattr`` with a ``True`` default so test doubles that monkeypatch
    ``get_plugin_manager()`` with a bare namespace are invoked untouched.
    r  T)r  r   r  r\  r  s    r1   _delivery_managerr    sF     !""G7M400 $"$$$!!###Nr3   r  r   r,  c                6     t                      j        | fi |S )a  Invoke a lifecycle hook on loaded plugins.

    Ensures plugins are discovered on first invocation so callers in
    processes that never explicitly call ``discover_plugins()`` (gateway
    platform events, TUI slash workers, query mode, cron) still fire
    callbacks registered by user plugins (tracking #64178).

    Returns a list of non-``None`` return values from plugin callbacks.
    )r  r2  )r  r   s     r1   r2  r2    s&     +*9?????r3   rh  ri  c                D    t                                          |           S )z@Render plugin prompt sections after idempotent plugin discovery.)_ensure_plugins_discoveredrs  )rh  s    r1   rs  rs    s     &''EElSSSr3   r   c                6     t                      j        | fi |S )u   Invoke registered middleware callbacks.

    Lazy-discovers plugins on first use — same delivery-parity guarantee as
    :func:`invoke_hook` (tracking #64178).

    Returns a list of non-``None`` return values from middleware callbacks.
    )r  rw  )r   r   s     r1   rw  rw    s&     10@@@@@r3   c                .   t                      }t          |dd          st                      }t          |dd          }t          |          rt	           ||                     S t	          t          |di                               |                     S )a  Return True when middleware callbacks are registered for ``kind``.

    Lazy-discovers first: callers use this as a gate before
    :func:`invoke_middleware`, so a pre-discovery ``False`` here would
    silently skip delivery on surfaces that never ran discovery (#64178).
    r  Tru  Nr  )r  r   r  r  rB   r   )r   rF  r   s      r1   ru  ru    s     !""G7M400 &#%%W.55F "FF4LL!!!3377==>>>r3   c                D    t                                          |           S )u   Return True when a loaded plugin handles a hook.

    Lazy-discovers first — same gate-before-invoke rationale as
    :func:`has_middleware` (tracking #64178).
    )r  rc  r  s    r1   rc  rc  !  s     ''	222r3   rd  c                D    t                                          |           S )rf  )r  rg  r  s    r1   rg  rg  *  s    33I>>>r3   )r  r   surfacer  
alias_usedargs_rawr  r   c           	     n   	 t                      }|                    d          sdS |                    d| |||||          }|D ]<}t          |t                    r%d|v sd|v rt
                              d|||            =dS # t          $ r&}	t
                              d|	           Y d}	~	dS d}	~	ww xY w)af  Fire the ``pre_command`` observer hook (#64204). Never raises.

    Observer-only in v1: return values are ignored. If a plugin returns a
    directive-shaped dict (``action``/``decision`` keys), a debug line is
    logged so future block/rewrite adopters are discoverable when the
    middleware variant ships against the #64231 command-event taxonomy.
    rX   N)r  r  r  r  r  r   actiondecisionzpre_command is observer-only in v1: ignoring directive %r for /%s (surface=%s). Block/rewrite will arrive with the command middleware variant (#64204/#64231).z0pre_command hook dispatch failed (non-fatal): %s)r  rc  r2  r   r   rQ   rT   r   )
r  r  r  r  r  r   rF  r/  r  r   s
             r1   fire_pre_command_hookr  /  s    N$&&.. 	F%%!# & 
 
  		 		F&$'' F""jF&:&:F GW	  			 		  N N NGMMMMMMMMMNs   #B AB 
B4B//B4c                  H    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<   dS )_PreToolCallDirectiveNrf  r  r  rule_keyr  modified_args)r6   r7   r8   r  r  r  r  r  r:   r3   r1   r  r  ]  sV          F    !G!!!!"H"""".2M222222r3   r  >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     r1   set_thread_tool_whitelistr  e  s     &-"!-r3   c                     d t           _        d S r   )r  r  r:   r3   r1   clear_thread_tool_whitelistr  m  s    %)"""r3   r  r   r  task_id
session_idtool_call_idturn_idapi_request_idmiddleware_traceOptional[List[Dict[str, Any]]]c                   t          t          dd          }|?| |vr;t          t          dd          }	t          d|	                    |                     S dd	lm}
  |
d
| t          |t                    r|ni |||||t          |pg           	  	        }d}d}|D ]N}t          |t                    s|	                    d          dk    rj|	                    d          }t          |t                    r?|r=|&t          |t                    rt          |          ni }|
                    |           |	                    d          }|dvr|	                    d          }t          |t                    r|r|nd}|dk    r|s|dk    r|	                    d          nd}t          |t                    r|                                nd}|sd}t          ||||          c S t          |          S )u@  Check ``pre_tool_call`` hooks for a blocking or approval directive.

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

        {"action": "block",   "message": "Reason the tool was blocked"}
        {"action": "approve", "message": "Why this needs human confirmation"}
        {"action": "approve", "message": "...", "rule_key": "write_file:ssh"}

    from their ``pre_tool_call`` callback.

    - ``block`` vetoes the tool call outright (the message becomes the tool
      result the model sees).
    - ``approve`` ESCALATES to the existing human-approval gate
      (``prompt_dangerous_approval`` on CLI, the approval callback on the
      gateway) — the same mechanism Tier-2 dangerous shell patterns use.
      This lets a plugin require a human ``[o]nce/[s]ession/[a]lways/[d]eny``
      decision on ANY tool, not just terminal command strings. The caller is
      responsible for invoking the gate (see
      :func:`tools.approval.request_tool_approval`).
    - ``rule_key`` is optional and only honored for ``approve`` directives. It
      lets plugins choose the allowlist grain for `[a]lways` approvals.

    The first valid 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block)r  )r  r  r   )r2  r\   )r  r   r  r  r	  r
  r  r  r  modifyr   )r  approver  r  r  )r  r  r  r  )r  )r   r  r  rp  hermes_cli.lifecycler2  r   r   r   r   rC  r   rE   )r  r   r  r  r	  r
  r  r  r  r  invoke_lifecycle_hookhook_results	block_msgr  r  rh  r  r  r  s                      r1   $_get_pre_tool_call_directive_detailsr  q  sl   H ,i>>Gy77,e5PQQ$JJJ33
 
 
 	

 JIIIII((d++3TT!%.4"55
 
 
L  $I.2M 
 
&$'' 	 ::h8++jj((G'4(( .W . (2<T42H2H$PDJJJbM$$W---H%%---**Y'''55K'K''t WW-3y-@-@6::j)))d'1(C'@'@J8>>###d 	H$7X'
 
 
 	
 	
 	

 !}====r3   #tuple[Optional[str], Optional[str]]c           
     L    t          | |||||||          }|j        |j        fS )aD  Check ``pre_tool_call`` hooks for a blocking or approval directive.

    Backward-compatible public helper: returns ``(directive, message)`` where
    ``directive`` is ``"block"``, ``"approve"``, or ``None``. Internal callers
    that need approve-specific metadata use
    :func:`_get_pre_tool_call_directive_details`.
    r  r  r	  r
  r  r  )r  r  r  	r  r   r  r  r	  r
  r  r  detailss	            r1   get_pre_tool_call_directiver    s=    " 34Z!7%8H  G
 NGO,,r3   c           
     J    t          | |||||||          \  }}	|dk    r|	ndS )aZ  Back-compat shim: return only a ``block`` message (or ``None``).

    Deprecated in favor of :func:`get_pre_tool_call_directive`, which also
    surfaces the ``approve`` escalation directive. Kept so any external caller
    importing the old name keeps working; ``approve`` directives are invisible
    to this shim (it only reports blocks).
    r  r  N)r  )
r  r   r  r  r	  r
  r  r  	directiver  s
             r1   get_pre_tool_call_block_messager    sG    " 54Z!7%8H  Iw
  7**774r3   c           
     X    t          | |||||||          }t          || |||          S )a  Resolve the pre_tool_call directive to a final block message (or None).

    Single entry point for every tool-dispatch site: fetches the plugin
    directive and, for an ``approve`` escalation, invokes the human-approval
    gate (:func:`tools.approval.request_tool_approval`). Returns the message
    the tool result should carry when the call is blocked, or ``None`` when
    the call may proceed.

    Centralizing this keeps the security-critical fail-closed logic in ONE
    place instead of copy-pasted across the concurrent/sequential/helper
    dispatch paths: an ``approve`` directive whose gate errors, denies, or
    times out is fail-closed to a block; ``block`` blocks with its message;
    anything else proceeds.
    r  r
  r	  r  )r  _resolve_block_from_detailsr  s	            r1   resolve_pre_tool_blockr#    sQ    0 34Z!7%8H  G
 'lz   r3   r!  r  '_PreToolCallDirective'c                   | j         dk    r| j        S | j         dk    r	 ddlm}m}m} d}	  ||||          }n# t          $ r Y nw xY w	  ||| j        pd| j        p|          }	|	  ||           n5# t          $ r Y n)w xY wn$# |	  ||           w # t          $ r Y w w xY ww xY wn# t          $ r d	| cY S w xY w|	                    d
          s't          |	                    d          pd|           S dS )a  Resolve a fetched directive to a final block message (or ``None``).

    Shared by :func:`resolve_pre_tool_block` and
    :func:`_dispatch_pre_tool_call_hooks` so the security-critical
    fail-closed approval logic lives in exactly ONE place: ``block``
    blocks with its message; an ``approve`` directive whose gate errors,
    denies, or times out is fail-closed to a block; anything else
    proceeds.
    r  r  r   )request_tool_approval#reset_current_observability_context!set_current_observability_contextNr!  r<   )r  z)BLOCKED: plugin approval gate failed for approvedr  z&BLOCKED: plugin approval required for )
r  r  tools.approvalr&  r'  r(  r   r  r   r   )
r  r  r
  r	  r  r&  r'  r(  approval_tokensr  s
             r1   r"  r"  '  s   " ~  ~""	K          #O"C"C#!-)# # #
    ..O)r$-:   #.;;OLLLL$    /?.;;OLLLL$    /
  	K 	K 	K KyJJJJJ	K zz*%% 	

9%% HGIGG   4s   B- ; B- 
AB- AB- B (B- +A7 6B- 7
BB- BB- B)BB)
B%"B)$B%%B))B- -B?>B?.Tuple[Optional[str], Optional[Dict[str, Any]]]c           
     j    t          | |||||||          }t          || |||          }	|	|j        fS )u  Invoke ``pre_tool_call`` hooks once and process all response types.

    Returns a ``(block_message, modified_args)`` tuple:
    - ``block_message`` — the first block/approve directive's resolved message
      (or ``None`` when the call may proceed).  Shares the exact fail-closed
      approval-gate logic of :func:`resolve_pre_tool_block` via
      :func:`_resolve_block_from_details`, including the observability
      context set around the human-approval gate.
    - ``modified_args`` — merged args from ``modify`` directives
      (or ``None`` when no hook requested modification).

    This is the single invocation point for ``pre_tool_call`` hooks.
    Callers that only need block detection should keep using
    :func:`get_pre_tool_call_block_message` or
    :func:`resolve_pre_tool_block` for backward compat.
    Callers that also need input transformation should call this
    function and apply ``modified_args`` if not ``None``.
    r  r!  )r  r"  r  )
r  r   r  r  r	  r
  r  r  r  r  s
             r1   _dispatch_pre_tool_call_hooksr.  c  s^    8 34Z!7%8H  G
 ,lz  I w,--r3   r  r   r  codingattemptfinal_responsechanged_pathsr  r0  r1  r2  r3  Optional[List[str]]c                   t          d| |||||t          |pg                     }|D ]}t          |t                    st	          |                    d          p|                    d          pd                                                                          }	|	dvrz|                    d          p|                    d          }
t          |
t                    r*|
                                r|
                                c S d	S )
u9  Check user ``pre_verify`` hooks for a directive to keep the agent going.

    Fired once per turn when the agent edited code and is about to verify/finish.
    A hook keeps the turn going (run a check, defer it, tidy the diff) by
    returning::

        {"action": "continue", "message": "<follow-up for the model>"}

    The Claude-Code Stop shape ``{"decision": "block", "reason": "..."}`` (block
    the stop == keep going) is accepted too. The first directive carrying a
    non-empty message wins; any other return lets the turn finish. Mirrors
    :func:`get_pre_tool_call_block_message` — the call site stays a one-liner.

    ``coding`` / ``attempt`` let a hook scope itself (``if not coding`` …) and
    self-throttle (``if attempt`` …), the same way a ``pre_tool_call`` hook
    scopes on ``tool_name``.
    rW   r/  r  r  r<   )continuer  r  reasonN)r2  r   r   r   r   r   rE   rF   )r  r   r  r0  r1  r2  r3  r  r  r  r  s              r1   get_pre_verify_continue_messager8    s   6 %=.B//	 	 	L  # #&$'' 	VZZ))IVZZ
-C-CIrJJPPRRXXZZ...**Y''?6::h+?+?gs## 	# 	#==??"""4r3   r2  r  status_code
error_type
error_codeerror_message
error_bodyr  approx_tokenscontext_lengthnum_messagesr2  r:  r  r;  r<  r=  r>  r  Optional[BaseException]r?  r@  rA  c                j   ddl m} t          d| |||||t          |t                    r|ni |||	|
          }d}d}|D ]S}t          |t                    s|                    d          }t          ||          r|}nWt          |t                    rA	  ||                                                                          }n# t          $ r Y w xY w||dz  }d|i}dD ]}||v rt          ||                   ||<   |                    d	          }t          |t                    r3|                                r|                                dd
         |d	<   |                    d          }t          |t                    r||d<   |}U||rt                              d|           |S )uA  Check ``transform_api_error_classification`` hooks for a directive.

    Consulted by :func:`agent.error_classifier.classify_api_error` BEFORE
    its built-in pipeline, so a provider plugin can both add classifications
    the core patterns miss and correct ones they get wrong for its provider.

    A callback returns ``None`` to decline, or a dict with a required
    ``"reason"`` (a :class:`agent.error_classifier.FailoverReason` member or
    its string name) plus optional recovery-hint overrides. Dispatch is
    run-all-then-pick-first: ``invoke_hook`` runs every registered callback
    with failures isolated, then the first result carrying a valid reason
    wins in registration order — mirroring
    :func:`get_pre_tool_call_block_message`, invalid or irrelevant returns
    are silently ignored so a misbehaving plugin degrades to a no-op.
    When more than one callback returns a valid classification, the losing
    results are skipped with a runtime warning (the #64714
    skipped-transform rule) so conflicting provider plugins are visible in
    logs instead of silently shadowed.

    Privacy: ``error_message`` and ``error_body`` may carry an unredacted
    provider error dump; callbacks must not log or forward them without
    redaction.

    Cold path: fires only on API failure, never on the request hot path.
    Contract: the transform-family first-valid-wins shape in
    ``docs/plugins/hook-taxonomy.md``.

    Returns a sanitized dict (``reason`` coerced to ``FailoverReason``, hint
    fields coerced to ``bool``) or ``None`` when no plugin claimed the error.
    r   )FailoverReasonr{   r9  Nr7  r   )	retryableshould_compressshould_rotate_credentialshould_fallbackr  i  error_contextztransform_api_error_classification: skipped %d valid classification(s) after the first result in registration order won (run-all-then-pick-first))agent.error_classifierrD  r2  r   r   r   r   rE   rF   r@  rB   rQ   rA  )r2  r  r:  r;  r<  r=  r>  r  r?  r@  rA  rD  r  winnerskipped_validr  
reason_rawr7  rF  r   r  rI  s                         r1   get_plugin_error_classificationrN    s"   X 655555,#!+J!=!=E::2#%!  L (,FM ! !&$'' 	ZZ))
j.11 	FF
C(( 	'
(8(8(:(:(@(@(B(BCC    QM'0
 	- 	-C f}}s,,C**Y''gs## 	3 	3$]]__TcT2C	N

?33mT** 	1#0C m, 		
 	
 	
 Ms   /C
CC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\  )rA   rF  s     r1   r  r  *  s+    
 !""GE***Nr3   c                 (    t                      j        S )z5Return the plugin-registered context engine, or None.)r  r&  r:   r3   r1   get_plugin_context_enginerQ  4  s    %''77r3   Optional[Callable]c                f    t                      j                            |           }|r|d         ndS )zFReturn the handler for a plugin-registered slash command, or ``None``.rU   N)r  r  r   )r   rV  s     r1   get_plugin_command_handlerrT  9  s3    &((9==dCCE$.5$.r3   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'   rC   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   )r  runr[  r   )r   ry  failureoutcomer  s    r1   _runnerz.resolve_plugin_command_result.<locals>._runnerX  sv    	&{622GG 	! 	! 	! GENNNNNN	! HHJJJJJDHHJJJJs%    A 
4/A 4A A#zhermes-plugin-command-awaitTr  r[  z5Plugin command async handler did not complete within z.0fsr   r   r  )r!  isawaitabler  r  r4  rW  r  EventrF  r  wait"_PLUGIN_COMMAND_AWAIT_TIMEOUT_SECSTimeoutErrorr   )r  rZ  threadry  rX  rY  s   `  @@@r1   rU  rU  B  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:   r3   r1   get_plugin_commandsrd  p  s     &''88r3   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   rl  rF  s     r1   r   z.get_plugin_auxiliary_tasks.<locals>.<listcomp>  s!    FFFaGq!FFFr3   )r  rD  r  r  s   @r1   get_plugin_auxiliary_tasksrg  y  s5     )**GFFFF6'2D+E+EFFFFr3   r  c                     t                      } | j        5  d | j                                        D             cddd           S # 1 swxY w Y   dS )a_  Return the inter-plugin event bus subscription registry.

    Returns a snapshot mapping each fully-qualified event name
    (``<plugin_key>:<event>`` or ``hermes:<event>``) to subscriber callbacks in
    registration order. Owner ledger metadata stays private to the manager.
    Triggers idempotent plugin discovery before reading the snapshot.
    c                .    i | ]\  }}|d  |D             S )c                    g | ]	}|j         
S r:   )r  )r   rV  s     r1   r   z7get_plugin_subscriptions.<locals>.<dictcomp>.<listcomp>  s    888uEN888r3   r:   )r   r  rU  s      r1   rm  z,get_plugin_subscriptions.<locals>.<dictcomp>  s=     
 
 
w 88888
 
 
r3   N)r  r  r  rC  r  s    r1   get_plugin_subscriptionsrk    s     )**G		 
 

 
")"8">">"@"@
 
 

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
s   #AA
A
rx  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  r  )r  r  r  r  r   r  r  r  r   r   rP  rC  r  rD  r   r  titler   r   r   )rF  r  toolset_toolstoolset_pluginr  rV  tsr  rQ  r  r  r  r  descs                 r1   r  r    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   )r'   r   )r   r   r'   r   )r   r
   r'   rB   )r   r   r   r   r'   r   )r   r   r'   r   )r   r	   )r   r   r'   rB   )r'   r   )r'   r   )r   r   r'   r   )r   r  r'   r   )r7  r   r   r   r'   r8  )rX  r   rP  r   rY  r   r'   rZ  )r   rc  r'   rZ  )r  r   r'   rf  )r  )r  rf  r  r0  r'   r   )r   r   r'   rf  )r   r   r  r0  r'   r   )r   r   r'   r  )r  r  r  r  r   r
   r'   r
   )r  r  r   r
   r'   r  )rX  r   r  r   r'   r   )r   r	   r'   r  )r   r	   )rF  r  r'   rC   )r'   r  r  r  )r  )r  r2  r'   rC   )r'   r  )r'   r  r   r  r  r  r  r  r  r  )r  r   r  r   r  r   r  r   r  rf  r   rf  r'   rC   )r  )r  r  r   r   r'   rC   )r<   r<   r<   r<   r<   N)r  r   r   r  r  r   r  r   r	  r   r
  r   r  r   r  r  r'   r  )r  r   r   r  r  r   r  r   r	  r   r
  r   r  r   r  r  r'   r  )r  r   r   r  r  r   r  r   r	  r   r
  r   r  r   r  r  r'   rf  )r  r$  r  r   r
  r   r	  r   r  r   r'   rf  )r  r   r   r  r  r   r  r   r	  r   r
  r   r  r   r  r  r'   r,  )r  r   r   r   r  r   r0  rB   r1  r0  r2  r   r3  r4  r'   rf  )r2  r   r  r   r:  r  r;  r   r<  r   r=  r   r>  r  r  rB  r?  r0  r@  r0  rA  r0  r'   r  )rA   rB   r'   r  )r   r   r'   rR  )r  r
   r'   r
   )r'   rb  r  )r'   r  r  )r9   
__future__r   r  rS  r  importlib.metadatar   importlib.utilr!  r0  rI   r+   r  r  rK   r  r  
contextlibr   dataclassesr   r   	functoolsr   pathlibr	   typingr
   r   r   r   r   r   r   r   r   r   r   r  r   r   r   r   registration_lifecycler   r=  r   r   r   r   r   hermes_cli.middlewarer   r   hermes_cli.plugin_capabilitiesr    r!   r"   r#   r   hermes_cli.relay_plugin_cutoverr$   r%   r&   r2   r  r  r`  r5   	getLoggerr6   rQ   r,   rE   rF   rG   rH   rV   r|   r  r}   r   r   r   r   r   	frozensetr  r  r  rl  rm  compiler   r   r   r   r   r   r   r  r_  r  r  r;  r  r  r  r   r   r   r   r   r   r   r  r  r-  rB  r   r0  r2  rB   r   r   r6  rW  rb  r  r   r  r  r   r   r  r  r  r  r  r  r  r  r,  r)  r  Lockr   r  r  r  r  r  r  r   rE  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r2  rs  rw  ru  rc  rg  r  r  r  r  r  r  r  r  r  r#  r"  r.  r8  rN  r  rQ  rT  r_  rU  rd  rg  rk  r  r:   r3   r1   <module>r     s    B # " " " " "               				  				 



      % % % % % % ( ( ( ( ( ( ( (             d d d d d d d d d d d d d d d d d d d d d d d d d d            ; : : : : : 1 1 1 1 1 1 1 1 ; ; ; ; ; ; ; ; K K K K K K K K         
              
> 
> 
> 
>KKKK   DDD    o    
	8	$	$" 1266<<>>DDFF K  !     6     b b b b b b bV )%      , !C < < < <A A A AH   ( #,)^,<"="= */ '"'  ). &)rz*IJJ (= %? ; Z Z Z Z
   [ [ [ [ "     VXX 
(** %'  ' ' ' '   * * * *! ! ! !
       D !e d d  d d d d	+ 	+ 	+ 	+/ / / / $ $ $        6f6v5\ElGwGWGg* *      @ @ @ @F% % % %PA A A AH   &    ,7 7 7 7tO O O O O K5 K5 K5 K5 K5 K5 K5 K5\ $        $        $        $               "  '  '  '  '  '  '  '  'N (RZ(LMM !*+W+W+W!X!X !rz"HII , 24  4 4 4 4*IN,,    6      0 0 0 0 F F F F @ @ @ @DF; F; F; F; F; F; F; F;RI I I I I I I I`>t!6 t!6 t!6 t!6 t!6 t!6 t!6 t!6~C ,0 / / / / 79  8 8 8 8'	)) . . . .": : : :B   D   ,@ @ @ @8 8 8 8 8 <@  ? ? ? ?+Y^-- - - - -@    D D D
I I I I*
 
 
 
> > > >23 3 3 3, >B	/ 	/ 	/ 	/ 	/   (
@ 
@ 
@ 
@T T T TA A A A? ? ? ? 3 3 3 3? ? ? ? "&"(N (N (N (N (N (NV )**  $3 3 3 3 3 3 3 3 Y. . . . .* * * * 7;^> ^> ^> ^> ^>H 7;- - - - -8 7;5 5 5 5 58 7;         N 9 9 9 9 9 9~ 7;%. %. %. %. %.T )-0 0 0 0 0 0j !%+/%)i i i i i iX    8 8 8
/ / / / &* "+  +  +  + \9 9 9 9G G G G
 
 
 
 * * * * * *s    C CC