Files
leocrm/docs/konzept-unified-messaging.md
T
Agent Zero cc3ac9a43d 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
2026-07-22 01:22:15 +02:00

25 KiB

Konzept: Unified Messaging System für LeoCRM

Vision

Alle Kommunikation in LeoCRM — KI-Chat, Mitarbeiter-Chat, System-Benachrichtigungen, externe Messenger — läuft über ein einheitliches Messaging-System, das als Plugin-Architektur realisiert wird.

Kernprinzip: Alles ist ein Teilnehmer. Die KI ist ein Teilnehmer. Das System ist ein Teilnehmer. Ein WhatsApp-Gateway ist ein Teilnehmer. Es gibt keine Sonderbehandlung.


Plugin-Architektur

Übersicht: Drei Plugin-Ebenen

┌─────────────────────────────────────────────────────────────┐
│                    Frontend (MessageSidebar)                 │
│         Ein Feed, eine Konversations-Liste, ein Eingabefeld   │
└──────────────────────────┬──────────────────────────────────┘
│                           │ WebSocket                         │
┌──────────────────────────┴──────────────────────────────────┐
│              Plugin: kommunikation (Core)                     │
│  ┌─────────────┐  ┌──────────────┐  ┌────────────────────┐  │
│  │ conversations│  │  messages    │  │  participants      │  │
│  │  + Räume     │  │  + Rich Cont.│  │  + Registrierung   │  │
│  └─────────────┘  └──────────────┘  └────────────────────┘  │
│  ┌──────────────────────────────────────────────────────┐   │
│  │           Participant Registry (Hook)                │   │
│  │  Andockpunkt für: ai_assistant, system_notif,         │   │
│  │  whatsapp_gateway, telegram_gateway, ...              │   │
│  └──────────────────────────────────────────────────────┘   │
└──────────────────────────┬──────────────────────────────────┘
│                           │ EventBus                          │
┌──────────────────────────┴──────────────────────────────────┐
│  Plugin: ai_assistant    │  Plugin: system_notif  │  Plugin:   │
│  dockt als Teilnehmer an  │  dockt als Teilnehmer │  whatsapp │
│  @KI → AI-Response        │  Events → Messages    │  Gateway  │
└─────────────────────────────────────────────────────────────┘

1. Plugin kommunikation (Core-Plugin)

Verantwortung: Chat-Infrastruktur — Konversationen, Nachrichten, Teilnehmer-Verwaltung, WebSocket, Rich Content Transport.

Basiert auf: AI Assistant Plugin (Sessions/Messages/Streaming) als Grundlage, erweitert um Multi-Teilnehmer und Rich Content.

Manifest:

PluginManifest(
    name="kommunikation",
    version="1.0.0",
    display_name="Kommunikation",
    description="Unified Messaging: Chat, KI, System, Messenger",
    dependencies=[],
    routes=[
        PluginRouteDef(path="/api/v1/comm", module="...routes", router_attr="router"),
    ],
    events=["message.received", "conversation.created", "participant.joined"],
    migrations=["0001_initial.sql"],
    permissions=["comm:read", "comm:write", "comm:manage"],
    is_core=True,
)

Komponenten:

  • models.py — Conversation, Message, Participant, MessageAttachment, MessageReaction
  • schemas.py — Pydantic-Schemas für API
  • routes.py — REST-API + WebSocket
  • services.py — Business Logic (Nachrichten senden, Konversationen verwalten)
  • participant_registry.py — Registrierungs-Interface für andere Plugins
  • content_types.py — Rich Content Type-Definitionen
  • websocket_manager.py — WebSocket-Verbindungs-Manager

2. Participant Registry (Andockpunkt)

Das kommunikation Plugin stellt eine Participant Registry bereit — ein Interface, über das sich andere Plugins als Teilnehmer registrieren.

# In kommunikation/participant_registry.py

class ParticipantType(Enum):
    USER = "user"
    AI = "ai"
    SYSTEM = "system"
    WHATSAPP = "whatsapp"
    TELEGRAM = "telegram"
    EMAIL = "email"
    SLACK = "slack"
    # Erweiterbar...

