Commit Graph

159 Commits

Author SHA1 Message Date
6080fe170f Fix all activity system edge cases
Critical fixes:
- Add threading.Lock for all shared mutable state (override, cache, current activity)
- Atomic YAML writes (temp file + os.replace) to prevent corruption on crash
- Deep-copy cache on reads to prevent callers from mutating shared state

High-severity fixes:
- Validate entries in pick_activity_for_mood() — skip/log malformed instead of KeyError
- Log warning on unrecognized activity type fallback
- Normalize empty-string state to None (avoid 'None' display)
- release_manual_override() now uses force=True so bot always shows activity
- Add try/except in release_manual_override() to handle failures gracefully

Medium fixes:
- Remove dead 'test' mood from activities.yaml
- Validate name length (128 char Discord limit) in CRUD and manual set
- Validate streaming entries have URL in CRUD path
- Add JSON parse error handling in API routes
- on_ready preserves active manual override instead of overwriting
- Log override expiry timestamp (HH:MM:SS) for easier debugging
- exc_info=True on presence update errors for full stack traces

Low fixes:
- JS activitySetFromEntry() shows notification on parse error
2026-04-28 00:18:25 +03:00
2d7acd7850 Add anime watching entries to all moods in activities.yaml
- Added 39 new watching entries across all 24 moods (7→46 total)
- Each mood gets 1-2 anime entries thematically matched:
  - bubbly: Cardcaptor Sakura, Precure (magical girl)
  - excited: Bocchi the Rock,, K-ON! (music/slice of life)
  - sleepy: Laid-Back Camp, Natsume's Book of Friends (iyashikei)
  - curious: Dr. Stone (science)
  - shy: Kimi ni Todoke, My Little Monster (shoujo romance)
  - serious: Code Geass (mecha strategy)
  - melancholy: Your Lie in April, Anohana (drama)
  - flirty: Ouran High School Host Club, Kaguya-sama (romcom)
  - romantic: Toradora,, Horimiya (romance)
  - irritated: Asuka's Angry Moments (Evangelion)
  - angry: Attack on Titan, Demon Slayer (action)
  - silly: Nichijou, Gintama (comedy)
  - evil moods: Hellsing, Death Note, NGE, Future Diary, etc.
2026-04-27 23:59:20 +03:00
9d1ad7f783 Add 'Set as Activity' button to each activity entry in Web UI
Each activity in the mood lists now has a 🎯 Set button that immediately
sets it as the bot's current Discord activity (30-min manual override),
so users can pick from existing entries instead of typing manually.
2026-04-27 23:43:18 +03:00
d6cdb89e42 Refactor activity system: energy-based probability, manual override, all 5 activity types
- Rewrite utils/activities.py with mood energy-driven activity probability
  (high-energy moods like excited/bubbly show activity ~80-85% of the time,
  low-energy moods like sleepy/melancholy only ~15-25%)
- Add manual override system with 30-min auto-expiry for Web UI control
- Support all 5 Discord activity types: listening, playing, watching,
  competing, streaming (with purple LIVE badge via discord.Streaming)
- Add current activity tracking (get_current_activity)
- Add force=True param to update_bot_presence for on_ready (bot.py)
- Add 4 new API routes for manual override:
  GET/POST/DELETE /activities/current, POST /activities/current/auto
- Expand activities.yaml from 139 to 157 entries, adding watching,
  competing, and streaming entries across 11 moods
- Update Web UI: activity type dropdown with all 5 types, conditional
  URL field for streaming, 'Current Activity' override panel with
  set/clear/auto controls, type-aware icons and labels
2026-04-27 23:39:18 +03:00
9bc618b526 feat: add 'state' field to mood activities for richer Discord presence
- Add 'state' field to all 139 activity entries in activities.yaml
  - Songs: state shows artist (e.g. 'by kz (livetune)')
  - Games: state shows genre (e.g. 'Rhythm Game', 'Sandbox', 'FPS')
