feat(vwb): log supervised competence verdicts

This commit is contained in:
Dom
2026-05-29 18:36:06 +02:00
parent 7ad260d02f
commit aba849324a
18 changed files with 1082 additions and 5 deletions

View File

@@ -103,6 +103,33 @@ def _execute_wait_for_state(params: Dict[str, Any]) -> dict:
output = result.to_dict()
if result.matched:
return {"success": True, "output": output}
if params.get("supervised_popup_detection", False):
detected_popup = None
try:
detected_popup = _check_screen_for_patterns()
except Exception as exc:
print(f"⚠️ [wait_for_state] Detection popup indisponible: {exc}")
from visual_workflow_builder.backend.services.supervised_popup_guard import (
build_unexpected_popup_pause,
)
pause_payload = build_unexpected_popup_pause(
detected_popup,
expected_state=expected_state,
competence_id=str(params.get("competence_id") or ""),
source_method_id=str(params.get("source_method_id") or ""),
)
if pause_payload:
return {
"success": True,
"needs_human_pause": True,
"output": {
"wait_for_state": output,
"human_pause": pause_payload,
"write_back_enabled": False,
},
}
return {
"success": False,
"error": "Etat attendu non observe avant timeout",
@@ -110,6 +137,23 @@ def _execute_wait_for_state(params: Dict[str, Any]) -> dict:
}
def _execute_pause_for_human(params: Dict[str, Any]) -> dict:
return {
"success": True,
"needs_human_pause": True,
"output": {
"needs_human": True,
"pause_reason": "supervised_pause",
"message": params.get("message", "Validation humaine requise"),
"phase": params.get("phase"),
"verdict_required": bool(params.get("verdict_required", False)),
"verdict_endpoint": params.get("verdict_endpoint"),
"competence_id": params.get("competence_id"),
"write_back_enabled": False,
},
}
def minimize_active_window():
"""Minimise le navigateur VWB et active la fenêtre suivante (VM, app cible)."""
try:
@@ -168,7 +212,8 @@ _execution_state = {
'pending_action': None, # Action en attente de choix utilisateur
'candidates': [], # Candidats proposés
'user_choice': None, # Choix de l'utilisateur (coordonnées ou 'skip' ou 'static')
'current_step_info': None # Info sur l'étape en cours pour affichage
'current_step_info': None, # Info sur l'étape en cours pour affichage
'human_pause': None,
}
@@ -307,6 +352,35 @@ def execute_workflow_thread(execution_id: str, workflow_id: str, app):
params['_step_label'] = step.label
result = execute_action(step.action_type, params)
if result.get('needs_human_pause'):
pause_payload = result.get('output', {})
print(f"⏸️ [Supervision] Pause humaine étape {index + 1}: {pause_payload}")
with _execution_lock:
_execution_state['is_paused'] = True
_execution_state['human_pause'] = pause_payload
_execution_state['current_step_info'] = {
'index': index,
'total': len(steps),
'step_id': step.id,
'action_type': step.action_type,
'label': step.label,
'human_pause': pause_payload,
}
execution.status = 'paused'
db.session.commit()
while _execution_state['is_paused'] and not _execution_state['should_stop']:
time.sleep(0.1)
if _execution_state['should_stop']:
execution.status = 'cancelled'
break
with _execution_lock:
_execution_state['human_pause'] = None
execution.status = 'running'
db.session.commit()
# === SELF-HEALING INTERACTIF ===
# Si l'action nécessite un choix utilisateur, attendre
if result.get('needs_user_choice'):
@@ -456,6 +530,7 @@ def execute_workflow_thread(execution_id: str, workflow_id: str, app):
with _execution_lock:
_execution_state['is_running'] = False
_execution_state['current_execution_id'] = None
_execution_state['human_pause'] = None
def execute_ai_analyze(params: dict) -> dict:
@@ -1198,6 +1273,9 @@ def execute_action(action_type: str, params: dict) -> dict:
elif action_type == 'wait_for_state':
return _execute_wait_for_state(params)
elif action_type == 'pause_for_human':
return _execute_pause_for_human(params)
elif action_type == 'keyboard_shortcut':
keys = params.get('keys', [])
if not keys:
@@ -1631,6 +1709,7 @@ def start_execution():
_execution_state['current_execution_id'] = execution.id
_execution_state['execution_mode'] = execution_mode
_execution_state['variables'] = {} # Reset des variables
_execution_state['human_pause'] = None
print(f"🎯 [API v3] Mode d'exécution: {execution_mode}")
@@ -1790,7 +1869,15 @@ def get_execution_status():
# Self-healing interactif
'waiting_for_choice': _execution_state.get('waiting_for_choice', False),
'candidates': _execution_state.get('candidates', []) if _execution_state.get('waiting_for_choice') else [],
'current_step_info': _execution_state.get('current_step_info') if _execution_state.get('waiting_for_choice') else None
'current_step_info': (
_execution_state.get('current_step_info')
if (
_execution_state.get('waiting_for_choice')
or _execution_state.get('human_pause')
)
else None
),
'human_pause': _execution_state.get('human_pause'),
})