class ParticipantHandler(ABC):
    """Interface das Plugins implementieren um als Teilnehmer zu fungieren."""
    
    @abstractmethod
    async def on_message_received(self, conversation_id, message, context) -> Message | None:
        """Wird aufgerufen wenn eine neue Nachricht in einer Konversation
        ankommt, an der dieser Teilnehmer beteiligt ist.
        
        Rückgabe: Optional eine neue Message (z.B. AI-Response).
        Für reine Leser (system) → return None.
        Für reaktive Teilnehmer (ai) → return Message(...).
        """
        pass
    
    @abstractmethod
    def get_participant_info(self) -> dict:
        """Metadaten: name, avatar_url, display_name, capabilities."""
        pass

class ParticipantRegistry:
    """Global registry für Plugin-Teilnehmer."""
    
    def register(self, participant_type: str, handler: ParticipantHandler) -> None:
        """Plugin registriert sich als Teilnehmer-Typ."""
        
    def get_handler(self, participant_type: str) -> ParticipantHandler | None:
        """Handler für einen Teilnehmer-Typ abrufen."""

Wie Plugins andocken:

# In ai_assistant/plugin.py on_activate():
from app.plugins.builtins.kommunikation.participant_registry import get_registry

class AIAssistantPlugin(BasePlugin):
    async def on_activate(self, db, container, event_bus):
        await super().on_activate(db, container, event_bus)
        # Als AI-Teilnehmer registrieren
        registry = get_registry()
        registry.register("ai", AIParticipantHandler(self.services))
# In system_notif/plugin.py on_activate():
class SystemNotificationPlugin(BasePlugin):
    async def on_activate(self, db, container, event_bus):
        await super().on_activate(db, container, event_bus)
        # Als System-Teilnehmer registrieren
        registry = get_registry()
        registry.register("system", SystemParticipantHandler(...))
        # Auf Events hören und Nachrichten erzeugen
        event_bus.subscribe("lead.created", self.on_lead_created)

3. Plugin ai_assistant (Anpassung)

Das bestehende AI Assistant Plugin wird angepasst:

  • Behält: Provider-Verwaltung, Modelle, Agents, Tools, Streaming
  • Neu: Implementiert ParticipantHandler und registriert sich bei kommunikation
  • Neu: Lauscht auf message.received Events → wenn @KI erwähnt wird oder Konversation AI als Teilnehmer hat → generiert Response
  • Alt: Eigene AIChatSession / AIChatMessage Tabellen bleiben für Abwärtskompatibilität, werden langfristig migriert
  • Neu: Schreibt Nachrichten in kommunikation.messages statt nur in eigene Tabellen

4. Plugin system_notif (Neu)

Verantwortung: System-Events in Nachrichten umwandeln.

  • Registriert sich als system Teilnehmer
  • Hört auf EventBus-Events (lead.created, contact.created, task.overdue, ...)
  • Erzeugt Nachrichten in der System-Konversation des Users
  • Notifications haben metadata.action_url und metadata.severity
  • Bestehende Notification-Tabelle wird migriert

5. Messenger-Gateway Plugins (Später)

Jeder Messenger ist ein eigenes Plugin:

  • whatsapp_gateway — registriert sich als whatsapp Teilnehmer
  • telegram_gateway — registriert sich als telegram Teilnehmer
  • email_gateway — registriert sich als email Teilnehmer

Jedes implementiert ParticipantHandler und ggf. Webhook-Routes.


Datenmodell

Tabelle comm_conversations

id            UUID PK
tenant_id     UUID NOT NULL
title         TEXT NULL          -- benannte Räume
title_set_by  UUID NULL          -- user_id der den Titel gesetzt hat
is_pinned     BOOLEAN DEFAULT FALSE
is_direct     BOOLEAN DEFAULT FALSE  -- 1:1 vs Gruppe
created_by    UUID NULL
last_msg_at   TIMESTAMP
last_msg_preview  TEXT NULL      -- für Konversations-Liste
last_msg_sender_type  TEXT NULL   -- für Icon in Liste
metadata      JSONB DEFAULT '{}'  -- z.B. {"pinned_by": "user_id"}
created_at    TIMESTAMP DEFAULT NOW()
updated_at    TIMESTAMP DEFAULT NOW()

Tabelle comm_participants

