Native Android-App (Kotlin/Compose) als Client für den Hermes Agent — Pendant zur iOS-App Hermes Conduit
Find a file
Hermes Agent 75e0f25def Hermes Conduit Android v1 (MVP): Login, Session-Liste, Streaming-Chat, Profile
- Kotlin + Jetpack Compose (Material 3, Dark Theme), minSdk 26, target 35
- Auth-Flow wie iOS Conduit: password-login → /api/auth/me → ws-ticket → WS
- JSON-RPC 2.0 über WS-Textframes, 1:1 portiert aus Conduit/Services/HermesClient.swift
- Streaming (message.delta/complete), Markdown mit Code-Highlighting (mikepenz)
- Profile-Anzeige/-Wechsel, Kontext-Auslastungs-Balken aus session.usage
- Zugangsdaten via EncryptedSharedPreferences (Android Keystore)
- Netzwerk-Core Android-frei → JVM-Live-Test gegen das echte Dashboard
2026-10-21 13:03:49 +00:00
app Hermes Conduit Android v1 (MVP): Login, Session-Liste, Streaming-Chat, Profile 2026-10-21 13:03:49 +00:00
gradle/wrapper Hermes Conduit Android v1 (MVP): Login, Session-Liste, Streaming-Chat, Profile 2026-10-21 13:03:49 +00:00
.gitignore Hermes Conduit Android v1 (MVP): Login, Session-Liste, Streaming-Chat, Profile 2026-10-21 13:03:49 +00:00
build.gradle.kts Hermes Conduit Android v1 (MVP): Login, Session-Liste, Streaming-Chat, Profile 2026-10-21 13:03:49 +00:00
gradle.properties Hermes Conduit Android v1 (MVP): Login, Session-Liste, Streaming-Chat, Profile 2026-10-21 13:03:49 +00:00
gradlew Hermes Conduit Android v1 (MVP): Login, Session-Liste, Streaming-Chat, Profile 2026-10-21 13:03:49 +00:00
gradlew.bat Hermes Conduit Android v1 (MVP): Login, Session-Liste, Streaming-Chat, Profile 2026-10-21 13:03:49 +00:00
README.md Hermes Conduit Android v1 (MVP): Login, Session-Liste, Streaming-Chat, Profile 2026-10-21 13:03:49 +00:00
settings.gradle.kts Hermes Conduit Android v1 (MVP): Login, Session-Liste, Streaming-Chat, Profile 2026-10-21 13:03:49 +00:00

Hermes Conduit — Android

Nativer Android-Client für einen self-hosted Hermes Agent über das Dashboard-Gateway. Android-Pendant zur iOS-App Hermes Conduit (SwiftUI, MIT) — das RPC-Protokoll ist 1:1 von dort portiert.

UI-Sprache: Deutsch. Dark Theme (Hermes-Look) ist Standard.

Features (v1, MVP)

  • Login: Host, Port, Benutzername, Passwort (Host/Port vorbefüllt mit 100.64.0.8 / 9119). Zugangsdaten werden verschlüsselt via EncryptedSharedPreferences (Android Keystore) gespeichert; Auto-Login beim nächsten Start.
  • Session-Liste: Titel, ID, Nachrichtenzahl, Suche, Pull-to-Reload via Refresh-Button, neue Session anlegen.
  • Chat: Streaming-Antworten (Live-Deltas), Markdown-Rendering mit Syntax-Highlighting für Code-Blöcke, Tool-Aufruf-Zeilen, Abbruch-Button, Verlauf via session.resume.
  • Profile: Anzeige und Wechsel der Hermes-Profile (profiles.list), Session-Liste lädt profil-scoped neu.
  • Kontext-Auslastung: Fortschrittsbalken im Chat-Header aus session.usage/context.update-Events.

Nicht in v1: Voice, Push, Kanban, Attachments/Bilder, OAuth, Approval/Clarify-Dialogs (Server-Requests werden mit -32601 abgelehnt, damit Tools nicht hängen).

Build

Voraussetzungen: JDK 17, Android SDK (cmdline-tools, platform-35, build-tools 35.0.0).

export ANDROID_HOME=/root/android-sdk   # oder eigenes SDK; alternativ local.properties mit sdk.dir
./gradlew assembleDebug
# APK: app/build/outputs/apk/debug/app-debug.apk

Architektur