- Update pick_activity_for_mood() to return 3-tuple (type, name, state)
- Update update_bot_presence() to pass state to discord.Activity()
- Add state validation in set_activities_for_mood() (optional string)
- Update Web UI editor: view shows state, edit form has state input
- State is fully optional — backward compatible, no breaking changes

The 'state' field appears as a secondary text line in Discord profile
popup, the richest display possible for bot accounts (full Rich Presence
with cover art/buttons is server-side restricted to OAuth applications).
2026-04-24 16:46:39 +03:00
4dc24b7da8 fix: copy activities.yaml into Docker image 2026-04-24 14:05:09 +03:00
1908b92ce8 fix: move Mood Activities section above Last Prompt in Status tab
Reorders the Status tab so the collapsible Mood Activities editor
appears before the Last Prompt section for better visibility.
2026-04-24 13:59:01 +03:00
6780f6de9e fix: register 'activity' logger component
The custom logger requires components to be registered in the
COMPONENTS dict. Added 'activity' for the mood-based presence system.
2026-04-24 13:58:37 +03:00
9293aec301 feat: add Mood Activities editor to Web UI Status tab
Collapsible section in the Status tab with:
- Normal and Evil mood sections, each collapsible
- Per-mood expandable rows showing songs (🎵) and games (🎮)
- Inline editing: change type, name, weight
- Add/remove entries per mood
- Save via API with client-side validation
- Reload from disk button
- Lazy-loads data only when section is expanded
2026-04-24 13:46:04 +03:00
0f39ccd3c4 feat: set initial Discord presence on startup and on mood detection
- In on_ready(), set presence based on current mood (evil or normal)
  after all state is restored
- When LLM-detected mood shift is applied, update presence immediately
2026-04-24 13:39:39 +03:00
55c3c27f6f feat: integrate activity presence into evil mode
Update Discord presence when:
- Evil mood rotates (shows evil song/game)
- Evil mode is enabled (switches to evil activity pool)
- Evil mode is disabled (restores normal mood activity)
2026-04-24 13:37:21 +03:00
53c07d40e9 feat: integrate activity presence into mood rotation
Call update_bot_presence() in rotate_dm_mood() and
rotate_server_mood() so the Discord status updates whenever
a normal mood rotates automatically.
2026-04-24 13:35:03 +03:00
d6742b0c85 feat: add activities API routes and register in api.py
New endpoints:
- GET /activities — full data (normal + evil)
- GET /activities/{section}/{mood} — per-mood activities
- POST /activities/{section}/{mood} — update activities with validation
- POST /activities/reload — force reload from disk
2026-04-24 13:32:55 +03:00
a5916645df feat: add activities.py module for mood-based Discord presence
New module that loads activities.yaml and provides:
- Weighted random activity selection per mood
- Discord presence update (Listening/Playing)
- File mtime caching for hot-reload
- Validation for CRUD operations
- Fallback for moods with no activities defined
2026-04-24 13:30:54 +03:00
e30316f383 feat: add activities.yaml with mood-based songs and games
Curated list of Vocaloid/Miku songs and real game titles for each
normal mood (13 moods, excluding asleep) and each evil mood (10 moods).
Each entry has type (listening/playing), name, and weight for
weighted random selection. Editable via this file or the Web UI.
2026-04-24 13:20:47 +03:00
edc9f27925 feat: add proper HTTP status codes to all API error responses
- 217 error returns across 18 route files + api.py now use JSONResponse
  with appropriate HTTP status codes instead of returning HTTP 200
- Status code distribution: 500 (121), 400 (39), 503 (28), 404 (24), 409 (3), 502 (2)
- Fixed language.py tuple-return bug (was serializing as JSON array)
- Fixed bare except clauses in bipolar_mode.py and voice.py
- Body-level error schemas preserved (status/error + success/error patterns)
  so web UI continues working without changes
