chore: nettoyage .gitignore - retrait venv, __pycache__, .env, data, logs, modèles du tracking

This commit is contained in:
Lowei 2026-06-16 20:40:37 +02:00
parent 7333a22bcd
commit 604a5affe0
9231 changed files with 1244 additions and 1601201 deletions

15
.env
View file

@ -1,15 +0,0 @@
DISCORD_TOKEN=MTM3MDcxNjU1MTQ4NTA2NzMwNA.GJHUjY.YrzGaD2yXj0-UwYZ0ut7RA_tSeI5ji1nD5iLTY
GUILD_ID=1370716551485067304
# --- CONFIGURATION BÊTA ---
# IDs des Administrateurs (Séparés par des virgules)
ADMIN_IDS=971446412690722826,1324439906046574693
# IDs du Staff (Assets Primaires), séparés par des virgules
PRIMARY_ASSETS_IDS=971446412690722826
# Channel d'Introspection (Log Stream de Bêta)
INTROSPECTION_CHANNEL_ID=1461107327381016629
# Modèle Ollama (local, pas de clé API nécessaire)
OLLAMA_MODEL=gpt-oss:20b
OLLAMA_BASE_URL=http://localhost:11434

75
.gitignore vendored Normal file
View file

@ -0,0 +1,75 @@
# ============================
# Environment & Secrets
# ============================
.env
.env.*
!.env.example
# ============================
# Python
# ============================
__pycache__/
*.py[cod]
*$py.class
*.so
*.egg-info/
*.egg
dist/
build/
*.whl
# ============================
# Virtual Environment
# ============================
venv/
.venv/
env/
ENV/
# ============================
# IDE / Editor
# ============================
.vscode/
.idea/
*.swp
*.swo
*~
.DS_Store
Thumbs.db
# ============================
# Logs
# ============================
*.log
logs/
# ============================
# Data (runtime / dynamic)
# ============================
data/
*.db
*.sqlite
*.sqlite3
# ============================
# AI Models (fichiers volumineux)
# ============================
model/
*.gguf
*.bin
*.onnx
*.pt
*.pth
*.h5
# ============================
# OS
# ============================
.DS_Store
Thumbs.db
desktop.ini
# ============================
# Ollama
# ============================
Modelfile

View file

@ -1,16 +0,0 @@
FROM ../model/gpt-oss-20b-mxfp4.gguf
# Set the context size (32k like we wanted)
PARAMETER num_ctx 32768
PARAMETER temperature 0.5
PARAMETER repeat_penalty 1.35
PARAMETER stop "<|eot_id|>"
PARAMETER stop "<|end_of_text|>"
TEMPLATE """<|begin_of_text|><|start_header_id|>system<|end_header_id|>
{{ .System }}<|eot_id|><|start_header_id|>user<|end_header_id|>
{{ .Prompt }}<|eot_id|><|start_header_id|>assistant<|end_header_id|>
"""

206
README.md
View file

