diff --git a/backend/autometabuilder/workflow/plugin_map.json b/backend/autometabuilder/workflow/plugin_map.json index 558092b..cfe77b1 100644 --- a/backend/autometabuilder/workflow/plugin_map.json +++ b/backend/autometabuilder/workflow/plugin_map.json @@ -12,6 +12,9 @@ "backend.load_tool_registry": "autometabuilder.workflow.plugins.backend.backend_load_tool_registry.backend_load_tool_registry.run", "backend.load_tools": "autometabuilder.workflow.plugins.backend.backend_load_tools.backend_load_tools.run", "backend.parse_cli_args": "autometabuilder.workflow.plugins.backend.backend_parse_cli_args.backend_parse_cli_args.run", + "control.get_bot_status": "autometabuilder.workflow.plugins.control.control_get_bot_status.control_get_bot_status.run", + "control.reset_bot_state": "autometabuilder.workflow.plugins.control.control_reset_bot_state.control_reset_bot_state.run", + "control.start_bot": "autometabuilder.workflow.plugins.control.control_start_bot.control_start_bot.run", "control.switch": "autometabuilder.workflow.plugins.control.control_switch.control_switch.run", "convert.parse_json": "autometabuilder.workflow.plugins.convert.convert_parse_json.convert_parse_json.run", "convert.to_boolean": "autometabuilder.workflow.plugins.convert.convert_to_boolean.convert_to_boolean.run", diff --git a/backend/autometabuilder/workflow/plugins/control/control_get_bot_status/control_get_bot_status.py b/backend/autometabuilder/workflow/plugins/control/control_get_bot_status/control_get_bot_status.py new file mode 100644 index 0000000..5620fc2 --- /dev/null +++ b/backend/autometabuilder/workflow/plugins/control/control_get_bot_status/control_get_bot_status.py @@ -0,0 +1,22 @@ +"""Workflow plugin: get current bot execution status.""" +from autometabuilder.workflow.plugins.control.control_start_bot.control_start_bot import ( + _bot_process, + _mock_running, + _current_run_config, +) + + +def run(_runtime, _inputs): + """Get current bot execution status. + + Returns: + Dictionary with: + - is_running: bool - Whether the bot is currently running + - config: dict - Current run configuration (empty if not running) + - process: object - Bot process object (or None if not running) + """ + return { + "is_running": _bot_process is not None or _mock_running, + "config": _current_run_config, + "process": _bot_process, + } diff --git a/backend/autometabuilder/workflow/plugins/control/control_get_bot_status/package.json b/backend/autometabuilder/workflow/plugins/control/control_get_bot_status/package.json new file mode 100644 index 0000000..95df70a --- /dev/null +++ b/backend/autometabuilder/workflow/plugins/control/control_get_bot_status/package.json @@ -0,0 +1,13 @@ +{ + "name": "@autometabuilder/control_get_bot_status", + "version": "1.0.0", + "description": "Get current bot execution status", + "author": "AutoMetabuilder", + "license": "MIT", + "keywords": ["control", "workflow", "plugin", "bot", "status"], + "main": "control_get_bot_status.py", + "metadata": { + "plugin_type": "control.get_bot_status", + "category": "control" + } +} diff --git a/backend/autometabuilder/workflow/plugins/control/control_reset_bot_state/control_reset_bot_state.py b/backend/autometabuilder/workflow/plugins/control/control_reset_bot_state/control_reset_bot_state.py new file mode 100644 index 0000000..46c1551 --- /dev/null +++ b/backend/autometabuilder/workflow/plugins/control/control_reset_bot_state/control_reset_bot_state.py @@ -0,0 +1,13 @@ +"""Workflow plugin: reset bot execution state.""" +from autometabuilder.workflow.plugins.control.control_start_bot.control_start_bot import _reset_run_state + + +def run(_runtime, _inputs): + """Reset bot execution state. + + Returns: + Dictionary with: + - reset: bool - Always True to indicate state was reset + """ + _reset_run_state() + return {"reset": True} diff --git a/backend/autometabuilder/workflow/plugins/control/control_reset_bot_state/package.json b/backend/autometabuilder/workflow/plugins/control/control_reset_bot_state/package.json new file mode 100644 index 0000000..7483809 --- /dev/null +++ b/backend/autometabuilder/workflow/plugins/control/control_reset_bot_state/package.json @@ -0,0 +1,13 @@ +{ + "name": "@autometabuilder/control_reset_bot_state", + "version": "1.0.0", + "description": "Reset bot execution state", + "author": "AutoMetabuilder", + "license": "MIT", + "keywords": ["control", "workflow", "plugin", "bot", "reset"], + "main": "control_reset_bot_state.py", + "metadata": { + "plugin_type": "control.reset_bot_state", + "category": "control" + } +} diff --git a/backend/autometabuilder/workflow/plugins/control/control_start_bot/control_start_bot.py b/backend/autometabuilder/workflow/plugins/control/control_start_bot/control_start_bot.py new file mode 100644 index 0000000..2c0f413 --- /dev/null +++ b/backend/autometabuilder/workflow/plugins/control/control_start_bot/control_start_bot.py @@ -0,0 +1,93 @@ +"""Workflow plugin: start bot execution in background thread.""" +import os +import subprocess +import sys +import threading +import time + +from autometabuilder.roadmap_utils import is_mvp_reached + +# Global state for bot process +_bot_process = None +_mock_running = False +_current_run_config = {} + + +def _reset_run_state() -> None: + """Reset the bot run state.""" + global _bot_process, _current_run_config + _bot_process = None + _current_run_config = {} + + +def _run_bot_task(mode: str, iterations: int, yolo: bool, stop_at_mvp: bool) -> None: + """Execute bot task in background thread.""" + global _bot_process, _mock_running, _current_run_config + _current_run_config = { + "mode": mode, + "iterations": iterations, + "yolo": yolo, + "stop_at_mvp": stop_at_mvp, + } + + if os.environ.get("MOCK_WEB_UI") == "true": + _mock_running = True + time.sleep(5) + _mock_running = False + _reset_run_state() + return + + try: + cmd = [sys.executable, "-m", "autometabuilder.main"] + if yolo: + cmd.append("--yolo") + if mode == "once": + cmd.append("--once") + if mode == "iterations" and iterations > 1: + for _ in range(iterations): + if stop_at_mvp and is_mvp_reached(): + break + _bot_process = subprocess.Popen(cmd + ["--once"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + _bot_process.wait() + else: + _bot_process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + _bot_process.wait() + finally: + _reset_run_state() + + +def run(_runtime, inputs): + """Start bot execution in background thread. + + Args: + inputs: Dictionary with keys: + - mode: str (default: "once") - Execution mode ("once", "iterations", etc.) + - iterations: int (default: 1) - Number of iterations for "iterations" mode + - yolo: bool (default: True) - Run in YOLO mode + - stop_at_mvp: bool (default: False) - Stop when MVP is reached + + Returns: + Dictionary with: + - started: bool - Whether the bot was started successfully + - error: str (optional) - Error message if bot is already running + """ + global _bot_process, _mock_running + + mode = inputs.get("mode", "once") + iterations = inputs.get("iterations", 1) + yolo = inputs.get("yolo", True) + stop_at_mvp = inputs.get("stop_at_mvp", False) + + # Check if bot is already running + if _bot_process is not None or _mock_running: + return {"started": False, "error": "Bot already running"} + + # Start bot in background thread + thread = threading.Thread( + target=_run_bot_task, + args=(mode, iterations, yolo, stop_at_mvp), + daemon=True + ) + thread.start() + + return {"started": True} diff --git a/backend/autometabuilder/workflow/plugins/control/control_start_bot/package.json b/backend/autometabuilder/workflow/plugins/control/control_start_bot/package.json new file mode 100644 index 0000000..26d4a8c --- /dev/null +++ b/backend/autometabuilder/workflow/plugins/control/control_start_bot/package.json @@ -0,0 +1,13 @@ +{ + "name": "@autometabuilder/control_start_bot", + "version": "1.0.0", + "description": "Start bot execution in a background thread", + "author": "AutoMetabuilder", + "license": "MIT", + "keywords": ["control", "workflow", "plugin", "bot"], + "main": "control_start_bot.py", + "metadata": { + "plugin_type": "control.start_bot", + "category": "control" + } +} diff --git a/backend/autometabuilder/workflow/plugins/web/web_route_context/web_route_context.py b/backend/autometabuilder/workflow/plugins/web/web_route_context/web_route_context.py index 2c9d843..448344a 100644 --- a/backend/autometabuilder/workflow/plugins/web/web_route_context/web_route_context.py +++ b/backend/autometabuilder/workflow/plugins/web/web_route_context/web_route_context.py @@ -2,12 +2,17 @@ import os from flask import Blueprint, jsonify from autometabuilder.loaders.metadata_loader import load_metadata -from autometabuilder.data.run_state import bot_process, current_run_config, mock_running +from autometabuilder.workflow.plugin_loader import load_plugin_callable from autometabuilder.roadmap_utils import is_mvp_reached def run(runtime, _inputs): """Create and return the context routes blueprint.""" + # Load the control.get_bot_status plugin + get_bot_status_plugin = load_plugin_callable( + "autometabuilder.workflow.plugins.control.control_get_bot_status.control_get_bot_status.run" + ) + context_bp = Blueprint("context", __name__) def build_context(): @@ -28,6 +33,9 @@ def run(runtime, _inputs): metadata = load_metadata() packages = load_workflow_packages() + # Get bot status from plugin + bot_status = get_bot_status_plugin(runtime, {}) + return { "logs": get_recent_logs(), "env_vars": get_env_vars(), @@ -41,9 +49,9 @@ def run(runtime, _inputs): "messages": get_ui_messages(lang), "lang": lang, "status": { - "is_running": bot_process is not None or mock_running, + "is_running": bot_status["is_running"], "mvp_reached": is_mvp_reached(), - "config": current_run_config, + "config": bot_status["config"], }, } diff --git a/backend/autometabuilder/workflow/plugins/web/web_route_run/web_route_run.py b/backend/autometabuilder/workflow/plugins/web/web_route_run/web_route_run.py index 48fcead..fc7c515 100644 --- a/backend/autometabuilder/workflow/plugins/web/web_route_run/web_route_run.py +++ b/backend/autometabuilder/workflow/plugins/web/web_route_run/web_route_run.py @@ -1,10 +1,15 @@ """Workflow plugin: run API routes blueprint.""" from flask import Blueprint, jsonify, request -from autometabuilder.data.run_state import start_bot +from autometabuilder.workflow.plugin_loader import load_plugin_callable def run(runtime, _inputs): """Create and return the run routes blueprint.""" + # Load the control.start_bot plugin + start_bot_plugin = load_plugin_callable( + "autometabuilder.workflow.plugins.control.control_start_bot.control_start_bot.run" + ) + run_bp = Blueprint("run", __name__) @run_bp.route("/api/run", methods=["POST"]) @@ -15,9 +20,16 @@ def run(runtime, _inputs): yolo = payload.get("yolo", True) stop_at_mvp = payload.get("stop_at_mvp", False) - started = start_bot(mode, iterations, yolo, stop_at_mvp) - if not started: - return jsonify({"error": "Bot already running"}), 400 + # Call the control.start_bot plugin + result = start_bot_plugin(runtime, { + "mode": mode, + "iterations": iterations, + "yolo": yolo, + "stop_at_mvp": stop_at_mvp + }) + + if not result.get("started"): + return jsonify({"error": result.get("error", "Bot already running")}), 400 return jsonify({"status": "started"}), 200