* fix(#472): refactor ChatViewModelTest to use Fake* + Turbine Replace Mockk-heavy test setup with FakeChatPersistenceRepository (+ in-memory FakeChatMessageDao) and add Turbine for Flow assertions. Changes: - Add Turbine v1.2.0 dependency (libs.versions.toml + build.gradle.kts) - Create FakeChatMessageDao – in-memory DAO with MutableStateFlow - Create FakeChatPersistenceRepository – delegates to FakeDao - ChatViewModel: make repo injectable (@VisibleForTesting lateinit var) - Rewrite ChatViewModelTest (751 lines vs 1,212): clearer helpers, Turbine test for streaming Flow sequence, same 29 tests with identical assertions, ~38% line reduction Test: ktlint --format passes clean * fix(#472): address review — constructor injection, ConcurrentHashMap, MockWebServer tests Addresses all feedback from Sourcery review on PR #474: Constructor injection (was lateinit var): - Move repo to ChatViewModel primary constructor with default - Remove @VisibleForTesting lateinit var + init block assignment - Tests pass repo directly via constructor Simplify FakeChatMessageDao: - Replace manual synchronized(lock) with ConcurrentHashMap - Same thread safety, 48% less code, no boilerplate lock object New MockWebServer tests: - HermesApiServiceMockWebServerTest: 6 tests covering getStatus, getSessions, getSessionMessages + edge cases (null fields, empty lists, slash-encoded session IDs) * fix: CI compilation errors — open class, TestScope receiver, runBlocking Three compilation errors blocking CI: - ChatPersistenceRepository was final, but FakeChatPersistenceRepository extends it → make it open class - createViewModelWithSession() called advanceUntilIdle (TestScope ext) and emit() (suspend) without a TestScope receiver or coroutine ctx → make it fun TestScope.xxx() and use tryEmit() for non-suspend emission - HermesApiServiceMockWebServerTest called suspend API methods from regular @Test methods → wrap each test in runBlocking * fix: test runtime failures — suspend helper + fix JSON escaping - Use so emit() (suspend) is valid and events are delivered synchronously instead of buffered via tryEmit() which was causing 17 test failures - Fix MalformedJsonException in MockWebServer test by using plain string content instead of unescaped JSON-within-JSON * fix: correct req-id values in ChatViewModelTest — SESSION_CREATE is req-id-3 * fix(test): correct ChatViewModelTest assertions for Fakes+Turbine refactor - testMessageStreamingFlow: collect streamingState via Turbine (ThinkingDelta/MessageToken mutate streamingState, not uiState) - testToolExecution_finalizesPreviousStreamingMessage: account for messages[0] = "Session created" system message - testMessageStart_finalizesPreviousStreamingMessage: same index offset (messages[1] is the finalized stream) - testClarifyRequestCustomResponse: options is emptyList(), not null; message count is 3 (session-create + user clarify reply) Fixes CI failures introduced in PR #474. --------- Co-authored-by: M57 <hy4ri@users.noreply.github.com>
Hermes Mobile
Native Android companion app for your Hermes AI agent.
Overview
Hermes Mobile is the native Android client for Hermes Agent. It connects securely to your local Hermes gateway (REST API and WebSocket TUI Gateway) over LAN, giving you pocket control over your AI assistant.
Features
- Real-Time Chat: Message your agent with Room-backed local database history.
- System Config: Manage active profiles, installed skills, plugins, and LLM model selections.
- Operations: Stream and filter live logs, manage cron jobs, edit environment keys, and test webhooks.
- Gateway Status: Monitor WebSocket connection, MCP servers, and messaging channel status.
- Productivity: View and manage tasks via integrated Kanban boards and track agent milestones.
- Modern UX: Native Material You design supporting dynamic color theming, scroll-aware TopBar, and pull-to-refresh.
Quick Start
Prerequisites
- JDK 21+ (required for Kotlin compilation and the Gradle toolchain)
- Android Studio (Ladybug+) or a Nix development environment
Build & Deploy
- Clone the repository:
git clone https://github.com/Hy4ri/hermes-mobile.git cd hermes-mobile - Build the debug APK:
./gradlew assembleDebug - Install on your emulator/device:
adb install app/build/outputs/apk/debug/app-debug.apk
Note: For release builds, ensure keystore environment variables (KEYSTORE_PATH, KEYSTORE_PASSWORD, KEY_ALIAS, KEY_PASSWORD) are configured, or let the GitHub Actions release workflow handle it on tag push (v*).
Authentication
Once the app is installed, you need to point it at your Hermes gateway. The app auto-detects which auth mode the dashboard is using — just fill in the fields it shows.
1. Start the dashboard
On your host machine, start the dashboard:
hermes dashboard # loopback (127.0.0.1:9119) — no auth needed
hermes dashboard --host 0.0.0.0 # LAN — requires auth
For LAN access, configure credentials in ~/.hermes/config.yaml:
dashboard:
basic_auth:
username: admin # pick your own
password: hermes # pick your own
2. Connect the app
Tap Sign in on the landing screen and enter the dashboard host and port. The app probes the dashboard and reveals the fields you need:
| Auth mode | When | What you fill |
|---|---|---|
| Token only | Dashboard on same machine (loopback) | Token — grab from ~/.hermes/dashboard-token.txt or ~/.hermes/.env (HERMES_DASHBOARD_SESSION_TOKEN). The app can also auto-extract it from the dashboard page |
| Basic auth | Dashboard on LAN with password gate | Username + Password (default admin / hermes). The app logs in, gets a session cookie, and mints a WebSocket ticket automatically |
The app communicates over plain HTTP — it's designed for trusted local networks only. Do not expose your Hermes gateway to untrusted networks.
Connection profiles
Have multiple gateways? Switch between them in Settings → Connection profiles. Each profile stores its own host, port, and token — just tap to swap.
Pairing (admin)
The Pairing screen lets you approve or revoke agents and services that are trying to connect to your gateway, such as Telegram or Discord sessions.
Project Structure
app/src/main/java/com/m57/hermescontrol/
├── data/ # Local (Room, EncryptedSharedPreferences) & Remote (Retrofit, OkHttp WS)
├── notification/ # Foreground service for message notifications
├── theme/ # Material You design system, status colors, spacing, and typography
└── ui/ # Compose screens (Chat, Settings, Profiles, Kanban, etc.) + Navigation
Tech Stack
- Language: Kotlin 2.4.0 with KSP compiler plugin
- UI & Layout: Jetpack Compose (BOM 2026.03.01) & Material 3 / Material You
- Navigation: Navigation3 (Compose-first Routing)
- Networking: Retrofit 3.0.0, OkHttp 5.4.0, Gson 2.14.0
- Database: Room 2.7.1 with SQLCipher encryption
- Security:
EncryptedSharedPreferences(AES256-GCM) - Formatting:
ktlintstyle rules (checked automatically in CI)
Contributing
Contributions are welcome! Please read CONTRIBUTING.md for our branch workflow, code style guidelines, and PR checklist.
For developer-specific details, code conventions, and project architecture notes, refer to AGENTS.md.
License
This project is licensed under the Apache License, Version 2.0. See the LICENSE file for details. renamed '/tmp/hermes-snap-800bd6e0cc89.sh.tmp.17855' -> '/tmp/hermes-snap-800bd6e0cc89.sh'