* fix: refresh loopback dashboard token after restart
The gateway regenerates its ephemeral session token on every restart, so the
token the app persisted becomes stale. After a dashboard restart REST calls
401 and the WebSocket auto-reconnect loop hangs in AUTH_EXPIRED until the user
manually re-authenticates.
- Add DashboardSessionTokenRefresher: re-reads the injected
window.__HERMES_SESSION_TOKEN__ from the dashboard SPA and writes it back to
AuthManager.
- TokenRefreshAuthenticator: on a 401 retry, scrape a fresh token from the
dashboard and retry instead of giving up.
- HermesWsClient: refresh the token before each non-gated WebSocket handshake so
reconnects don't get stuck on a stale token; guard serverStore read against
IllegalStateException.
Covers the loopback (--insecure) deployment path; gated/ticket mode is untouched.
* fix: harden dashboard token refresher (sync, body cap, blank-token test)
Second-opinion review flagged robustness gaps (no correctness/security
regressions in the happy path):
- DashboardSessionTokenRefresher.refresh(): @Synchronized to dedupe
concurrent 401 / reconnect storms instead of each firing its own GET.
- Cap SPA HTML read at 64 KiB before regex scan (defensive against OOM on
low-end devices); we only need the injected token near the document top.
- HermesWsClient: log (not silently swallow) the transient uninitialized
serverStore case so a real misconfig is still visible.
- Add test: blank-injected token returns null.
---------
Co-authored-by: M57 <m57@hermes-control.app>