Fix sudo follow-up status checks and intent repair gaps
This commit is contained in:
+107
-22
@@ -9,6 +9,7 @@ Config: /etc/mc_aigod.json
|
||||
import json, os, random, re, socket, struct, threading, time, logging
|
||||
from collections import deque
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict
|
||||
import shutil
|
||||
from urllib.parse import parse_qs, unquote, urljoin, urlparse
|
||||
import requests
|
||||
@@ -80,6 +81,9 @@ sudo_history: deque = deque() # entries: (ts, player, prompt, translated_
|
||||
SUDO_FAILURE_SIZE = 20
|
||||
sudo_failures: deque = deque() # entries: (ts, player, command, error)
|
||||
|
||||
# Last sudo execution feedback by player for follow-up questions.
|
||||
last_sudo_feedback: Dict[str, Dict[str, Any]] = {}
|
||||
|
||||
_memory_lock = threading.Lock()
|
||||
|
||||
# Gateway client session mapping (player+mode -> session_id)
|
||||
@@ -1012,6 +1016,21 @@ def get_sudo_failures_block(player: str = "") -> str:
|
||||
return "\n=== RECENT FAILED SUDO PATTERNS ===\n" + "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def set_last_sudo_feedback(player: str, prompt: str, results_seen: list, ineffective: bool):
|
||||
with _memory_lock:
|
||||
last_sudo_feedback[player] = {
|
||||
"ts": time.time(),
|
||||
"prompt": (prompt or "")[:240],
|
||||
"ineffective": bool(ineffective),
|
||||
"results": [(c[:220], (r or "")[:240]) for c, r in results_seen[:12]],
|
||||
}
|
||||
|
||||
|
||||
def get_last_sudo_feedback(player: str) -> Dict[str, Any]:
|
||||
with _memory_lock:
|
||||
return dict(last_sudo_feedback.get(player, {}))
|
||||
|
||||
|
||||
def _bug_log_path(config) -> str:
|
||||
return config.get("bug_log_path", "/var/log/mc_aigod_paper_bug.log")
|
||||
|
||||
@@ -2306,16 +2325,36 @@ def fix_weather_command(cmd: str) -> str:
|
||||
def fix_fill_fire_command(cmd: str) -> str:
|
||||
"""Fix legacy fill syntax like `... fire 0 replace air` for 1.21."""
|
||||
raw = (cmd or "").strip()
|
||||
m = re.match(r'^(fill\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+)(minecraft:)?(fire|soul_fire)\s+0\s+replace\s+air$', raw, flags=re.IGNORECASE)
|
||||
m = re.match(
|
||||
r'^(fill\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+)(minecraft:)?(fire|soul_fire)\s+0\s+replace\s+(\S+)$',
|
||||
raw,
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
if not m:
|
||||
return raw
|
||||
prefix, ns, block = m.groups()
|
||||
prefix, ns, block, repl = m.groups()
|
||||
block_id = f"minecraft:{block.lower()}" if not ns else f"{ns.lower()}{block.lower()}"
|
||||
fixed = f"{prefix}{block_id} replace air"
|
||||
fixed = f"{prefix}{block_id} replace {repl.lower()}"
|
||||
log.warning(f"Fixed fill fire syntax: '{cmd}' -> '{fixed}'")
|
||||
return fixed
|
||||
|
||||
|
||||
def _split_execute_tail(cmd: str):
|
||||
"""Return (wrappers, tail) for execute chains."""
|
||||
tail = (cmd or "").strip()
|
||||
wrappers = []
|
||||
for _ in range(6):
|
||||
if not tail.startswith("execute "):
|
||||
break
|
||||
marker = " run "
|
||||
idx = tail.find(marker)
|
||||
if idx < 0:
|
||||
break
|
||||
wrappers.append(tail[: idx + len(marker)])
|
||||
tail = tail[idx + len(marker):].strip()
|
||||
return wrappers, tail
|
||||
|
||||
|
||||
def fix_bow_enchant_syntax(cmd: str) -> str:
|
||||
"""Rewrite old bow Enchantments NBT to 1.21 component format."""
|
||||
raw = (cmd or "").strip()
|
||||
@@ -2418,6 +2457,11 @@ def _is_destructive_intent(prompt: str) -> bool:
|
||||
return any(k in p for k in keys)
|
||||
|
||||
|
||||
def _is_fire_intent(prompt: str) -> bool:
|
||||
p = (prompt or "").lower()
|
||||
return any(k in p for k in ("fire", "ignite", "burn", "flame"))
|
||||
|
||||
|
||||
def _normalize_sudo_command_shape(cmd: str, player: str) -> str:
|
||||
c = (cmd or "").strip()
|
||||
if not c:
|
||||
@@ -2565,6 +2609,20 @@ def _repair_failed_sudo_commands(player: str, results_seen: list, config) -> lis
|
||||
if len(out) >= max_retry:
|
||||
return out[:max_retry]
|
||||
|
||||
# Fire fill repair: remove legacy metadata and simplify execution anchor.
|
||||
if "incorrect argument" in r and "fill" in c and "fire" in c:
|
||||
w, tail = _split_execute_tail(c)
|
||||
tail = fix_fill_fire_command(tail)
|
||||
out.append(f"execute at {player} run {tail}")
|
||||
if len(out) >= max_retry:
|
||||
return out[:max_retry]
|
||||
|
||||
# Empty result for large fire fill: retry with tighter vertical band.
|
||||
if (not r.strip()) and ("fill" in c and "fire" in c):
|
||||
out.append(f"execute at {player} run fill ~-25 ~-1 ~-25 ~25 ~3 ~25 minecraft:fire replace air")
|
||||
if len(out) >= max_retry:
|
||||
return out[:max_retry]
|
||||
|
||||
return out[:max_retry]
|
||||
|
||||
|
||||
@@ -2582,22 +2640,23 @@ def _expand_tnt_commands_from_prompt(commands: list, prompt: str, player: str, c
|
||||
if len(commands) >= target:
|
||||
return commands
|
||||
|
||||
summons = [c for c in commands if "summon" in c and "tnt" in c]
|
||||
if not summons:
|
||||
first = None
|
||||
wrappers = []
|
||||
x = y = z = None
|
||||
for c in commands:
|
||||
w, tail = _split_execute_tail(c)
|
||||
m = re.match(r'^summon\s+(?:minecraft:)?tnt\s+(\S+)\s+(\S+)\s+(\S+)(?:\s+\{.*\})?$', tail)
|
||||
if not m:
|
||||
continue
|
||||
first = c
|
||||
wrappers = w
|
||||
x, y, z = m.groups()
|
||||
break
|
||||
if not first:
|
||||
return commands
|
||||
|
||||
base = summons[0]
|
||||
prefix = ""
|
||||
body = base
|
||||
m_pref = re.match(rf'^(execute\s+at\s+{re.escape(player)}\s+run\s+)(.+)$', base)
|
||||
if m_pref:
|
||||
prefix = m_pref.group(1)
|
||||
body = m_pref.group(2)
|
||||
|
||||
m = re.match(r'^summon\s+(?:minecraft:)?tnt\s+(\S+)\s+(\S+)\s+(\S+)(?:\s+\{.*\})?$', body)
|
||||
if not m:
|
||||
return commands
|
||||
x, y, z = m.groups()
|
||||
x = x or "~"
|
||||
y = y or "~1"
|
||||
z = z or "~"
|
||||
|
||||
expanded = []
|
||||
for i in range(target):
|
||||
@@ -2609,7 +2668,9 @@ def _expand_tnt_commands_from_prompt(commands: list, prompt: str, player: str, c
|
||||
xx = "~" if dx == 0 else f"~{dx}"
|
||||
if z.startswith("~"):
|
||||
zz = "~" if dz == 0 else f"~{dz}"
|
||||
expanded.append(f"{prefix}summon minecraft:tnt {xx} {y} {zz}")
|
||||
tail = f"summon minecraft:tnt {xx} {y} {zz}"
|
||||
cmd = "".join(wrappers) + tail if wrappers else tail
|
||||
expanded.append(cmd)
|
||||
|
||||
log.warning(f"Expanded TNT commands from {len(commands)} to {len(expanded)} (requested={requested}, cap={cap})")
|
||||
return expanded
|
||||
@@ -2865,9 +2926,15 @@ def process_sudo(player, prompt, config):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _local_lookup_fallback_answer(query: str, ref_cmd: str) -> str:
|
||||
def _local_lookup_fallback_answer(query: str, ref_cmd: str, last_feedback: Dict[str, Any]) -> str:
|
||||
q = (query or "").lower()
|
||||
rc = (ref_cmd or "").lower()
|
||||
if re.search(r'\bdid that command do what i asked\b', q):
|
||||
if not last_feedback:
|
||||
return "I do not have enough recent execution context to verify that yet."
|
||||
if bool(last_feedback.get("ineffective", False)):
|
||||
return "Likely no. Recent execution results indicate the command was ineffective or partially failed."
|
||||
return "Likely yes. Recent execution results indicate the command completed successfully."
|
||||
if "invisible" in q and "mob" in q and "invisibility" in rc:
|
||||
return "Invisibility greatly reduces mob detection, but it does not make you perfectly undetectable at close range or while making noise/actions."
|
||||
return ""
|
||||
@@ -2897,6 +2964,7 @@ def process_sudo(player, prompt, config):
|
||||
if last_cmd:
|
||||
_send_private(player, f"context command: {last_cmd}", config, "dark_gray")
|
||||
|
||||
last_fb = get_last_sudo_feedback(player)
|
||||
wiki_rows = _info_lookup_wiki(lookup_query)
|
||||
web_rows = _info_lookup_web(lookup_query)
|
||||
gateway_msg = ""
|
||||
@@ -2947,7 +3015,7 @@ def process_sudo(player, prompt, config):
|
||||
_send_private(player, f"- {tool}: {q}", config, "dark_gray")
|
||||
|
||||
if not wiki_rows and not web_rows and not gateway_msg:
|
||||
fb = _local_lookup_fallback_answer(lookup_query, last_cmd)
|
||||
fb = _local_lookup_fallback_answer(lookup_query, last_cmd, last_fb)
|
||||
if fb:
|
||||
_send_private(player, f"- {fb}", config, "gray")
|
||||
else:
|
||||
@@ -3193,7 +3261,7 @@ def process_sudo(player, prompt, config):
|
||||
ineffective = (len(executed) == 0) or (effective_hits == 0)
|
||||
|
||||
# Adaptive fallback for destructive intent when output appears ineffective.
|
||||
if _is_destructive_intent(prompt) and ineffective:
|
||||
if _is_destructive_intent(prompt) and ineffective and "tnt" not in prompt.lower():
|
||||
fallback_cmds = _build_destructive_fallback(player, config)
|
||||
log.warning(f"SUDO destructive fallback engaged for prompt={prompt!r}: {fallback_cmds}")
|
||||
_send_private(player, "[SUDO] Initial plan was weak; applying destructive fallback.", config, "yellow")
|
||||
@@ -3211,10 +3279,27 @@ def process_sudo(player, prompt, config):
|
||||
effective_hits = sum(1 for _, res in results_seen if _sudo_result_is_effective(res))
|
||||
ineffective = (len(executed) == 0) or (effective_hits == 0)
|
||||
|
||||
if _is_fire_intent(prompt) and ineffective:
|
||||
fire_retry = [f"execute at {player} run fill ~-25 ~-1 ~-25 ~25 ~3 ~25 minecraft:fire replace air"]
|
||||
log.warning(f"SUDO fire fallback engaged for prompt={prompt!r}: {fire_retry}")
|
||||
for cmd in fire_retry:
|
||||
resolved, is_safe = validate_command(cmd, online, player, config)
|
||||
if not is_safe:
|
||||
continue
|
||||
log.info(f"SUDO fire fallback execute: {resolved}")
|
||||
result = rcon(resolved, config["rcon_host"], config["rcon_port"], config["rcon_password"])
|
||||
log.info(f"SUDO fire fallback result: {result!r}")
|
||||
executed.append(resolved)
|
||||
results_seen.append((resolved, str(result or "")))
|
||||
time.sleep(0.15)
|
||||
effective_hits = sum(1 for _, res in results_seen if _sudo_result_is_effective(res))
|
||||
ineffective = (len(executed) == 0) or (effective_hits == 0)
|
||||
|
||||
for cmd, res in results_seen:
|
||||
if not _sudo_result_is_effective(res):
|
||||
add_sudo_failure(player, cmd, res)
|
||||
|
||||
set_last_sudo_feedback(player, prompt, results_seen, ineffective)
|
||||
_report_sudo_feedback(player, prompt, commands, results_seen, ineffective, config)
|
||||
|
||||
add_sudo_history(player, prompt, commands, executed)
|
||||
|
||||
Reference in New Issue
Block a user