hermes/backend/README.md
Nico ccee249618 v0.6.42: Hermes chat UI — Vue3/TS/Vite, audio STT/TTS, sidebar rail, MCP event loop
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 19:35:10 +02:00

215 lines
9.6 KiB
Markdown

# Hermes Backend
WebSocket gateway between browser clients and the OpenClaw agent runtime.
## Stack
- **Bun** — runtime, HTTP server, WebSocket server (native, no npm deps)
- **TypeScript** — all source files, run directly by Bun (no compile step)
## Files
| File | Role |
|----------------------|---------------------------------------------|
| `server.ts` | Main entry — HTTP + WebSocket via Bun.serve |
| `gateway.ts` | Upstream connection to OpenClaw gateway |
| `auth.ts` | Token auth, session tokens, OTP |
| `session-sm.ts` | Per-connection state machine |
| `session-watcher.ts` | JSONL session tail + event dispatch |
| `session-watcher.ts` | JSONL session tail + prev session lookup |
| `hud-builder.ts` | HUD event factory, result builders |
| `message-filter.ts` | Filter sensitive values from tool output |
| `system-access.ts` | System access request/approve flow |
| `mcp/index.ts` | MCP Streamable HTTP server, auth, routing |
| `mcp/dev.ts` | Health, subscribe, push_state, chat tools |
| `mcp/events.ts` | In-memory event queue + long-poll |
| `mcp/system.ts` | Start/stop/restart/deploy tools |
| `mcp/fs.ts` | Remote file read/write/edit/grep/ls |
| `mcp/deck.ts` | Nextcloud Deck integration |
| `mcp/takeover.ts` | Browser takeover bridge |
### HTTP Endpoints (added in 0.6.x)
| Endpoint | Auth | Purpose |
|-------------------------|----------|----------------------------------|
| `POST /api/tts` | session | ElevenLabs TTS generation |
| `GET /api/session-history` | session | Previous session messages |
| `POST /api/dev/counter` | session | Push counter events to MCP |
| `POST /api/dev/broadcast` | session | Push WS state to browsers |
## Dev Setup
```bash
cd projects/hermes/backend
NODE_TLS_REJECT_UNAUTHORIZED=0 PORT=3003 ~/.bun/bin/bun --watch run server.ts
```
- **`--watch`** — auto-restarts on file change (rsync push → instant reload)
- `NODE_TLS_REJECT_UNAUTHORIZED=0` — required because the OpenClaw gateway uses a self-signed TLS cert
- Default port: `3001` (prod) / `3003` (dev)
- Requires a running OpenClaw gateway on `:18789`
### Dev workflow (from Titan host)
```bash
# Pull remote → local mirror
rsync -avz --exclude='node_modules' --exclude='dist' --exclude='.git' \
openclaw:~/.openclaw/workspace-titan/projects/hermes/ \
/d/ClaudeCode/Titan/Openclaw/workspace-titan/projects/hermes/
# Edit locally (Claude Code Read/Edit tools — no escaping issues)
# Push local → remote (bun --watch restarts BE, Vite HMR updates FE)
rsync -avz --exclude='node_modules' --exclude='dist' --exclude='.git' \
/d/ClaudeCode/Titan/Openclaw/workspace-titan/projects/hermes/backend/ \
openclaw:~/.openclaw/workspace-titan/projects/hermes/backend/
```
## Production
Managed by systemd:
```bash
sudo systemctl status openclaw-web-gateway.service
sudo systemctl restart openclaw-web-gateway.service
journalctl -u openclaw-web-gateway.service -f
```
Health check:
```bash
curl https://chat.jqxp.org/health
```
## Architecture
```
Browser (WebSocket / HTTP)
server.ts ─── Bun.serve()
├── HTTP routes
│ /health, /agents
│ /api/auth, /api/auth/verify, /api/auth/logout
│ /api/viewer/token, /api/viewer/tree, /api/viewer/file
└── WebSocket (/ws)
├── auth.ts token validation, session tokens
├── gateway.ts RPC → openclaw gateway (:18789, WSS)
├── session-sm.ts state: IDLE / AGENT_RUNNING / HANDOVER_* / SWITCHING
└── session-watcher.ts tails .jsonl, emits events to WS client
```
## WebSocket Protocol
### Client → Server
| Type | Auth | Description |
|---------------------|------|--------------------------------------|
| `connect` | No | Identify by username |
| `auth` | No | Identify by session/static token |
| `message` | Yes | Send message to agent |
| `stop` | Yes | Request agent stop |
| `kill` | Yes | Kill agent turn |
| `switch_agent` | Yes | Switch to different agent |
| `handover_request` | Yes | Ask agent to write HANDOVER.md |
| `new` | Yes | Reset session |
| `new_with_handover` | Yes | Handover then reset |
| `cancel_handover` | Yes | Cancel pending handover |
| `ping` | No | Keepalive |
| `stats_request` | No | OpenRouter credits + model info |
| `disco_request` | Yes | Disconnect gateway (triggers reconnect) |
| `disco_chat_request`| Yes | Close WebSocket |
### Server → Client
| Type | Description |
|-----------------------|-----------------------------------------------|
| `ready` | Auth success — session info, agents list |
| `session_state` | State machine transition |
| `sent` | Message accepted by gateway |
| `delta` | Streaming text chunk from agent |
| `done` | Agent turn complete |
| `tool` | Tool call or result (action: call/result) |
| `finance_update` | Per-turn cost estimate |
| `handover_context` | HANDOVER.md content on connect |
| `handover_done` | Handover written, content included |
| `handover_writing` | Handover in progress |
| `new_ok` | Session reset acknowledged |
| `switch_ok` | Agent switch confirmed |
| `viewer_file_changed` | Watched file changed (viewer feature) |
| `stats` | OpenRouter credit + model data |
| `pong` | Ping reply |
| `killed` / `stopped` | Agent turn terminated |
| `error` | Error with `code` and `message` |
## HTTP Endpoints
| Method | Path | Auth | Description |
|--------|----------------------|---------|-----------------------------------|
| GET | `/health` | None | Status, version, gateway state |
| GET | `/agents` | None | Agent list with models |
| POST | `/api/auth` | None | Login with static token |
| POST | `/api/auth/verify` | None | Verify OTP challenge |
| POST | `/api/auth/logout` | Session | Revoke session token |
| POST | `/api/viewer/token` | Session | Issue fstoken for file viewer |
| GET | `/api/viewer/tree` | fstoken | List files/dirs in viewer root |
| GET | `/api/viewer/file` | fstoken | Serve file content or PDF |
| HEAD | `/api/viewer/file` | fstoken | Check file existence/type |
## Viewer
Read-only file browser exposed over HTTP. Roots:
| Key | Path |
|-------------------|---------------------------------------------|
| `shared` | `/home/openclaw/.openclaw/shared` |
| `titan` | `/home/openclaw/.openclaw/workspace-titan` |
| `adoree` | `/home/openclaw/.openclaw/workspace-adoree` |
| `alfred` | `/home/openclaw/.openclaw/workspace-alfred` |
| `ash` | `/home/openclaw/.openclaw/workspace-ash` |
| `eras` | `/home/openclaw/.openclaw/workspace-eras` |
| `willi` | `/home/openclaw/.openclaw/workspace-willi` |
Allowed extensions: `.md .txt .ts .js .json .sh .py .pdf .css .woff2 .ttf .otf`
Max file size: 2MB. Live reload via `fs.watch` pushed over WebSocket (`viewer_file_changed`).
## Session State Machine
```
IDLE
├─ message → AGENT_RUNNING
├─ handover_request → HANDOVER_PENDING
└─ new / new_with_handover → SWITCHING
AGENT_RUNNING
├─ agent_done → IDLE
├─ stop / kill → IDLE
└─ send_error → IDLE
HANDOVER_PENDING
├─ handover_written → HANDOVER_DONE
├─ handover_error → IDLE
└─ cancel_handover → IDLE
HANDOVER_DONE
└─ cancel_handover → IDLE
SWITCHING
├─ switch_ready → IDLE
└─ new_greeting → AGENT_RUNNING
```
## Environment Variables
| Variable | Default | Description |
|-------------------------------|------------------|-------------------------------------|
| `PORT` | `3001` | HTTP/WS listen port |
| `GATEWAY_HOST` | `10.0.0.10` | OpenClaw gateway host |
| `GATEWAY_PORT` | `18789` | OpenClaw gateway port |
| `GATEWAY_TOKEN` | (hardcoded) | Auth token for gateway connection |
| `NODE_TLS_REJECT_UNAUTHORIZED`| `1` (verify) | Set to `0` for self-signed certs |
| `SSL_KEY` | `../web-frontend/ssl/key.pem` | TLS key for HTTPS (optional) |
| `SSL_CERT` | `../web-frontend/ssl/cert.pem` | TLS cert for HTTPS (optional)|