id                UUID PK
conversation_id    UUID FK → comm_conversations
participant_id     UUID NULL       -- user_id (NULL für ai/system/gateways)
participant_type   TEXT NOT NULL   -- 'user', 'ai', 'system', 'whatsapp', ...
display_name       TEXT NULL       -- override (z.B. WhatsApp-Kontakt-Name)
joined_at          TIMESTAMP DEFAULT NOW()
left_at            TIMESTAMP NULL

Tabelle comm_messages

id              UUID PK
tenant_id       UUID NOT NULL
conversation_id  UUID FK → comm_conversations
sender_id       UUID NULL       -- user_id (NULL für ai/system/gateways)
sender_type     TEXT NOT NULL   -- 'user', 'ai', 'system', 'whatsapp', ...
content         TEXT NOT NULL   -- Text-Inhalt (Markdown)
content_format  TEXT DEFAULT 'text'  -- 'text', 'markdown', 'html'
metadata        JSONB DEFAULT '{}'  -- typ-spezifische Daten
reply_to_id     UUID NULL FK → comm_messages  -- Thread-Antwort
created_at      TIMESTAMP DEFAULT NOW()
read_at         TIMESTAMP NULL
edited_at       TIMESTAMP NULL
deleted_at      TIMESTAMP NULL

Tabelle comm_message_attachments

id           UUID PK
message_id   UUID FK → comm_messages
file_name    TEXT NOT NULL
file_path    TEXT NOT NULL     -- Pfad im DMS oder S3
file_type    TEXT NOT NULL    -- MIME type
file_size    BIGINT
thumbnail_path TEXT NULL      -- für Bilder/Videos
metadata     JSONB DEFAULT '{}'  -- z.B. {"width": 1920, "height": 1080}
created_at   TIMESTAMP DEFAULT NOW()

Tabelle comm_message_reactions

id           UUID PK
message_id   UUID FK → comm_messages
user_id      UUID NOT NULL
emoji        TEXT NOT NULL
created_at   TIMESTAMP DEFAULT NOW()
UNIQUE(message_id, user_id, emoji)

Tabelle comm_message_reads

id              UUID PK
conversation_id  UUID FK
user_id         UUID NOT NULL
last_read_msg_id  UUID FK → comm_messages
last_read_at    TIMESTAMP DEFAULT NOW()

Tabelle comm_message_blocks (Rich Content / Mini-Apps)

id           UUID PK
message_id   UUID FK → comm_messages
block_type   TEXT NOT NULL   -- 'file', 'image', 'audio', 'video', 
                               -- 'markdown', 'html', 'miniapp', 
                               -- 'action_card', 'contact_card', ...
block_data   JSONB NOT NULL  -- typ-spezifische strukturierte Daten
sort_order    INT DEFAULT 0

Das ist der Schlüssel für Rich Content: Eine Nachricht hat einen content (Text) plus beliebig viele blocks (strukturierte Elemente).


Rich Content Transport

Block-Typen (erweiterbar durch Plugins)

block_type Beschreibung block_data Beispiel
text Reiner Text (Fallback) {"text": "..."}
markdown Markdown-Content {"markdown": "# Titel\n..."}
html HTML-Content (sanitized) {"html": "<div>...</div>"}
image Bild {"url": "...", "alt": "...", "width": 800}
audio Audio-Datei {"url": "...", "duration": 120, "waveform": [...]}
video Video-Datei {"url": "...", "duration": 60, "thumbnail": "..."}
file Allgemeine Datei {"url": "...", "name": "...", "size": 1024}
action_card Interaktive Karte mit Buttons {"title": "...", "body": "...", "actions": [{"label": "Öffnen", "url": "..."}]}
contact_card Kontakt-Referenz {"contact_id": "...", "name": "..."}
miniapp Eingebettete Mini-App {"app_id": "...", "config": {...}}

Mini-App System

Mini-Apps sind kleine interaktive Komponenten, die von Plugins registriert und im Chat gerendert werden.

# Plugin registriert eine Mini-App:
class MiniAppRegistry:
    def register(self, app_id: str, component: dict) -> None:
        """Registriert eine Mini-App.
        
        component = {
            'name': 'Lead Qualifier',
            'icon': 'clipboard',
            'render_schema': {...},  # JSON-Schema für Frontend
            'handler': async function  # Backend-Handler
        }
        """

Beispiel: Ein Plugin lead_qualifier registriert eine Mini-App. Ein User schickt /miniapp lead_qualifier im Chat → eine Mini-App-Block wird erzeugt → Frontend rendert das interaktive Formular → Ergebnis wird als Nachricht zurückgeschrieben.

Nachricht mit Rich Content — Beispiel

{
  "id": "...",
  "conversation_id": "...",
  "sender_type": "ai",
  "content": "Hier ist die Zusammenfassung der neuen Leads:",
  "blocks": [
    {
      "block_type": "markdown",
      "block_data": {
        "markdown": "## 3 neue Leads\n- **Acme Corp** — €50k potential\n- **Globex** — €20k potential\n- **Initech** — €10k potential"
      }
    },
    {
      "block_type": "action_card",
      "block_data": {
        "title": "Nächste Schritte",
        "body": "3 Leads warten auf Qualifizierung.",
        "actions": [
          {"label": "Alle öffnen", "action": "open_leads", "type": "primary"},
          {"label": "Ignorieren", "action": "dismiss", "type": "secondary"}
        ]
      }
    }
  ]
}

API

REST Endpoints

# Konversationen
GET    /api/v1/comm/conversations                    -- Liste (für aktuellen User)
POST   /api/v1/comm/conversations                    -- Neue Konversation
GET    /api/v1/comm/conversations/{id}               -- Details + Teilnehmer
PATCH  /api/v1/comm/conversations/{id}               -- Titel ändern, pinnen
DELETE /api/v1/comm/conversations/{id}               -- Löschen/Verlassen

# Teilnehmer
POST   /api/v1/comm/conversations/{id}/participants  -- Teilnehmer hinzufügen
DELETE /api/v1/comm/conversations/{id}/participants/{pid}  -- Entfernen

# Nachrichten
GET    /api/v1/comm/conversations/{id}/messages      -- Nachrichten (paginiert)
POST   /api/v1/comm/conversations/{id}/messages      -- Nachricht senden
PATCH  /api/v1/comm/messages/{id}                    -- Bearbeiten/Lesen
DELETE /api/v1/comm/messages/{id}                    -- Löschen

# Attachments
POST   /api/v1/comm/messages/{id}/attachments         -- Datei hochladen
GET    /api/v1/comm/attachments/{id}                  -- Datei herunterladen

# Reaktionen
POST   /api/v1/comm/messages/{id}/reactions          -- Reaktion hinzufügen
DELETE /api/v1/comm/messages/{id}/reactions/{emoji}  -- Reaktion entfernen

# Read State
POST   /api/v1/comm/conversations/{id}/read          -- Als gelesen markieren

# Mini-Apps
GET    /api/v1/comm/miniapps                          -- Verfügbare Mini-Apps
POST   /api/v1/comm/conversations/{id}/miniapps       -- Mini-App starten

WebSocket

WS /api/v1/comm/ws

# Client → Server
{"type": "subscribe", "conversation_id": "..."}
{"type": "typing", "conversation_id": "...", "is_typing": true}
{"type": "ping"}

# Server → Client
{"type": "message.new", "conversation_id": "...", "message": {...}}
{"type": "message.updated", "message": {...}}
{"type": "message.deleted", "id": "..."}
{"type": "participant.joined", "conversation_id": "...", "participant": {...}}
{"type": "participant.left", "conversation_id": "...", "participant_id": "..."}
{"type": "typing", "conversation_id": "...", "user_id": "...", "is_typing": true}
{"type": "reaction.added", "message_id": "...", "emoji": "👍", "user_id": "..."}
{"type": "conversation.updated", "conversation": {...}}
{"type": "pong"}

UI-Konzept

MessageSidebar (ersetzt AISidebar)