- chat.py (SSE) unchanged: errors sent within stream protocol
- All 170 tests pass
2026-04-15 15:43:18 +03:00
33b2033cc3 fix: clarify angry_wakeup_timer intent with TODO comment (Phase E Step 20)
- Change misleading 'Unused, kept for structural completeness' to
  'TODO: implement angry-wakeup mechanic or remove field'
- Field is dead code: never read or written in any Python code
2026-04-15 12:26:09 +03:00
fc4674bb13 refactor: extract media processing from bot.py into image_handling.py (Phase D Step 19)
- Create process_media_in_message() in utils/image_handling.py that handles all 4 media
  types: image attachments, video/GIF attachments, Tenor GIF embeds, and rich embeds
- DRY the send→log→bipolar tail pattern (5x repeated) into _send_log_bipolar() helper
- Unify rich/article/link embed handling to use rephrase_as_miku() instead of inline
  Cat→LLM routing, fixing a mood-resolution bug (was using globals.DM_MOOD for servers)
- Add 'rich_embed' media_type to rephrase_as_miku() prefix switch
- Remove 3 inline 'import base64' from bot.py (already module-level in image_handling.py)
- bot.py: 986 → 623 lines (-363)
- image_handling.py: 559 → 881 lines (+322)
- All 170 tests pass (21 config/state + 149 route split)
2026-04-15 12:19:37 +03:00
979217e7cc refactor: split api.py monolith into 19 route modules (Phase B)
Split 3,598-line api.py into thin orchestrator (128 lines) + 19 route
modules in bot/routes/:

  core.py (7 routes), mood.py (10), language.py (3), evil_mode.py (6),
  bipolar_mode.py (9), gpu.py (2), bot_actions.py (4), autonomous.py (13),
  profile_picture.py (26), manual_send.py (3), servers.py (6),
  figurines.py (5), dms.py (18), image_generation.py (4), chat.py (1),
  config.py (7), logging_config.py (9), voice.py (3), memory.py (10)

All 146 routes verified present via test_route_split.py (149 tests).
21/21 regression tests (test_config_state.py) pass.
Monolith backup: bot/api_monolith_backup.py (revert: cp it to api.py).
2026-04-15 11:38:14 +03:00
8b14160028 refactor: consolidate conversation_history to ConversationHistory class
Remove legacy globals.conversation_history (defaultdict of deques) and
route all callers through utils.conversation_history.ConversationHistory:

- globals.py: remove conversation_history + unused collections imports
- llm.py: remove backward-compat dual-write to legacy system
- api.py: /conversation/{user_id} now reads from ConversationHistory
- actions.py: reset_conversation uses clear_channel()
- figurine_notifier.py: use add_message() instead of buggy setdefault()
- bipolar_mode.py: fix clear_history -> clear_channel (was AttributeError
  silently swallowed by bare except), fix bare except -> except Exception
2026-04-11 00:21:44 +03:00
02686c3b96 fix: PREFER_AMD_GPU now lives in globals so config API changes affect GPU routing
Previously gpu_router.py had its own module-level PREFER_AMD_GPU constant
that was frozen at import time. The config API wrote to globals.PREFER_AMD_GPU
which didn't exist, so runtime GPU preference changes never took effect.

Now globals.py owns PREFER_AMD_GPU and gpu_router reads it from there.
2026-04-10 23:53:14 +03:00
366bee2e43 test: add regression test suite for config/state hardening (steps 1-10)
21 tests across 6 groups:
A. Config loading & persistence (runtime path, YAML schema, overrides)
B. Runtime state (live globals reading, /config/set sync, restore)
C. Reset (full reset, single-key reset)
D. Server manager (zero-server default, corrupt handling, CRUD, no dead code)
E. GPU deduplication (delegates to config_manager, correct URL switching)
F. Clean imports (no dead os/Union/GUILD_SETTINGS)

