Adds the checks that make the constraints from Phase 001-007 mechanical
instead of tribal knowledge, and clears the legacy brand text that was still
reachable at runtime.
New red-line tests:
- no-legacy-brand mirrors the harness scan under vitest, and asserts its
allow-list has not drifted from harness-check.mjs
- hermes-db-readonly classifies every new DatabaseSync call site by
following the path it opens, and fails if a state.db handle drops
readOnly. It also pins the one legitimately writable database.
- i18n-parity compares the raw zh/en locale objects both ways. The
existing coverage test merged en into zh at import time, so a key
missing from zh could never fail there.
harness:check gains the four missing gates from the phase brief - retired
package directories, retired package scripts, a single UI library, and the
required root files. Each was verified to fail by mutating the repo.
The online-update channel had hardcoded upstream URLs. It now reads
HERMES_WEB_UI_VERSION_MANIFEST_URL and friends and degrades with a named
error, because a reimplementation has nowhere to pull releases from.
The remaining legacy strings are deliberate and each carries a reason in the
allow-list: the global-agent relay address and the OpenRouter referer are
load-bearing, the socket role identifier is a wire contract, and the pre-rename
DB filenames would orphan existing deployments if changed. AGENTS.md now says
so explicitly, so a future agent does not "clean them up".
Also removes a dead electron mock alias, an i18n exemption that had stopped
exempting anything, and a teardown race in plugins-view.test.ts where a-spin
animation frames outlived the jsdom environment.
Verified self-contained: with reference_code/ moved aside, harness:check,
build, test and test:e2e all pass, so the directory can now be deleted.
5.6 KiB
5.6 KiB
Architecture
hermes-ui is a TypeScript project that ships a browser dashboard and a Koa backend around Hermes Agent. It runs on Node.js and talks to Hermes over an Agent Bridge (see "Hermes Integration" below).
Package Boundaries
| Area | Path | Responsibility |
|---|---|---|
| Client | packages/client/src |
Vue UI, routing, Pinia stores, API wrappers, i18n, browser-visible state. |
| Server | packages/server/src |
HTTP API, auth, Socket.IO, SQLite stores, file access, Hermes runtime integration. |
| Skills | packages/skills |
Built-in skills injected into the Hermes skills directory. |
| Tests | tests |
Vitest unit/integration tests and Playwright browser tests. |
| CI | .github/workflows |
Build, e2e, lockfile, Docker, and release automation. |
This is not an npm workspaces monorepo. packages/client and
packages/server have no package.json; their dependencies are declared in the
root package.json.
Request Flow
- The browser loads the Vite-built client from the Koa server.
- Client modules call API helpers from
packages/client/src/api. - Server routes in
packages/server/src/routeswire HTTP paths to controllers. - Controllers validate request concerns and delegate reusable behavior to services.
- Services own side effects: files, SQLite, Hermes profiles, subprocesses, bridges, and credentials.
- Long-running chat and group-chat flows use Socket.IO namespaces managed by server services.
Keep each layer narrow. Routes should not grow business logic, and client code should not duplicate server persistence rules.
State And Data Ownership
- Web UI state defaults to
~/.hermes-web-uithroughconfig.appHome. HERMES_WEB_UI_HOMEandHERMES_WEBUI_STATE_DIRoverride Web UI state location. These identifiers are intentionally kept from the 0.6.x line: renaming them would break existing deployment scripts and orphan existing data directories.- Hermes Agent state lives under Hermes profile directories and must stay distinct
from Web UI state. Profile isolation is done by injecting
HERMES_HOMEinto child processes, never by copying directories. - Hermes
state.dbis read-only and owned by the Hermes Agent. Node-side code must never open it; read session history through the bridge'sgetHistory/getOutput. - The project's own SQLite store under
packages/server/src/dbis the only writable store for sessions, messages, and usage. There is no startup import sync from Hermes state. - Every
state.dbhandle in Node-side code opens withreadOnly: true;tests/server/hermes-db-readonly.test.tsclassifies eachnew DatabaseSynccall site and fails if one of them drops the flag or opens it writable. - Uploads default to
config.uploadDir, which is derived from the Web UI home unlessUPLOAD_DIRis set. - Runtime data directories must also live under the Web UI home, not beside built
distassets. - Profile-scoped Hermes data should use existing profile helpers instead of manually joining paths.
Server Structure
routes/registers HTTP and WebSocket entry points.controllers/handles request-level behavior.services/owns reusable IO, domain behavior, external process calls, and integration logic.db/owns SQLite schemas and stores.middleware/owns request middleware such as user auth.shared/contains cross-server constants and helpers.
Architecture rules:
- Register local API routes before proxy catch-all routes.
- Keep auth behavior centralized in
packages/server/src/services/auth.ts. - Prefer
execFileorspawnwith argument arrays over shell command strings. - All Hermes CLI calls go through
execHermesWithBin/spawnHermesWithBininservices/hermes/hermes-process.ts. - Use structured file and YAML/JSON parsers when editing structured data.
Hermes Integration
Chat runs through the Agent Bridge, which is the only supported path:
browser --Socket.IO--> Koa/Node server --JSON over Unix socket--> Python child
└- hermes-agent run_agent.AIAgent
services/hermes/agent-bridge/python/is vendored, read-only code. The only exception is theHERMES_DESKTOPchecks inbridge_runtime.py, which hide the Windows console window for git subprocesses and must stay.services/hermes/group-chat/,lib/context-compressor/, andservices/hermes/context-engine/form the chat chain. Changes there require adocs/chat-chain-changes/<date>-<slug>.mdfragment.- The Hermes CLI is user-installed. Point
HERMES_BINat the executable, or let the server resolvehermesfromPATH.
Client Structure
views/contains route-level screens.components/contains reusable UI.stores/contains Pinia state.api/contains HTTP clients and should usepackages/client/src/api/client.ts.i18n/contains locale messages for user-facing strings. Onlyzhandenare supported.styles/contains global styling and theme primitives.
Frontend rules:
- Use Vue 3 Composition API with
<script setup lang="ts">. - Use Arco Design Vue components. Naive UI is being removed; do not add new
naive-uiimports. - Add visible text to every locale file.
- Keep component styles scoped unless the style is intentionally global.
Validation Surface
The minimum mechanical harness is:
npm run harness:checkfor repository docs, workflow, and package-script invariants.npm run testor focused Vitest tests for local logic.npm run test:e2efor browser-visible routing/auth/chat regressions.npm run buildfor type checking and production bundles.
See docs/harness/validation.md for change-specific commands.