Gateway (gateway-plugin/): - Fix interactive_setup broken imports: print helpers were imported from the wrong hermes module (hermes_cli.config instead of hermes_cli.cli_output) plus a non-existent print_code; the try/except swallowed the ImportError so `hermes gateway setup` for android always bailed out early. - Fix release_scoped_lock type error (str | None passed where str required). - Rewrite empty `except: pass` blocks as contextlib.suppress with rationale. - Restructure two ambiguous ws_server try blocks (hello-auth, frame loop). - Ruff cleanup: type annotations, import sorting, line wrapping, magic values -> named constants, `raise ... from e`, complexity. Add gateway-plugin/ruff.toml. - Add pyrightconfig.json so the Python LSP resolves hermes-runtime imports. - Suppress verified false positives inline (parameterized SQL, column-name "secrets", hermes-generated media path). Android (app/androidApp + app/shared): - Consolidate launcher icons into a single mipmap-anydpi (minSdk 29 >= 26) with the monochrome layer; clears ObsoleteSdkInt + MonochromeLauncherIcon. - Bump core-splashscreen 1.0.1 -> 1.2.0; pin targetSdk 34 (deliberate). - Suppress verified findings inline (LAN ws:// default, correct GCM IV usage). Desktop (app/desktopApp): - Move the desktop to a Java 21 runtime (org.gradle.java.home) and set the desktop jvmTarget to 21 (Android stays JVM 17 / minSdk 29). Fixes the startup UnsupportedClassVersionError and restores Markdown renderer 0.44.0. Tooling/config: - .pi-lens.json: disable verified-noisy heuristics (documented in docs). - .gitleaks.toml: allowlist git-ignored false-positive paths. - docs/18-code-review.md: full findings + verification. Verified: ruff clean, pyright 0 errors, 64/64 gateway tests, all Kotlin tests, Android lint 0 issues, Android installed+launched on device, desktop launches on JDK 21.
Tests for the android gateway plugin.
Run via hermes's hermetic runner (never bare pytest)::
scripts/run_tests.sh tests/gateway/test_android.py
See docs/13-testing.md for the scenario list.
WS probe (ws_probe.py)
Manual test-client harness: connects to the real running gateway and
drives a turn, printing every frame. Run with the hermes venv python
(needs websockets); the gateway must already be up::
hermes-agent/.venv/bin/python gateway-plugin/tests/ws_probe.py \
--token <ANDROID_TOKEN> --send "hello"
Beyond the base modes (--send, --upload, --pull-offer, --sync,
--fcm-token/--fcm-reg, --authfail, --url, --token, --device,
--timeout), the probe has assertion and request modes:
--assert-turn— assert the turn producedmessage.start→ ≥1message.update→message.stop(scenario 2).--assert-reasoning— assert the finalmessage.stopcarries a non-emptyreasoningfield (scenario 3).--assert-tools— assert ≥1tool.startwith a matchingtool.end(matched byindex; scenario 4).--assert-commentary— assert ≥1commentaryframe (scenario 5).--assert-read-receipt— assert aread.receiptframe arrives after the sent message (new M7 frame; requires--send). SKIPs (exit 0, prints== SKIP: …) when the frame never arrives, e.g. against a gateway that predates the M7 frames.--assert-status— assert astatusframe is received (new M7 frame; SKIPs when absent).--search QUERY [--scope all|chat] [--chat-id C]— send asearchframe ({query, scope, limit}) and assert ≥1 hit insearch.results(scenario 8). With--send, the turn is driven first, then the search.--channel-create NAME/--channel-delete CHAT_ID/--channel-list— M3 channel directory management; create prints== channel created: <chat_id>for scripting.--watch CHAT_ID— wait up to--timeoutfor a message to land inCHAT_ID(cron delivery E2E, scenario 7).--offer-grace S— with--pull-offer, keep listening S seconds after the final message for amedia.offer(offers are emitted post-turn, right after the final; default 15).
Exit codes: 0 ok (incl. SKIP for absent M7 frames), 2 connect fail,
3 no hello.ack, 4 expected hello.ack, 5 authfail expected but
acked, 6 timeout, 7 no final message, 8 upload/sync fail,
9 pull fail, 10 assert-turn fail, 11 assert-reasoning fail,
12 assert-tools fail, 13 assert-commentary fail, 14 search fail
(error or zero hits), 15 channel.create/list fail, 16 channel.delete
fail, 17 watch timeout, 18 read.receipt arrived before the sent
message, 19 status frame with empty payload.
E2E driver (e2e.py)
Runs the docs/13-testing.md §13.4 scenarios 1–12 automated-where-
possible against the live gateway, invoking ws_probe.py (and the
hermes CLI for cron) as subprocesses. Prints PASS / PARTIAL / SKIP /
FAIL per scenario plus a summary table; exits 0 if no FAIL, 1 otherwise::
hermes-agent/.venv/bin/python gateway-plugin/tests/e2e.py
hermes-agent/.venv/bin/python gateway-plugin/tests/e2e.py --skip 3,5,7
hermes-agent/.venv/bin/python gateway-plugin/tests/e2e.py --url ws://host:8790/ws
The token is read from $ANDROID_TOKEN, else hermes-agent/.env, else
~/.hermes/.env. The gateway must already be running (the driver never
starts or stops it). It is idempotent: channels/jobs it creates are
cleaned up even on failure, and leftover e2e-* channels/jobs from
earlier runs are removed at start.
Scenario notes: 3 (reasoning) and 5 (commentary) are model-dependent and SKIP rather than FAIL when the current model does not emit them; 11 (push) and 12 (reconnect/sync) are PARTIAL by design — the WS leg is automated, the device-notification / gateway-kill leg is manual.