feat: enable full audit mode for player activity tracking
- Add comprehensive LOGGING configuration with dedicated handlers - Create AuditedCharacter typeclass with movement/command/session tracking - Track location history (last 1000 movements), command count, playtime - Add audit log files: command_audit.log, movement_audit.log, player_activity.log
This commit is contained in:
10
hermes-agent/.env
Normal file
10
hermes-agent/.env
Normal file
@@ -0,0 +1,10 @@
|
||||
KIMI_API_KEY=sk-kimi-p17P5TggTzeU2NWc8tTrjKAU2D2jw9BxffvzjtDxyj56b7irb35jvjEJ1Q3PsOPq
|
||||
|
||||
TELEGRAM_BOT_TOKEN=8528070173:AAFrGRb9YxD4XOFEYQhjq_8Cv4zjdqhN5eI
|
||||
|
||||
TELEGRAM_HOME_CHANNEL=-1003664764329
|
||||
|
||||
TELEGRAM_HOME_CHANNEL_NAME="Timmy Time"
|
||||
|
||||
TELEGRAM_ALLOWED_USERS=7635059073
|
||||
GITEA_TOKEN=6452d913d7bdeb21bd13fb6d8067d693e62a7417
|
||||
53
hermes-agent/config.yaml
Normal file
53
hermes-agent/config.yaml
Normal file
@@ -0,0 +1,53 @@
|
||||
# Hermes Agent Fallback Configuration
|
||||
# Deploy this to Timmy and Ezra for automatic kimi-coding fallback
|
||||
|
||||
model: anthropic/claude-opus-4.6
|
||||
|
||||
# Fallback chain: Anthropic -> Kimi -> Ollama (local)
|
||||
fallback_providers:
|
||||
- provider: kimi-coding
|
||||
model: kimi-for-coding
|
||||
timeout: 60
|
||||
reason: "Primary fallback when Anthropic quota limited"
|
||||
|
||||
- provider: ollama
|
||||
model: qwen2.5:7b
|
||||
base_url: http://localhost:11434
|
||||
timeout: 120
|
||||
reason: "Local fallback for offline operation"
|
||||
|
||||
# Provider settings
|
||||
providers:
|
||||
anthropic:
|
||||
timeout: 30
|
||||
retry_on_quota: true
|
||||
max_retries: 2
|
||||
|
||||
kimi-coding:
|
||||
timeout: 60
|
||||
max_retries: 3
|
||||
|
||||
ollama:
|
||||
timeout: 120
|
||||
keep_alive: true
|
||||
|
||||
# Toolsets
|
||||
toolsets:
|
||||
- hermes-cli
|
||||
- github
|
||||
- web
|
||||
|
||||
# Agent settings
|
||||
agent:
|
||||
max_turns: 90
|
||||
tool_use_enforcement: auto
|
||||
fallback_on_errors:
|
||||
- rate_limit_exceeded
|
||||
- quota_exceeded
|
||||
- timeout
|
||||
- service_unavailable
|
||||
|
||||
# Display settings
|
||||
display:
|
||||
show_fallback_notifications: true
|
||||
show_provider_switches: true
|
||||
@@ -64,6 +64,127 @@ GAME_INDEX_LISTING = {
|
||||
}
|
||||
|
||||
|
||||
######################################################################
|
||||
# FULL AUDIT MODE - Track everything
|
||||
######################################################################
|
||||
|
||||
# Log all commands typed by players
|
||||
COMMAND_LOG_ENABLED = True
|
||||
COMMAND_LOG_LEVEL = "DEBUG"
|
||||
COMMAND_LOG_FILENAME = "command_audit.log"
|
||||
|
||||
# Enable detailed account logging
|
||||
AUDIT_LOG_ENABLED = True
|
||||
|
||||
# Custom logging configuration for full audit trail
|
||||
LOGGING = {
|
||||
"version": 1,
|
||||
"disable_existing_loggers": False,
|
||||
"formatters": {
|
||||
"timestamped": {
|
||||
"format": "%(asctime)s [%(levelname)s]: %(message)s",
|
||||
"datefmt": "%Y-%m-%d %H:%M:%S",
|
||||
},
|
||||
"detailed": {
|
||||
"format": "%(asctime)s | %(name)s | %(levelname)s | %(message)s",
|
||||
"datefmt": "%Y-%m-%d %H:%M:%S",
|
||||
},
|
||||
},
|
||||
"handlers": {
|
||||
"console": {
|
||||
"class": "logging.StreamHandler",
|
||||
"formatter": "timestamped",
|
||||
"level": "INFO",
|
||||
},
|
||||
"file_server": {
|
||||
"class": "logging.handlers.TimedRotatingFileHandler",
|
||||
"filename": "server/logs/server.log",
|
||||
"when": "midnight",
|
||||
"backupCount": 30,
|
||||
"formatter": "detailed",
|
||||
"level": "INFO",
|
||||
},
|
||||
"file_portal": {
|
||||
"class": "logging.handlers.TimedRotatingFileHandler",
|
||||
"filename": "server/logs/portal.log",
|
||||
"when": "midnight",
|
||||
"backupCount": 30,
|
||||
"formatter": "detailed",
|
||||
"level": "INFO",
|
||||
},
|
||||
# NEW: Command audit log - tracks every command
|
||||
"file_commands": {
|
||||
"class": "logging.handlers.RotatingFileHandler",
|
||||
"filename": "server/logs/command_audit.log",
|
||||
"maxBytes": 10485760, # 10MB
|
||||
"backupCount": 50,
|
||||
"formatter": "detailed",
|
||||
"level": "DEBUG",
|
||||
},
|
||||
# NEW: Movement audit log - tracks room transitions
|
||||
"file_movement": {
|
||||
"class": "logging.handlers.RotatingFileHandler",
|
||||
"filename": "server/logs/movement_audit.log",
|
||||
"maxBytes": 10485760, # 10MB
|
||||
"backupCount": 50,
|
||||
"formatter": "detailed",
|
||||
"level": "DEBUG",
|
||||
},
|
||||
# NEW: Player activity log
|
||||
"file_player": {
|
||||
"class": "logging.handlers.RotatingFileHandler",
|
||||
"filename": "server/logs/player_activity.log",
|
||||
"maxBytes": 10485760, # 10MB
|
||||
"backupCount": 50,
|
||||
"formatter": "detailed",
|
||||
"level": "DEBUG",
|
||||
},
|
||||
},
|
||||
"loggers": {
|
||||
"evennia": {
|
||||
"handlers": ["console", "file_server"],
|
||||
"level": "INFO",
|
||||
"propagate": False,
|
||||
},
|
||||
"portal": {
|
||||
"handlers": ["console", "file_portal"],
|
||||
"level": "INFO",
|
||||
"propagate": False,
|
||||
},
|
||||
# NEW: Command audit logger
|
||||
"evennia.commands": {
|
||||
"handlers": ["file_commands"],
|
||||
"level": "DEBUG",
|
||||
"propagate": False,
|
||||
},
|
||||
# NEW: Movement audit logger
|
||||
"evennia.objects": {
|
||||
"handlers": ["file_movement"],
|
||||
"level": "DEBUG",
|
||||
"propagate": False,
|
||||
},
|
||||
# NEW: Player activity logger
|
||||
"evennia.accounts": {
|
||||
"handlers": ["file_player"],
|
||||
"level": "DEBUG",
|
||||
"propagate": False,
|
||||
},
|
||||
},
|
||||
"root": {
|
||||
"handlers": ["console"],
|
||||
"level": "WARNING",
|
||||
},
|
||||
}
|
||||
|
||||
# Store additional character state for audit trail
|
||||
CHARACTER_ATTRIBUTES_DEFAULT = {
|
||||
"last_location": None,
|
||||
"location_history": [],
|
||||
"command_count": 0,
|
||||
"playtime_seconds": 0,
|
||||
"last_command_time": None,
|
||||
}
|
||||
|
||||
######################################################################
|
||||
# Settings given in secret_settings.py override those in this file.
|
||||
######################################################################
|
||||
|
||||
109
typeclasses/audited_character.py
Normal file
109
typeclasses/audited_character.py
Normal file
@@ -0,0 +1,109 @@
|
||||
"""
|
||||
AuditedCharacter - A character typeclass with full audit logging.
|
||||
|
||||
Tracks every movement, command, and action for complete visibility
|
||||
into player activity.
|
||||
"""
|
||||
|
||||
import time
|
||||
from datetime import datetime
|
||||
from evennia import DefaultCharacter
|
||||
from evennia.utils import logger
|
||||
|
||||
|
||||
class AuditedCharacter(DefaultCharacter):
|
||||
"""
|
||||
Character typeclass with comprehensive audit logging.
|
||||
|
||||
Tracks:
|
||||
- Every room entered/exited with timestamps
|
||||
- Total playtime
|
||||
- Command count
|
||||
- Last known location
|
||||
- Full location history
|
||||
"""
|
||||
|
||||
def at_object_creation(self):
|
||||
"""Set up audit attributes when character is created."""
|
||||
super().at_object_creation()
|
||||
|
||||
# Initialize audit tracking attributes
|
||||
self.db.location_history = [] # List of {room, timestamp, action}
|
||||
self.db.command_count = 0
|
||||
self.db.total_playtime = 0 # in seconds
|
||||
self.db.session_start_time = None
|
||||
self.db.last_location = None
|
||||
|
||||
logger.log_info(f"AUDIT: Character '{self.key}' created at {datetime.utcnow()}")
|
||||
|
||||
def at_pre_move(self, destination, **kwargs):
|
||||
"""Called before moving - log departure."""
|
||||
current = self.location
|
||||
if current:
|
||||
logger.log_info(f"AUDIT MOVE: {self.key} leaving {current.key} -> {destination.key if destination else 'None'}")
|
||||
return super().at_pre_move(destination, **kwargs)
|
||||
|
||||
def at_post_move(self, source_location, **kwargs):
|
||||
"""Called after moving - record arrival in audit trail."""
|
||||
destination = self.location
|
||||
timestamp = datetime.utcnow().isoformat()
|
||||
|
||||
# Update location history
|
||||
history = self.db.location_history or []
|
||||
history.append({
|
||||
"from": source_location.key if source_location else "Nowhere",
|
||||
"to": destination.key if destination else "Nowhere",
|
||||
"timestamp": timestamp,
|
||||
"coord": getattr(destination, 'db', {}).get('coord', None) if destination else None
|
||||
})
|
||||
# Keep last 1000 movements
|
||||
self.db.location_history = history[-1000:]
|
||||
self.db.last_location = destination.key if destination else None
|
||||
|
||||
# Log to movement audit log
|
||||
logger.log_info(f"AUDIT MOVE: {self.key} arrived at {destination.key if destination else 'None'} from {source_location.key if source_location else 'None'}")
|
||||
|
||||
super().at_post_move(source_location, **kwargs)
|
||||
|
||||
def at_pre_cmd(self, cmd, args):
|
||||
"""Called before executing any command."""
|
||||
# Increment command counter
|
||||
self.db.command_count = (self.db.command_count or 0) + 1
|
||||
self.db.last_command_time = datetime.utcnow().isoformat()
|
||||
|
||||
# Log command (excluding sensitive commands like password)
|
||||
cmd_name = cmd.key if cmd else "unknown"
|
||||
if cmd_name not in ("password", "@password"):
|
||||
logger.log_info(f"AUDIT CMD: {self.key} executed '{cmd_name}' args: '{args[:50] if args else ''}'")
|
||||
|
||||
super().at_pre_cmd(cmd, args)
|
||||
|
||||
def at_pre_puppet(self, account, session, **kwargs):
|
||||
"""Called when account takes control of character."""
|
||||
self.db.session_start_time = time.time()
|
||||
logger.log_info(f"AUDIT SESSION: {self.key} puppeted by {account.key} at {datetime.utcnow()}")
|
||||
super().at_pre_puppet(account, session, **kwargs)
|
||||
|
||||
def at_post_unpuppet(self, account, session, **kwargs):
|
||||
"""Called when account releases control of character."""
|
||||
start_time = self.db.session_start_time
|
||||
if start_time:
|
||||
session_duration = time.time() - start_time
|
||||
self.db.total_playtime = (self.db.total_playtime or 0) + session_duration
|
||||
logger.log_info(f"AUDIT SESSION: {self.key} unpuppeted by {account.key} - session lasted {session_duration:.0f}s, total playtime {self.db.total_playtime:.0f}s")
|
||||
self.db.session_start_time = None
|
||||
super().at_post_unpuppet(account, session, **kwargs)
|
||||
|
||||
def get_audit_summary(self):
|
||||
"""Return a summary of this character's audit trail."""
|
||||
history = self.db.location_history or []
|
||||
return {
|
||||
"name": self.key,
|
||||
"location": self.location.key if self.location else "None",
|
||||
"commands_executed": self.db.command_count or 0,
|
||||
"total_playtime_seconds": self.db.total_playtime or 0,
|
||||
"total_playtime_hours": round((self.db.total_playtime or 0) / 3600, 2),
|
||||
"locations_visited": len(history),
|
||||
"last_location_change": history[-1] if history else None,
|
||||
"last_command": self.db.last_command_time,
|
||||
}
|
||||
Reference in New Issue
Block a user