Better handling for rich markup errors
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "strix-agent"
|
name = "strix-agent"
|
||||||
version = "0.1.15"
|
version = "0.1.17"
|
||||||
description = "Open-source AI Hackers for your apps"
|
description = "Open-source AI Hackers for your apps"
|
||||||
authors = ["Strix <hi@usestrix.com>"]
|
authors = ["Strix <hi@usestrix.com>"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import threading
|
|||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from typing import Any, ClassVar
|
from typing import Any, ClassVar
|
||||||
|
|
||||||
from rich.markup import MarkupError
|
|
||||||
from rich.markup import escape as rich_escape
|
from rich.markup import escape as rich_escape
|
||||||
|
from rich.text import Text
|
||||||
from textual import events, on
|
from textual import events, on
|
||||||
from textual.app import App, ComposeResult
|
from textual.app import App, ComposeResult
|
||||||
from textual.binding import Binding
|
from textual.binding import Binding
|
||||||
@@ -550,7 +550,9 @@ class StrixCLIApp(App): # type: ignore[misc]
|
|||||||
elif status == "llm_failed":
|
elif status == "llm_failed":
|
||||||
error_msg = agent_data.get("error_message", "")
|
error_msg = agent_data.get("error_message", "")
|
||||||
display_msg = (
|
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(status_text.update, display_msg)
|
||||||
self._safe_widget_operation(
|
self._safe_widget_operation(
|
||||||
@@ -954,7 +956,7 @@ class StrixCLIApp(App): # type: ignore[misc]
|
|||||||
content = "\n".join(lines)
|
content = "\n".join(lines)
|
||||||
|
|
||||||
lines = content.split("\n")
|
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)
|
return "\n".join(bordered_lines)
|
||||||
|
|
||||||
@on(Tree.NodeHighlighted) # type: ignore[misc]
|
@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:
|
def _update_static_content_safe(self, widget: Static, content: str) -> None:
|
||||||
try:
|
try:
|
||||||
widget.update(content)
|
widget.update(content)
|
||||||
except MarkupError:
|
except Exception: # noqa: BLE001
|
||||||
try:
|
try:
|
||||||
widget.update(rich_escape(content))
|
safe_text = Text.from_markup(content)
|
||||||
except Exception:
|
widget.update(safe_text)
|
||||||
widget.update(rich_escape(str(content)))
|
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:
|
async def run_strix_cli(args: argparse.Namespace) -> None:
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class CreateAgentRenderer(BaseToolRenderer):
|
|||||||
task = args.get("task", "")
|
task = args.get("task", "")
|
||||||
name = args.get("name", "Agent")
|
name = args.get("name", "Agent")
|
||||||
|
|
||||||
header = f"🤖 [bold #fbbf24]Creating {name}[/]"
|
header = f"🤖 [bold #fbbf24]Creating {cls.escape_markup(name)}[/]"
|
||||||
|
|
||||||
if task:
|
if task:
|
||||||
task_display = task[:400] + "..." if len(task) > 400 else task
|
task_display = task[:400] + "..." if len(task) > 400 else task
|
||||||
|
|||||||
@@ -48,10 +48,10 @@ class BaseToolRenderer(ABC):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def get_status_icon(cls, status: str) -> str:
|
def get_status_icon(cls, status: str) -> str:
|
||||||
status_icons = {
|
status_icons = {
|
||||||
"running": "[#f59e0b]●[/] In progress...",
|
"running": "[#f59e0b]●[/#f59e0b] In progress...",
|
||||||
"completed": "[#22c55e]✓[/] Done",
|
"completed": "[#22c55e]✓[/#22c55e] Done",
|
||||||
"failed": "[#dc2626]✗[/] Failed",
|
"failed": "[#dc2626]✗[/#dc2626] Failed",
|
||||||
"error": "[#dc2626]✗[/] Error",
|
"error": "[#dc2626]✗[/#dc2626] Error",
|
||||||
}
|
}
|
||||||
return status_icons.get(status, "[dim]○[/dim] Unknown")
|
return status_icons.get(status, "[dim]○[/dim] Unknown")
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ class BrowserRenderer(BaseToolRenderer):
|
|||||||
"double_click": "double clicking",
|
"double_click": "double clicking",
|
||||||
"hover": "hovering",
|
"hover": "hovering",
|
||||||
}
|
}
|
||||||
message = action_words[action]
|
message = cls.escape_markup(action_words[action])
|
||||||
|
|
||||||
return f"{browser_icon} [#06b6d4]{message}[/]"
|
return f"{browser_icon} [#06b6d4]{message}[/]"
|
||||||
|
|
||||||
@@ -97,9 +97,9 @@ class BrowserRenderer(BaseToolRenderer):
|
|||||||
}
|
}
|
||||||
|
|
||||||
if action in simple_actions:
|
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
|
@classmethod
|
||||||
def _format_url(cls, url: str) -> str:
|
def _format_url(cls, url: str) -> str:
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ class ViewRequestRenderer(BaseToolRenderer):
|
|||||||
|
|
||||||
part = args.get("part", "request")
|
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 result and isinstance(result, dict):
|
||||||
if "content" in result:
|
if "content" in result:
|
||||||
@@ -107,7 +107,7 @@ class SendRequestRenderer(BaseToolRenderer):
|
|||||||
method = args.get("method", "GET")
|
method = args.get("method", "GET")
|
||||||
url = args.get("url", "")
|
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):
|
if result and isinstance(result, dict):
|
||||||
status_code = result.get("status_code")
|
status_code = result.get("status_code")
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ def _render_default_tool_widget(tool_data: dict[str, Any]) -> Static:
|
|||||||
|
|
||||||
status_text = BaseToolRenderer.get_status_icon(status)
|
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]
|
content_parts = [header]
|
||||||
|
|
||||||
args_str = BaseToolRenderer.format_args(args)
|
args_str = BaseToolRenderer.format_args(args)
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class CreateVulnerabilityReportRenderer(BaseToolRenderer):
|
|||||||
if severity:
|
if severity:
|
||||||
severity_color = cls._get_severity_color(severity.lower())
|
severity_color = cls._get_severity_color(severity.lower())
|
||||||
content_parts.append(
|
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:
|
if content:
|
||||||
|
|||||||
@@ -28,12 +28,12 @@ class ScanStartInfoRenderer(BaseToolRenderer):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def _build_target_display(cls, target: dict[str, Any]) -> str:
|
def _build_target_display(cls, target: dict[str, Any]) -> str:
|
||||||
if target_url := target.get("target_url"):
|
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"):
|
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"):
|
if target_path := target.get("target_path"):
|
||||||
return f"[bold #22c55e]{cls.escape_markup(target_path)}[/]"
|
return cls.escape_markup(str(target_path))
|
||||||
return "[dim]unknown target[/dim]"
|
return "unknown target"
|
||||||
|
|
||||||
|
|
||||||
@register_tool_renderer
|
@register_tool_renderer
|
||||||
@@ -49,9 +49,11 @@ class SubagentStartInfoRenderer(BaseToolRenderer):
|
|||||||
name = args.get("name", "Unknown Agent")
|
name = args.get("name", "Unknown Agent")
|
||||||
task = args.get("task", "")
|
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:
|
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)
|
css_classes = cls.get_css_classes(status)
|
||||||
return Static(content, classes=css_classes)
|
return Static(content, classes=css_classes)
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ class TerminalRenderer(BaseToolRenderer):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if is_special:
|
if is_special:
|
||||||
return f"{terminal_icon} [#ef4444]{command}[/]"
|
return f"{terminal_icon} [#ef4444]{cls.escape_markup(command)}[/]"
|
||||||
|
|
||||||
if is_input:
|
if is_input:
|
||||||
formatted_command = cls._format_command_display(command)
|
formatted_command = cls._format_command_display(command)
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ class UserMessageRenderer(BaseToolRenderer):
|
|||||||
content = content[:297] + "..."
|
content = content[:297] + "..."
|
||||||
|
|
||||||
lines = content.split("\n")
|
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)
|
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)
|
css_classes = " ".join(cls.css_classes)
|
||||||
return Static(formatted_content, classes=css_classes)
|
return Static(formatted_content, classes=css_classes)
|
||||||
@@ -38,6 +38,6 @@ class UserMessageRenderer(BaseToolRenderer):
|
|||||||
content = content[:297] + "..."
|
content = content[:297] + "..."
|
||||||
|
|
||||||
lines = content.split("\n")
|
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)
|
bordered_content = "\n".join(bordered_lines)
|
||||||
return f"[#3b82f6]▍[/] [bold]You:[/]\n{bordered_content}"
|
return f"[#3b82f6]▍[/#3b82f6] [bold]You:[/]\n{bordered_content}"
|
||||||
|
|||||||
Reference in New Issue
Block a user