Added slash command handling

This commit is contained in:
ARIA committed 2026-08-21 10:18:48 +02:00
1 parent 10e9565e2e
commit 1bcadcf950
16 files changed
+567 -21

No files matched your search

+72
View File
@@ -126,6 +126,66 @@ from .pairing import ( # noqa: E402
from .ws_server import WsServer # noqa: E402
# ---------------------------------------------------------------------------
# Slash-command catalog (the app's "/" drawer)
#
# Derived from hermes' central ``COMMAND_REGISTRY`` (``hermes_cli/commands.py``)
# — the same source the gateway help text and the Telegram command menu use —
# restricted to commands available on gateway surfaces, plus plugin-registered
# commands. Never raises: any import/attribute problem (code skew between the
# plugin and the hermes checkout) degrades to an empty catalog, so the app's
# drawer simply stays closed.
# ---------------------------------------------------------------------------
def _slash_command_catalog() -> List[Dict[str, Any]]:
try:
from hermes_cli import commands as hermes_commands
except Exception:
logger.warning(
"android: slash catalog unavailable (hermes_cli.commands import failed)",
exc_info=True,
)
return []
def _entry(name: str, description: str, args_hint: str, category: str,
aliases: List[str]) -> Dict[str, Any]:
return {
"name": f"/{name}",
"description": description,
"args_hint": args_hint or "",
"category": category,
"aliases": [f"/{a}" for a in aliases],
}
entries: List[Dict[str, Any]] = []
try:
overrides = hermes_commands._resolve_config_gates()
for cmd in hermes_commands.COMMAND_REGISTRY:
if not hermes_commands._is_gateway_available(cmd, overrides):
continue
entries.append(
_entry(cmd.name, cmd.description, cmd.args_hint, cmd.category,
list(cmd.aliases))
)
except Exception:
# Code skew: the private helpers moved. Fall back to the plain
# cli_only filter (config-gated commands are dropped, acceptable).
logger.warning("android: slash catalog fell back to cli_only filter",
exc_info=True)
entries = [
_entry(cmd.name, cmd.description, cmd.args_hint, cmd.category,
list(cmd.aliases))
for cmd in hermes_commands.COMMAND_REGISTRY
if not cmd.cli_only
]
try:
for name, description, args_hint in hermes_commands._iter_plugin_command_entries():
entries.append(_entry(name, description, args_hint, "Plugin", []))
except Exception:
pass
return entries
# ---------------------------------------------------------------------------
# M2 — reasoning capture (streaming)
#
@@ -2244,6 +2304,17 @@ class AndroidAdapter(BasePlatformAdapter):
resp.id = frame.id
await self._ws_server.send_to(device_id, resp)
# ── Slash-command catalog (app's "/" drawer) ──────────────────────────
async def on_commands_catalog(self, frame: protocol.Frame, device_id: str) -> None:
"""Handle an inbound ``commands.catalog`` request: reply with the
gateway's slash-command catalog (hermes ``COMMAND_REGISTRY``,
gateway-available subset + plugin commands). The app fuzzy-matches
the typed prefix client-side; the catalog is static per gateway run,
so no caching is needed here."""
resp = protocol.commands_catalog(_slash_command_catalog(), id=frame.id)
await self._ws_server.send_to(device_id, resp)
# ── M3: search (app -> agent) ─────────────────────────────────────────
async def on_search(self, frame: protocol.Frame, device_id: str) -> None:
@@ -2562,6 +2633,7 @@ class AndroidAdapter(BasePlatformAdapter):
"tools": True, # M2: tool.start/progress/end
"media": True, # M4: media.upload/offer/pull
"search": True, # M3: search frame
"commands_catalog": True, # commands.catalog frame (the "/" drawer)
"push": self.push_backend,
# M5: ntfy server URL (app listener discovery; "" when not ntfy).
"push_ntfy_server": (
+21
View File
@@ -71,6 +71,9 @@ TYPE_CHANNEL_LIST = "channel.list"
TYPE_SEARCH = "search"
TYPE_SEARCH_RESULTS = "search.results"
# Slash-command catalog (app's "/" drawer)
TYPE_COMMANDS_CATALOG = "commands.catalog"
# Reconnect catch-up (M3 outbox; extended by M5 push)
TYPE_SYNC = "sync"
TYPE_SYNC_DONE = "sync.done"
@@ -502,6 +505,24 @@ def search_results(
)
# ---------------------------------------------------------------------------
# Slash-command catalog frames
# ---------------------------------------------------------------------------
def commands_catalog(commands: List[Dict[str, Any]], *, id: Optional[int] = None) -> Frame:
"""Response to a ``commands.catalog`` request: the gateway's slash-command
catalog for the app's ``/`` drawer.
Each entry: ``{name, description, args_hint, category, aliases}`` where
``name``/``aliases`` carry the leading slash (``"/new"``, ``["/reset"]``).
"""
return Frame(
type=TYPE_COMMANDS_CATALOG,
id=id,
payload={"commands": commands},
)
# ---------------------------------------------------------------------------
# Sync frames (M3 outbox)
# ---------------------------------------------------------------------------
+2
View File
@@ -380,6 +380,8 @@ class WsServer:
await self._adapter.on_channel_delete(frame, device_id)
elif frame.type == protocol.TYPE_CHANNEL_LIST:
await self._adapter.on_channel_list(frame, device_id)
elif frame.type == protocol.TYPE_COMMANDS_CATALOG:
await self._adapter.on_commands_catalog(frame, device_id)
elif frame.type == protocol.TYPE_SEARCH:
await self._adapter.on_search(frame, device_id)
elif frame.type == protocol.TYPE_SYNC: