205 Commits

Author SHA1 Message Date
cfd5eb16f7 fix: protect server config from truncation and recover from Discord guilds
- Save servers_config.json atomically via temp file + fsync + rename
- Keep .bak backup and auto-restore when main config is empty/corrupt
- Add /servers/recover endpoint for manual recovery
- Auto-recover basic server configs on startup when config is empty but bot is in guilds
2026-06-11 20:37:04 +03:00
486acb5c14 Fix reply-context speaker confusion with structured metadata pipeline
Previously, when a user replied to Miku's message via Discord's reply
feature, Miku's quoted words were embedded directly into the user's
message text using the format:
  [Replying to your message: "Miku's words"] User's response

This caused two problems:
1. The LLM had to parse "your message" to determine the quoted text
   was MIKU's words — fragile and frequently misattributed
2. When stored in episodic memory as [User]: ..., Miku's quoted words
   were permanently mislabeled under the user's speaker prefix

Now reply context flows through as structured metadata:
- bot/bot.py captures the replied-to text WITHOUT embedding it in prompt
- cat_client.py passes it as discord_reply_context in the WebSocket payload
- discord_bridge.py injects it as agent_input['reply_context'] — a
  CLEARLY LABELED note: [The user is replying to what you (Miku) said — ...]
- miku_personality.py + evil_miku_personality.py render it via
  {reply_context} placeholder in the prompt suffix, between memory
  context and conversation history

This keeps Miku's words as a separate context note, never mixed into
the user's HumanMessage. Episodic memory only stores the user's actual
words. The fallback path (when Cat is unavailable) also uses a cleaner
format with explicit speaker labels.
2026-06-03 22:50:03 +03:00
9d2c14fa0b Fix vision pipeline: ffmpeg removal by autoremove, increase vision timeout, reduce frame count, add Discord activity awareness
- bot/Dockerfile: Add ffmpeg to reinstall line after apt-get autoremove
  (autoremove was sweeping up ffmpeg as 'no longer needed' after playwright install)
- bot/utils/image_handling.py: Increase video analysis timeout 120s→300s, 6→3 for Tenor GIFs (GTX 1660 VRAM constraint)
- bot/utils/activities.py: Add _activity_changed_at timestamp tracking,
  get_current_activity_label() and get_current_activity_fresh() with 30-min decay
- bot/utils/cat_client.py: Pass current Discord activity to Cheshire Cat pipeline
- bot/utils/llm.py: Inject current Discord activity into system prompt
- cat-plugins/*: Forward Discord activity through working_memory to personality plugins
- bot/persona/*/preamble.txt: Add Discord status usage guidelines for character prompts
- llama-swap-rocm-config.yaml: Add qwen3.5 model entry for ComfyUI prompt generation
- AGENTS.md: New project documentation file
2026-05-27 01:18:12 +03:00
d333c61c8f fix: set default bedtime end time to 11 PM (was 9 PM) 2026-05-22 21:40:01 +03:00
e1f81e52e5 Fix Miku confusing who said what in conversations
Three interrelated fixes for speaker attribution confusion:

1. Fix misleading episodic memory header (discord_bridge.py):
   The Cat core hardcodes '## Context of things the Human said in the past:'
   when formatting recalled conversations. Our plugins store BOTH user messages
   ([User]: prefix) AND Miku's own responses ([Miku]: prefix) in episodic memory.
   This misleading header primes the LLM to attribute Miku's words to the user.
   Replaced with '## Past conversation excerpts (prefixed by who said what):'
   which accurately describes the mixed-speaker content.

2. Tighten episodic recall (discord_bridge.py):
   Added before_cat_recalls_episodic_memories hook setting threshold=0.75
   (vs default 0.7) to reduce the chance of Miku's own just-uttered response
   being recalled on the very next user message, which would feed her own
   words back as misleading context.

3. Add role clarification (miku_personality.py & evil_miku_personality.py):
   Added a clarifying note after '# Conversation until now:' in the prompt
   suffix to explicitly tell the model that 'Human = the user, AI = you (Miku)',
   helping it reconcile the two labeling systems (episodic [User]/[Miku] prefixes
   vs conversation history Human/AI roles).
