3030from openhands .tools .task_tracker .definition import TaskTrackerObservation
3131from openhands .tools .terminal .definition import TerminalAction
3232from openhands_cli .shared .delegate_formatter import format_delegate_title
33+ from openhands_cli .shared .rich_utils import escape_rich_markup
3334from openhands_cli .stores import CliSettings
3435from openhands_cli .theme import OPENHANDS_THEME
3536from openhands_cli .tui .widgets .collapsible import (
@@ -471,15 +472,15 @@ def _build_action_title(self, event: ActionEvent) -> str:
471472 """
472473 agent_prefix = self ._get_agent_prefix ()
473474 summary = (
474- self . _escape_rich_markup (str (event .summary ).strip ().replace ("\n " , " " ))
475+ escape_rich_markup (str (event .summary ).strip ().replace ("\n " , " " ))
475476 if event .summary
476477 else ""
477478 )
478479 action = event .action
479480
480481 # Terminal actions: show summary + command (truncated for display)
481482 if isinstance (action , TerminalAction ) and action .command :
482- cmd = self . _escape_rich_markup (action .command .strip ().replace ("\n " , " " ))
483+ cmd = escape_rich_markup (action .command .strip ().replace ("\n " , " " ))
483484 cmd = self ._truncate_for_display (cmd )
484485 if summary :
485486 return f"{ agent_prefix } [bold]{ summary } [/bold][dim]: $ { cmd } [/dim]"
@@ -488,7 +489,7 @@ def _build_action_title(self, event: ActionEvent) -> str:
488489 # File operations: include path with Reading/Editing
489490 elif isinstance (action , FileEditorAction ) and action .path :
490491 op = "Reading" if action .command == "view" else "Editing"
491- path = self . _escape_rich_markup (action .path )
492+ path = escape_rich_markup (action .path )
492493 if summary :
493494 return f"{ agent_prefix } [bold]{ summary } [/bold][dim]: { op } { path } [/dim]"
494495 return f"{ agent_prefix } [bold]{ op } [/bold][dim] { path } [/dim]"
@@ -523,16 +524,6 @@ def _build_observation_content(
523524 # The Collapsible widget can handle Rich renderables
524525 return str (event .visualize )
525526
526- def _escape_rich_markup (self , text : str ) -> str :
527- """Escape Rich markup characters in text to prevent markup errors.
528-
529- This is needed to handle content with special characters (e.g., Chinese text
530- with brackets) that would otherwise cause MarkupError when rendered in
531- Collapsible widgets with markup=True.
532- """
533- # Escape square brackets which are used for Rich markup
534- return text .replace ("[" , r"\[" ).replace ("]" , r"\]" )
535-
536527 def _truncate_for_display (
537528 self , text : str , max_length : int = MAX_LINE_LENGTH , * , from_start : bool = True
538529 ) -> str :
@@ -555,7 +546,7 @@ def _clean_and_truncate(self, text: str, *, from_start: bool = True) -> str:
555546 """Strip, collapse newlines, truncate, and escape Rich markup for display."""
556547 text = str (text ).strip ().replace ("\n " , " " )
557548 text = self ._truncate_for_display (text , from_start = from_start )
558- return self . _escape_rich_markup (text )
549+ return escape_rich_markup (text )
559550
560551 def _extract_meaningful_title (self , event , fallback_title : str ) -> str :
561552 """Extract a meaningful title from an event, with fallback to truncated
@@ -635,7 +626,7 @@ def _extract_meaningful_title(self, event, fallback_title: str) -> str:
635626 content_str = self ._truncate_for_display (content_str )
636627
637628 if content_str .strip ():
638- return f"{ fallback_title } : { self . _escape_rich_markup (content_str )} "
629+ return f"{ fallback_title } : { escape_rich_markup (content_str )} "
639630 except Exception :
640631 pass
641632
@@ -770,7 +761,7 @@ def _create_titled_collapsible(
770761 ) -> Collapsible :
771762 """Create a standard titled collapsible for non-action events."""
772763 title = self ._extract_meaningful_title (event , fallback_title )
773- content_string = self . _escape_rich_markup (str (event .visualize ))
764+ content_string = escape_rich_markup (str (event .visualize ))
774765 return self ._make_collapsible (
775766 content_string ,
776767 f"{ self ._get_agent_prefix ()} { title } " ,
@@ -816,7 +807,7 @@ def _create_event_collapsible(self, event: Event) -> Collapsible | None:
816807 if isinstance (event , ActionEvent ):
817808 title = self ._build_action_title (event )
818809 collapsible = self ._make_collapsible (
819- self . _escape_rich_markup (str (content )),
810+ escape_rich_markup (str (content )),
820811 title ,
821812 event ,
822813 )
@@ -838,9 +829,7 @@ def _create_event_collapsible(self, event: Event) -> Collapsible | None:
838829 title = self ._extract_meaningful_title (
839830 event , f"UNKNOWN Event: { event .__class__ .__name__ } "
840831 )
841- content_string = (
842- f"{ self ._escape_rich_markup (str (content ))} \n \n Source: { event .source } "
843- )
832+ content_string = f"{ escape_rich_markup (str (content ))} \n \n Source: { event .source } "
844833 return self ._make_collapsible (
845834 content_string ,
846835 f"{ self ._get_agent_prefix ()} { title } " ,
0 commit comments