""" hermes-relay plugin — registers android_* + desktop_* tools and the `hermes pair` + `hermes relay` CLI sub-commands into hermes-agent via the v0.3.0+ plugin system. Drop this folder into ~/.hermes/plugins/hermes-relay and add `hermes-relay` to `plugins.enabled` in ~/.hermes/config.yaml, then restart hermes. Run `hermes pair` to generate a QR code for pairing clients. """ import logging from .tools.android_tool import _SCHEMAS, _HANDLERS, _check_requirements from .tools.desktop_tool import ( _SCHEMAS as _DESKTOP_SCHEMAS, _HANDLERS as _DESKTOP_HANDLERS, _check_tool as _desktop_check_tool, ) from .tools.relay_plugin_tool import ( _SCHEMAS as _RELAY_PLUGIN_SCHEMAS, _HANDLERS as _RELAY_PLUGIN_HANDLERS, ) logger = logging.getLogger(__name__) def register(ctx): """Called by hermes-agent plugin loader. Registers tools and CLI commands.""" # Register 14 android_* tools for tool_name, schema in _SCHEMAS.items(): ctx.register_tool( name=tool_name, toolset="android", schema=schema, handler=_HANDLERS[tool_name], check_fn=(lambda: True) if tool_name == "android_setup" else _check_requirements, ) # Register 5 desktop_* tools (Phase B client-side tool routing). # Per-tool check_fn closure so the relay ping is scoped to THIS tool, # not the whole toolset — matches the pattern in desktop_tool.py module # level registration (which is a no-op fallback for non-plugin-context # imports like smoke tests). for tool_name, schema in _DESKTOP_SCHEMAS.items(): def _make_desktop_check(name: str): return lambda: _desktop_check_tool(name) ctx.register_tool( name=tool_name, toolset="desktop", schema=schema, handler=_DESKTOP_HANDLERS[tool_name], check_fn=_make_desktop_check(tool_name), ) # Declarative Android pages live on the Hermes host and need no paired # device or relay process. They remain available for authoring/inspection # whenever this plugin is loaded. for tool_name, schema in _RELAY_PLUGIN_SCHEMAS.items(): ctx.register_tool( name=tool_name, toolset="relay", schema=schema, handler=_RELAY_PLUGIN_HANDLERS[tool_name], check_fn=lambda: True, ) # Register the in-session `/relay` slash command (status/devices/pair) and # the `on_session_start` lifecycle hook. Both are self-guarded internally, # and wrapped here too so a missing register_command/register_hook on an # older hermes-agent build cannot block tool/CLI registration below. try: from .slash import register_slash_commands register_slash_commands(ctx) except Exception: # Slash commands are additive; never let them break plugin load. pass try: from .hooks import register_hooks register_hooks(ctx) except Exception: # The lifecycle hook is best-effort; never block plugin load. pass # Register the "phone" platform so the agent can proactively push to the # paired device (`send_message target=phone`, cron `deliver=phone`). # Additive + off-by-default (PHONE_ENABLED gate): guarded so an older # hermes-agent without register_platform — or a host where gateway.* is # unavailable at import — can't block tool/CLI registration above. try: from .phone_platform import register_phone_platform register_phone_platform(ctx) except (AttributeError, ImportError): # Older hermes-agent (no register_platform) — platform not registered. # Tools/CLI above still work. pass except Exception: # Don't silently swallow — a real registration failure (e.g. an # upstream PlatformEntry signature drift) should be visible, not lost # at DEBUG. The platform is still additive; tool/CLI registration above # is unaffected. logger.warning("Phone platform registration failed; continuing", exc_info=True) # Prefer the modern profile-keyed ownership ledger. Older Hermes hosts # retain the guarded process wrapper as a compatibility fallback. try: from .enhancements.context_injection import register_context_sections native_context_registered = register_context_sections(ctx) except Exception: # A current host exposing the API must not fall back to an unowned # process-global mutation after a real registration failure. native_context_registered = True logger.warning( "Relay system-prompt section registration failed; continuing without it", exc_info=True, ) if not native_context_registered: try: from .enhancements import apply_phase apply_phase("plugin_load") except Exception: logger.debug("Relay plugin-load enhancements failed; continuing", exc_info=True) # Register plugin-native CLI sub-commands: hermes pair + hermes relay. # Wrapped in try/except so the plugin still works on older hermes-agent # versions that do not expose register_cli_command. try: from .cli import ( register_cli, pair_command, register_relay_cli, relay_command, ) ctx.register_cli_command( name="pair", help="Generate QR code for pairing mobile devices with the Hermes API server", setup_fn=register_cli, handler_fn=pair_command, description=( "Reads API server config (host, port, key) and renders a scannable " "QR code in the terminal. Use to connect Hermes-Relay Android or any " "mobile client to your gateway." ), ) ctx.register_cli_command( name="relay", help="Run the Hermes-Relay WSS server (terminal + bridge channels)", setup_fn=register_relay_cli, handler_fn=relay_command, description=( "Runs the embedded WSS relay server that the Hermes-Relay Android " "app connects to for the terminal and bridge channels. Chat still " "goes direct to the Hermes API server." ), ) except (AttributeError, ImportError): # Older hermes-agent (v0.7.0 and earlier) — CLI commands not registered. # The tools above still work; users can fall back to `python -m plugin.relay`. pass