2026-05-22 16:38:34 +03:00
201f2e3df5 feat(ui): add model selection UI to LLM Settings tab
- Three dropdowns for Regular Miku, Evil Miku, Japanese Mode models
- GPU availability badges (Both GPUs / NVIDIA Only / AMD Only)
- Refresh Models + Refresh Status buttons
- Load models on tab switch with defensive checks
- Bump cache-busting version for all JS files
- Remove redundant Current Status section
2026-05-20 13:55:35 +03:00
b017a0ec04 feat(api): register models_selector routes
- Import and include models_selector router in FastAPI app
2026-05-20 13:55:29 +03:00
6bf9a30c33 feat(routes): sync model globals via config API, fix log message
- Add models.text, models.evil, models.japanese to config/set globals sync
- Fix language toggle log to show actual model name instead of hardcoded string
2026-05-20 13:55:22 +03:00
8e5260561a feat(config): persist model selections via config_manager
- Add models.text, models.evil, models.japanese to restore_runtime_settings
- Add model keys to reset_to_defaults with CONFIG defaults
- Include model info in runtime_state for API visibility
2026-05-20 13:55:11 +03:00
b4737c1ae1 fix(cat): use configured models instead of hardcoded strings
- switch_to_evil_personality now reads EVIL_TEXT_MODEL from globals
- switch_to_normal_personality now reads TEXT_MODEL from globals
- Removes desync risk when user changes models via Web UI
2026-05-20 13:55:05 +03:00
ae4e40f2d7 feat(models): add model selection API endpoints
- GET /models/available: query both llama-swap instances for model lists
- POST /models/select: set per-persona model (regular/evil/japanese) with persistence
- GET /models/status: return current per-persona model assignments
- Fall back to known model list when containers are unreachable
2026-05-20 13:54:59 +03:00
7cb21a372b feat: make Web UI mood dropdowns Evil Mode-aware
- Disable per-server mood controls when Evil Miku is active
- Show explanatory notice for disabled server mood dropdowns
- Populate global mood dropdown with evil moods when Evil Mode is on
- Fix initialization race condition by awaiting evil mode status first
- Add CSS styles for disabled mood controls
2026-05-18 21:43:44 +03:00
27f0659cc8 fix: Evil Miku now ignores questions — restore engagement + remove suffix hammer
Preamble:
- Sentence limit 1-3 → 2-4 (revert to original 'sting, then land' range)
- Remove 'if you can say it in one, say it in one' (encouraged lazy dismissals)
- Add engagement rule: 'Always engage with what was said — acknowledge the
  question or statement, then twist the knife. Ignoring isn\'t sharp, it\'s lazy.'

Suffix:
- Remove '[Keep responses short and cutting — 1-3 sentences. No monologues.]'
  The suffix was the LAST thing the model processed, so its brevity hammer
  overpowered the preamble's engagement instruction. Preamble alone is enough.
2026-05-18 11:09:24 +03:00
6b6d705024 fix: Evil Miku wordiness regression from username prompt changes
Five targeted fixes:

1. discord_bridge (priority 100): Skip 'cheerful virtual idol' wrapper and
   'CRITICAL INSTRUCTION' about facts when evil_mode is active. Evil Miku
   gets her own prompt from evil_miku_personality plugin.

2. memory_consolidation (priority 10): Soften fact-usage pressure:
   'Use THESE facts when answering' → 'You may reference these facts if
   relevant to the conversation'. Also soften username command tone.

3. evil_miku_personality (priority 100→101): Bump above discord_bridge
   so Evil Miku's prefix replacement deterministically discards any
   Miku-mode wrappers regardless of plugin load order.

4. evil preamble: Restructure for brevity — add 'Be SHORT and SHARP'
   declaration, move RESPONSE RULES before mood, tighten sentence limit
   from 2-4 to 1-3 with 'if you can say it in one, say it in one.'

5. evil suffix: Add final brevity reminder '[Keep responses short and
   cutting — 1-3 sentences. No monologues.]' right before conversation
   for maximum recency influence.
2026-05-18 10:56:07 +03:00
e091fc1417 fix: use discord_consolidation user ID for consolidation WS connection
Cat's WebSocket handler returns HTTP 500 for user IDs without the
'discord_' prefix. The consolidation WS was using 'system_consolidation'
which failed immediately with WSServerHandshakeError (500). Changed to
'discord_consolidation' which connects successfully.
2026-05-17 11:52:03 +03:00
a39aca2415 fix: make consolidation API async with background task + increased timeout
Three fixes for consolidation reliability:

