fix(plugin): restore native installer compatibility

This commit is contained in:
Bailey Dixon
2026-08-31 19:45:38 -04:00
parent 949add15b1
commit 201e204290
9 changed files with 78 additions and 9 deletions
+1
View File
@@ -101,6 +101,7 @@ jobs:
- name: Run focused Plugin tests
run: |
python -m pytest \
plugin/tests/test_manifest_compatibility.py \
plugin/tests/test_relay_security.py \
plugin/tests/test_voice_routes.py \
plugin/tests/test_session_grants.py \
+1
View File
@@ -85,6 +85,7 @@ jobs:
- name: Run focused Plugin tests
run: |
python -m pytest \
plugin/tests/test_manifest_compatibility.py \
plugin/tests/test_relay_security.py \
plugin/tests/test_voice_routes.py \
plugin/tests/test_session_grants.py \
+1
View File
@@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/), and this
### Fixed
- **Hermes-Relay Plugin installs through the native Hermes command again.** The manifest remains fully described for current hosts while avoiding the installer/runtime schema mismatch in affected Hermes releases.
- **Android keeps completed chat text visible when Dashboard sign-in expires.** Generic and reason-coded history `401` responses settle the local turn, preserve its transcript, and surface the existing sign-in recovery without reading another profile's API history.
- **Android keeps long-running context compaction alive.** A client-visible compaction status extends and refreshes the Gateway turn watchdog instead of interrupting healthy compression after the ordinary idle window. (Supersedes #484.)
- **Android Bot Chats render loaded history immediately.** Route-owned chat screens observe their own handler state from first composition, including fast history loads that settle before another frame. (Supersedes #453.)
+12
View File
@@ -6,6 +6,18 @@ For shipped work, see `DEVLOG.md`. For architectural decisions, see `docs/decisi
---
## Restore the plugin manifest v2 declaration after the Hermes installer fix ships
Hermes installers in affected stable releases reject `manifest_version: 2`
before the v2-capable runtime loader can inspect the plugin. Track upstream
[PR #85893](https://github.com/NousResearch/hermes-agent/pull/85893). Restore
`plugin/plugin.yaml` to `manifest_version: 2` only after that fix ships in a
stable Hermes release that Hermes-Relay can treat as its minimum supported
version. Until then, keep the v1 compatibility declaration and the additive
metadata consumed by newer hosts.
---
## Consider hosted Android emulator execution
The local API 36 Gradle Managed Device lanes are intentionally on demand and
+2 -1
View File
@@ -1,5 +1,6 @@
name: hermes-relay
manifest_version: 2
# Temporary v1 shim for Hermes installers that reject manifests the runtime supports; see TODO.md.
manifest_version: 1
api_version: 1
version: 1.11.0
description: "Hermes-Relay plugin for QR pairing, relay sessions, dashboard management, remote desktop/phone tooling, and optional legacy compatibility diagnostics. Standard chat, Manage, and dashboard voice remain vanilla upstream Hermes surfaces."
@@ -0,0 +1,53 @@
"""Regression coverage for the temporary Hermes installer compatibility manifest."""
from pathlib import Path
import unittest
import yaml
PLUGIN_ROOT = Path(__file__).resolve().parents[1]
class ManifestCompatibilityTest(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
cls.manifest = yaml.safe_load(
(PLUGIN_ROOT / "plugin.yaml").read_text(encoding="utf-8")
)
def test_declares_installer_compatible_manifest_version(self) -> None:
self.assertEqual(self.manifest["manifest_version"], 1)
def test_retains_additive_metadata_for_current_hosts(self) -> None:
self.assertEqual(self.manifest["api_version"], 1)
self.assertEqual(
self.manifest["python_dependencies"],
[
"requests>=2.28.0,<3",
"aiohttp>=3.14.1,<4",
"segno>=1.6.0,<2",
"pyyaml>=6.0,<7",
"httpx>=0.25.0,<1",
"websocket-client>=1.8.0,<2",
],
)
self.assertEqual(self.manifest["license"], "MIT")
self.assertEqual(
self.manifest["homepage"],
"https://github.com/Codename-11/hermes-relay",
)
self.assertEqual(
self.manifest["tags"],
["android", "dashboard", "gateway", "relay", "remote-access", "voice"],
)
def test_retains_v1_hook_declarations(self) -> None:
self.assertEqual(
self.manifest["provides_hooks"],
["on_session_start", "pre_llm_call", "post_llm_call"],
)
if __name__ == "__main__":
unittest.main()
+2 -2
View File
@@ -29,8 +29,8 @@ Run this from the `hermes-android/` repo root, or wherever `relay_server/` is lo
### As a Hermes plugin + relay
```bash
# 1. Install the Android plugin (18 android_* tools)
cp -r plugin ~/.hermes/plugins/hermes-relay
# 1. Install and enable the native Hermes plugin
hermes plugins install Codename-11/hermes-relay/plugin --enable
# 2. Install relay dependencies
pip install -r relay_server/requirements.txt
+4 -4
View File
@@ -34,7 +34,7 @@ Operators with the Hermes dashboard open can also mint the same QR from the web
## Prerequisites
1. **Hermes-Relay plugin installed into the Hermes venv.** Verify by running `python -m plugin.pair --help` — if it errors with `ModuleNotFoundError: No module named 'plugin'`, install it first: `pip install -e <path-to-hermes-relay-repo>`.
1. **Hermes-Relay plugin installed and enabled.** Verify with `hermes pair --help`. If the command is unavailable, run `hermes plugins install Codename-11/hermes-relay/plugin --enable`. Use the full `install.sh` path instead only when the host also needs the relay service, editable package, and shell shims.
2. **Hermes API server reachable** on `API_SERVER_HOST:API_SERVER_PORT` (default `127.0.0.1:8642`). `plugin.pair` auto-reads this from `~/.hermes/config.yaml` → `~/.hermes/.env` → env vars → defaults.
3. **Relay server running** on `RELAY_HOST:RELAY_PORT` (default `0.0.0.0:8767`) if the user wants terminal/bridge channels. The Relay may stay host-internal: current Android pairing normally reaches it through the Dashboard's same-origin plugin transport. Tailscale Serve normally exposes dedicated HTTPS `10443` and proxies the host-local Dashboard on `9119`; a raw LAN/tailnet route may reach `9119` directly. Listener `443` is an advanced explicit override only when it is free. Without a live relay, the QR will configure chat only.
4. **Host is Linux or macOS.** The relay uses a real PTY backend, which is POSIX-only. Windows hosts can generate API-only QRs but the terminal channel will not work.
@@ -46,7 +46,7 @@ Operators with the Hermes dashboard open can also mint the same QR from the web
2. **Generate the QR** — run via the `terminal` tool:
```bash
python -m plugin.pair
hermes pair
```
When the current Hermes surface exposes an exact Dashboard origin, pass it
@@ -65,7 +65,7 @@ Operators with the Hermes dashboard open can also mint the same QR from the web
explicitly selects that advanced listener and confirms it is free. Never
substitute the API server URL or infer public port `8767`.
If `python` resolves to the wrong interpreter (plugin not found), use the Hermes venv explicitly:
On a full-relay or legacy editable install where an older Hermes host cannot register plugin CLI commands, use the Hermes venv explicitly:
```bash
~/.hermes/hermes-agent/venv/bin/python -m plugin.pair
@@ -156,7 +156,7 @@ Pass `--transport-hint wss` only when you know the relay is actually running beh
## Pitfalls
- **Relay not running.** `plugin.pair` prints `[info] Relay not running ... QR will configure chat only` and renders an API-only QR. Terminal tab will then ask the user to paste a pairing code manually. Fix: start the relay first (`hermes relay start`) and re-run.
- **Plugin not installed.** `ModuleNotFoundError: No module named 'plugin'`. Fix: `pip install -e <hermes-relay-repo>` into the same Python environment Hermes uses. Use `which python` / `where python` to confirm you're targeting the Hermes venv.
- **Plugin not installed.** If `hermes pair --help` is unavailable, run `hermes plugins install Codename-11/hermes-relay/plugin --enable`. Use `install.sh` for the full relay/service/shim setup rather than constructing a manual editable install.
- **Wrong venv.** If `hermes` CLI is global but plugin is in the Hermes venv, `python -m plugin.pair` may resolve to the wrong Python. Call the venv Python explicitly: `~/.hermes/hermes-agent/venv/bin/python -m plugin.pair`.
- **Pairing code expired.** 10-minute TTL, one-shot. Re-run `python -m plugin.pair` to mint a fresh code; the previous code is automatically invalidated on the next run.
- **QR won't scan on terminal.** Likely causes: terminal font too small (zoom in), dark-mode color inversion mangling the blocks, or terminal lacks Unicode half-block support. Fix: re-run with `--png` and point the camera at the saved image, or open the PNG in an image viewer on a second screen.
+2 -2
View File
@@ -81,7 +81,7 @@ Do NOT use this skill to start or install the relay server itself — that is a
## Prerequisites
1. **Hermes-Relay plugin installed into the Hermes venv.** Verify by running `python -m plugin.status --help` — if it errors with `ModuleNotFoundError: No module named 'plugin'`, install it first: `pip install -e <path-to-hermes-relay-repo>`.
1. **Hermes-Relay plugin installed and enabled.** Verify with `hermes plugins show hermes-relay`. If it is missing, run `hermes plugins install Codename-11/hermes-relay/plugin --enable`. The standalone `python -m plugin.status` fallback additionally requires the full `install.sh` editable-package path.
2. **Relay server running** on `RELAY_HOST:RELAY_PORT` (default `0.0.0.0:8767`). Without a live relay, this skill exits with code `1` and a "relay unreachable" error.
3. **Phone has connected at least once** since the last relay restart. The relay tracks live phone-*connection* state in memory, so a restart clears that presence — the phone **reconnects** automatically (its paired session persists across restart, so no re-pair is needed). Until then, status returns "no phone connected" with exit code `2`.
@@ -119,7 +119,7 @@ Do NOT use this skill to start or install the relay server itself — that is a
## Pitfalls
- **Relay not running.** `status` prints `[error] Cannot reach hermes-relay on 127.0.0.1:8767` to stderr and exits `1`. Fix: start the relay first (`systemctl --user start hermes-relay` or `python -m plugin.relay --no-ssl`) and re-run.
- **Plugin not installed.** `ModuleNotFoundError: No module named 'plugin'`. Fix: `pip install -e <hermes-relay-repo>` into the same Python environment Hermes uses. Use `which python` / `where python` to confirm you're targeting the Hermes venv.
- **Plugin not installed.** If `hermes plugins show hermes-relay` fails, run `hermes plugins install Codename-11/hermes-relay/plugin --enable`. Use `install.sh` when the host also needs the relay service, editable package, and shell shims.
- **Wrong venv.** If `hermes` CLI is global but plugin is in the Hermes venv, `python -m plugin.status` may resolve to the wrong Python. Call the venv Python explicitly: `~/.hermes/hermes-agent/venv/bin/python -m plugin.status`.
- **Phone shows as disconnected immediately after a relay restart.** Expected — the relay holds phone state in memory and wipes it on restart. The phone reconnects automatically on its next ping cycle (within ~30s). If it doesn't, check the phone side: the session token may need re-pairing via `/hermes-relay-pair`.
- **`bridge.accessibility_granted = false` but everything else looks fine.** The user has opened the Android app but not yet granted the Hermes-Relay accessibility service. Tell them: "Open Hermes-Relay → Bridge screen → the permission checklist will have Accessibility as the top row. Tap it to open Android Settings → Installed services → Hermes-Relay and flip the switch."