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

133 lines
6.3 KiB
Markdown

# 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.