fix(discord): register /approve and /deny slash commands, wire up button-based approval UI (#4800)

Two fixes for Discord exec approval:

1. Register /approve and /deny as native Discord slash commands so they
   appear in Discord's command picker (autocomplete). Previously they
   were only handled as text commands, so users saw 'no commands found'
   when typing /approve.

2. Wire up the existing ExecApprovalView button UI (was dead code):
   - ExecApprovalView now calls resolve_gateway_approval() to actually
     unblock the waiting agent thread when a button is clicked
   - Gateway's _approval_notify_sync() detects adapters with
     send_exec_approval() and routes through the button UI
   - Added 'Allow Session' button for parity with /approve session
   - send_exec_approval() now accepts session_key and metadata for
     thread support
   - Graceful fallback to text-based /approve prompt if button send fails

Also updates test mocks to include grey/secondary ButtonStyle and
purple Color (used by new button styles).
This commit is contained in:
Teknium
2026-04-03 10:24:07 -07:00
committed by GitHub
parent 5db630aae4
commit aecbf7fa4a
6 changed files with 92 additions and 35 deletions

View File

@@ -5851,10 +5851,39 @@ class GatewayRunner:
from tools.approval import register_gateway_notify, unregister_gateway_notify
def _approval_notify_sync(approval_data: dict) -> None:
"""Send the approval request to the user from the agent thread."""
"""Send the approval request to the user from the agent thread.
If the adapter supports interactive button-based approvals
(e.g. Discord's ``send_exec_approval``), use that for a richer
UX. Otherwise fall back to a plain text message with
``/approve`` instructions.
"""
cmd = approval_data.get("command", "")
cmd_preview = cmd[:200] + "..." if len(cmd) > 200 else cmd
desc = approval_data.get("description", "dangerous command")
# Prefer button-based approval when the adapter supports it.
# Check the *class* for the method, not the instance — avoids
# false positives from MagicMock auto-attribute creation in tests.
if getattr(type(_status_adapter), "send_exec_approval", None) is not None:
try:
asyncio.run_coroutine_threadsafe(
_status_adapter.send_exec_approval(
chat_id=_status_chat_id,
command=cmd,
session_key=_approval_session_key,
description=desc,
metadata=_status_thread_metadata,
),
_loop_for_step,
).result(timeout=15)
return
except Exception as _e:
logger.warning(
"Button-based approval failed, falling back to text: %s", _e
)
# Fallback: plain text approval prompt
cmd_preview = cmd[:200] + "..." if len(cmd) > 200 else cmd
msg = (
f"⚠️ **Dangerous command requires approval:**\n"
f"```\n{cmd_preview}\n```\n"