feat(agent_chat): Intégration exécution réelle avec ActionExecutor

- Import des composants d'exécution (ActionExecutor, ExecutionLoop, etc.)
- Initialisation complète du pipeline d'exécution au démarrage
- Remplacement de la simulation par exécution réelle :
  - Capture d'écran avec ScreenCapturer
  - Exécution des actions avec ActionExecutor
  - Gestion des erreurs et fallback en mode simulé
- Mode dégradé automatique si composants non disponibles

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Dom
2026-01-15 16:03:05 +01:00
parent 855061c8ba
commit 87f2671920

View File

@@ -48,6 +48,17 @@ try:
except ImportError:
GPU_AVAILABLE = False
# Execution components (optional - pour exécution réelle)
try:
from core.execution import ActionExecutor, TargetResolver, ErrorHandler
from core.execution.execution_loop import ExecutionLoop, ExecutionMode as ExecMode, ExecutionState
from core.pipeline.workflow_pipeline import WorkflowPipeline
from core.capture import ScreenCapturer
EXECUTION_AVAILABLE = True
except ImportError as e:
logger.warning(f"Composants d'exécution non disponibles: {e}")
EXECUTION_AVAILABLE = False
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@@ -63,6 +74,12 @@ confirmation_loop: Optional[ConfirmationLoop] = None
response_generator: Optional[ResponseGenerator] = None
conversation_manager: Optional[ConversationManager] = None
# Execution components
workflow_pipeline = None
action_executor = None
execution_loop = None
screen_capturer = None
execution_status = {
"running": False,
"workflow": None,
@@ -110,6 +127,47 @@ def init_system():
response_generator = ResponseGenerator()
conversation_manager = ConversationManager()
# 4. Composants d'exécution réelle
global workflow_pipeline, action_executor, execution_loop, screen_capturer
if EXECUTION_AVAILABLE:
try:
# Pipeline de workflow (matching + actions)
workflow_pipeline = WorkflowPipeline()
logger.info("✓ WorkflowPipeline initialisé")
# Capture d'écran
screen_capturer = ScreenCapturer()
logger.info("✓ ScreenCapturer initialisé")
# Résolveur de cibles et gestionnaire d'erreurs
target_resolver = TargetResolver()
error_handler = ErrorHandler()
# Exécuteur d'actions
action_executor = ActionExecutor(
target_resolver=target_resolver,
error_handler=error_handler,
verify_postconditions=True
)
logger.info("✓ ActionExecutor initialisé")
# Boucle d'exécution (pour mode automatique)
execution_loop = ExecutionLoop(
pipeline=workflow_pipeline,
action_executor=action_executor,
screen_capturer=screen_capturer
)
logger.info("✓ ExecutionLoop initialisé")
except Exception as e:
logger.warning(f"⚠ Composants d'exécution partiels: {e}")
# Mode dégradé: simulation uniquement
workflow_pipeline = None
action_executor = None
execution_loop = None
else:
logger.info(" Mode simulation (composants d'exécution non disponibles)")
# =============================================================================
# Routes Web
@@ -580,34 +638,34 @@ def handle_cancel():
# =============================================================================
def execute_workflow(match, params):
"""Exécuter un workflow avec le vrai système."""
"""Exécuter un workflow avec le vrai système d'exécution."""
global execution_status
import time
try:
# Charger le workflow
with open(match.workflow_path, 'r') as f:
workflow_data = json.load(f)
# Créer le VariableManager et injecter les paramètres
var_manager = VariableManager()
var_manager.set_variables(params)
# Substituer les variables
workflow_data = var_manager.substitute_dict(workflow_data)
# Obtenir les étapes (edges)
edges = workflow_data.get("edges", [])
total_steps = len(edges) if edges else 5
total_steps = len(edges) if edges else 1
# Étape 1: Initialisation
update_progress(10, "Initialisation", 1, total_steps + 2)
time.sleep(0.5)
time.sleep(0.3)
# Étape 2: Préparation GPU (si disponible)
if gpu_manager and GPU_AVAILABLE:
update_progress(20, "Préparation GPU...", 2, total_steps + 2)
update_progress(15, "Préparation GPU...", 2, total_steps + 2)
try:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
@@ -615,36 +673,187 @@ def execute_workflow(match, params):
loop.close()
except Exception as e:
logger.warning(f"GPU mode change failed: {e}")
# Exécuter chaque étape du workflow
for i, edge in enumerate(edges):
if not execution_status["running"]:
break
action = edge.get("action", {})
action_type = action.get("type", "unknown")
progress = int(20 + (i + 1) / total_steps * 70)
step_name = f"Étape {i+1}: {action_type}"
update_progress(progress, step_name, i + 3, total_steps + 2)
# Simuler l'exécution de l'action
# TODO: Connecter au vrai ActionExecutor
time.sleep(0.8)
# Vérifier si l'exécution réelle est disponible
use_real_execution = (
EXECUTION_AVAILABLE and
action_executor is not None and
screen_capturer is not None
)
if use_real_execution:
logger.info(f"🚀 Exécution RÉELLE du workflow: {match.workflow_name}")
_execute_workflow_real(workflow_data, edges, total_steps, params)
else:
logger.info(f"🎭 Exécution SIMULÉE du workflow: {match.workflow_name}")
_execute_workflow_simulated(edges, total_steps)
# Finalisation
update_progress(95, "Finalisation...", total_steps + 1, total_steps + 2)
time.sleep(0.3)
# Terminé avec succès
finish_execution(match.workflow_name, True, "Workflow terminé avec succès")
if execution_status["running"]:
update_progress(95, "Finalisation...", total_steps + 1, total_steps + 2)
time.sleep(0.2)
finish_execution(match.workflow_name, True, "Workflow terminé avec succès")
except Exception as e:
logger.error(f"Execution error: {e}")
import traceback
traceback.print_exc()
finish_execution(match.workflow_name, False, f"Erreur: {str(e)}")
def _execute_workflow_real(workflow_data, edges, total_steps, params):
"""Exécution réelle avec ActionExecutor."""
import time
from dataclasses import dataclass
# Capturer l'écran initial
update_progress(20, "Capture écran initial...", 2, total_steps + 2)
try:
screenshot_path = screen_capturer.capture()
logger.info(f"📸 Screenshot capturé: {screenshot_path}")
except Exception as e:
logger.warning(f"Capture écran échouée: {e}, utilisation mode dégradé")
_execute_workflow_simulated(edges, total_steps)
return
# Créer le ScreenState pour l'exécution
try:
from core.models import ScreenState
screen_state = ScreenState.from_screenshot(screenshot_path)
except Exception as e:
logger.warning(f"Création ScreenState échouée: {e}")
# Créer un ScreenState minimal
screen_state = type('ScreenState', (), {
'screenshot_path': screenshot_path,
'detected_elements': [],
'timestamp': datetime.now()
})()
# Exécuter chaque edge avec ActionExecutor
success_count = 0
fail_count = 0
for i, edge in enumerate(edges):
if not execution_status["running"]:
logger.info("⏹️ Exécution annulée par l'utilisateur")
break
action = edge.get("action", {})
action_type = action.get("type", "unknown")
progress = int(20 + (i + 1) / total_steps * 70)
step_name = f"Étape {i+1}/{total_steps}: {action_type}"
update_progress(progress, step_name, i + 3, total_steps + 2)
logger.info(f"▶️ Exécution: {step_name}")
try:
# Créer un objet Edge compatible avec ActionExecutor
workflow_edge = _create_workflow_edge(edge, params)
# Exécuter l'action réelle
result = action_executor.execute_edge(workflow_edge, screen_state)
if result.status.value == "success":
success_count += 1
logger.info(f"{step_name} - Succès")
# Recapturer l'écran après chaque action réussie
try:
time.sleep(0.3) # Petit délai pour laisser l'UI se mettre à jour
screenshot_path = screen_capturer.capture()
screen_state = ScreenState.from_screenshot(screenshot_path)
except:
pass # Continuer même si la recapture échoue
else:
fail_count += 1
logger.warning(f"⚠️ {step_name} - {result.status.value}: {result.message}")
# Continuer malgré l'échec (mode best-effort)
if fail_count >= 3:
logger.error("❌ Trop d'échecs, arrêt de l'exécution")
break
except Exception as e:
fail_count += 1
logger.error(f"❌ Erreur lors de {step_name}: {e}")
if fail_count >= 3:
logger.error("❌ Trop d'erreurs, arrêt de l'exécution")
break
logger.info(f"📊 Résultat: {success_count} succès, {fail_count} échecs sur {total_steps} étapes")
def _execute_workflow_simulated(edges, total_steps):
"""Exécution simulée (fallback)."""
import time
for i, edge in enumerate(edges):
if not execution_status["running"]:
break
action = edge.get("action", {})
action_type = action.get("type", "unknown")
progress = int(20 + (i + 1) / total_steps * 70)
step_name = f"Étape {i+1}: {action_type} (simulé)"
update_progress(progress, step_name, i + 3, total_steps + 2)
# Simulation avec délai
time.sleep(0.5)
logger.info(f"🎭 Simulé: {step_name}")
def _create_workflow_edge(edge_dict, params):
"""Créer un objet WorkflowEdge depuis un dictionnaire."""
from dataclasses import dataclass, field
from typing import Optional, Dict, Any, List
@dataclass
class Action:
type: str
target: Optional[Dict] = None
value: Optional[str] = None
parameters: Dict[str, Any] = field(default_factory=dict)
@dataclass
class WorkflowEdge:
id: str
source: str
target: str
action: Action
pre_conditions: List[Dict] = field(default_factory=list)
post_conditions: List[Dict] = field(default_factory=list)
action_dict = edge_dict.get("action", {})
# Substituer les paramètres dans l'action
action_value = action_dict.get("value", "")
if action_value and isinstance(action_value, str):
for key, val in params.items():
action_value = action_value.replace(f"${{{key}}}", str(val))
action_value = action_value.replace(f"${key}", str(val))
action = Action(
type=action_dict.get("type", "unknown"),
target=action_dict.get("target"),
value=action_value,
parameters=action_dict.get("parameters", {})
)
return WorkflowEdge(
id=edge_dict.get("id", f"edge_{id(edge_dict)}"),
source=edge_dict.get("source", ""),
target=edge_dict.get("target", ""),
action=action,
pre_conditions=edge_dict.get("pre_conditions", []),
post_conditions=edge_dict.get("post_conditions", [])
)
def update_progress(progress: int, message: str, current: int, total: int):
"""Mettre à jour la progression."""
global execution_status