5 validator fixes, [Not Secure] regex, interventions 48/day
Validator improvements (deployed to paper-ai):
- Fix 1: @s → player name (RCON has no executor entity)
- Fix 2: Old NBT {Enchantments:[...]} → 1.21 [enchantments={...}] converter
- Fix 3: Strip invalid item components (display, durability, enc)
- Fix 4: Hallucinated effect repair (invincibility→resistance, etc.)
- Fix 5: Hallucinated command repair (setworldborder→worldborder set, etc.)
- All fixes applied in execute-tail chains too
Chat patterns updated for online-mode=false ([Not Secure] prefix)
Divine interventions set to 48/day
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -32,7 +32,7 @@
|
||||
],
|
||||
"cooldown_seconds": 10,
|
||||
"max_commands_per_response": 8,
|
||||
"interventions_per_day": 24,
|
||||
"interventions_per_day": 48,
|
||||
"god_chat_prefix": "[§b§lPAPER GOD§r]",
|
||||
"debug_commands": true,
|
||||
"sudo_enabled": true,
|
||||
|
||||
+197
-9
@@ -27,19 +27,19 @@ log = logging.getLogger(__name__)
|
||||
CONFIG_PATH = '/etc/mc_aigod_paper.json'
|
||||
|
||||
PRAY_PATTERNS = [
|
||||
re.compile(r'\[.*?\]: <(\w+)> [Pp]ray (.+)'),
|
||||
re.compile(r'\[.*?\]: (?:\[Not Secure\] )?<(\w+)> [Pp]ray (.+)'),
|
||||
]
|
||||
|
||||
BIBLE_PATTERNS = [
|
||||
re.compile(r'\[.*?\]: <(\w+)> [Bb]ible\s*$'),
|
||||
re.compile(r'\[.*?\]: (?:\[Not Secure\] )?<(\w+)> [Bb]ible\s*$'),
|
||||
]
|
||||
|
||||
SUDO_PATTERNS = [
|
||||
re.compile(r'\[.*?\]: <(\w+)> [Ss]udo (.+)'),
|
||||
re.compile(r'\[.*?\]: (?:\[Not Secure\] )?<(\w+)> [Ss]udo (.+)'),
|
||||
]
|
||||
|
||||
BUG_LOG_PATTERNS = [
|
||||
re.compile(r'\[.*?\]: <(\w+)> [Bb]ug[_ ]?[Ll]og(?:\s+(.+))?\s*$'),
|
||||
re.compile(r'\[.*?\]: (?:\[Not Secure\] )?<(\w+)> [Bb]ug[_ ]?[Ll]og(?:\s+(.+))?\s*$'),
|
||||
]
|
||||
|
||||
JOIN_PATTERN = re.compile(r'\[.*?\]: (\w+) joined the game')
|
||||
@@ -1162,10 +1162,6 @@ def process_bug_log(player: str, description: str, config):
|
||||
bf.write("(no raw lines available)\n")
|
||||
|
||||
log.info(f"BUG_LOG recorded by {player}: {desc} -> {bug_path}")
|
||||
|
||||
# Also write structured feedback to the training audit log
|
||||
write_training_feedback(player, desc, config)
|
||||
|
||||
rcon(
|
||||
f'tellraw {player} {{"text":"[BUG_LOG] Logged. Thank you.","color":"green"}}',
|
||||
config["rcon_host"], config["rcon_port"], config["rcon_password"]
|
||||
@@ -1177,6 +1173,12 @@ def process_bug_log(player: str, description: str, config):
|
||||
config["rcon_host"], config["rcon_port"], config["rcon_password"]
|
||||
)
|
||||
|
||||
# Training audit — separate try block so bug_log success isn't affected
|
||||
try:
|
||||
write_training_feedback(player, desc, config)
|
||||
except Exception as e:
|
||||
log.error(f"Training feedback write failed for {player}: {e}", exc_info=True)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Training Audit Log — structured JSONL for dataset expansion
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -1271,7 +1273,7 @@ def write_training_feedback(player: str, description: str, config: dict):
|
||||
"description": description,
|
||||
"last_sudo_context": {
|
||||
"prompt": last_sudo.get("prompt", ""),
|
||||
"results": last_sudo.get("results", []),
|
||||
"results": [list(r) if isinstance(r, tuple) else r for r in last_sudo.get("results", [])],
|
||||
"ineffective": last_sudo.get("ineffective", False),
|
||||
} if last_sudo else None,
|
||||
"last_prayer_context": {
|
||||
@@ -2395,6 +2397,172 @@ def _tp_safety_prefix_commands(resolved_cmd: str, config) -> list:
|
||||
f"effect give {target} minecraft:resistance 8 1",
|
||||
]
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Fix 1: @s → player name in RCON context
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def fix_at_s_selector(cmd: str, fallback_player: str) -> str:
|
||||
"""Replace @s with the requesting player's name.
|
||||
RCON has no executor entity, so @s always fails with 'No entity was found'."""
|
||||
if '@s' not in cmd:
|
||||
return cmd
|
||||
fixed = cmd.replace('@s', fallback_player)
|
||||
if fixed != cmd:
|
||||
log.warning(f"Fixed @s selector: '{cmd}' -> '{fixed}'")
|
||||
return fixed
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Fix 2: Old NBT enchantment → 1.21 component syntax
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def fix_nbt_enchantment_syntax(cmd: str) -> str:
|
||||
"""Convert old NBT {Enchantments:[{id:...,lvl:...}]} and {Enchantments:{...}}
|
||||
to 1.21 component syntax item[enchantments={name:level}]."""
|
||||
if 'Enchantments' not in cmd and 'enchantments' not in cmd:
|
||||
return cmd
|
||||
# Pattern 1: item{Enchantments:[{id:"name",lvl:N}, ...]}
|
||||
m = re.match(r'^(give\s+\S+\s+)(minecraft:\w+)\{Enchantments:\[(.+?)\]\}(.*)$', cmd, re.I)
|
||||
if m:
|
||||
pre, item, body, rest = m.groups()
|
||||
enchants = {}
|
||||
for eid, lvl in re.findall(r'id:[\"\']?(?:minecraft:)?([a-z_]+)[\"\']?\s*,\s*lvl[\"\']?:?\s*[\"\']?(\d+)', body):
|
||||
enchants[eid] = lvl
|
||||
if enchants:
|
||||
ench_str = ','.join(f'{k}:{v}' for k, v in enchants.items())
|
||||
fixed = f"{pre}{item}[enchantments={{{ench_str}}}]{rest}"
|
||||
log.warning(f"Fixed NBT enchant (list): '{cmd}' -> '{fixed}'")
|
||||
return fixed
|
||||
|
||||
# Pattern 2: item{Enchantments:{minecraft:name:N, ...}} (non-standard)
|
||||
m = re.match(r'^(give\s+\S+\s+)(?:minecraft:)?(\w+)\{Enchantments:\{(.+?)\}\}(.*)$', cmd, re.I)
|
||||
if m:
|
||||
pre, item, body, rest = m.groups()
|
||||
enchants = {}
|
||||
for eid, lvl in re.findall(r'(?:minecraft:)?([a-z_]+):(\d+)', body):
|
||||
enchants[eid] = lvl
|
||||
if enchants:
|
||||
ench_str = ','.join(f'{k}:{v}' for k, v in enchants.items())
|
||||
fixed = f"{pre}minecraft:{item}[enchantments={{{ench_str}}}]{rest}"
|
||||
log.warning(f"Fixed NBT enchant (dict): '{cmd}' -> '{fixed}'")
|
||||
return fixed
|
||||
|
||||
return cmd
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Fix 3: Strip invalid item components
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_INVALID_COMPONENTS = re.compile(
|
||||
r',?\s*(?:display|durability|enc|Durability|Display|Lore|CustomModelData|HideFlags'
|
||||
r'|Paper|Unbreakable|Displayname|CustomName)(?::\{[^}]*\}|=[^,\]}\s]+|:[^,\]}\s]+)',
|
||||
re.I,
|
||||
)
|
||||
|
||||
def fix_invalid_item_components(cmd: str) -> str:
|
||||
"""Strip unrecognized item components from give commands.
|
||||
Removes display:{...}, durability=N, enc=N, etc."""
|
||||
if not cmd.startswith('give ') or '[' not in cmd:
|
||||
return cmd
|
||||
# Find the component section: item[...]
|
||||
m = re.search(r'(\[.+?\])', cmd)
|
||||
if not m:
|
||||
return cmd
|
||||
bracket = m.group(1)
|
||||
cleaned = _INVALID_COMPONENTS.sub('', bracket)
|
||||
# Clean up dangling commas
|
||||
cleaned = re.sub(r'\{,', '{', cleaned)
|
||||
cleaned = re.sub(r',\}', '}', cleaned)
|
||||
cleaned = re.sub(r',\]', ']', cleaned)
|
||||
if cleaned != bracket:
|
||||
# Remove empty brackets entirely
|
||||
if cleaned in ('[]', '[,]'):
|
||||
cleaned = ''
|
||||
fixed = cmd[:m.start()] + cleaned + cmd[m.end():]
|
||||
log.warning(f"Stripped invalid components: '{cmd}' -> '{fixed}'")
|
||||
return fixed
|
||||
return cmd
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Fix 4: Hallucinated effect/item validation with fuzzy matching
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
VALID_EFFECTS = {
|
||||
'speed', 'slowness', 'haste', 'mining_fatigue', 'strength', 'instant_health',
|
||||
'instant_damage', 'jump_boost', 'nausea', 'regeneration', 'resistance',
|
||||
'fire_resistance', 'water_breathing', 'invisibility', 'blindness', 'night_vision',
|
||||
'hunger', 'weakness', 'poison', 'wither', 'health_boost', 'absorption',
|
||||
'saturation', 'glowing', 'levitation', 'luck', 'unluck', 'slow_falling',
|
||||
'conduit_power', 'dolphins_grace', 'bad_omen', 'hero_of_the_village',
|
||||
'darkness', 'trial_omen', 'raid_omen', 'wind_charged', 'weaving', 'oozing',
|
||||
'infested',
|
||||
}
|
||||
|
||||
EFFECT_ALIASES = {
|
||||
'invincibility': 'resistance', 'invulnerability': 'resistance',
|
||||
'invulnerable': 'resistance', 'invincible': 'resistance',
|
||||
'experience_boost': 'luck', 'xp_boost': 'luck',
|
||||
'slow_speed': 'slowness', 'slow': 'slowness',
|
||||
'fast': 'speed', 'swift': 'speed', 'quick': 'speed',
|
||||
'do_not_breed': 'weakness',
|
||||
'fire': 'fire_resistance',
|
||||
'healing': 'instant_health', 'heal': 'instant_health',
|
||||
'damage': 'instant_damage', 'harm': 'instant_damage',
|
||||
'swim': 'dolphins_grace', 'swimming': 'dolphins_grace',
|
||||
'flying': 'levitation', 'float': 'slow_falling',
|
||||
}
|
||||
|
||||
def fix_hallucinated_effect(cmd: str) -> str:
|
||||
"""Replace hallucinated effect names with the closest valid effect."""
|
||||
m = re.match(r'^(effect\s+give\s+\S+\s+minecraft:)(\w+)(.*)$', cmd)
|
||||
if not m:
|
||||
return cmd
|
||||
pre, effect, rest = m.groups()
|
||||
if effect in VALID_EFFECTS:
|
||||
return cmd
|
||||
replacement = EFFECT_ALIASES.get(effect)
|
||||
if replacement:
|
||||
fixed = f"{pre}{replacement}{rest}"
|
||||
log.warning(f"Fixed hallucinated effect: '{effect}' -> '{replacement}' in '{cmd}'")
|
||||
return fixed
|
||||
# No known alias — log but don't block (let RCON report the error for training data)
|
||||
log.warning(f"Unknown effect '{effect}' in: {cmd}")
|
||||
return cmd
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Fix 5: Hallucinated command repair
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
COMMAND_ALIASES = {
|
||||
r'^setworldborder\s+(\d+)': lambda m: f"worldborder set {m.group(1)}",
|
||||
r'^setspawn\b': lambda m: f"spawnpoint",
|
||||
r'^heal\s+(.+)': lambda m: f"effect give {m.group(1)} minecraft:instant_health 1 1",
|
||||
r'^difficulty set\s+(\w+)': lambda m: f"difficulty {m.group(1)}",
|
||||
r'^commands?\b': lambda m: None, # drop the garbage 'commands' token
|
||||
r'^gamerule\s+timeSpeed\s+': lambda m: None, # not a real gamerule
|
||||
}
|
||||
|
||||
def fix_hallucinated_command(cmd: str) -> str:
|
||||
"""Rewrite commonly hallucinated commands to their real equivalents."""
|
||||
for pattern, fixer in COMMAND_ALIASES.items():
|
||||
m = re.match(pattern, cmd, re.I)
|
||||
if m:
|
||||
result = fixer(m)
|
||||
if result is None:
|
||||
log.warning(f"Dropped hallucinated command: '{cmd}'")
|
||||
return ""
|
||||
log.warning(f"Fixed hallucinated command: '{cmd}' -> '{result}'")
|
||||
return result
|
||||
return cmd
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Existing fix functions
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def fix_give_command(cmd: str) -> str:
|
||||
"""
|
||||
Correct common LLM give command mistakes:
|
||||
@@ -2574,6 +2742,13 @@ def _repair_execute_tail(cmd: str, fallback_player: str) -> str:
|
||||
tail = tail[idx + len(marker):].strip()
|
||||
|
||||
fixed_tail = tail
|
||||
fixed_tail = fix_hallucinated_command(fixed_tail)
|
||||
if not fixed_tail:
|
||||
return ""
|
||||
fixed_tail = fix_at_s_selector(fixed_tail, fallback_player)
|
||||
fixed_tail = fix_nbt_enchantment_syntax(fixed_tail)
|
||||
fixed_tail = fix_invalid_item_components(fixed_tail)
|
||||
fixed_tail = fix_hallucinated_effect(fixed_tail)
|
||||
fixed_tail = fix_give_command(fixed_tail)
|
||||
fixed_tail = fix_effect_command(fixed_tail)
|
||||
fixed_tail = fix_gamemode_command(fixed_tail, fallback_player)
|
||||
@@ -2594,6 +2769,19 @@ def validate_command(cmd, online_players, fallback_player, config=None):
|
||||
resolved = resolved.strip()
|
||||
if resolved.startswith("/"):
|
||||
resolved = resolved[1:]
|
||||
# Fix 5: hallucinated commands (must run first — may drop or rewrite entire command)
|
||||
resolved = fix_hallucinated_command(resolved)
|
||||
if not resolved:
|
||||
return "", False
|
||||
# Fix 1: @s → player name (RCON has no executor entity)
|
||||
resolved = fix_at_s_selector(resolved, fallback_player)
|
||||
# Fix 2: old NBT enchantment → 1.21 component syntax
|
||||
resolved = fix_nbt_enchantment_syntax(resolved)
|
||||
# Fix 3: strip invalid item components (display, durability, enc, etc.)
|
||||
resolved = fix_invalid_item_components(resolved)
|
||||
# Fix 4: hallucinated effect names → closest valid effect
|
||||
resolved = fix_hallucinated_effect(resolved)
|
||||
# Existing fixes
|
||||
resolved = fix_give_command(resolved)
|
||||
resolved = fix_effect_command(resolved)
|
||||
resolved = fix_gamemode_command(resolved, fallback_player)
|
||||
|
||||
Reference in New Issue
Block a user