This is developer material, not an end-user guide. It explains how the Marinara Engine client is built. It covers the React app structure, the Zustand stores, the React Query hooks, the main components, and the server API map. If you just want to use the app, start with the user guides instead.
Marinara Engine is an AI chat application with Conversation, Roleplay, and Game modes. The client is a React 19 single-page app served by Vite, styled with Tailwind CSS v4, and packaged as a Progressive Web App (PWA).
The client lives in packages/client. It talks to a Fastify API server (packages/server) over REST and Server-Sent Events (SSE). Shared data contracts (types, Zod schemas, constants) live in packages/shared and are imported by both sides.
The UI uses a Discord-inspired three-column design managed by components/layout/AppShell.tsx:
+-------------+-----------------------------+--------------+
| Left | Center | Right |
| Sidebar | | Panel |
| | Chat area or Editor | |
| Chat list | (lazy-loaded) | Characters |
| Folders | | Lorebooks |
| Mode tabs | ChatConversationSurface | Presets |
| | ChatRoleplaySurface | Connections |
| | GameSurface | Agents |
| | CharacterEditor | Personas |
| | LorebookEditor | Settings |
| | PresetEditor | Browser |
| | ...other editors | |
+-------------+-----------------------------+--------------+
- Left sidebar (
components/layout/ChatSidebar.tsx): the chat list, organized by folders and filterable by mode (Conversation, Roleplay, Game). - Center pane: either the active chat surface or a full editor (character, lorebook, preset, and so on). Only one shows at a time. Editors replace the chat area.
- Right panel (
components/layout/RightPanel.tsx): a resource browser and settings, toggled from the top bar. Once a panel is mounted, it stays in the DOM (hidden with CSS) to keep its scroll position and local state. - Top bar (
components/layout/TopBar.tsx): quick-switch buttons for each right panel.
Navigation is state-driven. There is no URL router. The stores/ui.store.ts Zustand store controls what renders:
| Navigation target | Store field | Trigger function |
|---|---|---|
| Open character editor | characterDetailId |
openCharacterDetail(id) |
| Open lorebook editor | lorebookDetailId |
openLorebookDetail(id) |
| Open preset editor | presetDetailId |
openPresetDetail(id) |
| Open connection editor | connectionDetailId |
openConnectionDetail(id) |
| Open agent editor | agentDetailId |
openAgentDetail(id) |
| Open persona editor | personaDetailId |
openPersonaDetail(id) |
| Switch right panel | rightPanel |
openRightPanel(name) / toggleRightPanel(name) |
| Open modal | modal |
openModal(type, props?) |
Major editors and heavy components are lazy-loaded in AppShell.tsx using React.lazy() plus Suspense. This keeps the initial bundle small (see the bundle budget below).
The client uses a set of Zustand stores in packages/client/src/stores/ for UI and runtime state. ui.store.ts is the only persisted store. The others hold runtime state for chats, agents, games, the local model runtime, translation, dialogs, backfill, and table games.
The current store files are: agent.store.ts, backfill.store.ts, chat.store.ts, chess-game.store.ts, dialog.store.ts, encounter.store.ts, gallery.store.ts, game-asset.store.ts, game-mode.store.ts, game-state.store.ts, poker-game.store.ts, sidecar.store.ts, translation.store.ts, ui.store.ts, and uno-game.store.ts.
The only persisted store (localStorage via the Zustand persist middleware). It holds:
- Theme:
visualTheme("default" or "sillytavern"), thedata-themevalue (dark or light), and custom color overrides. - Appearance:
fontSize,chatFontSize,fontFamily, custom fonts, and cursor style. - Chat display:
boldDialogue,showTimestamps,showModelName, andmessagesPerPage. - Text styling: chat text color, roleplay message background opacity, and text stroke.
- Streaming:
enableStreamingandstreamingSpeed. - Conversation theme: gradient colors for message bubbles.
- Sound:
convoNotificationSoundandrpNotificationSound. - Behavior:
confirmBeforeDelete,enterToSendRP,enterToSendConvo,weatherEffects, andguideGenerations. - Navigation:
rightPanel,rightPanelOpen,sidebarOpen,settingsTab, all*DetailIdfields, andmodal.
Synced custom themes are not stored in ui.store.ts. They are fetched from the server through React Query and mirrored across devices connected to the same Marinara instance.
Not persisted. Tracks the active chat session:
activeChatId: which chat is displayed.messages: the current message array.isStreaming,streamBuffer: generation in progress.inputDrafts: per-chat draft messages.currentInput: the current value of the chat input.perChatTyping: typing indicator state.unreadCounts,chatNotifications: notification badges.abortControllers: cancel in-flight generations.
Tracks agent pipeline state during and after generation:
activeAgents: agents currently running.thoughtBubbles: agent reasoning shown in real time.echoMessages: the echo chamber (simulated viewer chat).cyoaChoices: the branching choice UI.debugLog: performance metrics and token usage.failedAgentTypes: agents that errored (for retry UI).
Holds scene and world context for Roleplay mode:
current(GameState): date, time, location, weather, present characters, events, player stats, quests, and inventory.isVisible,expandedSections: HUD display state.
Turn-based combat state:
active: whether an encounter is in progress.party,enemies: combatants with HP, attacks, and statuses.environment: arena details.playerActions,encounterLog: the action queue and history.combatResult: victory, defeat, fled, or interrupted.
pinnedImages: images pinned to the chat area as overlays.
All server data is fetched and cached through TanStack React Query, configured in main.tsx:
- Stale time: 30 seconds (global default).
- Retry: 1 attempt.
- Refetch on focus: disabled.
- Cache: in-memory only (no persistence).
Each entity has a dedicated hook file that exports query and mutation hooks.
All hooks live in src/hooks/ and follow the pattern use-{entity}.ts.
| Hook | Type | Description |
|---|---|---|
useChats() |
Query | All chats |
useChat(id) |
Query | Single chat by ID |
useChatMessages(chatId, perPage) |
Infinite Query | Paginated messages for a chat |
useChatGroup(groupId) |
Query | Chat group |
useCreateChat() |
Mutation | Create a new chat |
useDeleteChat() |
Mutation | Delete a chat |
useUpdateChatMetadata() |
Mutation | Update chat metadata (agents, sprites, more) |
useBranchChat() |
Mutation | Branch a chat from a specific message |
useUpdateMessage() |
Mutation | Edit message content (optimistic update) |
useDeleteMessage() |
Mutation | Delete a single message |
useDeleteMessages() |
Mutation | Delete multiple messages |
useSetActiveSwipe() |
Mutation | Switch to a different generation swipe |
usePeekPrompt() |
Mutation | Preview the assembled prompt |
useClearAllData() |
Mutation | Delete everything (destructive) |
| Hook | Type | Description |
|---|---|---|
useCharacters() |
Query | All characters |
useCharacter(id) |
Query | Single character with parsed card data |
useCreateCharacter() |
Mutation | Create character |
useUpdateCharacter() |
Mutation | Update character card data |
useDeleteCharacter() |
Mutation | Delete character |
useUploadAvatar() |
Mutation | Upload avatar image |
usePersonas() |
Query | All personas |
usePersona(id) |
Query | Single persona |
useCreatePersona() |
Mutation | Create persona |
useUpdatePersona() |
Mutation | Update persona |
useDeletePersona() |
Mutation | Delete persona |
useCharacterGroups() |
Query | Character groups |
usePersonaGroups() |
Query | Persona groups |
| Hook | Type | Description |
|---|---|---|
usePresets() |
Query | All presets |
usePreset(id) |
Query | Single preset |
usePresetFull(id) |
Query | Preset with sections, groups, and choices |
useDefaultPreset() |
Query | The default preset |
useCreatePreset() |
Mutation | Create preset |
useUpdatePreset() |
Mutation | Update preset |
useDeletePreset() |
Mutation | Delete preset |
usePresetSections(presetId) |
Query | Prompt sections for a preset |
usePresetGroups(presetId) |
Query | Section groups |
usePresetVariables(presetId) |
Query | Preset variables (formerly choice blocks) |
usePreviewPreset() |
Mutation | Rendered prompt preview for { presetId, chatId, choices } |
| Hook | Type | Description |
|---|---|---|
useAgentConfigs() |
Query | All agent configurations |
useAgentConfig(id) |
Query | Single agent config |
useCreateAgent() |
Mutation | Create custom agent |
useUpdateAgent() |
Mutation | Update agent config |
useDeleteAgent() |
Mutation | Delete agent |
useToggleAgent() |
Mutation | Toggle built-in agent on or off |
The most complex hook. It returns { generate, retryAgents }.
generate(params) takes one options object with fields such as chatId, connectionId, userMessage, regenerateMessageId, continueMessageId, impersonate, and attachments. It returns false if a generation is already in flight for that chat. The flow is:
- Set streaming state in
chat.store.ts. - Send the generation request to
/api/generate. - Parse SSE events such as
token,agent_start,agent_result,agent_error,thinking,tool_call,game_state,game_state_patch,text_rewrite,scene_created,done, anderror. - Update the React Query cache with new messages.
- Populate the agent store with thought bubbles and debug info.
- Handle errors with toast notifications.
The src/hooks/ folder also contains many feature-specific hooks. A representative sample:
| File | Purpose |
|---|---|
use-connections.ts |
API connection CRUD plus test |
use-lorebooks.ts |
Lorebook and entry CRUD |
use-scene.ts |
Scene planning, creation, conclusion |
use-encounter.ts |
Combat encounter init, action, summary |
use-autonomous-messaging.ts |
Autonomous message polling and scheduling |
use-idle-detection.ts |
10-minute inactivity detector |
use-background-autonomous.ts |
Background polling for inactive chats |
use-translate.ts |
Text translation |
use-apply-regex.ts |
Regex script execution on messages |
use-custom-tools.ts |
Custom tool CRUD |
use-knowledge-sources.ts |
Knowledge source management |
use-gallery.ts |
Chat gallery images |
use-chat-folders.ts |
Chat folder CRUD plus reordering |
use-regex-scripts.ts |
Regex script CRUD |
use-haptic.ts |
Haptic device connection and commands |
The chat system is the largest feature area. ChatArea.tsx lazy-loads three rendering surfaces: Conversation, Roleplay, and Game Mode.
Messenger-style chat bubbles. User messages on the right, assistant on the left. Features:
- Infinite scroll pagination (load older messages when you scroll up).
- Per-message actions: edit, copy, regenerate, delete, branch, peek prompt.
- Attachment support (images and files).
- Emoji and GIF pickers.
- Slash commands.
- Notification sounds on new messages.
- Draft persistence per chat.
A dark, immersive RPG-themed interface. It has all the Conversation features plus:
- Character sprites with expression changes driven by the expression agent.
- The Roleplay HUD showing game state (time, location, weather, present characters).
- Weather effects (particle overlays that match the scene weather).
- The echo chamber panel (simulated viewer reactions).
- Combat encounters with a turn-based action system.
- A world info panel showing active lorebook entries.
- A scene system for branching mini-roleplays.
- Background images with crossfade transitions.
The AI Game Master surface. It lives outside the chat folder, in components/game/GameSurface.tsx. ChatArea.tsx renders it when the chat mode is game. It reads the dedicated game stores (game-mode.store.ts, game-asset.store.ts, game-state.store.ts). It drives sessions, dice rolls, skill checks, maps, and turn storyboards through the hooks in use-game.ts and use-game-storyboards.ts.
ChatArea.tsx: the central orchestrator. It fetches all data (messages, characters, personas), builds the character map, determines the chat mode, and renders the right surface.ChatMessage.tsx: renders a single message with markdown, swipe navigation, editing, and action menus. It uses an uncontrolledEditTextareasubcomponent to avoid re-renders during editing.ChatInput.tsx: user input with auto-resize, draft persistence, slash command completion, attachment handling, and emoji or GIF insertion.
Each resource type has a full-page editor that replaces the chat area:
| Editor | File | Manages |
|---|---|---|
| Character Editor | components/characters/CharacterEditor.tsx |
Character card fields, avatar, greeting, personality, system prompt, metadata |
| Lorebook Editor | components/lorebooks/LorebookEditor.tsx |
Lorebook metadata and entries with keys, activation rules, injection settings |
| Preset Editor | components/presets/PresetEditor.tsx |
Prompt sections, groups, markers, generation parameters, choice blocks |
| Connection Editor | components/connections/ConnectionEditor.tsx |
API provider, base URL, model, context window, flags |
| Agent Editor | components/agents/AgentEditor.tsx |
Agent prompt template, phase, connection, tools, settings |
| Persona Editor | components/personas/PersonaEditor.tsx |
User persona with name, description, stats, avatar |
Modals are rendered by components/layout/ModalRenderer.tsx. It reads ui.store.modal and renders the matching component inside Suspense. The modal components live under components/modals/.
The current modal types include (this list is illustrative, not exhaustive):
| Type | Component | Purpose |
|---|---|---|
create-character |
CreateCharacterModal |
Quick character creation (name and avatar) |
create-connection |
CreateConnectionModal |
Quick connection creation |
create-persona |
CreatePersonaModal |
Quick persona creation |
create-lorebook |
CreateLorebookModal |
Quick lorebook creation |
create-preset |
CreatePresetModal |
Quick preset creation |
import-character |
ImportCharacterModal |
Import from file (JSON or PNG) |
import-connection |
ImportConnectionModal |
Import a connection package |
import-lorebook |
ImportLorebookModal |
Import from file |
import-preset |
ImportPresetModal |
Import from file |
import-persona |
ImportPersonaModal |
Import from file |
character-card-update |
CharacterCardUpdateModal |
Agent-proposed card evolution review |
agent-write-approval |
AgentWriteApprovalModal |
Agent write consent and review |
docs-viewer |
DocsViewerModal |
In-app documentation browser |
st-bulk-import |
STBulkImportModal |
Bulk import from SillyTavern data |
about-me-viewer |
AboutMeViewerModal |
View a Conversation-mode About Me |
scene-prompt-preferences |
ScenePromptPreferencesModal |
Scene prompt preference settings |
Modal pattern: all modals accept { open, onClose }, wrap content in the Modal base component, use mutations for API calls, and show loading state from mutation.isPending.
Right-side panels show resource lists with search, sort, and filtering. Clicking a resource opens its full editor in the center pane.
Panels are registered in RightPanel.tsx in two places:
PANEL_CONFIG: title, icon, and gradient color.PANELS: the component map.
Panels use module-level persistence. A mountedPanels Set tracks which panels have been visited. Once mounted, a panel stays in the DOM (hidden with display: none or aria-hidden) to keep its state.
| Component | Description |
|---|---|
Modal |
Base modal with backdrop click, escape key, enter and exit animations |
ColorPicker |
Solid color or gradient picker with preset swatches |
ExpandedTextarea |
Full-screen portal overlay for editing large text blocks |
EmojiPicker |
Searchable emoji popover (portal-rendered) |
GifPicker |
GIF search via the Giphy API |
HelpTooltip |
Hover icon that shows a portal-positioned tooltip |
All UI components use controlled props (value plus onChange) and portal rendering for overlays.
All server communication uses the api object:
import { api, ApiError } from "@/lib/api-client";| Method | Signature | Description |
|---|---|---|
api.get<T>(path) |
GET /api{path} |
Fetch JSON |
api.post<T>(path, body) |
POST /api{path} |
Send JSON, receive JSON |
api.put<T>(path, body) |
PUT /api{path} |
Full update |
api.patch<T>(path, body) |
PATCH /api{path} |
Partial update |
api.delete(path) |
DELETE /api{path} |
Delete resource |
api.upload(path, FormData) |
POST /api{path} |
Multipart file upload |
api.download(path, filename) |
GET /api{path} |
Download plus save-as dialog |
api.stream(path, body) |
POST /api{path} |
SSE async generator (tokens only) |
api.streamEvents(path, body) |
POST /api{path} |
SSE async generator (all event types) |
Errors throw ApiError, which carries status and message properties.
The project uses Tailwind CSS v4 with the @tailwindcss/vite plugin (no PostCSS config needed). Theme tokens map from CSS custom properties in globals.css:
@theme {
--color-primary: var(--primary);
--color-background: var(--background);
--color-border: var(--border);
/* ... */
}globals.css is organized into labeled sections. These include the Tailwind @theme mapping, dark theme variables, light theme overrides, base reset, custom cursors, scrollbars, glass panels, glow utilities, UI components, and keyframe animations. Other sections cover chat animations, per-mode chat styling, sprites and game HUD, function-call cards, responsive rules, the imported SillyTavern theme, accessibility rules, and performance hints.
Users can create custom themes. Theme definitions are stored on the Marinara server and sync across connected devices. The active custom theme is shared too. The CSS is injected as a style tag by CustomThemeInjector.tsx.
Synced theme CSS can request the built-in Accent Pulse engine with --marinara-theme-accent-pulse: enabled. Add --marinara-theme-accent-pulse-source: #a78bfa (or a gradient) when the pulse should use a specific theme accent instead of the current Appearance accent.
Personal Extensions are server-stored, exact-hash-approved sandboxed code. The Addons UI uses use-personal-extensions.ts; PersonalExtensionInjector.tsx hosts approved Browser code in a dedicated Worker inside an opaque-origin sandboxed iframe and brokers immutable active-chat context snapshots. The context fields are always present; outside an active chat, chatId and characterId are null and characterIds is empty. Bounded active Character-card and selected-Persona fields require separately declared, hash-bound permissions. Server extensions run in a separate Node process inside macOS Seatbelt or Linux Bubblewrap and fail closed when neither backend is available. External sources require the .env gate plus the Danger Zone opt-in at listing, approval, and runtime boundaries.
See Personal Extension Architecture before changing this feature.
The frontend imports types, schemas, and constants from @marinara-engine/shared.
Key files in packages/shared/src/constants/:
defaults.ts: exports such asAPP_VERSION,PROFESSOR_MARI_ID,DEFAULT_CONNECTION_ID,DEFAULT_GENERATION_PARAMS,MAX_FILE_SIZES, andLIMITS. This is the version source and holds default generation settings.providers.ts: exportsPROVIDERS, the API provider configs (OpenAI, Anthropic, Google, and more) with URLs and auth.model-lists.ts: static model catalogs per provider, plusIMAGE_GENERATION_SOURCESfor image generation providers.agent-prompts.ts: base-only summary and secret-plot prompts plus runtime lookup for prompts supplied by installed agent packages.
All input validation uses Zod schemas from packages/shared/src/schemas/. Representative files:
| Schema file | Entities |
|---|---|
agent.schema.ts |
AgentConfig create and update, agent phases, result types |
character.schema.ts |
Character cards, compatibility metadata, character books, groups |
chat.schema.ts |
Chat create, message create, generation request |
connection.schema.ts |
API connection create and update |
custom-tool.schema.ts |
Custom tool definitions |
lorebook.schema.ts |
Lorebook and entry create/update, activation conditions, schedules |
prompt.schema.ts |
Preset, section, group, choice block, generation parameters |
regex.schema.ts |
Regex script create and update |
personal-extension.schema.ts |
Personal Extension drafts, exact-hash approval, rollback, and private storage |
The folder also holds schemas for app settings, chat settings profiles, conversation calls, custom emojis and stickers, Noodle, and themes.
Entity type definitions live in packages/shared/src/types/. A sample of the key files:
| Type file | Key interfaces |
|---|---|
agent.ts |
AgentConfig, AgentResult, AgentContext, ToolDefinition, ToolCall, ToolResult, BUILT_IN_AGENTS |
character.ts |
Character, CharacterCardV2, CharacterData, RPGStatsConfig |
chat.ts |
Chat, ChatMetadata, Message, MessageExtra, GenerationInfo, StreamEvent |
connection.ts |
APIConnection, ModelInfo, ModelCapabilities, ConnectionTestResult |
combat-encounter.ts |
CombatPartyMember, CombatEnemy, CombatActionResult, EncounterSettings |
game-state.ts |
GameState, PresentCharacter, PlayerStats, QuestProgress, InventoryItem |
lorebook.ts |
Lorebook, LorebookEntry, ActivationCondition, LorebookSchedule, QuestData |
persona.ts |
Persona, PersonaStatsConfig |
personal-extension.ts |
PersonalExtension, runtime metadata, revisions, source, and server runtime state |
prompt.ts |
PromptPreset, PromptSection, PromptGroup, ChoiceBlock, GenerationParameters |
scene.ts |
SceneMeta, SceneFullPlan |
haptic.ts |
HapticDevice, HapticStatus, HapticDeviceCommand |
| File | Purpose |
|---|---|
macro-engine.ts |
resolveMacros(template, context): replaces macros such as {{date}}, {{char}}, {{random}}, {{roll:2d6}}, and {{getvar::name}} |
xml-wrapper.ts |
nameToXmlTag(): converts a display name to an XML tag slug ("World Info (Before)" becomes "world_info_before") |
The server (packages/server) exposes REST APIs under /api. This is a high-level map, not the exhaustive list. The file packages/server/src/routes/index.ts and the individual route files are the source of truth.
| Prefix | Methods | Description |
|---|---|---|
/api/characters |
GET, POST, PATCH, DELETE | Character CRUD, groups, export (JSON or PNG) |
/api/chats |
GET, POST, PATCH, DELETE | Chat CRUD, messages, metadata, connect and disconnect |
/api/prompts |
GET, POST, PATCH, DELETE | Preset CRUD, sections, groups, choice blocks, export |
/api/connections |
GET, POST, PATCH, DELETE | API connection CRUD, duplicate, test |
/api/agents |
GET, POST, PATCH, DELETE | Agent CRUD, echo messages, runs; built-in toggles use PUT /api/agents/toggle/:agentType |
/api/lorebooks |
GET, POST, PATCH, DELETE | Lorebook CRUD, entries, export |
/api/custom-tools |
GET, POST, PATCH, DELETE | Custom tool CRUD |
/api/regex-scripts |
GET, POST, PATCH, DELETE | Regex script CRUD |
Agent memory tools use /api/agents/memory/:agentType/:chatId, where agentType is the agent type string and chatId is the target chat id.
| Endpoint | Method | Description |
|---|---|---|
/api/generate |
POST | Main SSE generation with the agent pipeline |
/api/generate/retry-agents |
POST | SSE retry for the agent types supplied by the caller |
| Prefix | Endpoints | Description |
|---|---|---|
/api/chat-folders |
CRUD plus reorder | Chat folder management |
/api/conversation |
schedule, status, message, check | Autonomous messaging system |
/api/scene |
create, plan, conclude | Scene branching |
/api/encounter |
init, action, summary | Combat encounters |
/api/translate |
POST | Text translation |
/api/game |
CRUD and actions | Game Mode sessions and state |
/api/game-assets |
CRUD and upload | Game assets |
/api/turn-games |
Chess, UNO, Poker routes | Conversation table games |
/api/conversation-calls |
call and session routes | Conversation audio calls |
| Prefix | Description |
|---|---|
/api/avatars/file/:filename |
Avatar image serving |
/api/backgrounds |
Background CRUD plus upload |
/api/sprites/:characterId |
Sprite expression management |
/api/fonts |
Custom font management |
/api/gallery/:chatId |
Per-chat gallery images |
/api/global-gallery |
Global gallery images |
/api/tts |
Text-to-speech routes |
/api/youtube |
YouTube DJ routes |
/api/custom-emojis |
Custom emoji assets |
/api/custom-stickers |
Custom sticker assets |
/api/gifs/search |
GIF search (Giphy proxy) |
| Prefix | Description |
|---|---|
/api/bot-browser/chub/* |
Chub character search |
/api/bot-browser/chartavern/* |
CharacterTavern search |
/api/bot-browser/janny/* |
JannyAI search |
/api/bot-browser/pygmalion/* |
Pygmalion search |
/api/bot-browser/wyvern/* |
Wyvern search |
/api/bot-browser/datacat/* |
DataCat search |
/api/haptic/* |
Haptic device control |
/api/spotify/* |
Spotify auth |
/api/knowledge-sources |
Knowledge base for retrieval |
| Endpoint | Description |
|---|---|
/api/updates/check |
Version check against GitHub releases |
/api/updates/latest |
Latest release metadata |
/api/updates/commits-behind |
Git install update distance |
/api/backup |
Full backup, export, import |
/api/import/* |
SillyTavern and Marinara profile import |
/api/admin/clear-all |
Full data clear |
/api/themes |
Synced custom themes |
/api/personal-extensions |
Sandboxed extension policy, drafts, approval, runtime, and private storage |
/api/app-settings |
Server-side app settings |
/api/sidecar |
Local model runtime |
/api/chat-presets |
Chat settings profiles (legacy endpoint name) |
/api/connection-folders |
Connection folders |
/api/prompt-overrides |
Built-in prompt overrides |
/api/achievements |
Achievement unlocks |
/api/noodle |
Noodle social timeline |
/api/professor-mari/workspace |
Professor Mari workspace operations |
The app is a Progressive Web App configured with VitePWA:
- Manifest:
public/manifest.jsonwith the "Marinara Engine" app name, standalone display mode, and dark theme. - Icons: a 64px favicon, 192px and 512px maskable icons, and a splash logo.
- Service worker: Workbox with an auto-update strategy.
- Caching: static assets are cached;
/api/*routes use NetworkOnly. - Keep-alive:
lib/keep-alive.tsuses the Web Locks API plus BroadcastChannel pings to keep the tab from sleeping.
App.tsx polls /api/health every 5 minutes. If the server version differs from the client's cached version, the client unregisters the service worker. It also clears the caches to force an update.
The agent system processes AI responses through configurable pipelines. Agents run in three phases:
- Pre-generation: before the main LLM call (for example, context injection or knowledge retrieval).
- Parallel: alongside the main generation (for example, world-state tracking or combat).
- Post-processing: after the main response (for example, prose rewriting or lorebook updates).
Retry requests go through /api/generate/retry-agents with an explicit agentTypes list. A broad UI action like Re-run Trackers passes all active tracker types. An individual widget control passes only its target tracker.
Agent memory tools, such as the Narrative Director Secret Plot panel, use /api/agents/memory/:agentType/:chatId. The route applies to configured agents that store per-chat memory. Secret Plot memory is stored under director in current configs, while secret-plot-driver remains accepted for legacy chats.
The lightweight Engine ships with an empty runtime agent registry. Packages installed from the public Pasta-Devs/Marinara-Agents catalog contribute validated agent manifests, client/server feature entrypoints, and UI slots at runtime. The active definitions are exposed through BUILT_IN_AGENTS for compatibility, but they come from installed packages rather than bundled implementations. The official catalog contains these packages:
| Agent | Phase | What it does |
|---|---|---|
prose-guardian |
post_processing | Enforces writing quality (anti-repetition, show-don't-tell) |
continuity |
post_processing | Detects continuity issues and can produce rewrite guidance |
director |
pre_generation | Injects narrative directions and optional Secret Plot state |
echo-chamber |
parallel | Simulates audience reactions |
world-state |
post_processing | Extracts date, time, location, and weather from the narrative |
expression |
post_processing | Selects character sprite expressions |
quest |
post_processing | Tracks quest creation, updates, and completion |
background |
post_processing | Selects fitting background images |
character-tracker |
post_processing | Tracks character state changes |
persona-stats |
post_processing | Tracks player persona stat changes |
custom-tracker |
post_processing | Tracks user-defined structured state |
illustrator |
post_processing | Generates scene image prompts and media requests |
lorebook-keeper |
post_processing | Auto-creates and updates lorebook entries |
card-evolution-auditor |
post_processing | Audits character cards for suggested evolution |
combat |
parallel | Tracks combat rounds, HP, initiative, and outcomes |
html |
post_processing | Rewrites finished Roleplay responses to add diegetic HTML visuals |
spotify |
post_processing | Controls Music DJ playback (Spotify, YouTube, or local music) |
knowledge-retrieval |
pre_generation | Retrieves context from knowledge sources |
knowledge-router |
pre_generation | Routes relevant lorebook and knowledge entries |
long-term-memory |
feature | Stores durable memories and recalls relevant context |
haptic |
post_processing | Sends haptic device commands |
cyoa |
post_processing | Generates choice prompts |
storyboard |
post_processing | Plans still or animated Game and Roleplay storyboards |
conversation-calls |
feature | Adds Conversation audio/video calls and related settings |
hierarchical-maps |
feature | Adds Roleplay/Game maps, spatial context, and movement |
noodle |
feature | Adds the local Noodle and NoodleR social feeds to Home |
uno |
feature | Adds the Conversation UNO table |
chess |
feature | Adds the Conversation Chess board |
poker |
feature | Adds the Conversation Texas Hold'em table |
eightball |
feature | Adds the Conversation 8-Ball Pool table |
tic-tac-toe |
feature | Adds the Conversation Tic-Tac-Toe board |
rock-paper-scissors |
feature | Adds Conversation Rock-Paper-Scissors matches |
Agents produce typed results that the frontend handles. The AgentResultType union in packages/shared/src/types/agent.ts includes:
game_state_update, text_rewrite, sprite_change, echo_message, quest_update, image_prompt, context_injection, continuity_check, director_event, lorebook_update, character_card_update, background_change, character_tracker_update, persona_stats_update, custom_tracker_update, spotify_control, youtube_control, local_music_control, haptic_command, cyoa_choices, secret_plot, game_master_narration, party_action, game_map_update, game_state_transition, prompt_patch, frontend_theme_update, and about_me_update.
Plain dialogue with one or more AI characters. Characters can have different statuses (online, idle, do not disturb, offline) that influence response timing and style. Built-in agents are added per chat rather than enabled globally.
An immersive narrative experience with game-state tracking: scene context (location, time, weather), character presence and mood, player stats, inventory and quests, combat encounters, world info from lorebooks, and sprite expressions.
AI Game Master sessions with party members, dice, game state, assets, storyboards, a journal, and a structured session lifecycle. Game Mode uses dedicated stores and routes for game state, assets, table games, scene videos, and storyboards. See Game Mode: Getting Started for the user-facing workflow.
Install dependencies:
pnpm installStart the server and client with hot reload:
pnpm devRun the client dev server only:
pnpm dev:clientRun the API server only:
pnpm dev:serverRun the baseline validation (TypeScript plus ESLint):
pnpm checkBuild for production:
pnpm build- Main entry: max 1 MB.
- Per chunk: max 500 KB.
- Vendor splits: react, tanstack, motion, zustand, icons, and misc.
@/* resolves to ./src/* in both the TypeScript and Vite configs.