mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-11-15 18:35:29 +03:00
eef9a3e52c
Under plugins.isolation: host, discover_and_load(force=True) (reload-plugins, and every plugin install, enable or update) loads a plugin again into the same host process under the same plugin_key. A task the first load spawned kept running, and its emit_plugin_session_event was taken for the new load and published to the session's viewers. - Hermes numbers each load of a plugin in the host (load), sends it with the load request, and forgets it at unload. - The host binds that load, with the plugin, to each request it serves, so it follows the request's code onto the tasks it spawns; session_event carries it. - The gateway process publishes only when that load is the plugin's current one; otherwise it logs and returns False, as the hosted call did before this PR forwarded it. A category plugin's fresh instance keeps its key's load. - ctx.emit_to_session reads the manager's home_path directly: every manager sets it, as the other readers of the plugin's own profile assume. Suggested by @BearHuddleston in review. - The late-event test now covers a same-host reload and waits with tests.waiting.wait_until and Events instead of sleep loops. ctx.emit_to_session from such a task is unchanged (it reached viewers on main too). Refs #142005, #141982
44 lines
2.3 KiB
Python
44 lines
2.3 KiB
Python
"""Plugin JSON-RPC methods: a plugin's frontend calling its own Python half over the gateway socket.
|
|
|
|
Bound into ``PluginContext`` (``from hermes_cli.plugin_rpc import ...``), so they are ordinary
|
|
``ctx.*`` methods and ``register_rpc`` gets the abandoned-load guard of every registrar. The session
|
|
authority serves the methods (``gateway/session_plugin_rpc.py``); unload drops them with the ledger.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, Callable, Optional
|
|
|
|
from hermes_cli.plugin_events import PLUGIN_EVENT_PREFIX, emit_plugin_session_event, plugin_event_name
|
|
from hermes_cli.plugins_ledger import PluginRegistration
|
|
|
|
__all__ = ["emit_to_session", "plugin_rpc_handler", "register_rpc"]
|
|
|
|
|
|
def register_rpc(self, name: str, handler: Callable[..., Any]) -> PluginRegistration:
|
|
"""Serve ``handler(params, *, session_id, **kwargs)`` as ``plugin.<plugin_id>.<name>``: the namespace
|
|
is FORCED (``name`` is the bare dotted name). The return value, JSON-encodable, is the result."""
|
|
if not callable(handler):
|
|
raise TypeError(f"Plugin '{self.plugin_id}' register_rpc() handler must be callable")
|
|
method = plugin_event_name(self.plugin_id, name, kind="RPC method")
|
|
return self._register_entry("rpc_method", method, self._manager._plugin_rpc_methods, handler,
|
|
"Plugin %s registered RPC method: %s", method)
|
|
|
|
|
|
def emit_to_session(self, session_id: str, event: str, payload: Optional[dict[str, Any]] = None) -> bool:
|
|
"""Push ``plugin.<plugin_id>.<event>`` to the viewers of ``session_id`` only.
|
|
|
|
Looked up in the plugin's own profile (its manager's home), never the ambient scope: a thread the
|
|
plugin started carries none, and on a multiplexed gateway that would read as the launch profile."""
|
|
from hermes_cli.plugins_loader import _plugin_home_scope
|
|
with _plugin_home_scope(self._manager.home_path):
|
|
return emit_plugin_session_event(self.plugin_id, session_id, event, payload)
|
|
|
|
|
|
def plugin_rpc_handler(method: Any) -> Optional[Callable[..., Any]]:
|
|
"""The loaded plugin handler serving wire ``method`` for the active profile, else None."""
|
|
if not isinstance(method, str) or not method.startswith(PLUGIN_EVENT_PREFIX):
|
|
return None
|
|
from hermes_cli.plugins import _ensure_plugins_discovered
|
|
return _ensure_plugins_discovered()._plugin_rpc_methods.get(method)
|