┌──────────────────────────────────────────┐
│  Kommunikation                    [×]    │
├──────────────────────────────────────────┤
│  🔍 Suche...                             │
├──────────────────────────────────────────┤
│  📌 Projekt Alpha                        │  ←angepinnt
│     🤖 KI: 3 Leads zusammengefasst...    │
│  ┌────────────────────────────────────┐  │
│  │ Max: @KI fasse die Leads zusammen   │  │
│  │ 🤖 KI: 3 neue Leads, 2 aus...       │  │
│  │ Lisa: Super, danke!                 │  │
│  │ ┌──────────────────────────────┐   │  │
│  │ │ 📎 lead_report.pdf           │   │  │
│  │ │ ──────────────────────────── │   │  │
│  │ │ ## 3 neue Leads              │   │  │
│  │ │ - **Acme Corp** — €50k       │   │  │
│  │ │ ──────────────────────────── │   │  │
│  │ │ [Öffnen]  [Archivieren]      │   │  │
│  │ └──────────────────────────────┘   │  │
│  └────────────────────────────────────┘  │
│                                          │
│  📌 Assistent (1:1 mit KI)               │
│     🤖 47 Kontakte ohne Email...         │
│                                          │
│  👥 Sales Team                           │
│     Max: Hat jemand die Q3-Zahlen?       │
│                                          │
│  🔔 System                              │
│     3 neue Leads importiert             │
│                                          │
├──────────────────────────────────────────┤
│  [📎] [Eingabefeld...]        [Senden]  │
└──────────────────────────────────────────┘
  • Angespinnte Konversationen oben (📌)
  • Ungelesene-Badge pro Konversation
  • Letzte Nachricht mit Sender-Icon (🤖/👤/🔔)
  • Klick → öffnet Konversation im Feed

Feed (Mitte)

  • Chronologische Nachrichten
  • Sender-Icon + Name pro Nachricht
  • Rich Content Blocks inline gerendert
  • Action-Cards mit Buttons
  • Datei-Anhänge mit Vorschau
  • Reaktionen (Emoji-Bar beim Hover)
  • Lesebestätigung (gelesen-Häkchen)

Eingabefeld (unten)

  • Text-Eingabe mit Markdown-Support
  • Datei-Anhang Button (📎)
  • Mini-App Picker (/command)
  • @Mention Support (@KI, @Max)
  • Senden-Button
  • Kontextsensitiv: in System-Konversation → kein Eingabefeld

Teilnehmer-Info

  • In Konversations-Header: Avatare aller Teilnehmer
  • Klick auf Avatar → Info-Popover
  • KI-Teilnehmer: zeigt Modell/Agent
  • System-Teilnehmer: zeigt Quelle

Räume

  • Konversationen können benannt werden (Titel editierbar)
  • Anpinnen möglich (📌)
  • Gruppierung durch Titel, nicht durch spezielle Raum-Logik
  • Ein "Raum" ist einfach eine benannte Konversation

EventBus Integration

Events vom kommunikation Plugin

message.received     → {conversation_id, message, sender_type}
message.sent         → {conversation_id, message}
conversation.created → {conversation_id, participants, created_by}
participant.joined   → {conversation_id, participant_type, participant_id}
participant.left     → {conversation_id, participant_id}

Events die andere Plugins hören

# ai_assistant hört auf:
message.received → prüft ob @KI erwähnt oder AI Teilnehmer → generiert Response

# system_notif hört auf (vom Core-System):
lead.created    → erzeugt System-Nachricht
contact.created → erzeugt System-Nachricht
task.overdue     → erzeugt System-Nachricht

# whatsapp_gateway hört auf:
message.received → wenn Konversation WhatsApp-Teilnehmer hat → sende extern

Migration

Phase 1: Backend — Plugin kommunikation

  1. Neue Tabellen: comm_conversations, comm_participants, comm_messages, comm_message_attachments, comm_message_blocks, comm_message_reactions, comm_message_reads
  2. Participant Registry Interface
  3. REST-API + WebSocket
  4. Rich Content Block System
  5. Mini-App Registry Interface

Phase 2: Backend — Plugin ai_assistant anpassen

  1. ParticipantHandler implementieren
  2. Bei kommunikation registrieren
  3. Auf message.received hören → AI-Response generieren
  4. Streaming-Responses über WebSocket pushen
  5. Alte AIChatSession/AIChatMessage behalten für Abwärtskompatibilität

Phase 3: Backend — Plugin system_notif (neu)

  1. ParticipantHandler implementieren
  2. System-Events → Nachrichten in System-Konversation
  3. Bestehende Notifications migrieren
  4. Action-URLs als action_card Blocks

