refactor: centralize slash command registry (#1603)
* refactor: centralize slash command registry Replace 7+ scattered command definition sites with a single CommandDef registry in hermes_cli/commands.py. All downstream consumers now derive from this registry: - CLI process_command() resolves aliases via resolve_command() - Gateway _known_commands uses GATEWAY_KNOWN_COMMANDS frozenset - Gateway help text generated by gateway_help_lines() - Telegram BotCommands generated by telegram_bot_commands() - Slack subcommand map generated by slack_subcommand_map() Adding a command or alias is now a one-line change to COMMAND_REGISTRY instead of touching 6+ files. Bugfixes included: - Telegram now registers /rollback, /background (were missing) - Slack now has /voice, /update, /reload-mcp (were missing) - Gateway duplicate 'reasoning' dispatch (dead code) removed - Gateway help text can no longer drift from CLI help Backwards-compatible: COMMANDS and COMMANDS_BY_CATEGORY dicts are rebuilt from the registry, so existing imports work unchanged. * docs: update developer docs for centralized command registry Update AGENTS.md with full 'Slash Command Registry' and 'Adding a Slash Command' sections covering CommandDef fields, registry helpers, and the one-line alias workflow. Also update: - CONTRIBUTING.md: commands.py description - website/docs/reference/slash-commands.md: reference central registry - docs/plans/centralize-command-registry.md: mark COMPLETED - plans/checkpoint-rollback.md: reference new pattern - hermes-agent-dev skill: architecture table * chore: remove stale plan docs
This commit is contained in:
parent
b798062501
commit
46176c8029
14 changed files with 571 additions and 802 deletions
|
|
@ -1285,45 +1285,47 @@ class GatewayRunner:
|
|||
# Check for commands
|
||||
command = event.get_command()
|
||||
|
||||
# Emit command:* hook for any recognized slash command
|
||||
_known_commands = {"new", "reset", "help", "status", "stop", "model", "reasoning",
|
||||
"personality", "plan", "retry", "undo", "sethome", "set-home",
|
||||
"compress", "usage", "insights", "reload-mcp", "reload_mcp",
|
||||
"update", "title", "resume", "provider", "rollback",
|
||||
"background", "bg", "reasoning", "voice"}
|
||||
if command and command in _known_commands:
|
||||
# Emit command:* hook for any recognized slash command.
|
||||
# GATEWAY_KNOWN_COMMANDS is derived from the central COMMAND_REGISTRY
|
||||
# in hermes_cli/commands.py — no hardcoded set to maintain here.
|
||||
from hermes_cli.commands import GATEWAY_KNOWN_COMMANDS, resolve_command as _resolve_cmd
|
||||
if command and command in GATEWAY_KNOWN_COMMANDS:
|
||||
await self.hooks.emit(f"command:{command}", {
|
||||
"platform": source.platform.value if source.platform else "",
|
||||
"user_id": source.user_id,
|
||||
"command": command,
|
||||
"args": event.get_command_args().strip(),
|
||||
})
|
||||
|
||||
if command in ["new", "reset"]:
|
||||
|
||||
# Resolve aliases to canonical name so dispatch only checks canonicals.
|
||||
_cmd_def = _resolve_cmd(command) if command else None
|
||||
canonical = _cmd_def.name if _cmd_def else command
|
||||
|
||||
if canonical == "new":
|
||||
return await self._handle_reset_command(event)
|
||||
|
||||
if command == "help":
|
||||
if canonical == "help":
|
||||
return await self._handle_help_command(event)
|
||||
|
||||
if command == "status":
|
||||
if canonical == "status":
|
||||
return await self._handle_status_command(event)
|
||||
|
||||
if command == "stop":
|
||||
if canonical == "stop":
|
||||
return await self._handle_stop_command(event)
|
||||
|
||||
if command == "model":
|
||||
if canonical == "model":
|
||||
return await self._handle_model_command(event)
|
||||
|
||||
if command == "reasoning":
|
||||
if canonical == "reasoning":
|
||||
return await self._handle_reasoning_command(event)
|
||||
|
||||
if command == "provider":
|
||||
if canonical == "provider":
|
||||
return await self._handle_provider_command(event)
|
||||
|
||||
if command == "personality":
|
||||
if canonical == "personality":
|
||||
return await self._handle_personality_command(event)
|
||||
|
||||
if command == "plan":
|
||||
if canonical == "plan":
|
||||
try:
|
||||
from agent.skill_commands import build_plan_path, build_skill_invocation_message
|
||||
|
||||
|
|
@ -1340,51 +1342,48 @@ class GatewayRunner:
|
|||
)
|
||||
if not event.text:
|
||||
return "Failed to load the bundled /plan skill."
|
||||
command = None
|
||||
canonical = None
|
||||
except Exception as e:
|
||||
logger.exception("Failed to prepare /plan command")
|
||||
return f"Failed to enter plan mode: {e}"
|
||||
|
||||
if command == "retry":
|
||||
if canonical == "retry":
|
||||
return await self._handle_retry_command(event)
|
||||
|
||||
if command == "undo":
|
||||
if canonical == "undo":
|
||||
return await self._handle_undo_command(event)
|
||||
|
||||
if command in ["sethome", "set-home"]:
|
||||
if canonical == "sethome":
|
||||
return await self._handle_set_home_command(event)
|
||||
|
||||
if command == "compress":
|
||||
if canonical == "compress":
|
||||
return await self._handle_compress_command(event)
|
||||
|
||||
if command == "usage":
|
||||
if canonical == "usage":
|
||||
return await self._handle_usage_command(event)
|
||||
|
||||
if command == "insights":
|
||||
if canonical == "insights":
|
||||
return await self._handle_insights_command(event)
|
||||
|
||||
if command in ("reload-mcp", "reload_mcp"):
|
||||
if canonical == "reload-mcp":
|
||||
return await self._handle_reload_mcp_command(event)
|
||||
|
||||
if command == "update":
|
||||
if canonical == "update":
|
||||
return await self._handle_update_command(event)
|
||||
|
||||
if command == "title":
|
||||
if canonical == "title":
|
||||
return await self._handle_title_command(event)
|
||||
|
||||
if command == "resume":
|
||||
if canonical == "resume":
|
||||
return await self._handle_resume_command(event)
|
||||
|
||||
if command == "rollback":
|
||||
if canonical == "rollback":
|
||||
return await self._handle_rollback_command(event)
|
||||
|
||||
if command == "background":
|
||||
if canonical == "background":
|
||||
return await self._handle_background_command(event)
|
||||
|
||||
if command == "reasoning":
|
||||
return await self._handle_reasoning_command(event)
|
||||
|
||||
if command == "voice":
|
||||
if canonical == "voice":
|
||||
return await self._handle_voice_command(event)
|
||||
|
||||
# User-defined quick commands (bypass agent loop, no LLM call)
|
||||
|
|
@ -2093,30 +2092,10 @@ class GatewayRunner:
|
|||
|
||||
async def _handle_help_command(self, event: MessageEvent) -> str:
|
||||
"""Handle /help command - list available commands."""
|
||||
from hermes_cli.commands import gateway_help_lines
|
||||
lines = [
|
||||
"📖 **Hermes Commands**\n",
|
||||
"`/new` — Start a new conversation",
|
||||
"`/reset` — Reset conversation history",
|
||||
"`/status` — Show session info",
|
||||
"`/stop` — Interrupt the running agent",
|
||||
"`/model [provider:model]` — Show/change model (or switch provider)",
|
||||
"`/provider` — Show available providers and auth status",
|
||||
"`/personality [name]` — Set a personality",
|
||||
"`/retry` — Retry your last message",
|
||||
"`/undo` — Remove the last exchange",
|
||||
"`/sethome` — Set this chat as the home channel",
|
||||
"`/compress` — Compress conversation context",
|
||||
"`/title [name]` — Set or show the session title",
|
||||
"`/resume [name]` — Resume a previously-named session",
|
||||
"`/usage` — Show token usage for this session",
|
||||
"`/insights [days]` — Show usage insights and analytics",
|
||||
"`/reasoning [level|show|hide]` — Set reasoning effort or toggle display",
|
||||
"`/rollback [number]` — List or restore filesystem checkpoints",
|
||||
"`/background <prompt>` — Run a prompt in a separate background session",
|
||||
"`/voice [on|off|tts|status]` — Toggle voice reply mode",
|
||||
"`/reload-mcp` — Reload MCP servers from config",
|
||||
"`/update` — Update Hermes Agent to the latest version",
|
||||
"`/help` — Show this message",
|
||||
*gateway_help_lines(),
|
||||
]
|
||||
try:
|
||||
from agent.skill_commands import get_skill_commands
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue