jarvis-assist/docs/superpowers/specs/2026-09-13-calendar-integra...

6.3 KiB

JARVIS Nextcloud-Kalender-Integration (Phase 4c) - Design

Datum: 2026-09-13 Status: Approved, bereit fuer Implementierungsplan

Kontext

Naechste Erweiterung nach dem Wetter-Widget: Zugriff auf einen bestehenden Nextcloud-Kalender ("FFW-Onza-Alle", ein geteilter Organisations-Kalender der Freiwilligen Feuerwehr Onza) via CalDAV. JARVIS soll Termine anzeigen, im Chat abfragbar machen und - nach Bestaetigung im Chat - neue Termine anlegen koennen.

Verbindung verifiziert (13.09.2026):

  • CalDAV-URL: https://cloud.ffw-onza.de/remote.php/dav/calendars/jonny/ffw-onza-alle/
  • Auth: HTTP Basic mit Nextcloud-App-Passwort (User jonny)
  • Python-Library caldav (+ icalendar als Abhaengigkeit) liest und schreibt erfolgreich gegen diesen Kalender (Testtermin angelegt und wieder geloescht)
  • Kalender-Timezone: Europe/Berlin
  • Enthaelt sowohl Termine mit Uhrzeit (dtstart als datetime) als auch ganztaegige Termine (dtstart als date ohne Uhrzeit, z.B. "Volksfestwache")

Sicherheit: Schreibzugriff nur nach Bestaetigung

Der Kalender ist ein geteilter Organisations-Kalender, kein privater. Direkte, unbestaetigte Termin-Erstellung durch ein LLM (Halluzinationsrisiko) waere in einem geteilten Kalender riskanter als in einem privaten. Deshalb:

  • Der Chat-System-Prompt weist Claude explizit an, vor jedem Aufruf von create_calendar_event im Klartext nachzufragen ("Soll ich den Termin XY am [Datum] anlegen?") und das Tool erst aufzurufen, nachdem der Nutzer im naechsten Chat-Turn zugestimmt hat.
  • Das ist Prompt-Steuerung, kein hartes technisches Gate (kein separates Freigabe-UI) - Standardmuster fuer LLM-Tool-Use mit Human-in-the-loop bei diesem Umfang. Ein hartes Gate (z.B. ein Bestaetigungs-Endpoint) waere fuer diese Phase Overengineering (YAGNI) und ist ein moegliches Follow-up, falls sich in der Praxis zeigt, dass die Prompt-Steuerung nicht ausreicht.

Architektur

Neues Modul-internes CalDAV-Setup in main.py (kein eigenes File noetig bei diesem Umfang - passt zur bestehenden Ein-Datei-Struktur des Projekts):

NEXTCLOUD_CALDAV_URL = os.getenv("NEXTCLOUD_CALDAV_URL")
NEXTCLOUD_USER = os.getenv("NEXTCLOUD_USER")
NEXTCLOUD_APP_PASSWORD = os.getenv("NEXTCLOUD_APP_PASSWORD")

Zwei synchrone Helper-Funktionen (die caldav-Library ist nicht async-nativ, daher ueber asyncio.to_thread aufgerufen - gleiches Muster wie die bestehenden Postgres-Helper via db_query):

  • list_upcoming_events(days_ahead: int) -> list[dict] - liest Termine der naechsten N Tage, sortiert nach Start, gibt {summary, start, end, description, all_day} pro Termin zurueck (Datumswerte als ISO-Strings).
  • create_event(summary: str, start: str, end: str, description: str = "") -> dict - legt einen neuen Termin an (ISO-8601-Datums-/Zeitstrings als Input, vom Aufrufer bzw. von Claude im Tool-Call geliefert), gibt die gespeicherten Werte zurueck.

Backend-Endpoint (fuer das Dashboard-Widget)

GET /api/v1/calendar/events?days=14 (geschuetzt wie alle anderen Routen) ruft list_upcoming_events auf und gibt {"events": [...]} zurueck. Bei Verbindungs-/Auth-Fehler zu Nextcloud: 503 mit Fehlermeldung statt stillem leerem Array - ein leeres Array waere nicht von "wirklich keine Termine" unterscheidbar.

Chat-Integration: Claude Tool Use

/api/v1/chat bekommt einen tools-Parameter mit zwei Tool-Definitionen:

  1. list_calendar_events - Parameter days_ahead (Ganzzahl). Ruft list_upcoming_events auf.
  2. create_calendar_event - Parameter summary, start (ISO-8601), end (ISO-8601), optional description. Ruft create_event auf.

Ablauf pro Chat-Request: Claude wird mit den Tools aufgerufen. Antwortet Claude mit einem tool_use-Block, fuehrt das Backend die entsprechende Python-Funktion aus, haengt das Ergebnis als tool_result an die Konversation an und ruft Claude ein zweites Mal auf, um die finale, natuerlichsprachige Antwort zu bekommen (ein Tool-Call-Zyklus reicht fuer die hier vorgesehenen Anwendungsfaelle - keine mehrstufige Tool-Verkettung noetig, YAGNI). Der System-Prompt wird um die Bestaetigungs-Regel (siehe oben) ergaenzt.

Die vom Tool-Use erzeugten Zwischennachrichten (der tool_use- und tool_result-Turn) werden nicht in der messages-Tabelle gespeichert - nur die sichtbare finale Nutzer-/Assistant-Nachricht, wie bisher. Das haelt die Persistenz-Logik unveraendert und die Chat-Historie fuer das Frontend weiterhin einfach (reine user/assistant-Paare).

Frontend: "Nächste Termine"-Widget im Dashboard

Neue Sektion in Dashboard.tsx (oder eigene Komponente CalendarWidget, analog zu WeatherWidget): laedt GET /api/v1/calendar/events?days=14 und zeigt eine einfache Liste (Datum + Uhrzeit falls vorhanden + Titel). Kein eigenes Erstellen/Bearbeiten im Dashboard (das laeuft ueber den Chat) - YAGNI fuer diese Phase.

Fehlerbehandlung

  • Nextcloud nicht erreichbar/Auth ungueltig: list_upcoming_events/ create_event werfen eine Exception mit klarer Meldung. Im Dashboard-Endpoint wird daraus ein 503. Im Chat-Tool-Use-Pfad wird die Fehlermeldung als tool_result mit is_error: true an Claude zurueckgegeben, das sie dann verstaendlich im Chat formuliert (kein roher Stacktrace fuer den Nutzer).
  • Ganztaegige Termine (kein dtstart-Zeitanteil): werden mit all_day: true markiert, Frontend zeigt dafuer nur das Datum ohne Uhrzeit.

Testing

  • Backend: Unit-Tests fuer list_upcoming_events und create_event mit gemocktem caldav.DAVClient (keine echten Netzwerkaufrufe im Test).
  • Manueller End-to-End-Test nach Deployment: Dashboard-Widget zeigt echte Termine (bereits verifiziert: 11 vorhandene Termine im Kalender), im Chat nach "was steht diese Woche an?" fragen, dann testweise einen Termin anlegen lassen inkl. Bestaetigungs-Nachfrage - danach den Test-Termin wieder aus dem echten Kalender loeschen (manuell oder per Skript), damit der Organisations-Kalender sauber bleibt.

Out of Scope (bewusst nicht Teil dieser Phase)

  • Bearbeiten/Loeschen bestehender Termine ueber Chat oder Dashboard.
  • Mehrere Kalender/Kalenderauswahl (nur der eine angegebene Kalender).
  • Wiederkehrende Termine anlegen (nur einmalige Termine ueber create_calendar_event).
  • Hartes technisches Freigabe-Gate fuer Termin-Erstellung (siehe "Sicherheit" oben) - Prompt-Steuerung reicht fuer den Start.