- 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
- miku-stt: switch PyTorch CUDA -> CPU-only (~2.5 GB savings)
- Silero VAD already runs on CPU via ONNX (onnx=True), CUDA PyTorch was waste
- faster-whisper/CTranslate2 uses CUDA directly, no PyTorch GPU needed
- torch+torchaudio layer: 3.3 GB -> 796 MB; total image 9+ GB -> 6.83 GB
- Tested: Silero VAD loads (ONNX), Whisper loads on cuda, server ready
- llama-swap-rocm: add root .dockerignore to fix 31 GB build context
- Dockerfile clones all sources from git, never COPYs from context
- 19 GB of GGUF model files were being transferred on every build
- Now excludes everything (*), near-zero context transfer
- anime-face-detector: add .dockerignore to exclude accumulated outputs
- api/outputs/ (56 accumulated detection files) no longer baked into image
- api/__pycache__/ and images/ also excluded
- .gitignore: remove .dockerignore exclusion so these files are tracked
- Fixed missing client parameter in animated GIF webhook update path
- Added get_persona_avatar_urls() helper that returns bot's current Discord
avatar URL for Miku persona (always fresh, no cache lag)
- Pass avatar_url on every webhook.send() call in bipolar_mode.py,
persona_dialogue.py, and api.py so avatars always match current pfp
regardless of webhook cache state
- miku-bot: Re-add scikit-learn to requirements.txt (needed for vision color extraction)
- miku-stt: Upgrade from CUDA 12.6.2 to 12.8.1, PyTorch 2.5.1 to 2.7.1 per RealtimeSTT PR #295
- miku-stt: Use Ubuntu 24.04 with Python 3.12 (single installation, no dual Python)
- miku-stt: Add requirements-gpu-torch.txt for separate PyTorch installation
- miku-stt: Use --break-system-packages flag for Ubuntu 24.04 pip compatibility
#16 Timezone consistency — added TZ=Europe/Sofia to docker-compose.yml
so datetime.now() returns local time inside the container. Removed
the +3 hour hack from get_time_of_day(). All three time-of-day
consumers (autonomous_v1_legacy, moods, autonomous_engine) now
use the same correct local hour automatically.
#17 Decay truncation — replaced int() with round() in decay_events()
so a counter of 1 survives one more 15-minute cycle instead of
being immediately zeroed (round(0.841)=1 vs int(0.841)=0).
#20 Unpersisted rate limiter — _last_action_execution dict in
autonomous.py is now seeded from the engine's persisted
server_last_action on import, so restarts don't bypass the
30-second cooldown.
Note: #18 (dead config fields) was a false positive — autonomous_interval_minutes
IS used by the scheduler. #19 deferred to bipolar mode rework.
#10 Redundant coin flip in join_conversation — removed the 50% random
gate that doubled the V2 engine's own decision to act.
#11 Message-triggered actions skip _autonomous_paused — _check_and_act
and _check_and_react now bail out immediately when the autonomous
system is paused (voice session), matching the scheduled-tick path.
#12 Duplicate emoji dictionaries — removed MOOD_EMOJIS and
EVIL_MOOD_EMOJIS from globals.py (had different emojis from moods.py).
bipolar_mode.py and evil_mode.py now import the canonical dicts
from utils/moods.py so all code sees the same emojis.
#13 DM mood can spontaneously become 'asleep' — rotate_dm_mood() now
filters 'asleep' out of the candidate list since DMs have no
sleepy-to-asleep transition guard and no wakeup timer.
#15 Engage-user fallback misreports action type — log level raised to
WARNING with an explicit [engage_user->general] prefix so the
cooldown-triggered fallback is visible in logs.
#4 Sleep/mood desync — set_server_mood() now clears is_sleeping when
mood changes away from 'asleep', preventing ghost-sleep state.
#5 Race condition in _check_and_act — added per-guild asyncio.Lock so
overlapping ticks + message-triggered calls cannot fire concurrently.
#6 Class-level attrs on ServerConfig — sleepy_responses_left,
angry_wakeup_timer, and forced_angry_until are now proper dataclass
fields with defaults, so asdict()/from_dict() round-trip correctly.
Also strips unknown keys in from_dict() to survive schema changes.
#7 Persistence decay_factor crash — initialise decay_factor = 1.0
before the loop so empty-server or zero-downtime paths don't
raise NameError.
#8 Double record_action — removed the redundant call in
autonomous_tick_v2(); only _check_and_act records the action now.
#9 Engine mood desync — on_mood_change() is now called inside
set_server_mood() (single source of truth) and removed from 4
call-sites in api.py, moods.py, and server_manager wakeup task.
1. Momentum cliff at 10 messages (P0): The conversation momentum formula
had a discontinuity where the 10th message caused momentum to DROP from
0.9 to 0.5. Replaced with a smooth log1p curve that monotonically
increases (0→0→0.20→0.32→...→0.70→0.89→1.0 at 30 msgs).
2. Neutral keywords overriding all moods (P0): detect_mood_shift() checked
neutral early with generic keywords (okay, sure, hmm) that matched
almost any response, constantly resetting mood to neutral. Now: all
specific moods are scored by match count first (best-match wins),
neutral is only checked as fallback and requires 2+ keyword matches.
3. Uncancellable delayed_wakeup tasks (P0): Fire-and-forget sleep tasks
could stack and overwrite mood state after manual wake-up. Added a
centralized wakeup task registry in ServerManager with automatic
cancellation on manual wake or new sleep cycle.
- Added manual_trigger parameter to /autonomous/engage endpoint to bypass 12h cooldown
- Updated miku_engage_random_user_for_server() and miku_engage_random_user() to accept manual_trigger flag
- Modified Web UI to always send manual_trigger=true when engaging users from the UI
- Users can now manually engage the same user multiple times from web UI without cooldown restriction
- Regular autonomous schedules still respect the 12h cooldown between engagements to the same user
Changes:
- bot/api.py: Added manual_trigger parameter with string-to-boolean conversion
- bot/static/index.html: Added manual_trigger=true to engage user request
- bot/utils/autonomous_v1_legacy.py: Added manual_trigger parameter and cooldown bypass logic
- Add COPY config_manager.py to Dockerfile so it's included in the image
- Add 'config_manager' to logger COMPONENTS list to enable logging
Fixes the ModuleNotFoundError and ValueError when importing config_manager
Add restore_runtime_settings() to ConfigManager that reads config_runtime.yaml
on startup and restores persisted values into globals:
- LANGUAGE_MODE, AUTONOMOUS_DEBUG, VOICE_DEBUG_MODE
- USE_CHESHIRE_CAT, PREFER_AMD_GPU, DM_MOOD
Add missing persistence calls to API endpoints:
- POST /language/set now persists to config_runtime.yaml
- POST /voice/debug-mode now persists to config_runtime.yaml
- POST /memory/toggle now persists to config_runtime.yaml
Call restore_runtime_settings() in on_ready() after evil/bipolar restore.
Resolves#22
Replace the minimal sync-only shutdown (which only saved autonomous state)
with a comprehensive async graceful_shutdown() coroutine that:
1. Ends active voice sessions (disconnect, release GPU locks, cleanup audio)
2. Saves autonomous engine state
3. Stops the APScheduler
4. Cancels all tracked background tasks (from task_tracker)
5. Closes the Discord gateway connection
Signal handlers (SIGTERM/SIGINT) now schedule the async shutdown on the
running event loop. The atexit handler is kept as a last-resort sync fallback.
Resolves#5, also addresses #4 (voice cleanup at shutdown)
Major changes:
- Remove unused ML libraries: torch, scikit-learn, langchain-core, langchain-text-splitters, langchain-community, faiss-cpu
- Comment out unused langchain imports in utils/core.py (only used in commented-out code)
- Keep transformers (used in persona_dialogue.py for sentiment analysis)
Results:
- Container size reduced from 14.5GB to 2.6GB
- 82% reduction (11.9GB saved)
- Bot runs correctly without errors
- All functionality preserved
Removed packages:
- torch: ~1.0-1.5GB (not used, only in soprano_to_rvc/)
- scikit-learn: ~200-300MB (not used in bot/)
- langchain-core: ~50-100MB (not used, only in commented code)
- langchain-text-splitters: ~30-50MB (not used, only in commented code)
- langchain-community: ~50-80MB (not used, only in commented code)
- faiss-cpu: ~100-200MB (not used in bot/)
This is Phase 1 of container optimization (Quick Wins).
Further optimizations possible:
- OpenCV headless (150-200MB)
- Evaluate Playwright usage (500MB-1GB)
- Alpine base image (1-1.5GB)
- Multi-stage builds (200-400MB)
Major changes:
- Add Pydantic-based configuration system (bot/config.py, bot/config_manager.py)
- Add config.yaml with all service URLs, models, and feature flags
- Fix config.yaml path resolution in Docker (check /app/config.yaml first)
- Remove Fish Audio API integration (tested feature that didn't work)
- Remove hardcoded ERROR_WEBHOOK_URL, import from config instead
- Add missing Pydantic models (LogConfigUpdateRequest, LogFilterUpdateRequest)
- Enable Cheshire Cat memory system by default (USE_CHESHIRE_CAT=true)
- Add .env.example template with all required environment variables
- Add setup.sh script for user-friendly initialization
- Update docker-compose.yml with proper env file mounting
- Update .gitignore for config files and temporary files
Config system features:
- Static configuration from config.yaml
- Runtime overrides from config_runtime.yaml
- Environment variables for secrets (.env)
- Web UI integration via config_manager
- Graceful fallback to defaults
Secrets handling:
- Move ERROR_WEBHOOK_URL from hardcoded to .env
- Add .env.example with all placeholder values
- Document all required secrets
- Fish API key and voice ID removed from .env
Documentation:
- CONFIG_README.md - Configuration system guide
- CONFIG_SYSTEM_COMPLETE.md - Implementation summary
- FISH_API_REMOVAL_COMPLETE.md - Removal record
- SECRETS_CONFIGURED.md - Secrets setup record
- BOT_STARTUP_FIX.md - Pydantic model fixes
- MIGRATION_CHECKLIST.md - Setup checklist
- WEB_UI_INTEGRATION_COMPLETE.md - Web UI config guide
- Updated readmes/README.md with new features
- Added empty settings.json required by Cat plugin system
- Plugin now appears in ACTIVE PLUGINS list
- Enabled via /plugins/toggle API endpoint
- Ready to inject PFP descriptions when user asks about it
- Create profile_picture_context plugin to detect PFP queries via regex
- Inject current_description.txt only when user asks about profile picture
- Mount bot/memory directory in Cat container for PFP access
- Avoids context bloat by only adding PFP description when relevant
- Patterns match: 'what does your pfp look like', 'describe your avatar', etc.
- Works seamlessly with existing profile picture update system
- No manual sync needed - description auto-updates with PFP changes
MOOD SYSTEM FIX:
- Mount bot/moods directory in docker-compose.yml for Cat container access
- Update miku_personality plugin to load mood descriptions from .txt files
- Add Cat logger for debugging mood loading (replaces print statements)
- Moods now dynamically loaded from working_memory instead of hardcoded neutral
Root cause: The miku_personality plugin's agent_prompt_suffix hook was returning
an empty string, which wiped out the {declarative_memory} and {episodic_memory}
placeholders from the prompt template. This caused the LLM to never receive any
stored facts about users, resulting in hallucinated responses.
Changes:
- miku_personality: Changed agent_prompt_suffix to return the memory context
section with {episodic_memory}, {declarative_memory}, and {tools_output}
placeholders instead of empty string
- discord_bridge: Added before_cat_recalls_declarative_memories hook to increase
k-value from 3 to 10 and lower threshold from 0.7 to 0.5 for better fact
retrieval. Added agent_prompt_prefix to emphasize factual accuracy. Added
debug logging via before_agent_starts hook.
Result: Miku now correctly recalls user facts (favorite songs, games, etc.)
from declarative memory with 100% accuracy.
Tested with:
- 'What is my favorite song?' → Correctly answers 'Monitoring (Best Friend Remix) by DECO*27'
- 'Do you remember my favorite song?' → Correctly recalls the song
- 'What is my favorite video game?' → Correctly answers 'Sonic Adventure'
- Replaced Playwright browser scraping with direct API media extraction
- Both fetch_miku_tweets() and fetch_figurine_tweets_latest() now use twscrape's built-in media info
- Reduced tweet fetching from 10-15 minutes to ~5 seconds
- Eliminated browser timeout/hanging issues
- Relaxed autonomous tweet sharing conditions:
* Increased message threshold from 10 to 20 per hour
* Reduced cooldown from 3600s to 2400s (40 minutes)
* Increased energy threshold from 50% to 70%
* Added 'silly' and 'flirty' moods to allowed sharing moods
This makes both figurine notifications and tweet sharing much more reliable and responsive.
**Critical Bug Fixes:**
1. Per-user memory isolation bug
- Changed CatAdapter from HTTP POST to WebSocket /ws/{user_id}
- User_id now comes from URL path parameter (true per-user isolation)
- Verified: Different users can't see each other's memories
2. Memory API 405 errors
- Replaced non-existent Cat endpoint calls with Qdrant direct queries
- get_memory_points(): Now uses POST /collections/{collection}/points/scroll
- delete_memory_point(): Now uses POST /collections/{collection}/points/delete
3. Memory stats showing null counts
- Reimplemented get_memory_stats() to query Qdrant directly
- Now returns accurate counts: episodic: 20, declarative: 6, procedural: 4
4. Miku couldn't see usernames
- Modified discord_bridge before_cat_reads_message hook
- Prepends [Username says:] to every message text
- LLM now knows who is texting: [Alice says:] Hello Miku!
5. Web UI Memory tab layout
- Tab9 was positioned outside .tab-container div (showed to the right)
- Moved tab9 HTML inside container, before closing divs
- Memory tab now displays below tab buttons like other tabs
**Code Changes:**
bot/utils/cat_client.py:
- Line 25: Logger name changed to 'llm' (available component)
- get_memory_stats() (lines 256-285): Query Qdrant directly via HTTP GET
- get_memory_points() (lines 275-310): Use Qdrant POST /points/scroll
- delete_memory_point() (lines 350-370): Use Qdrant POST /points/delete
cat-plugins/discord_bridge/discord_bridge.py:
- Fixed .pop() → .get() (UserMessage is Pydantic BaseModelDict)
- Added before_cat_reads_message logic to prepend [Username says:]
- Message format: [Alice says:] message content
Dockerfile.llamaswap-rocm:
- Lines 37-44: Added conditional check for UI directory
- if [ -d ui ] before npm install && npm run build
- Fixes build failure when llama-swap UI dir doesn't exist
bot/static/index.html:
- Moved tab9 from lines 1554-1688 (outside container)
- To position before container closing divs (now inside)
- Memory tab button at line 673: 🧠 Memories
**Testing & Verification:**
✅ Per-user isolation verified (Docker exec test)
✅ Memory stats showing real counts (curl test)
✅ Memory API working (facts/episodic loading)
✅ Web UI layout fixed (tab displays correctly)
✅ All 5 services running (llama-swap, llama-swap-amd, qdrant, cat, bot)
✅ Username prepending working (message context for LLM)
**Result:** All Phase 3 critical bugs fixed and verified working.
Key changes:
- CatAdapter (bot/utils/cat_client.py): WebSocket /ws/{user_id} for chat
queries instead of HTTP POST (fixes per-user memory isolation when no
API keys are configured — HTTP defaults all users to user_id='user')
- Memory management API: 8 endpoints for status, stats, facts, episodic
memories, consolidation trigger, multi-step delete with confirmation
- Web UI: Memory tab (tab9) with collection stats, fact/episodic browser,
manual consolidation trigger, and 3-step delete flow requiring exact
confirmation string
- Bot integration: Cat-first response path with query_llama fallback for
both text and embed responses, server mood detection
- Discord bridge plugin: fixed .pop() to .get() (UserMessage is a Pydantic
BaseModelDict, not a raw dict), metadata extraction via extra attributes
- Unified docker-compose: Cat + Qdrant services merged into main compose,
bot depends_on Cat healthcheck
- All plugins (discord_bridge, memory_consolidation, miku_personality)
consolidated into cat-plugins/ for volume mount
- query_llama deprecated but functional for compatibility
Implements unified cross-server memory system for Miku bot:
**Core Changes:**
- discord_bridge plugin with 3 hooks for metadata enrichment
- Unified user identity: discord_user_{id} across servers and DMs
- Minimal filtering: skip only trivial messages (lol, k, 1-2 chars)
- Marks all memories as consolidated=False for Phase 2 processing
**Testing:**
- test_phase1.py validates cross-server memory recall
- PHASE1_TEST_RESULTS.md documents successful validation
- Cross-server test: User says 'blue' in Server A, Miku remembers in Server B ✅
**Documentation:**
- IMPLEMENTATION_PLAN.md - Complete architecture and roadmap
- Phase 2 (sleep consolidation) ready for implementation
This lays the foundation for human-like memory consolidation.