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,
|
"cooldown_seconds": 10,
|
||||||
"max_commands_per_response": 8,
|
"max_commands_per_response": 8,
|
||||||
"interventions_per_day": 24,
|
"interventions_per_day": 48,
|
||||||
"god_chat_prefix": "[§b§lPAPER GOD§r]",
|
"god_chat_prefix": "[§b§lPAPER GOD§r]",
|
||||||
"debug_commands": true,
|
"debug_commands": true,
|
||||||
"sudo_enabled": true,
|
"sudo_enabled": true,
|
||||||
|
|||||||
+197
-9
@@ -27,19 +27,19 @@ log = logging.getLogger(__name__)
|
|||||||
CONFIG_PATH = '/etc/mc_aigod_paper.json'
|
CONFIG_PATH = '/etc/mc_aigod_paper.json'
|
||||||
|
|
||||||
PRAY_PATTERNS = [
|
PRAY_PATTERNS = [
|
||||||
re.compile(r'\[.*?\]: <(\w+)> [Pp]ray (.+)'),
|
re.compile(r'\[.*?\]: (?:\[Not Secure\] )?<(\w+)> [Pp]ray (.+)'),
|
||||||
]
|
]
|
||||||
|
|
||||||
BIBLE_PATTERNS = [
|
BIBLE_PATTERNS = [
|
||||||
re.compile(r'\[.*?\]: <(\w+)> [Bb]ible\s*$'),
|
re.compile(r'\[.*?\]: (?:\[Not Secure\] )?<(\w+)> [Bb]ible\s*$'),
|
||||||
]
|
]
|
||||||
|
|
||||||
SUDO_PATTERNS = [
|
SUDO_PATTERNS = [
|
||||||
re.compile(r'\[.*?\]: <(\w+)> [Ss]udo (.+)'),
|
re.compile(r'\[.*?\]: (?:\[Not Secure\] )?<(\w+)> [Ss]udo (.+)'),
|
||||||
]
|
]
|
||||||
|
|
||||||
BUG_LOG_PATTERNS = [
|
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')
|
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")
|
bf.write("(no raw lines available)\n")
|
||||||
|
|
||||||
log.info(f"BUG_LOG recorded by {player}: {desc} -> {bug_path}")
|
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(
|
rcon(
|
||||||
f'tellraw {player} {{"text":"[BUG_LOG] Logged. Thank you.","color":"green"}}',
|
f'tellraw {player} {{"text":"[BUG_LOG] Logged. Thank you.","color":"green"}}',
|
||||||
config["rcon_host"], config["rcon_port"], config["rcon_password"]
|
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"]
|
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
|
# Training Audit Log — structured JSONL for dataset expansion
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -1271,7 +1273,7 @@ def write_training_feedback(player: str, description: str, config: dict):
|
|||||||
"description": description,
|
"description": description,
|
||||||
"last_sudo_context": {
|
"last_sudo_context": {
|
||||||
"prompt": last_sudo.get("prompt", ""),
|
"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),
|
"ineffective": last_sudo.get("ineffective", False),
|
||||||
} if last_sudo else None,
|
} if last_sudo else None,
|
||||||
"last_prayer_context": {
|
"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",
|
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:
|
def fix_give_command(cmd: str) -> str:
|
||||||
"""
|
"""
|
||||||
Correct common LLM give command mistakes:
|
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()
|
tail = tail[idx + len(marker):].strip()
|
||||||
|
|
||||||
fixed_tail = tail
|
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_give_command(fixed_tail)
|
||||||
fixed_tail = fix_effect_command(fixed_tail)
|
fixed_tail = fix_effect_command(fixed_tail)
|
||||||
fixed_tail = fix_gamemode_command(fixed_tail, fallback_player)
|
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()
|
resolved = resolved.strip()
|
||||||
if resolved.startswith("/"):
|
if resolved.startswith("/"):
|
||||||
resolved = resolved[1:]
|
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_give_command(resolved)
|
||||||
resolved = fix_effect_command(resolved)
|
resolved = fix_effect_command(resolved)
|
||||||
resolved = fix_gamemode_command(resolved, fallback_player)
|
resolved = fix_gamemode_command(resolved, fallback_player)
|
||||||
|
|||||||
Reference in New Issue
Block a user