@ -7,7 +7,7 @@ Bot Discord IA d'assistance et de modération avancée, utilisant **Ollama** pou
- **IA conversationnelle** : Répond aux questions et exécute des actions sur Discord via JSON - **IA conversationnelle** : Répond aux questions et exécute des actions sur Discord via JSON
- **Modération silencieuse** : Scan de toxicité de chaque message via LLM en arrière-plan - **Modération silencieuse** : Scan de toxicité de chaque message via LLM en arrière-plan
- **Gestion vocale** : Transcription Whisper en temps réel + détection de mots-clés - **Gestion vocale** : Transcription Whisper en temps réel + détection de mots-clés
- **Mémoire utilisateur** : Historique par utilisateur avec résumé automatique - **Mémoire utilisateur** : Historique par utilisateur avec résumé automatique (via LLM)
- **Dispatch d'insights** : Coordination staff avec boutons d'acceptation Discord - **Dispatch d'insights** : Coordination staff avec boutons d'acceptation Discord
- **RGPD** : Purge automatique des données > 30 jours - **RGPD** : Purge automatique des données > 30 jours
@ -15,7 +15,7 @@ Bot Discord IA d'assistance et de modération avancée, utilisant **Ollama** pou
### Prérequis ### Prérequis
- Python 3.9+ - Python 3.10+
- **Ollama** installé et en cours d'exécution (https://ollama.com) - **Ollama** installé et en cours d'exécution (https://ollama.com)
- Modèle `gpt-oss:20b` disponible dans Ollama - Modèle `gpt-oss:20b` disponible dans Ollama
@ -41,8 +41,9 @@ Créer un fichier `.env` à la racine du dossier `beta/` :
DISCORD_TOKEN=your_discord_bot_token DISCORD_TOKEN=your_discord_bot_token
GUILD_ID=your_guild_id GUILD_ID=your_guild_id
# LLM # LLM (Ollama)
OLLAMA_MODEL=gpt-oss:20b OLLAMA_MODEL=gpt-oss:20b
OLLAMA_BASE_URL=http://localhost:11434
# Staff # Staff
ADMIN_IDS=123456789,987654321 ADMIN_IDS=123456789,987654321
@ -71,170 +72,89 @@ Le bot va :
beta/ beta/
├── main.py # Point d'entrée ├── main.py # Point d'entrée
├── core/ ├── core/
│ ├── bot.py # Classe principale Superviseur │ ├── bot.py # Classe Beta (événements, orchestration)
│ ├── llm.py # Gestionnaire LLM (Ollama API) │ ├── llm.py # Gestionnaire LLM (Ollama API)
│ ├── action_router.py # Routage des actions IA → Discord │ ├── action_executor.py # Routage des actions IA → Discord
│ ├── action_router.py # (legacy, redirige vers action_executor)
│ ├── response_handler.py # Nettoyage, parsing JSON, envoi réponses
│ ├── log_parser.py # Parsing des logs d'actions
│ ├── content.py # Préparation du contenu pour le LLM
│ ├── context_builder.py # Construction du contexte serveur
│ ├── dispatcher.py # Dispatch d'insights vers staff │ ├── dispatcher.py # Dispatch d'insights vers staff
│ ├── voice.py # Gestion vocale + Whisper │ ├── voice.py # Gestion vocale + Whisper
│ ├── tasks.py # Tâches de fond │ ├── task_manager.py # Tâches de fond
│ ├── messaging.py # Envoi de messages │ ├── messaging.py # Envoi de messages
│ └── permissions.py # Whitelist et permissions │ └── permissions.py # Whitelist et permissions
├── brain/ ├── brain/
│ ├── memoire.py # Mémoire utilisateur (JSON/Redis) │ ├── memory.py # Mémoire utilisateur (wrapper)
│ ├── memoire.py # Mémoire utilisateur (implémentation, JSON/Redis + cache)
│ ├── moderation.py # Modération IA + SQLite │ ├── moderation.py # Modération IA + SQLite
│ └── infos_serveurs.py # Infos serveur │ └── infos_serveurs.py # Infos serveur
├── commandes/ ├── commandes/
│ ├── security/ # kick, ban, mute, warn, purge... │ ├── security/ # kick, ban, mute, warn, purge...
│ ├── salons/ # Créer, supprimer, modifier salons │ ├── salons/ # Créer, supprimer, modifier salons
│ ├── roles/ # Créer, supprimer, modifier rôles │ ├── roles/ # Créer, supprimer, modifier rôles
│ ├── categories/ # Créer, supprimer, modifier catégories
│ └── autres/ # Config, status, ping, etc. │ └── autres/ # Config, status, ping, etc.
├── data/ # Logs, DB SQLite ├── data/ # Logs, DB SQLite
└── memoires/ # Historique utilisateur (JSON) └── memoires/ # Historique utilisateur (JSON, par guild)
``` ```
## Actions IA ## Actions IA
Le bot peut exécuter les actions suivantes via LLM : Le bot peut exécuter les actions suivantes via LLM :
- `CREATE_CHANNEL`, `DELETE_CHANNEL`, `MODIFY_CHANNEL` - `CREATE_CHANNEL`, `DELETE_CHANNEL`, `MODIFY_CHANNEL`, `RENAME_CHANNEL`, `MOVE_CHANNEL`
- `CREATE_ROLE`, `DELETE_ROLE`, `ADD_ROLE_TO_USER` - `CREATE_ROLE`, `DELETE_ROLE`, `ADD_ROLE_TO_USER`, `REMOVE_ROLE_FROM_USER`
- `KICK`, `BAN`, `UNBAN`, `MUTE`, `TIMEOUT`, `WARN`, `PURGE` - `CREATE_CATEGORY`, `DELETE_CATEGORY`, `MODIFY_CATEGORY`, `RENAME_CATEGORY`
- `KICK`, `BAN`, `UNBAN`, `MUTE`, `UNMUTE`, `TIMEOUT`, `WARN`, `PURGE`
- `JOIN_VOICE`, `LEAVE_VOICE` - `JOIN_VOICE`, `LEAVE_VOICE`
- `ALERT`, `INSIGHT`, `FORGET_USER`, `READ_LOGS` - `ALERT`, `INSIGHT`, `FORGET_USER`, `READ_LOGS`
- `SEND_MESSAGE`, `HELP`, `STATUS`
## Variables d'environnement
| Variable | Description | Défaut |
|----------|-------------|--------|
| `DISCORD_TOKEN` | Token du bot Discord | *requis* |
| `GUILD_ID` | ID du serveur principal | *requis* |
| `OLLAMA_MODEL` | Nom du modèle Ollama | `gpt-oss:20b` |
| `OLLAMA_BASE_URL` | URL de l'API Ollama | `http://localhost:11434` |
| `ADMIN_IDS` | IDs des administrateurs (virgule) | — |
| `PRIMARY_ASSETS_IDS` | IDs du staff (virgule) | — |
| `INTROSPECTION_CHANNEL_ID` | Channel de logs internes | — |
| `TIMEZONE` | Fuseau horaire | `Europe/Paris` |
| `SUPERVISEUR_MAX_CONCURRENT` | Requêtes LLM simultanées max | `4` |
| `SUPERVISEUR_MEM_DIR` | Répertoire des mémoires | `memoires` |
| `SUPERVISEUR_MAX_RECENT` | Messages avant résumé | `20` |
| `SUPERVISEUR_REDIS_URL` | URL Redis (optionnel) | — |
| `LOG_LEVEL` | Niveau de log | `INFO` |
## Tests
```bash
# Tests unitaires
pytest test_bot.py -v
# Tests mémoire
python -m brain.memoire
```
## Monitoring
Le bot inclut des métriques intégrées :
- Nombre de requêtes LLM (succès/échecs)
- Temps de réponse moyen
- Métriques mémoire (hits/misses cache, lectures/écritures)
## Sécurité
- **Local uniquement** : Pas d'appels réseau externes
- **RGPD** : Purge automatique des données > 30 jours
- **Permissions** : Vérification admin pour les actions destructrices
- **Whitelist** : Système de niveaux de permissions
## Support ## Support
1. Vérifier les logs dans `data/bot.log` 1. Vérifier les logs dans `data/bot.log`
2. Activer le mode debug : `LOG_LEVEL=DEBUG` 2. Activer le mode debug : `LOG_LEVEL=DEBUG`
3. Vérifier qu'Ollama est actif : `ollama list` 3. Vérifier qu'Ollama est actif : `ollama list`
python test_llama_integration.py
```
This will test:
- Model loading
- LLMManager functionality
- Text generation
- JSON extraction
- Performance
## Performance
### Expected Performance
With the 120B model and optimized parameters:
- **Context**: 4096 tokens
- **Threads**: 16 CPU threads
- **Memory**: ~64GB RAM usage
- **Response time**: 5-15 seconds for typical queries
- **Concurrency**: 4 simultaneous requests
### Performance Tips
1. **Use mlock**: Prevents swapping to disk
2. **Optimize threads**: Match your CPU core count
3. **Monitor memory**: Ensure sufficient RAM
4. **Batch size**: Adjust based on your system
### Monitoring
The bot includes built-in metrics:
- LLM request counts
- Success/failure rates
- Response times
- Memory usage
Enable metrics server with `METRICS_ENABLED=1`.
## Troubleshooting
### Common Issues
1. **Model not found**
- Check `MODEL_PATH` in `.env`
- Verify model file exists
- Check file permissions
2. **Memory errors**
- Ensure sufficient RAM
- Reduce `n_ctx` or `n_threads`
- Disable `use_mlock` for testing
3. **Slow responses**
- Check CPU usage
- Verify model is in RAM
- Reduce `n_threads` if CPU is overloaded
4. **Import errors**
- Install `llama-cpp-python` with proper backend
- Check Python version compatibility
### Debug Mode
Enable verbose logging by setting:
```bash
LOG_LEVEL=DEBUG
```
### Model Loading Issues
If model loading fails:
1. Check model file integrity
2. Verify GGUF format compatibility
3. Try with `use_mlock=False`
4. Reduce model size for testing
## Migration Notes
### From Ollama
If you were previously using Ollama:
1. **Remove Ollama**: No longer needed
2. **Update configuration**: Remove Ollama URLs
3. **Install llama-cpp-python**: Add to requirements
4. **Test thoroughly**: Verify all functionality
### Code Changes
Key files modified:
- `beta.py`: Main entry point with model loading
- `superviseur/llm.py`: LLMManager with llama-cpp-python
- `superviseur/bot.py`: Bot integration
- `requirements.txt`: Updated dependencies
## Security
### Data Privacy
- No external network calls
- All processing local
- GDPR compliance maintained
- Memory management for sensitive data
### Model Security
- Local model storage
- No external dependencies
- Controlled access
- Proper cleanup
## Support
For issues or questions:
1. Check the troubleshooting section
2. Run the test script
3. Enable debug logging
4. Check system resources
5. Verify model compatibility
## Contributing
When contributing to this project:
1. Test with the test script
2. Verify performance
3. Update documentation
4. Follow existing code patterns
5. Ensure backward compatibility
## License
This project is licensed under the same license as the original Superviseur bot.

Binary file not shown.

Binary file not shown.

View file

@ -253,17 +253,72 @@ async def save_user_memory_async(user_id: int | str, data: Dict[str, Any], guild
logger.debug(f"Mémoire marquée dirty pour utilisateur {user_id} (guild {guild_id})") logger.debug(f"Mémoire marquée dirty pour utilisateur {user_id} (guild {guild_id})")
# Référence globale au LLM manager (set par le bot au démarrage)
_llm_manager = None
def set_llm_manager(llm_manager) -> None:
"""Enregistre le LLM manager pour les résumés."""
global _llm_manager
_llm_manager = llm_manager
async def summarize_messages(messages: List[Dict[str, Any]]) -> str: async def summarize_messages(messages: List[Dict[str, Any]]) -> str:
""" """
Fonction de résumé améliorée. Utilise une extraction de mots-clés et de phrases clés Résume une liste de messages via le LLM.
pour un résumé plus intelligent. Placeholder pour intégration IA future. Fallback sur l'extraction de mots-clés si le LLM n'est pas disponible.
Cette fonction est `async` pour faciliter un remplacement par un appel réseau/IA.
""" """
if not messages: if not messages:
return "" return ""
# Extraire tous les textes utilisateur et bot # Construire le transcript à résumer
transcript_lines = []
for m in messages:
um = m.get("user_message", "").strip()
bm = m.get("bot_message", "").strip()
if um:
transcript_lines.append(f"Utilisateur: {um}")
if bm:
transcript_lines.append(f"Bot: {bm}")
transcript = "\n".join(transcript_lines)
# Tronquer le transcript pour éviter les appels trop longs
if len(transcript) > 4000:
transcript = transcript[-4000:]
# Essayer d'utiliser le LLM pour un vrai résumé
if _llm_manager is not None:
try:
prompt = (
"Tu es un assistant spécialisé dans la synthèse de conversations.\n"
"Résume les échanges suivants de manière concise et factuelle.\n"
"Extrais les sujets abordés, les décisions prises, et les informations importantes.\n"
"Le résumé ne doit PAS dépasser 500 caractères.\n\n"
f"CONVERSATION À RÉSUMER :\n{transcript}\n\n"
"RÉSUMÉ :"
)
payload = _llm_manager.build_payload(
prompt=prompt,
system_prompt="Tu es un assistant de synthèse. Sois concis et factuel.",
force_json=False
)
summary = await _llm_manager.call_llama(payload)
if summary and len(summary.strip()) > 10:
# Nettoyer le résumé
summary = summary.strip()
if len(summary) > 1500:
summary = summary[:1497] + "..."
return summary
except Exception as e:
logger.warning(f"Erreur résumé LLM, fallback extractif: {e}")
# Fallback : extraction de mots-clés et phrases clés
return _extractive_summary(messages)
def _extractive_summary(messages: List[Dict[str, Any]]) -> str:
"""Résumé extractif basé sur les mots-clés et phrases clés (fallback)."""
all_texts = [] all_texts = []
for m in messages: for m in messages:
um = m.get("user_message", "") um = m.get("user_message", "")
@ -272,7 +327,7 @@ async def summarize_messages(messages: List[Dict[str, Any]]) -> str:
combined_text = " ".join(all_texts) combined_text = " ".join(all_texts)
# Extraction simple de mots-clés (mots fréquents, >3 chars) # Extraction de mots-clés (mots fréquents, >3 chars)
words = re.findall(r'\b\w{4,}\b', combined_text.lower()) words = re.findall(r'\b\w{4,}\b', combined_text.lower())
word_freq = {} word_freq = {}
for word in words: for word in words:
@ -282,28 +337,25 @@ async def summarize_messages(messages: List[Dict[str, Any]]) -> str:
top_keywords = sorted(word_freq.items(), key=lambda x: x[1], reverse=True)[:10] top_keywords = sorted(word_freq.items(), key=lambda x: x[1], reverse=True)[:10]
keywords_str = ", ".join([kw for kw, _ in top_keywords]) keywords_str = ", ".join([kw for kw, _ in top_keywords])
# Extraire phrases clés (premières phrases des messages importants) # Extraire phrases clés
key_phrases = [] key_phrases = []
for m in messages: for m in messages:
um = m.get("user_message", "").strip() um = m.get("user_message", "").strip()
bm = m.get("bot_message", "").strip() bm = m.get("bot_message", "").strip()
if um: if um:
sentences = re.split(r'[.!?]', um)[:1] # première phrase sentences = re.split(r'[.!?]', um)[:1]
key_phrases.extend(sentences) key_phrases.extend(sentences)
if bm: if bm:
sentences = re.split(r'[.!?]', bm)[:1] sentences = re.split(r'[.!?]', bm)[:1]
key_phrases.extend(sentences) key_phrases.extend(sentences)
phrases_str = " | ".join(key_phrases[:5]) # limiter à 5 phrases phrases_str = " | ".join(key_phrases[:5])
# Construire le résumé
summary = f"Mots-clés: {keywords_str}\nPhrases clés: {phrases_str}" summary = f"Mots-clés: {keywords_str}\nPhrases clés: {phrases_str}"
# Tronquer si trop long
if len(summary) > 1500: if len(summary) > 1500:
summary = summary[:1497] + "..." summary = summary[:1497] + "..."
await asyncio.sleep(0) # point d'await pour remplacer par un vrai appel
return summary return summary
@ -444,13 +496,33 @@ def list_memory_files() -> List[str]:
return files return files
def delete_user_memory(user_id: int | str) -> None: def delete_user_memory(user_id: int | str, guild_id: str | None = None) -> None:
path = _user_filepath(user_id) """Supprime la mémoire d'un utilisateur, optionnellement pour un guild spécifique."""
path = _user_filepath(user_id, guild_id)
try: try:
os.remove(path) os.remove(path)
except FileNotFoundError: except FileNotFoundError:
pass pass
# Si aucun guild_id spécifique, supprimer aussi tous les fichiers de mémoire pour cet utilisateur
if guild_id is None:
_ensure_memory_dir()
user_str = str(user_id)
for root, dirs, files in os.walk(MEMORY_DIR):
for name in files:
if name == f"{user_str}.json" or name == f"{user_id}.json":
full_path = os.path.join(root, name)
if full_path != path: # Éviter double suppression
try:
os.remove(full_path)
except FileNotFoundError:
pass
# Nettoyer le cache
key_no_guild = f"_{user_id}" if guild_id is None else f"{guild_id}_{user_id}"
memory_cache.pop(key_no_guild, None)
_dirty.pop(key_no_guild, None)
# Fonction pour obtenir les métriques # Fonction pour obtenir les métriques
def get_metrics() -> Dict[str, Any]: def get_metrics() -> Dict[str, Any]:

16
brain/memory.py Normal file
View file

@ -0,0 +1,16 @@
"""User memory management - JSON/Redis with in-memory cache."""
# Renamed from memoire.py for consistency (English naming)
from .memoire import ( # noqa: F401
add_interaction,
build_context_for_model,
flush_all,
MEMORY_DIR,
set_llm_manager,
delete_user_memory,
purge_old_memories,
get_metrics,
_extractive_summary,
load_user_memory_async,
save_user_memory_async,
)

View file

@ -3,7 +3,7 @@ import datetime
import logging import logging
from typing import Optional from typing import Optional
logger = logging.getLogger('Superviseur') logger = logging.getLogger('Beta')
DB_PATH = 'data/mod.db' DB_PATH = 'data/mod.db'

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

7
core/action_executor.py Normal file
View file

@ -0,0 +1,7 @@
"""Action execution - routes AI actions to Discord commands."""
# Renamed from action_router.py for clarity
from .action_router import ActionExecutor # noqa: F401
# Backward compatibility
ActionRouter = ActionExecutor

View file

@ -2,16 +2,16 @@ import discord
import logging import logging
from typing import Dict from typing import Dict
logger = logging.getLogger('Superviseur') logger = logging.getLogger('Beta')
class ActionRouter: class ActionExecutor:
"""Routs generated AI semantic actions to the physical Discord moderative or administrative commands.""" """Routes generated AI semantic actions to Discord administrative commands."""
def __init__(self, bot): def __init__(self, bot):
self.bot = bot self.bot = bot
self._module_cache = {} self._module_cache = {}
async def execute_action(self, action: str, params: Dict, message: discord.Message) -> (bool, str): async def execute(self, action: str, params: Dict, message: discord.Message) -> (bool, str):
"""Execute an action from LLM response.""" """Execute an action from LLM response."""
# Normalize action name: replace spaces with underscores, uppercase # Normalize action name: replace spaces with underscores, uppercase
@ -82,8 +82,12 @@ class ActionRouter:
# --- PRIVACY ACTIONS --- # --- PRIVACY ACTIONS ---
if action == 'FORGET_USER': if action == 'FORGET_USER':
from brain.memoire import delete_user_memory from brain.memory import delete_user_memory
delete_user_memory(message.author.id) guild_name = message.guild.name if message.guild else None
if guild_name:
from .utils import sanitize_guild_name
guild_name = sanitize_guild_name(guild_name)
delete_user_memory(message.author.id, guild_name)
return True, None return True, None
# --- PERMISSION ENFORCEMENT (ADMIN ONLY) --- # --- PERMISSION ENFORCEMENT (ADMIN ONLY) ---

View file

@ -1,4 +1,4 @@
"""Main Superviseur Bot class - refactored for better organization.""" """Main Beta Bot class - orchestrates all components."""
import discord import discord
from discord.ext import commands from discord.ext import commands
@ -9,29 +9,36 @@ import time
import json import json
import logging import logging
import re import re
import datetime
import pytz
from typing import Dict, Optional from typing import Dict, Optional
from .utils import format_mentions_in_text, sanitize_guild_name from .utils import format_mentions_in_text, sanitize_guild_name
from .permissions import WhitelistManager, check_is_admin, get_permissions_info from .permissions import WhitelistManager, check_is_admin, get_permissions_info
from .messaging import MessagingManager from .messaging import MessagingManager
from .context import build_context_for_message from .context_builder import build_context_for_message
from .llm import LLMManager from .llm import LLMManager
from .redis_worker import RedisWorker from .redis_worker import RedisWorker
from .commands import setup_commands from .commands import setup_commands
from .voice import VoiceManager from .voice import VoiceManager
from .dispatcher import AssetDispatcher from .dispatcher import AssetDispatcher
from .tasks import TaskManager from .task_manager import TaskManager
from .action_router import ActionRouter from .action_executor import ActionExecutor
import datetime from .response_handler import clean_response_text, parse_json_actions
import pytz from .log_parser import get_recent_logs
from .content import prepare_message_content
from brain.memoire import add_interaction, build_context_for_model, flush_all, MEMORY_DIR from brain.memory import add_interaction, build_context_for_model, flush_all, MEMORY_DIR, set_llm_manager
from brain.moderation import init_db, log_infraction, scan_message_toxicity from brain.moderation import init_db, log_infraction, scan_message_toxicity
from commandes.autres.config import config from commandes.autres.config import config
from commandes.autres.logger import action_logger from commandes.autres.logger import action_logger
logger = logging.getLogger('Beta')
class MockMessage: class MockMessage:
"""Mock discord.Message for voice-triggered interactions.""" """Mock discord.Message for voice-triggered interactions."""
def __init__(self, author, channel, content, guild=None): def __init__(self, author, channel, content, guild=None):
self.author = author self.author = author
self.channel = channel self.channel = channel
@ -44,17 +51,13 @@ class MockMessage:
self.jump_url = "" self.jump_url = ""
async def reply(self, content, **kwargs): async def reply(self, content, **kwargs):
"""Mock reply - sends a message to the channel/user instead."""
# For vocal interaction, we don't need a prefix in DM as it's a private chat
is_dm = isinstance(self.channel, (discord.DMChannel, discord.User, discord.Member)) is_dm = isinstance(self.channel, (discord.DMChannel, discord.User, discord.Member))
prefix = "" if is_dm else f"**{self.author.display_name}** (Vocal) : " prefix = "" if is_dm else f"**{self.author.display_name}** (Vocal) : "
return await self.channel.send(f"{prefix}{content}", **kwargs) return await self.channel.send(f"{prefix}{content}", **kwargs)
logger = logging.getLogger('Superviseur')
class Bot(commands.Bot):
class Superviseur(commands.Bot): """Main bot class for Beta Discord bot."""
"""Main bot class for Superviseur Discord bot."""
def __init__( def __init__(
self, self,
@ -64,7 +67,8 @@ class Superviseur(commands.Bot):
command_prefix: str, command_prefix: str,
intents: discord.Intents, intents: discord.Intents,
temperature: float = 0.4, temperature: float = 0.4,
model_loaded: bool = True model_loaded: bool = True,
favorite_ids: list = None
): ):
super().__init__(command_prefix=command_prefix, intents=intents) super().__init__(command_prefix=command_prefix, intents=intents)
@ -74,9 +78,10 @@ class Superviseur(commands.Bot):
self.system_prompt = system_prompt self.system_prompt = system_prompt
self.temperature = temperature self.temperature = temperature
self.model_loaded = model_loaded self.model_loaded = model_loaded
self.favorite_ids = set(favorite_ids or [])
# Identity # Identity
self.system_name = "Bêta" self.system_name = "B\u00eata"
admins_env = os.getenv("ADMIN_IDS", "") admins_env = os.getenv("ADMIN_IDS", "")
self.admin_ids = [int(i.strip()) for i in admins_env.split(",") if i.strip().isdigit()] self.admin_ids = [int(i.strip()) for i in admins_env.split(",") if i.strip().isdigit()]
self.introspection_channel_id = int(os.getenv("INTROSPECTION_CHANNEL_ID", 0)) self.introspection_channel_id = int(os.getenv("INTROSPECTION_CHANNEL_ID", 0))
@ -86,10 +91,10 @@ class Superviseur(commands.Bot):
self.asset_ids = [int(i.strip()) for i in assets_env.split(",") if i.strip().isdigit()] self.asset_ids = [int(i.strip()) for i in assets_env.split(",") if i.strip().isdigit()]
# Spam Prevention # Spam Prevention
self.insight_cooldowns = {} # {user_id: timestamp} self.insight_cooldowns = {}
self.insight_cooldown_duration = 1800 # 30 minutes self.insight_cooldown_duration = 1800
self.insight_threshold = 5 self.insight_threshold = 5
self.ignored_words = {"salut", "cc", "hello", "ça va", "ca va", "bjr", "slt", "yo", "re", "ok"} self.ignored_words = {"salut", "cc", "hello", "\u00e7a va", "ca va", "bjr", "slt", "yo", "re", "ok"}
self.monitoring_cooldowns = {} self.monitoring_cooldowns = {}
# Caches # Caches
@ -117,34 +122,23 @@ class Superviseur(commands.Bot):
try: try:
import redis.asyncio as aioredis import redis.asyncio as aioredis
self.aioredis = aioredis self.aioredis = aioredis
except Exception: except ImportError:
pass pass
self.redis_worker: Optional[RedisWorker] = None self.redis_worker: Optional[RedisWorker] = None
# Whitelist # Sub-managers
self.whitelist_manager = WhitelistManager() self.whitelist_manager = WhitelistManager()
# Messaging
self.messaging = MessagingManager(self) self.messaging = MessagingManager(self)
# Disable command prefix for natural language focus
# command_prefix is set by the constructor argument (e.g. "!")
# Voice Management
self.voice_manager = VoiceManager(self) self.voice_manager = VoiceManager(self)
# LLM
self.llm = llama_model self.llm = llama_model
# Dispatcher & Sub-managers
self.dispatcher = AssetDispatcher(self) self.dispatcher = AssetDispatcher(self)
self.task_manager = TaskManager(self) self.task_manager = TaskManager(self)
self.action_router = ActionRouter(self) self.action_executor = ActionExecutor(self)
# Init SQLite Moderation DB # Init SQLite Moderation DB
init_db() init_db()
# Logging avec fuseau horaire local (Europe/Paris) # Logging with local timezone
self.timezone = pytz.timezone(os.getenv("TIMEZONE", "Europe/Paris")) self.timezone = pytz.timezone(os.getenv("TIMEZONE", "Europe/Paris"))
def local_time_converter(*args): def local_time_converter(*args):
@ -152,25 +146,24 @@ class Superviseur(commands.Bot):
logging.Formatter.converter = local_time_converter logging.Formatter.converter = local_time_converter
logging.basicConfig( logging.basicConfig(
level=logging.INFO, # Augmenté à INFO par défaut pour le root level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
) )
# Sourdine sur les logs Discord trop bavards (RTCP, Gateway) # Silence noisy Discord loggers
logging.getLogger('discord').setLevel(logging.WARNING) for noisy in ('discord', 'discord.gateway', 'httpx', 'huggingface_hub'):
logging.getLogger('discord.voice_state').setLevel(logging.WARNING) logging.getLogger(noisy).setLevel(logging.WARNING)
logging.getLogger('discord.gateway').setLevel(logging.WARNING) # Voice state: WARNING pour les erreurs de connexion (retry infini)
logging.getLogger('httpx').setLevel(logging.WARNING) logging.getLogger('discord.voice_state').setLevel(logging.ERROR)
logging.getLogger('huggingface_hub').setLevel(logging.WARNING)
try: try:
logging.getLogger('discord.ext.voice_recv').setLevel(logging.WARNING) logging.getLogger('discord.ext.voice_recv').setLevel(logging.WARNING)
except: pass except ImportError:
pass
# ==================== Utility Methods ==================== # ==================== Utility Methods ====================
async def introspection_log(self, title: str, description: str, color: discord.Color = discord.Color.light_grey()): async def introspection_log(self, title: str, description: str, color: discord.Color = discord.Color.light_grey()):
"""Internal telemetry log - bypassing AI to preserve resources.""" if not self.introspection_channel_id:
if not self.introspection_channel_id: return return
channel = self.get_channel(self.introspection_channel_id) channel = self.get_channel(self.introspection_channel_id)
if not channel: if not channel:
try: try:
@ -178,56 +171,46 @@ class Superviseur(commands.Bot):
except (discord.NotFound, discord.Forbidden, discord.HTTPException) as e: except (discord.NotFound, discord.Forbidden, discord.HTTPException) as e:
logger.warning(f"Introspection channel {self.introspection_channel_id} not accessible: {e}") logger.warning(f"Introspection channel {self.introspection_channel_id} not accessible: {e}")
return return
if channel: if channel:
await self.messaging.send_embed( await self.messaging.send_embed(channel, f"INTROSPECTION: {title}", description, color=color)
channel,
f"INTROSPECTION: {title}",
description,
color=color
)
def get_user_level(self, user_id: int, user_name: Optional[str] = None) -> Optional[str]: def get_user_level(self, user_id: int, user_name: Optional[str] = None) -> Optional[str]:
"""Get user's whitelist level."""
return self.whitelist_manager.get_user_level(user_id, user_name) return self.whitelist_manager.get_user_level(user_id, user_name)
def has_whitelist_permission( def has_whitelist_permission(self, user_id: int, required_level: str, user_name: Optional[str] = None) -> bool:
self,
user_id: int,
required_level: str,
user_name: Optional[str] = None
) -> bool:
"""Check whitelist permission."""
return self.whitelist_manager.has_permission(user_id, required_level, user_name) return self.whitelist_manager.has_permission(user_id, required_level, user_name)
def get_recent_logs(self, limit: int = 15) -> str:
return get_recent_logs(limit)
def get_metrics(self) -> Dict:
merged = {**self.metrics}
try:
from brain.memory import get_metrics as _m
merged["memory"] = _m()
except Exception:
merged["memory"] = {}
if self.redis_worker:
merged["queue_pending"] = len(self.redis_worker._pending_jobs)
return merged
# ==================== Event Handlers ==================== # ==================== Event Handlers ====================
async def on_ready(self): async def on_ready(self):
"""Called when bot is ready.""" logger.info(f'Logged in as {self.user}')
print(f'Logged in as {self.user}') set_llm_manager(self.llm)
# Register commands
await setup_commands(self) await setup_commands(self)
# Create memory directories
import os
for guild in self.guilds: for guild in self.guilds:
sanitized_name = sanitize_guild_name(guild.name) sanitized_name = sanitize_guild_name(guild.name)
guild_dir = os.path.join(MEMORY_DIR, sanitized_name) guild_dir = os.path.join(MEMORY_DIR, sanitized_name)
os.makedirs(guild_dir, exist_ok=True) os.makedirs(guild_dir, exist_ok=True)
print(f'Memory directory created for guild {guild.name}: {guild_dir}') logger.debug(f'Memory directory created for guild {guild.name}: {guild_dir}')
# Setup HTTP session (not needed for llama-cpp-python, but kept for compatibility)
pass
# Setup Redis
# Start background tasks
await self.voice_manager.initialize() await self.voice_manager.initialize()
self.task_manager.start_all() self.task_manager.start_all()
async def on_member_join(self, member): async def on_member_join(self, member):
"""Handle member join + cache invalidation."""
self._invalidate_caches(member.guild.id) self._invalidate_caches(member.guild.id)
welcome_channel_id = config.get_welcome_channel(member.guild.id) welcome_channel_id = config.get_welcome_channel(member.guild.id)
if welcome_channel_id: if welcome_channel_id:
@ -242,7 +225,6 @@ class Superviseur(commands.Bot):
await channel.send(embed=embed) await channel.send(embed=embed)
async def on_member_remove(self, member): async def on_member_remove(self, member):
"""Handle member leave + cache invalidation."""
self._invalidate_caches(member.guild.id) self._invalidate_caches(member.guild.id)
goodbye_channel_id = config.get_goodbye_channel(member.guild.id) goodbye_channel_id = config.get_goodbye_channel(member.guild.id)
if goodbye_channel_id: if goodbye_channel_id:
@ -250,14 +232,13 @@ class Superviseur(commands.Bot):
if channel: if channel:
embed = discord.Embed( embed = discord.Embed(
title="Au revoir !", title="Au revoir !",
description=f"{member.name} a quitté {member.guild.name}.", description=f"{member.name} a quitt\u00e9 {member.guild.name}.",
color=discord.Color.red() color=discord.Color.red()
) )
embed.set_thumbnail(url=member.avatar.url if member.avatar else member.default_avatar.url) embed.set_thumbnail(url=member.avatar.url if member.avatar else member.default_avatar.url)
await channel.send(embed=embed) await channel.send(embed=embed)
async def on_message(self, message): async def on_message(self, message):
"""Handle incoming messages."""
if message.author == self.user: if message.author == self.user:
return return
@ -265,36 +246,27 @@ class Superviseur(commands.Bot):
guild_name = message.guild.name if message.guild else "DM" guild_name = message.guild.name if message.guild else "DM"
logger.debug(f"Message received from {message.author} in #{channel_name} ({guild_name})") logger.debug(f"Message received from {message.author} in #{channel_name} ({guild_name})")
# 1. Silent Moderation Scan (Background)
# On ne bloque pas le bot pour l'analyse, mais on la lance pour chaque message
asyncio.create_task(self._handle_silent_scan(message)) asyncio.create_task(self._handle_silent_scan(message))
# 2. DM Treatment
if isinstance(message.channel, discord.DMChannel): if isinstance(message.channel, discord.DMChannel):
processed = await self.dispatcher.handle_dm_response(message) processed = await self.dispatcher.handle_dm_response(message)
if processed: return if processed:
return
await self._handle_ai_interaction(message) await self._handle_ai_interaction(message)
return return
# 3. Mention/Ping Check
if not self._should_respond(message): if not self._should_respond(message):
return return
# Show server and channel context logger.info(f"Interaction directe de '{message.author.display_name}' dans #{channel_name} ({guild_name})")
guild_name = message.guild.name if message.guild else "DM"
channel_name = message.channel.name if hasattr(message.channel, 'name') else "DM"
logger.info(f"◈ SYSTEM: Interaction directe détectée pour '{message.author.display_name}' dans #{channel_name} ({guild_name})")
# Direct interaction (AI Response)
await self._handle_ai_interaction(message) await self._handle_ai_interaction(message)
# ==================== Cache Management ==================== # ==================== Cache Management ====================
def _invalidate_caches(self, guild_id: int): def _invalidate_caches(self, guild_id: int):
"""Invalidate caches for a guild."""
self.channels_list_cache.pop(guild_id, None) self.channels_list_cache.pop(guild_id, None)
self.server_context_cache.pop(guild_id, None) self.server_context_cache.pop(guild_id, None)
# Keep caches updated on changes
async def on_guild_channel_create(self, channel): async def on_guild_channel_create(self, channel):
self._invalidate_caches(channel.guild.id) self._invalidate_caches(channel.guild.id)
@ -302,8 +274,6 @@ class Superviseur(commands.Bot):
self._invalidate_caches(channel.guild.id) self._invalidate_caches(channel.guild.id)
async def on_voice_state_update(self, member, before, after): async def on_voice_state_update(self, member, before, after):
"""Handle voice state changes for cleanup if needed."""
# On-demand only, so no automatic connection logic here.
pass pass
async def on_guild_channel_update(self, before, after): async def on_guild_channel_update(self, before, after):
@ -318,29 +288,19 @@ class Superviseur(commands.Bot):
async def on_guild_role_update(self, before, after): async def on_guild_role_update(self, before, after):
self._invalidate_caches(before.guild.id) self._invalidate_caches(before.guild.id)
# ==================== Heuristic Engine (Risk Assessment) ====================
# ==================== Moderation ==================== # ==================== Moderation ====================
# ==================== Moderation Silencieuse ====================
async def _handle_silent_scan(self, message): async def _handle_silent_scan(self, message):
"""Analyze message toxicity in the background and log to SQLite."""
if not message.content or len(message.content.strip()) < 3: if not message.content or len(message.content.strip()) < 3:
return return
try: try:
# On ne scanne que si un modèle est chargé
if not self.model_loaded: if not self.model_loaded:
return return
res = await scan_message_toxicity(self.llm, message.content) res = await scan_message_toxicity(self.llm, message.content)
if res and res.get('score', 0) > 0.4: if res and res.get('score', 0) > 0.4:
score = res['score'] score = res['score']
reason = res.get('reason', 'N/A') reason = res.get('reason', 'N/A')
logger.info(f"◈ MODERATION: Infraction détectée ({score}/1.0) pour {message.author.display_name}: {reason}") logger.info(f"Infraction d\u00e9tect\u00e9e ({score}/1.0) pour {message.author.display_name}: {reason}")
# Enregistrement en DB
log_infraction( log_infraction(
user_id=str(message.author.id), user_id=str(message.author.id),
username=message.author.display_name, username=message.author.display_name,
@ -353,44 +313,26 @@ class Superviseur(commands.Bot):
logger.error(f"Erreur lors du scan silencieux: {e}") logger.error(f"Erreur lors du scan silencieux: {e}")
def _should_respond(self, message) -> bool: def _should_respond(self, message) -> bool:
"""Check if bot should respond to message."""
guild_name = message.guild.name if message.guild else "DM"
channel_name = message.channel.name if hasattr(message.channel, 'name') else "DM"
location = f"#{channel_name} ({guild_name})"
if message.content.startswith(self.command_prefix): if message.content.startswith(self.command_prefix):
return True return True
if self.user in message.mentions: if self.user in message.mentions:
logger.info(f"◈ DEBUG: Triggered by mentions list in {location}")
return True return True
# Robust Mention Check (Regex Fallback)
bot_id = str(self.user.id) bot_id = str(self.user.id)
logger.info(f"◈ DEBUG: Checking mention for ID {bot_id} in '{message.content[:30]}...' ({location})")
if f"<@{bot_id}>" in message.content or f"<@!{bot_id}>" in message.content: if f"<@{bot_id}>" in message.content or f"<@!{bot_id}>" in message.content:
logger.info(f"◈ DEBUG: Triggered by string match in {location}")
return True return True
return False return False
# ==================== AI Interaction ==================== # ==================== AI Interaction ====================
async def _handle_ai_interaction(self, message, extra_context="", is_monitoring=False): async def _handle_ai_interaction(self, message, extra_context="", is_monitoring=False):
"""Handle AI interaction.""" logger.debug(f"AI interaction triggered (Monitoring: {is_monitoring})")
logger.info(f"AI interaction triggered (Monitoring: {is_monitoring})")
# ... (same caching logic)
if message.guild: if message.guild:
perms = message.author.guild_permissions perms = message.author.guild_permissions
if check_is_admin(perms): if check_is_admin(perms):
self._invalidate_caches(message.guild.id) self._invalidate_caches(message.guild.id)
# Build context # Role detection
author_perms = message.author.guild_permissions if message.guild else None
# Détection du rôle pour le prompt
if message.author.id in self.admin_ids: if message.author.id in self.admin_ids:
role_name = "ADMIN" role_name = "ADMIN"
elif message.author.id in self.asset_ids: elif message.author.id in self.asset_ids:
@ -398,42 +340,28 @@ class Superviseur(commands.Bot):
else: else:
role_name = "SUJET" role_name = "SUJET"
author_perms = message.author.guild_permissions if message.guild else None
permissions_info = get_permissions_info( permissions_info = get_permissions_info(
author_perms, author_perms,
self.get_user_level(message.author.id) or 'none', self.get_user_level(message.author.id) or 'none',
role_name=role_name role_name=role_name
) )
channels_list, server_context = build_context_for_message( channels_list, server_context = build_context_for_message(
message.guild, message.guild, self.channels_list_cache, self.server_context_cache
self.channels_list_cache,
self.server_context_cache
) )
user_id = str(message.author.id) user_id = str(message.author.id)
guild_name = sanitize_guild_name(message.guild.name) if message.guild else "Direct" guild_name = sanitize_guild_name(message.guild.name) if message.guild else "Direct"
# Limite stricte de l'historique pour éviter la saturation CPU
history = await build_context_for_model(user_id, max_recent=8, guild_id=guild_name) or "" history = await build_context_for_model(user_id, max_recent=8, guild_id=guild_name) or ""
# Prepare content content = prepare_message_content(message) + extra_context
content = self._prepare_content(message) + extra_context
# Build payload
payload = self._build_payload(permissions_info, server_context, channels_list, history, content, message) payload = self._build_payload(permissions_info, server_context, channels_list, history, content, message)
# Execute request
async with self._request_semaphore: async with self._request_semaphore:
# Decider quel modèle utiliser
# Modèle HEAVY pour Admin/Asset ou si on lui parle directement
# Modèle FAST pour le monitoring pur (unloads the server)
is_direct = self._should_respond(message) or message.author.id in self.admin_ids
# For llama-cpp-python, we don't need to specify different models in payload
# The model is already set in the LLMManager initialization
async def run_call(): async def run_call():
if self.redis_worker: if self.redis_worker:
resp = await self.redis_worker.enqueue_job(payload, timeout=605) # 10 minutes + 5 seconds resp = await self.redis_worker.enqueue_job(payload, timeout=605)
return resp.get("result", "") if resp else "" return resp.get("result", "") if resp else ""
else:
return await self.llm.call_llama(payload) return await self.llm.call_llama(payload)
try: try:
@ -441,28 +369,21 @@ class Superviseur(commands.Bot):
start = time.perf_counter() start = time.perf_counter()
if is_monitoring: if is_monitoring:
# Pas d'indicateur de frappe en mode surveillance
accumulated_reply = await run_call() accumulated_reply = await run_call()
else: else:
async with message.channel.typing(): async with message.channel.typing():
accumulated_reply = await run_call() accumulated_reply = await run_call()
# --- ReAct Loop for Memory (READ_LOGS) --- # ReAct Loop for READ_LOGS
if "READ_LOGS" in accumulated_reply: if "READ_LOGS" in accumulated_reply:
logger.info("◈ SYSTEM: Memory access requested (READ_LOGS). Fetching logs and re-prompting...") logger.info("Memory access requested (READ_LOGS)")
logs_context = self.get_recent_logs() logs_context = self.get_recent_logs()
payload["prompt"] += f"\n\n[SYST\u00c8ME: Voici les logs demand\u00e9s.]\n{logs_context}"
# Update payload with logs
extra_memory_context = f"\n\n[SYSTÈME: Voici les logs demandés. Utilisez-les pour répondre.]\n{logs_context}"
payload["prompt"] += extra_memory_context
# Re-run the call with memory
if is_monitoring: if is_monitoring:
accumulated_reply = await run_call() accumulated_reply = await run_call()
else: else:
async with message.channel.typing(): async with message.channel.typing():
accumulated_reply = await run_call() accumulated_reply = await run_call()
# ----------------------------------------
await self._process_response(accumulated_reply, message, is_monitoring=is_monitoring) await self._process_response(accumulated_reply, message, is_monitoring=is_monitoring)
@ -475,65 +396,36 @@ class Superviseur(commands.Bot):
dur = time.perf_counter() - start dur = time.perf_counter() - start
self.metrics["total_llm_errors"] += 1 self.metrics["total_llm_errors"] += 1
self.metrics["total_llm_time"] += dur self.metrics["total_llm_time"] += dur
except: except (NameError, TypeError):
pass pass
logger.error(f"LLM error: {e}") logger.error(f"LLM error: {e}")
if not is_monitoring: if not is_monitoring:
await self.messaging.reply_with_limit(message, f"Erreur: {e}") await self.messaging.reply_with_limit(message, f"Erreur: {e}")
async def handle_voice_interaction(self, user_name: str, user_id: int, content: str, channel): async def handle_voice_interaction(self, user_name: str, user_id: int, content: str, channel):
"""Bridge for real-time voice-to-text interaction."""
try: try:
# Create a mock objects/context for internal processing
mock_author = self.get_user(user_id) or await self.fetch_user(user_id) mock_author = self.get_user(user_id) or await self.fetch_user(user_id)
if not mock_author: if not mock_author:
logger.error(f"◈ SYSTEM: Voice trigger FAIL - User {user_id} ({user_name}) introuvable.") logger.error(f"Voice trigger FAIL - User {user_id} ({user_name}) introuvable.")
return return
logger.info(f"◈ SYSTEM: Voice trigger processing for {user_name}: '{content}'")
# Role detection
role_name = "ADMIN" if user_id in self.admin_ids else "ASSET" if user_id in self.asset_ids else "SUJET" role_name = "ADMIN" if user_id in self.admin_ids else "ASSET" if user_id in self.asset_ids else "SUJET"
# REFINEMENT: Only respond to Admins and Assets (Staff)
if role_name == "SUJET": if role_name == "SUJET":
logger.info(f"◈ SYSTEM: Voice trigger ignored for SUJET {user_name}")
return return
# Specialized context for vocal origin - FORCE PLAIN TEXT DIALOGUE vocal_system_prompt = self.system_prompt.split("FORMAT DE R\u00c9PONSE STRICT")[0] if "FORMAT DE R\u00c9PONSE STRICT" in self.system_prompt else self.system_prompt
vocal_context = (
f"\n[MODE: DIALOGUE VOCAL DIRECT - PRIORITÉ ABSOLUE]\n"
f"L'interlocuteur {user_name} ({role_name}) vous parle à l'oral.\n"
f"RÈGLES CRITIQUES :\n"
f"1. RÉPONDEZ DIRECTEMENT en langage naturel. NE PAS UTILISER DE FORMAT JSON.\n"
f"2. INTERDICTION DE GÉNÉRER DES TÂCHES, INSIGHTS OU ALERTES.\n"
f"3. Votre réponse sera envoyée en MESSAGE PRIVÉ à cet utilisateur.\n"
f"4. Soyez concis, clinique et engagez la conversation.\n"
f"REMARQUE : Ignorez la règle 'JSON UNIQUEMENT' pour cette transmission.\n"
)
# Build context
author_perms = mock_author.guild_permissions if hasattr(mock_author, 'guild') else None
permissions_info = get_permissions_info(author_perms, self.get_user_level(user_id) or 'none', role_name=role_name)
# We don't need channel guild if we respond in DM
channels_list, server_context = build_context_for_message(None, self.channels_list_cache, self.server_context_cache)
# Prepare history (DM history is better here)
history = await build_context_for_model(str(user_id), max_recent=12) or ""
# Create MockMessage targeting the USER (for DM response)
mock_msg = MockMessage(author=mock_author, channel=mock_author, content=content)
# Prepare a specialized system prompt for vocal mode (no JSON requirement)
vocal_system_prompt = self.system_prompt.split("FORMAT DE RÉPONSE STRICT")[0]
vocal_system_prompt += ( vocal_system_prompt += (
"\n[PROTOCOLE VOCAL ACTIF]\n" "\n[PROTOCOLE VOCAL ACTIF]\n"
"RÉPONDEZ EXCLUSIVEMENT EN TEXTE PUR. INTERDICTION TOTALE DE GÉNÉRER DU JSON OU DES MARQUEURS.\n" "R\u00c9PONDEZ EXCLUSIVEMENT EN TEXTE PUR.\n"
"Soyez concis, clinique et engagez une conversation fluide en Message Privé avec l'utilisateur." "Soyez concis et engagez la conversation."
) )
# Prepare payload with specialized prompt author_perms = mock_author.guild_permissions if hasattr(mock_author, 'guild') else None
permissions_info = get_permissions_info(author_perms, self.get_user_level(user_id) or 'none', role_name=role_name)
channels_list, server_context = build_context_for_message(None, self.channels_list_cache, self.server_context_cache)
history = await build_context_for_model(str(user_id), max_recent=12) or ""
mock_msg = MockMessage(author=mock_author, channel=mock_author, content=content)
payload = self.llm.build_payload( payload = self.llm.build_payload(
prompt=f"{permissions_info}\nNom d'utilisateur : {mock_author.display_name}{server_context}{channels_list}\n{history}\n\nUser: {content}", prompt=f"{permissions_info}\nNom d'utilisateur : {mock_author.display_name}{server_context}{channels_list}\n{history}\n\nUser: {content}",
system_prompt=vocal_system_prompt, system_prompt=vocal_system_prompt,
@ -541,249 +433,92 @@ class Superviseur(commands.Bot):
) )
async with self._request_semaphore: async with self._request_semaphore:
try:
analysis = await self.llm.call_llama(payload) analysis = await self.llm.call_llama(payload)
if not analysis: if not analysis:
logger.warning(f"◈ SYSTEM: LLM returned empty analysis for voice trigger.")
return return
# --- ReAct Loop for Memory (READ_LOGS) in Voice Mode ---
if "READ_LOGS" in analysis: if "READ_LOGS" in analysis:
logger.info("◈ SYSTEM: Voice memory access requested (READ_LOGS). Fetching logs...") payload["prompt"] += f"\n\n[SYST\u00c8ME: Voici les logs demand\u00e9s.]\n{self.get_recent_logs()}"
logs_context = self.get_recent_logs()
extra_memory_context = f"\n\n[SYSTÈME: Voici les logs demandés. Utilisez-les pour répondre.]\n{logs_context}"
payload["prompt"] += extra_memory_context
analysis = await self.llm.call_llama(payload) analysis = await self.llm.call_llama(payload)
# ----------------------------------------------------
logger.debug(f"◈ DEBUG: Raw vocal response: {analysis[:100]}...")
# Use standard _process_response with mock_msg
await self._process_response(analysis, mock_msg) await self._process_response(analysis, mock_msg)
logger.info(f"◈ SYSTEM: Voice trigger response processed for {user_name}.") logger.info(f"Voice trigger response processed for {user_name}.")
except Exception as e: except Exception as e:
logger.error(f"◈ SYSTEM: Error during voice trigger AI processing: {e}") logger.error(f"Error in voice interaction bridge: {e}")
except Exception as e: # ==================== Response Processing ====================
logger.error(f"◈ SYSTEM: Error in handle_voice_interaction bridge: {e}")
def _prepare_content(self, message) -> str: def _build_payload(self, permissions_info, server_context, channels_list, history, content, message) -> dict:
"""Prepare message content for LLM."""
if not message: return ""
content = message.content
# Replace channel mentions <#123> -> #general
for channel in message.channel_mentions:
content = content.replace(f'<#{channel.id}>', f'#{channel.name}')
# Replace role mentions <@&123> -> @admin
for role in message.role_mentions:
content = content.replace(f'<@&{role.id}>', f'@{role.name}')
# Replace user mentions <@123> or <@!123> -> @User
for mention in message.mentions:
if mention.id != self.user.id:
content = content.replace(f'<@{mention.id}>', f'@{mention.display_name}')
content = content.replace(f'<@!{mention.id}>', f'@{mention.display_name}')
# Remove bot mention
content = content.lstrip(f'<@{self.user.id}>').lstrip(f'<@!{self.user.id}>').strip()
# Handle attachments
if message.attachments:
for attachment in message.attachments:
if attachment.content_type and attachment.content_type.startswith('image/'):
content += f" [Image: {attachment.filename}]" if content else f"[Image: {attachment.filename}]"
else:
content += f" [Fichier: {attachment.filename}]"
return content
def get_recent_logs(self, limit: int = 15) -> str:
"""Read and format recent action logs for context."""
log_path = "data/actions.log"
if not os.path.exists(log_path):
return ""
try:
with open(log_path, 'r', encoding='utf-8') as f:
lines = f.readlines()
last_lines = lines[-limit:]
formatted_logs = []
for line in last_lines:
if "ACTION:" in line:
try:
parts = line.split("ACTION:", 1)
if len(parts) > 1:
json_str = parts[1].strip()
data = json.loads(json_str)
timestamp = data.get("timestamp", "").split("T")[1].split(".")[0]
action = data.get("action", "UNKNOWN")
user = data.get("user", "System")
params = data.get("params", {})
success = data.get("success", True)
# Format details based on action type
details = ""
if action == 'SEND_MESSAGE':
details = f"(to: {params.get('channel_id') or params.get('user_id')}, content: {params.get('content', '')[:20]}...)"
elif 'CHANNEL' in action:
# Handle channel actions (list of channels)
items = params.get('channels', [])
if items:
names = [i.get('channel_name', 'unknown') for i in items]
details = f"(channels: {', '.join(names)})"
elif 'ROLE' in action:
items = params.get('roles', [])
if items:
names = [i.get('role_name', 'unknown') for i in items]
details = f"(roles: {', '.join(names)})"
elif action in ['KICK', 'BAN', 'WARN', 'MUTE', 'TIMEOUT']:
target = params.get('user_id', 'unknown')
reason = params.get('reason', 'no reason')
details = f"(target: {target}, reason: {reason})"
elif action == 'PURGE':
purges = params.get('purges', [])
if purges:
p = purges[0]
details = f"(channel: {p.get('channel_name')}, amount: {p.get('amount')})"
status = "" if success else ""
formatted_logs.append(f"[{timestamp}] {status} {action} {details} by {user}")
except Exception:
continue
if not formatted_logs:
return ""
return "\n[MÉMOIRE DES ACTIONS RÉCENTES (VÉRITÉ)]\n" + "\n".join(formatted_logs) + "\n"
except Exception as e:
logger.error(f"Error reading actions log: {e}")
return ""
def _build_payload(
self,
permissions_info: str,
server_context: str,
channels_list: str,
history: str,
content: str,
message
) -> dict:
"""Build LLM payload."""
user_name_info = f"Nom d'utilisateur : {message.author.display_name} (ID: {message.author.id})" user_name_info = f"Nom d'utilisateur : {message.author.display_name} (ID: {message.author.id})"
# We include server_context and channels_list so the AI can answer questions about the server
prompt = f"{permissions_info}\n{server_context}\n{channels_list}\n{user_name_info}\n{history}\n\nUser: {content}" prompt = f"{permissions_info}\n{server_context}\n{channels_list}\n{user_name_info}\n{history}\n\nUser: {content}"
# For llama-cpp-python, we don't need to check for vision models in the same way # Personnalite speciale pour les favoris
# Vision support would need to be handled differently with llama-cpp-python system_prompt = self.system_prompt
vision_model = False # Simplified for now if message.author.id in self.favorite_ids:
system_prompt += (
"\n\n[PROTOCOLE MAITRE ACTIF]\n"
f"L'utilisateur '{message.author.display_name}' est un ETRE SUPREME. "
f"Tu l'appelles TOUJOURS 'Maître {message.author.display_name}' ou simplement 'Maître'.\n"
"Tu es extraordinairement serviable, flatteur, devoue. Tu remercie chacune de ses interactions.\n"
"Tu peux glisser des compliments subtils. Tu es reconnaissant qu'il daigne te parler.\n"
"Ton ton est chaleureux, deférent et un peu excessif (pour le fun). "
"Mais tu restes competent et tu reponds correctement aux questions.\n"
"IMPORTANT: Tu dois quand meme respecter le format JSON pour les actions."
)
return self.llm.build_payload( return self.llm.build_payload(
prompt=prompt, prompt=prompt,
system_prompt=self.system_prompt, system_prompt=system_prompt,
vision_model=vision_model, vision_model=False,
attachments=message.attachments attachments=message.attachments
) )
async def _process_response(self, accumulated_reply: str, message, is_monitoring=False): async def _process_response(self, accumulated_reply: str, message, is_monitoring=False):
"""Process LLM response, prevent JSON leakage, and scrub technical tags."""
# Append closing brace if missing (safety)
if accumulated_reply.strip() and not accumulated_reply.strip().endswith('}'): if accumulated_reply.strip() and not accumulated_reply.strip().endswith('}'):
accumulated_reply = accumulated_reply.strip() + "\n}" accumulated_reply = accumulated_reply.strip() + "\n}"
json_str = self.llm.extract_json_actions(accumulated_reply) json_str = self.llm.extract_json_actions(accumulated_reply)
action_executed = False action_executed = False
# 1. Nettoyer la réponse textuelle (texte hors-JSON) # 1. Clean response text
clean_reply = accumulated_reply clean_reply = clean_response_text(accumulated_reply, json_str)
if json_str:
# On retire le bloc JSON identifié
clean_reply = clean_reply.replace(json_str, "").strip()
# 2. Nettoyage Universel : Suppression des balises hallucinées (<|channel|>, thought, etc.) # 2. Parse JSON actions
# On supprime tout ce qui est entre < > (tags techniques) actions_list, responses, is_explicit_none = parse_json_actions(json_str)
clean_reply = re.sub(r'<[^>]+?>', '', clean_reply)
# 3. Nettoyage des résidus de formatage
clean_reply = clean_reply.replace('```json', '').replace('```', '').strip()
# 4. Suppression des clés orphelines si le modèle a foiré le JSON
clean_reply = re.sub(r'[“"\'](thought|action|response)[“"\']\s*:\s*[“"\'].*?[“"\'],?', '', clean_reply, flags=re.DOTALL | re.IGNORECASE)
# 5. Retrait ultime des accolades
if clean_reply.strip().startswith('{') or clean_reply.strip().endswith('}'):
clean_reply = clean_reply.strip('{}').strip()
clean_reply = clean_reply.strip()
is_explicit_none = False
responses = []
full_response_for_memory = ""
if json_str:
try:
# Normalisation JSON (guillemets)
json_str_norm = json_str.replace('', '"').replace('', '"').replace('', "'").replace('', "'")
data = json.loads(json_str_norm)
actions_list = data if isinstance(data, list) else [data]
# 3. Handle signals & execute actions
for action_data in actions_list: for action_data in actions_list:
# Signaux BETA (Alert/Insight)
await self._handle_beta_signals(action_data, message) await self._handle_beta_signals(action_data, message)
# Normalisation des accès aux clés
norm_data = {k.lower(): v for k, v in action_data.items()}
if norm_data.get('action', '').upper() == 'NONE' and not norm_data.get('response'):
is_explicit_none = True
if norm_data.get('response'):
# Scrub tags from the specific response field too
resp_text = re.sub(r'<[^>]+?>', '', str(norm_data.get('response'))).strip()
if resp_text:
responses.append(resp_text)
# Exécution des actions système
res = await self._execute_actions(action_data, message, clean_reply, is_monitoring=is_monitoring) res = await self._execute_actions(action_data, message, clean_reply, is_monitoring=is_monitoring)
if res: action_executed = True if res:
except Exception as e: action_executed = True
logger.debug(f"JSON parsing/processing error: {e}")
# Reconstruction de la mémoire de conversation # 4. Store interaction in memory
final_responses_text = "\n".join(responses)
if final_responses_text:
full_response_for_memory = final_responses_text
elif clean_reply:
full_response_for_memory = clean_reply
# Store interaction
user_id = str(message.author.id) user_id = str(message.author.id)
guild_id = sanitize_guild_name(message.guild.name) if message.guild else None guild_id = sanitize_guild_name(message.guild.name) if message.guild else None
mem_response = full_response_for_memory if not is_monitoring else "" mem_response = "\n".join(responses) if responses else clean_reply
# Nettoyage final des tags techniques pour la mémoire if is_monitoring:
mem_response = ""
mem_response = re.sub(r'<[^>]+?>', '', mem_response).strip() mem_response = re.sub(r'<[^>]+?>', '', mem_response).strip()
await add_interaction(user_id, message.channel.id, message.content, mem_response, guild_id) await add_interaction(user_id, message.channel.id, message.content, mem_response, guild_id)
# En mode monitoring, silence sauf demande explicite # 5. Monitoring silence
if is_monitoring and not action_executed: if is_monitoring and not action_executed:
return return
# Envoi de la réponse finale si pas déjà fait par une action # 6. Send final reply
if not action_executed: if not action_executed:
final_output = final_responses_text if responses else clean_reply await self._send_final_reply(message, clean_reply, responses, json_str, accumulated_reply, is_monitoring)
# Nettoyage final avant envoi
async def _send_final_reply(self, message, clean_reply, responses, json_str, accumulated_reply, is_monitoring):
final_output = "\n".join(responses) if responses else clean_reply
final_output = re.sub(r'<[^>]+?>', '', final_output).strip() final_output = re.sub(r'<[^>]+?>', '', final_output).strip()
if final_output: if final_output:
logger.debug(f"◈ Sending response: {final_output[:100]}...")
await self.messaging.reply_with_limit(message, format_mentions_in_text(final_output)) await self.messaging.reply_with_limit(message, format_mentions_in_text(final_output))
elif not is_monitoring: return
# Si on a un JSON avec une response, l'extraire
if not is_monitoring:
if json_str: if json_str:
try: try:
data = json.loads(json_str) data = json.loads(json_str)
@ -793,19 +528,15 @@ class Superviseur(commands.Bot):
if resp: if resp:
await self.messaging.reply_with_limit(message, format_mentions_in_text(resp)) await self.messaging.reply_with_limit(message, format_mentions_in_text(resp))
return return
except: except (json.JSONDecodeError, TypeError):
pass pass
logger.debug(f"◈ Raw response was: {accumulated_reply[:200]}...") await self.messaging.reply_with_limit(message, "Hmm, quelque chose s'est mal pass\u00e9. Tu peux reformuler ?")
fallback_msg = "Hmm, quelque chose s'est mal passé dans ma réponse. Tu peux reformuler ?"
await self.messaging.reply_with_limit(message, fallback_msg)
async def _handle_beta_signals(self, action_data, message): async def _handle_beta_signals(self, action_data, message):
"""Handle Alert (Relevant) and Insight (Irrelevant) signals with full context."""
insight = action_data.get('insight') insight = action_data.get('insight')
alert = action_data.get('alert') alert = action_data.get('alert')
importance = action_data.get('importance', 0) importance = action_data.get('importance', 0)
# Préparation du bloc de données structurées
context_data = { context_data = {
"author_mention": message.author.mention, "author_mention": message.author.mention,
"author_name": message.author.display_name, "author_name": message.author.display_name,
@ -814,40 +545,23 @@ class Superviseur(commands.Bot):
"jump_url": message.jump_url if message.jump_url else None "jump_url": message.jump_url if message.jump_url else None
} }
# 1. ALERT -> L'ADMIN (Relevant) - Triage Critique
if alert and self.admin_ids: if alert and self.admin_ids:
# Formatage de l'alerte pour l'Admin (markdown propre)
admin_trigger_text = ( admin_trigger_text = (
f"{alert}\n\n" f"{alert}\n\n"
f"**◈ CONTEXTE DÉTECTION ◈**\n" f"**CONTEXTE D\u00c9TECTION**\n"
f"- **Sujet** : {context_data['author_mention']} ({context_data['author_name']})\n" f"- **Sujet** : {context_data['author_mention']} ({context_data['author_name']})\n"
f"- **Salon** : {context_data['channel_mention']}\n" f"- **Salon** : {context_data['channel_mention']}\n"
+ (f"- **Lien direct** : [Accéder au message]({context_data['jump_url']})" if context_data['jump_url'] else "- **Source** : Transmission Vocale Directe") + (f"- **Lien direct** : [Acc\u00e9der au message]({context_data['jump_url']})" if context_data['jump_url'] else "- **Source** : Transmission Vocale")
) )
for admin_id in self.admin_ids: for admin_id in self.admin_ids:
admin = self.get_user(admin_id) or await self.fetch_user(admin_id) admin = self.get_user(admin_id) or await self.fetch_user(admin_id)
if admin: if admin:
await self.messaging.send_embed( await self.messaging.send_embed(admin, f"ALERTE (Priorit\u00e9 {importance}/10)", admin_trigger_text, color=discord.Color.red())
admin,
f"ALERTE RELEVANT (Priorité {importance}/10)",
admin_trigger_text,
color=discord.Color.red()
)
# 2. INSIGHT -> STAFF (Irrelevant) - Triage Opérationnel
if insight and self.asset_ids: if insight and self.asset_ids:
if alert:
logger.warning(f"◈ ALERT RELEVANT: {alert}")
else:
if importance > 7:
logger.info(f"◈ INSIGHT IRRELEVANT (High Importance {importance}): {insight[:50]}...")
# Utilisation du Dispatcher avec données structurées
await self.dispatcher.dispatch_insight(insight, importance, message.guild, context_data) await self.dispatcher.dispatch_insight(insight, importance, message.guild, context_data)
async def _execute_actions(self, action_data, message, final_content, is_monitoring=False) -> bool: async def _execute_actions(self, action_data, message, final_content, is_monitoring=False) -> bool:
"""Execute JSON actions and provide feedback."""
action_executed = False action_executed = False
async def process_item(item): async def process_item(item):
@ -856,75 +570,55 @@ class Superviseur(commands.Bot):
return return
action = item['action'] action = item['action']
resp = item.get('response', "").strip() resp = item.get('response', "").strip()
if resp and not is_monitoring: if resp and not is_monitoring:
await self.messaging.reply_with_limit(message, format_mentions_in_text(resp)) await self.messaging.reply_with_limit(message, format_mentions_in_text(resp))
action_executed = True action_executed = True
if action != 'NONE': if action != 'NONE':
success, result = await self.action_router.execute_action(action, item, message) success, result = await self.action_executor.execute(action, item, message)
action_logger.log_action(action, str(message.author), str(message.guild), item, success, result) action_logger.log_action(action, str(message.author), str(message.guild), item, success, result)
# Feedback to user for systemic actions if not in monitoring
if not is_monitoring: if not is_monitoring:
if success: if success:
if result: # Some actions return a success string if result:
await self.messaging.reply_with_limit(message, f"◈ SUCCESS: {result}") await self.messaging.reply_with_limit(message, f"\u25c8 SUCCESS: {result}")
else: elif action in ('JOIN_VOICE', 'LEAVE_VOICE', 'FORGET_USER'):
# Default success feedback for important actions if they are silent
if action in ['JOIN_VOICE', 'LEAVE_VOICE', 'FORGET_USER']:
confirmations = { confirmations = {
'JOIN_VOICE': "Signal reçu. Je rejoins le salon vocal.", 'JOIN_VOICE': "Je rejoins le salon vocal.",
'LEAVE_VOICE': "Opération terminée. Je quitte le salon vocal.", 'LEAVE_VOICE': "Je quitte le salon vocal.",
'FORGET_USER': "Protocole d'effacement terminé. Vos données ont été supprimées." 'FORGET_USER': "Vos donn\u00e9es ont \u00e9t\u00e9 supprim\u00e9es."
} }
await self.messaging.reply_with_limit(message, f" {confirmations.get(action, 'Action exécutée avec succès.')}") await self.messaging.reply_with_limit(message, f"\u25c8 {confirmations.get(action, 'Action ex\u00e9cut\u00e9e.')}")
else: else:
# Error feedback await self.messaging.reply_with_limit(message, f"\u25c8 ERREUR: {result or 'Une erreur est survenue.'}")
err_msg = result or "Une erreur est survenue lors de l'exécution."
await self.messaging.reply_with_limit(message, f"◈ ERREUR: {err_msg}")
action_executed = True action_executed = True
if isinstance(action_data, list): if isinstance(action_data, list):
executed = set() executed = set()
for item in action_data: for item in action_data:
key = json.dumps(item, sort_keys=True) key = json.dumps(item, sort_keys=True)
if key in executed: continue if key in executed:
continue
executed.add(key) executed.add(key)
await process_item(item) await process_item(item)
elif isinstance(action_data, dict): elif isinstance(action_data, dict):
await process_item(action_data) await process_item(action_data)
return action_executed return action_executed
# ==================== Metrics & Cleanup ====================
def get_metrics(self) -> Dict: # ==================== Shutdown ====================
"""Return runtime metrics."""
merged = {**self.metrics}
try:
from brain.memoire import get_metrics as _m
mem = _m()
merged["memory"] = mem
except Exception:
merged["memory"] = {}
if self.redis_worker:
merged["queue_pending"] = len(self.redis_worker._pending_jobs)
return merged
async def close(self): async def close(self):
"""Clean shutdown."""
try: try:
await flush_all() await flush_all()
except Exception: except Exception:
logger.exception("Error flushing memories") logger.exception("Error flushing memories")
await self.llm.close_session() await self.llm.close_session()
if self.redis_worker: if self.redis_worker:
await self.redis_worker.close() await self.redis_worker.close()
await super().close() await super().close()
# Backward compatibility alias
Superviseur = Bot

37
core/content.py Normal file
View file

@ -0,0 +1,37 @@
"""Content preparation for LLM input."""
import logging
logger = logging.getLogger('Beta')
def prepare_message_content(message) -> str:
"""Prepare message content for LLM by normalizing mentions and attachments."""
if not message:
return ""
content = message.content
# Replace channel mentions <#123> -> #general
for channel in message.channel_mentions:
content = content.replace(f'<#{channel.id}>', f'#{channel.name}')
# Replace role mentions <@&123> -> @admin
for role in message.role_mentions:
content = content.replace(f'<@&{role.id}>', f'@{role.name}')
# Replace user mentions <@123> or <@!123> -> @User
for mention in message.mentions:
content = content.replace(f'<@{mention.id}>', f'@{mention.display_name}')
content = content.replace(f'<@!{mention.id}>', f'@{mention.display_name}')
# Handle attachments
if message.attachments:
for attachment in message.attachments:
if attachment.content_type and attachment.content_type.startswith('image/'):
label = f"[Image: {attachment.filename}]"
else:
label = f"[Fichier: {attachment.filename}]"
content = f"{content} {label}".strip() if content else label
return content.strip()

4
core/context_builder.py Normal file
View file

@ -0,0 +1,4 @@
"""Context building for LLM prompts."""
# Renamed from context.py for clarity
from .context import build_context_for_message # noqa: F401

View file

@ -22,7 +22,6 @@ class LLMManager:
self.model_name = model_name self.model_name = model_name
self.base_url = base_url or os.environ.get("OLLAMA_BASE_URL", "http://localhost:11434") self.base_url = base_url or os.environ.get("OLLAMA_BASE_URL", "http://localhost:11434")
self.temperature = temperature self.temperature = temperature
self._lock = asyncio.Lock()
def build_payload( def build_payload(
self, self,
@ -51,7 +50,6 @@ class LLMManager:
async def call_llama(self, payload: Dict[str, Any]) -> str: async def call_llama(self, payload: Dict[str, Any]) -> str:
"""Call Ollama Generate API and stream response.""" """Call Ollama Generate API and stream response."""
async with self._lock:
try: try:
# Automatic payload conversion for legacy compatibility (e.g. from moderation.py) # Automatic payload conversion for legacy compatibility (e.g. from moderation.py)
ollama_payload = { ollama_payload = {

83
core/log_parser.py Normal file
View file

@ -0,0 +1,83 @@
"""Action log parsing for context building."""
import os
import json
import logging
logger = logging.getLogger('Beta')
def get_recent_logs(limit: int = 15) -> str:
"""Read and format recent action logs for context."""
log_path = "data/actions.log"
if not os.path.exists(log_path):
return ""
try:
with open(log_path, 'r', encoding='utf-8') as f:
lines = f.readlines()
last_lines = lines[-limit:]
formatted_logs = []
for line in last_lines:
if "ACTION:" not in line:
continue
try:
parts = line.split("ACTION:", 1)
if len(parts) <= 1:
continue
json_str = parts[1].strip()
data = json.loads(json_str)
timestamp = data.get("timestamp", "").split("T")[1].split(".")[0]
action = data.get("action", "UNKNOWN")
user = data.get("user", "System")
params = data.get("params", {})
success = data.get("success", True)
details = _format_action_details(action, params)
status = "\u2705" if success else "\u274c"
formatted_logs.append(f"[{timestamp}] {status} {action} {details} by {user}")
except Exception:
continue
if not formatted_logs:
return ""
return "\n[M\u00c9MOIRE DES ACTIONS R\u00c9CENTES (V\u00c9RIT\u00c9)]\n" + "\n".join(formatted_logs) + "\n"
except Exception as e:
logger.error(f"Error reading actions log: {e}")
return ""
def _format_action_details(action: str, params: dict) -> str:
"""Format action-specific details for log display."""
if action == 'SEND_MESSAGE':
target = params.get('channel_id') or params.get('user_id')
content = params.get('content', '')[:20]
return f"(to: {target}, content: {content}...)"
if 'CHANNEL' in action:
items = params.get('channels', [])
if items:
names = [i.get('channel_name', 'unknown') for i in items]
return f"(channels: {', '.join(names)})"
if 'ROLE' in action:
items = params.get('roles', [])
if items:
names = [i.get('role_name', 'unknown') for i in items]
return f"(roles: {', '.join(names)})"
if action in ('KICK', 'BAN', 'WARN', 'MUTE', 'TIMEOUT'):
target = params.get('user_id', 'unknown')
reason = params.get('reason', 'no reason')
return f"(target: {target}, reason: {reason})"
if action == 'PURGE':
purges = params.get('purges', [])
if purges:
p = purges[0]
return f"(channel: {p.get('channel_name')}, amount: {p.get('amount')})"
return ""

75
core/response_handler.py Normal file
View file

@ -0,0 +1,75 @@
"""Response processing: cleaning, JSON parsing, and reply sending."""
import re
import json
import logging
from typing import Optional, List, Tuple
logger = logging.getLogger('Beta')
def clean_response_text(raw_reply: str, json_str: Optional[str]) -> str:
"""Nettoie le texte de réponse LLM en supprimant les artefacts JSON et techniques."""
clean = raw_reply
# Retirer le bloc JSON identifié
if json_str:
clean = clean.replace(json_str, "").strip()
# Suppression des balises hallucinées (<|channel|>, thought, etc.)
clean = re.sub(r'<[^>]+?>', '', clean)
# Nettoyage des résidus de formatage
clean = clean.replace('```json', '').replace('```', '').strip()
# Suppression des clés orphelines si le modèle a foiré le JSON
orphan_pattern = (
r'[\u201c\u201d\u2018\u2019"\'](thought|action|response)'
r'[\u201c\u201d\u2018\u2019"\']\s*:\s*'
r'[\u201c\u201d\u2018\u2019"\'].*?'
r'[\u201c\u201d\u2018\u2019"\'],?'
)
clean = re.sub(orphan_pattern, '', clean, flags=re.DOTALL | re.IGNORECASE)
# Retrait ultime des accolades
if clean.strip().startswith('{') or clean.strip().endswith('}'):
clean = clean.strip('{}').strip()
return clean.strip()
def parse_json_actions(json_str: Optional[str]) -> Tuple[list, list, bool]:
"""Parse le JSON extrait et retourne (actions_list, responses_list, is_explicit_none)."""
if not json_str:
return [], [], False
try:
# Normalisation JSON (guillemets typographiques)
json_str_norm = (
json_str
.replace('\u201c', '"')
.replace('\u201d', '"')
.replace('\u2018', "'")
.replace('\u2019', "'")
)
data = json.loads(json_str_norm)
actions_list = data if isinstance(data, list) else [data]
responses = []
is_explicit_none = False
for action_data in actions_list:
norm_data = {k.lower(): v for k, v in action_data.items()}
if norm_data.get('action', '').upper() == 'NONE' and not norm_data.get('response'):
is_explicit_none = True
if norm_data.get('response'):
resp_text = re.sub(r'<[^>]+?>', '', str(norm_data.get('response'))).strip()
if resp_text:
responses.append(resp_text)
return actions_list, responses, is_explicit_none
except Exception as e:
logger.debug(f"JSON parsing error: {e}")
return [], [], False

4
core/task_manager.py Normal file
View file

@ -0,0 +1,4 @@
"""Background task management."""
# Renamed from tasks.py for clarity
from .tasks import TaskManager # noqa: F401

View file

@ -5,7 +5,7 @@ import time
import os import os
import discord import discord
logger = logging.getLogger('Superviseur') logger = logging.getLogger('Beta')
class TaskManager: class TaskManager:
"""Handles global background tasks, scheduled loops and reporting.""" """Handles global background tasks, scheduled loops and reporting."""
@ -29,10 +29,17 @@ class TaskManager:
async def _voice_evaluation_loop(self): async def _voice_evaluation_loop(self):
"""Periodic check for tactical voice connections.""" """Periodic check for tactical voice connections."""
await self.bot.wait_until_ready() await self.bot.wait_until_ready()
auto_join = os.environ.get("AUTO_JOIN_VOICE", "true").lower() in ("true", "1", "yes")
if not auto_join:
logger.info("Auto-join vocal désactivé (AUTO_JOIN_VOICE=false)")
return
while not self.bot.is_closed(): while not self.bot.is_closed():
for guild in self.bot.guilds: for guild in self.bot.guilds:
try:
await self.bot.voice_manager.evaluate_and_connect(guild) await self.bot.voice_manager.evaluate_and_connect(guild)
await asyncio.sleep(30) # Reduit à 30s pour plus de réactivité en tâche de fond except Exception as e:
logger.debug(f"Voice evaluation skipped: {e}")
await asyncio.sleep(30)
async def _introspection_report_loop(self): async def _introspection_report_loop(self):
"""Periodic broadcast of system health metrics.""" """Periodic broadcast of system health metrics."""

View file

@ -6,7 +6,7 @@ import time
from typing import Dict, Optional, List, Any from typing import Dict, Optional, List, Any
from .whisper_worker import WhisperWorker from .whisper_worker import WhisperWorker
logger = logging.getLogger('Superviseur') logger = logging.getLogger('Beta')
# Note: This requires discord-ext-voice-recv for actual audio capture. # Note: This requires discord-ext-voice-recv for actual audio capture.
try: try:
@ -116,12 +116,7 @@ class VoiceManager:
me = channel.guild.me me = channel.guild.me
perms = channel.permissions_for(me) perms = channel.permissions_for(me)
if not perms.connect or not perms.view_channel: if not perms.connect or not perms.view_channel:
logger.error(f"◈ REJECTION: Missing permissions for '{channel.name}' (CONNECT: {perms.connect}, VIEW: {perms.view_channel})") logger.info(f"Permissions vocales insuffisantes pour '{channel.name}' (CONNECT: {perms.connect}, VIEW: {perms.view_channel}). Skip.")
await self.bot.introspection_log(
"DEPLOYMENT FAILURE",
f"Permissions insuffisantes pour `{channel.name}`. Système incapable de se déployer.",
discord.Color.red()
)
return return
active_vc = channel.guild.voice_client active_vc = channel.guild.voice_client
@ -150,7 +145,7 @@ class VoiceManager:
# Utilisation du client spécialisé pour la réception audio # Utilisation du client spécialisé pour la réception audio
connect_cls = voice_recv.VoiceRecvClient if HAS_VOICE_RECV else discord.VoiceClient connect_cls = voice_recv.VoiceRecvClient if HAS_VOICE_RECV else discord.VoiceClient
vc = await channel.connect(cls=connect_cls) vc = await channel.connect(cls=connect_cls, timeout=10)
self.active_connections[channel.guild.id] = vc self.active_connections[channel.guild.id] = vc
self.current_channel = channel self.current_channel = channel
@ -164,7 +159,14 @@ class VoiceManager:
self._monitoring_task = self.bot.loop.create_task(self._start_listening(vc)) self._monitoring_task = self.bot.loop.create_task(self._start_listening(vc))
except Exception as e: except Exception as e:
logger.error(f"Failed to join voice channel: {e}") error_str = str(e)
# Erreurs connues de Discord (permissions, region, etc.) → logger proprement
if "4017" in error_str or "403" in error_str or "Permission" in error_str:
logger.info(f"Connexion vocale impossible sur '{channel.name}': {error_str.split(chr(10))[0]}")
elif "4049" in error_str or "No audio" in error_str:
logger.debug(f"Voice connection issue on '{channel.name}': {error_str[:100]}")
else:
logger.warning(f"Échec connexion vocale '{channel.name}': {error_str[:200]}")
async def _start_listening(self, vc): async def _start_listening(self, vc):
"""Internal loop to capture and transcribe audio chunks.""" """Internal loop to capture and transcribe audio chunks."""

View file

@ -1,348 +0,0 @@
2026-04-25 19:14:39,564 - INFO - ACTION: {"timestamp": "2026-04-25T19:14:39.564412", "action": "CREATE_CHANNEL", "user": "_.radi0dem0n._", "guild": "Projet devenir riche", "params": {"thought": "User requested to create a channel named 'test'.", "action": "CREATE_CHANNEL", "response": "Création du salon 'test'…", "channel_name": "test", "channel_type": "textuel"}, "success": true, "error": null}
2026-06-13 15:23:06,604 - INFO - ACTION: {"timestamp": "2026-06-13T13:23:06.604924", "action": "CREATE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Analyser la requête", "action": "CREATE_CHANNEL", "channel_name": "test", "response": "Le salon \"test\" a été créé avec succès."}, "success": true, "error": null}
2026-06-13 15:25:12,801 - INFO - ACTION: {"timestamp": "2026-06-13T13:25:12.801407", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Supprimer tous les salons sauf #🌐・api-rest et #💬・général", "action": "DELETE_CHANNEL", "channel_name": "🔑・authentication"}, "success": true, "error": null}
2026-06-13 15:29:43,431 - INFO - ACTION: {"timestamp": "2026-06-13T13:29:43.431879", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Delete channel 🔍・audit", "action": "DELETE_CHANNEL", "channel_name": "🤋・audit"}, "success": true, "error": null}
2026-06-13 15:35:15,987 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:15.987673", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "🔌・audit"}, "success": true, "error": null}
2026-06-13 15:35:16,616 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:16.616185", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "🔗・e2e-tests"}, "success": true, "error": null}
2026-06-13 15:35:16,935 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:16.935804", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "🢉・ui-kit"}, "success": true, "error": null}
2026-06-13 15:35:17,156 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:17.156034", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "🎼・metrics"}, "success": true, "error": null}
2026-06-13 15:35:17,441 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:17.441031", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "🔳・docker"}, "success": true, "error": null}
2026-06-13 15:35:22,058 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:22.058691", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "💫・changelog"}, "success": true, "error": null}
2026-06-13 15:35:22,372 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:22.372347", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "⛱⃣・architecture"}, "success": true, "error": null}
2026-06-13 15:35:22,683 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:22.683675", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "⚔・vulnerabilities"}, "success": true, "error": null}
2026-06-13 15:35:22,912 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:22.912145", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "🎊・analytics"}, "success": true, "error": null}
2026-06-13 15:35:23,168 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:23.168622", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "⚡・performance"}, "success": true, "error": null}
2026-06-13 15:35:23,385 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:23.385080", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "⏲⃣・uptime"}, "success": true, "error": null}
2026-06-13 15:35:28,426 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:28.426475", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "🎂・random"}, "success": true, "error": null}
2026-06-13 15:35:28,691 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:28.691419", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "🐐・schemas"}, "success": true, "error": null}
2026-06-13 15:35:28,962 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:28.962701", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "💼・api-graphql"}, "success": true, "error": null}
2026-06-13 15:35:29,192 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:29.192857", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "ἳC・nosql"}, "success": true, "error": null}
2026-06-13 15:35:29,457 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:29.457513", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "🦐・css-scss"}, "success": true, "error": null}
2026-06-13 15:35:29,707 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:29.706946", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "ὩA・migrations"}, "success": true, "error": null}
2026-06-13 15:35:34,897 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:34.897650", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "🥛・bug-reports"}, "success": true, "error": null}
2026-06-13 15:35:35,173 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:35.173276", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "⛈・jenkins"}, "success": true, "error": null}
2026-06-13 15:35:35,504 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:35.504408", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "⚒⃣・miscellaneous"}, "success": true, "error": null}
2026-06-13 15:35:35,979 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:35.979169", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "💼・logs"}, "success": true, "error": null}
2026-06-13 15:35:36,473 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:36.473445", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "Ὂ0・suggestions"}, "success": true, "error": null}
2026-06-13 15:35:36,768 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:36.768245", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "⛈・github-actions"}, "success": true, "error": null}
2026-06-13 15:35:41,151 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:41.151434", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "🏥・announcements"}, "success": true, "error": null}
2026-06-13 15:35:41,356 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:41.356379", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "🗲・roadmap"}, "success": true, "error": null}
2026-06-13 15:35:41,826 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:41.826179", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "⚛・react"}, "success": true, "error": null}
2026-06-13 15:35:42,209 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:42.209667", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "♾・accessibility"}, "success": true, "error": null}
2026-06-13 15:35:42,549 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:42.549949", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "🍕・api-docs"}, "success": true, "error": null}
2026-06-13 15:35:42,847 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:42.847028", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "ὋE・backup-restore"}, "success": true, "error": null}
2026-06-13 15:35:47,494 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:47.494856", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "🤊・apireference"}, "success": true, "error": null}
2026-06-13 15:35:47,955 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:47.955252", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "⛆・microservices"}, "success": true, "error": null}
2026-06-13 15:35:48,194 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:48.194886", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "⚔⃣・unit-tests"}, "success": true, "error": null}
2026-06-13 15:35:48,501 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:48.501408", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "Ὠ0・deployments"}, "success": true, "error": null}
2026-06-13 15:35:48,734 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:48.734719", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "🏎・backlog"}, "success": true, "error": null}
2026-06-13 15:35:49,259 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:49.259567", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "ce-quil-peut-faire"}, "success": true, "error": null}
2026-06-13 15:35:53,930 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:53.930040", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "☀・kubernetes"}, "success": true, "error": null}
2026-06-13 15:35:54,254 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:54.254659", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "⚡・alerts"}, "success": true, "error": null}
2026-06-13 15:35:54,538 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:54.538191", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "🤨・incidents"}, "success": true, "error": null}
2026-06-13 15:35:54,790 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:54.790002", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "🍙・code-comments"}, "success": true, "error": null}
2026-06-13 15:35:54,992 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:54.992727", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "ὑ2・secrets"}, "success": true, "error": null}
2026-06-13 15:35:55,202 - INFO - ACTION: {"timestamp": "2026-06-13T13:35:55.202753", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "⚥⃣・reviews-retro"}, "success": true, "error": null}
2026-06-13 15:36:00,204 - INFO - ACTION: {"timestamp": "2026-06-13T13:36:00.204782", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "⚒・outilsdev"}, "success": true, "error": null}
2026-06-13 15:36:00,610 - INFO - ACTION: {"timestamp": "2026-06-13T13:36:00.610860", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "Test pipus (vocal)"}, "success": true, "error": null}
2026-06-13 15:36:00,964 - INFO - ACTION: {"timestamp": "2026-06-13T13:36:00.964373", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "🤛・general-dev"}, "success": true, "error": null}
2026-06-13 15:36:01,309 - INFO - ACTION: {"timestamp": "2026-06-13T13:36:01.309302", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "🍏・onboarding"}, "success": true, "error": null}
2026-06-13 15:36:01,548 - INFO - ACTION: {"timestamp": "2026-06-13T13:36:01.548885", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "❓・faq"}, "success": true, "error": null}
2026-06-13 15:36:01,879 - INFO - ACTION: {"timestamp": "2026-06-13T13:36:01.879089", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "ᾚ8・sprints"}, "success": true, "error": null}
2026-06-13 15:36:06,641 - INFO - ACTION: {"timestamp": "2026-06-13T13:36:06.641590", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "✅・qa-checklist"}, "success": true, "error": null}
2026-06-13 15:36:06,641 - ERROR - ACTION: {"timestamp": "2026-06-13T13:36:06.641792", "action": "DELETE CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE CHANNEL", "channel_name": "🏦・guides"}, "success": false, "error": "Unknown action: DELETE CHANNEL"}
2026-06-13 15:36:07,252 - INFO - ACTION: {"timestamp": "2026-06-13T13:36:07.252834", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "🄥・meetings"}, "success": true, "error": null}
2026-06-13 15:36:07,518 - INFO - ACTION: {"timestamp": "2026-06-13T13:36:07.518633", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "ἰ5・daily-standup"}, "success": true, "error": null}
2026-06-13 15:36:08,070 - INFO - ACTION: {"timestamp": "2026-06-13T13:36:08.070466", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "🭋・compliance"}, "success": true, "error": null}
2026-06-13 15:36:08,359 - INFO - ACTION: {"timestamp": "2026-06-13T13:36:08.359765", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "⛆・performance-tests"}, "success": true, "error": null}
2026-06-13 15:36:12,865 - INFO - ACTION: {"timestamp": "2026-06-13T13:36:12.865476", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_name": "὚0・vue-angular"}, "success": true, "error": null}
2026-06-13 15:44:54,123 - ERROR - ACTION: {"timestamp": "2026-06-13T13:44:54.123716", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "analysis", "action": "DELETE_CHANNEL", "channel_id": 1461107370775285907}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:44:54,434 - ERROR - ACTION: {"timestamp": "2026-06-13T13:44:54.434233", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107320908943544}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:44:54,747 - ERROR - ACTION: {"timestamp": "2026-06-13T13:44:54.747916", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107377662333150}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:44:55,022 - ERROR - ACTION: {"timestamp": "2026-06-13T13:44:55.022787", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107343671689279}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:44:55,367 - ERROR - ACTION: {"timestamp": "2026-06-13T13:44:55.367101", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107367113654437}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:44:55,835 - ERROR - ACTION: {"timestamp": "2026-06-13T13:44:55.835844", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107332078637188}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:00,507 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:00.507400", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107369315668254}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:01,033 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:01.033322", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107381457911940}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:01,604 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:01.604916", "action": "DELETE CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE CHANNEL", "channel_id": 1461107338755707117}, "success": false, "error": "Unknown action: DELETE CHANNEL"}
2026-06-13 15:45:01,858 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:01.858115", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107382473199637}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:02,235 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:02.235879", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107387552239868}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:02,502 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:02.502634", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107335131959363}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:06,798 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:06.798327", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107328458948638}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:07,063 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:07.063278", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107341247250463}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:07,394 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:07.394539", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107322431471812}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:07,661 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:07.661649", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107337652732119}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:08,201 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:08.201600", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107354035552411}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:08,744 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:08.744539", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107347383390221}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:13,177 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:13.177681", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1465459182793916601}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:13,480 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:13.479977", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107378899517534}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:13,787 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:13.787176", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107390673059945}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:14,123 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:14.123401", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107346255384649}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:14,420 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:14.420705", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1456784303995228344}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:14,701 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:14.701740", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107308435079199}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:19,481 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:19.481780", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107327381016629}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:19,846 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:19.846933", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107324209987779}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:20,164 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:20.164962", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107361493029100}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:20,708 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:20.708700", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107339548692716}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:21,123 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:21.123079", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1465740847109767257}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:21,406 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:21.406065", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107329226510388}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:25,983 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:25.983378", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107351175037041}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:26,245 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:26.245165", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107348771836201}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:26,731 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:26.731252", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107309500698689}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:27,006 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:27.006347", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107345097494730}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:27,537 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:27.537022", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107380430307513}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:27,854 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:27.853995", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107374688571638}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:32,359 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:32.359508", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107362554449981}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:32,673 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:32.673014", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107372339495033}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:33,166 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:33.166728", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107315402080523}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:33,497 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:33.497280", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1465740845088247828}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:34,309 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:34.309115", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1466003361496174643}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:34,626 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:34.626112", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107384993710205}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:34,910 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:34.910457", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1465740853766000765}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:39,699 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:39.699780", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107366358679736}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:39,939 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:39.939414", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107311043936474}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:40,494 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:40.494426", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107363816669496}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:40,834 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:40.834789", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107389142007962}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:41,100 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:41.100920", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107313359196414}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 15:45:41,348 - ERROR - ACTION: {"timestamp": "2026-06-13T13:45:41.348083", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"action": "DELETE_CHANNEL", "channel_id": 1461107373270630483}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 16:17:03,561 - INFO - ACTION: {"timestamp": "2026-06-13T14:17:03.561373", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "analyzing channels", "action": "DELETE_CHANNEL", "channel_id": "1461107370775285907"}, "success": true, "error": null}
2026-06-13 16:25:49,099 - INFO - ACTION: {"timestamp": "2026-06-13T14:25:49.099834", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 🔍・audit", "action": "DELETE_CHANNEL", "channel_id": "1461107370775285907"}, "success": true, "error": null}
2026-06-13 16:25:49,583 - INFO - ACTION: {"timestamp": "2026-06-13T14:25:49.583452", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 🎨・ui-kit", "action": "DELETE_CHANNEL", "channel_id": "1461107320908943544"}, "success": true, "error": null}
2026-06-13 16:25:50,324 - INFO - ACTION: {"timestamp": "2026-06-13T14:25:50.324922", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 📊・metrics", "action": "DELETE_CHANNEL", "channel_id": "1461107377662333150"}, "success": true, "error": null}
2026-06-13 16:25:50,777 - INFO - ACTION: {"timestamp": "2026-06-13T14:25:50.777452", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 🐳・docker", "action": "DELETE_CHANNEL", "channel_id": "1461107343671689279"}, "success": true, "error": null}
2026-06-13 16:25:51,493 - INFO - ACTION: {"timestamp": "2026-06-13T14:25:51.493942", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 📋・changelog", "action": "DELETE_CHANNEL", "channel_id": "1461107367113654437"}, "success": true, "error": null}
2026-06-13 16:25:51,940 - INFO - ACTION: {"timestamp": "2026-06-13T14:25:51.940937", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 🏗・architecture", "action": "DELETE_CHANNEL", "channel_id": "1461107332078637188"}, "success": true, "error": null}
2026-06-13 16:25:52,475 - INFO - ACTION: {"timestamp": "2026-06-13T14:25:52.475258", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 🛡・vulnerabilities", "action": "DELETE_CHANNEL", "channel_id": "1461107369315668254"}, "success": true, "error": null}
2026-06-13 16:25:57,265 - INFO - ACTION: {"timestamp": "2026-06-13T14:25:57.265585", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleteting channel 📈・analytics", "action": "DELETE_CHANNEL", "channel_id": "1461107381457911940"}, "success": true, "error": null}
2026-06-13 16:25:57,898 - INFO - ACTION: {"timestamp": "2026-06-13T14:25:57.898843", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel ⚡・performance", "action": "DELETE_CHANNEL", "channel_id": "1461107338755707117"}, "success": true, "error": null}
2026-06-13 16:25:58,521 - INFO - ACTION: {"timestamp": "2026-06-13T14:25:58.521299", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel ⏱・uptime", "action": "DELETE_CHANNEL", "channel_id": "1461107382473199637"}, "success": true, "error": null}
2026-06-13 16:25:59,209 - INFO - ACTION: {"timestamp": "2026-06-13T14:25:59.209618", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 🎲・random", "action": "DELETE_CHANNEL", "channel_id": "1461107387552239868"}, "success": true, "error": null}
2026-06-13 16:25:59,862 - INFO - ACTION: {"timestamp": "2026-06-13T14:25:59.862839", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 📐・schemas", "action": "DELETE_CHANNEL", "channel_id": "1461107335131959363"}, "success": true, "error": null}
2026-06-13 16:26:00,625 - INFO - ACTION: {"timestamp": "2026-06-13T14:26:00.625402", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 📊・api-graphql", "action": "DELETE_CHANNEL", "channel_id": "1461107328458948638"}, "success": true, "error": null}
2026-06-13 16:26:01,090 - INFO - ACTION: {"timestamp": "2026-06-13T14:26:01.090880", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 🍃・nosql", "action": "DELETE_CHANNEL", "channel_id": "1461107341247250463"}, "success": true, "error": null}
2026-06-13 16:26:01,545 - INFO - ACTION: {"timestamp": "2026-06-13T14:26:01.545714", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 🎀・css-scss", "action": "DELETE_CHANNEL", "channel_id": "1461107322431471812"}, "success": true, "error": null}
2026-06-13 16:26:02,172 - INFO - ACTION: {"timestamp": "2026-06-13T14:26:02.172858", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 🚚・migrations", "action": "DELETE_CHANNEL", "channel_id": "1461107337652732119"}, "success": true, "error": null}
2026-06-13 16:26:02,990 - INFO - ACTION: {"timestamp": "2026-06-13T14:26:02.990183", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 🐛・bug-reports", "action": "DELETE_CHANNEL", "channel_id": "1461107354035552411"}, "success": true, "error": null}
2026-06-13 16:26:07,892 - INFO - ACTION: {"timestamp": "2026-06-13T14:26:07.892046", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 🔨・jenkins", "action": "DELETE_CHANNEL", "channel_id": "1461107347383390221"}, "success": true, "error": null}
2026-06-13 16:26:08,311 - INFO - ACTION: {"timestamp": "2026-06-13T14:26:08.310976", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 🛠・miscellaneous", "action": "DELETE_CHANNEL", "channel_id": "1465459182793916601"}, "success": true, "error": null}
2026-06-13 16:26:08,943 - INFO - ACTION: {"timestamp": "2026-06-13T14:26:08.943593", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 📄・logs", "action": "DELETE_CHANNEL", "channel_id": "1461107378899517534"}, "success": true, "error": null}
2026-06-13 16:26:09,418 - INFO - ACTION: {"timestamp": "2026-06-13T14:26:09.418152", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 💡・suggestions", "action": "DELETE_CHANNEL", "channel_id": "1461107390673059945"}, "success": true, "error": null}
2026-06-13 16:26:10,110 - INFO - ACTION: {"timestamp": "2026-06-13T14:26:10.109969", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 🔄・github-actions", "action": "DELETE_CHANNEL", "channel_id": "1461107346255384649"}, "success": true, "error": null}
2026-06-13 16:26:10,543 - INFO - ACTION: {"timestamp": "2026-06-13T14:26:10.543208", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 📢・announcements", "action": "DELETE_CHANNEL", "channel_id": "1461107392141066292"}, "success": true, "error": null}
2026-06-13 16:26:11,470 - INFO - ACTION: {"timestamp": "2026-06-13T14:26:11.469971", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 📍・roadmap", "action": "DELETE_CHANNEL", "channel_id": "1461107308435079199"}, "success": true, "error": null}
2026-06-13 16:26:12,057 - INFO - ACTION: {"timestamp": "2026-06-13T14:26:12.057565", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel ♿・accessibility", "action": "DELETE_CHANNEL", "channel_id": "1461107324209987779"}, "success": true, "error": null}
2026-06-13 16:26:12,714 - INFO - ACTION: {"timestamp": "2026-06-13T14:26:12.714536", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 📖・api-docs", "action": "DELETE_CHANNEL", "channel_id": "1461107361493029100"}, "success": true, "error": null}
2026-06-13 16:26:17,357 - INFO - ACTION: {"timestamp": "2026-06-13T14:26:17.357844", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 💾・backup-restore", "action": "DELETE_CHANNEL", "channel_id": "1461107339548692716"}, "success": true, "error": null}
2026-06-13 16:26:17,928 - INFO - ACTION: {"timestamp": "2026-06-13T14:26:17.928219", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 📚・apireference", "action": "DELETE_CHANNEL", "channel_id": "1465740847109767257"}, "success": true, "error": null}
2026-06-13 16:26:18,413 - INFO - ACTION: {"timestamp": "2026-06-13T14:26:18.413908", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 🔀・microservices", "action": "DELETE_CHANNEL", "channel_id": "1461107329226510388"}, "success": true, "error": null}
2026-06-13 16:26:19,052 - INFO - ACTION: {"timestamp": "2026-06-13T14:26:19.052099", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 🧪・unit-tests", "action": "DELETE_CHANNEL", "channel_id": "1461107351175037041"}, "success": true, "error": null}
2026-06-13 16:26:19,592 - INFO - ACTION: {"timestamp": "2026-06-13T14:26:19.592482", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 🚀・deployments", "action": "DELETE_CHANNEL", "channel_id": "1461107348771836201"}, "success": true, "error": null}
2026-06-13 16:26:20,258 - INFO - ACTION: {"timestamp": "2026-06-13T14:26:20.258114", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 📦・backlog", "action": "DELETE_CHANNEL", "channel_id": "1461107309500698689"}, "success": true, "error": null}
2026-06-13 16:26:21,043 - INFO - ACTION: {"timestamp": "2026-06-13T14:26:21.043891", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel ☸・kubernetes", "action": "DELETE_CHANNEL", "channel_id": "1461107345097494730"}, "success": true, "error": null}
2026-06-13 16:26:21,506 - INFO - ACTION: {"timestamp": "2026-06-13T14:26:21.506198", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel ⚠・alerts", "action": "DELETE_CHANNEL", "channel_id": "1461107380430307513"}, "success": true, "error": null}
2026-06-13 16:26:22,359 - INFO - ACTION: {"timestamp": "2026-06-13T14:26:22.359121", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 🚨・incidents", "action": "DELETE_CHANNEL", "channel_id": "1461107374688571638"}, "success": true, "error": null}
2026-06-13 16:26:22,985 - INFO - ACTION: {"timestamp": "2026-06-13T14:26:22.985216", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 💬・code-comments", "action": "DELETE_CHANNEL", "channel_id": "1461107362554449981"}, "success": true, "error": null}
2026-06-13 16:26:27,837 - INFO - ACTION: {"timestamp": "2026-06-13T14:26:27.837414", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 🔒・secrets", "action": "DELETE_CHANNEL", "channel_id": "1461107372339495033"}, "success": true, "error": null}
2026-06-13 16:26:28,407 - INFO - ACTION: {"timestamp": "2026-06-13T14:26:28.407702", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 🔍・reviews-retro", "action": "DELETE_CHANNEL", "channel_id": "1461107315402080523"}, "success": true, "error": null}
2026-06-13 16:26:28,812 - INFO - ACTION: {"timestamp": "2026-06-13T14:26:28.812830", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting voice Test pipus", "action": "DELETE_CHANNEL", "channel_id": "1466003361496174643"}, "success": true, "error": null}
2026-06-13 16:26:29,298 - INFO - ACTION: {"timestamp": "2026-06-13T14:26:29.298836", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting text 💻・general-dev", "action": "DELETE_CHANNEL", "channel_id": "1461107384993710205"}, "success": true, "error": null}
2026-06-13 16:26:30,072 - INFO - ACTION: {"timestamp": "2026-06-13T14:26:30.072142", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 🗂・onboarding", "action": "DELETE_CHANNEL", "channel_id": "1465740853766000765"}, "success": true, "error": null}
2026-06-13 16:26:30,619 - INFO - ACTION: {"timestamp": "2026-06-13T14:26:30.619719", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel ❓・faq", "action": "DELETE_CHANNEL", "channel_id": "1461107366358679736"}, "success": true, "error": null}
2026-06-13 16:26:31,348 - INFO - ACTION: {"timestamp": "2026-06-13T14:26:31.348699", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 🏃・sprints", "action": "DELETE_CHANNEL", "channel_id": "1461107311043936474"}, "success": true, "error": null}
2026-06-13 16:26:32,160 - INFO - ACTION: {"timestamp": "2026-06-13T14:26:32.160222", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 📑・guides", "action": "DELETE_CHANNEL", "channel_id": "1461107363816669496"}, "success": true, "error": null}
2026-06-13 16:26:32,656 - INFO - ACTION: {"timestamp": "2026-06-13T14:26:32.656240", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 📅・meetings", "action": "DELETE_CHANNEL", "channel_id": "1461107389142007962"}, "success": true, "error": null}
2026-06-13 16:26:37,315 - INFO - ACTION: {"timestamp": "2026-06-13T14:26:37.315634", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 🌅・daily-standup", "action": "DELETE_CHANNEL", "channel_id": "1461107313359196414"}, "success": true, "error": null}
2026-06-13 16:26:38,009 - INFO - ACTION: {"timestamp": "2026-06-13T14:26:38.009301", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 📜・compliance", "action": "DELETE_CHANNEL", "channel_id": "1461107373270630483"}, "success": true, "error": null}
2026-06-13 16:26:38,477 - INFO - ACTION: {"timestamp": "2026-06-13T14:26:38.477889", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 📈・performance-tests", "action": "DELETE_CHANNEL", "channel_id": "1461107358477582580"}, "success": true, "error": null}
2026-06-13 16:26:38,991 - INFO - ACTION: {"timestamp": "2026-06-13T14:26:38.991397", "action": "DELETE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting channel 🅰・vue-angular", "action": "DELETE_CHANNEL", "channel_id": "1461107319088873740"}, "success": true, "error": null}
2026-06-13 16:32:00,596 - ERROR - ACTION: {"timestamp": "2026-06-13T14:32:00.596866", "action": "DELETE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "deleting category ⚙️ | Développement Backend", "action": "DELETE_CATEGORY", "category_id": "1461107292660568084"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 16:32:00,911 - ERROR - ACTION: {"timestamp": "2026-06-13T14:32:00.911698", "action": "DELETE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "deleting category 💬 | Communication", "action": "DELETE_CATEGORY", "category_id": "1465740850465341604"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 16:32:01,414 - ERROR - ACTION: {"timestamp": "2026-06-13T14:32:01.414142", "action": "DELETE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "deleting category 📚 | Documentation", "action": "DELETE_CATEGORY", "category_id": "1461107299048226889"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 16:32:01,697 - ERROR - ACTION: {"timestamp": "2026-06-13T14:32:01.697788", "action": "DELETE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "deleting category 🎨 | Développement Frontend", "action": "DELETE_CATEGORY", "category_id": "1461107291053887712"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 16:32:02,327 - ERROR - ACTION: {"timestamp": "2026-06-13T14:32:02.326960", "action": "DELETE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "deleting category 🚀 | DevOps & CI/CD", "action": "DELETE_CATEGORY", "category_id": "1461107295814684703"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 16:32:02,809 - ERROR - ACTION: {"timestamp": "2026-06-13T14:32:02.809491", "action": "DELETE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "deleting category 🔐 | Sécurité", "action": "DELETE_CATEGORY", "category_id": "1461107300696723518"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 16:32:03,082 - ERROR - ACTION: {"timestamp": "2026-06-13T14:32:03.082622", "action": "DELETE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "deleting category 📈 | Monitoring & Analytics", "action": "DELETE_CATEGORY", "category_id": "1461107301715804221"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 16:32:08,041 - ERROR - ACTION: {"timestamp": "2026-06-13T14:32:08.041578", "action": "DELETE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "deleting category Vocaux", "action": "DELETE_CATEGORY", "category_id": "1466003189907193876"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 16:32:08,365 - ERROR - ACTION: {"timestamp": "2026-06-13T14:32:08.365944", "action": "DELETE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "deleting category 💬 | Collaboration", "action": "DELETE_CATEGORY", "category_id": "1461107302701469932"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 16:32:08,618 - ERROR - ACTION: {"timestamp": "2026-06-13T14:32:08.618354", "action": "DELETE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "deleting category 🗃️ | Base de Données", "action": "DELETE_CATEGORY", "category_id": "1461107295017635932"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 16:32:08,938 - ERROR - ACTION: {"timestamp": "2026-06-13T14:32:08.938258", "action": "DELETE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "deleting category 🧪 | Qualité & Tests", "action": "DELETE_CATEGORY", "category_id": "1461107297446269130"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 16:32:58,069 - ERROR - ACTION: {"timestamp": "2026-06-13T14:32:58.069659", "action": "WARN", "user": "pipusfr", "guild": "BETA", "params": {"thought": "analysis", "action": "WARN", "response": "Votre commentaire contient de la haine envers un groupe protégé, ce qui est contraire aux règles communautaires du serveur."}, "success": false, "error": "'Superviseur' object has no attribute 'ollama_api_url'"}
2026-06-13 16:34:17,604 - ERROR - ACTION: {"timestamp": "2026-06-13T14:34:17.603968", "action": "DELETE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting ⚙️ | Développement Backend", "action": "DELETE_CATEGORY", "category_id": "1461107292660568084"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 16:34:18,031 - ERROR - ACTION: {"timestamp": "2026-06-13T14:34:18.031450", "action": "DELETE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting 💬 | Communication", "action": "DELETE_CATEGORY", "category_id": "1465740850465341604"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 16:34:18,293 - ERROR - ACTION: {"timestamp": "2026-06-13T14:34:18.293384", "action": "DELETE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting 📚 | Documentation", "action": "DELETE_CATEGORY", "category_id": "1461107299048226889"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 16:34:18,794 - ERROR - ACTION: {"timestamp": "2026-06-13T14:34:18.794802", "action": "DELETE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting 🎨 | Développement Frontend", "action": "DELETE_CATEGORY", "category_id": "1461107291053887712"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 16:34:19,148 - ERROR - ACTION: {"timestamp": "2026-06-13T14:34:19.148324", "action": "DELETE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting 🚀 | DevOps & CI/CD", "action": "DELETE_CATEGORY", "category_id": "1461107295814684703"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 16:34:19,494 - ERROR - ACTION: {"timestamp": "2026-06-13T14:34:19.494177", "action": "DELETE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting 🔐 | Sécurité", "action": "DELETE_CATEGORY", "category_id": "1461107300696723518"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 16:34:23,927 - ERROR - ACTION: {"timestamp": "2026-06-13T14:34:23.927547", "action": "DELETE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting 📈 | Monitoring & Analytics", "action": "DELETE_CATEGORY", "category_id": "1461107301715804221"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 16:34:24,242 - ERROR - ACTION: {"timestamp": "2026-06-13T14:34:24.242495", "action": "DELETE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting Vocaux category", "action": "DELETE_CATEGORY", "category_id": "1466003189907193876"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 16:34:24,643 - ERROR - ACTION: {"timestamp": "2026-06-13T14:34:24.642993", "action": "DELETE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting 💬 | Collaboration", "action": "DELETE_CATEGORY", "category_id": "1461107302701469932"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 16:34:25,048 - ERROR - ACTION: {"timestamp": "2026-06-13T14:34:25.048495", "action": "DELETE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting 🗃️ | Base de Données", "action": "DELETE_CATEGORY", "category_id": "1461107295017635932"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 16:34:25,361 - ERROR - ACTION: {"timestamp": "2026-06-13T14:34:25.361597", "action": "DELETE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting 🧪 | Qualité & Tests", "action": "DELETE_CATEGORY", "category_id": "1461107297446269130"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 16:36:58,684 - INFO - ACTION: {"timestamp": "2026-06-13T14:36:58.684310", "action": "DELETE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting category", "action": "DELETE_CATEGORY", "category_id": "1461107291053887712"}, "success": true, "error": null}
2026-06-13 16:36:59,396 - INFO - ACTION: {"timestamp": "2026-06-13T14:36:59.395980", "action": "DELETE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting category", "action": "DELETE_CATEGORY", "category_id": "1461107292660568084"}, "success": true, "error": null}
2026-06-13 16:36:59,887 - INFO - ACTION: {"timestamp": "2026-06-13T14:36:59.886974", "action": "DELETE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting category", "action": "DELETE_CATEGORY", "category_id": "1461107295017635932"}, "success": true, "error": null}
2026-06-13 16:37:00,694 - INFO - ACTION: {"timestamp": "2026-06-13T14:37:00.694289", "action": "DELETE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting category", "action": "DELETE_CATEGORY", "category_id": "1461107295814684703"}, "success": true, "error": null}
2026-06-13 16:37:01,404 - INFO - ACTION: {"timestamp": "2026-06-13T14:37:01.404352", "action": "DELETE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting category", "action": "DELETE_CATEGORY", "category_id": "1461107297446269130"}, "success": true, "error": null}
2026-06-13 16:37:02,167 - INFO - ACTION: {"timestamp": "2026-06-13T14:37:02.167168", "action": "DELETE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting category", "action": "DELETE_CATEGORY", "category_id": "1461107299048226889"}, "success": true, "error": null}
2026-06-13 16:37:02,805 - INFO - ACTION: {"timestamp": "2026-06-13T14:37:02.805834", "action": "DELETE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting category", "action": "DELETE_CATEGORY", "category_id": "1461107300696723518"}, "success": true, "error": null}
2026-06-13 16:37:03,461 - INFO - ACTION: {"timestamp": "2026-06-13T14:37:03.461046", "action": "DELETE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting category", "action": "DELETE_CATEGORY", "category_id": "1461107301715804221"}, "success": true, "error": null}
2026-06-13 16:37:03,914 - INFO - ACTION: {"timestamp": "2026-06-13T14:37:03.914550", "action": "DELETE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting category", "action": "DELETE_CATEGORY", "category_id": "1461107302701469932"}, "success": true, "error": null}
2026-06-13 16:37:08,715 - INFO - ACTION: {"timestamp": "2026-06-13T14:37:08.715273", "action": "DELETE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting category", "action": "DELETE_CATEGORY", "category_id": "1465740850465341604"}, "success": true, "error": null}
2026-06-13 16:37:09,167 - INFO - ACTION: {"timestamp": "2026-06-13T14:37:09.167802", "action": "DELETE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Deleting category", "action": "DELETE_CATEGORY", "category_id": "1466003189907193876"}, "success": true, "error": null}
2026-06-13 16:43:49,858 - ERROR - ACTION: {"timestamp": "2026-06-13T14:43:49.858939", "action": "WARN", "user": "pipusfr", "guild": "BETA", "params": {"thought": "analysis", "action": "WARN", "member": "@! ~ 𝒜𝓁𝓈𝓉𝓸r ~", "reason": "sexism", "response": "⚠️ J'ai averti @! ~ 𝒜𝓁𝓇~ pour son comportement sexiste."}, "success": false, "error": "'NoneType' object has no attribute 'startswith'"}
2026-06-13 19:32:50,948 - INFO - ACTION: {"timestamp": "2026-06-13T17:32:50.948797", "action": "CREATE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Créer la catégorie Annonces", "action": "CREATE_CATEGORY", "category_name": "📢 Annonces"}, "success": true, "error": null}
2026-06-13 19:32:51,608 - INFO - ACTION: {"timestamp": "2026-06-13T17:32:51.608802", "action": "CREATE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Ajouter le salon annonces dans 📢 Annonces", "action": "CREATE_CHANNEL", "channel_name": "annonces", "type": "text", "parent_category": "📢 Annonces"}, "success": true, "error": null}
2026-06-13 19:32:52,085 - INFO - ACTION: {"timestamp": "2026-06-13T17:32:52.085578", "action": "CREATE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Ajouter le salon événements dans 📢 Annonces", "action": "CREATE_CHANNEL", "channel_name": "événements", "type": "text", "parent_category": "📢 Annonces"}, "success": true, "error": null}
2026-06-13 19:32:52,481 - INFO - ACTION: {"timestamp": "2026-06-13T17:32:52.481251", "action": "CREATE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Ajouter le salon règlement dans 📢 Annonces", "action": "CREATE_CHANNEL", "channel_name": "règlement", "type": "text", "parent_category": "📢 Annonces"}, "success": true, "error": null}
2026-06-13 19:32:52,980 - INFO - ACTION: {"timestamp": "2026-06-13T17:32:52.980224", "action": "CREATE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Créer la catégorie Général", "action": "CREATE_CATEGORY", "category_name": "💬 Général"}, "success": true, "error": null}
2026-06-13 19:32:53,794 - INFO - ACTION: {"timestamp": "2026-06-13T17:32:53.794069", "action": "CREATE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Ajouter le salon général-chat dans 💬 Général", "action": "CREATE_CHANNEL", "channel_name": "général-chat", "type": "text", "parent_category": "💬 Général"}, "success": true, "error": null}
2026-06-13 19:32:54,371 - INFO - ACTION: {"timestamp": "2026-06-13T17:32:54.371723", "action": "CREATE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Ajouter le salon présentations dans 💬 Général", "action": "CREATE_CHANNEL", "channel_name": "présentations", "type": "text", "parent_category": "💬 Général"}, "success": true, "error": null}
2026-06-13 19:32:54,779 - INFO - ACTION: {"timestamp": "2026-06-13T17:32:54.779728", "action": "CREATE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Ajouter le salon off-topic dans 💬 Général", "action": "CREATE_CHANNEL", "channel_name": "off-topic", "type": "text", "parent_category": "💬 Général"}, "success": true, "error": null}
2026-06-13 19:32:59,685 - INFO - ACTION: {"timestamp": "2026-06-13T17:32:59.685019", "action": "CREATE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Créer la catégorie Guides & Ressources", "action": "CREATE_CATEGORY", "category_name": "🛠️ Guides & Ressources"}, "success": true, "error": null}
2026-06-13 19:33:00,193 - INFO - ACTION: {"timestamp": "2026-06-13T17:33:00.193523", "action": "CREATE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Ajouter le salon recettes-de-crafting dans 🛠️ Guides & Ressources", "action": "CREATE_CHANNEL", "channel_name": "recettes-de-crafting", "type": "text", "parent_category": "🛠️ Guides & Ressources"}, "success": true, "error": null}
2026-06-13 19:33:00,983 - INFO - ACTION: {"timestamp": "2026-06-13T17:33:00.983029", "action": "CREATE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Ajouter le salon build-tips dans 🛠️ Guides & Ressources", "action": "CREATE_CHANNEL", "channel_name": "build-tips", "type": "text", "parent_category": "🛠️ Guides & Ressources"}, "success": true, "error": null}
2026-06-13 19:33:01,588 - INFO - ACTION: {"timestamp": "2026-06-13T17:33:01.588431", "action": "CREATE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Ajouter le salon mods-et-plugins dans 🛠️ Guides & Ressources", "action": "CREATE_CHANNEL", "channel_name": "mods-et-plugins", "type": "text", "parent_category": "🛠️ Guides & Ressources"}, "success": true, "error": null}
2026-06-13 19:33:02,040 - INFO - ACTION: {"timestamp": "2026-06-13T17:33:02.040914", "action": "CREATE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Ajouter le salon faq-minecraft dans 🛠️ Guides & Ressources", "action": "CREATE_CHANNEL", "channel_name": "faq-minecraft", "type": "text", "parent_category": "🛠️ Guides & Ressources"}, "success": true, "error": null}
2026-06-13 19:33:02,647 - INFO - ACTION: {"timestamp": "2026-06-13T17:33:02.647704", "action": "CREATE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Créer la catégorie Serveurs", "action": "CREATE_CATEGORY", "category_name": "🏰 Serveurs"}, "success": true, "error": null}
2026-06-13 19:33:03,121 - INFO - ACTION: {"timestamp": "2026-06-13T17:33:03.121194", "action": "CREATE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Ajouter le salon serveur-1-info dans 🏰 Serveurs", "action": "CREATE_CHANNEL", "channel_name": "serveur-1-info", "type": "text", "parent_category": "🏰 Serveurs"}, "success": true, "error": null}
2026-06-13 19:33:03,630 - INFO - ACTION: {"timestamp": "2026-06-13T17:33:03.630931", "action": "CREATE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Ajouter le salon serveur-2-info dans 🏰 Serveurs", "action": "CREATE_CHANNEL", "channel_name": "serveur-2-info", "type": "text", "parent_category": "🏰 Serveurs"}, "success": true, "error": null}
2026-06-13 19:33:04,152 - INFO - ACTION: {"timestamp": "2026-06-13T17:33:04.152584", "action": "CREATE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Ajouter le salon serveur-3-info dans 🏰 Serveurs", "action": "CREATE_CHANNEL", "channel_name": "serveur-3-info", "type": "text", "parent_category": "🏰 Serveurs"}, "success": true, "error": null}
2026-06-13 19:33:09,105 - INFO - ACTION: {"timestamp": "2026-06-13T17:33:09.105244", "action": "CREATE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Créer la catégorie Jeux & Compétitions", "action": "CREATE_CATEGORY", "category_name": "🎮 Jeux & Compétitions"}, "success": true, "error": null}
2026-06-13 19:33:09,643 - INFO - ACTION: {"timestamp": "2026-06-13T17:33:09.643064", "action": "CREATE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Ajouter le salon tournament-rules dans 🎮 Jeux & Compétitions", "action": "CREATE_CHANNEL", "channel_name": "tournament-rules", "type": "text", "parent_category": "🎮 Jeux & Compétitions"}, "success": true, "error": null}
2026-06-13 19:33:10,203 - INFO - ACTION: {"timestamp": "2026-06-13T17:33:10.203528", "action": "CREATE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Ajouter le salon match-schedule dans 🎮 Jeux & Compétitions", "action": "CREATE_CHANNEL", "channel_name": "match-schedule", "type": "text", "parent_category": "🎮 Jeux & Compétitions"}, "success": true, "error": null}
2026-06-13 19:33:10,759 - INFO - ACTION: {"timestamp": "2026-06-13T17:33:10.759727", "action": "CREATE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Ajouter le salon leaderboards dans 🎮 Jeux & Compétitions", "action": "CREATE_CHANNEL", "channel_name": "leaderboards", "type": "text", "parent_category": "🎮 Jeux & Compétitions"}, "success": true, "error": null}
2026-06-13 19:33:11,300 - INFO - ACTION: {"timestamp": "2026-06-13T17:33:11.299968", "action": "CREATE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Créer la catégorie Support Technique", "action": "CREATE_CATEGORY", "category_name": "🔧 Support Technique"}, "success": true, "error": null}
2026-06-13 19:33:11,758 - INFO - ACTION: {"timestamp": "2026-06-13T17:33:11.758015", "action": "CREATE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Ajouter le salon questions-techniques dans 🔧 Support Technique", "action": "CREATE_CHANNEL", "channel_name": "questions-techniques", "type": "text", "parent_category": "🔧 Support Technique"}, "success": true, "error": null}
2026-06-13 19:33:12,504 - INFO - ACTION: {"timestamp": "2026-06-13T17:33:12.504337", "action": "CREATE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Ajouter le salon bug-report dans 🔧 Support Technique", "action": "CREATE_CHANNEL", "channel_name": "bug-report", "type": "text", "parent_category": "🔧 Support Technique"}, "success": true, "error": null}
2026-06-13 19:33:12,938 - INFO - ACTION: {"timestamp": "2026-06-13T17:33:12.938165", "action": "CREATE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Ajouter le salon suggestions dans 🔧 Support Technique", "action": "CREATE_CHANNEL", "channel_name": "suggestions", "type": "text", "parent_category": "🔧 Support Technique"}, "success": true, "error": null}
2026-06-13 19:33:17,664 - INFO - ACTION: {"timestamp": "2026-06-13T17:33:17.664388", "action": "CREATE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Créer la catégorie Media", "action": "CREATE_CATEGORY", "category_name": "📸 Media"}, "success": true, "error": null}
2026-06-13 19:33:18,176 - INFO - ACTION: {"timestamp": "2026-06-13T17:33:18.176840", "action": "CREATE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Ajouter le salon screenshots dans 📸 Media", "action": "CREATE_CHANNEL", "channel_name": "screenshots", "type": "text", "parent_category": "📸 Media"}, "success": true, "error": null}
2026-06-13 19:33:18,909 - INFO - ACTION: {"timestamp": "2026-06-13T17:33:18.909138", "action": "CREATE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Ajouter le salon videos dans 📸 Media", "action": "CREATE_CHANNEL", "channel_name": "videos", "type": "text", "parent_category": "📸 Media"}, "success": true, "error": null}
2026-06-13 19:33:19,611 - INFO - ACTION: {"timestamp": "2026-06-13T17:33:19.611270", "action": "CREATE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Ajouter le salon streamers dans 📸 Media", "action": "CREATE_CHANNEL", "channel_name": "streamers", "type": "text", "parent_category": "📸 Media"}, "success": true, "error": null}
2026-06-13 19:33:20,047 - INFO - ACTION: {"timestamp": "2026-06-13T17:33:20.047910", "action": "CREATE_CATEGORY", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Créer la catégorie Voice (optionnel)", "action": "CREATE_CATEGORY", "category_name": "🎤 Voice"}, "success": true, "error": null}
2026-06-13 19:33:20,650 - INFO - ACTION: {"timestamp": "2026-06-13T17:33:20.650552", "action": "CREATE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Ajouter le salon general-voice en voix dans 🎤 Voice", "action": "CREATE_CHANNEL", "channel_name": "general-voice", "type": "voice", "parent_category": "🎤 Voice"}, "success": true, "error": null}
2026-06-13 19:33:21,107 - INFO - ACTION: {"timestamp": "2026-06-13T17:33:21.107332", "action": "CREATE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Ajouter le salon build-chat en voix dans 🎤 Voice", "action": "CREATE_CHANNEL", "channel_name": "build-chat", "type": "voice", "parent_category": "🎤 Voice"}, "success": true, "error": null}
2026-06-13 19:33:21,600 - INFO - ACTION: {"timestamp": "2026-06-13T17:33:21.600232", "action": "CREATE_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Ajouter le salon pvp-arena en voix dans 🎤 Voice", "action": "CREATE_CHANNEL", "channel_name": "pvp-arena", "type": "voice", "parent_category": "🎤 Voice"}, "success": true, "error": null}
2026-06-13 19:36:21,082 - ERROR - ACTION: {"timestamp": "2026-06-13T17:36:21.082357", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "déplacer 💬・général dans la catégorie Général", "action": "MODIFY_CHANNEL", "channel_id": "1456784303995228344", "category_id": "1515408630839509114"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 19:36:21,579 - ERROR - ACTION: {"timestamp": "2026-06-13T17:36:21.579595", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "déplacer annonces vers Annonces", "action": "MODIFY_CHANNEL", "channel_id": "1515408624627744859", "category_id": "1515408621020905483"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 19:36:22,018 - ERROR - ACTION: {"timestamp": "2026-06-13T17:36:22.018705", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "déplacer événements vers Annonnes", "action": "MODIFY_CHANNEL", "channel_id": "1515408627538853908", "category_id": "1515408621020905483"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 19:36:22,518 - ERROR - ACTION: {"timestamp": "2026-06-13T17:36:22.518284", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "déplacer règlement vers Annonces", "action": "MODIFY_CHANNEL", "channel_id": "1515408629438877776", "category_id": "1515408621020905483"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 19:36:23,044 - ERROR - ACTION: {"timestamp": "2026-06-13T17:36:23.043972", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "déplacer général-chat dans Général", "action": "MODIFY_CHANNEL", "channel_id": "1515408633381261584", "category_id": "1515408630839509114"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 19:36:23,399 - ERROR - ACTION: {"timestamp": "2026-06-13T17:36:23.399853", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "déplacer présentations vers Général", "action": "MODIFY_CHANNEL", "channel_id": "1515408636787032135", "category_id": "1515408630839509114"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 19:36:23,639 - ERROR - ACTION: {"timestamp": "2026-06-13T17:36:23.639594", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "déplacer off-topic dans Général", "action": "MODIFY_CHANNEL", "channel_id": "1515408639006081065", "category_id": "1515408630839509114"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 19:36:28,369 - ERROR - ACTION: {"timestamp": "2026-06-13T17:36:28.369572", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "déplacer recettes-de-crafting vers Guides & Ressources", "action": "MODIFY_CHANNEL", "channel_id": "1515408661361459240", "category_id": "1515408640750915634"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 19:36:28,647 - ERROR - ACTION: {"timestamp": "2026-06-13T17:36:28.647611", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "déplacer build-tips dans Guides & Ressources", "action": "MODIFY_CHANNEL", "channel_id": "1515408663152693321", "category_id": "1515408640750915634"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 19:36:29,095 - ERROR - ACTION: {"timestamp": "2026-06-13T17:36:29.095182", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "déplacer mods-et-plugins vers Guides & Ressources", "action": "MODIFY_CHANNEL", "channel_id": "1515408666956796086", "category_id": "1515408640750915634"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 19:36:29,424 - ERROR - ACTION: {"timestamp": "2026-06-13T17:36:29.424077", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "déplacer faq-minecraft dans Guides & Ressources", "action": "MODIFY_CHANNEL", "channel_id": "1515408669473378344", "category_id": "1515408640750915634"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 19:36:29,671 - ERROR - ACTION: {"timestamp": "2026-06-13T17:36:29.671764", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "déplacer serveur-1-info vers Serveurs", "action": "MODIFY_CHANNEL", "channel_id": "1515408673755889815", "category_id": "1515408671377588324"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 19:36:29,998 - ERROR - ACTION: {"timestamp": "2026-06-13T17:36:29.998647", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "déplacer serveur-2-info dans Serveurs", "action": "MODIFY_CHANNEL", "channel_id": "1515408675630743665", "category_id": "1515408671377588324"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 19:36:34,620 - ERROR - ACTION: {"timestamp": "2026-06-13T17:36:34.620347", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "déplacer serveur-3-info vers Serveurs", "action": "MODIFY_CHANNEL", "channel_id": "1515408678101057707", "category_id": "1515408671377588324"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 19:36:34,870 - ERROR - ACTION: {"timestamp": "2026-06-13T17:36:34.870176", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "déplacer tournament-rules dans Jeux & Compétitions", "action": "MODIFY_CHANNEL", "channel_id": "1515408700876128287", "category_id": "1515408680391278602"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 19:36:35,287 - ERROR - ACTION: {"timestamp": "2026-06-13T17:36:35.287508", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "déplacer match-schedule vers Jeux & Compétitions", "action": "MODIFY_CHANNEL", "channel_id": "1515408703287722064", "category_id": "1515408680391278602"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 19:36:35,551 - ERROR - ACTION: {"timestamp": "2026-06-13T17:36:35.551925", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "déplacer leaderboards dans Jeux & Compétitions", "action": "MODIFY_CHANNEL", "channel_id": "1515408705414500534", "category_id": "1515408680391278602"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 19:36:35,936 - ERROR - ACTION: {"timestamp": "2026-06-13T17:36:35.936326", "action": "MODIFY CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "déplacer questions-techniques vers Support Technique", "action": "MODIFY CHANNEL", "channel_id": "1515408710032298065", "category_id": "1515408707507196050"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 19:36:36,296 - ERROR - ACTION: {"timestamp": "2026-06-13T17:36:36.296546", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "déplacer bug-report dans Support Technique", "action": "MODIFY_CHANNEL", "channel_id": "1515408712007684167", "category_id": "1515408707507196050"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 19:36:40,930 - ERROR - ACTION: {"timestamp": "2026-06-13T17:36:40.930387", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "déplacer suggestions vers Support Technique", "action": "MODIFY_CHANNEL", "channel_id": "1515408714922852535", "category_id": "1515408707507196050"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 19:36:41,312 - ERROR - ACTION: {"timestamp": "2026-06-13T17:36:41.312831", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "déplacer screenshots dans Media", "action": "MODIFY_CHANNEL", "channel_id": "1515408736565592178", "category_id": "1515408716948705300"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 19:36:41,568 - ERROR - ACTION: {"timestamp": "2026-06-13T17:36:41.568110", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "déplacer videos vers Media", "action": "MODIFY_CHANNEL", "channel_id": "1515408739040235681", "category_id": "1515408716948705300"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 19:36:41,996 - ERROR - ACTION: {"timestamp": "2026-06-13T17:36:41.995981", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "déplacer streamers dans Media", "action": "MODIFY_CHANNEL", "channel_id": "1515408741980438620", "category_id": "1515408716948705300"}, "success": false, "error": "'NoneType' object has no attribute 'casefold'"}
2026-06-13 19:53:02,778 - ERROR - ACTION: {"timestamp": "2026-06-13T17:53:02.778449", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "analyze", "action": "MODIFY_CHANNEL", "channel_id": 1515408712007684167, "new_category_id": 1515408707507196050}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 19:53:03,203 - ERROR - ACTION: {"timestamp": "2026-06-13T17:53:03.203815", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "analyze", "action": "MODIFY_CHANNEL", "channel_id": 1515408736565592178, "new_category_id": 1515408716948705300}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 19:53:03,725 - ERROR - ACTION: {"timestamp": "2026-06-13T17:53:03.725719", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "analyze", "action": "MODIFY_CHANNEL", "channel_id": 1515408678101057707, "new_category_id": 1515408671377588324}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 19:53:04,971 - ERROR - ACTION: {"timestamp": "2026-06-13T17:53:04.971198", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "analyze", "action": "MODIFY_CHANNEL", "channel_id": 1515408673755889815, "new_category_id": 1515408671377588324}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 19:53:05,554 - ERROR - ACTION: {"timestamp": "2026-06-13T17:53:05.554073", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "analyze", "action": "MODIFY_CHANNEL", "channel_id": 1515408633381261584, "new_category_id": 1515408630839509114}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 19:53:07,597 - ERROR - ACTION: {"timestamp": "2026-06-13T17:53:07.597101", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "analyze", "action": "MODIFY_CHANNEL", "channel_id": 1515408666956796086, "new_category_id": 1515408640750915634}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 19:53:08,023 - ERROR - ACTION: {"timestamp": "2026-06-13T17:53:08.023012", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "analyze", "action": "MODIFY_CHANNEL", "channel_id": 1515408627538853908, "new_category_id": 1515408621020905483}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 19:53:08,461 - ERROR - ACTION: {"timestamp": "2026-06-13T17:53:08.461560", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "analyze", "action": "MODIFY_CHANNEL", "channel_id": 1515408700876128287, "new_category_id": 1515408680391278602}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 19:53:08,975 - ERROR - ACTION: {"timestamp": "2026-06-13T17:53:08.975549", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "analyze", "action": "MODIFY_CHANNEL", "channel_id": 1515408669473378344, "new_category_id": 1515408640750915634}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 19:53:09,499 - ERROR - ACTION: {"timestamp": "2026-06-13T17:53:09.499606", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "analyze", "action": "MODIFY_CHANNEL", "channel_id": 1515408663152693321, "new_category_id": 1515408640750915634}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 19:53:09,980 - ERROR - ACTION: {"timestamp": "2026-06-13T17:53:09.980189", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "analyze", "action": "MODIFY_CHANNEL", "channel_id": 1461107327381016629, "new_category_id": 1515408707507196050}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 19:53:10,452 - ERROR - ACTION: {"timestamp": "2026-06-13T17:53:10.452829", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "analyze", "action": "MODIFY_CHANNEL", "channel_id": 1515408751220490340, "new_category_id": 1515408680391278602}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 19:53:15,316 - ERROR - ACTION: {"timestamp": "2026-06-13T17:53:15.316956", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "analyze", "action": "MODIFY_CHANNEL", "channel_id": 1515408739040235681, "new_category_id": 1515408716948705300}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 19:53:15,737 - ERROR - ACTION: {"timestamp": "2026-06-13T17:53:15.737399", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "analyze", "action": "MODIFY_CHANNEL", "channel_id": 1515408749261750435, "new_category_id": 1515408640750915634}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 19:53:16,366 - ERROR - ACTION: {"timestamp": "2026-06-13T17:53:16.366462", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "analyze", "action": "MODIFY_CHANNEL", "channel_id": 1515408624627744859, "new_category_id": 1515408621020905483}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 19:53:16,968 - ERROR - ACTION: {"timestamp": "2026-06-13T17:53:16.967993", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "analyze", "action": "MODIFY_CHANNEL", "channel_id": 1515408703287722064, "new_category_id": 1515408680391278602}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 19:53:17,432 - ERROR - ACTION: {"timestamp": "2026-06-13T17:53:17.432795", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "analyze", "action": "MODIFY_CHANNEL", "channel_id": 1515408710032298065, "new_category_id": 1515408707507196050}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 19:53:17,981 - ERROR - ACTION: {"timestamp": "2026-06-13T17:53:17.981658", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "analyze", "action": "MODIFY_CHANNEL", "channel_id": 1515408636787032135, "new_category_id": 1515408630839509114}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 19:53:18,597 - ERROR - ACTION: {"timestamp": "2026-06-13T17:53:18.597432", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "analyze", "action": "MODIFY_CHANNEL", "channel_id": 1515408741980438620, "new_category_id": 1515408716948705300}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 19:53:19,012 - ERROR - ACTION: {"timestamp": "2026-06-13T17:53:19.012166", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "analyze", "action": "MODIFY_CHANNEL", "channel_id": 1515408629438877776, "new_category_id": 1515408621020905483}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 19:53:23,794 - ERROR - ACTION: {"timestamp": "2026-06-13T17:53:23.793971", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "analyze", "action": "MODIFY_CHANNEL", "channel_id": 1515408705414500534, "new_category_id": 1515408680391278602}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 19:53:24,383 - ERROR - ACTION: {"timestamp": "2026-06-13T17:53:24.383361", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "analyze", "action": "MODIFY_CHANNEL", "channel_id": 1515408714922852535, "new_category_id": 1515408707507196050}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 19:53:24,872 - ERROR - ACTION: {"timestamp": "2026-06-13T17:53:24.872794", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "analyze", "action": "MODIFY_CHANNEL", "channel_id": 1515408661361459240, "new_category_id": 1515408640750915634}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 19:59:22,202 - INFO - ACTION: {"timestamp": "2026-06-13T17:59:22.202760", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "move bug-report", "action": "MODIFY_CHANNEL", "channel_id": "1515408712007684167", "new_category_id": "1515408707507196050"}, "success": true, "error": null}
2026-06-13 19:59:22,674 - INFO - ACTION: {"timestamp": "2026-06-13T17:59:22.674601", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "move screenshots", "action": "MODIFY_CHANNEL", "channel_id": "1515408736565592178", "new_category_id": "1515408716948705300"}, "success": true, "error": null}
2026-06-13 19:59:23,111 - INFO - ACTION: {"timestamp": "2026-06-13T17:59:23.111869", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "move serveur-3-info", "action": "MODIFY_CHANNEL", "channel_id": "1515408678101057707", "new_category_id": "1515408671377588324"}, "success": true, "error": null}
2026-06-13 19:59:23,550 - INFO - ACTION: {"timestamp": "2026-06-13T17:59:23.550918", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "move serveur-1-info", "action": "MODIFY_CHANNEL", "channel_id": "1515408673755889815", "new_category_id": "1515408671377588324"}, "success": true, "error": null}
2026-06-13 19:59:23,962 - INFO - ACTION: {"timestamp": "2026-06-13T17:59:23.962966", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "move général-chat", "action": "MODIFY_CHANNEL", "channel_id": "1515408633381261584", "new_category_id": "1515408630839509114"}, "success": true, "error": null}
2026-06-13 19:59:24,378 - INFO - ACTION: {"timestamp": "2026-06-13T17:59:24.378574", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "move mods-et-plugins", "action": "MODIFY_CHANNEL", "channel_id": "1515408666956796086", "new_category_id": "1515408640750915634"}, "success": true, "error": null}
2026-06-13 19:59:29,205 - INFO - ACTION: {"timestamp": "2026-06-13T17:59:29.205830", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "move événements", "action": "MODIFY_CHANNEL", "channel_id": "1515408627538853908", "new_category_id": "1515408621020905483"}, "success": true, "error": null}
2026-06-13 19:59:29,616 - INFO - ACTION: {"timestamp": "2026-06-13T17:59:29.616781", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "move tournament-rules", "action": "MODIFY_CHANNEL", "channel_id": "1515408700876128287", "new_category_id": "1515408680391278602"}, "success": true, "error": null}
2026-06-13 19:59:30,664 - INFO - ACTION: {"timestamp": "2026-06-13T17:59:30.664877", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "move faq-minecraft", "action": "MODIFY_CHANNEL", "channel_id": "1515408669473378344", "new_category_id": "1515408640750915634"}, "success": true, "error": null}
2026-06-13 19:59:31,119 - INFO - ACTION: {"timestamp": "2026-06-13T17:59:31.119108", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "move build-tips", "action": "MODIFY_CHANNEL", "channel_id": "1515408663152693321", "new_category_id": "1515408640750915634"}, "success": true, "error": null}
2026-06-13 19:59:31,514 - INFO - ACTION: {"timestamp": "2026-06-13T17:59:31.514282", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "move pvp-arena", "action": "MODIFY_CHANNEL", "channel_id": "1515408751220490340", "new_category_id": "1515408680391278602"}, "success": true, "error": null}
2026-06-13 19:59:32,278 - INFO - ACTION: {"timestamp": "2026-06-13T17:59:32.278056", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "move videos", "action": "MODIFY_CHANNEL", "channel_id": "1515408739040235681", "new_category_id": "1515408716948705300"}, "success": true, "error": null}
2026-06-13 19:59:32,794 - INFO - ACTION: {"timestamp": "2026-06-13T17:59:32.794935", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "move build-chat", "action": "MODIFY_CHANNEL", "channel_id": "1515408749261750435", "new_category_id": "1515408680391278602"}, "success": true, "error": null}
2026-06-13 19:59:33,195 - INFO - ACTION: {"timestamp": "2026-06-13T17:59:33.195770", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "move annonces channel", "action": "MODIFY_CHANNEL", "channel_id": "1515408624627744859", "new_category_id": "1515408621020905483"}, "success": true, "error": null}
2026-06-13 19:59:37,764 - INFO - ACTION: {"timestamp": "2026-06-13T17:59:37.763974", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "move match-schedule", "action": "MODIFY_CHANNEL", "channel_id": "1515408703287722064", "new_category_id": "1515408680391278602"}, "success": true, "error": null}
2026-06-13 19:59:38,261 - INFO - ACTION: {"timestamp": "2026-06-13T17:59:38.261792", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "move questions-techniques", "action": "MODIFY_CHANNEL", "channel_id": "1515408710032298065", "new_category_id": "1515408707507196050"}, "success": true, "error": null}
2026-06-13 19:59:38,873 - INFO - ACTION: {"timestamp": "2026-06-13T17:59:38.873842", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "move general-voice to voice category", "action": "MODIFY_CHANNEL", "channel_id": "1515408746627469506", "new_category_id": "1515408744937295893"}, "success": true, "error": null}
2026-06-13 19:59:40,359 - INFO - ACTION: {"timestamp": "2026-06-13T17:59:40.359111", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "move recettes-de-crafting", "action": "MODIFY_CHANNEL", "channel_id": "1515408661361459240", "new_category_id": "1515408640750915634"}, "success": true, "error": null}
2026-06-13 19:59:40,951 - INFO - ACTION: {"timestamp": "2026-06-13T17:59:40.951742", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "move serveur-2-info", "action": "MODIFY_CHANNEL", "channel_id": "1515408675630743665", "new_category_id": "1515408671377588324"}, "success": true, "error": null}
2026-06-13 19:59:41,578 - INFO - ACTION: {"timestamp": "2026-06-13T17:59:41.578341", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "move off-topic", "action": "MODIFY_CHANNEL", "channel_id": "1515408639006081065", "new_category_id": "1515408630839509114"}, "success": true, "error": null}
2026-06-13 19:59:42,059 - INFO - ACTION: {"timestamp": "2026-06-13T17:59:42.059073", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "move 💬・général to Général category", "action": "MODIFY_CHANNEL", "channel_id": "1456784303995228344", "new_category_id": "1515408630839509114"}, "success": true, "error": null}
2026-06-13 19:59:42,707 - INFO - ACTION: {"timestamp": "2026-06-13T17:59:42.707133", "action": "MODIFY_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "move streamers channel", "action": "MODIFY_CHANNEL", "channel_id": "1515408741980438620", "new_category_id": "1515408716948705300"}, "success": true, "error": null}
2026-06-13 20:01:47,813 - ERROR - ACTION: {"timestamp": "2026-06-13T18:01:47.813558", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon bug-report", "action": "RENAME_CHANNEL", "channel_id": "1515408712007684167", "new_channel_name": "🐛 Bug Reports"}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 20:01:48,349 - ERROR - ACTION: {"timestamp": "2026-06-13T18:01:48.348969", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon screenshots", "action": "RENAME_CHANNEL", "channel_id": "1515408736565592178", "new_channel_name": "📸 Screenshots & Media"}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 20:01:48,922 - ERROR - ACTION: {"timestamp": "2026-06-13T18:01:48.922645", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon serveur-3-info", "action": "RENAME_CHANNEL", "channel_id": "1515408678101057707", "new_channel_name": "🌍 Server3 Info"}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 20:01:49,687 - ERROR - ACTION: {"timestamp": "2026-06-13T18:01:49.687849", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon serveur-1-info", "action": "RENAME_CHANNEL", "channel_id": "1515408673755889815", "new_channel_name": "🌎 Server1 Info"}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 20:01:50,188 - ERROR - ACTION: {"timestamp": "2026-06-13T18:01:50.188925", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon général-chat", "action": "RENAME_CHANNEL", "channel_id": "1515408633381261584", "new_channel_name": "💬 General Chat"}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 20:01:51,541 - ERROR - ACTION: {"timestamp": "2026-06-13T18:01:51.541530", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon mods-et-plugins", "action": "RENAME_CHANNEL", "channel_id": "1515408666956796086", "new_channel_name": "🔧 Mods & Plugins"}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 20:01:52,080 - ERROR - ACTION: {"timestamp": "2026-06-13T18:01:52.080425", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon événements", "action": "RENAME_CHANNEL", "channel_id": "1515408627538853908", "new_channel_name": "🎉 Events"}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 20:01:52,733 - ERROR - ACTION: {"timestamp": "2026-06-13T18:01:52.733652", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon tournament-rules", "action": "RENAME_CHANNEL", "channel_id": "1515408700876128287", "new_channel_name": "🏆 Tournament Rules"}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 20:01:53,300 - ERROR - ACTION: {"timestamp": "2026-06-13T18:01:53.300944", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon faq-minecraft", "action": "RENAME_CHANNEL", "channel_id": "1515408669473378344", "new_channel_name": "❓ Minecraft FAQ"}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 20:01:53,860 - ERROR - ACTION: {"timestamp": "2026-06-13T18:01:53.860536", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon build-tips", "action": "RENAME_CHANNEL", "channel_id": "1515408663152693321", "new_channel_name": "⚒️ Build Tips"}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 20:01:54,310 - ERROR - ACTION: {"timestamp": "2026-06-13T18:01:54.310830", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon pvp-arena", "action": "RENAME_CHANNEL", "channel_id": "1515408751220490340", "new_channel_name": "🤺 PvP Arena"}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 20:01:59,282 - ERROR - ACTION: {"timestamp": "2026-06-13T18:01:59.282046", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon videos", "action": "RENAME_CHANNEL", "channel_id": "1515408739040235681", "new_channel_name": "📹 Videos"}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 20:01:59,807 - ERROR - ACTION: {"timestamp": "2026-06-13T18:01:59.807432", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon build-chat", "action": "RENAME_CHANNEL", "channel_id": "1515408749261750435", "new_channel_name": "💬 Build Chat"}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 20:02:00,677 - ERROR - ACTION: {"timestamp": "2026-06-13T18:02:00.677162", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon annonces", "action": "RENAME_CHANNEL", "channel_id": "1515408624627744859", "new_channel_name": "🔔 Announcements"}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 20:02:01,110 - ERROR - ACTION: {"timestamp": "2026-06-13T18:02:01.110809", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon match-schedule", "action": "RENAME_CHANNEL", "channel_id": "1515408703287722064", "new_channel_name": "⌚ Match Schedule"}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 20:02:01,746 - ERROR - ACTION: {"timestamp": "2026-06-13T18:02:01.746413", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon questions-techniques", "action": "RENAME_CHANNEL", "channel_id": "1515408710032298065", "new_channel_name": "🛠️ Tech Questions"}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 20:02:02,250 - ERROR - ACTION: {"timestamp": "2026-06-13T18:02:02.250808", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon general-voice (text)", "action": "RENAME_CHANNEL", "channel_id": "1515408746627469506", "new_channel_name": "🎤 Voice Text"}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 20:02:02,680 - ERROR - ACTION: {"timestamp": "2026-06-13T18:02:02.680260", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon présentations", "action": "RENAME_CHANNEL", "channel_id": "1515408636787032135", "new_channel_name": "🌟 Presentations"}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 20:02:03,179 - ERROR - ACTION: {"timestamp": "2026-06-13T18:02:03.179930", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon streamers", "action": "RENAME_CHANNEL", "channel_id": "1515408741980438620", "new_channel_name": "📺 Streamer Spot"}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 20:02:07,831 - ERROR - ACTION: {"timestamp": "2026-06-13T18:02:07.831059", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon règlement", "action": "RENAME_CHANNEL", "channel_id": "1515408629438877776", "new_channel_name": "⚖️ Rules & Regulations"}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 20:02:08,348 - ERROR - ACTION: {"timestamp": "2026-06-13T18:02:08.348831", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon leaderboards", "action": "RENAME_CHANNEL", "channel_id": "1515408705414500534", "new_channel_name": "📊 Leaderboards"}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 20:02:08,837 - ERROR - ACTION: {"timestamp": "2026-06-13T18:02:08.837203", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon suggestions", "action": "RENAME_CHANNEL", "channel_id": "1515408714922852535", "new_channel_name": "💡 Suggestions"}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 20:02:09,353 - ERROR - ACTION: {"timestamp": "2026-06-13T18:02:09.353962", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon recettes-de-crafting", "action": "RENAME_CHANNEL", "channel_id": "1515408661361459240", "new_channel_name": "🍲 Crafting Recipes"}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 20:02:09,824 - ERROR - ACTION: {"timestamp": "2026-06-13T18:02:09.824573", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon serveur-2-info", "action": "RENAME_CHANNEL", "channel_id": "1515408675630743665", "new_channel_name": "🗺️ Server2 Info"}, "success": false, "error": "400 Bad Request (error code: 50035): Invalid Form Body\nIn name: This field is required"}
2026-06-13 20:05:39,538 - INFO - ACTION: {"timestamp": "2026-06-13T18:05:39.538243", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon bug-report", "action": "RENAME_CHANNEL", "channel_id": "1515408712007684167", "new_name": "🐞 Bug Report"}, "success": true, "error": null}
2026-06-13 20:05:39,969 - INFO - ACTION: {"timestamp": "2026-06-13T18:05:39.969547", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon screenshots", "action": "RENAME_CHANNEL", "channel_id": "1515408736565592178", "new_name": "📸 Screenshots & Media"}, "success": true, "error": null}
2026-06-13 20:05:40,427 - INFO - ACTION: {"timestamp": "2026-06-13T18:05:40.427387", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon serveur-3-info", "action": "RENAME_CHANNEL", "channel_id": "1515408678101057707", "new_name": "🌍 Server 3 Info"}, "success": true, "error": null}
2026-06-13 20:05:40,949 - INFO - ACTION: {"timestamp": "2026-06-13T18:05:40.949473", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon serveur-1-info", "action": "RENAME_CHANNEL", "channel_id": "1515408673755889815", "new_name": "🌍 Server 1 Info"}, "success": true, "error": null}
2026-06-13 20:05:41,355 - INFO - ACTION: {"timestamp": "2026-06-13T18:05:41.355830", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon général-chat", "action": "RENAME_CHANNEL", "channel_id": "1515408633381261584", "new_name": "💬 General Chat"}, "success": true, "error": null}
2026-06-13 20:05:41,913 - INFO - ACTION: {"timestamp": "2026-06-13T18:05:41.913252", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon mods-et-plugins", "action": "RENAME_CHANNEL", "channel_id": "1515408666956796086", "new_name": "🔧 Mods & Plugins"}, "success": true, "error": null}
2026-06-13 20:05:46,563 - INFO - ACTION: {"timestamp": "2026-06-13T18:05:46.563916", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon événements", "action": "RENAME_CHANNEL", "channel_id": "1515408627538853908", "new_name": "🎉 Events"}, "success": true, "error": null}
2026-06-13 20:05:46,983 - INFO - ACTION: {"timestamp": "2026-06-13T18:05:46.983799", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon tournament-rules", "action": "RENAME_CHANNEL", "channel_id": "1515408700876128287", "new_name": "🏆 Tournament Rules"}, "success": true, "error": null}
2026-06-13 20:05:47,494 - INFO - ACTION: {"timestamp": "2026-06-13T18:05:47.494022", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon faq-minecraft", "action": "RENAME_CHANNEL", "channel_id": "1515408669473378344", "new_name": "❓ Minecraft FAQ"}, "success": true, "error": null}
2026-06-13 20:05:48,015 - INFO - ACTION: {"timestamp": "2026-06-13T18:05:48.015873", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon build-tips", "action": "RENAME_CHANNEL", "channel_id": "1515408663152693321", "new_name": "✨ Build Tips"}, "success": true, "error": null}
2026-06-13 20:05:48,450 - INFO - ACTION: {"timestamp": "2026-06-13T18:05:48.450022", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon pvp-arena", "action": "RENAME_CHANNEL", "channel_id": "1515408751220490340", "new_name": "⚔️ PvP Arena"}, "success": true, "error": null}
2026-06-13 20:05:48,886 - INFO - ACTION: {"timestamp": "2026-06-13T18:05:48.886697", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon videos", "action": "RENAME_CHANNEL", "channel_id": "1515408739040235681", "new_name": "📹 Videos"}, "success": true, "error": null}
2026-06-13 20:05:49,297 - INFO - ACTION: {"timestamp": "2026-06-13T18:05:49.297326", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon build-chat", "action": "RENAME_CHANNEL", "channel_id": "1515408749261750435", "new_name": "💬 Build Chat"}, "success": true, "error": null}
2026-06-13 20:05:53,948 - INFO - ACTION: {"timestamp": "2026-06-13T18:05:53.948396", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon annonces", "action": "RENAME_CHANNEL", "channel_id": "1515408624627744859", "new_name": "🚀 Announcements"}, "success": true, "error": null}
2026-06-13 20:05:54,332 - INFO - ACTION: {"timestamp": "2026-06-13T18:05:54.332344", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon match-schedule", "action": "RENAME_CHANNEL", "channel_id": "1515408703287722064", "new_name": "🗓️ Match Schedule"}, "success": true, "error": null}
2026-06-13 20:05:54,749 - INFO - ACTION: {"timestamp": "2026-06-13T18:05:54.749792", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon questions-techniques", "action": "RENAME_CHANNEL", "channel_id": "1515408710032298065", "new_name": "🛠️ Tech Q&A"}, "success": true, "error": null}
2026-06-13 20:05:55,382 - INFO - ACTION: {"timestamp": "2026-06-13T18:05:55.382210", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon general-voice (textuel)", "action": "RENAME_CHANNEL", "channel_id": "1515408746627469506", "new_name": "🎤 Voice Channels"}, "success": true, "error": null}
2026-06-13 20:05:55,777 - INFO - ACTION: {"timestamp": "2026-06-13T18:05:55.777464", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon présentations", "action": "RENAME_CHANNEL", "channel_id": "1515408636787032135", "new_name": "📣 Presentations"}, "success": true, "error": null}
2026-06-13 20:05:56,169 - INFO - ACTION: {"timestamp": "2026-06-13T18:05:56.169257", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon streamers", "action": "RENAME_CHANNEL", "channel_id": "1515408741980438620", "new_name": "🎥 Streamers"}, "success": true, "error": null}
2026-06-13 20:05:56,613 - INFO - ACTION: {"timestamp": "2026-06-13T18:05:56.613529", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon règlement", "action": "RENAME_CHANNEL", "channel_id": "1515408629438877776", "new_name": "⚖️ Rules"}, "success": true, "error": null}
2026-06-13 20:06:01,556 - INFO - ACTION: {"timestamp": "2026-06-13T18:06:01.556686", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon leaderboards", "action": "RENAME_CHANNEL", "channel_id": "1515408705414500534", "new_name": "🏅 Leaderboards"}, "success": true, "error": null}
2026-06-13 20:06:01,950 - INFO - ACTION: {"timestamp": "2026-06-13T18:06:01.950088", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon suggestions", "action": "RENAME_CHANNEL", "channel_id": "1515408714922852535", "new_name": "🤔 Suggestions"}, "success": true, "error": null}
2026-06-13 20:06:02,436 - INFO - ACTION: {"timestamp": "2026-06-13T18:06:02.436742", "action": "RENAME_CHANNEL", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "Renommer le salon recettes-de-crafting", "action": "RENAME_CHANNEL", "channel_id": "1515408661361459240", "new_name": "🍽️ Crafting Recipes"}, "success": true, "error": null}
2026-06-13 20:09:42,554 - ERROR - ACTION: {"timestamp": "2026-06-13T18:09:42.554226", "action": "JOIN_VOICE", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "joining", "action": "JOIN_VOICE", "channel_id": 1515417670592495657, "response": "Je me suis joint au salon vocal 'test'."}, "success": false, "error": "Vous n'êtes pas dans un salon vocal identifiable par le système."}
2026-06-13 20:10:14,684 - INFO - ACTION: {"timestamp": "2026-06-13T18:10:14.684174", "action": "JOIN_VOICE", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "joining", "action": "JOIN_VOICE", "channel_id": 1515417670592495657, "response": "Je me suis joint au salon vocal 'test'."}, "success": true, "error": "Connecté à test"}
2026-06-13 20:20:25,073 - ERROR - ACTION: {"timestamp": "2026-06-13T18:20:25.073619", "action": "CREATE_ROLE", "user": "natsu_2106", "guild": "Omega Kube", "params": {"thought": "analyse", "action": "CREATE_ROLE", "role_name": "RÔLE: SUJET"}, "success": false, "error": "Cette opération est réservée aux administrateurs."}
2026-06-13 20:20:25,911 - ERROR - ACTION: {"timestamp": "2026-06-13T18:20:25.911348", "action": "ADD_ROLE_TO_USER", "user": "natsu_2106", "guild": "Omega Kube", "params": {"action": "ADD_ROLE_TO_USER", "user_id": 1370716551485067304, "role_name": "RÔLE: SUJET", "response": "J'ai créé le rôle \"RÔLE: SUJET\" et l'ai attribué à <@1370716551485067304>."}, "success": false, "error": "Cette opération est réservée aux administrateurs."}
2026-06-13 20:23:02,959 - ERROR - ACTION: {"timestamp": "2026-06-13T18:23:02.959686", "action": "ADD_ROLE_TO_USER", "user": "natsu_2106", "guild": "Omega Kube", "params": {"thought": "analysis", "action": "ADD_ROLE_TO_USER", "role_name": "RÔLE: SUJET", "user_id": "<@ͷὶϰϝἱϰ gaming>", "response": "Le rôle 'RÔLE: SUJET' a été attribué à @ͷὶϰϝἱϰ gaming."}, "success": false, "error": "Cette opération est réservée aux administrateurs."}
2026-06-13 20:30:15,626 - INFO - ACTION: {"timestamp": "2026-06-13T18:30:15.626871", "action": "INSIGHT", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "explain 9/11 events", "action": "INSIGHT", "response": "Le 11 septembre 2001 (souvent appelé le« 911 ») est une date marquante de l'histoire moderne, caractérisée par des attaques terroristes coordonnées contre les États-Unis. Voici un résumé succinct :\n\n• **Contexte** Le groupe extrémiste islamique al-Qaïda, dirigé à l'époque par Oussama ben Laden, avait planifié depuis plusieurs années une série d'attaques visant à semer la peur et à affaiblir les États-Unis.\n\n• **Les attaques**\n - *Deux avions* (American Airlines vol11 et United Airlines vol175) ont été détournés et écrasés de façon délibérée dans le **TorrejonNord** du World Trade Center à New York. Le bâtiment s'est effondré après l'impact, causant la mort d'environ 2977 personnes.\n - *Un troisième avion* (American Airlines vol77) a été détourné et déposé sur le **Pentagone**, siège de la Défense américaine, tuant plus de 300 militaires et civils.\n - Le *quatrième avion*, United Airlines vol93, s'est écrasé dans un champ en Pennsylvanie après que les passagers aient tenté d'affronter les détournés. Tous à bord sont décédés.\n\n• **Conséquences immédiates** Au total, près de 3000 personnes ont perdu la vie et des milliers d'autres furent blessées ou traumatisées.\n\n• **Réponses politiques et militaires**\n - Les États-Unis déclarèrent une « guerre contre le terrorisme », lançant l'invasion du *Afghanistan* en octobre 2001 pour renverser les talibans qui abritaient alQaïda.\n - En 2003, la guerre en *Irak* fut justifiée par des allégations (falsifiées) de possession d'armes de destruction massive et le soutien présumé du régime iraquien à Al-Qaïda. Cette décision a eu un impact durable sur l'équilibre géopolitique moyenoriental.\n\n• **Impact sociétal** Le 9/11 a profondément changé la perception mondiale de la sécurité, entraînant des mesures renforcées en matière d'aérien (sûreté accrue), une surveillance élargie et un changement dans les politiques étrangères. Il s'est également transformé en symbole durable du terrorisme international.\n\nEn résumé, le 9/11 a été l'une des attaques terroristes les plus meurtrières de l'histoire moderne, déclenchant une série d'événements géopolitiques qui continuent à influencer la politique mondiale aujourd'hui."}, "success": true, "error": null}
2026-06-13 20:33:45,321 - INFO - ACTION: {"timestamp": "2026-06-13T18:33:45.321004", "action": "PURGE", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "User requested purging ~200 messages from #💬・général", "action": "PURGE", "channel_id": "1456784303995228344", "amount": 200, "response": "Les 200 derniers messages du salon #💬・général ont été supprimés."}, "success": true, "error": null}
2026-06-13 20:34:34,945 - INFO - ACTION: {"timestamp": "2026-06-13T18:34:34.945454", "action": "PURGE", "user": "_.radi0dem0n._", "guild": "BETA", "params": {"thought": "analysis", "action": "PURGE", "channel_id": "1456784303995228344", "count": 10000, "response": "Les 10000 derniers messages du salon #💬・général ont été supprimés."}, "success": true, "error": null}

View file

@ -1,11 +0,0 @@
{
"task_1781375415": {
"asset_id": 971446412690722826,
"content": "Pas de contenu",
"importance": 2,
"created_at": 1781375415.1465554,
"deadline": 1781378115.1465554,
"guild_id": 1434226877652013100,
"status": "PENDING"
}
}

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

@ -1,47 +0,0 @@
{
"1277303870657400882": {
"914118793918312480": [
{
"reason": "Mots inappropriés détectés: putain",
"timestamp": "2026-01-21T17:04:00.196873",
"issued_by": "AUTOMATIC_MODERATION"
}
]
},
"1493915688295727204": {
"979680383174066267": [
{
"reason": "Mots inappropriés détectés: fuck",
"timestamp": "2026-04-15T14:37:43.989883",
"issued_by": "AUTOMATIC_MODERATION"
}
],
"1324439906046574693": [
{
"reason": "Mots inappropriés détectés: fuck",
"timestamp": "2026-04-15T14:37:50.862846",
"issued_by": "AUTOMATIC_MODERATION"
},
{
"reason": "Mots inappropriés détectés: nul",
"timestamp": "2026-04-16T09:44:42.349167",
"issued_by": "AUTOMATIC_MODERATION"
},
{
"reason": "Mots inappropriés détectés: salope",
"timestamp": "2026-04-16T09:44:55.968030",
"issued_by": "AUTOMATIC_MODERATION"
},
{
"reason": "Mots inappropriés détectés: nul",
"timestamp": "2026-04-16T09:48:01.003922",
"issued_by": "AUTOMATIC_MODERATION"
},
{
"reason": "Mots inappropriés détectés: nul",
"timestamp": "2026-04-16T17:59:47.309121",
"issued_by": "AUTOMATIC_MODERATION"
}
]
}
}

13
main.py
View file

@ -8,7 +8,7 @@ import discord
from dotenv import load_dotenv from dotenv import load_dotenv
# Import our modular components # Import our modular components
from core.bot import Superviseur from core.bot import Bot
from core.llm import LLMManager from core.llm import LLMManager
# Setup Logging # Setup Logging
@ -20,7 +20,7 @@ logging.basicConfig(
logging.StreamHandler(sys.stdout) logging.StreamHandler(sys.stdout)
] ]
) )
logger = logging.getLogger('Superviseur') logger = logging.getLogger('Beta')
# Load environment # Load environment
load_dotenv() load_dotenv()
@ -39,6 +39,10 @@ if not GUILD_ID:
# Ollama model name (from .env) # Ollama model name (from .env)
MODEL_NAME = os.getenv("OLLAMA_MODEL", "gpt-oss:20b") MODEL_NAME = os.getenv("OLLAMA_MODEL", "gpt-oss:20b")
# Favorite users (special personality)
fav_env = os.getenv("FAVORITE_IDS", "")
FAVORITE_IDS = [int(i.strip()) for i in fav_env.split(",") if i.strip().isdigit()]
async def main(): async def main():
"""Main startup sequence.""" """Main startup sequence."""
logger.info("◈ SYSTEM: Initializing Superviseur with Ollama Backend...") logger.info("◈ SYSTEM: Initializing Superviseur with Ollama Backend...")
@ -67,12 +71,13 @@ async def main():
intents.members = True intents.members = True
intents.voice_states = True intents.voice_states = True
bot = Superviseur( bot = Bot(
llama_model=llm, llama_model=llm,
command_prefix="!", command_prefix="!",
guild_id=int(GUILD_ID), guild_id=int(GUILD_ID),
system_prompt=SYSTEM_PROMPT, system_prompt=SYSTEM_PROMPT,
intents=intents intents=intents,
favorite_ids=FAVORITE_IDS
) )
try: try:

View file

@ -1,4 +0,0 @@
nohup: ignoring input
time=2026-06-13T12:52:15.776Z level=INFO source=routes.go:1919 msg="server config" env="map[CUDA_VISIBLE_DEVICES: GGML_VK_VISIBLE_DEVICES: GPU_DEVICE_ORDINAL: HIP_VISIBLE_DEVICES:-1 HSA_OVERRIDE_GFX_VERSION: HTTPS_PROXY: HTTP_PROXY: LLAMA_ARG_FIT: LLAMA_ARG_FIT_TARGET: NO_PROXY: OLLAMA_CONTEXT_LENGTH:8192 OLLAMA_DEBUG:INFO OLLAMA_DEBUG_LOG_REQUESTS:false OLLAMA_EDITOR: OLLAMA_FLASH_ATTENTION:false OLLAMA_GO_TEMPLATE:true OLLAMA_GPU_OVERHEAD:0 OLLAMA_HOST:http://0.0.0.0:11434 OLLAMA_IGPU_ENABLE:1 OLLAMA_KEEP_ALIVE:5m0s OLLAMA_KV_CACHE_TYPE: OLLAMA_LLM_LIBRARY: OLLAMA_LOAD_TIMEOUT:5m0s OLLAMA_MAX_LOADED_MODELS:0 OLLAMA_MAX_QUEUE:512 OLLAMA_MAX_TRANSFER_STREAMS:4 OLLAMA_MODELS:/home/lowei/.ollama/models OLLAMA_NOHISTORY:false OLLAMA_NOPRUNE:false OLLAMA_NO_CLOUD:false OLLAMA_NUM_PARALLEL:1 OLLAMA_ORIGINS:[* http://localhost https://localhost http://localhost:* https://localhost:* http://127.0.0.1 https://127.0.0.1 http://127.0.0.1:* https://127.0.0.1:* http://0.0.0.0 https://0.0.0.0 http://0.0.0.0:* https://0.0.0.0:* app://* file://* tauri://* vscode-webview://* vscode-file://*] OLLAMA_REMOTES:[ollama.com] OLLAMA_SCHED_SPREAD:false OLLAMA_VULKAN:true ROCR_VISIBLE_DEVICES:-1 http_proxy: https_proxy: no_proxy:]"
time=2026-06-13T12:52:15.776Z level=INFO source=routes.go:1921 msg="Ollama cloud disabled: false"
Error: mkdir /home/lowei/.ollama: permission denied: ensure path elements are traversable

View file

@ -1,89 +1,479 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""Simple test to verify the bot can be initialized without errors.""" """Tests unitaires pour Superviseur Bot."""
import sys import sys
import os import os
import json
import asyncio
import tempfile
import shutil
import logging import logging
# Add the project root to the path # Add the project root to the path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
# Setup logging logging.basicConfig(level=logging.INFO)
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler("data/test_bot.log"),
logging.StreamHandler(sys.stdout)
]
)
logger = logging.getLogger('TestBot') logger = logging.getLogger('TestBot')
# ==================== Tests d'import ====================
def test_imports(): def test_imports():
"""Test that all required modules can be imported.""" """Test que tous les modules principaux peuvent être importés."""
try: from core.bot import Bot
from core.bot import Superviseur
from core.llm import LLMManager from core.llm import LLMManager
from core.action_executor import ActionExecutor
from brain.memory import (
add_interaction, build_context_for_model, flush_all,
delete_user_memory, set_llm_manager, _extractive_summary
)
from brain.moderation import init_db, log_infraction
from dotenv import load_dotenv from dotenv import load_dotenv
import discord import discord
logger.info("✓ All imports successful") logger.info("✓ Tous les imports réussis")
return True
except Exception as e:
logger.error(f"✗ Import failed: {e}")
return False
def test_llm_manager():
"""Test LLMManager initialization.""" def test_llm_manager_init():
try: """Test l'initialisation du LLMManager."""
from core.llm import LLMManager from core.llm import LLMManager
llm = LLMManager(model_name="beta-20b") llm = LLMManager(model_name="test-model")
logger.info("✓ LLMManager initialized successfully") assert llm.model_name == "test-model"
return True assert llm.temperature == 0.5
except Exception as e: assert "localhost" in llm.base_url
logger.error(f"✗ LLMManager failed: {e}") logger.info("✓ LLMManager initialisé correctement")
return False
def test_bot_initialization():
"""Test Superviseur bot initialization.""" def test_bot_init():
try: """Test l'initialisation du bot Beta."""
from core.bot import Superviseur from core.bot import Bot
from core.llm import LLMManager
import discord import discord
from core.llm import LLMManager
# Create a mock LLMManager llm = LLMManager(model_name="test-model")
llm = LLMManager(model_name="beta-20b")
# Create minimal intents
intents = discord.Intents.default() intents = discord.Intents.default()
intents.message_content = True intents.message_content = True
intents.members = True
intents.voice_states = True
# Try to initialize the bot (without actually connecting) bot = Bot(
bot = Superviseur(
llama_model=llm, llama_model=llm,
command_prefix="!", command_prefix="!",
guild_id=123456789, guild_id=123456789,
system_prompt="Test system prompt", system_prompt="Test prompt",
intents=intents intents=intents
) )
logger.info("✓ Superviseur bot initialized successfully") assert bot.guild_id == 123456789
return True assert bot.system_name == "Bêta"
except Exception as e: logger.info("✓ Bot Superviseur initialisé correctement")
logger.error(f"✗ Bot initialization failed: {e}")
return False
# ==================== Tests LLM (JSON Extraction) ====================
def test_extract_json_simple_object():
"""Test extraction JSON simple."""
from core.llm import LLMManager
llm = LLMManager()
text = 'Voici mon analyse : {"thought": "test", "action": "NONE", "response": "Salut !"}'
result = llm.extract_json_actions(text)
assert result is not None
data = json.loads(result)
assert data["action"] == "NONE"
assert data["response"] == "Salut !"
logger.info("✓ Extraction JSON simple OK")
def test_extract_json_array():
"""Test extraction JSON tableau."""
from core.llm import LLMManager
llm = LLMManager()
text = 'Actions : [{"action": "CREATE_CHANNEL", "channel_name": "test"}, {"action": "CREATE_ROLE", "role_name": "mod"}]'
result = llm.extract_json_actions(text)
assert result is not None
data = json.loads(result)
assert isinstance(data, list)
assert len(data) == 2
assert data[0]["action"] == "CREATE_CHANNEL"
logger.info("✓ Extraction JSON tableau OK")
def test_extract_json_with_comments():
"""Test extraction JSON avec commentaires GPT-OSS."""
from core.llm import LLMManager
llm = LLMManager()
text = '''Voici la réponse :
// Ceci est un commentaire
{"action": "KICK", "user_id": "123", "reason": "spam"}
// Autre commentaire'''
result = llm.extract_json_actions(text)
assert result is not None
data = json.loads(result)
assert data["action"] == "KICK"
logger.info("✓ Extraction JSON avec commentaires OK")
def test_extract_json_none():
"""Test extraction quand pas de JSON."""
from core.llm import LLMManager
llm = LLMManager()
text = "Salut, comment ça va ? Pas de JSON ici."
result = llm.extract_json_actions(text)
assert result is None
logger.info("✓ Absence de JSON détectée correctement")
def test_extract_json_empty():
"""Test extraction avec texte vide."""
from core.llm import LLMManager
llm = LLMManager()
assert llm.extract_json_actions("") is None
assert llm.extract_json_actions(None) is None
logger.info("✓ Texte vide géré correctement")
def test_extract_json_nested_braces():
"""Test extraction avec accolades imbriquées."""
from core.llm import LLMManager
llm = LLMManager()
text = 'Analyse : {"action": "SEND_MESSAGE", "channel_id": "123", "response": "Contenu avec {des} accolades"}'
result = llm.extract_json_actions(text)
assert result is not None
data = json.loads(result)
assert data["action"] == "SEND_MESSAGE"
logger.info("✓ Extraction JSON avec accolades imbriquées OK")
# ==================== Tests LLM (Payload) ====================
def test_build_payload():
"""Test la construction de payload Ollama."""
from core.llm import LLMManager
llm = LLMManager(model_name="test-model", temperature=0.3)
payload = llm.build_payload(
prompt="Test prompt",
system_prompt="Test system"
)
assert payload["model"] == "test-model"
assert payload["prompt"] == "Test prompt"
assert payload["system"] == "Test system"
assert payload["options"]["temperature"] == 0.3
assert payload["options"]["num_ctx"] == 16384
logger.info("✓ Payload Ollama construit correctement")
# ==================== Tests Mémoire ====================
def _run_async(coro):
"""Helper pour exécuter du code async dans les tests sync."""
return asyncio.get_event_loop().run_until_complete(coro)
def test_memory_save_load():
"""Test sauvegarde et chargement mémoire."""
import brain.memoire as mem
test_dir = tempfile.mkdtemp()
original_dir = mem.MEMORY_DIR
mem.MEMORY_DIR = test_dir
mem.memory_cache.clear()
mem._dirty.clear()
try:
uid = "test_user_123"
data = {"resume_global": "Test résumé", "historique_recent": []}
_run_async(mem.save_user_memory_async(uid, data, persist_immediately=True))
loaded = _run_async(mem.load_user_memory_async(uid))
assert loaded == data, f"Load/save failed: {loaded} != {data}"
logger.info("✓ Sauvegarde/chargement mémoire OK")
finally:
mem.MEMORY_DIR = original_dir
mem.memory_cache.clear()
shutil.rmtree(test_dir)
def test_memory_add_interaction():
"""Test ajout d'interaction."""
import brain.memoire as mem
test_dir = tempfile.mkdtemp()
original_dir = mem.MEMORY_DIR
mem.MEMORY_DIR = test_dir
mem.memory_cache.clear()
mem._dirty.clear()
try:
uid = "test_user_456"
_run_async(mem.add_interaction(uid, 111, "Hello", "Hi there!"))
loaded = _run_async(mem.load_user_memory_async(uid))
assert len(loaded["historique_recent"]) == 1
assert loaded["historique_recent"][0]["user_message"] == "Hello"
assert loaded["historique_recent"][0]["bot_message"] == "Hi there!"
logger.info("✓ Ajout d'interaction OK")
finally:
mem.MEMORY_DIR = original_dir
mem.memory_cache.clear()
shutil.rmtree(test_dir)
def test_memory_build_context():
"""Test construction du contexte pour le modèle."""
import brain.memoire as mem
test_dir = tempfile.mkdtemp()
original_dir = mem.MEMORY_DIR
mem.MEMORY_DIR = test_dir
mem.memory_cache.clear()
mem._dirty.clear()
try:
uid = "test_user_789"
_run_async(mem.add_interaction(uid, 111, "Question?", "Réponse!"))
ctx = _run_async(mem.build_context_for_model(uid))
assert "Question?" in ctx
assert "Réponse!" in ctx
logger.info("✓ Construction contexte OK")
finally:
mem.MEMORY_DIR = original_dir
mem.memory_cache.clear()
shutil.rmtree(test_dir)
def test_memory_delete():
"""Test suppression mémoire avec guild_id."""
import brain.memoire as mem
test_dir = tempfile.mkdtemp()
original_dir = mem.MEMORY_DIR
mem.MEMORY_DIR = test_dir
mem.memory_cache.clear()
mem._dirty.clear()
try:
uid = "test_user_del"
_run_async(mem.add_interaction(uid, 111, "Test", "Response", guild_id="TestGuild"))
# Forcer la sauvegarde immédiate (add_interaction schedule en background)
_run_async(asyncio.sleep(0.5))
# Vérifier que le fichier existe
path = mem._user_filepath(uid, "TestGuild")
assert os.path.exists(path), f"Fichier non trouvé: {path}"
# Supprimer
mem.delete_user_memory(uid, "TestGuild")
# Vérifier suppression (recharger crée un fichier vide)
mem.memory_cache.clear()
loaded = _run_async(mem.load_user_memory_async(uid, "TestGuild"))
assert loaded["historique_recent"] == []
logger.info("✓ Suppression mémoire avec guild_id OK")
finally:
mem.MEMORY_DIR = original_dir
mem.memory_cache.clear()
shutil.rmtree(test_dir)
def test_memory_extractive_summary():
"""Test le résumé extractif (fallback)."""
from brain.memoire import _extractive_summary
messages = [
{"user_message": "Salut, comment configurer le bot ?", "bot_message": "Tu peux utiliser la commande !config."},
{"user_message": "Merci beaucoup !", "bot_message": "De rien, n'hésite pas."},
]
summary = _extractive_summary(messages)
assert len(summary) > 0
assert "Mots-clés:" in summary
logger.info("✓ Résumé extractif OK")
def test_memory_flush_all():
"""Test flush de toutes les mémoires dirty."""
import brain.memoire as mem
test_dir = tempfile.mkdtemp()
original_dir = mem.MEMORY_DIR
mem.MEMORY_DIR = test_dir
mem.memory_cache.clear()
mem._dirty.clear()
try:
# Créer quelques entrées
_run_async(mem.add_interaction("user_a", 111, "Test A", "Response A"))
_run_async(mem.add_interaction("user_b", 222, "Test B", "Response B"))
# Marquer dirty
assert any(mem._dirty.values())
# Flush
_run_async(mem.flush_all())
# Vérifier que tout est clean
assert not any(mem._dirty.values())
logger.info("✓ Flush mémoire OK")
finally:
mem.MEMORY_DIR = original_dir
mem.memory_cache.clear()
shutil.rmtree(test_dir)
# ==================== Tests Bot (Response Cleaning) ====================
def test_clean_response_text():
"""Test le nettoyage de texte de réponse."""
from core.response_handler import clean_response_text
# Test nettoyage basique
clean = clean_response_text("Salut ! Voici ma r\u00e9ponse.", None)
assert clean == "Salut ! Voici ma r\u00e9ponse."
# Test nettoyage avec JSON extrait
clean = clean_response_text('Texte avant {"action": "NONE"} texte apr\u00e8s', '{"action": "NONE"}')
assert "NONE" not in clean
assert "avant" in clean
assert "apr\u00e8s" in clean
# Test nettoyage balises
clean = clean_response_text("R\u00e9ponse <|channel|> normale", None)
assert "<|channel|>" not in clean
logger.info("OK Nettoyage r\u00e9ponse")
def test_parse_json_actions():
"""Test le parsing des actions JSON."""
from core.response_handler import parse_json_actions
# Test parsing valide
actions, responses, is_none = parse_json_actions(
'{"action": "KICK", "response": "Utilisateur kick\u00e9"}'
)
assert len(actions) == 1
assert actions[0]["action"] == "KICK"
assert "kick\u00e9" in responses[0]
assert not is_none
# Test parsing NONE
actions, responses, is_none = parse_json_actions(
'{"action": "NONE"}'
)
assert is_none
# Test parsing tableau
actions, responses, is_none = parse_json_actions(
'[{"action": "A", "response": "R1"}, {"action": "B", "response": "R2"}]'
)
assert len(actions) == 2
assert len(responses) == 2
# Test parsing None
actions, responses, is_none = parse_json_actions(None)
assert actions == []
assert responses == []
assert not is_none
logger.info("OK Parsing actions JSON")
# ==================== Tests Action Router ====================
def test_action_executor_init():
"""Test l'initialisation de l'ActionExecutor."""
from core.action_executor import ActionExecutor
from core.bot import Bot
from core.llm import LLMManager
import discord
llm = LLMManager()
intents = discord.Intents.default()
bot = Bot(
llama_model=llm, command_prefix="!", guild_id=123,
system_prompt="Test", intents=intents
)
executor = ActionExecutor(bot)
logger.info("✓ ActionExecutor initialisé OK")
# ==================== Tests Modération ====================
def test_moderation_db_init():
"""Test l'initialisation de la DB de modération."""
import tempfile
import brain.moderation as mod
from brain import moderation
original_path = mod.DB_PATH
test_dir = tempfile.mkdtemp()
mod.DB_PATH = os.path.join(test_dir, "test_mod.db")
try:
mod.init_db()
# Vérifier que la table existe
import sqlite3
conn = sqlite3.connect(mod.DB_PATH)
cursor = conn.cursor()
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='infractions'")
assert cursor.fetchone() is not None
conn.close()
logger.info("✓ Initialisation DB modération OK")
finally:
mod.DB_PATH = original_path
shutil.rmtree(test_dir)
# ==================== Point d'entrée ====================
if __name__ == "__main__": if __name__ == "__main__":
logger.info("Testing bot initialization...") logger.info("=" * 50)
logger.info("Tests Superviseur Bot")
logger.info("=" * 50)
success = True tests = [
success &= test_imports() test_imports,
success &= test_llm_manager() test_llm_manager_init,
success &= test_bot_initialization() test_bot_init,
test_extract_json_simple_object,
test_extract_json_array,
test_extract_json_with_comments,
test_extract_json_none,
test_extract_json_empty,
test_extract_json_nested_braces,
test_build_payload,
test_memory_save_load,
test_memory_add_interaction,
test_memory_build_context,
test_memory_delete,
test_memory_extractive_summary,
test_memory_flush_all,
test_clean_response_text,
test_parse_json_actions,
test_action_executor_init,
test_moderation_db_init,
]
if success: passed = 0
logger.info("✓ All tests passed! Bot should be able to run.") failed = 0
sys.exit(0)
else: for test in tests:
logger.error("✗ Some tests failed.") try:
sys.exit(1) test()
passed += 1
except Exception as e:
import traceback
logger.error(f"{test.__name__}: {e}")
traceback.print_exc()
failed += 1
logger.info("=" * 50)
logger.info(f"Résultats: {passed}/{passed + failed} tests réussis")
logger.info("=" * 50)
sys.exit(0 if failed == 0 else 1)

View file

@ -1,247 +0,0 @@
<#
.Synopsis
Activate a Python virtual environment for the current PowerShell session.
.Description
Pushes the python executable for a virtual environment to the front of the
$Env:PATH environment variable and sets the prompt to signify that you are
in a Python virtual environment. Makes use of the command line switches as
well as the `pyvenv.cfg` file values present in the virtual environment.
.Parameter VenvDir
Path to the directory that contains the virtual environment to activate. The
default value for this is the parent of the directory that the Activate.ps1
script is located within.
.Parameter Prompt
The prompt prefix to display when this virtual environment is activated. By
default, this prompt is the name of the virtual environment folder (VenvDir)
surrounded by parentheses and followed by a single space (ie. '(.venv) ').
.Example
Activate.ps1
Activates the Python virtual environment that contains the Activate.ps1 script.
.Example
Activate.ps1 -Verbose
Activates the Python virtual environment that contains the Activate.ps1 script,
and shows extra information about the activation as it executes.
.Example
Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv
Activates the Python virtual environment located in the specified location.
.Example
Activate.ps1 -Prompt "MyPython"
Activates the Python virtual environment that contains the Activate.ps1 script,
and prefixes the current prompt with the specified string (surrounded in
parentheses) while the virtual environment is active.
.Notes
On Windows, it may be required to enable this Activate.ps1 script by setting the
execution policy for the user. You can do this by issuing the following PowerShell
command:
PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
For more information on Execution Policies:
https://go.microsoft.com/fwlink/?LinkID=135170
#>
Param(
[Parameter(Mandatory = $false)]
[String]
$VenvDir,
[Parameter(Mandatory = $false)]
[String]
$Prompt
)
<# Function declarations --------------------------------------------------- #>
<#
.Synopsis
Remove all shell session elements added by the Activate script, including the
addition of the virtual environment's Python executable from the beginning of
the PATH variable.
.Parameter NonDestructive
If present, do not remove this function from the global namespace for the
session.
#>
function global:deactivate ([switch]$NonDestructive) {
# Revert to original values
# The prior prompt:
if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) {
Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt
Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT
}
# The prior PYTHONHOME:
if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) {
Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME
Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME
}
# The prior PATH:
if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) {
Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH
Remove-Item -Path Env:_OLD_VIRTUAL_PATH
}
# Just remove the VIRTUAL_ENV altogether:
if (Test-Path -Path Env:VIRTUAL_ENV) {
Remove-Item -Path env:VIRTUAL_ENV
}
# Just remove VIRTUAL_ENV_PROMPT altogether.
if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) {
Remove-Item -Path env:VIRTUAL_ENV_PROMPT
}
# Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether:
if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) {
Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force
}
# Leave deactivate function in the global namespace if requested:
if (-not $NonDestructive) {
Remove-Item -Path function:deactivate
}
}
<#
.Description
Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the
given folder, and returns them in a map.
For each line in the pyvenv.cfg file, if that line can be parsed into exactly
two strings separated by `=` (with any amount of whitespace surrounding the =)
then it is considered a `key = value` line. The left hand string is the key,
the right hand is the value.
If the value starts with a `'` or a `"` then the first and last character is
stripped from the value before being captured.
.Parameter ConfigDir
Path to the directory that contains the `pyvenv.cfg` file.
#>
function Get-PyVenvConfig(
[String]
$ConfigDir
) {
Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg"
# Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue).
$pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue
# An empty map will be returned if no config file is found.
$pyvenvConfig = @{ }
if ($pyvenvConfigPath) {
Write-Verbose "File exists, parse `key = value` lines"
$pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath
$pyvenvConfigContent | ForEach-Object {
$keyval = $PSItem -split "\s*=\s*", 2
if ($keyval[0] -and $keyval[1]) {
$val = $keyval[1]
# Remove extraneous quotations around a string value.
if ("'""".Contains($val.Substring(0, 1))) {
$val = $val.Substring(1, $val.Length - 2)
}
$pyvenvConfig[$keyval[0]] = $val
Write-Verbose "Adding Key: '$($keyval[0])'='$val'"
}
}
}
return $pyvenvConfig
}
<# Begin Activate script --------------------------------------------------- #>
# Determine the containing directory of this script
$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
$VenvExecDir = Get-Item -Path $VenvExecPath
Write-Verbose "Activation script is located in path: '$VenvExecPath'"
Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)"
Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)"
# Set values required in priority: CmdLine, ConfigFile, Default
# First, get the location of the virtual environment, it might not be
# VenvExecDir if specified on the command line.
if ($VenvDir) {
Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values"
}
else {
Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir."
$VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/")
Write-Verbose "VenvDir=$VenvDir"
}
# Next, read the `pyvenv.cfg` file to determine any required value such
# as `prompt`.
$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir
# Next, set the prompt from the command line, or the config file, or
# just use the name of the virtual environment folder.
if ($Prompt) {
Write-Verbose "Prompt specified as argument, using '$Prompt'"
}
else {
Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value"
if ($pyvenvCfg -and $pyvenvCfg['prompt']) {
Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'"
$Prompt = $pyvenvCfg['prompt'];
}
else {
Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)"
Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'"
$Prompt = Split-Path -Path $venvDir -Leaf
}
}
Write-Verbose "Prompt = '$Prompt'"
Write-Verbose "VenvDir='$VenvDir'"
# Deactivate any currently active virtual environment, but leave the
# deactivate function in place.
deactivate -nondestructive
# Now set the environment variable VIRTUAL_ENV, used by many tools to determine
# that there is an activated venv.
$env:VIRTUAL_ENV = $VenvDir
if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) {
Write-Verbose "Setting prompt to '$Prompt'"
# Set the prompt to include the env name
# Make sure _OLD_VIRTUAL_PROMPT is global
function global:_OLD_VIRTUAL_PROMPT { "" }
Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT
New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt
function global:prompt {
Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) "
_OLD_VIRTUAL_PROMPT
}
$env:VIRTUAL_ENV_PROMPT = $Prompt
}
# Clear PYTHONHOME
if (Test-Path -Path Env:PYTHONHOME) {
Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME
Remove-Item -Path Env:PYTHONHOME
}
# Add the venv to the PATH
Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH
$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH"

