"""EventEmitter and event types for the Meeting Room discussion engine. Synchronous event bus. In Phase 2, an AsyncEventBridge will forward events to WebSocket clients. """ from __future__ import annotations import logging from dataclasses import dataclass, field from datetime import datetime from typing import Any, Callable logger = logging.getLogger(__name__) # --------------------------------------------------------------------------- # Event type constants # --------------------------------------------------------------------------- DISCUSSION_START = "discussion_start" ROUND_START = "round_start" AGENT_TURN_START = "agent_turn_start" AGENT_MESSAGE = "agent_message" TOOL_CALL = "tool_call" TOOL_RESULT = "tool_result" AGENT_ERROR = "agent_error" ROUND_END = "round_end" DISCUSSION_END = "discussion_end" BOSS_TURN = "boss_turn" # --------------------------------------------------------------------------- # Event dataclass # --------------------------------------------------------------------------- @dataclass class Event: """A single event emitted by the discussion engine.""" type: str data: dict[str, Any] = field(default_factory=dict) timestamp: datetime = field(default_factory=datetime.now) # --------------------------------------------------------------------------- # EventEmitter # --------------------------------------------------------------------------- # Type alias for handler callbacks Handler = Callable[[Event], None] class EventEmitter: """Synchronous pub/sub event bus. Usage:: bus = EventEmitter() def on_message(event: Event): print(event.data) bus.on(AGENT_MESSAGE, on_message) bus.emit(AGENT_MESSAGE, text="Hello", agent="Alice") bus.off(AGENT_MESSAGE, on_message) """ def __init__(self) -> None: self._handlers: dict[str, list[Handler]] = {} # -- subscription -------------------------------------------------------- def on(self, event_type: str, handler: Handler) -> Handler: """Subscribe *handler* to *event_type*. Returns the handler so callers can use ``off`` with the same reference, or chain calls. """ self._handlers.setdefault(event_type, []).append(handler) return handler def off(self, event_type: str, handler: Handler) -> None: """Unsubscribe *handler* from *event_type*. Silently ignores unknown event types or missing handlers. """ handlers = self._handlers.get(event_type) if handlers is None: return try: handlers.remove(handler) except ValueError: pass # Clean up empty lists to avoid unbounded growth if not handlers: del self._handlers[event_type] # -- emission ------------------------------------------------------------ def emit(self, event_type: str, **data: Any) -> Event: """Create an Event and call every subscribed handler synchronously. Handlers that raise exceptions are caught and logged; the emitter never crashes due to a bad handler. Returns the Event that was emitted (useful for testing). """ event = Event(type=event_type, data=data) handlers = self._handlers.get(event_type, []) for handler in handlers: try: handler(event) except Exception: logger.exception( "Handler %r raised for event %s", handler, event_type, ) return event # -- introspection ------------------------------------------------------- def handlers(self, event_type: str) -> list[Handler]: """Return a *copy* of the handler list for *event_type*.""" return list(self._handlers.get(event_type, [])) def clear(self) -> None: """Remove all subscriptions.""" self._handlers.clear()