Skip to content

Architecture

Nerve is a web interface for OpenClaw — chat, voice input, TTS, and agent monitoring in the browser. It connects to the OpenClaw gateway over WebSocket and provides a rich UI for interacting with AI agents.

System Diagram

┌──────────────────────────────────────────────────────────────────┐
│  Browser (React SPA)                                             │
│                                                                  │
│  ┌──────────┐  ┌──────────┐  ┌───────────┐  ┌────────────────┐  │
│  │ ChatPanel│  │ Sessions │  │ Workspace │  │ Command Palette│  │
│  └────┬─────┘  └────┬─────┘  └─────┬─────┘  └────────────────┘  │
│       │              │              │                             │
│  ┌────┴──────────────┴──────────────┴────────────────────────┐   │
│  │           React Contexts (Gateway, Session, Chat, Settings)│   │
│  └────────────────────────────┬──────────────────────────────┘   │
│                               │ WebSocket (/ws proxy)            │
└───────────────────────────────┼──────────────────────────────────┘

┌───────────────────────────────┼──────────────────────────────────┐
│  Nerve Server (Hono + Node)   │                                  │
│                               │                                  │
│  ┌────────────────────────────┴─────────────┐                    │
│  │         WebSocket Proxy (ws-proxy.ts)     │                   │
│  │  - Intercepts connect.challenge           │                   │
│  │  - Injects device identity (Ed25519)      │                   │
│  └────────────────────────────┬──────────────┘                   │
│                               │                                  │
│  ┌───────────────┐  ┌────────┴─────┐  ┌───────────────────────┐ │
│  │ REST API      │  │ SSE Stream   │  │ Static File Server    │ │
│  │ /api/*        │  │ /api/events  │  │ Vite build → dist/    │ │
│  └───────┬───────┘  └──────────────┘  └───────────────────────┘ │
│          │                                                       │
│  ┌───────┴──────────────────────────────────────────────────┐    │
│  │  Services: TTS (OpenAI, Replicate, Edge), Whisper,       │    │
│  │  Claude Usage, TTS Cache, Usage Tracker                  │    │
│  └──────────────────────────────────────────────────────────┘    │
└──────────────────────────────┬───────────────────────────────────┘
                               │ HTTP / WS
                    ┌──────────┴──────────┐
                    │  OpenClaw Gateway    │
                    │  (ws://127.0.0.1:    │
                    │       18789)         │
                    └─────────────────────┘

Frontend Structure

Built with React 19, TypeScript, Vite, and Tailwind CSS v4.

Entry Point

FilePurpose
src/main.tsxMounts the React tree with the provider hierarchy: ErrorBoundary → StrictMode → GatewayProvider → SettingsProvider → SessionProvider → ChatProvider → App
src/App.tsxRoot layout — wires contexts to lazy-loaded panels, manages keyboard shortcuts and command palette

Context Providers (State Management)

All global state flows through four React contexts, nested in dependency order:

ContextFileResponsibilities
GatewayContextsrc/contexts/GatewayContext.tsxWebSocket connection lifecycle, RPC method calls, event fan-out via pub/sub pattern, model/thinking status polling, activity sparkline
SettingsContextsrc/contexts/SettingsContext.tsxSound, TTS provider/model, wake word, panel ratio, theme, font, telemetry/events visibility. Persists to localStorage
SessionContextsrc/contexts/SessionContext.tsxSession list (via gateway RPC), granular agent status tracking (IDLE/THINKING/STREAMING/DONE/ERROR), busy state derivation, unread session tracking, agent log, event log, session CRUD (delete, spawn, rename, abort)
ChatContextsrc/contexts/ChatContext.tsxChat messages, streaming state, processing stage indicator, activity log (tool calls), send/abort/reset, infinite scroll history, TTS voice fallback

Data flow pattern: Contexts subscribe to gateway events via GatewayContext.subscribe(). The SessionContext listens for agent and chat events to update granular status. The ChatContext listens for streaming deltas and lifecycle events to render real-time responses.

Feature Modules

Each feature lives in src/features/<name>/ with its own components, hooks, types, and operations.

features/chat/

The main chat interface.

FilePurpose
ChatPanel.tsxFull chat view — message list with infinite scroll, input bar, streaming indicator, search
InputBar.tsxText input with voice recording, image attachment, tab completion, input history
MessageBubble.tsxRenders individual messages (user, assistant, tool, system) with markdown
ToolCallBlock.tsxRenders tool call blocks with name, arguments, and results
DiffView.tsxSide-by-side diff rendering for file edits
FileContentView.tsxSyntax-highlighted file content display
ImageLightbox.tsxFull-screen image viewer
SearchBar.tsxIn-chat message search (Cmd+F)
MemoriesSection.tsxInline memory display within chat
edit-blocks.tsParses edit/diff blocks from tool output
extractImages.tsExtracts image content blocks from messages
image-compress.tsClient-side image compression before upload
types.tsChat-specific types (ChatMsg, ImageAttachment)
utils.tsChat utility functions
useMessageSearch.tsHook for message search filtering
operations/Pure business logic (no React): loadHistory.ts, sendMessage.ts, streamEventHandler.ts
components/Sub-components: ActivityLog, ChatHeader, HeartbeatPulse, ProcessingIndicator, ScrollToBottomButton, StreamingMessage, ThinkingDots, ToolGroupBlock, useModelEffort

features/sessions/

Session management sidebar.

FilePurpose
SessionList.tsxHierarchical session tree with parent-child relationships
SessionNode.tsxIndividual session row with status indicator, context menu
SessionInfoPanel.tsxSession detail panel (model, tokens, thinking level)
SpawnAgentDialog.tsxDialog for spawning sub-agents with task/model/thinking config
sessionTree.tsBuilds tree structure from flat session list using parentId
statusUtils.tsMaps agent status to icons and labels

features/workspace/

Workspace file editor and management tabs.

FilePurpose
WorkspacePanel.tsxContainer for workspace tabs
WorkspaceTabs.tsxTab switcher (Memory, Config, Crons, Skills)
tabs/MemoryTab.tsxView/edit MEMORY.md and daily files
tabs/ConfigTab.tsxEdit workspace files (SOUL.md, TOOLS.md, USER.md, etc.)
tabs/CronsTab.tsxCron job management (list, create, toggle, run)
tabs/CronDialog.tsxCron creation/edit dialog
tabs/SkillsTab.tsxView installed skills with eligibility status
hooks/useWorkspaceFile.tsFetch/save workspace files via REST API
hooks/useCrons.tsCron CRUD operations via REST API
hooks/useSkills.tsFetch skills list

features/settings/

Settings drawer with tabbed sections.

FilePurpose
SettingsDrawer.tsxSlide-out drawer container
ConnectionSettings.tsxGateway URL/token, reconnect
AudioSettings.tsxTTS provider, model, voice, wake word
AppearanceSettings.tsxTheme, font selection

features/tts/

Text-to-speech integration.

FilePurpose
useTTS.tsCore TTS hook — speaks text via server /api/tts endpoint. Supports OpenAI, Replicate, Edge providers
useTTSConfig.tsServer-side TTS voice configuration management

features/voice/

Voice input and audio feedback.

FilePurpose
useVoiceInput.tsWeb Speech API integration for voice-to-text with Whisper fallback
audio-feedback.tsNotification sounds (ping on response complete)

features/markdown/

Markdown rendering pipeline.

FilePurpose
MarkdownRenderer.tsxreact-markdown with remark-gfm, syntax highlighting via highlight.js
CodeBlockActions.tsxCopy/run buttons on code blocks

features/charts/

Inline chart rendering.

FilePurpose
InlineChart.tsxRenders bar, line, pie, area charts from [chart:{...}] markers
extractCharts.tsParses chart markers from message text

features/command-palette/

Cmd+K command palette.

FilePurpose
CommandPalette.tsxFuzzy-search command list
commands.tsCommand definitions (new session, reset, theme, TTS, etc.)

features/connect/

FilePurpose
ConnectDialog.tsxInitial gateway connection dialog with auto-connect from /api/connect-defaults

features/activity/

FilePurpose
AgentLog.tsxScrolling agent activity log (tool calls, lifecycle events)
EventLog.tsxRaw gateway event stream display

features/dashboard/

FilePurpose
TokenUsage.tsxToken usage and cost display
MemoryList.tsxMemory listing component
useLimits.tsClaude Code / Codex rate limit polling

features/memory/

FilePurpose
MemoryEditor.tsxInline memory editing
MemoryItem.tsxIndividual memory display with edit/delete
AddMemoryDialog.tsxDialog for adding new memories
ConfirmDeleteDialog.tsxDelete confirmation
useMemories.tsMemory CRUD operations

Shared Components

PathPurpose
components/TopBar.tsxHeader with agent log, token data, event indicators
components/StatusBar.tsxFooter with connection state, session count, sparkline, context meter
components/ResizablePanels.tsxDraggable split layout (chat left, panels right)
components/ContextMeter.tsxVisual context window usage bar
components/ConfirmDialog.tsxReusable confirmation modal
components/ErrorBoundary.tsxTop-level error boundary
components/PanelErrorBoundary.tsxPer-panel error boundary (isolates failures)
components/NerveLogo.tsxSVG logo component
components/skeletons/Loading skeleton components (Message, Session, Memory)
components/ui/Primitives: button, card, dialog, input, switch, scroll-area, collapsible, AnimatedNumber, InlineSelect

Hooks

HookFilePurpose
useWebSockethooks/useWebSocket.tsCore WebSocket management — connect, RPC, auto-reconnect with exponential backoff
useConnectionManagerhooks/useConnectionManager.tsAuto-connect logic, credential persistence in sessionStorage
useDashboardDatahooks/useDashboardData.tsFetches memories and token data via REST + SSE
useServerEventshooks/useServerEvents.tsSSE client for /api/events
useInputHistoryhooks/useInputHistory.tsUp/down arrow input history
useTabCompletionhooks/useTabCompletion.tsTab completion for slash commands
useKeyboardShortcutshooks/useKeyboardShortcuts.tsGlobal keyboard shortcut registration
useGitInfohooks/useGitInfo.tsGit branch/status display

Libraries

FilePurpose
lib/constants.tsApp constants: context window limits (with dynamic getContextLimit() fallback), wake/stop/cancel phrase builders, attachment limits
lib/themes.tsTheme definitions and CSS variable application
lib/fonts.tsFont configuration
lib/formatting.tsMessage formatting utilities
lib/sanitize.tsHTML sanitization via DOMPurify
lib/highlight.tsSyntax highlighting configuration
lib/utils.tscn() classname merge utility (clsx + tailwind-merge)
lib/progress-colors.tsColor scales for progress indicators
lib/text/isStructuredMarkdown.tsDetects structured markdown for rendering decisions

Backend Structure

Built with Hono (lightweight web framework), TypeScript, running on Node.js ≥22.

Entry Point

FilePurpose
server/index.tsStarts HTTP + HTTPS servers, sets up WebSocket proxy, file watchers, graceful shutdown
server/app.tsHono app definition — middleware stack, route mounting, static file serving with SPA fallback

Middleware Stack

Applied in order in app.ts:

MiddlewareFilePurpose
Error handlermiddleware/error-handler.tsCatches unhandled errors, returns consistent JSON. Shows stack in dev
LoggerHono built-inRequest logging
CORSHono built-in + customWhitelist of localhost origins + ALLOWED_ORIGINS env var. Validates via URL constructor
Security headersmiddleware/security-headers.tsStandard security headers (CSP, X-Frame-Options, etc.)
Body limitHono built-inConfigurable max body size (from config.limits.maxBodyBytes)
CompressionHono built-ingzip/deflate/brotli on all routes except SSE (/api/events)
Cache headersmiddleware/cache-headers.tsHashed assets → immutable, API → no-cache, non-hashed static → must-revalidate
Rate limitingmiddleware/rate-limit.tsPer-IP sliding window, applied per-route (not as global middleware in app.ts). Separate limits for general API vs TTS/transcribe. Client ID from socket or custom header

API Routes

RouteFileMethodsPurpose
/healthroutes/health.tsGETHealth check with gateway connectivity probe
/api/connect-defaultsroutes/connect-defaults.tsGETPre-fill gateway URL/token for browser. Token only returned for loopback clients
/api/eventsroutes/events.tsGETSSE stream for real-time push (memory.changed, tokens.updated, status.changed, ping)
/api/events/testroutes/events.tsPOSTBroadcasts a test event (development only)
/api/ttsroutes/tts.tsPOSTText-to-speech with provider auto-selection (OpenAI → Replicate → Edge). LRU cache with TTL
/api/tts/configroutes/tts.tsGET, PUTTTS voice configuration per provider (read / partial update)
/api/transcriberoutes/transcribe.tsPOSTAudio transcription via OpenAI Whisper. Multipart file upload, MIME validation
/api/agentlogroutes/agent-log.tsGET, POSTAgent activity log persistence. Zod-validated entries. Mutex-protected file I/O
/api/tokensroutes/tokens.tsGETToken usage statistics — scans session transcripts, persists high water mark
/api/memoriesroutes/memories.tsGET, POST, DELETEMemory management — reads MEMORY.md + daily files, stores/deletes via gateway tool invocation
/api/memories/sectionroutes/memories.tsGET, PUTRead/replace a specific memory section by title
/api/gateway/modelsroutes/gateway.tsGETAvailable models via openclaw models list. Allowlist support
/api/gateway/session-inforoutes/gateway.tsGETCurrent session model/thinking level
/api/gateway/session-patchroutes/gateway.tsPOSTChange model/effort for a session
/api/server-inforoutes/server-info.tsGETServer time, gateway uptime, agent name
/api/versionroutes/version.tsGETPackage version from package.json
/api/git-inforoutes/git-info.tsGET, POST, DELETEGit branch/status. Session workdir registration
/api/workspaceroutes/workspace.tsGETList available workspace file keys and their existence status
/api/workspace/:keyroutes/workspace.tsGET, PUTRead/write workspace files (strict key→file allowlist: soul, tools, identity, user, agents, heartbeat)
/api/cronsroutes/crons.tsGET, POST, PATCH, DELETECron job CRUD via gateway tool invocation
/api/crons/:id/toggleroutes/crons.tsPOSTToggle cron enabled/disabled
/api/crons/:id/runroutes/crons.tsPOSTRun cron job immediately
/api/crons/:id/runsroutes/crons.tsGETCron run history
/api/skillsroutes/skills.tsGETList skills via openclaw skills list --json
/api/filesroutes/files.tsGETServe local image files (MIME-type restricted, directory traversal blocked)
/api/claude-code-limitsroutes/claude-code-limits.tsGETClaude Code rate limits via PTY + CLI parsing
/api/codex-limitsroutes/codex-limits.tsGETCodex rate limits via OpenAI API with local file fallback

Server Libraries

FilePurpose
lib/config.tsCentralized configuration from env vars — ports, keys, paths, limits. Validated at startup
lib/ws-proxy.tsWebSocket proxy — client→gateway with device identity injection (Ed25519 challenge-response)
lib/device-identity.tsEd25519 keypair generation/persistence for gateway auth. Stored in ~/.nerve/device-identity.json
lib/gateway-client.tsHTTP client for gateway tool invocation API (/tools/invoke)
lib/file-watcher.tsWatches MEMORY.md and memory/ directory, broadcasts SSE events on changes
lib/files.tsAsync file helpers (readJSON, writeJSON, readText)
lib/mutex.tsAsync mutex for serializing file read-modify-write. Includes keyed mutex variant
lib/cached-fetch.tsGeneric TTL cache with in-flight request deduplication
lib/usage-tracker.tsPersistent token usage high water mark tracking
lib/tts-config.tsTTS voice configuration file management
lib/openclaw-bin.tsResolves openclaw binary path (env → sibling of node → common paths → PATH)

Services

FilePurpose
services/openai-tts.tsOpenAI TTS API client (gpt-4o-mini-tts, tts-1, tts-1-hd)
services/replicate-tts.tsReplicate API client for hosted TTS models (Qwen3-TTS). WAV→MP3 via ffmpeg
services/edge-tts.tsMicrosoft Edge Read-Aloud TTS via WebSocket protocol. Free, zero-config. Includes Sec-MS-GEC token generation
services/tts-cache.tsLRU in-memory TTS cache with TTL expiry (100 MB budget)
services/openai-whisper.tsOpenAI Whisper transcription client
services/claude-usage.tsClaude Code CLI usage/limits parser via node-pty

Data Flow

WebSocket Proxy

Browser WS → /ws?target=ws://gateway:18789/ws → ws-proxy.ts → OpenClaw Gateway
  1. Client connects to /ws endpoint on Nerve server
  2. Proxy validates target URL against WS_ALLOWED_HOSTS allowlist
  3. Proxy opens upstream WebSocket to the gateway
  4. On connect.challenge event, proxy intercepts the client's connect request and injects Ed25519 device identity (device block with signed nonce)
  5. After handshake, all messages are transparently forwarded bidirectionally
  6. Pending messages are buffered (capped at 100 messages / 1 MB) while upstream connects

Server-Sent Events (SSE)

Browser → GET /api/events → SSE stream (text/event-stream)

Events pushed by the server:

  • memory.changed — File watcher detects MEMORY.md or daily file changes
  • tokens.updated — Token usage data changed
  • status.changed — Gateway status changed
  • ping — Keep-alive every 30 seconds

SSE is excluded from compression middleware to avoid buffering.

REST API

REST endpoints serve two purposes:

  1. Proxy to gateway — Routes like /api/crons, /api/memories (POST/DELETE), /api/gateway/* invoke gateway tools via invokeGatewayTool()
  2. Local server data — Routes like /api/tokens, /api/agentlog, /api/server-info read from local files or process info

Gateway RPC (via WebSocket)

The frontend calls gateway methods via GatewayContext.rpc():

MethodPurpose
statusGet current agent model, thinking level
sessions.listList active sessions
sessions.deleteDelete a session
sessions.resetClear session context
sessions.patchRename a session
chat.sendSend a message (with idempotency key)
chat.historyLoad message history
chat.abortAbort current generation
connectInitial handshake with auth/device identity

Event Types (Gateway → Client)

EventPayloadPurpose
connect.challenge{ nonce }Auth handshake initiation
chat{ sessionKey, state, message?, content? }Chat state changes: started, delta, final, error, aborted
agent{ sessionKey, state, stream, data? }Agent lifecycle: lifecycle.start/end/error, tool.start/result, assistant stream
cron{ name }Cron job triggered
exec.approval.requestExec approval requested
exec.approval.resolvedExec approval granted
presencePresence updates

Build System

Development

bash
npm run dev          # Vite dev server (frontend) — port 3080
npm run dev:server   # tsx watch (backend) — port 3081

Vite proxies /api and /ws to the backend dev server.

Production

bash
npm run prod         # Builds frontend + backend, then starts
# Equivalent to:
npm run build        # tsc -b && vite build → dist/
npm run build:server # tsc -p config/tsconfig.server.json → server-dist/
npm start            # node server-dist/index.js

Vite Configuration

  • Plugins: @vitejs/plugin-react, @tailwindcss/vite
  • Path alias: @/./src/
  • Manual chunks: react-vendor, markdown (react-markdown + highlight.js), ui-vendor (lucide-react), utils (clsx, tailwind-merge, dompurify)
  • HTTPS: Auto-enabled if certs/cert.pem and certs/key.pem exist

TypeScript Configuration

Project references with four configs:

  • config/tsconfig.app.json — Frontend (src/)
  • config/tsconfig.node.json — Vite/build tooling
  • config/tsconfig.server.json — Backend (server/) → compiled to server-dist/
  • config/tsconfig.scripts.json — Setup scripts

Testing

Framework: Vitest with jsdom environment for React tests.

bash
npm test              # Run all tests
npm run test:coverage # With V8 coverage

Test Files

TestCoverage
src/hooks/useWebSocket.test.tsWebSocket connection, RPC, reconnection
src/hooks/useServerEvents.test.tsSSE client
src/features/tts/useTTS.test.tsTTS hook behavior
src/features/voice/useVoiceInput.test.tsVoice input
src/features/voice/audio-feedback.test.tsAudio feedback
src/features/sessions/unreadSessions.test.tsUnread tracking
src/lib/formatting.test.tsMessage formatting
src/lib/constants.test.tsConstants validation
src/lib/sanitize.test.tsHTML sanitization
src/lib/voice-prefix.test.tsVoice prefix parsing
server/routes/health.test.tsHealth endpoint
server/services/tts-cache.test.tsTTS cache LRU/TTL
server/middleware/rate-limit.test.tsRate limiting
server/lib/mutex.test.tsAsync mutex

Configuration

  • Environment: jsdom (browser APIs mocked)
  • Setup: src/test/setup.ts
  • Exclusions: node_modules/, server-dist/ (avoids duplicate compiled test files)
  • Coverage: V8 provider, text + HTML + lcov reporters

Released under the MIT License.