hermes-agent/apps/desktop
ethernet a4bc1ca502
fix(timeline): persist typed display events (#69771)
* fix(desktop): hide persisted agent-only history scaffolding

Filter verification-stop nudges and context-compaction handoffs at the
stored-history mapper boundary. Preserve a real reply when a compaction
handoff shares its stored message.

* test(desktop): build persisted E2E sessions through the real agent

Drive tui_gateway.entry over its stdio JSON-RPC transport against the mock
provider, wait for real completion events, and persist normal session history
through AIAgent and SessionDB. Migrate resume and hidden-history coverage,
including real compression and live verify-on-stop scaffolding, then remove
the unused direct SessionDB import scripts.

* fix(desktop): use the provisioned Python for real-session E2Es

Run the stdio gateway through uv's synced project environment outside the
Nix dev shell, while retaining the fully provisioned Nix Python when the
shell advertises HERMES_PYTHON_SRC_ROOT.

* fix(nix): expose the provisioned Python environment to uv

Mark the Nix-built Python environment active in the dev shell so the shared
E2E session builder can always run through `uv run --active --no-sync`.

* fix(timeline): persist typed display events

* fix(timeline): strip display-only fields from provider payloads, preserve through rewrites, fix /resume display history

Three review findings from PR #69771:

1. Provider payload leak: display_kind and display_metadata were forwarded
   to the provider API as unknown message fields. Strict OpenAI-compatible
   backends can reject the next request after a model switch or resumed
   typed event. Strip both from the per-request api_msg copy in
   conversation_loop alongside the existing api_content pop.

2. Rewrite/import data loss: _insert_message_rows preserved display_kind
   but silently dropped display_metadata. After replace_messages,
   archive_and_compact, or session import, async-delegation completion
   events lost their task counts and fell back to generic display text.
   Add display_metadata to the INSERT columns and bind tuple.

3. CLI /resume stale recap: startup --resume A set _resume_display_history
   from A's lineage. A subsequent in-session /resume B loaded B only into
   conversation_history via get_messages_as_conversation, leaving the stale
   A display projection. _display_resumed_history preferentially read the
   stale attribute, showing A's recap for B. Switch /resume to
   get_resume_conversations and update _resume_display_history alongside
   conversation_history.

Tests: 890 Python (5 files), 35 desktop TS — all green.

* feat(tui): render typed display events as ◈ markers in the Ink TUI

The TUI was not handling display_kind at all — model switch markers and
async delegation completions rendered as opaque user messages with the
full [System: ...] text, and hidden compaction handoffs were visible.

Wire display_kind through the full TUI chain:

- _history_to_messages (tui_gateway/server.py) forwards display_kind
  and display_metadata to the gateway transcript payload.
- GatewayTranscriptMessage (gatewayTypes.ts) gains both fields.
- Msg.kind (types.ts) gains 'event' value.
- toTranscriptMessages (domain/messages.ts) maps:
  - hidden → skip entirely
  - model_switch → event "model changed"
  - async_delegation_complete → event "N background agents finished"
    (or "background agent work finished" without metadata)
- messageGroup (blockLayout.ts) routes event to its own group, with
  SELF_SPACED + PAINTS_TRAILING_GAP so it owns its margins.
- messageLine.tsx renders event-kind as a dim ◈ marker with no gutter,
  matching the CLI's ◈ event rendering.
- 4 new TUI tests for hidden/model_switch/async_delegation mapping.

TUI typecheck: clean. TUI lint: 0 errors (2 pre-existing warnings).
TUI tests: 9 passed (1 pre-existing failure on main, unrelated).
2026-07-23 14:46:24 -04:00
..
assets
e2e fix(timeline): persist typed display events (#69771) 2026-07-23 14:46:24 -04:00
electron fmt(js): npm run fix on merge (#69805) 2026-07-23 03:32:32 +00:00
pr-assets chore(desktop): drop PR screenshot assets from tree 2026-07-07 13:04:32 -07:00
public fix(providers): align Fireworks integration with project policy 2026-07-11 05:43:35 -07:00
scripts feat(desktop/e2e): Playwright E2E suite with visual regression diffs 2026-07-20 11:44:40 -04:00
src fix(timeline): persist typed display events (#69771) 2026-07-23 14:46:24 -04:00
AGENTS.md fix(desktop): avoid false remote gateway reauthentication (#68250) 2026-07-20 20:54:36 -04:00
components.json feat(desktop): add shared project UI primitives 2026-06-25 16:40:27 -05:00
DESIGN.md feat(desktop): button tooltip keybind hints + keybinds settings tab + unified worktree dialog (#65204) 2026-07-16 18:26:21 -04:00
eslint.config.mjs refactor(lint): hoist shared eslint + prettier config to root 2026-07-16 01:42:02 +05:30
index.html
package.json test(desktop): add E2E coverage for session lifecycle (#69580) 2026-07-22 22:56:13 +00:00
playwright.config.ts fix(desktop): address review — overlay a11y, e2e typecheck, nits 2026-07-20 14:37:39 -04:00
preview-demo.html
README.md Merge pull request #63103 from NousResearch/bb/desktop-docs-alignment 2026-07-12 04:34:21 -05:00
tsconfig.e2e.json fix(desktop): address review — overlay a11y, e2e typecheck, nits 2026-07-20 14:37:39 -04:00
tsconfig.electron.json feat(desktop): ts-ify everything 2026-07-08 16:24:16 -07:00
tsconfig.json fix(desktop): minor type fixes and devShell cage dep 2026-07-20 11:44:40 -04:00
vite.config.ts feat(desktop): billing settings tab (#61054) 2026-07-18 19:38:02 +05:30
vitest.config.ts fix(desktop): bump skills test timeout to fix cold-start flake (#68235) 2026-07-20 21:27:34 +00:00
vitest.setup.ts test(desktop): widen Testing Library async deadline to de-flake UI panels (#67849) 2026-07-20 04:09:40 +00:00

Hermes Desktop ☤

Download Documentation Discord License: MIT

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 agentStreaming responses, live tool activity, structured tool summaries, and the same conversation history as every other Hermes surface.
Side-by-side previewsRender web pages, files, and tool outputs in a right-hand pane while you keep chatting.
File browserExplore and preview the working directory without leaving the app.
VoiceTalk to Hermes and hear it back.
Settings & onboardingManage providers, models, tools, and credentials from a real UI. First-run setup gets you to your first message in seconds.
Stays currentBuilt-in updates pull the latest agent and rebuild the app in place.

Install

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. On first launch Hermes walks you through picking a provider and model; nothing else to configure.

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/react transcript.
  • Hermes Agent runs as a headless hermes serve process and exposes the tui_gateway JSON-RPC/WebSocket API. The renderer connects through apps/shared, which is also used by the browser dashboard.

Backend resolution is an ordered ladder:

  1. HERMES_DESKTOP_HERMES_ROOT
  2. the current source checkout during development
  3. a completed managed install
  4. HERMES_DESKTOP_HERMES, or hermes on PATH
  5. a system Python that can import the Hermes runtime
  6. 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.

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 the HERMES_HOME env var if you've relocated it.


Community


License

MIT — see LICENSE.

Built by Nous Research.