mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
Adds lifecycle hooks to the base platform adapter so Discord (and future platforms) can react to message processing events: 👀 when processing starts ✅ on successful completion (delivery confirmed) ❌ on failure, error, or cancellation Implementation: - base.py: on_processing_start/on_processing_complete hooks with _run_processing_hook error isolation wrapper; delivery tracking via _record_delivery closure for accurate success detection - discord.py: _add_reaction/_remove_reaction helpers + hook overrides - Tests for base hook lifecycle and Discord-specific reactions Co-authored-by: alanwilhelm <alanwilhelm@users.noreply.github.com>
This commit is contained in:
parent
fd29933a6d
commit
227601c200
4 changed files with 335 additions and 0 deletions
|
|
@ -898,6 +898,26 @@ class BasePlatformAdapter(ABC):
|
|||
except Exception:
|
||||
pass
|
||||
|
||||
# ── Processing lifecycle hooks ──────────────────────────────────────────
|
||||
# Subclasses override these to react to message processing events
|
||||
# (e.g. Discord adds 👀/✅/❌ reactions).
|
||||
|
||||
async def on_processing_start(self, event: MessageEvent) -> None:
|
||||
"""Hook called when background processing begins."""
|
||||
|
||||
async def on_processing_complete(self, event: MessageEvent, success: bool) -> None:
|
||||
"""Hook called when background processing completes."""
|
||||
|
||||
async def _run_processing_hook(self, hook_name: str, *args: Any, **kwargs: Any) -> None:
|
||||
"""Run a lifecycle hook without letting failures break message flow."""
|
||||
hook = getattr(self, hook_name, None)
|
||||
if not callable(hook):
|
||||
return
|
||||
try:
|
||||
await hook(*args, **kwargs)
|
||||
except Exception as e:
|
||||
logger.warning("[%s] %s hook failed: %s", self.name, hook_name, e)
|
||||
|
||||
@staticmethod
|
||||
def _is_retryable_error(error: Optional[str]) -> bool:
|
||||
"""Return True if the error string looks like a transient network failure."""
|
||||
|
|
@ -1060,6 +1080,18 @@ class BasePlatformAdapter(ABC):
|
|||
|
||||
async def _process_message_background(self, event: MessageEvent, session_key: str) -> None:
|
||||
"""Background task that actually processes the message."""
|
||||
# Track delivery outcomes for the processing-complete hook
|
||||
delivery_attempted = False
|
||||
delivery_succeeded = False
|
||||
|
||||
def _record_delivery(result):
|
||||
nonlocal delivery_attempted, delivery_succeeded
|
||||
if result is None:
|
||||
return
|
||||
delivery_attempted = True
|
||||
if getattr(result, "success", False):
|
||||
delivery_succeeded = True
|
||||
|
||||
# Create interrupt event for this session
|
||||
interrupt_event = asyncio.Event()
|
||||
self._active_sessions[session_key] = interrupt_event
|
||||
|
|
@ -1069,6 +1101,8 @@ class BasePlatformAdapter(ABC):
|
|||
typing_task = asyncio.create_task(self._keep_typing(event.source.chat_id, metadata=_thread_metadata))
|
||||
|
||||
try:
|
||||
await self._run_processing_hook("on_processing_start", event)
|
||||
|
||||
# Call the handler (this can take a while with tool calls)
|
||||
response = await self._message_handler(event)
|
||||
|
||||
|
|
@ -1138,6 +1172,7 @@ class BasePlatformAdapter(ABC):
|
|||
reply_to=event.message_id,
|
||||
metadata=_thread_metadata,
|
||||
)
|
||||
_record_delivery(result)
|
||||
|
||||
# Human-like pacing delay between text and media
|
||||
human_delay = self._get_human_delay()
|
||||
|
|
@ -1237,6 +1272,10 @@ class BasePlatformAdapter(ABC):
|
|||
except Exception as file_err:
|
||||
logger.error("[%s] Error sending local file %s: %s", self.name, file_path, file_err)
|
||||
|
||||
# Determine overall success for the processing hook
|
||||
processing_ok = delivery_succeeded if delivery_attempted else not bool(response)
|
||||
await self._run_processing_hook("on_processing_complete", event, processing_ok)
|
||||
|
||||
# Check if there's a pending message that was queued during our processing
|
||||
if session_key in self._pending_messages:
|
||||
pending_event = self._pending_messages.pop(session_key)
|
||||
|
|
@ -1253,7 +1292,11 @@ class BasePlatformAdapter(ABC):
|
|||
await self._process_message_background(pending_event, session_key)
|
||||
return # Already cleaned up
|
||||
|
||||
except asyncio.CancelledError:
|
||||
await self._run_processing_hook("on_processing_complete", event, False)
|
||||
raise
|
||||
except Exception as e:
|
||||
await self._run_processing_hook("on_processing_complete", event, False)
|
||||
logger.error("[%s] Error handling message: %s", self.name, e, exc_info=True)
|
||||
# Send the error to the user so they aren't left with radio silence
|
||||
try:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue