@@ -40,7 +40,36 @@ def _get_console():
4040 global _console
4141 if _console is None and _get_rich_available ():
4242 from rich .console import Console
43- _console = Console ()
43+ import sys
44+ import os
45+
46+ # Detect Windows legacy encoding (CP1252) and use safe fallback
47+ if sys .platform == "win32" and hasattr (sys .stdout , 'encoding' ):
48+ encoding = getattr (sys .stdout , 'encoding' , '' ).lower ()
49+ # Check for Windows legacy code pages that can't handle Unicode
50+ if encoding in ('cp1252' , 'cp1251' , 'cp850' , 'ascii' ) or ('cp' in encoding and encoding != 'cp65001' ):
51+ # Force UTF-8 mode or create console with safe encoding handling
52+ try :
53+ # Set PYTHONIOENCODING to utf-8 for subprocess safety (won't affect current process)
54+ if 'PYTHONIOENCODING' not in os .environ :
55+ os .environ ['PYTHONIOENCODING' ] = 'utf-8'
56+
57+ # Create console with safe encoding options
58+ _console = Console (
59+ force_terminal = True ,
60+ legacy_windows = True , # Use legacy Windows mode
61+ safe_box = True , # Use safe box characters
62+ emoji = False , # Disable emojis on Windows legacy
63+ color_system = 'standard' , # Use basic colors
64+ _environ = os .environ # Pass updated environment
65+ )
66+ except Exception :
67+ # Fallback to basic console if Rich options fail
68+ _console = Console (force_terminal = False , no_color = True )
69+ else :
70+ _console = Console ()
71+ else :
72+ _console = Console ()
4473 return _console
4574
4675
@@ -137,6 +166,19 @@ def is_screen_reader(self) -> bool:
137166 """Check if in screen reader mode."""
138167 return self .mode == OutputMode .SCREEN_READER
139168
169+ def _safe_console_print (self , * args , ** kwargs ) -> None :
170+ """Safely print to console with UnicodeEncodeError protection."""
171+ if self .console :
172+ try :
173+ self .console .print (* args , ** kwargs )
174+ except UnicodeEncodeError :
175+ # Fallback to plain text if Rich can't handle the encoding
176+ message = str (args [0 ]) if args else ""
177+ print (message .encode ('ascii' , 'replace' ).decode ('ascii' ))
178+ else :
179+ message = str (args [0 ]) if args else ""
180+ print (message )
181+
140182 def print (self , message : str , style : Optional [str ] = None , ** kwargs ) -> None :
141183 """Print a message respecting the current mode."""
142184 if self .is_quiet :
@@ -150,11 +192,8 @@ def print(self, message: str, style: Optional[str] = None, **kwargs) -> None:
150192 # Plain text output
151193 print (message )
152194 else :
153- # Rich formatted output
154- if self .console :
155- self .console .print (message , style = style , ** kwargs )
156- else :
157- print (message )
195+ # Rich formatted output with encoding safety
196+ self ._safe_console_print (message , style = style , ** kwargs )
158197
159198 def print_error (self , message : str , code : Optional [str ] = None , remediation : Optional [str ] = None ) -> None :
160199 """Print an error message with optional remediation."""
@@ -187,10 +226,16 @@ def print_error(self, message: str, code: Optional[str] = None, remediation: Opt
187226 if remediation :
188227 content .append (f"\n 💡 Fix: { remediation } " , style = "yellow" )
189228
190- if self .console :
191- self .console .print (Panel (content , title = "Error" , border_style = "red" ))
192- else :
229+ try :
230+ if self .console :
231+ self .console .print (Panel (content , title = "Error" , border_style = "red" ))
232+ else :
233+ print (f"ERROR: { message } " , file = sys .stderr )
234+ except UnicodeEncodeError :
235+ # Fallback to plain text if emoji rendering fails
193236 print (f"ERROR: { message } " , file = sys .stderr )
237+ if remediation :
238+ print (f"FIX: { remediation } " , file = sys .stderr )
194239
195240 def print_success (self , message : str , data : Optional [Dict [str , Any ]] = None ) -> None :
196241 """Print a success message."""
@@ -214,7 +259,10 @@ def print_success(self, message: str, data: Optional[Dict[str, Any]] = None) ->
214259 if self .is_screen_reader or self .no_color or not _get_rich_available ():
215260 print (f"SUCCESS: { message } " )
216261 else :
217- self .print (f"✅ { message } " , style = "bold green" )
262+ try :
263+ self .print (f"✅ { message } " , style = "bold green" )
264+ except UnicodeEncodeError :
265+ print (f"SUCCESS: { message } " )
218266
219267 def print_warning (self , message : str ) -> None :
220268 """Print a warning message."""
@@ -224,7 +272,10 @@ def print_warning(self, message: str) -> None:
224272 if self .is_screen_reader or self .no_color or not _get_rich_available ():
225273 print (f"WARNING: { message } " )
226274 else :
227- self .print (f"⚠️ { message } " , style = "bold yellow" )
275+ try :
276+ self .print (f"⚠️ { message } " , style = "bold yellow" )
277+ except UnicodeEncodeError :
278+ print (f"WARNING: { message } " )
228279
229280 def print_info (self , message : str ) -> None :
230281 """Print an info message."""
@@ -234,7 +285,10 @@ def print_info(self, message: str) -> None:
234285 if self .is_screen_reader or self .no_color or not _get_rich_available ():
235286 print (f"INFO: { message } " )
236287 else :
237- self .print (f"ℹ️ { message } " , style = "bold blue" )
288+ try :
289+ self .print (f"ℹ️ { message } " , style = "bold blue" )
290+ except UnicodeEncodeError :
291+ print (f"INFO: { message } " )
238292
239293 def print_debug (self , message : str ) -> None :
240294 """Print a debug message (only in verbose mode)."""
@@ -244,7 +298,10 @@ def print_debug(self, message: str) -> None:
244298 if self .is_screen_reader or self .no_color or not _get_rich_available ():
245299 print (f"DEBUG: { message } " )
246300 else :
247- self .print (f"🔍 { message } " , style = "dim" )
301+ try :
302+ self .print (f"🔍 { message } " , style = "dim" )
303+ except UnicodeEncodeError :
304+ print (f"DEBUG: { message } " )
248305
249306 def emit_event (self , event_type : str , message : Optional [str ] = None , data : Optional [Dict [str , Any ]] = None , agent_id : Optional [str ] = None ) -> None :
250307 """Emit a stream event (for stream-json mode)."""
@@ -321,7 +378,28 @@ def print_table(self, headers: List[str], rows: List[List[Any]], title: Optional
321378 table .add_row (* [str (cell ) for cell in row ])
322379
323380 if self .console :
324- self .console .print (table )
381+ try :
382+ self .console .print (table )
383+ except UnicodeEncodeError :
384+ # Fallback to plain text table if Rich fails
385+ if title :
386+ print (f"\n { title } " )
387+ print ("-" * len (title ))
388+
389+ # Calculate column widths
390+ widths = [len (h ) for h in headers ]
391+ for row in rows :
392+ for i , cell in enumerate (row ):
393+ widths [i ] = max (widths [i ], len (str (cell )))
394+
395+ # Print header
396+ header_line = " | " .join (h .ljust (widths [i ]) for i , h in enumerate (headers ))
397+ print (header_line )
398+ print ("-" * len (header_line ))
399+
400+ # Print rows
401+ for row in rows :
402+ print (" | " .join (str (cell ).ljust (widths [i ]) for i , cell in enumerate (row )))
325403
326404 def print_panel (self , content : str , title : Optional [str ] = None , style : str = "cyan" ) -> None :
327405 """Print a panel."""
@@ -338,7 +416,15 @@ def print_panel(self, content: str, title: Optional[str] = None, style: str = "c
338416 from rich .panel import Panel
339417
340418 if self .console :
341- self .console .print (Panel (content , title = title , border_style = style ))
419+ try :
420+ self .console .print (Panel (content , title = title , border_style = style ))
421+ except UnicodeEncodeError :
422+ # Fallback to plain text if panel rendering fails
423+ if title :
424+ print (f"\n === { title } ===" )
425+ print (content )
426+ if title :
427+ print ("=" * (len (title ) + 8 ))
342428
343429 def get_events (self ) -> List [Dict [str , Any ]]:
344430 """Get all collected events."""
0 commit comments