cc3ac9a43d
Phase 1: Backend plugin kommunikation (13 files, 10 tables, REST API, WebSocket, RBAC, DMS Bridge, Participant Registry, Mini-App Registry, Search Provider) Phase 2: AI plugins as participants (ai_assistant + ai_proactive dock as participants, heartbeat job) Phase 3: system_notif plugin (system events → chat messages, pinned System room) Phase 4: Frontend MessageSidebar (replaces AISidebar, same design, comm API client, WebSocket hook, commStore) Phase 5: Rich Content Block Renderer (11 components: Markdown, HTML, Image, Audio, Video, File, ActionCard, ContactCard, MiniApp, BlockRenderer) BasePlugin: added services property + _container in on_activate
90 lines
2.9 KiB
Python
90 lines
2.9 KiB
Python
"""Participant Registry — Andockpunkt für Plugins als Teilnehmer."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from abc import ABC, abstractmethod
|
|
from typing import Any
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ParticipantHandler(ABC):
|
|
"""Interface that plugins implement to act as a conversation participant."""
|
|
|
|
@abstractmethod
|
|
async def on_message_received(
|
|
self,
|
|
conversation_id: Any,
|
|
message: dict[str, Any],
|
|
conversation: dict[str, Any],
|
|
mentions: list[str],
|
|
context: dict[str, Any],
|
|
) -> list[dict[str, Any]] | None:
|
|
"""Called when a new message arrives in a conversation this participant is part of.
|
|
|
|
Args:
|
|
conversation_id: UUID of the conversation.
|
|
message: The new message dict.
|
|
conversation: Full conversation dict with participants.
|
|
mentions: Parsed @mention participant types.
|
|
context: Tenant, user, and other context.
|
|
|
|
Returns:
|
|
Optional list of new message dicts (e.g. AI response).
|
|
For passive readers (system) → return None.
|
|
For reactive participants (ai) → return [message_dict, ...].
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def get_participant_info(self) -> dict[str, Any]:
|
|
"""Return metadata: display_name, avatar_url, capabilities, description."""
|
|
pass
|
|
|
|
|
|
class ParticipantRegistry:
|
|
"""Global registry for plugin participants."""
|
|
|
|
def __init__(self) -> None:
|
|
self._handlers: dict[str, ParticipantHandler] = {}
|
|
|
|
def register(self, participant_type: str, handler: ParticipantHandler) -> None:
|
|
"""Register a plugin as a participant type."""
|
|
self._handlers[participant_type] = handler
|
|
logger.info(f"Participant registered: {participant_type}")
|
|
|
|
def unregister(self, participant_type: str) -> None:
|
|
"""Unregister a participant type."""
|
|
handler = self._handlers.pop(participant_type, None)
|
|
if handler:
|
|
logger.info(f"Participant unregistered: {participant_type}")
|
|
|
|
def get_handler(self, participant_type: str) -> ParticipantHandler | None:
|
|
"""Get the handler for a participant type."""
|
|
return self._handlers.get(participant_type)
|
|
|
|
def list_types(self) -> list[str]:
|
|
"""List all registered participant types."""
|
|
return list(self._handlers.keys())
|
|
|
|
def get_all_info(self) -> dict[str, dict[str, Any]]:
|
|
"""Get info for all registered participants."""
|
|
return {ptype: handler.get_participant_info() for ptype, handler in self._handlers.items()}
|
|
|
|
|
|
# Global instance
|
|
_registry = ParticipantRegistry()
|
|
|
|
|
|
def get_participant_registry() -> ParticipantRegistry:
|
|
"""Get the global participant registry."""
|
|
return _registry
|
|
|
|
|
|
def reset_participant_registry_for_testing() -> ParticipantRegistry:
|
|
"""Create a fresh registry for testing."""
|
|
global _registry
|
|
_registry = ParticipantRegistry()
|
|
return _registry
|