| """Pretty console formatter for magentic-ui CLI with improved visual formatting.""" |
|
|
| from __future__ import annotations |
|
|
| import json |
| import logging |
| import re |
| import sys |
| import textwrap |
| import warnings |
| from typing import Any, AsyncGenerator, Dict, Optional |
|
|
| from autogen_agentchat.base import Response, TaskResult |
| from autogen_agentchat.messages import BaseAgentEvent, BaseChatMessage |
|
|
|
|
| |
| |
| |
| def _ansi(seq: str) -> str: |
| """Return full ESC sequence if colours are on, else empty string.""" |
| return f"\033[{seq}m" if _COLOR_ENABLED else "" |
|
|
|
|
| |
| _COLOR_ENABLED = sys.stdout.isatty() |
|
|
| |
| RESET = _ansi("0") |
| BOLD = _ansi("1") |
| UNDERLINE = _ansi("4") |
|
|
| |
| RED = _ansi("31") |
| GREEN = _ansi("32") |
| YELLOW = _ansi("33") |
| BLUE = _ansi("34") |
| MAGENTA = _ansi("35") |
| CYAN = _ansi("36") |
|
|
| |
| CURSOR_UP = "\033[A" if _COLOR_ENABLED else "" |
| CLEAR_LINE = "\033[2K" if _COLOR_ENABLED else "" |
| CURSOR_TO_START = "\033[G" if _COLOR_ENABLED else "" |
|
|
| |
| _LINES_TO_CLEAR = 4 |
|
|
| |
| _AGENT_COLORS = { |
| "orchestrator": MAGENTA, |
| "coder_agent": RED, |
| "coder": RED, |
| "web_surfer": BLUE, |
| "file_surfer": YELLOW, |
| "user": GREEN, |
| "user_proxy": GREEN, |
| "no_action_agent": CYAN, |
| "reviewer": GREEN, |
| } |
|
|
| |
| _COLOR_POOL = [BLUE, GREEN, YELLOW, CYAN, MAGENTA] |
|
|
|
|
| def agent_color(name: str) -> str: |
| ln = name.lower() |
| for key, col in _AGENT_COLORS.items(): |
| if key in ln: |
| return col |
| return _COLOR_POOL[hash(name) % len(_COLOR_POOL)] |
|
|
|
|
| |
| |
| |
| def terminal_width(fallback: int = 100) -> int: |
| """Return terminal width minus a 10โcolumn safety margin.""" |
| try: |
| import shutil |
|
|
| return max(20, shutil.get_terminal_size().columns - 10) |
| except Exception: |
| return fallback |
|
|
|
|
| def try_parse_json(raw: str) -> tuple[bool, Any]: |
| """Lightweight JSON detector โ avoids `json.loads` when blatantly not JSON.""" |
| raw = raw.strip() |
| |
| if raw.startswith("[") and ", <autogen_core._image.Image object" in raw: |
| |
| return False, raw |
| if not (raw.startswith("{") and raw.endswith("}")) and not ( |
| raw.startswith("[") and raw.endswith("]") |
| ): |
| return False, None |
| try: |
| return True, json.loads(raw) |
| except (ValueError, TypeError) as e: |
| |
| if sys.__stdout__ is not None: |
| print(f"\n{RED}[JSON PARSE ERROR]{RESET} {type(e).__name__}: {str(e)}") |
| |
| preview = raw[:200] + "..." if len(raw) > 200 else raw |
| print(f"{RED}Problem content:{RESET}\n{preview}\n") |
| return False, None |
|
|
|
|
| |
| |
| |
| def header_box(agent: str) -> str: |
| """Return a symmetric ASCII box with the agent name centered.""" |
|
|
| |
| INNER = 24 |
|
|
| |
| if agent.upper() == "USER_PROXY": |
| agent = "USER" |
|
|
| colour = agent_color(agent) |
| text = agent.upper()[:INNER] |
| pad = INNER - len(text) |
| left, right = pad // 2, pad - pad // 2 |
|
|
| top = f"{BOLD}{colour}โ{'โ' * INNER}โ" |
| mid = f"โ{' ' * left}{text}{RESET}{colour}{' ' * right}โ" |
| bot = f"โ{'โ' * INNER}โ{RESET}" |
|
|
| return f"{top}\n{mid}\n{bot}" |
|
|
|
|
| def transition_line(prev: str, curr: str) -> str: |
| |
| if prev.upper() == "USER_PROXY": |
| prev = "USER" |
| if curr.upper() == "USER_PROXY": |
| curr = "USER" |
|
|
| return ( |
| f"{BOLD}{agent_color(prev)}{str(prev).upper()}{RESET} " |
| f"{BOLD}{YELLOW}โโโโโถ{RESET} " |
| f"{BOLD}{agent_color(curr)}{str(curr).upper()}{RESET}" |
| ) |
|
|
|
|
| |
| |
| |
| def pretty_print_json(raw: str, colour: str) -> bool: |
| ok, obj = try_parse_json(raw) |
| if not ok or obj in ([], {}): |
| return False |
|
|
| width = terminal_width() |
| left = f"{colour}โ{RESET} " |
| indent_json = json.dumps(obj, indent=2, ensure_ascii=False) |
| indent_json = re.sub(r'"([^"\\]+)":', rf'"{BOLD}\1{RESET}":', indent_json) |
|
|
| for line in indent_json.splitlines(): |
| if len(line) <= width - len(left): |
| print(f"{left}{line}") |
| else: |
| lead = len(line) - len(line.lstrip()) |
| body = line[lead:] |
| for i, chunk in enumerate( |
| textwrap.wrap( |
| body, width=width - len(left) - lead, break_long_words=False |
| ) |
| ): |
| prefix = " " * lead if i else "" |
| print(f"{left}{prefix}{chunk}") |
| print() |
| return True |
|
|
|
|
| |
| |
| |
| def format_plan(obj: dict[str, Any], colour: str) -> None: |
| width = terminal_width() |
| left = f"{colour}โ{RESET} " |
| body_w = width - len(left) |
|
|
| def _wrap(text: str, indent: int = 3): |
| for ln in textwrap.wrap(text, body_w - indent): |
| print(f"{left}{' ' * indent}{ln}") |
|
|
| |
| if ( |
| "is_current_step_complete" in obj |
| and "need_to_replan" in obj |
| and "instruction_or_question" in obj |
| and "progress_summary" in obj |
| ): |
| print(f"{left}{BOLD}โโโโโโโโโโ STATUS UPDATE โโโโโโโโโโ{RESET}") |
|
|
| |
| step_complete = obj["is_current_step_complete"] |
| if isinstance(step_complete, dict) and "answer" in step_complete: |
| status = ( |
| f"{GREEN}Yes{RESET}" |
| if step_complete["answer"] |
| else f"{YELLOW}No{RESET}" |
| ) |
| print(f"{left}{BOLD}Current Step Complete:{RESET} {status}") |
|
|
| |
| if "reason" in step_complete: |
| print(f"{left}{' ' * 3}{BOLD}Reason:{RESET}") |
| _wrap(str(step_complete.get("reason", "")), 5) |
| else: |
| |
| status = f"{GREEN}Yes{RESET}" if step_complete else f"{YELLOW}No{RESET}" |
| print(f"{left}{BOLD}Current Step Complete:{RESET} {status}") |
|
|
| print(left) |
|
|
| |
| replan = obj["need_to_replan"] |
| if isinstance(replan, dict) and "answer" in replan: |
| status = f"{YELLOW}Yes{RESET}" if replan["answer"] else f"{GREEN}No{RESET}" |
| print(f"{left}{BOLD}Need to Replan:{RESET} {status}") |
|
|
| |
| if "reason" in replan: |
| print(f"{left}{' ' * 3}{BOLD}Reason:{RESET}") |
| _wrap(str(replan.get("reason", "")), 5) |
| else: |
| |
| status = f"{YELLOW}Yes{RESET}" if replan else f"{GREEN}No{RESET}" |
| print(f"{left}{BOLD}Need to Replan:{RESET} {status}") |
|
|
| print(left) |
|
|
| |
| instruction = obj["instruction_or_question"] |
| if isinstance(instruction, dict): |
| if "answer" in instruction: |
| print(f"{left}{BOLD}Next Action:{RESET}") |
| _wrap(str(instruction.get("answer", "")), 3) |
|
|
| |
| if "agent_name" in instruction: |
| agent = str(instruction.get("agent_name", "")).upper() |
| print(f"{left}{' ' * 3}{BOLD}Agent:{RESET} {agent}") |
| else: |
| |
| print(f"{left}{BOLD}Next Action:{RESET}") |
| _wrap(str(instruction), 3) |
|
|
| print(left) |
|
|
| |
| print(f"{left}{BOLD}Progress Summary:{RESET}") |
| _wrap(str(obj["progress_summary"]), 3) |
| print(left) |
|
|
| print(f"{left}{BOLD}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ{RESET}") |
|
|
| elif "response" in obj and "task" in obj and "steps" in obj: |
| |
| _wrap(str(obj["response"]), 0) |
|
|
| |
| print(left) |
| print(f"{left}{BOLD}Task:{RESET}") |
| _wrap(str(obj["task"])) |
|
|
| |
| steps: list[dict[str, Any]] = obj.get("steps", []) or [] |
| if steps: |
| print(f"{left}\n{left}{BOLD}Steps:{RESET}") |
| for i, step in enumerate(steps, 1): |
| |
| if i > 1: |
| print(f"{left}") |
|
|
| |
| if ( |
| isinstance(step, dict) |
| and step.get("step_type") == "SentinelPlanStep" |
| ): |
| step_type = "SentinelPlanStep" |
| else: |
| step_type = "PlanStep" |
|
|
| |
| type_indicator = "" |
| if step_type == "PlanStep": |
| type_indicator = f"{BOLD}{GREEN}[R]{RESET} " |
| elif step_type == "SentinelPlanStep": |
| type_indicator = f"{BOLD}{YELLOW}[S]{RESET} " |
|
|
| |
| step_title = ( |
| step.get("title", step) if isinstance(step, dict) else str(step) |
| ) |
| print(f"{left}{BOLD}{i}. {type_indicator}{step_title}{RESET}") |
|
|
| if isinstance(step, dict): |
| |
| if step.get("details"): |
| print(f"{left}{' ' * 3}{BOLD}Details:{RESET}") |
| _wrap(str(step["details"]), 5) |
|
|
| |
| if step.get("instruction"): |
| print(f"{left}{' ' * 3}{BOLD}Instruction:{RESET}") |
| _wrap(str(step["instruction"]), 7) |
|
|
| |
| if step.get("progress_summary"): |
| print(f"{left}{' ' * 3}{BOLD}Progress Summary:{RESET}") |
| _wrap(str(step["progress_summary"]), 7) |
|
|
| |
| if step.get("agent_name"): |
| print( |
| f"{left}{' ' * 3}{BOLD}Agent:{RESET} {BOLD}{agent_color(step['agent_name'])}{step['agent_name'].upper()}{RESET}" |
| ) |
|
|
| |
| if step_type: |
| type_name = ( |
| "Regular Step" |
| if step_type == "PlanStep" |
| else "Sentinel Step" |
| ) |
| print(f"{left}{' ' * 3}{BOLD}Type:{RESET} {type_name}") |
| if step_type == "SentinelPlanStep": |
| if "condition" in step: |
| print( |
| f"{left}{' ' * 5}{BOLD}Condition:{RESET} {step['condition']}" |
| ) |
| if "sleep_duration" in step: |
| print( |
| f"{left}{' ' * 5}{BOLD}Sleep Duration:{RESET} {step['sleep_duration']}s" |
| ) |
|
|
| |
| print() |
| print(f"{BOLD}{YELLOW}Type 'accept' to proceed or describe changes:{RESET}") |
|
|
| |
| |
| elif {"title", "index", "agent_name"}.issubset(obj): |
| idx = obj["index"] + 1 if isinstance(obj.get("index"), int) else obj["index"] |
|
|
| if "title" in obj: |
| print(f"{left}{BOLD}Step #{idx}:{RESET}") |
| _wrap(str(obj["title"])) |
|
|
| if "details" in obj: |
| print(left) |
| print(f"{left}{BOLD}Details:{RESET}") |
| _wrap(str(obj["details"])) |
|
|
| if "agent_name" in obj: |
| print(left) |
| print( |
| f"{left}{BOLD}Agent:{RESET} {BOLD}{agent_color(obj['agent_name'])}{str(obj['agent_name']).upper()}{RESET}" |
| ) |
|
|
| if "instruction" in obj: |
| print(left) |
| print(f"{left}{BOLD}Instruction:{RESET}") |
| _wrap(str(obj["instruction"])) |
|
|
| if "step_type" in obj: |
| step_type = ( |
| "Sentinel Step" |
| if obj["step_type"] == "SentinelPlanStep" |
| else "Regular Step" |
| ) |
| print(left) |
| print(f"{left}{BOLD}Type:{RESET} {step_type}") |
|
|
| if "condition" in obj: |
| print(left) |
| print(f"{left}{BOLD}Condition:{RESET} {obj['condition']}") |
|
|
| if "sleep_duration" in obj: |
| print(left) |
| print(f"{left}{BOLD}Sleep Duration:{RESET} {obj['sleep_duration']}s") |
|
|
| print() |
|
|
| |
| agent_name = obj.get("agent_name", "").lower() |
| is_user_proxy = "user" in agent_name and "proxy" in agent_name |
|
|
| |
| if is_user_proxy: |
| print(f"{BOLD}{YELLOW}Type 'accept' to proceed or describe changes:{RESET}") |
|
|
| |
| elif "task" in obj or "plan_summary" in obj: |
| print() |
| print(f"{BOLD}{YELLOW}You may need to clarify your task:{RESET}") |
|
|
|
|
| def pretty_print_plan(raw: str, colour: str) -> bool: |
| ok, obj = try_parse_json(raw) |
| if not ok: |
| return False |
| if any( |
| k in obj |
| for k in ( |
| "task", |
| "plan_summary", |
| "steps", |
| "title", |
| "is_current_step_complete", |
| "agent_name", |
| ) |
| ): |
| format_plan(obj, colour) |
| return True |
|
|
| return False |
|
|
|
|
| |
| def try_format_step(raw: str, colour: str) -> bool: |
| ok, obj = try_parse_json(raw) |
| if not ok or not {"step", "content"}.issubset(obj): |
| return False |
| width = terminal_width() |
| title = obj.get("title", f"Step {obj['step']}") |
| print( |
| f"{BOLD}{colour}โ{'โ' * (width - 4)}โ\n" |
| f"โ {title:<{width - 6}}โ\n" |
| f"โ{'โ' * (width - 4)}โ{RESET}\n" |
| ) |
| print(f"{BOLD}{colour}โ{RESET} {obj['content']}\n") |
| return True |
|
|
|
|
| |
| |
| |
| def format_web_surfer_actions(raw: str, colour: str) -> bool: |
| """Format and display Web Surfer actions and observations in a structured way.""" |
| |
| if ( |
| not isinstance(raw, str) |
| or "The actions the websurfer performed are the following" not in raw |
| ): |
| return False |
|
|
| width = terminal_width() |
| left = f"{colour}โ{RESET} " |
| body_w = width - len(left) |
|
|
| |
| def _wrap_text(text: str, indent: int = 0): |
| for line in text.splitlines(): |
| if not line.strip(): |
| print(f"{left}") |
| continue |
|
|
| if len(line) + indent <= body_w: |
| print(f"{left}{' ' * indent}{line}") |
| else: |
| for chunk in textwrap.wrap(line, width=body_w - indent): |
| print(f"{left}{' ' * indent}{chunk}") |
|
|
| print(f"{BOLD}{colour}โ{'โ' * (width - 4)}โ") |
| print(f"{colour}โ {BOLD}WEB SURFER ACTIONS{RESET}{colour}{' ' * (width - 20)}โ") |
| print(f"{colour}โ{'โ' * (width - 4)}โ{RESET}\n") |
|
|
| |
| content = raw.strip() |
| parts = content.split("Action: ") |
|
|
| |
| if parts[0]: |
| intro = parts[0].strip() |
| _wrap_text(intro) |
| print(f"{left}") |
|
|
| |
| for i, part in enumerate(parts[1:], 1): |
| if not part.strip(): |
| continue |
|
|
| |
| action_obs = part.split("Observation:", 1) |
|
|
| if len(action_obs) != 2: |
| |
| print(f"{left}{BOLD}{YELLOW}Malformed action/observation:{RESET}") |
| _wrap_text(part, 2) |
| continue |
|
|
| action, observation = action_obs |
|
|
| |
| action = action.strip() |
|
|
| |
| print(f"{left}{BOLD}Action #{i}:{RESET}") |
|
|
| |
| if "(" in action and ")" in action: |
| action_name, action_params = action.split("(", 1) |
| action_params = action_params.rsplit(")", 1)[0] |
|
|
| print(f"{left}{' ' * 2}{BOLD}{BLUE}{action_name}{RESET}(") |
|
|
| |
| try: |
| |
| action_params = action_params.strip() |
| if action_params.startswith("{") and action_params.endswith("}"): |
| params_obj = json.loads(action_params) |
| |
| params_json = json.dumps(params_obj, indent=2) |
| for line in params_json.splitlines(): |
| print(f"{left}{' ' * 4}{line}") |
| print(f"{left}{' ' * 2})") |
| else: |
| |
| _wrap_text(action_params, 4) |
| print(f"{left}{' ' * 2})") |
| except json.JSONDecodeError: |
| |
| _wrap_text(action_params, 4) |
| print(f"{left}{' ' * 2})") |
| else: |
| |
| _wrap_text(action, 2) |
|
|
| |
| observation = observation.strip() |
| print(f"{left}{' ' * 2}{BOLD}Observation:{RESET}") |
| _wrap_text(observation, 4) |
|
|
| |
| print(f"{left}") |
|
|
| |
| if "We are at the following webpage" in content: |
| webpage_info = content.split("We are at the following webpage", 1)[1].strip() |
| print(f"{left}{BOLD}Current Webpage:{RESET}") |
| _wrap_text(webpage_info, 2) |
|
|
| print() |
| return True |
|
|
|
|
| def clear_previous_lines(num_lines: int) -> None: |
| """Clear the specified number of previous lines in the terminal. |
| |
| This function moves the cursor up and clears lines, effectively |
| erasing previous output from the terminal. |
| """ |
| if num_lines <= 0: |
| return |
|
|
| for _ in range(num_lines): |
| print(f"{CURSOR_UP}{CLEAR_LINE}", end="", flush=True) |
|
|
|
|
| def display_initial_user_task(task: str) -> None: |
| """Display the initial user task in the same format as other messages. |
| |
| This function is meant to be called before starting the agent stream |
| to display the user's initial input in a consistent format. |
| """ |
| colour = agent_color("user") |
| print(header_box("USER")) |
| left = f"{colour}โ{RESET} " |
|
|
| width = terminal_width() |
| body_w = width - len(left) |
|
|
| |
| lines = task.splitlines() |
| for line in lines: |
| if not line.strip(): |
| print(f"{left}") |
| continue |
|
|
| if len(line) <= body_w: |
| print(f"{left}{line}") |
| else: |
| |
| for chunk in textwrap.wrap(line, body_w): |
| print(f"{left}{chunk}") |
|
|
| print() |
|
|
|
|
| |
| def display_orchestrator_welcome() -> None: |
| agent_name = "orchestrator" |
| colour = agent_color(agent_name) |
| print(header_box(agent_name)) |
| left = f"{colour}โ{RESET} " |
|
|
| width = terminal_width() |
| body_w = width - len(left) |
|
|
| welcome_message = "Hi, welcome to Magentic-UI CLI! How can I help you?" |
|
|
| |
| for chunk in textwrap.wrap(welcome_message, body_w): |
| print(f"{left}{chunk}") |
|
|
| |
| print(f"{BOLD}{GREEN}> {RESET}", end="") |
|
|
|
|
| |
| |
| |
| async def _PrettyConsole( |
| stream: AsyncGenerator[Any, None], |
| *, |
| debug: bool = False, |
| no_inline_images: bool = False, |
| output_stats: bool = False, |
| ) -> Any: |
| current_agent: Optional[str] = None |
| previous_agent: Optional[str] = None |
| last_processed: Any = None |
|
|
| |
| if not debug: |
| warnings.filterwarnings("ignore") |
| logging.disable(logging.CRITICAL) |
|
|
| class _Gate: |
| """Allow writes only when inside `process_message`.""" |
|
|
| def __init__(self, dbg: bool, flag: Dict[str, bool]): |
| self.dbg, self.flag = dbg, flag |
|
|
| def write(self, txt: str): |
| if self.dbg or self.flag["open"]: |
| if sys.__stdout__ is not None: |
| sys.__stdout__.write(txt) |
|
|
| def flush(self): |
| if sys.__stdout__ is not None: |
| sys.__stdout__.flush() |
|
|
| gate = {"open": False} |
| sys.stdout = _Gate(debug, gate) |
| sys.stderr = _Gate(debug, gate) |
| sys.__stdout__ = sys.__stdout__ |
|
|
| |
| input_prompt_shown = False |
|
|
| async def process(msg: BaseChatMessage | BaseAgentEvent | TaskResult | Response): |
| nonlocal current_agent, previous_agent, last_processed, input_prompt_shown |
| last_processed = msg |
| gate["open"] = True |
|
|
| try: |
| |
| if isinstance(msg, BaseChatMessage): |
| meta = getattr(msg, "metadata", {}) |
| if meta.get("internal") == "yes" and not debug: |
| return |
| src = msg.source |
|
|
| |
| if src.lower() in ("user", "user_proxy") and input_prompt_shown: |
| |
| content = str(getattr(msg, "content", "")) |
|
|
| |
| width = terminal_width() |
|
|
| |
| lines_to_clear = _LINES_TO_CLEAR |
|
|
| |
| if content: |
| |
| newline_count = content.count("\n") |
|
|
| |
| |
| chars_per_line = max(20, width - 2) |
| wrapped_lines = ( |
| len(content) + chars_per_line - 1 |
| ) // chars_per_line |
|
|
| |
| content_lines = max(1, newline_count + 1, wrapped_lines) |
|
|
| lines_to_clear += content_lines |
|
|
| |
| clear_previous_lines(lines_to_clear) |
| input_prompt_shown = False |
|
|
| if src != current_agent: |
| previous_agent, current_agent = current_agent, src |
| if previous_agent: |
| print("\n" + transition_line(previous_agent, current_agent)) |
| print(header_box(src)) |
|
|
| colour = agent_color(src) |
| content = str(getattr(msg, "content", "")) |
|
|
| |
| if src.lower() == "web_surfer" and format_web_surfer_actions( |
| content, colour |
| ): |
| pass |
| if pretty_print_plan(content, colour): |
| pass |
| elif pretty_print_json(content, colour): |
| pass |
| elif try_format_step(content, colour): |
| pass |
| else: |
| width = terminal_width() |
| left = f"{colour}โ{RESET} " |
| body_w = width - len(left) |
|
|
| |
| lines = content.splitlines() |
| i = 0 |
| while i < len(lines): |
| |
| if not lines[i].strip(): |
| if ( |
| i > 0 and i < len(lines) - 1 |
| ): |
| print(f"{left}") |
| i += 1 |
| |
| while i < len(lines) and not lines[i].strip(): |
| i += 1 |
| continue |
|
|
| |
| line = lines[i] |
| if len(line) <= body_w: |
| print(f"{left}{line}") |
| else: |
| |
| wrapped_chunks = textwrap.wrap(line, body_w) |
| for chunk in wrapped_chunks: |
| print(f"{left}{chunk}") |
| i += 1 |
|
|
| |
| elif isinstance(msg, BaseAgentEvent): |
| if debug: |
| print( |
| f"{BOLD}{YELLOW}[EVENT]{RESET} " |
| f"{msg.__class__.__name__} from {getattr(msg, 'source', 'unknown')}" |
| ) |
|
|
| |
| if msg.__class__.__name__ == "UserInputRequestedEvent": |
| box = ( |
| "\n" |
| f"{BOLD}{GREEN}โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ{RESET}\n" |
| f"{BOLD}{GREEN}โ Input requested - please type below โ{RESET}\n" |
| f"{BOLD}{GREEN}โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ{RESET}\n" |
| f"{BOLD}{GREEN}> {RESET}" |
| ) |
| print(box, end="", flush=True) |
|
|
| |
| input_prompt_shown = True |
|
|
| |
| elif isinstance(msg, (TaskResult, Response)): |
| print( |
| f"\n{BOLD}{MAGENTA}โโโโโโโโโโโโโโโโโโโโโโโโโโโ\n" |
| f"โ SESSION COMPLETE โ\n" |
| f"โโโโโโโโโโโโโโโโโโโโโโโโโโโ{RESET}\n" |
| ) |
| |
| else: |
| print(f"{BOLD}{RED}[WARN]{RESET} Unhandled message type: {type(msg)}") |
|
|
| finally: |
| gate["open"] = False |
|
|
| |
| if debug: |
| print(f"{BOLD}{YELLOW}[DEBUG] Starting console stream processingโฆ{RESET}") |
|
|
| async for m in stream: |
| await process(m) |
|
|
| return last_processed |
|
|
|
|
| |
| PrettyConsole = _PrettyConsole |
|
|