feat(plugins): add inject_message interface for remote message injection (#3778)

This commit is contained in:
Wing Lian
2026-03-30 05:48:06 -04:00
committed by GitHub
parent 5148682b43
commit efae525dc5
2 changed files with 34 additions and 0 deletions

5
cli.py
View File

@@ -6210,6 +6210,11 @@ class HermesCLI:
self._interrupt_queue = queue.Queue() # For messages typed while agent is running self._interrupt_queue = queue.Queue() # For messages typed while agent is running
self._should_exit = False self._should_exit = False
self._last_ctrl_c_time = 0 # Track double Ctrl+C for force exit self._last_ctrl_c_time = 0 # Track double Ctrl+C for force exit
# Give plugin manager a CLI reference so plugins can inject messages
from hermes_cli.plugins import get_plugin_manager
get_plugin_manager()._cli_ref = self
# Config file watcher — detect mcp_servers changes and auto-reload # Config file watcher — detect mcp_servers changes and auto-reload
from hermes_cli.config import get_config_path as _get_config_path from hermes_cli.config import get_config_path as _get_config_path
_cfg_path = _get_config_path() _cfg_path = _get_config_path()

View File

@@ -152,6 +152,34 @@ class PluginContext:
self._manager._plugin_tool_names.add(name) self._manager._plugin_tool_names.add(name)
logger.debug("Plugin %s registered tool: %s", self.manifest.name, name) logger.debug("Plugin %s registered tool: %s", self.manifest.name, name)
# -- message injection --------------------------------------------------
def inject_message(self, content: str, role: str = "user") -> bool:
"""Inject a message into the active conversation.
If the agent is idle (waiting for user input), this starts a new turn.
If the agent is running, this interrupts and injects the message.
This enables plugins (e.g. remote control viewers, messaging bridges)
to send messages into the conversation from external sources.
Returns True if the message was queued successfully.
"""
cli = self._manager._cli_ref
if cli is None:
logger.warning("inject_message: no CLI reference (not available in gateway mode)")
return False
msg = content if role == "user" else f"[{role}] {content}"
if getattr(cli, "_agent_running", False):
# Agent is mid-turn — interrupt with the message
cli._interrupt_queue.put(msg)
else:
# Agent is idle — queue as next input
cli._pending_input.put(msg)
return True
# -- hook registration -------------------------------------------------- # -- hook registration --------------------------------------------------
def register_hook(self, hook_name: str, callback: Callable) -> None: def register_hook(self, hook_name: str, callback: Callable) -> None:
@@ -184,6 +212,7 @@ class PluginManager:
self._hooks: Dict[str, List[Callable]] = {} self._hooks: Dict[str, List[Callable]] = {}
self._plugin_tool_names: Set[str] = set() self._plugin_tool_names: Set[str] = set()
self._discovered: bool = False self._discovered: bool = False
self._cli_ref = None # Set by CLI after plugin discovery
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Public # Public