feat(dashboard): always enable embedded chat; remove dashboard --tui flag
The dashboard's embedded Chat surface (/chat, /api/ws, /api/pty) was gated behind `hermes dashboard --tui` / HERMES_DASHBOARD_TUI=1. The desktop app and the dashboard's own Chat tab both drive the agent over the /api/ws + /api/pty WebSockets, so a dashboard started without the flag would pass the /api/status health check but slam the chat WebSocket shut with WS code 4403 — the app connects, reports "ready", and chat stays dead. This was the root cause behind multiple user reports of the desktop app failing to connect to a self-hosted gateway/dashboard, and it bit Docker and host installs alike. Make the embedded chat unconditional: - web_server.py: _DASHBOARD_EMBEDDED_CHAT_ENABLED defaults to True; drop the embedded_chat parameter and the runtime reassignment from start_server(). The WS gates still read the constant (now always true) so the seam — and its "rejects when disabled" contract test — stays meaningful. - main.py: remove the `--tui` argument from the dashboard subparser and the `embedded_chat = args.tui or HERMES_DASHBOARD_TUI==1` derivation. - web/: isDashboardEmbeddedChatEnabled() returns true unconditionally; drop the deprecated __HERMES_DASHBOARD_TUI__ alias and the dead LEGACY_TUI_RE scrape in the vite dev-token plugin. - apps/desktop/electron/main.cjs: drop `--tui` from the spawned dashboardArgs (it would now error with "unrecognized arguments: --tui") and the redundant HERMES_DASHBOARD_TUI env injection. - Docker: no s6 run-script change needed — the script never passed --tui; the HERMES_DASHBOARD_TUI env var is now simply a no-op, so the image works out of the box with no extra var. - Docs: remove every dashboard --tui / HERMES_DASHBOARD_TUI reference across the CLI reference, env-var reference, docker/desktop/web-dashboard guides, in-app tips, and the zh-Hans translations. The terminal `hermes --tui` / HERMES_TUI references are intentionally left untouched. Tests: 270 passing across web_server, dashboard lifecycle, host-header, auth-gate, and docker-override-scripts suites.
This commit is contained in:
parent
bf82a7f1cc
commit
cae6b5486f
18 changed files with 43 additions and 80 deletions
|
|
@ -28,7 +28,6 @@ This starts a local web server and opens `http://127.0.0.1:9119` in your browser
|
|||
| `--host` | `127.0.0.1` | Bind address |
|
||||
| `--no-open` | — | Don't auto-open the browser |
|
||||
| `--insecure` | off | Allow binding to non-localhost hosts (**DANGEROUS** — exposes API keys on the network; pair with a firewall and strong auth) |
|
||||
| `--tui` | off | Expose the in-browser Chat tab (embedded `hermes --tui` via PTY/WebSocket). Alternatively set `HERMES_DASHBOARD_TUI=1`. |
|
||||
|
||||
```bash
|
||||
# Custom port
|
||||
|
|
@ -39,9 +38,6 @@ hermes dashboard --host 0.0.0.0
|
|||
|
||||
# Start without opening browser
|
||||
hermes dashboard --no-open
|
||||
|
||||
# Enable the in-browser Chat tab
|
||||
hermes dashboard --tui
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
|
@ -56,7 +52,7 @@ The `web` extra pulls in FastAPI/Uvicorn; `pty` pulls in `ptyprocess` (POSIX) or
|
|||
|
||||
When you run `hermes dashboard` without the dependencies, it will tell you what to install. If the frontend hasn't been built yet and `npm` is available, it builds automatically on first launch.
|
||||
|
||||
The Chat tab is intentionally off for a plain `hermes dashboard` launch. Start the dashboard with `hermes dashboard --tui` or set `HERMES_DASHBOARD_TUI=1` when you want the embedded browser chat pane.
|
||||
The Chat tab is part of every `hermes dashboard` launch — the embedded browser chat pane (running the TUI over PTY/WebSocket) is always available, with no extra flag required.
|
||||
|
||||
## Pages
|
||||
|
||||
|
|
@ -101,9 +97,8 @@ Hermes Desktop normally launches its own local backend, but it can also attach t
|
|||
|
||||
Desktop's "remote backend is ready" probe only hits `GET /api/status`, which is a public endpoint — it answers as soon as *any* dashboard is running on the host. The live chat connection is a **separate** WebSocket to `/api/ws` (and `/api/pty`), and that socket is gated by two more checks the status probe never touches:
|
||||
|
||||
1. **The embedded chat must be enabled.** `/api/ws` and `/api/pty` close immediately with WS code **4403** unless the dashboard was started with `--tui` (or `HERMES_DASHBOARD_TUI=1`). A plain `hermes dashboard` or `hermes gateway` serves the status page but refuses the chat socket.
|
||||
2. **The session token must match.** Even with chat enabled, the socket closes with WS code **4401** if the token Desktop sends doesn't match the dashboard's session token. By default the dashboard generates a **fresh random token on every restart**, so a token you saved in Desktop yesterday is invalid after the service restarts. Pin it by setting `HERMES_DASHBOARD_SESSION_TOKEN` to a stable value.
|
||||
3. **The bind host must allow the client and match the Host header.** A loopback bind (`127.0.0.1`) only accepts loopback clients, so a remote machine is rejected at the socket layer regardless of token. Bind to a non-loopback address (`--host 0.0.0.0 --insecure` for a trusted LAN) so the peer-IP guard lets the remote client through. The remote URL you enter in Desktop must reach the dashboard by the same host it bound to — the DNS-rebinding guard requires the Host header to match.
|
||||
1. **The session token must match.** The socket closes with WS code **4401** if the token Desktop sends doesn't match the dashboard's session token. By default the dashboard generates a **fresh random token on every restart**, so a token you saved in Desktop yesterday is invalid after the service restarts. Pin it by setting `HERMES_DASHBOARD_SESSION_TOKEN` to a stable value.
|
||||
2. **The bind host must allow the client and match the Host header.** A loopback bind (`127.0.0.1`) only accepts loopback clients, so a remote machine is rejected at the socket layer regardless of token. Bind to a non-loopback address (`--host 0.0.0.0 --insecure` for a trusted LAN) so the peer-IP guard lets the remote client through. The remote URL you enter in Desktop must reach the dashboard by the same host it bound to — the DNS-rebinding guard requires the Host header to match.
|
||||
|
||||
#### Remote dashboard setup
|
||||
|
||||
|
|
@ -716,8 +711,6 @@ The "session token" is the dashboard's session token — the same secret the loc
|
|||
|
||||
The desktop app sends the token as an `X-Hermes-Session-Token` header. The backend accepts it only in legacy session-token mode — i.e. when bound non-loopback **with `--insecure`**. A non-loopback bind *without* `--insecure` engages the [OAuth gate](#oauth-authentication-gated-mode) instead, which ignores the session token. So a remote desktop connection means: `--insecure` + a token you control.
|
||||
|
||||
The backend must also be started with **`--tui`** (or `HERMES_DASHBOARD_TUI=1`). The desktop's chat runs over the `/api/ws` + `/api/pty` WebSockets, and those are refused unless the embedded-chat surface is enabled. Without `--tui` the desktop still passes the `/api/status` health check (so the app reports the backend "ready") but the chat WebSocket is closed on connect — connects, looks ready, chat stays dead. A plain `hermes dashboard` or `hermes gateway` is not enough.
|
||||
|
||||
### On the backend (the remote machine)
|
||||
|
||||
```bash
|
||||
|
|
@ -731,11 +724,9 @@ chmod 600 ~/.hermes/.env
|
|||
echo "$TOKEN" # copy this value into the desktop app
|
||||
|
||||
# 2. Run the dashboard bound to a reachable address.
|
||||
# --tui enables the embedded chat (the /api/ws + /api/pty WebSockets the
|
||||
# desktop drives) — without it the app connects but chat stays dead.
|
||||
# --insecure is required for any non-loopback bind and keeps the
|
||||
# legacy session-token auth path (instead of the OAuth gate).
|
||||
hermes dashboard --tui --no-open --insecure --host 0.0.0.0 --port 9119
|
||||
hermes dashboard --no-open --insecure --host 0.0.0.0 --port 9119
|
||||
```
|
||||
|
||||
If you run the dashboard as a systemd service, `~/.hermes/.env` is picked up automatically when the unit has `EnvironmentFile=%h/.hermes/.env`, so the token is in the environment at boot.
|
||||
|
|
@ -770,7 +761,6 @@ Both must be set together — setting only the URL is an error.
|
|||
|
||||
- **"Remote gateway incomplete"** — you haven't entered both a URL and a token. The token only needs re-entering if `remoteTokenSet` is false (no saved token yet).
|
||||
- **Test remote fails with 401** — the token doesn't match the backend's `HERMES_DASHBOARD_SESSION_TOKEN`, or the backend is running *without* `--insecure` on a non-loopback bind (the OAuth gate is on and ignores the session token). Confirm `--insecure` and that the env var is actually loaded (`curl -s -H "X-Hermes-Session-Token: $TOKEN" http://<host>:9119/api/status` should return JSON, not 401).
|
||||
- **Backend reports "ready" but chat does nothing** — the backend was started without `--tui` (or `HERMES_DASHBOARD_TUI=1`), so `/api/status` answers but the chat WebSocket (`/api/ws` / `/api/pty`) is refused. Restart the backend with `--tui`.
|
||||
- **Connection refused / times out** — the backend bound to `127.0.0.1` (the default) instead of a reachable address, or a firewall/VPN is blocking the port. Bind to `0.0.0.0` or the tailscale IP and open the port to your trusted network.
|
||||
- **No token anywhere to copy** — expected. You mint it (`HERMES_DASHBOARD_SESSION_TOKEN`); Hermes never auto-surfaces the default ephemeral one.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue