fixes to linting
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.8.6
|
||||
rev: v0.14.14
|
||||
hooks:
|
||||
- id: ruff
|
||||
name: ruff lint (core)
|
||||
|
||||
@@ -244,29 +244,27 @@ def cmd_run(args: argparse.Namespace) -> int:
|
||||
# Run the agent (with TUI or standard)
|
||||
if getattr(args, "tui", False):
|
||||
from framework.tui.app import AdenTUI
|
||||
|
||||
|
||||
# Test Minimal App to isolate crash source
|
||||
# from textual.app import App, ComposeResult
|
||||
# from textual.widgets import Label, Header, Footer
|
||||
|
||||
|
||||
# class MinimalTUI(App):
|
||||
# CSS = "Screen { layout: vertical; }"
|
||||
# def compose(self) -> ComposeResult:
|
||||
# yield Header()
|
||||
# yield Label("Minimal TUI Mode - Debugging")
|
||||
# yield Footer()
|
||||
|
||||
|
||||
# We need to run inside the existing loop or use proper async handling
|
||||
# Since cmd_run is called from sync main(), we need to use asyncio.run or similar
|
||||
# But wait, result = asyncio.run(...) is below.
|
||||
|
||||
|
||||
pass # Placeholder to indicate logic insertion point
|
||||
|
||||
|
||||
async def run_with_tui():
|
||||
# Initialize TUI
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.widgets import Label, Header, Footer
|
||||
|
||||
|
||||
try:
|
||||
# Load runner FRESH inside the loop to ensure strict loop affinity
|
||||
print("DEBUG: Loading AgentRunner inside TUI loop...")
|
||||
@@ -285,11 +283,10 @@ def cmd_run(args: argparse.Namespace) -> int:
|
||||
if runner._agent_runtime is None:
|
||||
runner._setup()
|
||||
print("DEBUG: AgentRuntime setup forced inside TUI loop")
|
||||
|
||||
|
||||
|
||||
app = AdenTUI(runner._agent_runtime)
|
||||
print("DEBUG: AdenTUI initialized with runtime in cli.py")
|
||||
|
||||
|
||||
# Define agent task
|
||||
# Define agent task
|
||||
async def run_agent_task():
|
||||
@@ -300,23 +297,25 @@ def cmd_run(args: argparse.Namespace) -> int:
|
||||
except Exception as e:
|
||||
# Log error to TUI if possible
|
||||
if hasattr(app, "log_handler"):
|
||||
import logging
|
||||
logging.error(f"Agent Execution Failed: {e}", exc_info=True)
|
||||
import logging
|
||||
|
||||
logging.error(f"Agent Execution Failed: {e}", exc_info=True)
|
||||
finally:
|
||||
# Don't auto-exit, let user inspect result
|
||||
pass
|
||||
|
||||
|
||||
# Start agent task in background
|
||||
agent_task = asyncio.create_task(run_agent_task())
|
||||
|
||||
_agent_task = asyncio.create_task(run_agent_task())
|
||||
|
||||
# Start TUI (blocks this coroutine until quit)
|
||||
await app.run_async()
|
||||
except Exception as e:
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
print(f"TUI crashed during init/run: {e}")
|
||||
logging.error(f"TUI Crash: {e}", exc_info=True)
|
||||
|
||||
|
||||
# Cleanup
|
||||
# if agent_task and not agent_task.done():
|
||||
# agent_task.cancel()
|
||||
@@ -324,14 +323,14 @@ def cmd_run(args: argparse.Namespace) -> int:
|
||||
# await agent_task
|
||||
# except asyncio.CancelledError:
|
||||
# pass
|
||||
|
||||
|
||||
# Return result if completed
|
||||
# if agent_task.done() and not agent_task.cancelled():
|
||||
# return agent_task.result()
|
||||
return None
|
||||
|
||||
result = asyncio.run(run_with_tui())
|
||||
|
||||
|
||||
if result is None:
|
||||
# TUI quit before completion or error
|
||||
print("TUI session ended.")
|
||||
|
||||
@@ -573,13 +573,15 @@ class AgentRunner:
|
||||
# If TUI enabled but no entry points (single-entry agent), create default
|
||||
if not entry_points and self.enable_tui and self.graph.entry_node:
|
||||
logger.info("Creating default entry point for TUI")
|
||||
entry_points.append(EntryPointSpec(
|
||||
id="default",
|
||||
name="Default",
|
||||
entry_node=self.graph.entry_node,
|
||||
trigger_type="manual",
|
||||
isolation_level="shared",
|
||||
))
|
||||
entry_points.append(
|
||||
EntryPointSpec(
|
||||
id="default",
|
||||
name="Default",
|
||||
entry_node=self.graph.entry_node,
|
||||
trigger_type="manual",
|
||||
isolation_level="shared",
|
||||
)
|
||||
)
|
||||
|
||||
# Create AgentRuntime with all entry points
|
||||
self._agent_runtime = create_agent_runtime(
|
||||
|
||||
+81
-74
@@ -1,16 +1,19 @@
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.widgets import Label, Header, Footer
|
||||
from textual.containers import Container, Horizontal, Vertical
|
||||
from textual.binding import Binding
|
||||
from framework.runtime.agent_runtime import AgentRuntime
|
||||
from framework.tui.widgets.log_pane import LogPane
|
||||
from framework.tui.widgets.graph_view import GraphOverview
|
||||
from framework.tui.widgets.chat_repl import ChatRepl
|
||||
import logging
|
||||
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.binding import Binding
|
||||
from textual.containers import Container, Horizontal, Vertical
|
||||
from textual.widgets import Footer, Label
|
||||
|
||||
from framework.runtime.agent_runtime import AgentRuntime
|
||||
from framework.tui.widgets.chat_repl import ChatRepl
|
||||
from framework.tui.widgets.graph_view import GraphOverview
|
||||
from framework.tui.widgets.log_pane import LogPane
|
||||
|
||||
|
||||
class StaticHeader(Container):
|
||||
"""Custom static header that replaces standard Header widget."""
|
||||
|
||||
|
||||
DEFAULT_CSS = """
|
||||
StaticHeader {
|
||||
dock: top;
|
||||
@@ -21,10 +24,11 @@ class StaticHeader(Container):
|
||||
align: center middle;
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Label(self.app.title)
|
||||
|
||||
|
||||
class AdenTUI(App):
|
||||
TITLE = "Aden TUI Dashboard"
|
||||
COMMAND_PALETTE_BINDING = "ctrl+o"
|
||||
@@ -40,7 +44,7 @@ class AdenTUI(App):
|
||||
layout: vertical;
|
||||
background: $surface;
|
||||
}
|
||||
|
||||
|
||||
#graph-overview-container {
|
||||
height: 40%;
|
||||
background: $panel;
|
||||
@@ -61,7 +65,7 @@ class AdenTUI(App):
|
||||
border-left: tall $primary;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
#chat-history {
|
||||
height: 1fr;
|
||||
width: 100%;
|
||||
@@ -70,52 +74,52 @@ class AdenTUI(App):
|
||||
scrollbar-background: $panel;
|
||||
scrollbar-color: $primary;
|
||||
}
|
||||
|
||||
|
||||
TextArea {
|
||||
background: $surface;
|
||||
border: none;
|
||||
scrollbar-background: $panel;
|
||||
scrollbar-color: $primary;
|
||||
}
|
||||
|
||||
|
||||
Input {
|
||||
background: $surface;
|
||||
border: tall $primary;
|
||||
margin-top: 1;
|
||||
}
|
||||
|
||||
|
||||
Input:focus {
|
||||
border: tall $accent;
|
||||
}
|
||||
|
||||
|
||||
StaticHeader {
|
||||
background: $primary;
|
||||
color: $text;
|
||||
text-style: bold;
|
||||
height: 1;
|
||||
}
|
||||
|
||||
|
||||
/* Force height 1 even if tall class is added (prevents expansion) */
|
||||
StaticHeader.-tall {
|
||||
height: 1;
|
||||
}
|
||||
|
||||
|
||||
StaticHeader > .header--title {
|
||||
text-style: bold;
|
||||
}
|
||||
|
||||
|
||||
/* Hide the clock icon and top-left icon/button */
|
||||
Header .header--clock, StaticHeader .header--clock,
|
||||
Header .header--icon, StaticHeader .header--icon {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
Footer {
|
||||
background: $panel;
|
||||
color: $text-muted;
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
BINDINGS = [
|
||||
Binding("q", "quit", "Quit"),
|
||||
Binding("ctrl+s", "screenshot", "Screenshot (SVG)", show=True, priority=True),
|
||||
@@ -126,25 +130,25 @@ class AdenTUI(App):
|
||||
def __init__(self, runtime: AgentRuntime):
|
||||
with open("tui_debug.log", "a") as f:
|
||||
f.write("DEBUG: AdenTUI.__init__ started\n")
|
||||
|
||||
|
||||
print("DEBUG: AdenTUI.__init__ called")
|
||||
super().__init__()
|
||||
|
||||
|
||||
self.runtime = runtime
|
||||
self.log_pane = LogPane()
|
||||
self.graph_view = GraphOverview(runtime)
|
||||
self.chat_repl = ChatRepl(runtime)
|
||||
self.is_ready = False
|
||||
|
||||
|
||||
with open("tui_debug.log", "a") as f:
|
||||
f.write("DEBUG: Widgets initialized\n")
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
with open("tui_debug.log", "a") as f:
|
||||
f.write("DEBUG: compose() called\n")
|
||||
|
||||
|
||||
yield StaticHeader()
|
||||
|
||||
|
||||
yield Horizontal(
|
||||
Vertical(
|
||||
Container(self.log_pane, id="log-pane-container"),
|
||||
@@ -153,9 +157,9 @@ class AdenTUI(App):
|
||||
),
|
||||
Container(self.chat_repl, id="chat-repl-container"),
|
||||
)
|
||||
|
||||
|
||||
yield Footer()
|
||||
|
||||
|
||||
with open("tui_debug.log", "a") as f:
|
||||
f.write("DEBUG: compose() complete\n")
|
||||
|
||||
@@ -163,26 +167,26 @@ class AdenTUI(App):
|
||||
"""Called when app starts."""
|
||||
with open("tui_debug.log", "a") as f:
|
||||
f.write("DEBUG: on_mount() called\n")
|
||||
|
||||
|
||||
self.title = "Aden TUI Dashboard"
|
||||
|
||||
|
||||
# Add logging setup
|
||||
self._setup_logging_queue()
|
||||
|
||||
|
||||
# Set ready immediately so _poll_logs can process messages
|
||||
self.is_ready = True
|
||||
|
||||
|
||||
# Add event subscription with delay to ensure TUI is fully initialized
|
||||
self.call_later(self._init_runtime_connection)
|
||||
|
||||
|
||||
# Delay initial log messages until layout is fully rendered
|
||||
def write_initial_logs():
|
||||
logging.info("TUI Dashboard initialized successfully")
|
||||
logging.info("Waiting for agent execution to start...")
|
||||
|
||||
|
||||
# Wait for layout to be fully rendered before writing logs
|
||||
self.set_timer(0.2, write_initial_logs)
|
||||
|
||||
|
||||
with open("tui_debug.log", "a") as f:
|
||||
f.write("DEBUG: on_mount() complete\n")
|
||||
|
||||
@@ -191,31 +195,31 @@ class AdenTUI(App):
|
||||
try:
|
||||
import queue
|
||||
from logging.handlers import QueueHandler
|
||||
|
||||
|
||||
self.log_queue = queue.Queue()
|
||||
self.queue_handler = QueueHandler(self.log_queue)
|
||||
self.queue_handler.setLevel(logging.INFO)
|
||||
|
||||
|
||||
# Get root logger
|
||||
root_logger = logging.getLogger()
|
||||
|
||||
|
||||
# Remove ALL existing handlers to prevent stdout output
|
||||
# This is critical - StreamHandlers cause text to appear in header
|
||||
for handler in root_logger.handlers[:]:
|
||||
root_logger.removeHandler(handler)
|
||||
|
||||
|
||||
# Add ONLY our queue handler
|
||||
root_logger.addHandler(self.queue_handler)
|
||||
root_logger.setLevel(logging.INFO)
|
||||
|
||||
|
||||
# Suppress LiteLLM logging completely
|
||||
litellm_logger = logging.getLogger("LiteLLM")
|
||||
litellm_logger.setLevel(logging.CRITICAL) # Only show critical errors
|
||||
litellm_logger.propagate = False # Don't propagate to root logger
|
||||
|
||||
|
||||
# Start polling
|
||||
self.set_interval(0.1, self._poll_logs)
|
||||
|
||||
|
||||
with open("tui_debug.log", "a") as f:
|
||||
f.write("DEBUG: Logging setup complete\n")
|
||||
except Exception as e:
|
||||
@@ -226,7 +230,7 @@ class AdenTUI(App):
|
||||
"""Poll the log queue and update UI."""
|
||||
if not self.is_ready:
|
||||
return
|
||||
|
||||
|
||||
try:
|
||||
count = 0
|
||||
while not self.log_queue.empty():
|
||||
@@ -234,11 +238,11 @@ class AdenTUI(App):
|
||||
# Filter out framework/library logs
|
||||
if record.name.startswith(("textual", "LiteLLM", "litellm")):
|
||||
continue
|
||||
|
||||
|
||||
msg = logging.Formatter().format(record)
|
||||
self.log_pane.write_log(msg)
|
||||
count += 1
|
||||
|
||||
|
||||
if count > 0:
|
||||
with open("tui_debug.log", "a") as f:
|
||||
f.write(f"DEBUG: _poll_logs processed {count} messages\n")
|
||||
@@ -251,7 +255,7 @@ class AdenTUI(App):
|
||||
try:
|
||||
with open("tui_debug.log", "a") as f:
|
||||
f.write("DEBUG: _init_runtime_connection called\n")
|
||||
|
||||
|
||||
# Use call_soon_threadsafe wrapper for the handler
|
||||
def safe_event_handler(event):
|
||||
"""Thread-safe event handler wrapper."""
|
||||
@@ -261,18 +265,16 @@ class AdenTUI(App):
|
||||
except Exception as e:
|
||||
with open("tui_debug.log", "a") as f:
|
||||
f.write(f"ERROR in safe_event_handler: {e}\n")
|
||||
|
||||
self.runtime.subscribe_to_events(
|
||||
event_types=[],
|
||||
handler=safe_event_handler
|
||||
)
|
||||
|
||||
|
||||
self.runtime.subscribe_to_events(event_types=[], handler=safe_event_handler)
|
||||
|
||||
with open("tui_debug.log", "a") as f:
|
||||
f.write("DEBUG: Event subscription complete\n")
|
||||
except Exception as e:
|
||||
with open("tui_debug.log", "a") as f:
|
||||
f.write(f"ERROR in _init_runtime_connection: {e}\n")
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
|
||||
def _handle_event_sync(self, event) -> None:
|
||||
@@ -280,94 +282,99 @@ class AdenTUI(App):
|
||||
try:
|
||||
with open("tui_debug.log", "a") as f:
|
||||
f.write(f"DEBUG: _handle_event_sync called with event: {event}\n")
|
||||
|
||||
|
||||
if not self.is_ready:
|
||||
with open("tui_debug.log", "a") as f:
|
||||
f.write("DEBUG: App not ready, skipping event\n")
|
||||
return
|
||||
|
||||
|
||||
# Update graph view
|
||||
if hasattr(event, "type"):
|
||||
with open("tui_debug.log", "a") as f:
|
||||
f.write(f"DEBUG: Event has type: {event.type}\n")
|
||||
|
||||
|
||||
if hasattr(event.type, "value"):
|
||||
event_type = event.type.value
|
||||
with open("tui_debug.log", "a") as f:
|
||||
f.write(f"DEBUG: Event type value: {event_type}\n")
|
||||
|
||||
|
||||
if event_type.startswith(("execution_", "node_")):
|
||||
self.graph_view.update_execution(event)
|
||||
|
||||
|
||||
with open("tui_debug.log", "a") as f:
|
||||
f.write(f"DEBUG: Handled event {event_type}\n")
|
||||
except Exception as e:
|
||||
with open("tui_debug.log", "a") as f:
|
||||
f.write(f"ERROR in _handle_event_sync: {e}\n")
|
||||
import traceback
|
||||
|
||||
with open("tui_debug.log", "a") as f:
|
||||
f.write(f"{traceback.format_exc()}\n")
|
||||
|
||||
|
||||
def save_png_screenshot(self, filename: str | None = None) -> str:
|
||||
"""Save a screenshot of the current screen as SVG (viewable in browsers).
|
||||
|
||||
|
||||
Note: Saves as SVG format since PNG conversion requires system libraries.
|
||||
SVG files can be opened in any web browser or converted to PNG using online tools.
|
||||
|
||||
|
||||
Args:
|
||||
filename: Optional filename for the screenshot. If None, generates timestamp-based name.
|
||||
|
||||
|
||||
Returns:
|
||||
Path to the saved SVG file.
|
||||
"""
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
# Create screenshots directory
|
||||
screenshots_dir = Path("screenshots")
|
||||
screenshots_dir.mkdir(exist_ok=True)
|
||||
|
||||
|
||||
# Generate filename if not provided
|
||||
if filename is None:
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
filename = f"tui_screenshot_{timestamp}.svg"
|
||||
|
||||
|
||||
# Ensure .svg extension
|
||||
if not filename.endswith('.svg'):
|
||||
filename += '.svg'
|
||||
|
||||
if not filename.endswith(".svg"):
|
||||
filename += ".svg"
|
||||
|
||||
# Full path
|
||||
filepath = screenshots_dir / filename
|
||||
|
||||
|
||||
# Temporarily hide borders for cleaner screenshot
|
||||
chat_container = self.query_one("#chat-repl-container")
|
||||
original_chat_border = chat_container.styles.border_left
|
||||
chat_container.styles.border_left = ("none", "transparent")
|
||||
|
||||
|
||||
# Hide all Input widget borders
|
||||
input_widgets = self.query("Input")
|
||||
original_input_borders = []
|
||||
for input_widget in input_widgets:
|
||||
original_input_borders.append(input_widget.styles.border)
|
||||
input_widget.styles.border = ("none", "transparent")
|
||||
|
||||
|
||||
try:
|
||||
# Get SVG data from Textual and save it
|
||||
svg_data = self.export_screenshot()
|
||||
filepath.write_text(svg_data, encoding='utf-8')
|
||||
filepath.write_text(svg_data, encoding="utf-8")
|
||||
finally:
|
||||
# Restore the original borders
|
||||
chat_container.styles.border_left = original_chat_border
|
||||
for i, input_widget in enumerate(input_widgets):
|
||||
input_widget.styles.border = original_input_borders[i]
|
||||
|
||||
|
||||
return str(filepath)
|
||||
|
||||
|
||||
def action_screenshot(self) -> None:
|
||||
"""Take a screenshot (bound to Ctrl+S)."""
|
||||
try:
|
||||
filepath = self.save_png_screenshot()
|
||||
self.notify(f"Screenshot saved: {filepath} (SVG - open in browser)", severity="information", timeout=5)
|
||||
self.notify(
|
||||
f"Screenshot saved: {filepath} (SVG - open in browser)",
|
||||
severity="information",
|
||||
timeout=5,
|
||||
)
|
||||
except Exception as e:
|
||||
self.notify(f"Screenshot failed: {e}", severity="error", timeout=5)
|
||||
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
Logging Handler for TUI.
|
||||
"""
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from framework.tui.app import AdenTUI
|
||||
|
||||
|
||||
class TUILogHandler(logging.Handler):
|
||||
"""Redirects logging records to the TUI LogPane."""
|
||||
|
||||
@@ -20,16 +21,14 @@ class TUILogHandler(logging.Handler):
|
||||
# Avoid infinite recursion by ignoring textual logs
|
||||
if record.name.startswith("textual"):
|
||||
return
|
||||
|
||||
|
||||
try:
|
||||
if not hasattr(self.app, "is_ready") or not self.app.is_ready:
|
||||
return
|
||||
|
||||
msg = self.format(record)
|
||||
# We need to schedule the update on the main thread
|
||||
self.app.call_later(
|
||||
self.app.log_pane.write_log, msg
|
||||
)
|
||||
self.app.call_later(self.app.log_pane.write_log, msg)
|
||||
except Exception:
|
||||
# If app is closed or error, fallback
|
||||
pass
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
Chat / REPL Widget - Uses TextArea for reliable display.
|
||||
"""
|
||||
|
||||
from textual.widgets import Input, TextArea
|
||||
from textual.containers import Vertical
|
||||
from textual.app import ComposeResult
|
||||
from textual.containers import Vertical
|
||||
from textual.widgets import Input, TextArea
|
||||
|
||||
from framework.runtime.agent_runtime import AgentRuntime
|
||||
import logging
|
||||
|
||||
|
||||
class ChatRepl(Vertical):
|
||||
"""Widget for interactive chat/REPL."""
|
||||
@@ -17,7 +18,7 @@ class ChatRepl(Vertical):
|
||||
height: 100%;
|
||||
layout: vertical;
|
||||
}
|
||||
|
||||
|
||||
ChatRepl > TextArea {
|
||||
width: 100%;
|
||||
height: 1fr;
|
||||
@@ -26,7 +27,7 @@ class ChatRepl(Vertical):
|
||||
scrollbar-background: $panel;
|
||||
scrollbar-color: $primary;
|
||||
}
|
||||
|
||||
|
||||
ChatRepl > Input {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
@@ -35,7 +36,7 @@ class ChatRepl(Vertical):
|
||||
border: tall $primary;
|
||||
margin-top: 1;
|
||||
}
|
||||
|
||||
|
||||
ChatRepl > Input:focus {
|
||||
border: tall $accent;
|
||||
}
|
||||
@@ -49,7 +50,7 @@ class ChatRepl(Vertical):
|
||||
# Use TextArea (read-only) like LogPane
|
||||
yield TextArea("", id="chat-history", read_only=True)
|
||||
yield Input(placeholder="Enter input for agent...", id="chat-input")
|
||||
|
||||
|
||||
def on_mount(self) -> None:
|
||||
"""Add welcome message when widget mounts."""
|
||||
history = self.query_one("#chat-history", TextArea)
|
||||
@@ -60,92 +61,93 @@ class ChatRepl(Vertical):
|
||||
async def on_input_submitted(self, message: Input.Submitted) -> None:
|
||||
"""Handle input submission."""
|
||||
with open("tui_debug.log", "a") as f:
|
||||
f.write(f"DEBUG: on_input_submitted called\n")
|
||||
|
||||
f.write("DEBUG: on_input_submitted called\n")
|
||||
|
||||
user_input = message.value.strip()
|
||||
|
||||
|
||||
with open("tui_debug.log", "a") as f:
|
||||
f.write(f"DEBUG: ChatREPL input: '{user_input}'\n")
|
||||
|
||||
|
||||
if not user_input:
|
||||
return
|
||||
|
||||
|
||||
# Get chat history
|
||||
with open("tui_debug.log", "a") as f:
|
||||
f.write(f"DEBUG: Getting chat history\n")
|
||||
f.write("DEBUG: Getting chat history\n")
|
||||
history = self.query_one("#chat-history", TextArea)
|
||||
|
||||
|
||||
# Display user message
|
||||
with open("tui_debug.log", "a") as f:
|
||||
f.write(f"DEBUG: Adding user message\n")
|
||||
f.write("DEBUG: Adding user message\n")
|
||||
current_text = history.text
|
||||
history.load_text(f"{current_text}\nYou: {user_input}\n")
|
||||
|
||||
|
||||
with open("tui_debug.log", "a") as f:
|
||||
f.write(f"DEBUG: User message added\n")
|
||||
|
||||
f.write("DEBUG: User message added\n")
|
||||
|
||||
# Clear input
|
||||
message.input.value = ""
|
||||
|
||||
|
||||
# Execute agent
|
||||
try:
|
||||
with open("tui_debug.log", "a") as f:
|
||||
f.write(f"DEBUG: Starting agent execution\n")
|
||||
|
||||
f.write("DEBUG: Starting agent execution\n")
|
||||
|
||||
# Show processing
|
||||
current_text = history.text
|
||||
history.load_text(f"{current_text}Agent is processing...\n")
|
||||
|
||||
|
||||
with open("tui_debug.log", "a") as f:
|
||||
f.write(f"DEBUG: Processing message shown\n")
|
||||
|
||||
f.write("DEBUG: Processing message shown\n")
|
||||
|
||||
# Get entry point
|
||||
entry_points = self.runtime.get_entry_points()
|
||||
if not entry_points:
|
||||
current_text = history.text
|
||||
history.load_text(f"{current_text}Error: No entry points\n")
|
||||
return
|
||||
|
||||
|
||||
with open("tui_debug.log", "a") as f:
|
||||
f.write(f"DEBUG: Calling trigger_and_wait\n")
|
||||
|
||||
f.write("DEBUG: Calling trigger_and_wait\n")
|
||||
|
||||
# Execute
|
||||
result = await self.runtime.trigger_and_wait(
|
||||
entry_point_id=entry_points[0].id,
|
||||
input_data={"input_string": user_input},
|
||||
timeout=30.0
|
||||
timeout=30.0,
|
||||
)
|
||||
|
||||
|
||||
with open("tui_debug.log", "a") as f:
|
||||
f.write(f"DEBUG: Got result: {result}\n")
|
||||
|
||||
|
||||
# Remove "processing" line and display result
|
||||
lines = history.text.split('\n')
|
||||
lines = [line for line in lines if 'processing' not in line.lower()]
|
||||
|
||||
lines = history.text.split("\n")
|
||||
lines = [line for line in lines if "processing" not in line.lower()]
|
||||
|
||||
# Display result
|
||||
if result and result.success and result.output:
|
||||
output_str = str(result.output.get('output_string', result.output))
|
||||
output_str = str(result.output.get("output_string", result.output))
|
||||
lines.append(f"Agent: {output_str}")
|
||||
with open("tui_debug.log", "a") as f:
|
||||
f.write(f"DEBUG: Added success result\n")
|
||||
f.write("DEBUG: Added success result\n")
|
||||
elif result and result.error:
|
||||
lines.append(f"Error: {result.error}")
|
||||
else:
|
||||
lines.append("No result")
|
||||
|
||||
history.load_text('\n'.join(lines) + '\n')
|
||||
|
||||
|
||||
history.load_text("\n".join(lines) + "\n")
|
||||
|
||||
with open("tui_debug.log", "a") as f:
|
||||
f.write(f"DEBUG: Execution complete\n")
|
||||
|
||||
f.write("DEBUG: Execution complete\n")
|
||||
|
||||
except Exception as e:
|
||||
with open("tui_debug.log", "a") as f:
|
||||
f.write(f"ERROR: Exception in handler: {e}\n")
|
||||
import traceback
|
||||
|
||||
f.write(f"{traceback.format_exc()}\n")
|
||||
current_text = history.text
|
||||
lines = current_text.split('\n')
|
||||
lines = [line for line in lines if 'processing' not in line.lower()]
|
||||
lines = current_text.split("\n")
|
||||
lines = [line for line in lines if "processing" not in line.lower()]
|
||||
lines.append(f"Error: {str(e)}")
|
||||
history.load_text('\n'.join(lines) + '\n')
|
||||
history.load_text("\n".join(lines) + "\n")
|
||||
|
||||
@@ -2,22 +2,24 @@
|
||||
Graph/Tree Overview Widget - Displays real agent graph structure.
|
||||
"""
|
||||
|
||||
from textual.widgets import Tree, Static, RichLog
|
||||
from textual.app import ComposeResult
|
||||
from textual.containers import Vertical
|
||||
from textual.widgets import RichLog
|
||||
|
||||
from framework.runtime.agent_runtime import AgentRuntime
|
||||
from framework.runtime.event_bus import EventType
|
||||
|
||||
|
||||
class GraphOverview(Vertical):
|
||||
"""Widget to display Agent execution graph/tree with real data."""
|
||||
|
||||
|
||||
DEFAULT_CSS = """
|
||||
GraphOverview {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: $panel;
|
||||
}
|
||||
|
||||
|
||||
GraphOverview > RichLog {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@@ -27,37 +29,37 @@ class GraphOverview(Vertical):
|
||||
scrollbar-color: $primary;
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
def __init__(self, runtime: AgentRuntime):
|
||||
super().__init__()
|
||||
self.runtime = runtime
|
||||
self.active_node = None
|
||||
self.execution_path = []
|
||||
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
# Use RichLog for formatted output
|
||||
yield RichLog(id="graph-display", highlight=True, markup=True)
|
||||
|
||||
|
||||
def on_mount(self) -> None:
|
||||
"""Display initial graph structure."""
|
||||
self._display_graph()
|
||||
|
||||
|
||||
def _display_graph(self) -> None:
|
||||
"""Display the graph structure with nodes and entry points."""
|
||||
display = self.query_one("#graph-display", RichLog)
|
||||
|
||||
|
||||
# Clear and display header
|
||||
display.clear()
|
||||
display.write("[bold cyan]Agent Graph Structure[/bold cyan]\n")
|
||||
|
||||
|
||||
# Get graph from runtime
|
||||
graph = self.runtime.graph
|
||||
|
||||
|
||||
# Display graph info
|
||||
display.write(f"[dim]Graph ID:[/dim] {graph.id}")
|
||||
display.write(f"[dim]Goal:[/dim] {self.runtime.goal.description[:50]}...")
|
||||
display.write("")
|
||||
|
||||
|
||||
# Display entry points
|
||||
entry_points = self.runtime.get_entry_points()
|
||||
if entry_points:
|
||||
@@ -66,14 +68,14 @@ class GraphOverview(Vertical):
|
||||
display.write(f" • {ep.name} → [cyan]{ep.entry_node}[/cyan]")
|
||||
else:
|
||||
display.write(f"[bold]Entry Node:[/bold] [cyan]{graph.entry_node}[/cyan]")
|
||||
|
||||
|
||||
display.write("")
|
||||
|
||||
|
||||
# Display nodes
|
||||
display.write(f"[bold]Nodes ({len(graph.nodes)}):[/bold]")
|
||||
for node in graph.nodes:
|
||||
node_type = node.type if hasattr(node, 'type') else 'unknown'
|
||||
|
||||
node_type = node.type if hasattr(node, "type") else "unknown"
|
||||
|
||||
# Highlight active node
|
||||
if self.active_node == node.id:
|
||||
display.write(f" ▶ [bold green]{node.id}[/bold green] ({node_type})")
|
||||
@@ -81,50 +83,50 @@ class GraphOverview(Vertical):
|
||||
display.write(f" ✓ [dim]{node.id}[/dim] ({node_type})")
|
||||
else:
|
||||
display.write(f" • {node.id} ({node_type})")
|
||||
|
||||
|
||||
display.write("")
|
||||
|
||||
|
||||
# Display terminal nodes
|
||||
if graph.terminal_nodes:
|
||||
display.write(f"[bold]Terminal Nodes:[/bold]")
|
||||
display.write("[bold]Terminal Nodes:[/bold]")
|
||||
for node_id in graph.terminal_nodes:
|
||||
display.write(f" • [yellow]{node_id}[/yellow]")
|
||||
|
||||
|
||||
# Display execution status
|
||||
if self.active_node:
|
||||
display.write("")
|
||||
display.write(f"[bold green]Currently Executing:[/bold green] {self.active_node}")
|
||||
|
||||
|
||||
if self.execution_path:
|
||||
display.write(f"[dim]Path:[/dim] {' → '.join(self.execution_path[-5:])}")
|
||||
|
||||
|
||||
def update_active_node(self, node_id: str) -> None:
|
||||
"""Update the currently active node."""
|
||||
self.active_node = node_id
|
||||
if node_id not in self.execution_path:
|
||||
self.execution_path.append(node_id)
|
||||
self._display_graph()
|
||||
|
||||
|
||||
def update_execution(self, event) -> None:
|
||||
"""Update the displayed node status based on event."""
|
||||
display = self.query_one("#graph-display", RichLog)
|
||||
|
||||
|
||||
if event.type == EventType.NODE_STARTED:
|
||||
node_id = event.data.get("node_id")
|
||||
if node_id:
|
||||
self.update_active_node(node_id)
|
||||
|
||||
|
||||
elif event.type == EventType.NODE_COMPLETED:
|
||||
node_id = event.data.get("node_id")
|
||||
if node_id and node_id == self.active_node:
|
||||
self.active_node = None
|
||||
self._display_graph()
|
||||
|
||||
|
||||
elif event.type == EventType.EXECUTION_COMPLETED:
|
||||
display.write("")
|
||||
display.write("[bold green]✓ Execution Complete![/bold green]")
|
||||
self.active_node = None
|
||||
|
||||
|
||||
elif event.type == EventType.EXECUTION_FAILED:
|
||||
display.write("")
|
||||
error = event.data.get("error", "Unknown error")
|
||||
|
||||
@@ -2,19 +2,20 @@
|
||||
Log Pane Widget - Uses RichLog for reliable rendering.
|
||||
"""
|
||||
|
||||
from textual.widgets import RichLog
|
||||
from textual.app import ComposeResult
|
||||
from textual.containers import Container
|
||||
from textual.widgets import RichLog
|
||||
|
||||
|
||||
class LogPane(Container):
|
||||
"""Widget to display logs with reliable rendering."""
|
||||
|
||||
|
||||
DEFAULT_CSS = """
|
||||
LogPane {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
LogPane > RichLog {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@@ -27,12 +28,7 @@ class LogPane(Container):
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
# RichLog is designed for log display and doesn't have TextArea's rendering issues
|
||||
yield RichLog(
|
||||
id="main-log",
|
||||
highlight=True,
|
||||
markup=True,
|
||||
auto_scroll=True
|
||||
)
|
||||
yield RichLog(id="main-log", highlight=True, markup=True, auto_scroll=True)
|
||||
|
||||
def write_log(self, message: str) -> None:
|
||||
"""Write a log message to the log pane."""
|
||||
@@ -40,16 +36,16 @@ class LogPane(Container):
|
||||
# Check if widget is mounted
|
||||
if not self.is_mounted:
|
||||
return
|
||||
|
||||
|
||||
log = self.query_one("#main-log", RichLog)
|
||||
|
||||
|
||||
# Check if log is mounted
|
||||
if not log.is_mounted:
|
||||
return
|
||||
|
||||
|
||||
# Write message - RichLog handles rendering correctly
|
||||
log.write(message)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
# Widget might not be ready
|
||||
with open("tui_debug.log", "a") as f:
|
||||
|
||||
Reference in New Issue
Block a user