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:
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user