# CR — Durcissement du setup auto Windows (gardes visuelles + skip pixel-change) **Date** : 2026-05-22 **Branche** : `backup/post-demo-2026-05-19` **Périmètre** : `agent_v0/server_v1/replay_engine.py`, `agent_v0/agent_v1/core/executor.py`, `agent_v0/agent_v1/ui/chat_window.py` **Statut** : patch + tests implémentés et verts (104/104 sur le périmètre). --- ## 1. Constat live (run du 22 mai 2026 — `replay_sess_76b7d067`) Sur `sess_20260520T102916_066851`, le trim `neutral=True` passe correctement (patch 20 mai). En revanche, le setup auto Windows enchaîne ses étapes sans aucune garde visuelle intermédiaire : - `act_setup_sess_click_start` finit en `position_fallback` (clic blind, sans résolution VLM). - Succès jugé sur **simple changement d'écran** (`_wait_for_screen_change` dans `executor.py:1313`). - `type_search` et `wait_results` partent sans garde de fenêtre. - `click_app_result` découvre **trop tard** que la fenêtre attendue n'est pas `Rechercher` mais `Fenêtre de dépassement de capacité de la barre d'état système.`. - Pendant ce temps, `bloc-notes` a déjà été tapé dans la mauvaise surface (le popup overflow), polluant l'état. ## 2. Cause racine Deux trous combinés : | # | Trou | Détail | |---|---|---| | A | **Pas de pré/post-conditions visuelles** entre les étapes du setup | `verify_screen` côté agent (`executor.py:1196`) ne faisait qu'un `time.sleep` et déléguait toute vérification au serveur — qui n'a pas de node CLIP pour ces étapes intermédiaires | | B | **Validation par simple pixel-change** sur `click_start` | `executor.py:1313` considère un click `_setup_phase` valide dès qu'un seul pixel change. Or l'overflow popup change l'écran sans pour autant ouvrir le bon menu | ## 3. Patch minimal — nouveau contrat de contrôle visuel ### 3.1 Nouvelle chaîne setup (12 actions, 3 gardes intermédiaires + 1 finale) ``` 1. click_start_menu clic visuel, fallback x/y 2. wait_start_menu 1000 ms 3. verify_start_menu_open GARDE 1 — titre ∈ {Rechercher, Search, Cortana, Démarrer, Start, SearchHost, StartMenuExperienceHost} 4. click_search_box clic visuel (uniquement si search_mode = click_then_type) 5. wait_search_ready 500 ms 6. verify_search_box_active GARDE 2 — titre ∈ {, Rechercher, Search} 7. type_app_name frappe « Bloc-notes » 8. wait_search_results 1200 ms 9. verify_search_results_visible GARDE 3 — titre ∈ {Rechercher, Search, Cortana, SearchHost, StartMenuExperienceHost} 10. click_app_result clic visuel + expected_window_before 11. wait_app_launch 2000 ms (3000 pour Office) 12. verify_screen final CLIP node setup_initial (pré-existant) ``` ### 3.2 Mécanisme des gardes (nouveau champ sur `verify_screen`) Champ ajouté sur le contrat `verify_screen` : `expected_window_title_contains: List[str]`. Côté agent : 1. `time.sleep` d'attente (comportement legacy). 2. Si patterns présents : `get_active_window_info()` → comparaison substring case-insensitive avec les patterns. 3. Match positif → succès, on continue. 4. Match négatif → bascule en **mode apprentissage humain** (`_capture_human_correction`, 120 s). - Si l'utilisateur agit : warning `setup_guard_window_mismatch`, success=True, la correction remonte au serveur. - Si timeout : success=False, `needs_human=True`, pause supervisée. Pas de changement côté serveur. Le node CLIP final (`setup_initial`) reste pour le verify post-launch. ### 3.3 Skip pixel-change pour `_setup_phase` Dans `executor.py`, juste avant la branche `_wait_for_screen_change` : ``` is_setup_action = bool(action.get("_setup_phase")) if needs_screen_check and hash_before and is_setup_action: # Setup phase : pixel-change neutralisé, la garde verify_screen tranche time.sleep(0.5) elif needs_screen_check and hash_before: # Comportement legacy pour les actions utilisateur ... ``` Conséquences : - `click_start_menu` ne peut plus être validé sur la seule ouverture du systray overflow popup. - Le verify_screen suivant détecte le mauvais titre fenêtre et déclenche immédiatement le mode apprentissage. - Les actions utilisateur **hors setup** conservent strictement le comportement précédent (non-régression vérifiée). ### 3.4 Fix troncature bulle pause supervisée (livré au tour précédent) Pour mémoire — déjà appliqué : - `chat_window._compute_paused_bubble_height(reason_str)` : helper statique testable. - Calcul : `max(wrapped_lines, explicit_lines)` avec cap à 12 lignes (vs 8 avant). - Scrollbar activée dès que **cap atteint OU contenu ≥ 200 chars** (vs > 280 chars avant). - Les longs `reason` serveur listant plusieurs candidats (avec `\n`) ne sont plus tronqués silencieusement. ## 4. Fichiers modifiés | Fichier | Modification | SCP Windows | |---|---|---| | `agent_v0/server_v1/replay_engine.py` | `_generate_setup_actions` : insertion de 3 actions `verify_screen` (`verify_start_menu_open`, `verify_search_box_active`, `verify_search_results_visible`) | Non | | `agent_v0/agent_v1/core/executor.py` | Helper statique `_window_title_matches_any` ; branche `verify_screen` étendue avec garde titre fenêtre + mode apprentissage ; skip `_wait_for_screen_change` pour `_setup_phase=True` | **Oui** → `C:/rpa_vision/agent_v1/core/executor.py` | | `agent_v0/agent_v1/ui/chat_window.py` | Helper statique `_compute_paused_bubble_height` ; cap relevé à 12 lignes, scrollbar dès cap atteint ou ≥ 200 chars (tour précédent) | **Oui** → `C:/rpa_vision/agent_v1/ui/chat_window.py` | ⚠️ Le miroir `agent_v0/deploy/windows_client/` est obsolète (setup initial uniquement). Canal d'incrémental réel = SCP manuel direct vers `C:/rpa_vision/`. ## 5. Tests ajoutés ou adaptés | Fichier | Nature | Tests | |---|---|---| | `tests/unit/test_env_setup.py` | NEW classe `TestSetupVisualGuards` | 6 tests : insertion `verify_start_menu_open`, `verify_search_box_active` (mode `click_then_type`), absence en `direct_typing`, `verify_search_results_visible` toujours présent (les 2 modes), timeout ≤ 2 s sur toutes les gardes | | `tests/unit/test_env_setup.py` | Adaptation de 5 tests existants | `test_notepad_setup_visual` (12 actions), `test_skips_search_click_for_direct_typing`, `test_verify_screen_final_present_with_title`, `test_no_final_verify_without_title`, `test_full_pipeline_from_events` (séquence canonique mise à jour) | | `tests/unit/test_executor_verify_window_guard.py` | NEW fichier | 13 tests : helper `_window_title_matches_any` (7 cas) + routage garde (4 cas : match, mismatch+correction, mismatch+timeout, neutre sans patterns) + skip pixel-change `_setup_phase` (2 cas : setup skippe, hors-setup garde le comportement) | | `tests/unit/test_chat_window_paused_dispatch.py` | Ajout classe `TestPausedBubbleHeight` (tour précédent) | 6 tests : empty, court, long single line, multi-lignes `\n`, cap atteint, seuil 200 chars | | `tests/integration/test_replay_session_trim_neutral.py` | Inchangé (tour précédent) | 1 test bout-en-bout — toujours vert avec le nouveau setup | **Bilan tests sur le périmètre** : **104 / 104 verts**. ```bash cd /home/dom/ai/rpa_vision_v3 source .venv/bin/activate set -a && source .env.local && set +a python -m pytest \ tests/unit/test_env_setup.py \ tests/unit/test_executor_verify_window_guard.py \ tests/unit/test_chat_window_paused_dispatch.py \ tests/unit/test_server_client_replay_controls.py \ tests/integration/test_replay_session_trim_neutral.py -v ``` ## 6. Comportement attendu en live Après SCP `executor.py` + `chat_window.py` et redémarrage Léa, sur un nouveau `/replay-session` de `sess_20260520T102916_066851` : | Scénario | Log Léa attendu | Issue | |---|---|---| | `click_start` touche le vrai bouton Windows | `[LEA] verify_screen garde OK : 'Recherche' matche [...]` | Setup avance, frappe protégée | | `click_start` ouvre systray overflow popup | Pixel-change observé MAIS log explicite `Setup action … : validation pixel-change skippée (garde verify_screen ultérieure)` puis `[LEA] verify_screen garde KO : attendu un titre contenant [...], actuel 'Fenêtre de dépassement…'` | Mode apprentissage humain immédiat, aucune frappe à l'aveugle | | Focus perdu pendant `wait_search_results` (notification surgit) | `[LEA] verify_screen garde KO` sur `verify_search_results_visible` | Apprentissage humain avant `click_app_result` | | Bulle de pause avec un long `reason` | Scrollbar visible | Plus de troncature | ## 7. Risques / limites - **Patterns FR+EN uniquement** : couverture Windows 10/11 FR et EN. Sur OS exotique (DE, ES, ZH), il faudra étendre `expected_window_title_contains`. Localisé dans `_generate_setup_actions`, extension triviale. - **Skip pixel-change conditionné à `_setup_phase`** : seules les actions marquées `_setup_phase=True` perdent la validation pixel-change. Si une future contribution ajoute une action setup sans garde verify_screen derrière, on perdrait le filet. À surveiller / documenter dans la convention de génération. - **Mode `direct_typing`** : couverture par `verify_start_menu_open` (avant frappe) + `verify_search_results_visible` (avant clic résultat). Pas de `verify_search_box_active` car pas de `click_search_box` à valider — testé explicitement. - **Helper `_compute_paused_bubble_height`** : prend en compte les `\n` explicites et la longueur ; cap 12 lignes. Compromis volontairement conservateur — afficher une scrollbar légèrement trop tôt vaut mieux que tronquer du contenu critique de pause. ## 8. Synthèse pour décision - **Avant ce patch** : setup auto enchaînait click → wait → type → click_result sans contrôle entre, et un seul changement de pixel suffisait à valider la première étape. Constat live = `bloc` tapé dans `Fenêtre de dépassement…`, click_result en erreur tardive, `paused_need_help`. - **Après ce patch** : 3 gardes verify_screen titre fenêtre + skip pixel-change setup → chaque transition critique est verrouillée. Mode apprentissage humain immédiat à la première dérive. Pixel-change ne décide plus de la validité d'une étape setup. - **Scope** : 2 fichiers prod modifiés (≈ 90 lignes ajoutées dans `replay_engine.py`, ≈ 75 dans `executor.py`), 2 fichiers test (≈ 350 lignes neuves + adaptations). Aucun changement côté serveur ni protocole. - **SCP** : `executor.py` et `chat_window.py` à pousser vers `C:/rpa_vision/agent_v1/…` avant relance Léa. `replay_engine.py` reste côté serveur Linux. - **Validation live à faire** : lancer un `/replay-session` sur `sess_20260520T102916_066851`, vérifier la présence des 3 logs `verify_screen garde OK` (ou un mode apprentissage propre en cas de dérive).