fix(gateway): visually expire Discord interactive views on timeout

All Discord interactive views (ExecApprovalView, SlashConfirmView,
UpdatePromptView, ModelPickerView, ClarifyChoiceView) now edit their
message when the view times out, disabling buttons and updating the
embed to show a 'Prompt expired' footer. Previously, timed-out buttons
remained visually clickable in the UI, causing Discord's generic
'Interaction failed' error when clicked.

Fixes #38022
This commit is contained in:
kyssta-exe
2026-06-03 10:47:46 +00:00
committed by Teknium
parent 38d3c49aaf
commit 86c64cfb5b

View File

@ -4101,6 +4101,7 @@ class DiscordAdapter(BasePlatformAdapter):
)
msg = await channel.send(embed=embed, view=view)
view._message = msg # store for on_timeout expiration editing
return SendResult(success=True, message_id=str(msg.id))
except Exception as e:
@ -4140,6 +4141,7 @@ class DiscordAdapter(BasePlatformAdapter):
)
msg = await channel.send(embed=embed, view=view)
view._message = msg # store for on_timeout expiration editing
return SendResult(success=True, message_id=str(msg.id))
except Exception as e:
return SendResult(success=False, error=str(e))
@ -4217,6 +4219,8 @@ class DiscordAdapter(BasePlatformAdapter):
view = None
msg = await channel.send(embed=embed, view=view) if view else await channel.send(embed=embed)
if view:
view._message = msg # store for on_timeout expiration editing
return SendResult(success=True, message_id=str(msg.id))
except Exception as e:
logger.warning("[%s] send_clarify failed: %s", self.name, e)
@ -4252,6 +4256,7 @@ class DiscordAdapter(BasePlatformAdapter):
allowed_role_ids=self._allowed_role_ids,
)
msg = await channel.send(embed=embed, view=view)
view._message = msg # store for on_timeout expiration editing
return SendResult(success=True, message_id=str(msg.id))
except Exception as e:
return SendResult(success=False, error=str(e))
@ -4311,6 +4316,7 @@ class DiscordAdapter(BasePlatformAdapter):
)
msg = await channel.send(embed=embed, view=view)
view._message = msg # store for on_timeout expiration editing
return SendResult(success=True, message_id=str(msg.id))
except Exception as e:
@ -5141,6 +5147,17 @@ def _define_discord_view_classes() -> None:
self.resolved = True
for child in self.children:
child.disabled = True
# Visually update the Discord message so buttons appear disabled.
msg = getattr(self, '_message', None)
if msg:
try:
embed = msg.embeds[0] if msg.embeds else None
if embed:
embed.color = discord.Color.greyple()
embed.set_footer(text="⏱ Prompt expired — no action taken")
await msg.edit(embed=embed, view=self)
except Exception:
pass # message deleted or too old to edit
class SlashConfirmView(discord.ui.View):
"""Three-button view for generic slash-command confirmations.
@ -5245,6 +5262,17 @@ def _define_discord_view_classes() -> None:
self.resolved = True
for child in self.children:
child.disabled = True
# Visually update the Discord message so buttons appear disabled.
msg = getattr(self, '_message', None)
if msg:
try:
embed = msg.embeds[0] if msg.embeds else None
if embed:
embed.color = discord.Color.greyple()
embed.set_footer(text="⏱ Prompt expired — no action taken")
await msg.edit(embed=embed, view=self)
except Exception:
pass
class UpdatePromptView(discord.ui.View):
"""Interactive Yes/No buttons for ``hermes update`` prompts.
@ -5330,6 +5358,17 @@ def _define_discord_view_classes() -> None:
self.resolved = True
for child in self.children:
child.disabled = True
# Visually update the Discord message so buttons appear disabled.
msg = getattr(self, '_message', None)
if msg:
try:
embed = msg.embeds[0] if msg.embeds else None
if embed:
embed.color = discord.Color.greyple()
embed.set_footer(text="⏱ Prompt expired — no action taken")
await msg.edit(embed=embed, view=self)
except Exception:
pass
class ModelPickerView(discord.ui.View):
"""Interactive select-menu view for model switching.
@ -5555,6 +5594,18 @@ def _define_discord_view_classes() -> None:
async def on_timeout(self):
self.resolved = True
self.clear_items()
# Visually update the Discord message so it appears expired.
msg = getattr(self, '_message', None)
if msg:
try:
embed = discord.Embed(
title="⚙ Model Configuration",
description="⏱ Selection expired — no model change.",
color=discord.Color.greyple(),
)
await msg.edit(embed=embed, view=self)
except Exception:
pass
class ClarifyChoiceView(discord.ui.View):
@ -5740,6 +5791,17 @@ def _define_discord_view_classes() -> None:
self.resolved = True
for child in self.children:
child.disabled = True
# Visually update the Discord message so buttons appear disabled.
msg = getattr(self, '_message', None)
if msg:
try:
embed = msg.embeds[0] if msg.embeds else None
if embed:
embed.color = discord.Color.greyple()
embed.set_footer(text="⏱ Prompt expired — no action taken")
await msg.edit(embed=embed, view=self)
except Exception:
pass
if DISCORD_AVAILABLE:
_define_discord_view_classes()