docs: v1.0.0 release prep
- CHANGELOG: fold the [Unreleased] open/save-attachments + cold-start-flash entries into [1.0.0] (the tag isn't cut yet; it's all release-day work). - DEVLOG: add the open/save + cold-start session entry with on-device verify. - CLAUDE.md: Key Files entries for MediaSaver / ChatImageViewer / ChatImageContent and the InboundAttachmentCard long-press menu. - README / RELEASE_NOTES / whats_new / play-store listing / privacy / security / user-docs: 1.0.0 release-prep refresh (standard-first story, version pins, branding). - Assets: regenerated play-store feature graphic (RelayRefresh indigo, Play- accurate trio) via new scripts/gen-feature-graphic.mjs; chat demo poster jpg -> png. - Tooling: pnpm lockfile + workspace for the user-docs build. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
37a24c00fa
commit
d433d09906
+15
-1
@@ -6,11 +6,23 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/), and this
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.0.0] - 2026-06-13
|
||||
|
||||
### Added
|
||||
|
||||
- **Open & save chat images and attachments.** Tapping an image in chat — a generated/inline image in an assistant reply or an inbound attachment — now opens a full-screen viewer with pinch-to-zoom and pan (double-tap to toggle 1×/2.5×) and **Share** / **Save** controls. Save lands in `Pictures/Hermes-Relay` with no storage permission on Android 10+ (MediaStore scoped storage); older versions and any failure path fall back to the system share sheet. Non-image attachment cards gained a **long-press menu** — Open / Share / Save to device (files go to `Download/Hermes-Relay`) — while a tap still opens externally as before. Saved files preserve the original bytes (read back from the cached source, never a re-encode), and a magic-byte sniff fixes the extension for remote images that arrive without a usable content-type. (`util/MediaSaver.kt`, `ui/components/ChatImageViewer.kt`)
|
||||
|
||||
- **Gateway chat transport — live thinking (ADR pending).** Chat can now ride the upstream dashboard's `/api/ws` WebSocket (the `tui_gateway` JSON-RPC surface the official hermes-desktop client speaks) instead of the API-server SSE paths. The gateway is the only vanilla-upstream surface that streams reasoning live (`reasoning.delta`/`thinking.delta`), so the existing Thinking block + sphere thinking state finally light up *during* generation instead of staying dark through a reasoning model's 50–70s think phase. "Auto" endpoint resolution prefers the gateway when the dashboard is reachable and Manage is signed in; every failure (sign-in expired, old server build, attachments, socket loss before submit) falls back to the SSE endpoints per turn. New Chat Settings endpoint option **Gateway** with a sign-in hint; interactive server asks (approval/clarify/sudo/secret) surface as a system notice — answering them from the phone is a follow-up. Gateway sessions persist into the same session DB the drawer lists via `/api/sessions`.
|
||||
|
||||
- **Gateway desktop-parity wave.** The gateway transport now carries most of what the official hermes-desktop client can do: **image attachments** upload natively over the socket (`image.attach_bytes`, with a legacy-name fallback; non-image files still ride the SSE fallback); **steer** — sending while a gateway turn streams injects guidance into the running turn (`session.steer`), auto-queueing with an honest caption when the agent isn't between tools; **interactive asks become actionable cards** — approval Approve/Deny, clarify choice-chips + free text, sudo with hold-to-confirm + Deny and a live countdown, secret with masked entry + Skip (values never logged or synced); **edit & resend** on your own messages rewinds the server conversation (gateway-only, guarded mid-stream and on very long sessions); **live subagent lanes** under the streaming bubble (per-task guide rail, compact tool rows, auto-collapse with status); **tool "preparing" state** while the model writes tool arguments; **context-window meter** (silent 2dp strip under the header until 50%, amber at 75%, danger at 90%, "· NN% ctx" subtitle from 85%); **server slash commands** merged into the autocomplete (catalog from the gateway, `slash.exec`/`command.dispatch` routing with output as system notices); and **turn-complete notifications** when the app is backgrounded (new channel, tap opens the chat, Chat Settings toggle, POST_NOTIFICATIONS added to the main manifest).
|
||||
- **Gateway desktop-parity wave.** The gateway transport now carries most of what the official hermes-desktop client can do: **attachments** upload natively over the socket, routed by type exactly as the desktop client routes them — images via `image.attach_bytes` (with a legacy-name fallback), PDFs via `pdf.attach` (rendered to vision tiles server-side), and any other file via `file.attach` (staged as an `@file:` workspace artifact the agent's file tools can read), matching the hermes-desktop client. Turns that fall back to (or resolve to) an SSE endpoint — which can't carry files on a vanilla-upstream server — now post a visible in-chat notice naming what couldn't be sent instead of dropping it silently (the message still sends as text); **steer** — sending while a gateway turn streams injects guidance into the running turn (`session.steer`), auto-queueing with an honest caption when the agent isn't between tools; **interactive asks become actionable cards** — approval Approve/Deny, clarify choice-chips + free text, sudo with hold-to-confirm + Deny and a live countdown, secret with masked entry + Skip (values never logged or synced); **edit & resend** on your own messages rewinds the server conversation (gateway-only, guarded mid-stream and on very long sessions); **live subagent lanes** under the streaming bubble (per-task guide rail, compact tool rows, auto-collapse with status); **tool "preparing" state** while the model writes tool arguments; **context-window meter** (silent 2dp strip under the header until 50%, amber at 75%, danger at 90%, "· NN% ctx" subtitle from 85%); **server slash commands** merged into the autocomplete (catalog from the gateway, `slash.exec`/`command.dispatch` routing with output as system notices); and **turn-complete notifications** when the app is backgrounded (new channel, tap opens the chat, Chat Settings toggle, POST_NOTIFICATIONS added to the main manifest).
|
||||
|
||||
- **Chat transport latency tracing.** Each chat turn now emits one comparable `TurnLatency` INFO line per transport so a perceived "slower than hermes-desktop on the same backend" can be diagnosed from logcat instead of by feel. Gateway turns log `turn[gateway] warm|cold connect@… session@… submit@… ttfe@… ttft@… done@…` (plus a `ticket=…ms ws=…ms` split on cold connects); the SSE paths (`sessions`/`completions`/`runs`) log `ttfe`/`ttft`/`done`. `ttft` — time to first *visible* token, reasoning or text — is the metric that exposes the SSE reasoning dead-air vs the gateway's live reasoning. Durations only, never message content (`util/TurnLatencyTracer.kt`).
|
||||
|
||||
- **Gateway warm-start + cleaner history (acting on the tracing above).** On-device timings showed a *warm* gateway turn reaches first token in ~50 ms, while a *cold* one pays ~4 s of `session.resume`/`session.create` before the prompt is even submitted — the entire "feels slower than desktop" gap. So: the chat surface now **pre-warms the gateway** (connect + resume the current session) on foreground/visibility when the gateway is the resolved transport, moving that cost off the "I pressed send" path the way desktop's connect-at-launch does; and the **background-close grace went 30 s → 120 s** so a quick app-switch returns to a still-warm socket instead of a cold rejoin. (It never creates a session on pre-warm — a brand-new chat's first `session.create` stays on its first real send so the session list isn't littered with empties.) Separately, the gateway **no longer prepends a phone-context preamble** to the user's message: `prompt.submit` is bare text with no system-message slot, so it persisted into the session transcript (ugly on history reload and visible from desktop), and the protocol has no invisible per-turn context channel to match. Phone context still rides the SSE `systemMessage` invisibly and the on-demand `android_phone_status` tool.
|
||||
|
||||
- **Keep connected in background (opt-in, both flavors).** A new off-by-default Chat Settings toggle holds the gateway chat socket open while the app is backgrounded, via a persistent foreground-service notification — so returning to a long-backgrounded conversation skips even the warm rejoin. This is the Home-Assistant-class persistent-connection pattern (the ongoing "connected" notification its Companion app ships on Play). The service (`specialUse` foreground type) only holds the process up; the socket stays open because the client's background-close timer is suppressed while the toggle is on. Disconnect from the notification's action or the toggle; swiping the app from recents stops it too. Ships on **both flavors including Google Play** — `specialUse` is the honest type for an always-on connection (`dataSync` is force-stopped after a 6h/day cap on SDK 35) and requires a one-time Play Console foreground-service declaration at submission. (`network/GatewayKeepAliveService.kt`, `data/GatewayKeepAlivePrefs.kt`)
|
||||
|
||||
- **Seamless connection handoff + slide-down status toast.** Network route handoffs (LAN↔Tailscale) and reconnects no longer repaint or reload the chat — it keeps its messages, scroll, and in-flight stream while the API client is swapped underneath. (The reload effect was keyed on the client *instance*, which churns on every handoff; it now keys on connection/profile/session identity, and a client swap takes a reference-only `updateApiClient` path instead of re-initializing.) The connection-status indicator is now a floating, in-theme **toast** that slides down from the top *over* the content — with a spinner while a switch/handoff is in progress — instead of a banner that pushed the whole UI down to make room. Tap to act, swipe up to dismiss (`ui/components/ConnectionStatusToast`). The **update-available** banner got the same slide-down overlay treatment (the two stack in one top overlay when both apply), so an available update no longer resizes/cuts the UI either. The unattended-access safety strip stays inline — it's a steady-state indicator that shouldn't float over content or be dismissible.
|
||||
|
||||
- **Chat input bar redesigned (Telegram-clean).** The slash button is gone (typing `/` still opens autocomplete; long-press `+` opens the full command palette), the text field is a pill, and one trailing button morphs between Send / Voice / Stop / Steer / Queue with a caption explaining what send will do mid-turn. Voice entry now uses a waveform glyph with a one-shot "Live voice conversation" hint and an amber needs-setup badge instead of a half-dimmed mic.
|
||||
|
||||
@@ -98,6 +110,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/), and this
|
||||
|
||||
### Fixed
|
||||
|
||||
- **No more "Connect to Hermes" flash on cold start.** The chat empty-state could briefly show the loud **Connect to Hermes** call-to-action during app launch while the connection store was still hydrating from disk — an empty store and a not-yet-loaded store looked identical. The empty-state now reads a three-way gate (`ConnectionStore.isHydrated` → `ConnectionViewModel.chatConnectState`): a quiet **"Connecting to Hermes…"** spinner (with a low-key "Manage connections" escape hatch) while hydrating or while a configured connection is coming up, and the connect CTA *only* once hydration confirms nothing is configured.
|
||||
|
||||
- **App-start UI freeze (frozen sphere) from Keystore lock contention.** Cold starts could freeze the UI for many seconds (logcat: `Skipped 1386 frames`, `Davey! duration=11596ms`): every `EncryptedDashboardCookieStore` eagerly built its Keystore-backed prefs in its constructor — a 1–4 s operation on StrongBox devices that serializes through a process-global Tink lock — and several code paths (Manage section loads, connection validation, the Manage pre-warm) each constructed their own instance, stacking multi-second lock holds that main-thread keystore users then queued behind. The store now builds lazily on first cookie access (always an I/O thread), all dashboard-surface consumers share one cached instance per connection, and the pre-warm uses a single client plus the shared store for its whole sweep instead of one of each per section.
|
||||
|
||||
- **"Re-check" / "Use now" no longer fail silently.** When every saved route failed its probe, the user-triggered re-probe early-returned without publishing anything: the Routes card sat on "Current: Resolving" forever (showing the internal relay URL underneath, which read as "stuck on the internal route") with zero feedback. The probe now always publishes its outcome, the card states "No route reachable — using saved URL …" explicitly, and per-route rows show why each candidate failed. The old 100 ms post-probe delay — always shorter than a real resolve, leaving the follow-up health checks pointed at the stale route — is replaced by actually awaiting the resolve.
|
||||
|
||||
@@ -179,7 +179,9 @@ hermes-android/
|
||||
| `viewmodel/ConnectionViewModel.kt` | Dual connection model (API + relay); `resolveStreamingEndpoint()`; derived `relayUiState` flow + `markPaired` hook stamp the active Connection |
|
||||
| `viewmodel/RelayUiState.kt` | Shared sealed state for the relay row — 5 cases + `asBadgeState()` / `statusText()` extensions; 5s grace window before Stale |
|
||||
| `network/HermesApiClient.kt` | Direct HTTP/SSE — `sendRunStream()`, `sendChatStream()`, `probeCapabilities()` |
|
||||
| `network/GatewayChatClient.kt` | Gateway chat transport — JSON-RPC over dashboard `/api/ws` (tui_gateway); live `reasoning.delta`; fresh ws-ticket per connect; per-turn SSE fallback via `onPreflightFailure` |
|
||||
| `network/GatewayChatClient.kt` | Gateway chat transport — JSON-RPC over dashboard `/api/ws` (tui_gateway); live `reasoning.delta`; fresh ws-ticket per connect; per-turn SSE fallback via `onPreflightFailure`; `prewarm()` (connect+resume off the send path); `setKeepAliveInBackground()` suppresses the 120s idle-close |
|
||||
| `network/GatewayKeepAliveService.kt` | Opt-in `specialUse` foreground service (BOTH flavors; declared in main manifest; Play needs a Console FGS declaration) holding the process up so the gateway socket survives background/Doze; driven by ConnectionViewModel from the `KEY_GATEWAY_KEEP_ALIVE` toggle; stops on task-removal |
|
||||
| `data/GatewayKeepAlivePrefs.kt` | Shared `KEY_GATEWAY_KEEP_ALIVE` pref key + `Context.setGatewayKeepAlive()` — used by ConnectionViewModel (StateFlow/setter) and the FGS Stop action |
|
||||
| `network/GatewayEventMapper.kt` | Pure-JVM gateway event→callback mapping for one turn; unknown event types silently ignored; tui_gateway usage-key translation |
|
||||
| `network/GatewayModels.kt` | `GatewayAvailability`, `ActiveTurnHandle`, `GatewayTurnCallbacks` (all members REQUIRED — forces dispatchOn main-thread wrap), `GatewayAsk`, `GatewaySubagentEvent`, `resolveStreamingEndpointPreference()` |
|
||||
| `ui/components/ChatInputBar.kt` | Redesigned input bar — pill field, one trailing slot morphing Send/Voice/Stop/Steer/Queue, no slash button (long-press + opens palette) |
|
||||
@@ -223,12 +225,16 @@ hermes-android/
|
||||
| `user-docs/.vitepress/theme/components/SphereMark.vue` | Docs-site sphere embed — imports `preview/web/sphere.js` directly; autonomous fbm drift + pointer-proximity gaze/state blend; `<ClientOnly>` + `IntersectionObserver` + `prefers-reduced-motion` aware |
|
||||
| **App — Media + Notifications** | |
|
||||
| `util/MediaCacheWriter.kt` | `cacheDir/hermes-media/` LRU writer; returns FileProvider URIs |
|
||||
| `ui/components/InboundAttachmentCard.kt` | Discord-style attachment card for images/video/audio/pdf/text/generic |
|
||||
| `util/MediaSaver.kt` | Save/share/open for chat media — MediaStore scoped-storage save (Pictures/Download `Hermes-Relay`, no perms on API 29+; pre-Q → share sheet); FileProvider share staging; remote-byte fetch; magic-byte image-MIME sniff for correct extensions |
|
||||
| `ui/components/ChatImageViewer.kt` | Full-screen image viewer — pinch-zoom/pan (`detectTransformGestures`), double-tap 1×/2.5×, Share/Save/Close; `ChatImageViewerSource` decouples Coil-model/bitmap display from a suspend `bytesProvider` so Save keeps original bytes |
|
||||
| `ui/components/InboundAttachmentCard.kt` | Discord-style attachment card for images/video/audio/pdf/text/generic; image tap → ChatImageViewer, file card long-press → Open/Share/Save menu |
|
||||
| `ui/components/ChatImageContent.kt` | Parses `` out of assistant content; remote http(s) → Coil (tap → ChatImageViewer), server-local/failed → inline "can't render" notice with the path |
|
||||
| `data/HermesCard.kt` | `CARD:{json}` envelope (ADR 26) — type/accent/fields/actions; kotlinx.serialization |
|
||||
| `ui/components/HermesCardBubble.kt` | Rich-card renderer — accent stripe + FlowRow actions + dispatch stamp collapse |
|
||||
| `viewmodel/CardDispatchSyncBuilder.kt` | Twin of VoiceIntentSyncBuilder — synthesizes card dispatches as `hermes_card_action` OpenAI pairs for session memory |
|
||||
| `notifications/HermesNotificationCompanion.kt` | NotificationListenerService; cold-start buffer (50); forwards via ChannelMultiplexer |
|
||||
| `util/RelayErrorClassifier.kt` | `classifyError(Throwable, context) → HumanError`; used by Voice/Chat/Connection |
|
||||
| `util/TurnLatencyTracer.kt` | One `TurnLatency` INFO line per chat turn — `warm/cold` + `connect/session/submit/ttfe/ttft/done@…ms`; gateway + 3 SSE paths use it for desktop-comparable latency diagnosis; durations only |
|
||||
| **Relay — Server** | |
|
||||
| `plugin/relay/server.py` | Canonical relay — WSS + HTTP routes; bridge, media, voice, session, pairing handlers. `handle_pairing_mint` mirrors `pair.py:762` — top-level = API server, `relay.{url,code}` nested |
|
||||
| `plugin/relay/auth.py` | PairingManager, SessionManager, RateLimiter; `math.inf` for never-expire |
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# Hermes-Relay — Dev Log
|
||||
|
||||
## 2026-06-13 — open/save attachments + cold-start connect-button gate
|
||||
|
||||
**Why.** v1.0.0 polish, two asks: (1) let users open/save chat images and other attachments; (2) stop the chat empty-state from flashing the "Connect to Hermes" button on cold start while we're already logged in and just hydrating.
|
||||
|
||||
- **Open/save attachments.** New `util/MediaSaver.kt` — permission-free save via MediaStore scoped storage on **API 29+** (images → `Pictures/Hermes-Relay`, files → `Download/Hermes-Relay`); **pre-Q falls back to the share sheet** (no `WRITE_EXTERNAL_STORAGE` request for a niche path). Share stages bytes into the existing FileProvider `hermes-media/` cache (can't `ACTION_SEND` an `http://` URL). Saves preserve original bytes — read back from the cached `content://` (inbound) or base64 (outbound), not a re-encode — and a magic-byte sniff sets the right extension for remote images with no usable content-type. New `ui/components/ChatImageViewer.kt` — full-screen `Dialog` with `detectTransformGestures` pinch-zoom + pan, double-tap 1×/2.5×, Share/Save/Close overlaid past the status bar inset; decoupled display (Coil model OR `ImageBitmap`) from a `suspend bytesProvider()` for save/share. Wired: `ChatImageContent.RemoteChatImage` tap → viewer (remote URL, bytes via OkHttp GET); `InboundAttachmentCard.ImageRender` tap → viewer (bitmap + cached bytes); `FileCardRender` now `combinedClickable` (tap = open external as before, long-press → Open/Share/Save dropdown).
|
||||
- **Cold-start gate.** Root cause: `ConnectionStore` seeds `_connections = emptyList()` / `_activeConnectionId = null` and hydrates DataStore async, so "nothing configured" and "not loaded yet" are indistinguishable — and `chatReady` (client != null && reachable) is false during launch, so the empty-state painted the **Connect to Hermes** CTA for a beat. Fix: `ConnectionStore.isHydrated` StateFlow (flips true in the init `finally`, even on read failure) → `ConnectionViewModel.chatConnectState` (`Connecting` / `Ready` / `NeedsConnection`, seeded `Connecting`). ChatScreen's empty-state switches on it: quiet "Connecting to Hermes…" spinner + low-key "Manage connections" escape hatch while hydrating or while a configured connection comes up; the CTA renders **only** once hydration confirms no connection exists.
|
||||
- **Verified e2e on-device** (USB ADB, `R5CY61QE9CT`). Cold-start gate: force-stop → relaunch shows splash → hydration checklist → chat with history, never the connect CTA. Attachment viewer: had gpt-5.5 echo ``, the inline image rendered, tap → full-screen viewer, **Save** wrote `Pictures/Hermes-Relay/sunset.jpg` (21,590 B; magic-byte sniff turned the `image/*` guess into `.jpg`) with a "Saved to Pictures/Hermes-Relay" toast. Found + fixed one bug mid-verify: the **share** path staged `sunset.bin` (`ensureNamed` mapped the `image/*` wildcard → `.bin`); `stageForShare` now sniffs too — re-verified the share sheet reads "1 image". Watch the comment `image/*` → `image/…`: the literal `/*` in a KDoc opened a nested block comment and swallowed the file (memory: `kotlin-nested-comments-and-pipefail`). `lintGooglePlayDebug` green; `:app:assembleGooglePlayDebug` green.
|
||||
|
||||
## 2026-06-13 — gateway turn survival across network blips + chat UI (images, scroll, empty bubble)
|
||||
|
||||
**Why.** v1.0.0 polish. Bailey hit a gateway chat turn that "restarted/reloaded" and replied to an earlier message after switching to Settings mid-tool-call. Root-caused, fixed, and verified e2e on-device (USB ADB, forced Wi-Fi drop). Then folded in image rendering, scroll, and the empty-bubble polish.
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<h1 align="center">Hermes-Relay</h1>
|
||||
|
||||
<p align="center">
|
||||
<strong>Your self-hosted Hermes agent, native on your phone.</strong><br>
|
||||
Chat, voice, and full agent management over your own infrastructure —<br>
|
||||
plus an experimental desktop CLI that gives the agent hands on your computer.
|
||||
<strong>Your Hermes agent, native on your phone.</strong><br>
|
||||
Chat, voice, and full agent management — it runs on your machine and lives on your devices —<br>
|
||||
plus an experimental CLI that gives the agent hands on any machine you pair.
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
|
||||
+43
-25
@@ -1,44 +1,62 @@
|
||||
# Unreleased
|
||||
# Hermes-Relay-Android v1.0.0
|
||||
|
||||
## Changed
|
||||
**Release Date:** June 13, 2026
|
||||
**Since v0.8.1:** The 1.0 milestone — a rechromed app, a first-class standard (no-plugin) path, live-thinking gateway chat, and a broad polish pass.
|
||||
|
||||
- Android now defaults to a standard Hermes layout with **Chat**, **Manage**, and **Settings** in bottom navigation. Terminal and Bridge remain available under **Settings → Power tools** and through existing routes.
|
||||
- Added a native **Manage** surface backed by the Hermes dashboard/admin API for Skills, Cron, MCP servers/catalog, Profiles, Models, and Config. It supports dashboard sign-in, common management actions, cron run details, and read-only profile SOUL details without requiring relay pairing.
|
||||
- Relay-only features now show a consistent **Requires pairing** / **Pair to unlock** gate when the active connection is not paired.
|
||||
- Connections now model API auth, dashboard auth, and relay pairing separately. Dashboard URLs derive from the API host on port `9119` by default.
|
||||
|
||||
---
|
||||
|
||||
# Hermes-Relay-Android v0.8.1
|
||||
|
||||
**Release Date:** May 26, 2026
|
||||
**Since v0.8.0:** A focused patch fixing a voice-mode crash. No new features.
|
||||
|
||||
v0.8.1 is a patch release. If you don't use voice mode with barge-in enabled, v0.8.0 is unaffected — but updating is still recommended.
|
||||
v1.0.0 is the first stable release. The headline is that a **plain, unmodified Hermes agent is now enough**: chat, Manage, and voice all work against vanilla upstream with no relay plugin. The relay plugin is now purely additive (phone control, terminal, notification companion, extra voice engines).
|
||||
|
||||
---
|
||||
|
||||
## Download
|
||||
|
||||
v0.8.1 ships in two Android build flavors. APK and AAB filenames are version-tagged:
|
||||
v1.0.0 ships in two Android build flavors. APK and AAB filenames are version-tagged:
|
||||
|
||||
| Flavor | File | Who it's for |
|
||||
|---|---|---|
|
||||
| Google Play | `hermes-relay-0.8.1-googlePlay-release.aab` | Upload this Android App Bundle to Play Console. It has no AccessibilityService, screen reading, screenshots, gestures, SMS/calls, contacts/location, overlays, wake locks, or unattended phone control. |
|
||||
| sideload | `hermes-relay-0.8.1-sideload-release.apk` | Direct-install APK for full Device Control. Installs as `com.axiomlabs.hermesrelay.sideload`. |
|
||||
| googlePlay APK | `hermes-relay-0.8.1-googlePlay-release.apk` | Parity/testing artifact. |
|
||||
| sideload AAB | `hermes-relay-0.8.1-sideload-release.aab` | Parity/testing artifact. |
|
||||
| Google Play | `hermes-relay-1.0.0-googlePlay-release.aab` | Upload this Android App Bundle to Play Console. It has no AccessibilityService, screen reading, screenshots, gestures, SMS/calls, contacts/location, overlays, or unattended phone control. |
|
||||
| sideload | `hermes-relay-1.0.0-sideload-release.apk` | Direct-install APK for full Device Control. Installs as `com.axiomlabs.hermesrelay.sideload`. |
|
||||
| googlePlay APK | `hermes-relay-1.0.0-googlePlay-release.apk` | Parity/testing artifact. |
|
||||
| sideload AAB | `hermes-relay-1.0.0-sideload-release.aab` | Parity/testing artifact. |
|
||||
|
||||
Verify integrity with `SHA256SUMS.txt` from the same release. See the [Sideload guide](https://codename-11.github.io/hermes-relay/guide/getting-started.html#sideload-apk) for APK install steps.
|
||||
|
||||
---
|
||||
|
||||
## Fixed
|
||||
## Highlights
|
||||
|
||||
### Voice mode crash with barge-in on legacy TTS playback
|
||||
### Standard path is first-class — no plugin required
|
||||
|
||||
Starting voice mode with **barge-in enabled** while the relay served audio over the legacy `/voice/synthesize` path crashed the app the instant the agent began speaking — the first word or two played, then the app died with `Player is accessed on the wrong thread`.
|
||||
Chat, Manage, and voice now work against an unmodified upstream Hermes agent. Chat streams over the API server; Manage and voice use the Hermes dashboard with a single sign-in. The relay plugin stays optional and only adds power tools.
|
||||
|
||||
The barge-in listener reads the audio session id from a background thread to attach the echo canceller, but Media3's `ExoPlayer` is thread-confined and throws when its `audioSessionId` getter is read off the main thread. `VoicePlayer.audioSessionId` is now backed by a thread-safe cache populated from main-thread playback callbacks, so it's safe to read from any thread.
|
||||
### Gateway chat transport with live thinking
|
||||
|
||||
This only affected the **opt-in** barge-in feature on the legacy text-to-speech path; the provider-native Realtime Agent and Voice Output paths were never affected.
|
||||
Chat can now ride the upstream dashboard `/api/ws` gateway (the same surface the official hermes-desktop client speaks). It's the only vanilla-upstream path that streams reasoning **live**, so the Thinking block and sphere light up *during* generation instead of after. "Auto" prefers the gateway when the dashboard is reachable and Manage is signed in, and falls back to the SSE endpoints per turn on any failure.
|
||||
|
||||
- **Warm-start + keep-alive.** The app pre-warms the gateway on foreground so the first token lands fast (the cold session-setup cost moves off the send path). An opt-in **Keep connected in background** toggle (both flavors) holds the connection open via a foreground service so a long-backgrounded conversation resumes instantly.
|
||||
- **Attachments at desktop parity.** Images, PDFs, and any other file upload natively over the gateway (`image.attach_bytes` / `pdf.attach` / `file.attach`). Turns that fall back to an endpoint that can't carry a file now post a visible notice instead of dropping it silently.
|
||||
- **Steering, edit & resend, subagent lanes.** Send mid-turn to inject guidance into the running turn; edit your own messages to rewind and regenerate; watch per-task subagent lanes stream under the bubble; a context-window meter warns as the window fills.
|
||||
- **Turn-complete notifications** when the app is backgrounded.
|
||||
|
||||
### Manage parity with the desktop dashboard
|
||||
|
||||
The Manage tab now does what the desktop dashboard does: change models from the full provider catalog, manage provider keys (write-only, masked, reveal), create/edit profiles and SOUL.md, and browse/install/update skills. Manage data is cached to disk so a cold launch renders instantly.
|
||||
|
||||
### Redesigned chat input + seamless connection UX
|
||||
|
||||
A cleaner Telegram-style input bar (pill field, one morphing Send/Voice/Stop/Steer/Queue button, no slash button). Network route handoffs (LAN↔Tailscale) and reconnects no longer repaint or reload the chat, and connection/update status now slide down as in-theme toasts over the content instead of pushing the UI around.
|
||||
|
||||
### Voice
|
||||
|
||||
The provider-native Realtime Agent keeps one session open across turns (follow-ups retain context), and long Hermes runs are promoted to tracked background tasks so the conversation stays responsive and the answer is spoken when it's ready.
|
||||
|
||||
### Docs + branding
|
||||
|
||||
The documentation site was rechromed to the app's cockpit theme and repositioned around the two-path story (just connect → give it hands), with a reworked Android getting-started funnel and a Google Play badge.
|
||||
|
||||
---
|
||||
|
||||
## Upgrade notes
|
||||
|
||||
- **Google Play submission:** the opt-in keep-alive feature adds a `FOREGROUND_SERVICE_SPECIAL_USE` service. Complete the Play Console **Foreground service permissions** declaration for `specialUse` at submission (see `docs/play-store-listing.md`).
|
||||
- **PDF attachments** over the gateway require `poppler-utils` (`pdftoppm`) on the Hermes host; without it, PDF attach reports an error and the message still sends as text.
|
||||
- `appVersionCode` is **12**.
|
||||
|
||||
@@ -1,7 +1,29 @@
|
||||
v0.8.1 - Voice mode crash fix
|
||||
v1.0.0 - The 1.0 release
|
||||
|
||||
Standard path
|
||||
* Chat, Manage, and voice now work on a plain Hermes agent — no relay
|
||||
plugin required. The plugin is optional and only adds power tools.
|
||||
|
||||
Chat
|
||||
* New gateway transport streams the agent's reasoning live, so the
|
||||
Thinking block fills in during generation instead of after.
|
||||
* Warm-start + opt-in "Keep connected in background" make returning to a
|
||||
conversation fast.
|
||||
* Attachments at desktop parity: images, PDFs, and files upload over the
|
||||
gateway. If a connection can't carry a file, you'll see a notice
|
||||
instead of a silent drop.
|
||||
* Steer a running turn, edit & resend your messages, watch subagent
|
||||
lanes, and a context-window meter — plus turn-complete notifications.
|
||||
* Redesigned input bar: pill field, one morphing Send/Voice/Stop button.
|
||||
|
||||
Manage
|
||||
* Models, provider keys, profiles + SOUL.md, and a skills hub — parity
|
||||
with the desktop dashboard. Cached for instant cold-launch.
|
||||
|
||||
Voice
|
||||
* Fixed a crash that could hit voice mode when barge-in was enabled on the
|
||||
legacy text-to-speech path — the agent's first words no longer cut off
|
||||
into a crash. Barge-in is opt-in; the Realtime Agent and Voice Output
|
||||
paths were never affected.
|
||||
* Realtime Agent keeps one session across turns; long runs continue in
|
||||
the background and are spoken when ready.
|
||||
|
||||
Polish
|
||||
* Seamless LAN/Tailscale handoffs (no chat reload), slide-down status
|
||||
toasts, and a broad round of fixes.
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 80 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 224 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 71 KiB |
+57
-57
@@ -4,93 +4,67 @@
|
||||
|
||||
## Short Description (≤80 chars)
|
||||
|
||||
Your self-hosted Hermes AI agent, in your pocket — chat, voice, and control.
|
||||
Your Hermes AI agent, in your pocket — chat, voice, and control.
|
||||
|
||||
## Full Description
|
||||
## Full Description (plain text, no markdown — paste as-is; ≤4000 chars)
|
||||
|
||||
**Hermes-Relay** is the native Android client for the Hermes agent platform. Point it at your own self-hosted Hermes server and chat with your agent, talk to it hands-free, and manage it — models, keys, skills, profiles — from anywhere.
|
||||
Hermes-Relay is the native Android client for the Hermes agent platform. Point it at your own Hermes server and chat with your agent, talk to it hands-free, and manage it — models, keys, skills, profiles — from anywhere.
|
||||
|
||||
Built for developers and AI enthusiasts who run their own Hermes agent instance. This is not a hosted AI service — it is a companion app for your own infrastructure, and it talks only to servers you configure.
|
||||
It's not a hosted AI service. It's a companion app for the Hermes agent you run, and it talks only to the servers you configure.
|
||||
|
||||
━━━ QUICK START ━━━
|
||||
QUICK START
|
||||
|
||||
1. Run hermes-agent with its API server enabled on your computer or home server.
|
||||
2. Install Hermes-Relay and enter your server's address (for example http://192.168.1.100:8642).
|
||||
3. The setup wizard probes what your server supports and shows you a readiness card — then you're talking.
|
||||
3. The setup wizard checks what your server supports and shows a readiness card — then you're talking.
|
||||
|
||||
A plain Hermes install is enough: chat, management, and voice all work without any plugin or extra services.
|
||||
A plain Hermes install is enough. Chat, management, and voice all work with no plugin or extra services.
|
||||
|
||||
━━━ HOW IT WORKS ━━━
|
||||
HOW IT WORKS
|
||||
|
||||
Hermes-Relay connects directly to your Hermes API Server for real-time streaming chat. The Manage tab and voice use your Hermes dashboard with one sign-in. If you also run the optional Hermes relay service, the app can pair by QR code and add power tools: remote terminal, notification companion, media handoff, relay-session management, and additional voice engines.
|
||||
Chat streams directly from your Hermes API Server in real time. Manage and voice use your Hermes dashboard with one sign-in. Run the optional relay service and the app can pair by QR code to add power tools: remote terminal, notification companion, media handoff, relay-session management, and more voice engines.
|
||||
|
||||
━━━ GOOGLE PLAY BUILD ━━━
|
||||
GOOGLE PLAY BUILD
|
||||
|
||||
The Google Play build ships **Hermes Bridge Core** only. It does not include AccessibilityService-based Device Control and cannot read your screen, tap, type, swipe, capture screenshots, send SMS, place calls, access contacts or location, or perform unattended phone control.
|
||||
The Google Play build ships Hermes Bridge Core only. It has no AccessibilityService Device Control: it cannot read your screen, tap, type, swipe, screenshot, send SMS, place calls, or access contacts or location. Device Control is reserved for sideload builds distributed outside Google Play.
|
||||
|
||||
Device Control is reserved for sideload builds distributed outside Google Play.
|
||||
FEATURES
|
||||
|
||||
━━━ FEATURES ━━━
|
||||
◆ Streaming Chat — real-time and token-by-token, with live reasoning, markdown, tool-call visibility, image/PDF/file attachments, mid-turn steering, edit-and-resend, and a searchable command palette.
|
||||
◆ Manage Your Agent — the Hermes dashboard on your phone: switch models from your provider catalog, manage provider keys (masked), edit profiles, and browse, install, and update skills.
|
||||
◆ Voice Mode — talk hands-free using your server's speech providers, no plugin needed. Relay-paired setups add per-profile voices and an experimental realtime engine.
|
||||
◆ Works Away From Home — add a Tailscale or public URL and the app switches routes automatically; when a server is unreachable it tells you what to fix instead of just going red.
|
||||
◆ Sessions — create, switch, rename, and delete chats; message history loads on demand.
|
||||
◆ Multiple Servers & Profiles — connect to more than one server (home and work) and switch in a tap; overlay an agent profile or personality per conversation.
|
||||
◆ Relay Power Tools (optional) — pair by QR code for a remote terminal, relay-session management, and per-feature grants.
|
||||
◆ Notification Companion (optional) — forward notification metadata to your paired relay so your assistant can summarize it. Toggle it anytime in system settings.
|
||||
◆ Stats for Nerds — local-only counters for response timing, token usage, cost, and stream health.
|
||||
◆ Material You — Material 3 dynamic color, light/dark/system themes, and haptics.
|
||||
|
||||
◆ Streaming Chat
|
||||
Chat with your Hermes agent in real time. Responses stream token-by-token with markdown rendering, tool-call visibility, file attachments, and a searchable command palette.
|
||||
|
||||
◆ Manage Your Agent
|
||||
The full Hermes dashboard, native on your phone: switch models from your provider catalog, manage provider keys (write-only and masked), create and edit agent profiles, and browse, install, and update skills.
|
||||
|
||||
◆ Voice Mode
|
||||
Talk to your agent hands-free. Voice works on a plain Hermes install using the speech providers your server is configured with — no plugin needed. Relay-paired setups add per-profile voice providers and an experimental realtime conversation engine.
|
||||
|
||||
◆ Works Away From Home
|
||||
Add your server's Tailscale or public URL and the app switches routes automatically — local network at home, your secure fallback everywhere else. Routes are editable any time, and when a server is unreachable the status tells you what to fix instead of just going red.
|
||||
|
||||
◆ Sessions
|
||||
Create, switch, rename, and delete chat sessions. Message history loads from your Hermes server on demand.
|
||||
|
||||
◆ Multiple Servers, Profiles, and Personalities
|
||||
Connect to more than one Hermes server (home and work, dev and prod) and switch in one tap. Overlay an agent profile or personality per conversation.
|
||||
|
||||
◆ Relay Power Tools (optional)
|
||||
Scan a relay QR code to pair, then open a remote terminal to your Hermes host, manage relay sessions, and control per-feature grants from the app.
|
||||
|
||||
◆ Notification Companion
|
||||
Optional Android notification access lets Hermes-Relay forward posted-notification metadata to your paired relay so your assistant can summarize recent notifications. You enable or revoke this in Android system settings at any time.
|
||||
|
||||
◆ Media Handoff
|
||||
Fetch relay-registered media into chat and share supported media through Android-native flows.
|
||||
|
||||
◆ Stats for Nerds
|
||||
Built-in local analytics show response timing, token usage, cost estimates, and stream health. These counters stay on your phone.
|
||||
|
||||
◆ Material You Design
|
||||
Full Material 3 with dynamic color theming, light/dark/system modes, animated splash screen, and haptic feedback.
|
||||
|
||||
━━━ SECURITY & PRIVACY ━━━
|
||||
SECURITY & PRIVACY
|
||||
|
||||
• API keys and relay tokens are stored in encrypted Android storage
|
||||
• HTTPS is enforced for remote connections
|
||||
• Cleartext is permitted only for localhost/LAN development servers
|
||||
• No telemetry, no ads, no tracking, no third-party analytics SDKs
|
||||
• Notification companion is optional and user-controlled
|
||||
• Microphone is optional and requested only when you use Voice mode
|
||||
• HTTPS is enforced for remote connections; cleartext only for localhost/LAN
|
||||
• No telemetry, ads, tracking, or third-party analytics SDKs
|
||||
• Notification access and the microphone are optional and user-controlled
|
||||
• All app traffic goes only to servers you configure
|
||||
|
||||
━━━ REQUIREMENTS ━━━
|
||||
REQUIREMENTS
|
||||
|
||||
• Android 8.0 or later (API 26+)
|
||||
• A running Hermes agent instance (chat, management, and voice need nothing else)
|
||||
• A running Hermes agent (chat, management, and voice need nothing else)
|
||||
• Optional Hermes relay service for power tools (terminal, notifications, media)
|
||||
• Network access to your server (local network, VPN, or internet)
|
||||
|
||||
━━━ OPEN SOURCE ━━━
|
||||
OPEN SOURCE
|
||||
|
||||
Hermes-Relay is MIT licensed. Source code, documentation, and issue tracking are available on GitHub.
|
||||
Hermes-Relay is MIT licensed. Source, docs, and issue tracking are on GitHub.
|
||||
|
||||
This app is a community project and is not affiliated with or endorsed by NousResearch.
|
||||
|
||||
## Release Notes
|
||||
|
||||
v0.8.1 makes the standard no-plugin setup first-class. Voice now works on a plain Hermes install — one dashboard sign-in unlocks it. The Manage tab reaches parity with the desktop dashboard: models, provider keys, profiles, and a skills hub with install and update. Connection routes are editable on the phone, and remote access is built into the whole journey — a setup field, a readiness card, a smarter "unreachable" diagnosis, and a one-tap Tailscale shortcut.
|
||||
Hermes-Relay 1.0 — a plain Hermes agent is now all you need: chat, Manage, and voice work with no plugin. Chat streams the agent's reasoning live, attaches images, PDFs, and files, and lets you steer a running turn or edit and resend. Manage reaches desktop-dashboard parity (models, keys, profiles, skills). New: keep-connected-in-background, turn-complete notifications, a redesigned input bar, and seamless LAN/Tailscale handoffs.
|
||||
|
||||
## Category
|
||||
|
||||
@@ -104,3 +78,29 @@ Not designed for children.
|
||||
## Tags
|
||||
|
||||
ai, agent, hermes, developer tools, chat, voice, self-hosted, remote, open source
|
||||
|
||||
## Play Console Declarations
|
||||
|
||||
Submission-time declarations the Play Console requires — keep in sync with the merged `googlePlay` manifest.
|
||||
|
||||
### Foreground service permissions
|
||||
|
||||
The Play build declares **`FOREGROUND_SERVICE_SPECIAL_USE`** for `GatewayKeepAliveService`, backing the opt-in **Keep connected in background** feature (off by default). At submission, complete **App content → Foreground service permissions** for `specialUse`:
|
||||
|
||||
- **Use case:** maintains a persistent connection to the user's own Hermes agent server so the assistant stays responsive and delivers replies while the app is backgrounded.
|
||||
- **Why a foreground service:** it's a real-time, user-initiated streaming connection that must survive Doze / background execution limits; `dataSync` is force-stopped after a 6-hour/day cap on Android 15, so `specialUse` is the only fit for "stay connected."
|
||||
- **User control:** off by default; enabled only via *Settings → Chat → Keep connected in background*; shows an ongoing notification with a **Disconnect** action; ends when the app is swiped from recents.
|
||||
- Google usually asks for a short screen recording of the toggle + notification.
|
||||
|
||||
The Play build does **not** declare `FOREGROUND_SERVICE_MEDIA_PROJECTION` or the Device Control accessibility/bridge services — those are sideload-only.
|
||||
|
||||
### Data safety
|
||||
|
||||
No data collection or sharing to declare: no telemetry, ads, or third-party analytics SDKs; all traffic goes only to user-configured servers; credentials are stored in encrypted on-device storage. Mirror the **Security & Privacy** section above when filling the Data safety form.
|
||||
|
||||
### Sensitive / runtime permissions in the Play build
|
||||
|
||||
- `RECORD_AUDIO` — Voice mode, requested at use.
|
||||
- `POST_NOTIFICATIONS` — turn-complete + keep-alive notifications, requested on API 33+.
|
||||
- `CAMERA` — QR pairing / attachments, requested at use.
|
||||
- Notification listener (companion) — user-enabled in system settings.
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# Privacy & Data Handling
|
||||
|
||||
Hermes-Relay is a self-hosted AI agent companion app. It connects only to servers you configure — there are no cloud accounts, hosted backends, ads, or third-party analytics.
|
||||
Hermes-Relay is a companion app for the Hermes agent. It connects only to servers you configure — there are no cloud accounts, hosted backends, ads, or third-party analytics.
|
||||
|
||||
## No External Data Transmission
|
||||
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@ Multi-endpoint pairing (ADR 24) makes "same phone, different networks" a first-c
|
||||
|
||||
See [docs/privacy.md](privacy.md) for the full privacy and data handling policy. Key points:
|
||||
|
||||
- **No external data transmission** — the app connects only to your self-hosted Hermes servers
|
||||
- **No external data transmission** — the app connects only to the Hermes servers you configure
|
||||
- **Local-only analytics** — Stats for Nerds counters are stored in DataStore on-device, never sent externally
|
||||
- **Encrypted credential storage** — API keys and session tokens use EncryptedSharedPreferences (AES-256-GCM, hardware-backed Android Keystore)
|
||||
- **No tracking, ads, or third-party SDKs**
|
||||
|
||||
Generated
+1623
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
||||
allowBuilds:
|
||||
esbuild: true
|
||||
@@ -0,0 +1,97 @@
|
||||
// scripts/gen-feature-graphic.mjs
|
||||
//
|
||||
// Regenerates the Google Play Store feature graphic
|
||||
// (assets/play-store-feature-1024x500.png) from an inline SVG, so the asset is
|
||||
// reproducible instead of a mystery binary. Edit the SVG below (tagline, trio,
|
||||
// palette) and re-run to update the banner.
|
||||
//
|
||||
// Palette: RelayRefresh — base #08090D, electric-indigo accent
|
||||
// (#6E7CFF / #4F5BD5 / #3A44B8), warm-white ink #F4F1E9. Mark = the "Chevron
|
||||
// Compass" from assets/logo.svg, recolored from the legacy purple to indigo.
|
||||
//
|
||||
// Rasterizer: @resvg/resvg-js (Rust resvg — exact pixel dims, no headless
|
||||
// browser). It is not a declared dependency; it is present transitively via
|
||||
// vitepress (the docs toolchain). Run from the repo root so Node resolves it
|
||||
// from the root node_modules:
|
||||
//
|
||||
// node scripts/gen-feature-graphic.mjs
|
||||
//
|
||||
// Play feature-graphic spec: exactly 1024x500 PNG/JPEG, < 15 MB.
|
||||
|
||||
import { Resvg } from '@resvg/resvg-js'
|
||||
import { writeFileSync } from 'node:fs'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const OUT = fileURLToPath(new URL('../assets/play-store-feature-1024x500.png', import.meta.url))
|
||||
|
||||
const svg = `<svg width="1024" height="500" viewBox="0 0 1024 500" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#0B0C16"/>
|
||||
<stop offset="1" stop-color="#08090D"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="glow" cx="50%" cy="22%" r="42%">
|
||||
<stop offset="0" stop-color="#6E7CFF" stop-opacity="0.16"/>
|
||||
<stop offset="1" stop-color="#6E7CFF" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="mark" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stop-color="#8E9BFF"/>
|
||||
<stop offset="1" stop-color="#4F5BD5"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="ghost" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stop-color="#6E7CFF" stop-opacity="0.30"/>
|
||||
<stop offset="1" stop-color="#6E7CFF" stop-opacity="0.04"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<rect width="1024" height="500" fill="url(#bg)"/>
|
||||
<rect width="1024" height="500" fill="url(#glow)"/>
|
||||
|
||||
<g stroke="#6E7CFF" stroke-opacity="0.05" stroke-width="22" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M -40 150 L 110 0 L 260 150"/>
|
||||
<path d="M 764 500 L 914 350 L 1064 500"/>
|
||||
</g>
|
||||
|
||||
<g transform="translate(412,40) scale(0.39)" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<g transform="translate(256,256) scale(1.30) translate(-256,-256)" stroke="url(#ghost)" stroke-width="22">
|
||||
<path d="M 112 188 L 192 268 L 320 268 L 400 188"/>
|
||||
<path d="M 112 116 L 192 196"/>
|
||||
<path d="M 400 116 L 320 196"/>
|
||||
</g>
|
||||
<g stroke="url(#mark)" stroke-width="36">
|
||||
<path d="M 256 192 L 320 256 L 256 320 L 192 256 Z"/>
|
||||
<path d="M 128 192 L 256 64 L 384 192"/>
|
||||
<path d="M 128 320 L 256 448 L 384 320"/>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<text x="512" y="288" text-anchor="middle" font-family="Segoe UI, Arial, sans-serif"
|
||||
font-size="68" font-weight="700" letter-spacing="-1" fill="#F4F1E9">Hermes-Relay</text>
|
||||
|
||||
<text x="512" y="330" text-anchor="middle" font-family="Segoe UI, Arial, sans-serif"
|
||||
font-size="20" font-weight="600" letter-spacing="5" fill="#6E7CFF">YOUR HERMES AGENT, IN YOUR POCKET</text>
|
||||
|
||||
<line x1="462" y1="356" x2="562" y2="356" stroke="#3A44B8" stroke-width="2" stroke-linecap="round"/>
|
||||
|
||||
<g font-family="Segoe UI, Arial, sans-serif" text-anchor="middle">
|
||||
<text x="256" y="415" font-size="27" font-weight="700" letter-spacing="1" fill="#F4F1E9">Chat</text>
|
||||
<text x="512" y="415" font-size="27" font-weight="700" letter-spacing="1" fill="#F4F1E9">Voice</text>
|
||||
<text x="768" y="415" font-size="27" font-weight="700" letter-spacing="1" fill="#F4F1E9">Manage</text>
|
||||
|
||||
<text x="256" y="443" font-size="14" font-weight="600" letter-spacing="2" fill="#68647D">LIVE STREAMING</text>
|
||||
<text x="512" y="443" font-size="14" font-weight="600" letter-spacing="2" fill="#68647D">HANDS-FREE</text>
|
||||
<text x="768" y="443" font-size="14" font-weight="600" letter-spacing="2" fill="#68647D">FULL DASHBOARD</text>
|
||||
</g>
|
||||
|
||||
<circle cx="384" cy="406" r="3" fill="#4F5BD5"/>
|
||||
<circle cx="640" cy="406" r="3" fill="#4F5BD5"/>
|
||||
</svg>`
|
||||
|
||||
const resvg = new Resvg(svg, {
|
||||
fitTo: { mode: 'width', value: 1024 },
|
||||
background: '#08090D',
|
||||
font: { loadSystemFonts: true, defaultFontFamily: 'Segoe UI' },
|
||||
})
|
||||
const png = resvg.render().asPng()
|
||||
writeFileSync(OUT, png)
|
||||
console.log('wrote', OUT, '—', png.length, 'bytes')
|
||||
@@ -1,5 +1,23 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
import { defineConfig } from 'vitepress'
|
||||
|
||||
// Single source of truth for the displayed version: read appVersionName from
|
||||
// the Android version catalog at docs-build time so the nav badge can never
|
||||
// drift from the shipped app. Returns '' (badge hidden) on any failure.
|
||||
function resolveAppVersion(): string {
|
||||
try {
|
||||
const tomlPath = fileURLToPath(new URL('../../gradle/libs.versions.toml', import.meta.url))
|
||||
const match = readFileSync(tomlPath, 'utf-8').match(/^appVersionName\s*=\s*"([^"]+)"/m)
|
||||
return match ? match[1] : ''
|
||||
} catch {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
const appVersion = resolveAppVersion()
|
||||
|
||||
export default defineConfig({
|
||||
base: '/hermes-relay/',
|
||||
title: 'Hermes-Relay',
|
||||
@@ -59,6 +77,11 @@ export default defineConfig({
|
||||
{ text: 'Reference', link: '/reference/api' },
|
||||
{ text: 'GitHub', link: 'https://github.com/Codename-11/hermes-relay' },
|
||||
{ text: 'Privacy', link: '/privacy' },
|
||||
// Release badge — derived from gradle/libs.versions.toml, links to the
|
||||
// GitHub Releases list. Hidden if the version couldn't be resolved.
|
||||
...(appVersion
|
||||
? [{ text: `v${appVersion}`, link: 'https://github.com/Codename-11/hermes-relay/releases' }]
|
||||
: []),
|
||||
],
|
||||
|
||||
sidebar: {
|
||||
|
||||
@@ -56,17 +56,20 @@ The `VoiceBridgeIntentHandler` interface lives in `main/`; each flavor provides
|
||||
|
||||
### Main manifest
|
||||
|
||||
The shared manifest declares only the app permissions and services needed by both tracks:
|
||||
The shared manifest declares the app permissions and services needed by both tracks:
|
||||
|
||||
```
|
||||
INTERNET, ACCESS_NETWORK_STATE, CAMERA, RECORD_AUDIO, MODIFY_AUDIO_SETTINGS
|
||||
INTERNET, ACCESS_NETWORK_STATE, CAMERA, RECORD_AUDIO, MODIFY_AUDIO_SETTINGS,
|
||||
POST_NOTIFICATIONS, FOREGROUND_SERVICE, FOREGROUND_SERVICE_SPECIAL_USE
|
||||
```
|
||||
|
||||
It also declares the optional notification companion service. It does not declare the accessibility service, Device Control foreground service, overlay permission, wake lock, MediaProjection foreground-service type, contacts, location, SMS, or call permissions.
|
||||
It also declares the optional notification companion service and the opt-in `GatewayKeepAliveService` — the "Keep connected in background" feature, a `specialUse` foreground service that is off by default and ships on **both** flavors. It does not declare the accessibility service, the Device Control bridge foreground service, overlay permission, wake lock, MediaProjection foreground-service type, contacts, location, SMS, or call permissions.
|
||||
|
||||
### Google Play manifest
|
||||
|
||||
The Play manifest does not add Device Control permissions or services. The merged Play manifest should contain no `AccessibilityService`, `BIND_ACCESSIBILITY_SERVICE`, `SYSTEM_ALERT_WINDOW`, `FOREGROUND_SERVICE_SPECIAL_USE`, `WAKE_LOCK`, or `BridgeForegroundService` declaration.
|
||||
The Play manifest does not add Device Control permissions or services. The merged Play manifest should contain no `AccessibilityService`, `BIND_ACCESSIBILITY_SERVICE`, `SYSTEM_ALERT_WINDOW`, `WAKE_LOCK`, `FOREGROUND_SERVICE_MEDIA_PROJECTION`, or `BridgeForegroundService` declaration.
|
||||
|
||||
> **Exception — `FOREGROUND_SERVICE_SPECIAL_USE` + `GatewayKeepAliveService` _are_ in the merged Play manifest.** They back the opt-in "Keep connected in background" feature (off by default), which ships on both flavors via the main manifest. This means the Play build carries one `specialUse` foreground service and therefore needs a Play Console foreground-service declaration at submission (see `docs/play-store-listing.md`). The Device Control bridge's `specialUse|mediaProjection` service stays sideload-only.
|
||||
|
||||
### Sideload manifest
|
||||
|
||||
|
||||
+21
-4
@@ -209,7 +209,24 @@ Each assistant message shows token usage below the timestamp:
|
||||
|
||||
When enabled (**Settings → Chat → App context prompt**, on by default),
|
||||
Hermes-Relay tells the agent it's talking to a phone so replies stay
|
||||
mobile-friendly and concise — this travels with every message on any connection.
|
||||
The standard connection can additionally attach optional bridge/permission and
|
||||
safety-rail summaries; privacy-sensitive fields (foreground app, battery) default
|
||||
off and are only added when you opt in.
|
||||
mobile-friendly and concise, and can attach optional bridge/permission and
|
||||
safety-rail summaries. On the standard (API-server) connection this rides an
|
||||
invisible system message. The Gateway connection carries no app-context preamble
|
||||
— its protocol has no hidden per-turn slot, and adding one would leave the text
|
||||
in your saved chat history — so there the agent reads phone state on demand via
|
||||
the `android_phone_status` tool. Privacy-sensitive fields (foreground app,
|
||||
battery) default off and are only added when you opt in.
|
||||
|
||||
## Keep connected in background
|
||||
|
||||
By default the chat connection is held open while Hermes-Relay is on screen and
|
||||
for a couple of minutes after you switch away, so a quick return is instant; on
|
||||
a longer absence it reconnects when you reopen the chat. Returning to a chat the
|
||||
app pre-warms the connection in the background so your first message is fast.
|
||||
|
||||
If you want the connection to stay fully open even when the app is in the
|
||||
background, turn on **Settings → Chat → Keep connected in background** (off by
|
||||
default). It shows an ongoing notification — tap **Disconnect** on it, or flip
|
||||
the toggle, to stop. This uses more battery; swiping the app away from recents
|
||||
also ends it. It's the same approach apps like Home Assistant use to stay
|
||||
connected.
|
||||
|
||||
@@ -54,7 +54,7 @@ device.
|
||||
[github.com/Codename-11/hermes-relay/releases](https://github.com/Codename-11/hermes-relay/releases),
|
||||
open the newest Android release (`android-v*`; historical Android releases used
|
||||
bare `v*`), and grab the file ending in **`-sideload-release.apk`** — for
|
||||
example, `hermes-relay-0.8.0-sideload-release.apk`.
|
||||
example, `hermes-relay-1.0.0-sideload-release.apk`.
|
||||
|
||||
::: warning Download the .apk, not the .aab
|
||||
Each release also ships `-release.aab` files. That's the Android App Bundle
|
||||
|
||||
@@ -40,7 +40,7 @@ Phone (WSS/HTTP) → Relay Server (:8767) [Bridge Core, terminal, TUI, me
|
||||
|
||||
Chat connects directly to the Hermes API Server using the Sessions API with SSE streaming. The relay server handles Bridge Core, terminal, TUI, media, notification companion, sessions, and `/voice/*` routes. Sideload builds additionally expose Android Device Control routes.
|
||||
|
||||
## Current Status — v0.8.1
|
||||
## Current Status — v1.0.0
|
||||
|
||||
| Feature | Status |
|
||||
|---------|--------|
|
||||
|
||||
@@ -45,7 +45,7 @@ One tap from the Play Store listing. Updates arrive automatically.
|
||||
|
||||
### Sideload
|
||||
|
||||
Download the file ending in `-sideload-release.apk` from the latest GitHub Release (for example, `hermes-relay-0.8.0-sideload-release.apk`), allow installs from your browser, and tap the file. Full step-by-step instructions (including how to verify the APK signature) live in [Installation & Setup → Sideload APK](/guide/getting-started#sideload-apk).
|
||||
Download the file ending in `-sideload-release.apk` from the latest GitHub Release (for example, `hermes-relay-1.0.0-sideload-release.apk`), allow installs from your browser, and tap the file. Full step-by-step instructions (including how to verify the APK signature) live in [Installation & Setup → Sideload APK](/guide/getting-started#sideload-apk).
|
||||
|
||||
[GitHub Android Releases →](https://github.com/Codename-11/hermes-relay/releases)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user