Remove debug pane
This commit is contained in:
parent
52deb1db6a
commit
0458811e02
@ -28,7 +28,6 @@ class GameEngine:
|
|||||||
on_thought: callable = None,
|
on_thought: callable = None,
|
||||||
on_action: callable = None,
|
on_action: callable = None,
|
||||||
on_player_roll: callable = None,
|
on_player_roll: callable = None,
|
||||||
on_debug: callable = None,
|
|
||||||
) -> TurnResult:
|
) -> TurnResult:
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
state.append_llm_log(f"\n{'='*60}")
|
state.append_llm_log(f"\n{'='*60}")
|
||||||
@ -48,8 +47,6 @@ class GameEngine:
|
|||||||
|
|
||||||
if on_action:
|
if on_action:
|
||||||
on_action("DM is preparing a response")
|
on_action("DM is preparing a response")
|
||||||
if on_debug:
|
|
||||||
on_debug("config", {"model": model, "temperature": lm.get("temperature"), "max_tokens": lm.get("max_tokens"), "strategy": "tools"})
|
|
||||||
|
|
||||||
system = build_system_prompt(recent_narrative=recent_narrative, recent_log=session_log)
|
system = build_system_prompt(recent_narrative=recent_narrative, recent_log=session_log)
|
||||||
base_parts = []
|
base_parts = []
|
||||||
@ -89,7 +86,6 @@ class GameEngine:
|
|||||||
text = call_llm(
|
text = call_llm(
|
||||||
[{"role": "system", "content": system}, {"role": "user", "content": user}],
|
[{"role": "system", "content": system}, {"role": "user", "content": user}],
|
||||||
label="Turn generation",
|
label="Turn generation",
|
||||||
on_debug=on_debug,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if not text or not text.strip():
|
if not text or not text.strip():
|
||||||
@ -102,7 +98,7 @@ class GameEngine:
|
|||||||
raw = text.strip()
|
raw = text.strip()
|
||||||
state.append_llm_log(f"\n[TOOL] got {len(raw)} chars in {(datetime.now() - start_time).total_seconds() * 1000:.1f}ms")
|
state.append_llm_log(f"\n[TOOL] got {len(raw)} chars in {(datetime.now() - start_time).total_seconds() * 1000:.1f}ms")
|
||||||
|
|
||||||
tool_calls = extract_tool_calls(raw, on_debug=on_debug)
|
tool_calls = extract_tool_calls(raw)
|
||||||
if not tool_calls:
|
if not tool_calls:
|
||||||
state.append_llm_log("\n[TOOL] no tool blocks found")
|
state.append_llm_log("\n[TOOL] no tool blocks found")
|
||||||
|
|
||||||
@ -141,12 +137,8 @@ class GameEngine:
|
|||||||
changes=state_changes,
|
changes=state_changes,
|
||||||
story=recent_narrative,
|
story=recent_narrative,
|
||||||
log=session_log,
|
log=session_log,
|
||||||
on_debug=on_debug,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if on_debug:
|
|
||||||
on_debug("turn_validation", {"valid": valid, "reason": reason, "action": action, "attempt": total_attempts})
|
|
||||||
|
|
||||||
if valid:
|
if valid:
|
||||||
state.append_llm_log(f"\n[TURN VALID] {reason}")
|
state.append_llm_log(f"\n[TURN VALID] {reason}")
|
||||||
elif reason == "Unrecognized":
|
elif reason == "Unrecognized":
|
||||||
@ -219,19 +211,6 @@ class GameEngine:
|
|||||||
|
|
||||||
if on_action:
|
if on_action:
|
||||||
on_action("Turn complete")
|
on_action("Turn complete")
|
||||||
if on_debug:
|
|
||||||
applied = len([tc for tc in tool_calls if tc.get("tool") not in ("finalize_turn", "narrative")])
|
|
||||||
on_debug("phase_done", {
|
|
||||||
"book_log_chars": len(book_log),
|
|
||||||
"log_entry": log_entry,
|
|
||||||
"ambience": ambience,
|
|
||||||
"extract_errors": errors or None,
|
|
||||||
"total_elapsed_ms": total_elapsed,
|
|
||||||
"tool_calls_count": len(tool_calls),
|
|
||||||
"applied_changes_count": applied,
|
|
||||||
"tool_call_results": tool_calls,
|
|
||||||
"total_attempts": total_attempts,
|
|
||||||
})
|
|
||||||
|
|
||||||
log_turn_details(
|
log_turn_details(
|
||||||
player_action=player_action or "",
|
player_action=player_action or "",
|
||||||
@ -245,7 +224,6 @@ class GameEngine:
|
|||||||
log_entry=log_entry or "",
|
log_entry=log_entry or "",
|
||||||
ambience=ambience,
|
ambience=ambience,
|
||||||
tool_calls=tool_calls,
|
tool_calls=tool_calls,
|
||||||
on_debug=on_debug,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return TurnResult(
|
return TurnResult(
|
||||||
|
|||||||
@ -30,7 +30,6 @@ def call_llm(
|
|||||||
timeout: int | None = None,
|
timeout: int | None = None,
|
||||||
max_tokens: int | None = None,
|
max_tokens: int | None = None,
|
||||||
label: str = "",
|
label: str = "",
|
||||||
on_debug: callable = None,
|
|
||||||
) -> str | None:
|
) -> str | None:
|
||||||
"""Make a single LLM call. Loads config automatically. Returns content text or None on error."""
|
"""Make a single LLM call. Loads config automatically. Returns content text or None on error."""
|
||||||
from .config import load_config
|
from .config import load_config
|
||||||
@ -45,8 +44,6 @@ def call_llm(
|
|||||||
try:
|
try:
|
||||||
import litellm
|
import litellm
|
||||||
except ImportError:
|
except ImportError:
|
||||||
if on_debug:
|
|
||||||
on_debug("llm_error", {"label": label, "error": "litellm not installed"})
|
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
response = litellm.completion(
|
response = litellm.completion(
|
||||||
@ -67,6 +64,4 @@ def call_llm(
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
err_msg = f"{type(e).__name__}: {e}"
|
err_msg = f"{type(e).__name__}: {e}"
|
||||||
append_llm_log(f"\n--- LLM ERROR ({label}) ---\n{err_msg}")
|
append_llm_log(f"\n--- LLM ERROR ({label}) ---\n{err_msg}")
|
||||||
if on_debug:
|
|
||||||
on_debug("llm_error", {"label": label, "error": err_msg})
|
|
||||||
return None
|
return None
|
||||||
|
|||||||
@ -18,13 +18,11 @@ def log_turn_details(
|
|||||||
log_entry: str,
|
log_entry: str,
|
||||||
ambience: Optional[str],
|
ambience: Optional[str],
|
||||||
tool_calls: list,
|
tool_calls: list,
|
||||||
on_debug=None,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Write structured turn summary to llm.log and fire TUI debug event."""
|
"""Write structured turn summary to llm.log and fire TUI debug event."""
|
||||||
ts = datetime.now().isoformat()
|
ts = datetime.now().isoformat()
|
||||||
output_chars = len(book_log)
|
output_chars = len(book_log)
|
||||||
output_words = len(book_log.split()) if book_log else 0
|
output_words = len(book_log.split()) if book_log else 0
|
||||||
applied = len([tc for tc in tool_calls if tc.get("tool") not in ("finalize_turn", "narrative")])
|
|
||||||
|
|
||||||
state.append_llm_log("")
|
state.append_llm_log("")
|
||||||
state.append_llm_log(f"┌─ Turn Details — {ts}")
|
state.append_llm_log(f"┌─ Turn Details — {ts}")
|
||||||
@ -41,20 +39,3 @@ def log_turn_details(
|
|||||||
state.append_llm_log(
|
state.append_llm_log(
|
||||||
"└─────────────────────────────────────────────────────────────────────────────────────────┘"
|
"└─────────────────────────────────────────────────────────────────────────────────────────┘"
|
||||||
)
|
)
|
||||||
|
|
||||||
if on_debug:
|
|
||||||
on_debug("turn_details", {
|
|
||||||
"timestamp": ts,
|
|
||||||
"model": model,
|
|
||||||
"temperature": temperature,
|
|
||||||
"max_tokens": max_tokens,
|
|
||||||
"strategy_name": strategy_name,
|
|
||||||
"die_roll": die_roll,
|
|
||||||
"player_action": player_action,
|
|
||||||
"book_log_chars": output_chars,
|
|
||||||
"book_log_words": output_words,
|
|
||||||
"ambience": ambience,
|
|
||||||
"tool_calls_count": len(tool_calls),
|
|
||||||
"applied_changes_count": applied,
|
|
||||||
"tool_call_results": tool_calls,
|
|
||||||
})
|
|
||||||
|
|||||||
@ -230,7 +230,7 @@ def describe_change(tool_name: str, args: dict) -> str:
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def extract_tool_calls(text: str, on_debug: callable = None) -> list[dict]:
|
def extract_tool_calls(text: str) -> list[dict]:
|
||||||
"""Extract tool calls from ```tool blocks in LLM response."""
|
"""Extract tool calls from ```tool blocks in LLM response."""
|
||||||
calls = []
|
calls = []
|
||||||
seen = set()
|
seen = set()
|
||||||
|
|||||||
@ -51,7 +51,6 @@ def validate_action(
|
|||||||
*,
|
*,
|
||||||
story: str = "",
|
story: str = "",
|
||||||
log: str = "",
|
log: str = "",
|
||||||
on_debug: callable = None,
|
|
||||||
) -> tuple[bool, str]:
|
) -> tuple[bool, str]:
|
||||||
"""Ask the LLM whether a player action is valid given the game state. Returns (valid, reason)."""
|
"""Ask the LLM whether a player action is valid given the game state. Returns (valid, reason)."""
|
||||||
if not player_action:
|
if not player_action:
|
||||||
@ -72,7 +71,6 @@ def validate_action(
|
|||||||
max_tokens=1024,
|
max_tokens=1024,
|
||||||
temperature=0.2,
|
temperature=0.2,
|
||||||
label="Action validation",
|
label="Action validation",
|
||||||
on_debug=on_debug,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if not text:
|
if not text:
|
||||||
@ -86,12 +84,8 @@ def validate_action(
|
|||||||
data = json.loads(cleaned)
|
data = json.loads(cleaned)
|
||||||
valid = data.get("valid", True)
|
valid = data.get("valid", True)
|
||||||
reason = data.get("reason", "")
|
reason = data.get("reason", "")
|
||||||
if on_debug:
|
|
||||||
on_debug("action_validation", {"valid": valid, "reason": reason, "action": player_action})
|
|
||||||
return valid, reason
|
return valid, reason
|
||||||
except (json.JSONDecodeError, ValueError):
|
except (json.JSONDecodeError, ValueError):
|
||||||
if on_debug:
|
|
||||||
on_debug("action_validation", {"valid": True, "reason": "parse_failed", "raw": text[:200]})
|
|
||||||
if attempt == 0:
|
if attempt == 0:
|
||||||
messages.append({
|
messages.append({
|
||||||
"role": "system",
|
"role": "system",
|
||||||
@ -198,7 +192,6 @@ def validate_turn(
|
|||||||
changes: list[dict] | None = None,
|
changes: list[dict] | None = None,
|
||||||
story: str = "",
|
story: str = "",
|
||||||
log: str = "",
|
log: str = "",
|
||||||
on_debug: callable = None,
|
|
||||||
) -> tuple[bool, str, str]:
|
) -> tuple[bool, str, str]:
|
||||||
"""Validate a complete generated turn.
|
"""Validate a complete generated turn.
|
||||||
|
|
||||||
@ -229,7 +222,6 @@ def validate_turn(
|
|||||||
max_tokens=1024,
|
max_tokens=1024,
|
||||||
temperature=0.2,
|
temperature=0.2,
|
||||||
label="Turn validation",
|
label="Turn validation",
|
||||||
on_debug=on_debug,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if not text:
|
if not text:
|
||||||
@ -250,12 +242,8 @@ def validate_turn(
|
|||||||
action = args.get("action", "ok")
|
action = args.get("action", "ok")
|
||||||
if action not in ("ok", "reject", "regenerate"):
|
if action not in ("ok", "reject", "regenerate"):
|
||||||
action = "ok" if valid else "reject"
|
action = "ok" if valid else "reject"
|
||||||
if on_debug:
|
|
||||||
on_debug("turn_validation", {"valid": valid, "reason": reason, "action": action})
|
|
||||||
return valid, reason, action
|
return valid, reason, action
|
||||||
except (json.JSONDecodeError, ValueError):
|
except (json.JSONDecodeError, ValueError):
|
||||||
if on_debug:
|
|
||||||
on_debug("turn_validation", {"valid": True, "reason": "parse_failed", "raw": text[:200]})
|
|
||||||
if attempt == 0:
|
if attempt == 0:
|
||||||
messages.append({
|
messages.append({
|
||||||
"role": "system",
|
"role": "system",
|
||||||
|
|||||||
110
tools/run.py
110
tools/run.py
@ -28,7 +28,7 @@ from run_utils import (
|
|||||||
from run_ambience import AmbiencePlayer
|
from run_ambience import AmbiencePlayer
|
||||||
from run_widgets import (
|
from run_widgets import (
|
||||||
app_ambience_player as _widget_player_ref,
|
app_ambience_player as _widget_player_ref,
|
||||||
RollModal, DebugPane, CharPane, StatusBar, TodoPane, TranscriptPane,
|
RollModal, CharPane, StatusBar, TodoPane, TranscriptPane,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -66,10 +66,6 @@ class ChaosTUI(App):
|
|||||||
VerticalScroll { overflow-y: auto; scrollbar-size-vertical:2; scrollbar-color:#555; scrollbar-color-hover:#777; scrollbar-color-active:#999; }
|
VerticalScroll { overflow-y: auto; scrollbar-size-vertical:2; scrollbar-color:#555; scrollbar-color-hover:#777; scrollbar-color-active:#999; }
|
||||||
#char-content { background: #1e1e2a; color: #c0c0c0; padding: 0 1; }
|
#char-content { background: #1e1e2a; color: #c0c0c0; padding: 0 1; }
|
||||||
#transcript { background: #1a2a1a; color: #c8c8c8; padding: 0 1; }
|
#transcript { background: #1a2a1a; color: #c8c8c8; padding: 0 1; }
|
||||||
#debug-content { background: #1a1a1a; color: #88b0a0; padding: 0 1; }
|
|
||||||
#debug-content .dm-thought { color: #c0a060; }
|
|
||||||
#debug-content .dm-tool { color: #60a0c0; }
|
|
||||||
#debug-content .dm-result { color: #80a080; }
|
|
||||||
#play-narrative { background: #161616; color: #d8d8d8; padding: 1 2; height: auto; }
|
#play-narrative { background: #161616; color: #d8d8d8; padding: 1 2; height: auto; }
|
||||||
#play-status { background: #1a2a1a; color: #e0b060; padding: 0 2; height: 1; text-style: bold italic; text-align: center; }
|
#play-status { background: #1a2a1a; color: #e0b060; padding: 0 2; height: 1; text-style: bold italic; text-align: center; }
|
||||||
#play-status.processing { background: #2a1a0a; color: #ffd93d; }
|
#play-status.processing { background: #2a1a0a; color: #ffd93d; }
|
||||||
@ -171,9 +167,6 @@ class ChaosTUI(App):
|
|||||||
yield Button("Next >>", id="book-next")
|
yield Button("Next >>", id="book-next")
|
||||||
with VerticalScroll(id="book-scroll"):
|
with VerticalScroll(id="book-scroll"):
|
||||||
yield Static("", id="book-content")
|
yield Static("", id="book-content")
|
||||||
with TabPane("DEBUG", id="debug-tab"):
|
|
||||||
with VerticalScroll():
|
|
||||||
yield DebugPane("", id="debug-content")
|
|
||||||
yield StatusBar(id="status-bar")
|
yield StatusBar(id="status-bar")
|
||||||
yield Button("♫", id="mute-btn", classes="mute-button")
|
yield Button("♫", id="mute-btn", classes="mute-button")
|
||||||
|
|
||||||
@ -247,8 +240,6 @@ class ChaosTUI(App):
|
|||||||
input_widget.disabled = True
|
input_widget.disabled = True
|
||||||
input_widget.placeholder = "DM is at work..."
|
input_widget.placeholder = "DM is at work..."
|
||||||
self._show_thinking()
|
self._show_thinking()
|
||||||
self.query_one("#debug-content", DebugPane).clear()
|
|
||||||
self._append_debug(f"▶ {'player action: ' + player_action if player_action else 'starting new turn'}")
|
|
||||||
t = threading.Thread(target=self._run_generation, args=(player_action,), daemon=True)
|
t = threading.Thread(target=self._run_generation, args=(player_action,), daemon=True)
|
||||||
t.start()
|
t.start()
|
||||||
|
|
||||||
@ -260,27 +251,18 @@ class ChaosTUI(App):
|
|||||||
def on_action(action: str) -> None:
|
def on_action(action: str) -> None:
|
||||||
self.call_from_thread(self._on_action, action)
|
self.call_from_thread(self._on_action, action)
|
||||||
|
|
||||||
def on_debug(event_type: str, data: dict) -> None:
|
|
||||||
self.call_from_thread(self._on_debug, event_type, data)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = self.engine.generate_turn(
|
result = self.engine.generate_turn(
|
||||||
player_action=player_action,
|
player_action=player_action,
|
||||||
on_thought=on_thought,
|
on_thought=on_thought,
|
||||||
on_action=on_action,
|
on_action=on_action,
|
||||||
on_player_roll=self._on_player_roll,
|
on_player_roll=self._on_player_roll,
|
||||||
on_debug=on_debug,
|
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.call_from_thread(self._on_generation_error, e, traceback.format_exc())
|
self.call_from_thread(self._on_generation_error, e, traceback.format_exc())
|
||||||
return
|
return
|
||||||
self.call_from_thread(self._on_generation_done, result, player_action)
|
self.call_from_thread(self._on_generation_done, result, player_action)
|
||||||
|
|
||||||
def _append_debug(self, text: str) -> None:
|
|
||||||
from datetime import datetime
|
|
||||||
ts = datetime.now().strftime("%H:%M:%S")
|
|
||||||
self.query_one("#debug-content", DebugPane).append(f"[{ts}] {text}")
|
|
||||||
|
|
||||||
def _show_thinking(self) -> None:
|
def _show_thinking(self) -> None:
|
||||||
self._dm_action = "DM is preparing a response"
|
self._dm_action = "DM is preparing a response"
|
||||||
self._thinking_frame = 0
|
self._thinking_frame = 0
|
||||||
@ -304,7 +286,6 @@ class ChaosTUI(App):
|
|||||||
status = self.query_one("#play-status", Static)
|
status = self.query_one("#play-status", Static)
|
||||||
status.add_class("processing")
|
status.add_class("processing")
|
||||||
status.update(f"✦ {self._spinner_frames[0]} {display} ✦")
|
status.update(f"✦ {self._spinner_frames[0]} {display} ✦")
|
||||||
self._append_debug(f"✦ {display}")
|
|
||||||
|
|
||||||
def _on_action(self, action: str) -> None:
|
def _on_action(self, action: str) -> None:
|
||||||
self._dm_action = action
|
self._dm_action = action
|
||||||
@ -312,91 +293,8 @@ class ChaosTUI(App):
|
|||||||
status = self.query_one("#play-status", Static)
|
status = self.query_one("#play-status", Static)
|
||||||
status.add_class("processing")
|
status.add_class("processing")
|
||||||
status.update(f"✦ {self._spinner_frames[0]} {action} ✦")
|
status.update(f"✦ {self._spinner_frames[0]} {action} ✦")
|
||||||
self._append_debug(action)
|
|
||||||
|
|
||||||
def _on_debug(self, event_type: str, data: dict) -> None:
|
|
||||||
if event_type == "phase":
|
|
||||||
p = data.get("phase", 0)
|
|
||||||
status = data.get("status", "")
|
|
||||||
if status == "start":
|
|
||||||
n = data.get("name", "")
|
|
||||||
d = f" dice={data['dice']}" if data.get("dice") else ""
|
|
||||||
o = f" [attempt {data['outer_attempt']}/3]" if data.get("outer_attempt") else ""
|
|
||||||
self._append_debug(f"▸ Phase {p}: {n}{o}{d}")
|
|
||||||
elif status == "done":
|
|
||||||
if p == 1:
|
|
||||||
self._append_debug(f" ✔ prose: {data.get('chars', 0)} chars")
|
|
||||||
elif p == 2:
|
|
||||||
self._append_debug(f" ✔ summary: {data.get('summary', '')}")
|
|
||||||
elif p == 3:
|
|
||||||
self._append_debug(f" ✔ extract: {data.get('applied', 0)} state changes applied")
|
|
||||||
elif status == "empty":
|
|
||||||
self._append_debug(f" ⚠ phase {p} attempt {data.get('attempt', '?')} empty — retry")
|
|
||||||
elif status == "fallback":
|
|
||||||
self._append_debug(f" ⚠ phase {p} used fallback: {data.get('summary', '')}")
|
|
||||||
elif status == "tools_found":
|
|
||||||
tools = data.get("tools", [])
|
|
||||||
t = ", ".join(tools) if tools else "none"
|
|
||||||
self._append_debug(f" 🔧 tools: {t}" + (" + finalize" if data.get("has_finalize") else ""))
|
|
||||||
elif status == "errors":
|
|
||||||
for e in data.get("errors", []):
|
|
||||||
self._append_debug(f" ✖ {e}")
|
|
||||||
self._append_debug(f" ⟳ retry ({data.get('attempt', '?')})")
|
|
||||||
elif status == "exhausted":
|
|
||||||
self._append_debug(" ✖ Phase 3 exhausted retries — state changes may be missing!")
|
|
||||||
for e in data.get("errors", []):
|
|
||||||
self._append_debug(f" {e}")
|
|
||||||
elif status == "retry_after_phase3_failure":
|
|
||||||
self._append_debug(f" ⟳ Phase 3 failed — retry from Phase 1 (attempt {data.get('outer_attempt', '?')}/3)")
|
|
||||||
elif status == "validation_failed":
|
|
||||||
self._append_debug(f" ✖ narrative rejected: {data.get('reason', '?')} (attempt {data.get('outer_attempt', '?')}/3)")
|
|
||||||
elif event_type == "phase_done":
|
|
||||||
self._append_debug(f" ✔ turn — book_log: {data.get('book_log_chars', 0)} chars")
|
|
||||||
if data.get("log_entry"):
|
|
||||||
self._append_debug(f" log: {data['log_entry']}")
|
|
||||||
if data.get("ambience"):
|
|
||||||
self._append_debug(f" ambience: {data['ambience']}")
|
|
||||||
if data.get("extract_errors"):
|
|
||||||
self._append_debug(f" extract errors: {data['extract_errors']}")
|
|
||||||
elif event_type == "config":
|
|
||||||
m = data.get("model", "?")
|
|
||||||
t = data.get("temperature", "?")
|
|
||||||
tk = data.get("max_tokens", "?")
|
|
||||||
s = data.get("strategy", "?")
|
|
||||||
self._append_debug(f"▸ LLM: {m} | temp={t} | tokens={tk} | strategy={s}")
|
|
||||||
elif event_type == "tool_call":
|
|
||||||
self._append_debug(f" 🔧 {data['tool']}({json.dumps(data.get('args', {}))})")
|
|
||||||
elif event_type == "tool_result":
|
|
||||||
r = data.get("result", "")
|
|
||||||
self._append_debug(f" → {r[:80].replace(chr(10),' ').strip()}{'…' if len(r)>80 else ''}")
|
|
||||||
elif event_type == "parse_error":
|
|
||||||
self._append_debug(f" ⚠ bad tool block: {data.get('content', '')}")
|
|
||||||
elif event_type == "llm_error":
|
|
||||||
self._append_debug(f" ✖ LLM [{data.get('label','')}]: {data.get('error','')}")
|
|
||||||
elif event_type == "turn_details":
|
|
||||||
ts = data.get("timestamp", "")
|
|
||||||
m = data.get("model", "?")
|
|
||||||
t = data.get("temperature", "?")
|
|
||||||
tk = data.get("max_tokens", "?")
|
|
||||||
s = data.get("strategy_name", "?")
|
|
||||||
d = data.get("die_roll", "?")
|
|
||||||
ic = len(data.get("player_action", ""))
|
|
||||||
oc = data.get("book_log_chars", 0)
|
|
||||||
w = data.get("book_log_words", 0)
|
|
||||||
a = data.get("ambience", "None")
|
|
||||||
tc = data.get("tool_calls_count", 0)
|
|
||||||
ap = data.get("applied_changes_count", 0)
|
|
||||||
tms = data.get("total_elapsed_ms", 0)
|
|
||||||
ams = data.get("apply_elapsed_ms", 0)
|
|
||||||
self._append_debug(f" ━━━ Turn ━━━ {ts}")
|
|
||||||
self._append_debug(f" LLM: {m} | temp={t} | tokens={tk} | strategy={s}")
|
|
||||||
self._append_debug(f" Dice: {d} | In: {ic}c | Out: {oc}c ({w}w)")
|
|
||||||
self._append_debug(f" Amb: {a} | Tools: {tc} (applied: {ap}) | Time: {tms:.0f}ms ({ams:.0f}ms apply)")
|
|
||||||
for tc2 in data.get("tool_call_results", []):
|
|
||||||
self._append_debug(f" 🔧 {tc2['tool']}: {json.dumps(tc2.get('args',{}))[:120]}")
|
|
||||||
|
|
||||||
def _on_player_roll(self, dice: str, reason: str) -> str:
|
def _on_player_roll(self, dice: str, reason: str) -> str:
|
||||||
self.call_from_thread(self._append_debug, f"🎲 roll {dice} ({reason})")
|
|
||||||
self.call_from_thread(self._show_roll_modal, dice, reason)
|
self.call_from_thread(self._show_roll_modal, dice, reason)
|
||||||
self._roll_event.wait()
|
self._roll_event.wait()
|
||||||
self._roll_event.clear()
|
self._roll_event.clear()
|
||||||
@ -426,7 +324,6 @@ class ChaosTUI(App):
|
|||||||
self._hide_thinking()
|
self._hide_thinking()
|
||||||
if result.error:
|
if result.error:
|
||||||
self._show_error(result.error, result.debug_info)
|
self._show_error(result.error, result.debug_info)
|
||||||
self._append_debug(f"✖ error: {result.error}")
|
|
||||||
return
|
return
|
||||||
if result.book_log:
|
if result.book_log:
|
||||||
turn_num = state.archive_turn(result.book_log)
|
turn_num = state.archive_turn(result.book_log)
|
||||||
@ -435,6 +332,7 @@ class ChaosTUI(App):
|
|||||||
else:
|
else:
|
||||||
summary = result.book_log.strip().split(chr(10))[0][:80]
|
summary = result.book_log.strip().split(chr(10))[0][:80]
|
||||||
state.append_log(f"- **Turn {turn_num}** — {summary}")
|
state.append_log(f"- **Turn {turn_num}** — {summary}")
|
||||||
|
result.book_log = load_book_pages()[-1]
|
||||||
elif result.log_entry:
|
elif result.log_entry:
|
||||||
state.append_log(f"- {result.log_entry}")
|
state.append_log(f"- {result.log_entry}")
|
||||||
state.apply_state(result)
|
state.apply_state(result)
|
||||||
@ -448,15 +346,11 @@ class ChaosTUI(App):
|
|||||||
self._display_scene(result)
|
self._display_scene(result)
|
||||||
if result.book_log:
|
if result.book_log:
|
||||||
self._last_result = result
|
self._last_result = result
|
||||||
self._append_debug("✔ turn complete")
|
|
||||||
|
|
||||||
def _on_generation_error(self, error: Exception, traceback_str: str) -> None:
|
def _on_generation_error(self, error: Exception, traceback_str: str) -> None:
|
||||||
self._is_processing = False
|
self._is_processing = False
|
||||||
self._hide_thinking()
|
self._hide_thinking()
|
||||||
err_msg = f"{type(error).__name__}: {error}"
|
err_msg = f"{type(error).__name__}: {error}"
|
||||||
self._append_debug(f"✖ UNHANDLED: {err_msg}")
|
|
||||||
for line in traceback_str.rstrip().split("\n")[-10:]:
|
|
||||||
self._append_debug(f" {line}")
|
|
||||||
self._show_error(err_msg, traceback_str)
|
self._show_error(err_msg, traceback_str)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@ -112,29 +112,6 @@ class TranscriptPane(AutoStatic):
|
|||||||
self.parent.scroll_end(animate=False)
|
self.parent.scroll_end(animate=False)
|
||||||
|
|
||||||
|
|
||||||
class DebugPane(Static):
|
|
||||||
MAX_LINES = 200
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self._lines: list[str] = []
|
|
||||||
|
|
||||||
def append(self, text: str) -> None:
|
|
||||||
self._lines.append(text)
|
|
||||||
if len(self._lines) > self.MAX_LINES:
|
|
||||||
self._lines.pop(0)
|
|
||||||
self.update("\n".join(self._lines[-100:]))
|
|
||||||
self.call_after_refresh(self._scroll_bottom)
|
|
||||||
|
|
||||||
def _scroll_bottom(self):
|
|
||||||
if self.parent and hasattr(self.parent, 'scroll_end'):
|
|
||||||
self.parent.scroll_end(animate=False)
|
|
||||||
|
|
||||||
def clear(self) -> None:
|
|
||||||
self._lines.clear()
|
|
||||||
self.update("")
|
|
||||||
|
|
||||||
|
|
||||||
class CharPane(AutoStatic):
|
class CharPane(AutoStatic):
|
||||||
def load(self):
|
def load(self):
|
||||||
if not CHAR_PATH.exists():
|
if not CHAR_PATH.exists():
|
||||||
|
|||||||
@ -104,29 +104,6 @@ def test_missing_character_sheet(mock_truncate_world, mock_read_file):
|
|||||||
print("✓ handles missing character sheet gracefully")
|
print("✓ handles missing character sheet gracefully")
|
||||||
|
|
||||||
|
|
||||||
@patch("engine_lib.validation.state.read_file")
|
|
||||||
@patch("engine_lib.validation.state.truncate_world")
|
|
||||||
@patch("engine_lib.validation.call_llm")
|
|
||||||
def test_on_debug_called(mock_call_llm, mock_truncate_world, mock_read_file):
|
|
||||||
from engine_lib.validation import validate_action
|
|
||||||
|
|
||||||
mock_read_file.side_effect = lambda p: "HP: 10" if "character" in str(p).lower() else "## Location\nTavern"
|
|
||||||
mock_truncate_world.return_value = "## Location\nTavern"
|
|
||||||
mock_call_llm.return_value = json.dumps({"valid": True, "reason": "ok"})
|
|
||||||
|
|
||||||
events = []
|
|
||||||
def debug_cb(key, data):
|
|
||||||
events.append((key, data))
|
|
||||||
|
|
||||||
valid, reason = validate_action("I open the door", story="In a hallway", log="- Heard noises", on_debug=debug_cb)
|
|
||||||
|
|
||||||
assert valid is True
|
|
||||||
assert len(events) == 1
|
|
||||||
assert events[0][0] == "action_validation"
|
|
||||||
assert events[0][1]["valid"] is True
|
|
||||||
print("✓ on_debug callback receives action_validation event")
|
|
||||||
|
|
||||||
|
|
||||||
# ── validate_turn tests ────────────────────────────────────
|
# ── validate_turn tests ────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
@ -273,36 +250,6 @@ def test_turn_bad_json(mock_call_llm, mock_truncate_world, mock_read_file):
|
|||||||
print("✓ turn validation bad JSON gives (False, 'Unrecognized', 'reject')")
|
print("✓ turn validation bad JSON gives (False, 'Unrecognized', 'reject')")
|
||||||
|
|
||||||
|
|
||||||
@patch("engine_lib.validation.state.read_file")
|
|
||||||
@patch("engine_lib.validation.state.truncate_world")
|
|
||||||
@patch("engine_lib.validation.call_llm")
|
|
||||||
def test_turn_on_debug(mock_call_llm, mock_truncate_world, mock_read_file):
|
|
||||||
from engine_lib.validation import validate_turn
|
|
||||||
|
|
||||||
mock_read_file.side_effect = _mock_read_basic
|
|
||||||
mock_truncate_world.return_value = "## Location\nTavern"
|
|
||||||
mock_call_llm.return_value = _tool_response(True, "ok", "ok")
|
|
||||||
|
|
||||||
events = []
|
|
||||||
def debug_cb(key, data):
|
|
||||||
events.append((key, data))
|
|
||||||
|
|
||||||
valid, reason, action = validate_turn(
|
|
||||||
"I open the door",
|
|
||||||
narrative="Dillion opens the door.",
|
|
||||||
log_entry="Dillion opened the door and entered the hall.",
|
|
||||||
story="In a hallway",
|
|
||||||
log="- Heard noises",
|
|
||||||
on_debug=debug_cb,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert valid is True
|
|
||||||
assert len(events) == 1
|
|
||||||
assert events[0][0] == "turn_validation"
|
|
||||||
assert events[0][1]["valid"] is True
|
|
||||||
print("✓ on_debug callback receives turn_validation event")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
test_empty_action()
|
test_empty_action()
|
||||||
test_valid_action()
|
test_valid_action()
|
||||||
@ -310,11 +257,9 @@ if __name__ == "__main__":
|
|||||||
test_llm_returns_none()
|
test_llm_returns_none()
|
||||||
test_llm_returns_bad_json()
|
test_llm_returns_bad_json()
|
||||||
test_missing_character_sheet()
|
test_missing_character_sheet()
|
||||||
test_on_debug_called()
|
|
||||||
test_turn_empty_inputs()
|
test_turn_empty_inputs()
|
||||||
test_turn_valid()
|
test_turn_valid()
|
||||||
test_turn_reject()
|
test_turn_reject()
|
||||||
test_turn_regenerate()
|
test_turn_regenerate()
|
||||||
test_turn_bad_json()
|
test_turn_bad_json()
|
||||||
test_turn_on_debug()
|
|
||||||
print("\n✓ All validation tests passed")
|
print("\n✓ All validation tests passed")
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user