Run: ./bot/tests/run_tests.sh (builds + runs in Docker container)
2026-04-10 17:30:14 +03:00
5ac1f7fa8c cleanup: remove dead code + deduplicate GPU state reads
Dead code removed:
- globals.py: GUILD_SETTINGS (empty dict, zero consumers)
- config.py: unused 'import os'
- config_manager.py: unused 'import os' and 'Union'
- server_manager.py: duplicate 'from datetime import datetime, timedelta'

GPU deduplication:
- get_current_gpu_url() now delegates to config_manager.get_gpu()
- get_gpu_status() endpoint now delegates to config_manager.get_gpu()
- Both previously re-read memory/gpu_state.json directly
2026-04-09 20:34:17 +03:00
834b2ea188 fix: start with zero servers when config is missing or corrupt
Removed _create_default_config() which hardcoded a specific guild ID
(759889672804630530) as a fallback. Now:
- Missing servers_config.json → starts with empty servers dict
- Corrupt JSON → logs error, starts with empty servers dict
- Servers are added via the API/dashboard, not by magic defaults

All code that iterates server_manager.servers handles empty dicts safely.
2026-04-09 20:15:57 +03:00
7804aa4d76 cleanup: remove dead server_memories code
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.
2026-04-09 20:10:53 +03:00
5c5c9e2723 cleanup: remove dead server config methods from config_manager
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.
2026-04-08 15:47:36 +03:00
b4e48ce375 fix: /config/set now syncs all runtime-relevant globals
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
2026-04-08 15:05:25 +03:00
7c9cf0d8b4 fix: /config/reset now resets live globals to defaults
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.
2026-04-08 14:58:29 +03:00
9be7c0b1d2 fix: make /config/state return live runtime values from globals
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.
2026-04-08 14:53:13 +03:00
0831f721e1 cleanup: remove dead backward-compat globals from config.py
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.
2026-04-08 14:40:16 +03:00
4b5be3bf97 fix: align config.yaml structure with Pydantic AppConfig schema
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.
2026-04-08 14:14:56 +03:00
742b7b6b64 fix: persist config_runtime.yaml inside volume-mounted memory dir
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.
2026-04-08 14:14:31 +03:00
f50c677baf ui: move Re-crop Current button under cropped avatar preview in tab11
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.
2026-03-31 15:16:40 +03:00
56a70705b2 feat: add PFP album/gallery system with batch upload, cropping, and disk management
- 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
2026-03-31 15:09:57 +03:00
f092cadb9d feat: add Profile Picture Management tab with manual crop, description editor
- 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
2026-03-30 15:10:19 +03:00
08fb465c67 Fix: Cache regular Miku avatar URL to prevent pfp bleed in bipolar arguments
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
2026-03-30 14:30:34 +03:00
e6529f1bc3 added check to only crop pfp if > minimum Discord pfp resolution 2026-03-30 12:50:34 +03:00
54d9a80089 fixed webhook pfp for regular miku being wrong when evil mode active 2026-03-05 22:16:14 +02:00
832fc0d039 added test log with multiple various test scenarios between models and evil/regular miku 2026-03-05 22:04:26 +02:00
d5b9964ce7 Fix vision pipeline: route images through Cat, pass user question to vision model
- 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)
2026-03-05 21:59:27 +02:00
ae1e0aa144 add: cheshire-cat configuration, tooling, tests, and documentation
Configuration:
- .env.example, .gitignore, compose.yml (main docker compose)
- docker-compose-amd.yml (ROCm), docker-compose-macos.yml
- start.sh, stop.sh convenience scripts
- LICENSE (Apache 2.0, from upstream Cheshire Cat)

Memory management utilities:
- analyze_consolidation.py, manual_consolidation.py, verify_consolidation.py
- check_memories.py, extract_declarative_facts.py, store_declarative_facts.py
- compare_systems.py (system comparison tool)
- benchmark_cat.py, streaming_benchmark.py, streaming_benchmark_v2.py