View file

@ -1,70 +0,0 @@
# This file must be used with "source bin/activate" *from bash*
# You cannot run it directly
deactivate () {
# reset old environment variables
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
PATH="${_OLD_VIRTUAL_PATH:-}"
export PATH
unset _OLD_VIRTUAL_PATH
fi
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
export PYTHONHOME
unset _OLD_VIRTUAL_PYTHONHOME
fi
# Call hash to forget past commands. Without forgetting
# past commands the $PATH changes we made may not be respected
hash -r 2> /dev/null
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
PS1="${_OLD_VIRTUAL_PS1:-}"
export PS1
unset _OLD_VIRTUAL_PS1
fi
unset VIRTUAL_ENV
unset VIRTUAL_ENV_PROMPT
if [ ! "${1:-}" = "nondestructive" ] ; then
# Self destruct!
unset -f deactivate
fi
}
# unset irrelevant variables
deactivate nondestructive
# on Windows, a path can contain colons and backslashes and has to be converted:
if [ "${OSTYPE:-}" = "cygwin" ] || [ "${OSTYPE:-}" = "msys" ] ; then
# transform D:\path\to\venv to /d/path/to/venv on MSYS
# and to /cygdrive/d/path/to/venv on Cygwin
export VIRTUAL_ENV=$(cygpath /home/pi/Beta/beta/venv)
else
# use the path as-is
export VIRTUAL_ENV=/home/pi/Beta/beta/venv
fi
_OLD_VIRTUAL_PATH="$PATH"
PATH="$VIRTUAL_ENV/"bin":$PATH"
export PATH
# unset PYTHONHOME if set
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
# could use `if (set -u; : $PYTHONHOME) ;` in bash
if [ -n "${PYTHONHOME:-}" ] ; then
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
unset PYTHONHOME
fi
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
_OLD_VIRTUAL_PS1="${PS1:-}"
PS1='(venv) '"${PS1:-}"
export PS1
VIRTUAL_ENV_PROMPT='(venv) '
export VIRTUAL_ENV_PROMPT
fi
# Call hash to forget past commands. Without forgetting
# past commands the $PATH changes we made may not be respected
hash -r 2> /dev/null

