Hermes Agent CN Desktop — Project Structure
Overview
Hermes Agent CN Desktop is a standalone desktop application built with Tauri v2 + React, replacing the original Electron shell. It pairs with the Hermes-CN-Core backend runtime (the CN community runtime, originally named hermes-agent-cn).
- Version: 0.6.3
- Bundle identifier:
cn.org.hermesagent.desktop (DO NOT CHANGE — used for upgrade path)
- Rust crate name:
hermes_agent_cn (lib), hermes-agent-cn-desktop (package)
- Package manager: pnpm 9.15.0 (monorepo)
The desktop-managed runtime defaults to port 9120, avoiding port 9119 used by the user's global Hermes Agent.
Top-Level Directory Layout
Hermes-CN-Desktop/
├── src/ Rust Tauri backend (~24,000 lines)
├── web/ React frontend (Vite + TanStack Query + Jotai)
├── packages/
│ ├── protocol/ Shared Zod schemas, IPC types, session log parsing
│ └── shared-ui/ Design tokens, shared React components, hooks
├── e2e/ Playwright E2E (real web → real Core backend → fake model)
├── tests/ Rust integration tests (crate: hermes_agent_cn)
├── scripts/ Build & dev automation scripts (.mjs)
├── static/ Stage targets for bundled builds
│ ├── bundled-runtime/ Managed runtime binaries
│ ├── bundled-skills/ Bundled skill packs
│ ├── bundled-plugins/ Bundled plugins
│ └── dashboard/ Dashboard web dist
├── docs/ Project documentation & PRD specs
├── icons/ App icons (Windows/macOS/Linux)
├── installer/ NSIS installer customization
├── legal/ EULA & license files
├── gen/schemas/ Generated JSON schemas
├── capabilities/ Tauri capability definitions
├── .github/workflows/ CI/CD pipelines
├── Cargo.toml Rust dependencies & build config
├── tauri.conf.json Tauri window/bundle/CSP configuration
├── pnpm-workspace.yaml pnpm monorepo (web + packages/* + e2e)
└── package.json Workspace root scripts
Rust Backend (src/)
The Rust crate hermes_agent_cn is the Tauri backend. All source lives under src/.
Entry Points & Core Modules
| File |
Purpose |
main.rs |
Entry point: resolves HERMES_HOME, starts dashboard, registers 60 commands (generate_handler!), system tray |
lib.rs |
Library root: declares 18 public modules |
state.rs |
AppState (Mutex<AppStateInner>) — shared state injected into every Tauri command |
error.rs |
AppError unified domain error type (thiserror + Serialize) |
tray.rs |
System tray menu |
Bootstrap & Environment
| File |
Purpose |
bootstrap.rs |
Startup sequence: probe/spawn dashboard, connect backend, fetch token |
environment.rs |
Environment resolution and validation |
connection.rs |
Connection backend + mode (local/remote), remote-mode WS connection |
path_resolver.rs |
PATH/PATHEXT resolution for child processes |
env_file.rs |
Parse $HERMES_HOME/.env for per-spawn child env injection |
Runtime & Process Management
| File |
Purpose |
supervisor.rs |
Child process supervision |
prevent_sleep.rs |
Keep-awake during long-running tasks |
cron_runs.rs |
Scheduled task orchestration |
update_stage.rs |
Update staging logic |
process/ |
Subprocess management |
process/dashboard.rs |
Dashboard subprocess: probe/spawn/port fallback |
process/gateway.rs |
Gateway subprocess + conflict detection |
process/runtime.rs |
Managed runtime install/signature verification |
process/instance.rs |
Single-instance guard per runtime root |
process/port_lock.rs |
Port lock management |
Session & Logging
| File |
Purpose |
session_archive.rs |
Session archiving |
session_log.rs |
Session log reading |
oauth_session.rs |
OAuth session handling |
Other Core Modules
| File |
Purpose |
coding_agents.rs |
Coding agent management |
desktop_control.rs |
Desktop-level controls |
ui_store.rs |
UI state persistence |
util.rs |
Shared utilities |
Commands (src/commands/) — 60 Tauri IPC Commands
| Module |
Purpose |
api_proxy.rs |
HTTP proxy: api_request, external_request, upload_file |
ws_proxy.rs |
/api/ws WebSocket relay (fallback when webview native WS blocked) |
gateway.rs |
Runtime config + gateway URL refresh |
runtime_manager.rs |
Managed runtime download/update/rollback |
desktop_update.rs |
Desktop self-update |
profiles.rs |
Profile switching (incl. fault recovery) |
config_migration.rs |
Configuration migration |
im_onboarding.rs |
Feishu/DingTalk/WeCom/WeChat onboarding |
connection.rs |
Connection management commands |
connection_auth.rs |
OAuth-based connection authentication |
backup.rs |
Backup operations |
memory.rs |
Memory management |
skills.rs |
Skill management (hidden from mod.rs) |
terminal.rs |
Embedded terminal (portable-pty) |
log_export.rs |
Log export |
debug_bundle.rs |
Debug bundle generation |
notify.rs |
Desktop notifications |
preview.rs |
File preview with native filesystem watch |
environment.rs |
Environment variables |
file_dialogs.rs |
Native file dialogs |
restart.rs |
App restart |
ui_store.rs |
UI state persistence commands |
yolo.rs |
YOLO mode |
devtools.rs |
WebView devtools toggle |
coding_agents.rs |
Coding agent management commands |
git.rs |
Git operations |
React Frontend (web/)
Built with Vite + React 19 + TanStack Query + Jotai. CSS Modules (no Tailwind).
Key Files & Directories
web/src/
├── main.tsx App entry
├── App.tsx Root component
├── lib/ Core library (~156 files, most with co-located tests)
│ ├── tauri-bridge.ts Tauri invoke wrapper + hermesDesktop shim
│ ├── runtime.ts Platform detection (web / electron / tauri)
│ ├── transport.ts HTTP routing (native IPC vs fetch) + auth header injection
│ ├── gateway-client.ts Gateway WS client (JSON-RPC over /api/ws, backoff/reconnect)
│ ├── gateway-socket-path.ts Native WS vs Rust relay socket path selection
│ └── ... ~150 other lib modules (models, skills, sessions, etc.)
├── hooks/ React hooks (~42 files)
│ ├── use-gateway.ts Gateway WebSocket connection hook
│ ├── use-config.ts Configuration hook
│ ├── use-sessions.ts Sessions management
│ ├── use-skills.ts Skills management
│ └── ... Many more domain hooks
├── stores/ Jotai atoms (~13 files)
│ ├── chat.ts Chat state
│ ├── panel.ts Panel state
│ ├── ui.ts UI state
│ └── ...
├── routes/ Page components (~39 files)
│ ├── guide.tsx Onboarding guide
│ ├── health.tsx Health dashboard
│ ├── settings.tsx Settings page
│ ├── chat.tsx Chat interface
│ └── ... Many more page routes
├── components/ UI components
│ ├── app-shell/ App shell layout
│ ├── chat/ Chat components (incl. preview-rail)
│ ├── composer/ Message composer (incl. workspace-picker)
│ ├── console/ Console/terminal
│ ├── settings/ Settings panels
│ ├── sidebar/ Sidebar navigation
│ ├── top-bar/ Top navigation bar
│ ├── command-palette/ Command palette (⌘K)
│ ├── mcp/ MCP server management
│ ├── profiles/ Profile management
│ ├── projects/ Project/workspace management
│ ├── session-actions/ Session actions
│ ├── panel/ Panel components
│ ├── brand/ Branding components
│ └── ui/ Generic UI primitives
├── styles/ Global styles
├── types/ TypeScript type definitions
└── assets/ Static assets (incl. provider-icons)
State Management Strategy
| Layer |
Technology |
| Server state (REST API data) |
TanStack Query |
| Local / real-time stream state |
Jotai atoms |
| Rust-side state |
AppState (Mutex<AppStateInner>) via tauri::State |
Key Architecture Patterns
- Transport: All HTTP requests go through
transport.ts (auth header injection, native IPC vs fetch routing). NEVER hand-write fetch elsewhere.
- Gateway: JSON-RPC over WebSocket at
/api/ws. Use use-gateway.ts hook, never call gateway-client.ts raw socket directly.
- Tauri Bridge:
tauri-bridge.ts mounts Tauri invoke wrappers onto window.hermesDesktop at startup. Existing code checking window.hermesDesktop?.someMethod works unchanged.
- CSS: CSS Modules only — no Tailwind, no styled-components. Design tokens in
packages/shared-ui/src/tokens/.
Packages (packages/)
@hermes/protocol
Shared type definitions and validation:
- Zod schemas for the Hermes API (
hermes-api.ts)
- IPC types
- Session log parsing (
session-log.ts)
- MCP API schemas
- Channel types
@hermes/shared-ui
Shared UI primitives:
- Design tokens (
tokens/): colors, typography, spacing, motion, z-index, component tokens, semantic tokens, primitives
- Components: alert, badge, button, card, copy-button, empty-state, field, input
- Composites: dialog, popover
- Hooks
- Utilities
Testing
Unit Tests (Vitest)
- ~93 test files across the monorepo
web/src/lib/ — most modules have co-located .test.ts files
packages/protocol/ — Zod schema tests
- Run:
pnpm test:unit (serial per workspace)
Rust Integration Tests (tests/)
- Crate name:
hermes_agent_cn
- Mock-based:
wiremock for HTTP, tempfile::TempDir for FS
- Tests:
api_proxy.rs, connection_config.rs, connection_ws_e2e.rs, dashboard_probe.rs, dashboard_spawn_retry.rs, dashboard_token.rs, runtime_manifest.rs
- Run:
cargo test --all-features
E2E Tests (e2e/)
- Playwright (real web → real Core backend → local fake model)
- Specs: chat-loop, guide-layout, image-paste, models-cli-custom-provider, skills-provenance
- Fake model server:
e2e/fake-model/server.py
- Harness: config, global-warmup, protocol-smoke, start-backend, wait
- Run:
pnpm test:e2e
Scripts (scripts/)
| Script |
Purpose |
tauri-dev-managed.mjs |
Install backend into dev-runtime, launch Tauri dev |
tauri-dev-external.mjs |
Tauri dev with external backend |
install-local-runtime.mjs |
Copy Hermes-CN-Core into dev-runtime |
install-release-dmg.mjs |
Download & install release DMG |
stage-bundled-runtime.mjs |
Stage runtime for bundled build |
stage-dashboard-web-dist.mjs |
Stage dashboard web dist |
stage-bundled-skills.mjs |
Stage bundled skills |
stage-bundled-plugins.mjs |
Stage bundled plugins |
sync-desktop-version.mjs |
Sync version across packages |
generate-license-rtf.mjs |
Generate license RTF |
migrate-runtime-trees.mjs |
Migrate polluted runtime trees |
package-portable-windows.mjs |
Create Windows portable package |
package-portable-macos.mjs |
Create macOS portable package |
only-pnpm.mjs |
Enforce pnpm as package manager |
cdp-eval.mjs |
Chrome DevTools Protocol evaluation |
Configuration Files
| File |
Purpose |
Cargo.toml |
Rust dependencies (tauri 2, tokio, reqwest, rusqlite, etc.) |
tauri.conf.json |
Tauri window config, CSP, bundle targets (NSIS/DMG/deb/AppImage) |
package.json |
Root workspace scripts (version sync, build, test) |
pnpm-workspace.yaml |
Workspace members: web, packages/*, e2e |
web/vite.config.ts |
Vite config (dev server on port 9545, strictPort) |
CI/CD (.github/workflows/)
| Workflow |
Trigger |
Purpose |
rust-test.yml |
PR / push to main |
cargo fmt --check, cargo clippy -D warnings, cargo test |
web-test.yml |
PR / push to main |
TypeScript typecheck + vitest unit tests |
web-e2e.yml |
PR / push to main |
Playwright E2E (checkout Hermes-CN-Core + fake model) |
release-desktop.yml |
Tag push |
Release build & publish |
Documentation (docs/)
| File |
Content |
agents/ |
Coding-agent policy + human git workflow(编码代理不执行 git 写操作;人工双仓同步/worktree/commit/push/PR/tag/Landing 同步见 git-workflow.md) |
desktop-prd/ |
Product Requirements Document (6 docs: feature inventory, PRD, IA, specs, backend contract, parity gap) |
gateway-connection-overhaul.md |
Gateway connection architecture |
managed-runtime.md |
Managed runtime design |
hot-update.md |
Hot update design(统一自更新 + UI 热更 + 开发热更 + 使用/验证) |
macos-signing-and-notarization.md |
macOS code signing |
portable-mode.md |
Portable mode |
yolo-mode.md |
YOLO mode |
custom-model-context-window.md |
Custom model context window |
Port Conventions
| Port |
Purpose |
| 9120 |
Hermes Dashboard (desktop managed runtime) |
| 9119 |
User global Hermes Agent (AVOID — managed runtime only) |
| 9545 |
Vite dev server (strictPort) |
Dev vs Production Mode
| Aspect |
Dev |
Production |
| WebView loads |
http://localhost:9545 (Vite) |
Bundled web/dist/ |
| REST API |
Vite proxy → dashboard (same-origin) |
Rust IPC proxy (api_request command) |
| Gateway events |
WebSocket → Vite proxy /api/ws |
Official /api/ws, fallback to Rust WS relay (ws_proxy.rs) |
| Session token |
Vite /__hermes_token endpoint |
Rust get_runtime_config command |
apiBaseUrl |
Not set (relative path) |
Set to dashboard URL |
Rust Testing Conventions
- Unit tests:
#[cfg(test)] mod tests { ... } inline in source files; can access private functions
- Integration tests: In
tests/ directory, only use pub API via hermes_agent_cn crate
- Env-dependent tests: Must use
#[serial_test::serial]
- Filesystem tests: Use
tempfile::TempDir; never write to /tmp, cwd, or fixed paths
- HTTP tests: Use
wiremock::MockServer; never real network
- Assertions: Prefer
pretty_assertions::assert_eq
- Pre-commit:
cargo test --all-features
Key Dependencies
Rust
- Tauri v2 with
tray-icon and devtools features
- tokio (full), reqwest (rustls-tls), tokio-tungstenite
- tauri-plugin-dialog, tauri-plugin-notification, tauri-plugin-clipboard-manager
- rusqlite (bundled), zip, sha2, ed25519-dalek
- thiserror, serde/serde_json, portable-pty, notify
- Platform: windows-sys + winreg (Windows), objc2 (macOS)
Frontend
- React 19, react-router 7, TanStack Query 5, Jotai 2
- @tauri-apps/api, @tauri-apps/plugin-clipboard-manager
- streamdown (Markdown renderer with CJK/math/mermaid extensions)
- Radix UI (dialog, dropdown-menu, popover)
- xterm (terminal), cmdk (command palette), recharts, lucide-react
Architecture Rules (DO NOT Violate)
- ❌ Don't hand-write
fetch outside web/src/lib/transport.ts — auth header injection lives there
- ❌ Don't call
gateway-client.ts raw socket directly — use hooks/use-gateway.ts
- ❌ Don't put business logic in
web/src/routes/ — extract to hooks/ or lib/
- ❌ Don't hardcode colors in components — use CSS variables from
packages/shared-ui/src/tokens/
- ❌ Don't change the bundle identifier
cn.org.hermesagent.desktop
- ❌ Don't use port 9119 (reserved for user's global Hermes Agent)
Commit Convention
- Conventional Commits:
feat / fix / style / docs / refactor / chore
- English subject line, imperative mood ("add ...", "fix ...", "rework ...")
- Description can mix Chinese/English; explain "why" not "what"
1---2name: project-structure3description: Hermes Agent CN Desktop — Project Structure4---56# Hermes Agent CN Desktop — Project Structure78## Overview910**Hermes Agent CN Desktop** is a standalone desktop application built with **Tauri v2 + React**, replacing the original Electron shell. It pairs with the [Hermes-CN-Core](https://github.com/Eynzof/Hermes-CN-Core) backend runtime (the CN community runtime, originally named `hermes-agent-cn`).1112- **Version**: 0.6.313- **Bundle identifier**: `cn.org.hermesagent.desktop` (DO NOT CHANGE — used for upgrade path)14- **Rust crate name**: `hermes_agent_cn` (lib), `hermes-agent-cn-desktop` (package)15- **Package manager**: pnpm 9.15.0 (monorepo)1617The desktop-managed runtime defaults to port **9120**, avoiding port 9119 used by the user's global Hermes Agent.1819---2021## Top-Level Directory Layout2223```24Hermes-CN-Desktop/25├── src/ Rust Tauri backend (~24,000 lines)26├── web/ React frontend (Vite + TanStack Query + Jotai)27├── packages/28│ ├── protocol/ Shared Zod schemas, IPC types, session log parsing29│ └── shared-ui/ Design tokens, shared React components, hooks30├── e2e/ Playwright E2E (real web → real Core backend → fake model)31├── tests/ Rust integration tests (crate: hermes_agent_cn)32├── scripts/ Build & dev automation scripts (.mjs)33├── static/ Stage targets for bundled builds34│ ├── bundled-runtime/ Managed runtime binaries35│ ├── bundled-skills/ Bundled skill packs36│ ├── bundled-plugins/ Bundled plugins37│ └── dashboard/ Dashboard web dist38├── docs/ Project documentation & PRD specs39├── icons/ App icons (Windows/macOS/Linux)40├── installer/ NSIS installer customization41├── legal/ EULA & license files42├── gen/schemas/ Generated JSON schemas43├── capabilities/ Tauri capability definitions44├── .github/workflows/ CI/CD pipelines45├── Cargo.toml Rust dependencies & build config46├── tauri.conf.json Tauri window/bundle/CSP configuration47├── pnpm-workspace.yaml pnpm monorepo (web + packages/* + e2e)48└── package.json Workspace root scripts49```5051---5253## Rust Backend (`src/`)5455The Rust crate `hermes_agent_cn` is the Tauri backend. All source lives under `src/`.5657### Entry Points & Core Modules5859| File | Purpose |60|------|---------|61| `main.rs` | Entry point: resolves `HERMES_HOME`, starts dashboard, registers **60 commands** (`generate_handler!`), system tray |62| `lib.rs` | Library root: declares **18 public modules** |63| `state.rs` | `AppState` (`Mutex<AppStateInner>`) — shared state injected into every Tauri command |64| `error.rs` | `AppError` unified domain error type (thiserror + Serialize) |65| `tray.rs` | System tray menu |6667### Bootstrap & Environment6869| File | Purpose |70|------|---------|71| `bootstrap.rs` | Startup sequence: probe/spawn dashboard, connect backend, fetch token |72| `environment.rs` | Environment resolution and validation |73| `connection.rs` | Connection backend + mode (local/remote), remote-mode WS connection |74| `path_resolver.rs` | PATH/PATHEXT resolution for child processes |75| `env_file.rs` | Parse `$HERMES_HOME/.env` for per-spawn child env injection |7677### Runtime & Process Management7879| File | Purpose |80|------|---------|81| `supervisor.rs` | Child process supervision |82| `prevent_sleep.rs` | Keep-awake during long-running tasks |83| `cron_runs.rs` | Scheduled task orchestration |84| `update_stage.rs` | Update staging logic |85| `process/` | Subprocess management |86| `process/dashboard.rs` | Dashboard subprocess: probe/spawn/port fallback |87| `process/gateway.rs` | Gateway subprocess + conflict detection |88| `process/runtime.rs` | Managed runtime install/signature verification |89| `process/instance.rs` | Single-instance guard per runtime root |90| `process/port_lock.rs` | Port lock management |9192### Session & Logging9394| File | Purpose |95|------|---------|96| `session_archive.rs` | Session archiving |97| `session_log.rs` | Session log reading |98| `oauth_session.rs` | OAuth session handling |99100### Other Core Modules101102| File | Purpose |103|------|---------|104| `coding_agents.rs` | Coding agent management |105| `desktop_control.rs` | Desktop-level controls |106| `ui_store.rs` | UI state persistence |107| `util.rs` | Shared utilities |108109### Commands (`src/commands/`) — 60 Tauri IPC Commands110111| Module | Purpose |112|--------|---------|113| `api_proxy.rs` | HTTP proxy: `api_request`, `external_request`, `upload_file` |114| `ws_proxy.rs` | /api/ws WebSocket relay (fallback when webview native WS blocked) |115| `gateway.rs` | Runtime config + gateway URL refresh |116| `runtime_manager.rs` | Managed runtime download/update/rollback |117| `desktop_update.rs` | Desktop self-update |118| `profiles.rs` | Profile switching (incl. fault recovery) |119| `config_migration.rs` | Configuration migration |120| `im_onboarding.rs` | Feishu/DingTalk/WeCom/WeChat onboarding |121| `connection.rs` | Connection management commands |122| `connection_auth.rs` | OAuth-based connection authentication |123| `backup.rs` | Backup operations |124| `memory.rs` | Memory management |125| `skills.rs` | Skill management (hidden from mod.rs) |126| `terminal.rs` | Embedded terminal (portable-pty) |127| `log_export.rs` | Log export |128| `debug_bundle.rs` | Debug bundle generation |129| `notify.rs` | Desktop notifications |130| `preview.rs` | File preview with native filesystem watch |131| `environment.rs` | Environment variables |132| `file_dialogs.rs` | Native file dialogs |133| `restart.rs` | App restart |134| `ui_store.rs` | UI state persistence commands |135| `yolo.rs` | YOLO mode |136| `devtools.rs` | WebView devtools toggle |137| `coding_agents.rs` | Coding agent management commands |138| `git.rs` | Git operations |139140---141142## React Frontend (`web/`)143144Built with **Vite + React 19 + TanStack Query + Jotai**. CSS Modules (no Tailwind).145146### Key Files & Directories147148```149web/src/150├── main.tsx App entry151├── App.tsx Root component152├── lib/ Core library (~156 files, most with co-located tests)153│ ├── tauri-bridge.ts Tauri invoke wrapper + hermesDesktop shim154│ ├── runtime.ts Platform detection (web / electron / tauri)155│ ├── transport.ts HTTP routing (native IPC vs fetch) + auth header injection156│ ├── gateway-client.ts Gateway WS client (JSON-RPC over /api/ws, backoff/reconnect)157│ ├── gateway-socket-path.ts Native WS vs Rust relay socket path selection158│ └── ... ~150 other lib modules (models, skills, sessions, etc.)159├── hooks/ React hooks (~42 files)160│ ├── use-gateway.ts Gateway WebSocket connection hook161│ ├── use-config.ts Configuration hook162│ ├── use-sessions.ts Sessions management163│ ├── use-skills.ts Skills management164│ └── ... Many more domain hooks165├── stores/ Jotai atoms (~13 files)166│ ├── chat.ts Chat state167│ ├── panel.ts Panel state168│ ├── ui.ts UI state169│ └── ...170├── routes/ Page components (~39 files)171│ ├── guide.tsx Onboarding guide172│ ├── health.tsx Health dashboard173│ ├── settings.tsx Settings page174│ ├── chat.tsx Chat interface175│ └── ... Many more page routes176├── components/ UI components177│ ├── app-shell/ App shell layout178│ ├── chat/ Chat components (incl. preview-rail)179│ ├── composer/ Message composer (incl. workspace-picker)180│ ├── console/ Console/terminal181│ ├── settings/ Settings panels182│ ├── sidebar/ Sidebar navigation183│ ├── top-bar/ Top navigation bar184│ ├── command-palette/ Command palette (⌘K)185│ ├── mcp/ MCP server management186│ ├── profiles/ Profile management187│ ├── projects/ Project/workspace management188│ ├── session-actions/ Session actions189│ ├── panel/ Panel components190│ ├── brand/ Branding components191│ └── ui/ Generic UI primitives192├── styles/ Global styles193├── types/ TypeScript type definitions194└── assets/ Static assets (incl. provider-icons)195```196197### State Management Strategy198199| Layer | Technology |200|-------|-----------|201| Server state (REST API data) | TanStack Query |202| Local / real-time stream state | Jotai atoms |203| Rust-side state | `AppState` (`Mutex<AppStateInner>`) via `tauri::State` |204205### Key Architecture Patterns206207- **Transport**: All HTTP requests go through `transport.ts` (auth header injection, native IPC vs fetch routing). NEVER hand-write fetch elsewhere.208- **Gateway**: JSON-RPC over WebSocket at `/api/ws`. Use `use-gateway.ts` hook, never call `gateway-client.ts` raw socket directly.209- **Tauri Bridge**: `tauri-bridge.ts` mounts Tauri invoke wrappers onto `window.hermesDesktop` at startup. Existing code checking `window.hermesDesktop?.someMethod` works unchanged.210- **CSS**: CSS Modules only — no Tailwind, no styled-components. Design tokens in `packages/shared-ui/src/tokens/`.211212---213214## Packages (`packages/`)215216### `@hermes/protocol`217Shared type definitions and validation:218- Zod schemas for the Hermes API (`hermes-api.ts`)219- IPC types220- Session log parsing (`session-log.ts`)221- MCP API schemas222- Channel types223224### `@hermes/shared-ui`225Shared UI primitives:226- Design tokens (`tokens/`): colors, typography, spacing, motion, z-index, component tokens, semantic tokens, primitives227- Components: alert, badge, button, card, copy-button, empty-state, field, input228- Composites: dialog, popover229- Hooks230- Utilities231232---233234## Testing235236### Unit Tests (Vitest)237- **~93 test files** across the monorepo238- `web/src/lib/` — most modules have co-located `.test.ts` files239- `packages/protocol/` — Zod schema tests240- Run: `pnpm test:unit` (serial per workspace)241242### Rust Integration Tests (`tests/`)243- Crate name: `hermes_agent_cn`244- Mock-based: `wiremock` for HTTP, `tempfile::TempDir` for FS245- Tests: `api_proxy.rs`, `connection_config.rs`, `connection_ws_e2e.rs`, `dashboard_probe.rs`, `dashboard_spawn_retry.rs`, `dashboard_token.rs`, `runtime_manifest.rs`246- Run: `cargo test --all-features`247248### E2E Tests (`e2e/`)249- Playwright (real web → real Core backend → local fake model)250- Specs: chat-loop, guide-layout, image-paste, models-cli-custom-provider, skills-provenance251- Fake model server: `e2e/fake-model/server.py`252- Harness: config, global-warmup, protocol-smoke, start-backend, wait253- Run: `pnpm test:e2e`254255---256257## Scripts (`scripts/`)258259| Script | Purpose |260|--------|---------|261| `tauri-dev-managed.mjs` | Install backend into dev-runtime, launch Tauri dev |262| `tauri-dev-external.mjs` | Tauri dev with external backend |263| `install-local-runtime.mjs` | Copy Hermes-CN-Core into dev-runtime |264| `install-release-dmg.mjs` | Download & install release DMG |265| `stage-bundled-runtime.mjs` | Stage runtime for bundled build |266| `stage-dashboard-web-dist.mjs` | Stage dashboard web dist |267| `stage-bundled-skills.mjs` | Stage bundled skills |268| `stage-bundled-plugins.mjs` | Stage bundled plugins |269| `sync-desktop-version.mjs` | Sync version across packages |270| `generate-license-rtf.mjs` | Generate license RTF |271| `migrate-runtime-trees.mjs` | Migrate polluted runtime trees |272| `package-portable-windows.mjs` | Create Windows portable package |273| `package-portable-macos.mjs` | Create macOS portable package |274| `only-pnpm.mjs` | Enforce pnpm as package manager |275| `cdp-eval.mjs` | Chrome DevTools Protocol evaluation |276277---278279## Configuration Files280281| File | Purpose |282|------|---------|283| `Cargo.toml` | Rust dependencies (tauri 2, tokio, reqwest, rusqlite, etc.) |284| `tauri.conf.json` | Tauri window config, CSP, bundle targets (NSIS/DMG/deb/AppImage) |285| `package.json` | Root workspace scripts (version sync, build, test) |286| `pnpm-workspace.yaml` | Workspace members: web, packages/*, e2e |287| `web/vite.config.ts` | Vite config (dev server on port 9545, strictPort) |288289---290291## CI/CD (`.github/workflows/`)292293| Workflow | Trigger | Purpose |294|----------|---------|---------|295| `rust-test.yml` | PR / push to main | `cargo fmt --check`, `cargo clippy -D warnings`, `cargo test` |296| `web-test.yml` | PR / push to main | TypeScript typecheck + vitest unit tests |297| `web-e2e.yml` | PR / push to main | Playwright E2E (checkout Hermes-CN-Core + fake model) |298| `release-desktop.yml` | Tag push | Release build & publish |299300---301302## Documentation (`docs/`)303304| File | Content |305|------|---------|306| `agents/` | Coding-agent policy + human git workflow(编码代理不执行 git 写操作;人工双仓同步/worktree/commit/push/PR/tag/Landing 同步见 `git-workflow.md`) |307| `desktop-prd/` | Product Requirements Document (6 docs: feature inventory, PRD, IA, specs, backend contract, parity gap) |308| `gateway-connection-overhaul.md` | Gateway connection architecture |309| `managed-runtime.md` | Managed runtime design |310| `hot-update.md` | Hot update design(统一自更新 + UI 热更 + 开发热更 + 使用/验证) |311| `macos-signing-and-notarization.md` | macOS code signing |312| `portable-mode.md` | Portable mode |313| `yolo-mode.md` | YOLO mode |314| `custom-model-context-window.md` | Custom model context window |315316---317318## Port Conventions319320| Port | Purpose |321|------|---------|322| **9120** | Hermes Dashboard (desktop managed runtime) |323| **9119** | User global Hermes Agent (AVOID — managed runtime only) |324| **9545** | Vite dev server (strictPort) |325326---327328## Dev vs Production Mode329330| Aspect | Dev | Production |331|--------|-----|------------|332| WebView loads | `http://localhost:9545` (Vite) | Bundled `web/dist/` |333| REST API | Vite proxy → dashboard (same-origin) | Rust IPC proxy (`api_request` command) |334| Gateway events | WebSocket → Vite proxy `/api/ws` | Official `/api/ws`, fallback to Rust WS relay (`ws_proxy.rs`) |335| Session token | Vite `/__hermes_token` endpoint | Rust `get_runtime_config` command |336| `apiBaseUrl` | Not set (relative path) | Set to dashboard URL |337338---339340## Rust Testing Conventions341342- **Unit tests**: `#[cfg(test)] mod tests { ... }` inline in source files; can access private functions343- **Integration tests**: In `tests/` directory, only use `pub` API via `hermes_agent_cn` crate344- **Env-dependent tests**: Must use `#[serial_test::serial]`345- **Filesystem tests**: Use `tempfile::TempDir`; never write to `/tmp`, cwd, or fixed paths346- **HTTP tests**: Use `wiremock::MockServer`; never real network347- **Assertions**: Prefer `pretty_assertions::assert_eq`348- **Pre-commit**: `cargo test --all-features`349350---351352## Key Dependencies353354### Rust355- **Tauri v2** with `tray-icon` and `devtools` features356- **tokio** (full), **reqwest** (rustls-tls), **tokio-tungstenite**357- **tauri-plugin-dialog**, **tauri-plugin-notification**, **tauri-plugin-clipboard-manager**358- **rusqlite** (bundled), **zip**, **sha2**, **ed25519-dalek**359- **thiserror**, **serde/serde_json**, **portable-pty**, **notify**360- Platform: **windows-sys** + **winreg** (Windows), **objc2** (macOS)361362### Frontend363- **React 19**, **react-router 7**, **TanStack Query 5**, **Jotai 2**364- **@tauri-apps/api**, **@tauri-apps/plugin-clipboard-manager**365- **streamdown** (Markdown renderer with CJK/math/mermaid extensions)366- **Radix UI** (dialog, dropdown-menu, popover)367- **xterm** (terminal), **cmdk** (command palette), **recharts**, **lucide-react**368369---370371## Architecture Rules (DO NOT Violate)372373- ❌ Don't hand-write `fetch` outside `web/src/lib/transport.ts` — auth header injection lives there374- ❌ Don't call `gateway-client.ts` raw socket directly — use `hooks/use-gateway.ts`375- ❌ Don't put business logic in `web/src/routes/` — extract to `hooks/` or `lib/`376- ❌ Don't hardcode colors in components — use CSS variables from `packages/shared-ui/src/tokens/`377- ❌ Don't change the bundle identifier `cn.org.hermesagent.desktop`378- ❌ Don't use port 9119 (reserved for user's global Hermes Agent)379380---381382## Commit Convention383384- Conventional Commits: `feat` / `fix` / `style` / `docs` / `refactor` / `chore`385- English subject line, imperative mood ("add ...", "fix ...", "rework ...")386- Description can mix Chinese/English; explain "why" not "what"