From 0458811e02d33d2240ad2958b9037fb4ff55408d Mon Sep 17 00:00:00 2001 From: Dejvino Date: Fri, 3 Jul 2026 21:26:24 +0200 Subject: [PATCH] Remove debug pane --- tools/engine.py | 24 +------ tools/engine_lib/llm.py | 5 -- tools/engine_lib/parsing.py | 19 ------ tools/engine_lib/tools_handler.py | 2 +- tools/engine_lib/validation.py | 12 ---- tools/run.py | 110 +----------------------------- tools/run_widgets.py | 23 ------- tools/test_validation.py | 55 --------------- 8 files changed, 4 insertions(+), 246 deletions(-) diff --git a/tools/engine.py b/tools/engine.py index 33166ac..b3dafff 100644 --- a/tools/engine.py +++ b/tools/engine.py @@ -28,7 +28,6 @@ class GameEngine: on_thought: callable = None, on_action: callable = None, on_player_roll: callable = None, - on_debug: callable = None, ) -> TurnResult: now = datetime.now() state.append_llm_log(f"\n{'='*60}") @@ -48,8 +47,6 @@ class GameEngine: if on_action: 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) base_parts = [] @@ -89,7 +86,6 @@ class GameEngine: text = call_llm( [{"role": "system", "content": system}, {"role": "user", "content": user}], label="Turn generation", - on_debug=on_debug, ) if not text or not text.strip(): @@ -102,7 +98,7 @@ class GameEngine: raw = text.strip() 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: state.append_llm_log("\n[TOOL] no tool blocks found") @@ -141,12 +137,8 @@ class GameEngine: changes=state_changes, story=recent_narrative, 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: state.append_llm_log(f"\n[TURN VALID] {reason}") elif reason == "Unrecognized": @@ -219,19 +211,6 @@ class GameEngine: if on_action: 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( player_action=player_action or "", @@ -245,7 +224,6 @@ class GameEngine: log_entry=log_entry or "", ambience=ambience, tool_calls=tool_calls, - on_debug=on_debug, ) return TurnResult( diff --git a/tools/engine_lib/llm.py b/tools/engine_lib/llm.py index 67b4edb..799e61d 100644 --- a/tools/engine_lib/llm.py +++ b/tools/engine_lib/llm.py @@ -30,7 +30,6 @@ def call_llm( timeout: int | None = None, max_tokens: int | None = None, label: str = "", - on_debug: callable = None, ) -> str | None: """Make a single LLM call. Loads config automatically. Returns content text or None on error.""" from .config import load_config @@ -45,8 +44,6 @@ def call_llm( try: import litellm except ImportError: - if on_debug: - on_debug("llm_error", {"label": label, "error": "litellm not installed"}) return None try: response = litellm.completion( @@ -67,6 +64,4 @@ def call_llm( except Exception as e: err_msg = f"{type(e).__name__}: {e}" 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 diff --git a/tools/engine_lib/parsing.py b/tools/engine_lib/parsing.py index 6278f21..152c776 100644 --- a/tools/engine_lib/parsing.py +++ b/tools/engine_lib/parsing.py @@ -18,13 +18,11 @@ def log_turn_details( log_entry: str, ambience: Optional[str], tool_calls: list, - on_debug=None, ) -> None: """Write structured turn summary to llm.log and fire TUI debug event.""" ts = datetime.now().isoformat() output_chars = len(book_log) 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(f"┌─ Turn Details — {ts}") @@ -41,20 +39,3 @@ def log_turn_details( 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, - }) diff --git a/tools/engine_lib/tools_handler.py b/tools/engine_lib/tools_handler.py index 79128c3..1d3343f 100644 --- a/tools/engine_lib/tools_handler.py +++ b/tools/engine_lib/tools_handler.py @@ -230,7 +230,7 @@ def describe_change(tool_name: str, args: dict) -> str: 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.""" calls = [] seen = set() diff --git a/tools/engine_lib/validation.py b/tools/engine_lib/validation.py index e3c5ba7..b0b8e19 100644 --- a/tools/engine_lib/validation.py +++ b/tools/engine_lib/validation.py @@ -51,7 +51,6 @@ def validate_action( *, story: str = "", log: str = "", - on_debug: callable = None, ) -> tuple[bool, str]: """Ask the LLM whether a player action is valid given the game state. Returns (valid, reason).""" if not player_action: @@ -72,7 +71,6 @@ def validate_action( max_tokens=1024, temperature=0.2, label="Action validation", - on_debug=on_debug, ) if not text: @@ -86,12 +84,8 @@ def validate_action( data = json.loads(cleaned) valid = data.get("valid", True) reason = data.get("reason", "") - if on_debug: - on_debug("action_validation", {"valid": valid, "reason": reason, "action": player_action}) return valid, reason except (json.JSONDecodeError, ValueError): - if on_debug: - on_debug("action_validation", {"valid": True, "reason": "parse_failed", "raw": text[:200]}) if attempt == 0: messages.append({ "role": "system", @@ -198,7 +192,6 @@ def validate_turn( changes: list[dict] | None = None, story: str = "", log: str = "", - on_debug: callable = None, ) -> tuple[bool, str, str]: """Validate a complete generated turn. @@ -229,7 +222,6 @@ def validate_turn( max_tokens=1024, temperature=0.2, label="Turn validation", - on_debug=on_debug, ) if not text: @@ -250,12 +242,8 @@ def validate_turn( action = args.get("action", "ok") if action not in ("ok", "reject", "regenerate"): action = "ok" if valid else "reject" - if on_debug: - on_debug("turn_validation", {"valid": valid, "reason": reason, "action": action}) return valid, reason, action except (json.JSONDecodeError, ValueError): - if on_debug: - on_debug("turn_validation", {"valid": True, "reason": "parse_failed", "raw": text[:200]}) if attempt == 0: messages.append({ "role": "system", diff --git a/tools/run.py b/tools/run.py index 6926c0f..f8fc9a2 100755 --- a/tools/run.py +++ b/tools/run.py @@ -28,7 +28,7 @@ from run_utils import ( from run_ambience import AmbiencePlayer from run_widgets import ( 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; } #char-content { background: #1e1e2a; color: #c0c0c0; 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-status { background: #1a2a1a; color: #e0b060; padding: 0 2; height: 1; text-style: bold italic; text-align: center; } #play-status.processing { background: #2a1a0a; color: #ffd93d; } @@ -171,9 +167,6 @@ class ChaosTUI(App): yield Button("Next >>", id="book-next") with VerticalScroll(id="book-scroll"): yield Static("", id="book-content") - with TabPane("DEBUG", id="debug-tab"): - with VerticalScroll(): - yield DebugPane("", id="debug-content") yield StatusBar(id="status-bar") yield Button("♫", id="mute-btn", classes="mute-button") @@ -247,8 +240,6 @@ class ChaosTUI(App): input_widget.disabled = True input_widget.placeholder = "DM is at work..." 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.start() @@ -260,27 +251,18 @@ class ChaosTUI(App): def on_action(action: str) -> None: 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: result = self.engine.generate_turn( player_action=player_action, on_thought=on_thought, on_action=on_action, on_player_roll=self._on_player_roll, - on_debug=on_debug, ) except Exception as e: self.call_from_thread(self._on_generation_error, e, traceback.format_exc()) return 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: self._dm_action = "DM is preparing a response" self._thinking_frame = 0 @@ -304,7 +286,6 @@ class ChaosTUI(App): status = self.query_one("#play-status", Static) status.add_class("processing") status.update(f"✦ {self._spinner_frames[0]} {display} ✦") - self._append_debug(f"✦ {display}") def _on_action(self, action: str) -> None: self._dm_action = action @@ -312,91 +293,8 @@ class ChaosTUI(App): status = self.query_one("#play-status", Static) status.add_class("processing") 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: - self.call_from_thread(self._append_debug, f"🎲 roll {dice} ({reason})") self.call_from_thread(self._show_roll_modal, dice, reason) self._roll_event.wait() self._roll_event.clear() @@ -426,7 +324,6 @@ class ChaosTUI(App): self._hide_thinking() if result.error: self._show_error(result.error, result.debug_info) - self._append_debug(f"✖ error: {result.error}") return if result.book_log: turn_num = state.archive_turn(result.book_log) @@ -435,6 +332,7 @@ class ChaosTUI(App): else: summary = result.book_log.strip().split(chr(10))[0][:80] state.append_log(f"- **Turn {turn_num}** — {summary}") + result.book_log = load_book_pages()[-1] elif result.log_entry: state.append_log(f"- {result.log_entry}") state.apply_state(result) @@ -448,15 +346,11 @@ class ChaosTUI(App): self._display_scene(result) if result.book_log: self._last_result = result - self._append_debug("✔ turn complete") def _on_generation_error(self, error: Exception, traceback_str: str) -> None: self._is_processing = False self._hide_thinking() 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) @staticmethod diff --git a/tools/run_widgets.py b/tools/run_widgets.py index 5859ca1..1fd0516 100644 --- a/tools/run_widgets.py +++ b/tools/run_widgets.py @@ -112,29 +112,6 @@ class TranscriptPane(AutoStatic): 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): def load(self): if not CHAR_PATH.exists(): diff --git a/tools/test_validation.py b/tools/test_validation.py index ff74368..a87d6b0 100644 --- a/tools/test_validation.py +++ b/tools/test_validation.py @@ -104,29 +104,6 @@ def test_missing_character_sheet(mock_truncate_world, mock_read_file): 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 ──────────────────────────────────── @@ -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')") -@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__": test_empty_action() test_valid_action() @@ -310,11 +257,9 @@ if __name__ == "__main__": test_llm_returns_none() test_llm_returns_bad_json() test_missing_character_sheet() - test_on_debug_called() test_turn_empty_inputs() test_turn_valid() test_turn_reject() test_turn_regenerate() test_turn_bad_json() - test_turn_on_debug() print("\n✓ All validation tests passed")