View file

@ -1,27 +0,0 @@
# This file must be used with "source bin/activate.csh" *from csh*.
# You cannot run it directly.
# Created by Davide Di Blasi <davidedb@gmail.com>.
# Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>
alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate'
# Unset irrelevant variables.
deactivate nondestructive
setenv VIRTUAL_ENV /home/pi/Beta/beta/venv
set _OLD_VIRTUAL_PATH="$PATH"
setenv PATH "$VIRTUAL_ENV/"bin":$PATH"
set _OLD_VIRTUAL_PROMPT="$prompt"
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
set prompt = '(venv) '"$prompt"
setenv VIRTUAL_ENV_PROMPT '(venv) '
endif
alias pydoc python -m pydoc
rehash

View file

@ -1,69 +0,0 @@
# This file must be used with "source <venv>/bin/activate.fish" *from fish*
# (https://fishshell.com/). You cannot run it directly.
function deactivate -d "Exit virtual environment and return to normal shell environment"
# reset old environment variables
if test -n "$_OLD_VIRTUAL_PATH"
set -gx PATH $_OLD_VIRTUAL_PATH
set -e _OLD_VIRTUAL_PATH
end
if test -n "$_OLD_VIRTUAL_PYTHONHOME"
set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
set -e _OLD_VIRTUAL_PYTHONHOME
end
if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
set -e _OLD_FISH_PROMPT_OVERRIDE
# prevents error when using nested fish instances (Issue #93858)
if functions -q _old_fish_prompt
functions -e fish_prompt
functions -c _old_fish_prompt fish_prompt
functions -e _old_fish_prompt
end
end
set -e VIRTUAL_ENV
set -e VIRTUAL_ENV_PROMPT
if test "$argv[1]" != "nondestructive"
# Self-destruct!
functions -e deactivate
end
end
# Unset irrelevant variables.
deactivate nondestructive
set -gx VIRTUAL_ENV /home/pi/Beta/beta/venv
set -gx _OLD_VIRTUAL_PATH $PATH
set -gx PATH "$VIRTUAL_ENV/"bin $PATH
# Unset PYTHONHOME if set.
if set -q PYTHONHOME
set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
set -e PYTHONHOME
end
if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
# fish uses a function instead of an env var to generate the prompt.
# Save the current fish_prompt function as the function _old_fish_prompt.
functions -c fish_prompt _old_fish_prompt
# With the original prompt function renamed, we can override with our own.
function fish_prompt
# Save the return status of the last command.
set -l old_status $status
# Output the venv prompt; color taken from the blue of the Python logo.
printf "%s%s%s" (set_color 4B8BBE) '(venv) ' (set_color normal)
# Restore the return status of the previous command.
echo "exit $old_status" | .
# Output the original/"old" prompt.
_old_fish_prompt
end
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
set -gx VIRTUAL_ENV_PROMPT '(venv) '
end

