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.
117 lines
5.6 KiB
Markdown
117 lines
5.6 KiB
Markdown
# 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
|
|
|
|
1. The browser loads the Vite-built client from the Koa server.
|
|
2. Client modules call API helpers from `packages/client/src/api`.
|
|
3. Server routes in `packages/server/src/routes` wire HTTP paths to controllers.
|
|
4. Controllers validate request concerns and delegate reusable behavior to services.
|
|
5. Services own side effects: files, SQLite, Hermes profiles, subprocesses, bridges, and credentials.
|
|
6. 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-ui` through `config.appHome`.
|
|
- `HERMES_WEB_UI_HOME` and `HERMES_WEBUI_STATE_DIR` override 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_HOME` into child
|
|
processes, never by copying directories.
|
|
- Hermes `state.db` is read-only and owned by the Hermes Agent. Node-side code must
|
|
never open it; read session history through the bridge's `getHistory`/`getOutput`.
|
|
- The project's own SQLite store under `packages/server/src/db` is the only writable
|
|
store for sessions, messages, and usage. There is no startup import sync from
|
|
Hermes state.
|
|
- Every `state.db` handle in Node-side code opens with `readOnly: true`;
|
|
`tests/server/hermes-db-readonly.test.ts` classifies each `new DatabaseSync` call
|
|
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 unless `UPLOAD_DIR` is set.
|
|
- Runtime data directories must also live under the Web UI home, not beside built `dist` assets.
|
|
- 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 `execFile` or `spawn` with argument arrays over shell command strings.
|
|
- All Hermes CLI calls go through `execHermesWithBin`/`spawnHermesWithBin` in
|
|
`services/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 the `HERMES_DESKTOP` checks in `bridge_runtime.py`, which hide the
|
|
Windows console window for git subprocesses and must stay.
|
|
- `services/hermes/group-chat/`, `lib/context-compressor/`, and
|
|
`services/hermes/context-engine/` form the chat chain. Changes there require a
|
|
`docs/chat-chain-changes/<date>-<slug>.md` fragment.
|
|
- The Hermes CLI is user-installed. Point `HERMES_BIN` at the executable, or let
|
|
the server resolve `hermes` from `PATH`.
|
|
|
|
## Client Structure
|
|
|
|
- `views/` contains route-level screens.
|
|
- `components/` contains reusable UI.
|
|
- `stores/` contains Pinia state.
|
|
- `api/` contains HTTP clients and should use `packages/client/src/api/client.ts`.
|
|
- `i18n/` contains locale messages for user-facing strings. Only `zh` and `en` are 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-ui` imports.
|
|
- 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:check` for repository docs, workflow, and package-script invariants.
|
|
- `npm run test` or focused Vitest tests for local logic.
|
|
- `npm run test:e2e` for browser-visible routing/auth/chat regressions.
|
|
- `npm run build` for type checking and production bundles.
|
|
|
|
See `docs/harness/validation.md` for change-specific commands.
|