Architecture Overview
Elftia is an Electron + React desktop AI chat application built on a strict frontend/backend separation architecture. This document describes the system's overall structure, core design principles, and key technology choices.
Multi-Process Model
An Electron application runs across three processes plus multiple Worker threads:
graph TB
subgraph "Renderer Process (React)"
UI[React UI Components]
Ctx[Context / Zustand Store]
Hooks[Custom Hooks]
end
subgraph "Preload Script (contextBridge)"
Bridge[window.api / window.native]
end
subgraph "Main Process (Node.js)"
Services[45+ Service Modules]
Routers[68+ IPC Routers]
Engines[5 Engines: API / Chat / ClaudeSDK / TinyElf / CLI]
Security[Security Control Layer]
end
subgraph "Worker Threads"
DbWorker[db.worker — SQLite read/write]
FileSearch[fileSearch.worker — file search]
FileWatcher[fileWatcher.worker — file watching]
McpWorker[mcp.worker — MCP server]
DiagWorker[diagnostics.worker — diagnostics]
ProjWorker[project.worker — project indexing]
end
subgraph "External APIs"
LLM[LLM Provider APIs]
Media[Media Generation APIs]
Search[Search Engine APIs]
Channel[Channel Platform APIs]
end
UI --> Bridge
Bridge --> Routers
Routers --> Services
Services --> Engines
Services --> Security
Services --> DbWorker
Services --> FileSearch
Services --> FileWatcher
Services --> McpWorker
Services --> DiagWorker
Services --> ProjWorker
Services --> LLM
Services --> Media
Services --> Search
Engines --> Channel
Process Responsibilities
| Process | Responsibilities | Key Files |
|---|---|---|
| Main Process | All business logic, external API calls, database operations, filesystem access, security controls | packages/desktop/app/main/index.ts |
| Renderer Process | Pure UI rendering and user interaction; does not directly access external APIs or the filesystem | packages/renderer/src/app/App.tsx |
| Preload Script | Exposes a safe IPC interface to the renderer process via contextBridge | packages/desktop/app/preload/index.ts |
| Worker Threads | Time-consuming I/O operations (database, file indexing, MCP process management) | packages/desktop/app/main/workers/ |
Core Design Principles
1. Strict Frontend/Backend Separation
The renderer process is responsible only for UI; all external calls are handled exclusively by the main process:
Renderer ──(IPC)──> Main Process ──> External API / Filesystem / Database
│
└──> Process response and return final result
│
Renderer <──(IPC)──< Main Process
Prohibited: making external API calls directly from the frontend or processing large amounts of data there before sending it back to the backend.
2. Security First
- Context Isolation: The renderer process is fully isolated and can only communicate via interfaces exposed through
contextBridge - IPC Auth Token: Every IPC call carries an authentication token validated by
secureHandlein the main process - API keys stored only in main process: All keys are encrypted with
SecurityService(AES-256-GCM) before storage - ExecutionFirewall: Blocks access to system directories and credential files
- GuardianAgent: AI-based review of tool call safety
3. Plugin Architecture
The system supports multiple plugin types:
| Plugin Type | Description | Load Location |
|---|---|---|
| Agent Packages | Pre-configured Agent definitions (MCP, Skill, Prompt) | agent-packages/ |
| Persona Packages | Pre-configured Persona (character) definitions | persona-packages/ |
| Script Plugins | Hot-pluggable MCP tool servers | script-plugins/ |
| Channel Plugins | Multi-platform messaging channels (Discord, Telegram, etc.) | elftia-channels/ |
| Extensions | SillyTavern-compatible extensions | Dynamically loaded |
4. Multi-Engine Dispatch
Five engines are managed uniformly through EngineDispatcher, routing to the appropriate engine based on Agent configuration:
{/* Simplified engine registration */}
interface IEngine {
type: EngineType;
chat(session: EngineSession): Promise<EngineResult>;
cancel(sessionId: string): Promise<void>;
}
type EngineType = 'api' | 'chat' | 'claude-sdk' | 'tinyelf' | 'cli' | 'st-roleplay';
| Engine | Purpose | Implementation File |
|---|---|---|
ApiEngine | Media generation APIs (images, music, etc.) | services/agent-core/engine/ApiEngine.ts |
ChatEngine | General LLM chat completion | services/agent-core/engine/ChatEngine.ts |
ClaudeSdkEngine | Claude Agent SDK sessions | services/agent-core/engine/ClaudeSdkEngine.ts |
TinyElfEngine | Built-in lightweight Agent engine | services/agent-core/engine/tinyelf/ |
CliRunnerEngine | CLI subprocess Agents (Claude CLI, Codex CLI) | services/agent-core/engine/cli/CliRunnerEngine.ts |
STChatEngine | SillyTavern-compatible RP pipeline | services/agent-core/engine/STChatEngine.ts |
Custom Protocols
Electron registers 4 custom protocols for safely loading local resources:
| Protocol | Purpose | Permissions |
|---|---|---|
elftia:// | Deep Link (OAuth callbacks, etc.) | Default protocol client |
wallpaper:// | Wallpaper image loading | secure, fetchAPI, stream, bypassCSP, CORS |
media:// | Media files (images, audio, video) | secure, fetchAPI, stream, bypassCSP, CORS |
resource:// | General resource files | secure, fetchAPI, stream, bypassCSP, CORS |
Database Overview
- Engine: better-sqlite3 + Drizzle ORM
- Mode: WAL (Write-Ahead Logging), supports concurrent reads
- Location:
{userData}/elftia.db - Access pattern: Main process via Worker Thread (
DbClient→db.worker.ts) async RPC
See Database for detailed database design.
Monorepo Package Structure
elftia/
├── packages/
│ ├── desktop/ # Electron main process + preload
│ │ └── app/
│ │ ├── main/ # Main process code
│ │ │ ├── services/ # 45+ service modules
│ │ │ ├── workers/ # Worker threads
│ │ │ ├── db/ # Drizzle ORM schema
│ │ │ └── ipc/ # IPC security utilities
│ │ ├── preload/ # contextBridge definitions
│ │ └── shared/ # Frontend/backend shared types
│ ├── renderer/ # React frontend (Vite)
│ │ └── src/
│ │ ├── app/ # App entry, layout, Provider host
│ │ ├── features/ # Feature domains (components/hooks/state)
│ │ ├── pages/ # Page-level components
│ │ ├── shared/ # Cross-feature shared (state/Zustand, hooks, utils, components/ui)
│ │ ├── components/ # Legacy shared UI components (including components/ui)
│ │ ├── contexts/ # React Context (limited, retained)
│ │ └── locales/ # i18n (en/zh/ja)
│ ├── server/ # Web server (Fastify) — optional
│ ├── channel-sdk/ # Channel plugin SDK
│ └── pack-cli/ # Agent package management CLI
├── agent-packages/ # Built-in Agent definitions
├── persona-packages/ # Built-in Persona definitions
├── script-plugins/ # Built-in Script plugins
├── elftia-channels/ # Built-in Channel plugins
└── docs/ # Development documentation
Path Aliases
| Alias | Points To | Used In |
|---|---|---|
@/* | packages/renderer/src/* | Renderer process |
@shared/* | packages/desktop/app/shared/* | Global shared types |
@main/* | packages/desktop/app/main/* | Main process internals |
Tech Stack
| Layer | Technology | Version |
|---|---|---|
| Desktop Framework | Electron | 31.7 |
| Frontend Framework | React + TypeScript | 18.2 / 5.6 |
| Build Tools | Vite (renderer) / tsup (main) | 7.0 |
| Styling | Tailwind CSS + CSS variables | 3.4 |
| State Management | React Context + Zustand | — |
| Database | better-sqlite3 + Drizzle ORM | — |
| Code Editor | CodeMirror 6 | — |
| Terminal Emulator | xterm 5.5 | — |
| AI SDK | @anthropic-ai/claude-agent-sdk | — |
Related Files
| File | Description |
|---|---|
packages/desktop/app/main/index.ts | Main process entry; service initialization and startup phase definitions |
packages/renderer/src/app/App.tsx | Renderer process entry; Context Provider hierarchy |
packages/desktop/app/preload/index.ts | Preload script; contextBridge interface exposure |
packages/desktop/app/shared/contracts/ | Shared type contracts between frontend and backend |
packages/desktop/app/main/services/agent-core/engine/EngineDispatcher.ts | Engine dispatcher |
packages/desktop/app/main/ipc/safe-handle.ts | IPC secure handle utility |
vite.config.js | Vite build configuration |
tsconfig.base.json | TypeScript base configuration |