The server_memories dict and its methods (get_server_memory,
set_server_memory) plus API endpoints (GET/POST /servers/{guild_id}/memory)
were never called by any bot logic, command, or frontend code.
All per-server state is stored as ServerConfig dataclass fields and
persisted via servers_config.json. The generic key-value store was an
unfinished scaffolding feature superseded by the dataclass approach.
get_server_config() and set_server_config() in ConfigManager had zero
callers — every part of the codebase already uses the server_manager
singleton. Removing them eliminates the risk of a stale write that
bypasses the in-memory cache in ServerManager.
server_manager is now the sole owner of servers_config.json.
Previously only 4 of 5+ settings were synced to globals when set via
the generic /config/set endpoint. Added:
- memory.use_cheshire_cat -> globals.USE_CHESHIRE_CAT
- runtime.mood.dm_mood -> globals.DM_MOOD + DM_MOOD_DESCRIPTION
- Uses same _GLOBALS_SYNC mapping pattern as restore_runtime_settings
reset_to_defaults() previously only cleared the runtime_config dict and
saved config_runtime.yaml, but never touched the actual globals that
control runtime behavior. After a reset, LANGUAGE_MODE, AUTONOMOUS_DEBUG,
VOICE_DEBUG_MODE, USE_CHESHIRE_CAT, PREFER_AMD_GPU, and DM_MOOD all kept
their current in-memory values until the next restart.
Now reset_to_defaults() also resets the corresponding globals to their
default values from CONFIG (the static config loaded from config.yaml).
Both full reset and single-key reset are supported. The default values
come from the Pydantic AppConfig schema, ensuring consistency.
Tested: set non-default values, full reset -> all back to defaults,
single-key reset -> only that key back to default, runtime_state property
reflects the reset immediately.
config_manager.runtime_state was a plain dict initialized with hardcoded
defaults (dm_mood='neutral', evil_mode=False, etc.) that were never updated
by any code path except current_gpu. The /config/state endpoint and
get_full_config() both returned this stale dict, so the API always reported
neutral mood and english mode regardless of actual state.
Replaced the static dict with a @property that reads live values from
globals (DM_MOOD, EVIL_MODE, BIPOLAR_MODE, LANGUAGE_MODE) on every access.
GPU state is still managed via _current_gpu and persisted to gpu_state.json.
get_state() and set_state() continue to work for the GPU path.
Removed the Config Manager Integration block and all 19 backward-compat
variable re-exports (LLAMA_URL, CHESHIRE_CAT_URL, LANGUAGE_MODE, etc.)
from config.py. These were dead code because:
1. Circular import: config.py tried to import config_manager at module
level, but config_manager.py imports from config.py first, so
HAS_CONFIG_MANAGER was always False and _get_config_value() was a
no-op that always returned the static value.
2. Frozen snapshots: Even if the circular import worked, the values were
assigned to module-level names at import time and never updated. Other
modules importing 'from config import LLAMA_URL' would get a stale
snapshot, not a live value.
3. Nothing imports them: The entire codebase uses globals.py for mutable
runtime state, not these config.py copies. Only ERROR_WEBHOOK_URL was
imported (by error_handler.py), so it is kept as a simple re-export
from SECRETS.
Also cleaned up unused imports: Any, field_validator.
Japanese mode is NOT affected — LANGUAGE_MODE and JAPANESE_TEXT_MODEL live
in globals.py and are untouched.
config.yaml nested cheshire_cat and face_detector under the 'services' key,
and llama URLs under 'services.llama'. But AppConfig expects:
- services -> {url, amd_url} (llama endpoints directly)
- cheshire_cat -> top-level key
- face_detector -> top-level key
Because Pydantic silently ignores extra fields, ServicesConfig received
{llama: {...}, cheshire_cat: {...}, face_detector: {...}} and none matched
its 'url'/'amd_url' fields, so ALL service config from YAML was silently
ignored and Pydantic defaults were always used instead.
Flattened services to contain url/amd_url directly, and moved cheshire_cat
and face_detector to top-level keys matching the AppConfig model. Verified
both AppConfig(**yaml_data) and config_manager dot-path traversal work.
config_runtime.yaml was written to the container root (/) because the path
resolved via Path(__file__).parent.parent from /app/config_manager.py = /.
This location is not volume-mounted, so all runtime config changes (language,
debug flags, Cheshire Cat toggle, mood, GPU preference) were lost on every
container restart.
Moved runtime_config_path to memory/config_runtime.yaml, which lives inside
the volume-mounted ./bot/memory:/app/memory directory and persists across
restarts. Also reordered __init__ so memory_dir is initialized before
runtime_config_path depends on it.
Relocated the button from the top action row to below the '512x512
displayed as circle' label for more intuitive placement next to the
avatar it acts on.
- Backend: album storage in memory/profile_pictures/album/{uuid}/ with
original.png, cropped.png, and metadata.json per entry
- add_to_album/add_batch_to_album with efficient resource management
(vision model + face detector kept alive across batch)
- set_album_entry_as_current auto-archives current PFP before replacing
- manual/auto crop album entries without applying to Discord
- Disk usage tracking, single & bulk delete
- API: full CRUD endpoints under /profile-picture/album/*
- Frontend: collapsible album grid in tab11 with thumbnail cards,
multi-select checkboxes for bulk delete, detail panel with crop
interface (Cropper.js), description editor, set-as-current action
- profile_picture_manager.py:
- Add ORIGINAL_PATH constant; save full-res original before every crop
- Add skip_crop param to change_profile_picture() for manual crop workflow
- Add manual_crop(x,y,w,h) method with Discord avatar update + role color sync
- Add auto_crop_only() to re-run face-detection crop on stored original
- Add update_description() with Cheshire Cat declarative memory re-injection
- Add regenerate_description() via vision model
- Skip crop step if image is already at/below 512x512
- api.py:
- GET /profile-picture/image/original — serve full-res original (no-cache)
- GET /profile-picture/image/current — serve current cropped avatar (no-cache)
- POST /profile-picture/change-no-crop — acquire image, skip auto-crop
- POST /profile-picture/manual-crop — apply crop coords {x,y,width,height}
- POST /profile-picture/auto-crop — re-run intelligent crop on original
- POST /profile-picture/description — save freeform description + Cat inject
- POST /profile-picture/regenerate-description — re-generate via vision model
- GET /profile-picture/description — fetch current description text
- index.html:
- Add new tab11 '🖼️ Profile Picture Management'
- Remove PFP + role color sections from Actions tab (tab2)
- Add Cropper.js 1.6.2 via CDN for manual square crop
- Tab layout: action buttons, file upload, auto/manual crop toggle,
Cropper.js interface, side-by-side original/cropped previews,
role color management, freeform description editor, metadata box (bottom)
- Wire switchTab hook for tab11 → loadPfpTab()
- All new JS functions: pfpChangeDanbooru, pfpUploadCustom, pfpRestoreFallback,
pfpShowCropInterface, pfpApplyManualCrop, pfpApplyAutoCrop, pfpSaveDescription,
pfpRegenerateDescription, pfpRefreshPreviews, setCustomRoleColor, resetRoleColor
When Evil Mode activates, the bot's Discord account avatar is changed to evil_pfp.png.
Previously, get_persona_avatar_urls() would read this swapped avatar and pass it to
the Miku webhook, causing both webhooks to display Evil Miku's pfp.
Now caching the regular Miku CDN URL before Evil Mode changes the bot's avatar.
When Evil Mode is active, the cached URL is used instead of reading from the bot
account. Discord CDN URLs remain valid after avatar changes, so this reliably
preserves the correct pfp for both regular and Evil Miku webhooks during arguments.
- Added MIKU_NORMAL_AVATAR_URL global in bot/globals.py
- Updated get_persona_avatar_urls() to cache and return the cached URL
- Save the normal avatar URL before Evil Mode switches the bot's avatar
- Fix silent None return in analyze_image_with_vision exception handler
- Add None/empty guards after vision analysis in bot.py (image, video, GIF, Tenor)
- Route all image/video/GIF responses through Cheshire Cat pipeline (was
calling query_llama directly), enabling episodic memory storage for media
interactions and correct Last Prompt display in Web UI
- Add media_type parameter to cat_adapter.query() and forward as
discord_media_type in WebSocket payload
- Update discord_bridge plugin to read media_type from payload and inject
MEDIA NOTE into system prefix in before_agent_starts hook
- Add _extract_vision_question() helper to strip Discord mentions and bot-name
triggers from user message; pass cleaned question to vision model so specific
questions (e.g. 'what is the person wearing?') go directly to the vision model
instead of the generic 'Describe this image in detail.' fallback
- Pass user_prompt to all analyze_image_with_qwen / analyze_video_with_vision
call sites in bot.py (image, video, GIF, Tenor, embed paths)
- Fix autonomous reaction loops skipping messages that @mention the bot or have
media attachments in DMs, preventing duplicate vision model calls for images
already being processed by the main message handler
- Increase vision max_tokens: images 300->800, video/GIF 400->1000 (no VRAM
impact; KV cache is pre-allocated at model load time)
- Change bot/memory/*.json to bot/memory/** to properly ignore all
subdirectories (dms/, dm_reports/, profile_pictures/)
- Untrack bot/memory/ files from index (DMs, profile pics, dm reports)
- Untrack cheshire-cat discord_bridge __pycache__/*.pyc from index
- These files are runtime/user data that should never be in version control
Voice conversion pipeline (Soprano TTS → RVC) with Docker support.
Previously tracked as bare gitlink; removed .git/ directories and
absorbed into main repo for unified tracking.
Includes: Soprano TTS, RVC WebUI integration, Docker configs,
WebSocket API, and benchmark scripts.
Updated .gitignore to exclude large model weights (*.pth, *.pt, *.onnx, *.index).
287 files (3.1GB of ML weights properly excluded via gitignore).
UNO card game web app (Node.js/React) with Miku bot integration.
Previously an independent git repo (fork of mizanxali/uno-online).
Removed .git/ and absorbed into main repo for unified tracking.
Includes bot integration code: botActionExecutor, cardParser,
gameStateBuilder, and server-side bot action support.
37 files, node_modules excluded via local .gitignore.
- Moved 20 root-level markdown files to readmes/
- Includes COMMANDS.md, CONFIG_README.md, all UNO docs, all completion reports
- Added new: MEMORY_EDITOR_FEATURE.md, MEMORY_EDITOR_ESCAPING_FIX.md,
CONFIG_SOURCES_ANALYSIS.md, MCP_TOOL_CALLING_ANALYSIS.md, and others
- Root directory is now clean of documentation clutter
- Moved 8 root-level test scripts + 2 from bot/ to tests/
- Moved run_rocinante_test.sh runner script to tests/
- Added tests/README.md documenting each test's purpose, type, and requirements
- Added test_pfp_context.py and test_rocinante_comparison.py (previously untracked)
- Pre-compile 393 name variants into 4 regex patterns at module load
(was 7,300+ raw re.search() calls per message)
- Strict addressing detection using punctuation context:
START: name at beginning + punctuation (Miku, ... / みく!...)
END: comma + name at end (..., Miku / ...、ミク)
MIDDLE: commas on both sides - vocative (..., Miku, ...)
ALONE: name is the entire message (Miku! / ミクちゃん)
- Rejects mere mentions: 'I like Miku' / 'Miku is cool' no longer trigger
- Script-family-aware pattern generation (Latin, Cyrillic, Japanese)
eliminates nonsensical cross-script combos (e.g. o-みく)
- Word boundary enforcement prevents substring matches (mikumiku)
- Fixes regex 'unbalanced parenthesis' errors from old implementation
- Add comprehensive test suite (94 cases, all passing)
- discord_bridge before_agent_starts now checks evil_mode from
working_memory to load the correct personality files:
Normal: miku_lore/prompt/lyrics + /app/moods/{mood}.txt
Evil: evil_miku_lore/prompt/lyrics + /app/moods/evil/{mood}.txt
- Reads files directly instead of relying on cross-plugin working_memory
- cat_client.query() returns (response, full_prompt) tuple
- Full prompt includes system prefix + recalled memories + conversation
- API /prompt/cat returns full_prompt field
Bot was calling restore_evil_cat_state() in on_ready() before Cheshire
Cat finished booting (~25s), causing all plugin toggle API calls to fail
silently. Evil Miku plugin was left disabled and the bot used Cat's
default personality instead.
Changes:
- cat_client.py: add wait_for_ready() that polls Cat health endpoint
every 5s for up to 120s before attempting any admin API calls
- evil_mode.py: rewrite restore_evil_cat_state() with:
- wait_for_ready() gate before any plugin/model switching
- 3-second extra delay after Cat is up (plugin registry fully loaded)
- up to 3 retries on failure
- post-switch verification that the correct plugins are actually active
Also fixes helcyon model references that leaked into the container image
(cat_client.py was switching Cat's LLM to 'helcyon' which has no
llama-swap handler; reverted to correct 'darkidol' / 'llama3.1').
Show a CSS spinner overlay when switching to Autonomous Stats (tab6),
Memories (tab9), and DM Management (tab10). Spinner only shows on
first visit when content is empty, removed after data loads.
Replace hardcoded <option> lists in #mood (tab1 DM mood) and
#chat-mood-select (tab7 chat mood) with empty selects populated
by populateMoodDropdowns(). Respects evil mode emoji mapping.
Called on DOMContentLoaded and after server cards render.
Convert 47 raw fetch+response.json+error-handling patterns to use the
centralized apiCall() utility. The 11 remaining raw fetch() calls are
FormData uploads or SSE streaming that require direct fetch access.
- Extract initTabState, initTabWheelScroll, initVisibilityPolling,
initChatImagePreview, initModalAccessibility as named functions
- Move polling interval vars to outer scope for accessibility
- Single DOMContentLoaded calls all init functions in logical order
- Replace scattered listeners with comment markers at original locations
- Escape sender name via escapeHtml in innerHTML template
- Set message content via textContent instead of innerHTML injection
- Prevents HTML/script injection from user input or LLM responses
- Escape key closes any open memory modal
- Clicking the dark backdrop behind a modal closes it
- Add role=dialog, aria-modal, aria-label for accessibility
First block of conversation-view, conversations-list, conversation-message,
message-header, sender, timestamp, message-content, message-attachments was
silently overridden by identical selectors defined later. Kept the unique
reaction/delete-button styles.
- Cancel previous timer before starting new one (prevents early dismissal)
- Add green background for type='success' notifications
- Bump z-index from 1000 to 3000 so notifications show above modals
- Add fade-out transition for smoother dismissal
- Replace raw setInterval with startPolling/stopPolling functions
- Add visibilitychange listener to pause when tab is hidden
- Immediately refresh data when tab becomes visible again
- Saves bandwidth and CPU when the dashboard is in background
- Add data-tab attributes to tab buttons for reliable identification
- Replace implicit window.event usage with querySelector by data-tab
- Save active tab to localStorage on switch, restore on page load