From e016546db6754daa52ab4e46500b958bbaa83095 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Wed, 2 Sep 2026 15:48:29 -0700 Subject: [PATCH] refactor(cli/plugins): unify PluginContext type-gate warnings (_wrong_type, 5 sites) and refusal errors (_refuse, 4 sites); drop in-class section banners --- hermes_cli/plugins.py | 133 ++++++++---------------------------------- 1 file changed, 24 insertions(+), 109 deletions(-) diff --git a/hermes_cli/plugins.py b/hermes_cli/plugins.py index ffa331ec4d..c36c6f2f6d 100644 --- a/hermes_cli/plugins.py +++ b/hermes_cli/plugins.py @@ -330,8 +330,6 @@ class PluginContext: for key, loaded in self._manager._plugins.items() ) - # -- namespaced config and durable state -------------------------------- - def get_config(self, key: str, default: Any = None) -> Any: """Read plugin-relative ``plugins.entries..settings.`` (falls back to the legacy ``config`` subtree for migration compatibility).""" @@ -410,6 +408,20 @@ class PluginContext: self._platform_actions = PlatformActions(self.plugin_id) return self._platform_actions + def _wrong_type(self, obj: Any, base_class: type, label: str, article: str = "a") -> bool: + """Warn-and-ignore gate shared by every registrar that requires a base class.""" + if isinstance(obj, base_class): + return False + logger.warning( + "Plugin '%s' tried to register %s %s that does not inherit from %s. Ignoring.", + self.manifest.name, article, label, base_class.__name__, + ) + return True + + def _refuse(self, what: str) -> ValueError: + """``ValueError`` for a malformed registration (``what`` completes "tried to register ...").""" + return ValueError(f"Plugin '{self.manifest.name}' tried to register {what}.") + def _track( self, kind: str, @@ -470,11 +482,7 @@ class PluginContext: slot so unload restores the displaced entry. Returns ``None`` when the registry refused or replaced the provider (``ValueError`` with ``reject_message`` set, or a falsy ``register``). """ - if not isinstance(provider, base_class): - logger.warning( - "Plugin '%s' tried to register %s %s that does not inherit from %s. Ignoring.", - self.manifest.name, article, label, base_class.__name__, - ) + if self._wrong_type(provider, base_class, label, article): return None registry_name = provider.name if normalize is None else normalize(provider.name) scope = self._manager.scope_key @@ -497,8 +505,6 @@ class PluginContext: logger.info("Plugin '%s' registered %s: %s", self.manifest.name, label, registry_name) return handle - # -- host-owned LLM access ---------------------------------------------- - @property def llm(self) -> Any: """Host-owned :class:`agent.plugin_llm.PluginLlm` facade: completions on the user's active @@ -521,8 +527,6 @@ class PluginContext: self._subagent_lifecycle = SubagentLifecycleService(get_active_subagent_parent) return self._subagent_lifecycle - # -- profile awareness -------------------------------------------------- - @property def profile_name(self) -> str: """Active profile name: ``"default"``, the ``~/.hermes/profiles/`` id, or ``"custom"``. @@ -536,8 +540,6 @@ class PluginContext: except Exception: return "default" - # -- lifecycle: unload callbacks and supervised tasks -------------------- - def on_unload(self, callback: Callable[[], None]) -> PluginRegistration: """Register a cleanup callback for unload: runs in reverse acquisition order interleaved with registration teardown; exceptions are logged, never propagated.""" @@ -564,8 +566,6 @@ class PluginContext: logger.debug("Plugin %s spawned supervised task: %s", self.manifest.name, task_name) return task - # -- approval transport registration ------------------------------------ - def register_approval_transport(self, name: str, present_fn: Callable) -> None: """Register a human approval transport, inactive until ``security.approval.transport: `` selects it. It receives a redacted ``ApprovalRequest`` and returns only a @@ -584,8 +584,6 @@ class PluginContext: "approval_transport", clean, self._manager._approval_transports, entry, None ) - # -- tool registration -------------------------------------------------- - @_serialized_replacement def register_tool( self, @@ -659,8 +657,6 @@ class PluginContext: ) return handle - # -- capability probing (#64228) ----------------------------------------- - def has_capability(self, capability: str) -> bool: """Return True when *capability* is live for this plugin (probe, then degrade gracefully). @@ -672,8 +668,6 @@ class PluginContext: return True return plugin_capability_granted(self.plugin_id, capability) - # -- capability-gated MCP access ---------------------------------------- - def call_mcp( self, server: str, @@ -758,8 +752,6 @@ class PluginContext: return [] return [str(item) for item in allowlist] - # -- override trust gate ------------------------------------------------ - def _tool_override_allowed(self, tool_name: str) -> bool: """Return True if this plugin may override built-in tools. @@ -781,8 +773,6 @@ class PluginContext: # active profile's consent state instead. return plugin_capability_granted(self.plugin_id, "tools.override", config=cfg) - # -- message injection -------------------------------------------------- - def inject_message( self, content: str, @@ -858,8 +848,6 @@ class PluginContext: is True ) - # -- CLI command registration -------------------------------------------- - @_serialized_replacement def register_cli_command( self, @@ -887,8 +875,6 @@ class PluginContext: logger.debug("Plugin %s registered CLI command: %s", self.manifest.name, name) return handle - # -- slash command registration ------------------------------------------- - @_serialized_replacement def register_command( self, @@ -944,8 +930,6 @@ class PluginContext: logger.debug("Plugin %s registered command: /%s", self.manifest.name, clean) return handle - # -- tool dispatch ------------------------------------------------------- - def dispatch_tool(self, tool_name: str, args: dict, **kwargs) -> str: """Dispatch a tool call through the registry with the parent agent (when available) resolved automatically; returns the handler's JSON string. ``kwargs`` forward to dispatch.""" @@ -960,8 +944,6 @@ class PluginContext: return registry.dispatch(tool_name, args, scope=self._manager.scope_key, **kwargs) - # -- context engine registration ----------------------------------------- - @_serialized_replacement def register_context_engine(self, engine) -> Optional[PluginRegistration]: """Register the (single) ``agent.context_engine.ContextEngine`` replacing the built-in @@ -974,12 +956,7 @@ class PluginContext: ) return from agent.context_engine import ContextEngine - if not isinstance(engine, ContextEngine): - logger.warning( - "Plugin '%s' tried to register a context engine that does not " - "inherit from ContextEngine. Ignoring.", - self.manifest.name, - ) + if self._wrong_type(engine, ContextEngine, "context engine"): return previous = self._manager._context_engine self._manager._context_engine = engine @@ -994,8 +971,6 @@ class PluginContext: logger.info("Plugin '%s' registered context engine: %s", self.manifest.name, engine.name) return handle - # -- context reference registration ------------------------------------- - def register_context_reference(self, provider) -> None: """Register a :class:`agent.context_references.ContextReferenceProvider`; ``provider.prefix`` defines ``@:``. Built-in prefixes (diff, staged, file, folder, git, url) are @@ -1005,12 +980,7 @@ class PluginContext: register_context_reference_provider as _register, ) - if not isinstance(provider, _CRP): - logger.warning( - "Plugin '%s' tried to register a context reference provider " - "that does not inherit from ContextReferenceProvider. Ignoring.", - self.manifest.name, - ) + if self._wrong_type(provider, _CRP, "context reference provider"): return try: _register(provider) @@ -1025,20 +995,13 @@ class PluginContext: self.manifest.name, provider.prefix, ) - # -- memory provider registration --------------------------------------- - def register_memory_provider(self, provider) -> None: """Record a memory provider (inert). Activation is owned by ``plugins/memory`` via ``memory.provider``; a provider reaching here was loaded by the general manager, and without this method its ``register()`` would fail on a missing attribute.""" from agent.memory_provider import MemoryProvider - if not isinstance(provider, MemoryProvider): - logger.warning( - "Plugin '%s' tried to register a memory provider that does not " - "inherit from MemoryProvider. Ignoring.", - self.manifest.name, - ) + if self._wrong_type(provider, MemoryProvider, "memory provider"): return self._memory_provider = provider logger.debug( @@ -1046,8 +1009,6 @@ class PluginContext: self.manifest.name, getattr(provider, "name", "?"), ) - # -- dashboard auth provider registration -------------------------------- - @_serialized_replacement def register_dashboard_auth_provider(self, provider) -> Optional[PluginRegistration]: """Register a :class:`hermes_cli.dashboard_auth.DashboardAuthProvider` for the dashboard @@ -1059,12 +1020,7 @@ class PluginContext: unregister_global_provider, ) - if not isinstance(provider, DashboardAuthProvider): - logger.warning( - "Plugin '%s' tried to register a dashboard-auth provider " - "that does not inherit from DashboardAuthProvider. Ignoring.", - self.manifest.name, - ) + if self._wrong_type(provider, DashboardAuthProvider, "dashboard-auth provider"): return registry_name = provider.name # The auth registry is process-global (lifetime = web server). Disposing it on a routine @@ -1091,8 +1047,6 @@ class PluginContext: ) return handle - # -- platform adapter registration --------------------------------------- - @_serialized_replacement def register_platform( self, @@ -1142,8 +1096,6 @@ class PluginContext: logger.debug("Plugin %s registered platform: %s", self.manifest.name, name) return handle - # -- slack action handler registration ---------------------------------- - def register_slack_action_handler( self, action_id: Any, @@ -1156,15 +1108,9 @@ class PluginContext: for a non-callable callback or empty ``action_id``. """ if not callable(callback): - raise ValueError( - f"Plugin '{self.manifest.name}' tried to register a Slack " - f"action handler with a non-callable callback." - ) + raise self._refuse("a Slack action handler with a non-callable callback") if action_id is None or (isinstance(action_id, str) and not action_id.strip()): - raise ValueError( - f"Plugin '{self.manifest.name}' tried to register a Slack " - f"action handler with an empty action_id." - ) + raise self._refuse("a Slack action handler with an empty action_id") entry = (action_id, callback, self.manifest.name) self._manager._slack_action_handlers.append(entry) handle = self._track( @@ -1177,8 +1123,6 @@ class PluginContext: logger.debug("Plugin %s registered Slack action handler: %s", self.manifest.name, action_id) return handle - # -- platform handler registration ---------------------------------------- - def register_platform_handler(self, platform: str, factory: Callable) -> None: """Register a native-client handler factory for a gateway platform, invoked at ``connect()`` as ``factory(native, adapter)`` before/as the core handlers register (``adapter`` read-only). @@ -1191,16 +1135,10 @@ class PluginContext: or empty platform. """ if not callable(factory): - raise ValueError( - f"Plugin '{self.manifest.name}' tried to register a platform " - f"handler factory with a non-callable factory." - ) + raise self._refuse("a platform handler factory with a non-callable factory") key = (platform or "").strip().lower() if not key: - raise ValueError( - f"Plugin '{self.manifest.name}' tried to register a platform " - f"handler factory with an empty platform name." - ) + raise self._refuse("a platform handler factory with an empty platform name") self._manager._platform_handler_factories.setdefault(key, []).append( (factory, self.manifest.name) ) @@ -1210,8 +1148,6 @@ class PluginContext: getattr(factory, "__name__", repr(factory)), ) - # -- telegram handler registration --------------------------------------- - def register_telegram_handler(self, factory: Callable) -> None: """Alias of ``register_platform_handler("telegram", factory)``: ``factory(application, adapter)`` runs before the core handlers. PTB dispatches only the FIRST matching handler per @@ -1219,8 +1155,6 @@ class PluginContext: ``pattern=`` or you swallow the core button flows. Raises ``ValueError`` if not callable.""" self.register_platform_handler("telegram", factory) - # -- auxiliary task registration --------------------------------------- - @_serialized_replacement def register_auxiliary_task( self, @@ -1239,8 +1173,7 @@ class PluginContext: """ if not key or not isinstance(key, str): raise ValueError( - f"Plugin '{self.manifest.name}' tried to register auxiliary task " - f"with invalid key {key!r}" + f"Plugin '{self.manifest.name}' tried to register auxiliary task with invalid key {key!r}" ) if not all(c.isalnum() or c == "_" for c in key): raise ValueError( @@ -1295,8 +1228,6 @@ class PluginContext: ) return handle - # -- redaction pattern registration -------------------------------------- - def register_redaction_patterns(self, patterns) -> int: """Additively register secret-token regexes with :mod:`agent.redact`; returns the count accepted. @@ -1391,8 +1322,6 @@ class PluginContext: logger.debug("Plugin %s registered system prompt section: %s", self.manifest.name, id) return handle - # -- inter-plugin event bus -------------------------------------------- - def emit(self, event: str, payload: Optional[dict] = None) -> int: """Publish bare *event* as ``:`` (namespace FORCED to this plugin); return the subscriber count scheduled. @@ -1435,8 +1364,6 @@ class PluginContext: self._manager._subscribe_event(plugin_key, event, callback) logger.debug("Plugin %s subscribed to event: %s", self.manifest.name, event) - # -- middleware registration ------------------------------------------- - def register_middleware(self, kind: str, callback: Callable) -> PluginRegistration: """Register behavior-changing middleware (request kinds rewrite the payload, execution kinds wrap the callback). Unknown kinds warn but are stored.""" @@ -1444,8 +1371,6 @@ class PluginContext: "middleware", kind, callback, self._manager._middleware, VALID_MIDDLEWARE ) - # -- skill registration ------------------------------------------------- - @_serialized_replacement def register_skill( self, @@ -1681,9 +1606,6 @@ class PluginManager(PluginLoaderMixin, PluginDispatchMixin, PluginLedgerMixin): # Native platform handler factories keyed by lowercase platform name. self._platform_handler_factories: Dict[str, List[tuple]] = {} - # -- registration ledger internals ---------------------------------------- - - # -- public ---------------------------------------------------------------- @property def has_gateway_message_injector(self) -> bool: @@ -1952,8 +1874,6 @@ class PluginManager(PluginLoaderMixin, PluginDispatchMixin, PluginLedgerMixin): continue # fail closed on an unreadable package; full discovery reports it return False - # -- directory scanning ----------------------------------------------------- - def _scan_directory( self, path: Path, @@ -1967,11 +1887,6 @@ class PluginManager(PluginLoaderMixin, PluginDispatchMixin, PluginLedgerMixin): """Read installed plugin entry points (see :func:`discover_entrypoint_manifests`).""" return discover_entrypoint_manifests() - # -- loading --------------------------------------------------------------- - - # -- hook invocation ---------------------------------------------------------- - - # -- adapter accessors / introspection ----------------------------------------- def get_slack_action_handlers(self) -> List[tuple]: """``(action_id, callback, plugin_name)`` tuples for the Slack adapter to wire at connect."""