from tools_runtime import execute_tool_call from self_heal import SelfHealer import shlex class Executor: def __init__(self, llm=None): self.llm = llm self.healer = SelfHealer(llm=llm) # ===================================================== # MAIN PIPELINE # ===================================================== def run_plan(self, plan): if not isinstance(plan, dict): return {"error": "Plan must be dict"} steps = plan.get("steps", []) if not isinstance(steps, list): return {"error": "Invalid steps format"} results = [] for step in steps: print(f"🚀 Executing step: {step}") # STEP 1: convert to action action = self._interpret_step(step) # STEP 2: execute tool tool_result = self._execute_tool(action) # STEP 3: self-heal (safe retry) tool_result = self._safe_heal(step, tool_result) # STEP 4: optional LLM analysis llm_result = self._llm_analyze(step, action, tool_result) # STEP 5: store results.append({ "step": step, "action": action, "tool_result": tool_result, "llm_result": llm_result }) return { "status": "completed", "results": results } # ===================================================== # STEP INTERPRETER (FIXED - NO NOOP TRAP) # ===================================================== def _interpret_step(self, step): # already structured tool if isinstance(step, dict): return step if not isinstance(step, str): return { "tool": "run_shell", "args": {"cmd": "echo invalid_step"} } cmd = step.strip() if not cmd: return { "tool": "run_shell", "args": {"cmd": "echo empty_step"} } # 🔥 IMPORTANT FIX: # DO NOT block natural language anymore # Instead: ALWAYS convert to shell-safe fallback shell_like = self._looks_like_shell(cmd) if shell_like: return { "tool": "run_shell", "args": { "cmd": cmd } } # fallback: convert to safe echo (never noop) return { "tool": "run_shell", "args": { "cmd": f"echo '[interpreted step] {shlex.quote(cmd)}'" } } # ===================================================== # SIMPLE HEURISTIC (NOT BLOCKING) # ===================================================== def _looks_like_shell(self, cmd: str): shell_signals = [ "ls", "cd", "mkdir", "touch", "python", "pip", "rm", "echo", "./", "git", "npm", "curl" ] first = cmd.split()[0].lower() if cmd.split() else "" return first in shell_signals # ===================================================== # TOOL EXECUTION # ===================================================== def _execute_tool(self, action): try: if not isinstance(action, dict): return {"error": "invalid_action"} return execute_tool_call(action) except Exception as e: return {"error": str(e)} # ===================================================== # SELF HEAL (SAFE RETRY ONLY) # ===================================================== def _safe_heal(self, step, tool_result): try: fix = self.healer.heal(step, tool_result) if isinstance(fix, dict) and "tool" in fix: print("🛠️ Self-healing triggered") return execute_tool_call(fix) except Exception as e: return { "error": f"healer_error: {str(e)}" } return tool_result # ===================================================== # LLM ANALYSIS (NO EXECUTION) # ===================================================== def _llm_analyze(self, step, action, tool_result): if not self.llm: return None try: response = self.llm([ { "role": "system", "content": "Analyze only. NEVER execute tools." }, { "role": "user", "content": f""" Step: {step} Action: {action} Result: {tool_result} """ } ]) if isinstance(response, dict): return ( response.get("choices", [{}])[0] .get("message", {}) .get("content", "LLM_EMPTY") ) return str(response) except Exception as e: return f"llm_error: {str(e)}"