Test suite:
- quick_test.py, test_setup.py, test_setup_simple.py
- test_consolidation_direct.py, test_declarative_recall.py, test_recall.py
- test_end_to_end.py, test_full_pipeline.py
- test_phase2.py, test_phase2_comprehensive.py

Documentation:
- README.md, QUICK_START.txt, TEST_README.md, SETUP_COMPLETE.md
- PHASE2_IMPLEMENTATION_NOTES.md, PHASE2_TEST_RESULTS.md
- POST_OPTIMIZATION_ANALYSIS.md
2026-03-04 00:51:14 +02:00
eafab336b4 feat: add Traefik proxy, custom chat template, improve Cheshire Cat memory
docker-compose.yml:
- Add Traefik proxy network + labels for miku.panel domain
- Connect miku-bot service to proxy network

llama-swap-config.yaml / llama-swap-rocm-config.yaml:
- Add --chat-template-file flag to disable Llama 3.1 built-in tool
  calling (was causing malformed responses)
- ROCm config: add Rocinante-X 12B model entry for comparison testing

cheshire-cat discord_bridge plugin:
- Increase declarative memory recall (k=3→10, threshold 0.7→0.5)
  for better factual retrieval
- Add agent_prompt_prefix hook to enforce factual accuracy from
  declarative memories
- Add before_agent_starts debug logging for memory inspection
- Add passthrough hooks for message/suffix pipeline
2026-03-04 00:48:58 +02:00
335b58a867 feat: fix evil mode race conditions, expand moods and PFP detection
bipolar_mode.py:
- Replace unsafe globals.EVIL_MODE temporary overrides with
  force_evil_context parameter to fix async race conditions (3 sites)

moods.py:
- Add 6 new evil mood emojis: bored, manic, jealous, melancholic,
  playful_cruel, contemptuous
- Refactor rotate_dm_mood() to skip when evil mode active (evil mode
  has its own independent 2-hour rotation timer)

persona_dialogue.py:
- Same force_evil_context race condition fix (2 sites)
- Fix over-aggressive response cleanup that stripped common words
  (YES/NO/HIGH) — now uses targeted regex for structural markers only
- Update evil mood multipliers to match new mood set

profile_picture_context:
- Expand PFP detection regex for broader coverage (appearance questions,
  opinion queries, selection/change questions)
- Add plugin.json metadata file
2026-03-04 00:45:23 +02:00
5898b0eb3b fix: update .gitignore to cover all bot/memory subdirs, untrack runtime data
- 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
2026-03-04 00:43:10 +02:00
8ca716029e add: absorb soprano_to_rvc as regular subdirectory
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).
2026-03-04 00:24:53 +02:00
34b184a05a add: absorb uno-online as regular subdirectory
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.
2026-03-04 00:21:38 +02:00
c708770266 reorganize: consolidate all documentation into readmes/
- 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
2026-03-04 00:19:49 +02:00
fdde12c03d reorganize: move all test scripts to tests/ directory
- 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)
2026-03-04 00:18:21 +02:00
431f675fc7 cleanup: update .gitignore, sanitize .env.example, remove stale files
- Expanded .gitignore: miku-app/, dashboard/, .continue/, *.code-workspace,
  cheshire-cat artifacts (venv, benchmarks, test output), jinja templates
- Sanitized .env.example: replaced real webhook URL and user ID with placeholders
- Removed SECRETS_CONFIGURED.md (contained sensitive token info)
- Removed bot/static/system.html.bak (stale backup)
- Removed bot/utils/voice_receiver.py.old (superseded)
2026-03-04 00:17:05 +02:00
a226bc41df Rewrite is_miku_addressed() to only trigger when addressed, not mentioned
- 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)
2026-03-03 12:42:33 +02:00