fix: auto-register all gateway commands as Discord slash commands (#10528)

Discord's _register_slash_commands() had a hardcoded list of ~27 commands
while COMMAND_REGISTRY defines 34+ gateway-available commands. Missing
commands (debug, branch, rollback, snapshot, profile, yolo, fast, reload,
commands) were invisible in Discord's / autocomplete — users couldn't
discover them.

Add a dynamic catch-all loop after the explicit registrations that
iterates COMMAND_REGISTRY, skips already-registered commands, and
auto-registers the rest using discord.app_commands.Command(). Commands
with args_hint get an optional string parameter; parameterless commands
get a simple callback.

This ensures any future commands added to COMMAND_REGISTRY automatically
appear on Discord without needing a manual entry in discord.py.

Telegram and Slack already derive dynamically from COMMAND_REGISTRY
via telegram_bot_commands() and slack_subcommand_map() — no changes
needed there.
This commit is contained in:
Teknium
2026-04-15 14:25:27 -07:00
committed by GitHub
parent c4674cbe21
commit 22d22cd75c
2 changed files with 121 additions and 0 deletions

View File

@@ -134,6 +134,57 @@ async def test_registers_native_restart_slash_command(adapter):
)
# ------------------------------------------------------------------
# Auto-registration from COMMAND_REGISTRY
# ------------------------------------------------------------------
@pytest.mark.asyncio
async def test_auto_registers_missing_gateway_commands(adapter):
"""Commands in COMMAND_REGISTRY that aren't explicitly registered should
be auto-registered by the dynamic catch-all block."""
adapter._run_simple_slash = AsyncMock()
adapter._register_slash_commands()
tree_names = set(adapter._client.tree.commands.keys())
# These commands are gateway-available but were not in the original
# hardcoded registration list — they should be auto-registered.
expected_auto = {"debug", "yolo", "reload", "profile"}
for name in expected_auto:
assert name in tree_names, f"/{name} should be auto-registered on Discord"
@pytest.mark.asyncio
async def test_auto_registered_command_dispatches_correctly(adapter):
"""Auto-registered commands should dispatch via _run_simple_slash."""
adapter._run_simple_slash = AsyncMock()
adapter._register_slash_commands()
# /debug has no args — test parameterless dispatch
debug_cmd = adapter._client.tree.commands["debug"]
interaction = SimpleNamespace()
adapter._run_simple_slash.reset_mock()
await debug_cmd.callback(interaction)
adapter._run_simple_slash.assert_awaited_once_with(interaction, "/debug")
@pytest.mark.asyncio
async def test_auto_registered_command_with_args(adapter):
"""Auto-registered commands with args_hint should accept an optional args param."""
adapter._run_simple_slash = AsyncMock()
adapter._register_slash_commands()
# /branch has args_hint="[name]" — test dispatch with args
branch_cmd = adapter._client.tree.commands["branch"]
interaction = SimpleNamespace()
adapter._run_simple_slash.reset_mock()
await branch_cmd.callback(interaction, args="my-branch")
adapter._run_simple_slash.assert_awaited_once_with(
interaction, "/branch my-branch"
)
# ------------------------------------------------------------------
# _handle_thread_create_slash — success, session dispatch, failure
# ------------------------------------------------------------------