| import asyncio |
| import json |
| import logging |
| import os |
| import signal |
| import sys |
| import types |
| from datetime import datetime |
| from pathlib import Path |
| from typing import Any, List, Literal, Optional, cast |
| from typing_extensions import Annotated |
|
|
| import typer |
| import yaml |
| from autogen_core import EVENT_LOGGER_NAME, CancellationToken |
| from .cli import Console, PrettyConsole |
| from .cli.pretty_console import ( |
| display_initial_user_task, |
| display_orchestrator_welcome, |
| clear_previous_lines, |
| terminal_width, |
| ) |
| from .task_team import get_task_team |
| from loguru import logger |
|
|
| from .agents.mcp._config import McpAgentConfig |
| from .magentic_ui_config import MagenticUIConfig, ModelClientConfigs, SentinelPlanConfig |
| from .types import RunPaths |
| from .utils import LLMCallFilter |
| from ._docker import ( |
| check_docker_running, |
| check_browser_image, |
| check_python_image, |
| pull_browser_image, |
| pull_python_image, |
| ) |
|
|
| BOLD = "\033[1m" |
| RESET = "\033[0m" |
| MAGENTA = "\033[35m" |
| GREEN = "\033[32m" |
|
|
| |
| app = typer.Typer(help="Magentic-UI CLI") |
|
|
|
|
| |
| |
| def log_debug(msg: str, debug: bool = False) -> None: |
| """Log debug information if debug mode is enabled.""" |
| if debug: |
| |
| print(f"DEBUG: {msg}") |
|
|
|
|
| logging.basicConfig(level=logging.WARNING, handlers=[]) |
| logger_llm = logging.getLogger(EVENT_LOGGER_NAME) |
| logger_llm.setLevel(logging.INFO) |
|
|
| ApprovalPolicy = Literal["always", "never", "auto-conservative", "auto-permissive"] |
|
|
|
|
| async def cancellable_input( |
| prompt: str, |
| cancellation_token: Optional[CancellationToken], |
| input_type: Optional[str] = "text_input", |
| ) -> str: |
| |
| assert input_type in ["text_input", "approval"] |
| log_debug( |
| f"Starting cancellable_input with type: {input_type}", |
| getattr(cancellation_token, "_debug", False), |
| ) |
|
|
| |
| if prompt.strip() == "Enter your response:": |
| prompt = "" |
|
|
| |
| |
| if input_type == "text_input" and not prompt.startswith("\n"): |
| prompt = f"\n{prompt}" |
|
|
| task: asyncio.Task[str] = asyncio.create_task(asyncio.to_thread(input, prompt)) |
| if cancellation_token is not None: |
| log_debug( |
| "Linking cancellation token to input task", |
| getattr(cancellation_token, "_debug", False), |
| ) |
| cancellation_token.link_future(task) |
| result = await task |
| log_debug( |
| f"Cancellable input completed with result length: {len(result)}", |
| getattr(cancellation_token, "_debug", False), |
| ) |
| return result |
|
|
|
|
| def setup_llm_logging(log_dir: str) -> None: |
| """Set up logging to capture only LLM calls to a directory.""" |
| log_file = os.path.join( |
| log_dir, f"llm_calls_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log" |
| ) |
| file_handler = logging.FileHandler(log_file) |
| file_handler.setLevel(logging.INFO) |
| formatter = logging.Formatter( |
| "%(message)s" |
| ) |
| file_handler.setFormatter(formatter) |
| file_handler.addFilter(LLMCallFilter()) |
| for handler in logger_llm.handlers[:]: |
| logger_llm.removeHandler(handler) |
| logger_llm.addHandler(file_handler) |
| log_debug(f"LLM logging configured to: {log_file}", True) |
|
|
|
|
| async def get_team( |
| cooperative_planning: bool, |
| autonomous_execution: bool, |
| work_dir: str, |
| reset: bool = False, |
| task: str | None = None, |
| final_answer_prompt: str | None = None, |
| debug: bool = False, |
| state_file: str = ".magentic_ui_state.json", |
| internal_workspace_root: str | None = None, |
| external_workspace_root: str | None = None, |
| playwright_port: int = -1, |
| novnc_port: int = -1, |
| inside_docker: bool = False, |
| model_context_token_limit: int = 128000, |
| client_config: str | None = None, |
| action_policy: ApprovalPolicy = "never", |
| user_proxy_type: str | None = None, |
| task_metadata: str | None = None, |
| hints: str | None = None, |
| answer: str | None = None, |
| mcp_agents: List[McpAgentConfig] | None = None, |
| use_pretty_ui: bool = True, |
| run_without_docker: bool = False, |
| browser_headless: bool = False, |
| browser_local: bool = False, |
| sentinel_tasks: bool = False, |
| dynamic_sentinel_sleep: bool = False, |
| ) -> None: |
| log_debug("=== Starting get_team function ===", debug) |
| log_debug( |
| f"Args: cooperative_planning={cooperative_planning}, autonomous_execution={autonomous_execution}, reset={reset}", |
| debug, |
| ) |
| log_debug( |
| f"Args: inside_docker={inside_docker}, action_policy={action_policy}, user_proxy_type={user_proxy_type}", |
| debug, |
| ) |
|
|
| if reset: |
| |
| if os.path.exists(state_file): |
| log_debug(f"Deleting existing state file at: {state_file}", debug) |
| os.remove(state_file) |
|
|
| if inside_docker: |
| log_debug("Running inside Docker container", debug) |
| |
| if internal_workspace_root is None: |
| internal_workspace_root = os.environ.get("INTERNAL_WORKSPACE_ROOT") |
| log_debug( |
| f"Using INTERNAL_WORKSPACE_ROOT from env: {internal_workspace_root}", |
| debug, |
| ) |
| if external_workspace_root is None: |
| external_workspace_root = os.environ.get("EXTERNAL_WORKSPACE_ROOT") |
| log_debug( |
| f"Using EXTERNAL_WORKSPACE_ROOT from env: {external_workspace_root}", |
| debug, |
| ) |
|
|
| if not internal_workspace_root or not external_workspace_root: |
| error_msg = "When running inside docker, both internal and external workspace root paths must be provided either via arguments or environment variables" |
| log_debug(f"ERROR: {error_msg}", debug) |
| raise ValueError(error_msg) |
|
|
| |
| |
| |
| cli_files_path = os.path.join(internal_workspace_root, "cli_files") |
| log_debug(f"Creating CLI files directory at: {cli_files_path}", debug) |
| os.makedirs(cli_files_path, exist_ok=True) |
|
|
| paths = RunPaths( |
| internal_root_dir=Path(internal_workspace_root), |
| external_root_dir=Path(external_workspace_root), |
| run_suffix="run", |
| internal_run_dir=Path(cli_files_path), |
| external_run_dir=Path(os.path.join(external_workspace_root, "cli_files")), |
| ) |
| log_debug( |
| f"RunPaths created with internal_root_dir: {paths.internal_root_dir}", debug |
| ) |
| else: |
| log_debug("Running outside Docker container", debug) |
| if not work_dir: |
| error_msg = "When running outside docker, work_dir path must be provided" |
| log_debug(f"ERROR: {error_msg}", debug) |
| raise ValueError(error_msg) |
|
|
| work_dir_path = Path(work_dir) |
| log_debug(f"Using work_dir: {work_dir_path}", debug) |
|
|
| cli_files_path = os.path.join(work_dir, "cli_files") |
| log_debug(f"Creating CLI files directory at: {cli_files_path}", debug) |
| os.makedirs(cli_files_path, exist_ok=True) |
| work_dir_files = work_dir_path / "cli_files" |
|
|
| paths = RunPaths( |
| internal_root_dir=work_dir_path, |
| external_root_dir=work_dir_path, |
| run_suffix="run", |
| internal_run_dir=work_dir_files, |
| external_run_dir=work_dir_files, |
| ) |
| log_debug("RunPaths created with internal/external run dirs", debug) |
|
|
| client_config_dict: dict[str, Any] = {} |
| if client_config: |
| log_debug(f"Loading client configuration from: {client_config}", debug) |
| with open(client_config, "r") as f: |
| client_config_dict = yaml.safe_load(f) |
| log_debug( |
| f"Client config loaded with {len(client_config_dict)} keys", debug |
| ) |
|
|
| |
| model_client_configs = ModelClientConfigs( |
| orchestrator=client_config_dict.get("orchestrator_client", None), |
| web_surfer=client_config_dict.get("web_surfer_client", None), |
| coder=client_config_dict.get("coder_client", None), |
| file_surfer=client_config_dict.get("file_surfer_client", None), |
| action_guard=client_config_dict.get("action_guard_client", None), |
| ) |
|
|
| mcp_agents = mcp_agents or [] |
| log_debug("Model client configs created for agents", debug) |
|
|
| magentic_ui_config = MagenticUIConfig( |
| model_client_configs=model_client_configs, |
| mcp_agent_configs=mcp_agents, |
| cooperative_planning=cooperative_planning, |
| autonomous_execution=autonomous_execution, |
| approval_policy=action_policy, |
| allow_for_replans=True, |
| do_bing_search=False, |
| model_context_token_limit=model_context_token_limit, |
| allow_follow_up_input=False, |
| final_answer_prompt=final_answer_prompt, |
| playwright_port=playwright_port, |
| novnc_port=novnc_port, |
| user_proxy_type=user_proxy_type, |
| task=task_metadata, |
| hints=hints, |
| answer=answer, |
| inside_docker=inside_docker, |
| sentinel_plan=SentinelPlanConfig( |
| enable_sentinel_steps=sentinel_tasks, |
| dynamic_sentinel_sleep=dynamic_sentinel_sleep, |
| ), |
| run_without_docker=run_without_docker, |
| browser_headless=browser_headless, |
| browser_local=browser_local, |
| ) |
| log_debug( |
| f"MagenticUIConfig created with planning={cooperative_planning}, execution={autonomous_execution}", |
| debug, |
| ) |
|
|
| log_debug("Starting team creation", debug) |
|
|
| |
| log_debug("Calling get_task_team to create team object", debug) |
| team = await get_task_team( |
| magentic_ui_config=magentic_ui_config, |
| input_func=cancellable_input, |
| paths=paths, |
| ) |
| log_debug(f"Team created with type: {type(team).__name__}", debug) |
|
|
| |
| log_debug("Team creation completed successfully", debug) |
|
|
| if use_pretty_ui: |
| display_magentic_ui_logo() |
| log_debug("Logo displayed", debug) |
|
|
| try: |
| if state_file and os.path.exists(state_file) and not reset: |
| state = None |
| log_debug(f"Loading state from: {state_file}", debug) |
|
|
| with open(state_file, "r") as f: |
| state = json.load(f) |
| log_debug("State loaded successfully", debug) |
|
|
| log_debug("Calling team.load_state with loaded state", debug) |
| await team.load_state(state) |
| log_debug("State loading completed", debug) |
|
|
| if not task: |
| log_debug("No task provided, prompting user for input", debug) |
|
|
| |
| def flushed_input(prompt: str) -> str: |
| |
| print(prompt, end="", flush=True) |
| user_input = input() |
| log_debug(f"User input received, length: {len(user_input)}", debug) |
|
|
| |
| input_lines = 1 |
| if user_input: |
| term_width = terminal_width() |
| input_lines += len(user_input) // term_width |
|
|
| return user_input |
|
|
| if use_pretty_ui: |
| |
| display_orchestrator_welcome() |
|
|
| |
| log_debug("Creating input task in event loop with pretty UI", debug) |
| task = await asyncio.get_event_loop().run_in_executor( |
| None, |
| flushed_input, |
| "", |
| ) |
|
|
| |
| term_width = terminal_width() |
|
|
| |
| lines_to_clear = 1 |
|
|
| |
| if task: |
| |
| first_line_chars = term_width - 2 |
| if len(task) > first_line_chars: |
| |
| remaining_chars = len(task) - first_line_chars |
| |
| additional_lines = ( |
| remaining_chars + term_width - 1 |
| ) // term_width |
| lines_to_clear += additional_lines |
|
|
| |
| log_debug( |
| f"Clearing {lines_to_clear} lines for user input (term width: {term_width})", |
| debug, |
| ) |
|
|
| |
| clear_previous_lines(lines_to_clear) |
|
|
| else: |
| |
| log_debug("Creating input task in event loop", debug) |
| task = await asyncio.get_event_loop().run_in_executor( |
| None, |
| flushed_input, |
| f"{MAGENTA}{BOLD}Enter your task (or press Ctrl+C to cancel): {RESET}", |
| ) |
|
|
| log_debug("User input task completed", debug) |
|
|
| log_debug( |
| f"Task to execute: {task[:50] if task else ''}{'...' if task and len(task) > 50 else ''}", |
| debug, |
| ) |
|
|
| |
| print() |
|
|
| |
| if use_pretty_ui and task: |
| |
| display_initial_user_task(task) |
| elif task: |
| |
| display_initial_user_task(task) |
|
|
| log_debug("Creating team run stream with task", debug) |
| stream = team.run_stream(task=task) |
| log_debug( |
| f"Stream created, passing to {'PrettyConsole' if use_pretty_ui else 'Console'} with debug={debug}", |
| debug, |
| ) |
|
|
| |
| if use_pretty_ui: |
| await PrettyConsole(stream, debug=debug) |
| |
| else: |
| await Console(stream) |
|
|
| log_debug("Console processing completed", debug) |
|
|
| log_debug("Saving team state", debug) |
| state = await team.save_state() |
| log_debug("State saved successfully", debug) |
|
|
| log_debug(f"Writing state to file: {state_file}", debug) |
| with open(state_file, "w") as f: |
| json.dump(state, f, indent=2) |
| log_debug("State file write completed", debug) |
|
|
| finally: |
| logger.info("Closing team...") |
| log_debug("Closing team in finally block", debug) |
| await team.close() |
| log_debug("Team closed successfully", debug) |
|
|
|
|
| def display_magentic_ui_logo(): |
| """Display the MAGENTIC UI entry text.""" |
|
|
| magentic_logo = f"""{MAGENTA}{BOLD}╔═══════════════════════════════════════════════════════════════════╗ |
| ║ __ __ _ ____ _____ _ _ _____ ___ ____ _ _ ___ ║ |
| ║ | \\/ | / \\ / ___| ____| \\ | |_ _|_ _/ ___| | | | |_ _| ║ |
| ║ | |\\/| | / _ \\| | _| _| | \\| | | | | | | | | | || | ║ |
| ║ | | | |/ ___ \\ |_| | |___| |\\ | | | | | |___ | |_| || | ║ |
| ║ |_| |_/_/ \\_\\____|_____|_| \\_| |_| |___\\____| \\___/|___| ║ |
| ║ ║ |
| ╚═══════════════════════════════════════════════════════════════════╝\n{RESET}""" |
|
|
| print(magentic_logo) |
|
|
|
|
| @app.callback(invoke_without_command=True) |
| def cli_main( |
| |
| work_dir: str = typer.Option( |
| ..., |
| "--work-dir", |
| help="Working directory path: where the team will store its state and files (required)", |
| ), |
| |
| config: Optional[str] = typer.Option( |
| None, |
| "--config", |
| help="Path to the configuration file (default: 'config.yaml')", |
| ), |
| run_without_docker: bool = typer.Option( |
| False, |
| "--run-without-docker", |
| help="Run without docker. This will remove coder and filesurfer agents and disable live browser view.", |
| ), |
| browser_headless: Annotated[ |
| bool, |
| typer.Option( |
| "--headless", |
| help="Run browser in headless mode (default: False, browser runs with GUI)", |
| ), |
| ] = False, |
| browser_local: Annotated[ |
| bool, |
| typer.Option( |
| "--local-browser", |
| help="Run browser locally on your machine instead of in Docker (default: False, uses Docker browser with noVNC)", |
| ), |
| ] = False, |
| debug: bool = typer.Option( |
| False, |
| "--debug", |
| help="Enable debug mode to show internal messages and detailed agent interactions (default: disabled)", |
| ), |
| use_state: Annotated[ |
| bool, |
| typer.Option( |
| "--use-state", |
| help="Use and save the team state before and after running the task (default: always start fresh and do not use state)", |
| ), |
| ] = False, |
| |
| cooperative_planning: Annotated[ |
| bool, |
| typer.Option( |
| "--disable-planning/--enable-planning", |
| help="Disable co-planning mode (default: enabled), user will not be involved in the planning process", |
| ), |
| ] = True, |
| autonomous_execution: bool = typer.Option( |
| False, |
| "--autonomous-execution", |
| help="Enable autonomous execution mode (default: disabled), user will not be involved in the execution", |
| ), |
| autonomous: bool = typer.Option( |
| False, |
| "--autonomous", |
| help="Enable autonomous mode (default: disabled), no co-planning and no human involvment in execution", |
| ), |
| |
| task: str = typer.Option( |
| "", |
| "--task", |
| help="Specifies the initial task. If a plain string, use this input verbatim. If the string matches a filename, read the initial task from a file. Use '-' to read from stdin. (default: prompt's the user for the task)", |
| ), |
| final_answer_prompt: str = typer.Option( |
| "", |
| "--final-answer-prompt", |
| help="Overrides the final answer prompt used to summarize the conversation. If a plain string, use this input verbatim. If the string matches a filename, read the prompt from a file. (default: use orchestrator's built-in prompt)", |
| ), |
| |
| internal_workspace_root: Optional[str] = typer.Option( |
| None, |
| "--internal-root", |
| help="Deprecated: Internal workspace root directory path (default: use INTERNAL_WORKSPACE_ROOT environment variable)", |
| ), |
| external_workspace_root: Optional[str] = typer.Option( |
| None, |
| "--external-root", |
| help="Deprecated: External workspace root directory path (default: use EXTERNAL_WORKSPACE_ROOT environment variable)", |
| ), |
| playwright_port: int = typer.Option( |
| -1, |
| "--playwright-port", |
| help="Port to run the Playwright browser on (default: -1 means use default port)", |
| ), |
| novnc_port: int = typer.Option( |
| -1, |
| "--novnc-port", |
| help="Port to run the noVNC server on (default: -1 means use default port)", |
| ), |
| inside_docker: bool = typer.Option( |
| False, |
| "--inside-docker", |
| help="Deprecated:Indicates if running inside docker container (default: False)", |
| ), |
| |
| user_proxy_type: Optional[str] = typer.Option( |
| None, |
| "--user-proxy-type", |
| help="Type of user proxy agent to use for simulations ('dummy', 'metadata', or None for default; default: None)", |
| ), |
| metadata_task: Optional[str] = typer.Option( |
| None, |
| "--metadata-task", |
| help="Task description for metadata user proxy (required if user-proxy-type is 'metadata')", |
| ), |
| metadata_hints: Optional[str] = typer.Option( |
| None, |
| "--metadata-hints", |
| help="Task hints for metadata user proxy (required if user-proxy-type is 'metadata')", |
| ), |
| metadata_answer: Optional[str] = typer.Option( |
| None, |
| "--metadata-answer", |
| help="Task answer for metadata user proxy (required if user-proxy-type is 'metadata')", |
| ), |
| |
| llmlog_dir: Optional[str] = typer.Option( |
| None, |
| "--llmlog-dir", |
| help="Directory path to save LLM call logs (if not provided, LLM logging is disabled)", |
| ), |
| action_policy: str = typer.Option( |
| "never", |
| "--action-policy", |
| help="ActionGuard policy ('always', 'never', 'auto-conservative', 'auto-permissive'; default: never)", |
| ), |
| mcp_agents_file: Optional[str] = typer.Option( |
| None, |
| "--mcp-agents-file", |
| help="Path to a .yaml file containing configuration compatible with MagenticUIConfig.mcp_agents", |
| ), |
| use_pretty_ui: Annotated[ |
| bool, |
| typer.Option( |
| "--pretty-cli/--old-cli", |
| help="Use the old console without fancy formatting (default: use pretty terminal)", |
| ), |
| ] = True, |
| sentinel_tasks: bool = typer.Option( |
| False, |
| "--sentinel-tasks", |
| help="Use sentinel tasks to guide the agent's behavior (default: False)", |
| ), |
| dynamic_sentinel_sleep: bool = typer.Option( |
| False, |
| "--dynamic-sentinel-sleep", |
| help="Enable dynamic sleep duration adaptation for sentinel tasks (only available when --sentinel-tasks is enabled, default: False)", |
| ), |
| ) -> None: |
| """ |
| Magentic-UI CLI: A command-line interface for multi-agent task execution. |
| |
| Run tasks using a team of AI agents with web browsing, coding, and file manipulation capabilities. |
| """ |
| log_debug(f"Command line arguments parsed: debug={debug}", debug) |
|
|
| |
| if debug: |
| log_debug(f"Cooperative planning: {cooperative_planning}", debug) |
| log_debug(f"Autonomous execution: {autonomous_execution}", debug) |
| log_debug(f"Autonomous mode: {autonomous}", debug) |
| log_debug(f"Use state: {use_state}", debug) |
| log_debug(f"Task specified: {bool(task)}", debug) |
| log_debug(f"Action policy: {action_policy}", debug) |
| log_debug(f"Inside Docker: {inside_docker}", debug) |
| log_debug(f"Work directory: {work_dir}", debug) |
| log_debug(f"Config file: {config}", debug) |
| log_debug(f"User proxy type: {user_proxy_type}", debug) |
| log_debug(f"LLM log directory: {llmlog_dir}", debug) |
| log_debug(f"Sentinel tasks: {sentinel_tasks}", debug) |
| log_debug(f"Dynamic sentinel sleep: {dynamic_sentinel_sleep}", debug) |
| log_debug(f"Console mode: {'Pretty' if use_pretty_ui else 'Old'}", debug) |
| log_debug(f"Browser headless: {browser_headless}", debug) |
| log_debug(f"Browser local: {browser_local}", debug) |
|
|
| |
| log_debug("Validating user proxy type", debug) |
| if user_proxy_type not in [None, "dummy", "metadata"]: |
| error_msg = f"Invalid user proxy type: {user_proxy_type}. Valid options are None, 'dummy', or 'metadata'." |
| log_debug(f"ERROR: {error_msg}", debug) |
| raise ValueError(error_msg) |
|
|
| |
| log_debug("Validating metadata user proxy parameters", debug) |
| if user_proxy_type == "metadata": |
| if not all([metadata_task, metadata_hints, metadata_answer]): |
| error_msg = "When using metadata user proxy type, all metadata parameters (--metadata-task, --metadata-hints, --metadata-answer) must be provided." |
| log_debug(f"ERROR: {error_msg}", debug) |
| raise ValueError(error_msg) |
|
|
| |
| log_debug("Validating action policy", debug) |
| if action_policy not in [ |
| "always", |
| "never", |
| "auto-conservative", |
| "auto-permissive", |
| ]: |
| error_msg = f"Invalid action policy: {action_policy}. Valid options are 'always', 'never', 'auto-conservative', 'auto-permissive'." |
| log_debug(f"ERROR: {error_msg}", debug) |
| raise ValueError(error_msg) |
|
|
| |
| log_debug("Validating dynamic sentinel sleep configuration", debug) |
| if dynamic_sentinel_sleep and not sentinel_tasks: |
| error_msg = "Dynamic sentinel sleep (--dynamic-sentinel-sleep) can only be enabled when sentinel tasks (--sentinel-tasks) are enabled." |
| log_debug(f"ERROR: {error_msg}", debug) |
| raise ValueError(error_msg) |
|
|
| |
| if llmlog_dir: |
| log_debug(f"Setting up LLM logging to directory: {llmlog_dir}", debug) |
| setup_llm_logging(llmlog_dir) |
|
|
| |
| client_config: str | None = config |
| if not client_config: |
| if os.path.isfile("config.yaml"): |
| client_config = "config.yaml" |
| log_debug( |
| "Using default config.yaml file found in current directory", debug |
| ) |
| else: |
| log_debug( |
| "No config file provided or found. Using default settings.", debug |
| ) |
| logger.info("Config file not provided. Using default settings.") |
|
|
| |
| log_debug("Processing task input", debug) |
| processed_task: str | None = None |
| if task: |
| if task == "-": |
| log_debug("Reading task from stdin", debug) |
| processed_task = sys.stdin.buffer.read().decode("utf-8") |
| log_debug( |
| f"Task read from stdin, length: {len(processed_task if processed_task else '')}", |
| debug, |
| ) |
| elif os.path.isfile(task): |
| log_debug(f"Reading task from file: {task}", debug) |
| with open(task, "r") as f: |
| processed_task = f.read() |
| log_debug( |
| f"Task read from file, length: {len(processed_task if processed_task else '')}", |
| debug, |
| ) |
| else: |
| log_debug("Using task from command line argument", debug) |
| processed_task = task |
| log_debug( |
| f"Task from argument, length: {len(processed_task if processed_task else '')}", |
| debug, |
| ) |
|
|
| if not run_without_docker: |
| |
| log_debug("Checking Docker setup...", debug) |
| logger.info("Checking if Docker is running...") |
|
|
| if not check_docker_running(): |
| logger.error("Docker is not running. Please start Docker and try again.") |
| sys.exit(1) |
| else: |
| logger.success("Docker is running") |
|
|
| |
| logger.info("Checking Docker vnc browser image...") |
| if not check_browser_image(): |
| logger.warning("VNC browser image needs to be pulled") |
| logger.info("Pulling Docker vnc image (this WILL take a few minutes)") |
| pull_browser_image() |
| else: |
| logger.success("VNC browser image is available") |
|
|
| logger.info("Checking Docker python image...") |
| if not check_python_image(): |
| logger.warning("Python image needs to be pulled") |
| logger.info("Pulling Docker python image (this WILL take a few minutes)") |
| pull_python_image() |
| else: |
| logger.success("Python image is available") |
|
|
| |
| if not check_browser_image() or not check_python_image(): |
| logger.error( |
| "Docker images not found. Please pull or build the images and try again." |
| ) |
| sys.exit(1) |
|
|
| log_debug("Docker setup completed successfully", debug) |
|
|
| log_debug("Processing final answer prompt", debug) |
| processed_final_answer_prompt: str | None = None |
| if final_answer_prompt: |
| if os.path.isfile(final_answer_prompt): |
| log_debug( |
| f"Reading final answer prompt from file: {final_answer_prompt}", |
| debug, |
| ) |
| with open(final_answer_prompt, "r") as f: |
| processed_final_answer_prompt = f.read() |
| log_debug( |
| f"Final answer prompt read from file, length: {len(processed_final_answer_prompt if processed_final_answer_prompt else '')}", |
| debug, |
| ) |
| else: |
| log_debug("Using final answer prompt from command line argument", debug) |
| processed_final_answer_prompt = final_answer_prompt |
| log_debug( |
| f"Final answer prompt from argument, length: {len(processed_final_answer_prompt if processed_final_answer_prompt else '')}", |
| debug, |
| ) |
|
|
| |
| if autonomous: |
| log_debug( |
| "Autonomous mode enabled, setting autonomous_execution=True and cooperative_planning=False", |
| debug, |
| ) |
| autonomous_execution = True |
| cooperative_planning = False |
|
|
| |
| mcp_agents: List[McpAgentConfig] = [] |
| if mcp_agents_file: |
| with open(mcp_agents_file) as fd: |
| mcp_agents_data: Any = yaml.safe_load(fd) |
|
|
| if not isinstance(mcp_agents_data, list): |
| raise TypeError( |
| f"Expected root element of mcp_agents_file to be a list but found: {type(mcp_agents_data)}" |
| ) |
|
|
| for value in mcp_agents_data: |
| mcp_agent = McpAgentConfig.model_validate(value) |
| mcp_agents.append(mcp_agent) |
| logger.info( |
| f"Loaded MCP Agent '{mcp_agent.name}' with MCP Servers [{', '.join(server.server_name for server in mcp_agent.mcp_servers)}]" |
| ) |
|
|
| |
| def signal_handler(sig: int, frame: types.FrameType | None) -> Any: |
| log_debug(f"Signal handler caught signal: {sig}", debug) |
| logger.info("magentic-ui cli caught SIGINT...") |
| log_debug("Raising KeyboardInterrupt to terminate application", debug) |
| raise KeyboardInterrupt |
|
|
| log_debug("Registering SIGINT signal handler", debug) |
| signal.signal(signal.SIGINT, signal_handler) |
|
|
| |
| log_debug("Starting asyncio event loop for get_team", debug) |
| asyncio.run( |
| |
| get_team( |
| cooperative_planning=cooperative_planning, |
| autonomous_execution=autonomous_execution, |
| reset=not use_state, |
| task=processed_task, |
| final_answer_prompt=processed_final_answer_prompt, |
| debug=debug, |
| internal_workspace_root=internal_workspace_root, |
| external_workspace_root=external_workspace_root, |
| playwright_port=playwright_port, |
| novnc_port=novnc_port, |
| inside_docker=inside_docker, |
| work_dir=work_dir, |
| client_config=client_config, |
| action_policy=cast(ApprovalPolicy, action_policy), |
| user_proxy_type=user_proxy_type, |
| task_metadata=metadata_task |
| if user_proxy_type == "metadata" |
| else processed_task, |
| hints=metadata_hints if user_proxy_type == "metadata" else None, |
| answer=metadata_answer if user_proxy_type == "metadata" else None, |
| use_pretty_ui=use_pretty_ui, |
| mcp_agents=mcp_agents, |
| run_without_docker=run_without_docker, |
| browser_headless=browser_headless, |
| browser_local=browser_local, |
| sentinel_tasks=sentinel_tasks, |
| dynamic_sentinel_sleep=dynamic_sentinel_sleep, |
| ) |
| ) |
| log_debug("Asyncio event loop and get_team function completed", debug) |
|
|
|
|
| def main() -> None: |
| """ |
| Entry point for the magentic-cli command. |
| Called from pyproject.toml's [project.scripts] section. |
| """ |
| app() |
|
|
|
|
| if __name__ == "__main__": |
| app() |
|
|