Le resolve_engine suit désormais l'ordre de méthodes décidé par l'ExecutionCompiler
au lieu de sa cascade improvisée. C'est la pièce maîtresse du V4 :
- execution_plan_runner.py : ajout de 'resolve_order' dans target_spec
["ocr", "template", "vlm"] = stratégies dans l'ordre de préférence
- resolve_engine.py : _resolve_with_precompiled_order() honore l'ordre
- Court-circuite la cascade legacy quand resolve_order est présent
- Fallback sur la cascade si toutes les méthodes V4 échouent
- _resolve_by_ocr_text() : résolution OCR directe via docTR (~200ms)
Chemin rapide V4 — pas de VLM pour les éléments avec texte visible
- 12 nouveaux tests : propagation resolve_order, cascade, fallback, pipeline E2E
220 tests passent (208 existants + 12 nouveaux), 0 régression.
"Le LLM compile. Le runtime exécute."
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Format canonique entre RawTrace (capture) et ExecutionPlan (exécution).
C'est ce que Léa a COMPRIS en observant l'utilisateur.
- WorkflowIR : steps, variables, intentions, pré/postconditions
- IRBuilder : transforme les événements bruts en WorkflowIR via gemma4
- Générique : fonctionne pour TIM, compta, RH, stocks — le domaine est une couche par-dessus
- Versionné, sérialisable JSON, save/load
- Détection automatique des variables (texte saisi → substituable)
- 18 tests (format, sérialisation, builder, segmentation, variables)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Les clics taskbar (sans window_capture.rect) ne passent plus par le
grounding VLM qui trouve "Rechercher" dans l'explorateur au lieu de
la taskbar. Le template matching du crop 80x80 est utilisé à la place.
Règle : fenêtre = grounding, taskbar = template matching.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Quand le magnétoscope ne trouve pas la cible, au lieu de la pause
supervisée, gemma4 (Docker port 11435, think=True) reçoit le contexte
(action prévue + fenêtre active) et décide :
- PASSER : le résultat est déjà atteint (onglet actif, dialog ouvert)
- STOPPER : état incohérent (mauvaise app)
- EXECUTER : fallback vers la pause supervisée
Testé : gemma4 décide PASSER quand l'onglet est déjà actif (5s).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
La description de la cible dans les notifications et logs utilise
by_text et window_title au lieu de by_role="yolo" qui n'a pas de
sens pour l'utilisateur.
Testé : gemma4 en mode texte (CPU, 0.2s) prend la décision "PASSER"
quand l'onglet est déjà actif. Base pour l'acteur intelligent.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Avant la résolution visuelle, compare l'embedding CLIP de l'écran
actuel (fenêtre) avec l'embedding de référence (enregistrement).
Si similarité < 0.75 → mauvaise application → STOP.
CLIP sur fenêtre = insensible au fond d'écran.
CLIP ne distingue pas les états fins (texte différent) → le titre
de fenêtre reste la vérification principale.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Approche hybride :
- Actions du magnétoscope (by_text, target_spec, grounding)
- Embeddings CLIP du workflow (512D par screenshot de clic)
- Au replay : CLIP vérifie l'état de l'écran AVANT chaque clic
Pipeline complet mesuré :
- ScreenAnalyzer (OCR) : 1.05s/screenshot
- CLIP embeddings : 0.093s/screenshot
- FAISS : <0.01s pour 13 vecteurs
- GraphBuilder : 0.7s (13 nodes, 12 edges)
- Total : 15.7s pour 1.5 min de session
- Extrapolation 1h : ~10 min
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. Le grounding se déclenche pour by_text_source="vlm" (pas juste "ocr")
Les textes lus par gemma4 (onglets, labels) sont du texte visible,
le grounding doit les chercher comme n'importe quel texte OCR.
2. gemma4 est automatiquement déchargé après le build_replay
pour libérer la VRAM et permettre à qwen2.5vl de charger au replay.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Quand plusieurs éléments ont le même texte ("Rechercher" dans la taskbar
ET dans l'explorateur), la position relative (en bas, en haut, à gauche)
aide le VLM à choisir le bon.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Quand l'OCR et SomEngine ne trouvent pas de texte sur un élément cliqué,
gemma4 (Ollama 0.20 Docker) analyse le screenshot fenêtre + position du
clic pour identifier l'élément ("voiture elec", "Settings", etc.).
Résultat : 0 clic sans by_text (vs 3 avant). Validation locale 7/8 (87%).
L'onglet Bloc-notes est maintenant correctement identifié.
Docker : ollama/ollama:0.20.2 sur port 11435 (GEMMA4_PORT env var).
Host : Ollama 0.16.3 sur port 11434 (qwen2.5vl grounding).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Compare 'Bloc-notes' (après le –) au lieu du titre complet.
'blocnote.txt – Bloc-notes' et 'voiture.txt – Bloc-notes'
sont la même app → pré-vérif et post-vérif passent.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Le resolve_target reçoit un screenshot temp de l'agent — le fichier
_window.png n'existe pas à cet emplacement. Au lieu de chercher un
fichier, on crop directement la fenêtre depuis le full screenshot
en utilisant window_rect du target_spec.
Fonctionne au replay (screenshot live) comme à l'enregistrement.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Après chaque clic, poll le titre de la fenêtre active toutes les 300ms
jusqu'à ce qu'il corresponde au titre attendu (max 10s).
100% visuel — pas de wait arbitraire.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pré-vérification : avant chaque clic, vérifie que le titre de la
fenêtre active correspond à celui de l'enregistrement. Stop si mismatch.
Post-vérification : après chaque clic, vérifie que le titre a changé
vers expected_window_title (titre du prochain clic). Warning si mismatch.
expected_window_title enrichi dans build_replay depuis la séquence des clics.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Le premier clic (barre de recherche Windows) a un titre
"unknown_window" qui déclenchait la coupure de fin de session.
Ajout d'un guard : pas de coupure avant 3 actions significatives.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Template matching des icônes limité à la fenêtre active (window.png)
pour éviter les faux positifs sur le full screen. Seuil relevé de
0.70 à 0.90. Coordonnées fenêtre converties en coordonnées écran.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Utilise shot_XXXX_window.png (capture fenêtre active) au lieu du
full screen pour le grounding VLM. Image plus petite, ciblée,
sans bruit (taskbar, autres fenêtres).
Coordonnées fenêtre converties en coordonnées écran via window_rect.
window_capture (rect, window_size, click_relative) ajouté au target_spec.
Résultat : 50% → 80% de précision sur la session VM (16/20 clics).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Le prompt JSON ("Answer ONLY: {x, y}") ne fonctionne plus — retourne
[0.0, 0.0] systématiquement. Le prompt natif "Detect X with a bounding
box" retourne des bbox_2d précis. C'est le format pour lequel
Qwen2.5-VL est entraîné.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Qwen2.5-VL occupe 9.8 GB de VRAM → plus de place pour YOLO.
SomEngine passe en CPU (1.4s au lieu de 0.1s, acceptable car
utilisé uniquement pendant le build_replay, pas le replay).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
_resolve_by_grounding() essaie vLLM d'abord (API OpenAI-compatible,
port 8100) puis Ollama en fallback. vLLM utilise Qwen2.5-VL-7B-AWQ
sur GPU (~2-3s) vs Ollama sur CPU (~16s).
Config via env vars : VLLM_PORT (défaut 8100), VLLM_MODEL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Résolution 4/4 (100%) validée localement :
- Texte OCR (by_text_source="ocr") → grounding Qwen2.5-VL (dist < 0.04)
- Icônes sans texte (by_text_source="") → template matching crop 80x80 (dist = 0.000)
Le VLM identify element est supprimé pour les icônes (descriptions
non-déterministes qui faisaient échouer le grounding). Le template
matching est instantané et parfait quand le crop est net (80x80).
Ajout de by_text_source dans target_spec pour distinguer OCR vs VLM.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Nouvelle approche basée sur les recherches état de l'art :
- _resolve_by_grounding() : le VLM retourne directement les coordonnées
(pas de SomEngine + numérotation intermédiaire)
- Utilise Qwen2.5-VL (entraîné pour le GUI grounding) au lieu de qwen3-vl
- Parse les formats natifs : bbox_2d, JSON x/y, arrays bruts
- Fallback multi-image : screenshot + crop → grounding sans description
- Identification des icônes via Qwen2.5-VL (meilleur que qwen3-vl)
Résultats sur session réelle (validation locale) :
- Éléments avec texte (Word, Document, Fichier) : 100% corrects
- Icônes sans texte (Windows logo, disquette) : en cours d'amélioration
Cascade strict mode :
0. Grounding VLM direct (Qwen2.5-VL) — NOUVEAU
0.5. Template matching pour icônes
1. VLM Quick Find (fallback)
1.5. SoM + VLM
2. Template matching strict
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Quand by_text est vide (icônes : logo Windows, disquette, croix),
le template matching du crop 80x80 est plus fiable que le VLM qui
choisit des éléments au hasard.
Cascade strict mode :
0. Template matching (si by_text vide) — crop 80x80 discriminant
1. VLM Quick Find (compréhension sémantique)
1.5. SoM + VLM
2. Template matching (fallback avec seuil 0.90)
3. Échec → STOP
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pour les environnements Citrix avec détection de robots :
- Souris : courbe de Bézier quadratique avec déviation aléatoire
et vitesse variable (25 étapes, plus lent début/fin)
- Texte : frappe caractère par caractère via KeyCode.from_char()
avec délai aléatoire 40-120ms (pas de copier-coller)
- Plus de presse-papiers (Ctrl+V détectable)
Annulation du fix raw_keys→clipboard (plus nécessaire).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. Crop réduit de 150x150 à 80x80 (config + fallback serveur)
Plus discriminant pour les icônes de barre de titre
2. Email AZERTY : supprimer raw_keys quand le texte contient des
chars fusionnés depuis key_combos (@ de AltGr) → copier-coller
Le @ était perdu car absent des raw_keys individuels
3. Anchor match : template matching sur screenshot entier puis
élément SomEngine le plus proche (max 100px)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Le template matching du crop anchor contre les régions YOLO échouait
car l'anchor (150x150) est plus grand que les éléments détectés.
Maintenant : match sur le screenshot entier → centre du match →
élément SomEngine le plus proche (max 100px).
Fonctionne pour les icônes mais limité par la taille du crop
(150x150 de barre de titre matche à plusieurs endroits).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Le VLM 8B répond souvent avec "several UI elements", "I can see",
etc. au lieu d'un label court. Ces réponses remplissaient by_text
avec du non-sens, empêchant le som_anchor_match de se déclencher
pour les icônes sans texte (disquette, fermer, etc.).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
La condition vérifiait anchor_label (du SomEngine) au lieu de by_text.
Pour les icônes (disquette, loupe), by_text est vide même si anchor_label
contient du bavardage VLM. Maintenant le template matching anchor vs YOLO
se déclenche correctement.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
build_replay (stream_processor.py) :
- Remplir by_text depuis vision_info.text ou som_element.label
- VLM identification pour les éléments sans texte (icônes)
- Nettoyage du bavardage VLM (retrait préfixes courants)
resolve_target (api_stream.py) :
- Nouveau som_anchor_match : template matching du crop anchor vs régions YOLO
- Pour les icônes sans texte (disquette, loupe, etc.)
- Cascade : text match → anchor match → VLM
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Le template matching compare des pixels et donne des faux positifs
quand l'écran n'est pas dans le même état que l'enregistrement.
SomEngine + VLM comprend sémantiquement ce qu'on cherche.
Nouvelle cascade :
1. Serveur SomEngine + VLM (compréhension sémantique)
2. Template matching local (fallback si serveur down)
3. VLM local (fallback dev/test)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- from .config → from ..config (executor.py est dans core/, config dans agent_v1/)
- run_agent_v1.py charge config.txt et .env au démarrage (fonctionne sans Lea.bat)
- Ajout file logging dans agent_debug.log pour diagnostic Windows
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bug : _schedule_retry stockait retry_count=N dans _retry_pending, mais
l'envoi de l'action (ligne 2173) écrasait avec retry_count=0. Résultat :
le retry_count retombait toujours à 0, la condition retry_count < 3 restait
vraie → boucle infinie de retries.
Corrections :
- Ne pas écraser _retry_pending si l'entrée existe déjà (set par _schedule_retry)
- Guard de sécurité : extraire retry_count depuis les suffixes _retry de l'action_id
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Feature 4 — Résolution serveur :
- Nouvelle méthode _server_resolve_target() dans executor.py
- Cascade : template local → serveur /resolve_target → VLM local (fallback)
- Popup handling via serveur aussi
- L'agent Windows peut maintenant résoudre les clics via SomEngine+VLM
Feature 5 — VLM multi-image :
- _resolve_by_som() envoie l'anchor crop en 2ème image au VLM
- Le VLM voit les marks numérotés + le crop de l'élément recherché
Feature 6 — Métriques de résolution :
- resolution_method, resolution_score, resolution_elapsed_ms
- Propagés agent → serveur via /replay/result
- Résumé en fin de replay (méthodes, score moyen, temps moyen)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- agent_rust/ supprimé entièrement (on reste sur Python pour Léa)
- deploy/build/Lea/ supprimé (package stale avec fichiers obsolètes)
- deploy/build_lea_exe.sh supprimé (script PyInstaller Rust, obsolète)
- window_info*.py dupliqués retirés du package Windows
- __pycache__ nettoyé du deploy
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Match texte exact avant partiel pour éviter les faux positifs
- Disambiguïsation par proximité (center_norm) quand plusieurs matchs
- Prompt VLM simplifié (liste labelée, 30 max, JSON concis)
- Fallback regex pour extraire un numéro de réponse VLM non-JSON
- Résultat : 0.3s par texte vs 5-15s par VLM
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Phase 1 : enrichit chaque clic avec som_element (id, label, bbox) via YOLO+docTR
Phase 2 : nouvelle résolution SoM+VLM — SomEngine numérote, VLM identifie le mark
10 tests unitaires ajoutés, conftest unit/ pour le bon path agent_v0
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>