Files

169 lines
6.5 KiB
Python

"""
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