feat: Unified Messaging System — kommunikation plugin, AI/Proactive/System participants, MessageSidebar, Rich Content Renderer

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
This commit is contained in:
Agent Zero
2026-07-22 01:22:15 +02:00
parent 5980d38c66
commit cc3ac9a43d
45 changed files with 8154 additions and 113 deletions
@@ -0,0 +1,89 @@
"""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