Phase 4: Frontend — MessageSidebar

  1. AISidebar → MessageSidebar umbauen
  2. Konversations-Liste mit Pinning
  3. Unified Feed mit Rich Content Rendering
  4. Eingabefeld mit Datei-Upload + @Mention
  5. WebSocket-Verbindung
  6. Mini-App Rendering Framework

Phase 5: Frontend — Rich Content Renderer

  1. Block-Renderer: Markdown, HTML, Image, Audio, Video, File
  2. Action-Card Renderer mit Button-Handler
  3. Mini-App Renderer (Plugin-basiert)
  4. Contact-Card, Lead-Card, etc.

Phase 6: Messenger-Gateway Plugins (später)

  1. whatsapp_gateway Plugin
  2. telegram_gateway Plugin
  3. email_gateway Plugin
  4. Jeweils: ParticipantHandler + Webhook-Routes + Gateway-Adapter

Technische Entscheidungen

WebSocket vs Polling

WebSocket — eine Verbindung pro User, pusht alle Konversationen. Grund: Real-time ist essenziell für Chat, und eine Verbindung für alles ist effizienter als Multiple Polling.

Rich Content: Blocks vs Inline

Blocks — separate Tabelle comm_message_blocks mit block_type + block_data. Grund: Erweiterbar durch Plugins, strukturiert, frontend kann unbekannte Typen graceful ignorieren.

Mini-Apps: Plugin-basiert

Registry Pattern — Plugins registrieren Mini-Apps bei kommunikation. Grund: Plugins können eigene Mini-Apps mitbringen, Frontend rendert sie dynamisch.

Räume: Keine separate Tabelle

Titel + Pinning — eine Konversation mit Titel ist ein Raum. Grund: Minimalistisch, keine zusätzliche Komplexität, flexibel.

@Mention Detection

Im Backendmessage.received Event enthält geparste mentions. Grund: Zentrale Logik, alle Teilnehmer-Plugins bekommen saubere Daten.

Abwärtskompatibilität

Alte Tabellen behaltenai_chat_sessions, ai_chat_messages, ai_conversations, ai_messages bleiben erhalten. Grund: Bestehende Daten gehen nicht verloren, Migration schrittweise.


Offene Fragen

  1. Soll kommunikation das bestehende AI Copilot System (AIConversation/AIMessage) ersetzen oder parallel laufen?

    • Vorschlag: Parallel, langfristig migrieren
  2. Datei-Speicherung: DMS-Plugin nutzen oder eigener Speicher für Attachments?

    • Vorschlag: DMS-Integration, file_path verweist auf DMS-Dokument
  3. Berechtigungen: Wer darf Konversationen erstellen? Wer darf Teilnehmer hinzufügen?

    • Vorschlag: comm:write für erstellen, comm:manage für Teilnehmer verwalten
  4. Gruppen-Chat-Limit: Maximale Anzahl Teilnehmer?

    • Vorschlag: Kein Limit, Performance-Test später
  5. Nachrichten-Historie: Endlos oder Paginierung mit Lazy-Loading?

    • Vorschlag: Paginierung (50 pro Seite), Lazy-Load beim Scrollen
  6. Suche: Über alle Konversationen? Global mit unified_search Plugin?

    • Vorschlag: Ja, unified_search Provider für kommunikation
  7. Push-Notifications: Browser-Notifications bei neuen Nachrichten?

    • Vorschlag: Ja, über Notification API + Service Worker
  8. Verschlüsselung: E2E für bestimmte Konversationen?

    • Vorschlag: Nein in Phase 1, später evaluieren

Zusammenfassung

Ein Plugin (kommunikation)     → Chat-Infrastruktur + Rich Content + WebSocket
Ein Interface (ParticipantHandler) → Plugins docken als Teilnehmer an
Ein Datenmodell (3+Tabellen)   → Konversationen, Teilnehmer, Nachrichten + Blocks
Eine UI (MessageSidebar)       → Ein Feed, eine Liste, ein Eingabefeld
Eine WebSocket                 → Real-time für alles
Ein EventBus                   → Plugins reagieren auf Nachrichten

KI = Teilnehmer               → @KI in jedem Chat
System = Teilnehmer           → Notifications als Nachrichten
WhatsApp = Teilnehmer         → Externe Messenger andocken
Mini-Apps = Plugin-Blocks      → Erweiterbar im Chat
Räume = Benannte Chats         → Titel + Pinning