1. Fire-and-forget API: POST /memory/consolidate now launches consolidation
   as an asyncio background task and returns immediately. The old approach
   blocked until Cat's WS response, which could take 5+ minutes (LLM
   extraction calls), exceeding both the WS timeout and browser fetch
   timeout. Web UI now polls /memory/status to track completion.

2. Increased timeout: cat_client.trigger_consolidation() timeout raised
   from 300s to 600s (configurable via parameter). Logs unexpected WS
   message types for debugging.

3. Better logging: Consolidation log messages prefixed with 🌙 for
   grep-friendliness. cat_client errors include exc_info=True for
   traceback visibility. Web UI shows elapsed time while polling.
2026-05-17 11:31:26 +03:00
46ea4f2c53 fix(memory): prevent stale name facts from overriding Discord display name
Two bugs were causing Miku to call users by wrong names:

BUG 1 - No authoritative source:
  Declarative name facts ('The user's name is Lily') were injected into
  the prompt without any counterweight. If an old consolidation run
  extracted a wrong name, Miku would believe it forever.
  Fix: agent_prompt_prefix now appends the user's Discord display name
  as AUTHORITATIVE context, with explicit instruction to prefer it over
  any contradictory name facts.

BUG 2 - Dedup prevented name updates:
  _is_duplicate_fact() used vector similarity to detect duplicates.
  'The user's name is Lily' and 'The user's name is koko210Serve' are
  ~80% identical text, giving high cosine similarity (>0.85 threshold).
  New correct name facts were silently rejected as 'duplicates'.
  Fix: name facts now use _find_existing_fact() to compare fact_value
  directly. If the name changed, old fact is deleted and new one stored.

Also: the extraction prompt now includes the user's Discord display
name as a hint, so the LLM knows the authoritative name when extracting
facts during consolidation.
2026-05-17 11:20:49 +03:00
5f06758c3e fix: sync llm.py inline fallback with updated preamble.txt
The MOOD GUIDELINES section in the emergency fallback (used only when
preamble.txt is missing) still had the old wording. Now matches.
2026-05-15 14:46:29 +03:00
8b3bc02f9e refactor: DRY system prompts into shared preamble files
Step 4 of memory system overhaul: single source of truth for prompts.

Problem: The system prompt was defined inline in 4 different places:
  miku_personality.py, evil_miku_personality.py, llm.py, discord_bridge.py.
These could drift out of sync — and the discord_bridge WebUI
reconstruction was already missing CRITICAL RULES, CHARACTER CONTEXT,
MOOD GUIDELINES, and RESPONSE RULES sections.

Fix:
- Create persona/miku/preamble.txt — canonical normal Miku preamble
- Create persona/evil/preamble.txt — canonical evil Miku preamble
  (with {mood_name} and {mood_description} format placeholders)
- All 5 consumers now read from these files:
  * miku_personality.py (Cat plugin, primary path)
  * evil_miku_personality.py (Cat plugin, primary path)
  * discord_bridge.py (WebUI 'Last Prompt' reconstruction)
  * llm.py (fallback path, normal Miku)
  * evil_mode.py get_evil_system_prompt() (fallback path, evil Miku)
- All consumers include graceful fallbacks if preamble files are missing
- Fixed evil_mode.py discrepancy: 'body and size' now matches canonical

The preamble files are Docker volume-mounted into both containers:
  bot/persona/ → /app/persona/ (bot, via Dockerfile COPY)
  bot/persona/ → /app/cat/data/ (Cat, via docker-compose volume mount)
Editing the preamble file on the host immediately updates the Cat path
(bot path requires rebuild due to COPY).
2026-05-15 14:43:19 +03:00
e7ec82d154 feat(memory): add [User]: prefix to user messages for speaker clarity
Prevents Miku from confusing her own words with what users said.

User messages stored by discord_bridge now get a '[User]: ' prefix on
page_content, mirroring the existing '[Miku]: ' prefix on Miku's own
responses. When episodic memories are recalled via RAG and injected
into the prompt, the LLM can now clearly distinguish:

  [User]: I like pizza
  [Miku]: That's great! What toppings do you like?

Without this, raw user text looked identical to Miku's text in the
recalled memory context, causing potential confusion about who said what.

The consolidation classifier strips the [User]: prefix before analyzing
content, so word counts and pattern matching remain accurate.
2026-05-15 14:13:29 +03:00
5a740c9334 feat(memory): hybrid trivial-message classifier (heuristics + LLM batch)
Step 3 of memory system overhaul: smart junk detection.

Replaces the old 37-pattern frozenset (44% accuracy) with a 3-tier hybrid:

TIER 1 - DEFINITELY_TRIVIAL (instant delete, no LLM):
  50+ exact-match patterns, pure emoji, single char, punctuation-only

TIER 2 - DEFINITELY_IMPORTANT (instant keep, no LLM):
  8+ words, question with substance, first-person statements,
  numbers/dates, links, mentions

TIER 3 - BORDERLINE (batch → LLM for economical classification):
  2-7 word messages without clear markers
  Compact prompt: ~150-200 tokens per 20-message batch
  Safety default: KEEP on any parsing error

Real-time filtering (discord_bridge) uses conservative heuristics only:
  - 1-char, pure reactions, single emoji, custom emoji-only
  - 50+ single-word fillers
  - Never deletes multi-word messages in real-time
  - Philosophy: false negatives (junk stored) > false positives (data lost)

Consolidation gets the full hybrid pipeline with LLM for borderline
cases, achieving much better accuracy than the old 44% while keeping
token costs minimal (LLM only called during nightly consolidation,
not real-time chat).
2026-05-15 14:07:35 +03:00
cb4be35f13 fix: register 'consolidation' component in logger whitelist
The new consolidation_scheduler.py uses get_logger('consolidation')
but the component wasn't registered in COMPONENTS dict, causing
on_ready to crash with a ValueError.
2026-05-15 13:58:39 +03:00
f3c4a8fe5a feat(memory): add automated nightly consolidation at 4:00 AM UTC
Step 2 of memory system overhaul: automated scheduling.

- New consolidation_scheduler.py: run_nightly_consolidation() function that
  checks Cat health, triggers consolidation via WebSocket, and tracks
  run history with success/failure stats
- bot.py on_ready: register APScheduler cron job (hour=4, minute=0)
  alongside the existing daily DM analysis job
- routes/memory.py: expose consolidation status (last_run, last_result,
  last_error, is_running, total_runs, successful_runs) in the
  /memory/status API response
- Web UI: show consolidation schedule info (last run time, success/fail,
  run counts) below the manual consolidate button, with 'running now'
  indicator when active

The 'sleep consolidation' metaphor is now actually automated instead of
being manual-only.
2026-05-15 13:54:54 +03:00
811bcc0a5d feat: add Miku favicon to Web UI with transparent background 2026-05-13 01:29:17 +03:00
e6e81885b3 feat(memory): tag all memories with source persona (miku/evil_miku)
Step 1 of memory system overhaul: persona tagging.

- discord_bridge: tag user messages with 'persona' metadata at storage time
- memory_consolidation: tag Miku's own responses with 'persona' metadata
- memory_consolidation: tag declarative facts with source persona during extraction
- memory_consolidation: pass persona context to LLM extraction prompt
- memory_consolidation: annotate cross-persona facts in prompt injection
  (e.g., '(learned as Evil Miku)' when Evil facts appear for Normal Miku)
- Web UI: show persona badge (🎤 Miku / 😈 Evil Miku) on facts and episodic
  memories in the Memory Management tab

This lets both personas know which version of Miku each memory came from,
enabling Evil Miku to distinguish her own memories from Normal Miku's.
2026-05-12 15:12:49 +03:00
9eb081efb1 llama-swap: use pre-built images (:cuda, :rocm) with GPU-specific flags
- Drop custom Dockerfiles; docker-compose uses ghcr.io pre-built images
  which ship llama-swap + llama-server with no pinned versions (always latest)
- NVIDIA GTX 1660 (6GB): add -fit off --no-kv-offload --cache-type-k q4_0 --cache-type-v q4_0
  to fix OOM segfault with new llama.cpp b9014's GPU-side KV cache default
- AMD RX 6800 (16GB): flags unchanged; KV cache stays on GPU for max speed
- Both running llama-swap v211 + llama.cpp b9014 (2026-05-05)
2026-05-05 16:53:34 +03:00
4e28236b06 fix: preserve collapsible subsection state across polling re-renders
- Use stable section IDs (without Date.now()) so collapse state can be
  tracked across re-renders
- Snapshot collapsed state before innerHTML replacement, restore after
- Prevents the 10s polling from expanding all subsections every time
2026-05-02 16:17:26 +03:00
c5e49c73df fix: add cache-busting to prevent stale JS/CSS from breaking the UI
- Added ?v=20260502 query param to all <script src=...> and <link> tags
- Added Cache-Control: no-cache, no-store, must-revalidate to index route
- Added <meta> cache-control tags in HTML head for extra coverage
- This ensures the browser always fetches fresh HTML/JS/CSS after deploy,
  preventing the old loadLastPrompt() from running against new HTML
  (which would crash since #prompt-cat-info no longer exists)
2026-05-02 16:08:47 +03:00
393921e524 fix: add min-height to #prompt-display and placeholder text in clearPromptDisplay()
The empty #prompt-display div collapsed to 0 height, making it appear
'gone'. Added min-height: 3rem and a 'No prompt selected.' placeholder
that clearPromptDisplay() now sets via innerHTML.
2026-05-02 15:55:19 +03:00
2dd32d0ef1 fix: move <pre> outside #prompt-display to prevent innerHTML from destroying it
The renderPromptEntry() function sets innerHTML on #prompt-display, which
was wiping out the child <pre id="last-prompt"> element. This caused
copyPromptToClipboard() to fail silently and the display to appear empty.

Fix: keep <pre> as a hidden sibling outside #prompt-display, used only as
a text buffer for the copy function.
2026-05-02 15:45:54 +03:00
a980b90c0a fix: escape content in buildCollapsibleSection, avoid double-escaping response 2026-05-02 15:27:18 +03:00
6b922d84ae frontend: rewrite Last Prompt as Prompt History viewer
- status.js: replace loadLastPrompt() with loadPromptHistory() + helpers
  - fetch /prompts with optional source filter, populate dropdown
  - selectPromptEntry() renders metadata bar + collapsible subsections
  - parsePromptSections() splits full_prompt into System/Context/Conversation
  - buildCollapsibleSection() with toggle arrows (▼/▶)
  - copyPromptToClipboard() copies raw text
  - toggleMiddleTruncation() truncates response from middle
  - togglePromptHistoryCollapse() collapses entire section
  - legacy loadLastPrompt() delegates to loadPromptHistory()
- core.js: add promptInterval to polling (10s), visibility resume
  - update switchPromptSource() for 'all' filter + new button IDs
  - update initPromptSourceToggle() default to 'all'
  - declare promptInterval variable
2026-05-02 15:25:05 +03:00
f33e2afdf7 frontend: new Prompt History section HTML + CSS
- Replace single <pre> Last Prompt with rich Prompt History viewer
- Add source filter buttons (All/Cat/Fallback), history dropdown selector
- Add metadata bar, copy-to-clipboard button, middle-truncation toggle
- Add collapsible section CSS classes for expandable subsections
2026-05-02 15:19:10 +03:00
87de8f8b3a backend: replace LAST_FULL_PROMPT/LAST_CAT_INTERACTION with unified PROMPT_HISTORY deque
- globals.py: add collections.deque(maxlen=10) PROMPT_HISTORY with _prompt_id_counter
- globals.py: add legacy accessor functions _get_last_fallback_prompt() and _get_last_cat_interaction()
- bot.py: append to PROMPT_HISTORY instead of setting LAST_CAT_INTERACTION, remove 500-char truncation, add guild/channel/model fields
- image_handling.py: same pattern for Cat media responses
- llm.py: append fallback prompts to PROMPT_HISTORY with response filled after LLM reply
- routes/core.py: new GET /prompts and GET /prompts/{id} endpoints, legacy /prompt and /prompt/cat use accessor functions
2026-05-02 15:17:15 +03:00
2d0c80b7ef fix: prevent infinite dialogue loops + make Evil Miku actually engage
- Question override now decays after 6 turns: after turn 6, the LLM's own
  [CONTINUE] signal is respected even when questions are asked. This prevents
  infinite question-ping-pong where both personas keep asking questions.
- _parse_response now accepts turn_count parameter; generate_response_with_continuation
  and handle_dialogue_turn pass it through.
- Rewrote Evil Miku's conversation-mode overlay with explicit CRITICAL RULES:
  ANSWER questions, engage with what she says, ask questions too, don't just
  repeat dismissive one-liners. The old overlay said 'be playful-cruel' but
  didn't actually tell her to participate in the conversation.
2026-04-30 15:39:53 +03:00
17842f24d4 fix: remove broken personality snippet system — now redundant
The snippet loader used wrong file paths (/app/cat/data/ instead of persona/)
causing 'Loaded 0 personality snippets' for both personas. Since the previous
commit now injects full system prompts (get_miku_system_prompt_compact and
get_evil_system_prompt) into every argument exchange, the snippet system is
redundant — all lore/lyrics/personality are already provided by the system prompts.
2026-04-30 15:16:43 +03:00
4e064ad89b fix: import is_persona_dialogue_active from correct module
Was importing from utils.bipolar_mode instead of utils.persona_dialogue
2026-04-30 15:10:13 +03:00
97c7133fdc fix: both personas now use full system prompts in arguments and dialogues
Created get_miku_system_prompt() and get_miku_system_prompt_compact() in
context_manager.py — mirrors get_evil_system_prompt() so both personas have
equally rich prompts with lore, lyrics, mood integration, and personality.

Previously only Evil Miku had a proper system prompt function. Regular Miku's
arguments and dialogues used a bare-bones hardcoded prompt with no lore/lyrics
— making arguments feel flat compared to normal conversation.

Changes:
- context_manager.py: added get_miku_system_prompt() (full) and
  get_miku_system_prompt_compact() (lore+personality, no lyrics for tokens)
- bipolar_mode.py: both argument prompt functions now accept system_prompt
  param; run_argument() builds miku_system and evil_system once and passes
  them to every exchange
- persona_dialogue.py: dialogue prompts now use get_miku_system_prompt_compact()
  instead of hardcoded stub, matching Evil Miku's full prompt approach
- Removed redundant hardcoded personality text from argument prompts since
  the system prompts now provide it
2026-04-30 15:07:55 +03:00
7d5881ebe7 fix: inject argument topic into EVERY exchange, not just the first message
The topic was only being injected into the initial breakthrough message via
get_argument_start_prompt(). After that, every subsequent exchange called
get_miku_argument_prompt() / get_evil_argument_prompt() which had no concept
of the topic — so both personas forgot what they were arguing about after the
first exchange and reverted to generic identity-crisis arguments.

Fix: added argument_topic parameter to both persona prompt functions and inject
it as a bold ARGUMENT THEME reminder in every single exchange. The topic block
explicitly tells the LLM to stay on-topic and not drift into generic territory.
2026-04-30 12:57:48 +03:00
e6c818f647 fix: merge context + topic into single field — one clear purpose
- Removed separate 'topic' field from BipolarTriggerRequest model
- Removed topic parameter from force_trigger_argument, force_trigger_argument_from_message_id, and run_argument
- trigger_context now doubles as the argument theme: if provided by user, it becomes the topic;
  if blank, a random topic is selected from the rotation pool
- Web UI: replaced two confusing fields (Context + Topic) with one clear field labeled
  'What should they argue about? (optional)' with a plain-English description
- JS: removed topic field reference, context.trim() ensures empty strings aren't sent
2026-04-30 12:30:49 +03:00
846557fa96 feat: add optional custom argument topic override via Web UI
- Added optional 'topic' field to BipolarTriggerRequest model
- Added topic parameter to force_trigger_argument and force_trigger_argument_from_message_id
- Updated run_argument to accept optional custom topic (None=random, ''=no topic, str=custom)
- Added topic input field to Web UI trigger-argument section
- Updated JS to send topic in API request body
- Custom topics bypass the random rotation system, allowing manual theme control
2026-04-30 12:07:28 +03:00
98fca53066 Phase 3: Polish & immersion — mood-aware arguments, personality snippets, parting shots
- Added mood-specific argument behavioral guidance: 9 moods for Evil Miku, 9 for Miku
  Each mood changes argument style (e.g. cunning=chess moves, manic=chaotic, bubbly=playful deflections)
- Added personality snippet injection from Cat plugin lore/lyrics data files
  40% chance per prompt to include a random lore/lyric snippet for unique material
- Added parting shot feature: 20% chance the LOSER gets a bitter final line before the winner's victory
  Adds dramatic tension and prevents clean-win monotony
- Mood guidance and personality flavor injected into both argument prompts
2026-04-30 11:50:37 +03:00
a52b36135f Phase 2: Fix triggers & dialogue — per-channel cooldowns, tension rebalance, user-message triggers
- Changed cooldown from global (ALL channels blocked) to per-channel dict keyed by channel_id
- Added conversation streak tracker: 3 near-miss interjection scores in a row force a dialogue trigger
- Expanded topic relevance keywords: added enthusiasm/vulnerability for Evil Miku, provocation/dismissal for Miku
- Lowered keyword divisor from /3.0 to /2.0 for higher base trigger scores
- Tension rebalance: added natural decay (-0.03/turn), reduced escalation weight (0.08->0.05), increased de-escalation weight (0.06->0.08)
- Reduced momentum multiplier (1.2->1.1) and intensity multiplier (1.3->1.2)
- Added spike cooldown: if last turn tension delta >0.15, next delta halved (prevents runaway spirals)
- Added user-message interjection check in bot.py on_message() (was only checking bot's own messages)
- Added random 15% argument trigger roll on user messages in normal message flow (was only from autonomous.py)
2026-04-30 11:45:13 +03:00
7a4122fd02 Phase 1: Argument system overhaul — arbiter, memory, topics, stats
- Changed arbiter LLM from llama3.1 to darkidol (uncensored, unbiased)
- Rewrote arbiter criteria to judge debate skill equally
- Added argument history injection (last 6 exchanges) to prevent repetition
- Added dynamic topic rotation system (11 weighted topics) with per-channel history
- Added keyword-based argument stats tracking (wit/composure/impact) fed to arbiter
- Removed hardcoded suggestion lists from prompts
2026-04-30 11:37:33 +03:00
20891179ee fix(twitter): update twscrape monkey patch for JS bundle format change
Twitter changed the JS bundle structure from the old single-map format
(e=>e+"."+{...}[e]+"a.js") to a new two-map format
(u.u=e=>""+(({name})[e]||e)+"."+({hash})[e]+"a.js"), breaking
x-client-transaction-id generation.

This caused IndexError: list index out of range, which twscrape
interpreted as an account timeout (15-min lockout), preventing Miku
from fetching/sharing tweets.

The fix adds:
- A robust multi-pattern parser that tries known formats in order
- The _js_obj_to_dict helper from PR #303 for handling unquoted numeric
  keys and scientific notation in JS object literals
- Debug logging to capture the JS snippet when ALL patterns fail,
  making future breakage easier to diagnose

References:
- https://github.com/vladkens/twscrape/issues/302
- https://github.com/vladkens/twscrape/pull/303
2026-04-29 21:32:27 +03:00
694590a620 refactor: Modularize monolithic HTML control panel into organized components
This commit completes a major refactoring of the Miku control panel from a single 7,191-line monolithic HTML file to a modern modular architecture:

CHANGES:
- Extracted 872 lines of CSS into css/style.css
- Created 10 specialized JavaScript modules (4,964 lines total):
  * core.js: Global state, utilities, initialization, polling system
  * servers.js: Server management and mood handling
  * modes.js: Evil mode, GPU selection, bipolar mode, scoreboard
  * actions.js: Autonomous/manual actions, custom prompts, reactions
  * image-gen.js: Image generation system
  * status.js: Status display and statistics
  * dm.js: DM user management and conversation analysis
  * chat.js: LLM chat interface with streaming and voice calls
  * memories.js: Cheshire Cat memory integration (episodic/declarative/procedural)
  * profile.js: Profile picture, album gallery, activities editor
- Cleaned index.html to 1,351 lines (structure only, zero inline JS/CSS)
- Removed 12 duplicate variable declarations
- Maintained strict script load order for dependency resolution
- Added backup comment to index.html.bak for historical reference

VERIFICATION COMPLETED:
✓ All 191 functions/variables from original accounted for
✓ Cross-referenced with backup to ensure nothing lost
✓ All onclick handlers and modal systems validated
✓ No circular dependencies or broken references
✓ HTML structure integrity verified (11 tabs, all buttons/modals intact)
✓ CropperJS CDN links preserved

The refactored code is production-ready with improved maintainability and clear separation of concerns.
2026-04-29 20:56:49 +03:00
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