app/src/main/java/de/derittyp/hermesconduit/
├── core/                    # Android-frei (reines JVM+OkHttp+kotlinx.serialization) → JVM-testbar
│   ├── AuthClient.kt        # HTTP-Auth: password-login, /api/auth/me, ws-ticket, CookieJar
│   ├── HermesClient.kt      # JSON-RPC 2.0 über WS-Textframes, Event-Parser, RPC-Methoden
│   ├── Models.kt            # StreamEvent, SessionSummary, ChatMessage, RpcException
│   └── Json.kt              # kotlinx.serialization Helfer
├── AppViewModel.kt          # StateFlow-basierter UI-State, Event-Kollektion
├── CredentialStore.kt       # EncryptedSharedPreferences (Keystore)
├── MainActivity.kt          # Compose Navigation (login/sessions/chat)
└── ui/                      # LoginScreen, SessionsScreen, ChatScreen, MarkdownMessage, Theme

Auth-Flow (verifiziert gegen Live-Dashboard am 2026-10-21)

  1. POST /auth/password-login — JSON {username, password, provider:"basic"} → {"ok":true} + Set-Cookie: hermes_session_at, hermes_session_rt, hermes_session_provider
  2. GET /api/auth/me (mit Cookies) → {"user_id":"...", ...}
  3. POST /api/auth/ws-ticket (mit Cookies) → {"ticket":"...", "ttl_seconds":30} (single-use)
  4. WebSocket: ws://<host>/api/ws?ticket=<ticket>

Das Refresh-Cookie hat 30 Tage TTL; der Server refresht das Access-Cookie transparent. Die App speichert v1 keine Cookies, sondern loggt bei Bedarf erneut ein (Credentials liegen verschlüsselt vor). Ablauf der Session → Fehler in der UI → erneutes Einloggen.

RPC-Protokoll (Port von iOS Conduit/Services/HermesClient.swift)

  • Frames: JSON-RPC 2.0 Textframes. Client-Requests mit Integer-id; Responses matchen die id.
  • Events: {"method":"event","params":{"type":...,"session_id":...,"payload":{...}}}
  • Server→Client-Requests (method + String-id): v1 lehnt alle mit -32601 ab.
  • Auf gateway.ready antwortet der Client mit client.capabilities {server_requests: true}.
  • Nicht-default-Profil: profile-Param wird an jede scoped RPC gehängt.

Genutzte Methoden: session.list, session.resume (cols:96, source:"android", inline_images:false), session.create, session.title, session.interrupt, prompt.submit (session_id, text, surface), profiles.list, session.delete (optional, nur zum Aufräumen von Test-Sessions).

ID-Modell (live verifiziert): session.list liefert die stored id (z. B. 20261021_130231_e692eb), session.resume/session.delete verlangen sie. session.create liefert beide (session_id = kurze Runtime-id, stored_session_id). prompt.submit/session.title akzeptieren die Runtime-id; die Resume-Antwort liefert sie als session_id zurück — die App nutzt nach dem Resume die Runtime-id für Sends und zum Matchen von Streaming-Events. session.delete verweigert aktive Sessions (4023) und doppelte Titel werden mit 4022 abgelehnt.

Streaming-Events: message.start/delta/complete, tool.start/tool.complete, session.busy, session.usage/context.update (Kontext-Balken), sessions.changed (triggert Reload, 800 ms debounced).

Live-Test

Der Netzwerk-Core ist bewusst Android-frei und damit auf der JVM gegen das echte Dashboard testbar:

HERMES_TEST_PASSWORD='<passwort>' ./gradlew :app:testDebugUnitTest --tests '*LiveGatewayTest'

Der Test (ohne Env-Var automatisch übersprungen, Credentials nie im Repo): login → me → ws-ticket → connect → session.list → profiles.list → session.resume (readonly) → neue Test-Session conduit-android-test anlegen → eine kurze Nachricht senden (startet den echten Agenten, kostet Tokens!) → Streaming-Antwort abwarten → Verlauf prüfen → Test-Session löschen (falls der Gateway session.delete kann, sonst bleibt sie bestehen). Zusätzlich Negativtest: falsches Passwort schlägt fehl.

Wichtig: Nie in bestehenden Sessions (z. B. WhatsApp-Hauptsession) Nachrichten senden.

Bekannte TODOs für v2

  • Cookie-Persistenz + transparentes Session-Refresh statt erneutem Passwort-Login
  • Approval-/Clarify-Dialoge (Server→Client-Requests beantworten statt ablehnen)
  • Attachments/Bilder (image.attach_bytes, REST-History /api/sessions/{id}/messages für paginierte History statt Full-Resume)
  • REST-History-Hydration wie iOS (bounded pages) statt komplettem Transcript im Resume
  • Voice, Push-Benachrichtigungen, Kanban-Tab, OAuth (RFC 8252 Native Flow)
  • Reconnect-Logik mit Backoff bei Socket-Verlust
  • Release-Signing, adaptive Icons, Edge-to-Edge-Padding-Feinschliff

Lizenz

MIT (wie die iOS-Referenz-App).