fix: filtre UIA-aware + polling pré-vérif tolérant

Filtre d'événements parasites basé sur la CIBLE UIA :
- Un clic n'est filtré que si son uia_snapshot indique que l'élément
  cliqué (ou un parent) est dans la fenêtre de Léa.
- Avant : on filtrait sur window.title qui pouvait être "Lea" même
  quand le clic visait la taskbar (Léa au premier plan).
- Après : on regarde où va VRAIMENT le clic via parent_path UIA.

Extraction du expected_window depuis le parent_path UIA :
- Priorité au nom de la fenêtre racine du parent_path (plus fiable).
- Fallback sur window.title si pas de snapshot UIA ou pas de racine.
- Les fenêtres Léa sont neutralisées (effective_title="").

Pré-vérif avec polling tolérant (executor.py) :
- 5 tentatives avec 300ms entre chaque (total 1.5s max).
- Ignore les transitions "unknown_window" et fenêtre Léa.
- Évite les faux négatifs sur fenêtres en cours de changement.

Note : le filtrage reste basé sur des heuristiques. Un tri intelligent
par gemma4 au build reste à implémenter pour gérer les workflows
enregistrés avec des actions parasites (mail, chat, etc.).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dom
2026-04-10 14:25:40 +02:00
parent cecdf417b7
commit e66629ce1a
2 changed files with 168 additions and 40 deletions

View File

@@ -526,38 +526,66 @@ class ActionExecutorV1:
)
if expected_title and expected_title != "unknown_window":
from ..window_info_crossplatform import get_active_window_info
current_info = get_active_window_info()
current_title = current_info.get("title", "")
current_app = _app_name(current_title)
expected_app = _app_name(expected_title)
title_match = (
current_app == expected_app
or expected_title.lower() in current_title.lower()
or current_title.lower() in expected_title.lower()
)
# Ignorer la fenêtre de Léa elle-même (overlay agent)
# On utilise `messages.est_fenetre_lea` centralisé pour la
# cohérence avec les autres modules (tests, activity panel).
from ..ui.messages import est_fenetre_lea
is_lea_window = est_fenetre_lea(current_title)
if not title_match and not is_lea_window:
logger.warning(
f"[LEA] Fenêtre incorrecte : attendu '{expected_title}', "
f"actuel '{current_title}'"
# Polling court pour laisser le temps à la fenêtre de
# se stabiliser (évite les faux négatifs sur transitions
# rapides : menu qui se ferme, taskbar qui perd le focus, etc.)
current_title = ""
title_match = False
is_lea_window = False
for attempt in range(5):
current_info = get_active_window_info()
current_title = current_info.get("title", "")
# Si on tombe sur Léa elle-même → on attend un peu
if est_fenetre_lea(current_title):
is_lea_window = True
time.sleep(0.3)
continue
# Si on tombe sur unknown_window → on attend aussi
if not current_title or current_title == "unknown_window":
time.sleep(0.3)
continue
current_app = _app_name(current_title)
expected_app = _app_name(expected_title)
title_match = (
current_app == expected_app
or expected_title.lower() in current_title.lower()
or current_title.lower() in expected_title.lower()
)
print(f" [PRÉ-VÉRIF] STOP — fenêtre '{current_title}' ≠ attendu '{expected_title}'")
# Notification utilisateur en français naturel
try:
self.notifier.replay_wrong_window(current_title, expected_title)
except Exception:
pass
result["success"] = False
result["error"] = f"Fenêtre incorrecte: '{current_title}' (attendu: '{expected_title}')"
return result
elif is_lea_window:
logger.info("[LEA] Fenêtre de Léa détectée — ignorée, on continue")
if title_match:
break
# Sinon on retente un peu au cas où la fenêtre
# est en cours de transition
time.sleep(0.3)
if not title_match:
if is_lea_window:
# Si après 5 essais on est encore sur Léa,
# on ignore (l'utilisateur a Léa au premier plan)
logger.info("[LEA] Fenêtre de Léa persistante — ignorée, on continue")
elif not current_title or current_title == "unknown_window":
# unknown_window persistant : on continue avec un
# warning, UIA décidera peut-être
logger.warning(
f"[LEA] Fenêtre active inconnue — on tente quand même"
)
else:
logger.warning(
f"[LEA] Fenêtre incorrecte : attendu '{expected_title}', "
f"actuel '{current_title}'"
)
print(f" [PRÉ-VÉRIF] STOP — fenêtre '{current_title}' ≠ attendu '{expected_title}'")
try:
self.notifier.replay_wrong_window(current_title, expected_title)
except Exception:
pass
result["success"] = False
result["error"] = f"Fenêtre incorrecte: '{current_title}' (attendu: '{expected_title}')"
return result
else:
logger.info(f"[LEA] Pré-vérif OK : '{current_title}'")