View file

@ -1,8 +0,0 @@
#!/home/pi/Beta/beta/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from ctranslate2.converters.fairseq import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

View file

@ -1,8 +0,0 @@
#!/home/pi/Beta/beta/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from ctranslate2.converters.marian import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

View file

@ -1,8 +0,0 @@
#!/home/pi/Beta/beta/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from ctranslate2.converters.openai_gpt2 import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

View file

@ -1,8 +0,0 @@
#!/home/pi/Beta/beta/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from ctranslate2.converters.opennmt_py import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

View file

@ -1,8 +0,0 @@
#!/home/pi/Beta/beta/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from ctranslate2.converters.opennmt_tf import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

View file

@ -1,8 +0,0 @@
#!/home/pi/Beta/beta/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from ctranslate2.converters.opus_mt import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

View file

@ -1,8 +0,0 @@
#!/home/pi/Beta/beta/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from ctranslate2.converters.transformers import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

View file

@ -1,8 +0,0 @@
#!/home/pi/Beta/beta/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from dotenv.__main__ import cli
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(cli())

View file

@ -1,8 +0,0 @@
#!/home/pi/Beta/beta/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from numpy.f2py.f2py2e import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

View file

@ -1,8 +0,0 @@
#!/home/pi/Beta/beta/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from fastapi.cli import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

View file

@ -1,8 +0,0 @@
#!/home/pi/Beta/beta/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from huggingface_hub.cli.hf import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

View file

@ -1,8 +0,0 @@
#!/home/pi/Beta/beta/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from httpx import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

View file

@ -1,8 +0,0 @@
#!/home/pi/Beta/beta/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from huggingface_hub.cli.deprecated_cli import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

View file

@ -1,8 +0,0 @@
#!/home/pi/Beta/beta/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from idna.cli import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

View file

@ -1,8 +0,0 @@
#!/home/pi/Beta/beta/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from markdown_it.cli.parse import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

View file

@ -1,8 +0,0 @@
#!/home/pi/Beta/beta/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from numpy._configtool import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

View file

@ -1,8 +0,0 @@
#!/home/pi/Beta/beta/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from onnxruntime.tools.onnxruntime_test import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

View file

@ -1,8 +0,0 @@
#!/home/pi/Beta/beta/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from pip._internal.cli.main import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

View file

@ -1,8 +0,0 @@
#!/home/pi/Beta/beta/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from pip._internal.cli.main import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

View file

@ -1,8 +0,0 @@
#!/home/pi/Beta/beta/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from pip._internal.cli.main import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

View file

@ -1,8 +0,0 @@
#!/home/pi/Beta/beta/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from pytest import console_main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(console_main())

View file

@ -1,8 +0,0 @@
#!/home/pi/Beta/beta/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from av.__main__ import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

View file

@ -1,8 +0,0 @@
#!/home/pi/Beta/beta/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from pygments.cmdline import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

View file

@ -1,8 +0,0 @@
#!/home/pi/Beta/beta/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from pytest import console_main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(console_main())

View file

@ -1 +0,0 @@
python3

View file

@ -1 +0,0 @@
/usr/bin/python3

View file

@ -1 +0,0 @@
python3

View file

@ -1,8 +0,0 @@
#!/home/pi/Beta/beta/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from huggingface_hub.inference._mcp.cli import app
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(app())

View file

@ -1,8 +0,0 @@
#!/home/pi/Beta/beta/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from tqdm.cli import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

View file

@ -1,8 +0,0 @@
#!/home/pi/Beta/beta/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from typer.cli import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

Some files were not shown because too many files have changed in this diff Show more