* fix(desktop): hide the terminal overlay when its pane is inactive One xterm is CSS-overlayed onto whichever `<TerminalSlot />` is active, positioned with `position: fixed` from the slot's bounding rect. Keep-alive tab layers stay MOUNTED when inactive — hidden with `visibility: hidden` + `data-pane-hidden`, deliberately preserving their layout box so scroll state and xterm survive a tab round-trip. So an inactive terminal slot still reports a full-size rect identical to the front tab's, and `rect.width > 0 && rect.height > 0` cannot tell the two apart. The overlay stayed painted at z-4 over whatever tab the user switched to, swallowing its clicks. Sample the hidden state alongside the geometry: `Rect` carries `hidden` from `isElementInHiddenPane(slot)`, `sameRect` compares it so a tab switch wakes the tracker, the ancestor MutationObserver watches `PANE_HIDDEN_ATTR`, and the overlay gates on `!rect.hidden`. `TerminalWorkspace` stays mounted throughout — PTYs are never torn down, only the surface stops painting. `opacity: 0` rides alongside `visibility: hidden` because Electron can keep xterm's WebGL canvas composited after an ancestor goes hidden. Refs #71407 * fix(desktop): collapse an active tool pane onto the workspace, not a neighbour `setPaneCollapsed` on the ACTIVE pane of a shared zone that holds the uncloseable workspace handed the active slot to `group.panes[at - 1]` — the tab to its left, whichever that happened to be. The workspace can't minimize (it would strand the app), so tab-switching to a sibling is the right shape; picking a positional neighbour is not. In the Focus preset the terminal is a tab in the workspace's own group: [workspace, files, review, terminal] Collapsing the active terminal therefore selected `review`. The user asked for the terminal to go away and landed on a diff pane they never opened — and with the overlay still painting (before the previous commit), it read as "the terminal came back". Hand the slot to the uncloseable pane itself. That pane is the zone's anchor: it's the one member guaranteed to be a real destination rather than another tool the user was not asking for. The positional fallback stays for the defensive case of collapsing the uncloseable pane itself. This is deliberately broader than one entry point — every route into `setPaneCollapsed` for a shared zone gets it: the rail, the tab toggle, and ⌃`. Pure tool-only zones are untouched and still fold as a unit. * fix(desktop): front the workspace when a fresh chat starts `startFreshSessionDraft` resets the whole view — messages, usage, timers, route intent, cwd — but left `$terminalTakeover` set. That atom is not a cosmetic flag: `controller.tsx` binds it as the terminal's toggle store via `bindToolPaneCollapse`, so while it stays true the terminal keeps the pane fronted and ⌘N appeared to bounce straight back into the shell. Clear it, then `revealTreePane('workspace')`. The reveal is not redundant with the clear: takeover can already be false while the terminal is simply the active tab (the flag stays true behind a stacked sibling, and tile flows never touch it), so the state the user sees and the state the flag describes drift apart. Clearing homes the common case; revealing states the intent outright — a new chat shows the chat. The terminal is not torn down. Tool panels collapse to a rail and keep their PTYs; re-opening finds the same shell. The `+` / ⌘T tile path needs no takeover clear — it fronts its new tile through `revealTreePane` and relies on the hidden-pane-aware overlay. * fix(desktop): reveal the workspace without closing the terminal The fresh-session commit cleared `$terminalTakeover` on the way to fronting the workspace. That atom is not a Focus-only fronting flag — it is the terminal's open/closed state in every layout, and clearing it is wrong twice over. Only the Focus preset stacks the terminal with the workspace. Default, Terminal deck, and Quad each give it a zone of its own, where it sits beside the chat and obscures nothing — and there ⌘N minimized a terminal the user had deliberately open. The flag is also persisted, so the damage outlived the session. On the next boot the Focus terminal tab is still in the strip and its zone is not minimized, so clicking it only calls `activateTreePane`; `PersistentTerminal` mounts its workspace solely while takeover is true, so the tab fronted empty. `revealTreePane('workspace')` already carries the whole intent. Behind another tab the terminal is HIDDEN, not closed: it keeps its PTYs, and the overlay stops painting on the pane-hidden marker from the first commit in this branch — which is what was actually covering the chat. Removing the clear costs nothing and keeps the toggle store truthful. Two regression tests, both verified to fail when the clear is reinstated: a terminal in its own zone stays open and visible across a fresh chat, and a Focus terminal tab still mounts after a restart. Reported by Copilot review on #81019. --------- Co-authored-by: izumi0uu <izumi0uu@gmail.com> Co-authored-by: Ritesh Patel <60716910+DECRUX9812@users.noreply.github.com>
Hermes Desktop ☤
The native desktop app for Hermes Agent — the self-improving AI agent from Nous Research. Same agent, same skills, same memory as the CLI and gateway, in a polished native window — chat with streaming tool output, side-by-side previews, a file browser, voice, and settings, no terminal required. Available for macOS, Windows, and Linux.
| Chat with the full agent | Streaming responses, live tool activity, structured tool summaries, and the same conversation history as every other Hermes surface. |
| Side-by-side previews | Render web pages, files, and tool outputs in a right-hand pane while you keep chatting. |
| File browser | Explore and preview the working directory without leaving the app. |
| Voice | Talk to Hermes and hear it back. |
| Settings & onboarding | Manage providers, models, tools, and credentials from a real UI. First-run setup gets you to your first message in seconds. |
| Stays current | Built-in updates pull the latest agent and rebuild the app in place. |
Install
Install with Hermes (recommended)
Already have the Hermes CLI? Just run:
hermes desktop
It builds and launches the GUI against your existing install — same config, keys, sessions, and skills. If Desktop cannot find a usable runtime or saved remote connection, first launch lets you connect to an existing Hermes gateway or install Hermes locally. Local onboarding then walks you through choosing a provider and model.
Prebuilt installers
Prebuilt installers are built and distributed via the Hermes Desktop website..
Updating
The app checks for updates in the background and offers a one-click update when one is ready. You can also update any time from the CLI:
hermes update
Requirements
The installer handles everything for you (Python 3.11+, a portable Git, ripgrep).
Development
Want to hack on the app itself? Install workspace deps from the repo root once, then run the dev server from this directory:
npm install # from repo root — links apps/desktop, web, apps/shared
cd apps/desktop
npm run dev # Vite renderer + Electron, which boots the Python backend
Point the app at a specific source checkout, or sandbox it away from your real config:
# throwaway HERMES_HOME, separate Electron userData, distinct app name to avoid the single-instance lock
../scripts/dev-sandbox.sh npm run dev
HERMES_DESKTOP_HERMES_ROOT=/path/to/clone npm run dev
HERMES_HOME=/tmp/throwaway npm run dev
npm run dev:fake-boot # exercise the startup overlay with deterministic delays
Building installers
npm run dist:mac # DMG + zip
npm run dist:win # NSIS + MSI
npm run dist:linux # AppImage + deb + rpm
npm run pack # unpacked app under release/ (no installer)
Installers are built and uploaded to GitHub Releases manually. macOS/Windows signing & notarization happen automatically when the relevant credentials are present in the environment (CSC_LINK / CSC_KEY_PASSWORD / APPLE_* for macOS, WIN_CSC_* for Windows).
How it works
The packaged app ships the Electron shell and a native React chat surface. On
first launch it can install the Hermes Agent runtime into HERMES_HOME
(~/.hermes, or %LOCALAPPDATA%\hermes on Windows), using the same layout as a
CLI install.
The app has three boundaries:
- Electron resolves and validates a runnable backend, owns native filesystem/git/window capabilities, and exposes a narrow preload bridge.
- React owns the Desktop routes, panes, interaction state, and
@assistant-ui/reacttranscript. - Hermes Agent runs as a headless
hermes serveprocess and exposes thetui_gatewayJSON-RPC/WebSocket API. The renderer connects throughapps/shared, which is also used by the browser dashboard.
Backend resolution is an ordered ladder:
HERMES_DESKTOP_HERMES_ROOT- the current source checkout during development
- a completed managed install
HERMES_DESKTOP_HERMES, orhermesonPATH- a system Python that can import the Hermes runtime
- the first-launch bootstrap installer
Candidates are probed before use; an existing shim or interpreter is not enough.
A runtime that predates serve falls back to headless
dashboard --no-open. This is compatibility for the backend command only and
does not launch or embed the dashboard UI.
The Electron orchestration entry point is electron/main.ts; pure resolution,
probe, hardening, and platform policies live in focused modules beside it. The
renderer is under src/, with shared atoms in src/store and transport/native
adapters in src/lib.
Before changing the app, read:
AGENTS.md: architecture, state ownership, resolver/fallback, transport, performance, and testing rules.DESIGN.md: visual system, information architecture, motion, direct manipulation, and keyboard behavior.
Connections, projects, and switching
Desktop supports a managed local backend, explicit remote gateways, and Hermes Cloud connections. Remote and cloud modes use the same remote-capability path; authentication and discovery differ, not the renderer feature model.
When no usable local runtime or saved remote connection exists, the first-run screen offers Connect to existing Hermes before starting the local installer. Desktop probes the gateway to discover token or OAuth authentication, requires a successful HTTP and WebSocket connection test, and saves the connection using the same encrypted Desktop configuration used by Settings. A saved remote connection bypasses this choice on later launches. The regular Desktop build still includes the local-install option; this is a remote operating mode, not a separate client-only application.
In remote mode the gateway host is the execution boundary: agent tools, terminal commands, and file operations run against the remote Hermes host, not the computer displaying the Desktop UI.
Projects are the workspace abstraction. A project may own multiple folders, repositories, worktrees, and sessions; a bare new chat remains detached unless the user enters a project or configures a default project directory. Use the Projects UI rather than adding a second per-session folder-picker workflow.
Changing profiles or connection modes is a soft workspace switch, not another cold boot. The shell and current management overlay remain mounted while gateway-bound nanostores are wiped, query-backed data is invalidated, and the new connection repopulates skeletons. This prevents rows or transcripts from the previous gateway bleeding into the next one.
Verification
Run before opening a PR (lint may surface pre-existing warnings but must exit cleanly):
npm run fix
npm run typecheck
npm run lint
npm run test:ui
npm run test:desktop:platforms
Run npm run test:desktop:all for install, boot, update, packaging, or other
release-path changes.
Troubleshooting
Boot logs land in HERMES_HOME/logs/desktop.log (includes backend output and recent Python tracebacks) — check it first if the app reports a boot failure.
macOS / Linux:
# Force a clean first-launch setup
rm "$HOME/.hermes/hermes-agent/.hermes-bootstrap-complete"
# Rebuild a broken Python venv
rm -rf "$HOME/.hermes/hermes-agent/venv"
# Reset a stuck macOS microphone prompt (macOS only)
tccutil reset Microphone com.nousresearch.hermes
Windows (PowerShell):
# Force a clean first-launch setup
Remove-Item "$env:LOCALAPPDATA\hermes\hermes-agent\.hermes-bootstrap-complete"
# Rebuild a broken Python venv
Remove-Item -Recurse -Force "$env:LOCALAPPDATA\hermes\hermes-agent\venv"
The default Hermes home on Windows is
%LOCALAPPDATA%\hermes. Set theHERMES_HOMEenv var if you've relocated it.
Community
- 💬 Discord
- 📖 Documentation
- 🐛 Issues
License
MIT — see LICENSE.
Built by Nous Research.