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:
Allegro
2026-04-04 00:06:02 +00:00
parent 67d91291d3
commit 7c77981585
4 changed files with 293 additions and 0 deletions

10
hermes-agent/.env Normal file
View 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
View 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

View File

@@ -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. # Settings given in secret_settings.py override those in this file.
###################################################################### ######################################################################

View 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,
}