From af01294c4693f6f04af0f5531ae8f6aff56f51e1 Mon Sep 17 00:00:00 2001 From: Ahmed Allam Date: Wed, 24 Sep 2025 01:13:02 -0700 Subject: [PATCH] Better handling for rich markup errors --- pyproject.toml | 2 +- strix/cli/app.py | 20 ++++++++++++------- .../tool_components/agents_graph_renderer.py | 2 +- strix/cli/tool_components/base_renderer.py | 8 ++++---- strix/cli/tool_components/browser_renderer.py | 6 +++--- strix/cli/tool_components/proxy_renderer.py | 4 ++-- strix/cli/tool_components/registry.py | 2 +- .../cli/tool_components/reporting_renderer.py | 2 +- .../cli/tool_components/scan_info_renderer.py | 14 +++++++------ .../cli/tool_components/terminal_renderer.py | 2 +- .../tool_components/user_message_renderer.py | 8 ++++---- 11 files changed, 39 insertions(+), 31 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 30f919d..8d755be 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "strix-agent" -version = "0.1.15" +version = "0.1.17" description = "Open-source AI Hackers for your apps" authors = ["Strix "] readme = "README.md" diff --git a/strix/cli/app.py b/strix/cli/app.py index 3953cab..63414f0 100644 --- a/strix/cli/app.py +++ b/strix/cli/app.py @@ -9,8 +9,8 @@ import threading from collections.abc import Callable from typing import Any, ClassVar -from rich.markup import MarkupError from rich.markup import escape as rich_escape +from rich.text import Text from textual import events, on from textual.app import App, ComposeResult from textual.binding import Binding @@ -550,7 +550,9 @@ class StrixCLIApp(App): # type: ignore[misc] elif status == "llm_failed": error_msg = agent_data.get("error_message", "") display_msg = ( - f"[red]{error_msg}[/red]" if error_msg else "[red]LLM request failed[/red]" + f"[red]{escape_markup(error_msg)}[/red]" + if error_msg + else "[red]LLM request failed[/red]" ) self._safe_widget_operation(status_text.update, display_msg) self._safe_widget_operation( @@ -954,7 +956,7 @@ class StrixCLIApp(App): # type: ignore[misc] content = "\n".join(lines) lines = content.split("\n") - bordered_lines = [f"[{color}]▍[/] {line}" for line in lines] + bordered_lines = [f"[{color}]▍[/{color}] {line}" for line in lines] return "\n".join(bordered_lines) @on(Tree.NodeHighlighted) # type: ignore[misc] @@ -1146,11 +1148,15 @@ class StrixCLIApp(App): # type: ignore[misc] def _update_static_content_safe(self, widget: Static, content: str) -> None: try: widget.update(content) - except MarkupError: + except Exception: # noqa: BLE001 try: - widget.update(rich_escape(content)) - except Exception: - widget.update(rich_escape(str(content))) + safe_text = Text.from_markup(content) + widget.update(safe_text) + except Exception: # noqa: BLE001 + import re + + plain_text = re.sub(r"\[.*?\]", "", content) + widget.update(plain_text) async def run_strix_cli(args: argparse.Namespace) -> None: diff --git a/strix/cli/tool_components/agents_graph_renderer.py b/strix/cli/tool_components/agents_graph_renderer.py index b5ae5ad..ef8c47d 100644 --- a/strix/cli/tool_components/agents_graph_renderer.py +++ b/strix/cli/tool_components/agents_graph_renderer.py @@ -31,7 +31,7 @@ class CreateAgentRenderer(BaseToolRenderer): task = args.get("task", "") name = args.get("name", "Agent") - header = f"🤖 [bold #fbbf24]Creating {name}[/]" + header = f"🤖 [bold #fbbf24]Creating {cls.escape_markup(name)}[/]" if task: task_display = task[:400] + "..." if len(task) > 400 else task diff --git a/strix/cli/tool_components/base_renderer.py b/strix/cli/tool_components/base_renderer.py index baa32c3..80b8f54 100644 --- a/strix/cli/tool_components/base_renderer.py +++ b/strix/cli/tool_components/base_renderer.py @@ -48,10 +48,10 @@ class BaseToolRenderer(ABC): @classmethod def get_status_icon(cls, status: str) -> str: status_icons = { - "running": "[#f59e0b]●[/] In progress...", - "completed": "[#22c55e]✓[/] Done", - "failed": "[#dc2626]✗[/] Failed", - "error": "[#dc2626]✗[/] Error", + "running": "[#f59e0b]●[/#f59e0b] In progress...", + "completed": "[#22c55e]✓[/#22c55e] Done", + "failed": "[#dc2626]✗[/#dc2626] Failed", + "error": "[#dc2626]✗[/#dc2626] Error", } return status_icons.get(status, "[dim]○[/dim] Unknown") diff --git a/strix/cli/tool_components/browser_renderer.py b/strix/cli/tool_components/browser_renderer.py index 1c4723c..8b2d252 100644 --- a/strix/cli/tool_components/browser_renderer.py +++ b/strix/cli/tool_components/browser_renderer.py @@ -76,7 +76,7 @@ class BrowserRenderer(BaseToolRenderer): "double_click": "double clicking", "hover": "hovering", } - message = action_words[action] + message = cls.escape_markup(action_words[action]) return f"{browser_icon} [#06b6d4]{message}[/]" @@ -97,9 +97,9 @@ class BrowserRenderer(BaseToolRenderer): } if action in simple_actions: - return f"{browser_icon} [#06b6d4]{simple_actions[action]}[/]" + return f"{browser_icon} [#06b6d4]{cls.escape_markup(simple_actions[action])}[/]" - return f"{browser_icon} [#06b6d4]{action}[/]" + return f"{browser_icon} [#06b6d4]{cls.escape_markup(action)}[/]" @classmethod def _format_url(cls, url: str) -> str: diff --git a/strix/cli/tool_components/proxy_renderer.py b/strix/cli/tool_components/proxy_renderer.py index 469e56b..5c829d8 100644 --- a/strix/cli/tool_components/proxy_renderer.py +++ b/strix/cli/tool_components/proxy_renderer.py @@ -64,7 +64,7 @@ class ViewRequestRenderer(BaseToolRenderer): part = args.get("part", "request") - header = f"👀 [bold #06b6d4]Viewing {part}[/]" + header = f"👀 [bold #06b6d4]Viewing {cls.escape_markup(part)}[/]" if result and isinstance(result, dict): if "content" in result: @@ -107,7 +107,7 @@ class SendRequestRenderer(BaseToolRenderer): method = args.get("method", "GET") url = args.get("url", "") - header = f"📤 [bold #06b6d4]Sending {method}[/]" + header = f"📤 [bold #06b6d4]Sending {cls.escape_markup(method)}[/]" if result and isinstance(result, dict): status_code = result.get("status_code") diff --git a/strix/cli/tool_components/registry.py b/strix/cli/tool_components/registry.py index 753a20b..583ab3a 100644 --- a/strix/cli/tool_components/registry.py +++ b/strix/cli/tool_components/registry.py @@ -54,7 +54,7 @@ def _render_default_tool_widget(tool_data: dict[str, Any]) -> Static: status_text = BaseToolRenderer.get_status_icon(status) - header = f"→ Using tool [bold blue]{tool_name}[/]" + header = f"→ Using tool [bold blue]{BaseToolRenderer.escape_markup(tool_name)}[/]" content_parts = [header] args_str = BaseToolRenderer.format_args(args) diff --git a/strix/cli/tool_components/reporting_renderer.py b/strix/cli/tool_components/reporting_renderer.py index 37aec8d..8e7ee94 100644 --- a/strix/cli/tool_components/reporting_renderer.py +++ b/strix/cli/tool_components/reporting_renderer.py @@ -27,7 +27,7 @@ class CreateVulnerabilityReportRenderer(BaseToolRenderer): if severity: severity_color = cls._get_severity_color(severity.lower()) content_parts.append( - f" [dim]Severity: [{severity_color}]{severity.upper()}[/{severity_color}][/]" + f" [dim]Severity: [{severity_color}]{cls.escape_markup(severity.upper())}[/{severity_color}][/]" ) if content: diff --git a/strix/cli/tool_components/scan_info_renderer.py b/strix/cli/tool_components/scan_info_renderer.py index 22a438d..31d852e 100644 --- a/strix/cli/tool_components/scan_info_renderer.py +++ b/strix/cli/tool_components/scan_info_renderer.py @@ -28,12 +28,12 @@ class ScanStartInfoRenderer(BaseToolRenderer): @classmethod def _build_target_display(cls, target: dict[str, Any]) -> str: if target_url := target.get("target_url"): - return f"[bold #22c55e]{cls.escape_markup(target_url)}[/]" + return cls.escape_markup(str(target_url)) if target_repo := target.get("target_repo"): - return f"[bold #22c55e]{cls.escape_markup(target_repo)}[/]" + return cls.escape_markup(str(target_repo)) if target_path := target.get("target_path"): - return f"[bold #22c55e]{cls.escape_markup(target_path)}[/]" - return "[dim]unknown target[/dim]" + return cls.escape_markup(str(target_path)) + return "unknown target" @register_tool_renderer @@ -49,9 +49,11 @@ class SubagentStartInfoRenderer(BaseToolRenderer): name = args.get("name", "Unknown Agent") task = args.get("task", "") - content = f"🤖 Spawned subagent [bold #22c55e]{cls.escape_markup(name)}[/]" + name = cls.escape_markup(str(name)) + content = f"🤖 Spawned subagent {name}" if task: - content += f"\n Task: [dim]{cls.escape_markup(task)}[/dim]" + task = cls.escape_markup(str(task)) + content += f"\n Task: {task}" css_classes = cls.get_css_classes(status) return Static(content, classes=css_classes) diff --git a/strix/cli/tool_components/terminal_renderer.py b/strix/cli/tool_components/terminal_renderer.py index 8114f16..f6159cd 100644 --- a/strix/cli/tool_components/terminal_renderer.py +++ b/strix/cli/tool_components/terminal_renderer.py @@ -111,7 +111,7 @@ class TerminalRenderer(BaseToolRenderer): ) if is_special: - return f"{terminal_icon} [#ef4444]{command}[/]" + return f"{terminal_icon} [#ef4444]{cls.escape_markup(command)}[/]" if is_input: formatted_command = cls._format_command_display(command) diff --git a/strix/cli/tool_components/user_message_renderer.py b/strix/cli/tool_components/user_message_renderer.py index 149718d..4494575 100644 --- a/strix/cli/tool_components/user_message_renderer.py +++ b/strix/cli/tool_components/user_message_renderer.py @@ -22,9 +22,9 @@ class UserMessageRenderer(BaseToolRenderer): content = content[:297] + "..." lines = content.split("\n") - bordered_lines = [f"[#3b82f6]▍[/] {line}" for line in lines] + bordered_lines = [f"[#3b82f6]▍[/#3b82f6] {line}" for line in lines] bordered_content = "\n".join(bordered_lines) - formatted_content = f"[#3b82f6]▍[/] [bold]You:[/]\n{bordered_content}" + formatted_content = f"[#3b82f6]▍[/#3b82f6] [bold]You:[/]\n{bordered_content}" css_classes = " ".join(cls.css_classes) return Static(formatted_content, classes=css_classes) @@ -38,6 +38,6 @@ class UserMessageRenderer(BaseToolRenderer): content = content[:297] + "..." lines = content.split("\n") - bordered_lines = [f"[#3b82f6]▍[/] {line}" for line in lines] + bordered_lines = [f"[#3b82f6]▍[/#3b82f6] {line}" for line in lines] bordered_content = "\n".join(bordered_lines) - return f"[#3b82f6]▍[/] [bold]You:[/]\n{bordered_content}" + return f"[#3b82f6]▍[/#3b82f6] [bold]You:[/]\n{bordered_content}"