From cae6b5486fec8e9b5f366ea3ed5254d53fbc341d Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 4 Jun 2026 10:53:49 +1000 Subject: [PATCH] feat(dashboard): always enable embedded chat; remove dashboard --tui flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- apps/desktop/README.md | 2 +- apps/desktop/electron/main.cjs | 3 +-- hermes_cli/main.py | 13 +++-------- hermes_cli/tips.py | 2 +- hermes_cli/web_server.py | 15 ++++++------ tests/docker/test_dashboard.py | 2 +- .../test_dashboard_lifecycle_flags.py | 2 +- tests/test_docker_home_override_scripts.py | 2 +- web/src/lib/dashboard-flags.ts | 23 +++++++++++++------ web/vite.config.ts | 9 +------- website/docs/reference/cli-commands.md | 6 +---- .../docs/reference/environment-variables.md | 1 - website/docs/user-guide/desktop.md | 11 +++------ website/docs/user-guide/docker.md | 1 - .../docs/user-guide/features/web-dashboard.md | 18 ++++----------- .../current/reference/cli-commands.md | 6 +---- .../current/user-guide/docker.md | 1 - .../user-guide/features/web-dashboard.md | 6 +---- 18 files changed, 43 insertions(+), 80 deletions(-) diff --git a/apps/desktop/README.md b/apps/desktop/README.md index feb163d43..1f4a693c4 100644 --- a/apps/desktop/README.md +++ b/apps/desktop/README.md @@ -94,7 +94,7 @@ Installers are built and uploaded to GitHub Releases manually. macOS/Windows sig ### How it works -The packaged app ships only the Electron shell. On first launch it installs the Hermes Agent runtime into `HERMES_HOME` (`~/.hermes`, or `%LOCALAPPDATA%\hermes` on Windows) — the **same layout a CLI install uses**, so the two are interchangeable. The renderer (React, in `src/`) talks to a `hermes dashboard --tui` backend over the standard gateway APIs and reuses the embedded TUI rather than reimplementing chat. The install, backend-resolution, and self-update logic all live in `electron/main.cjs`. +The packaged app ships only the Electron shell. On first launch it installs the Hermes Agent runtime into `HERMES_HOME` (`~/.hermes`, or `%LOCALAPPDATA%\hermes` on Windows) — the **same layout a CLI install uses**, so the two are interchangeable. The renderer (React, in `src/`) talks to a `hermes dashboard` backend over the standard gateway APIs and reuses the embedded TUI rather than reimplementing chat. The install, backend-resolution, and self-update logic all live in `electron/main.cjs`. ### Verification diff --git a/apps/desktop/electron/main.cjs b/apps/desktop/electron/main.cjs index 6ce12fb8f..093b90ade 100644 --- a/apps/desktop/electron/main.cjs +++ b/apps/desktop/electron/main.cjs @@ -3747,7 +3747,7 @@ async function startHermes() { await advanceBootProgress('backend.port', 'Finding an open local port', 16) const port = await pickPort() const token = crypto.randomBytes(32).toString('base64url') - const dashboardArgs = ['dashboard', '--no-open', '--tui', '--host', '127.0.0.1', '--port', String(port)] + const dashboardArgs = ['dashboard', '--no-open', '--host', '127.0.0.1', '--port', String(port)] await advanceBootProgress('backend.runtime', 'Resolving Hermes runtime', 28) const backend = await ensureRuntime(resolveHermesBackend(dashboardArgs)) const hermesCwd = resolveHermesCwd() @@ -3771,7 +3771,6 @@ async function startHermes() { HERMES_HOME, ...backend.env, HERMES_DASHBOARD_SESSION_TOKEN: token, - HERMES_DASHBOARD_TUI: '1', HERMES_WEB_DIST: webDist }, shell: backend.shell, diff --git a/hermes_cli/main.py b/hermes_cli/main.py index 40c9778d1..b7ddc55fe 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -12022,13 +12022,14 @@ def cmd_dashboard(args): from hermes_cli.web_server import start_server - embedded_chat = args.tui or os.environ.get("HERMES_DASHBOARD_TUI") == "1" + # The in-browser Chat tab (the embedded TUI over PTY/WebSocket) is always + # available — the desktop app and the dashboard's own Chat tab both rely on + # the `/api/ws` + `/api/pty` sockets, so there is no reason to gate them. start_server( host=args.host, port=args.port, open_browser=not args.no_open, allow_public=getattr(args, "insecure", False), - embedded_chat=embedded_chat, ) @@ -15314,14 +15315,6 @@ Examples: action="store_true", help="Allow binding to non-localhost (DANGEROUS: exposes API keys on the network)", ) - dashboard_parser.add_argument( - "--tui", - action="store_true", - help=( - "Expose the in-browser Chat tab (embedded `hermes --tui` via PTY/WebSocket). " - "Alternatively set HERMES_DASHBOARD_TUI=1." - ), - ) dashboard_parser.add_argument( "--skip-build", action="store_true", diff --git a/hermes_cli/tips.py b/hermes_cli/tips.py index 25324a756..3a17055e7 100644 --- a/hermes_cli/tips.py +++ b/hermes_cli/tips.py @@ -381,7 +381,7 @@ TIPS = [ 'Ctrl+G or Ctrl+X Ctrl+E in the TUI opens the input buffer in $EDITOR for long multi-line prompts.', 'The TUI renders LaTeX inline — $E=mc^2$ becomes Unicode math instead of raw TeX.', 'hermes dashboard launches a local web UI at 127.0.0.1:9119 — zero data leaves localhost.', - 'hermes dashboard --tui embeds the full Hermes TUI in your browser via xterm.js and a WebSocket PTY.', + 'hermes dashboard embeds the full Hermes TUI in your browser via xterm.js and a WebSocket PTY.', 'Drop a YAML in ~/.hermes/dashboard-themes/ with two palette colors to reskin the entire dashboard.', 'Dashboard plugins are drop-in: manifest.json + JS bundle in ~/.hermes/dashboard-plugins/ — no npm build required.', 'layoutVariant: cockpit in a dashboard theme adds a 260px left rail that plugins can populate via the sidebar slot.', diff --git a/hermes_cli/web_server.py b/hermes_cli/web_server.py index e1f4ef60b..d25ca1643 100644 --- a/hermes_cli/web_server.py +++ b/hermes_cli/web_server.py @@ -135,9 +135,13 @@ app = FastAPI(title="Hermes Agent", version=__version__, lifespan=_lifespan) _SESSION_TOKEN = os.environ.get("HERMES_DASHBOARD_SESSION_TOKEN") or secrets.token_urlsafe(32) _SESSION_HEADER_NAME = "X-Hermes-Session-Token" -# In-browser Chat tab (/chat, /api/pty, …). Off unless ``hermes dashboard --tui`` -# or HERMES_DASHBOARD_TUI=1. Set from :func:`start_server`. -_DASHBOARD_EMBEDDED_CHAT_ENABLED = False +# In-browser Chat tab (/chat, /api/pty, /api/ws, …). Always enabled: the +# desktop app and the dashboard's own Chat tab both drive the agent over the +# `/api/ws` + `/api/pty` WebSockets, so the embedded-chat surface is an +# unconditional part of the dashboard. Kept as a module-level constant (rather +# than inlining ``True`` at every gate) so the WS endpoints and the SPA token +# injection share a single, testable seam. +_DASHBOARD_EMBEDDED_CHAT_ENABLED = True # Simple rate limiter for the reveal endpoint _reveal_timestamps: List[float] = [] @@ -8677,15 +8681,10 @@ def start_server( port: int = 9119, open_browser: bool = True, allow_public: bool = False, - *, - embedded_chat: bool = False, ): """Start the web UI server.""" import uvicorn - global _DASHBOARD_EMBEDDED_CHAT_ENABLED - _DASHBOARD_EMBEDDED_CHAT_ENABLED = embedded_chat - # Phase 0: stash the auth-gate flag on app.state so middleware / SPA-token # injection / WS-auth paths can branch on it consistently. Phase 3.5 # uses this to decide whether to refuse the bind, log the gate-on diff --git a/tests/docker/test_dashboard.py b/tests/docker/test_dashboard.py index d615ef94d..91dc1051b 100644 --- a/tests/docker/test_dashboard.py +++ b/tests/docker/test_dashboard.py @@ -389,7 +389,7 @@ def test_dashboard_insecure_env_var_opts_out_of_gate( """``HERMES_DASHBOARD_INSECURE=1`` re-enables the legacy no-gate mode for operators running on trusted LANs behind a reverse proxy without the OAuth contract. Same opt-out shape as the rest of the s6 boolean - envs (``HERMES_DASHBOARD``, ``HERMES_DASHBOARD_TUI``). + envs (e.g. ``HERMES_DASHBOARD``). With the gate off, ``/api/status`` (a public endpoint under the legacy ``_SESSION_TOKEN`` middleware) returns 200 with the diff --git a/tests/hermes_cli/test_dashboard_lifecycle_flags.py b/tests/hermes_cli/test_dashboard_lifecycle_flags.py index 0e6f16157..1d2745a98 100644 --- a/tests/hermes_cli/test_dashboard_lifecycle_flags.py +++ b/tests/hermes_cli/test_dashboard_lifecycle_flags.py @@ -22,7 +22,7 @@ def _ns(**kw): """Build an argparse.Namespace with dashboard defaults plus overrides.""" defaults = dict( port=9119, host="127.0.0.1", no_open=False, insecure=False, - tui=False, stop=False, status=False, + stop=False, status=False, ) defaults.update(kw) return argparse.Namespace(**defaults) diff --git a/tests/test_docker_home_override_scripts.py b/tests/test_docker_home_override_scripts.py index ff028f7ef..5de91ca84 100644 --- a/tests/test_docker_home_override_scripts.py +++ b/tests/test_docker_home_override_scripts.py @@ -72,7 +72,7 @@ def test_dashboard_run_does_not_derive_insecure_from_bind_host() -> None: "Explicit HERMES_DASHBOARD_INSECURE opt-in is missing." ) # Truthy values aligned with the rest of the s6 scripts - # (HERMES_DASHBOARD, HERMES_DASHBOARD_TUI). + # (e.g. HERMES_DASHBOARD). for truthy in ("1", "true", "TRUE", "True", "yes", "YES", "Yes"): assert truthy in text, ( f"HERMES_DASHBOARD_INSECURE should accept truthy value {truthy!r}" diff --git a/web/src/lib/dashboard-flags.ts b/web/src/lib/dashboard-flags.ts index 0ce9e03be..fb831131b 100644 --- a/web/src/lib/dashboard-flags.ts +++ b/web/src/lib/dashboard-flags.ts @@ -1,15 +1,24 @@ declare global { interface Window { - /** Set true by the server only for `hermes dashboard --tui` (or HERMES_DASHBOARD_TUI=1). */ + /** + * Injected by the server as `true`. The embedded TUI Chat surface + * (`/chat`, `/api/ws`, `/api/pty`) is always enabled, so this is + * effectively a constant; kept on `window` for any consumer that reads + * it directly and for parity with the server's bootstrap script. + */ __HERMES_DASHBOARD_EMBEDDED_CHAT__?: boolean; - /** @deprecated Older injected name; treated as on when true. */ - __HERMES_DASHBOARD_TUI__?: boolean; } } -/** True only when the dashboard was started with embedded TUI Chat (`hermes dashboard --tui`). */ +/** + * Whether the dashboard's embedded TUI Chat surface is available. + * + * The embedded chat (`/chat` tab, `/api/ws` + `/api/pty` WebSockets) is now + * an unconditional part of the dashboard — the desktop app and the in-browser + * Chat tab both depend on it — so this always returns `true`. The function is + * retained as a stable seam so call sites don't need to change if the surface + * ever becomes conditional again. + */ export function isDashboardEmbeddedChatEnabled(): boolean { - if (typeof window === "undefined") return false; - if (window.__HERMES_DASHBOARD_EMBEDDED_CHAT__ === true) return true; - return window.__HERMES_DASHBOARD_TUI__ === true; + return true; } diff --git a/web/vite.config.ts b/web/vite.config.ts index 24654173f..fc92eb924 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -19,8 +19,6 @@ function hermesDevToken(): Plugin { const TOKEN_RE = /window\.__HERMES_SESSION_TOKEN__\s*=\s*"([^"]+)"/; const EMBEDDED_RE = /window\.__HERMES_DASHBOARD_EMBEDDED_CHAT__\s*=\s*(true|false)/; - const LEGACY_TUI_RE = - /window\.__HERMES_DASHBOARD_TUI__\s*=\s*(true|false)/; return { name: "hermes:dev-session-token", @@ -38,12 +36,7 @@ function hermesDevToken(): Plugin { return; } const embeddedMatch = html.match(EMBEDDED_RE); - const legacyMatch = html.match(LEGACY_TUI_RE); - const embeddedJs = embeddedMatch - ? embeddedMatch[1] - : legacyMatch - ? legacyMatch[1] - : "false"; + const embeddedJs = embeddedMatch ? embeddedMatch[1] : "true"; return [ { tag: "script", diff --git a/website/docs/reference/cli-commands.md b/website/docs/reference/cli-commands.md index 593d32e5c..945dd73d5 100644 --- a/website/docs/reference/cli-commands.md +++ b/website/docs/reference/cli-commands.md @@ -1342,14 +1342,13 @@ hermes claw migrate --source /home/user/old-openclaw hermes dashboard [options] ``` -Launch the web dashboard — a browser-based UI for managing configuration, API keys, and monitoring sessions. Requires `pip install hermes-agent[web]` (FastAPI + Uvicorn). The embedded browser Chat tab requires `--tui` plus the `pty` extra. See [Web Dashboard](/user-guide/features/web-dashboard) for full documentation. +Launch the web dashboard — a browser-based UI for managing configuration, API keys, and monitoring sessions. Requires `pip install hermes-agent[web]` (FastAPI + Uvicorn). The embedded browser Chat tab is always available and additionally needs the `pty` extra (`pip install 'hermes-agent[web,pty]'`) plus a POSIX PTY environment such as Linux, macOS, or WSL2. See [Web Dashboard](/user-guide/features/web-dashboard) for full documentation. | Option | Default | Description | |--------|---------|-------------| | `--port` | `9119` | Port to run the web server on | | `--host` | `127.0.0.1` | Bind address | | `--no-open` | — | Don't auto-open the browser | -| `--tui` | off | Enable the in-browser Chat tab by running `hermes --tui` behind a PTY/WebSocket bridge. Requires `pip install 'hermes-agent[web,pty]'` and a POSIX PTY environment such as Linux, macOS, or WSL2. | | `--insecure` | off | Allow binding to non-localhost hosts. Exposes dashboard credentials on the network; use only behind trusted network controls. | | `--stop` | — | Stop running `hermes dashboard` processes and exit. | | `--status` | — | List running `hermes dashboard` processes and exit. | @@ -1360,9 +1359,6 @@ hermes dashboard # Custom port, no browser hermes dashboard --port 8080 --no-open - -# Enable the browser Chat tab -hermes dashboard --tui ``` ## `hermes profile` diff --git a/website/docs/reference/environment-variables.md b/website/docs/reference/environment-variables.md index 8803ebe93..c74e1067f 100644 --- a/website/docs/reference/environment-variables.md +++ b/website/docs/reference/environment-variables.md @@ -425,7 +425,6 @@ Auth for the [web dashboard](/user-guide/features/web-dashboard) and for connect | `HERMES_DASHBOARD_SESSION_TOKEN` | Pins the dashboard session token instead of generating a random one per boot. Set this (e.g. `openssl rand -base64 32`) on the backend, then paste the same value into Hermes Desktop → Settings → Gateway → Remote gateway → Session token. Required for a stable remote desktop connection. | | `HERMES_DESKTOP_REMOTE_URL` | (Desktop side) Base URL of the remote backend, e.g. `http://host:9119`. When set, overrides the in-app Gateway settings. Must be paired with `HERMES_DESKTOP_REMOTE_TOKEN`. | | `HERMES_DESKTOP_REMOTE_TOKEN` | (Desktop side) The session token to authenticate with the remote backend — the same value as the backend's `HERMES_DASHBOARD_SESSION_TOKEN`. | -| `HERMES_DASHBOARD_TUI` | `1` exposes the in-browser Chat tab (embedded `hermes --tui`), same as the `--tui` flag. | | `HERMES_DASHBOARD_OAUTH_CLIENT_ID` | OAuth client id (`agent:{instance_id}`) for the gated/public dashboard. Overrides `dashboard.oauth.client_id`. Provisioned by the Nous Portal for hosted deploys. | | `HERMES_DASHBOARD_PORTAL_URL` | OAuth portal URL (default: `https://portal.nousresearch.com`). Override only for staging/custom deploys. | | `HERMES_DASHBOARD_PUBLIC_URL` | Complete public URL the dashboard is reached at, for OAuth callback construction behind reverse proxies. Overrides `dashboard.public_url`. | diff --git a/website/docs/user-guide/desktop.md b/website/docs/user-guide/desktop.md index 5eb2fcb17..782be06a0 100644 --- a/website/docs/user-guide/desktop.md +++ b/website/docs/user-guide/desktop.md @@ -57,7 +57,7 @@ The center of the app. You get: - **Drag-and-drop files** anywhere in the chat area to attach them to your next message. - **A right-hand preview rail** — render web pages, files, and tool outputs side by side while you keep chatting. -Chatting against a Hermes instance on another machine instead of the bundled local backend? See [Connecting to a remote backend](#connecting-to-a-remote-backend) below — and for the full picture of how the remote-hosted dashboard connection works (the `/api/ws` chat socket, the `--tui` requirement, session-token pinning, and WebSocket close-code triage), see [Web Dashboard → Connecting Hermes Desktop to a remote backend](./features/web-dashboard.md#connecting-hermes-desktop-to-a-remote-backend). +Chatting against a Hermes instance on another machine instead of the bundled local backend? See [Connecting to a remote backend](#connecting-to-a-remote-backend) below — and for the full picture of how the remote-hosted dashboard connection works (the `/api/ws` chat socket, session-token pinning, and WebSocket close-code triage), see [Web Dashboard → Connecting Hermes Desktop to a remote backend](./features/web-dashboard.md#connecting-hermes-desktop-to-a-remote-backend). ### File browser @@ -104,7 +104,7 @@ To launch via the CLI, simply run `hermes desktop`. By default it installs works ## How it works -The packaged app ships only the Electron shell. On first launch it installs the Hermes Agent runtime into `HERMES_HOME` (`~/.hermes`, or `%LOCALAPPDATA%\hermes` on Windows) — **the same layout a CLI install uses**, which is why the two are interchangeable. The React renderer talks to a `hermes dashboard --tui` backend over the standard gateway APIs and reuses the agent rather than reimplementing it. Install, backend-resolution, and self-update logic live in the Electron main process. +The packaged app ships only the Electron shell. On first launch it installs the Hermes Agent runtime into `HERMES_HOME` (`~/.hermes`, or `%LOCALAPPDATA%\hermes` on Windows) — **the same layout a CLI install uses**, which is why the two are interchangeable. The React renderer talks to a `hermes dashboard` backend over the standard gateway APIs and reuses the agent rather than reimplementing it. Install, backend-resolution, and self-update logic live in the Electron main process. ## Connecting to a remote backend @@ -115,8 +115,6 @@ By default the app starts and manages its own **local** backend. You can instead The session token is the part that trips people up. **Hermes does not print it for you to copy** — by default the backend mints a fresh random token on every boot and injects it straight into the served HTML, so there is nothing in `config.yaml`, in `/gateway`, or in the logs to grab. For a remote connection you pin the token yourself on the backend, then paste that same value into the app. -The backend also has to be started with **`--tui`** (or `HERMES_DASHBOARD_TUI=1`). The desktop's chat runs over the dashboard's `/api/ws` + `/api/pty` WebSockets, and those endpoints are refused unless the embedded-chat surface is enabled. Without `--tui` the connection still passes the `/api/status` health check and the app reports "Remote Hermes backend is ready" — but chat never works because the WebSocket is closed immediately. A plain `hermes dashboard` or `hermes gateway` is not enough. - ### On the backend (the remote machine) ```bash @@ -129,12 +127,10 @@ 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 (a non-loopback bind WITHOUT --insecure engages # the OAuth gate, which ignores the session token). -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 ``` Running the dashboard as a systemd service? Give the unit `EnvironmentFile=%h/.hermes/.env` so the token is in the environment at boot. @@ -157,7 +153,6 @@ The token is stored encrypted in the app's local config; leave the field blank o ### Troubleshooting - **Test fails with 401** — the token doesn't match the backend's `HERMES_DASHBOARD_SESSION_TOKEN`, or the backend is bound non-loopback *without* `--insecure` (OAuth gate is on, ignoring the token). Verify with `curl -s -H "X-Hermes-Session-Token: $TOKEN" http://:9119/api/status` — that should return JSON, not a 401. -- **App says "Remote Hermes backend is ready" but chat does nothing** — the backend was started without `--tui` (or `HERMES_DASHBOARD_TUI=1`). The status probe passes, 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) 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 to copy** — expected. You mint it yourself; Hermes never surfaces the default ephemeral one. diff --git a/website/docs/user-guide/docker.md b/website/docs/user-guide/docker.md index 85b6984d8..0d7ef1bde 100644 --- a/website/docs/user-guide/docker.md +++ b/website/docs/user-guide/docker.md @@ -96,7 +96,6 @@ The dashboard is supervised by s6 — if it crashes, `s6-supervise` restarts it | `HERMES_DASHBOARD` | Set to `1` (or `true` / `yes`) to enable the supervised dashboard service | *(unset — service is registered but stays down)* | | `HERMES_DASHBOARD_HOST` | Bind address for the dashboard HTTP server | `0.0.0.0` | | `HERMES_DASHBOARD_PORT` | Port for the dashboard HTTP server | `9119` | -| `HERMES_DASHBOARD_TUI` | Set to `1` to expose the in-browser Chat tab (embedded `hermes --tui` via PTY/WebSocket) | *(unset)* | | `HERMES_DASHBOARD_INSECURE` | Set to `1` (or `true` / `yes`) to bind without the OAuth auth gate. Only use on trusted networks behind a reverse proxy without the OAuth contract — the dashboard exposes API keys and session data | *(unset — gate enforced when a `DashboardAuthProvider` is registered)* | The dashboard inside the container defaults to binding `0.0.0.0` — without it, the published `-p 9119:9119` port would not be reachable from the host. To restrict the bind to container loopback (for sidecar / reverse-proxy setups), set `HERMES_DASHBOARD_HOST=127.0.0.1`. diff --git a/website/docs/user-guide/features/web-dashboard.md b/website/docs/user-guide/features/web-dashboard.md index 6a25c76ab..94eae13a0 100644 --- a/website/docs/user-guide/features/web-dashboard.md +++ b/website/docs/user-guide/features/web-dashboard.md @@ -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://: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. diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/reference/cli-commands.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/reference/cli-commands.md index 7e3da1c49..ad0e8efe2 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/reference/cli-commands.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/reference/cli-commands.md @@ -1144,14 +1144,13 @@ hermes claw migrate --source /home/user/old-openclaw hermes dashboard [options] ``` -启动 Web 控制台——基于浏览器的界面,用于管理配置、API 密钥和监控会话。需要 `pip install hermes-agent[web]`(FastAPI + Uvicorn)。内嵌浏览器 Chat 标签页需要 `--tui` 加上 `pty` extra。完整文档请参阅 [Web 控制台](/user-guide/features/web-dashboard)。 +启动 Web 控制台——基于浏览器的界面,用于管理配置、API 密钥和监控会话。需要 `pip install hermes-agent[web]`(FastAPI + Uvicorn)。内嵌浏览器 Chat 标签页始终可用,但额外需要 `pty` extra(`pip install 'hermes-agent[web,pty]'`)以及 POSIX PTY 环境(如 Linux、macOS 或 WSL2)。完整文档请参阅 [Web 控制台](/user-guide/features/web-dashboard)。 | 选项 | 默认值 | 说明 | |--------|---------|-------------| | `--port` | `9119` | Web 服务器运行端口 | | `--host` | `127.0.0.1` | 绑定地址 | | `--no-open` | — | 不自动打开浏览器 | -| `--tui` | 关闭 | 通过 PTY/WebSocket 桥接在后台运行 `hermes --tui`,启用浏览器内 Chat 标签页。需要 `pip install 'hermes-agent[web,pty]'` 以及 Linux、macOS 或 WSL2 等 POSIX PTY 环境。 | | `--insecure` | 关闭 | 允许绑定到非 localhost 主机。会在网络上暴露控制台凭据;仅在受信任的网络控制下使用。 | | `--stop` | — | 停止正在运行的 `hermes dashboard` 进程并退出。 | | `--status` | — | 列出正在运行的 `hermes dashboard` 进程并退出。 | @@ -1162,9 +1161,6 @@ hermes dashboard # 自定义端口,不打开浏览器 hermes dashboard --port 8080 --no-open - -# 启用浏览器 Chat 标签页 -hermes dashboard --tui ``` ## `hermes profile` diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/docker.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/docker.md index b3714bc79..5828c7a98 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/docker.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/docker.md @@ -79,7 +79,6 @@ docker run -d \ | `HERMES_DASHBOARD` | 设为 `1`(或 `true` / `yes`)以在主命令旁启动 dashboard | *(未设置——不启动 dashboard)* | | `HERMES_DASHBOARD_HOST` | dashboard HTTP 服务器的绑定地址 | `127.0.0.1` | | `HERMES_DASHBOARD_PORT` | dashboard HTTP 服务器的端口 | `9119` | -| `HERMES_DASHBOARD_TUI` | 设为 `1` 以启用浏览器内 Chat 标签页(通过 PTY/WebSocket 嵌入 `hermes --tui`) | *(未设置)* | | `HERMES_DASHBOARD_INSECURE` | 设为 `1`(或 `true` / `yes`)以在不启用 OAuth 鉴权门控的情况下绑定。仅在可信网络(且通过没有 OAuth 契约的反向代理时)使用——dashboard 会暴露 API 密钥与会话数据 | *(未设置——当注册了 `DashboardAuthProvider` 时启用门控)* | 默认情况下,dashboard 保持在回环地址(`127.0.0.1`),以避免将 diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/web-dashboard.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/web-dashboard.md index cc4f880b6..725eef2e7 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/web-dashboard.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/web-dashboard.md @@ -24,7 +24,6 @@ hermes dashboard | `--host` | `127.0.0.1` | 绑定地址 | | `--no-open` | — | 不自动打开浏览器 | | `--insecure` | 关闭 | 允许绑定到非 localhost 主机(**危险**——会在网络上暴露 API 密钥;请配合防火墙和强认证使用) | -| `--tui` | 关闭 | 启用浏览器内 Chat 标签页(通过 PTY/WebSocket 嵌入 `hermes --tui`)。也可设置 `HERMES_DASHBOARD_TUI=1`。 | ```bash # 自定义端口 @@ -35,9 +34,6 @@ hermes dashboard --host 0.0.0.0 # 启动时不打开浏览器 hermes dashboard --no-open - -# 启用浏览器内 Chat 标签页 -hermes dashboard --tui ``` ## 前置条件 @@ -52,7 +48,7 @@ pip install 'hermes-agent[web,pty]' 在没有依赖项的情况下运行 `hermes dashboard` 时,它会告诉你需要安装什么。如果前端尚未构建且 `npm` 可用,则会在首次启动时自动构建。 -Chat 标签页在普通 `hermes dashboard` 启动时默认关闭。如需嵌入式浏览器聊天面板,请使用 `hermes dashboard --tui` 启动,或设置 `HERMES_DASHBOARD_TUI=1`。 +Chat 标签页是每次 `hermes dashboard` 启动的一部分——内嵌的浏览器聊天面板(通过 PTY/WebSocket 运行 TUI)始终可用,无需任何额外参数。 ## 页面