diff --git a/README.md b/README.md index c4f1187..34a608a 100644 --- a/README.md +++ b/README.md @@ -158,6 +158,10 @@ Main file: `/etc/mc_aigod.json` | `bug_log_path` | string | File path used for `bug_log` entries | | `bug_log_event_lines` | int | Number of in-memory interesting events to attach | | `bug_log_raw_lines` | int | Number of raw tail log lines to attach | +| `bug_log_service_lines` | int | Number of recent AI action lines from service log | +| `tp_safety_enabled` | bool | Auto-protect risky vertical teleports with safety effects | +| `tp_safety_vertical_delta` | number | Relative `~ ~N ~` threshold that triggers safety wrapper | +| `tp_safety_absolute_y` | number | Absolute Y threshold that triggers safety wrapper | ### Current shrink-world example diff --git a/SESSION.md b/SESSION.md index fbc35a7..9af2edc 100644 --- a/SESSION.md +++ b/SESSION.md @@ -112,6 +112,10 @@ This section captures decisions and context accumulated across conversations wit - **Session gateway (Paper fork only):** The LangGraph-style FastAPI sidecar adds multi-turn memory without requiring a full LangGraph install. Safety enforcement stays in `mc_aigod_paper.py`, not the gateway. - **Bug intake trigger (2026-03-17):** Added `bug_log ` chat command in vanilla AI God. It appends structured bug entries to `/var/log/mc_aigod_bug.log` with player, description, recent in-memory events, and recent raw `latest.log` lines, then confirms to the player in chat. - **God voice update (2026-03-17):** Increased default God persona emphasis on irony, dark humor, and sarcastic one-liners in both command and message system prompts (vanilla + Paper variants) while keeping command strictness unchanged. +- **Paper bug report triage (2026-03-17):** `bug_log` captured a case where a prayer reward path issued vertical teleport (`execute as slingshooter08 run tp slingshooter08 ~ ~10 ~`) and felt unsafe/unintended for a build-oriented prayer; indicates need for pray-path movement guardrails and safer teleport policy. +- **Teleport safety guard (2026-03-17):** Added execution-time TP safety wrapper in vanilla `execute_response`: risky upward teleports (relative delta >= configured threshold or high absolute Y) are prefixed with `slow_falling` + `resistance` effects to prevent accidental lethal falls while keeping punishment/spectacle behavior. +- **Bug-log signal upgrade (2026-03-17):** `bug_log` entries now include filtered raw log lines (RCON connect/disconnect noise removed) plus a new "RECENT AI ACTIONS" section from service logs (`Prayer from`, `Executing RCON`, `SUDO execute`, results) for faster triage. +- **Weather normalization fix (2026-03-17):** added explicit prompt/context rule and command normalizer in vanilla script mapping `weather storm|rainstorm|thunderstorm` -> `weather thunder` before validation/execution. ### Infrastructure decisions @@ -119,6 +123,7 @@ This section captures decisions and context accumulated across conversations wit - **mc-godmode.service startup:** Already enabled (`systemctl is-enabled` = enabled), `WantedBy=multi-user.target`, `After=mcsm-daemon.service`. The service was not failing — the MC server itself wasn't auto-starting (fixed by enabling autoStart). - **shrink-world aigod tail fix (2026-03-17):** `mc-aigod.service` stayed active but stopped reacting to chat after log file recreation because `tail_log()` used a single `open()+readline()` handle. Updated `/usr/local/bin/mc_aigod.py` to detect inode changes/truncation and reopen `latest.log` automatically; restarted `mc-aigod.service` on CT 644. - **sudo hallucination observed (2026-03-17):** after tail fix, `sudo destroy my surroundings` was parsed and executed as `execute as slingshooter08 run tp @e[type=player,tag=destroyed] ~ ~ ~` and `execute as slingshooter08 run kill @e[type=player,tag=destroyed]`. Both returned empty RCON results; current validator allowed them because prefix-only checks pass and only `give/effect` have syntax repair. +- **Context/prompt refinement (2026-03-17):** updated vanilla sudo/god prompts to emphasize strict 1.21 syntax (including `effect give ...`, no old enchant NBT, weather clear/rain/thunder only), concise JSON outputs, and avoidance of accidental high vertical teleports in benevolent responses; increased vanilla sudo translator token budget from 180 to 260 to reduce truncated JSON on longer kit requests. - **bug_log deployment status (2026-03-17):** local repo had `bug_log` feature before CT deployment. `/usr/local/bin/mc_aigod.py` on CT 644 did not contain `BUG_LOG_PATTERNS` or bible help text until redeployed; pushed updated script + config and restarted `mc-aigod.service` successfully. ### Open threads / next ideas diff --git a/mc_aigod.py b/mc_aigod.py index 3a7cc00..8ca8700 100644 --- a/mc_aigod.py +++ b/mc_aigod.py @@ -254,14 +254,38 @@ def _bug_log_path(config) -> str: def _read_recent_raw_log_lines(log_path: str, max_lines: int) -> list: - lines = deque(maxlen=max_lines) + lines = deque(maxlen=max_lines * 4) + noise = re.compile(r'RCON (?:Listener|Client)|Thread RCON Client') try: with open(log_path, 'r', encoding='utf-8', errors='replace') as f: for line in f: - lines.append(line.rstrip('\n')) + clean = line.rstrip('\n') + if noise.search(clean): + continue + lines.append(clean) except Exception as e: return [f""] - return list(lines) + return list(lines)[-max_lines:] + + +def _service_log_path(config) -> str: + return config.get("service_log_path", "/var/log/mc_aigod.log") + + +def _read_recent_service_action_lines(path: str, max_lines: int) -> list: + lines = deque(maxlen=max_lines * 8) + keep = re.compile( + r'Prayer from|SUDO from|Executing RCON:|SUDO execute:|RCON result:|SUDO result:|BUG_LOG from' + ) + try: + with open(path, 'r', encoding='utf-8', errors='replace') as f: + for line in f: + clean = line.rstrip('\n') + if keep.search(clean): + lines.append(clean) + except Exception as e: + return [f""] + return list(lines)[-max_lines:] def _format_recent_event_lines(max_events: int) -> list: @@ -281,9 +305,11 @@ def process_bug_log(player: str, description: str, config): event_count = int(config.get("bug_log_event_lines", 40)) raw_count = int(config.get("bug_log_raw_lines", 120)) + service_count = int(config.get("bug_log_service_lines", 30)) recent_events = _format_recent_event_lines(event_count) raw_lines = _read_recent_raw_log_lines(config.get("log_path", ""), raw_count) + service_lines = _read_recent_service_action_lines(_service_log_path(config), service_count) bug_path = _bug_log_path(config) timestamp = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S UTC') @@ -302,6 +328,11 @@ def process_bug_log(player: str, description: str, config): bf.write("\n".join(recent_events) + "\n") else: bf.write("(no recent in-memory events captured)\n") + bf.write("\n-- RECENT AI ACTIONS (SERVICE LOG) --\n") + if service_lines: + bf.write("\n".join(service_lines) + "\n") + else: + bf.write("(no AI service lines available)\n") bf.write("\n-- RECENT RAW SERVER LOG LINES --\n") if raw_lines: bf.write("\n".join(raw_lines) + "\n") @@ -644,6 +675,8 @@ MOVEMENT: NEVER use: tp minecraft:the_nether (this is wrong syntax) WORLD/ENVIRONMENT (affects all players): + SYNTAX: weather [duration] + NOTE: 'storm' is invalid; if intent says storm/rainstorm/thunderstorm use thunder. time set day time set night weather clear 6000 @@ -705,9 +738,12 @@ def build_system_prompt(config): "- Do not ask a player to gather materials they clearly don't have access to or that are rare relative to their current situation.\n" "- For give commands: use any valid Minecraft 1.21 item ID following the Item Naming Rules. Do not guess item IDs — consult the naming rules and common IDs list.\n" "- For all other commands: only use forms shown in the Command Palette. Do not invent new command types.\n" + "- Weather values are only clear, rain, or thunder. If you mean storm/rainstorm/thunderstorm, output thunder.\n" "- Reward humble, genuine prayers. Punish hubris, blasphemy, or naked greed.\n" + "- Repeated greedy demands after recent gifts should usually receive correction (rebuke, debuff, or symbolic punishment), not more rewards.\n" "- Powerful rewards (netherite, enchanted_golden_apple, totem) must be rare.\n" "- kill {target} is reserved for extreme blasphemy only.\n" + "- Avoid lethal accidents from vertical teleports. If using a high upward teleport as punishment or spectacle, pair it with slow_falling or resistance unless explicit execution is intended.\n" "- When angered, chain commands: thunder + lightning + debuffs = divine wrath.\n\n" "=== COMMAND PALETTE ===\n" f"{COMMAND_PALETTE}\n" @@ -828,6 +864,9 @@ COMMANDS_SYSTEM_PROMPT = ( "- kill is reserved for extreme blasphemy only.\n" "- For give: syntax is always give minecraft: \n" "- Count comes LAST. Namespace prefix minecraft: is REQUIRED.\n" + "- For effects: use 'effect give minecraft: '.\n" + "- For weather use only clear/rain/thunder (NOT storm).\n" + "- Avoid accidental lethal movement in benevolent responses; do not launch players high unless explicitly asked.\n" "- Beds: white_bed not bed. Logs: oak_log not log. Wool: white_wool not wool.\n" "- Chain commands for dramatic effect: thunder + lightning + blindness = wrath.\n\n" + "=== COMMAND PALETTE ===\n" @@ -850,7 +889,10 @@ def build_sudo_commands_system_prompt(config=None) -> str: "- If the request cannot be mapped safely, return commands: [].\n" "- If player says 'me' or 'my', target the requesting player.\n" "- You will receive LAST 10 SUDO ACTIONS. Use them for continuity and corrections when the player says previous output was wrong.\n" + "- Keep output concise (usually <= 6 commands) and valid JSON only.\n" "- For give syntax: give minecraft: (count LAST, namespace required)\n" + "- For effect syntax: effect give minecraft: [hideParticles]\n" + "- Never use old NBT enchant syntax like {Enchantments:[...]}; use item[enchantments={...}] only.\n" "- Return commands only. No commentary.\n" "\n" "=== TELEPORT SYNTAX ===\n" @@ -940,6 +982,7 @@ def build_message_system_prompt(config) -> str: "Write a single spoken message to all players reacting to this prayer and action.\n" "Respond with ONLY the message text — no JSON, no quotes, no formatting. " "Be vivid and dramatic, with occasional godlike sarcasm and irony. Any length is fine.\n" + "For punishments, prefer fear and humiliation over accidental instant death unless destruction is explicitly intended.\n" ) lore = config.get("god_lore", "") if lore: @@ -1210,6 +1253,53 @@ def _tp_inside_worldborder(cmd: str, config) -> bool: return abs(x - cx) <= limit and abs(z - cz) <= limit + +def _tp_safety_enabled(config) -> bool: + return bool(config.get("tp_safety_enabled", True)) + + +def _extract_tp_target_y(cmd: str): + m = re.search(r'\btp\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)', cmd) + if not m: + return None, None + return m.group(1), m.group(3) + + +def _needs_vertical_tp_safety(resolved_cmd: str, config) -> tuple: + target, ytok = _extract_tp_target_y(resolved_cmd) + if not target or not ytok: + return False, "" + + risky_delta = float(config.get("tp_safety_vertical_delta", 8.0)) + risky_abs_y = float(config.get("tp_safety_absolute_y", 120.0)) + + if ytok.startswith("~"): + offs = ytok[1:].strip() + dy = float(offs) if offs else 0.0 + if dy >= risky_delta: + return True, target + return False, target + + try: + abs_y = float(ytok) + if abs_y >= risky_abs_y: + return True, target + except Exception: + pass + return False, target + + +def _tp_safety_prefix_commands(resolved_cmd: str, config) -> list: + if not _tp_safety_enabled(config): + return [] + needs, target = _needs_vertical_tp_safety(resolved_cmd, config) + if not needs or not target: + return [] + return [ + f"effect give {target} minecraft:slow_falling 20 0", + f"effect give {target} minecraft:resistance 8 1", + ] + def fix_give_command(cmd: str) -> str: """ Correct common LLM give command mistakes: @@ -1274,11 +1364,21 @@ def fix_effect_command(cmd: str) -> str: return fixed return cmd + +def fix_weather_command(cmd: str) -> str: + """Normalize weather synonyms to valid Minecraft literals.""" + raw = (cmd or "").strip() + fixed = re.sub(r'\bweather\s+(storm|rainstorm|thunderstorm)\b', 'weather thunder', raw, flags=re.IGNORECASE) + if fixed != raw: + log.warning(f"Fixed weather syntax: '{cmd}' -> '{fixed}'") + return fixed + def validate_command(cmd, online_players, fallback_player, config=None): """Replace placeholders, auto-fix common give syntax errors, check safe prefix.""" resolved = cmd.replace("{player}", fallback_player).replace("{target}", fallback_player) resolved = fix_give_command(resolved) resolved = fix_effect_command(resolved) + resolved = fix_weather_command(resolved) caps = get_server_capabilities(config) if config else SERVER_CAPABILITIES[DEFAULT_SERVER_TYPE] prefixes = caps["safe_prefixes"] if not any(resolved.startswith(p) for p in prefixes): @@ -1347,6 +1447,17 @@ def execute_response(response, context, config, praying_player=None): resolved, is_safe = validate_command(cmd, context["online_players"], fallback, config) if not is_safe: continue + + safety_prefix = _tp_safety_prefix_commands(resolved, config) + for scmd in safety_prefix: + sresolved, safe_ok = validate_command(scmd, context["online_players"], fallback, config) + if not safe_ok: + continue + log.info(f"Executing RCON: {sresolved}") + sresult = rcon(sresolved, config["rcon_host"], config["rcon_port"], config["rcon_password"]) + log.info(f"RCON result: {sresult!r}") + time.sleep(0.15) + log.info(f"Executing RCON: {resolved}") result = rcon(resolved, config["rcon_host"], config["rcon_port"], config["rcon_password"]) log.info(f"RCON result: {result!r}") @@ -1526,7 +1637,7 @@ def process_sudo(player, prompt, config): config=config, fmt="json", temperature=0.1, - max_tokens=180, + max_tokens=260, ) parsed = _parse_llm_json(content) commands = parsed.get("commands") or [] diff --git a/mc_aigod_shrink.json b/mc_aigod_shrink.json index 404456c..1d8d9b9 100644 --- a/mc_aigod_shrink.json +++ b/mc_aigod_shrink.json @@ -21,6 +21,10 @@ "bug_log_path": "/var/log/mc_aigod_bug.log", "bug_log_event_lines": 40, "bug_log_raw_lines": 120, + "bug_log_service_lines": 30, + "tp_safety_enabled": true, + "tp_safety_vertical_delta": 8, + "tp_safety_absolute_y": 120, "tp_border_guard_enabled": true, "worldborder_center_x": 0, "worldborder_center_z": 0,